新增verlet物理引擎(实验性)
This commit is contained in:
128
source/bin/framework.d.ts
vendored
128
source/bin/framework.d.ts
vendored
@@ -3854,6 +3854,7 @@ declare module es {
|
|||||||
* @param isBox
|
* @param isBox
|
||||||
*/
|
*/
|
||||||
constructor(points: Vector2[], isBox?: boolean);
|
constructor(points: Vector2[], isBox?: boolean);
|
||||||
|
create(vertCount: number, radius: number): void;
|
||||||
_edgeNormals: Vector2[];
|
_edgeNormals: Vector2[];
|
||||||
/**
|
/**
|
||||||
* 边缘法线用于SAT碰撞检测。缓存它们用于避免squareRoots
|
* 边缘法线用于SAT碰撞检测。缓存它们用于避免squareRoots
|
||||||
@@ -4095,6 +4096,133 @@ declare module es {
|
|||||||
static intervalDistance(minA: number, maxA: number, minB: number, maxB: number): number;
|
static intervalDistance(minA: number, maxA: number, minB: number, maxB: number): number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
declare module es {
|
||||||
|
class Particle {
|
||||||
|
position: Vector2;
|
||||||
|
lastPosition: Vector2;
|
||||||
|
mass: number;
|
||||||
|
radius: number;
|
||||||
|
collidesWithColliders: boolean;
|
||||||
|
isPinned: boolean;
|
||||||
|
acceleration: Vector2;
|
||||||
|
pinnedPosition: Vector2;
|
||||||
|
constructor(position: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
});
|
||||||
|
applyForce(force: Vector2): void;
|
||||||
|
pin(): Particle;
|
||||||
|
pinTo(position: Vector2): Particle;
|
||||||
|
unpin(): Particle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
declare module es {
|
||||||
|
class VerletWorld {
|
||||||
|
gravity: Vector2;
|
||||||
|
constraintIterations: number;
|
||||||
|
maximumStepIterations: number;
|
||||||
|
simulationBounds: Rectangle;
|
||||||
|
allowDragging: boolean;
|
||||||
|
selectionRadiusSquared: number;
|
||||||
|
_draggedParticle: Particle;
|
||||||
|
_composites: Composite[];
|
||||||
|
static _colliders: Collider[];
|
||||||
|
_tempCircle: Circle;
|
||||||
|
_leftOverTime: number;
|
||||||
|
_fixedDeltaTime: number;
|
||||||
|
_iterationSteps: number;
|
||||||
|
_fixedDeltaTimeSq: number;
|
||||||
|
constructor(simulationBounds?: Rectangle);
|
||||||
|
update(): void;
|
||||||
|
constrainParticleToBounds(p: Particle): void;
|
||||||
|
handleCollisions(p: Particle, collidesWithLayers: number): void;
|
||||||
|
updateTiming(): void;
|
||||||
|
addComposite<T extends Composite>(composite: T): T;
|
||||||
|
removeComposite(composite: Composite): void;
|
||||||
|
handleDragging(): void;
|
||||||
|
getNearestParticle(position: Vector2): Particle;
|
||||||
|
debugRender(batcher: IBatcher): void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
declare module es {
|
||||||
|
class Composite {
|
||||||
|
friction: Vector2;
|
||||||
|
drawParticles: boolean;
|
||||||
|
drawConstraints: boolean;
|
||||||
|
collidesWithLayers: number;
|
||||||
|
particles: Particle[];
|
||||||
|
_constraints: Constraint[];
|
||||||
|
addParticle(particle: Particle): Particle;
|
||||||
|
removeParticle(particle: Particle): void;
|
||||||
|
removeAll(): void;
|
||||||
|
addConstraint<T extends Constraint>(constraint: T): T;
|
||||||
|
removeConstraint(constraint: Constraint): void;
|
||||||
|
applyForce(force: Vector2): void;
|
||||||
|
solveConstraints(): void;
|
||||||
|
updateParticles(deltaTimeSquared: number, gravity: Vector2): void;
|
||||||
|
handleConstraintCollisions(): void;
|
||||||
|
debugRender(batcher: IBatcher): void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
declare module es {
|
||||||
|
class Ball extends Composite {
|
||||||
|
constructor(position: Vector2, radius?: number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
declare module es {
|
||||||
|
class VerletBox extends es.Composite {
|
||||||
|
constructor(center: es.Vector2, width: number, height: number, borderStiffness?: number, diagonalStiffness?: number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
declare module es {
|
||||||
|
class LineSegments extends Composite {
|
||||||
|
constructor(vertices: Vector2[], stiffness: number);
|
||||||
|
pinParticleAtIndex(index: number): LineSegments;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
declare module es {
|
||||||
|
abstract class Constraint {
|
||||||
|
composite: Composite;
|
||||||
|
collidesWithColliders: boolean;
|
||||||
|
abstract solve(): void;
|
||||||
|
handleCollisions(collidesWithLayers: number): void;
|
||||||
|
debugRender(batcher: IBatcher): void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
declare module es {
|
||||||
|
class AngleConstraint extends Constraint {
|
||||||
|
stiffness: number;
|
||||||
|
angleInRadius: number;
|
||||||
|
_particleA: Particle;
|
||||||
|
_centerParticle: Particle;
|
||||||
|
_particleC: Particle;
|
||||||
|
constructor(a: Particle, center: Particle, c: Particle, stiffness: number);
|
||||||
|
angleBetweenParticles(): number;
|
||||||
|
solve(): void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
declare module es {
|
||||||
|
class DistanceConstraint extends Constraint {
|
||||||
|
stiffness: number;
|
||||||
|
restingDistance: number;
|
||||||
|
tearSensitivity: number;
|
||||||
|
shouldApproximateCollisionsWithPoints: boolean;
|
||||||
|
totalPointsToApproximateCollisionsWith: number;
|
||||||
|
_particleOne: Particle;
|
||||||
|
_particleTwo: Particle;
|
||||||
|
static _polygon: Polygon;
|
||||||
|
constructor(first: Particle, second: Particle, stiffness: number, distance?: number);
|
||||||
|
static create(a: Particle, center: Particle, c: Particle, stiffness: number, angleInDegrees: number): DistanceConstraint;
|
||||||
|
setTearSensitivity(tearSensitivity: number): this;
|
||||||
|
setCollidesWithColliders(collidesWithColliders: boolean): this;
|
||||||
|
setShouldApproximateCollisionsWithPoints(shouldApproximateCollisionsWithPoints: boolean): this;
|
||||||
|
solve(): void;
|
||||||
|
handleCollisions(collidesWithLayers: number): void;
|
||||||
|
approximateCollisionsWithPoints(collidesWithLayers: number): void;
|
||||||
|
preparePolygonForCollisionChecks(midPoint: Vector2): void;
|
||||||
|
debugRender(batcher: IBatcher): void;
|
||||||
|
}
|
||||||
|
}
|
||||||
declare module es {
|
declare module es {
|
||||||
interface IAnimFrame {
|
interface IAnimFrame {
|
||||||
t: number;
|
t: number;
|
||||||
|
|||||||
@@ -1303,14 +1303,10 @@ var es;
|
|||||||
* @param value
|
* @param value
|
||||||
*/
|
*/
|
||||||
Vector2.prototype.divide = function (value) {
|
Vector2.prototype.divide = function (value) {
|
||||||
this.x /= value.x;
|
return new Vector2(this.x / value.x, this.y / value.y);
|
||||||
this.y /= value.y;
|
|
||||||
return this;
|
|
||||||
};
|
};
|
||||||
Vector2.prototype.divideScaler = function (value) {
|
Vector2.prototype.divideScaler = function (value) {
|
||||||
this.x /= value;
|
return new Vector2(this.x / value, this.y / value);
|
||||||
this.y /= value;
|
|
||||||
return this;
|
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -9456,6 +9452,9 @@ var es;
|
|||||||
_this.isBox = isBox;
|
_this.isBox = isBox;
|
||||||
return _this;
|
return _this;
|
||||||
}
|
}
|
||||||
|
Polygon.prototype.create = function (vertCount, radius) {
|
||||||
|
Polygon.buildSymmetricalPolygon(vertCount, radius);
|
||||||
|
};
|
||||||
Object.defineProperty(Polygon.prototype, "edgeNormals", {
|
Object.defineProperty(Polygon.prototype, "edgeNormals", {
|
||||||
/**
|
/**
|
||||||
* 边缘法线用于SAT碰撞检测。缓存它们用于避免squareRoots
|
* 边缘法线用于SAT碰撞检测。缓存它们用于避免squareRoots
|
||||||
@@ -10403,6 +10402,471 @@ var es;
|
|||||||
es.ShapeCollisionsPolygon = ShapeCollisionsPolygon;
|
es.ShapeCollisionsPolygon = ShapeCollisionsPolygon;
|
||||||
})(es || (es = {}));
|
})(es || (es = {}));
|
||||||
var es;
|
var es;
|
||||||
|
(function (es) {
|
||||||
|
var Particle = /** @class */ (function () {
|
||||||
|
function Particle(position) {
|
||||||
|
this.mass = 1;
|
||||||
|
this.collidesWithColliders = true;
|
||||||
|
this.position = new es.Vector2(position.x, position.y);
|
||||||
|
this.lastPosition = new es.Vector2(position.x, position.y);
|
||||||
|
}
|
||||||
|
Particle.prototype.applyForce = function (force) {
|
||||||
|
this.acceleration = this.acceleration.add(force.divideScaler(this.mass));
|
||||||
|
};
|
||||||
|
Particle.prototype.pin = function () {
|
||||||
|
this.isPinned = true;
|
||||||
|
this.pinnedPosition = this.position;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
Particle.prototype.pinTo = function (position) {
|
||||||
|
this.isPinned = true;
|
||||||
|
this.pinnedPosition = position;
|
||||||
|
this.position = this.pinnedPosition;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
Particle.prototype.unpin = function () {
|
||||||
|
this.isPinned = false;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
return Particle;
|
||||||
|
}());
|
||||||
|
es.Particle = Particle;
|
||||||
|
})(es || (es = {}));
|
||||||
|
var es;
|
||||||
|
(function (es) {
|
||||||
|
var VerletWorld = /** @class */ (function () {
|
||||||
|
function VerletWorld(simulationBounds) {
|
||||||
|
if (simulationBounds === void 0) { simulationBounds = null; }
|
||||||
|
this.gravity = new es.Vector2(0, 980);
|
||||||
|
this.constraintIterations = 3;
|
||||||
|
this.maximumStepIterations = 5;
|
||||||
|
this.allowDragging = true;
|
||||||
|
this.selectionRadiusSquared = 20 * 20;
|
||||||
|
this._composites = [];
|
||||||
|
this._tempCircle = new es.Circle(1);
|
||||||
|
this._fixedDeltaTime = 1 / 60;
|
||||||
|
this.simulationBounds = simulationBounds;
|
||||||
|
this._fixedDeltaTime = Math.pow(this._fixedDeltaTime, 2);
|
||||||
|
}
|
||||||
|
VerletWorld.prototype.update = function () {
|
||||||
|
this.updateTiming();
|
||||||
|
if (this.allowDragging)
|
||||||
|
this.handleDragging();
|
||||||
|
for (var iteration = 1; iteration <= this._iterationSteps; iteration++) {
|
||||||
|
for (var i = this._composites.length - 1; i >= 0; i--) {
|
||||||
|
var composite = this._composites[i];
|
||||||
|
for (var s = 0; s < this.constraintIterations; s++)
|
||||||
|
composite.solveConstraints();
|
||||||
|
composite.updateParticles(this._fixedDeltaTimeSq, this.gravity);
|
||||||
|
composite.handleConstraintCollisions();
|
||||||
|
for (var j = 0; j < composite.particles.length; j++) {
|
||||||
|
var p = composite.particles[j];
|
||||||
|
if (this.simulationBounds) {
|
||||||
|
this.constrainParticleToBounds(p);
|
||||||
|
}
|
||||||
|
if (p.collidesWithColliders)
|
||||||
|
this.handleCollisions(p, composite.collidesWithLayers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
VerletWorld.prototype.constrainParticleToBounds = function (p) {
|
||||||
|
var tempPos = p.position;
|
||||||
|
var 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;
|
||||||
|
};
|
||||||
|
VerletWorld.prototype.handleCollisions = function (p, collidesWithLayers) {
|
||||||
|
var collidedCount = es.Physics.overlapCircleAll(p.position, p.radius, VerletWorld._colliders, collidesWithLayers);
|
||||||
|
for (var i = 0; i < collidedCount; i++) {
|
||||||
|
var collider = VerletWorld._colliders[i];
|
||||||
|
if (collider.isTrigger)
|
||||||
|
continue;
|
||||||
|
var collisionResult = new es.CollisionResult();
|
||||||
|
if (p.radius < 2) {
|
||||||
|
if (collider.shape.pointCollidesWithShape(p.position, collisionResult)) {
|
||||||
|
p.position = p.position.sub(collisionResult.minimumTranslationVector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._tempCircle.radius = p.radius;
|
||||||
|
this._tempCircle.position = p.position;
|
||||||
|
if (this._tempCircle.collidesWithShape(collider.shape, collisionResult)) {
|
||||||
|
p.position = p.position.sub(collisionResult.minimumTranslationVector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
VerletWorld.prototype.updateTiming = function () {
|
||||||
|
this._leftOverTime += es.Time.deltaTime;
|
||||||
|
this._iterationSteps = Math.trunc(this._leftOverTime / this._fixedDeltaTime);
|
||||||
|
this._leftOverTime -= this._iterationSteps * this._fixedDeltaTime;
|
||||||
|
this._iterationSteps = Math.min(this._iterationSteps, this.maximumStepIterations);
|
||||||
|
};
|
||||||
|
VerletWorld.prototype.addComposite = function (composite) {
|
||||||
|
this._composites.push(composite);
|
||||||
|
return composite;
|
||||||
|
};
|
||||||
|
VerletWorld.prototype.removeComposite = function (composite) {
|
||||||
|
var index = this._composites.indexOf(composite);
|
||||||
|
this._composites.splice(index, 1);
|
||||||
|
};
|
||||||
|
VerletWorld.prototype.handleDragging = function () {
|
||||||
|
};
|
||||||
|
VerletWorld.prototype.getNearestParticle = function (position) {
|
||||||
|
var nearestSquaredDistance = this.selectionRadiusSquared;
|
||||||
|
var particle = null;
|
||||||
|
for (var j = 0; j < this._composites.length; j++) {
|
||||||
|
var particles = this._composites[j].particles;
|
||||||
|
for (var i = 0; i < particles.length; i++) {
|
||||||
|
var p = particles[i];
|
||||||
|
var squaredDistanceToParticle = es.Vector2.sqrDistance(p.position, position);
|
||||||
|
if (squaredDistanceToParticle <= this.selectionRadiusSquared &&
|
||||||
|
(particle == null || squaredDistanceToParticle < nearestSquaredDistance)) {
|
||||||
|
particle = p;
|
||||||
|
nearestSquaredDistance = squaredDistanceToParticle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return particle;
|
||||||
|
};
|
||||||
|
VerletWorld.prototype.debugRender = function (batcher) {
|
||||||
|
for (var i = 0; i < this._composites.length; i++) {
|
||||||
|
this._composites[i].debugRender(batcher);
|
||||||
|
}
|
||||||
|
if (this.allowDragging) {
|
||||||
|
if (this._draggedParticle != null) {
|
||||||
|
batcher.drawCircle(this._draggedParticle.position, 8, es.Color.White);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
VerletWorld._colliders = [];
|
||||||
|
return VerletWorld;
|
||||||
|
}());
|
||||||
|
es.VerletWorld = VerletWorld;
|
||||||
|
})(es || (es = {}));
|
||||||
|
var es;
|
||||||
|
(function (es) {
|
||||||
|
var Composite = /** @class */ (function () {
|
||||||
|
function Composite() {
|
||||||
|
this.friction = new es.Vector2(0.98, 1);
|
||||||
|
this.drawParticles = true;
|
||||||
|
this.drawConstraints = true;
|
||||||
|
this.collidesWithLayers = es.Physics.allLayers;
|
||||||
|
this.particles = [];
|
||||||
|
this._constraints = [];
|
||||||
|
}
|
||||||
|
Composite.prototype.addParticle = function (particle) {
|
||||||
|
this.particles.push(particle);
|
||||||
|
return particle;
|
||||||
|
};
|
||||||
|
Composite.prototype.removeParticle = function (particle) {
|
||||||
|
var index = this.particles.indexOf(particle);
|
||||||
|
this.particles.splice(index, 1);
|
||||||
|
};
|
||||||
|
Composite.prototype.removeAll = function () {
|
||||||
|
this.particles.length = 0;
|
||||||
|
this._constraints.length = 0;
|
||||||
|
};
|
||||||
|
Composite.prototype.addConstraint = function (constraint) {
|
||||||
|
this._constraints.push(constraint);
|
||||||
|
constraint.composite = this;
|
||||||
|
return constraint;
|
||||||
|
};
|
||||||
|
Composite.prototype.removeConstraint = function (constraint) {
|
||||||
|
var index = this._constraints.indexOf(constraint);
|
||||||
|
this._constraints.splice(index, 1);
|
||||||
|
};
|
||||||
|
Composite.prototype.applyForce = function (force) {
|
||||||
|
for (var j = 0; j < this.particles.length; j++)
|
||||||
|
this.particles[j].applyForce(force);
|
||||||
|
};
|
||||||
|
Composite.prototype.solveConstraints = function () {
|
||||||
|
for (var i = this._constraints.length - 1; i >= 0; i--)
|
||||||
|
this._constraints[i].solve();
|
||||||
|
};
|
||||||
|
Composite.prototype.updateParticles = function (deltaTimeSquared, gravity) {
|
||||||
|
for (var j = 0; j < this.particles.length; j++) {
|
||||||
|
var p = this.particles[j];
|
||||||
|
if (p.isPinned) {
|
||||||
|
p.position = p.pinnedPosition;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
p.applyForce(gravity.scale(p.mass));
|
||||||
|
var vel = p.position.sub(p.lastPosition).multiply(this.friction);
|
||||||
|
var nextPos = p.position.add(vel).add(p.acceleration.scale(0.5 * deltaTimeSquared));
|
||||||
|
p.lastPosition = p.position;
|
||||||
|
p.position = nextPos;
|
||||||
|
p.acceleration.x = p.acceleration.y = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Composite.prototype.handleConstraintCollisions = function () {
|
||||||
|
for (var i = this._constraints.length - 1; i >= 0; i--) {
|
||||||
|
if (this._constraints[i].collidesWithColliders)
|
||||||
|
this._constraints[i].handleCollisions(this.collidesWithLayers);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Composite.prototype.debugRender = function (batcher) {
|
||||||
|
if (this.drawConstraints) {
|
||||||
|
for (var i = 0; i < this._constraints.length; i++)
|
||||||
|
this._constraints[i].debugRender(batcher);
|
||||||
|
}
|
||||||
|
if (this.drawParticles) {
|
||||||
|
for (var i = 0; i < this.particles.length; i++) {
|
||||||
|
if (this.particles[i].radius == 0)
|
||||||
|
batcher.drawPixel(this.particles[i].position, new es.Color(220, 52, 94), 4);
|
||||||
|
else
|
||||||
|
batcher.drawCircleLow(this.particles[i].position, this.particles[i].radius, new es.Color(220, 52, 94), 1, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return Composite;
|
||||||
|
}());
|
||||||
|
es.Composite = Composite;
|
||||||
|
})(es || (es = {}));
|
||||||
|
///<reference path="./Composite.ts" />
|
||||||
|
var es;
|
||||||
|
///<reference path="./Composite.ts" />
|
||||||
|
(function (es) {
|
||||||
|
var Ball = /** @class */ (function (_super) {
|
||||||
|
__extends(Ball, _super);
|
||||||
|
function Ball(position, radius) {
|
||||||
|
if (radius === void 0) { radius = 10; }
|
||||||
|
var _this = _super.call(this) || this;
|
||||||
|
_this.addParticle(new es.Particle(position)).radius = radius;
|
||||||
|
return _this;
|
||||||
|
}
|
||||||
|
return Ball;
|
||||||
|
}(es.Composite));
|
||||||
|
es.Ball = Ball;
|
||||||
|
})(es || (es = {}));
|
||||||
|
///<reference path="./Composite.ts" />
|
||||||
|
var es;
|
||||||
|
///<reference path="./Composite.ts" />
|
||||||
|
(function (es) {
|
||||||
|
var VerletBox = /** @class */ (function (_super) {
|
||||||
|
__extends(VerletBox, _super);
|
||||||
|
function VerletBox(center, width, height, borderStiffness, diagonalStiffness) {
|
||||||
|
if (borderStiffness === void 0) { borderStiffness = 0.2; }
|
||||||
|
if (diagonalStiffness === void 0) { diagonalStiffness = 0.5; }
|
||||||
|
var _this = _super.call(this) || this;
|
||||||
|
var tl = _this.addParticle(new es.Particle(center.add(new es.Vector2(-width / 2, -height / 2))));
|
||||||
|
var tr = _this.addParticle(new es.Particle(center.add(new es.Vector2(width / 2, -height / 2))));
|
||||||
|
var br = _this.addParticle(new es.Particle(center.add(new es.Vector2(width / 2, height / 2))));
|
||||||
|
var bl = _this.addParticle(new es.Particle(center.add(new es.Vector2(-width / 2, height / 2))));
|
||||||
|
_this.addConstraint(new es.DistanceConstraint(tl, tr, borderStiffness));
|
||||||
|
_this.addConstraint(new es.DistanceConstraint(tr, br, borderStiffness));
|
||||||
|
_this.addConstraint(new es.DistanceConstraint(br, bl, borderStiffness));
|
||||||
|
_this.addConstraint(new es.DistanceConstraint(bl, tl, borderStiffness));
|
||||||
|
_this.addConstraint(new es.DistanceConstraint(tl, br, diagonalStiffness))
|
||||||
|
.setCollidesWithColliders(false);
|
||||||
|
_this.addConstraint(new es.DistanceConstraint(bl, tr, diagonalStiffness))
|
||||||
|
.setCollidesWithColliders(false);
|
||||||
|
return _this;
|
||||||
|
}
|
||||||
|
return VerletBox;
|
||||||
|
}(es.Composite));
|
||||||
|
es.VerletBox = VerletBox;
|
||||||
|
})(es || (es = {}));
|
||||||
|
var es;
|
||||||
|
(function (es) {
|
||||||
|
var LineSegments = /** @class */ (function (_super) {
|
||||||
|
__extends(LineSegments, _super);
|
||||||
|
function LineSegments(vertices, stiffness) {
|
||||||
|
var _this = _super.call(this) || this;
|
||||||
|
for (var i = 0; i < vertices.length; i++) {
|
||||||
|
var p = new es.Particle(vertices[i]);
|
||||||
|
_this.addParticle(p);
|
||||||
|
if (i > 0)
|
||||||
|
_this.addConstraint(new es.DistanceConstraint(_this.particles[i], _this.particles[i - 1], stiffness));
|
||||||
|
}
|
||||||
|
return _this;
|
||||||
|
}
|
||||||
|
LineSegments.prototype.pinParticleAtIndex = function (index) {
|
||||||
|
this.particles[index].pin();
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
return LineSegments;
|
||||||
|
}(es.Composite));
|
||||||
|
es.LineSegments = LineSegments;
|
||||||
|
})(es || (es = {}));
|
||||||
|
var es;
|
||||||
|
(function (es) {
|
||||||
|
var Constraint = /** @class */ (function () {
|
||||||
|
function Constraint() {
|
||||||
|
this.collidesWithColliders = true;
|
||||||
|
}
|
||||||
|
Constraint.prototype.handleCollisions = function (collidesWithLayers) {
|
||||||
|
};
|
||||||
|
Constraint.prototype.debugRender = function (batcher) {
|
||||||
|
};
|
||||||
|
return Constraint;
|
||||||
|
}());
|
||||||
|
es.Constraint = Constraint;
|
||||||
|
})(es || (es = {}));
|
||||||
|
///<reference path="./Constraint.ts" />
|
||||||
|
var es;
|
||||||
|
///<reference path="./Constraint.ts" />
|
||||||
|
(function (es) {
|
||||||
|
var AngleConstraint = /** @class */ (function (_super) {
|
||||||
|
__extends(AngleConstraint, _super);
|
||||||
|
function AngleConstraint(a, center, c, stiffness) {
|
||||||
|
var _this = _super.call(this) || this;
|
||||||
|
_this._particleA = a;
|
||||||
|
_this._centerParticle = center;
|
||||||
|
_this._particleC = c;
|
||||||
|
_this.stiffness = stiffness;
|
||||||
|
_this.collidesWithColliders = false;
|
||||||
|
_this.angleInRadius = _this.angleBetweenParticles();
|
||||||
|
return _this;
|
||||||
|
}
|
||||||
|
AngleConstraint.prototype.angleBetweenParticles = function () {
|
||||||
|
var first = this._particleA.position.sub(this._centerParticle.position);
|
||||||
|
var second = this._particleC.position.sub(this._centerParticle.position);
|
||||||
|
return Math.atan2(first.x * second.y - first.y * second.x, first.x * second.x + first.y * second.y);
|
||||||
|
};
|
||||||
|
AngleConstraint.prototype.solve = function () {
|
||||||
|
var angleBetween = this.angleBetweenParticles();
|
||||||
|
var diff = angleBetween - this.angleInRadius;
|
||||||
|
if (diff <= -Math.PI)
|
||||||
|
diff += 2 * Math.PI;
|
||||||
|
else if (diff >= Math.PI)
|
||||||
|
diff -= 2 * Math.PI;
|
||||||
|
diff *= this.stiffness;
|
||||||
|
this._particleA.position = es.MathHelper.rotateAround2(this._particleA.position, this._centerParticle.position, diff);
|
||||||
|
this._particleC.position = es.MathHelper.rotateAround2(this._particleC.position, this._centerParticle.position, -diff);
|
||||||
|
this._centerParticle.position = es.MathHelper.rotateAround2(this._centerParticle.position, this._particleA.position, diff);
|
||||||
|
this._centerParticle.position = es.MathHelper.rotateAround2(this._centerParticle.position, this._particleC.position, -diff);
|
||||||
|
};
|
||||||
|
return AngleConstraint;
|
||||||
|
}(es.Constraint));
|
||||||
|
es.AngleConstraint = AngleConstraint;
|
||||||
|
})(es || (es = {}));
|
||||||
|
var es;
|
||||||
|
(function (es) {
|
||||||
|
var DistanceConstraint = /** @class */ (function (_super) {
|
||||||
|
__extends(DistanceConstraint, _super);
|
||||||
|
function DistanceConstraint(first, second, stiffness, distance) {
|
||||||
|
if (distance === void 0) { distance = -1; }
|
||||||
|
var _this = _super.call(this) || this;
|
||||||
|
_this.tearSensitivity = Number.POSITIVE_INFINITY;
|
||||||
|
_this.totalPointsToApproximateCollisionsWith = 5;
|
||||||
|
DistanceConstraint._polygon.create(2, 1);
|
||||||
|
_this._particleOne = first;
|
||||||
|
_this._particleTwo = second;
|
||||||
|
_this.stiffness = stiffness;
|
||||||
|
if (distance > -1)
|
||||||
|
_this.restingDistance = distance;
|
||||||
|
else
|
||||||
|
_this.restingDistance = es.Vector2.distance(first.position, second.position);
|
||||||
|
return _this;
|
||||||
|
}
|
||||||
|
DistanceConstraint.create = function (a, center, c, stiffness, angleInDegrees) {
|
||||||
|
var aToCenter = es.Vector2.distance(a.position, center.position);
|
||||||
|
var cToCenter = es.Vector2.distance(c.position, center.position);
|
||||||
|
var distance = Math.sqrt(aToCenter * aToCenter + cToCenter * cToCenter - (2 * aToCenter * cToCenter * Math.cos(angleInDegrees * es.MathHelper.Deg2Rad)));
|
||||||
|
return new DistanceConstraint(a, c, stiffness, distance);
|
||||||
|
};
|
||||||
|
DistanceConstraint.prototype.setTearSensitivity = function (tearSensitivity) {
|
||||||
|
this.tearSensitivity = tearSensitivity;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
DistanceConstraint.prototype.setCollidesWithColliders = function (collidesWithColliders) {
|
||||||
|
this.collidesWithColliders = collidesWithColliders;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
DistanceConstraint.prototype.setShouldApproximateCollisionsWithPoints = function (shouldApproximateCollisionsWithPoints) {
|
||||||
|
this.shouldApproximateCollisionsWithPoints = shouldApproximateCollisionsWithPoints;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
DistanceConstraint.prototype.solve = function () {
|
||||||
|
var diff = this._particleOne.position.sub(this._particleTwo.position);
|
||||||
|
var d = diff.magnitude();
|
||||||
|
var difference = (this.restingDistance - d) / d;
|
||||||
|
if (d / this.restingDistance > this.tearSensitivity) {
|
||||||
|
this.composite.removeConstraint(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var im1 = 1 / this._particleOne.mass;
|
||||||
|
var im2 = 1 / this._particleTwo.mass;
|
||||||
|
var scalarP1 = (im1 / (im1 + im2)) * this.stiffness;
|
||||||
|
var scalarP2 = this.stiffness - scalarP1;
|
||||||
|
this._particleOne.position = this._particleOne.position.add(diff.scale(scalarP1 * difference));
|
||||||
|
this._particleTwo.position = this._particleTwo.position.sub(diff.scale(scalarP2 * difference));
|
||||||
|
};
|
||||||
|
DistanceConstraint.prototype.handleCollisions = function (collidesWithLayers) {
|
||||||
|
if (this.shouldApproximateCollisionsWithPoints) {
|
||||||
|
this.approximateCollisionsWithPoints(collidesWithLayers);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var minX = Math.min(this._particleOne.position.x, this._particleTwo.position.x);
|
||||||
|
var maxX = Math.max(this._particleOne.position.x, this._particleTwo.position.x);
|
||||||
|
var minY = Math.min(this._particleOne.position.y, this._particleTwo.position.y);
|
||||||
|
var maxY = Math.max(this._particleOne.position.y, this._particleTwo.position.y);
|
||||||
|
DistanceConstraint._polygon.bounds = es.Rectangle.fromMinMax(minX, minY, maxX, maxY);
|
||||||
|
var midPoint;
|
||||||
|
this.preparePolygonForCollisionChecks(midPoint);
|
||||||
|
var colliders = es.Physics.boxcastBroadphase(DistanceConstraint._polygon.bounds, collidesWithLayers);
|
||||||
|
for (var i = 0; i < colliders.length; i++) {
|
||||||
|
var collider = colliders[i];
|
||||||
|
var result = new es.CollisionResult();
|
||||||
|
if (DistanceConstraint._polygon.collidesWithShape(collider.shape, result)) {
|
||||||
|
this._particleOne.position = this._particleOne.position.sub(result.minimumTranslationVector);
|
||||||
|
this._particleTwo.position = this._particleTwo.position.sub(result.minimumTranslationVector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
DistanceConstraint.prototype.approximateCollisionsWithPoints = function (collidesWithLayers) {
|
||||||
|
var pt;
|
||||||
|
for (var j = 0; j < this.totalPointsToApproximateCollisionsWith - 1; j++) {
|
||||||
|
pt = es.Vector2.lerp(this._particleOne.position, this._particleTwo.position, (j + 1) / this.totalPointsToApproximateCollisionsWith);
|
||||||
|
var collidedCount = es.Physics.overlapCircleAll(pt, 3, es.VerletWorld._colliders, collidesWithLayers);
|
||||||
|
for (var i = 0; i < collidedCount; i++) {
|
||||||
|
var collider = es.VerletWorld._colliders[i];
|
||||||
|
var collisionResult = new es.CollisionResult();
|
||||||
|
if (collider.shape.pointCollidesWithShape(pt, collisionResult)) {
|
||||||
|
this._particleOne.position = this._particleOne.position.sub(collisionResult.minimumTranslationVector);
|
||||||
|
this._particleTwo.position = this._particleTwo.position.sub(collisionResult.minimumTranslationVector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
DistanceConstraint.prototype.preparePolygonForCollisionChecks = function (midPoint) {
|
||||||
|
var tempMidPoint = es.Vector2.lerp(this._particleOne.position, this._particleTwo.position, 0.5);
|
||||||
|
midPoint.setTo(tempMidPoint.x, tempMidPoint.y);
|
||||||
|
DistanceConstraint._polygon.position = midPoint;
|
||||||
|
DistanceConstraint._polygon.points[0] = this._particleOne.position.sub(DistanceConstraint._polygon.position);
|
||||||
|
DistanceConstraint._polygon.points[1] = this._particleTwo.position.sub(DistanceConstraint._polygon.position);
|
||||||
|
DistanceConstraint._polygon.recalculateCenterAndEdgeNormals();
|
||||||
|
};
|
||||||
|
DistanceConstraint.prototype.debugRender = function (batcher) {
|
||||||
|
batcher.drawLine(this._particleOne.position, this._particleTwo.position, new es.Color(67, 62, 54), 1);
|
||||||
|
};
|
||||||
|
DistanceConstraint._polygon = new es.Polygon([]);
|
||||||
|
return DistanceConstraint;
|
||||||
|
}(es.Constraint));
|
||||||
|
es.DistanceConstraint = DistanceConstraint;
|
||||||
|
})(es || (es = {}));
|
||||||
|
var es;
|
||||||
(function (es) {
|
(function (es) {
|
||||||
var AnimCurve = /** @class */ (function () {
|
var AnimCurve = /** @class */ (function () {
|
||||||
function AnimCurve(points) {
|
function AnimCurve(points) {
|
||||||
@@ -13591,12 +14055,13 @@ var es;
|
|||||||
var worldPosY = parentPosition.y + position.y;
|
var worldPosY = parentPosition.y + position.y;
|
||||||
var tempMat = void 0;
|
var tempMat = void 0;
|
||||||
// 考虑到原点,将参考点设置为世界参考
|
// 考虑到原点,将参考点设置为世界参考
|
||||||
var transformMatrix = es.Matrix2D.createTranslation(-worldPosX - origin.x, -worldPosY - origin.y);
|
var transformMatrix = new es.Matrix2D();
|
||||||
tempMat = es.Matrix2D.createScale(scale.x, scale.y);
|
es.Matrix2D.createTranslation(-worldPosX - origin.x, -worldPosY - origin.y, transformMatrix);
|
||||||
|
es.Matrix2D.createScale(scale.x, scale.y, tempMat);
|
||||||
transformMatrix = transformMatrix.multiply(tempMat);
|
transformMatrix = transformMatrix.multiply(tempMat);
|
||||||
tempMat = es.Matrix2D.createRotation(rotation);
|
es.Matrix2D.createRotation(rotation, tempMat);
|
||||||
transformMatrix = transformMatrix.multiply(tempMat);
|
transformMatrix = transformMatrix.multiply(tempMat);
|
||||||
tempMat = es.Matrix2D.createTranslation(worldPosX, worldPosY);
|
es.Matrix2D.createTranslation(worldPosX, worldPosY, tempMat);
|
||||||
transformMatrix = transformMatrix.multiply(tempMat);
|
transformMatrix = transformMatrix.multiply(tempMat);
|
||||||
// TODO: 我们可以把世界变换留在矩阵中,避免在世界空间中得到所有的四个角
|
// TODO: 我们可以把世界变换留在矩阵中,避免在世界空间中得到所有的四个角
|
||||||
var topLeft = new es.Vector2(worldPosX, worldPosY);
|
var topLeft = new es.Vector2(worldPosX, worldPosY);
|
||||||
|
|||||||
2
source/bin/framework.min.js
vendored
2
source/bin/framework.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -227,15 +227,11 @@ module es {
|
|||||||
* @param value
|
* @param value
|
||||||
*/
|
*/
|
||||||
public divide(value: Vector2): Vector2 {
|
public divide(value: Vector2): Vector2 {
|
||||||
this.x /= value.x;
|
return new Vector2(this.x / value.x, this.y / value.y);
|
||||||
this.y /= value.y;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public divideScaler(value: number): Vector2 {
|
public divideScaler(value: number): Vector2 {
|
||||||
this.x /= value;
|
return new Vector2(this.x / value, this.y / value);
|
||||||
this.y /= value;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ module es {
|
|||||||
this.isBox = isBox;
|
this.isBox = isBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public create(vertCount: number, radius: number) {
|
||||||
|
Polygon.buildSymmetricalPolygon(vertCount, radius);
|
||||||
|
}
|
||||||
|
|
||||||
public _edgeNormals: Vector2[];
|
public _edgeNormals: Vector2[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -99,10 +103,10 @@ module es {
|
|||||||
* @param radius
|
* @param radius
|
||||||
*/
|
*/
|
||||||
public static buildSymmetricalPolygon(vertCount: number, radius: number) {
|
public static buildSymmetricalPolygon(vertCount: number, radius: number) {
|
||||||
let verts = new Array(vertCount);
|
const verts = new Array(vertCount);
|
||||||
|
|
||||||
for (let i = 0; i < vertCount; i++) {
|
for (let i = 0; i < vertCount; i++) {
|
||||||
let a = 2 * Math.PI * (i / vertCount);
|
const a = 2 * Math.PI * (i / vertCount);
|
||||||
verts[i] = new Vector2(Math.cos(a) * radius, Math.sin(a) * radius);
|
verts[i] = new Vector2(Math.cos(a) * radius, Math.sin(a) * radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
9
source/src/Physics/Verlet/Composites/Ball.ts
Normal file
9
source/src/Physics/Verlet/Composites/Ball.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
///<reference path="./Composite.ts" />
|
||||||
|
module es {
|
||||||
|
export class Ball extends Composite {
|
||||||
|
constructor(position: Vector2, radius: number = 10) {
|
||||||
|
super();
|
||||||
|
this.addParticle(new Particle(position)).radius = radius;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
source/src/Physics/Verlet/Composites/Box.ts
Normal file
23
source/src/Physics/Verlet/Composites/Box.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
///<reference path="./Composite.ts" />
|
||||||
|
module es {
|
||||||
|
export class VerletBox extends es.Composite {
|
||||||
|
constructor(center: es.Vector2, width: number, height: number, borderStiffness: number = 0.2, diagonalStiffness: number = 0.5) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
const tl = this.addParticle(new Particle(center.add(new Vector2(-width / 2, -height / 2))));
|
||||||
|
const tr = this.addParticle(new Particle(center.add(new Vector2(width / 2, -height / 2))));
|
||||||
|
const br = this.addParticle(new Particle(center.add(new Vector2(width / 2, height / 2))));
|
||||||
|
const bl = this.addParticle(new Particle(center.add(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
88
source/src/Physics/Verlet/Composites/Composite.ts
Normal file
88
source/src/Physics/Verlet/Composites/Composite.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
module es {
|
||||||
|
export class Composite {
|
||||||
|
public friction: Vector2 = new Vector2(0.98, 1);
|
||||||
|
public drawParticles: boolean = true;
|
||||||
|
public drawConstraints: boolean = true;
|
||||||
|
public collidesWithLayers: number = Physics.allLayers;
|
||||||
|
public particles: Particle[] = [];
|
||||||
|
_constraints: Constraint[] = [];
|
||||||
|
|
||||||
|
public addParticle(particle: Particle): Particle {
|
||||||
|
this.particles.push(particle);
|
||||||
|
return particle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeParticle(particle: Particle) {
|
||||||
|
const index = this.particles.indexOf(particle);
|
||||||
|
this.particles.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeAll() {
|
||||||
|
this.particles.length = 0;
|
||||||
|
this._constraints.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public addConstraint<T extends Constraint>(constraint: T): T {
|
||||||
|
this._constraints.push(constraint);
|
||||||
|
constraint.composite = this;
|
||||||
|
return constraint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeConstraint(constraint: Constraint) {
|
||||||
|
const index = this._constraints.indexOf(constraint);
|
||||||
|
this._constraints.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public applyForce(force: Vector2) {
|
||||||
|
for (let j = 0; j < this.particles.length; j ++)
|
||||||
|
this.particles[j].applyForce(force);
|
||||||
|
}
|
||||||
|
|
||||||
|
public solveConstraints() {
|
||||||
|
for (let i = this._constraints.length - 1; i >= 0; i --)
|
||||||
|
this._constraints[i].solve();
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateParticles(deltaTimeSquared: number, gravity: Vector2) {
|
||||||
|
for (let j = 0; j < this.particles.length; j ++) {
|
||||||
|
const p = this.particles[j];
|
||||||
|
if (p.isPinned) {
|
||||||
|
p.position = p.pinnedPosition;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.applyForce(gravity.scale(p.mass));
|
||||||
|
|
||||||
|
const vel = p.position.sub(p.lastPosition).multiply(this.friction);
|
||||||
|
const nextPos = p.position.add(vel).add(p.acceleration.scale(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(batcher: IBatcher) {
|
||||||
|
if (this.drawConstraints) {
|
||||||
|
for (let i = 0; i < this._constraints.length; i ++)
|
||||||
|
this._constraints[i].debugRender(batcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.drawParticles) {
|
||||||
|
for (let i = 0; i < this.particles.length; i ++) {
|
||||||
|
if (this.particles[i].radius == 0)
|
||||||
|
batcher.drawPixel(this.particles[i].position, new Color(220, 52, 94), 4);
|
||||||
|
else
|
||||||
|
batcher.drawCircleLow(this.particles[i].position, this.particles[i].radius, new Color(220, 52, 94), 1, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
source/src/Physics/Verlet/Composites/LineSegments.ts
Normal file
20
source/src/Physics/Verlet/Composites/LineSegments.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
module es {
|
||||||
|
export class LineSegments extends Composite {
|
||||||
|
constructor(vertices: Vector2[], stiffness: number) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
for (let i = 0; i < vertices.length; i ++) {
|
||||||
|
const p = new Particle(vertices[i]);
|
||||||
|
this.addParticle(p);
|
||||||
|
|
||||||
|
if (i > 0)
|
||||||
|
this.addConstraint(new DistanceConstraint(this.particles[i], this.particles[i - 1], stiffness));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public pinParticleAtIndex(index: number): LineSegments {
|
||||||
|
this.particles[index].pin();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
source/src/Physics/Verlet/Constraints/AngleConstraint.ts
Normal file
47
source/src/Physics/Verlet/Constraints/AngleConstraint.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
///<reference path="./Constraint.ts" />
|
||||||
|
module es {
|
||||||
|
export class AngleConstraint extends Constraint {
|
||||||
|
public stiffness: number;
|
||||||
|
public angleInRadius: number;
|
||||||
|
|
||||||
|
_particleA: Particle;
|
||||||
|
_centerParticle: Particle;
|
||||||
|
_particleC: Particle;
|
||||||
|
|
||||||
|
constructor(a: Particle, center: Particle, c: Particle, stiffness: number) {
|
||||||
|
super();
|
||||||
|
this._particleA = a;
|
||||||
|
this._centerParticle = center;
|
||||||
|
this._particleC = c;
|
||||||
|
this.stiffness = stiffness;
|
||||||
|
|
||||||
|
this.collidesWithColliders = false;
|
||||||
|
|
||||||
|
this.angleInRadius = this.angleBetweenParticles();
|
||||||
|
}
|
||||||
|
|
||||||
|
angleBetweenParticles(): number {
|
||||||
|
const first = this._particleA.position.sub(this._centerParticle.position);
|
||||||
|
const second = this._particleC.position.sub(this._centerParticle.position);
|
||||||
|
|
||||||
|
return Math.atan2(first.x * second.y - first.y * second.x, first.x * second.x + first.y * second.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public solve() {
|
||||||
|
const angleBetween = this.angleBetweenParticles();
|
||||||
|
let diff = angleBetween - this.angleInRadius;
|
||||||
|
|
||||||
|
if (diff <= -Math.PI)
|
||||||
|
diff += 2 * Math.PI;
|
||||||
|
else if(diff >= Math.PI)
|
||||||
|
diff -= 2 * Math.PI;
|
||||||
|
|
||||||
|
diff *= this.stiffness;
|
||||||
|
|
||||||
|
this._particleA.position = MathHelper.rotateAround2(this._particleA.position, this._centerParticle.position, diff);
|
||||||
|
this._particleC.position = MathHelper.rotateAround2(this._particleC.position, this._centerParticle.position, -diff);
|
||||||
|
this._centerParticle.position = MathHelper.rotateAround2(this._centerParticle.position, this._particleA.position, diff);
|
||||||
|
this._centerParticle.position = MathHelper.rotateAround2(this._centerParticle.position, this._particleC.position, -diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
source/src/Physics/Verlet/Constraints/Constraint.ts
Normal file
16
source/src/Physics/Verlet/Constraints/Constraint.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
module es {
|
||||||
|
export abstract class Constraint {
|
||||||
|
public composite: Composite;
|
||||||
|
public collidesWithColliders: boolean = true;
|
||||||
|
|
||||||
|
public abstract solve(): void;
|
||||||
|
|
||||||
|
public handleCollisions(collidesWithLayers: number) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public debugRender(batcher: IBatcher) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
123
source/src/Physics/Verlet/Constraints/DistanceConstraint.ts
Normal file
123
source/src/Physics/Verlet/Constraints/DistanceConstraint.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
module es {
|
||||||
|
export class DistanceConstraint extends Constraint {
|
||||||
|
public stiffness: number;
|
||||||
|
public restingDistance: number;
|
||||||
|
public tearSensitivity = Number.POSITIVE_INFINITY;
|
||||||
|
public shouldApproximateCollisionsWithPoints: boolean;
|
||||||
|
public totalPointsToApproximateCollisionsWith = 5;
|
||||||
|
_particleOne: Particle;
|
||||||
|
_particleTwo: Particle;
|
||||||
|
static _polygon: Polygon = new Polygon([]);
|
||||||
|
|
||||||
|
constructor(first: Particle, second: Particle, stiffness: number, distance: number = -1) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
DistanceConstraint._polygon.create(2, 1);
|
||||||
|
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 static create(a: Particle, center: Particle, c: Particle, stiffness: number, angleInDegrees: number) {
|
||||||
|
const aToCenter = Vector2.distance(a.position, center.position);
|
||||||
|
const cToCenter = Vector2.distance(c.position, center.position);
|
||||||
|
const distance = Math.sqrt(aToCenter * aToCenter + cToCenter * cToCenter - (2 * aToCenter * cToCenter * Math.cos(angleInDegrees * MathHelper.Deg2Rad)));
|
||||||
|
|
||||||
|
return new DistanceConstraint(a, c, stiffness, distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setTearSensitivity(tearSensitivity: number) {
|
||||||
|
this.tearSensitivity = tearSensitivity;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setCollidesWithColliders(collidesWithColliders: boolean) {
|
||||||
|
this.collidesWithColliders = collidesWithColliders;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setShouldApproximateCollisionsWithPoints(shouldApproximateCollisionsWithPoints: boolean) {
|
||||||
|
this.shouldApproximateCollisionsWithPoints = shouldApproximateCollisionsWithPoints;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public solve(): void {
|
||||||
|
const diff = this._particleOne.position.sub(this._particleTwo.position);
|
||||||
|
const d = diff.magnitude();
|
||||||
|
|
||||||
|
const difference = (this.restingDistance - d) / d;
|
||||||
|
if (d / this.restingDistance > this.tearSensitivity) {
|
||||||
|
this.composite.removeConstraint(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const im1 = 1 / this._particleOne.mass;
|
||||||
|
const im2 = 1 / this._particleTwo.mass;
|
||||||
|
const scalarP1 = (im1 / (im1 + im2)) * this.stiffness;
|
||||||
|
const scalarP2 = this.stiffness - scalarP1;
|
||||||
|
|
||||||
|
this._particleOne.position = this._particleOne.position.add(diff.scale(scalarP1 * difference));
|
||||||
|
this._particleTwo.position = this._particleTwo.position.sub(diff.scale(scalarP2 * difference));
|
||||||
|
}
|
||||||
|
|
||||||
|
public handleCollisions(collidesWithLayers: number) {
|
||||||
|
if (this.shouldApproximateCollisionsWithPoints) {
|
||||||
|
this.approximateCollisionsWithPoints(collidesWithLayers);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minX = Math.min(this._particleOne.position.x, this._particleTwo.position.x);
|
||||||
|
const maxX = Math.max(this._particleOne.position.x, this._particleTwo.position.x);
|
||||||
|
const minY = Math.min(this._particleOne.position.y, this._particleTwo.position.y);
|
||||||
|
const 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(midPoint);
|
||||||
|
|
||||||
|
const colliders = Physics.boxcastBroadphase(DistanceConstraint._polygon.bounds, collidesWithLayers);
|
||||||
|
for (let i = 0; i < colliders.length; i ++) {
|
||||||
|
const collider = colliders[i];
|
||||||
|
const result = new CollisionResult();
|
||||||
|
if (DistanceConstraint._polygon.collidesWithShape(collider.shape, result)) {
|
||||||
|
this._particleOne.position = this._particleOne.position.sub(result.minimumTranslationVector);
|
||||||
|
this._particleTwo.position = this._particleTwo.position.sub(result.minimumTranslationVector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
approximateCollisionsWithPoints(collidesWithLayers: number) {
|
||||||
|
let pt: Vector2;
|
||||||
|
for (let j = 0; j < this.totalPointsToApproximateCollisionsWith - 1; j ++) {
|
||||||
|
pt = Vector2.lerp(this._particleOne.position, this._particleTwo.position, (j + 1) / this.totalPointsToApproximateCollisionsWith);
|
||||||
|
const collidedCount = Physics.overlapCircleAll(pt, 3, VerletWorld._colliders, collidesWithLayers);
|
||||||
|
for (let i = 0; i < collidedCount; i ++) {
|
||||||
|
const collider = VerletWorld._colliders[i];
|
||||||
|
const collisionResult = new CollisionResult();
|
||||||
|
if (collider.shape.pointCollidesWithShape(pt, collisionResult)) {
|
||||||
|
this._particleOne.position = this._particleOne.position.sub(collisionResult.minimumTranslationVector);
|
||||||
|
this._particleTwo.position = this._particleTwo.position.sub(collisionResult.minimumTranslationVector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
preparePolygonForCollisionChecks(midPoint: Vector2) {
|
||||||
|
const tempMidPoint = Vector2.lerp(this._particleOne.position, this._particleTwo.position, 0.5);
|
||||||
|
midPoint.setTo(tempMidPoint.x, tempMidPoint.y);
|
||||||
|
DistanceConstraint._polygon.position = midPoint;
|
||||||
|
DistanceConstraint._polygon.points[0] = this._particleOne.position.sub(DistanceConstraint._polygon.position);
|
||||||
|
DistanceConstraint._polygon.points[1] = this._particleTwo.position.sub(DistanceConstraint._polygon.position);
|
||||||
|
DistanceConstraint._polygon.recalculateCenterAndEdgeNormals();
|
||||||
|
}
|
||||||
|
|
||||||
|
public debugRender(batcher: IBatcher) {
|
||||||
|
batcher.drawLine(this._particleOne.position, this._particleTwo.position, new Color(67, 62, 54), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
source/src/Physics/Verlet/Particle.ts
Normal file
39
source/src/Physics/Verlet/Particle.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
module es {
|
||||||
|
export class Particle {
|
||||||
|
public position: Vector2;
|
||||||
|
public lastPosition: Vector2;
|
||||||
|
public mass = 1;
|
||||||
|
public radius: number;
|
||||||
|
public collidesWithColliders: boolean = true;
|
||||||
|
public isPinned: boolean;
|
||||||
|
public acceleration: Vector2;
|
||||||
|
public pinnedPosition: Vector2;
|
||||||
|
|
||||||
|
constructor(position: {x: number, y: number}) {
|
||||||
|
this.position = new Vector2(position.x, position.y);
|
||||||
|
this.lastPosition = new Vector2(position.x, position.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public applyForce(force: Vector2) {
|
||||||
|
this.acceleration = this.acceleration.add(force.divideScaler(this.mass));
|
||||||
|
}
|
||||||
|
|
||||||
|
public pin(): Particle {
|
||||||
|
this.isPinned = true;
|
||||||
|
this.pinnedPosition = this.position;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public pinTo(position: Vector2): Particle {
|
||||||
|
this.isPinned = true;
|
||||||
|
this.pinnedPosition = position;
|
||||||
|
this.position = this.pinnedPosition;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unpin(): Particle {
|
||||||
|
this.isPinned = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
160
source/src/Physics/Verlet/VerletWorld.ts
Normal file
160
source/src/Physics/Verlet/VerletWorld.ts
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
module es {
|
||||||
|
export class VerletWorld {
|
||||||
|
public gravity: Vector2 = new Vector2(0, 980);
|
||||||
|
public constraintIterations = 3;
|
||||||
|
public maximumStepIterations = 5;
|
||||||
|
public simulationBounds: Rectangle;
|
||||||
|
public allowDragging: boolean = true;
|
||||||
|
public selectionRadiusSquared = 20 * 20;
|
||||||
|
_draggedParticle: Particle;
|
||||||
|
_composites: Composite[] = [];
|
||||||
|
public static _colliders: Collider[] = [];
|
||||||
|
_tempCircle: Circle = new Circle(1);
|
||||||
|
|
||||||
|
_leftOverTime: number;
|
||||||
|
_fixedDeltaTime: number = 1 / 60;
|
||||||
|
_iterationSteps: number;
|
||||||
|
_fixedDeltaTimeSq: number;
|
||||||
|
|
||||||
|
constructor(simulationBounds: Rectangle = null) {
|
||||||
|
this.simulationBounds = simulationBounds;
|
||||||
|
this._fixedDeltaTime = Math.pow(this._fixedDeltaTime, 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 --) {
|
||||||
|
const 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 ++) {
|
||||||
|
const p = composite.particles[j];
|
||||||
|
|
||||||
|
if (this.simulationBounds) {
|
||||||
|
this.constrainParticleToBounds(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p.collidesWithColliders)
|
||||||
|
this.handleCollisions(p, composite.collidesWithLayers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constrainParticleToBounds(p: Particle) {
|
||||||
|
const tempPos = p.position;
|
||||||
|
const 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCollisions(p: Particle, collidesWithLayers: number) {
|
||||||
|
const collidedCount = Physics.overlapCircleAll(p.position, p.radius, VerletWorld._colliders, collidesWithLayers);
|
||||||
|
for (let i = 0; i < collidedCount; i++) {
|
||||||
|
const collider = VerletWorld._colliders[i];
|
||||||
|
if (collider.isTrigger)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const collisionResult = new CollisionResult();
|
||||||
|
|
||||||
|
if (p.radius < 2) {
|
||||||
|
if (collider.shape.pointCollidesWithShape(p.position, collisionResult)) {
|
||||||
|
p.position = p.position.sub(collisionResult.minimumTranslationVector);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._tempCircle.radius = p.radius;
|
||||||
|
this._tempCircle.position = p.position;
|
||||||
|
|
||||||
|
if (this._tempCircle.collidesWithShape(collider.shape, collisionResult)) {
|
||||||
|
p.position = p.position.sub(collisionResult.minimumTranslationVector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addComposite<T extends Composite>(composite: T): T {
|
||||||
|
this._composites.push(composite);
|
||||||
|
return composite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeComposite(composite: Composite) {
|
||||||
|
const index = this._composites.indexOf(composite);
|
||||||
|
this._composites.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDragging() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public getNearestParticle(position: Vector2) {
|
||||||
|
let nearestSquaredDistance = this.selectionRadiusSquared;
|
||||||
|
let particle: Particle = null;
|
||||||
|
|
||||||
|
for (let j = 0; j < this._composites.length; j++) {
|
||||||
|
const particles = this._composites[j].particles;
|
||||||
|
for (let i = 0; i < particles.length; i++) {
|
||||||
|
const p = particles[i];
|
||||||
|
const squaredDistanceToParticle = Vector2.sqrDistance(p.position, position);
|
||||||
|
if (squaredDistanceToParticle <= this.selectionRadiusSquared &&
|
||||||
|
(particle == null || squaredDistanceToParticle < nearestSquaredDistance)) {
|
||||||
|
particle = p;
|
||||||
|
nearestSquaredDistance = squaredDistanceToParticle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return particle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public debugRender(batcher: IBatcher) {
|
||||||
|
for (let i = 0; i < this._composites.length; i ++) {
|
||||||
|
this._composites[i].debugRender(batcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.allowDragging) {
|
||||||
|
if (this._draggedParticle != null) {
|
||||||
|
batcher.drawCircle(this._draggedParticle.position, 8, Color.White);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -345,12 +345,13 @@ module es {
|
|||||||
let tempMat: Matrix2D;
|
let tempMat: Matrix2D;
|
||||||
|
|
||||||
// 考虑到原点,将参考点设置为世界参考
|
// 考虑到原点,将参考点设置为世界参考
|
||||||
let transformMatrix = Matrix2D.createTranslation(-worldPosX - origin.x, -worldPosY - origin.y);
|
let transformMatrix = new Matrix2D();
|
||||||
tempMat = Matrix2D.createScale(scale.x, scale.y);
|
Matrix2D.createTranslation(-worldPosX - origin.x, -worldPosY - origin.y, transformMatrix);
|
||||||
|
Matrix2D.createScale(scale.x, scale.y, tempMat);
|
||||||
transformMatrix = transformMatrix.multiply(tempMat);
|
transformMatrix = transformMatrix.multiply(tempMat);
|
||||||
tempMat = Matrix2D.createRotation(rotation);
|
Matrix2D.createRotation(rotation, tempMat);
|
||||||
transformMatrix =transformMatrix.multiply(tempMat);
|
transformMatrix =transformMatrix.multiply(tempMat);
|
||||||
tempMat = Matrix2D.createTranslation(worldPosX, worldPosY);
|
Matrix2D.createTranslation(worldPosX, worldPosY, tempMat);
|
||||||
transformMatrix = transformMatrix.multiply(tempMat);
|
transformMatrix = transformMatrix.multiply(tempMat);
|
||||||
|
|
||||||
// TODO: 我们可以把世界变换留在矩阵中,避免在世界空间中得到所有的四个角
|
// TODO: 我们可以把世界变换留在矩阵中,避免在世界空间中得到所有的四个角
|
||||||
|
|||||||
Reference in New Issue
Block a user