JS物理引擎p2.js中文文档

本文复制于Github p2.js项目的 中文维基页面,鉴于国内访问Github网速不稳定,特粘贴到CSDN,促进知识更快传播,也希望有能力者继续完善此文档。
以下是原文,更新内容请查阅Github p2.js项目中文维基页面。



p2.js 项目 中文维基

Quadra-Kill edited this page on 24 Dec 2015 ·  20 revisions

Welcome to the p2.js manual. This manual is supposed to cover the things that the automatically generated documentation don't. It should cover the majority of the p2.js API from the latest release (see releases).

欢迎使用p2.js手册。这篇手册的目的在于覆盖p2.js API文档中没有提及的知识点,点击进入API文档地址

It's assumed that you are familiar with basic physics concepts, such as mass, force, torque, and impulses. If not, consult Google and/or Wikipedia. p2.js is written in JavaScript, and hence you are also expected to be experienced in JavaScript programming.

使用p2框架前,你必须懂得基础的物理学概念,比如质量、力、扭力和推力。否则,你只能先上谷歌和维基百科了。由于p2.js是基于javascript语言编写的,显然你还得有js的编程能力。

If you have questions or feedback, please create a new issue.

如果你有问题反馈的话,请发帖

Core concepts核心概念

Shape A geometrical shape, such as a sphere or a box.

Shape(形状),一个几何形状,可以是矩形、圆形等等。

Rigid body A piece of matter that is assumed indefinitely stiff. Any two points in a rigid body are assumed to always be at a constant distance from each other. We may refer to a rigid body by just saying "body". A rigid body has got a shape and a number of physical properties such as mass and inertia.

Body(刚体),它是一块无限坚硬的物体。因此,在这块物体上任何两点之间的距离都被认为是固定的。Body(刚体)有自己的参数用来规定位置、质量和速度等,刚体的形状是由Shape创建的形状确定的。

Constraint A constraint is a physical connection that removes degrees of freedom from bodies. In 3D a body has 6 degrees of freedom (three translation coordinates and three rotation coordinates). In 2D, there are 3 (two translational and one rotational). If we take a 3D door and put it on a door hinge we have constrained the door body to the wall. At this point the body can only rotate about the door hinge, so the constraint has removed 5 degrees of freedom.

Constraint(约束),constraint 是一个物理连接件,用来控制刚体的自由度。在3d世界,物体有6个自由度(3个平移坐标和3个旋转坐标)。在2d世界,物体只有3个自由度(2个平移坐标和1个旋转坐标)。众所周知,人类世界是3d的,因此我们家里的门本来应该是有6个自由度的,但是由于门的一侧被门铰链固定在墙上,它失去了另外5个自由度,只能照着门铰链这个轴旋转了。门铰链就相当于一个constraint(约束)。

Contact constraint A special constraint designed to prevent penetration of rigid bodies and to simulate friction and restitution. You do not create contact constraints; they are created automatically.

Contact constraint(接触约束),这是一个特别的约束,作用在于防止刚体之间的渗透重叠,并且它可以模拟摩擦和弹性。你无须创建这个约束,系统会自动创建它的。

World A physics world is a collection of bodies and constraints that interact together.

World(世界),这就是一个模拟的物理世界,所有的刚体和约束创建后都要放进来。

Solver The physics world has a solver that is used to resolve constraints.

Solver(求解器),物理世界的solver(求解器)专门用于处理约束情况。

Units A unit is a way of measuring quantities such as length and time. In p2.js, we use meters-kilogram-second (MKS) units, and radians are used for angles. Do not use pixels for units.

Units(单位),就是用来测量长度、时间等等数量的单位。在p2.js中,我们用 meters-kilogram-second (MKS) 为单位,用弧度作为角度单位。不要用像素做单位哦。

Hello p2.js!

Let's create a simple physics world, with a dynamic circle on a static plane. We begin with creating the world.

接下来我们开始建一个简单的物理世界,只用一个动态的圆形和一个静态的平面就好了。搞起来:

var world = new p2.World({
   gravity:[0,-9.82]
 });

This creates a World instance and sets gravity to 9.82 along the negative Y axis.

这就是一个简单的世界实例了,并且我们把这个世界的重力设置为了9.82,也就是在y轴的负半轴方向。

Now, let's create a circle body.

现在,我们就来创建一个圆形刚体吧。

