Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Matrix transformations aren't interpreted correctly #449

Closed
Bunabun opened this issue Feb 18, 2020 · 22 comments
Closed

Matrix transformations aren't interpreted correctly #449

Bunabun opened this issue Feb 18, 2020 · 22 comments
Assignees
Labels

Comments

@Bunabun
Copy link

Bunabun commented Feb 18, 2020

When I try to interpret an svg with transformation="matrix(... it renders very strangely then how it should render. I've tried using the dev branch with no luck. It appears as though the math for rotating is off. The shape should be rotated by about 45 degrees but it's rotated by nearly 0. It's also offset by quite a bit. I've verified that chrome renders the shape correctly and it's when it's interpreted that things go wrong. This occurs in both svg and canvas mode.
Here is the input

<g style="opacity:1; fill:none; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;">
    <path d="M 22,10 C 32.5,11 38.5,18 38,39 L 15,39 C 15,30 25,32.5 23,18" style="fill:#000000; stroke:#000000;" />
    <path d="M 24,18 C 24.38,20.91 18.45,25.37 16,27 C 13,29 13.18,31.34 11,31 C 9.958,30.06 12.41,27.96 11,28 C 10,28 11.19,29.23 10,30 C 9,30 5.997,31 6,26 C 6,24 12,14 12,14 C 12,14 13.89,12.1 14,10.5 C 13.27,9.506 13.5,8.5 13.5,7.5 C 14.5,6.5 16.5,10 16.5,10 L 18.5,10 C 18.5,10 19.28,8.008 21,7 C 22,7 22,10 22,10" style="fill:#000000; stroke:#000000;" />
    <path d="M 9.5 25.5 A 0.5 0.5 0 1 1 8.5,25.5 A 0.5 0.5 0 1 1 9.5 25.5 z" style="fill:#ffffff; stroke:#ffffff;" />
    <path d="M 15 15.5 A 0.5 1.5 0 1 1 14,15.5 A 0.5 1.5 0 1 1 15 15.5 z" transform="matrix(0.866,0.5,-0.5,0.866,9.693,-5.173)" style="fill:#ffffff; stroke:#ffffff;" />
    <path d="M 24.55,10.4 L 24.1,11.85 L 24.6,12 C 27.75,13 30.25,14.49 32.5,18.75 C 34.75,23.01 35.75,29.06 35.25,39 L 35.2,39.5 L 37.45,39.5 L 37.5,39 C 38,28.94 36.62,22.15 34.25,17.66 C 31.88,13.17 28.46,11.02 25.06,10.5 L 24.55,10.4 z" style="fill:#ffffff; stroke:none;" />
</g>

And here is the output svg after being interpreted

<g id="two-184" transform="matrix(1.977 0 0 1.977 142.5 53.5)" opacity="1">
    <g id="two-185" transform="matrix(1 0 0 1 -22.015 -23)" opacity="1">
        <path transform="matrix(1 0 0 1 26.514 24.5)" d="M -4.515 -14.5 C 5.985 -13.5 11.985 -6.5 11.485 14.5 L -11.515 14.5 C -11.515 5.5 -1.515 8 -3.515 -6.5 " fill="#ffffff" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-opacity="1" fill-opacity="1" visibility="visible" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" id="two-186"></path>
        <path transform="matrix(1 0 0 1 15.008 19.016)" d="M 8.991 -1.017 C 9.371 1.893 3.441 6.353 0.991 7.983 C -2.009 9.983 -1.829 12.323 -4.009 11.983 C -5.051 11.043 -2.599 8.943 -4.009 8.983 C -5.009 8.983 -3.819 10.213 -5.009 10.983 C -6.009 10.983 -9.012 11.983 -9.009 6.983 C -9.009 4.983 -3.009 -5.017 -3.009 -5.017 C -3.009 -5.017 -1.119 -6.917 -1.009 -8.517 C -1.739 -9.511 -1.509 -10.517 -1.509 -11.517 C -0.509 -12.517 1.491 -9.017 1.491 -9.017 L 3.491 -9.017 C 3.491 -9.017 4.271 -11.009 5.991 -12.017 C 6.991 -12.017 6.991 -9.017 6.991 -9.017 " fill="#ffffff" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-opacity="1" fill-opacity="1" visibility="visible" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" id="two-187"></path>
        <path transform="matrix(1 0 0 1 9 25.5)" d="M 0.5 0 A 0.5 0.5 0 1 1 -0.5 0 A 0.5 0.5 0 1 1 0.5 0 Z " fill="#000000" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-opacity="1" fill-opacity="1" visibility="visible" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" id="two-188"></path>
        <path transform="matrix(0.865 -0.008 0.007 0.865 24.193 10.326)" d="M 0.5 0 A 0.5 1.5 0 1 1 -0.5 0 A 0.5 1.5 0 1 1 0.5 0 Z " fill="#000000" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-opacity="1" fill-opacity="1" visibility="visible" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="4" id="two-189"></path>
    </g>
</g>
@jonobr1
Copy link
Owner

jonobr1 commented Feb 18, 2020

