移除物理引擎 移动到新库
This commit is contained in:
24
source/src/ECS/Components/Physics/Colliders/BoxCollider.ts
Normal file
24
source/src/ECS/Components/Physics/Colliders/BoxCollider.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
///<reference path="./Collider.ts" />
|
||||
class BoxCollider extends Collider {
|
||||
|
||||
public get width(){
|
||||
return (this.shape as Box).width;
|
||||
}
|
||||
|
||||
public set width(value: number){
|
||||
this.setWidth(value);
|
||||
}
|
||||
|
||||
public setWidth(width: number): BoxCollider{
|
||||
this._colliderRequiresAutoSizing = false;
|
||||
let box = this.shape as Box;
|
||||
if (width != box.width){
|
||||
box.updateBox(width, box.height);
|
||||
this._isPositionDirty = true;
|
||||
if (this.entity && this._isParentEntityAddedToScene)
|
||||
Physics.updateCollider(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,16 @@ abstract class Collider extends Component{
|
||||
public shape: Shape;
|
||||
public physicsLayer = 1 << 0;
|
||||
public isTrigger: boolean;
|
||||
public registeredPhysicsBounds: Rectangle;
|
||||
|
||||
protected _isParentEntityAddedToScene;
|
||||
protected _isPositionDirty = true;
|
||||
protected _colliderRequiresAutoSizing;
|
||||
|
||||
public get bounds(): Rectangle {
|
||||
return this.shape.bounds;
|
||||
}
|
||||
|
||||
public initialize() {
|
||||
}
|
||||
}
|
||||
@@ -32,11 +32,11 @@ class Rectangle {
|
||||
this.y = value.y;
|
||||
}
|
||||
|
||||
constructor(x: number, y: number, width: number, height: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
constructor(x?: number, y?: number, width?: number, height?: number) {
|
||||
this.x = x ? x : 0;
|
||||
this.y = y ? y : 0;
|
||||
this.width = width ? width : 0;
|
||||
this.height = height ? height : 0;
|
||||
}
|
||||
|
||||
public intersects(value: Rectangle) {
|
||||
|
||||
@@ -10,4 +10,9 @@ class Physics {
|
||||
public static boxcastBroadphase(rect: Rectangle, layerMask: number = this.allLayers){
|
||||
return this._spatialHash.aabbBroadphase(rect, null, layerMask);
|
||||
}
|
||||
|
||||
public static updateCollider(collider: Collider){
|
||||
this._spatialHash.remove(collider);
|
||||
this._spatialHash.register(collider);
|
||||
}
|
||||
}
|
||||
28
source/src/Physics/Shapes/Box.ts
Normal file
28
source/src/Physics/Shapes/Box.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
///<reference path="./Polygon.ts" />
|
||||
class Box extends Polygon {
|
||||
public width: number;
|
||||
public height: number;
|
||||
|
||||
public updateBox(width: number, height: number){
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
let halfWidth = width / 2;
|
||||
let halfHeight = height / 2;
|
||||
|
||||
this.points[0] = new Vector2(-halfWidth, -halfHeight);
|
||||
this.points[1] = new Vector2(halfWidth, -halfHeight);
|
||||
this.points[2] = new Vector2(halfWidth, halfHeight);
|
||||
this.points[3] = new Vector2(-halfWidth, halfHeight);
|
||||
|
||||
for (let i = 0; i < this.points.length; i ++)
|
||||
this._originalPoints[i] = this.points[i];
|
||||
}
|
||||
|
||||
public containsPoint(point: Vector2){
|
||||
if (this.isUnrotated)
|
||||
return this.bounds.contains(point);
|
||||
|
||||
return super.containsPoint(point);
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,8 @@ class Circle extends Shape {
|
||||
}
|
||||
|
||||
public collidesWithShape(other: Shape): CollisionResult{
|
||||
if (other instanceof Rect && (other as Rect).isUnrotated){
|
||||
return ShapeCollisions.circleToRect(this, other as Rect);
|
||||
if (other instanceof Box && (other as Box).isUnrotated){
|
||||
return ShapeCollisions.circleToRect(this, other as Box);
|
||||
}
|
||||
|
||||
throw new Error(`Collisions of Circle to ${other} are not supported`);
|
||||
|
||||
@@ -4,7 +4,7 @@ class Polygon extends Shape {
|
||||
public isUnrotated: boolean = true;
|
||||
private _polygonCenter: Vector2;
|
||||
private _areEdgeNormalsDirty = true;
|
||||
private _originalPoint: Vector2[];
|
||||
protected _originalPoints: Vector2[];
|
||||
|
||||
public _edgeNormals: Vector2[];
|
||||
public get edgeNormals(){
|
||||
@@ -42,8 +42,8 @@ class Polygon extends Shape {
|
||||
this.points = points;
|
||||
this.recalculateCenterAndEdgeNormals();
|
||||
|
||||
this._originalPoint = new Vector2[points.length];
|
||||
this._originalPoint = points;
|
||||
this._originalPoints = new Vector2[points.length];
|
||||
this._originalPoints = points;
|
||||
}
|
||||
|
||||
public collidesWithShape(other: Shape){
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
class Rect extends Polygon {
|
||||
public containsPoint(point: Vector2){
|
||||
if (this.isUnrotated)
|
||||
return this.bounds.contains(point);
|
||||
|
||||
return super.containsPoint(point);
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ class ShapeCollisions {
|
||||
}
|
||||
}
|
||||
|
||||
public static circleToRect(circle: Circle, box: Rect): CollisionResult{
|
||||
public static circleToRect(circle: Circle, box: Box): CollisionResult{
|
||||
let result = new CollisionResult();
|
||||
let closestPointOnBounds = box.bounds.getClosestPointOnRectangleBorderToPoint(circle.position).res;
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
///<reference path="./Composite.ts"/>
|
||||
class Box extends Composite{
|
||||
constructor(center: Vector2, width: number, height: number, borderStiffness = 0.2, diagonalStiffness = 0.5){
|
||||
super();
|
||||
|
||||
let tl = this.addParticle(new Particle(Vector2.add(center, new Vector2(-width / 2, -height / 2))));
|
||||
let tr = this.addParticle(new Particle(Vector2.add(center, new Vector2(width / 2, -height / 2))));
|
||||
let br = this.addParticle(new Particle(Vector2.add(center, new Vector2(width / 2, height / 2))));
|
||||
let bl = this.addParticle(new Particle(Vector2.add(center, new Vector2(-width / 2, height / 2))));
|
||||
|
||||
this.addConstraint(new DistanceConstraint(tl, tr, borderStiffness));
|
||||
this.addConstraint(new DistanceConstraint(tr, br, borderStiffness));
|
||||
this.addConstraint(new DistanceConstraint(br, bl, borderStiffness));
|
||||
this.addConstraint(new DistanceConstraint(bl, tl, borderStiffness));
|
||||
|
||||
this.addConstraint(new DistanceConstraint(tl, br, diagonalStiffness)).setCollidesWithColliders(false);
|
||||
this.addConstraint(new DistanceConstraint(bl, tr, diagonalStiffness)).setCollidesWithColliders(false);
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
class Composite {
|
||||
private _constraints: Constraint[] = [];
|
||||
|
||||
public friction = new Vector2(0.98, 1);
|
||||
public drawParticles: boolean = true;
|
||||
public drawConstraints: boolean = true;
|
||||
public particles: Particle[] = [];
|
||||
public collidesWithLayers = Physics.allLayers;
|
||||
|
||||
/**
|
||||
* 处理解决所有约束条件
|
||||
*/
|
||||
public solveConstraints(){
|
||||
for (let i = this._constraints.length - 1; i >= 0; i --){
|
||||
this._constraints[i].solve();
|
||||
}
|
||||
}
|
||||
|
||||
public addParticle(particle: Particle){
|
||||
this.particles.push(particle);
|
||||
return particle;
|
||||
}
|
||||
|
||||
public addConstraint<T extends Constraint>(constraint: T): T{
|
||||
this._constraints.push(constraint);
|
||||
constraint.composite = this;
|
||||
return constraint;
|
||||
}
|
||||
|
||||
public removeConstraint(constraint: Constraint){
|
||||
this._constraints.remove(constraint);
|
||||
}
|
||||
|
||||
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, p.mass), gravity));
|
||||
|
||||
let vel = Vector2.multiply(Vector2.subtract(p.position, p.lastPosition), this.friction);
|
||||
let nextPos = Vector2.add(Vector2.add(p.position, vel), Vector2.multiply(p.acceleration, new Vector2(0.5 * deltaTimeSquared, 0.5 * deltaTimeSquared)));
|
||||
|
||||
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(graphics: egret.Graphics){
|
||||
if (this.drawConstraints){
|
||||
for (let i = 0; i < this._constraints.length; i ++){
|
||||
this._constraints[i].debugRender(graphics);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.drawParticles){
|
||||
for (let i = 0; i < this.particles.length; i ++){
|
||||
let size = this.particles[i].radius ? this.particles[i].radius : 4;
|
||||
graphics.lineStyle(4, DebugDefaults.verletParticle);
|
||||
graphics.drawRect(this.particles[i].position.x, this.particles[i].position.y, size, size);
|
||||
graphics.endFill();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
abstract class Constraint {
|
||||
public composite: Composite;
|
||||
public collidesWithColliders = true;
|
||||
|
||||
public abstract solve();
|
||||
|
||||
public handleCollisions(collidesWithLayers: number){
|
||||
|
||||
}
|
||||
|
||||
public debugRender(graphics: egret.Graphics) {}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
///<reference path="../../Shapes/Polygon.ts"/>
|
||||
class DistanceConstraint extends Constraint {
|
||||
public stiffness: number = 0;
|
||||
public restingDistance: number = 0;
|
||||
public tearSensitivity = Number.POSITIVE_INFINITY;
|
||||
public shouldApproximateCollisionWithPoints: boolean;
|
||||
public totalPointsToApproximateCollisionsWith = 5;
|
||||
|
||||
private _particleOne: Particle;
|
||||
private _particleTwo: Particle;
|
||||
private static _polygon = new Polygon(2, 1);
|
||||
|
||||
constructor(first: Particle, second: Particle, stiffness: number, distance = -1){
|
||||
super();
|
||||
|
||||
this._particleOne = first;
|
||||
this._particleTwo = second;
|
||||
this.stiffness = stiffness;
|
||||
|
||||
if (distance > -1){
|
||||
this.restingDistance = distance;
|
||||
}else{
|
||||
this.restingDistance = Vector2.distance(first.position, second.position);
|
||||
}
|
||||
}
|
||||
|
||||
public setCollidesWithColliders(collidesWithColliders: boolean){
|
||||
this.collidesWithColliders = collidesWithColliders;
|
||||
return this;
|
||||
}
|
||||
|
||||
public handleCollisions(collidersWithLayers){
|
||||
if (this.shouldApproximateCollisionWithPoints){
|
||||
this.approximateCollisionWithPoints(collidersWithLayers)
|
||||
return;
|
||||
}
|
||||
|
||||
let minX = Math.min(this._particleOne.position.x, this._particleTwo.position.x);
|
||||
let maxX = Math.max(this._particleOne.position.x, this._particleTwo.position.x);
|
||||
let minY = Math.min(this._particleOne.position.y, this._particleTwo.position.y);
|
||||
let maxY = Math.max(this._particleOne.position.y, this._particleTwo.position.y);
|
||||
DistanceConstraint._polygon.bounds = Rectangle.fromMinMax(minX, minY, maxX, maxY);
|
||||
|
||||
let midPoint: Vector2 = this.preparePolygonForCollisionChecks();
|
||||
let colliders = Physics.boxcastBroadphase(DistanceConstraint._polygon.bounds, collidersWithLayers);
|
||||
colliders.forEach(collider => {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private preparePolygonForCollisionChecks(){
|
||||
let midPoint = Vector2.lerp(this._particleOne.position, this._particleTwo.position, 0.5);
|
||||
DistanceConstraint._polygon.position = midPoint;
|
||||
DistanceConstraint._polygon.points[0] = Vector2.subtract(this._particleOne.position, DistanceConstraint._polygon.position);
|
||||
DistanceConstraint._polygon.points[1] = Vector2.subtract(this._particleTwo.position, DistanceConstraint._polygon.position);
|
||||
DistanceConstraint._polygon.recalculateCenterAndEdgeNormals();
|
||||
|
||||
return midPoint;
|
||||
}
|
||||
|
||||
private approximateCollisionWithPoints(collidersWithLayers: number){
|
||||
let pt;
|
||||
for (let j = 0; j < this.totalPointsToApproximateCollisionsWith - 1; j ++){
|
||||
pt = Vector2.lerp(this._particleOne.position, this._particleTwo.position, (j + 1) / this.totalPointsToApproximateCollisionsWith);
|
||||
let collidedCount = Physics.overlapCircleAll(pt, 3, VerletWorld.colliders, collidersWithLayers);
|
||||
for (let i = 0; i < collidedCount; i ++){
|
||||
let collider = VerletWorld.colliders[i];
|
||||
let collisionResult: CollisionResult = collider.shape.pointCollidesWithShape(pt);
|
||||
if (collisionResult){
|
||||
this._particleOne.position = Vector2.subtract(this._particleOne.position, collisionResult.minimumTranslationVector);
|
||||
this._particleTwo.position = Vector2.subtract(this._particleTwo.position, collisionResult.minimumTranslationVector);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public solve() {
|
||||
let diff = Vector2.subtract(this._particleOne.position, this._particleTwo.position);
|
||||
let d = diff.length();
|
||||
|
||||
let difference = (this.restingDistance - d) / d;
|
||||
|
||||
if (d / this.restingDistance > this.tearSensitivity){
|
||||
this.composite.removeConstraint(this);
|
||||
return;
|
||||
}
|
||||
|
||||
let im1 = 1 / this._particleOne.mass;
|
||||
let im2 = 1 / this._particleTwo.mass;
|
||||
let scalarP1 = (im1 / (im1 + im2)) * this.stiffness;
|
||||
let scalarP2 = this.stiffness - scalarP1;
|
||||
|
||||
this._particleOne.position = Vector2.add(this._particleOne.position, Vector2.multiply(diff, new Vector2(scalarP1 * difference, scalarP1 * difference)));
|
||||
this._particleTwo.position = Vector2.subtract(this._particleTwo.position, Vector2.multiply(diff, new Vector2(scalarP2 * difference, scalarP2 * difference)))
|
||||
}
|
||||
|
||||
public debugRender(graphics: egret.Graphics){
|
||||
graphics.lineStyle(1, DebugDefaults.verletConstraintEdge);
|
||||
graphics.lineTo(this._particleOne.position.x, this._particleOne.position.y);
|
||||
graphics.lineTo(this._particleTwo.position.x, this._particleTwo.position.y);
|
||||
graphics.endFill();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
class SpatialHash {
|
||||
public gridBounds: Rectangle = new Rectangle();
|
||||
|
||||
private _raycastParser: RaycastResultParser;
|
||||
private _cellSize: number;
|
||||
private _inverseCellSize: number;
|
||||
@@ -12,6 +14,29 @@ class SpatialHash {
|
||||
this._raycastParser = new RaycastResultParser();
|
||||
}
|
||||
|
||||
public remove(collider: Collider){
|
||||
let bounds = collider.registeredPhysicsBounds;
|
||||
let p1 = this.cellCoords(bounds.x, bounds.y);
|
||||
let p2 = this.cellCoords(bounds.right, bounds.bottom);
|
||||
|
||||
for (let x = p1.x; x <= p2.x; x ++){
|
||||
for (let y = p1.y; y <= p2.y; y ++){
|
||||
let cell = this.cellAtPosition(x, y);
|
||||
if (!cell)
|
||||
console.error(`removing Collider [${collider}] from a cell that it is not present in`);
|
||||
else
|
||||
cell.remove(collider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public register(collider: Collider){
|
||||
let bounds = collider.bounds;
|
||||
collider.registeredPhysicsBounds = bounds;
|
||||
let p1 = this.cellCoords(bounds.x, bounds.y);
|
||||
let p2 = this.cellCoords(bounds.right, bounds.bottom);
|
||||
}
|
||||
|
||||
public overlapCircle(circleCenter: Vector2, radius: number, results: Collider[], layerMask){
|
||||
let bounds = new Rectangle(circleCenter.x - radius, circleCenter.y - radius, radius * 2, radius * 2);
|
||||
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
/**
|
||||
* 基于verlet进行改造的物理引擎 ts重写
|
||||
* https://github.com/subprotocol/verlet-js
|
||||
*/
|
||||
class VerletWorld {
|
||||
public gravity: Vector2 = new Vector2(0, 980);
|
||||
public maximumStepIterations = 5;
|
||||
public constraintIterations = 3;
|
||||
public simulationBounds: Rectangle;
|
||||
|
||||
private _leftOverTime: number = 0;
|
||||
private _iterationSteps: number = 0;
|
||||
private _fixedDeltaTime = 1 / 60;
|
||||
private _composites: Composite[] = [];
|
||||
private _fixedDeltaTimeSq: number;
|
||||
|
||||
public static colliders: Collider[] = new Array(4);
|
||||
private _tempCircle: Circle = new Circle(1);
|
||||
|
||||
constructor(simulationBounds?: Rectangle){
|
||||
this.simulationBounds = simulationBounds;
|
||||
this._fixedDeltaTimeSq = Math.pow(this._fixedDeltaTime, 2);
|
||||
}
|
||||
|
||||
public update(){
|
||||
this.updateTiming();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private 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;
|
||||
|
||||
if (p.radius < 2){
|
||||
let collisionResult = collider.shape.pointCollidesWithShape(p.position);
|
||||
if (collisionResult){
|
||||
p.position = Vector2.subtract(p.position, collisionResult.minimumTranslationVector);
|
||||
}
|
||||
}else{
|
||||
this._tempCircle.radius = p.radius;
|
||||
this._tempCircle.position = p.position;
|
||||
|
||||
let collisionResult = this._tempCircle.collidesWithShape(collider.shape);
|
||||
if (collisionResult){
|
||||
p.position = Vector2.subtract(p.position, collisionResult.minimumTranslationVector);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private 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 debugRender(displayObject: egret.DisplayObject){
|
||||
if (!displayObject)
|
||||
return;
|
||||
|
||||
displayObject.stage.removeChildren();
|
||||
for (let i = 0; i < this._composites.length; i ++){
|
||||
let shape = new egret.Shape();
|
||||
this._composites[i].debugRender(shape.graphics);
|
||||
displayObject.stage.addChild(shape);
|
||||
}
|
||||
}
|
||||
|
||||
public addComposite<T extends Composite>(composite: T): T{
|
||||
this._composites.push(composite);
|
||||
return composite;
|
||||
}
|
||||
|
||||
private updateTiming(){
|
||||
this._leftOverTime += Time.deltaTime;
|
||||
this._iterationSteps = Math.trunc(this._leftOverTime / this._fixedDeltaTime);
|
||||
this._leftOverTime -= this._iterationSteps * this._fixedDeltaTime;
|
||||
|
||||
this._iterationSteps = Math.min(this._iterationSteps, this.maximumStepIterations);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user