作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Michael Cole
Verified Expert in Engineering

Michael is an expert full-stack web engineer, speaker, 拥有20多年经验和计算机科学学位的顾问.

Read More

PREVIOUSLY AT

Ernst & Young
Share

Hurray! We set out to create a Proof of Concept for WebVR. 我们之前的博文已经完成了模拟,所以现在是时候进行一些创造性的尝试了.

对于设计师和开发者来说,这是一个非常激动人心的时刻,因为VR是一种范式转变.

2007年,苹果公司销售了第一款iPhone,开启了智能手机消费革命. By 2012, we were well into “mobile-first” and “responsive” web design. In 2019, Facebook and Oculus released the first mobile VR headset. Let’s do this!

“移动优先”的互联网不是一时的时尚,我预测“虚拟现实优先”的互联网也不会. 在前三篇文章和演示中,我演示了您的 current browser.

If you’re picking this up in the middle of the series, we’re building a celestial gravity simulation of spinny planets.

Standing on the work we’ve done, it’s time for some creative play. 在最后两篇文章中,我们将探讨画布和WebVR以及用户体验.

  • Part 4: 画布数据可视化 (this post)
  • Part 5: WebVR数据可视化

Today, we are going to bring our simulation to life. Looking back, 我注意到,当我开始制作可视化工具时,我对完成这个项目是多么的兴奋和感兴趣. The visualizations made it interesting to other people.

这次模拟的目的是探索WebVR(浏览器中的虚拟现实)及其未来的技术 VR-first web. These same technologies can power browser-edge computing.

完成我们的概念证明,今天我们将首先创建一个画布可视化.

帆布可视化

在最后一篇文章中,我们将着眼于VR设计,并制作一个WebVR版本来完成这个项目.”

WebVR数据可视化

The Simplest Thing That Could Possibly Work: console.log()

回到RR(真实的现实). 让我们为基于浏览器的“n-body”模拟创建一些可视化效果. 在过去的项目中,我在网络视频应用程序中使用过画布,但从未作为艺术家的画布使用过. 看看我们能做些什么.

如果您还记得我们的项目架构,我们将可视化委托给 nBodyVisualizer.js.

将可视化委托给nBodyVisualizer.js

nBodySimulator.js 有一个模拟循环 start() that calls its step() 函数,和底 step() calls this.visualize()

/ / src / nBodySimulator.js

  /**

   *这是模拟循环.

   */

  async step() {

	//如果worker没有准备好,跳过计算. 每33ms (30fps)运行一次. Will skip.

	if (this.ready()) {

  		await this.calculateForces ()

	} else {

  	console.跳过计算:${this . log.workerReady} $ {.workerCalculating}”)

	}

	// Remove any "debris" that has traveled out of bounds

	// This keeps the button from creating uninteresting work.

	this.trimDebris()

	//现在更新力. Reuse old forces if worker is already busy calculating.

	this.applyForces()

	// Now Visualize

	this.visualize()

  }

当我们按下绿色按钮时,主线程会向系统中添加10个随机体. 我们触摸了按钮代码 first post,你可以在回购中看到它 here. Those bodies are great for testing a proof of concept, but remember we are in dangerous performance territory - O(n²).

人类天生就会关心他们能看到的人和事,所以 trimDebris() 移除飞出视线的物体,这样它们就不会减慢其他物体的速度. This is the difference between perceived and actual performance.

Now that we’ve covered everything but the final this.visualize(),让我们一起来看看!

/ / src / nBodySimulator.js

  /**

   * Loop through our visualizers and paint()

   */

  visualize() {

	this.visualizations.forEach(vis => {

  		vis.paint(this.objBodies)

	})

  }

  /**

   *在我们的列表中添加一个可视化工具

   */

  addVisualization (vis) {

	this.visualizations.push(vis)

  }

These two functions let us add multiple visualizers. There are two visualizers in the canvas version:

// src/main.js 