var circleBody = new p2.Body({
     mass:5,
     position:[0,10]
 });

This will create an empty, 5-kilogram body at position x=0, y=10. To make it into a circle, we need to add a Shape to it.

这样,我们就在 x=0, y=10 的位置上创建了一个5kg的刚体了,不过它暂时还没有形状,因此我们得给它添加一个形状。

var circleShape = new p2.Circle({ radius: 1 });
circleBody.addShape(circleShape);

Now we have created a body and added a circle shape to it. If you start the simulation now, the circle will start falling and never stop. Let's create a plane that it can rest on.

至此,我们已经创建了一个圆形的刚体了,如果我们现在就运行程序模拟世界的话,这个圆形刚体将会开始下坠永不停息,并且速度会越来越快,因为我们给它指定了一个9.82的重力加速度,如图真实世界。所以我们还得创建一个平面模拟地面来接住这个圆形刚体。

var groundShape = new p2.Plane();
var groundBody = new p2.Body({
mass:0
});
groundBody.addShape(groundShape);

By setting mass to zero, we tell the physics engine that the body should be static. By default, the position of a body is at the origin, which is fine for our Plane.

我们通过设置mass为0,来告诉物理引擎这个刚体应该是静止的。这里我们没有设置它的position(位置),因此默认它会从坐标原点开始生产这个平面。

Before we can start our simulation, we must add our bodies to the world.

在我们开始运行这个world之前,必须先把制作好的圆形刚体添加到world中。

world.addBody(circleBody);
world.addBody(groundBody);

Now we are ready to integrate the world.

现在,我们可以开始整合这个世界了

var timeStep = 1/60;
setInterval(function(){
world.step(timeStep);
console.log("Circle x position: " + circleBody.position[0]);
console.log("Circle y position: " + circleBody.position[1]);
console.log("Circle angle: " + circleBody.angle);
}, 1000 * timeStep);

You will get the position and angle of the circle body printed to the console (no graphical output this time). The result might look something like this:

通过上面的代码你可以在控制台看到刚体的位置和角度,就像下面这样:

Circle x position: 0
Circle y position: 10
Circle angle: 0
Circle x position: 0
Circle y position: 9
Circle angle: 0
...
Circle x position: 0
Circle y position: 0.5
Circle angle: 0

Math

p2.js uses an own, stripped version of the glMatrix math library. The p2 vector methods are documented here. The glMatrix math functions are exposed through the library, so you can use them like this:

在定义矩阵和向量方面,p2.js 使用一个特别的轻量级的的数学库glMatrix。p2的向量方法记载于此。glMatrix库的数学函数都公开在这个库里面,所以你可以这样使用它们:

var force = p2.vec2.fromValues(1,0);
p2.vec2.add(body.force, body.force, force); // body.force += force

Collision(碰撞)

TODO

Dynamics(力学)

TODO

Shapes(形状)

The available shapes are Box, Capsule, Circle, Convex, Heightfield, Line, Particle and Plane.

在p2中,我们可以创建矩形Box,胶囊形状Capsule,圆形Circle,凸形状Convex,起伏平面Heightfield,线形Line,粒子Particle 和 平面Plane。

Filtering shape collisions(过滤碰撞形状)

You can use bit masks to enable or disable collisions between individual groups of shapes. For a full explanation, see this tutorial.

位掩饰bit masks遮罩你就可以开启或者是禁止各组形状间的碰撞,当然我们得先给形状分组(groups)。更多相关知识,请看这个教程

// Setup bits for each available group
var PLAYER = Math.pow(2,0),
    ENEMY =  Math.pow(2,1),
    GROUND = Math.pow(2,2)

// Put shapes into their groups
player1Shape.collisionGroup = PLAYER;
player2Shape.collisionGroup = PLAYER;
enemyShape  .collisionGroup = ENEMY;
groundShape .collisionGroup = GROUND;

// Assign groups that each shape collide with.
// Note that the players can collide with ground and enemies, but not with other players.
player1Shape.collisionMask = ENEMY | GROUND;
player2Shape.collisionMask = ENEMY | GROUND;
enemyShape  .collisionMask = PLAYER | GROUND;
groundShape .collisionMask = PLAYER | ENEMY;

This is how the collision filter check is done between two shapes:

下面的代码演示了两个形状间的碰撞过滤检测:

