-
-
Save davay42/710e87b9e8e675c2a21f68ad86b42123 to your computer and use it in GitHub Desktop.
The `RenderSVG` module is an SVG alternative to the Matter.js built-in canvas-based renderer.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Overwrite `Common.isElement` to allow for SVG elements also | |
*/ | |
Matter.Common.isElement = function(obj) { | |
try { | |
return obj instanceof HTMLElement || obj instanceof SVGElement; | |
} catch (e) { | |
return (typeof obj === "object") && | |
(obj.nodeType === 1) && (typeof obj.style === "object") && | |
(typeof obj.ownerDocument === "object"); | |
} | |
}; | |
/** | |
* The `Matter.RenderSVG` module is an SVG alternative to Matter's built-in | |
* canvas-based renderer. | |
* | |
* @class RenderSVG | |
* @requires gsap/TweenMax Uses the 'static.set' function to move SVG elements each render cycle, | |
* this dependency could be removed by implementing the transform setting | |
* but TweenMax has some nice features like setting transformOrigin relative | |
* to the SVG element itself, and all transforms are translated into a single | |
* matrix transform for consistency. | |
*/ | |
var RenderSVG = {}; | |
//module.exports = RenderSVG; | |
(function() { | |
var { Body, Bounds, Composite, Common, Events, Vector, Vertices } = Matter; | |
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; | |
} | |
/** | |
* Creates a new SVG renderer | |
* @method create | |
* @param {object} options | |
* @return {RenderSVG} A new renderer | |
*/ | |
RenderSVG.create = function(options) { | |
var defaults = { | |
controller: RenderSVG, | |
engine: null, | |
element: null, | |
svg: null, | |
container: null, | |
spriteContainer: null, | |
frameRequestId: null, | |
options: { | |
width: 800, | |
height: 600, | |
background: '#18181d', | |
wireframeBackground: '#222', | |
hasBounds: true, | |
enabled: true, | |
wireframes: true, | |
showContextMenu: false, | |
closeConstraintPath: false, | |
mouseConstraint: true, | |
//@TODO Options below here are currently not supported | |
showSleeping: false, | |
showDebug: false, | |
showBroadphase: false, | |
showBounds: false, | |
showVelocity: false, | |
showCollisions: false, | |
showAxes: false, | |
showPositions: false, | |
showAngleIndicator: false, | |
showIds: false, | |
showShadows: false | |
} | |
}; | |
var render = Common.extend(defaults, options) | |
if (render.options.background === 'transparent') render.options.background = 'none'; | |
if (!options.engine) | |
Common.warn('No "render.engine" passed, no world to render'); | |
render.engine = options.engine; | |
render.mouse = options.mouse; | |
render.element = options.element; | |
render.svg = options.svg; | |
render.container = options.container; | |
render.spriteContainer = options.spriteContainer; | |
// SVG and SVG parent node | |
if (!Common.isElement(render.svg) || render.svg.tagName.toLowerCase() !== 'svg') { | |
if (typeof render.svg === 'string') | |
render.svg = document.querySelector(render.svg) | |
if (!Common.isElement(render.svg) || render.svg.tagName.toLowerCase() !== 'svg') { | |
render.svg = document.createElementNS("http://www.w3.org/2000/svg", "svg") | |
render.svg.setAttribute("width", render.options.width + "px") | |
render.svg.setAttribute("height", render.options.height + "px") | |
} | |
} | |
render.svg = _checkSvgXmlns(render.svg) | |
if (!document.body.contains(render.svg)) { | |
if (!Common.isElement(render.element)) { | |
if (typeof render.element === 'string') | |
render.element = document.querySelector(render.element) | |
if (!Common.isElement(render.element)) | |
render.element = document.body | |
} | |
render.element.appendChild(render.svg) | |
if (!document.body.contains(render.element)) { | |
render.body.appendChild(render.element) | |
} | |
} else { | |
if (!Common.isElement(render.element)) { | |
if (typeof render.element === 'string') | |
render.element = document.querySelector(render.element) | |
if (!Common.isElement(render.element)) | |
render.element = render.svg.parentNode | |
else | |
render.element.appendChild(render.svg) | |
} | |
} | |
// SVG stage container element | |
if (!Common.isElement(render.container)) { | |
if (typeof render.container === 'string') | |
render.container = document.querySelector(render.container) | |
if (!Common.isElement(render.container)) | |
render.container = render.svg | |
} | |
if (!render.svg.contains(render.container)) | |
render.svg.appendChild(render.container) | |
// SVG sprite container element | |
if (!Common.isElement(render.spriteContainer)) { | |
if (typeof render.spriteContainer === 'string') | |
render.spriteContainer = document.querySelector(render.spriteContainer) | |
if (!Common.isElement(render.spriteContainer)) | |
render.spriteContainer = render.container | |
} | |
if (!render.svg.contains(render.spriteContainer)) | |
render.svg.appendChild(render.spriteContainer) | |
// Bounds (stops DOM updates for elements outside this range) | |
render.bounds = render.bounds || { | |
min: { | |
x: -10, | |
y: -10 | |
}, | |
max: { | |
x: render.options.width + 10, | |
y: render.options.height + 10 | |
} | |
}; | |
// caches | |
render.sprites = {}; | |
render.domNodes = {}; | |
// prevent menus on canvas | |
if (!render.options.showContextMenu) { | |
render.svg.oncontextmenu = function(e) { | |
e.preventDefault(); | |
return false; | |
}; | |
render.svg.onselectstart = function(e) { | |
e.preventDefault(); | |
return false; | |
}; | |
} | |
if (render.options.mouseConstraint) { | |
// ============================================= | |
// Add mouse control | |
// ============================================= | |
/* Built in doesn't work for SVG renderer | |
* const mouse = Mouse.create(render.svg) | |
* var mConstraint = MouseConstraint.create({mouse:mouse}) | |
* World.add(world,mConstraint) | |
* So using own implementation. | |
*/ | |
/*Events.on(render.engine, 'beforeUpdate', function() { | |
});*/ | |
render.mouseConstraint = { | |
lastLoc: null, | |
draggedBody: null, | |
wasStatic: null, | |
dx: 0, | |
dy: 0 | |
}; | |
render.svg.addEventListener('mousedown', function(evt) { | |
let loc = RenderSVG.screenToSVGCoords(render, { | |
x: evt.clientX, | |
y: evt.clientY | |
}), | |
mouseConstraint = render.mouseConstraint, | |
bodies = Composite.allBodies(render.engine.world); | |
//Sort bodies so we drag the smallest body our mouse is over | |
bodies = bodies.sort((a, b) => { | |
let ab = (a.bounds.max.x - a.bounds.min.x) * (a.bounds.max.y - a.bounds.min.y) | |
let bb = (b.bounds.max.x - b.bounds.min.x) * (b.bounds.max.y - b.bounds.min.y) | |
return ab < bb ? -1 : ab > bb ? 1 : 0 | |
}) | |
for (let i = 0; i < bodies.length; i++) { | |
let body = bodies[i]; | |
if(Vertices.contains(body.vertices, loc)){ | |
mouseConstraint.draggedBody = body; | |
mouseConstraint.dx = body.position.x - loc.x | |
mouseConstraint.dy = body.position.y - loc.y | |
if (!body.isStatic) | |
Body.setStatic(body, true); | |
else | |
mouseConstraint.wasStatic = true | |
break; | |
} | |
} | |
mouseConstraint.lastLoc = loc; | |
}); | |
render.svg.addEventListener('mousemove', function(evt) { | |
let mouseConstraint = render.mouseConstraint | |
if (!mouseConstraint.draggedBody) return false; | |
let loc = RenderSVG.screenToSVGCoords(render, { | |
x: evt.clientX, | |
y: evt.clientY | |
}) | |
Body.setPosition(mouseConstraint.draggedBody, { | |
x: loc.x + mouseConstraint.dx, | |
y: loc.y + mouseConstraint.dy | |
}); | |
if (mouseConstraint.lastLoc && !mouseConstraint.wasStatic) | |
Body.setVelocity(mouseConstraint.draggedBody, { | |
x: Math.max(Math.min(Math.sign(loc.x - mouseConstraint.lastLoc.x) * Math.pow(Math.abs(loc.x - mouseConstraint.lastLoc.x), 1), 15), -15), | |
y: Math.max(Math.min(Math.sign(loc.y - mouseConstraint.lastLoc.y) * Math.pow(Math.abs(loc.y - mouseConstraint.lastLoc.y), 1), 15), -15) | |
}); | |
mouseConstraint.lastLoc = loc; | |
}); | |
let endDrag = function(evt) { | |
let mouseConstraint = render.mouseConstraint | |
if (mouseConstraint.draggedBody && !mouseConstraint.wasStatic) | |
Body.setStatic(mouseConstraint.draggedBody, false); | |
render.mouseConstraint = { | |
lastLoc: null, | |
draggedBody: null, | |
wasStatic: null, | |
dx: 0, | |
dy: 0 | |
}; | |
} | |
render.svg.addEventListener('mouseleave', endDrag); | |
render.svg.addEventListener('mouseup', endDrag); | |
} | |
return render; | |
}; | |
function _checkSvgXmlns(svg) { | |
if (svg.namespaceURI !== 'http://www.w3.org/2000/svg') { | |
let newSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg') | |
newSvg.setAttributeNS("http://www.w3.org/2000/xmlns", "xmlns:xlink", "http://www.w3.org/1999/xlink"); | |
while (svg.children.length) { | |
newSvg.appendChild(svg.children[0]) | |
} | |
newSvg.id = svg.id | |
svg.classList.forEach((cls, i, arr) => { | |
newSvg.classList.add(cls) | |
}) | |
newSvg.setAttribute("width", svg.width.baseVal.value + "px") | |
newSvg.setAttribute("height", svg.height.baseVal.value + "px") | |
svg.parentNode.appendChild(newSvg) | |
svg.parentNode.removeChild(svg) | |
return newSvg | |
} | |
if (svg.lookupNamespaceURI('xlink') !== 'http://www.w3.org/1999/xlink') { | |
svg.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', 'http://www.w3.org/1999/xlink'); | |
} | |
return svg | |
} | |
/** | |
* Continuously updates the SVG elements on the `requestAnimationFrame` event. | |
* @method run | |
* @param {render} render | |
*/ | |
RenderSVG.run = function(render) { | |
(function loop(time) { | |
render.frameRequestId = _requestAnimationFrame(loop); | |
if (render.options.enabled) | |
RenderSVG.world(render); | |
})(); | |
}; | |
/** | |
* Ends execution of `Render.run` on the given `render`, by canceling the animation frame request event loop. | |
* @method stop | |
* @param {render} render | |
*/ | |
RenderSVG.stop = function(render) { | |
_cancelAnimationFrame(render.frameRequestId); | |
}; | |
/** | |
* Clears the scene | |
* @method clear | |
* @param {RenderSVG} render | |
*/ | |
RenderSVG.clear = function(render) { | |
// clear stage container | |
while (render.container.children.length) { | |
render.container.removeChild(render.container.children[0]); | |
} | |
// clear sprites | |
while (render.spriteContainer.children.length) { | |
render.spriteContainer.removeChild(render.spriteContainer.children[0]); | |
} | |
// clear caches | |
render.sprites = {}; | |
render.domNodes = {}; | |
// reset background state | |
render.currentBackground = null; | |
// reset any transforms | |
}; | |
/** | |
* Sets the background of the SVG | |
* @method setBackground | |
* @param {RenderSVG} render | |
* @param {string} background | |
*/ | |
RenderSVG.setBackground = function(render, background) { | |
if (render.currentBackground !== background) { | |
render.svg.style.backgroundColor = background; | |
render.currentBackground = background; | |
} | |
}; | |
/** | |
* Render the world | |
* @method world | |
* @param {engine} engine | |
*/ | |
RenderSVG.world = function(render) { | |
var engine = render.engine, | |
world = engine.world, | |
container = render.container, | |
options = render.options, | |
bodies = Composite.allBodies(world), | |
allConstraints = Composite.allConstraints(world), | |
constraints = [], | |
i; | |
if (options.wireframes) | |
RenderSVG.setBackground(render, options.wireframeBackground); | |
else | |
RenderSVG.setBackground(render, options.background); | |
// handle bounds | |
var boundsWidth = render.bounds.max.x - render.bounds.min.x, | |
boundsHeight = render.bounds.max.y - render.bounds.min.y, | |
boundsScaleX = boundsWidth / render.options.width, | |
boundsScaleY = boundsHeight / render.options.height; | |
if (options.hasBounds) { | |
// Set bodies that are currently in view | |
for (i = 0; i < bodies.length; i++) { | |
let body = bodies[i]; | |
body.render.visible = Bounds.overlaps(body.bounds, render.bounds) | |
} | |
// filter out constraints that are not in view | |
for (i = 0; i < allConstraints.length; i++) { | |
let constraint = allConstraints[i], | |
bodyA = constraint.bodyA, | |
bodyB = constraint.bodyB, | |
pointAWorld = constraint.pointA, | |
pointBWorld = constraint.pointB; | |
if (bodyA) pointAWorld = Vector.add(bodyA.position, constraint.pointA); | |
if (bodyB) pointBWorld = Vector.add(bodyB.position, constraint.pointB); | |
if (!pointAWorld || !pointBWorld) | |
continue; | |
if (Bounds.contains(render.bounds, pointAWorld) || Bounds.contains(render.bounds, pointBWorld)) | |
constraints.push(constraint); | |
} | |
// transform the view | |
} else { | |
constraints = allConstraints; | |
} | |
for (i = 0; i < bodies.length; i++) | |
RenderSVG.body(render, bodies[i]); | |
for (i = 0; i < constraints.length; i++) | |
RenderSVG.constraint(render, constraints[i]); | |
}; | |
/** | |
* Render the given constraint - currently a simple line | |
* @method constraint | |
* @param {engine} engine | |
* @param {constraint} constraint | |
* @todo Render a visual clue on the "stiffess" of the constraint via zig-zag lines | |
* @todo Allow sprites for constraints (assume unit length, render with transform="rotation(..) scale(...)") | |
*/ | |
RenderSVG.constraint = function(render, constraint) { | |
var engine = render.engine, | |
bodyA = constraint.bodyA, | |
bodyB = constraint.bodyB, | |
pointA = constraint.pointA, | |
pointB = constraint.pointB, | |
container = render.container, | |
constraintRender = constraint.render, | |
domId = 'c-' + constraint.id, | |
domNode = render.domNodes[domId]; | |
// don't render if constraint does not have two end points | |
if (!constraintRender.visible || !constraint.pointA || !constraint.pointB) { | |
if (domNode && domNode.parentNode) | |
domNode.parentNode.removeChild(domNode); | |
return; | |
} | |
// initialise constraint node if not existing | |
if (!domNode) | |
render.domNodes[domId] = domNode = document.createElementNS(render.svg.namespaceURI, 'path'); | |
// add to stage container if not already there | |
if (Common.indexOf(container.children, domNode) === -1) | |
container.appendChild(domNode); | |
// render the constraint on every update, since they can change dynamically | |
var ax = pointA.x + (bodyA ? bodyA.position.x : 0), | |
ay = pointA.y + (bodyA ? bodyA.position.y : 0), | |
bx = pointB.x + (bodyB ? bodyB.position.x : 0), | |
by = pointB.y + (bodyB ? bodyB.position.y : 0), | |
cx = (ax + bx) / 2, | |
cy = (ay + by) / 2 | |
ax -= cx, ay -= cy, bx -= cx, by -= cy | |
var d = 'M ' + [ax.toFixed(1), ay.toFixed(1), bx.toFixed(1), by.toFixed(1)].join(' ') + (constraintRender.closePath === false ? '' : (constraintRender.closePath || render.options.closeConstraintPath ? ' Z' : '')) | |
domNode.setAttributeNS(null, 'd', d) | |
TweenMax.set(domNode, { | |
x: cx.toFixed(1), | |
y: cy.toFixed(1), | |
fill: render.options.wireframes ? 'none' : (constraintRender.fillStyle || 'none'), | |
strokeWidth: render.options.wireframes ? 2 : (constraintRender.lineWidth || 0), | |
stroke: render.options.wireframes ? '#bbb' : (constraintRender.strokeStyle || '#bbb'), | |
opacity: render.options.wireframes ? 1 : (constraintRender.opacity || 1) | |
}) | |
}; | |
/** | |
* Render the given body / sprite | |
* @method body | |
* @param {engine} engine | |
* @param {body} body | |
* @todo Add Sleeping/Debug/Broadphase/Bounds/Velocity/Collisions/Axes/Positions/AngleIndicator/Ids/Shadows indicators on wireframes | |
*/ | |
RenderSVG.body = function(render, body) { | |
var engine = render.engine, | |
bodyRender = body.render, | |
sprite, | |
spriteContainer = render.spriteContainer; | |
if (bodyRender.sprite && bodyRender.sprite.texture) { | |
let spriteId = 'b-' + body.id; | |
sprite = render.sprites[spriteId]; | |
if (!sprite) { | |
if (Common.isElement(bodyRender.sprite.texture)) | |
sprite = bodyRender.sprite.texture | |
else if (typeof bodyRender.sprite.texture === 'string') | |
sprite = document.querySelector(bodyRender.sprite.texture) | |
if (sprite) { | |
if (sprite.parentNode && sprite.parentNode.tagName === "defs") { | |
let spriteCopy = document.createElementNS(render.svg.namespaceURI, 'use') | |
spriteCopy.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#' + sprite.id) | |
spriteCopy.setAttributeNS(null, 'x', 0) | |
spriteCopy.setAttributeNS(null, 'y', 0) | |
spriteContainer.appendChild(spriteCopy) | |
sprite = spriteCopy | |
} | |
render.sprites[spriteId] = sprite | |
_saveCOMRef(render, body, sprite) | |
_setTransformOrigin(body, sprite) | |
} | |
} | |
} | |
if (sprite) { | |
if (!bodyRender.visible || bodyRender.sprite.visible === false) { | |
if (sprite.parentNode) | |
sprite.parentNode.removeChild(sprite) | |
return | |
} else if (!render.svg.contains(sprite)) | |
spriteContainer.appendChild(sprite) | |
// update body sprite | |
let deg = 180 / Math.PI | |
TweenMax.set(sprite, { | |
x: body.position.x.toFixed(1) + (bodyRender.sprite.xOffset || 0), | |
y: body.position.y.toFixed(1) + (bodyRender.sprite.yOffset || 0), | |
rotation: (body.angle * deg).toFixed(1), | |
scaleX: bodyRender.sprite.xScale || 1, | |
scaleY: bodyRender.sprite.yScale || 1 | |
}) | |
if (render.options.wireframes) { | |
if (!bodyRender.sprite.origStyle) { | |
bodyRender.sprite.origStyle = { | |
fillStyle: sprite.style.fill || bodyRender.fillStyle, | |
lineWidth: sprite.style.strokeWidth || bodyRender.lineWidth, | |
strokeStyle: sprite.style.stroke || bodyRender.strokeStyle, | |
opacity: sprite.style.opacity || bodyRender.opacity | |
} | |
TweenMax.set(sprite, { | |
fill: 'none', | |
strokeWidth: 2, | |
stroke: '#bbb', | |
opacity: 1 | |
}) | |
} | |
} else if (bodyRender.sprite.origStyle) { | |
TweenMax.set(sprite, { | |
fill: bodyRender.sprite.origStyle.fillStyle, | |
strokeWidth: bodyRender.sprite.origStyle.lineWidth, | |
stroke: bodyRender.sprite.origStyle.strokeStyle, | |
opacity: bodyRender.sprite.origStyle.opacity | |
}) | |
delete bodyRender.sprite.origStyle | |
} | |
} else { | |
let domId = 'b-' + body.id, | |
domNode = render.domNodes[domId], | |
container = render.container; | |
if (!bodyRender.visible) { | |
if (domNode && domNode.parentNode) | |
domNode.parentNode.removeChild(domNode); | |
return; | |
} | |
// initialise body node if not existing | |
if (!domNode) | |
domNode = render.domNodes[domId] = _createBodyDomNode(render, body); | |
// add to staging container if not already there | |
if (Common.indexOf(container.children, domNode) === -1) | |
container.appendChild(domNode); | |
// update body node | |
let deg = 180 / Math.PI | |
TweenMax.set(domNode, { | |
x: body.position.x.toFixed(1), | |
y: body.position.y.toFixed(1), | |
rotation: (body.angle * deg).toFixed(1), | |
fill: render.options.wireframes ? 'none' : (bodyRender.fillStyle || 'none'), | |
strokeWidth: render.options.wireframes ? 2 : (bodyRender.lineWidth || 0), | |
stroke: render.options.wireframes ? '#bbb' : (bodyRender.strokeStyle || '#bbb'), | |
opacity: render.options.wireframes ? 1 : (bodyRender.opacity || 1) | |
}) | |
} | |
}; | |
/** | |
* Turns an array of vertices into an SVG d attribute string | |
* @param {array} vertices | |
* @param {boolean} leaveOpen If true the path will not be closed | |
*/ | |
function _dAttr(vertices, leaveOpen) { | |
return 'M ' + vertices.map(v => v.x.toFixed(1) + ' ' + v.y.toFixed(1)).join(' ') + (leaveOpen ? '' : ' Z') | |
} | |
/** | |
* Makes a clone of the given body but with the centre of mass at (0,0) | |
* @param {body} body | |
* @param {boolean} recurs (Whether the current call is a recursive call or not, only for internal use) | |
*/ | |
function _cloneAtOrigin(body, recurs) { | |
var angleBefore, posBefore, result | |
if (!recurs) { | |
angleBefore = body.angle, posBefore = Object.assign({}, body.position) | |
Body.setAngle(body, 0) | |
Body.setPosition(body, { | |
x: 0, | |
y: 0 | |
}) | |
} | |
if (body.parts.length > 1) { | |
let clonedParts = [] | |
body.parts.forEach((part, i, parts) => { | |
if (i == 0) return; //ignore self-referential first part | |
clonedParts.push(_cloneAtOrigin(part, true)); | |
}) | |
result = Body.create({ | |
parts: clonedParts | |
}); | |
} else { | |
result = Bodies.fromVertices(body.position.x, body.position.y, [body.vertices], { | |
isStatic: body.isStatic, | |
render: body.render | |
}, undefined, 0, 0) | |
} | |
if (!recurs) { | |
Body.setAngle(body, angleBefore) | |
Body.setPosition(body, posBefore) | |
} | |
return result | |
} | |
/** | |
* Saves where the bodies center of mass is relative to the visual centre of the element | |
* @param {body} bodyAtOrigin | |
* @param {node} domNodeAtOrigin | |
*/ | |
function _saveCOMRef(render, bodyAtOrigin, domNodeAtOrigin) { | |
var clientBox = domNodeAtOrigin.getBoundingClientRect() | |
var COMOffset = RenderSVG.screenToSVGCoords(render, { | |
x: clientBox.x + clientBox.width / 2, | |
y: clientBox.y + clientBox.height / 2 | |
}) | |
COMOffset.x *= -1 | |
COMOffset.y *= -1 | |
bodyAtOrigin._COM = COMOffset | |
return COMOffset | |
} | |
/** | |
* Sets the origin for all transforms at the bodies centre of mass | |
* @param {body} body | |
* @param {node} domNode | |
*/ | |
function _setTransformOrigin(body, domNode) { | |
var w = body.bounds.max.x - body.bounds.min.x, | |
h = body.bounds.max.y - body.bounds.min.y, | |
tx = 100 * (w / 2 + body._COM.x) / w, | |
ty = 100 * (h / 2 + body._COM.y) / h | |
TweenMax.set(domNode, { | |
transformOrigin: tx + '% ' + ty + '%' | |
}) | |
} | |
/** | |
* Creates a body DOM node | |
* @method _createBodyDomNode | |
* @private | |
* @param {RenderSVG} render | |
* @param {body} body | |
* @return {node} domNode | |
* @deprecated | |
*/ | |
var _createBodyDomNode = function(render, body, recurs) { | |
var domNode | |
body = recurs ? body : _cloneAtOrigin(body) | |
if (body.parts.length > 1) { | |
domNode = document.createElementNS(render.svg.namespaceURI, "g"); | |
body.parts.forEach((part, i, parts) => { | |
if (i == 0) return; //ignore self-referential first part | |
var partNode = _createBodyDomNode(render, part, true); | |
domNode.appendChild(partNode) | |
}) | |
} else { | |
domNode = document.createElementNS(render.svg.namespaceURI, "path"); | |
domNode.setAttributeNS(null, 'd', _dAttr(body.vertices)) | |
} | |
if (!recurs) { | |
_saveCOMRef(render, body, domNode) | |
_setTransformOrigin(body, domNode) | |
} | |
return domNode; | |
}; | |
//Coordinate transformer | |
RenderSVG.screenToSVGCoords = function(render, pos) { | |
let ptTransformer = render.svg.createSVGPoint() | |
ptTransformer.x = pos.x; | |
ptTransformer.y = pos.y; | |
return ptTransformer.matrixTransform(render.svg.getScreenCTM().inverse()); | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment