matter-dom-plugin
Version:
A plugin to apply matter.js's physics to DOM elements
733 lines (607 loc) • 24.6 kB
JavaScript
/*!
* matter-dom-plugin 0.1.2 by Edgar Lopez-Garci 2017-07-08
* https://github.com/elopezga/matter-dom-plugin
* License MIT
*/
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory(require("matter-js"));
else if(typeof define === 'function' && define.amd)
define(["matter-js"], factory);
else if(typeof exports === 'object')
exports["MatterDomPlugin"] = factory(require("matter-js"));
else
root["MatterDomPlugin"] = factory(root["Matter"]);
})(this, function(__WEBPACK_EXTERNAL_MODULE_5__) {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // identity function for calling harmony imports with the correct context
/******/ __webpack_require__.i = function(value) { return value; };
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "/libs";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 6);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
;
var DomBody = {};
module.exports = function (Matter) {
var Vertices = Matter.Vertices;
var Vector = Matter.Vector;
var Sleeping = Matter.Sleeping;
var Render = Matter.Render;
var Common = Matter.Common;
var Bounds = Matter.Bounds;
var Axes = Matter.Axes;
var Body = Matter.Body;
var Events = Matter.Events;
// Extend Body
DomBody = Common.clone(Body, true);
DomBody.create = function (options) {
var body = Body.create.apply(null, arguments);
//body.Dom.element.setAttribute('matter-id', body.id);
return body;
};
DomBody.setVertices = function (body, vertices) {
Body.setVertices.apply(null, arguments);
};
DomBody.setPosition = function (body, position) {
Body.setPosition.apply(null, arguments);
};
DomBody.setAngle = function (body, angle) {
Body.setAngle.apply(null, arguments);
};
DomBody.scale = function (body, scaleX, scaleY, point) {
Body.scale.apply(null, arguments);
};
DomBody.update = function (body, deltaTime, timeScale, correction) {
Body.update.apply(null, arguments);
};
return DomBody;
};
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
;
var DomMouseConstraint = {};
module.exports = function (Matter) {
var Vertices = Matter.Vertices;
var Sleeping = Matter.Sleeping;
var Mouse = Matter.Mouse;
var Events = Matter.Events;
var Detector = Matter.Detector;
var Constraint = Matter.Constraint;
var Composite = Matter.Composite;
var Common = Matter.Common;
var Bounds = Matter.Bounds;
DomMouseConstraint.create = function (engine, options) {
var mouse = (engine ? engine.mouse : null) || (options ? options.mouse : null);
if (!mouse) {
if (engine && engine.render && engine.render.canvas) {
mouse = Mouse.create(engine.render.canvas);
} else if (options && options.element) {
mouse = Mouse.create(options.element);
} else {
mouse = Mouse.create();
Common.warn('MouseConstraint.create: options.mouse was undefined, options.element was undefined, may not function as expected');
}
}
var constraint = Constraint.create({
label: 'Mouse Constraint',
pointA: mouse.position,
pointB: { x: 0, y: 0 },
length: 0.01,
stiffness: 0.1,
angularStiffness: 1,
render: {
strokeStyle: '#90EE90',
lineWidth: 3
}
});
var defaults = {
type: 'mouseConstraint',
mouse: mouse,
element: null,
body: null,
constraint: constraint,
collisionFilter: {
category: 0x0001,
mask: 0xFFFFFFFF,
group: 0
}
};
var domMouseConstraint = Common.extend(defaults, options);
Events.on(engine, 'beforeUpdate', function () {
var allBodies = Composite.allBodies(engine.world);
DomMouseConstraint.update(domMouseConstraint, allBodies);
_triggerEvents(domMouseConstraint);
});
return domMouseConstraint;
};
DomMouseConstraint.update = function (mouseConstraint, bodies) {
var mouse = mouseConstraint.mouse,
constraint = mouseConstraint.constraint,
body = mouseConstraint.body;
var mousePositionInWorld;
if (mouse.button === 0) {
if (!constraint.bodyB) {
var allDomBodies = document.querySelectorAll('[matter]');
for (var i = 0; i < bodies.length; i++) {
body = bodies[i];
mousePositionInWorld = body.Dom.render.mapping.viewToWorld(mouse.position);
var bodyPositionInView = body.Dom.render.mapping.worldToView(body.position);
if (Bounds.contains(body.bounds, mousePositionInWorld)) {
constraint.pointA = mousePositionInWorld;
constraint.bodyB = mouseConstraint.body = body;
//constraint.pointB = {x: mousePositionInWorld.x - body.position.x, y: mousePositionInWorld.y - body.position.y};
constraint.pointB = { x: 0, y: 0 };
constraint.angleB = body.angle;
break;
}
}
} else {
Sleeping.set(constraint.bodyB, false);
mousePositionInWorld = body.Dom.render.mapping.viewToWorld(mouse.position);
constraint.pointA = mousePositionInWorld;
}
} else {
constraint.bodyB = mouseConstraint.body = null;
constraint.pointB = null;
if (body) Events.trigger(mouseConstraint, 'enddrag', { mouse: mouse, body: body });
}
};
var _triggerEvents = function _triggerEvents(mouseConstraint) {
var mouse = mouseConstraint.mouse,
mouseEvents = mouse.sourceEvents;
if (mouseEvents.mousemove) Events.trigger(mouseConstraint, 'mousemove', { mouse: mouse });
if (mouseEvents.mousedown) Events.trigger(mouseConstraint, 'mousedown', { mouse: mouse });
if (mouseEvents.mouseup) Events.trigger(mouseConstraint, 'mouseup', { mouse: mouse });
// reset the mouse state ready for the next step
Mouse.clearSourceEvents(mouse);
};
return DomMouseConstraint;
};
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
;
module.exports = function (Matter) {
// Patch Engine
var World = Matter.World;
var Sleeping = Matter.Sleeping;
var Resolver = Matter.Resolver;
var Render = Matter.Render;
var Pairs = Matter.Pair;
var Metrics = Matter.Metrics;
var Grid = Matter.Grid;
var Events = Matter.Events;
var Composite = Matter.Composite;
var Constraint = Matter.Constraint;
var Common = Matter.Common;
var Body = Matter.Body;
var DomBody = Matter.DomBody;
var Engine = Matter.Engine;
var superUpdate = Engine.update;
Engine.update = function (engine, delta, correction) {
superUpdate(engine, delta, correction);
delta = delta || 1000 / 60;
correction = correction || 1;
var world = engine.world;
var timing = engine.timing;
var allBodies = Composite.allBodies(world);
_bodiesUpdate(allBodies, delta, timing.timeScale, correction, world.bounds);
return engine;
};
var _bodiesUpdate = function _bodiesUpdate(bodies, deltaTime, timeScale, correction, worldBounds) {
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i];
if (body.isStatic || body.isSleeping) continue;
DomBody.update(body, deltaTime, timeScale, correction);
}
};
};
/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
;
var DomBodies = {};
module.exports = function (Matter) {
var Body = Matter.Body;
var Bodies = Matter.Bodies;
var DomBody = Matter.DomBody;
var Vertices = Matter.Vertices;
var Common = Matter.Common;
var World = Matter.World;
var Bounds = Matter.Bounds;
var Vector = Matter.Vector;
DomBodies.create = function (options) {
var bodyType = options.bodyType; // Required
var el = options.el; // Required
var render = options.render; // Required
var position = options.position; // Required
delete options.bodyType;
//delete options.el;
delete options.render;
delete options.position;
options.domRenderer = render;
/*
options.Dom = {
render: render,
element: null
}
*/
var worldBody = null;
var domBody = document.querySelector(el);
var positionInWorld = render.mapping.viewToWorld({ x: position.x, y: position.y });
if (bodyType == "block") {
var blockDimensionsInWorld = render.mapping.viewToWorld({
x: domBody.offsetWidth,
y: domBody.offsetHeight
});
//console.log("One block, please!")
worldBody = DomBodies.OGblock(positionInWorld.x, positionInWorld.y, blockDimensionsInWorld.x, blockDimensionsInWorld.y, options);
} else if (bodyType == "circle") {
var circleRadiusInWorld = render.mapping.viewToWorld(domBody.offsetWidth / 2);
//console.log("One circle, please!");
worldBody = DomBodies.circle(positionInWorld.x, positionInWorld.y, circleRadiusInWorld, options);
}
if (worldBody) {
// TODO TEST THIS!!
//domBody.setAttribute('matter-id', worldBody.id);
World.add(render.engine.world, [worldBody]);
}
return worldBody;
};
DomBodies.OGblock = function (x, y, width, height, options) {
var options = options || {};
var block = {
label: 'Block Body',
position: { x: x, y: y },
vertices: Vertices.fromPath('L 0 0 L ' + width + ' 0 L ' + width + ' ' + height + ' L 0 ' + height)
};
if (options.chamfer) {
var chamfer = option.chamfer;
block.vertices = Vertices.chamfer(block.vertices, chamfer.radius, chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
delete options.chamfer;
}
return DomBody.create(Common.extend({}, block, options));
};
DomBodies.block = function (x, y, options) {
var defaults = {
Dom: {
element: null,
render: null
}
};
var options = options || {};
options = Common.extend(defaults, options);
var render = options.Dom.render;
var element = options.Dom.element;
var positionInWorld = render.mapping.viewToWorld({
x: x,
y: y
});
var elementDimensionsInWorld = render.mapping.viewToWorld({
x: element.offsetWidth,
y: element.offsetHeight
});
var block = {
label: 'DOM Block Body',
position: { x: positionInWorld.x, y: positionInWorld.y },
vertices: Vertices.fromPath('L 0 0 L ' + elementDimensionsInWorld.x + ' 0 L ' + elementDimensionsInWorld.x + ' ' + elementDimensionsInWorld.y + ' L 0 ' + elementDimensionsInWorld.y)
};
if (options.chamfer) {
var chamfer = option.chamfer;
block.vertices = Vertices.chamfer(block.vertices, chamfer.radius, chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
delete options.chamfer;
}
var body = DomBody.create(Common.extend({}, block, options));
//element.setAttribute('matter-id', body.id);
return body;
};
DomBodies.circle = function (x, y, radius, options, maxSides) {
options = options || {};
var circle = {
label: 'Circle Body',
circleRadius: radius
// approximate circles with polygons until true circles implemented in SAT
};maxSides = maxSides || 25;
var sides = Math.ceil(Math.max(10, Math.min(maxSides, radius)));
// optimisation: always use even number of sides (half the number of unique axes)
if (sides % 2 === 1) sides += 1;
return DomBodies.polygon(x, y, sides, radius, Common.extend({}, circle, options));
};
DomBodies.polygon = function (x, y, sides, radius, options) {
options = options || {};
if (sides < 3) return Bodies.circle(x, y, radius, options);
var theta = 2 * Math.PI / sides,
path = '',
offset = theta * 0.5;
for (var i = 0; i < sides; i += 1) {
var angle = offset + i * theta,
xx = Math.cos(angle) * radius,
yy = Math.sin(angle) * radius;
path += 'L ' + xx.toFixed(3) + ' ' + yy.toFixed(3) + ' ';
}
var polygon = {
label: 'Polygon Body',
position: { x: x, y: y },
vertices: Vertices.fromPath(path)
};
if (options.chamfer) {
var chamfer = options.chamfer;
polygon.vertices = Vertices.chamfer(polygon.vertices, chamfer.radius, chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
delete options.chamfer;
}
return DomBody.create(Common.extend({}, polygon, options));
};
return DomBodies;
};
/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {
;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var RenderDom = {};
module.exports = function (Matter) {
var Common = Matter.Common;
var Composite = Matter.Composite;
var Events = Matter.Events;
var Render = Matter.Render;
var _requestAnimationFrame, _cancelAnimationFrame;
if (typeof window !== 'undefined') {
_requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {
window.setTimeout(function () {
callback(Common.now());
}, 1000 / 60);
};
_cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame || window.msCancelAnimationFrame;
}
RenderDom.create = function (options) {
var defaults = {
engine: null,
element: window,
controller: RenderDom,
frameRequestId: null,
options: {}
/*
try{
if(!options){
throw (new Error('No engine was specified. Create an options object and specify the engine with the engine property.'));
}
if(!options.engine){
throw (new Error('No engine was specified. Create an options object and specify the engine with the engine property.'));
}
}catch(e){
console.log(`${e.name}: ${e.message}`);
return;
}*/
};var engine = options.engine;
var render = Common.extend(defaults, options);
render.mapping = {};
render.mapping.ratioMultiplier = 1 / 6; // VIEW is base ratio. Mapping to World.
render.mapping.VIEW = {
width: window.innerWidth,
height: window.innerHeight
};
render.mapping.VIEW.center = {
x: render.mapping.VIEW.width / 2,
y: render.mapping.VIEW.height / 2
};
render.mapping.WORLD = {
width: render.mapping.VIEW.width * render.mapping.ratioMultiplier,
height: render.mapping.VIEW.height * render.mapping.ratioMultiplier
};
render.mapping.WORLD.center = {
x: render.mapping.WORLD.width / 2,
y: render.mapping.WORLD.height / 2
};
render.mapping.viewToWorld = function (value) {
if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && value !== null) {
return {
x: render.mapping.ratioMultiplier * value.x,
y: render.mapping.ratioMultiplier * value.y
};
} else {
return render.mapping.ratioMultiplier * value;
}
};
render.mapping.worldToView = function (value) {
if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && value !== null) {
return {
x: value.x / render.mapping.ratioMultiplier,
y: value.y / render.mapping.ratioMultiplier
};
} else {
return value / render.mapping.ratioMultiplier;
}
};
var debugElement = document.querySelector('#debug');
debugElement.style.position = "absolute";
var debugRender = Render.create({
element: document.querySelector('#debug'),
engine: engine,
options: {
width: render.mapping.WORLD.width,
height: render.mapping.WORLD.height,
background: '#fafafa',
wireframeBackground: '#222',
hasBounds: false,
enabled: true,
wireframes: true,
showSleeping: true,
showDebug: false,
showBroadphase: false,
showBounds: false,
showVelocity: false,
showCollisions: false,
showAxes: false,
showPositions: false,
showAngleIndicator: false,
showIds: false,
showShadows: false
}
});
Render.run(debugRender);
render.DebugRender = debugRender;
return render;
};
RenderDom.run = function (render) {
(function loop(time) {
render.frameRequestId = _requestAnimationFrame(loop);
RenderDom.world(render);
})();
};
RenderDom.stop = function (render) {
_cancelAnimationFrame(render.frameRequestId);
};
RenderDom.world = function (render) {
var engine = render.engine,
world = engine.world,
allBodies = Composite.allBodies(world),
allConstraints = Composite.allConstraints(world),
domBodies = document.querySelectorAll('[matter]');
var event = {
timestamp: engine.timing.timestamp
};
Events.trigger(render, 'beforeRender', event);
// TODO bounds if specified
RenderDom.bodies(render, domBodies);
};
/**
* Map Dom view elements position to matter world bodys position
*/
RenderDom.bodies = function (render, bodies, context) {
var c = context,
engine = render.engine,
world = engine.world,
options = render.options,
matterBodies = Composite.allBodies(world),
domBody;
for (var i = 0; i < matterBodies.length; i++) {
var matterBody = matterBodies[i];
for (var k = matterBody.parts.length > 1 ? 1 : 0; k < matterBody.parts.length; k++) {
var matterPart = matterBody.parts[k];
var domPart = matterPart.Dom.element;
var bodyWorldPoint = render.mapping.worldToView({ x: matterPart.position.x, y: matterPart.position.y });
var bodyViewOffset = { x: domPart.offsetWidth / 2, y: domPart.offsetHeight / 2 };
domPart.style.position = "absolute";
domPart.style.transform = 'translate(' + (bodyWorldPoint.x - bodyViewOffset.x) + 'px, ' + (bodyWorldPoint.y - bodyViewOffset.y) + 'px)';
domPart.style.transform += 'rotate(' + matterBody.angle + 'rad)';
}
}
};
return RenderDom;
};
/***/ }),
/* 5 */
/***/ (function(module, exports) {
module.exports = __WEBPACK_EXTERNAL_MODULE_5__;
/***/ }),
/* 6 */
/***/ (function(module, exports, __webpack_require__) {
;
var Matter = __webpack_require__(5);
var RenderDom = __webpack_require__(4);
var DomBody = __webpack_require__(0);
var DomBodies = __webpack_require__(3);
var DomMouseConstraint = __webpack_require__(1);
var Engine = __webpack_require__(2);
var MatterDomPlugin = {
name: 'matter-dom-plugin',
version: '0.1.2',
for: 'matter-js@^0.12.0',
install: function install(matter) {
MatterDomPlugin.installRenderDom(matter);
MatterDomPlugin.installDomBody(matter);
MatterDomPlugin.installDomBodies(matter); // Depends on DomBody
MatterDomPlugin.installDomMouseConstraint(matter);
MatterDomPlugin.installEngine(matter);
},
installRenderDom: function installRenderDom(matter) {
console.log("Installing RenderDom module.");
matter.RenderDom = RenderDom(matter);
},
installDomBodies: function installDomBodies(matter) {
console.log("Installing DomBodies module.");
matter.DomBodies = DomBodies(matter);
},
installDomMouseConstraint: function installDomMouseConstraint(matter) {
console.log("Installing DomMouseConstraint.");
matter.DomMouseConstraint = DomMouseConstraint(matter);
},
installDomBody: function installDomBody(matter) {
console.log("Installing DomBody updates.");
matter.DomBody = DomBody(matter);
},
installEngine: function installEngine(matter) {
console.log("Patching Engine.");
Engine(matter);
}
};
Matter.Plugin.register(MatterDomPlugin);
module.exports.MatterDomPlugin = MatterDomPlugin;
/***/ })
/******/ ]);
});