window.Onload = function() {

  //创建模拟

  const sim = new nBodySimulator()

  

  //添加一些可视化工具

  sim.addVisualization (

    新nBodyVisPrettyPrint(文档.getElementById(“visPrettyPrint”))

  )

  sim.addVisualization (

    新nBodyVisCanvas(文档.getElementById(“visCanvas”))

  )

  …

在画布版本中,第一个可视化工具是显示为HTML的白色数字表. The second visualizer is a black canvas element underneath.

油画专业

To create this, I started with a simple base class in nBodyVisualizer.js:

/ / src / nBodyVisualizer.js

/**

 * This is a toolkit of visualizers for our simulation.

 */

/**

 *控制台的基类.Log()是模拟状态.

 */

导出类nBodyVisualizer {

  构造函数(htmlElement) {

	this.htmlElement = htmlElement

	this.resize()

  }

  resize() {}

  paint(bodies) {

	console.log(JSON.Stringify (body, null, 2))

  }

}

This class prints to the console (every 33ms!),并跟踪一个htmlElement——我们将在子类中使用它,使它们易于声明 main.js.

This is the simplest thing 这是可行的.

然而,尽管如此 console visualization is definitely simple, it doesn’t actually “work.“浏览器控制台(和浏览的人)不是为以33ms的速度处理日志消息而设计的. Let’s find the 下一个最简单的事情 这是可行的.

用数据可视化模拟

The next “pretty print” iteration was to print text to an HTML element. This is also the pattern we use for the canvas implementation.

注意,我们保存了对an的引用 htmlElement 可视化器将在上面作画. Like everything else on the web, it has a mobile-first design. 在桌面上,这将在页面左侧打印对象的数据表及其坐标. On mobile it would result in visual clutter so we skip it.

/**

 * Pretty print simulation to an htmlElement's innerHTML

 */

export class nBodyVisPrettyPrint extends nBodyVisualizer {

  构造函数(htmlElement) {

	超级(htmlElement)

	this.isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);

  }

  resize() {}

  paint(bodies) {

    

	if (this.isMobile) return

	let text = ''

	函数pretty(number) {

  		return number.toPrecision(2).padStart(10)

	}

	bodies.forEach( body => {

  	text += `
${body.name.padStart(12)} {x:${pretty(body.x)} y: ${漂亮(身体.y)} z: ${漂亮(身体.z)}质量:${漂亮(身体.mass)}) }` }) if (this.htmlElement),这.htmlElement.innerHTML = text } }

This “data stream” visualizer has two functions:

  1. 这是一种“完整性检查”模拟输入到可视化器的方法. 这是一个“调试”窗口.
  2. It’s cool to look at, so let’s keep it for the desktop demo!

现在我们对我们的输入相当有信心,让我们谈谈图形和画布.

用2D画布可视化模拟

A “Game Engine” is a “Simulation Engine” with explosions. 两者都是非常复杂的工具,因为它们专注于资产管道, 流级加载, and all kinds of incredibly boring stuff that should never be noticed.

网络也通过“移动优先”的设计创造了自己的“不应该被注意到的东西”. 如果浏览器调整大小, our canvas’s CSS will resize the canvas element in the DOM, so our visualizer must adapt or suffer users’ contempt.

#visCanvas {

  	margin: 0;

  	padding: 0;

  	background - color: # 1 f1f1f;

  	溢出:隐藏;

  	width: 100vw;

  	height: 100vh;

}

这个需求驱动 resize() in the nBodyVisualizer 基类和画布实现.

/**

 *绘制模拟状态画布

 */

export class nBodyVisCanvas extends nBodyVisualizer {

  构造函数(htmlElement) {

	超级(htmlElement)

	// Listen for resize to scale our simulation

	window.onresize = this.resize.bind(this)

  }

	// If the window is resized, we need to resize our visualization

  resize() {

	if (!this.htmlElement),返回

	this.sizeX = this.htmlElement.offsetWidth

	this.sizeY = this.htmlElement.offsetHeight

	this.htmlElement.width = this.sizeX

	this.htmlElement.height = this.sizeY

	this.vis = this.htmlElement.getContext('2d')

  }

This results in our visualizer having three essential properties:

  • this.vis -可以用来绘制原语
  • this.sizeX
  • this.sizeY -绘图区域的尺寸

Canvas 2D可视化设计说明

我们的大小调整工作 against 默认画布实现. If we were visualizing a product or data graph, we’d want to:

  1. Draw to the canvas (at a preferred size and aspect ratio)
  2. 然后让浏览器在页面布局期间调整绘制到DOM元素的大小

在这个更常见的用例中,产品或图形是体验的焦点.

Our visualization is instead a theatrical visualization of the 浩瀚的太空, dramatized by flinging dozens of tiny worlds into the void for fun.

我们的天体通过谦虚来展示空间——保持自己在0到20像素之间的宽度. 的大小调整 space 在点之间创造一种“科学”的空间感,并提高感知速度.

为了在质量差别很大的物体之间创建一种比例感,我们用a初始化物体 drawSize 与质量成正比的:

/ / nBodySimulation.js

导出类Body {

  constructor(name, color, x, y, z, mass, vX, vY, vZ) {

	...

	this.drawSize = Math.min(   Math.max( Math.Log10(质量),1),10)

  }

}

手工制作定制太阳能系统

现在,当我们在 main.js, we’ll have all the tools we need for our visualization:

	// Set Z coords to 1 for best visualization in overhead 2D canvas

	//创造稳定的宇宙是困难的

	//   name        	color 	x	y	z	m  	vz	vy   vz

  sim.addBody(new Body("star",  "yellow", 0,   0,   0,   1e9))

  sim.addBody(new Body("hot jupiter",  "red",   -1,  -1,   0,   1e4,  .24,  -0.05,  0))

