UNPKG

@zimjs/physics

Version:

Physics with Box2D for ZIM JavaScript Canvas Framework

1,225 lines (1,087 loc) 51.9 kB
// ZIM - https://zimjs.com - Code Creativity! // JavaScript Canvas Framework for General Interactive Media // free to use - donations welcome of course! https://zimjs.com/donate // ZIM PHYSICS - see https://zimjs.com/code#libraries for examples // ~~~~~~~~~~~~~~~~~~~~~~~~ // DESCRIPTION - coded in 2016 (c) ZIM // physics.js is an add-on ZIM module to help with Box2D // (17K) currently only providing a non-minified file - minify to save 10K // built for and requires Box2dWeb // physics.js abstracts the world creation // makes border around the world // makes rectangles, circles and triangles and adds x, y and rotation // maps these to ZIM assets like shapes and bitmaps // wraps mouse control code // abstracts the debug mode // abstracts the update function and provides Ticker access before and after stepping // provides movement controls and world following // the Physics Module has Physics(), addPhysics() and removePhysics() // as well as lots of methods on Physics() and added to ZIM objects // DOCS // Docs are provided at https://zimjs.com/docs.html // See PHYSICS MODULE at bottom // ~~~~~~~~~~~~~~~~~~~~~~~~ import zim from "zimjs"; import Box2D from "box2dweb"; // note - removed the ES5 module pattern as we are getting zim from import // ~~~~~~~~~~~~~~~~~~~~~~~~ var WW = window||{}; var zon = WW.zon?WW.zon:true; WW.b2Vec2 = Box2D.Common.Math.b2Vec2; WW.b2BodyDef = Box2D.Dynamics.b2BodyDef; WW.b2Body = Box2D.Dynamics.b2Body; WW.b2FixtureDef = Box2D.Dynamics.b2FixtureDef; WW.b2Fixture = Box2D.Dynamics.b2Fixture; WW.b2World = Box2D.Dynamics.b2World; WW.b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape; WW.b2CircleShape = Box2D.Collision.Shapes.b2CircleShape; WW.b2MouseJointDef = Box2D.Dynamics.Joints.b2MouseJointDef; WW.b2DistanceJointDef = Box2D.Dynamics.Joints.b2DistanceJointDef; WW.b2RevoluteJointDef = Box2D.Dynamics.Joints.b2RevoluteJointDef; WW.b2WeldJointDef = Box2D.Dynamics.Joints.b2WeldJointDef; WW.b2AABB = Box2D.Collision.b2AABB; WW.b2DebugDraw = Box2D.Dynamics.b2DebugDraw; WW.b2BuoyancyController = Box2D.Dynamics.Controllers.b2BuoyancyController; WW.b2ContactListener = Box2D.Dynamics.b2ContactListener; WW.zimPhysics = true; zim.Physics = function(gravity, borders, scroll, frame) { var sig = "gravity, borders, scroll, frame"; var duo; if (duo = zob(zim.Physics, arguments, sig, this)) return duo; var that = this; if (zot(frame)) { if (WW.zdf) frame = WW.zdf; else if (WW.zimDefaultFrame) frame = WW.zimDefaultFrame; else {console.log("zim.Physics() - please provide a zim Frame object"); return;} } that.frame = frame; if (zot(borders)) borders = {x:0, y:0, width:that.frame.width, height:that.frame.height}; borders = {x:borders.x, y:borders.y, width:borders.width, height:borders.height}; if (zot(gravity)) gravity = 10; if (zot(scroll)) scroll = false; this.scroll = scroll; if (zot(WW.zimDefaultPhysics)) WW.zimDefaultPhysics = this; var stage = that.frame.stage; // adjust for ZIM 10.9.0 and CreateJS 1.3.0 // also turned all stage.scaleX to zim.scaX, etc. if (zot(zim.scaX)) { createjs.stageTransformable = false; zim.scaX = stage.scaleX; zim.scaY = stage.scaleY; } var scale = this.scale = 30; var step = this.step = 20; this.timeStep = 1/step; var isMouseDown = false; var world = this.world = new b2World(new b2Vec2(0, gravity), true); // gravity, allow sleep // each of these return a b2Body with x, y, and rotation properties added this.makeRectangle = function(width, height, dynamic, friction, angular, density, restitution, maskBits, categoryBits, linear, sensor) { var duo; if (duo = zob(that.makeRectangle, arguments)) return duo; return makeShape(["rectangle", width, height], dynamic, friction, angular, density, restitution, maskBits, categoryBits, linear, sensor); } this.makeCircle = function(radius, dynamic, friction, angular, density, restitution, maskBits, categoryBits, linear, sensor) { var duo; if (duo = zob(that.makeCircle, arguments)) return duo; return makeShape(["circle", radius], dynamic, friction, angular, density, restitution, maskBits, categoryBits, linear, sensor); } this.makeTriangle = function(a, b, c, dynamic, friction, angular, density, restitution, maskBits, categoryBits, linear, sensor) { var duo; if (duo = zob(that.makeTriangle, arguments)) return duo; return makeShape(["triangle", a, b, c], dynamic, friction, angular, density, restitution, maskBits, categoryBits, linear, sensor); } this.makePoly = function(points, width, height, dynamic, friction, angular, density, restitution, maskBits, categoryBits, linear, sensor) { var duo; if (duo = zob(that.makePoly, arguments)) return duo; return makeShape(["poly", points, width, height], dynamic, friction, angular, density, restitution, maskBits, categoryBits, linear, sensor); } function makeShape(shape, dynamic, friction, angular, density, restitution, maskBits, categoryBits, linear, sensor) { var type = shape[0]; if (zot(dynamic)) dynamic = true; if (zot(friction)) friction = .8; if (zot(angular)) angular = .5; // rotational damping if (zot(linear)) linear = .5; // linear damping if (zot(density)) density = 1; if (zot(restitution)) restitution = 0; var definition = new b2BodyDef(); if (dynamic == "kinematic") { definition.type = b2Body.b2_kinematicBody; } else if (dynamic) { definition.type = b2Body.b2_dynamicBody; } else { definition.type = b2Body.b2_staticBody; } definition.angularDamping = angular; definition.linearDamping = linear; var body = world.CreateBody(definition); var s; var concave = false; if (type=="rectangle") { s = new b2PolygonShape(); if (zot(shape[1])) shape[1] = 100; if (zot(shape[2])) shape[2] = 100; s.width = shape[1]; s.height = shape[2]; s.SetAsBox(s.width/scale/2, s.height/scale/2); } else if (type=="triangle") { if (!zim) return; s = new b2PolygonShape(); if (zot(shape[1])) shape[1] = 100; if (zot(shape[2])) shape[2] = 100; if (zot(shape[3])) shape[3] = 100; // uses zim Triangle to match Box2D shape var tri = new zim.Triangle(shape[1], shape[2], shape[3]); s.width = tri.width; s.height = tri.height; var points = []; // outside is right of line - so needed to reverse order points[2] = new b2Vec2((tri.one.x-tri.regX)/scale, (tri.one.y+tri.regY)/scale); points[1] = new b2Vec2((tri.two.x-tri.regX)/scale, (tri.two.y+tri.regY)/scale); points[0] = new b2Vec2((tri.three.x-tri.regX)/scale, (tri.three.y+tri.regY)/scale); s.SetAsArray(points, points.length); } else if (type=="poly") { if (!zim) return; s = new b2PolygonShape(); if (zot(shape[1])) shape[1] = [{x:0,y:0}, {x:100,y:0}, {x:100,y:100}, {x:0,y:100}]; if (zot(shape[2])) shape[2] = 100; if (zot(shape[3])) shape[3] = 100; var points = shape[1]; s.width = shape[2]; s.height = shape[3]; for (var i=0; i<points.length; i++) { points[i] = {x:points[i].x/scale, y:points[i].y/scale}; } body.points = points s.SetAsArray(points, points.length); if (!s.Validate()) concave = true; // zogy("ZIM Physics - Poly must be convex"); } else { // circle if (zot(shape[1])) shape[1] = 50; s = new b2CircleShape(shape[1]/scale); s.width = s.height = shape[1]*2; } var fixture = new b2FixtureDef(); if (!zot(categoryBits)) fixture.filter.categoryBits = categoryBits; if (!zot(maskBits)) fixture.filter.maskBits = maskBits; fixture.density = density; fixture.friction = friction; fixture.restitution = restitution; fixture.isSensor = sensor; if (concave) { that.separate(body, fixture, points); } else { fixture.shape = s; body.CreateFixture(fixture); } body.fixture = fixture; body.width = s.width; body.height = s.height; // these hold x, y and rotation local values body.zimX = 0; body.zimY = 0; body.zimR = 0; setBasicProperties(body); return body; } function setBasicProperties(body) { Object.defineProperty(body, 'x', { get: function() { return body.GetWorldCenter().x*that.scale; }, set: function(x) { body.zimX = x; body.SetPosition(new b2Vec2(body.zimX/scale, body.zimY/scale)); } }); Object.defineProperty(body, 'y', { get: function() { return body.GetWorldCenter().y*that.scale; }, set: function(y) { body.zimY = y; body.SetPosition(new b2Vec2(body.zimX/scale, body.zimY/scale)); } }); Object.defineProperty(body, 'rotation', { get: function() { return body.GetAngle()*180/Math.PI; }, set: function(rotation) { body.zimR = rotation; body.SetAngle(rotation*Math.PI/180); } }); body.loc = function(x,y) { if (body.zimObj) { x = x==null ? body.zimObj.x : x; y = y==null ? body.zimObj.y : y; } body.SetPosition(new b2Vec2(x/scale, y/scale)); if (body.zimObj) body.zimObj.force(.01); return body; } body.rot = function(a) { body.rotation = a; return body; } } Object.defineProperty(this, 'gravity', { get: function() { return this.world.GetGravity().y; }, set: function(val) { this.world.SetGravity(new b2Vec2(0, val)); } }); // START b2Separate CODE // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /* * Convex Separator for Box2D Flash * * This class has been written by Antoan Angelov. * It is designed to work with Erin Catto's Box2D physics library. * * Everybody can use this software for any purpose, under two restrictions: * 1. You cannot claim that you wrote this software. * 2. You can not remove or alter this notice. * */ // Adapted for JS by https://github.com/isaksky - see Asteroids // used internally by ZIM Physics for making a concave blob this.separate = function(body, fixtureDef, verticesAry, scale) { scale = scale != null ? scale : 30; var i, n = verticesAry.length, j, m; var vec = [], figsAry; var polyShape; for (i = 0; i < n; i++) { vec.push(new b2Vec2(verticesAry[i].x * scale, verticesAry[i].y * scale)); } figsAry = calcShapes(vec); n = figsAry.length; for (i = 0; i < n; i++) { verticesAry = []; vec = figsAry[i]; m = vec.length; for (j = 0; j < m; j++) { verticesAry.push(new b2Vec2(vec[j].x / scale, vec[j].y / scale)); } polyShape = new b2PolygonShape; polyShape.SetAsVector(verticesAry); fixtureDef.shape = polyShape; body.CreateFixture(fixtureDef); } }; this.validate = function(obj) { // pass in Blob or an array of points // 0 if the vertices can be properly processed // 1 If there are overlapping lines // 2 if the points are not in clockwise order // 3 if there are overlapping lines and the points are not in clockwise order var verticesAry = obj; if (obj.type == "Blob") { var points = obj.points; verticesAry = []; for (var i=0; i<points.length; i++) { verticesAry[i] = {x:points[i][0]/that.scale, y:points[i][1]/that.scale}; } } var i, n = verticesAry.length, j, j2, i2, i3, d, ret = 0; var fl, fl2 = false; for (i = 0; i < n; i++) { i2 = (i < n - 1) ? i + 1 : 0; i3 = (i > 0) ? i - 1 : n - 1; fl = false; for (j = 0; j < n; j++) { if (((j != i) && j != i2)) { if (!fl) { d = det(verticesAry[i].x, verticesAry[i].y, verticesAry[i2].x, verticesAry[i2].y, verticesAry[j].x, verticesAry[j].y); if ((d > 0)) { fl = true; } } if ((j != i3)) { j2 = (j < n - 1) ? j + 1 : 0; if (hitSegment(verticesAry[i].x, verticesAry[i].y, verticesAry[i2].x, verticesAry[i2].y, verticesAry[j].x, verticesAry[j].y, verticesAry[j2].x, verticesAry[j2].y)) { ret = 1; } } } } if (!fl) { fl2 = true; } } if (fl2) { if ((ret == 1)) { ret = 3; } else { ret = 2; } } return ret; }; var calcShapes = function(verticesAry) { var vec; var i, n, j; var d, t, dx, dy, minLen; var i1, i2, i3, p1, p2, p3; var j1, j2, v1, v2, k, h; var vec1, vec2; var v, hitV; var isConvex; var figsAry = [], queue = []; queue.push(verticesAry); while (queue.length) { vec = queue[0]; n = vec.length; isConvex = true; for (i = 0; i < n; i++) { i1 = i; i2 = (i < n - 1) ? i + 1 : i + 1 - n; i3 = (i < n - 2) ? i + 2 : i + 2 - n; p1 = vec[i1]; p2 = vec[i2]; p3 = vec[i3]; d = det(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); if ((d < 0)) { isConvex = false; minLen = Number.MAX_VALUE; for (j = 0; j < n; j++) { if (((j != i1) && j != i2)) { j1 = j; j2 = (j < n - 1) ? j + 1 : 0; v1 = vec[j1]; v2 = vec[j2]; v = hitRay(p1.x, p1.y, p2.x, p2.y, v1.x, v1.y, v2.x, v2.y); if (v) { dx = p2.x - v.x; dy = p2.y - v.y; t = dx * dx + dy * dy; if ((t < minLen)) { h = j1; k = j2; hitV = v; minLen = t; } } } } if ((minLen == Number.MAX_VALUE)) { err(); } vec1 = []; vec2 = []; j1 = h; j2 = k; v1 = vec[j1]; v2 = vec[j2]; if (!pointsMatch(hitV.x, hitV.y, v2.x, v2.y)) { vec1.push(hitV); } if (!pointsMatch(hitV.x, hitV.y, v1.x, v1.y)) { vec2.push(hitV); } h = -1; k = i1; while (true) { if ((k != j2)) { vec1.push(vec[k]); } else { if (((h < 0) || h >= n)) { err(); } if (!isOnSegment(v2.x, v2.y, vec[h].x, vec[h].y, p1.x, p1.y)) { vec1.push(vec[k]); } break; } h = k; if (((k - 1) < 0)) { k = n - 1; } else { k--; } } vec1 = vec1.reverse(); h = -1; k = i2; while (true) { if ((k != j1)) { vec2.push(vec[k]); } else { if (((h < 0) || h >= n)) { err(); } if (((k == j1) && !isOnSegment(v1.x, v1.y, vec[h].x, vec[h].y, p2.x, p2.y))) { vec2.push(vec[k]); } break; } h = k; if (((k + 1) > n - 1)) { k = 0; } else { k++; } } queue.push(vec1, vec2); queue.shift(); break; } } if (isConvex) { figsAry.push(queue.shift()); } } return figsAry; }; var hitRay = function(x1, y1, x2, y2, x3, y3, x4, y4) { var t1 = x3 - x1, t2 = y3 - y1, t3 = x2 - x1, t4 = y2 - y1, t5 = x4 - x3, t6 = y4 - y3, t7 = t4 * t5 - t3 * t6, a; a = (((t5 * t2) - t6 * t1) / t7); var px = x1 + a * t3, py = y1 + a * t4; var b1 = isOnSegment(x2, y2, x1, y1, px, py); var b2 = isOnSegment(px, py, x3, y3, x4, y4); if ((b1 && b2)) { return new b2Vec2(px, py); } return null; }; var hitSegment = function(x1, y1, x2, y2, x3, y3, x4, y4) { var t1 = x3 - x1, t2 = y3 - y1, t3 = x2 - x1, t4 = y2 - y1, t5 = x4 - x3, t6 = y4 - y3, t7 = t4 * t5 - t3 * t6, a; a = (((t5 * t2) - t6 * t1) / t7); var px = x1 + a * t3, py = y1 + a * t4; var b1 = isOnSegment(px, py, x1, y1, x2, y2); var b2 = isOnSegment(px, py, x3, y3, x4, y4); if ((b1 && b2)) { return new b2Vec2(px, py); } return null; }; var isOnSegment = function(px, py, x1, y1, x2, y2) { var b1 = ((((x1 + 0.1) >= px) && px >= x2 - 0.1) || (((x1 - 0.1) <= px) && px <= x2 + 0.1)); var b2 = ((((y1 + 0.1) >= py) && py >= y2 - 0.1) || (((y1 - 0.1) <= py) && py <= y2 + 0.1)); return ((b1 && b2) && isOnLine(px, py, x1, y1, x2, y2)); }; var pointsMatch = function(x1, y1, x2, y2) { var dx = (x2 >= x1) ? x2 - x1 : x1 - x2, dy = (y2 >= y1) ? y2 - y1 : y1 - y2; return ((dx < 0.1) && dy < 0.1); }; var isOnLine = function(px, py, x1, y1, x2, y2) { if ((((x2 - x1) > 0.1) || x1 - x2 > 0.1)) { var a = (y2 - y1) / (x2 - x1), possibleY = a * (px - x1) + y1, diff = (possibleY > py) ? possibleY - py : py - possibleY; return (diff < 0.1); } return (((px - x1) < 0.1) || x1 - px < 0.1); }; var det = function det(x1, y1, x2, y2, x3, y3) { return x1 * y2 + x2 * y3 + x3 * y1 - y1 * x2 - y2 * x3 - y3 * x1; }; var err = function err() { throw new Error("A problem has occurred. Use physics.validate(blob) to see where the problem is."); }; // END b2Separate CODE // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ this.join = function(obj1, obj2, point1, point2, minAngle, maxAngle, type, body1, body2) { var duo; if (duo = zob(that.join, arguments)) return duo; if (!zot(body1) && zot(obj1)) obj1 = body1; if (!zot(body2) && zot(obj2)) obj2 = body2; if (zot(obj1) || zot(obj2)) return; if (obj1.body) obj1 = obj1.body; if (obj2.body) obj2 = obj2.body; if (zot(point1)) point1 = new zim.Point(obj1.GetWorldCenter().x*that.scale, obj1.GetWorldCenter().y*that.scale); if (zot(point2)) point2 = new zim.Point(obj2.GetWorldCenter().x*that.scale, obj2.GetWorldCenter().y*that.scale); if (zot(type)) type = "weld"; var def; if (type=="distance") def = new b2DistanceJointDef(); if (type=="revolute") def = new b2RevoluteJointDef(); if (type=="weld") def = new b2WeldJointDef(); def.Initialize(obj1, obj2, new b2Vec2(point1.x/that.scale, point1.y/that.scale), new b2Vec2(point2.x/that.scale, point2.y/that.scale)); if (!zot(minAngle) || !zot(maxAngle)) { def.enableLimit = true; def.lowerAngle = minAngle*Math.PI/180; def.upperAngle = maxAngle*Math.PI/180; } return that.world.CreateJoint(def); } this.break = function(joint) { that.world.DestroyJoint(joint); } // for backwards compatibility - use join() now... this.makeJoint = function(body1, body2, pointX, pointY, minAngle, maxAngle, type) { var def = (!zot(type) && type=="distance") ? new b2DistanceJointDef() : new b2RevoluteJointDef(); def.Initialize(body1, body2, body1.GetWorldPoint(new b2Vec2(pointX/that.scale, pointY/that.scale))); if (!zot(minAngle) || !zot(maxAngle)) { def.enableLimit = true; def.lowerAngle = minAngle*Math.PI/180; def.upperAngle = maxAngle*Math.PI/180; } return that.world.CreateJoint(def); } var debug; this.debug = function() { if (debug) { debug.debugCanvas.style.display = "block"; } else { // make the Debug object with its canvas only once debug = new this.Debug(); } debug.active = true; that.updateDebug(); return that; } this.Debug = function() { var debugCanvas = this.debugCanvas = document.createElement("canvas"); debugCanvas.setAttribute("id", "debugCanvas"); if (that.frame.scale != 1) { debugCanvas.setAttribute("width", that.frame.width); debugCanvas.setAttribute("height", that.frame.height); } else { var largest = Math.max(WW.innerWidth, screen.width, WW.innerHeight, screen.height); debugCanvas.setAttribute("width", largest); debugCanvas.setAttribute("height", largest); } that.frame.canvas.parentElement.appendChild(debugCanvas); debugCanvas.style.pointerEvents = "none"; var debugDraw = new b2DebugDraw(); debugDraw.SetSprite(debugCanvas.getContext('2d')); debugDraw.SetDrawScale(scale); debugDraw.SetFillAlpha(0.7); debugDraw.SetLineThickness(1.0); debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit); world.SetDebugDraw(debugDraw); this.update = function() { world.m_debugDraw.m_sprite.graphics.clear(); world.DrawDebugData(); } } this.updateDebug = function() { if (zot(debug)) return; var canvasPosition = getElementPosition(that.frame.canvas); var c = debug.debugCanvas; c.style.position = "absolute"; c.style.zIndex = 2; c.style.left = that.frame.canvas.style.left; c.style.top = that.frame.canvas.style.top; c.style.width = that.frame.canvas.style.width; c.style.height = that.frame.canvas.style.height; return this; } this.removeDebug = function() { if (!debug) return; debug.active = false; debug.debugCanvas.style.display = "none"; return this; } // keep a remove list and remove in update function // at the correct time so world does not get confused var removeList = []; this.remove = function(body) { removeList.push(body); } function doRemove() { var len = removeList.length; if (len==0) return; var body; for (var i=len-1; i>=0; i--) { body = removeList[i]; mappings.remove(body); world.DestroyBody(body); body = null; removeList.pop(); } } this.follow = function(obj, damp, dampY, leftOffset, rightOffset, upOffset, downOffset, offsetDamp, offsetDampY, horizontal, vertical, borderLock, borderOriginal) { var duo; if (duo = zob(that.follow, arguments)) return duo; that.followObj = null; if (zot(obj)) return; if (obj.zimObj) obj = obj.zimObj; // passed in a box2DBody instead // Ticker uses these if controls are used so just set them to 0 here obj.controlX = obj.controlDirX = obj.controlY = obj.controlDirY = 0; that.followObj = obj; obj.followDampX = null; obj.followDampY = null; if (zot(damp)) damp = .05; if (zot(dampY)) dampY = damp; if (zot(leftOffset)) leftOffset = 0; if (zot(rightOffset)) rightOffset = that.frame.stage.width; if (zot(upOffset)) upOffset = 0; if (zot(downOffset)) downOffset = that.frame.stage.height; if (zot(offsetDamp)) offsetDamp = .02; if (zot(offsetDampY)) offsetDampY = offsetDamp; if (zot(horizontal)) horizontal = true; if (zot(vertical)) vertical = true; if (zot(borderLock)) borderLock = borders.constructor === {}.constructor; if (zot(borderOriginal)) borderOriginal = false; obj.frameBorderLock = borderLock; obj.frameBorderOriginal = borderOriginal; if (horizontal) { obj.followDampX = new Damp(0,damp); obj.offsetDampX = new Damp((leftOffset+rightOffset)/2,offsetDamp); obj.followOffsetX = [(leftOffset+rightOffset)/2, leftOffset, rightOffset]; } if (vertical) { obj.followDampY = new Damp(0,dampY); obj.offsetDampY = new Damp((upOffset+downOffset)/2,offsetDampY); obj.followOffsetY = [(upOffset+downOffset)/2, upOffset, downOffset]; } that.followSpeed = 0; var lastPos = stage.x; var lastTime = Date.now(); obj.followTicker = that.Ticker.add(function() { var t = Date.now(); var dT = t-lastTime; if (dT==0) return; var dP = lastPos-stage.x; that.followSpeed = dP/dT*1000; lastTime = t; lastPos = stage.x; }); } this.control = function(obj, type, speed, speedY, horizontal, vertical) { obj.ground = true; // default assumes on ground var duo; if (duo = zob(that.control, arguments)) return duo; if (zot(obj)) return; if (obj.zimObj) zimObj = obj.zimObj; // passed in a box2DBody instead that.controlObj = obj; obj.controlX = 0; obj.controlY = 0; if (zot(type)) type = "both"; if (zot(speed)) speed = 200; var speedLock; if (zot(speedY)) { speedLock = true; speedY = speed; } if (zot(horizontal)) horizontal = true; if (zot(vertical)) vertical = true; Object.defineProperty(obj, 'speed', { get: function() { return speed; }, set: function(s) { speed = s; if (speedLock) speedY = s; } }); Object.defineProperty(obj, 'speedY', { get: function() { return speedY; }, set: function(s) { speedY = s; speedLock = false; } }); if (type.type == "DPad") { var body = obj.body; var dPad = type; obj.controlTicker = that.Ticker.add(function() { if (dPad.dirX*dPad.dirY == 0) return; body.ApplyForce(new b2Vec2( dPad.dirX*speed, dPad.dirY*speedY ), body.GetWorldCenter()); }); return; } var k = [0,0,0,0,0,0,0,0,0,0,0,0]; // keep track of wasd 87, 65, 83, 68 and arrows down and custom four directions obj.controlKeydown = that.frame.on("keydown", function(e) { if (!obj.ground) return; checkCodes(e.keyCode, speed, speedY); }); obj.controlKeyup = that.frame.on("keyup", function(e) {checkCodes(e.keyCode, 0, 0);}); that.customControl = function(code, desiredSpeed, desiredSpeedY) { checkCodes(code, !zot(desiredSpeed)?desiredSpeed:speed, !zot(desiredSpeedY)?desiredSpeedY:speedY); } function checkCodes(code, speed, speedY) { if (code == 37 && horizontal && (type == "both" || type == "arrows")) k[0] = -speed; if (code == 38 && vertical && (type == "both" || type == "arrows")) k[1] = -speedY; if (code == 39 && horizontal && (type == "both" || type == "arrows")) k[2] = speed; if (code == 40 && vertical && (type == "both" || type == "arrows")) k[3] = speedY; if (code == 65 && horizontal && (type == "both" || type == "wasd")) k[4] = -speed; if (code == 87 && vertical && (type == "both" || type == "wasd")) k[5] = -speedY; if (code == 68 && horizontal && (type == "both" || type == "wasd")) k[6] = speed; if (code == 83 && vertical && (type == "both" || type == "wasd")) k[7] = speedY; // custom if (code == "left" && horizontal) k[8] = -speed; if (code == "up" && vertical) k[9] = -speed; if (code == "right" && horizontal) k[10] = speed; if (code == "down" && vertical) k[11] = speed; obj.controlDirX = k[0]+k[2]+k[4]+k[6]+k[8]+k[10]==0?0:(k[0]+k[2]+k[4]+k[6]+k[8]+k[10]>0?1:-1); // 0,-1,1 obj.controlDirY = k[1]+k[3]+k[5]+k[7]+k[9]+k[11]==0?0:(k[1]+k[3]+k[5]+k[7]+k[9]+k[11]>0?1:-1); obj.controlX = k[0]+k[2]+k[4]+k[6]+k[8]+k[10]==0?0:(k[0]+k[2]+k[4]+k[6]+k[8]+k[10]>0?1:2); // 0,1,2 obj.controlY = k[1]+k[3]+k[5]+k[7]+k[9]+k[11]==0?0:(k[1]+k[3]+k[5]+k[7]+k[9]+k[11]>0?1:2); } var body = obj.body; obj.controlTicker = that.Ticker.add(function() { // k[0]+k[2] is the total x speed, for instance if (Math.abs(k[0]+k[2]+k[4]+k[6]+k[8]+k[10]) + Math.abs(k[1]+k[3]+k[5]+k[7]+k[9]+k[11]) == 0) return; body.ApplyForce(new b2Vec2( k[0]+k[2]+k[4]+k[6]+k[8]+k[10]>0?speed:(k[0]+k[2]+k[4]+k[6]+k[8]+k[10]<0?-speed:0), k[1]+k[3]+k[5]+k[7]+k[9]+k[11]>0?speedY:(k[1]+k[3]+k[5]+k[7]+k[9]+k[11]<0?-speedY:0) ), body.GetWorldCenter()); }); } this.noControl = function(obj) { if (!zot(obj) && obj.controlTicker) { that.controlObj = null; that.Ticker.remove(obj.controlTicker); that.frame.off("keydown", obj.controlKeydown); that.frame.off("keyup", obj.controlKeyup); obj.controlX = obj.controlDirX = obj.controlY = obj.controlDirY = 0; } } this.buoyancy = function(height, denisity, linear, angular) { if (zot(height)) height=that.frame.stage.height/2; if (zot(denisity)) denisity = 3; if (zot(linear)) linear = 4; if (zot(angular)) angular = 4; var bc = new b2BuoyancyController(); bc.normal.Set(0,-1); bc.offset = -(H-height)/that.scale; bc.density = denisity; bc.linearDrag = linear; bc.angularDrag = angular; that.world.AddController(bc); bc.add = function(obj) { if (!Array.isArray(obj)) obj = [obj] zim.loop(obj, function(o) { if (o.body) o = o.body; bc.AddBody(o); if (o.zimObj) o.zimObj.impulse(0,.1); }); return bc; } bc.remove = function(obj) { if (!Array.isArray(obj)) obj = [obj] zim.loop(obj, function(o) { if (o.body) o = o.body; try { bc.RemoveBody(o); if (o.zimObj) o.zimObj.impulse(0,.1); } catch (e) {} }); return bc; } bc.clear = function() { bc.Clear(); return bc; } bc.dispose = function() { bc.Clear(); that.world.RemoveController(bc); bc = null; } return bc; } // Drag wraps the demo example mouse code var drag; this.dragList = []; this.noDragList = []; this.drag = function(list) { if (zot(list)) list = []; if (!Array.isArray(list)) list = [list]; if (list.length==0) that.noDragList = []; // overwrite the no drags var newList = []; for (var i=0; i<list.length; i++) { if (list[i].body) newList[i] = list[i].body; var index = this.noDragList.indexOf(newList[i]); if (index>=0) this.noDragList.splice(index, 1); // overwrite a specific no drag } if (drag) this.dragList = this.dragList.concat(newList); else drag = new that.Drag(newList); return this; } this.noDrag = function(list) { if (!this.dragList) return this; if (!zot(list)) { if (!Array.isArray(list)) list = [list]; var lastLength = that.dragList.length; var newList = []; for (var i=0; i<list.length; i++) { if (list[i].body) newList[i] = list[i].body; var index = that.dragList.indexOf(newList[i]); if (index>=0) that.dragList.splice(index, 1); else that.noDragList.push(newList[i]); } if (that.dragList.length==0 && lastLength>0) { drag.removeListeners(); drag = null; } } else { drag.removeListeners(); drag = null; } } this.Drag = function(list) { if (zot(frame)) frame = {scale:1}; if (typeof that == "undefined") { // not using ZIM 10 Physics for (var i=0; i<list.length; i++) { if (list[i].body) list[i] = list[i].body; } that = this; } var that2 = this; this.removeListeners = function() { isMouseDown = false; if (that2.mousedownEvent) stage.off("stagemousedown", that2.mousedownEvent); if (that2.mousemoveEvent) stage.off("stagemousemove", that2.mousemoveEvent); if (that2.mouseupEvent) stage.off("stagemouseup", that2.mouseupEvent); if (mouseJoint) world.DestroyJoint(mouseJoint); mouseJoint = null; that.dragList = null; }; this.removeListeners(); that.dragList = list; var stage = that.frame.stage; // modified demo.html code at https://code.google.com/p/box2dweb/ var canvasPosition, mouseX, mouseY, mousePVec, selectedBody, mouseJoint; // that2.mousedownEvent = stage.on("stagemousedown", function(e) { // isMouseDown = true; // mouseX = (e.stageX/zim.scaX-(that.scroll?stage.x:0))/scale; // mouseY = (e.stageY/zim.scaY-(that.scroll?stage.y:0))/scale; // that2.mousemoveEvent = stage.on("stagemousemove", function(e) { // mouseX = (e.stageX/zim.scaX-(that.scroll?stage.x:0))/scale; // mouseY = (e.stageY/zim.scaY-(that.scroll?stage.y:0))/scale; // // zog(Math.round(e.stageY)) // }); // }); // that2.mouseupEvent = stage.on("stagemouseup", function(e) { // isMouseDown = false; // stage.off("stagemousemove", that2.mousemoveEvent); // mouseX = (e.stageX/zim.scaX-(that.scroll?stage.x:0))/scale; // mouseY = (e.stageY/zim.scaY-(that.scroll?stage.y:0))/scale; // }); that2.mousedownEvent = stage.on("stagemousedown", function(e) { isMouseDown = true; mouseX = (e.stageX/zim.scaX)/scale; mouseY = (e.stageY/zim.scaY)/scale; that2.mousemoveEvent = stage.on("stagemousemove", function(e) { mouseX = (e.stageX/zim.scaX)/scale; mouseY = (e.stageY/zim.scaY)/scale; // zog(Math.round(e.stageY)) }); }); that2.mouseupEvent = stage.on("stagemouseup", function(e) { isMouseDown = false; stage.off("stagemousemove", that2.mousemoveEvent); mouseX = (e.stageX/zim.scaX)/scale; mouseY = (e.stageY/zim.scaY)/scale; }); function getBodyAtMouse() { mousePVec = new b2Vec2(mouseX, mouseY); var aabb = new b2AABB(); aabb.lowerBound.Set(mouseX - 0.001, mouseY - 0.001); aabb.upperBound.Set(mouseX + 0.001, mouseY + 0.001); // Query the world for overlapping shapes. selectedBody = null; world.QueryAABB(getBodyCB, aabb); return selectedBody; } function getBodyCB(fixture) { if(fixture.GetBody().GetType() != b2Body.b2_staticBody) { if(fixture.GetShape().TestPoint(fixture.GetBody().GetTransform(), mousePVec)) { selectedBody = fixture.GetBody(); return false; } } return true; } this.update = function() { if(isMouseDown && (!mouseJoint)) { var body = getBodyAtMouse(); if(body) { if (that.noDragList.indexOf(body) >= 0 || (that.dragList.length > 0 && that.dragList.indexOf(body) < 0)) return; var md = new b2MouseJointDef(); md.bodyA = world.GetGroundBody(); md.bodyB = body; md.target.Set(mouseX, mouseY); md.collideConnected = true; md.maxForce = 300.0 * body.GetMass(); mouseJoint = world.CreateJoint(md); body.SetAwake(true); } } if (mouseJoint) { if (isMouseDown) { mouseJoint.SetTarget(new b2Vec2(mouseX, mouseY)); } else { world.DestroyJoint(mouseJoint); mouseJoint = null; } } } } //https://js-tut.aardon.de/js-tut/tutorial/position.html function getElementPosition(element) { var elem=element, tagname="", x=0, y=0; while((typeof(elem) == "object") && (typeof(elem.tagName) != "undefined")) { y += elem.offsetTop; x += elem.offsetLeft; tagname = elem.tagName.toUpperCase(); if(tagname == "BODY") elem=0; if(typeof(elem) == "object") { if(typeof(elem.offsetParent) == "object") elem = elem.offsetParent; } } return {x:x-zim.scrollX(), y:y-zim.scrollY()}; } this.attach = function(control, obj) { if (zot(control) || zot(obj)) return; var body = (obj.body) ? obj.body : obj; var md = new b2MouseJointDef(); md.bodyA = this.world.GetGroundBody(); md.bodyB = body; md.target.Set(control.x/this.scale, control.y/this.scale); md.collideConnected = true; md.maxForce = 300.0 * body.GetMass(); var mouseJoint = this.world.CreateJoint(md); body.SetAwake(true); var attachTicker = this.Ticker.add(function() { mouseJoint.SetTarget(new b2Vec2(control.x/that.scale, control.y/that.scale)); }, false) if (!control.attachTickers) control.attachTickers = []; control.attachTickers.push(attachTicker); attachTicker.mouseJoint = mouseJoint; attachTicker.control = control; return attachTicker; } this.unattach = function(id) { world.DestroyJoint(id.mouseJoint); id.mouseJoint = null; this.Ticker.remove(id); if (id.control && id.control.attachTickers) id.control.attachTickers.splice(id.control.attachTickers.indexOf(id),1); } // mappings put zim assets to the x, y and rotation of Box2D bodies // a dictionary is used for easy adding and removing var mappings = new zim.Dictionary(true); function updateMap() { for (var i=0; i<mappings.length; i++) { var zimObj = mappings.values[i]; var box2DBody = mappings.objects[i]; var p = box2DBody.GetWorldPoint(new b2Vec2(0, 0)); if (!that.scroll) { var point = zimObj.parent.globalToLocal(p.x * scale, p.y * scale); zimObj.x = point.x; zimObj.y = point.y; } else { zimObj.x = p.x * scale; zimObj.y = p.y * scale; //if (that.followObj && that.followObj==zimObj && zimObj.followDampX) that.frame.stage.x = zimObj.followDampX.convert(zimObj.offsetDampX.convert(zimObj.followOffsetX[zimObj.controlX])-zimObj.x); //if (that.followObj && that.followObj==zimObj && zimObj.followDampY) that.frame.stage.y = zimObj.followDampY.convert(zimObj.offsetDampY.convert(zimObj.followOffsetY[zimObj.controlY])-zimObj.y); var stage = that.frame.stage; if (!isMouseDown) { var sX = (frame.retina?frame.scale:1)*zim.scaX; var sY = (frame.retina?frame.scale:1)*zim.scaY; if (that.followObj && that.followObj==zimObj && zimObj.followDampX) { stage.x = sX*zimObj.followDampX.convert(zimObj.offsetDampX.convert(zimObj.followOffsetX[zimObj.controlX])-zimObj.x); } if (that.followObj && that.followObj==zimObj && zimObj.followDampY) { stage.y = sY*zimObj.followDampY.convert(zimObj.offsetDampY.convert(zimObj.followOffsetY[zimObj.controlY])-zimObj.y); } if (zimObj.frameBorderLock) { if (stage.x < sX*(stage.width-that._borders.x-that._borders.width) && ((zimObj.frameBorderOriginal && that.borderRight.m_fixtureCount == 0) || that.borderRight.m_fixtureCount == 1)) stage.x = sX*(stage.width-that._borders.x-that._borders.width); if (stage.y < sY*(stage.height-that._borders.y-that._borders.height) && ((zimObj.frameBorderOriginal && that.borderBottom.m_fixtureCount == 0) || that.borderBottom.m_fixtureCount == 1)) stage.y = sY*(stage.height-that._borders.y-that._borders.height); if (stage.x > -sX*that._borders.x && ((zimObj.frameBorderOriginal && that.borderLeft.m_fixtureCount == 0) || that.borderLeft.m_fixtureCount == 1)) stage.x = -sX*that._borders.x; if (stage.y > -sY*that._borders.y && ((zimObj.frameBorderOriginal && that.borderTop.m_fixtureCount == 0) || that.borderTop.m_fixtureCount == 1)) stage.y = -sY*that._borders.y; // if (that.frame.stage.x < that.frame.zim.scaX*borders.x && ((zimObj.frameBorderOriginal && that.borderRight.m_fixtureCount == 0) || that.borderRight.m_fixtureCount == 1)) that.frame.stage.x = that.frame.zim.scaX*borders.x; // if (that.frame.stage.y < that.frame.zim.scaY*borders.y && ((zimObj.frameBorderOriginal && that.borderBottom.m_fixtureCount == 0) || that.borderBottom.m_fixtureCount == 1)) that.frame.stage.y = that.frame.zim.scaY*borders.y; // if (that.frame.stage.x > that.frame.zim.scaX*(borders.x + borders.width - that.frame.stage.width) && ((zimObj.frameBorderOriginal && that.borderLeft.m_fixtureCount == 0) || that.borderLeft.m_fixtureCount == 1)) that.frame.stage.x = that.frame.zim.scaX*(borders.x + borders.width - that.frame.stage.width); // if (that.frame.stage.y > that.frame.zim.scaY*(borders.y + borders.height - that.frame.stage.height) && ((zimObj.frameBorderOriginal && that.borderTop.m_fixtureCount == 0) || that.borderTop.m_fixtureCount == 1)) that.frame.stage.y = that.frame.zim.scaY*(borders.y + borders.height - that.frame.stage.height); } } } if (!zimObj.noRotation) zimObj.rotation = box2DBody.GetAngle() * (180 / Math.PI); } if (that.frame && that.frame.stage) that.frame.stage.update(); } this.addMap = function(box2DBody, zimObj) { if (zot(box2DBody) || zot(zimObj)) { console.log("physics.Map() - please provide a box2DBody and zimObj"); return; } zimObj.body = box2DBody; box2DBody.zimObj = zimObj; mappings.add(box2DBody, zimObj); } this.removeMap = function(box2DBody) { mappings.remove(box2DBody); } this.borders = function(rect) { if (zot(rect.x) || zot(rect.y) || zot(rect.width) || zot(rect.height)) return; if (that.borderTop) that.remove(that.borderTop); if (that.borderBottom) that.remove(that.borderBottom); if (that.borderLeft) that.remove(that.borderLeft); if (that.borderRight) that.remove(that.borderRight); var w = 2000; // width of wall // Create border of boxes var wall = new b2PolygonShape(); var wallBd = new b2BodyDef(); var wallB; // Left // wallBd.position.Set((rect.x-w)/scale, (rect.y+rect.height/2)/scale); // wall.SetAsBox(w/scale, rect.height/2/scale); // wallB = that.borderLeft = world.CreateBody(wallBd); // wallB.CreateFixture2(wall); that.borderLeft = that.makeRectangle(w*2, rect.height, false); that.borderLeft.x = rect.x-w; that.borderLeft.y = rect.y+rect.height/2; that.borderLeft.zimObj = {type:"Border", side:"left"}; // Right // wallBd.position.Set((rect.x+rect.width+w)/scale, (rect.y+rect.height/2)/scale); // wallB = that.borderRight = world.CreateBody(wallBd); // wallB.CreateFixture2(wall); that.borderRight = that.makeRectangle(w*2, rect.height, false); that.borderRight.x = rect.x+rect.width+w; that.borderRight.y = rect.y+rect.height/2; that.borderRight.zimObj = {type:"Border", side:"right"}; // Top // wallBd.position.Set((rect.x + rect.width/2)/scale, (rect.y-w)/scale); // wall.SetAsBox(rect.width/2/scale, w/scale); // wallB = that.borderTop = world.CreateBody(wallBd); // wallB.CreateFixture2(wall); that.borderTop = that.makeRectangle(rect.width, w*2, false); that.borderTop.x = rect.x + rect.width/2; that.borderTop.y = rect.y-w; that.borderTop.zimObj = {type:"Border", side:"top"}; // Bottom // wallBd.position.Set((rect.x + rect.width/2)/scale, (rect.y+rect.height+w)/scale); // wallB = that.borderBottom = world.CreateBody(wallBd); // wallB.CreateFixture2(wall); that.borderBottom = that.makeRectangle(rect.width, w*2, false); that.borderBottom.x = rect.x + rect.width/2; that.borderBottom.y = rect.y+rect.height+w; that.borderBottom.zimObj = {type:"Border", side:"bottom"}; that._borders = rect; } this.borders(borders); this.paused = false; this.pause = function(type) { if (zot(type)) type = true; if (type) { this.timeStep = 0; } else { this.timeStep = 1/this.step; } this.paused = type; } // the Ticker keeps add and remove methods // to add and remove functions to the update function // either before the world step