0
votes

I am getting kind of headache… I am using jsbox2d.js for a 2d game, based on SVG graphics. I would like to »connect« a b2d-body to an svg element, what already works for the debug draw, but not for the graphics in the scene.

setup:

  • there is an SVG, including an arbitrary deeply nested structure of groups and shapes. Each one my have some transformations, so each element resides in its own coordinate space.

  • aside there is a box2d simulation and some bodies represent an element in the svg.

I would like to apply the b2body's transform to the svg-element it represents, so that the animation looks correctly. It is already working for the debug draw, where I am using this code:

// links is an array like [[b2body, svgelement],…]

for (var i = 0; i < links.length; i++) {
    var t = links[i][0].GetTransform();

    //tiny helper function just taking the values and
    //setting it to the element
    // transform(element, a,b,c,d,e,f)
    //|a c e|
    //|b d f|
    svghelper.transform(links[i][1],
      t.q.c, t.q.s, -t.q.s,
      t.q.c, t.p.x, t.p.y);
}

The main difference is, that the elements used for the debug draw are generated on the fly, without any transformations, using the world coordinates of the vertices of the b2dShapes.

But the Graphics for the scene are taken from an SVG graphic created with inkscape and, f.i. using groups for an arm or the head of the charackter.

How can I apply the transform of the body right on the elements? I guess I need to change the basis of the transform matrix, but I somehow can't make it work.

I have tried this:

var t = body.GetTransform(),
    mtr = svg,createSVGMatrix(
            t.q.c, t.q.s, -t.q.s,
            t.q.c, t.p.x, t.p.y),
    toElement = element.getTransformToElement(element.ownerSVGDocument),
    toElement_inv = toElement.invert();

    mtr = mtr.multiply(toElement);
    mtr = toElement_inv.multiply(mtr);

 //applying the result

But that resulted in a wrong result and errors by inverting the matrix.

Thanks in ahead!

1
getTransformToElement takes an Element argument, not a Document. SVGMatrix has an inverse() method, not invert(). Have you tried simply adding the transform matrix as an attribute? E.g element.setAttribute("transform", "matrix(" + t.q.c + " " + t.q.s + " " + (-t.q.s) + " " + t.q.c + " " + t.p.c + t.p.y + " " + ")");?Erik Dahlström
Possible problem: mtr = svg,createSVGMatrix, should be mtr = svg.createSVGMatrix, is svg the id of the root svg, or elem.nearestViewPortElement?Francis Hemsher

1 Answers

0
votes

I have an example below that may be helpful, and hopefully will not add to your headache;) It addresses placing elements in different viewPorts

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Find X,Y in Transforms &amp; ViewPorts</title>
<meta http-equiv="content-type" content="text/html;charset=UTF-8">
</head>
<body style='padding:10px;font-family:arial'>
<center>
<h4>Find X,Y in Transforms &amp; ViewPorts</h4>
<div style='width:90%;background-color:gainsboro;text-align:justify;padding:10px;border-radius:6px;'>
Elements are transformed and reside it different viewPorts. This example uses  <b>getScreenCTM</b> and <b>inverse</b> to access x,y values.
</div>
<table>
<tr><td align=left>
<b>Click on an element target.<br />
This will place the black circle<br />so it matches the target's transformations</b><br />
 Scenerio:<br />