if(shapeA.collisionGroup & shapeB.collisionMask)!=0 && (shapeB.collisionGroup & shapeA.collisionMask)!=0){
    // The shapes can collide
}

In JavaScript, you can use 32 valid groups (Math.pow(2,0) up to Math.pow(2,31)). However, group Math.pow(2,0) won't collide with any other shape group, and group Math.pow(2,31) will collide with all other groups.

在javascript中,你可以使用32个有效的组(Math.pow(2,0) 到 Math.pow(2,31))。然而,当组group的类型设置为Math.pow(2,0)时,该组中的形状是不能与其他组碰撞的,当组group的类型设置为Math.pow(2,31),该组中的形状可以与其他任何组中的形状碰撞。

Bodies(刚体)

Body types(刚体类型)

The body can be one of three types, Body.STATIC, Body.KINEMATIC, and Body.DYNAMIC.刚体有三种类型,Body.STATIC, Body.KINEMATIC, and Body.DYNAMIC.

Dynamic bodies interact with all other bodies and can move.Dynamic类型刚体可以与任何类型的刚体交互,可以移动。

Static bodies do not move, but interact with dynamic bodies.Static类型刚体不可以移动,但是可以与 dynamic类型刚体交互。

Kinematic bodies can be controlled via velocity, but they behave like static bodies otherwise.Kinematic类型刚体通过设置速度来控制,其他方面则和Static刚体相同。

// By setting the mass of a body to a nonzero number, the body
// will become dynamic and will move and interact with other bodies.
   var dynamicBody = new Body({
      mass : 1
   });
   console.log(dynamicBody.type == Body.DYNAMIC); // true

// Bodies are static if mass is not specified or zero. Static bodies will never move.
var staticBody = new Body();
console.log(staticBody.type == Body.STATIC); // true

// Kinematic bodies will only move if you change their velocity.
var kinematicBody = new Body({
    type: Body.KINEMATIC
});

Mass properties(质量属性)

The body has mass and inertia. You can set the body mass when you create the body, or change it dynamically during simulation.刚体有质量和惯性。在创建刚体时你可以设置它的质量,或者在模拟物理世界时动态的设置质量值。

var body = new p2.Body({
  mass: 3
});
// Dynamically
body.mass = 1;
body.updateMassProperties();

Constraints(约束)

TODO

Equations(方程式)

TODO

Events(事件)

postStep

The postStep event is probably where you want to apply forces on your bodies, since the forces are set to zero after each step.

给刚体施加力的时候,你可能要用到步后(postStep)事件,在每一步后力都会逐渐变为0。

world.on("postStep", function(){
    body.force[0] -= 10 * body.position[0];
});

Events fired during step(在步中销毁事件)

Some events are fired during the execution of world.step(). This is great, because it allows you to modify the behavior of the step. But you have to be careful: if you for example remove a body during a step then the world will most likely break apart. Therefore: read the documentation for the events you use, and be careful!

某些事件在执行world.step()方法的时候会被销毁。这是极好的,这样你就可以改变步的行为了。千万小心:如果你像下面中的例子那样,在一个步期间就移除刚体,那么你的世界极有可能崩裂。因此:阅读清楚你所使用事件的文档要求。

Don't do this:别这样:

world.on("beginContact",function(evt){
    world.removeBody(evt.bodyA); // BAD!
});

Instead, you can save the removal until after the step:相反,你要保存这个需移除的刚体直到过了这个步:

var removeBody;
world.on("beginContact",function(evt){
    removeBody = evt.bodyA;
});
// Simulation loop
setInterval(function(){
    world.step(timeStep);
    if(removeBody){
        world.removeBody(removeBody); // GOOD!
        removeBody = null;
    }
},1/60*1000);

Materials(材料)

Materials are set per shape. Let's create two circles, one made of ice and one made of steel.材料是设置给形状的。下面我们创建两个圆形,一个是冰做的,一个是铁做的。

var iceMaterial = new p2.Material();
var steelMaterial = new p2.Material();

var iceCircleShape = new p2.Circle({ radius: 0.5 });
var steelCircleShape = new p2.Circle({ radius: 0.5 });

iceCircleShape.material = iceMaterial;
steelCircleShape.material = steelMaterial;

When two materials meet, phenomena like friction and restitution occur. You can look up friction values for different material pairs on wikipedia. In our case with ice and steel, we can see that the friction value is 0.03.两块材料接触时,摩擦和弹性就产生了。那我们如何知道冰和铁接触时的摩擦系数是多少呢?简单,维基百科一下就OK了。