Thanks for posting your question. There is a rendering bug here.., but that's minor compared to what you're currently experiencing. If you wrap your <g /> tags in an <svg /> it should work. Like so:

<svg>
  <g>
  <!-- ... Your SVG Code -->
  </g>
</svg>

The problem I'm currently seeing on dev is that there is a transform interpretation issue with the 4th element (a small circular reflection on the horses face) that is not applied correctly. I'll have to sit down sometime soon in order to debug this.

Edit February 18, 2020: This is actually working on the latest dev.

Hope this helps

@Bunabun
Copy link
Author

Bunabun commented Feb 20, 2020

That is my mistake, I forgot to include the svg tags when I copied everything. They were already wrapped in svg tags. Here is the source for it https://upload.wikimedia.org/wikipedia/commons/7/70/Chess_nlt45.svg

@jonobr1
Copy link
Owner

jonobr1 commented Feb 20, 2020

Then which version are you using? Because it interprets fine for me.

@Bunabun
Copy link
Author

Bunabun commented Feb 20, 2020

That's very odd because I can verify that I'm running v0.8.0-dev.

@jonobr1
Copy link
Owner

jonobr1 commented Feb 20, 2020

Okay, I think I'm seeing the issue now. It's with how the horse's eye is rendered, right? Like so:

Screen Shot 2020-02-20 at 12 13 57 PM

@jonobr1 jonobr1 added bug and removed question labels Feb 20, 2020
@Bunabun
Copy link
Author

Bunabun commented Feb 21, 2020

Oh that's very strange because here is what I get. And this is with no other scripts or css or elements except the one I append to and the svg.

image

@Bunabun
Copy link
Author

Bunabun commented Feb 21, 2020

If it helps at all these are the values from var transforms = Two.Utils.decomposeMatrix(m); and the one below is the value of elem.rotation = Math.PI * (transforms.rotation / 180); which is in the most recent branch. elem.rotation = -transforms.rotation; looks to be right, but the translation is still off and I'm not sure what the math would be to fix that.

{
  translateX: 9.692999839782715
  translateY: -5.172999858856201
  scaleX: 0.8659999966621399
  scaleY: 0.8659999966621399
  rotation: -0.5235987755982989
}
-0.009138522593601258

@Bunabun
Copy link
Author

Bunabun commented Feb 21, 2020

So I created this . It does a side by side comparison of what happens when the values for the matrix change.

@jonobr1
Copy link
Owner

jonobr1 commented Feb 21, 2020

Thanks for the clarification and sorry about the the discrepancy in my screenshot. This was me fiddling with the transforms. I believe there is a place further upstream where the transforms get applied again which is causing the problem. I'll get to the bottom of this and report back. Thanks again for posting!

@jonobr1
Copy link
Owner

jonobr1 commented Feb 21, 2020

Okay, I was able to identify the problem and find a solution. But, there is a trade off. You won't be able to use the translation, rotation, or scale properties on the path that draws the eye, because I'm directly modifying the underlying matrix to make it accurate. It'll take some more time to resolve the decomposeMatrix logic... I'll continue to track this though.

If you'd like to use the patch, I've put it on a new branch here: https://github.com/jonobr1/two.js/tree/449

@Bunabun
Copy link
Author

Bunabun commented Feb 22, 2020

Thank you very much, I don't need those thansforms so this branch works for me. If I have any time I'll see if I can help out. I'm not too bad with matrices. You can close this issue or keep it open for tracking the branch.

@jonobr1
Copy link
Owner

jonobr1 commented Feb 22, 2020

Sounds good, I'll leave it open to track the issue. If you have any reference material on how to pull out translation, rotation, and scale properties from a 3x3 matrix that would be super helpful!

@Bunabun
Copy link
Author

Bunabun commented Feb 24, 2020

This here doesn't seem too bad. I think the matrix is multiplied. So for a it's cos(a)*sx which works if you don't want to rotate because cos(0) = 1 so it doesn't affect scaling. Both rotation and scaling is very odd. Scaling is done from the shape's origin and not its current location. Rotation is just a skew in both the x and y direction followed by a scale down. It scales down because -1 < cos(a) < 1 so the scale value is always reduced after multiplication. In order to call functions from the lib and not use the underlying matrix I think scaling and skewing has to happen around the origin. Or perhaps you can force it by calculating the translation needed.

@jonobr1
Copy link
Owner

jonobr1 commented Feb 29, 2020

Thanks for sharing that article. It's actually the one I used to construct the methods on Two.Matrix. Unfortunately, I haven't squared away the right work around to go in the other direction... Instead of constructing the transform string, what are the translation, scale, and rotation based on a matrix string?

One major problem is that rotation in Two.js is a simple radians value. There aren't any indications to account for skew yet 😞

@Bunabun
Copy link
Author

Bunabun commented Mar 4, 2020

If there isn't any support for skew yet I think you'd need to use some math to check if the matrix string is a rotation or a skew and then return an error if it involves a skew. Otherwise I think for every scale and rotation you would need to add a translation to go from absolute to relative.. or is relative to absolute, I'm not great with css stuff 😆

