接上一篇,基于rotate的方式模拟三维空间。
一开始定一个旋转原点,让物体随着这个原点旋转。(一般定为画布中心)
vpx = canvas.width/2;
vpy = canvas.height/2;
然后为了让旋转效果和用户有交互,让旋转速度和角度和鼠标位置挂上关系
stage.addEventListener('mousemove', function (x, y) {
angleY = (x - vpx) * .001;
angleX = (y - vpy) * .001;
});
接下来,如果我们有个三维空间里的小球ball,它有三向坐标(xpos, ypos, zpos)。下面就要计算出经过一个角度变化后的三个坐标
// 绕y轴变化,得出新的x,z坐标
function rotateY(ball, angleY) {
var cosy = Math.cos(angleY),
siny = Math.sin(angleY),
x1 = ball.xpos * cosy - ball.zpos * siny,
z1 = ball.zpos * cosy + ball.xpos * siny;
ball.xpos = x1;
ball.zpos = z1;
}
上面是绕y轴的,同理,可以得出绕x轴的
function rotateX(ball, angleX) {
var cosx = Math.cos(angleX),
sinx = Math.sin(angleX),
y1 = ball.ypos * cosx - ball.zpos * sinx,
z1 = ball.zpos * cosx + ball.ypos * sinx;
// 新位置
ball.ypos = y1;
ball.zpos = z1;
}
经过变换得到新的(xpos, ypos, zpos)后,接下来要做的事情就是把这个三维坐标的zpos去掉,也就是变换到二维空间来。
根据上文说的,在普通的模型中,z坐标对x,y的影响在二维体系里一般表现在对x,y造成偏移量,对大小,透明度这几个属性的影响。所以,把三维空间降到二维中来
function render (ball) {
// focalLength 表示当前焦距,一般可设为一个常量
if (ball.zpos > -focalLength) {
// 把z方向扁平化
var scale = focalLength / (focalLength + ball.zpos);
ball.x = vpx + ball.xpos * scale;
ball.y = vpy + ball.ypos * scale;
ball.width = ballR*2*scale;
}
}
到此,基本上绕一个固定点的旋转模型就可以了。但是由于canvas绘制矢量图是根据代码执行顺序画上去的。而通常我们都是直接用一个数组作为collection来搜集管理各个点(比如小球)。而这个数组并没有按照我们需要的根据z方向的大小来排序,所以直接一个for循环进去按个绘制小球的时候会发现:
z方向对于x,y的偏移,小球大小,透明度等都是正确渲染的,但是有个问题就是会出现距离近的球反而被挡在距离远的球后面(不透的球在半透球后面)。造成这个现象的原因就是上面所说。
所以为了解决这个问题,有个办法就是每次变换后对我们collection里面的元素按z的大小顺序重排一下就ok
function sortZ () {
balls.sort(function (a, b) { return b.zpos-a.zpos })
stage.children.sort(function (a, b) { return b.zpos-a.zpos })
}
至此,一个【多点绕固定点旋转】3d模型表现如下:
var initialize = function () {
var focalLength = 250,
ballR = 20,
ballN = 20,
balls = [],
vpx = 0,
vpy = 0,
angleY = 0,
angleX = 0;
for (var i=0; i<ballN; i++) {
var ball = createBall(ballR);
stage.addChild(ball);
ball.xpos = Math.random() * 200 – 100;
ball.ypos = Math.random() * 200 – 100;
ball.zpos = Math.random() * 200 – 100;
balls.push(ball);
}
vpx = canvas.width/2;
vpy = canvas.height/2;
stage.addEventListener(‘mousemove’, function (x, y) {
angleY = (x – vpx) * .001;
angleX = (y – vpy) * .001;
});
function rotateY(ball, angleY) {
var cosy = Math.cos(angleY),
siny = Math.sin(angleY),
x1 = ball.xpos * cosy – ball.zpos * siny,
z1 = ball.zpos * cosy + ball.xpos * siny;
ball.xpos = x1;
ball.zpos = z1;
}
function rotateX(ball, angleX) {
var cosx = Math.cos(angleX),
sinx = Math.sin(angleX),
y1 = ball.ypos * cosx – ball.zpos * sinx,
z1 = ball.zpos * cosx + ball.ypos * sinx;
ball.ypos = y1;
ball.zpos = z1;
}
function render (ball) {
if (ball.zpos > -focalLength) {
var scale = focalLength / (focalLength + ball.zpos);
ball.x = vpx + ball.xpos * scale;
ball.y = vpy + ball.ypos * scale;
ball.width = ballR*2*scale;
}
}
function sortZ () {
balls.sort(function (a, b) { return b.zpos-a.zpos })
stage.children.sort(function (a, b) { return b.zpos-a.zpos })
}
stage.onRefresh = function () {
for (var i=0,ball; ball=balls[i]; i++) {
rotateX(ball, angleX);
rotateY(ball, angleY);
render(ball);
}
sortZ();
}
stage.start();
};
onload = initialize;
</script></html>
【线条的rotate】
上面是多点rotate的3d模型,根据 点-线-面 过度的原则,有了关于点的模型,那么离下面线和面的也就不远了。
接着上面的点的模型,把点和点之间用线连起来,就可以出一个多线条选装版本。
// 连接点与点之间
function drawLinesBetweenBalls () {
var ctx = stage.ctx;
ctx.beginPath();
// 移动到第一个点
ctx.moveTo(balls[0].x, balls[0].y);
// 顺序连接点之间成线
for (var i = 0; i < balls.length; i ++) {
ctx.lineTo(balls[i].x, balls[i].y);
}
ctx.closePath();
ctx.stroke();
}
每次重绘的时候执行一下画线这个方法就可以,表现如下:
var initialize = function () {
var focalLength = 250,
ballR = 5,
ballN = 20,
balls = [],
vpx = 0,
vpy = 0,
angleY = 0,
angleX = 0;
for (var i=0; i<ballN; i++) {
var ball = createBall(ballR);
stage.addChild(ball);
ball.xpos = Math.random() * 200 – 100;
ball.ypos = Math.random() * 200 – 100;
ball.zpos = Math.random() * 200 – 100;
balls.push(ball);
}
vpx = canvas.width/2;
vpy = canvas.height/2;
stage.addEventListener(‘mousemove’, function (x, y) {
angleY = (x – vpx) * .001;
angleX = (y – vpy) * .001;
});
function rotateY(ball, angleY) {
var cosy = Math.cos(angleY),
siny = Math.sin(angleY),
x1 = ball.xpos * cosy – ball.zpos * siny,
z1 = ball.zpos * cosy + ball.xpos * siny;
ball.xpos = x1;
ball.zpos = z1;
}
function rotateX(ball, angleX) {
var cosx = Math.cos(angleX),
sinx = Math.sin(angleX),
y1 = ball.ypos * cosx – ball.zpos * sinx,
z1 = ball.zpos * cosx + ball.ypos * sinx;
ball.ypos = y1;
ball.zpos = z1;
}
function render (ball) {
if (ball.zpos > -focalLength) {
var scale = focalLength / (focalLength + ball.zpos);
ball.x = vpx + ball.xpos * scale;
ball.y = vpy + ball.ypos * scale;
ball.width = ballR*2*scale;
var ck = document.getElementById(‘ck’).checked;
if (ck == false) ball.width = 0;
}
}
function drawLinesBetweenBalls () {
var ctx = stage.ctx;
ctx.beginPath();
ctx.moveTo(balls[0].x, balls[0].y);
for (var i = 0; i < balls.length; i ++) {
ctx.lineTo(balls[i].x, balls[i].y);
}
ctx.closePath();
ctx.stroke();
}
function sortZ () {
balls.sort(function (a, b) { return b.zpos-a.zpos })
stage.children.sort(function (a, b) { return b.zpos-a.zpos })
}
stage.onRefresh = function () {
for (var i=0,ball; ball=balls[i]; i++) {
rotateX(ball, angleX);
rotateY(ball, angleY);
render(ball);
}
//sortZ();
drawLinesBetweenBalls();
}
stage.start();
};
onload = initialize;
</script></html>
最新评论