UNPKG

planck-js

Version:

2D JavaScript physics engine for cross-platform HTML5 game development

284 lines (234 loc) 7.5 kB
/* * MIT License * Copyright (c) 2019 Erin Catto * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ planck.testbed('DynamicTreeTest', function(testbed) { var pl = planck, Vec2 = pl.Vec2; var world = new pl.World(); var ACTOR_COUNT = 128; var worldExtent = 15.0; var proxyExtent = 0.5; var tree = new pl.internal.DynamicTree(); var queryAABB = pl.AABB(); var rayCastInput = {}; var rayCastOutput = {}; var rayActor; var actors = []; // Actor[e_actorCount]; var automated = false; for (var i = 0; i < ACTOR_COUNT; ++i) { var actor = actors[i] = new Actor(); getRandomAABB(actor.aabb); actor.proxyId = tree.createProxy(actor.aabb, actor); } var h = worldExtent; queryAABB.lowerBound.set(-3.0, -4.0 + h); queryAABB.upperBound.set(5.0, 6.0 + h); rayCastInput.p1 = Vec2(-5.0, 5.0 + h); rayCastInput.p2 = Vec2(7.0, -4.0 + h); // rayCastInput.p1 = Vec2(0.0, 2.0 + h); // rayCastInput.p2 = Vec2(0.0, -2.0 + h); rayCastInput.maxFraction = 1.0; testbed.step = function() { rayActor = null; for (var i = 0; i < ACTOR_COUNT; ++i) { actors[i].fraction = 1.0; actors[i].overlap = false; } if (automated == true) { var actionCount = Math.max(1, ACTOR_COUNT >> 2); for (var i = 0; i < actionCount; ++i) { Action(); } } Query(); rayCast(); for (var i = 0; i < ACTOR_COUNT; ++i) { var actor = actors[i]; if (actor.proxyId == null) continue; var c = testbed.color(0.9, 0.9, 0.9); if (actor == rayActor && actor.overlap) { c = testbed.color(0.9, 0.6, 0.6); } else if (actor == rayActor) { c = testbed.color(0.6, 0.9, 0.6); } else if (actor.overlap) { c = testbed.color(0.6, 0.6, 0.9); } testbed.drawAABB(actor.aabb, c); } testbed.drawAABB(queryAABB, testbed.color(0.7, 0.7, 0.7)); testbed.drawSegment(rayCastInput.p1, rayCastInput.p2, c); testbed.drawPoint(rayCastInput.p1, 6.0, testbed.color(0.2, 0.9, 0.2)); testbed.drawPoint(rayCastInput.p2, 6.0, testbed.color(0.9, 0.2, 0.2)); if (rayActor) { var p = Vec2.combine(1 - rayActor.fraction, rayCastInput.p1, rayActor.fraction, rayCastInput.p2); testbed.drawPoint(p, 6.0, testbed.color(0.2, 0.2, 0.9)); } var height = tree.getHeight(); testbed.status("dynamic tree height", height); }; testbed.keydown = function(code, char) { switch (char) { case 'Z': automated = !automated; break; case 'C': createProxy(); break; case 'X': destroyProxy(); break; case 'M': moveProxy(); break; } }; function queryCallback(proxyId) { var actor = tree.getUserData(proxyId); // Actor actor.overlap = pl.AABB.testOverlap(queryAABB, actor.aabb); return true; } function rayCastCallback(input, proxyId) { var actor = tree.getUserData(proxyId); var output = {}; // RayCastOutput var hit = actor.aabb.rayCast(output, input); if (hit) { rayCastOutput = output; rayActor = actor; rayActor.fraction = output.fraction; return output.fraction; } return input.maxFraction; } function Actor() { this.aabb = pl.AABB(); this.fraction; this.overlap; this.proxyId; } function getRandomAABB(aabb) { var w = Vec2(2.0 * proxyExtent, 2.0 * proxyExtent); // aabb.lowerBound.x = -proxyExtent; // aabb.lowerBound.y = -proxyExtent + worldExtent; aabb.lowerBound.x = pl.Math.random(-worldExtent, worldExtent); aabb.lowerBound.y = pl.Math.random(0.0, 2.0 * worldExtent); aabb.upperBound = w.add(aabb.lowerBound); } function moveAABB(aabb) { var d = Vec2(); d.x = pl.Math.random(-0.5, 0.5); d.y = pl.Math.random(-0.5, 0.5); // d.x = 2.0; // d.y = 0.0; aabb.lowerBound.add(d); aabb.upperBound.add(d); var c0 = Vec2.mid(aabb.lowerBound, aabb.upperBound); var min = Vec2(-worldExtent, 0.0); var max = Vec2(worldExtent, 2.0 * worldExtent); var c = Vec2.clamp(c0, min, max); aabb.lowerBound.add(c).sub(c0); aabb.upperBound.add(c).sub(c0); } function createProxy() { for (var i = 0; i < ACTOR_COUNT; ++i) { var j = Math.random() * ACTOR_COUNT | 0; var actor = actors[j]; if (actor.proxyId == null) { getRandomAABB(actor.aabb); actor.proxyId = tree.createProxy(actor.aabb, actor); return; } } } function destroyProxy() { for (var i = 0; i < ACTOR_COUNT; ++i) { var j = Math.random() * ACTOR_COUNT | 0; var actor = actors[j]; if (actor.proxyId != null) { tree.destroyProxy(actor.proxyId); actor.proxyId = null; return; } } } function moveProxy() { for (var i = 0; i < ACTOR_COUNT; ++i) { var j = Math.random() * ACTOR_COUNT | 0; actor = actors[j]; if (actor.proxyId == null) { continue; } var aabb0 = actor.aabb; moveAABB(actor.aabb); var displacement = Vec2.sub(actor.aabb.getCenter(), aabb0.getCenter()); tree.moveProxy(actor.proxyId, actor.aabb, displacement); return; } } function Action() { var choice = Math.random() * 20 | 0; switch (choice) { case 0: createProxy(); break; case 1: destroyProxy(); break; default: moveProxy(); } } function Query() { tree.query(queryAABB, queryCallback); for (var i = 0; i < ACTOR_COUNT; ++i) { if (actors[i].proxyId == null) { continue; } var overlap = pl.AABB.testOverlap(queryAABB, actors[i].aabb); // assert(overlap == actors[i].overlap); } } function rayCast() { rayActor = null; var input = rayCastInput; // RayCastInput // Ray cast against the dynamic tree. tree.rayCast(input, rayCastCallback); // Brute force ray cast. var bruteActor = null; // Actor var bruteOutput = {}; // RayCastOutput for (var i = 0; i < ACTOR_COUNT; ++i) { if (actors[i].proxyId == null) { continue; } var output = {}; // RayCastOutput var hit = actors[i].aabb.rayCast(output, input); if (hit) { bruteActor = actors[i]; bruteOutput = output; input.maxFraction = output.fraction; } } if (bruteActor != null) { // Assert(bruteOutput.fraction == rayCastOutput.fraction); } } return world; });