From 8db028db1cb954c53532df6346411f6d8c22613a Mon Sep 17 00:00:00 2001 From: YHH <359807859@qq.com> Date: Mon, 31 Aug 2020 09:28:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=87=86=E5=A4=87=E6=96=B0=E5=A2=9Everlet?= =?UTF-8?q?=E7=89=A9=E7=90=86=E5=BC=95=E6=93=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- demo/src/Scenes/Empty Scene/BasicScene.ts | 9 - .../src/Physics/{Verlet => }/SpatialHash.ts | 0 .../Physics/Verlet/Composites/Composite.ts | 64 +++++++ .../Physics/Verlet/Constraints/Constraint.ts | 18 ++ source/src/Physics/Verlet/Particle.ts | 36 ++++ source/src/Physics/Verlet/VerletWorld.ts | 158 ++++++++++++++++++ 6 files changed, 276 insertions(+), 9 deletions(-) rename source/src/Physics/{Verlet => }/SpatialHash.ts (100%) create mode 100644 source/src/Physics/Verlet/Composites/Composite.ts create mode 100644 source/src/Physics/Verlet/Constraints/Constraint.ts create mode 100644 source/src/Physics/Verlet/Particle.ts create mode 100644 source/src/Physics/Verlet/VerletWorld.ts diff --git a/demo/src/Scenes/Empty Scene/BasicScene.ts b/demo/src/Scenes/Empty Scene/BasicScene.ts index a0b06647..22eedc02 100644 --- a/demo/src/Scenes/Empty Scene/BasicScene.ts +++ b/demo/src/Scenes/Empty Scene/BasicScene.ts @@ -12,14 +12,5 @@ module samples { this.camera.entity.addComponent(new es.FollowCamera(moonEntity)); }); } - - public update(){ - super.update(); - let moonEntity = this.findEntity("moon"); - if (!moonEntity) - return; - let spriteRenderer = moonEntity.getComponent(es.SpriteRenderer); - console.log(spriteRenderer.bounds, this.camera.bounds); - } } } \ No newline at end of file diff --git a/source/src/Physics/Verlet/SpatialHash.ts b/source/src/Physics/SpatialHash.ts similarity index 100% rename from source/src/Physics/Verlet/SpatialHash.ts rename to source/src/Physics/SpatialHash.ts diff --git a/source/src/Physics/Verlet/Composites/Composite.ts b/source/src/Physics/Verlet/Composites/Composite.ts new file mode 100644 index 00000000..a4050687 --- /dev/null +++ b/source/src/Physics/Verlet/Composites/Composite.ts @@ -0,0 +1,64 @@ +module es { + /** + * 代表了Verlet世界中的一个对象。由粒子和约束组成,并处理更新它们 + */ + export class Composite { + /** + * 摩擦作用于所有粒子运动以使其阻尼。值应该非常接近1。 + */ + public friction: Vector2 = new Vector2(0.98, 1); + /** + * 当实体的时候,碰撞器应该碰撞的所有层的图层蒙版。使用了移动方法。默认为所有层。 + */ + public collidesWithLayers = Physics.allLayers; + + public particles: Particle[] = []; + public _constraints: Constraint[] = []; + /** + * 处理解决所有约束条件 + */ + public solveConstraints(){ + for (let i = this._constraints.length - 1; i >= 0; i --){ + this._constraints[i].solve(); + } + } + + /** + * 对每个粒子应用重力,并做Velet积分 + * @param deltaTimeSquared + * @param gravity + */ + public updateParticles(deltaTimeSquared: number, gravity: Vector2){ + for (let j = 0; j < this.particles.length; j ++){ + let p = this.particles[j]; + if (p.isPinned){ + p.position = p.pinnedPosition; + continue; + } + + p.applyForce(Vector2.multiply(new Vector2(p.mass), gravity)); + + // 计算速度并用摩擦阻尼 + let vel = Vector2.subtract(p.position, p.lastPosition).multiply(this.friction); + + // 使用verlet积分计算下一个位置 + let nextPos = Vector2.add(p.position, vel).add(Vector2.multiply(new Vector2(0.5 * deltaTimeSquared), p.acceleration)); + + p.lastPosition = p.position; + p.position = nextPos; + p.acceleration.x = p.acceleration.y = 0; + } + } + + public handleConstraintCollisions(){ + for (let i = this._constraints.length - 1; i >= 0; i --){ + if (this._constraints[i].collidesWithColliders) + this._constraints[i].handleCollisions(this.collidesWithLayers); + } + } + + public debugRender(camera: Camera){ + + } + } +} \ No newline at end of file diff --git a/source/src/Physics/Verlet/Constraints/Constraint.ts b/source/src/Physics/Verlet/Constraints/Constraint.ts new file mode 100644 index 00000000..6ab92304 --- /dev/null +++ b/source/src/Physics/Verlet/Constraints/Constraint.ts @@ -0,0 +1,18 @@ +module es { + export abstract class Constraint { + /** + * 如果为true,约束将用标准碰撞器检查碰撞。内部约束不需要将此设置为true。 + */ + public collidesWithColliders: boolean = true; + /** + * 解决约束 + */ + public abstract solve(); + + /** + * 如果collidesWithColliders为true,这个会被调用 + * @param collidesWithLayers + */ + public handleCollisions(collidesWithLayers: number){} + } +} \ No newline at end of file diff --git a/source/src/Physics/Verlet/Particle.ts b/source/src/Physics/Verlet/Particle.ts new file mode 100644 index 00000000..21f2aebd --- /dev/null +++ b/source/src/Physics/Verlet/Particle.ts @@ -0,0 +1,36 @@ +module es { + export class Particle { + /** + * 粒子的当前位置 + */ + public position: Vector2; + /** + * 粒子最近移动之前的位置 + */ + public lastPosition: Vector2; + /** + * 粒子的质量。考虑到所有的力量和限制 + */ + public mass: number = 1; + /** + * 粒子的半径 + */ + public radius: number = 0; + /** + * 如果为true,粒子将与标准对撞机相撞 + */ + public collidesWithColliders: boolean = true; + + public isPinned: boolean; + public acceleration: Vector2; + public pinnedPosition: Vector2; + + /** + * 对质点施加一个考虑质量的力 + * @param force + */ + public applyForce(force: Vector2){ + this.acceleration.add(Vector2.divide(force, new Vector2(this.mass))); + } + } +} \ No newline at end of file diff --git a/source/src/Physics/Verlet/VerletWorld.ts b/source/src/Physics/Verlet/VerletWorld.ts new file mode 100644 index 00000000..71dd1208 --- /dev/null +++ b/source/src/Physics/Verlet/VerletWorld.ts @@ -0,0 +1,158 @@ +module es { + /** + * Verlet模拟的根。创建一个世界,并调用它的更新方法。 + */ + export class VerletWorld { + /** + * 用于模拟的重力 + */ + public gravity: Vector2 = new Vector2(0, 980); + /** + * 整个模拟的最大迭代次数 + */ + public constraintIterations = 3; + /** + * 整个模拟的最大迭代次数 + */ + public maximumStepIterations = 5; + /** + * 世界的边界。粒子将被限制在这个空间中。 + */ + public simulationBounds?: Rectangle; + /** + * 粒子是否允许被拖拽 + */ + public allowDragging: boolean = true; + + public _composites: Composite[] = []; + public static _colliders: Collider[] = new Array(4); + public _tempCircle: Circle = new Circle(1); + + public _leftOverTime: number = 0; + public _fixedDeltaTime = 1 / 60; + public _iterationSteps: number = 0; + public _fixedDeltaTimeSq: number = 0; + + constructor(simulationBounds: Rectangle = null) { + this.simulationBounds = simulationBounds; + this._fixedDeltaTimeSq = Math.pow(this._fixedDeltaTimeSq, 2); + } + + public update(){ + this.updateTiming(); + + if (this.allowDragging) + this.handleDragging(); + + for (let iteration = 1; iteration <= this._iterationSteps; iteration++){ + for (let i = this._composites.length - 1; i >= 0; i --){ + let composite = this._composites[i]; + + for (let s = 0; s < this.constraintIterations; s++) + composite.solveConstraints(); + + composite.updateParticles(this._fixedDeltaTimeSq, this.gravity); + composite.handleConstraintCollisions(); + + for (let j = 0; j < composite.particles.length; j ++){ + let p = composite.particles[j]; + + if (this.simulationBounds){ + this.constrainParticleToBounds(p); + } + + if (p.collidesWithColliders) + this.handleCollisions(p, composite.collidesWithLayers); + } + } + } + } + + public handleCollisions(p: Particle, collidesWithLayers: number){ + let collidedCount = Physics.overlapCircleAll(p.position, p.radius, VerletWorld._colliders, collidesWithLayers); + for (let i = 0; i < collidedCount; i ++){ + let collider = VerletWorld._colliders[i]; + if (collider.isTrigger) + continue; + + let collisionResult = new CollisionResult(); + // 如果我们有一个足够大的粒子半径使用一个圆来检查碰撞,否则回落到一个点 + if (p.radius < 2){ + if (collider.shape.pointCollidesWithShape(p.position, collisionResult)){ + // TODO: 添加一个碰撞器字典,让碰撞器设置为力的体积。然后,number可以在这里乘以mtv。它应该是非常小的值,比如0.002。 + p.position.subtract(collisionResult.minimumTranslationVector); + } + }else{ + this._tempCircle.radius = p.radius; + this._tempCircle.position = p.position; + + if (this._tempCircle.collidesWithShape(collider.shape, collisionResult)){ + p.position.subtract(collisionResult.minimumTranslationVector); + } + } + } + } + + public constrainParticleToBounds(p: Particle){ + let tempPos = p.position; + let bounds = this.simulationBounds; + + if (p.radius == 0){ + if (tempPos.y > bounds.height) + tempPos.y = bounds.height; + else if(tempPos.y < bounds.y) + tempPos.y = bounds.y; + + if (tempPos.x < bounds.x) + tempPos.x = bounds.x; + else if (tempPos.x > bounds.width) + tempPos.x = bounds.width; + }else{ + if (tempPos.y < bounds.y + p.radius) + tempPos.y = 2 * (bounds.y + p.radius) - tempPos.y; + if (tempPos.y > bounds.height - p.radius) + tempPos.y = 2 * (bounds.height - p.radius) - tempPos.y; + if (tempPos.x > bounds.width - p.radius) + tempPos.x = 2 * (bounds.width - p.radius) - tempPos.x; + if (tempPos.x < bounds.x + p.radius) + tempPos.x = 2 * (bounds.x + p.radius) - tempPos.x; + } + + p.position = tempPos; + } + + public updateTiming(){ + this._leftOverTime += Time.deltaTime; + this._iterationSteps = Math.floor(Math.trunc(this._leftOverTime / this._fixedDeltaTime)); + this._leftOverTime -= this._iterationSteps * this._fixedDeltaTime; + + this._iterationSteps = Math.min(this._iterationSteps, this.maximumStepIterations); + } + + /** + * 向模拟添加composite + * @param composite + */ + public addComposite(composite: T): T{ + this._composites.push(composite); + return composite; + } + + /** + * 从模拟中删除一个composite + * @param composite + */ + public removeComposite(composite: Composite){ + this._composites.remove(composite); + } + + public handleDragging(){ + + } + + public debugRender(camera: Camera){ + for (let i = 0; i < this._composites.length; i ++) + this._composites[i].debugRender(camera); + } + } +} \ No newline at end of file