To define the friction and restitution for a material pair in p2.js, you must create a ContactMaterial.

为了定义两个物体间的摩擦系数和弹性系数,我们得实例化一个ContactMaterial(材料接触)。

var iceSteelContactMaterial = new p2.ContactMaterial(iceMaterial, steelMaterial, {
    friction : 0.03
});
world.addContactMaterial(iceSteelContactMaterial);

The ContactMaterial holds many other properties such as restitution and surface velocity.

ContactMaterial还可以规定材料间接触时的其他属性,比如弹性、表面速度。

If there is a contact between to shapes that don't have a ContactMaterial, then a default contact material is used. This contact material can be reached via world.defaultContactMaterial.

如果两块材料间的接触(ContactMaterial)没有被设置,则会使用默认的ContactMaterial

World(世界)

Gravity

Gravity is global in the World, and it will be applied to all bodies each time step. You can set and get the current gravity vector from world.gravity.

Gravity(重力)是全局的,它会被运用在所有刚体上,每一步都会。通过world.gravity,你可以设置和获得当前的重力向量。

world.gravity[0] = 0;     // x
world.gravity[1] = -9.82; // y
// or:
p2.vec2.set(world.gravity,0,-9.82);

There are times when you don't want to apply gravity to all bodies. In this case you must turn off global gravity and start applying gravity force yourself.

有时我们不想给所有的刚体运用重力,这时我们应该turn off掉全局重力了,然后我们自己来给刚体施力。

// Turn off global gravity
world.applyGravity=false;

// Keep track of which bodies you want to apply gravity on:
var gravityBodies=[body1,body2,body3];

// And just before running world.step(), do this:
var gravity = p2.vec2.fromValues(0,-9.82),
    gravityForce = p2.vec2.create();
for(var i=0; i<gravityBodies.length; i++){
    var b =  gravityBodies[i];
    p2.vec2.scale(gravityForce,gravity,b.mass); // F_gravity = m*g
    p2.vec2.add(b.force,b.force,gravityForce);  // F_body += F_gravity
}

Stepping the world(步进世界)

When you simply want to move the simulation forward in time, you run world.step(fixedTimeStep), where fixedTimeStep is the "resolution" of your physics simulation.

当你只想按照时间向前模拟world,你可以运行方法world.step(fixedTimeStep),fixedTimeStep将会决定模拟的世界的“分辨率”。

// Framerate dependent - Fail!
var fixedTimeStep = 1 / 60;
requestAnimationFrame(function animloop(timeMilliseconds){
        requestAnimationFrame(animloop);
        world.step(fixedTimeStep);
});

If you try using this method in your rendering loop, you'll soon notice that the speed of objects will depend on the frame rate in which you are running your simulation. On mobile devices, the framerate is often capped to 30Hz instead of 60Hz that you get on desktop. Therefore, objects will move slower on mobile than on desktop. Luckily, p2.js has you covered!

如果你在物理世界的渲染环节使用这种方法,你会发现刚体的速度会依赖于我们此时运行world的帧率。在移动设备上,帧率经常会被覆盖为30帧,而不是我们在pc端得到的60帧。

The full signature of World.prototype.step is:World.prototype.step的完整情况是这样的:

world.step(
   fixedTimeStep,
   timeSinceLastCall,
   maxSubSteps
);

If you pass all three parameters, p2.js will make sure that your simulation runs at the same pace at every frame rate.

如果你把这三个参数都忽略掉,p2.js会让你的world在每一帧上都以相同的步调运行。

