Merge branch 'master' of https://github.com/esengine/ecs-framework into master
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
+6514
File diff suppressed because it is too large
Load Diff
Vendored
+1441
-138
File diff suppressed because it is too large
Load Diff
+4434
-674
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+5
-3
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 我们的光线投射原点角的支架(TR、TL、BR、BL)
|
||||
*/
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,5 +8,9 @@ module es {
|
||||
* 每帧更新事件
|
||||
*/
|
||||
frameUpdated,
|
||||
/**
|
||||
* 当渲染发生时触发
|
||||
*/
|
||||
renderChanged,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
module es {
|
||||
export interface ICamera extends Component {
|
||||
bounds: Rectangle;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,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 {
|
||||
|
||||
@@ -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(){
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,列表碰撞器字典
|
||||
* 它的主要目的是将int、int x、y坐标散列到单个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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
module es {
|
||||
/**
|
||||
* AbstractTweenable作为你可能想做的任何可以执行的自定义类的基础。
|
||||
* 这些类不同于ITweens,因为他们没有实现ITweenT接口。
|
||||
* 它只是说一个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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
* 助手的一个方法,它接收一个EaseType,并通过给定的持续时间和时间参数来应用该Ease方程。
|
||||
* 我们这样做是为了避免传来传去的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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
module es {
|
||||
/**
|
||||
* 标准缓和方程通过将b和c参数(起始值和变化值)用0和1替换,然后进行简化。
|
||||
* 这样做的目的是为了让我们可以得到一个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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
module es {
|
||||
/**
|
||||
* 一系列静态方法来处理所有常见的tween类型结构,以及它们的unclamped lerps.unclamped lerps对于超过0-1范围的bounce、elastic或其他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的引用。如果在两次调用之间改变targetValue,请务必将其重置为0
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
/**
|
||||
* 允许你添加一个tween,这个tween完成后会被运行。
|
||||
* 注意 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扭曲为elapsedTime,并将其限制在0和duration之间,无论tween对象是暂停、完成还是运行,都会立即更新
|
||||
* @param elapsedTime 所用时间
|
||||
*/
|
||||
jumpToElapsedTime(elapsedTime: number);
|
||||
/**
|
||||
* 当从StartCoroutine调用时,它将直到tween完成
|
||||
*/
|
||||
waitForCompletion(): any;
|
||||
/**
|
||||
* 获取tween的目标,如果TweenTargets不一定都是一个对象,则为null,它的唯一真正用途是让TweenManager按目标查找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不一定都是一个对象,则为null,它的唯一真正用途是让TweenManager按目标查找tweens的列表
|
||||
*/
|
||||
getTargetObject(): any;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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[];
|
||||
}
|
||||
}
|
||||
@@ -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 +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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
// 点在三角形上
|
||||
|
||||
Reference in New Issue
Block a user