  sim.addBody(new Body("cold jupiter", "purple", 4,   4, -.1,   1e4, -.07,   0.04,  0))

	// A couple far-out asteroids to pin the canvas visualization in place.

  sim.addBody(新机构(“小行星”, 	“黑色”,-15,-15,0,0))  

  sim.addBody(新机构(“小行星”, 	"black", 15,15,0,0))

	//启动模拟  

  sim.start()

You may notice the two “asteroids” at the bottom. 这些零质量对象是用来将模拟的最小视口“固定”到以0为中心的30x30区域的方法,0.

现在我们已经为paint函数做好了准备. The cloud of bodies can “wobble” away from the origin (0,0,0), so we must also shift in addition to scale.

We are “done” when the simulation has a natural feel to it. 没有“正确”的方法. 来安排行星的初始位置, 我只是胡乱摆弄这些数字,直到它们长到足够有趣为止.

	//在画布上绘制

paint(bodies) {

	if (!this.htmlElement),返回

	// We need to convert our 3d float universe to a 2d pixel visualization

	//计算移位和缩放

	Const bounds = this.bounds(bodies)

	const shiftX = bounds.xMin

	const shiftY = bounds.yMin

	const twoPie = 2 * Math.PI

    

	让scaleX =这个.sizeX / (bounds.xMax - bounds.xMin)

	let scaleY = this.sizeY / (bounds.yMax - bounds.yMin)

	if (isNaN(scaleX)) || !isFinite(scaleX) || scaleX < 15) scaleX = 15

	if (isNaN(scaleY)) || !isFinite(scaleY) || scaleY < 15) scaleY = 15

	// Begin Draw

	this.vis.clearRect(0,0, this.vis.canvas.width, this.vis.canvas.height)

		bodies.forEach((body, index) => {

  	// Center

  		const drawX = (body.x - shiftX) * scaleX

  		const drawY = (body.y - shiftY) * scaleY

  	//在画布上绘制

  		this.vis.beginPath();

  		this.vis.圆弧(drawX, drawY, body.drawSize, 0, twoPie, false);

  		this.vis.fillStyle = body.color || "#aaa"

  		this.vis.fill();

	});

  }

	// Because we draw the 3D space in 2D from the top, we ignore z

bounds(bodies) {

	const ret = { xMin: 0, xMax: 0, yMin: 0, yMax: 0, zMin: 0, zMax: 0 }
    
	bodies.forEach(body => {
    
		if (ret.xMin > body.x) ret.xMin = body.x
            
		if (ret.xMax < body.x) ret.xMax = body.x
            
		if (ret.yMin > body.y) ret.yMin = body.y
            
		if (ret.yMax < body.y) ret.yMax = body.y
            
		if (ret.zMin > body.z) ret.zMin = body.z
            
		if (ret.zMax < body.z) ret.zMax = body.z
	})
	return ret
  }

}

The actual canvas drawing code is only five lines - each starting with this.vis. 剩下的代码是场景的 grip.

Art Is Never Finished, It Must Be Abandoned

当客户似乎在花钱时,他们不会赚钱, 现在是提出来的好时机. 投资艺术是一项商业决策.

这个项目的客户端(我)决定从画布实现转向WebVR. 我想要一个华丽的WebVR演示. So let’s wrap this up and get some of that!

有了我们所学到的,我们可以把这个画布项目带向不同的方向. 如果你还记得第二篇文章,我们正在内存中制作身体数据的几个副本:

内存中主体数据的副本

If performance is more important than design complexity, 可以直接将画布的内存缓冲区传递给WebAssembly. This saves a couple of memory copies, which adds up for performance:

就像WebAssembly和AssemblyScript一样, 这些项目正在处理上游兼容性问题,因为规范设想了这些令人惊叹的新浏览器功能.

所有这些项目——以及我在这里使用的所有开源——都在为虚拟现实优先的互联网公共资源的未来奠定基础. 再见,谢谢大家!

在最后一篇文章中,我们将看看创建VR场景与虚拟现实场景之间的一些重要设计差异. a flat-web page. 因为VR是非常重要的,所以我们将使用WebVR框架来构建我们的旋转世界. I chose Google’s A-Frame, which is also built upon canvas.

It’s been a long journey to get to the beginning of WebVR. 但是这个系列不是关于 A-Frame hello world演示. 我兴奋地写了这个系列,向您展示将推动互联网vr世界到来的浏览器技术基础.

聘请Toptal这方面的专家.
Hire Now
Michael Cole

Michael Cole

Verified Expert in Engineering

达拉斯,美国

2014年9月10日成为会员

About the author

Michael is an expert full-stack web engineer, speaker, 拥有20多年经验和计算机科学学位的顾问.

Read More
作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

PREVIOUSLY AT

Ernst & Young

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Toptal开发者

Join the Toptal® community.