Merge branch 'master' of https://github.com/esengine/ecs-framework into master

This commit is contained in:
yhh
2021-07-16 07:09:54 +08:00
86 changed files with 17614 additions and 1510 deletions
-3
View File
@@ -10,6 +10,3 @@
[submodule "extensions/behaviourTree-ai"]
path = extensions/behaviourTree-ai
url = https://github.com/esengine/BehaviourTree-ai
[submodule "extensions/ecs-tween"]
path = extensions/ecs-tween
url = https://github.com/esengine/ecs-tween
+4 -1
View File
@@ -5,7 +5,7 @@ ecs-framework 的目标是成为功能强大的框架。它为您构建游戏提
- AABB,圆和多边形碰撞/触发检测
- 高效的协程,可在多个帧或动画定时中分解大型任务(Core.startCoroutine
- 通过Astar和广度优先搜索提供寻路支持,以查找图块地图或您自己的自定义格式 ( 参见 https://github.com/esengine/ecs-astar )
- tween系统。任何number / Vector / 矩形/字段或属性都可以tween。 (参见 https://github.com/esengine/ecs-tween
- tween系统。任何number / Vector / 矩形/字段或属性都可以tween。
- 针对核心事件的优化的事件发射器(发射器类),您也可以将其添加到自己的任何类中
- 延迟和重复任务的调度程序(核心调度方法)
@@ -269,6 +269,9 @@ Debug类提供日志记录。 Insist类提供各种断言条件。 您可以在
#### [laya-demo](https://github.com/esengine/ecs-laya-demo)
#### [egret-demo](https://github.com/esengine/ecs-egret-demo)
### 渲染集成框架
#### [cocos-framework](https://github.com/esengine/cocos-framework)
## 扩展库
#### [基于ecs-framework开发的astar/BreadthFirst/Dijkstra/GOAP目标导向计划 路径寻找库](https://github.com/esengine/ecs-astar)
File diff suppressed because it is too large Load Diff
+1441 -138
View File
File diff suppressed because it is too large Load Diff
+4434 -674
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+5 -3
View File
@@ -43,6 +43,7 @@ module es {
Core.emitter.addObserver(CoreEvents.frameUpdated, this.update, this);
Core.registerGlobalManager(this._coroutineManager);
Core.registerGlobalManager(new TweenManager());
Core.registerGlobalManager(this._timerManager);
Core.entitySystemsEnabled = enableEntitySystems;
@@ -122,9 +123,10 @@ module es {
* @param type
*/
public static getGlobalManager<T extends es.GlobalManager>(type: new (...args) => T): T {
for (let i = 0; i < this._instance._globalManagers.length; i++) {
if (this._instance._globalManagers[i] instanceof type)
return this._instance._globalManagers[i] as T;
for (let i = 0, s = Core._instance._globalManagers.length; i < s; ++ i) {
let manager = Core._instance._globalManagers[i];
if (manager instanceof type)
return manager;
}
return null;
}
+3 -1
View File
@@ -80,9 +80,11 @@ module es {
*
* @param comp
*/
public onEntityTransformChanged(comp: transform.Component) {
public onEntityTransformChanged(comp: ComponentTransform) {
}
public debugRender(batcher: IBatcher) {}
/**
*
*/
@@ -54,7 +54,7 @@ module es {
/**
*
*/
public velocity: Vector2 = new Vector2();
public velocity: Vector2 = Vector2.zero;
/**
* 0
@@ -116,19 +116,30 @@ module es {
return this;
}
public setVelocity(velocity: Vector2): ArcadeRigidbody {
this.velocity = velocity;
return this;
}
/**
* 100000使使
* @param force
*/
public addImpulse(force: Vector2) {
if (!this.isImmovable) {
this.velocity = this.velocity.add(Vector2.multiplyScaler(force, 100000)
.multiplyScaler(this._inverseMass * Time.deltaTime * Time.deltaTime));
this.velocity.addEqual(force.scale(100000 * (this._inverseMass * (Time.deltaTime * Time.deltaTime))));
}
}
public onAddedToEntity() {
this._collider = this.entity.getComponent(es.Collider);
this._collider = null;
for (let i = 0; i < this.entity.components.buffer.length; i++) {
let component = this.entity.components.buffer[i];
if (component instanceof Collider) {
this._collider = component;
break;
}
}
Debug.warnIf(this._collider == null, "ArcadeRigidbody 没有 Collider。ArcadeRigidbody需要一个Collider!");
}
@@ -139,14 +150,17 @@ module es {
}
if (this.shouldUseGravity)
this.velocity = this.velocity.add(Vector2.multiplyScaler(Physics.gravity, Time.deltaTime));
this.velocity.addEqual(Physics.gravity.scale(Time.deltaTime));
this.entity.position = this.entity.position.add(this.velocity.scale(Time.deltaTime));
this.entity.transform.position = this.entity.transform.position.add(Vector2.multiplyScaler(this.velocity, Time.deltaTime));
let collisionResult = new CollisionResult();
// 捞取我们在新的位置上可能会碰撞到的任何东西
let neighbors = Physics.boxcastBroadphaseExcludingSelfNonRect(this._collider, this._collider.collidesWithLayers.value);
for (let neighbor of neighbors) {
let neighbors = Physics.boxcastBroadphaseExcludingSelf(this._collider, this._collider.bounds, this._collider.collidesWithLayers.value);
for (const neighbor of neighbors) {
if (!neighbor)
continue;
// 如果邻近的对撞机是同一个实体,则忽略它
if (neighbor.entity.equals(this.entity)) {
continue;
@@ -160,10 +174,9 @@ module es {
this.processCollision(neighborRigidbody, collisionResult.minimumTranslationVector);
} else {
// 没有ArcadeRigidbody,所以我们假设它是不动的,只移动我们自己的
this.entity.transform.position = this.entity.transform.position.subtract(collisionResult.minimumTranslationVector);
let relativeVelocity = this.velocity.clone();
this.calculateResponseVelocity(relativeVelocity, collisionResult.minimumTranslationVector, relativeVelocity);
this.velocity = this.velocity.add(relativeVelocity);
this.entity.position = this.entity.position.sub(collisionResult.minimumTranslationVector);
const relativeVelocity = this.calculateResponseVelocity(this.velocity, collisionResult.minimumTranslationVector);
this.velocity.addEqual(relativeVelocity);
}
}
}
@@ -176,12 +189,12 @@ module es {
*/
public processOverlap(other: ArcadeRigidbody, minimumTranslationVector: Vector2) {
if (this.isImmovable) {
other.entity.transform.position = other.entity.transform.position.add(minimumTranslationVector);
other.entity.position = other.entity.position.add(minimumTranslationVector);
} else if (other.isImmovable) {
this.entity.transform.position = this.entity.transform.position.subtract(minimumTranslationVector);
this.entity.position = this.entity.position.sub(minimumTranslationVector);
} else {
this.entity.transform.position = this.entity.transform.position.subtract(Vector2.multiplyScaler(minimumTranslationVector, 0.5));
other.entity.transform.position = other.entity.transform.position.add(Vector2.multiplyScaler(minimumTranslationVector, 0.5));
this.entity.position = this.entity.position.sub(minimumTranslationVector.scale(0.5));
other.entity.position = other.entity.position.add(minimumTranslationVector.scale(0.5));
}
}
@@ -194,17 +207,18 @@ module es {
// 我们计算两个相撞物体的响应。
// 计算的基础是沿碰撞表面法线反射的物体的相对速度。
// 然后,响应的一部分会根据质量加到每个物体上
let relativeVelocity = Vector2.subtract(this.velocity, other.velocity);
let relativeVelocity = this.velocity.sub(other.velocity);
this.calculateResponseVelocity(relativeVelocity, minimumTranslationVector, relativeVelocity);
relativeVelocity = this.calculateResponseVelocity(relativeVelocity, minimumTranslationVector);
// 现在,我们使用质量来线性缩放两个刚体上的响应
let totalinverseMass = this._inverseMass + other._inverseMass;
let ourResponseFraction = this._inverseMass / totalinverseMass;
let otherResponseFraction = other._inverseMass / totalinverseMass;
const totalinverseMass = this._inverseMass + other._inverseMass;
const ourResponseFraction = this._inverseMass / totalinverseMass;
const otherResponseFraction = other._inverseMass / totalinverseMass;
this.velocity = this.velocity.add(Vector2.multiplyScaler(relativeVelocity, ourResponseFraction));
other.velocity = other.velocity.subtract(Vector2.multiplyScaler(relativeVelocity, otherResponseFraction));
this.velocity = this.velocity.add(relativeVelocity.scale(ourResponseFraction));
other.velocity = other.velocity.sub(relativeVelocity.scale(otherResponseFraction));
}
/**
@@ -213,17 +227,16 @@ module es {
* @param minimumTranslationVector
* @param responseVelocity
*/
public calculateResponseVelocity(relativeVelocity: Vector2, minimumTranslationVector: Vector2, responseVelocity: Vector2 = new Vector2()) {
public calculateResponseVelocity(relativeVelocity: Vector2, minimumTranslationVector: Vector2) {
// 首先,我们得到反方向的归一化MTV:表面法线
let inverseMTV = Vector2.multiplyScaler(minimumTranslationVector, -1);
let normal = Vector2.normalize(inverseMTV);
let inverseMTV = minimumTranslationVector.scale(-1);
let normal = inverseMTV.normalize();
// 速度是沿碰撞法线和碰撞平面分解的。
// 弹性将影响沿法线的响应(法线速度分量),摩擦力将影响速度的切向分量(切向速度分量)
let n = Vector2.dot(relativeVelocity, normal);
let n = relativeVelocity.dot(normal);
let normalVelocityComponent = Vector2.multiplyScaler(normal, n);
let tangentialVelocityComponent = Vector2.subtract(relativeVelocity, normalVelocityComponent);
let normalVelocityComponent = normal.scale(n);
let tangentialVelocityComponent = relativeVelocity.sub(normalVelocityComponent);
if (n > 0)
normalVelocityComponent = Vector2.zero;
@@ -234,8 +247,10 @@ module es {
coefficientOfFriction = 1.01;
// 弹性影响速度的法向分量,摩擦力影响速度的切向分量
responseVelocity = Vector2.multiplyScaler(normalVelocityComponent, -(1 + this._elasticity))
.subtract(Vector2.multiplyScaler(tangentialVelocityComponent, coefficientOfFriction));
return normalVelocityComponent
.scale(1 + this._elasticity)
.sub(tangentialVelocityComponent.scale(coefficientOfFriction))
.scale(-1);
}
}
}
@@ -0,0 +1,587 @@
module es {
class CharacterRaycastOrigins {
public topLeft: Vector2;
public bottomRight: Vector2;
public bottomLeft: Vector2;
public constructor() {
this.topLeft = Vector2.zero;
this.bottomRight = Vector2.zero;
this.bottomLeft = Vector2.zero;
}
}
class CharacterCollisionState2D {
public right: boolean = false;
public left: boolean = false;
public above: boolean = false;
public below: boolean = false;
public becameGroundedThisFrame: boolean = false;
public wasGroundedLastFrame: boolean = false;
public movingDownSlope: boolean = false;
public slopeAngle: number = 0;
public hasCollision(): boolean {
return this.below || this.right || this.left || this.above;
}
public reset(): void {
this.right = this.left = false;
this.above = this.below = false;
this.becameGroundedThisFrame = this.movingDownSlope = false;
this.slopeAngle = 0;
}
public toString(): string {
return `[CharacterCollisionState2D] r: ${this.right}, l: ${this.left}, a: ${this.above}, b: ${this.below}, movingDownSlope: ${this.movingDownSlope}, angle: ${this.slopeAngle}, wasGroundedLastFrame: ${this.wasGroundedLastFrame}, becameGroundedThisFrame: ${this.becameGroundedThisFrame}`;
}
}
export class CharacterController implements ITriggerListener {
public onControllerCollidedEvent: ObservableT<RaycastHit>;
public onTriggerEnterEvent: ObservableT<Collider>;
public onTriggerExitEvent: ObservableT<Collider>;
/**
* true
*/
public ignoreOneWayPlatformsTime: number;
public supportSlopedOneWayPlatforms: boolean;
public ignoredColliders: Set<Collider> = new Set();
/**
* 线
* 使 0 线
*/
public get skinWidth() {
return this._skinWidth;
}
public set skinWidth(value: number) {
this._skinWidth = value;
this.recalculateDistanceBetweenRays();
}
/**
* CC2D
*/
public slopeLimit: number = 30;
/**
*
*/
public jumpingThreshold: number = -7;
/**
* 线 = =
*/
public slopeSpeedMultiplier: AnimCurve;
public totalHorizontalRays: number = 5;
public totalVerticalRays: number = 3;
public collisionState: CharacterCollisionState2D = new CharacterCollisionState2D();
public velocity: Vector2 = new Vector2(0, 0);
public get isGrounded(): boolean {
return this.collisionState.below;
}
public get raycastHitsThisFrame(): RaycastHit[] {
return this._raycastHitsThisFrame;
}
public constructor(
player: Entity,
skinWidth?: number,
platformMask: number = -1,
onewayPlatformMask: number = -1,
triggerMask: number = -1
) {
this.onTriggerEnterEvent = new ObservableT();
this.onTriggerExitEvent = new ObservableT();
this.onControllerCollidedEvent = new ObservableT();
this.platformMask = platformMask;
this.oneWayPlatformMask = onewayPlatformMask;
this.triggerMask = triggerMask;
// 将我们的单向平台添加到我们的普通平台掩码中,以便我们可以从上方降落
this.platformMask |= this.oneWayPlatformMask;
this._player = player;
let collider = null;
for (let i = 0; i < this._player.components.buffer.length; i++) {
let component = this._player.components.buffer[i];
if (component instanceof Collider) {
collider = component;
break;
}
}
collider.isTrigger = false;
if (collider instanceof BoxCollider) {
this._collider = collider as BoxCollider;
} else {
throw new Error('player collider must be box');
}
// 在这里,我们触发了具有主体的 setter 的属性
this.skinWidth = skinWidth || collider.width * 0.05;
this._slopeLimitTangent = Math.tan(75 * MathHelper.Deg2Rad);
this._triggerHelper = new ColliderTriggerHelper(this._player);
// 我们想设置我们的 CC2D 忽略所有碰撞层,除了我们的 triggerMask
for (let i = 0; i < 32; i++) {
// 查看我们的 triggerMask 是否包含此层,如果不包含则忽略它
if ((this.triggerMask & (1 << i)) === 0) {
Flags.unsetFlag(this._collider.collidesWithLayers, i);
}
}
}
public onTriggerEnter(other: Collider, local: Collider): void {
this.onTriggerEnterEvent.notify(other);
}
public onTriggerExit(other: Collider, local: Collider): void {
this.onTriggerExitEvent.notify(other);
}
/**
* + deltaMovement
* @param deltaMovement
* @param deltaTime
*/
public move(deltaMovement: Vector2, deltaTime: number): void {
this.collisionState.wasGroundedLastFrame = this.collisionState.below;
this.collisionState.reset();
this._raycastHitsThisFrame = [];
this._isGoingUpSlope = false;
this.primeRaycastOrigins();
if (deltaMovement.y > 0 && this.collisionState.wasGroundedLastFrame) {
deltaMovement = this.handleVerticalSlope(deltaMovement);
}
if (deltaMovement.x !== 0) {
deltaMovement = this.moveHorizontally(deltaMovement);
}
if (deltaMovement.y !== 0) {
deltaMovement = this.moveVertically(deltaMovement);
}
this._player.setPosition(
this._player.position.x + deltaMovement.x,
this._player.position.y + deltaMovement.y
);
if (deltaTime > 0) {
this.velocity.x = deltaMovement.x / deltaTime;
this.velocity.y = deltaMovement.y / deltaTime;
}
if (
!this.collisionState.wasGroundedLastFrame &&
this.collisionState.below
) {
this.collisionState.becameGroundedThisFrame = true;
}
if (this._isGoingUpSlope) {
this.velocity.y = 0;
}
if (!this._isWarpingToGround) {
this._triggerHelper.update();
}
for (let i = 0; i < this._raycastHitsThisFrame.length; i++) {
this.onControllerCollidedEvent.notify(this._raycastHitsThisFrame[i]);
}
if (this.ignoreOneWayPlatformsTime > 0) {
this.ignoreOneWayPlatformsTime -= deltaTime;
}
}
/**
*
* @param maxDistance
*/
public warpToGrounded(maxDistance: number = 1000): void {
this.ignoreOneWayPlatformsTime = 0;
this._isWarpingToGround = true;
let delta = 0;
do {
delta += 1;
this.move(new Vector2(0, 1), 0.02);
if (delta > maxDistance) {
break;
}
} while (!this.isGrounded);
this._isWarpingToGround = false;
}
/**
* BoxCollider2D
* 线
* skinWidth setter
*/
public recalculateDistanceBetweenRays(): void {
const colliderUsableHeight =
this._collider.height * Math.abs(this._player.scale.y) -
2 * this._skinWidth;
this._verticalDistanceBetweenRays =
colliderUsableHeight / (this.totalHorizontalRays - 1);
const colliderUsableWidth =
this._collider.width * Math.abs(this._player.scale.x) -
2 * this._skinWidth;
this._horizontalDistanceBetweenRays =
colliderUsableWidth / (this.totalVerticalRays - 1);
}
/**
* raycastOrigins skinWidth
* 线线
*/
private primeRaycastOrigins(): void {
const rect = this._collider.bounds;
this._raycastOrigins.topLeft = new Vector2(
rect.x + this._skinWidth,
rect.y + this._skinWidth
);
this._raycastOrigins.bottomRight = new Vector2(
rect.right - this._skinWidth,
rect.bottom - this._skinWidth
);
this._raycastOrigins.bottomLeft = new Vector2(
rect.x + this._skinWidth,
rect.bottom - this._skinWidth
);
}
/**
* 使
* 线skinWidth线线
* 线 skinWidth deltaMovement skinWidth
* @param deltaMovement
* @returns
*/
private moveHorizontally(deltaMovement: Vector2): Vector2 {
const isGoingRight = deltaMovement.x > 0;
let rayDistance =
Math.abs(deltaMovement.x) +
this._skinWidth * this.rayOriginSkinMutiplier;
const rayDirection: Vector2 = isGoingRight ? Vector2.right : Vector2.left;
const initialRayOriginY = this._raycastOrigins.bottomLeft.y;
const initialRayOriginX = isGoingRight
? this._raycastOrigins.bottomRight.x -
this._skinWidth * (this.rayOriginSkinMutiplier - 1)
: this._raycastOrigins.bottomLeft.x +
this._skinWidth * (this.rayOriginSkinMutiplier - 1);
for (let i = 0; i < this.totalHorizontalRays; i++) {
const ray = new Vector2(
initialRayOriginX,
initialRayOriginY - i * this._verticalDistanceBetweenRays
);
// 如果我们接地,我们将只在第一条射线(底部)上包含 oneWayPlatforms。
// 允许我们走上倾斜的 oneWayPlatforms
if (
i === 0 &&
this.supportSlopedOneWayPlatforms &&
this.collisionState.wasGroundedLastFrame
) {
this._raycastHit = Physics.linecast(
ray,
ray.add(rayDirection.scaleEqual(rayDistance)),
this.platformMask,
this.ignoredColliders
);
} else {
this._raycastHit = Physics.linecast(
ray,
ray.add(rayDirection.scaleEqual(rayDistance)),
this.platformMask & ~this.oneWayPlatformMask,
this.ignoredColliders
);
}
if (this._raycastHit.collider) {
if (
i === 0 &&
this.handleHorizontalSlope(
deltaMovement,
Vector2.unsignedAngle(this._raycastHit.normal, Vector2.up)
)
) {
this._raycastHitsThisFrame.push(this._raycastHit);
break;
}
deltaMovement.x = this._raycastHit.point.x - ray.x;
rayDistance = Math.abs(deltaMovement.x);
if (isGoingRight) {
deltaMovement.x -= this._skinWidth * this.rayOriginSkinMutiplier;
this.collisionState.right = true;
} else {
deltaMovement.x += this._skinWidth * this.rayOriginSkinMutiplier;
this.collisionState.left = true;
}
this._raycastHitsThisFrame.push(this._raycastHit);
if (
rayDistance <
this._skinWidth * this.rayOriginSkinMutiplier +
this.kSkinWidthFloatFudgeFactor
) {
break;
}
}
}
return deltaMovement;
}
private moveVertically(deltaMovement: Vector2): Vector2 {
const isGoingUp = deltaMovement.y < 0;
let rayDistance =
Math.abs(deltaMovement.y) +
this._skinWidth * this.rayOriginSkinMutiplier;
const rayDirection = isGoingUp ? Vector2.up : Vector2.down;
let initialRayOriginX = this._raycastOrigins.topLeft.x;
const initialRayOriginY = isGoingUp
? this._raycastOrigins.topLeft.y +
this._skinWidth * (this.rayOriginSkinMutiplier - 1)
: this._raycastOrigins.bottomLeft.y -
this._skinWidth * (this.rayOriginSkinMutiplier - 1);
initialRayOriginX += deltaMovement.x;
let mask = this.platformMask;
if (isGoingUp || this.ignoreOneWayPlatformsTime > 0) {
mask &= ~this.oneWayPlatformMask;
}
for (let i = 0; i < this.totalVerticalRays; i++) {
const rayStart = new Vector2(
initialRayOriginX + i * this._horizontalDistanceBetweenRays,
initialRayOriginY
);
this._raycastHit = Physics.linecast(
rayStart,
rayStart.add(rayDirection.scaleEqual(rayDistance)),
mask,
this.ignoredColliders
);
if (this._raycastHit.collider) {
deltaMovement.y = this._raycastHit.point.y - rayStart.y;
rayDistance = Math.abs(deltaMovement.y);
if (isGoingUp) {
deltaMovement.y += this._skinWidth * this.rayOriginSkinMutiplier;
this.collisionState.above = true;
} else {
deltaMovement.y -= this._skinWidth * this.rayOriginSkinMutiplier;
this.collisionState.below = true;
}
this._raycastHitsThisFrame.push(this._raycastHit);
if (!isGoingUp && deltaMovement.y < -0.00001) {
this._isGoingUpSlope = true;
}
if (
rayDistance <
this._skinWidth * this.rayOriginSkinMutiplier +
this.kSkinWidthFloatFudgeFactor
) {
break;
}
}
}
return deltaMovement;
}
/**
* BoxCollider2D
* deltaMovement 便slopeSpeedModifier
* @param deltaMovement
* @returns
*/
private handleVerticalSlope(deltaMovement: Vector2): Vector2 {
const centerOfCollider =
(this._raycastOrigins.bottomLeft.x +
this._raycastOrigins.bottomRight.x) *
0.5;
const rayDirection = Vector2.down;
const slopeCheckRayDistance =
this._slopeLimitTangent *
(this._raycastOrigins.bottomRight.x - centerOfCollider);
const slopeRay = new Vector2(
centerOfCollider,
this._raycastOrigins.bottomLeft.y
);
this._raycastHit = Physics.linecast(
slopeRay,
slopeRay.add(rayDirection.scaleEqual(slopeCheckRayDistance)),
this.platformMask,
this.ignoredColliders
);
if (this._raycastHit.collider) {
const angle = Vector2.unsignedAngle(this._raycastHit.normal, Vector2.up);
if (angle === 0) {
return deltaMovement;
}
const isMovingDownSlope =
Math.sign(this._raycastHit.normal.x) === Math.sign(deltaMovement.x);
if (isMovingDownSlope) {
const slopeModifier = this.slopeSpeedMultiplier
? this.slopeSpeedMultiplier.lerp(-angle)
: 1;
deltaMovement.y +=
this._raycastHit.point.y - slopeRay.y - this.skinWidth;
deltaMovement.x *= slopeModifier;
this.collisionState.movingDownSlope = true;
this.collisionState.slopeAngle = angle;
}
}
return deltaMovement;
}
/**
* deltaMovement
* @param deltaMovement
* @param angle
* @returns
*/
private handleHorizontalSlope(
deltaMovement: Vector2,
angle: number
): boolean {
if (Math.round(angle) === 90) {
return false;
}
if (angle < this.slopeLimit) {
if (deltaMovement.y > this.jumpingThreshold) {
const slopeModifier = this.slopeSpeedMultiplier
? this.slopeSpeedMultiplier.lerp(angle)
: 1;
deltaMovement.x *= slopeModifier;
deltaMovement.y = Math.abs(
Math.tan(angle * MathHelper.Deg2Rad) * deltaMovement.x
);
const isGoingRight = deltaMovement.x > 0;
const ray = isGoingRight
? this._raycastOrigins.bottomRight
: this._raycastOrigins.bottomLeft;
let raycastHit = null;
if (
this.supportSlopedOneWayPlatforms &&
this.collisionState.wasGroundedLastFrame
) {
raycastHit = Physics.linecast(
ray,
ray.add(deltaMovement),
this.platformMask,
this.ignoredColliders
);
} else {
raycastHit = Physics.linecast(
ray,
ray.add(deltaMovement),
this.platformMask & ~this.oneWayPlatformMask,
this.ignoredColliders
);
}
if (raycastHit.collider) {
deltaMovement.x = raycastHit.point.x - ray.x;
deltaMovement.y = raycastHit.point.y - ray.y;
if (isGoingRight) {
deltaMovement.x -= this._skinWidth;
} else {
deltaMovement.x += this._skinWidth;
}
}
this._isGoingUpSlope = true;
this.collisionState.below = true;
}
} else {
deltaMovement.x = 0;
}
return true;
}
private _player: Entity;
private _collider: BoxCollider;
private _skinWidth: number = 0.02;
private _triggerHelper: ColliderTriggerHelper;
/**
* 线
* 使 75 线
*/
private _slopeLimitTangent: number;
private readonly kSkinWidthFloatFudgeFactor: number = 0.001;
/**
* 线TRTLBRBL
*/
private _raycastOrigins: CharacterRaycastOrigins = new CharacterRaycastOrigins();
/**
* 线
*/
private _raycastHit: RaycastHit = new RaycastHit();
/**
* 线
* 便
*/
private _raycastHitsThisFrame: RaycastHit[];
// 水平/垂直移动数据
private _verticalDistanceBetweenRays: number;
private _horizontalDistanceBetweenRays: number;
/**
* 使 delta.y
*
*/
private _isGoingUpSlope: boolean = false;
private _isWarpingToGround: boolean = true;
private platformMask: number = -1;
private triggerMask: number = -1;
private oneWayPlatformMask: number = -1;
private readonly rayOriginSkinMutiplier = 4;
}
}
@@ -8,10 +8,15 @@ module es {
* @param width
* @param height
*/
constructor(x: number, y: number, width: number, height: number) {
constructor(x: number = 0, y: number = 0, width: number = 1, height: number = 1) {
super();
this._localOffset = new Vector2(x + width / 2, y + height / 2);
if (width == 1 && height == 1) {
this._colliderRequiresAutoSizing = true;
} else {
this._localOffset = new Vector2(x + width / 2, y + height / 2);
}
this.shape = new Box(width, height);
}
@@ -61,7 +66,7 @@ module es {
// 更新框,改变边界,如果我们需要更新物理系统中的边界
box.updateBox(width, box.height);
this._isPositionDirty = true;
if (this.entity && this._isParentEntityAddedToScene)
if (this.entity != null && this._isParentEntityAddedToScene)
Physics.updateCollider(this);
}
@@ -84,6 +89,18 @@ module es {
}
}
public debugRender(batcher: IBatcher) {
let poly = this.shape as Polygon;
batcher.drawHollowRect(this.bounds.x, this.bounds.y, this.bounds.width, this.bounds.height, new Color(76, 76, 76, 76), 2);
batcher.end();
batcher.drawPolygon(this.shape.position, poly.points, new Color(139, 0, 0, 255), true, 2);
batcher.end();
batcher.drawPixel(this.entity.position, new Color(255, 255, 0), 4);
batcher.end();
batcher.drawPixel(es.Vector2.add(this.transform.position, this.shape.center), new Color(255, 0, 0), 2);
batcher.end();
}
public toString() {
return `[BoxCollider: bounds: ${this.bounds}]`;
}
@@ -7,10 +7,13 @@ module es {
*
* @param radius
*/
constructor(radius: number) {
constructor(radius: number = 1) {
super();
this.shape = new Circle(radius);
if (radius == 1) {
this._colliderRequiresAutoSizing = true;
}
}
public get radius(): number {
@@ -40,6 +43,17 @@ module es {
return this;
}
public debugRender(batcher: IBatcher) {
batcher.drawHollowRect(this.bounds.x, this.bounds.y, this.bounds.width, this.bounds.height, new Color(76, 76, 76, 76), 2);
batcher.end();
batcher.drawCircle(this.shape.position, this.radius, new Color(139, 0, 0), 2);
batcher.end();
batcher.drawPixel(this.entity.transform.position, new Color(255, 255, 0), 4);
batcher.end();
batcher.drawPixel(this.shape.position, new Color(255, 0, 0), 2);
batcher.end();
}
public toString() {
return `[CircleCollider: bounds: ${this.bounds}, radius: ${(this.shape as Circle).radius}]`
}
@@ -1,5 +1,7 @@
module es {
export class Collider extends Component {
export abstract class Collider extends Component {
public static readonly lateSortOrder = 999;
public castSortOrder: number = 0;
/**
*
*/
@@ -94,8 +96,8 @@ module es {
public setLocalOffset(offset: Vector2): Collider {
if (!this._localOffset.equals(offset)) {
this.unregisterColliderWithPhysicsSystem();
this._localOffset = offset;
this._localOffsetLength = this._localOffset.length();
this._localOffset.setTo(offset.x, offset.y);
this._localOffsetLength = this._localOffset.magnitude();
this._isPositionDirty = true;
this.registerColliderWithPhysicsSystem();
}
@@ -114,6 +116,33 @@ module es {
}
public onAddedToEntity() {
if (this._colliderRequiresAutoSizing) {
let renderable = null;
for (let i = 0; i < this.entity.components.buffer.length; i ++) {
let component = this.entity.components.buffer[i];
if (component instanceof RenderableComponent){
renderable = component;
break;
}
}
if (renderable != null) {
let renderableBounds = renderable.bounds.clone();
let width = renderableBounds.width / this.entity.transform.scale.x;
let height = renderableBounds.height / this.entity.transform.scale.y;
if (this instanceof CircleCollider) {
this.radius = Math.max(width, height) * 0.5;
this.localOffset = renderableBounds.center.sub(this.entity.transform.position);
} else if (this instanceof BoxCollider) {
this.width = width;
this.height = height;
this.localOffset = renderableBounds.center.sub(this.entity.transform.position);
}
}
}
this._isParentEntityAddedToScene = true;
this.registerColliderWithPhysicsSystem();
}
@@ -123,15 +152,15 @@ module es {
this._isParentEntityAddedToScene = false;
}
public onEntityTransformChanged(comp: transform.Component) {
public onEntityTransformChanged(comp: ComponentTransform) {
switch (comp) {
case transform.Component.position:
case ComponentTransform.position:
this._isPositionDirty = true;
break;
case transform.Component.scale:
case ComponentTransform.scale:
this._isPositionDirty = true;
break;
case transform.Component.rotation:
case ComponentTransform.rotation:
this._isRotationDirty = true;
break;
}
@@ -186,10 +215,10 @@ module es {
*/
public collidesWith(collider: Collider, motion: Vector2, result: CollisionResult = new CollisionResult()): boolean {
// 改变形状的位置,使它在移动后的位置,这样我们可以检查重叠
let oldPosition = this.entity.position.clone();
this.entity.position = Vector2.add(this.entity.position, motion);
const oldPosition = this.entity.position;
this.entity.position = this.entity.position.add(motion);
let didCollide = this.shape.collidesWithShape(collider.shape, result);
const didCollide = this.shape.collidesWithShape(collider.shape, result);
if (didCollide)
result.collider = collider;
@@ -210,6 +239,7 @@ module es {
return true;
}
result.collider = null;
return false;
}
@@ -236,14 +266,14 @@ module es {
continue;
if (this.collidesWithNonMotion(neighbor, result)) {
motion = Vector2.subtract(motion, result.minimumTranslationVector);
this.shape.position = Vector2.subtract(this.shape.position, result.minimumTranslationVector);
motion = motion.sub(result.minimumTranslationVector);
this.shape.position = this.shape.position.sub(result.minimumTranslationVector);
didCollide = true;
}
}
// 将形状位置返回到检查之前的位置
this.shape.position = oldPosition;
this.shape.position = oldPosition.clone();
return didCollide;
}
@@ -13,10 +13,9 @@ module es {
// 第一点和最后一点决不能相同。我们想要一个开放的多边形
let isPolygonClosed = points[0] == points[points.length - 1];
let linqPoints = new es.List(points);
// 最后一个移除
if (isPolygonClosed)
linqPoints.remove(linqPoints.last());
points = points.slice(0, points.length - 1);
let center = Polygon.findPolygonCenter(points);
this.setLocalOffset(center);
+23 -7
View File
@@ -19,12 +19,26 @@ module es {
* @param collisionResult
*/
public calculateMovement(motion: Vector2, collisionResult: CollisionResult): boolean {
if (this.entity.getComponent(Collider) == null || this._triggerHelper == null) {
let collider = null;
for (let i = 0; i < this.entity.components.buffer.length; i++) {
let component = this.entity.components.buffer[i];
if (component instanceof Collider) {
collider = component;
break;
}
}
if (collider == null || this._triggerHelper == null) {
return false;
}
// 移动所有的非触发碰撞器并获得最近的碰撞
let colliders: Collider[] = this.entity.getComponents(Collider);
let colliders: Collider[] = [];
for (let i = 0; i < this.entity.components.buffer.length; i ++) {
let component = this.entity.components.buffer[i];
if (component instanceof Collider) {
colliders.push(component);
}
}
for (let i = 0; i < colliders.length; i++) {
let collider = colliders[i];
@@ -38,8 +52,7 @@ module es {
bounds.y += motion.y;
let neighbors = Physics.boxcastBroadphaseExcludingSelf(collider, bounds, collider.collidesWithLayers.value);
neighbors.forEach(value => {
let neighbor = value;
for (let neighbor of neighbors) {
// 不检测触发器
if (neighbor.isTrigger)
return;
@@ -47,14 +60,17 @@ module es {
let _internalcollisionResult: CollisionResult = new CollisionResult();
if (collider.collidesWith(neighbor, motion, _internalcollisionResult)) {
// 如果碰撞 则退回之前的移动量
motion.subtract(_internalcollisionResult.minimumTranslationVector);
motion.sub(_internalcollisionResult.minimumTranslationVector);
// 如果我们碰到多个对象,为了简单起见,只取第一个。
if (_internalcollisionResult.collider != null) {
collisionResult = _internalcollisionResult;
collisionResult.collider = _internalcollisionResult.collider;
collisionResult.minimumTranslationVector = _internalcollisionResult.minimumTranslationVector;
collisionResult.normal = _internalcollisionResult.normal;
collisionResult.point = _internalcollisionResult.point;
}
}
});
}
}
ListPool.free(colliders);
@@ -8,7 +8,15 @@ module es {
private _collider: Collider;
public onAddedToEntity() {
this._collider = this.entity.getComponent(Collider);
let collider = null;
for (let i = 0; i < this.entity.components.buffer.length; i++) {
let component = this.entity.components.buffer[i];
if (component instanceof Collider) {
collider = component;
break;
}
}
this._collider = collider;
Debug.warnIf(this._collider == null, "ProjectileMover没有Collider。ProjectilMover需要一个Collider!");
}
@@ -0,0 +1,9 @@
module es {
export interface IRenderable {
enabled: boolean;
renderLayer: number;
isVisibleFromCamera(camera: ICamera): boolean;
render(batcher: IBatcher, camera: ICamera): void;
debugRender(batcher: IBatcher): void;
}
}
@@ -0,0 +1,133 @@
module es {
export abstract class RenderableComponent extends es.Component implements IRenderable {
public getwidth() {
return this.bounds.width;
}
public getheight() {
return this.bounds.height;
}
protected _bounds: es.Rectangle = new es.Rectangle();
public getbounds(): es.Rectangle {
if (this._areBoundsDirty) {
this._bounds.calculateBounds(this.entity.transform.position, this._localOffset, new es.Vector2(this.getwidth() / 2, this.getheight() / 2),
this.entity.transform.scale, this.entity.transform.rotation, this.getwidth(), this.getheight());
this._areBoundsDirty = false;
}
return this._bounds;
}
public get bounds() {
return this.getbounds();
}
protected _areBoundsDirty: boolean = true;
public color: Color = Color.White;
public get renderLayer() {
return this._renderLayer;
}
public set renderLayer(value: number) {
this.setRenderLayer(value);
}
protected _renderLayer: number = 0;
public onEntityTransformChanged(comp: ComponentTransform) {
this._areBoundsDirty = true;
}
public get localOffset() {
return this._localOffset;
}
public set localOffset(value: es.Vector2) {
this.setLocalOffset(value);
}
public setLocalOffset(offset: es.Vector2) {
if (!this._localOffset.equals(offset)) {
this._localOffset = offset;
this._areBoundsDirty = true;
}
return this;
}
public get isVisible() {
return this._isVisible;
}
public set isVisible(value: boolean) {
if (this._isVisible != value) {
this._isVisible = value;
if (this._isVisible) {
this.onBecameVisible();
} else {
this.onBecameInvisible();
}
}
}
public debugRenderEnabled: boolean = true;
protected _isVisible: boolean = false;
protected _localOffset: es.Vector2 = new es.Vector2();
public abstract render(batcher: IBatcher, camera: ICamera): void;
protected onBecameVisible() {
}
protected onBecameInvisible() {
}
public setRenderLayer(renderLayer: number): RenderableComponent {
if (renderLayer != this._renderLayer) {
let oldRenderLayer = this._renderLayer;
this._renderLayer = renderLayer;
if (this.entity != null && this.entity.scene != null)
es.Core.scene.renderableComponents.updateRenderableRenderLayer(this, oldRenderLayer, this._renderLayer);
}
return this;
}
public isVisibleFromCamera(cam: ICamera): boolean {
this.isVisible = cam.bounds.intersects(this.bounds);
return this.isVisible;
}
public debugRender(batcher: IBatcher) {
if (!this.debugRenderEnabled)
return;
let collider = null;
for (let i = 0; i < this.entity.components.buffer.length; i++) {
let component = this.entity.components.buffer[i];
if (component instanceof Collider) {
collider = component;
break;
}
}
if (collider == null) {
batcher.drawHollowRect(this.bounds.x, this.bounds.y, this.bounds.width, this.bounds.height, new Color(255, 255, 0));
batcher.end();
}
batcher.drawPixel(es.Vector2.add(this.entity.transform.position, this._localOffset), new Color(153, 50, 204), 4);
batcher.end();
}
public tweenColorTo(to: Color, duration: number) {
const tween = Pool.obtain(RenderableColorTween);
tween.setTarget(this);
tween.initialize(tween, to, duration);
return tween;
}
}
}
+4
View File
@@ -8,5 +8,9 @@ module es {
*
*/
frameUpdated,
/**
*
*/
renderChanged,
}
}
+68 -3
View File
@@ -192,7 +192,7 @@ module es {
return this.transform.worldToLocalTransform;
}
public onTransformChanged(comp: transform.Component) {
public onTransformChanged(comp: ComponentTransform) {
// 通知我们的子项改变了位置
this.components.onEntityTransformChanged(comp);
}
@@ -245,7 +245,7 @@ module es {
if (scale instanceof Vector2) {
this.transform.setScale(scale);
} else {
this.transform.setScale(new Vector2(scale));
this.transform.setScale(new Vector2(scale, scale));
}
return this;
@@ -257,7 +257,7 @@ module es {
if (scale instanceof Vector2) {
this.transform.setLocalScale(scale);
} else {
this.transform.setLocalScale(new Vector2(scale));
this.transform.setLocalScale(new Vector2(scale, scale));
}
return this;
@@ -376,6 +376,11 @@ module es {
this.components.update();
}
public debugRender(batcher: IBatcher) {
if (!batcher) return;
this.components.debugRender(batcher);
}
/**
*
* @param componentType
@@ -486,6 +491,66 @@ module es {
}
}
public tweenPositionTo(to: Vector2, duration: number = 0.3): ITween<Vector2> {
const tween = Pool.obtain(TransformVector2Tween);
tween.setTargetAndType(this.transform, TransformTargetType.position);
tween.initialize(tween, to, duration);
return tween;
}
public tweenLocalPositionTo(to: Vector2, duration = 0.3): ITween<Vector2> {
const tween = Pool.obtain(TransformVector2Tween);
tween.setTargetAndType(this.transform, TransformTargetType.localPosition);
tween.initialize(tween, to, duration);
return tween;
}
public tweenScaleTo(to: Vector2, duration?: number);
public tweenScaleTo(to: number, duration?: number);
public tweenScaleTo(to: Vector2 | number, duration: number = 0.3) {
if (typeof (to) == 'number') {
return this.tweenScaleTo(new Vector2(to, to), duration);
}
const tween = Pool.obtain(TransformVector2Tween);
tween.setTargetAndType(this.transform, TransformTargetType.scale);
tween.initialize(tween, to, duration);
return tween;
}
public tweenLocalScaleTo(to: Vector2, duration?);
public tweenLocalScaleTo(to: number, duration?);
public tweenLocalScaleTo(to: Vector2 | number, duration = 0.3) {
if (typeof (to) == 'number') {
return this.tweenLocalScaleTo(new Vector2(to, to), duration);
}
const tween = Pool.obtain(TransformVector2Tween);
tween.setTargetAndType(this.transform, TransformTargetType.localScale);
tween.initialize(tween, to, duration);
return tween;
}
public tweenRotationDegreesTo(to: number, duration = 0.3) {
const tween = Pool.obtain(TransformVector2Tween);
tween.setTargetAndType(this.transform, TransformTargetType.rotationDegrees);
tween.initialize(tween, new Vector2(to, to), duration);
return tween;
}
public tweenLocalRotationDegreesTo(to: number, duration = 0.3) {
const tween = Pool.obtain(TransformVector2Tween);
tween.setTargetAndType(this.transform, TransformTargetType.localRotationDegrees);
tween.initialize(tween, new Vector2(to, to), duration);
return tween;
}
public compareTo(other: Entity): number {
let compare = this._updateOrder - other._updateOrder;
if (compare == 0)
+47 -7
View File
@@ -2,22 +2,22 @@
module es {
/** 场景 */
export class Scene {
/**
*
*/
public camera: ICamera;
/** 这个场景中的实体列表 */
public readonly entities: EntityList;
/**
*
*/
public readonly renderableComponents: RenderableComponentList;
/** 管理所有实体处理器 */
public readonly entityProcessors: EntityProcessorList;
public readonly _sceneComponents: SceneComponent[] = [];
public _renderers: Renderer[] = [];
public readonly identifierPool: IdentifierPool;
private _didSceneBegin: boolean;
constructor() {
this.entities = new EntityList(this);
this.renderableComponents = new RenderableComponentList();
this.entityProcessors = new EntityProcessorList();
this.identifierPool = new IdentifierPool();
@@ -45,6 +45,10 @@ module es {
}
public begin() {
if (this._renderers.length == 0) {
this.addRenderer(new DefaultRenderer());
}
Physics.reset();
if (this.entityProcessors != null)
@@ -58,6 +62,9 @@ module es {
public end() {
this._didSceneBegin = false;
for (let i = 0; i < this._renderers.length; i ++)
this._renderers[i].unload();
this.entities.removeAllEntities();
for (let i = 0; i < this._sceneComponents.length; i++) {
@@ -65,6 +72,7 @@ module es {
}
this._sceneComponents.length = 0;
this.camera = null;
Physics.clear();
if (this.entityProcessors)
@@ -91,6 +99,38 @@ module es {
if (this.entityProcessors != null)
this.entityProcessors.lateUpdate();
this.renderableComponents.updateLists();
this.render();
}
public render() {
for (let i = 0; i < this._renderers.length; i ++) {
this._renderers[i].render(this);
}
}
public addRenderer<T extends Renderer>(renderer: T): T {
this._renderers.push(renderer);
this._renderers.sort((self, other) => self.renderOrder - other.renderOrder);
renderer.onAddedToScene(this);
return renderer;
}
public getRenderer<T extends Renderer>(type: new (...args: any[]) => T): T {
for (let i = 0; i < this._renderers.length; i ++) {
if (this._renderers[i] instanceof type)
return this._renderers[i] as T;
}
return null;
}
public removeRenderer(renderer: Renderer) {
new List(this._renderers).remove(renderer);
renderer.unload();
}
/**
+30 -33
View File
@@ -1,12 +1,10 @@
module transform {
export enum Component {
module es {
export enum ComponentTransform {
position,
scale,
rotation,
}
}
module es {
export enum DirtyType {
clean = 0,
positionDirty = 1,
@@ -28,14 +26,14 @@ module es {
/**
*
*/
public _localTransform: Matrix2D;
public _localTransform: Matrix2D = Matrix2D.identity;
/**
*
*/
public _worldTransform = Matrix2D.identity;
public _rotationMatrix: Matrix2D = Matrix2D.identity;
public _translationMatrix: Matrix2D = Matrix2D.identity;
public _scaleMatrix: Matrix2D;
public _scaleMatrix: Matrix2D = Matrix2D.identity;
public _children: Transform[] = [];
constructor(entity: Entity) {
@@ -106,7 +104,7 @@ module es {
public get worldToLocalTransform(): Matrix2D {
if (this._worldToLocalDirty) {
if (!this.parent) {
if (this.parent == null) {
this._worldToLocalTransform = Matrix2D.identity;
} else {
this.parent.updateTransform();
@@ -267,13 +265,13 @@ module es {
return this;
if (this._parent != null) {
let children = new es.List(this._parent._children);
children.remove(this);
const index = this._parent._children.findIndex(t => t == this);
if (index != -1)
this._parent._children.splice(index, 1);
}
if (parent != null) {
let children = new es.List(parent._children);
children.add(this);
parent._children.push(this);
}
this._parent = parent;
@@ -294,7 +292,7 @@ module es {
this._position = position;
if (this.parent != null) {
this.localPosition = Vector2.transform(this._position, this._worldToLocalTransform);
this.localPosition = Vector2.transform(this._position, this.worldToLocalTransform);
} else {
this.localPosition = position;
}
@@ -325,7 +323,7 @@ module es {
*/
public setRotation(radians: number): Transform {
this._rotation = radians;
if (this.parent) {
if (this.parent != null) {
this.localRotation = this.parent.rotation + radians;
} else {
this.localRotation = radians;
@@ -347,9 +345,9 @@ module es {
* @param pos
*/
public lookAt(pos: Vector2) {
let sign = this.position.x > pos.x ? -1 : 1;
let vectorToAlignTo = Vector2.normalize(Vector2.subtract(this.position, pos));
this.rotation = sign * Math.acos(Vector2.dot(vectorToAlignTo, Vector2.unitY));
const sign = this.position.x > pos.x ? -1 : 1;
const vectorToAlignTo = this.position.sub(pos).normalize();
this.rotation = sign * Math.acos(vectorToAlignTo.dot(Vector2.unitY));
}
/**
@@ -378,7 +376,7 @@ module es {
*/
public setScale(scale: Vector2): Transform {
this._scale = scale;
if (this.parent) {
if (this.parent != null) {
this.localScale = Vector2.divide(scale, this.parent._scale);
} else {
this.localScale = scale;
@@ -412,22 +410,22 @@ module es {
if (this._localDirty) {
if (this._localPositionDirty) {
this._translationMatrix = Matrix2D.createTranslation(this._localPosition.x, this._localPosition.y);
Matrix2D.createTranslation(this._localPosition.x, this._localPosition.y, this._translationMatrix);
this._localPositionDirty = false;
}
if (this._localRotationDirty) {
this._rotationMatrix = Matrix2D.createRotation(this._localRotation);
Matrix2D.createRotation(this._localRotation, this._rotationMatrix);
this._localRotationDirty = false;
}
if (this._localScaleDirty) {
this._scaleMatrix = Matrix2D.createScale(this._localScale.x, this._localScale.y);
Matrix2D.createScale(this._localScale.x, this._localScale.y, this._scaleMatrix);
this._localScaleDirty = false;
}
this._localTransform = this._scaleMatrix.multiply(this._rotationMatrix);
this._localTransform = this._localTransform.multiply(this._translationMatrix);
Matrix2D.multiply(this._scaleMatrix, this._rotationMatrix, this._localTransform);
Matrix2D.multiply(this._localTransform, this._translationMatrix, this._localTransform);
if (this.parent == null) {
this._worldTransform = this._localTransform;
@@ -440,10 +438,9 @@ module es {
}
if (this.parent != null) {
this._worldTransform = this._localTransform.multiply(this.parent._worldTransform);
Matrix2D.multiply(this._localTransform, this.parent._worldTransform, this._worldTransform);
this._rotation = this._localRotation + this.parent._rotation;
this._scale = Vector2.multiply(this.parent._scale, this._localScale);
this._scale = this.parent._scale.multiply(this._localScale);;
this._worldInverseDirty = true;
}
@@ -458,14 +455,14 @@ module es {
this.hierarchyDirty |= dirtyFlagType;
switch (dirtyFlagType) {
case es.DirtyType.positionDirty:
this.entity.onTransformChanged(transform.Component.position);
case DirtyType.positionDirty:
this.entity.onTransformChanged(ComponentTransform.position);
break;
case es.DirtyType.rotationDirty:
this.entity.onTransformChanged(transform.Component.rotation);
case DirtyType.rotationDirty:
this.entity.onTransformChanged(ComponentTransform.rotation);
break;
case es.DirtyType.scaleDirty:
this.entity.onTransformChanged(transform.Component.scale);
case DirtyType.scaleDirty:
this.entity.onTransformChanged(ComponentTransform.scale);
break;
}
@@ -480,8 +477,8 @@ module es {
* @param transform
*/
public copyFrom(transform: Transform) {
this._position = transform.position;
this._localPosition = transform._localPosition;
this._position = transform.position.clone();
this._localPosition = transform._localPosition.clone();
this._rotation = transform._rotation;
this._localRotation = transform._localRotation;
this._scale = transform._scale;
+31 -10
View File
@@ -77,16 +77,16 @@ module es {
for (let i = 0, s = this._components.length; i < s; ++ i) {
this.handleRemove(this._components[i]);
}
this.componentsByType.clear();
this.componentsToAddByType.clear();
this._components.length = 0;
this._updatableComponents.length = 0;
this._componentsToAdd = {};
this._componentsToRemove = {};
this._componentsToAddList.length = 0;
this._componentsToRemoveList.length = 0;
}
this.componentsByType.clear();
this.componentsToAddByType.clear();
this._components.length = 0;
this._updatableComponents.length = 0;
this._componentsToAdd = {};
this._componentsToRemove = {};
this._componentsToAddList.length = 0;
this._componentsToRemoveList.length = 0;
}
public deregisterAllComponents() {
@@ -95,6 +95,9 @@ module es {
let component = this._components[i];
if (!component) continue;
if (component instanceof RenderableComponent)
this._entity.scene.renderableComponents.remove(component);
// 处理IUpdatable
if (isIUpdatable(component))
new es.List(this._updatableComponents).remove(component);
@@ -109,6 +112,9 @@ module es {
if (this._components.length > 0) {
for (let i = 0, s = this._components.length; i < s; ++ i) {
let component = this._components[i];
if (component instanceof RenderableComponent)
this._entity.scene.renderableComponents.remove(component);
if (isIUpdatable(component))
this._updatableComponents.push(component);
@@ -151,6 +157,9 @@ module es {
if (this._componentsToAddList.length > 0) {
for (let i = 0, l = this._componentsToAddList.length; i < l; ++ i) {
let component = this._componentsToAddList[i];
if (component instanceof RenderableComponent)
this._entity.scene.renderableComponents.add(component);
if (isIUpdatable(component))
this._updatableComponents.push(component);
@@ -186,6 +195,9 @@ module es {
}
public handleRemove(component: Component) {
if (component instanceof RenderableComponent)
this._entity.scene.renderableComponents.remove(component);
if (isIUpdatable(component) && this._updatableComponents.length > 0) {
let index = this._updatableComponents.findIndex((c) => (<any>c as Component).id == component.id);
if (index != -1)
@@ -282,7 +294,7 @@ module es {
}
}
public onEntityTransformChanged(comp: transform.Component) {
public onEntityTransformChanged(comp: ComponentTransform) {
if (this._components.length > 0 ){
for (let i = 0, s = this._components.length; i < s; ++ i) {
let component = this._components[i];
@@ -313,5 +325,14 @@ module es {
this._components[i].onDisabled();
}
}
public debugRender(batcher: IBatcher) {
if (!batcher) return;
for (let i = 0; i < this._components.length; i ++) {
if (this._components[i].enabled) {
this._components[i].debugRender(batcher);
}
}
}
}
}
@@ -0,0 +1,81 @@
module es {
export class RenderableComponentList {
private _components: IRenderable[] = [];
private _componentsByRenderLayer: Map<number, IRenderable[]> = new Map();
private _unsortedRenderLayers: number[] = [];
private _componentsNeedSort = true;
public get count() {
return this._components.length;
}
public get(index: number) {
return this._components[index];
}
public add(component: IRenderable) {
this._components.push(component);
this.addToRenderLayerList(component, component.renderLayer);
}
public remove(component: IRenderable) {
new List(this._components).remove(component);
new List(this._componentsByRenderLayer.get(component.renderLayer)).remove(component);
}
public updateRenderableRenderLayer(component: IRenderable, oldRenderLayer: number, newRenderLayer: number) {
if (this._componentsByRenderLayer.has(oldRenderLayer) && new List(this._componentsByRenderLayer.get(oldRenderLayer)).contains(component)) {
new List(this._componentsByRenderLayer.get(oldRenderLayer)).remove(component);
this.addToRenderLayerList(component, newRenderLayer);
}
}
public setRenderLayerNeedsComponentSort(renderLayer: number) {
const unsortedRenderLayersList = new List(this._unsortedRenderLayers);
if (!unsortedRenderLayersList.contains(renderLayer))
unsortedRenderLayersList.add(renderLayer);
this._componentsNeedSort = true;
}
public setNeedsComponentSort() {
this._componentsNeedSort = true;
}
private addToRenderLayerList(component: IRenderable, renderLayer: number) {
let list = this.componentsWithRenderLayer(renderLayer);
es.Insist.isFalse(!!list.find(c => c == component), "组件renderLayer列表已包含此组件");
list.push(component);
const unsortedRenderLayersList = new List(this._unsortedRenderLayers);
if (!unsortedRenderLayersList.contains(renderLayer))
unsortedRenderLayersList.add(renderLayer);
this._componentsNeedSort = true;
}
public componentsWithRenderLayer(renderLayer: number) {
if (!this._componentsByRenderLayer.get(renderLayer)) {
this._componentsByRenderLayer.set(renderLayer, []);
}
return this._componentsByRenderLayer.get(renderLayer);
}
public updateLists() {
if (this._componentsNeedSort) {
this._components.sort((self, other) => other.renderLayer - self.renderLayer);
this._componentsNeedSort = false;
}
if (this._unsortedRenderLayers.length > 0) {
for (let i = 0, count = this._unsortedRenderLayers.length; i < count; i ++) {
const renderLayerComponents = this._componentsByRenderLayer.get(this._unsortedRenderLayers[i]);
if (renderLayerComponents) {
renderLayerComponents.sort((self, other) => other.renderLayer - self.renderLayer);
}
this._unsortedRenderLayers.length = 0;
}
}
}
}
}
+475
View File
@@ -0,0 +1,475 @@
module es {
export class Color {
/**
*
*/
public r: number;
/**
* 绿
*/
public g: number;
/**
*
*/
public b: number;
/**
* (0-1)
*/
public a: number;
/**
*
*/
public h: number;
/**
*
*/
public s: number;
/**
*
*/
public l: number;
/**
* r, g, b, a Color
*
* @param r (0-255)
* @param g 绿 (0-255)
* @param b (0-255)
* @param a alpha (0-1.0)
*/
constructor(r: number, g: number, b: number, a?: number) {
this.r = r;
this.g = g;
this.b = b;
this.a = a != null ? a : 1;
}
/**
* r, g, b, a Color
*
* @param r (0-255)
* @param g 绿 (0-255)
* @param b (0-255)
* @param a alpha (0-1.0)
*/
public static fromRGB(r: number, g: number, b: number, a?: number): Color {
return new Color(r, g, b, a);
}
/**
* Color
*
* @param hex #ffffff CSS alpha
*/
public static createFromHex(hex: string): Color {
const color = new Color(1, 1, 1);
color.fromHex(hex);
return color;
}
/**
* hsl Color
*
* @param h [0-1]
* @param s [0-1]
* @param l [0-1]
* @param a [0-1]
*/
public static fromHSL(
h: number,
s: number,
l: number,
a: number = 1.0
): Color {
const temp = new HSLColor(h, s, l, a);
return temp.toRGBA();
}
/**
*
*
* @param factor
*/
public lighten(factor: number = 0.1): Color {
const temp = HSLColor.fromRGBA(this.r, this.g, this.b, this.a);
temp.l += temp.l * factor;
return temp.toRGBA();
}
/**
*
*
* @param factor
*/
public darken(factor: number = 0.1): Color {
const temp = HSLColor.fromRGBA(this.r, this.g, this.b, this.a);
temp.l -= temp.l * factor;
return temp.toRGBA();
}
/**
* 使
*
* @param factor
*/
public saturate(factor: number = 0.1): Color {
const temp = HSLColor.fromRGBA(this.r, this.g, this.b, this.a);
temp.s += temp.s * factor;
return temp.toRGBA();
}
/**
*
*
* @param factor
*/
public desaturate(factor: number = 0.1): Color {
const temp = HSLColor.fromRGBA(this.r, this.g, this.b, this.a);
temp.s -= temp.s * factor;
return temp.toRGBA();
}
/**
*
*
* @param color
*/
public mulitiply(color: Color): Color {
const newR = (((color.r / 255) * this.r) / 255) * 255;
const newG = (((color.g / 255) * this.g) / 255) * 255;
const newB = (((color.b / 255) * this.b) / 255) * 255;
const newA = color.a * this.a;
return new Color(newR, newG, newB, newA);
}
/**
*
*
* @param color
*/
public screen(color: Color): Color {
const color1 = color.invert();
const color2 = color.invert();
return color1.mulitiply(color2).invert();
}
/**
*
*/
public invert(): Color {
return new Color(255 - this.r, 255 - this.g, 255 - this.b, 1.0 - this.a);
}
/**
*
*
* @param color
*/
public average(color: Color): Color {
const newR = (color.r + this.r) / 2;
const newG = (color.g + this.g) / 2;
const newB = (color.b + this.b) / 2;
const newA = (color.a + this.a) / 2;
return new Color(newR, newG, newB, newA);
}
/**
* CSS
*
* @param format
*/
public toString(format: 'rgb' | 'hsl' | 'hex' = 'rgb') {
switch (format) {
case 'rgb':
return this.toRGBA();
case 'hsl':
return this.toHSLA();
case 'hex':
return this.toHex();
default:
throw new Error('Invalid Color format');
}
}
/**
*
* @param c
* @see https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
*/
private _componentToHex(c: number) {
const hex = c.toString(16);
return hex.length === 1 ? '0' + hex : hex;
}
/**
*
*/
public toHex(): string {
return (
'#' +
this._componentToHex(this.r) +
this._componentToHex(this.g) +
this._componentToHex(this.b) +
this._componentToHex(this.a)
);
}
/**
*
*
* @param hex #ffffff CSS alpha
*/
public fromHex(hex: string) {
const hexRegEx: RegExp = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i;
const match = hex.match(hexRegEx);
if (match) {
const r = parseInt(match[1], 16);
const g = parseInt(match[2], 16);
const b = parseInt(match[3], 16);
let a = 1;
if (match[4]) {
a = parseInt(match[4], 16) / 255;
}
this.r = r;
this.g = g;
this.b = b;
this.a = a;
} else {
throw new Error('Invalid hex string: ' + hex);
}
}
/**
* RGBA
*/
public toRGBA() {
const result =
String(this.r.toFixed(0)) +
', ' +
String(this.g.toFixed(0)) +
', ' +
String(this.b.toFixed(0));
if (this.a !== undefined || this.a != null) {
return 'rgba(' + result + ', ' + String(this.a) + ')';
}
return 'rgb(' + result + ')';
}
/**
* HSLA
*/
public toHSLA() {
return HSLColor.fromRGBA(this.r, this.g, this.b, this.a).toString();
}
/**
* CSS
*/
public fillStyle() {
return this.toString();
}
/**
*
*/
public clone(): Color {
return new Color(this.r, this.g, this.b, this.a);
}
/**
* Black (#000000)
*/
public static Black: Color = Color.createFromHex('#000000');
/**
* White (#FFFFFF)
*/
public static White: Color = Color.createFromHex('#FFFFFF');
/**
* Gray (#808080)
*/
public static Gray: Color = Color.createFromHex('#808080');
/**
* Light gray (#D3D3D3)
*/
public static LightGray: Color = Color.createFromHex('#D3D3D3');
/**
* Dark gray (#A9A9A9)
*/
public static DarkGray: Color = Color.createFromHex('#A9A9A9');
/**
* Yellow (#FFFF00)
*/
public static Yellow: Color = Color.createFromHex('#FFFF00');
/**
* Orange (#FFA500)
*/
public static Orange: Color = Color.createFromHex('#FFA500');
/**
* Red (#FF0000)
*/
public static Red: Color = Color.createFromHex('#FF0000');
/**
* Vermillion (#FF5B31)
*/
public static Vermillion: Color = Color.createFromHex('#FF5B31');
/**
* Rose (#FF007F)
*/
public static Rose: Color = Color.createFromHex('#FF007F');
/**
* Magenta (#FF00FF)
*/
public static Magenta: Color = Color.createFromHex('#FF00FF');
/**
* Violet (#7F00FF)
*/
public static Violet: Color = Color.createFromHex('#7F00FF');
/**
* Blue (#0000FF)
*/
public static Blue: Color = Color.createFromHex('#0000FF');
/**
* Azure (#007FFF)
*/
public static Azure: Color = Color.createFromHex('#007FFF');
/**
* Cyan (#00FFFF)
*/
public static Cyan: Color = Color.createFromHex('#00FFFF');
/**
* Viridian (#59978F)
*/
public static Viridian: Color = Color.createFromHex('#59978F');
/**
* Green (#00FF00)
*/
public static Green: Color = Color.createFromHex('#00FF00');
/**
* Chartreuse (#7FFF00)
*/
public static Chartreuse: Color = Color.createFromHex('#7FFF00');
/**
* Transparent (#FFFFFF00)
*/
public static Transparent: Color = Color.createFromHex('#FFFFFF00');
}
/**
* HSL
*
* http://en.wikipedia.org/wiki/HSL_and_HSV
* http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
*/
class HSLColor {
constructor(
public h: number,
public s: number,
public l: number,
public a: number
) { }
public static hue2rgb(p: number, q: number, t: number): number {
if (t < 0) {
t += 1;
}
if (t > 1) {
t -= 1;
}
if (t < 1 / 6) {
return p + (q - p) * 6 * t;
}
if (t < 1 / 2) {
return q;
}
if (t < 2 / 3) {
return p + (q - p) * (2 / 3 - t) * 6;
}
return p;
}
public static fromRGBA(
r: number,
g: number,
b: number,
a: number
): HSLColor {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h = (max + min) / 2;
let s = h;
const l = h;
if (max === min) {
h = s = 0; // achromatic
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
h /= 6;
}
return new HSLColor(h, s, l, a);
}
public toRGBA(): Color {
let r: number;
let g: number;
let b: number;
if (this.s === 0) {
r = g = b = this.l; // achromatic
} else {
const q =
this.l < 0.5
? this.l * (1 + this.s)
: this.l + this.s - this.l * this.s;
const p = 2 * this.l - q;
r = HSLColor.hue2rgb(p, q, this.h + 1 / 3);
g = HSLColor.hue2rgb(p, q, this.h);
b = HSLColor.hue2rgb(p, q, this.h - 1 / 3);
}
return new Color(r * 255, g * 255, b * 255, this.a);
}
public toString(): string {
const h = this.h.toFixed(0);
const s = this.s.toFixed(0);
const l = this.l.toFixed(0);
const a = this.a.toFixed(0);
return `hsla(${h}, ${s}, ${l}, ${a})`;
}
}
}
+15
View File
@@ -0,0 +1,15 @@
module es {
export interface IBatcher {
begin(cam: ICamera);
end();
drawPoints(points: Vector2[], color: Color, thickness?: number);
drawPolygon(poisition: Vector2, points: Vector2[], color: Color, closePoly: boolean, thickness?: number);
drawHollowRect(x: number, y: number, width: number, height: number, color: Color, thickness?: number);
drawCircle(position: Vector2, raidus: number, color: Color, thickness?: number);
drawCircleLow(position: es.Vector2, radius: number, color: Color, thickness?: number, resolution?: number);
drawRect(x: number, y: number, width: number, height: number, color: Color);
drawLine(start: Vector2, end: Vector2, color: Color, thickness: number);
drawPixel(position: Vector2, color: Color, size?: number);
}
}
+5
View File
@@ -0,0 +1,5 @@
module es {
export interface ICamera extends Component {
bounds: Rectangle;
}
}
+10
View File
@@ -0,0 +1,10 @@
module es {
export class Graphics {
public static instance: Graphics;
public batcher: IBatcher;
constructor(batcher: IBatcher) {
this.batcher = batcher;
}
}
}
@@ -0,0 +1,29 @@
///<reference path="Renderer.ts" />
module es {
export class DefaultRenderer extends Renderer {
constructor(renderOrder: number = 0, camera: ICamera = null) {
super(renderOrder, camera);
}
public render(scene: Scene): void {
if (!this.renderDirty)
return;
this.renderDirty = false;
let cam = this.camera ? this.camera : scene.camera;
this.beginRender(cam);
for (let i = 0; i < scene.renderableComponents.count; i ++) {
let renderable = scene.renderableComponents.get(i);
if (renderable.enabled && renderable.isVisibleFromCamera(scene.camera))
this.renderAfterStateCheck(renderable, cam);
}
if (this.shouldDebugRender && es.Core.debugRenderEndabled) {
this.debugRender(scene);
}
this.endRender();
}
}
}
+60
View File
@@ -0,0 +1,60 @@
module es {
export abstract class Renderer {
public camera: ICamera;
public readonly renderOrder: number = 0;
public shouldDebugRender: boolean = true;
protected renderDirty: boolean = true;
constructor(renderOrder: number, camera: ICamera) {
this.renderOrder = renderOrder;
this.camera = camera;
Core.emitter.addObserver(CoreEvents.renderChanged, this.onRenderChanged, this);
}
public onAddedToScene(scene: es.Scene) { }
public unload() { }
protected beginRender(cam: ICamera) {
if (!Graphics.instance)
return;
Graphics.instance.batcher.begin(cam);
}
protected endRender() {
if (!Graphics.instance)
return;
Graphics.instance.batcher.end();
}
protected onRenderChanged() {
this.renderDirty = true;
}
public abstract render(scene: Scene): void;
protected renderAfterStateCheck(renderable: IRenderable, cam: ICamera) {
if (!Graphics.instance)
return;
renderable.render(Graphics.instance.batcher, cam);
}
protected debugRender(scene: Scene) {
if (!Graphics.instance)
return;
es.Physics.debugDraw(2);
for (let i = 0; i < scene.entities.count; i ++) {
let entity = scene.entities.buffer[i];
if (entity.enabled) {
entity.debugRender(Graphics.instance.batcher);
}
}
}
}
}
+20 -20
View File
@@ -13,9 +13,9 @@ module es {
public static getPoint(p0: Vector2, p1: Vector2, p2: Vector2, t: number): Vector2 {
t = MathHelper.clamp01(t);
let oneMinusT = 1 - t;
return new Vector2(oneMinusT * oneMinusT).multiply(p0)
.add(new Vector2(2 * oneMinusT * t).multiply(p1))
.add(new Vector2(t * t).multiply(p2));
return p0.scale(oneMinusT * oneMinusT)
.addEqual(p1.scale(2 * oneMinusT * t))
.addEqual(p2.scale(t * t));
}
/**
@@ -29,11 +29,11 @@ module es {
public static getPointThree(start: Vector2, firstControlPoint: Vector2, secondControlPoint: Vector2,
end: Vector2, t: number): Vector2 {
t = MathHelper.clamp01(t);
let oneMinusT = 1 - t;
return new Vector2(oneMinusT * oneMinusT * oneMinusT).multiply(start)
.add(new Vector2(3 * oneMinusT * oneMinusT * t).multiply(firstControlPoint))
.add(new Vector2(3 * oneMinusT * t * t).multiply(secondControlPoint))
.add(new Vector2(t * t * t).multiply(end));
const oneMinusT = 1 - t;
return start.scale(oneMinusT * oneMinusT * oneMinusT)
.addEqual(firstControlPoint.scale(3 * oneMinusT * oneMinusT * t))
.addEqual(secondControlPoint.scale(3 * oneMinusT * t * t))
.addEqual(end.scale(t * t * t));
}
/**
@@ -44,8 +44,8 @@ module es {
* @param t
*/
public static getFirstDerivative(p0: Vector2, p1: Vector2, p2: Vector2, t: number) {
return new Vector2(2 * (1 - t)).multiply(Vector2.subtract(p1, p0))
.add(new Vector2(2 * t).multiply(Vector2.subtract(p2, p1)));
return p1.sub(p0).scale(2 * (1 - t))
.addEqual(p2.sub(p1).scale(2 * t));
}
/**
@@ -60,9 +60,9 @@ module es {
end: Vector2, t: number) {
t = MathHelper.clamp01(t);
let oneMunusT = 1 - t;
return new Vector2(3 * oneMunusT * oneMunusT).multiply(Vector2.subtract(firstControlPoint, start))
.add(new Vector2(6 * oneMunusT * t).multiply(Vector2.subtract(secondControlPoint, firstControlPoint)))
.add(new Vector2(3 * t * t).multiply(Vector2.subtract(end, secondControlPoint)));
return firstControlPoint.sub(start).scale(3 * oneMunusT * oneMunusT)
.addEqual(secondControlPoint.sub(firstControlPoint).scale(6 * oneMunusT * t))
.addEqual(end.sub(secondControlPoint).scale(3 * t * t));
}
/**
@@ -96,19 +96,19 @@ module es {
private static recursiveGetOptimizedDrawingPoints(start: Vector2, firstCtrlPoint: Vector2, secondCtrlPoint: Vector2,
end: Vector2, points: Vector2[], distanceTolerance: number) {
// 计算线段的所有中点
let pt12 = Vector2.divide(Vector2.add(start, firstCtrlPoint), new Vector2(2));
let pt23 = Vector2.divide(Vector2.add(firstCtrlPoint, secondCtrlPoint), new Vector2(2));
let pt34 = Vector2.divide(Vector2.add(secondCtrlPoint, end), new Vector2(2));
let pt12 = Vector2.divideScaler(start.add(firstCtrlPoint), 2);
let pt23 = Vector2.divideScaler(firstCtrlPoint.add(secondCtrlPoint), 2);
let pt34 = Vector2.divideScaler(secondCtrlPoint.add(end), 2);
// 计算新半直线的中点
let pt123 = Vector2.divide(Vector2.add(pt12, pt23), new Vector2(2));
let pt234 = Vector2.divide(Vector2.add(pt23, pt34), new Vector2(2));
let pt123 = Vector2.divideScaler(pt12.add(pt23), 2);
let pt234 = Vector2.divideScaler(pt23.add(pt34), 2);
// 最后再细分最后两个中点。如果我们满足我们的距离公差,这将是我们使用的最后一点。
let pt1234 = Vector2.divide(Vector2.add(pt123, pt234), new Vector2(2));
let pt1234 = Vector2.divideScaler(pt123.add(pt234), 2);
// 试着用一条直线来近似整个三次曲线
let deltaLine = Vector2.subtract(end, start);
let deltaLine = end.sub(start);
let d2 = Math.abs(((firstCtrlPoint.x, end.x) * deltaLine.y - (firstCtrlPoint.y - end.y) * deltaLine.x));
let d3 = Math.abs(((secondCtrlPoint.x - end.x) * deltaLine.y - (secondCtrlPoint.y - end.y) * deltaLine.x));
+19 -16
View File
@@ -10,19 +10,20 @@ module es {
* t被修改为在曲线段的范围内
* @param t
*/
public pointIndexAtTime(t: Ref<number>): number {
let i = 0;
if (t.value >= 1) {
t.value = 1;
i = this._points.length - 4;
public pointIndexAtTime(t: number): {time: number, range: number} {
const res = {time: 0, range: 0};
if (t >= 1) {
t = 1;
res.range = this._points.length - 4;
} else {
t.value = MathHelper.clamp01(t.value) * this._curveCount;
i = ~~t;
t.value -= i;
i *= 3;
t = MathHelper.clamp01(t) * this._curveCount;
res.range = Math.floor(t);
t -= res.range;
res.range *= 3;
}
return i;
res.time = t;
return res;
}
/**
@@ -32,12 +33,12 @@ module es {
*/
public setControlPoint(index: number, point: Vector2) {
if (index % 3 == 0) {
let delta = Vector2.subtract(point, this._points[index]);
const delta = point.sub(this._points[index]);
if (index > 0)
this._points[index - 1].add(delta);
this._points[index - 1].addEqual(delta);
if (index + 1 < this._points.length)
this._points[index + 1].add(delta);
this._points[index + 1].addEqual(delta);
}
this._points[index] = point;
@@ -48,7 +49,8 @@ module es {
* @param t
*/
public getPointAtTime(t: number): Vector2{
let i = this.pointIndexAtTime(new Ref(t));
const res = this.pointIndexAtTime(t);
const i = res.range;
return Bezier.getPointThree(this._points[i], this._points[i + 1], this._points[i + 2],
this._points[i + 3], t);
}
@@ -58,7 +60,8 @@ module es {
* @param t
*/
public getVelocityAtTime(t: number): Vector2 {
let i = this.pointIndexAtTime(new Ref(t));
const res = this.pointIndexAtTime(t);
const i = res.range;
return Bezier.getFirstDerivativeThree(this._points[i], this._points[i + 1], this._points[i + 2],
this._points[i + 3], t);
}
@@ -68,7 +71,7 @@ module es {
* @param t
*/
public getDirectionAtTime(t: number) {
return Vector2.normalize(this.getVelocityAtTime(t));
return this.getVelocityAtTime(t).normalize();
}
/**
+86 -1
View File
@@ -129,6 +129,10 @@ module es {
return from + (to - from) * this.clamp01(t);
}
public static betterLerp(a: number, b: number, t: number, epsilon: number): number {
return Math.abs(a - b) < epsilon ? b : MathHelper.lerp(a, b, t);
}
/**
* 使a和b之间
* 360
@@ -381,6 +385,16 @@ module es {
return this.repeat(this.approach(start, start + deltaAngle, shift), 360);
}
/**
* Vector Vector
* @param other
*/
public static project(self: Vector2, other: Vector2) {
let amt = self.dot(other) / other.lengthSquared();
let vec = other.scale(amt);
return vec;
}
/**
*
*
@@ -456,6 +470,10 @@ module es {
return t - Math.floor(t / length) * length;
}
public static floorToInt(f: number) {
return Math.trunc(Math.floor(f));
}
/**
*
* @param position
@@ -588,7 +606,74 @@ module es {
return false;
}
return !Number.isFinite(x);
return x !== Infinity;
}
public static smoothDamp(current: number, target: number, currentVelocity: number, smoothTime: number, maxSpeed: number, deltaTime: number): { value: number; currentVelocity: number } {
smoothTime = Math.max(0.0001, smoothTime);
const num: number = 2 / smoothTime;
const num2: number = num * deltaTime;
const num3: number =
1 /
(1 + (num2 + (0.48 * (num2 * num2) + 0.235 * (num2 * (num2 * num2)))));
let num4: number = current - target;
const num5: number = target;
const num6: number = maxSpeed * smoothTime;
num4 = this.clamp(num4, num6 * -1, num6);
target = current - num4;
const num7: number = (currentVelocity + num * num4) * deltaTime;
currentVelocity = (currentVelocity - num * num7) * num3;
let num8: number = target + (num4 + num7) * num3;
if (num5 - current > 0 === num8 > num5) {
num8 = num5;
currentVelocity = (num8 - num5) / deltaTime;
}
return { value: num8, currentVelocity };
}
public static smoothDampVector(current: Vector2, target: Vector2, currentVelocity: Vector2, smoothTime: number, maxSpeed: number, deltaTime: number): Vector2 {
const v = Vector2.zero;
const resX = this.smoothDamp(
current.x,
target.x,
currentVelocity.x,
smoothTime,
maxSpeed,
deltaTime
);
v.x = resX.value;
currentVelocity.x = resX.currentVelocity;
const resY = this.smoothDamp(
current.y,
target.y,
currentVelocity.y,
smoothTime,
maxSpeed,
deltaTime
);
v.y = resY.value;
currentVelocity.y = resY.currentVelocity;
return v;
}
/**
* leftMin - leftMax rightMin - rightMax
* @param value
* @param leftMin
* @param leftMax
* @param rightMin
* @param rightMax
* @returns
*/
public static mapMinMax(value: number, leftMin: number, leftMax: number, rightMin: number, rightMax): number {
return rightMin + ((MathHelper.clamp(value, leftMin, leftMax) - leftMin) * (rightMax - rightMin)) / (leftMax - leftMin);
}
public static fromAngle(angle: number) {
return new Vector2(Math.cos(angle), Math.sin(angle)).normalizeEqual();
}
}
}
+78 -16
View File
@@ -3,6 +3,15 @@ module es {
* 4x4浮点矩阵
*/
export class Matrix {
private static identity = new Matrix(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
public static get Identity() {
return this.identity;
}
public m11: number;
public m12: number;
public m13: number;
@@ -20,6 +29,26 @@ module es {
public m43: number;
public m44: number;
constructor(m11?, m12?, m13?, m14?, m21?, m22?, m23?, m24?, m31?,
m32?, m33?, m34?, m41?, m42?, m43?, m44?) {
this.m11 = m11;
this.m12 = m12;
this.m13 = m13;
this.m14 = m14;
this.m21 = m21;
this.m22 = m22;
this.m23 = m23;
this.m24 = m24;
this.m31 = m31;
this.m32 = m32;
this.m33 = m33;
this.m34 = m34;
this.m41 = m41;
this.m42 = m42;
this.m43 = m43;
this.m44 = m44;
}
/**
*
* @param left
@@ -47,6 +76,39 @@ module es {
result.m44 = 1;
}
public static createTranslation(position: Vector2, result: Matrix)
{
result.m11 = 1;
result.m12 = 0;
result.m13 = 0;
result.m14 = 0;
result.m21 = 0;
result.m22 = 1;
result.m23 = 0;
result.m24 = 0;
result.m31 = 0;
result.m32 = 0;
result.m33 = 1;
result.m34 = 0;
result.m41 = position.x;
result.m42 = position.y;
result.m43 = 0;
result.m44 = 1;
}
public static createRotationZ(radians: number, result: Matrix)
{
result = Matrix.Identity;
var val1 = Math.cos(radians);
var val2 = Math.sin(radians);
result.m11 = val1;
result.m12 = val2;
result.m21 = -val2;
result.m22 = val1;
}
/**
*
* @param matrix1
@@ -69,23 +131,23 @@ module es {
let m41 = (((matrix1.m41 * matrix2.m11) + (matrix1.m42 * matrix2.m21)) + (matrix1.m43 * matrix2.m31)) + (matrix1.m44 * matrix2.m41);
let m42 = (((matrix1.m41 * matrix2.m12) + (matrix1.m42 * matrix2.m22)) + (matrix1.m43 * matrix2.m32)) + (matrix1.m44 * matrix2.m42);
let m43 = (((matrix1.m41 * matrix2.m13) + (matrix1.m42 * matrix2.m23)) + (matrix1.m43 * matrix2.m33)) + (matrix1.m44 * matrix2.m43);
let m44 = (((matrix1.m41 * matrix2.m14) + (matrix1.m42 * matrix2.m24)) + (matrix1.m43 * matrix2.m34)) + (matrix1.m44 * matrix2.m44);
let m44 = (((matrix1.m41 * matrix2.m14) + (matrix1.m42 * matrix2.m24)) + (matrix1.m43 * matrix2.m34)) + (matrix1.m44 * matrix2.m44);
result.m11 = m11;
result.m12 = m12;
result.m13 = m13;
result.m14 = m14;
result.m21 = m21;
result.m22 = m22;
result.m23 = m23;
result.m24 = m24;
result.m31 = m31;
result.m32 = m32;
result.m33 = m33;
result.m34 = m34;
result.m41 = m41;
result.m42 = m42;
result.m43 = m43;
result.m44 = m44;
result.m12 = m12;
result.m13 = m13;
result.m14 = m14;
result.m21 = m21;
result.m22 = m22;
result.m23 = m23;
result.m24 = m24;
result.m31 = m31;
result.m32 = m32;
result.m33 = m33;
result.m34 = m34;
result.m41 = m41;
result.m42 = m42;
result.m43 = m43;
result.m44 = m44;
}
}
}
+83 -41
View File
@@ -16,7 +16,28 @@ module es {
*
*/
public static get identity(): Matrix2D {
return new Matrix2D(1, 0, 0, 1, 0, 0);
return new Matrix2D().setIdentity();
}
public setIdentity(): Matrix2D {
return this.setValues(1, 0, 0, 1, 0, 0);
}
public setValues(
m11: number,
m12: number,
m21: number,
m22: number,
m31: number,
m32: number
): Matrix2D {
this.m11 = m11;
this.m12 = m12;
this.m21 = m21;
this.m22 = m22;
this.m31 = m31;
this.m32 = m32;
return this;
}
/**
@@ -38,7 +59,7 @@ module es {
return Math.atan2(this.m21, this.m11);
}
public set rotation(value: number){
public set rotation(value: number) {
let val1 = Math.cos(value);
let val2 = Math.sin(value);
@@ -71,33 +92,21 @@ module es {
this.m22 = value.y;
}
/**
*
* @param m11
* @param m12
* @param m21
* @param m22
* @param m31
* @param m32
*/
constructor(m11: number, m12: number, m21: number, m22: number, m31: number, m32: number){
this.m11 = m11;
this.m12 = m12;
this.m21 = m21;
this.m22 = m22;
this.m31 = m31;
this.m32 = m32;
}
/**
* Z轴的旋转矩阵2D
* @param radians
*/
public static createRotation(radians: number){
let result: Matrix2D = this.identity;
public static createRotation(radians: number, result: Matrix2D) {
result.setIdentity();
const val1 = Math.cos(radians);
const val2 = Math.sin(radians);
result.m11 = val1;
result.m12 = val2;
result.m21 = val2 * -1;
result.m22 = val1;
}
public static createRotationOut(radians: number, result: Matrix2D) {
let val1 = Math.cos(radians);
let val2 = Math.sin(radians);
@@ -105,8 +114,6 @@ module es {
result.m12 = val2;
result.m21 = -val2;
result.m22 = val1;
return result;
}
/**
@@ -114,8 +121,16 @@ module es {
* @param xScale
* @param yScale
*/
public static createScale(xScale: number, yScale: number){
let result: Matrix2D = this.identity;
public static createScale(xScale: number, yScale: number, result: Matrix2D) {
result.m11 = xScale;
result.m12 = 0;
result.m21 = 0;
result.m22 = yScale;
result.m31 = 0;
result.m32 = 0;
}
public static createScaleOut(xScale: number, yScale: number, result: Matrix2D) {
result.m11 = xScale;
result.m12 = 0;
@@ -124,8 +139,6 @@ module es {
result.m31 = 0;
result.m32 = 0;
return result;
}
/**
@@ -133,18 +146,26 @@ module es {
* @param xPosition
* @param yPosition
*/
public static createTranslation(xPosition: number, yPosition: number) {
let result: Matrix2D = this.identity;
public static createTranslation(xPosition: number, yPosition: number, result: Matrix2D) {
result.m11 = 1;
result.m12 = 0;
result.m21 = 0;
result.m22 = 1;
result.m31 = xPosition;
result.m32 = yPosition;
return result;
}
public static createTranslationOut(position: Vector2, result: Matrix2D) {
result.m11 = 1;
result.m12 = 0;
result.m21 = 0;
result.m22 = 1;
result.m31 = xPosition;
result.m32 = yPosition;
return result;
result.m31 = position.x;
result.m32 = position.y;
}
public static invert(matrix: Matrix2D) {
@@ -228,6 +249,26 @@ module es {
return this;
}
public static multiply(matrix1: Matrix2D, matrix2: Matrix2D, result: Matrix2D) {
const m11 = (matrix1.m11 * matrix2.m11) + (matrix1.m12 * matrix2.m21);
const m12 = (matrix1.m11 * matrix2.m12) + (matrix1.m12 * matrix2.m22);
const m21 = (matrix1.m21 * matrix2.m11) + (matrix1.m22 * matrix2.m21);
const m22 = (matrix1.m21 * matrix2.m12) + (matrix1.m22 * matrix2.m22);
const m31 = (matrix1.m31 * matrix2.m11) + (matrix1.m32 * matrix2.m21) + matrix2.m31;
const m32 = (matrix1.m31 * matrix2.m12) + (matrix1.m32 * matrix2.m22) + matrix2.m32;
result.m11 = m11;
result.m12 = m12;
result.m21 = m21;
result.m22 = m22;
result.m31 = m31;
result.m32 = m32;
}
public determinant() {
return this.m11 * this.m22 - this.m12 * this.m21;
}
@@ -238,10 +279,10 @@ module es {
* @param matrix2
* @param amount
*/
public static lerp(matrix1: Matrix2D, matrix2: Matrix2D, amount: number){
public static lerp(matrix1: Matrix2D, matrix2: Matrix2D, amount: number) {
matrix1.m11 = matrix1.m11 + ((matrix2.m11 - matrix1.m11) * amount);
matrix1.m12 = matrix1.m12 + ((matrix2.m12 - matrix1.m12) * amount);
matrix1.m21 = matrix1.m21 + ((matrix2.m21 - matrix1.m21) * amount);
matrix1.m22 = matrix1.m22 + ((matrix2.m22 - matrix1.m22) * amount);
@@ -267,8 +308,9 @@ module es {
return ret;
}
public mutiplyTranslation(x: number, y: number){
let trans = Matrix2D.createTranslation(x, y);
public mutiplyTranslation(x: number, y: number) {
let trans = new Matrix2D();
Matrix2D.createTranslation(x, y, trans);
return MatrixHelper.mutiply(this, trans);
}
@@ -276,7 +318,7 @@ module es {
* Matrix2D
* @param other
*/
public equals(other: Matrix2D){
public equals(other: Matrix2D) {
return this == other;
}
+36 -35
View File
@@ -107,8 +107,8 @@ module es {
}
// temp 用于计算边界的矩阵
public _tempMat: Matrix2D;
public _transformMat: Matrix2D;
public _tempMat: Matrix2D = new Matrix2D();
public _transformMat: Matrix2D = new Matrix2D();
/**
* Rectanglestruct实例
@@ -213,49 +213,50 @@ module es {
this.top < value.bottom;
}
public rayIntersects(ray: Ray2D, distance: Ref<number>): boolean {
distance.value = 0;
public rayIntersects(ray: Ray2D): { intersected: boolean; distance: number } {
const res = {intersected: false, distance: 0};
let maxValue = Number.MAX_VALUE;
if (Math.abs(ray.direction.x) < 1E-06) {
if ((ray.start.x < this.x) || (ray.start.x > this.x + this.width))
return false;
return res;
} else {
let num11 = 1 / ray.direction.x;
const num11 = 1 / ray.direction.x;
let num8 = (this.x - ray.start.x) * num11;
let num7 = (this.x + this.width - ray.start.x) * num11;
if (num8 > num7) {
let num14 = num8;
const num14 = num8;
num8 = num7;
num7 = num14;
}
distance.value = Math.max(num8, distance.value);
res.distance = Math.max(num8, res.distance);
maxValue = Math.min(num7, maxValue);
if (distance.value > maxValue)
return false;
if (res.distance > maxValue)
return res;
}
if (Math.abs(ray.direction.y) < 1E-06) {
if (Math.abs(ray.direction.y) < 1e-06) {
if ((ray.start.y < this.y) || (ray.start.y > this.y + this.height))
return false;
return res;
} else {
let num10 = 1 / ray.direction.y;
const num10 = 1 / ray.direction.y;
let num6 = (this.y - ray.start.y) * num10;
let num5 = (this.y + this.height - ray.start.y) * num10;
if (num6 > num5) {
let num13 = num6;
const num13 = num6;
num6 = num5;
num5 = num13;
}
distance.value = Math.max(num6, distance.value);
res.distance = Math.max(num6, res.distance);
maxValue = Math.max(num5, maxValue);
if (distance.value > maxValue)
return false;
if (res.distance > maxValue)
return res;
}
return true;
res.intersected = true;
return res;
}
/**
@@ -304,7 +305,7 @@ module es {
*/
public getClosestPointOnRectangleToPoint(point: Vector2) {
// 对于每条轴,如果点在框外,就把它限制在框内,否则就不要管它
let res = new Vector2();
let res = es.Vector2.zero;
res.x = MathHelper.clamp(point.x, this.left, this.right);
res.y = MathHelper.clamp(point.y, this.top, this.bottom);
@@ -319,7 +320,7 @@ module es {
*/
public getClosestPointOnRectangleBorderToPoint(point: Vector2, edgeNormal: Vector2): Vector2 {
// 对于每条轴,如果点在框外,就把它限制在框内,否则就不要管它
let res = new Vector2();
let res = es.Vector2.zero;
res.x = MathHelper.clamp(point.x, this.left, this.right);
res.y = MathHelper.clamp(point.y, this.top, this.bottom);
@@ -411,22 +412,22 @@ module es {
public calculateBounds(parentPosition: Vector2, position: Vector2, origin: Vector2, scale: Vector2,
rotation: number, width: number, height: number) {
if (rotation == 0) {
this.x = parentPosition.x + position.x - origin.x * scale.x;
this.y = parentPosition.y + position.y - origin.y * scale.y;
this.width = width * scale.x;
this.height = height * scale.y;
this.x = Math.trunc(parentPosition.x + position.x - origin.x * scale.x);
this.y = Math.trunc(parentPosition.y + position.y - origin.y * scale.y);
this.width = Math.trunc(width * scale.x);
this.height = Math.trunc(height * scale.y);
} else {
// 我们需要找到我们的绝对最小/最大值,并据此创建边界
let worldPosX = parentPosition.x + position.x;
let worldPosY = parentPosition.y + position.y;
// 考虑到原点,将参考点设置为世界参考
this._transformMat = Matrix2D.createTranslation(-worldPosX - origin.x, -worldPosY - origin.y);
this._tempMat = Matrix2D.createScale(scale.x, scale.y);
Matrix2D.createTranslation(-worldPosX - origin.x, -worldPosY - origin.y, this._transformMat);
Matrix2D.createScale(scale.x, scale.y, this._tempMat);
this._transformMat = this._transformMat.multiply(this._tempMat);
this._tempMat = Matrix2D.createRotation(rotation);
Matrix2D.createRotation(rotation, this._tempMat);
this._transformMat = this._transformMat.multiply(this._tempMat);
this._tempMat = Matrix2D.createTranslation(worldPosX, worldPosY);
Matrix2D.createTranslation(worldPosX, worldPosY, this._tempMat);
this._transformMat = this._transformMat.multiply(this._tempMat);
// TODO: 我们可以把世界变换留在矩阵中,避免在世界空间中得到所有的四个角
@@ -441,14 +442,14 @@ module es {
Vector2Ext.transformR(bottomRight, this._transformMat, bottomRight);
// 找出最小值和最大值,这样我们就可以计算出我们的边界框。
let minX = Math.min(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x);
let maxX = Math.max(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x);
let minY = Math.min(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y);
let maxY = Math.max(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y);
let minX = Math.trunc(Math.min(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x));
let maxX = Math.trunc(Math.max(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x));
let minY = Math.trunc(Math.min(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y));
let maxY = Math.trunc(Math.max(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y));
this.location = new Vector2(minX, minY);
this.width = maxX - minX;
this.height = maxY - minY;
this.width = Math.trunc(maxX - minX);
this.height = Math.trunc(maxY - minY);
}
}
@@ -549,7 +550,7 @@ module es {
*
*/
public getHashCode(): number{
return (this.x ^ this.y ^ this.width ^ this.height);
return (Math.trunc(this.x) ^ Math.trunc(this.y) ^ Math.trunc(this.width) ^ Math.trunc(this.height));
}
public clone(): Rectangle {
+1 -1
View File
@@ -16,7 +16,7 @@ module es {
*/
public update(amount: number){
this.remainder += amount;
let motion = Math.floor(Math.trunc(this.remainder));
let motion = Math.trunc(this.remainder);
this.remainder -= motion;
amount = motion;
return amount;
+129 -98
View File
@@ -9,9 +9,9 @@ module es {
* @param x x坐标
* @param y y坐标
*/
constructor(x?: number, y?: number) {
this.x = x ? x : 0;
this.y = y != undefined ? y : this.x;
constructor(x: number = 0, y: number = 0) {
this.x = x;
this.y = y;
}
public static get zero() {
@@ -30,6 +30,22 @@ module es {
return new Vector2(0, 1);
}
public static get up() {
return new Vector2(0, -1);
}
public static get down() {
return new Vector2(0, 1);
}
public static get left() {
return new Vector2(-1, 0);
}
public static get right() {
return new Vector2(1, 0);
}
/**
*
* @param value1
@@ -54,73 +70,20 @@ module es {
return result;
}
/**
*
* @param value1
* @param value2
*/
public static multiply(value1: Vector2, value2: Vector2) {
let result: Vector2 = new Vector2(0, 0);
result.x = value1.x * value2.x;
result.y = value1.y * value2.y;
public static divideScaler(value1: Vector2, value2: number) {
let result: Vector2 = Vector2.zero;
result.x = value1.x / value2;
result.y = value1.y / value2;
return result;
}
/**
*
* @param value1
* @param value2
* @returns
*/
public static multiplyScaler(value1: Vector2, value2: number) {
let result = new Vector2(0, 0);
result.x = value1.x * value2;
result.y = value1.x * value2;
return result;
}
/**
*
* @param value1
* @param value2
*/
public static subtract(value1: Vector2, value2: Vector2) {
let result: Vector2 = new Vector2(0, 0);
result.x = value1.x - value2.x;
result.y = value1.y - value2.y;
return result;
}
/**
* Vector2
*
* @param value
*/
public static normalize(value: Vector2) {
let nValue = new Vector2(value.x, value.y);
let val = 1 / Math.sqrt((nValue.x * nValue.x) + (nValue.y * nValue.y));
nValue.x *= val;
nValue.y *= val;
return nValue;
}
/**
*
* @param value1
* @param value2
*/
public static dot(value1: Vector2, value2: Vector2): number {
return (value1.x * value2.x) + (value1.y * value2.y);
}
/**
*
* @param value1
* @param value2
*/
public static distanceSquared(value1: Vector2, value2: Vector2) {
let v1 = value1.x - value2.x, v2 = value1.y - value2.y;
return (v1 * v1) + (v2 * v2);
public static sqrDistance(value1: Vector2, value2: Vector2) {
return Math.pow(value1.x - value2.x, 2) + Math.pow(value1.y - value2.y, 2);
}
/**
@@ -183,9 +146,8 @@ module es {
* @param value2
* @returns
*/
public static distance(value1: Vector2, value2: Vector2): number {
let v1 = value1.x - value2.x, v2 = value1.y - value2.y;
return Math.sqrt((v1 * v1) + (v2 * v2));
public static distance(vec1: Vector2, vec2: Vector2): number {
return Math.sqrt(Math.pow(vec1.x - vec2.x, 2) + Math.pow(vec1.y - vec2.y, 2));
}
/**
@@ -193,10 +155,10 @@ module es {
* @param from
* @param to
*/
public static angle(from: Vector2, to: Vector2): number{
from = Vector2.normalize(from);
to = Vector2.normalize(to);
return Math.acos(MathHelper.clamp(Vector2.dot(from, to), -1, 1)) * MathHelper.Rad2Deg;
public static angle(from: Vector2, to: Vector2): number {
from = from.normalize();
to = to.normalize();
return Math.acos(MathHelper.clamp(from.dot(to), -1, 1)) * MathHelper.Rad2Deg;
}
/**
@@ -218,7 +180,7 @@ module es {
* @returns
*/
public static reflect(vector: Vector2, normal: Vector2) {
let result: Vector2 = new Vector2();
let result: Vector2 = es.Vector2.zero;
let val = 2 * ((vector.x * normal.x) + (vector.y * normal.y));
result.x = vector.x - (normal.x * val);
result.y = vector.y - (normal.y * val);
@@ -237,13 +199,26 @@ module es {
MathHelper.smoothStep(value1.y, value2.y, amount));
}
public setTo(x: number, y: number) {
this.x = x;
this.y = y;
}
public negate(): Vector2 {
return this.scale(-1);
}
/**
*
* @param value
*/
public add(value: Vector2): Vector2 {
this.x += value.x;
this.y += value.y;
public add(v: Vector2): Vector2 {
return new Vector2(this.x + v.x, this.y + v.y);
}
public addEqual(v: Vector2): Vector2 {
this.x += v.x;
this.y += v.y;
return this;
}
@@ -252,9 +227,11 @@ module es {
* @param value
*/
public divide(value: Vector2): Vector2 {
this.x /= value.x;
this.y /= value.y;
return this;
return new Vector2(this.x / value.x, this.y / value.y);
}
public divideScaler(value: number): Vector2 {
return new Vector2(this.x / value, this.y / value);
}
/**
@@ -262,9 +239,7 @@ module es {
* @param value
*/
public multiply(value: Vector2): Vector2 {
this.x *= value.x;
this.y *= value.y;
return this;
return new Vector2(value.x * this.x, value.y * this.y);
}
/**
@@ -283,24 +258,75 @@ module es {
* @param value Vector2
* @returns Vector2
*/
public subtract(value: Vector2) {
this.x -= value.x;
this.y -= value.y;
public sub(value: Vector2) {
return new Vector2(this.x - value.x, this.y - value.y);
}
public subEqual(v: Vector2): Vector2 {
this.x -= v.x;
this.y -= v.y;
return this;
}
public dot(v: Vector2): number {
return this.x * v.x + this.y * v.y;
}
/**
*
* @param size
* @returns
*/
public scale(size: number): Vector2 {
return new Vector2(this.x * size, this.y * size);
}
public scaleEqual(size: number): Vector2 {
this.x *= size;
this.y *= size;
return this;
}
public transform(matrix: Matrix2D): Vector2 {
return new Vector2(
this.x * matrix.m11 + this.y * matrix.m21 + matrix.m31,
this.x * matrix.m12 + this.y * matrix.m22 + matrix.m32
);
}
public normalize(): Vector2 {
const d = this.distance();
if (d > 0) {
return new Vector2(this.x / d, this.y / d);
} else {
return new Vector2(0, 1);
}
}
/**
* Vector2变成一个方向相同的单位向量
*/
public normalize() {
let val = 1 / Math.sqrt((this.x * this.x) + (this.y * this.y));
this.x *= val;
this.y *= val;
public normalizeEqual(): Vector2 {
const d = this.distance();
if (d > 0) {
this.setTo(this.x / d, this.y / d);
return this;
} else {
this.setTo(0, 1);
return this;
}
}
/** 返回它的长度 */
public length() {
return Math.sqrt((this.x * this.x) + (this.y * this.y));
public magnitude(): number {
return this.distance();
}
public distance(v?: Vector2): number {
if (!v) {
v = Vector2.zero;
}
return Math.sqrt(Math.pow(this.x - v.x, 2) + Math.pow(this.y - v.y, 2));
}
/**
@@ -324,8 +350,8 @@ module es {
* @param right
*/
public angleBetween(left: Vector2, right: Vector2) {
let one = Vector2.subtract(left, this);
let two = Vector2.subtract(right, this);
let one = left.sub(this);
let two = right.sub(this);
return Vector2Ext.angle(one, two);
}
@@ -334,12 +360,8 @@ module es {
* @param other
* @returns true false
*/
public equals(other: Vector2 | object): boolean {
if (other instanceof Vector2){
return other.x == this.x && other.y == this.y;
}
return false;
public equals(other: Vector2, tolerance: number = 0.001): boolean {
return Math.abs(this.x - other.x) <= tolerance && Math.abs(this.y - other.y) <= tolerance;
}
public isValid(): boolean {
@@ -377,11 +399,20 @@ module es {
* @param amount
* @returns
*/
public static hermite(value1: Vector2, tangent1: Vector2, value2: Vector2, tangent2: Vector2, amount: number){
public static hermite(value1: Vector2, tangent1: Vector2, value2: Vector2, tangent2: Vector2, amount: number) {
return new Vector2(MathHelper.hermite(value1.x, tangent1.x, value2.x, tangent2.x, amount),
MathHelper.hermite(value1.y, tangent1.y, value2.y, tangent2.y, amount));
}
public static unsignedAngle(from: Vector2, to: Vector2, round: boolean = true) {
from.normalizeEqual();
to.normalizeEqual();
const angle =
Math.acos(MathHelper.clamp(from.dot(to), -1, 1)) * MathHelper.Rad2Deg;
return round ? Math.round(angle) : angle;
}
public clone(): Vector2 {
return new Vector2(this.x, this.y);
}
+15 -7
View File
@@ -19,35 +19,43 @@ module es {
* Collider重叠的ITriggerListeners
*/
public update() {
const lateColliders = [];
// 对所有实体.colliders进行重叠检查,这些实体.colliders是触发器,与所有宽相碰撞器,无论是否触发器。
// 任何重叠都会导致触发事件
let colliders = this._entity.getComponents(Collider);
for (let i = 0; i < colliders.length; i++) {
let collider = colliders[i];
let neighbors = Physics.boxcastBroadphase(collider.bounds, collider.collidesWithLayers);
for (let j = 0; j < neighbors.size; j++) {
let neighbors = Physics.boxcastBroadphaseExcludingSelf(collider.bounds, collider.collidesWithLayers);
for (let j = 0; j < neighbors.length; j++) {
let neighbor = neighbors[j];
// 我们至少需要一个碰撞器作为触发器
if (!collider.isTrigger && !neighbor.isTrigger)
continue;
if (collider.overlaps(neighbor)) {
let pair = new Pair<Collider>(collider, neighbor);
const pair = new Pair<Collider>(collider, neighbor);
// 如果我们的某一个集合中已经有了这个对子(前一个或当前的触发交叉点),就不要调用输入事件了
let shouldReportTriggerEvent = !this._activeTriggerIntersections.contains(pair) &&
const shouldReportTriggerEvent = !this._activeTriggerIntersections.contains(pair) &&
!this._previousTriggerIntersections.contains(pair);
if (shouldReportTriggerEvent)
this.notifyTriggerListeners(pair, true);
if (shouldReportTriggerEvent) {
if (neighbor.castSortOrder >= Collider.lateSortOrder) {
lateColliders.push(pair);
} else {
this.notifyTriggerListeners(pair, true);
}
}
this._activeTriggerIntersections.add(pair);
}
}
}
ListPool.free(colliders);
for (const pair of lateColliders) {
this.notifyTriggerListeners(pair, true);
}
this.checkForExitedColliders();
}
+33 -31
View File
@@ -13,48 +13,50 @@ module es {
export class Collisions {
public static lineToLine(a1: Vector2, a2: Vector2, b1: Vector2, b2: Vector2): boolean {
let b = Vector2.subtract(a2, a1);
let d = Vector2.subtract(b2, b1);
let bDotDPerp = b.x * d.y - b.y * d.x;
const b = a2.sub(a1);
const d = b2.sub(b1);
const bDotDPerp = b.x * d.y - b.y * d.x;
// 如果b*d = 0,表示这两条直线平行,因此有无穷个交点
if (bDotDPerp == 0)
return false;
let c = Vector2.subtract(b1, a1);
let t = (c.x * d.y - c.y * d.x) / bDotDPerp;
if (t < 0 || t > 1)
const c = b1.sub(a1);
const t = (c.x * d.y - c.y * d.x) / bDotDPerp;
if (t < 0 || t > 1) {
return false;
}
let u = (c.x * b.y - c.y * b.x) / bDotDPerp;
if (u < 0 || u > 1)
const u = (c.x * b.y - c.y * b.x) / bDotDPerp;
if (u < 0 || u > 1) {
return false;
}
return true;
}
public static lineToLineIntersection(a1: Vector2, a2: Vector2, b1: Vector2, b2: Vector2, intersection: Vector2 = new Vector2()): boolean {
public static lineToLineIntersection(a1: Vector2, a2: Vector2, b1: Vector2, b2: Vector2, intersection: Vector2 = es.Vector2.zero): boolean {
intersection.x = 0;
intersection.y = 0;
let b = Vector2.subtract(a2, a1);
let d = Vector2.subtract(b2, b1);
let bDotDPerp = b.x * d.y - b.y * d.x;
const b = a2.sub(a1);
const d = b2.sub(b1);
const bDotDPerp = b.x * d.y - b.y * d.x;
// 如果b*d = 0,表示这两条直线平行,因此有无穷个交点
if (bDotDPerp == 0)
return false;
let c = Vector2.subtract(b1, a1);
let t = (c.x * d.y - c.y * d.x) / bDotDPerp;
const c = b1.sub(a1);
const t = (c.x * d.y - c.y * d.x) / bDotDPerp;
if (t < 0 || t > 1)
return false;
let u = (c.x * b.y - c.y * b.x) / bDotDPerp;
const u = (c.x * b.y - c.y * b.x) / bDotDPerp;
if (u < 0 || u > 1)
return false;
let temp = Vector2.add(a1, new Vector2(t * b.x, t * b.y));
const temp = a1.add(b.scale(t));
intersection.x = temp.x;
intersection.y = temp.y;
@@ -62,24 +64,24 @@ module es {
}
public static closestPointOnLine(lineA: Vector2, lineB: Vector2, closestTo: Vector2) {
let v = Vector2.subtract(lineB, lineA);
let w = Vector2.subtract(closestTo, lineA);
let t = Vector2.dot(w, v) / Vector2.dot(v, v);
const v = lineB.sub(lineA);
const w = closestTo.sub(lineA);
let t = w.dot(v) / v.dot(v);
t = MathHelper.clamp(t, 0, 1);
return Vector2.add(lineA, new Vector2(v.x * t, v.y * t));
return lineA.add(v.scale(t));
}
public static circleToCircle(circleCenter1: Vector2, circleRadius1: number, circleCenter2: Vector2, circleRadius2: number): boolean {
return Vector2.distanceSquared(circleCenter1, circleCenter2) < (circleRadius1 + circleRadius2) * (circleRadius1 + circleRadius2);
return Vector2.sqrDistance(circleCenter1, circleCenter2) < (circleRadius1 + circleRadius2) * (circleRadius1 + circleRadius2);
}
public static circleToLine(circleCenter: Vector2, radius: number, lineFrom: Vector2, lineTo: Vector2): boolean {
return Vector2.distanceSquared(circleCenter, this.closestPointOnLine(lineFrom, lineTo, circleCenter)) < radius * radius;
return Vector2.sqrDistance(circleCenter, this.closestPointOnLine(lineFrom, lineTo, circleCenter)) < radius * radius;
}
public static circleToPoint(circleCenter: Vector2, radius: number, point: Vector2): boolean {
return Vector2.distanceSquared(circleCenter, point) < radius * radius;
return Vector2.sqrDistance(circleCenter, point) < radius * radius;
}
public static rectToCircle(rect: Rectangle, cPosition: Vector2, cRadius: number): boolean {
@@ -90,30 +92,30 @@ module es {
// 对照相关边缘检查圆圈
let edgeFrom: Vector2;
let edgeTo: Vector2;
let sector = this.getSector(rect.x, rect.y, rect.width, rect.height, cPosition);
const sector = this.getSector(rect.x, rect.y, rect.width, rect.height, cPosition);
if ((sector & PointSectors.top) != 0){
if ((sector & PointSectors.top) !== 0) {
edgeFrom = new Vector2(rect.x, rect.y);
edgeTo = new Vector2(rect.x + rect.width, rect.y);
if (this.circleToLine(cPosition, cRadius, edgeFrom, edgeTo))
return true;
}
if ((sector & PointSectors.bottom) != 0){
if ((sector & PointSectors.bottom) !== 0) {
edgeFrom = new Vector2(rect.x, rect.y + rect.width);
edgeTo = new Vector2(rect.x + rect.width, rect.y + rect.height);
if (this.circleToLine(cPosition, cRadius, edgeFrom, edgeTo))
return true;
}
if ((sector & PointSectors.left) != 0){
if ((sector & PointSectors.left) !== 0) {
edgeFrom = new Vector2(rect.x, rect.y);
edgeTo = new Vector2(rect.x, rect.y + rect.height);
if (this.circleToLine(cPosition, cRadius, edgeFrom, edgeTo))
return true;
}
if ((sector & PointSectors.right) != 0) {
if ((sector & PointSectors.right) !== 0) {
edgeFrom = new Vector2(rect.x + rect.width, rect.y);
edgeTo = new Vector2(rect.x + rect.width, rect.y + rect.height);
if (this.circleToLine(cPosition, cRadius, edgeFrom, edgeTo))
@@ -124,15 +126,15 @@ module es {
}
public static rectToLine(rect: Rectangle, lineFrom: Vector2, lineTo: Vector2) {
let fromSector = this.getSector(rect.x, rect.y, rect.width, rect.height, lineFrom);
let toSector = this.getSector(rect.x, rect.y, rect.width, rect.height, lineTo);
const fromSector = this.getSector(rect.x, rect.y, rect.width, rect.height, lineFrom);
const toSector = this.getSector(rect.x, rect.y, rect.width, rect.height, lineTo);
if (fromSector == PointSectors.center || toSector == PointSectors.center) {
return true;
} else if ((fromSector & toSector) != 0) {
return false;
} else {
let both = fromSector | toSector;
const both = fromSector | toSector;
// 线对边进行检查
let edgeFrom: Vector2;
let edgeTo: Vector2;
+35 -20
View File
@@ -3,7 +3,7 @@ module es {
export class Physics {
public static _spatialHash: SpatialHash;
/** 用于在全局范围内存储重力值的方便字段 */
public static gravity = new Vector2(0, 300);
public static gravity = new Vector2(0, -300);
/** 调用reset并创建一个新的SpatialHash时使用的单元格大小 */
public static spatialHashCellSize = 100;
/** 接受layerMask的所有方法的默认值 */
@@ -16,6 +16,7 @@ module es {
* 线/线
*/
public static raycastsStartInColliders = false;
public static debugRender: boolean = false;
/**
* raycast发生时分配它
*/
@@ -42,6 +43,11 @@ module es {
this._spatialHash.clear();
}
public static debugDraw(secondsToDisplay) {
if (this.debugRender)
this._spatialHash.debugDraw(secondsToDisplay);
}
/**
*
* @param center
@@ -61,13 +67,13 @@ module es {
* @param results
* @param layerMask
*/
public static overlapCircleAll(center: Vector2, randius: number, results: any[], layerMask = -1) {
if (results.length == 0) {
console.warn("传入了一个空的结果数组。不会返回任何结果");
return;
}
return this._spatialHash.overlapCircle(center, randius, results, layerMask);
public static overlapCircleAll(center: Vector2, radius: number, results: Collider[], layerMask: number = this.allLayers) {
return this._spatialHash.overlapCircle(
center,
radius,
results,
layerMask
);
}
/**
@@ -96,7 +102,7 @@ module es {
* @param layerMask
*/
public static boxcastBroadphaseExcludingSelfNonRect(collider: Collider, layerMask = this.allLayers) {
let bounds = collider.bounds.clone();
let bounds = collider.bounds;
return this._spatialHash.aabbBroadphase(bounds, collider, layerMask);
}
@@ -108,7 +114,7 @@ module es {
* @param layerMask
*/
public static boxcastBroadphaseExcludingSelfDelta(collider: Collider, deltaX: number, deltaY: number, layerMask: number = Physics.allLayers) {
let colliderBounds = collider.bounds.clone();
let colliderBounds = collider.bounds;
let sweptBounds = colliderBounds.getSweptBroadphaseBounds(deltaX, deltaY);
return this._spatialHash.aabbBroadphase(sweptBounds, collider, layerMask);
}
@@ -144,10 +150,18 @@ module es {
* @param end
* @param layerMask
*/
public static linecast(start: Vector2, end: Vector2, layerMask: number = Physics.allLayers): RaycastHit{
public static linecast(start: Vector2, end: Vector2, layerMask: number = this.allLayers, ignoredColliders: Set<Collider> = null): RaycastHit {
this._hitArray[0].reset();
this.linecastAll(start, end, this._hitArray, layerMask);
return this._hitArray[0];
this._hitArray[0].reset();
Physics.linecastAll(
start,
end,
this._hitArray,
layerMask,
ignoredColliders
);
return this._hitArray[0].clone();
}
/**
@@ -157,13 +171,14 @@ module es {
* @param hits
* @param layerMask
*/
public static linecastAll(start: Vector2, end: Vector2, hits: RaycastHit[], layerMask: number = Physics.allLayers){
if (hits.length == 0){
console.warn("传入了一个空的hits数组。没有点击会被返回");
return 0;
}
return this._spatialHash.linecast(start, end, hits, layerMask);
public static linecastAll(start: Vector2, end: Vector2, hits: RaycastHit[], layerMask: number = this.allLayers, ignoredColliders: Set<Collider> = null) {
return this._spatialHash.linecast(
start,
end,
hits,
layerMask,
ignoredColliders
);
}
/**
@@ -184,7 +199,7 @@ module es {
* @param layerMask
*/
public static overlapRectangleAll(rect: Rectangle, results: Collider[], layerMask: number = Physics.allLayers) {
if (results.length == 0){
if (results.length == 0) {
console.warn("传入了一个空的结果数组。不会返回任何结果");
return 0;
}
+20 -8
View File
@@ -3,14 +3,26 @@ module es {
* 线(线)线线
*/
export class Ray2D {
public start: Vector2;
public end: Vector2;
public direction: Vector2;
constructor(position: Vector2, end: Vector2){
this.start = position;
this.end = end;
this.direction = Vector2.subtract(this.end, this.start);
public get start(): Vector2 {
return this._start;
}
public get direction(): Vector2 {
return this._direction;
}
public get end(): Vector2 {
return this._end;
}
constructor(pos: Vector2, end: Vector2) {
this._start = pos.clone();
this._end = end.clone();
this._direction = this._end.sub(this._start);
}
private _start: Vector2;
private _direction: Vector2;
private _end: Vector2;
}
}
+23 -10
View File
@@ -30,7 +30,7 @@ module es {
*/
public centroid: Vector2;
constructor(collider?: Collider, fraction?: number, distance?: number, point?: Vector2, normal?: Vector2){
constructor(collider?: Collider, fraction?: number, distance?: number, point?: Vector2, normal?: Vector2) {
this.collider = collider;
this.fraction = fraction;
this.distance = distance;
@@ -38,26 +38,39 @@ module es {
this.centroid = Vector2.zero;
}
public setValues(collider: Collider, fraction: number, distance: number, point: Vector2){
public setAllValues(collider: Collider, fraction: number, distance: number, point: Vector2, normal: Vector2) {
this.collider = collider;
this.fraction = fraction;
this.distance = distance;
this.point = point;
}
public setValuesNonCollider(fraction: number, distance: number, point: Vector2, normal: Vector2){
this.fraction = fraction;
this.distance = distance;
this.point = point;
this.normal = normal;
}
public reset(){
public setValues(fraction: number, distance: number, point: Vector2, normal: Vector2) {
this.fraction = fraction;
this.distance = distance;
this.point = point;
this.normal = normal;
}
public reset() {
this.collider = null;
this.fraction = this.distance = 0;
}
public toString(){
public clone(): RaycastHit {
const hit = new RaycastHit();
hit.setAllValues(
this.collider,
this.fraction,
this.distance,
this.point,
this.normal
);
return hit;
}
public toString() {
return `[RaycastHit] fraction: ${this.fraction}, distance: ${this.distance}, normal: ${this.normal}, centroid: ${this.centroid}, point: ${this.point}`;
}
}
+16 -9
View File
@@ -16,20 +16,20 @@ module es {
if (collider.shouldColliderScaleAndRotateWithTransform) {
// 我们只将直线缩放为一个圆,所以我们将使用最大值
let scale = collider.entity.transform.scale;
let hasUnitScale = scale.x == 1 && scale.y == 1;
let maxScale = Math.max(scale.x, scale.y);
const scale = collider.entity.transform.scale;
const hasUnitScale = scale.x === 1 && scale.y === 1;
const maxScale = Math.max(scale.x, scale.y);
this.radius = this._originalRadius * maxScale;
if (collider.entity.transform.rotation != 0) {
if (collider.entity.transform.rotation !== 0) {
// 为了处理偏移原点的旋转,我们只需要将圆心围绕(0,0)在一个圆上移动,我们的偏移量就是0角
let offsetAngle = Math.atan2(collider.localOffset.y, collider.localOffset.x) * MathHelper.Rad2Deg;
let offsetLength = hasUnitScale ? collider._localOffsetLength : Vector2.multiply(collider.localOffset, collider.entity.transform.scale).length();
this.center = MathHelper.pointOnCirlce(Vector2.zero, offsetLength, collider.entity.transform.rotationDegrees + offsetAngle);
const offsetAngle = Math.atan2(collider.localOffset.y, collider.localOffset.x) * MathHelper.Rad2Deg;
const offsetLength = hasUnitScale ? collider._localOffsetLength : collider.localOffset.multiply(collider.entity.transform.scale).magnitude();
this.center = MathHelper.pointOnCirlce(Vector2.zero, offsetLength, collider.entity.transform.rotation + offsetAngle);
}
}
this.position = Vector2.add(collider.entity.transform.position, this.center);
this.position = collider.transform.position.add(this.center);
this.bounds = new Rectangle(this.position.x - this.radius, this.position.y - this.radius, this.radius * 2, this.radius * 2);
}
@@ -67,12 +67,19 @@ module es {
return ShapeCollisionsLine.lineToCircle(start, end, this, hit);
}
public getPointAlongEdge(angle: number): Vector2 {
return new Vector2(
this.position.x + this.radius * Math.cos(angle),
this.position.y + this.radius * Math.sin(angle)
);
}
/**
*
* @param point
*/
public containsPoint(point: Vector2) {
return (Vector2.subtract(point, this.position)).lengthSquared() <= this.radius * this.radius;
return (point.sub(this.position)).lengthSquared() <= this.radius * this.radius;
}
public pointCollidesWithShape(point: Vector2, result: CollisionResult): boolean {
+30 -7
View File
@@ -17,15 +17,39 @@ module es {
*/
public point: Vector2 = Vector2.zero;
public reset() {
this.collider = null;
this.normal.setTo(0, 0);
this.minimumTranslationVector.setTo(0, 0);
if (this.point) {
this.point.setTo(0, 0);
}
}
public cloneTo(cr: CollisionResult) {
cr.collider = this.collider;
cr.normal.setTo(this.normal.x, this.normal.y);
cr.minimumTranslationVector.setTo(
this.minimumTranslationVector.x,
this.minimumTranslationVector.y
);
if (this.point) {
if (!cr.point) {
cr.point = new Vector2(0, 0);
}
cr.point.setTo(this.point.x, this.point.y);
}
}
/**
* x分量
* @param deltaMovement
*/
public removeHorizontal(deltaMovement: Vector2){
public removeHorizontalTranslation(deltaMovement: Vector2){
// 检查是否需要横向移动,如果需要,移除并固定响应
if (Math.sign(this.normal.x) != Math.sign(deltaMovement.x) || (deltaMovement.x == 0 && this.normal.x != 0)){
let responseDistance = this.minimumTranslationVector.length();
let fix = responseDistance / this.normal.y;
if (Math.sign(this.normal.x) !== Math.sign(deltaMovement.x) || (deltaMovement.x === 0 && this.normal.x !== 0)){
const responseDistance = this.minimumTranslationVector.magnitude();
const fix = responseDistance / this.normal.y;
// 检查一些边界情况。因为我们除以法线 使得x == 1和一个非常小的y这将导致一个巨大的固定值
if (Math.abs(this.normal.x) != 1 && Math.abs(fix) < Math.abs(deltaMovement.y * 3)){
@@ -35,9 +59,8 @@ module es {
}
public invertResult() {
this.minimumTranslationVector = Vector2.negate(this.minimumTranslationVector);
this.normal = Vector2.negate(this.normal);
return this;
this.minimumTranslationVector = this.minimumTranslationVector.negate();
this.normal = this.normal.negate();
}
public toString(){
+77 -47
View File
@@ -34,6 +34,10 @@ module es {
this.isBox = isBox;
}
public create(vertCount: number, radius: number) {
Polygon.buildSymmetricalPolygon(vertCount, radius);
}
public _edgeNormals: Vector2[];
/**
@@ -54,7 +58,10 @@ module es {
this.points = points;
this.recalculateCenterAndEdgeNormals();
this._originalPoints = this.points.slice();
this._originalPoints = [];
this.points.forEach(p => {
this._originalPoints.push(p.clone());
});
}
/**
@@ -96,10 +103,10 @@ module es {
* @param radius
*/
public static buildSymmetricalPolygon(vertCount: number, radius: number) {
let verts = new Array(vertCount);
const verts = new Array(vertCount);
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);
}
@@ -111,9 +118,9 @@ module es {
* @param points
*/
public static recenterPolygonVerts(points: Vector2[]) {
let center = this.findPolygonCenter(points);
const center = this.findPolygonCenter(points);
for (let i = 0; i < points.length; i++)
points[i] = Vector2.subtract(points[i], center);
points[i] = points[i].sub(center);
}
/**
@@ -136,13 +143,13 @@ module es {
* @param points
* @param direction
*/
public static getFarthestPointInDirection(points: Vector2[], direction: Vector2): Vector2{
public static getFarthestPointInDirection(points: Vector2[], direction: Vector2): Vector2 {
let index = 0;
let maxDot = Vector2.dot(points[index], direction);
let maxDot = points[index].dot(direction);
for (let i = 1; i < points.length; i ++){
let dot = Vector2.dot(points[i], direction);
if (dot > maxDot){
for (let i = 1; i < points.length; i++) {
let dot = points[i].dot(direction);
if (dot > maxDot) {
maxDot = dot;
index = i;
}
@@ -160,35 +167,35 @@ module es {
* @param distanceSquared
* @param edgeNormal
*/
public static getClosestPointOnPolygonToPoint(points: Vector2[], point: Vector2, distanceSquared: Ref<number>, edgeNormal: Vector2): Vector2 {
distanceSquared.value = Number.MAX_VALUE;
edgeNormal.x = 0;
edgeNormal.y = 0;
let closestPoint = Vector2.zero;
public static getClosestPointOnPolygonToPoint(points: Vector2[], point: Vector2): { distanceSquared: number; edgeNormal: Vector2; closestPoint: Vector2 } {
const res = {
distanceSquared: Number.MAX_VALUE,
edgeNormal: Vector2.zero,
closestPoint: Vector2.zero,
};
let tempDistanceSquared = 0;
for (let i = 0; i < points.length; i++) {
let j = i + 1;
if (j == points.length)
if (j === points.length)
j = 0;
let closest = ShapeCollisionsCircle.closestPointOnLine(points[i], points[j], point);
tempDistanceSquared = Vector2.distanceSquared(point, closest);
const closest = ShapeCollisionsCircle.closestPointOnLine(points[i], points[j], point);
tempDistanceSquared = Vector2.sqrDistance(point, closest);
if (tempDistanceSquared < distanceSquared.value) {
distanceSquared.value = tempDistanceSquared;
closestPoint = closest;
if (tempDistanceSquared < res.distanceSquared) {
res.distanceSquared = tempDistanceSquared;
res.closestPoint = closest;
// 求直线的法线
let line = Vector2.subtract(points[j], points[i]);
edgeNormal.x = -line.y;
edgeNormal.y = line.x;
const line = points[j].sub(points[i]);
res.edgeNormal.x = line.y;
res.edgeNormal.y = -line.x;
}
}
Vector2Ext.normalize(edgeNormal);
return closestPoint;
res.edgeNormal = res.edgeNormal.normalize();
return res;
}
/**
@@ -197,11 +204,11 @@ module es {
* @param originalPoints
* @param rotatedPoints
*/
public static rotatePolygonVerts(radians: number, originalPoints: Vector2[], rotatedPoints: Vector2[]){
public static rotatePolygonVerts(radians: number, originalPoints: Vector2[], rotatedPoints: Vector2[]) {
let cos = Math.cos(radians);
let sin = Math.sin(radians);
for (let i = 0; i < originalPoints.length; i ++){
for (let i = 0; i < originalPoints.length; i++) {
let position = originalPoints[i];
rotatedPoints[i] = new Vector2(position.x * cos + position.y * -sin, position.x * sin + position.y * cos);
}
@@ -209,40 +216,63 @@ module es {
public recalculateBounds(collider: Collider) {
// 如果我们没有旋转或不关心TRS我们使用localOffset作为中心,我们会从那开始
this.center = collider.localOffset.clone();
this.center = collider.localOffset;
if (collider.shouldColliderScaleAndRotateWithTransform) {
let hasUnitScale = true;
let tempMat: Matrix2D;
let combinedMatrix = Matrix2D.createTranslation(-this._polygonCenter.x, -this._polygonCenter.y);
const tempMat: Matrix2D = new Matrix2D();
const combinedMatrix: Matrix2D = new Matrix2D();
Matrix2D.createTranslation(
this._polygonCenter.x * -1,
this._polygonCenter.y * -1,
combinedMatrix
);
if (!collider.entity.transform.scale.equals(Vector2.one)) {
tempMat = Matrix2D.createScale(collider.entity.transform.scale.x, collider.entity.transform.scale.y);
combinedMatrix = combinedMatrix.multiply(tempMat);
Matrix2D.createScale(
collider.entity.scale.x,
collider.entity.scale.y,
tempMat
);
Matrix2D.multiply(combinedMatrix, tempMat, combinedMatrix);
hasUnitScale = false;
// 缩放偏移量并将其设置为中心。如果我们有旋转,它会在下面重置
this.center = Vector2.multiply(collider.localOffset, collider.entity.transform.scale);
const scaledOffset = new Vector2(
collider.localOffset.x * collider.entity.scale.x,
collider.localOffset.y * collider.entity.scale.y
);
this.center = scaledOffset;
}
if (collider.entity.transform.rotation != 0) {
tempMat = Matrix2D.createRotation(collider.entity.transform.rotation);
combinedMatrix = combinedMatrix.multiply(tempMat);
Matrix2D.createRotation(
MathHelper.Deg2Rad * collider.entity.rotation,
tempMat
);
Matrix2D.multiply(combinedMatrix, tempMat, combinedMatrix);
// 为了处理偏移原点的旋转我们只需要将圆心在(0,0)附近移动
// 我们的偏移使角度为0我们还需要处理这里的比例所以我们先对偏移进行缩放以得到合适的长度。
let offsetAngle = Math.atan2(collider.localOffset.y * collider.entity.transform.scale.y, collider.localOffset.x * collider.entity.transform.scale.x) * MathHelper.Rad2Deg;
let offsetLength = hasUnitScale ? collider._localOffsetLength :
Vector2.multiply(collider.localOffset, collider.entity.transform.scale).length();
const offsetAngle = Math.atan2(collider.localOffset.y * collider.entity.transform.scale.y, collider.localOffset.x * collider.entity.transform.scale.x) * MathHelper.Rad2Deg;
const offsetLength = hasUnitScale ? collider._localOffsetLength :
collider.localOffset.multiply(collider.entity.transform.scale).magnitude();
this.center = MathHelper.pointOnCirlce(Vector2.zero, offsetLength,
collider.entity.transform.rotationDegrees + offsetAngle);
}
tempMat = Matrix2D.createTranslation(this._polygonCenter.x, this._polygonCenter.y);
combinedMatrix = combinedMatrix.multiply(tempMat);
Matrix2D.createTranslation(
this._polygonCenter.x,
this._polygonCenter.y,
tempMat
);
Matrix2D.multiply(combinedMatrix, tempMat, combinedMatrix);
// 最后变换原始点
Vector2Ext.transform(this._originalPoints, combinedMatrix, this.points);
this.points = [];
this._originalPoints.forEach(p => {
this.points.push(p.transform(combinedMatrix));
});
this.isUnrotated = collider.entity.transform.rotation == 0;
@@ -251,9 +281,9 @@ module es {
this._areEdgeNormalsDirty = true;
}
this.position = Vector2.add(collider.entity.transform.position, this.center);
this.position = collider.transform.position.add(this.center);
this.bounds = Rectangle.rectEncompassingPoints(this.points);
this.bounds.location = Vector2.add(this.bounds.location, this.position);
this.bounds.location = this.bounds.location.add(this.position);
}
public overlaps(other: Shape) {
@@ -301,11 +331,11 @@ module es {
*/
public containsPoint(point: Vector2) {
// 将点归一化到多边形坐标空间中
point.subtract(this.position);
point = point.sub(this.position);
let isInside = false;
for (let i = 0, j = this.points.length - 1; i < this.points.length; j = i++) {
if (((this.points[i].y > point.y) != (this.points[j].y > point.y)) &&
if (((this.points[i].y > point.y) !== (this.points[j].y > point.y)) &&
(point.x < (this.points[j].x - this.points[i].x) * (point.y - this.points[i].y) / (this.points[j].y - this.points[i].y) +
this.points[i].x)) {
isInside = !isInside;
+11 -10
View File
@@ -1,20 +1,21 @@
module es {
export class RealtimeCollisions {
public static intersectMovingCircleBox(s: Circle, b: Box, movement: Vector2, time: Ref<number>): boolean {
public static intersectMovingCircleBox(s: Circle, b: Box, movement: Vector2, time: number): boolean {
// 计算将b按球面半径r扩大后的AABB
let e = b.bounds.clone();
const e = b.bounds;
e.inflate(s.radius, s.radius);
// 将射线与展开的矩形e相交,如果射线错过了e,则以无交点退出,否则得到交点p和时间t作为结果。
let ray = new Ray2D(Vector2.subtract(s.position, movement), s.position);
if (!e.rayIntersects(ray, time) && time.value > 1)
const ray = new Ray2D(s.position.sub(movement), s.position);
const res = e.rayIntersects(ray);
if (!res.intersected && res.distance > 1)
return false;
// 求交点
let point = Vector2.add(ray.start, Vector2.multiply(ray.direction, new Vector2(time.value)));
const point = ray.start.add(ray.direction.scale(time));
// 计算交点p位于b的哪个最小面和最大面之外。注意,u和v不能有相同的位集,它们之间必须至少有一个位集。
let u, v = 0;
let u: number, v: number = 0;
if (point.x < b.bounds.left)
u |= 1;
if (point.x > b.bounds.right)
@@ -25,7 +26,7 @@ module es {
v |= 2;
// 'or'将所有的比特集合在一起,形成一个比特掩码(注意u + v == u | v)
let m = u + v;
const m = u + v;
// 如果这3个比特都被设置,那么该点就在顶点区域内。
if (m == 3){
@@ -49,7 +50,7 @@ module es {
* @param n
*/
public static corner(b: Rectangle, n: number){
let p = new Vector2();
let p = es.Vector2.zero;
p.x = (n & 1) == 0 ? b.right : b.left;
p.y = (n & 1) == 0 ? b.bottom : b.top;
return p;
@@ -66,8 +67,8 @@ module es {
point = box.bounds.getClosestPointOnRectangleToPoint(cirlce.position);
// 圆和方块相交,如果圆心到点的距离小于圆的半径,则圆和方块相交
let v = Vector2.subtract(point, cirlce.position);
let dist = Vector2.dot(v, v);
const v = point.sub(cirlce.position);
const dist = v.dot(v);
return dist <= cirlce.radius * cirlce.radius;
}
@@ -1,7 +1,7 @@
module es {
export class ShapeCollisionsBox {
public static boxToBox(first: Box, second: Box, result: CollisionResult): boolean {
let minkowskiDiff = this.minkowskiDifference(first, second);
const minkowskiDiff = this.minkowskiDifference(first, second);
if (minkowskiDiff.contains(0, 0)) {
// 计算MTV。如果它是零,我们就可以称它为非碰撞
result.minimumTranslationVector = minkowskiDiff.getClosestPointOnBoundsToOrigin();
@@ -9,8 +9,8 @@ module es {
if (result.minimumTranslationVector.equals(Vector2.zero))
return false;
result.normal = new Vector2(-result.minimumTranslationVector.x, -result.minimumTranslationVector.y);
result.normal.normalize();
result.normal = result.minimumTranslationVector.scale(-1);
result.normal = result.normal.normalize();
return true;
}
@@ -27,29 +27,29 @@ module es {
*/
public static boxToBoxCast(first: Box, second: Box, movement: Vector2, hit: RaycastHit): boolean {
// 首先,我们检查是否有重叠。如果有重叠,我们就不做扫描测试
let minkowskiDiff = this.minkowskiDifference(first, second);
const minkowskiDiff = this.minkowskiDifference(first, second);
if (minkowskiDiff.contains(0, 0)) {
// 计算MTV。如果它是零,我们就可以称它为非碰撞
let mtv = minkowskiDiff.getClosestPointOnBoundsToOrigin();
const mtv = minkowskiDiff.getClosestPointOnBoundsToOrigin();
if (mtv.equals(Vector2.zero))
return false;
hit.normal = new Vector2(-mtv.x);
hit.normal.normalize();
hit.normal = new Vector2(-mtv.x, -mtv.y);
hit.normal = hit.normal.normalize();
hit.distance = 0;
hit.fraction = 0;
return true;
} else {
// 射线投射移动矢量
let ray = new Ray2D(Vector2.zero, new Vector2(-movement.x));
let fraction = new Ref(0);
if (minkowskiDiff.rayIntersects(ray, fraction) && fraction.value <= 1) {
hit.fraction = fraction.value;
hit.distance = movement.length() * fraction.value;
hit.normal = new Vector2(-movement.x, -movement.y);
hit.normal.normalize();
hit.centroid = Vector2.add(first.bounds.center, Vector2.multiply(movement, new Vector2(fraction.value)));
const ray = new Ray2D(Vector2.zero, movement.scale(-1));
const res = minkowskiDiff.rayIntersects(ray);
if (res.intersected && res.distance <= 1) {
hit.fraction = res.distance;
hit.distance = movement.magnitude() * res.distance;
hit.normal = movement.scale(-1);
hit.normal = hit.normal.normalize();
hit.centroid = first.bounds.center.add(movement.scale(res.distance));
return true;
}
@@ -61,9 +61,9 @@ module es {
private static minkowskiDifference(first: Box, second: Box): Rectangle {
// 我们需要第一个框的左上角
// 碰撞器只会修改运动的位置所以我们需要用位置来计算出运动是什么。
let positionOffset = Vector2.subtract(first.position, Vector2.add(first.bounds.location, new Vector2(first.bounds.size.x / 2, first.bounds.size.y / 2)));
let topLeft = Vector2.subtract(Vector2.add(first.bounds.location, positionOffset), second.bounds.max);
let fullSize = Vector2.add(first.bounds.size, second.bounds.size);
const positionOffset = first.position.sub(first.bounds.center);
const topLeft = first.bounds.location.add(positionOffset.sub(second.bounds.max));
const fullSize = first.bounds.size.add(second.bounds.size);
return new Rectangle(topLeft.x, topLeft.y, fullSize.x, fullSize.y)
}
@@ -1,19 +1,46 @@
module es {
export class ShapeCollisionsCircle {
public static circleToCircle(first: Circle, second: Circle, result: CollisionResult = new CollisionResult()): boolean {
let distanceSquared = Vector2.distanceSquared(first.position, second.position);
let sumOfRadii = first.radius + second.radius;
let collided = distanceSquared < sumOfRadii * sumOfRadii;
if (collided) {
result.normal = Vector2.normalize(Vector2.subtract(first.position, second.position));
let depth = sumOfRadii - Math.sqrt(distanceSquared);
result.minimumTranslationVector = Vector2.multiply(new Vector2(-depth), result.normal);
result.point = Vector2.add(second.position, Vector2.multiply(result.normal, new Vector2(second.radius)));
public static circleToCircleCast(first: Circle,second: Circle,deltaMovement: Vector2,hit: RaycastHit): boolean {
let endPointOfCast = first.position.add(deltaMovement);
let d = this.closestPointOnLine(first.position,endPointOfCast,second.position);
// 这可以得到实际的碰撞点,可能有用也可能没用,所以我们暂时把它留在这里
// let collisionPointX = ((first.position.x * second.radius) + (second.position.x * first.radius)) / sumOfRadii;
// let collisionPointY = ((first.position.y * second.radius) + (second.position.y * first.radius)) / sumOfRadii;
// result.point = new Vector2(collisionPointX, collisionPointY);
let closestDistanceSquared = Vector2.sqrDistance(second.position, d);
const sumOfRadiiSquared = (first.radius + second.radius) * (first.radius + second.radius);
if (closestDistanceSquared <= sumOfRadiiSquared) {
const normalizedDeltaMovement = deltaMovement.normalize();
if (d === endPointOfCast) {
endPointOfCast = first.position.add(
deltaMovement.add(normalizedDeltaMovement.scale(second.radius))
);
d = this.closestPointOnLine(
first.position,
endPointOfCast,
second.position
);
closestDistanceSquared = Vector2.sqrDistance(second.position, d);
}
const backDist = Math.sqrt(sumOfRadiiSquared - closestDistanceSquared);
hit.centroid = d.sub(normalizedDeltaMovement.scale(backDist));
hit.normal = hit.centroid.sub(second.position).normalize();
hit.fraction = (hit.centroid.x - first.position.x) / deltaMovement.x;
hit.distance = Vector2.distance(first.position, hit.centroid);
hit.point = second.position.add(hit.normal.scale(second.radius));
return true;
}
return false;
}
public static circleToCircle(first: Circle, second: Circle, result: CollisionResult = new CollisionResult()): boolean {
const distanceSquared = Vector2.sqrDistance(first.position, second.position);
const sumOfRadii = first.radius + second.radius;
const collided = distanceSquared < sumOfRadii * sumOfRadii;
if (collided) {
result.normal = first.position.sub(second.position).normalize();
const depth = sumOfRadii - Math.sqrt(distanceSquared);
result.minimumTranslationVector = result.normal.scale(-depth);
result.point = second.position.add(result.normal.scale(second.radius));
return true;
}
@@ -28,31 +55,31 @@ module es {
* @param result
*/
public static circleToBox(circle: Circle, box: Box, result: CollisionResult = new CollisionResult()): boolean {
let closestPointOnBounds = box.bounds.getClosestPointOnRectangleBorderToPoint(circle.position, result.normal);
const closestPointOnBounds = box.bounds.getClosestPointOnRectangleBorderToPoint(circle.position, result.normal);
// 先处理中心在盒子里的圆,如果我们是包含的, 它的成本更低,
if (box.containsPoint(circle.position)) {
result.point = closestPointOnBounds.clone();
result.point = closestPointOnBounds;
// 计算MTV。找出安全的、非碰撞的位置,并从中得到MTV
let safePlace = Vector2.add(closestPointOnBounds, Vector2.multiply(result.normal, new Vector2(circle.radius)));
result.minimumTranslationVector = Vector2.subtract(circle.position, safePlace);
const safePlace = closestPointOnBounds.add(result.normal.scale(circle.radius));
result.minimumTranslationVector = circle.position.sub(safePlace);
return true;
}
let sqrDistance = Vector2.distanceSquared(closestPointOnBounds, circle.position);
const sqrDistance = Vector2.sqrDistance(closestPointOnBounds, circle.position);
// 看框上的点距圆的半径是否小于圆的半径
if (sqrDistance == 0) {
result.minimumTranslationVector = Vector2.multiply(result.normal, new Vector2(circle.radius));
result.minimumTranslationVector = result.normal.scale(circle.radius);
} else if (sqrDistance <= circle.radius * circle.radius) {
result.normal = Vector2.subtract(circle.position, closestPointOnBounds);
let depth = result.normal.length() - circle.radius;
result.normal = circle.position.sub(closestPointOnBounds);
const depth = result.normal.magnitude() - circle.radius;
result.point = closestPointOnBounds;
Vector2Ext.normalize(result.normal);
result.minimumTranslationVector = Vector2.multiply(new Vector2(depth), result.normal);
result.normal = result.normal.normalize();
result.minimumTranslationVector = result.normal.scale(depth);
return true;
}
@@ -62,47 +89,47 @@ module es {
public static circleToPolygon(circle: Circle, polygon: Polygon, result: CollisionResult = new CollisionResult()): boolean {
// 圆圈在多边形中的位置坐标
let poly2Circle = Vector2.subtract(circle.position, polygon.position);
const poly2Circle = circle.position.sub(polygon.position);
// 首先,我们需要找到从圆到多边形的最近距离
let distanceSquared = new Ref(0);
let closestPoint = Polygon.getClosestPointOnPolygonToPoint(polygon.points, poly2Circle, distanceSquared, result.normal);
const res = Polygon.getClosestPointOnPolygonToPoint(polygon.points,poly2Circle);
result.normal = res.edgeNormal;
// 确保距离的平方小于半径的平方,否则我们不会相撞。
// 请注意,如果圆完全包含在多边形中,距离可能大于半径。
// 正因为如此,我们还要确保圆的位置不在多边形内。
let circleCenterInsidePoly = polygon.containsPoint(circle.position);
if (distanceSquared.value > circle.radius * circle.radius && !circleCenterInsidePoly)
const circleCenterInsidePoly = polygon.containsPoint(circle.position);
if (res.distanceSquared > circle.radius * circle.radius && !circleCenterInsidePoly)
return false;
// 算出MTV。我们要注意处理完全包含在多边形中的圆或包含其中心的圆
let mtv: Vector2;
if (circleCenterInsidePoly) {
mtv = Vector2.multiply(result.normal, new Vector2(Math.sqrt(distanceSquared.value) - circle.radius));
mtv = result.normal.scale(Math.sqrt(res.distanceSquared) - circle.radius);
} else {
// 如果我们没有距离,这意味着圆心在多边形的边缘上。只需根据它的半径移动它
if (distanceSquared.value == 0) {
mtv = new Vector2(result.normal.x * circle.radius, result.normal.y * circle.radius);
if (res.distanceSquared === 0) {
mtv = result.normal.scale(circle.radius);
} else {
let distance = Math.sqrt(distanceSquared.value);
mtv = Vector2.subtract(new Vector2(-1), Vector2.subtract(poly2Circle, closestPoint))
.multiply(new Vector2((circle.radius - distance) / distance));
const distance = Math.sqrt(res.distanceSquared);
mtv = poly2Circle
.sub(res.closestPoint)
.scale(((circle.radius - distance) / distance) * -1);
}
}
result.minimumTranslationVector = mtv;
result.point = Vector2.add(closestPoint, polygon.position);
result.point = res.closestPoint.add(polygon.position);
return true;
}
public static closestPointOnLine(lineA: Vector2, lineB: Vector2, closestTo: Vector2): Vector2 {
let v = Vector2.subtract(lineB, lineA);
let w = Vector2.subtract(closestTo, lineA);
let t = Vector2.dot(w, v) / Vector2.dot(v, v);
const v = lineB.sub(lineA);
const w = closestTo.sub(lineA);
let t = w.dot(v) / v.dot(v);
t = MathHelper.clamp(t, 0, 1);
return Vector2.add(lineA, Vector2.multiply(v, new Vector2(t)));
return lineA.add(v.scaleEqual(t));
}
}
}
@@ -7,20 +7,20 @@ module es {
let hasIntersection = false;
for (let j = polygon.points.length - 1, i = 0; i < polygon.points.length; j = i, i ++){
let edge1 = Vector2.add(polygon.position, polygon.points[j]);
let edge2 = Vector2.add(polygon.position, polygon.points[i]);
let intersection: Vector2 = Vector2.zero;
if (this.lineToLine(edge1, edge2, start, end, intersection)){
const edge1 = Vector2.add(polygon.position, polygon.points[j]);
const edge2 = Vector2.add(polygon.position, polygon.points[i]);
const intersection: Vector2 = Vector2.zero;
if (ShapeCollisionsLine.lineToLine(edge1, edge2, start, end, intersection)){
hasIntersection = true;
// TODO: 这是得到分数的正确和最有效的方法吗?
// 先检查x分数。如果是NaN,就用y代替
let distanceFraction = (intersection.x - start.x) / (end.x - start.x);
if (Number.isNaN(distanceFraction) || Number.isFinite(distanceFraction))
if (Number.isNaN(distanceFraction) || Math.abs(distanceFraction) == Infinity)
distanceFraction = (intersection.y - start.y) / (end.y - start.y);
if (distanceFraction < fraction){
let edge = Vector2.subtract(edge2, edge1);
const edge = edge2.sub(edge1);
normal = new Vector2(edge.y, -edge.x);
fraction = distanceFraction;
intersectionPoint = intersection;
@@ -29,9 +29,9 @@ module es {
}
if (hasIntersection){
normal.normalize();
let distance = Vector2.distance(start, intersectionPoint);
hit.setValuesNonCollider(fraction, distance, intersectionPoint, normal);
normal = normal.normalize();
const distance = Vector2.distance(start, intersectionPoint);
hit.setValues(fraction, distance, intersectionPoint, normal);
return true;
}
@@ -39,35 +39,37 @@ module es {
}
public static lineToLine(a1: Vector2, a2: Vector2, b1: Vector2, b2: Vector2, intersection: Vector2){
let b = Vector2.subtract(a2, a1);
let d = Vector2.subtract(b2, b1);
let bDotDPerp = b.x * d.y - b.y * d.x;
const b = a2.sub(a1);
const d = b2.sub(b1);
const bDotDPerp = b.x * d.y - b.y * d.x;
// 如果b*d = 0,表示这两条直线平行,因此有无穷个交点
if (bDotDPerp == 0)
return false;
let c = Vector2.subtract(b1, a1);
let t = (c.x * d.y - c.y * d.x) / bDotDPerp;
const c = b1.sub(a1);
const t = (c.x * d.y - c.y * d.x) / bDotDPerp;
if (t < 0 || t > 1)
return false;
let u = (c.x * b.y - c.y * b.x) / bDotDPerp;
const u = (c.x * b.y - c.y * b.x) / bDotDPerp;
if (u < 0 || u > 1)
return false;
intersection = Vector2.add(a1, Vector2.multiply(new Vector2(t), b));
const r = a1.add(b.scale(t));
intersection.x = r.x;
intersection.y = r.y;
return true;
}
public static lineToCircle(start: Vector2, end: Vector2, s: Circle, hit: RaycastHit): boolean{
// 计算这里的长度并分别对d进行标准化,因为如果我们命中了我们需要它来得到分数
let lineLength = Vector2.distance(start, end);
let d = Vector2.divide(Vector2.subtract(end, start), new Vector2(lineLength));
let m = Vector2.subtract(start, s.position);
let b = Vector2.dot(m, d);
let c = Vector2.dot(m, m) - s.radius * s.radius;
const lineLength = Vector2.distance(start, end);
const d = Vector2.divideScaler(end.sub(start), lineLength);
const m = start.sub(s.position);
const b = m.dot(d);
const c = m.dot(m) - s.radius * s.radius;
// 如果r的原点在s之外,(c>0)和r指向s (b>0) 则返回
if (c > 0 && b > 0)
@@ -85,9 +87,9 @@ module es {
if (hit.fraction < 0)
hit.fraction = 0;
hit.point = Vector2.add(start, Vector2.multiply(new Vector2(hit.fraction), d));
hit.point = start.add(d.scale(hit.fraction));
hit.distance = Vector2.distance(start, hit.point);
hit.normal = Vector2.normalize(Vector2.subtract(hit.point, s.position));
hit.normal = hit.point.sub(s.position).normalize();
hit.fraction = hit.distance / lineLength;
return true;
@@ -1,14 +1,14 @@
module es {
export class ShapeCollisionsPoint {
public static pointToCircle(point: Vector2, circle: Circle, result: CollisionResult): boolean {
let distanceSquared = Vector2.distanceSquared(point, circle.position);
let distanceSquared = Vector2.sqrDistance(point, circle.position);
let sumOfRadii = 1 + circle.radius;
let collided = distanceSquared < sumOfRadii * sumOfRadii;
if (collided) {
result.normal = Vector2.normalize(Vector2.subtract(point, circle.position));
result.normal = point.sub(circle.position).normalize();
let depth = sumOfRadii - Math.sqrt(distanceSquared);
result.minimumTranslationVector = Vector2.multiply(new Vector2(-depth, -depth), result.normal);
result.point = Vector2.add(circle.position, Vector2.multiply(result.normal, new Vector2(circle.radius, circle.radius)));
result.minimumTranslationVector = result.normal.scale(-depth);;
result.point = circle.position.add(result.normal.scale(circle.radius));
return true;
}
@@ -16,11 +16,11 @@ module es {
return false;
}
public static pointToBox(point: Vector2, box: Box, result: CollisionResult = new CollisionResult()){
if (box.containsPoint(point)){
public static pointToBox(point: Vector2, box: Box, result: CollisionResult = new CollisionResult()) {
if (box.containsPoint(point)) {
// 在方框的空间里找到点
result.point = box.bounds.getClosestPointOnRectangleBorderToPoint(point, result.normal);
result.minimumTranslationVector = Vector2.subtract(point, result.point);
result.minimumTranslationVector = point.sub(result.point);
return true;
}
@@ -30,12 +30,15 @@ module es {
public static pointToPoly(point: Vector2, poly: Polygon, result: CollisionResult = new CollisionResult()): boolean {
if (poly.containsPoint(point)) {
let distanceSquared = new Ref(0);
let closestPoint = Polygon.getClosestPointOnPolygonToPoint(poly.points, Vector2.subtract(point, poly.position), distanceSquared, result.normal);
result.minimumTranslationVector = new Vector2(result.normal.x * Math.sqrt(distanceSquared.value), result.normal.y * Math.sqrt(distanceSquared.value));
result.point = Vector2.add(closestPoint, poly.position);
const res = Polygon.getClosestPointOnPolygonToPoint(
poly.points,
point.sub(poly.position)
);
result.normal = res.edgeNormal;
result.minimumTranslationVector = result.normal.scale(
Math.sqrt(res.distanceSquared)
);
result.point = res.closestPoint.sub(poly.position);
return true;
}
@@ -9,39 +9,31 @@ module es {
public static polygonToPolygon(first: Polygon, second: Polygon, result: CollisionResult): boolean {
let isIntersecting = true;
let firstEdges = first.edgeNormals.slice();
let secondEdges = second.edgeNormals.slice();
const firstEdges = first.edgeNormals;
const secondEdges = second.edgeNormals;
let minIntervalDistance = Number.POSITIVE_INFINITY;
let translationAxis = new Vector2();
let polygonOffset = Vector2.subtract(first.position, second.position);
let translationAxis = Vector2.zero;
let polygonOffset = first.position.sub(second.position);
let axis: Vector2;
// 循环穿过两个多边形的所有边
for (let edgeIndex = 0; edgeIndex < firstEdges.length + secondEdges.length; edgeIndex++) {
// 1. 找出当前多边形是否相交
// 多边形的归一化轴垂直于缓存给我们的当前边
if (edgeIndex < firstEdges.length) {
axis = firstEdges[edgeIndex];
} else {
axis = secondEdges[edgeIndex - firstEdges.length];
}
axis = edgeIndex < firstEdges.length ? firstEdges[edgeIndex] : secondEdges[edgeIndex - firstEdges.length];
// 求多边形在当前轴上的投影
let minA = new Ref(0);
let minB = new Ref(0);
let maxA = new Ref(0);
let maxB = new Ref(0);
let intervalDist = 0;
this.getInterval(axis, first, minA, maxA);
this.getInterval(axis, second, minB, maxB);
let {min: minA, max: maxA} = this.getInterval(axis, first);
const {min: minB, max: maxB} = this.getInterval(axis, second);
// 将区间设为第二个多边形的空间。由轴上投影的位置差偏移。
let relativeIntervalOffset = Vector2.dot(polygonOffset, axis);
minA.value += relativeIntervalOffset;
maxA.value += relativeIntervalOffset;
const relativeIntervalOffset = polygonOffset.dot(axis);
minA += relativeIntervalOffset;
maxA += relativeIntervalOffset;
// 检查多边形投影是否正在相交
intervalDist = this.intervalDistance(minA.value, maxA.value, minB.value, maxB.value);
intervalDist = this.intervalDistance(minA, maxA, minB, maxB);
if (intervalDist > 0)
isIntersecting = false;
@@ -56,16 +48,16 @@ module es {
intervalDist = Math.abs(intervalDist);
if (intervalDist < minIntervalDistance) {
minIntervalDistance = intervalDist;
translationAxis = axis;
translationAxis.setTo(axis.x, axis.y);
if (Vector2.dot(translationAxis, polygonOffset) < 0)
translationAxis = new Vector2(-translationAxis.x, -translationAxis.y);
if (translationAxis.dot(polygonOffset) < 0)
translationAxis = translationAxis.scale(-1);
}
}
// 利用最小平移向量对多边形进行推入。
result.normal = translationAxis;
result.minimumTranslationVector = new Vector2(-translationAxis.x * minIntervalDistance, -translationAxis.y * minIntervalDistance);
result.minimumTranslationVector = translationAxis.scale(-minIntervalDistance);
return true;
}
@@ -77,18 +69,21 @@ module es {
* @param min
* @param max
*/
public static getInterval(axis: Vector2, polygon: Polygon, min: Ref<number>, max: Ref<number>) {
let dot = Vector2.dot(polygon.points[0], axis);
min.value = max.value = dot;
public static getInterval(axis: Vector2, polygon: Polygon): {min: number, max: number} {
const res = {min: 0, max: 0};
let dot: number;
dot = polygon.points[0].dot(axis);
res.max = dot;
res.min = dot;
for (let i = 1; i < polygon.points.length; i++) {
dot = Vector2.dot(polygon.points[i], axis);
if (dot < min.value) {
min.value = dot;
} else if (dot > max.value) {
max.value = dot;
dot = polygon.points[i].dot(axis);
if (dot < res.min) {
res.min = dot;
} else if (dot > res.max) {
res.max = dot;
}
}
return res;
}
/**
@@ -98,7 +93,7 @@ module es {
* @param minB
* @param maxB
*/
public static intervalDistance(minA: number, maxA: number, minB: number, maxB) {
public static intervalDistance(minA: number, maxA: number, minB: number, maxB: number) {
if (minA < minB)
return minB - maxA;
+75 -50
View File
@@ -22,7 +22,7 @@ module es {
/**
*
*/
public _cellDict: NumberDictionary = new NumberDictionary();
public _cellDict: NumberDictionary<Collider> = new NumberDictionary<Collider>();
/**
* HashSet
*/
@@ -94,27 +94,42 @@ module es {
this._cellDict.clear();
}
public debugDraw(secondsToDisplay: number) {
for (let x = this.gridBounds.x; x <= this.gridBounds.right; x++) {
for (let y = this.gridBounds.y; y <= this.gridBounds.bottom; y++) {
let cell = this.cellAtPosition(x, y);
if (cell != null && cell.length > 0)
this.debugDrawCellDetails(x, y, secondsToDisplay);
}
}
}
private debugDrawCellDetails(x: number, y: number, secondsToDisplay: number = 0.5) {
Graphics.instance.batcher.drawHollowRect(x * this._cellSize, y * this._cellSize, this._cellSize, this._cellSize, new Color(255, 0, 0), secondsToDisplay);
Graphics.instance.batcher.end();
}
/**
*
* @param bounds
* @param excludeCollider
* @param layerMask
*/
public aabbBroadphase(bounds: Rectangle, excludeCollider: Collider, layerMask: number): Set<Collider> {
public aabbBroadphase(bounds: Rectangle, excludeCollider: Collider, layerMask: number): Collider[] {
this._tempHashSet.clear();
let p1 = this.cellCoords(bounds.x, bounds.y);
let p2 = this.cellCoords(bounds.right, bounds.bottom);
const p1 = this.cellCoords(bounds.x, bounds.y);
const 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 == null)
const cell = this.cellAtPosition(x, y);
if (!cell)
continue;
// 当cell不为空。循环并取回所有碰撞器
for (let i = 0; i < cell.length; i++) {
let collider = cell[i];
const collider = cell[i];
// 如果它是自身或者如果它不匹配我们的层掩码 跳过这个碰撞器
if (collider == excludeCollider || !Flags.isFlagSet(layerMask, collider.physicsLayer.value))
@@ -127,7 +142,7 @@ module es {
}
}
return this._tempHashSet;
return Array.from(this._tempHashSet);
}
/**
@@ -139,9 +154,9 @@ module es {
* @param hits
* @param layerMask
*/
public linecast(start: Vector2, end: Vector2, hits: RaycastHit[], layerMask: number){
public linecast(start: Vector2, end: Vector2, hits: RaycastHit[], layerMask: number, ignoredColliders: Set<Collider>) {
let ray = new Ray2D(start, end);
this._raycastParser.start(ray, hits, layerMask);
this._raycastParser.start(ray, hits, layerMask, ignoredColliders);
// 获取我们的起始/结束位置,与我们的网格在同一空间内
let currentCell = this.cellCoords(start.x, start.y);
@@ -174,24 +189,24 @@ module es {
// 开始遍历并返回交叉单元格。
let cell = this.cellAtPosition(currentCell.x, currentCell.y);
if (cell && this._raycastParser.checkRayIntersection(currentCell.x, currentCell.y, cell)){
if (cell != null && this._raycastParser.checkRayIntersection(currentCell.x, currentCell.y, cell)) {
this._raycastParser.reset();
return this._raycastParser.hitCounter;
}
while (currentCell.x != lastCell.x || currentCell.y != lastCell.y){
if (tMaxX < tMaxY){
currentCell.x = Math.floor(MathHelper.approach(currentCell.x, lastCell.x, Math.abs(stepX)));
while (currentCell.x != lastCell.x || currentCell.y != lastCell.y) {
if (tMaxX < tMaxY) {
currentCell.x = MathHelper.approach(currentCell.x, lastCell.x, Math.abs(stepX));
tMaxX += tDeltaX;
}else{
currentCell.y = Math.floor(MathHelper.approach(currentCell.y, lastCell.y, Math.abs(stepY)));
} else {
currentCell.y = MathHelper.approach(currentCell.y, lastCell.y, Math.abs(stepY));
tMaxY += tDeltaY;
}
cell = this.cellAtPosition(currentCell.x, currentCell.y);
if (cell && this._raycastParser.checkRayIntersection(currentCell.x, currentCell.y, cell)){
if (cell && this._raycastParser.checkRayIntersection(currentCell.x, currentCell.y, cell)) {
this._raycastParser.reset();
return this._raycastParser.hitCounter;
}
@@ -202,6 +217,7 @@ module es {
return this._raycastParser.hitCounter;
}
/**
*
* @param rect
@@ -210,23 +226,23 @@ module es {
*/
public overlapRectangle(rect: Rectangle, results: Collider[], layerMask: number) {
this._overlapTestBox.updateBox(rect.width, rect.height);
this._overlapTestBox.position = rect.location;
this._overlapTestBox.position = rect.location.clone();
let resultCounter = 0;
let potentials = this.aabbBroadphase(rect, null, layerMask);
for (let collider of potentials) {
if (collider instanceof BoxCollider) {
results[resultCounter] = collider;
resultCounter ++;
} else if(collider instanceof CircleCollider) {
resultCounter++;
} else if (collider instanceof CircleCollider) {
if (Collisions.rectToCircle(rect, collider.bounds.center, collider.bounds.width * 0.5)) {
results[resultCounter] = collider;
resultCounter ++;
resultCounter++;
}
} else if(collider instanceof PolygonCollider) {
} else if (collider instanceof PolygonCollider) {
if (collider.shape.overlaps(this._overlapTestBox)) {
results[resultCounter] = collider;
resultCounter ++;
resultCounter++;
}
} else {
throw new Error("overlapRectangle对这个类型没有实现!");
@@ -247,17 +263,19 @@ module es {
* @param layerMask
*/
public overlapCircle(circleCenter: Vector2, radius: number, results: Collider[], layerMask): number {
let bounds = new Rectangle(circleCenter.x - radius, circleCenter.y - radius, radius * 2, radius * 2);
const bounds = new Rectangle(circleCenter.x - radius, circleCenter.y - radius, radius * 2, radius * 2);
this._overlapTestCircle.radius = radius;
this._overlapTestCircle.position = circleCenter;
let resultCounter = 0;
let potentials = this.aabbBroadphase(bounds, null, layerMask);
const potentials = this.aabbBroadphase(bounds, null, layerMask);
for (let collider of potentials) {
if (collider instanceof BoxCollider) {
results[resultCounter] = collider;
resultCounter++;
if (collider.shape.overlaps(this._overlapTestCircle)) {
results[resultCounter] = collider;
resultCounter++;
}
} else if (collider instanceof CircleCollider) {
if (collider.shape.overlaps(this._overlapTestCircle)) {
results[resultCounter] = collider;
@@ -273,7 +291,7 @@ module es {
}
// 如果我们所有的结果数据有了则返回
if (resultCounter == results.length)
if (resultCounter === results.length)
return resultCounter;
}
@@ -308,14 +326,10 @@ module es {
}
}
/**
* Unit32
* intint xy坐标散列到单个Uint32键中使O(1)
*/
export class NumberDictionary {
public _store: Map<number, Collider[]> = new Map<number, Collider[]>();
export class NumberDictionary<T> {
public _store: Map<string, T[]> = new Map<string, T[]>();
public add(x: number, y: number, list: Collider[]) {
public add(x: number, y: number, list: T[]) {
this._store.set(this.getKey(x, y), list);
}
@@ -323,7 +337,7 @@ module es {
* 使
* @param obj
*/
public remove(obj: Collider) {
public remove(obj: T) {
this._store.forEach(list => {
let linqList = new es.List(list);
if (linqList.contains(obj))
@@ -331,12 +345,12 @@ module es {
})
}
public tryGetValue(x: number, y: number): Collider[] {
public tryGetValue(x: number, y: number): T[] {
return this._store.get(this.getKey(x, y));
}
public getKey(x: number, y: number){
return x << 16 | (y >>> 0);
public getKey(x: number, y: number) {
return `${x}_${y}`;
}
/**
@@ -350,7 +364,11 @@ module es {
export class RaycastResultParser {
public hitCounter: number;
public static compareRaycastHits = (a: RaycastHit, b: RaycastHit) => {
return a.distance - b.distance;
if (a.distance !== b.distance) {
return a.distance - b.distance;
} else {
return a.collider.castSortOrder - b.collider.castSortOrder;
}
};
public _hits: RaycastHit[];
@@ -359,11 +377,13 @@ module es {
public _cellHits: RaycastHit[] = [];
public _ray: Ray2D;
public _layerMask: number;
private _ignoredColliders: Set<Collider>;
public start(ray: Ray2D, hits: RaycastHit[], layerMask: number) {
public start(ray: Ray2D, hits: RaycastHit[], layerMask: number, ignoredColliders: Set<Collider>) {
this._ray = ray;
this._hits = hits;
this._layerMask = layerMask;
this._ignoredColliders = ignoredColliders;
this.hitCounter = 0;
}
@@ -374,9 +394,8 @@ module es {
* @param cell
*/
public checkRayIntersection(cellX: number, cellY: number, cell: Collider[]): boolean {
let fraction: Ref<number> = new Ref(0);
for (let i = 0; i < cell.length; i++) {
let potential = cell[i];
const potential = cell[i];
// 管理我们已经处理过的碰撞器
if (new es.List(this._checkedColliders).contains(potential))
@@ -391,11 +410,16 @@ module es {
if (!Flags.isFlagSet(this._layerMask, potential.physicsLayer.value))
continue;
if (this._ignoredColliders && this._ignoredColliders.has(potential)) {
continue;
}
// TODO: rayIntersects的性能够吗?需要测试它。Collisions.rectToLine可能更快
// TODO: 如果边界检查返回更多数据,我们就不需要为BoxCollider检查做任何事情
// 在做形状测试之前先做一个边界检查
let colliderBounds = potential.bounds.clone();
if (colliderBounds.rayIntersects(this._ray, fraction) && fraction.value <= 1){
const colliderBounds = potential.bounds;
const res = colliderBounds.rayIntersects(this._ray);
if (res.intersected && res.distance <= 1) {
if (potential.shape.collidesWithLine(this._ray.start, this._ray.end, this._tempHit)) {
// 检查一下,我们应该排除这些射线,射线cast是否在碰撞器中开始
if (!Physics.raycastsStartInColliders && potential.shape.containsPoint(this._ray.start))
@@ -409,27 +433,28 @@ module es {
}
}
if (this._cellHits.length == 0)
if (this._cellHits.length === 0)
return false;
// 所有处理单元完成。对结果进行排序并将命中结果打包到结果数组中
this._cellHits.sort(RaycastResultParser.compareRaycastHits);
for (let i = 0; i < this._cellHits.length; i ++){
for (let i = 0; i < this._cellHits.length; i++) {
this._hits[this.hitCounter] = this._cellHits[i];
// 增加命中计数器,如果它已经达到数组大小的限制,我们就完成了
this.hitCounter ++;
if (this.hitCounter == this._hits.length)
this.hitCounter++;
if (this.hitCounter === this._hits.length)
return true;
}
return false;
}
public reset(){
public reset() {
this._hits = null;
this._checkedColliders.length = 0;
this._cellHits.length = 0;
this._ignoredColliders = null;
}
}
}
@@ -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;
}
}
}
@@ -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);
}
}
}
@@ -0,0 +1,33 @@
module es {
export class Cloth extends Composite {
constructor(topLeftPosition: Vector2, width: number, height: number, segments: number = 20, stiffness: number = 0.25,
tearSensitivity: number = 5, connectHorizontalParticles: boolean = true) {
super();
const xStride = width / segments;
const yStride = height / segments;
for (let y = 0; y < segments; y++) {
for (let x = 0; x < segments; x++) {
const px = topLeftPosition.x + x * xStride;
const py = topLeftPosition.y + y + yStride;
const particle = this.addParticle(new Particle(new Vector2(px, py)));
if (connectHorizontalParticles && x > 0)
this.addConstraint(new DistanceConstraint(this.particles[y * segments + x],
this.particles[y * segments + x - 1], stiffness))
.setTearSensitivity(tearSensitivity)
.setCollidesWithColliders(false);
if (y > 0)
this.addConstraint(new DistanceConstraint(this.particles[y * segments + x],
this.particles[(y - 1) * segments + x], stiffness))
.setTearSensitivity(tearSensitivity)
.setCollidesWithColliders(false);
if (y == 0)
particle.pin();
}
}
}
}
}
@@ -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);
}
}
}
}
}
@@ -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;
}
}
}
@@ -0,0 +1,55 @@
module es {
export class Ragdoll extends Composite {
constructor(x: number, y: number, bodyHeight: number) {
super();
const headLength = bodyHeight / 7.5;
const head = this.addParticle(new Particle({ x: x + RandomUtils.randint(-5, 5), y: y + RandomUtils.randint(-5, 5) }));
head.radius = headLength * 0.75;
head.mass = 4;
const shoulder = this.addParticle(new Particle({ x: x + RandomUtils.randint(-5, 5), y: y + RandomUtils.randint(-5, 5) }));
shoulder.mass = 26;
this.addConstraint(new DistanceConstraint(head, shoulder, 1, 5 / 4 * headLength));
const elbowLeft = this.addParticle(new Particle({ x: x + RandomUtils.randint(-5, 5), y: y + RandomUtils.randint(-5, 5) }));
const elbowRight = this.addParticle(new Particle({ x: x + RandomUtils.randint(-5, 5), y: y + RandomUtils.randint(-5, 5) }));
elbowLeft.mass = 2;
elbowRight.mass = 2;
this.addConstraint(new DistanceConstraint(elbowLeft, shoulder, 1, headLength * 3 / 2));
this.addConstraint(new DistanceConstraint(elbowRight, shoulder, 1, headLength * 3 / 2));
const handLeft = this.addParticle(new Particle({ x: x + RandomUtils.randint(-5, 5), y: y + RandomUtils.randint(-5, 5) }));
const handRight = this.addParticle(new Particle({ x: x + RandomUtils.randint(-5, 5), y: y + RandomUtils.randint(-5, 5) }));
handLeft.mass = 2;
handRight.mass = 2;
this.addConstraint(new DistanceConstraint(handLeft, elbowLeft, 1, headLength * 2));
this.addConstraint(new DistanceConstraint(handRight, elbowRight, 1, headLength * 2));
const pelvis = this.addParticle(new Particle({ x: x + RandomUtils.randint(-5, 5), y: y + RandomUtils.randint(-5, 5) }));
pelvis.mass = 15;
this.addConstraint(new DistanceConstraint(pelvis, shoulder, 0.8, headLength * 3.5));
this.addConstraint(new DistanceConstraint(pelvis, head, 0.02, bodyHeight * 2))
.setCollidesWithColliders(false);
const kneeLeft = this.addParticle(new Particle({ x: x + RandomUtils.randint(-5, 5), y: y + RandomUtils.randint(-5, 5) }));
const kneeRight = this.addParticle(new Particle({ x: x + RandomUtils.randint(-5, 5), y: y + RandomUtils.randint(-5, 5) }));
kneeLeft.mass = 10;
kneeRight.mass = 10;
this.addConstraint(new DistanceConstraint(kneeLeft, pelvis, 1, headLength * 2));
this.addConstraint(new DistanceConstraint(kneeRight, pelvis, 1, headLength * 2));
const footLeft = this.addParticle(new Particle({ x: x + RandomUtils.randint(-5, 5), y: y + RandomUtils.randint(-5, 5) }));
const footRight = this.addParticle(new Particle({ x: x + RandomUtils.randint(-5, 5), y: y + RandomUtils.randint(-5, 5) }));
footLeft.mass = 5;
footRight.mass = 5;
this.addConstraint(new DistanceConstraint(footLeft, kneeLeft, 1, headLength * 2));
this.addConstraint(new DistanceConstraint(footRight, kneeRight, 1, headLength * 2));
this.addConstraint(new DistanceConstraint(footLeft, shoulder, 0.001, bodyHeight * 2))
.setCollidesWithColliders(false);
this.addConstraint(new DistanceConstraint(footLeft, shoulder, 0.001, bodyHeight * 2))
.setCollidesWithColliders(false);
}
}
}
@@ -0,0 +1,23 @@
module es {
export class Tire extends Composite {
constructor(origin: Vector2, radius: number, segments: number, spokeStiffness: number = 1, treadStiffness: number = 1) {
super();
const stride = 2 * Math.PI / segments;
for (let i = 0; i < segments; i ++) {
const theta = i * stride;
this.addParticle(new Particle(new Vector2(origin.x + Math.cos(theta) * radius,
origin.y + Math.sin(theta) * radius)));
}
const centerParticle = this.addParticle(new Particle(origin));
for (let i = 0; i < segments; i ++) {
this.addConstraint(new DistanceConstraint(this.particles[i], this.particles[(i + 1) % segments], treadStiffness));
this.addConstraint(new DistanceConstraint(this.particles[i], centerParticle, spokeStiffness))
.setCollidesWithColliders(false);
this.addConstraint(new DistanceConstraint(this.particles[i], this.particles[(i + 5) % segments], treadStiffness));
}
}
}
}
@@ -0,0 +1,47 @@
///<reference path="./Constraint.ts" />
module es {
export class AngleConstraint extends Constraint {
public stiffness: number = 0;
public angleInRadius: number = 0;
_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);
}
}
}
@@ -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) {
}
}
}
@@ -0,0 +1,123 @@
module es {
export class DistanceConstraint extends Constraint {
public stiffness: number = 0;
public restingDistance: number = 0;
public tearSensitivity = Number.POSITIVE_INFINITY;
public shouldApproximateCollisionsWithPoints: boolean = false;
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 = first.position.distance(second.position);
}
public static create(a: Particle, center: Particle, c: Particle, stiffness: number, angleInDegrees: number) {
const aToCenter = a.position.distance(center.position);
const cToCenter = c.position.distance(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.distance();
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 = Vector2.zero;
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
View File
@@ -0,0 +1,39 @@
module es {
export class Particle {
public position: Vector2 = Vector2.zero;
public lastPosition: Vector2 = Vector2.zero;
public mass = 1;
public radius: number = 0;
public collidesWithColliders: boolean = true;
public isPinned: boolean = false;
public acceleration: Vector2 = Vector2.zero;
public pinnedPosition: Vector2 = Vector2.zero;
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;
}
}
}
+163
View File
@@ -0,0 +1,163 @@
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 = 0;
_fixedDeltaTime: number = 1 / 60;
_iterationSteps: number = 0;
_fixedDeltaTimeSq: number = 0;
onHandleDrag: Function;
constructor(simulationBounds: Rectangle = null) {
this.simulationBounds = simulationBounds;
this._fixedDeltaTimeSq = 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() {
if (this.onHandleDrag)
this.onHandleDrag();
}
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);
}
}
}
}
}
+51
View File
@@ -0,0 +1,51 @@
module es {
/**
* AbstractTweenable作为你可能想做的任何可以执行的自定义类的基础
* ITweensITweenT接口
* AbstractTweenable不仅仅是将一个值从开始移动到结束
*
*/
export abstract class AbstractTweenable implements ITweenable {
protected _isPaused: boolean;
/**
* abstractTweenable在完成后往往会被保留下来
* TweenManager盯上了便
*/
protected _isCurrentlyManagedByTweenManager: boolean;
public abstract tick(): boolean;
public recycleSelf() {
}
public isRunning(): boolean {
return this._isCurrentlyManagedByTweenManager && !this._isPaused;
}
public start() {
if (this._isCurrentlyManagedByTweenManager) {
this._isPaused = false;
return;
}
TweenManager.addTween(this);
this._isCurrentlyManagedByTweenManager = true;
this._isPaused = false;
}
public pause() {
this._isPaused = true;
}
public resume() {
this._isPaused = false;
}
public stop(bringToCompletion: boolean = false) {
TweenManager.removeTween(this);
this._isCurrentlyManagedByTweenManager = false;
this._isPaused = true;
}
}
}
+207
View File
@@ -0,0 +1,207 @@
module es {
export enum EaseType {
linear,
sineIn,
sineOut,
sineInOut,
quadIn,
quadOut,
quadInOut,
quintIn,
quintOut,
quintInOut,
cubicIn,
cubicOut,
cubicInOut,
quartIn,
quartOut,
quartInOut,
expoIn,
expoOut,
expoInOut,
circleIn,
circleOut,
circleInOut,
elasticIn,
elasticOut,
elasticInOut,
punch,
backIn,
backOut,
backInOut,
bounceIn,
bounceOut,
bounceInOut
}
/**
* EaseTypeEase方程
* Funcs为垃圾收集器制造大量垃圾
*/
export class EaseHelper {
/**
* easeType EaseType
* @param easeType
*/
public static oppositeEaseType(easeType: EaseType) {
switch (easeType) {
case EaseType.linear:
return easeType;
case EaseType.backIn:
return EaseType.backOut;
case EaseType.backOut:
return EaseType.backIn;
case EaseType.backInOut:
return easeType;
case EaseType.bounceIn:
return EaseType.bounceOut;
case EaseType.bounceOut:
return EaseType.bounceIn;
case EaseType.bounceInOut:
return easeType;
case EaseType.circleIn:
return EaseType.circleOut;
case EaseType.circleOut:
return EaseType.circleIn;
case EaseType.circleInOut:
return easeType;
case EaseType.cubicIn:
return EaseType.cubicOut;
case EaseType.cubicOut:
return EaseType.cubicIn;
case EaseType.circleInOut:
return easeType;
case EaseType.punch:
return easeType;
case EaseType.expoIn:
return EaseType.expoOut;
case EaseType.expoOut:
return EaseType.expoIn;
case EaseType.expoInOut:
return easeType;
case EaseType.quadIn:
return EaseType.quadOut;
case EaseType.quadOut:
return EaseType.quadIn;
case EaseType.quadInOut:
return easeType;
case EaseType.quartIn:
return EaseType.quadOut;
case EaseType.quartOut:
return EaseType.quartIn;
case EaseType.quadInOut:
return easeType;
case EaseType.sineIn:
return EaseType.sineOut;
case EaseType.sineOut:
return EaseType.sineIn;
case EaseType.sineInOut:
return easeType;
default:
return easeType;
}
}
public static ease(easeType: EaseType, t: number, duration: number) {
switch (easeType) {
case EaseType.linear:
return Easing.Linear.easeNone(t, duration);
case EaseType.backIn:
return Easing.Back.easeIn(t, duration);
case EaseType.backOut:
return Easing.Back.easeOut(t, duration);
case EaseType.backInOut:
return Easing.Back.easeInOut(t, duration);
case EaseType.bounceIn:
return Easing.Bounce.easeIn(t, duration);
case EaseType.bounceOut:
return Easing.Bounce.easeOut(t, duration);
case EaseType.bounceInOut:
return Easing.Bounce.easeInOut(t, duration);
case EaseType.circleIn:
return Easing.Circular.easeIn(t, duration);
case EaseType.circleOut:
return Easing.Circular.easeOut(t, duration);
case EaseType.circleInOut:
return Easing.Circular.easeInOut(t, duration);
case EaseType.cubicIn:
return Easing.Cubic.easeIn(t, duration);
case EaseType.cubicOut:
return Easing.Cubic.easeOut(t, duration);
case EaseType.cubicInOut:
return Easing.Cubic.easeInOut(t, duration);
case EaseType.elasticIn:
return Easing.Elastic.easeIn(t, duration);
case EaseType.elasticOut:
return Easing.Elastic.easeOut(t, duration);
case EaseType.elasticInOut:
return Easing.Elastic.easeInOut(t, duration);
case EaseType.punch:
return Easing.Elastic.punch(t, duration);
case EaseType.expoIn:
return Easing.Exponential.easeIn(t, duration);
case EaseType.expoOut:
return Easing.Exponential.easeOut(t, duration);
case EaseType.expoInOut:
return Easing.Exponential.easeInOut(t, duration);
case EaseType.quadIn:
return Easing.Quadratic.easeIn(t, duration);
case EaseType.quadOut:
return Easing.Quadratic.easeOut(t, duration);
case EaseType.quadInOut:
return Easing.Quadratic.easeInOut(t, duration);
case EaseType.quadIn:
return Easing.Quadratic.easeIn(t, duration);
case EaseType.quadOut:
return Easing.Quadratic.easeOut(t, duration);
case EaseType.quadInOut:
return Easing.Quadratic.easeInOut(t, duration);
case EaseType.quintIn:
return Easing.Quintic.easeIn(t, duration);
case EaseType.quintOut:
return Easing.Quintic.easeOut(t, duration);
case EaseType.quintInOut:
return Easing.Quintic.easeInOut(t, duration);
case EaseType.sineIn:
return Easing.Sinusoidal.easeIn(t, duration);
case EaseType.sineOut:
return Easing.Sinusoidal.easeOut(t, duration);
case EaseType.sineInOut:
return Easing.Sinusoidal.easeInOut(t, duration);
default:
return Easing.Linear.easeNone(t, duration);
}
}
}
}
+230
View File
@@ -0,0 +1,230 @@
module es {
/**
* b和c参数01
* 0 - 1/lerp任何东西
*/
export module Easing {
export class Linear {
public static easeNone(t: number, d: number) {
return t / d;
}
}
export class Quadratic {
public static easeIn(t: number, d: number) {
return (t /= d) * t;
}
public static easeOut(t: number, d: number) {
return -1 * (t /= d) * (t - 2);
}
public static easeInOut(t: number, d: number) {
if ((t /= d / 2) < 1)
return 0.5 * t * t;
return -0.5 * ((--t) * (t - 2) - 1);
}
}
export class Back {
public static easeIn(t: number, d: number) {
return (t /= d) * t * ((1.70158 + 1) * t - 1.70158);
}
public static easeOut(t: number, d: number) {
return ((t = t / d - 1) * t * ((1.70158 + 1) * t + 1.70158) + 1);
}
public static easeInOut(t: number, d: number) {
let s = 1.70158;
if ((t /= d / 2) < 1) {
return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s));
}
return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
}
}
export class Bounce {
public static easeOut(t: number, d: number) {
if ((t /= d) < (1 / 2.75)) {
return (7.5625 * t * t);
} else if (t < (2 / 2.75)) {
return (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
} else if (t < (2.5 / 2.75)) {
return (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
} else {
return (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
}
}
public static easeIn(t: number, d: number) {
return 1 - this.easeOut(d - t, d);
}
public static easeInOut(t: number, d: number) {
if (t < d / 2)
return this.easeIn(t * 2, d) * 0.5;
else
return this.easeOut(t * 2 - d, d) * 0.5 + 1 * 0.5;
}
}
export class Circular {
public static easeIn(t: number, d: number) {
return -(Math.sqrt(1 - (t /= d) * t) - 1);
}
public static easeOut(t: number, d: number) {
return Math.sqrt(1 - (t = t / d - 1) * t);
}
public static easeInOut(t: number, d: number) {
if ((t /= d / 2) < 1)
return -0.5 * (Math.sqrt(1 - t * t) - 1);
return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
}
}
export class Cubic {
public static easeIn(t: number, d: number) {
return (t /= d) * t * t;
}
public static easeOut(t: number, d: number) {
return ((t = t / d - 1) * t * t + 1);
}
public static easeInOut(t: number, d: number) {
if ((t /= d / 2) < 1)
return 0.5 * t * t * t;
return 0.5 * ((t -= 2) * t * t + 2);
}
}
export class Elastic {
public static easeIn(t: number, d: number) {
if (t == 0)
return 0;
if ((t /= d) == 1)
return 1;
let p = d * 0.3;
let s = p / 4;
return -(1 * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p));
}
public static easeOut(t: number, d: number) {
if (t == 0)
return 0;
if ((t /= d) == 1)
return 1;
let p = d * 0.3;
let s = p / 4;
return (1 * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + 1);
}
public static easeInOut(t: number, d: number) {
if (t == 0)
return 0;
if ((t /= d / 2) == 2)
return 1;
let p = d * (0.3 * 1.5);
let s = p / 4;
if (t < 1)
return -0.5 * (Math.pow(2, 10 * (t -= 1)) * Math.sin(t * d - s) * (2 * Math.PI) / p);
return (Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + 1);
}
public static punch(t: number, d: number) {
if (t == 0)
return 0;
if ((t /= d) == 1)
return 0;
const p = 0.3;
return (Math.pow(2, -10 * t) * Math.sin(t * (2 * Math.PI) / p));
}
}
export class Exponential {
public static easeIn(t: number, d: number) {
return (t == 0) ? 0 : Math.pow(2, 10 * (t / d - 1));
}
public static easeOut(t: number, d: number) {
return t == d ? 1 : (-Math.pow(2, -10 * t / d) + 1);
}
public static easeInOut(t: number, d: number) {
if (t == 0)
return 0;
if (t == d)
return 1;
if ((t /= d / 2) < 1) {
return 0.5 * Math.pow(2, 10 * (t - 1));
}
return 0.5 * (-Math.pow(2, -10 * --t) + 2);
}
}
export class Quartic {
public static easeIn(t: number, d: number) {
return (t /= d) * t * t * t;
}
public static easeOut(t: number, d: number) {
return -1 * ((t = t / d - 1) * t * t * t - 1);
}
public static easeInOut(t: number, d: number) {
t /= d / 2;
if (t < 1)
return 0.5 * t * t * t * t;
t -= 2;
return -0.5 * (t * t * t * t - 2);
}
}
export class Quintic {
public static easeIn(t: number, d: number) {
return (t /= d) * t * t * t * t;
}
public static easeOut(t: number, d: number) {
return ((t = t / d - 1) * t * t * t * t + 1);
}
public static easeInOut(t: number, d: number) {
if ((t /= d / 2) < 1)
return 0.5 * t * t * t * t * t;
return 0.5 * ((t -= 2) * t * t * t * t + 2);
}
}
export class Sinusoidal {
public static easeIn(t: number, d: number) {
return -1 * Math.cos(t / d * (Math.PI / 2)) + 1;
}
public static easeOut(t: number, d: number) {
return Math.sin(t / d * (Math.PI / 2));
}
public static easeInOut(t: number, d: number) {
return -0.5 * (Math.cos(Math.PI * t / d) - 1);
}
}
}
}
+84
View File
@@ -0,0 +1,84 @@
module es {
/**
* tween类型结构unclamped lerps.unclamped lerps对于超过0-1bounceelastic或其他tweens是必需的
*/
export class Lerps {
public static lerp(from: Color, to: Color, t: number);
public static lerp(from: number, to: number, t: number);
public static lerp(from: Rectangle, to: Rectangle, t: number);
public static lerp(from: Vector2, to: Vector2, t: number);
public static lerp(from: any, to: any, t: number) {
if (typeof(from) == "number" && typeof(to) == "number") {
return from + (to - from) * t;
}
if (from instanceof Color && to instanceof Color) {
const t255 = t * 255;
return new Color(from.r + (to.r - from.r) * t255 / 255, from.g + (to.g - from.g) * t255 / 255,
from.b + (to.b - from.b) * t255 / 255, from.a + (to.a - from.a) * t255 / 255)
}
if (from instanceof Rectangle && to instanceof Rectangle) {
return new Rectangle(
(from.x + (to.x - from.x) * t),
(from.y + (to.x - from.y) * t),
(from.width + (to.width - from.width) * t),
(from.height + (to.height - from.height) * t)
);
}
if (from instanceof Vector2 && to instanceof Vector2) {
return new Vector2(from.x + (to.x - from.x) * t, from.y + (to.y - from.y) * t);
}
}
public static angleLerp(from: Vector2, to: Vector2, t: number) {
// 我们计算这个lerp的最短角差
let toMinusFrom = new Vector2(MathHelper.deltaAngle(from.x, to.x), MathHelper.deltaAngle(from.y, to.y));
return new Vector2(from.x + toMinusFrom.x * t, from.y + toMinusFrom.y * t);
}
public static ease(easeType: EaseType, from: Rectangle, to: Rectangle, t: number, duration: number);
public static ease(easeType: EaseType, from: Vector2, to: Vector2, t: number, duration: number);
public static ease(easeType: EaseType, from: number, to: number, t: number, duration: number);
public static ease(easeType: EaseType, from: Color, to: Color, t: number, duration: number);
public static ease(easeType: EaseType, from: any, to: any, t: number, duration: number) {
if (typeof(from) == 'number' && typeof(to) == "number") {
return this.lerp(from, to, EaseHelper.ease(easeType, t, duration));
}
if (from instanceof Vector2 && to instanceof Vector2) {
return this.lerp(from, to, EaseHelper.ease(easeType, t, duration));
}
if (from instanceof Rectangle && to instanceof Rectangle) {
return this.lerp(from, to, EaseHelper.ease(easeType, t, duration));
}
if (from instanceof Color && to instanceof Color) {
return this.lerp(from, to, EaseHelper.ease(easeType, t, duration));
}
}
public static easeAngle(easeType: EaseType, from: Vector2, to: Vector2, t: number, duration: number) {
return this.angleLerp(from, to, EaseHelper.ease(easeType, t, duration));
}
/**
* 使
* http://allenchou.net/2015/04/game-math-more-on-numeric-springing/
* @param currentValue
* @param targetValue
* @param velocity Velocity的引用targetValue0
* @param dampingRatio 0.01-1
* @param angularFrequency 2pi(/)1Hz.35
*/
public static fastSpring(currentValue: Vector2, targetValue: Vector2, velocity: Vector2,
dampingRatio: number, angularFrequency: number) {
velocity.add(velocity.scale(-2 * Time.deltaTime * dampingRatio * angularFrequency)
.add(targetValue.sub(currentValue).scale(Time.deltaTime * angularFrequency * angularFrequency)));
currentValue.add(velocity.scale(Time.deltaTime));
return currentValue;
}
}
}
+79
View File
@@ -0,0 +1,79 @@
module es {
/**
* tween属性
*/
export interface ITween<T> extends ITweenControl {
/**
* tween的易用性类型
* @param easeType
*/
setEaseType(easeType: EaseType): ITween<T>;
/**
* tween前的延迟
* @param delay
*/
setDelay(delay: number): ITween<T>;
/**
* tween的持续时间
* @param duration
*/
setDuration(duration: number): ITween<T>;
/**
* tween使用的timeScale
* TimeScale将与Time.deltaTime/Time.unscaledDeltaTime相乘tween实际使用的delta时间
* @param timeScale
*/
setTimeScale(timeScale: number): ITween<T>;
/**
* tween使用Time.unscaledDeltaTime代替Time.deltaTime
*/
setIsTimeScaleIndependent(): ITween<T>;
/**
* tween完成时应该调用的动作
* @param completionHandler
*/
setCompletionHandler(completionHandler: (tween: ITween<T>) => void): ITween<T>;
/**
* tween的循环类型pingpong循环意味着从开始--
* @param loopType
* @param loops
* @param delayBetweenLoops
*/
setLoops(loopType: LoopType, loops: number, delayBetweenLoops: number): ITween<T>;
/**
* tween的起始位置
* @param from
*/
setFrom(from: T): ITween<T>;
/**
* tween的from/to值和持续时间使tween做准备
* @param from
* @param to
* @param duration
*/
prepareForReuse(from: T, to: T, duration: number): ITween<T>;
/**
* true()tween将在使用后被回收
* TweenManager类中进行了配置Tween<T>
* @param shouldRecycleTween
*/
setRecycleTween(shouldRecycleTween: boolean): ITween<T>;
/**
* tween的to值设置为相对于其当前值的+使tween
*/
setIsRelative(): ITween<T>;
/**
* tween.context.context来设置任何可检索的对象引用
* 便
* TweenManager中搜索具有特定上下文的所有tweens
* @param context
*/
setContext(context): ITween<T>;
/**
* tweentween完成后会被运行
* nextTween ITweenable! ITweenT都是ITweenable
* @param nextTween
*/
setNextTween(nextTween: ITweenable): ITween<T>;
}
}
@@ -0,0 +1,24 @@
module es {
/**
* Tween播放控制在这里
*/
export interface ITweenControl extends ITweenable {
/**
* 使使
*/
context;
/**
* tween扭曲为elapsedTime0duration之间tween对象是暂停
* @param elapsedTime
*/
jumpToElapsedTime(elapsedTime: number);
/**
* StartCoroutine调用时tween完成
*/
waitForCompletion(): any;
/**
* tween的目标TweenTargets不一定都是一个对象nullTweenManager按目标查找tweens的列表
*/
getTargetObject(): any;
}
}
@@ -0,0 +1,19 @@
module es {
/**
* weened的对象都需要实现这个功能
* TweenManager内部喜欢做一个简单的对象来实现这个接口tweened对象的引用
*/
export interface ITweenTarget<T> {
/**
* tweened值
* @param value
*/
setTweenedValue(value: T);
getTweenedValue(): T;
/**
* tween的目标TweenTargets不一定都是一个对象nullTweenManager按目标查找tweens的列表
*/
getTargetObject(): any;
}
}
+34
View File
@@ -0,0 +1,34 @@
module es {
export interface ITweenable {
/**
* Update一样TweenManager调用
*/
tick(): boolean;
/**
* tween被移除时TweenManager调用
* _shouldRecycleTween bool!
*/
recycleSelf();
/**
* tween在运行
*/
isRunning(): boolean;
/**
* tween
*/
start();
/**
*
*/
pause();
/**
* tween
*/
resume();
/**
* tween
* @param bringToCompletion
*/
stop(bringToCompletion: boolean);
}
}
+44
View File
@@ -0,0 +1,44 @@
module es {
/**
* ITweenTarget用于所有属性tweens
*/
class PropertyTarget<T> implements ITweenTarget<T> {
protected _target;
protected _propertyName;
constructor(target, propertyName: string) {
this._target = target;
this._propertyName = propertyName;
}
public getTargetObject() {
return this._target;
}
public setTweenedValue(value: T) {
this._target[this._propertyName] = value;
}
public getTweenedValue(): T {
return this._target[this._propertyName];
}
}
export class PropertyTweens {
public static NumberPropertyTo(self, memberName: string, to: number, duration: number): ITween<number> {
let tweenTarget = new PropertyTarget<number>(self, memberName);
let tween = TweenManager.cacheNumberTweens ? Pool.obtain<NumberTween>(NumberTween) : new NumberTween();
tween.initialize(tweenTarget, to, duration);
return tween;
}
public static Vector2PropertyTo(self, memeberName: string, to: Vector2, duration: number): ITween<Vector2> {
let tweenTarget = new PropertyTarget<Vector2>(self, memeberName);
let tween = TweenManager.cacheVector2Tweens ? Pool.obtain<Vector2Tween>(Vector2Tween) : new Vector2Tween();
tween.initialize(tweenTarget, to, duration);
return tween;
}
}
}
+38
View File
@@ -0,0 +1,38 @@
///<reference path="./Tweens.ts"/>
module es {
export class RenderableColorTween extends ColorTween implements ITweenTarget<Color> {
_renderable: RenderableComponent;
setTweenedValue(value: Color) {
this._renderable.color = value;
}
getTweenedValue(): Color {
return this._renderable.color;
}
public getTargetObject() {
return this._renderable;
}
public updateValue() {
this.setTweenedValue(Lerps.ease(this._easeType, this._fromValue, this._toValue, this._elapsedTime, this._duration));
}
public setTarget(renderable: RenderableComponent) {
this._renderable = renderable;
}
public recycleSelf() {
if (this._shouldRecycleTween) {
this._renderable = null;
this._target = null;
this._nextTween = null;
}
if (this._shouldRecycleTween && TweenManager.cacheColorTweens) {
Pool.free(this);
}
}
}
}
+100
View File
@@ -0,0 +1,100 @@
module es {
export class TransformSpringTween extends AbstractTweenable {
public get targetType() {
return this._targetType;
}
private _transform: Transform;
private _targetType: TransformTargetType;
private _targetValue: Vector2;
private _velocity: Vector2;
// 阻尼比(dampingRatio)和角频率(angularFrequency)的配置是公开的,以便于在设计时进行调整
/**
* 0.01-1
*/
public dampingRatio: number = 0.23;
/**
* 2pi(/)1Hz.35
*/
public angularFrequency: number = 25;
constructor(transform: Transform, targetType: TransformTargetType, targetValue: Vector2) {
super();
this._transform = transform;
this._targetType = targetType;
this.setTargetValue(targetValue);
}
/**
* setTargetValue来重置目标值到一个新的Vector2
* start来添加spring tween
* @param targetValue
*/
public setTargetValue(targetValue: Vector2) {
this._velocity = Vector2.zero;
this._targetValue = targetValue;
if (!this._isCurrentlyManagedByTweenManager)
this.start();
}
/**
* lambda应该是振荡幅度减少50%
* @param lambda
*/
public updateDampingRatioWithHalfLife(lambda: number) {
this.dampingRatio = (-lambda / this.angularFrequency) * Math.log(0.5);
}
public tick() {
if (!this._isPaused)
this.setTweenedValue(Lerps.fastSpring(this.getCurrentValueOfTweenedTargetType(), this._targetValue, this._velocity,
this.dampingRatio, this.angularFrequency));
return false;
}
private setTweenedValue(value: Vector2) {
switch (this._targetType) {
case TransformTargetType.position:
this._transform.position = value;
break;
case TransformTargetType.localPosition:
this._transform.localPosition = value;
break;
case TransformTargetType.scale:
this._transform.scale = value;
break;
case TransformTargetType.localScale:
this._transform.localScale = value;
break;
case TransformTargetType.rotationDegrees:
this._transform.rotationDegrees = value.x;
case TransformTargetType.localRotationDegrees:
this._transform.localRotationDegrees = value.x;
break;
}
}
private getCurrentValueOfTweenedTargetType() {
switch (this._targetType) {
case TransformTargetType.position:
return this._transform.position;
case TransformTargetType.localPosition:
return this._transform.localPosition;
case TransformTargetType.scale:
return this._transform.scale;
case TransformTargetType.localScale:
return this._transform.localScale;
case TransformTargetType.rotationDegrees:
return new Vector2(this._transform.rotationDegrees);
case TransformTargetType.localRotationDegrees:
return new Vector2(this._transform.localRotationDegrees, 0);
default:
return Vector2.zero;
}
}
}
}
+90
View File
@@ -0,0 +1,90 @@
///<reference path="./Tweens.ts"/>
module es {
/**
* Transform相关的属性tweens都是有用的枚举
*/
export enum TransformTargetType {
position,
localPosition,
scale,
localScale,
rotationDegrees,
localRotationDegrees,
}
/**
* Transform是迄今为止最被ween的对象
* Tween和ITweenTarget封装在一个单一的
*/
export class TransformVector2Tween extends Vector2Tween implements ITweenTarget<Vector2> {
private _transform: Transform;
private _targetType: TransformTargetType;
public setTweenedValue(value: Vector2) {
switch (this._targetType) {
case TransformTargetType.position:
this._transform.position = value;
break;
case TransformTargetType.localPosition:
this._transform.localPosition = value;
break;
case TransformTargetType.scale:
this._transform.scale = value;
break;
case TransformTargetType.localScale:
this._transform.localScale = value;
break;
case TransformTargetType.rotationDegrees:
this._transform.rotationDegrees = value.x;
case TransformTargetType.localRotationDegrees:
this._transform.localRotationDegrees = value.x;
break;
}
}
public getTweenedValue(): Vector2 {
switch (this._targetType) {
case TransformTargetType.position:
return this._transform.position;
case TransformTargetType.localPosition:
return this._transform.localPosition;
case TransformTargetType.scale:
return this._transform.scale;
case TransformTargetType.localScale:
return this._transform.localScale;
case TransformTargetType.rotationDegrees:
return new Vector2(this._transform.rotationDegrees, this._transform.rotationDegrees);
case TransformTargetType.localRotationDegrees:
return new Vector2(this._transform.localRotationDegrees, 0);
}
}
public getTargetObject() {
return this._transform;
}
public setTargetAndType(transform: Transform, targetType: TransformTargetType) {
this._transform = transform;
this._targetType = targetType;
}
protected updateValue() {
// 非相对角勒普的特殊情况,使他们采取尽可能短的旋转
if ((this._targetType == TransformTargetType.rotationDegrees ||
this._targetType == TransformTargetType.localRotationDegrees) && !this._isRelative) {
this.setTweenedValue(Lerps.easeAngle(this._easeType, this._fromValue, this._toValue, this._elapsedTime, this._duration));
} else {
this.setTweenedValue(Lerps.ease(this._easeType, this._fromValue, this._toValue, this._elapsedTime, this._duration));
}
}
public recycleSelf() {
if (this._shouldRecycleTween) {
this._target = null;
this._nextTween = null;
this._transform = null;
Pool.free(this);
}
}
}
}
+319
View File
@@ -0,0 +1,319 @@
module es {
export enum LoopType {
none,
restartFromBeginning,
pingpong
}
export enum TweenState {
running,
paused,
complete
}
export abstract class Tween<T> implements ITweenable, ITween<T> {
protected _target: ITweenTarget<T>;
protected _isFromValueOverridden: boolean;
protected _fromValue: T;
protected _toValue: T;
protected _easeType: EaseType;
protected _shouldRecycleTween: boolean = true;
protected _isRelative: boolean;
protected _completionHandler: (tween: ITween<T>) => void;
protected _loopCompleteHandler: (tween: ITween<T>) => void;
protected _nextTween: ITweenable;
protected _tweenState: TweenState = TweenState.complete;
private _isTimeScaleIndependent: boolean;
protected _delay: number;
protected _duration: number;
protected _timeScale: number = 1;
protected _elapsedTime: number;
protected _loopType: LoopType;
protected _loops: number;
protected _delayBetweenLoops: number;
private _isRunningInReverse: boolean;
public context: any;
public setEaseType(easeType: EaseType): ITween<T> {
this._easeType = easeType;
return this;
}
public setDelay(delay: number): ITween<T> {
this._delay = delay;
this._elapsedTime = -this._delay;
return this;
}
public setDuration(duration: number): ITween<T> {
this._duration = duration;
return this;
}
public setTimeScale(timeSclae: number): ITween<T> {
this._timeScale = timeSclae;
return this;
}
public setIsTimeScaleIndependent(): ITween<T> {
this._isTimeScaleIndependent = true;
return this;
}
public setCompletionHandler(completeHandler: (tween: ITween<T>) => void): ITween<T> {
this._completionHandler = completeHandler;
return this;
}
public setLoops(loopType: LoopType, loops: number = 1, delayBetweenLoops: number = 0): ITween<T> {
this._loopType = loopType;
this._delayBetweenLoops = delayBetweenLoops;
if (loops < 0)
loops = -1;
if (loopType == LoopType.pingpong)
loops = loops * 2;
this._loops = loops;
return this;
}
public setLoopCompletionHanlder(loopCompleteHandler: (tween: ITween<T>) => void): ITween<T> {
this._loopCompleteHandler = loopCompleteHandler;
return this;
}
public setFrom(from: T): ITween<T> {
this._isFromValueOverridden = true;
this._fromValue = from;
return this;
}
public prepareForReuse(from: T, to: T, duration: number): ITween<T> {
this.initialize(this._target, to, duration);
return this;
}
public setRecycleTween(shouldRecycleTween: boolean): ITween<T> {
this._shouldRecycleTween = shouldRecycleTween;
return this;
}
public abstract setIsRelative(): ITween<T>;
public setContext(context): ITween<T> {
this.context = context;
return this;
}
public setNextTween(nextTween: ITweenable): ITween<T> {
this._nextTween = nextTween;
return this;
}
public tick(): boolean {
if (this._tweenState == TweenState.paused)
return false;
// 当我们进行循环时,我们会在0和持续时间之间限制数值
let elapsedTimeExcess = 0;
if (!this._isRunningInReverse && this._elapsedTime >= this._duration) {
elapsedTimeExcess = this._elapsedTime - this._duration;
this._elapsedTime = this._duration;
this._tweenState = TweenState.complete;
} else if (this._isRunningInReverse && this._elapsedTime <= 0) {
elapsedTimeExcess = 0 - this._elapsedTime;
this._elapsedTime = 0;
this._tweenState = TweenState.complete;
}
// 当我们延迟开始tween的时候,经过的时间会是负数,所以不要更新这个值。
if (this._elapsedTime >= 0 && this._elapsedTime <= this._duration) {
this.updateValue();
}
// 如果我们有一个loopType,并且我们是Complete(意味着我们达到了0或持续时间)处理循环。
// handleLooping将采取任何多余的elapsedTime,并将其因子化,并在必要时调用udpateValue来保持tween的完美准确性
if (this._loopType != LoopType.none && this._tweenState == TweenState.complete && this._loops != 0) {
this.handleLooping(elapsedTimeExcess);
}
let deltaTime = this._isTimeScaleIndependent ? Time.unscaledDeltaTime : Time.deltaTime;
deltaTime *= this._timeScale;
// 我们需要减去deltaTime
if (this._isRunningInReverse)
this._elapsedTime -= deltaTime;
else
this._elapsedTime += deltaTime;
if (this._tweenState == TweenState.complete) {
this._completionHandler && this._completionHandler(this);
// 如果我们有一个nextTween,把它添加到TweenManager中,这样它就可以开始运行了
if (this._nextTween != null) {
this._nextTween.start();
this._nextTween = null;
}
return true;
}
return false;
}
public recycleSelf() {
if (this._shouldRecycleTween) {
this._target = null;
this._nextTween = null;
}
}
public isRunning(): boolean {
return this._tweenState == TweenState.running;
}
public start() {
if (!this._isFromValueOverridden)
this._fromValue = this._target.getTargetObject();
if (this._tweenState == TweenState.complete) {
this._tweenState = TweenState.running;
TweenManager.addTween(this);
}
}
public pause() {
this._tweenState = TweenState.paused;
}
public resume() {
this._tweenState = TweenState.running;
}
public stop(bringToCompletion: boolean = false) {
this._tweenState = TweenState.complete;
if (bringToCompletion) {
// 如果我们逆向运行,我们在0处结束,否则我们进入持续时间
this._elapsedTime = this._isRunningInReverse ? 0 : this._duration;
this._loopType = LoopType.none;
this._loops = 0;
// TweenManager将在下一个tick上进行删除处理
} else {
TweenManager.removeTween(this);
}
}
public jumpToElapsedTime(elapsedTime) {
this._elapsedTime = MathHelper.clamp(elapsedTime, 0, this._duration);
this.updateValue();
}
/**
* tween
*/
public reverseTween() {
this._isRunningInReverse = !this._isRunningInReverse;
}
/**
* StartCoroutine调用时tween完成
*/
public * waitForCompletion() {
while (this._tweenState != TweenState.complete)
yield null;
}
public getTargetObject() {
return this._target.getTargetObject();
}
private resetState() {
this.context = null;
this._completionHandler = this._loopCompleteHandler = null;
this._isFromValueOverridden = false;
this._isTimeScaleIndependent = false;
this._tweenState = TweenState.complete;
// TODO: 我认为在没有得到用户同意的情况下,我们绝对不应该从_shouldRecycleTween=false。需要研究和思考
// this._shouldRecycleTween = true;
this._isRelative = false;
this._easeType = TweenManager.defaultEaseType;
if (this._nextTween != null) {
this._nextTween.recycleSelf();
this._nextTween = null;
}
this._delay = 0;
this._duration = 0;
this._timeScale = 1;
this._elapsedTime = 0;
this._loopType = LoopType.none;
this._delayBetweenLoops = 0;
this._loops = 0;
this._isRunningInReverse = false;
}
/**
*
* Tween子类就可以调用它tweens就可以被回收
*
* @param target
* @param to
* @param duration
*/
public initialize(target: ITweenTarget<T>, to: T, duration: number) {
// 重置状态,以防我们被回收
this.resetState();
this._target = target;
this._toValue = to;
this._duration = duration;
}
/**
*
* @param elapsedTimeExcess
*/
private handleLooping(elapsedTimeExcess: number) {
this._loops--;
if (this._loopType == LoopType.pingpong) {
this.reverseTween();
}
if (this._loopType == LoopType.restartFromBeginning || this._loops % 2 == 0) {
this._loopCompleteHandler && this._completionHandler(this);
}
// 如果我们还有循环要处理,就把我们的状态重置为Running,这样我们就可以继续处理它们了
if (this._loops != 0) {
this._tweenState = TweenState.running;
// 现在,我们需要设置我们的经过时间,并考虑到我们的elapsedTimeExcess
if (this._loopType == LoopType.restartFromBeginning) {
this._elapsedTime = elapsedTimeExcess - this._delayBetweenLoops;
} else {
if (this._isRunningInReverse)
this._elapsedTime += this._delayBetweenLoops - elapsedTimeExcess;
else
this._elapsedTime = elapsedTimeExcess - this._delayBetweenLoops;
}
// 如果我们有一个elapsedTimeExcess,并且没有delayBetweenLoops,则更新该值
if (this._delayBetweenLoops == 0 && elapsedTimeExcess > 0) {
this.updateValue();
}
}
}
protected abstract updateValue();
}
}
+152
View File
@@ -0,0 +1,152 @@
///<reference path="./Easing/EaseType.ts" />
///<reference path="../Utils/GlobalManager.ts"/>
module es {
export class TweenManager extends GlobalManager {
public static defaultEaseType: EaseType = EaseType.quartIn;
/**
* tween列表将被清除
*/
public static removeAllTweensOnLevelLoad: boolean = false;
/**
*
* 使tweens时tweens时从缓存中获取tween时
* tween
*/
public static cacheNumberTweens = true;
public static cacheVector2Tweens = true;
public static cacheColorTweens = true;
public static cacheRectTweens = false;
/**
*
*/
private _activeTweens: ITweenable[] = [];
private _tempTweens: ITweenable[] = [];
/**
* tween更新循环正在运行
*/
private _isUpdating: boolean;
/**
* 便API以方便访问
*/
private static _instance: TweenManager;
constructor() {
super();
TweenManager._instance = this;
}
public update() {
this._isUpdating = true;
// 反向循环,这样我们就可以把完成的weens删除了
for (let i = this._activeTweens.length - 1; i >= 0; --i) {
let tween = this._activeTweens[i];
if (tween.tick())
this._tempTweens.push(tween);
}
this._isUpdating = false;
for (let i = 0; i < this._tempTweens.length; i++) {
this._tempTweens[i].recycleSelf();
new List(this._activeTweens).remove(this._tempTweens[i]);
}
this._tempTweens.length = 0;
}
/**
* tween添加到活动tweens列表中
* @param tween
*/
public static addTween(tween: ITweenable) {
TweenManager._instance._activeTweens.push(tween);
}
/**
* tweens列表中删除一个tween
* @param tween
*/
public static removeTween(tween: ITweenable) {
if (TweenManager._instance._isUpdating) {
TweenManager._instance._tempTweens.push(tween);
} else {
tween.recycleSelf();
new List(TweenManager._instance._activeTweens).remove(tween);
}
}
/**
* tween并选择地把他们全部完成
* @param bringToCompletion
*/
public static stopAllTweens(bringToCompletion: boolean = false) {
for (let i = TweenManager._instance._activeTweens.length - 1; i >= 0; --i)
TweenManager._instance._activeTweens[i].stop(bringToCompletion);
}
/**
* tweens
* Tweens以ITweenable的形式返回TweenManager所知道的所有内容
* @param context
*/
public static allTweensWithContext(context): ITweenable[] {
let foundTweens = [];
for (let i = 0; i < TweenManager._instance._activeTweens.length; i++) {
if ((TweenManager._instance._activeTweens[i] as ITweenControl).context == context)
foundTweens.push(TweenManager._instance._activeTweens[i]);
}
return foundTweens;
}
/**
* tweens
* @param context
* @param bringToCompletion
*/
public static stopAllTweensWithContext(context, bringToCompletion: boolean = false) {
for (let i = TweenManager._instance._activeTweens.length - 1; i >= 0; --i) {
if ((TweenManager._instance._activeTweens[i] as ITweenControl).context == context)
TweenManager._instance._activeTweens[i].stop(bringToCompletion);
}
}
/**
* tweens
* Tweens以ITweenControl的形式返回TweenManager只知道这些
* @param target
*/
public static allTweenWithTarget(target): ITweenable[] {
let foundTweens = [];
for (let i = 0; i < TweenManager._instance._activeTweens.length; i++) {
if (TweenManager._instance._activeTweens[i]) {
let tweenControl = TweenManager._instance._activeTweens[i] as ITweenControl;
if (tweenControl.getTargetObject() == target)
foundTweens.push(TweenManager._instance._activeTweens[i]);
}
}
return foundTweens;
}
/**
* TweenManager知道的特定目标的tweens
* @param target
* @param bringToCompletion
*/
public static stopAllTweensWithTarget(target, bringToCompletion: boolean = false) {
for (let i = TweenManager._instance._activeTweens.length - 1; i >= 0; --i) {
if (TweenManager._instance._activeTweens[i]) {
let tweenControl = TweenManager._instance._activeTweens[i] as ITweenControl;
if (tweenControl.getTargetObject() == target)
tweenControl.stop(bringToCompletion);
}
}
}
}
}
+117
View File
@@ -0,0 +1,117 @@
///<reference path="./Tween.ts"/>
module es {
export class NumberTween extends Tween<number> {
public static create(): NumberTween {
return TweenManager.cacheNumberTweens ? Pool.obtain(NumberTween) : new NumberTween();
}
constructor(target?: ITweenTarget<number>, to?: number, duration?: number) {
super();
this.initialize(target, to, duration);
}
public setIsRelative(): ITween<number> {
this._isRelative = true;
this._toValue += this._fromValue;
return this;
}
protected updateValue() {
this._target.setTweenedValue(Lerps.ease(this._easeType, this._fromValue, this._toValue, this._elapsedTime, this._duration));
}
public recycleSelf() {
super.recycleSelf();
if (this._shouldRecycleTween && TweenManager.cacheNumberTweens)
Pool.free(this);
}
}
export class Vector2Tween extends Tween<Vector2> {
public static create(): Vector2Tween {
return TweenManager.cacheVector2Tweens ? Pool.obtain(Vector2Tween) : new Vector2Tween();
}
constructor(target?: ITweenTarget<Vector2>, to?: Vector2, duration?: number) {
super();
this.initialize(target, to, duration);
}
public setIsRelative(): ITween<Vector2> {
this._isRelative = true;
this._toValue.add(this._fromValue);
return this;
}
protected updateValue() {
this._target.setTweenedValue(Lerps.ease(this._easeType, this._fromValue, this._toValue, this._elapsedTime, this._duration));
}
public recycleSelf() {
super.recycleSelf();
if (this._shouldRecycleTween && TweenManager.cacheVector2Tweens)
Pool.free(this);
}
}
export class RectangleTween extends Tween<Rectangle> {
public static create(): RectangleTween {
return TweenManager.cacheRectTweens ? Pool.obtain(RectangleTween) : new RectangleTween();
}
constructor(target?: ITweenTarget<Rectangle>, to?: Rectangle, duration?: number) {
super();
this.initialize(target, to, duration);
}
public setIsRelative(): ITween<Rectangle> {
this._isRelative = true;
this._toValue = new Rectangle(
this._toValue.x + this._fromValue.x,
this._toValue.y + this._fromValue.y,
this._toValue.width + this._fromValue.width,
this._toValue.height + this._fromValue.height);
return this;
}
protected updateValue() {
this._target.setTweenedValue(Lerps.ease(this._easeType, this._fromValue, this._toValue, this._elapsedTime, this._duration));
}
public recycleSelf() {
super.recycleSelf();
if (this._shouldRecycleTween && TweenManager.cacheRectTweens)
Pool.free(this);
}
}
export class ColorTween extends Tween<Color> {
public static create() : ColorTween {
return TweenManager.cacheColorTweens ? Pool.obtain(ColorTween) : new ColorTween();
}
constructor(target?: ITweenTarget<Color>, to?: Color, duration?: number) {
super();
this.initialize(target, to, duration);
}
public setIsRelative() {
this._isRelative = true;
this._toValue.r += this._fromValue.r;
this._toValue.g += this._fromValue.g;
this._toValue.b += this._fromValue.b;
this._toValue.a += this._fromValue.a;
return this;
}
protected updateValue() {
this._target.setTweenedValue(Lerps.ease(this._easeType, this._fromValue as any, this._toValue as any, this._elapsedTime, this._duration));
}
}
}
+48
View File
@@ -0,0 +1,48 @@
module es {
export interface IAnimFrame {
t: number;
value: number;
}
export class AnimCurve {
public get points(): IAnimFrame[] {
return this._points;
}
public constructor(points: IAnimFrame[]) {
if (points.length < 2) {
throw new Error('curve length must be >= 2');
}
points.sort((a: IAnimFrame, b: IAnimFrame) => {
return a.t - b.t;
});
if (points[0].t !== 0) {
throw new Error('curve must start with 0');
}
if (points[points.length - 1].t !== 1) {
throw new Error('curve must end with 1');
}
this._points = points;
}
public lerp(t: number): number {
for (let i = 1; i < this._points.length; i++) {
if (t <= this._points[i].t) {
const m = MathHelper.map01(
t,
this._points[i - 1].t,
this._points[i].t
);
return MathHelper.lerp(
this._points[i - 1].value,
this._points[i].value,
m
);
}
}
throw new Error('should never be here');
}
public _points: IAnimFrame[];
}
}
+57 -10
View File
@@ -250,7 +250,7 @@ module es {
*/
public static getClosestPointOnRectangleToPoint(rect: Rectangle, point: Vector2) {
// 对于每个轴,如果该点在盒子外面,则将在盒子上,否则不理会它
let res = new Vector2();
let res = es.Vector2.zero;
res.x = MathHelper.clamp(point.x, rect.left, rect.right)
res.y = MathHelper.clamp(point.y, rect.top, rect.bottom);
@@ -264,9 +264,9 @@ module es {
*/
public static getClosestPointOnRectangleBorderToPoint(rect: Rectangle, point: Vector2) {
// 对于每个轴,如果该点在盒子外面,则将在盒子上,否则不理会它
let res = new Vector2();
res.x = MathHelper.clamp(point.x, rect.left, rect.right)
res.y = MathHelper.clamp(point.y, rect.top, rect.bottom);
let res = es.Vector2.zero;
res.x = MathHelper.clamp(Math.trunc(point.x), rect.left, rect.right)
res.y = MathHelper.clamp(Math.trunc(point.y), rect.top, rect.bottom);
// 如果点在矩形内,我们需要将res推到边框,因为它将在矩形内
if (rect.contains(res.x, res.y)) {
@@ -327,7 +327,54 @@ module es {
maxY = pt.y;
}
return this.fromMinMaxVector(new Vector2(minX, minY), new Vector2(maxX, maxY));
return this.fromMinMaxVector(new Vector2(Math.trunc(minX), Math.trunc(minY)), new Vector2(Math.trunc(maxX), Math.trunc(maxY)));
}
public static calculateBounds(rect: Rectangle, parentPosition: Vector2, position: Vector2, origin: Vector2, scale: Vector2,
rotation: number, width: number, height: number) {
if (rotation == 0) {
rect.x = Math.trunc(parentPosition.x + position.x - origin.x * scale.x);
rect.y = Math.trunc(parentPosition.y + position.y - origin.y * scale.y);
rect.width = Math.trunc(width * scale.x);
rect.height = Math.trunc(height * scale.y);
} else {
// 我们需要找到我们的绝对最小/最大值,并据此创建边界
let worldPosX = parentPosition.x + position.x;
let worldPosY = parentPosition.y + position.y;
let tempMat: Matrix2D;
// 考虑到原点,将参考点设置为世界参考
let transformMatrix = new Matrix2D();
Matrix2D.createTranslation(-worldPosX - origin.x, -worldPosY - origin.y, transformMatrix);
Matrix2D.createScale(scale.x, scale.y, tempMat);
transformMatrix = transformMatrix.multiply(tempMat);
Matrix2D.createRotation(rotation, tempMat);
transformMatrix =transformMatrix.multiply(tempMat);
Matrix2D.createTranslation(worldPosX, worldPosY, tempMat);
transformMatrix = transformMatrix.multiply(tempMat);
// TODO: 我们可以把世界变换留在矩阵中,避免在世界空间中得到所有的四个角
let topLeft = new Vector2(worldPosX, worldPosY);
let topRight = new Vector2(worldPosX + width, worldPosY);
let bottomLeft = new Vector2(worldPosX, worldPosY + height);
let bottomRight = new Vector2(worldPosX + width, worldPosY + height);
Vector2Ext.transformR(topLeft, transformMatrix, topLeft);
Vector2Ext.transformR(topRight, transformMatrix, topRight);
Vector2Ext.transformR(bottomLeft, transformMatrix, bottomLeft);
Vector2Ext.transformR(bottomRight, transformMatrix, bottomRight);
// 找出最小值和最大值,这样我们就可以计算出我们的边界框。
let minX = Math.trunc(Math.min(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x));
let maxX = Math.trunc(Math.max(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x));
let minY = Math.trunc(Math.min(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y));
let maxY = Math.trunc(Math.max(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y));
rect.location = new Vector2(minX, minY);
rect.width = Math.trunc(maxX - minX);
rect.height = Math.trunc(maxY - minY);
}
}
/**
@@ -336,14 +383,14 @@ module es {
* @param scale
*/
public static scale(rect: Rectangle, scale: Vector2) {
rect.x = rect.x * scale.x;
rect.y = rect.y * scale.y;
rect.width = rect.width * scale.x;
rect.height = rect.height * scale.y;
rect.x = Math.trunc(rect.x * scale.x);
rect.y = Math.trunc(rect.y * scale.y);
rect.width = Math.trunc(rect.width * scale.x);
rect.height = Math.trunc(rect.height * scale.y);
}
public static translate(rect: Rectangle, vec: Vector2) {
rect.location.add(vec);
rect.location.addEqual(vec);
}
}
}
+7 -7
View File
@@ -7,7 +7,7 @@ module es {
* @param c
*/
public static isTriangleCCW(a: Vector2, center: Vector2, c: Vector2) {
return this.cross(Vector2.subtract(center, a), Vector2.subtract(c, center)) < 0;
return this.cross(center.sub(a), c.sub(center)) < 0;
}
public static halfVector(): Vector2 {
@@ -48,7 +48,7 @@ module es {
public static angle(from: Vector2, to: Vector2) {
this.normalize(from);
this.normalize(to);
return Math.acos(MathHelper.clamp(Vector2.dot(from, to), -1, 1)) * MathHelper.Rad2Deg;
return Math.acos(MathHelper.clamp(from.dot(to), -1, 1)) * MathHelper.Rad2Deg;
}
/**
@@ -58,8 +58,8 @@ module es {
* @param right
*/
public static angleBetween(self: Vector2, left: Vector2, right: Vector2) {
let one = Vector2.subtract(left, self);
let two = Vector2.subtract(right, self);
const one = left.sub(self);
const two = right.sub(self);
return this.angle(one, two);
}
@@ -71,7 +71,7 @@ module es {
* @param d
* @param intersection
*/
public static getRayIntersection(a: Vector2, b: Vector2, c: Vector2, d: Vector2, intersection: Vector2 = new Vector2()) {
public static getRayIntersection(a: Vector2, b: Vector2, c: Vector2, d: Vector2, intersection: Vector2 = es.Vector2.zero) {
let dy1 = b.y - a.y;
let dx1 = b.x - a.x;
let dy2 = d.y - c.y;
@@ -99,7 +99,7 @@ module es {
public static normalize(vec: Vector2) {
let magnitude = Math.sqrt((vec.x * vec.x) + (vec.y * vec.y));
if (magnitude > MathHelper.Epsilon) {
vec.divide(new Vector2(magnitude));
vec.divideScaler(magnitude);
} else {
vec.x = vec.y = 0;
}
@@ -131,7 +131,7 @@ module es {
* @param matrix
* @param result
*/
public static transformR(position: Vector2, matrix: Matrix2D, result: Vector2 = new Vector2()) {
public static transformR(position: Vector2, matrix: Matrix2D, result: Vector2 = es.Vector2.zero) {
let x = (position.x * matrix.m11) + (position.y * matrix.m21) + matrix.m31;
let y = (position.x * matrix.m12) + (position.y * matrix.m22) + matrix.m32;
result.x = x;
+184
View File
@@ -0,0 +1,184 @@
module es {
export interface IListener {
caller: object;
callback: Function;
}
export interface IObservable {
addListener(caller: object, callback: Function);
removeListener(caller: object, callback: Function);
clearListener();
clearListenerWithCaller(caller: object);
}
export class Observable implements IObservable {
public constructor() {
this._listeners = [];
}
public addListener(caller: object, callback: Function) {
if (
this._listeners.findIndex(
listener =>
listener.callback === callback && listener.caller === caller
) === -1
) {
this._listeners.push({ caller, callback });
}
}
public removeListener(caller: object, callback: Function) {
const index = this._listeners.findIndex(
listener => listener.callback === callback && listener.caller === caller
);
if (index >= 0) {
this._listeners.splice(index, 1);
}
}
public clearListener() {
this._listeners = [];
}
public clearListenerWithCaller(caller: object) {
for (let i = this._listeners.length - 1; i >= 0; i--) {
const listener = this._listeners[i];
if (listener.caller === caller) {
this._listeners.splice(i, 1);
}
}
}
public notify(...args) {
for (let i = this._listeners.length - 1; i >= 0; i--) {
const listener = this._listeners[i];
if (listener.caller) {
listener.callback.call(listener.caller, ...args);
} else {
listener.callback(...args);
}
}
}
private _listeners: IListener[];
}
export class ObservableT<T> extends Observable {
public addListener(caller: object, callback: (arg: T) => void) {
super.addListener(caller, callback);
}
public removeListener(caller: object, callback: (arg: T) => void) {
super.removeListener(caller, callback);
}
public notify(arg: T) {
super.notify(arg);
}
}
export class ObservableTT<T, R> extends Observable {
public addListener(caller: object, callback: (arg1: T, arg2: R) => void) {
super.addListener(caller, callback);
}
public removeListener(caller: object, callback: (arg: T, arg2: R) => void) {
super.removeListener(caller, callback);
}
public notify(arg1: T, arg2: R) {
super.notify(arg1, arg2);
}
}
export class Command implements IObservable {
public constructor(caller: object, action: Function) {
this.bindAction(caller, action);
this._onExec = new Observable();
}
public bindAction(caller: object, action: Function) {
this._caller = caller;
this._action = action;
}
public dispatch(...args: any[]) {
if (this._action) {
if (this._caller) {
this._action.call(this._caller, ...args);
} else {
this._action(...args);
}
this._onExec.notify();
} else {
console.warn('command not bind with an action');
}
}
public addListener(caller: object, callback: Function) {
this._onExec.addListener(caller, callback);
}
public removeListener(caller: object, callback: Function) {
this._onExec.removeListener(caller, callback);
}
public clearListener() {
this._onExec.clearListener();
}
public clearListenerWithCaller(caller: object) {
this._onExec.clearListenerWithCaller(caller);
}
private _onExec: Observable;
private _caller: object;
private _action: Function;
}
export class ValueChangeCommand<T> implements IObservable {
public constructor(value: T) {
this._onValueChange = new Observable();
this._value = value;
}
public get onValueChange() {
return this._onValueChange;
}
public get value() {
return this._value;
}
public set value(newValue: T) {
this._value = newValue;
}
public dispatch(value: T) {
if (value !== this._value) {
const oldValue = this._value;
this._value = value;
this._onValueChange.notify(this._value, oldValue);
}
}
public addListener(caller: object, callback: Function) {
this._onValueChange.addListener(caller, callback);
}
public removeListener(caller: object, callback: Function) {
this._onValueChange.removeListener(caller, callback);
}
public clearListener() {
this._onValueChange.clearListener();
}
public clearListenerWithCaller(caller: object) {
this._onValueChange.clearListenerWithCaller(caller);
}
private _onValueChange: Observable;
private _value: T;
}
}
@@ -63,14 +63,14 @@ module es {
* @param radius
*/
public addCircleOccluder(position: Vector2, radius: number){
let dirToCircle = Vector2.subtract(position, this._origin);
let dirToCircle = position.sub(this._origin);
let angle = Math.atan2(dirToCircle.y, dirToCircle.x);
let stepSize = Math.PI / this.lineCountForCircleApproximation;
let startAngle = angle + MathHelper.PiOver2;
let lastPt = MathHelper.angleToVector(startAngle, radius).add(position);
let lastPt = MathHelper.angleToVector(startAngle, radius).addEqual(position);
for (let i = 1; i < this.lineCountForCircleApproximation; i ++) {
let nextPt = MathHelper.angleToVector(startAngle + i * stepSize, radius).add(position);
let nextPt = MathHelper.angleToVector(startAngle + i * stepSize, radius).addEqual(position);
this.addLineOccluder(lastPt, nextPt);
lastPt = nextPt;
}
+3 -3
View File
@@ -13,15 +13,15 @@ module es {
public static testPointTriangle(point: Vector2, a: Vector2, b: Vector2, c: Vector2): boolean {
// 如果点在AB的右边,那么外边的三角形是
if (Vector2Ext.cross(Vector2.subtract(point, a), Vector2.subtract(b, a)) < 0)
if (Vector2Ext.cross(point.sub(a), b.sub(a)) < 0)
return false;
// 如果点在BC的右边,则在三角形的外侧
if (Vector2Ext.cross(Vector2.subtract(point, b), Vector2.subtract(c, b)) < 0)
if (Vector2Ext.cross(point.sub(b), c.sub(b)) < 0)
return false;
// 如果点在ca的右边,则在三角形的外面
if (Vector2Ext.cross(Vector2.subtract(point, c), Vector2.subtract(a, c)) < 0)
if (Vector2Ext.cross(point.sub(c), a.sub(c)) < 0)
return false;
// 点在三角形上