p2s
Version:
A JavaScript 2D physics engine.
975 lines (820 loc) • 32 kB
JavaScript
/* global PIXI,Renderer */
(function(p2){
p2.WebGLRenderer = WebGLRenderer;
var Renderer = p2.Renderer;
/**
* Renderer using Pixi.js
* @class WebGLRenderer
* @constructor
* @extends Renderer
* @param {World} scenes
* @param {Object} [options]
* @param {Number} [options.lineWidth=0.01]
* @param {Number} [options.scrollFactor=0.1]
* @param {Number} [options.width] Num pixels in horizontal direction
* @param {Number} [options.height] Num pixels in vertical direction
*/
function WebGLRenderer(scenes, options){
options = options || {};
var that = this;
var settings = {
lineWidth : 0.01,
scrollFactor : 0.1,
width : 1280, // Pixi screen resolution
height : 720,
useDeviceAspect : false,
sleepOpacity : 0.2,
};
for(var key in options){
settings[key] = options[key];
}
if(settings.useDeviceAspect){
settings.height = window.innerHeight / window.innerWidth * settings.width;
}
//this.settings = settings;
this.lineWidth = settings.lineWidth;
this.scrollFactor = settings.scrollFactor;
this.sleepOpacity = settings.sleepOpacity;
this.sprites = [];
this.springSprites = [];
this.debugPolygons = false;
this.islandColors = {}; // id -> int
Renderer.call(this,scenes,options);
for(var key in settings){
this.settings[key] = settings[key];
}
this.pickPrecision = 0.1;
// Update "ghost draw line"
this.on("drawPointsChange",function(e){
var g = that.drawShapeGraphics;
var path = that.drawPoints;
g.clear();
var path2 = [];
for(var j=0; j<path.length; j++){
var v = path[j];
path2.push([v[0], v[1]]);
}
that.drawPath(g,path2,0xff0000,false,that.lineWidth,false);
});
// Update draw circle
this.on("drawCircleChange",function(e){
var g = that.drawShapeGraphics;
g.clear();
var center = that.drawCircleCenter;
var R = p2.vec2.distance(center, that.drawCirclePoint);
that.drawCircle(g,center[0], center[1], 0, R,false, that.lineWidth);
});
// Update draw circle
this.on("drawRectangleChange",function(e){
var g = that.drawShapeGraphics;
g.clear();
var start = that.drawRectStart;
var end = that.drawRectEnd;
var width = start[0] - end[0];
var height = start[1] - end[1];
that.drawRectangle(g, start[0] - width/2, start[1] - height/2, 0, width, height, false, false, that.lineWidth, false);
});
}
WebGLRenderer.prototype = Object.create(Renderer.prototype);
WebGLRenderer.prototype.stagePositionToPhysics = function(out,stagePosition){
var x = stagePosition[0],
y = stagePosition[1];
p2.vec2.set(out, x, y);
return out;
};
/**
* Initialize the renderer and stage
*/
var init_stagePosition = p2.vec2.create(),
init_physicsPosition = p2.vec2.create();
WebGLRenderer.prototype.init = function(){
var w = this.w,
h = this.h,
s = this.settings;
var that = this;
var renderer = this.renderer = PIXI.autoDetectRenderer(s.width, s.height, null, null, true);
var stage = this.stage = new PIXI.DisplayObjectContainer();
var container = this.container = new PIXI.Stage(0xFFFFFF,true);
var el = this.element = this.renderer.view;
el.tabIndex = 1;
el.classList.add(Renderer.elementClass);
el.setAttribute('style','width:100%;');
var div = this.elementContainer = document.createElement('div');
div.classList.add(Renderer.containerClass);
div.setAttribute('style','width:100%; height:100%');
div.appendChild(el);
document.body.appendChild(div);
el.focus();
el.oncontextmenu = function(e){
return false;
};
this.container.addChild(stage);
// Graphics object for drawing shapes
this.drawShapeGraphics = new PIXI.Graphics();
stage.addChild(this.drawShapeGraphics);
// Graphics object for contacts
this.contactGraphics = new PIXI.Graphics();
stage.addChild(this.contactGraphics);
// Graphics object for AABBs
this.aabbGraphics = new PIXI.Graphics();
stage.addChild(this.aabbGraphics);
// Graphics object for pick
this.pickGraphics = new PIXI.Graphics();
stage.addChild(this.pickGraphics);
stage.scale.x = 200; // Flip Y direction.
stage.scale.y = -200;
var lastX, lastY, lastMoveX, lastMoveY, startX, startY, down=false;
var physicsPosA = p2.vec2.create();
var physicsPosB = p2.vec2.create();
var stagePos = p2.vec2.create();
var initPinchLength = 0;
var initScaleX = 1;
var initScaleY = 1;
var lastNumTouches = 0;
container.mousedown = container.touchstart = function(e){
lastMoveX = e.global.x;
lastMoveY = e.global.y;
if(e.originalEvent.touches){
lastNumTouches = e.originalEvent.touches.length;
}
if(e.originalEvent.touches && e.originalEvent.touches.length === 2){
var touchA = that.container.interactionManager.touchs[0];
var touchB = that.container.interactionManager.touchs[1];
var pos = touchA.getLocalPosition(stage);
p2.vec2.set(stagePos, pos.x, pos.y);
that.stagePositionToPhysics(physicsPosA, stagePos);
var pos = touchB.getLocalPosition(stage);
p2.vec2.set(stagePos, pos.x, pos.y);
that.stagePositionToPhysics(physicsPosB, stagePos);
initPinchLength = p2.vec2.distance(physicsPosA, physicsPosB);
var initScaleX = stage.scale.x;
var initScaleY = stage.scale.y;
return;
}
lastX = e.global.x;
lastY = e.global.y;
startX = stage.position.x;
startY = stage.position.y;
down = true;
that.lastMousePos = e.global;
var pos = e.getLocalPosition(stage);
p2.vec2.set(init_stagePosition, pos.x, pos.y);
that.stagePositionToPhysics(init_physicsPosition, init_stagePosition);
that.handleMouseDown(init_physicsPosition);
};
container.mousemove = container.touchmove = function(e){
if(e.originalEvent.touches){
if(lastNumTouches !== e.originalEvent.touches.length){
lastX = e.global.x;
lastY = e.global.y;
startX = stage.position.x;
startY = stage.position.y;
}
lastNumTouches = e.originalEvent.touches.length;
}
lastMoveX = e.global.x;
lastMoveY = e.global.y;
if(e.originalEvent.touches && e.originalEvent.touches.length === 2){
var touchA = that.container.interactionManager.touchs[0];
var touchB = that.container.interactionManager.touchs[1];
var pos = touchA.getLocalPosition(stage);
p2.vec2.set(stagePos, pos.x, pos.y);
that.stagePositionToPhysics(physicsPosA, stagePos);
var pos = touchB.getLocalPosition(stage);
p2.vec2.set(stagePos, pos.x, pos.y);
that.stagePositionToPhysics(physicsPosB, stagePos);
var pinchLength = p2.vec2.distance(physicsPosA, physicsPosB);
// Get center
p2.vec2.add(physicsPosA, physicsPosA, physicsPosB);
p2.vec2.scale(physicsPosA, physicsPosA, 0.5);
that.zoom(
(touchA.global.x + touchB.global.x) * 0.5,
(touchA.global.y + touchB.global.y) * 0.5,
null,
pinchLength / initPinchLength * initScaleX, // zoom relative to the initial scale
pinchLength / initPinchLength * initScaleY
);
return;
}
if(down && that.state === Renderer.PANNING){
stage.position.x = e.global.x - lastX + startX;
stage.position.y = e.global.y - lastY + startY;
}
that.lastMousePos = e.global;
var pos = e.getLocalPosition(stage);
p2.vec2.set(init_stagePosition, pos.x, pos.y);
that.stagePositionToPhysics(init_physicsPosition, init_stagePosition);
that.handleMouseMove(init_physicsPosition);
};
container.mouseup = container.touchend = function(e){
if(e.originalEvent.touches){
lastNumTouches = e.originalEvent.touches.length;
}
down = false;
lastMoveX = e.global.x;
lastMoveY = e.global.y;
that.lastMousePos = e.global;
var pos = e.getLocalPosition(stage);
p2.vec2.set(init_stagePosition, pos.x, pos.y);
that.stagePositionToPhysics(init_physicsPosition, init_stagePosition);
that.handleMouseUp(init_physicsPosition);
};
// http://stackoverflow.com/questions/7691551/touchend-event-in-ios-webkit-not-firing
this.element.ontouchmove = function(e){
e.preventDefault();
};
function MouseWheelHandler(e) {
// cross-browser wheel delta
e = window.event || e; // old IE support
//var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
var o = e,
d = o.detail, w = o.wheelDelta,
n = 225, n1 = n-1;
// Normalize delta: http://stackoverflow.com/a/13650579/2285811
var f;
d = d ? w && (f = w/d) ? d/f : -d/1.35 : w/120;
// Quadratic scale if |d| > 1
d = d < 1 ? d < -1 ? (-Math.pow(d, 2) - n1) / n : d : (Math.pow(d, 2) + n1) / n;
// Delta *should* not be greater than 2...
var delta = Math.min(Math.max(d / 2, -1), 1);
var out = delta >= 0;
if(typeof lastMoveX !== 'undefined'){
that.zoom(lastMoveX, lastMoveY, out, undefined, undefined, delta);
}
}
if (el.addEventListener) {
el.addEventListener("mousewheel", MouseWheelHandler, false); // IE9, Chrome, Safari, Opera
el.addEventListener("DOMMouseScroll", MouseWheelHandler, false); // Firefox
} else {
el.attachEvent("onmousewheel", MouseWheelHandler); // IE 6/7/8
}
this.centerCamera(0, 0);
};
WebGLRenderer.prototype.zoom = function(x, y, zoomOut, actualScaleX, actualScaleY, multiplier){
var scrollFactor = this.scrollFactor,
stage = this.stage;
if(typeof actualScaleX === 'undefined'){
if(!zoomOut){
scrollFactor *= -1;
}
scrollFactor *= Math.abs(multiplier);
stage.scale.x *= (1 + scrollFactor);
stage.scale.y *= (1 + scrollFactor);
stage.position.x += (scrollFactor) * (stage.position.x - x);
stage.position.y += (scrollFactor) * (stage.position.y - y);
} else {
stage.scale.x *= actualScaleX;
stage.scale.y *= actualScaleY;
stage.position.x += (actualScaleX - 1) * (stage.position.x - x);
stage.position.y += (actualScaleY - 1) * (stage.position.y - y);
}
stage.updateTransform();
};
WebGLRenderer.prototype.centerCamera = function(x, y){
this.stage.position.x = this.renderer.width / 2 - this.stage.scale.x * x;
this.stage.position.y = this.renderer.height / 2 - this.stage.scale.y * y;
this.stage.updateTransform();
};
/**
* Make sure that a rectangle is visible in the canvas.
* @param {number} centerX
* @param {number} centerY
* @param {number} width
* @param {number} height
*/
WebGLRenderer.prototype.frame = function(centerX, centerY, width, height){
var ratio = this.renderer.width / this.renderer.height;
if(ratio < width / height){
this.stage.scale.x = this.renderer.width / width;
this.stage.scale.y = -this.stage.scale.x;
} else {
this.stage.scale.y = -this.renderer.height / height;
this.stage.scale.x = -this.stage.scale.y;
}
this.centerCamera(centerX, centerY);
};
/**
* Draw a circle onto a graphics object
* @method drawCircle
* @static
* @param {PIXI.Graphics} g
* @param {Number} x
* @param {Number} y
* @param {Number} radius
* @param {Number} color
* @param {Number} lineWidth
*/
WebGLRenderer.prototype.drawCircle = function(g,x,y,angle,radius,color,lineWidth,isSleeping){
lineWidth = typeof(lineWidth)==="number" ? lineWidth : 1;
color = typeof(color)==="number" ? color : 0xffffff;
g.lineStyle(lineWidth, 0x000000, 1);
g.beginFill(color, isSleeping ? this.sleepOpacity : 1.0);
g.drawCircle(x, y, radius);
g.endFill();
// line from center to edge
g.moveTo(x,y);
g.lineTo( x + radius*Math.cos(angle),
y + radius*Math.sin(angle) );
};
WebGLRenderer.drawSpring = function(g,restLength,color,lineWidth){
lineWidth = typeof(lineWidth)==="number" ? lineWidth : 1;
color = typeof(color)==="undefined" ? 0xffffff : color;
g.lineStyle(lineWidth, color, 1);
if(restLength < lineWidth*10){
restLength = lineWidth*10;
}
var M = 12;
var dx = restLength/M;
g.moveTo(-restLength/2,0);
for(var i=1; i<M; i++){
var x = -restLength/2 + dx*i;
var y = 0;
if(i<=1 || i>=M-1 ){
// Do nothing
} else if(i % 2 === 0){
y -= 0.1*restLength;
} else {
y += 0.1*restLength;
}
g.lineTo(x,y);
}
g.lineTo(restLength/2,0);
};
/**
* Draw a finite plane onto a PIXI.Graphics.
* @method drawPlane
* @param {PIXI.Graphics} g
* @param {Number} x0
* @param {Number} x1
* @param {Number} color
* @param {Number} lineWidth
* @param {Number} diagMargin
* @param {Number} diagSize
* @todo Should consider an angle
*/
WebGLRenderer.drawPlane = function(g, x0, x1, color, lineColor, lineWidth, diagMargin, diagSize, maxLength){
lineWidth = typeof(lineWidth)==="number" ? lineWidth : 1;
color = typeof(color)==="undefined" ? 0xffffff : color;
g.lineStyle(lineWidth, lineColor, 1);
// Draw a fill color
g.lineStyle(0,0,0);
g.beginFill(color);
var max = maxLength;
g.moveTo(-max,0);
g.lineTo(max,0);
g.lineTo(max,-max);
g.lineTo(-max,-max);
g.endFill();
// Draw the actual plane
g.lineStyle(lineWidth,lineColor);
g.moveTo(-max,0);
g.lineTo(max,0);
};
WebGLRenderer.drawLine = function(g, offset, angle, len, color, lineWidth){
lineWidth = typeof(lineWidth)==="number" ? lineWidth : 1;
color = typeof(color)==="undefined" ? 0x000000 : color;
g.lineStyle(lineWidth, color, 1);
var startPoint = p2.vec2.fromValues(-len/2,0);
var endPoint = p2.vec2.fromValues(len/2,0);
p2.vec2.rotate(startPoint, startPoint, angle);
p2.vec2.rotate(endPoint, endPoint, angle);
p2.vec2.add(startPoint, startPoint, offset);
p2.vec2.add(endPoint, endPoint, offset);
g.moveTo(startPoint[0], startPoint[1]);
g.lineTo(endPoint[0], endPoint[1]);
};
WebGLRenderer.prototype.drawCapsule = function(g, x, y, angle, len, radius, color, fillColor, lineWidth, isSleeping){
lineWidth = typeof(lineWidth)==="number" ? lineWidth : 1;
color = typeof(color)==="undefined" ? 0x000000 : color;
g.lineStyle(lineWidth, color, 1);
var vec2 = p2.vec2;
// Draw circles at ends
var c = Math.cos(angle);
var s = Math.sin(angle);
var hl = len / 2;
g.beginFill(fillColor, isSleeping ? this.sleepOpacity : 1.0);
var localPos = vec2.fromValues(x, y);
var p0 = vec2.fromValues(-hl, 0);
var p1 = vec2.fromValues(hl, 0);
vec2.rotate(p0, p0, angle);
vec2.rotate(p1, p1, angle);
vec2.add(p0, p0, localPos);
vec2.add(p1, p1, localPos);
g.drawCircle(p0[0], p0[1], radius);
g.drawCircle(p1[0], p1[1], radius);
g.endFill();
// Draw rectangle
var pp2 = vec2.create();
var p3 = vec2.create();
vec2.set(p0, -hl, radius);
vec2.set(p1, hl, radius);
vec2.set(pp2, hl, -radius);
vec2.set(p3, -hl, -radius);
vec2.rotate(p0, p0, angle);
vec2.rotate(p1, p1, angle);
vec2.rotate(pp2, pp2, angle);
vec2.rotate(p3, p3, angle);
vec2.add(p0, p0, localPos);
vec2.add(p1, p1, localPos);
vec2.add(pp2, pp2, localPos);
vec2.add(p3, p3, localPos);
g.lineStyle(lineWidth, color, 0);
g.beginFill(fillColor, isSleeping ? this.sleepOpacity : 1.0);
g.moveTo(p0[0], p0[1]);
g.lineTo(p1[0], p1[1]);
g.lineTo(pp2[0], pp2[1]);
g.lineTo(p3[0], p3[1]);
// g.lineTo( hl*c - radius*s + x, hl*s - radius*c + y);
// g.lineTo(-hl*c - radius*s + x, -hl*s - radius*c + y);
g.endFill();
// Draw lines in between
for(var i=0; i<2; i++){
g.lineStyle(lineWidth, color, 1);
var sign = (i===0?1:-1);
vec2.set(p0, -hl, sign*radius);
vec2.set(p1, hl, sign*radius);
vec2.rotate(p0, p0, angle);
vec2.rotate(p1, p1, angle);
vec2.add(p0, p0, localPos);
vec2.add(p1, p1, localPos);
g.moveTo(p0[0], p0[1]);
g.lineTo(p1[0], p1[1]);
}
};
WebGLRenderer.prototype.drawRectangle = function(g,x,y,angle,w,h,color,fillColor,lineWidth,isSleeping){
var path = [
[w / 2, h / 2],
[-w / 2, h / 2],
[-w / 2, -h / 2],
[w / 2, -h / 2],
];
// Rotate and add position
for (var i = 0; i < path.length; i++) {
var v = path[i];
p2.vec2.rotate(v, v, angle);
p2.vec2.add(v, v, [x, y]);
}
this.drawPath(g,path,color,fillColor,lineWidth,isSleeping);
};
WebGLRenderer.prototype.drawConvex = function(g,verts,triangles,color,fillColor,lineWidth,debug,offset,isSleeping){
lineWidth = typeof(lineWidth)==="number" ? lineWidth : 1;
color = typeof(color)==="undefined" ? 0x000000 : color;
if(!debug){
g.lineStyle(lineWidth, color, 1);
g.beginFill(fillColor, isSleeping ? this.sleepOpacity : 1.0);
for(var i=0; i!==verts.length; i++){
var v = verts[i],
x = v[0],
y = v[1];
if(i===0){
g.moveTo(x,y);
} else {
g.lineTo(x,y);
}
}
g.endFill();
if(verts.length>2){
g.moveTo(verts[verts.length-1][0],verts[verts.length-1][1]);
g.lineTo(verts[0][0],verts[0][1]);
}
} else {
// triangles
// var centroid = p2.vec2.create();
// for(var i=0; i<triangles.length; i++){
// var v0 = verts[triangles[i][0]],
// v1 = verts[triangles[i][1]],
// v2 = verts[triangles[i][2]];
// g.lineStyle(lineWidth, 0x000000, 1);
// g.moveTo(v0[0],v0[1]);
// g.lineTo(v1[0],v1[1]);
// g.lineTo(v2[0],v2[1]);
// g.lineTo(v0[0],v0[1]);
// // triangle centroid
// p2.vec2.centroid(centroid,v0,v1,v2);
// g.drawCircle(centroid[0],centroid[1],lineWidth*2);
// }
// convexes
var colors = [0xff0000,0x00ff00,0x0000ff];
for(var i=0; i!==verts.length+1; i++){
var v0 = verts[i%verts.length],
v1 = verts[(i+1)%verts.length],
x0 = v0[0],
y0 = v0[1],
x1 = v1[0],
y1 = v1[1];
g.lineStyle(lineWidth, colors[i%colors.length], 1);
g.moveTo(x0,y0);
g.lineTo(x1,y1);
g.drawCircle(x0,y0,lineWidth*2);
}
g.lineStyle(lineWidth, 0xff0000, 1);
g.drawCircle(offset[0],offset[1],lineWidth*2);
}
};
WebGLRenderer.prototype.drawPath = function(g,path,color,fillColor,lineWidth,isSleeping){
lineWidth = typeof(lineWidth)==="number" ? lineWidth : 1;
color = typeof(color)==="undefined" ? 0x000000 : color;
g.lineStyle(lineWidth, color, 1);
if(typeof(fillColor)==="number"){
g.beginFill(fillColor, isSleeping ? this.sleepOpacity : 1.0);
}
var lastx = null,
lasty = null;
for(var i=0; i<path.length; i++){
var v = path[i],
x = v[0],
y = v[1];
if(x !== lastx || y !== lasty){
if(i===0){
g.moveTo(x,y);
} else {
// Check if the lines are parallel
var p1x = lastx,
p1y = lasty,
p2x = x,
p2y = y,
p3x = path[(i+1)%path.length][0],
p3y = path[(i+1)%path.length][1];
var area = ((p2x - p1x)*(p3y - p1y))-((p3x - p1x)*(p2y - p1y));
if(area !== 0){
g.lineTo(x,y);
}
}
lastx = x;
lasty = y;
}
}
if(typeof(fillColor)==="number"){
g.endFill();
}
// Close the path
if(path.length>2 && typeof(fillColor)==="number"){
g.moveTo(path[path.length-1][0],path[path.length-1][1]);
g.lineTo(path[0][0],path[0][1]);
}
};
WebGLRenderer.prototype.updateSpriteTransform = function(sprite,body){
if(this.useInterpolatedPositions && !this.paused){
sprite.position.x = body.interpolatedPosition[0];
sprite.position.y = body.interpolatedPosition[1];
sprite.rotation = body.interpolatedAngle;
} else {
sprite.position.x = body.position[0];
sprite.position.y = body.position[1];
sprite.rotation = body.angle;
}
};
var X = p2.vec2.fromValues(1,0),
distVec = p2.vec2.fromValues(0,0),
worldAnchorA = p2.vec2.fromValues(0,0),
worldAnchorB = p2.vec2.fromValues(0,0);
WebGLRenderer.prototype.render = function(){
var w = this.renderer.width,
h = this.renderer.height,
springSprites = this.springSprites,
sprites = this.sprites;
// Update body transforms
for(var i=0; i!==this.bodies.length; i++){
this.updateSpriteTransform(this.sprites[i],this.bodies[i]);
}
// Update graphics if the body changed sleepState or island
for(var i=0; i!==this.bodies.length; i++){
var body = this.bodies[i];
var isSleeping = (body.sleepState===p2.Body.SLEEPING);
var sprite = this.sprites[i];
var islandColor = this.getIslandColor(body);
if(sprite.drawnSleeping !== isSleeping || sprite.drawnColor !== islandColor){
sprite.clear();
this.drawRenderable(body, sprite, islandColor, sprite.drawnLineColor);
}
}
// Update spring transforms
for(var i=0; i!==this.springs.length; i++){
var s = this.springs[i],
sprite = springSprites[i],
bA = s.bodyA,
bB = s.bodyB;
if(this.useInterpolatedPositions && !this.paused){
p2.vec2.toGlobalFrame(worldAnchorA, s.localAnchorA, bA.interpolatedPosition, bA.interpolatedAngle);
p2.vec2.toGlobalFrame(worldAnchorB, s.localAnchorB, bB.interpolatedPosition, bB.interpolatedAngle);
} else {
s.getWorldAnchorA(worldAnchorA);
s.getWorldAnchorB(worldAnchorB);
}
sprite.scale.y = 1;
if(worldAnchorA[1] < worldAnchorB[1]){
var tmp = worldAnchorA;
worldAnchorA = worldAnchorB;
worldAnchorB = tmp;
sprite.scale.y = -1;
}
var sxA = worldAnchorA[0],
syA = worldAnchorA[1],
sxB = worldAnchorB[0],
syB = worldAnchorB[1];
// Spring position is the mean point between the anchors
sprite.position.x = ( sxA + sxB ) / 2;
sprite.position.y = ( syA + syB ) / 2;
// Compute distance vector between anchors, in screen coords
distVec[0] = sxA - sxB;
distVec[1] = syA - syB;
// Compute angle
sprite.rotation = Math.acos( p2.vec2.dot(X, distVec) / p2.vec2.length(distVec) );
// And scale
sprite.scale.x = p2.vec2.length(distVec) / s.restLength;
}
// Clear contacts
if(this.drawContacts){
this.contactGraphics.clear();
this.stage.removeChild(this.contactGraphics);
this.stage.addChild(this.contactGraphics);
var g = this.contactGraphics;
g.lineStyle(this.lineWidth,0x000000,1);
for(var i=0; i!==this.world.narrowphase.contactEquations.length; i++){
var eq = this.world.narrowphase.contactEquations[i],
bi = eq.bodyA,
bj = eq.bodyB,
ri = eq.contactPointA,
rj = eq.contactPointB,
xi = bi.position[0],
yi = bi.position[1],
xj = bj.position[0],
yj = bj.position[1];
g.moveTo(xi,yi);
g.lineTo(xi+ri[0], yi+ri[1]);
g.moveTo(xj,yj);
g.lineTo(xj+rj[0], yj+rj[1]);
}
this.contactGraphics.cleared = false;
} else if(!this.contactGraphics.cleared){
this.contactGraphics.clear();
this.contactGraphics.cleared = true;
}
// Draw AABBs
if(this.drawAABBs){
this.aabbGraphics.clear();
this.stage.removeChild(this.aabbGraphics);
this.stage.addChild(this.aabbGraphics);
var g = this.aabbGraphics;
g.lineStyle(this.lineWidth,0x000000,1);
for(var i=0; i!==this.world.bodies.length; i++){
var aabb = this.world.bodies[i].getAABB();
g.drawRect(aabb.lowerBound[0], aabb.lowerBound[1], aabb.upperBound[0] - aabb.lowerBound[0], aabb.upperBound[1] - aabb.lowerBound[1]);
}
this.aabbGraphics.cleared = false;
} else if(!this.aabbGraphics.cleared){
this.aabbGraphics.clear();
this.aabbGraphics.cleared = true;
}
// Draw pick line
if(this.mouseConstraint){
var g = this.pickGraphics;
g.clear();
this.stage.removeChild(g);
this.stage.addChild(g);
g.lineStyle(this.lineWidth,0x000000,1);
var c = this.mouseConstraint;
var worldPivotB = p2.vec2.create();
c.bodyB.toWorldFrame(worldPivotB, c.pivotB);
g.moveTo(c.pivotA[0], c.pivotA[1]);
g.lineTo(worldPivotB[0], worldPivotB[1]);
g.cleared = false;
} else if(!this.pickGraphics.cleared){
this.pickGraphics.clear();
this.pickGraphics.cleared = true;
}
if(this.followBody){
app.centerCamera(this.followBody.interpolatedPosition[0], this.followBody.interpolatedPosition[1]);
}
this.renderer.render(this.container);
};
//http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
function componentToHex(c) {
var hex = c.toString(16);
return hex.length === 1 ? "0" + hex : hex;
}
function rgbToHex(r, g, b) {
return componentToHex(r) + componentToHex(g) + componentToHex(b);
}
//http://stackoverflow.com/questions/43044/algorithm-to-randomly-generate-an-aesthetically-pleasing-color-palette
function randomPastelHex(){
var mix = [255,255,255];
var red = Math.floor(Math.random()*256);
var green = Math.floor(Math.random()*256);
var blue = Math.floor(Math.random()*256);
// mix the color
red = Math.floor((red + 3*mix[0]) / 4);
green = Math.floor((green + 3*mix[1]) / 4);
blue = Math.floor((blue + 3*mix[2]) / 4);
return rgbToHex(red,green,blue);
}
WebGLRenderer.prototype.drawRenderable = function(obj, graphics, color, lineColor){
var lw = this.lineWidth;
var zero = [0,0];
graphics.drawnSleeping = false;
graphics.drawnColor = color;
graphics.drawnLineColor = lineColor;
if(obj instanceof p2.Body && obj.shapes.length){
var isSleeping = (obj.sleepState === p2.Body.SLEEPING);
graphics.drawnSleeping = isSleeping;
if(obj.concavePath && !this.debugPolygons){
var path = [];
for(var j=0; j!==obj.concavePath.length; j++){
var v = obj.concavePath[j];
path.push([v[0], v[1]]);
}
this.drawPath(graphics, path, lineColor, color, lw, isSleeping);
} else {
for(var i=0; i<obj.shapes.length; i++){
var child = obj.shapes[i],
offset = child.position,
angle = child.angle;
if(child instanceof p2.Circle){
this.drawCircle(graphics, offset[0], offset[1], angle, child.radius,color,lw,isSleeping);
} else if(child instanceof p2.Particle){
this.drawCircle(graphics, offset[0], offset[1], angle, 2*lw, lineColor, 0);
} else if(child instanceof p2.Plane){
// TODO use shape angle
WebGLRenderer.drawPlane(graphics, -10, 10, color, lineColor, lw, lw*10, lw*10, 100);
} else if(child instanceof p2.Line){
WebGLRenderer.drawLine(graphics, offset, angle, child.length, lineColor, lw);
} else if(child instanceof p2.Box){
this.drawRectangle(graphics, offset[0], offset[1], angle, child.width, child.height, lineColor, color, lw, isSleeping);
} else if(child instanceof p2.Capsule){
this.drawCapsule(graphics, offset[0], offset[1], angle, child.length, child.radius, lineColor, color, lw, isSleeping);
} else if(child instanceof p2.Convex){
// Scale verts
var verts = [],
vrot = p2.vec2.create();
for(var j=0; j!==child.vertices.length; j++){
var v = child.vertices[j];
p2.vec2.rotate(vrot, v, angle);
verts.push([(vrot[0]+offset[0]), (vrot[1]+offset[1])]);
}
this.drawConvex(graphics, verts, child.triangles, lineColor, color, lw, this.debugPolygons, offset, isSleeping);
} else if(child instanceof p2.Heightfield){
var path = [[0,-100]];
for(var j=0; j!==child.heights.length; j++){
var v = child.heights[j];
path.push([j*child.elementWidth, v]);
}
path.push([child.heights.length*child.elementWidth,-100]);
this.drawPath(graphics, path, lineColor, color, lw, isSleeping);
}
}
}
} else if(obj instanceof p2.Spring){
var restLengthPixels = obj.restLength;
WebGLRenderer.drawSpring(graphics,restLengthPixels,0x000000,lw);
}
};
WebGLRenderer.prototype.getIslandColor = function(body){
var islandColors = this.islandColors;
if(body.islandId === -1){
color = 0xdddddd; // Gray for static objects
} else if(islandColors[body.islandId]){
color = islandColors[body.islandId];
} else {
color = islandColors[body.islandId] = parseInt(randomPastelHex(),16);
}
return color;
};
WebGLRenderer.prototype.addRenderable = function(obj){
var lw = this.lineWidth;
// Random color
var lineColor = 0x000000;
var zero = [0,0];
var sprite = new PIXI.Graphics();
if(obj instanceof p2.Body && obj.shapes.length){
var color = this.getIslandColor(obj);
this.drawRenderable(obj, sprite, color, lineColor);
this.sprites.push(sprite);
this.stage.addChild(sprite);
} else if(obj instanceof p2.Spring){
this.drawRenderable(obj, sprite, 0x000000, lineColor);
this.springSprites.push(sprite);
this.stage.addChild(sprite);
}
};
WebGLRenderer.prototype.removeRenderable = function(obj){
if(obj instanceof p2.Body){
var i = this.bodies.indexOf(obj);
if(i!==-1){
this.stage.removeChild(this.sprites[i]);
this.sprites.splice(i,1);
}
} else if(obj instanceof p2.Spring){
var i = this.springs.indexOf(obj);
if(i!==-1){
this.stage.removeChild(this.springSprites[i]);
this.springSprites.splice(i,1);
}
}
};
WebGLRenderer.prototype.resize = function(w,h){
var renderer = this.renderer;
var view = renderer.view;
var ratio = w / h;
renderer.resize(w, h);
};
})(p2);