// Frame rate independent! Success!
var fixedTimeStep = 1 / 60, maxSubSteps = 10, lastTimeMilliseconds;
requestAnimationFrame(function animloop(timeMilliseconds){
    requestAnimationFrame(animloop);
var timeSinceLastCall = 0;
if(timeMilliseconds !== undefined && lastTimeMilliseconds !== undefined){
    timeSinceLastCall = (timeMilliseconds - lastTimeMilliseconds) / 1000;
}
world.step(fixedTimeStep, timeSinceLastCall, maxSubSteps);
lastTimeMilliseconds = timeMilliseconds;
}

But how does this work?

这是怎么工作的呢?

You've already used the first parameter. Each time p2.js moves the time forward, it will progress its internal physics clock by this value. If you only pass this parameter, p2.js will take a single fixed step forward in time and use this time step.

如果你使用第一个参数fixedTimeStep的话,那么每当p2.js让时间前进的时候,它都会推进它内部的物理时钟用这个参数值。

The second parameter, timeSinceLastCall, is simply the delta time since you last called the method. p2.js has an internal "wall clock" that it will accumulate this value onto.

第二个参数timeSinceLastCall,规定每隔多长的时间唤醒 world.step()方法。p2.js有个内置的“挂钟”,会把每隔多长这个时间的累积量映射出来。

When you call the method with three arguments, p2.js will do fixed steps until the "physics clock" is in sync with the "wall clock". This is the trick to get framerate independence. The last parameter should be self-explanatory: it's the maximum number of fixed steps to use for each step() call.

当你把三个参数都传给world.step()方法时,p2.js会执行fixed steps直到“物理时钟”和“挂钟”的时间同步了。这个花招是为了得到独立的帧速率。最后一个参数 maxSubSteps就不要解释了:这个就是每次使用world.step()方法时,规定最大的fixed steps。

It's important that timeSinceLastCall is always less than maxSubSteps * fixedTimeStep, otherwise you are losing time.

切记,timeSinceLastCall 的值总是要小于 maxSubSteps * fixedTimeStep,否则的话你就是在流失时间了。

Note that the time values are measured in seconds, and not milliseconds. A common and easy mistake is to just pass it the value passed by requestAnimationFrame, Date.now() or performance.now(). This mistake can give strange results such as: No framerate dependence no matter what you do. Objects not moving at all until you apply a huge force and then they give huge acceleration. Simply divide the time by 1000 before passing to step().

请记住这里的时间值是以秒来衡量的,不是毫秒。人们在使用requestAnimationFrame, Date.now()performance.now()时经常犯这样的错误。这会导致一些莫名奇怪的bug出现,比如:无论如何都是帧速率独立。刚体不会动直到你给它施加一个巨大的力并且随之它会拥有巨大的加速度。在运行step()方法之前把时间除以1000,问题就解决了。

fixedTimeStep size(固定时间步的大小)

By decreasing the size of fixedTimeStep, you are increasing the "resolution" of the simulation. If you are finding that your objects are moving very fast and escaping from your walls instead of colliding with them, then one way to help fix this problem is by decreasing fixedTimeStep. If you do this, then you will need to increase maxSubSteps to ensure that timeSinceLastCall < maxSubSteps * fixedTimeStep.

减少fixedTimeStep(固定时间步),可以增加模拟的世界的“分辨率”。如果你发现你的物体移动得非常快,并且不经碰撞直接脱离墙体,那么你可以通过减少时间步fixedTimeStep来纠正这个问题。并且要记得增加maxSubSteps的值以确保符合要求timeSinceLastCall < maxSubSteps * fixedTimeStep

Interpolation(插值)

Another thing that p2.js does is interpolation of movement. If you pass all three parameters to world.step(), then you'll get an interpolated position for each Body via body.interpolatedPosition and body.interpolatedAngle. The movement will look a lot smoother if you use these values for rendering instead of .position and .angle.

p2.js是动态传值的。如果你传三个参数值给world.step()方法,那么你会得到一个为每个刚体插值的位置通过 body.interpolatedPosition 和 body.interpolatedAngle。比起body.position and body.angle ,使用body.interpolatedPosition andbody.interpolatedAngle去渲染物理世界能让动作看起来更流畅。

Example

var maxSubSteps = 10;
var fixedTimeStep = 1 / 60;
var lastTimeSeconds;
function animate(t){
    requestAnimationFrame(animate);
    var timeSeconds = t / 1000;
    lastTimeSeconds = lastTimeSeconds || timeSeconds;
    var timeSinceLastCall = timeSeconds - lastTimeSeconds;
    world.step(fixedTimeStep, timeSinceLastCall, maxSubSteps);
    renderBody(body.interpolatedPosition, body.interpolatedAngle);
}
requestAnimationFrame(animate);

Solvers(求解器)

A solver is an algorithm for solving a linear systems of equations. In p2.js, it resolves constraints, contacts, and friction.

一个solver就是一条解决一个线性方程组的运算法则。在p2.js,它处理着约束,接触,摩擦。

GSSolver(高斯求解器)

There are currently two solvers in p2.js. The most stable one is the GSSolver. This solver is iterative, which means that it converges to a solution in a number of iterations. In general, more iterations means better solution.

在p2.js中有两种求解器,高斯求解器GSSolver是最稳固的了。这个求解器是重复的,设定好重复次数iterations后它能得出一个较平均的解。通常,重复次数越多,解也就越精确。

world.solver = new GSSolver();
world.solver.iterations =  5; // Fast, but contacts might look squishy...
world.solver.iterations = 50; // Slow, but contacts look good!

Island solving(岛屿处理)

Instead of solving the whole system at once, one can split it into independent parts (called "islands") and solve them independently.

不同于一次性处理整个系统,Island solving(岛屿处理)会把整体分割成独立的部分(亦称“岛屿”),然后去分别处理它们。

This has most benefit if the islands can be solved in parallel, but it still has advantages when solving serially in a single thread, especially when the solver tolerance is larger than zero. The solver can then bail out very early for some islands of the simulation while other islands gets more iterations.

岛屿并行处理,效果显然是最好的。而且有助于在单线程的状态下连续处理,特别是当solver tolerance求解器的容差大于0的时候。求解器能够很早的就保释一些岛屿,其他的岛屿则得到更多的重复次数

var world = new p2.World({
    islandSplit: true
});
world.solver.tolerance = 0.01;

Solver parameters(求解器参数)

The solver parameters are set on the Equation objects. You provide constraint stiffness and relaxation, like so:

求解器的参数在Equation对象上设置。你要提供硬度和放松度,想这样:

equation.stiffness = 1e8;
equation.relaxation = 4;
equation.updateSpookParams(timeStep);

You can think of stiffness as the stiffness of a spring, which gives a force F=-k*x where x is the displacement of the spring. Relaxation is corresponds to the number of time steps you need to take to stabilize the constraint (larger value leads to a softer contact).

你可以把硬度stiffness想象成一个弹簧spring的硬度,这个弹簧给出一个F=-k*x 的力,X代表弹簧的位移。放松度relaxation和时间步的数量一致,窝们用这个来稳定约束(Relaxation 值越大,接触越柔软)。

The mostly central equation types are ContactEquation and FrictionEquation. These equations are automatically created as contacts appear in your scene. To change the stiffness and relaxation of these, use the following ContactMaterial properties.

ContactEquation(接触方程) 和 FrictionEquation(摩擦方程)是最主要的方程类。这些方程是自动创建的。想改变接触的硬度和放松度,设置ContactMaterial的属性值:

contactMaterial.stiffness = 1e8;
contactMaterial.relaxation = 3;
contactMaterial.frictionStiffness = 1e8;
contactMaterial.frictionRelaxation = 3;

You can also set stiffness and relaxation on Constraint equations. Just loop over all its equations one by one.

你也可以设置硬度和放松度在Constraint equations。只需遍历它所有的的方程式即可。

The Demo framework(demo的代码框架)

To make it simple to debug and test the features of p2.js, a custom rendering library was made. This library is completely separated from p2.js, so you can replace it with your own renderer.

为了能简便的调试p2.js的特性,我们自制了一个渲染库。这个库和p2.js库完全独立,所以你可以用自己的渲染库来替换掉它。

The demo framework has an interactive menu to the right. Use it to:

demo框架在world的右边做了一个交互菜单。你可以用它:

Interact with objects via the mouse (pick and pull)

在(pick and pull)模式下,可以用鼠标和物体互动。
Create new objects on the fly

在非(pick and pull)模式下,可以飞速创建新物体。
Pause and play simulation

暂停和继续模拟。
Manually stepping through the simulation

手动地步进通过模拟。
Control simulation parameters such as step size, max sub steps, etc.

控制模拟参数,比如 step size, max sub steps等等。
Change gravity

改变重力。
Tweak global solver parameters (iterations, stiffness, relaxation, tolerance)

改变全局的求解器参数(iterations, stiffness, relaxation, tolerance)。

Limitations(限制)

TODO

References(说明)

p2.js is based on the literature from the course Visual Interactive Simulation at Umeå University. Dive into the course readings and lab information if you want to know more about how the physics engine works!

p2.js发轫于默奥大学的视觉交互模拟课程成果。如果你想深入了解物理引擎是如何工作的,赶紧钻到课程资料和实验讯息里去吧!

猜你喜欢

转载自blog.csdn.net/qilei2010/article/details/51901741
今日推荐