1.) SVG image is inline, contained in a DIV.<br />
2.) The blue rect element is contained in a &lt;g&gt;.<br />
3.) The &lt;g&gt; element has been transformed.<br />
4.) The maroon rect resides in a different viewPort.<br />
5.) The orange circle has been transformed.<br />
6.) Move cursor over elements to get their x,y values<br />
6.) Click over element place black circle in its viewport or target's transform.<br />
</td>
<td align=left>
<div id="svgDiv" style='background-color:lightgreen;width:400px;height:400px;'>
<svg id="mySVG" width="400" height="400" onmousemove="svgCursor(evt)"  onclick="placeBlackCircle(evt)" >
<circle pointer-events="none" id="blackCircle" r="10" fill="black" />
<circle onmousemove=getXY(evt)  onmouseout=clearXY() id="redCircle" cx="120" cy="180" r="40" fill="red" stroke="black" stroke-width="2" />
<circle onmousemove=getXY(evt) onmouseout=clearXY()  id="orangeCircle" cx="200" cy="200" r="40" fill="orange" stroke="black" stroke-width="2" />
<svg viewBox="0 100 800 800">
<rect onmousemove=getXY(evt) onmouseout=clearXY()  id="maroonRect"  x="220" y="250" width="60" height="60" fill="maroon" stroke="black" stroke-width="2"  />
</svg>
<g id="myG" >
<rect onmousemove=getXY(evt)  onmouseout=clearXY() id="blueRect"  x="220" y="250" width="60" height="60" fill="blue" stroke="black" stroke-width="2"  />
</g>
</svg>
</div>
</td>
<td align=left>
<table style='font-family:lucida console'>
<tr><td colspan=4><b>HTML Page Values:</b></td></tr>
<tr style='font-size:110%'>
<td align=right>mouse X:</td><td><input style='font-size:120%' type=text id=htmlMouseXValue size=1 /></td>
<td><input style='font-size:120%'  type=text id=htmlMouseYValue size=1 /></td><td align=left>:mouse Y</td>
</tr>
<tr><td colspan=4><b>SVG Image Values:</b></td></tr>
<tr style='font-size:110%'>
<td align=right>svg X:</td> <td><input style='font-size:120%'  type=text id=svgXValue size=1 /></td>
<td><input style='font-size:120%'  type=text id=svgYValue size=1 /></td><td align=left>:svg Y</td>
</tr>
<tr><td colspan=4><b>Target:<input id=elemIdValue size=10 /></b></td></tr>
<tr style='font-size:110%'>
<td align=right>client X:</td> <td><input style='font-size:120%'  type=text id=clientXValue size=1 /></td>
<td><input style='font-size:120%'  type=text id=clientYValue size=1 /></td><td align=left>:client Y</td>
</tr>
<tr style='font-size:110%'>
<td align=right>screen X:</td>   <td><input style='font-size:120%'  type=text id=screenXValue size=1 /></td>
<td><input style='font-size:120%'  type=text id=screenYValue size=1 /></td><td align=left>:screen Y</td>
</tr>
</table>
</td>
</tr></table>
<br />SVG Source:<br />
<textarea id=svgSourceValue style='font-size:110%;font-family:lucida console;width:90%;height:200px'></textarea>
<br />Javascript:<br />
<textarea id=jsValue style='border-radius:26px;font-size:110%;font-weight:bold;color:midnightblue;padding:16px;background-color:beige;border-width:0px;font-size:100%;font-family:lucida console;width:90%;height:400px'></textarea>
</center>
<script id=myScript>
//---mouse move---
function getXY(evt)
{
    var target=evt.target
    elemIdValue.value=target.id

    var pnt = target.ownerSVGElement.createSVGPoint();
    pnt.x = evt.clientX;
    pnt.y = evt.clientY;
    clientXValue.value=pnt.x
    clientYValue.value=pnt.y

    //---element's x,y  screen transformed/inversevalues---
    var sCTM = target.getScreenCTM();
    var PNT = pnt.matrixTransform(sCTM.inverse());

    screenXValue.value=PNT.x
    screenYValue.value=PNT.y
}
function placeBlackCircle(evt)
{
    var target=evt.target;
    //---initialize a point in its respective viewport--
    if(target.nearestViewportElement) //--must click on an element not svg root--
    {
        var pnt = target.nearestViewportElement.createSVGPoint();
        //---client area click point---
        pnt.x = evt.clientX;
        pnt.y = evt.clientY;

        var sCTM = target.getScreenCTM();
        //---return viewport's Pnt.x, Pnt.y---
        PNT = pnt.matrixTransform(sCTM.inverse());
        //----place blackDot on top in viewport and locate it---
        target.nearestViewportElement.appendChild(blackCircle)
        blackCircle.setAttribute("cx",PNT.x)
        blackCircle.setAttribute("cy",PNT.y)

        if(target.parentNode.nodeName=="g")
        {
            target.parentNode.appendChild(blackCircle)
        }
        if(target.getAttribute("transform"))
        {
            var transform=target.getAttribute("transform")
            blackCircle.setAttribute("transform",transform)
        }
        else
            blackCircle.removeAttribute("transform")
    }
}

function clearXY()
{
    elemIdValue.value=""
    clientXValue.value=""
    clientYValue.value=""

    screenXValue.value=""
    screenYValue.value=""
}

//---onload---
function initTransforms()
{
//---place some transforms on the elements---

    //--- transform orange circle---
    var transformRequestObj=mySVG.createSVGTransform()
    var animTransformList=orangeCircle.transform
    var transformList=animTransformList.baseVal
    //---translate---
    transformRequestObj.setTranslate(180,-260)
    transformList.appendItem(transformRequestObj)
    transformList.consolidate()
    //----scale---
    transformRequestObj.setScale(.5,.9)
    transformList.appendItem(transformRequestObj)
    transformList.consolidate()
    //----skewY---
    transformRequestObj.setSkewY(52)
    transformList.appendItem(transformRequestObj)
    transformList.consolidate()

    //--init Transform on myG---
    var transformRequestObj=mySVG.createSVGTransform()
    var animTransformList=myG.transform
    var transformList=animTransformList.baseVal
    //---translate---
    transformRequestObj.setTranslate(-50,-80)
    transformList.appendItem(transformRequestObj)
    transformList.consolidate()
    //----skewX---
    transformRequestObj.setSkewX(15)
    transformList.appendItem(transformRequestObj)
    transformList.consolidate()
    //----skewY---
    transformRequestObj.setSkewY(20)
    transformList.appendItem(transformRequestObj)
    transformList.consolidate()
    //---rotate---
    transformRequestObj.setRotate(30,200,200)
    transformList.appendItem(transformRequestObj)
    transformList.consolidate()
}

document.onmousemove = htmCursor
//---'event' is the html event object---
function htmCursor(event)
{
    var event = event || window.event;
    myMouseX=event.clientX;
    myMouseY=event.clientY;
    myMouseX = myMouseX + document.documentElement.scrollLeft;
    myMouseY = myMouseY + document.documentElement.scrollTop;

    htmlMouseXValue.value=myMouseX
    htmlMouseYValue.value=myMouseY
}
//---'evt' is the svg event object--
function svgCursor(evt)
{
    var rect = svgDiv.getBoundingClientRect();
    svgXValue.value=evt.clientX-rect.left
    svgYValue.value=evt.clientY-rect.top
}
</script>
<script>
document.addEventListener("onload",init(),false)
function init()
{
    initTransforms()
    svgSourceValue.value=svgDiv.innerHTML
    jsValue.value=myScript.text
}
</script>

</body>

</html>