本篇教程我们将会构建一个齿轮系统,用HTML canvas和JavaScript描述出来。
本教程适合刚学习JavaScript以及对齿轮系统只有最基本了解的读者。
第一部分 渲染单个齿轮这个简单的物理齿轮制作 教程 是一个好的开端。根据这个教程,我们的齿轮将由它的半径和齿数来定义。
首先,让我们用齿轮的有关属性来创建一个齿轮类:
var Gear = function(x, y, connectionRadius, teeth, fillStyle, strokeStyle) { // 齿轮参数 this.x = x; this.y = y; this.connectionRadius = connectionRadius; this.teeth = teeth; // 渲染参数 this.fillStyle = fillStyle; this.strokeStyle = strokeStyle; // 计算属性 this.diameter = teeth * 4 * connectionRadius; // 每个轮齿是通过两个相连的半圆组成的 this.radius = this.diameter / (2 * Math.PI); // D = 2 PI r // 运动属性 this.phi0 = 0; // 起始角度 this.angularSpeed = 0; // 角速度cond this.createdAt = new Date(); // 时间戳 }
接着写渲染的方法:
Gear.prototype.render = function (context) { // 更新旋转角 var ellapsed = new Date() - this.createdAt; var phiDegrees = this.angularSpeed * (ellapsed / 1000); var phi = this.phi0 + deg2rad(phiDegrees); // 当前的角度 // 构建渲染参数 context.fillStyle = this.fillStyle; context.strokeStyle = this.strokeStyle; context.lineCap = 'round'; context.lineWidth = 1; // 绘制齿轮轮身 context.beginPath(); for (var i = 0; i < this.teeth * 2; i++) { var alpha = 2 * Math.PI * (i / (this.teeth * 2)) + phi; // 计算每个轮齿的位置 var x = this.x + Math.cos(alpha) * this.radius; var y = this.y + Math.sin(alpha) * this.radius; // 画一个半圆,随着alpha旋转 // 在每个奇数齿,画相反的半圆 context.arc(x, y, this.connectionRadius, -Math.PI / 2 + alpha, Math.PI / 2 + alpha, i % 2 == 0); } context.fill(); context.stroke(); // 画中心的圆 context.beginPath(); context.arc(this.x, this.y, this.connectionRadius, 0, 2 * Math.PI, true); context.fill(); context.stroke(); }
使用方法:
var canvas = document.getElementById('myCanvas'); var context = canvas.getContext('2d'); var W = canvas.width; var H = canvas.height; var gear = new Gear(W / 2, H / 2, 5, 12, "white", "rgba(61, 142, 198, 1)"); gear.angularSpeed = 36; setInterval(function () { canvas.width = canvas.width; gear.render(context); }, 20);
以及HTML:
<canvas id="myCanvas" width="200" height="200"></canvas> <script src="myScript.js" type="text/javascript"></script>
See the Pen PpgQBB by molunerfinn ( @molunerfinn ) on CodePen .
第二部分 渲染两个啮合的齿轮我们的目的是让用户控制第二个齿轮的位置。同时我们想要保证齿轮仍然啮合并且同步转动。
下面是个例子:
Gear.prototype.connect = function (x, y) { var r = this.radius; var dist = distance(x, y, this.x, this.y); // 计算两个齿轮之间的距离 // 要创建一个新的齿轮我们必须知道它的齿数 var newRadius = Math.max(dist - r, 10); var newDiam = newRadius * 2 * Math.PI; var newTeeth = Math.round(newDiam / (4 * this.connectionRadius)); // 齿数必须是整数 // 创建一个新的齿轮 var newGear = new Gear(x, y, this.connectionRadius, newTeeth, this.fillStyle, this.strokeStyle); // 调整新齿轮的旋转方向使其与原来的方向相反 var gearRatio = this.teeth / newTeeth; newGear.angularSpeed = -this.angularSpeed * gearRatio; return newGear; }
我们可以把这个挂载在Canvas的 mousemove 的事件上。
var gear2 = gear.connect(3 * (W / 4), H / 2); // 这是一个辅助函数,用于转换鼠标在canvas内部的坐标值 function getMousePos(canvas, evnt) { var rect = canvas.getBoundingClientRect(); return { x: evnt.clientX - rect.left, y: evnt.clientY - rect.top }; } canvas.onmousemove = function (evnt) { var pos = getMousePos(canvas, evnt); var x = Math.min(0.7 * W, Math.max(0.3 * W, pos.x)); var y = Math.min(0.7 * H, Math.max(0.3 * H, pos.y)); gear2 = gear.connect(x, y); } setInterval(function () { canvas.width = canvas.width; gear.render(context); gear2.render(context); }, 20);
See the Pen Mpdvwo by molunerfinn ( @molunerfinn ) on CodePen .
上面的步骤可以实现正确的旋转方向,但是并不是那么准确——齿轮没有啮合,并且每个齿轮只是在做自己的运动。
以下是我们必须解决的两个问题:
要解决第一个问题,我们必须让我们的新齿轮改变它的实际位置以确保在拥有它所需要的齿数的同时还能和第一个齿轮啮合。
(下面是对于问题1的解决方案)
Gear.prototype.connect = function (x, y) { var r = this.radius; var dist = distance(x, y, this.x, this.y); // 要创建一个新的齿轮我们必须知道它的齿数 var newRadius = Math.max(dist - r, 10); var newDiam = newRadius * 2 * Math.PI; var newTeeth = Math.round(newDiam / (4 * this.connectionRadius)); // 计算新齿轮的实际位置,使其能够于该齿轮啮合 var actualDiameter = newTeeth * 4 * this.connectionRadius; var actualRadius = actualDiameter / (2 * Math.PI); var actualDist = r + actualRadius; // 距离该齿轮中心的实际距离 var alpha = Math.atan2(y - this.y, x - this.x); // 该齿轮中心和(x,y)的角度值 var actualX = this.x + Math.cos(alpha) * actualDist; var actualY = this.y + Math.sin(alpha) * actualDist; // 创建一个新的齿轮 var newGear = new Gear(actualX, actualY, this.connectionRadius, newTeeth, this.fillStyle, this.strokeStyle); // 调整新齿轮的旋转方向使其与原来的方向相反 var gearRatio = this.teeth / newTeeth; newGear.angularSpeed = -this.angularSpeed * gearRatio; return newGear; }
See the Pen Gear-2-2 by molunerfinn ( @molunerfinn ) on CodePen .
上面的调整单独处理了一个令人讨厌的问题,我们可以在一个齿轮上放置另一个齿轮了。但是这还不足以同步两个齿轮的旋转。
现在我们必须在连接点使齿轮互相啮合: