diff --git a/v3/src/checksum.js b/v3/src/checksum.js index f4e7ee38a..ded89feaf 100644 --- a/v3/src/checksum.js +++ b/v3/src/checksum.js @@ -1,4 +1,4 @@ var CHECKSUM = { -build: 'b1e0db40-5608-11e7-9343-73f81cbe0a9c' +build: '93f8c470-56db-11e7-a08e-87dbe325345b' }; module.exports = CHECKSUM; \ No newline at end of file diff --git a/v3/src/physics/impact/Body.js b/v3/src/physics/impact/Body.js new file mode 100644 index 000000000..5baa78724 --- /dev/null +++ b/v3/src/physics/impact/Body.js @@ -0,0 +1,195 @@ +// Phaser.Physics.Impact.Body + +var Class = require('../../utils/Class'); +var UpdateVelocity = require('./UpdateVelocity'); +var UpdateMotion = require('./UpdateMotion'); +var Trace = require('./Trace'); +var COLLIDES = require('./COLLIDES'); +var TYPE = require('./TYPE'); + +/** +* An Impact.js compatible physics body. +* This re-creates the properties you'd get on an Entity and the math needed to update them. +* +* @class +*/ + +var Body = new Class({ + + initialize: + + function Body (world, x, y) + { + this.world = world; + + this.enabled = true; + + this.size = { x: 16, y: 16 }; + this.offset = { x: 0, y: 0 }; + this.pos = { x: x, y: y }; + this.last = { x: 0, y: 0 }; + this.vel = { x: 0, y: 0 }; + this.accel = { x: 0, y: 0 }; + this.friction = { x: 0, y: 0 }; + this.maxVel = { x: 100, y: 100 }; + this.gravityFactor = 1; + this.standing = false; + this.bounciness = 0; + this.minBounceVelocity = 40; + + this.type = TYPE.NONE; + this.checkAgainst = TYPE.NONE; + this.collides = COLLIDES.NEVER; + + // min 44 deg, max 136 deg + this.slopeStanding = { min: 0.767944870877505, max: 2.3736477827122884 }; + }, + + update: function (delta) + { + this.last.x = this.pos.x; + this.last.y = this.pos.y; + + this.vel.y += this.world.gravity * delta * this.gravityFactor; + + UpdateVelocity(this, delta); + + var mx = this.vel.x * delta; + var my = this.vel.y * delta; + + var res = Trace(this.pos.x, this.pos.y, mx, my, this.size.x, this.size.y); + + UpdateMotion(this, res); + }, + + skipHash: function () + { + return (!this.enabled || (this.type === 0 && this.checkAgainst === 0 && this.collides === 0)); + }, + + touches: function (other) + { + return !( + this.pos.x >= other.pos.x + other.size.x || + this.pos.x + this.size.x <= other.pos.x || + this.pos.y >= other.pos.y + other.size.y || + this.pos.y + this.size.y <= other.pos.y + ); + }, + + setVelocityX: function (x) + { + this.vel.x = x; + + return this; + }, + + setVelocityY: function (y) + { + this.vel.y = y; + + return this; + }, + + setVelocity: function (x, y) + { + this.vel.x = x; + this.vel.y = y; + + return this; + }, + + setMaxVelocity: function (x, y) + { + if (y === undefined) { y = x; } + + this.maxVel.x = x; + this.maxVel.y = y; + + return this; + }, + + setTypeNone: function () + { + this.type = TYPE.NONE; + + return this; + }, + + setTypeA: function () + { + this.type = TYPE.A; + + return this; + }, + + setTypeB: function () + { + this.type = TYPE.B; + + return this; + }, + + setCheckAgainstNone: function () + { + this.checkAgainst = TYPE.NONE; + + return this; + }, + + setCheckAgainstA: function () + { + this.checkAgainst = TYPE.A; + + return this; + }, + + setCheckAgainstB: function () + { + this.checkAgainst = TYPE.B; + + return this; + }, + + setCollidesNever: function () + { + this.collides = COLLIDES.NEVER; + + return this; + }, + + setLite: function () + { + this.collides = COLLIDES.LITE; + + return this; + }, + + setPassive: function () + { + this.collides = COLLIDES.PASSIVE; + + return this; + }, + + setActive: function () + { + this.collides = COLLIDES.ACTIVE; + + return this; + }, + + setFixed: function () + { + this.collides = COLLIDES.FIXED; + + return this; + }, + + check: function( other ) {}, + + collideWith: function( other, axis ) {} + +}); + +module.exports = Body; diff --git a/v3/src/physics/impact/COLLIDES.js b/v3/src/physics/impact/COLLIDES.js new file mode 100644 index 000000000..344636902 --- /dev/null +++ b/v3/src/physics/impact/COLLIDES.js @@ -0,0 +1,17 @@ +// Collision Types - Determine if and how entities collide with each other + +// In ACTIVE vs. LITE or FIXED vs. ANY collisions, only the "weak" entity moves, +// while the other one stays fixed. In ACTIVE vs. ACTIVE and ACTIVE vs. PASSIVE +// collisions, both entities are moved. LITE or PASSIVE entities don't collide +// with other LITE or PASSIVE entities at all. The behaiviour for FIXED vs. +// FIXED collisions is undefined. + +module.exports = { + + NEVER: 0, + LITE: 1, + PASSIVE: 2, + ACTIVE: 4, + FIXED: 8 + +}; diff --git a/v3/src/physics/impact/SeperateX.js b/v3/src/physics/impact/SeperateX.js new file mode 100644 index 000000000..4dbd766b6 --- /dev/null +++ b/v3/src/physics/impact/SeperateX.js @@ -0,0 +1,35 @@ +var Trace = require('./Trace'); + +var SeperateX = function (world, left, right, weak) +{ + var nudge = left.pos.x + left.size.x - right.pos.x; + + // We have a weak entity, so just move this one + if (weak) + { + var strong = (left === weak) ? right : left; + + weak.vel.x = -weak.vel.x * weak.bounciness + strong.vel.x; + + var resWeak = Trace(weak.pos.x, weak.pos.y, weak === left ? -nudge : nudge, 0, weak.size.x, weak.size.y); + + weak.pos.x = resWeak.pos.x; + } + else + { + var v2 = (left.vel.x - right.vel.x) / 2; + + left.vel.x = -v2; + right.vel.x = v2; + + var resLeft = Trace(left.pos.x, left.pos.y, -nudge / 2, 0, left.size.x, left.size.y); + + left.pos.x = Math.floor(resLeft.pos.x); + + var resRight = Trace(right.pos.x, right.pos.y, nudge / 2, 0, right.size.x, right.size.y); + + right.pos.x = Math.ceil(resRight.pos.x); + } +}; + +module.exports = SeperateX; diff --git a/v3/src/physics/impact/SeperateY.js b/v3/src/physics/impact/SeperateY.js new file mode 100644 index 000000000..534d32562 --- /dev/null +++ b/v3/src/physics/impact/SeperateY.js @@ -0,0 +1,62 @@ +var Trace = require('./Trace'); + +var SeperateY = function (world, top, bottom, weak) +{ + var nudge = (top.pos.y + top.size.y - bottom.pos.y); + + if (weak) + { + var strong = (top === weak) ? bottom : top; + + weak.vel.y = -weak.vel.y * weak.bounciness + strong.vel.y; + + // Riding on a platform? + var nudgeX = 0; + + if (weak === top && Math.abs(weak.vel.y - strong.vel.y) < weak.minBounceVelocity) + { + weak.standing = true; + nudgeX = strong.vel.x * world.delta; + } + + var resWeak = Trace(weak.pos.x, weak.pos.y, nudgeX, weak == top ? -nudge : nudge, weak.size.x, weak.size.y); + + weak.pos.y = resWeak.pos.y; + weak.pos.x = resWeak.pos.x; + } + else if (world.gravity && (bottom.standing || top.vel.y > 0)) + { + var resTop = Trace(top.pos.x, top.pos.y, 0, -(top.pos.y + top.size.y - bottom.pos.y), top.size.x, top.size.y); + + top.pos.y = resTop.pos.y; + + if (top.bounciness > 0 && top.vel.y > top.minBounceVelocity) + { + top.vel.y *= -top.bounciness; + } + else + { + top.standing = true; + top.vel.y = 0; + } + } + else + { + var v2 = (top.vel.y - bottom.vel.y) / 2; + + top.vel.y = -v2; + bottom.vel.y = v2; + + var nudgeX = bottom.vel.x * world.delta; + + var resTop = Trace(top.pos.x, top.pos.y, nudgeX, -nudge/2, top.size.x, top.size.y); + + top.pos.y = resTop.pos.y; + + var resBottom = Trace(bottom.pos.x, bottom.pos.y, 0, nudge/2, bottom.size.x, bottom.size.y); + + bottom.pos.y = resBottom.pos.y; + } +}; + +module.exports = SeperateY; diff --git a/v3/src/physics/impact/Solver.js b/v3/src/physics/impact/Solver.js new file mode 100644 index 000000000..80769edaf --- /dev/null +++ b/v3/src/physics/impact/Solver.js @@ -0,0 +1,51 @@ +var SeperateX = require('./SeperateX'); +var SeperateY = require('./SeperateY'); +var COLLIDES = require('./COLLIDES'); +var TYPE = require('./TYPE'); + +// Impact Physics Solver + +var Solver = function (world, bodyA, bodyB) +{ + var weak = null; + + if (bodyA.collides === COLLIDES.LITE || bodyB.collides === COLLIDES.FIXED) + { + weak = bodyA; + } + else if (bodyB.collides === COLLIDES.LITE || bodyA.collides === COLLIDES.FIXED) + { + weak = bodyB; + } + + if (bodyA.last.x + bodyA.size.x > bodyB.last.x && bodyA.last.x < bodyB.last.x + bodyB.size.x) + { + if (bodyA.last.y < bodyB.last.y) + { + SeperateY(world, bodyA, bodyB, weak); + } + else + { + SeperateY(world, bodyB, bodyA, weak); + } + + bodyA.collideWith(bodyB, 'y'); + bodyB.collideWith(bodyA, 'y'); + } + else if (bodyA.last.y + bodyA.size.y > bodyB.last.y && bodyA.last.y < bodyB.last.y + bodyB.size.y) + { + if (bodyA.last.x < bodyB.last.x) + { + SeperateX(world, bodyA, bodyB, weak); + } + else + { + SeperateX(world, bodyB, bodyA, weak); + } + + bodyA.collideWith(bodyB, 'x'); + bodyB.collideWith(bodyA, 'x'); + } +}; + +module.exports = Solver; diff --git a/v3/src/physics/impact/TYPE.js b/v3/src/physics/impact/TYPE.js new file mode 100644 index 000000000..061076c61 --- /dev/null +++ b/v3/src/physics/impact/TYPE.js @@ -0,0 +1,16 @@ +// Collision Types - Determine if and how entities collide with each other + +// In ACTIVE vs. LITE or FIXED vs. ANY collisions, only the "weak" entity moves, +// while the other one stays fixed. In ACTIVE vs. ACTIVE and ACTIVE vs. PASSIVE +// collisions, both entities are moved. LITE or PASSIVE entities don't collide +// with other LITE or PASSIVE entities at all. The behaiviour for FIXED vs. +// FIXED collisions is undefined. + +module.exports = { + + NONE: 0, + A: 1, + B: 2, + BOTH: 3 + +}; diff --git a/v3/src/physics/impact/Trace.js b/v3/src/physics/impact/Trace.js new file mode 100644 index 000000000..a1d784b4c --- /dev/null +++ b/v3/src/physics/impact/Trace.js @@ -0,0 +1,10 @@ +var Trace = function (x, y, vx, vy) +{ + return { + collision: { x: false, y: false, slope: false }, + pos: { x: x + vx, y: y + vy }, + tile: { x: 0, y: 0 } + }; +}; + +module.exports = Trace; diff --git a/v3/src/physics/impact/UpdateMotion.js b/v3/src/physics/impact/UpdateMotion.js new file mode 100644 index 000000000..375f3d7ba --- /dev/null +++ b/v3/src/physics/impact/UpdateMotion.js @@ -0,0 +1,79 @@ +// Set up the trace-result +// var res = { +// collision: {x: false, y: false, slope: false}, +// pos: {x: x, y: y}, +// tile: {x: 0, y: 0} +// }; + +var UpdateMotion = function (body, res) +{ + body.standing = false; + + // Y + if (res.collision.y) + { + if (body.bounciness > 0 && Math.abs(body.vel.y) > body.minBounceVelocity) + { + body.vel.y *= -body.bounciness; + } + else + { + if (body.vel.y > 0) + { + body.standing = true; + } + + body.vel.y = 0; + } + } + + // X + if (res.collision.x) + { + if (body.bounciness > 0 && Math.abs(body.vel.x) > body.minBounceVelocity) + { + body.vel.x *= -body.bounciness; + } + else + { + body.vel.x = 0; + } + } + + // SLOPE + if (res.collision.slope) + { + var s = res.collision.slope; + + if (body.bounciness > 0) + { + var proj = body.vel.x * s.nx + body.vel.y * s.ny; + + body.vel.x -= s.nx * proj * 2; + body.vel.y -= s.ny * proj * 2; + + body.vel.x *= body.bounciness; + body.vel.y *= body.bounciness; + } + else + { + var lengthSquared = s.x * s.x + s.y * s.y; + var dot = (body.vel.x * s.x + body.vel.y * s.y) / lengthSquared; + + body.vel.x = s.x * dot; + body.vel.y = s.y * dot; + + var angle = Math.atan2(s.x, s.y); + + if (angle > body.slopeStanding.min && angle < body.slopeStanding.max) + { + body.standing = true; + } + } + } + + body.pos.x = res.pos.x; + body.pos.y = res.pos.y; +}; + +module.exports = UpdateMotion; diff --git a/v3/src/physics/impact/UpdateVelocity.js b/v3/src/physics/impact/UpdateVelocity.js new file mode 100644 index 000000000..419972392 --- /dev/null +++ b/v3/src/physics/impact/UpdateVelocity.js @@ -0,0 +1,73 @@ +var Clamp = require('../../math/Clamp'); + +var UpdateVelocity = function (body, delta) +{ + var vel = body.vel.x; + var accel = body.accel.x; + var friction = body.friction.x; + var max = body.maxVel.x; + var frictionDelta; + + // X + if (accel) + { + body.vel.x = Clamp(vel + accel * delta, -max, max); + } + else if (friction) + { + frictionDelta = friction * delta; + + if (vel - frictionDelta > 0) + { + body.vel.x = vel - frictionDelta; + } + else if (vel + frictionDelta < 0) + { + body.vel.x = vel + frictionDelta; + } + else + { + body.vel.x = 0; + } + } + else + { + body.vel.x = Clamp(vel, -max, max); + } + + vel = body.vel.y; + accel = body.accel.y; + friction = body.friction.y; + max = body.maxVel.y; + + // Y + if (accel) + { + body.vel.y = Clamp(vel + accel * delta, -max, max); + } + else if (friction) + { + frictionDelta = friction * delta; + + if (vel - frictionDelta > 0) + { + body.vel.y = vel - frictionDelta; + } + else if (vel + frictionDelta < 0) + { + body.vel.y = vel + frictionDelta; + } + else + { + body.vel.y = 0; + } + } + else + { + body.vel.y = Clamp(vel, -max, max); + } + + return body; +}; + +module.exports = UpdateVelocity; diff --git a/v3/src/physics/impact/World.js b/v3/src/physics/impact/World.js new file mode 100644 index 000000000..f5c099f4e --- /dev/null +++ b/v3/src/physics/impact/World.js @@ -0,0 +1,143 @@ +// Phaser.Physics.Impact.World + +var Class = require('../../utils/Class'); +var Set = require('../../structs/Set'); +var Body = require('./Body'); +var Solver = require('./Solver'); +var COLLIDES = require('./COLLIDES'); +var TYPE = require('./TYPE'); + +var World = new Class({ + + initialize: + + function World () + { + this.bodies = new Set(); + + this.gravity = 0; + + this.delta = 0; + + // Spatial hash cell dimensions + this.cellSize = 64; + }, + + create: function (x, y) + { + var body = new Body(this, x, y); + + this.bodies.set(body); + + return body; + }, + + update: function (time, delta) + { + if (this.bodies.size === 0) + { + return; + } + + // Impact uses a divided delta value + delta /= 1000; + + this.delta = delta; + + // Update all bodies + + this.bodies.iterate(function (body) { + + if (body.enabled) + { + body.update(delta); + } + + }); + + // Run collision against them all + + var hash = {}; + var size = this.cellSize; + + var bodies = this.bodies.entries; + + for (var i = 0; i < bodies.length; i++) + { + var body = bodies[i]; + + if (body.skipHash()) + { + continue; + } + else + { + this.checkHash(body, hash, size); + } + } + }, + + // Check the body against the spatial hash + checkHash: function (body, hash, size) + { + var checked = {}; + var xmin = Math.floor(body.pos.x / size); + var ymin = Math.floor(body.pos.y / size); + var xmax = Math.floor((body.pos.x + body.size.x) / size) + 1; + var ymax = Math.floor((body.pos.y + body.size.y) / size) + 1; + + for (var x = xmin; x < xmax; x++) + { + for (var y = ymin; y < ymax; y++) + { + if (!hash[x]) + { + hash[x] = {}; + hash[x][y] = [body]; + } + else if (!hash[x][y]) + { + hash[x][y] = [body]; + } + else + { + var cell = hash[x][y]; + + for (var c = 0; c < cell.length; c++) + { + if (body.touches(cell[c]) && !checked[cell[c].id]) + { + checked[cell[c].id] = true; + + this.checkBodies(body, cell[c]); + } + } + + cell.push(body); + } + } + } + }, + + checkBodies: function (bodyA, bodyB) + { + // bitwise checks + if (bodyA.checkAgainst & bodyB.type) + { + bodyA.check(bodyB); + } + + if (bodyB.checkAgainst & bodyA.type) + { + bodyB.check(bodyA); + } + + if (bodyA.collides && bodyB.collides && bodyA.collides + bodyB.collides > COLLIDES.ACTIVE) + { + Solver(this, bodyA, bodyB); + } + } + +}); + +module.exports = World; diff --git a/v3/src/physics/impact/index.js b/v3/src/physics/impact/index.js new file mode 100644 index 000000000..8688a0a05 --- /dev/null +++ b/v3/src/physics/impact/index.js @@ -0,0 +1,10 @@ +// Phaser.Physics.Impact + +module.exports = { + + Body: require('./Body'), + World: require('./World'), + COLLIDES: require('./COLLIDES'), + TYPE: require('./TYPE'), + +}; diff --git a/v3/src/physics/index.js b/v3/src/physics/index.js index 7ad1e3e95..e0086e610 100644 --- a/v3/src/physics/index.js +++ b/v3/src/physics/index.js @@ -1,6 +1,7 @@ // Phaser.Physics module.exports = { + Impact: require('./impact'), PolyDecomp: require('./poly-decomp/'), MatterJS: require('./matter-js/module/main') };