代码改变世界

码农干货系列【2】--由关节(Joint)说到割绳子(cut the rope)

2012-06-08 08:03  【当耐特】  阅读(4971)  评论(14编辑  收藏  举报

简介

关节是相互连结且互相约束的物体,常见于各类物理引擎当中。关节的运用非常广泛,例如人体模拟、动物行走模拟、器材、绳子、机关、链桥等都可以灵活利用关节去模拟。

image 普通的关节分两种,一种是有固定点,一种没有固定点。本文分别对两种关节进行计算并且输出图片进行模拟。

关节

关节通常用下面这种表达方式:

(function (window) {
var Joint = function (segLength, segCount, isFixed, startPoint) {
this.segLength = segLength;
this.segCount = segCount;
this.isFixed = isFixed;
this.startPoint = startPoint;
this.points = [];
for (var i = 0; i < this.segCount; i++) {
this.points.push(new Vector2(this.startPoint.x, this.startPoint.y + i * this.segLength));
}
}
window.Joint = Joint;
} (window))

其中:

segLength表示关节每一段的长度(这里假定关节每一段是相等的)

segCount表示关节个数(包括起点和终点)

isFixed表示关节是否有固定点(如果isFixed为true,假设startPoint为固定点)

startPoint表示关节的起点(这里假定关节的初始状态是笔直向下的)

points表示关节上所有的支点(包括起点和终点)

这里需要了解的是 ,在完整的关节表示当中,为了更好的模拟现实世界当中的物体,关节还会加上一个角度区间限制,即关节的最大张开角度和最小的角度。本文的关节不加此限制,任其360度无障碍旋转。

图形化输出

这里使用Easeljs输出关节的图像。先引用相关的脚本:

    <script src="script/Vector2.js" type="text/javascript"></script>
    <script src="script/joint.js" type="text/javascript"></script>
    <script src="script/easel.js" type="text/javascript"></script>

定义一个拥有2个关节段,每段长度为60的关节。完整代码如下所示:

var joint = new Joint(60,4,false,new Vector2(200,100));
var canvas;
var stage;
var shape;
(function (){
canvas = document.getElementById("testCanvas");
stage = new Stage(canvas);

shape = new Shape();
stage.addChild(shape);
drawjoint(joint);
}())

function drawjoint(joint) {
shape.graphics.clear();
shape.graphics.ss(14, 'round', 'round');
for (var i = 0; i < joint.points.length - 1; i++) {
if (i % 2 === 0) {
shape.graphics.beginStroke("green");
}
else {
shape.graphics.beginStroke("red");
}
shape.graphics.mt(joint.points[i].x, joint.points[i].y).lt(joint.points[i + 1].x, joint.points[i + 1].y);
}
shape.graphics.endStroke();
stage.update();
}

效果如图所示:
image

与鼠标交互

要让关节绕着对应的支点动起来,这里让关节的终点跟随鼠标的位置移动。

在方法中加入:

canvas.onmousemove = canvasMouseMoveHandler; 

所以,当鼠标移动的时候,需要实时的更新关节的位置。如下图所示:

image

这里需要注意的两点是:

上图描述的是一次微小的拉动,真正要呈现如图所示的前后状态,其实已经经历的很多次位置更新

上图反向延长线经过初始点是不准确的,准确的位置是初始点靠右一段距离(取决于两条线段的合力方向,但这不影响关节的模拟)

image

添加鼠标处理的方法:

function canvasMouseMoveHandler(e) {
var r = canvas.getBoundingClientRect();
joint.points[joint.points.length - 1] = new Vector2(e.clientX - r.left, e.clientY - r.top);
joint.updatePointsPosition(joint.points[joint.points.length - 1], joint.points.length - 1);
drawjoint(joint);
}

在分析完具体的过程之后,利用递归的思路依次更新所有的点。更新方法接收两个参数:一个是更新的点、一个是该点的index,当index为1的时候退出递归。


var p = Joint.prototype;
p.updatePointsPosition = function (point, index) {
var tempV = this.points[index - 1].sub(point).setLength(this.segLength);
this.points[index - 1] = point.add(tempV);
if (index > 1) {
this.updatePointsPosition(this.points[index - 1], index - 1);
}
}

其中var tempV = this.points[index - 1].sub(point).setLength(this.segLength);是计算支点的偏移量,Vector2.setLength是经过normalize(转换为该向量的单位向量) 再multiplyScalar(设置长度)。如下代码所示:

setLength: function (l) {

return this.normalize().multiplyScalar(l);

},
完整代码参见Vector2类。
运行效果如下所示:
请使用现代浏览器观看在线演示!

固定起始点

上面呈现的是没有固定点的关节,那么如果拥有固定点,该怎么更新关节上所有点的位置呢?需要做的仅仅是校正startPoint(启始点、固定点)的位置。

var p = Joint.prototype;
p.updatePointsPosition = function (point, index) {
var tempV = this.points[index - 1].sub(point).setLength(this.segLength);
this.points[index - 1] = point.add(tempV);
if (index > 1) {
this.updatePointsPosition(this.points[index - 1], index - 1);
} else {
if (this.isFixed) {
var v = this.points[0].sub(this.startPoint);
for (var i = 0; i < this.points.length; i++) {
this.points[i].subSelf(v);
}
}
}
}

当递归到最后,如果该关节是有固定点的,校正所有关节点的位置。效果如下所示:

请使用现代浏览器观看在线演示!

思考??

这样子呢?

var joint = new Joint(2,140,true,new Vector2(200,100));

一共140个点,点与点之间距离为2,再加上点颜色区间变化。

请使用现代浏览器观看在线演示!

它是割绳子(cut the rope)中绳子的表达方式吗?这样表示绳子会有什么问题出现?

那么请继续关注我后续的干货~~~~~