@mejaz
Copy link

mejaz commented Jan 12, 2021

Thank you very much, I don't need those thansforms so this branch works for me. If I have any time I'll see if I can help out. I'm not too bad with matrices. You can close this issue or keep it open for tracking the branch.

Hi @ErenForce , I also faced the same issue where the interpret is not taking the transformations.
Where you able to install this branch provided by @jonobr1 ? Is it a patch? Please guide how you installed the patch.

Thanks.

@jonobr1
Copy link
Owner

jonobr1 commented Jan 13, 2021

@mejaz, thanks for posting. Two.js recently incremented its latest version (to v0.7.1), so first make sure you're using that version. Then you'll be able to set a Two.js specific property to have matrices copied exactly as the imported SVG. You'll be able to do that like so:

var two = new Two();
Two.AutoCalculateImportedMatrices = false;
var firstSvgElem = two.interpret(svgElement);
// You will not be able to use the `translate`, `rotate`, or `scale`
// properties on this or child elements of that SVG. Because the
// transformation has been hard copied into the matrix

// You can modify the matrix yourself like so:
// firstSvgElem.matrix.translate(x, y);
// And you can switch back to the default interpretation by
// setting the boolean back to `true` before you interpret or load an SVG.
Two.AutoCalculateImportedMatrices = true;
var secondSvgElem = two.interpret(svgElement);
secondSvgElem.translation.x += 50; // This will be respected.

Hope this clarifies things for you.

@mejaz
Copy link

mejaz commented Jan 13, 2021

Thanks @jonobr1 for your reply.

I have corrected my two.js version and added Two.AutoCalculateImportedMatrices = false; in my code.

I am using two.js in the nodejs environment.
I am converting the svgString to SVGNode using JSDOM which is working fine, like below.

const dom = new JSDOM(svgString);
let n = dom.window.document.querySelector('svg')
let node = two.interpret(n);

The interpreted SVG generated after I render has one path missing the stroke color and the transformation. (please see the below first path of both SVGs)

Any ideas where I am going wrong?
Thank you again for your time.

Source SVG:

<?xml version='1.0' encoding='UTF-8'?>
<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='40pt' height='40pt'
     viewBox='0 0 40 40' version='1.1'>
    <g id='surface1'>
        <path style='fill-rule:nonzero;fill:rgb(86.27451%,7.843137%,23.529412%);fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(86.27451%,7.843137%,23.529412%);stroke-opacity:1;stroke-miterlimit:4;' d='M -10 0 L 0 -10 L 10 0 L 0 10 Z M -10 0 ' transform='matrix(1,0,0,1,20,15)'/>
        <path style='fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;'
              d='M 30 25 L 20 35 '/>
        <path style='fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;'
              d='M 20 35 L 10 25 '/>
    </g>
</svg>

Interpreted SVG:

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="40pt" height="40pt"
     viewBox="0 0 40 40" version="1.1">
    <g id="surface46">
        <path style="fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;"
              d="M -10 0 L 0 -10 L 10 0 L 0 10 Z M -10 0 "/>
        <path style="fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;"
              d="M 5 -5 L -5 5 " transform="matrix(1,0,0,1,25,30)"/>
        <path style="fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;"
              d="M 5 5 L -5 -5 " transform="matrix(1,0,0,1,15,30)"/>
    </g>
</svg>

@jonobr1
Copy link
Owner

jonobr1 commented Jan 13, 2021

This is what I get when I run the same thing. Is this what you get? (Two.js is the one in gray outline border).

Screen Shot 2021-01-13 at 2 59 39 PM

@jonobr1 jonobr1 self-assigned this Jan 13, 2021
@mejaz
Copy link

mejaz commented Jan 13, 2021

Hi @jonobr1 ,
I am getting the interpreted svg i shared in my previous post. You got it working in nodejs environment?

@jonobr1
Copy link
Owner

jonobr1 commented Jan 13, 2021

Sorry, I didn't catch that you were developing this in node.js environment. I'll give that a try. One thing that may help is to not use the style attribute and instead construct your SVG with what's known as "presentation attributes". E.g: <path fill="rgb(255, 50, 50)" /> instead of <path style="fill: rgb(255, 50, 50);" />. You may not have that capacity in your workflow.

I'll report back when I test the node.js environment version.

@jonobr1
Copy link
Owner

jonobr1 commented Jan 16, 2021

Sorry for the delay on this. There are two problems.

The first is on your end. rgb strings are only compatible with percentages starting in SVG spec 1.2. Two.js is based on / compatible with 1.0 and 1.1. So you need to convert rgb(50%, 50%, 50%) to rgb(127, 127, 127);.

The second is on Two.js's end. In node.js environments using jsdom there isn't a way to interpret a transform matrix. I've made a new issue to track that here (link). I'm going to close out this issue because its original issue has been resolved. @mejaz if you continue to have problems feel free to file a new issue.

Hope this helps!

@jonobr1 jonobr1 closed this as completed Jan 16, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants