Quick Links: Download Gideros Studio | Gideros Documentation | Gideros Development Center | Gideros community chat | DONATE
GiderosCodingEasy - I don't understand Matrix:multiply() — Gideros Forum

GiderosCodingEasy - I don't understand Matrix:multiply()

bowerandybowerandy Guru
edited March 2013 in General questions
Hi @ar2rsawseen,

I've been trying to implement scaling and rotation about arbitrary points using matrices and I've been using the matrix maths that you so kindly included in GiderosCodingEasy. However, I don't understand your implementation of Matrix:multiply(). The current library has this:
function Matrix:multiply(matrix)
	local m11 = matrix:getM11()*self:getM11() + matrix:getM12()*self:getM21()
	local m12 = matrix:getM11()*self:getM12() + matrix:getM12()*self:getM22()
	local m21 = matrix:getM21()*self:getM11() + matrix:getM22()*self:getM21()
	local m22 = matrix:getM21()*self:getM12() + matrix:getM22()*self:getM22()
 
***	local tx = self:getTx() + matrix:getTx()
***	local ty = self:getTy() + matrix:getTy()
 
	return self:setElements(m11, m12, m21, m22, tx, ty)
end
and I think it should be:
function Matrix:multiply(matrix)
	local m11 = matrix:getM11()*self:getM11() + matrix:getM12()*self:getM21()
	local m12 = matrix:getM11()*self:getM12() + matrix:getM12()*self:getM22()
	local m21 = matrix:getM21()*self:getM11() + matrix:getM22()*self:getM21()
	local m22 = matrix:getM21()*self:getM12() + matrix:getM22()*self:getM22()
 
***	local tx  = matrix:getM11()*self:getTx()  + matrix:getM12()*self:getTy() + matrix:getTx()
***	local ty  = matrix:getM21()*self:getTx()  + matrix:getM22()*self:getTy() + matrix:getTy()
 
	return self:setElements(m11, m12, m21, m22, tx, ty)
end
Am I just missing something obvious?

best regards

Likes: Niicodemus

+1 -1 (+1 / -0 )Share on Facebook
«1

Comments

  • ar2rsawseenar2rsawseen Maintainer
    Oh, yes you are most probably right, I must have miscalculated those values.
    Let me fix that
  • @ar2rsawseen, what your version of multiply() appears to do is to allow scale() to act about the translated origin of the object in question. This is not normally what a matrix scale should do. AFAIK, the correct way to scale in this way would be to shift the object back to 0,0 - do the scale - and then shift the object back to its original position again:
    local tx, ty=matrix:getTx(), matrix:getTy()
    matrix=matrix:translate(-tx, -ty)
    matrix=matrix:scale(SCALEX, SCALEY)
    matrix=matrix:translate(tx, ty)
    So what I'm saying is that, I think my version is correct, but it may break some existing uses of Matrix:scale() that are out there already. The above code shows how to fix any breakages.

    best regards

    Likes: ar2rsawseen

    +1 -1 (+1 / -0 )Share on Facebook
  • ar2rsawseenar2rsawseen Maintainer
    edited March 2013
    @bowerandy exactly, now I remembered the purpose of that.
    But you are completely right, it should behave as expected and thus I've updated GiderosCodingEasy

    Additionally I've separated physics extension as Box2Easy.lua as you've suggested before ;)

    I might try separate components by logic in similar way, so user could include and require inside init only modules he or she feels are needed

    Likes: hgvyas123

    +1 -1 (+1 / -0 )Share on Facebook
  • NiicodemusNiicodemus Member
    edited April 2013
    I've been doing a lot of matrix transformation stuff lately and fixed this problem and maybe a few more. I found this thread so I thought I'd share. I've been trying to get translations to work as documented in the SVG spec as well as the HTML Canvas reference.

    First, multiply(). I came up with the exact same fix as @bowerandy for fixing the tx and ty fields. However, I also flipped the order of the multiplication. Matrix multiplication is not communicative, so A*B != B*A. With the way your function is currently written, if you do A:multiply(B) then it results in B*A, which is backwards to me. This is probably a preference thing, but in my mind, I always assumed that the given matrix (self) was being multiplied by the passed in variable (matrix). So that A:multiply(B) results in A*B. So here is my modified multiply().
    function Matrix:multiply(matrix)
    	local m11 = self:getM11()*matrix:getM11() + self:getM12()*matrix:getM21()
    	local m12 = self:getM11()*matrix:getM12() + self:getM12()*matrix:getM22()
    	local m21 = self:getM21()*matrix:getM11() + self:getM22()*matrix:getM21()
    	local m22 = self:getM21()*matrix:getM12() + self:getM22()*matrix:getM22()
    	local tx  = self:getM11()*matrix:getTx()  + self:getM12()*matrix:getTy() + self:getTx()
    	local ty  = self:getM21()*matrix:getTx()  + self:getM22()*matrix:getTy() + self:getTy()
    	return self:setElements(m11, m12, m21, m22, tx, ty)
    end
    Second, rotate. Currently the rotate() function rotates objects counter-clockwise, which is opposite of the built-in Sprite:setRotation(). Again, possibly preference, but I wasn't expecting that. Moving the unary '-' operator to the second element (M12) instead of the third (M21) fixes this.
    function Matrix:rotate(deg)
    	local rad = math.rad(deg)
    	return self:multiply(Matrix.new(math.cos(rad), -math.sin(rad), math.sin(rad), math.cos(rad), 0, 0))
    end
    Lastly, skew. It appears that skewX and skewY are reversed. Again, swapping M12 and M22 fix this back to normal. Now for this, neither SVG or HTML Canvas have a "skew" function, but CSS does, so I think this skew should do the same.
    function Matrix:skew(xAng,yAng)
    	xAng = math.rad(xAng)
    	yAng = math.rad(yAng or xAng)
    	return self:multiply(Matrix.new(1, math.tan(xAng), math.tan(yAng), 1, 0, 0))
    end
    Now with this all fixed up, it matches what seem to be the standards. Plus, I can now multiply (concatenate) multiple matrices and end up with what I expect. For example, to rotate a sprite around a given point, translate(x, y) * rotate(deg) * translate(-x, -y).
    local box = Shape.new()
    box:setLineStyle(2, 0x000000, 1)
    box:beginPath()
    box:moveTo(0,0)
    box:lineTo(100,0)
    box:lineTo(100,100)
    box:lineTo(0,100)
    box:closePath()
    box:endPath()
    stage:addChild(box)
     
    local matrix = box:getMatrix()
    matrix:translate(50,50)
    matrix:rotate(25)
    matrix:translate(-50,-50)
    box:setMatrix(matrix)
    That will also work for scale, skew or any combination of them. You can string together all combinations of things and they always get applied in order. So this would be another way to implement arbitrary "anchorpoints" for any sprite.

    I've posted my version and an example project at https://github.com/nshafer/Matrix

    Thanks,
    Nathan Shafer
  • ar2rsawseenar2rsawseen Maintainer
    edited April 2013
    @Niicodemus awesome, thank. This is probably the standard that should be used, I agree. ;)

    My original Matrix class was created for easier porting of a game from another JavaScript framework I had before, thus there were difference, sorry if it confused anyone :)
  • No worries at all. I've had a blast learning a lot about matrix transformations and matrix math in the last couple days. =)
  • bowerandybowerandy Guru
    edited April 2013
    @Niicodemus, this is uncanny. I was just about to post a request for help on this very subject. Since you've been looking at this maybe you (or someone else) can help. I am trying to implement a function:

    function Sprite:setScaleXYAboutAnchor(scaleX, scaleY, ax, ay)
    ..
    end

    which will scale about an arbitrary anchor point. I've tried the translate(x, y) * scale(sx, sy) * translate(-x, -y) idea but I can't get it to work. Note that the sx, sy are absolute scale values and not relative to the current scale.

    Any help greatly appreciated.

    @ar2rsawsenn, will you update GiderosCodingEasy with the suggested changes. I'd just like to know which version to rely on.

    best regards
  • @ar2rsawseen: Cool, glad I could help! =)

    @bowerandy: This should be as simple as this:
    function Sprite:setScaleXYAboutAnchor(scaleX, scaleY, ax, ay)
    	local matrix = self:getMatrix()
    	matrix:translate(ax, ay)
    	matrix:scale(scaleX, scaleY)
    	matrix:translate(-ax, -ay)
    	self:setMatrix(matrix)
    end
    Give that a try.

    Thanks,
    Nathan Shafer
  • @Niicodemus, that is sort of what I've tried already (although without your latest changes). Doesn't this multiply the current scale by scaleX and scaleY rather than setting it to an absolute value?

    best regards
  • Ah, sorry I missed that part in your question. I only know how to apply the transforms additively. I think the math would be horribly complicated to try to pull the scale out of the 4 variables in the matrix that are responsible for rotation, scaling and skewing just by themselves. What you should do is just keep track of the absolute transformations you want to make, then apply them all from scratch to an identity (default) matrix. Give me a bit, I'm going to create a new Sprite class that should add anchor support using matrix transforms.
  • bowerandybowerandy Guru
    edited April 2013
    @Niicodemus, I think I have it now. One can set the absolute scale by addressing the M11 and M22 matrix elements directly. Here is my resultant method:
    function Sprite:setScaleXYAboutAnchor(scaleX, scaleY, ax, ay)
    	local x, y, w, h=self:getBounds(self)
    	local px=x+ax*w
    	local py=y+ay*h
     
    	local matrix=self:getMatrix()
    	matrix=matrix:translate(px, py)
    	if scaleX then
    		matrix:setM11(scaleX)
    	end
    	if scaleY then
    		matrix:setM22(scaleY)
    	end
    	matrix=matrix:translate(-px, -py)
    	self:setMatrix(matrix)
    end
    This seems to work as I expect. Now if we add a couple of new properties to Sprite, say scaleAboutCenterX and scaleAboutCenterY, we can get GTween scaling to be useful for all objects and not just Bitmaps.
    Sprite._bhSpriteSet=Sprite.set
     
    function Sprite:set(param, value)
    	if param=="scaleAboutCenterX" then
    		self:setScaleXYAboutAnchor(value, nil, 0.5, 0.5)
    	elseif param=="scaleAboutCenterY" then
    		self:setScaleXYAboutAnchor(nil, value, 0.5, 0.5)
    	else
    		-- Call previous implementation to set standard parameters
    		self:_bhSpriteSet(param, value)
    	end
    	return self
    end
     
    Sprite._bhSpriteGet=Sprite.get
     
    function Sprite:get(param, value)
    	if param=="scaleAboutCenterX" then
    		return self:getScaleX()
    	end
    	if param=="scaleAboutCenterY" then
    		return self:getScaleY()
    	end
    	-- Again, call the previous implementation to get standard parameters
    	return Sprite._bhSpriteGet(self, param, value)
    end
    As an example one can use a pulse() method to "throb" an object about its centre:
    function Sprite:pulse(period, factor, optCount, optCompletionFunc)
    	local trueXFactor=factor/self:getScaleX()
    	local trueYFactor=factor/self:getScaleY()
    	self._pulseTween=GTween.new(self, period/2, {scaleAboutCenterX=trueXFactor, scaleAboutCenterY=trueYFactor}, {repeatCount=optCount or 0, reflect=true, onComplete=optCompletionFunc})
    end
     
    function Sprite:cancelPulse()
    	if self._pulseTween then
    		self._pulseTween:toEnd()
    		self._pulseTween:setPaused(true)
    		self._pulseTween=nil
    	end
    end
    best regards
  • I think by just overwriting M11 and M22 you can reset the scale, but if there are any other transformations applied, such as rotation or skew, you're going to get weird and unpredictable results. I just checked in my version of adding anchors to sprites. It's not extensively tested, but it seems to work. You can see how it's basically allowing you to set absolute values for rotation, scale and position, and behind the scenes it applies all the transforms in the proper order. Good idea on using tween to see how it actually works. I added that in. Once the animation completes, you can use arrow keys and 'x' and 'y' on your keyboard to interactively play with the variables.

    https://github.com/nshafer/AnchorSprite

    Thanks,
    Nathan Shafer
  • NiicodemusNiicodemus Member
    edited April 2013
    BTW, I just checked, and if you just call Sprite:setRotation(1) then Gideros will reset the scale back to default, and leave rotation and skew alone. My guess is that internally it's tracking these things, and only concatenating the matrices when it renders to screen or when you call getMatrix(). In other words, it should be possible to just merge my changes to the Sprite class into the Gideros base code to add anchor support for all sprites. I know in the past atlim has said he wasn't sure how to deal with children in the scene graph after that happens, but I just checked and my code properly applies to children no problem. It seems to run fine, but I'm sure if it was moved into C++ instead of Lua it would present no performance penalties of any kind.

    Honestly, it's a testament to the solidity of Gideros that this stuff just works, and works well. And the ability to extend and modify this stuff in Lua is why I'm loving Gideros.

    Thanks,
    Nathan Shafer
  • bowerandybowerandy Guru
    edited April 2013
    @Niicodemus, yes, you're right about that M11 and M22 interfering with the rotation. I'll have to think of a different way to do it.

    Unfortunately, something in your new matrix stuff has broken some of my existing code to do with SVG loading. I'm using a modified version of Gideros Illustrator, which is an add-on by @Jack888. Basically, I am getting the SVG transform and applying it to a text field but now the y origin of the field is at the top left of the text rather than the baseline. I'm not sure what the problem is but it may, or may not, be related to this:
    local text1=TextField.new(TTFont.new("Tahoma.ttf", 64), "This is some text")
    stage:addChild(text1)
    text1:setPosition(0, 480)
     
    local text2=TextField.new(TTFont.new("Tahoma.ttf", 64), "This is some more text")
    text1:setTextColor(0x00ff00)
    stage:addChild(text2)
    text2:setAnchor(0.5, 0.5)
    text2:setPosition(0, 480)
    You should see that the anchor point doesn't appear to have any effect on TextFields.

    BTW, if you put my tag (@bowerandy) in any reply I should get an e-mail to say you've replied.
  • bowerandybowerandy Guru
    edited April 2013
    @Niicodemus, @ar2rsawseen, okay the issue I had with my SVG loading was not caused by the new Matrix code. It is something in the updated GiderosCodingEasy, instead. I think it is probably to do with the removal of some "baseline fix" code. @ar2rsawseen, why did you make this change? It seems to break TextField origins. I have had to revert to my older GiderosCodingEasy.lua and manually import the new matrix fixes.

    @Niicodemus, the problem with setAnchor() not working for TextFields still remains, I think.

    best regards

  • ar2rsawseenar2rsawseen Maintainer
    edited April 2013
    @bowerandy oh yes I remember that.
    It was done for many reasons, first because of the improper anchor points for textfield because of baseline fix.
    Second @moopf stated that it was difficult to align the texts without proper baseline.

    Additionally when implementing setting shadows for TextField, I've had to wrap them in Sprite element, which also fixed the baseline problem in most cases (at least it seemed to fix it for anchor points), as it wrapped the TextField bounds.

    And even more additionally there is (or there was supposed to be) kerning option, which would allow to fix the baseline by user themselves if they wanted it too.
  • @ar2rsawseen, okay, but can you tell me where the TextField origin is meant to be now?
    It appears to be "somewhere near" top left, is that right? Given a particular text field and font, how can I now calculate where the old origin used to be?

    Previously, the matrix transforms that Gideros uses were directly compatible with those in an SVG file. Now, for text fields, they are not because of this origin issue.

    Basically, I have a matrix (loaded from SVG) containing a translation to the old origin. I now need to modify this matrix so that it accounts for the changes in GiderosCodingEasy. Currently it appears to set the text offset down by about the M height and offset left by a few pixels. I am not using anchor points.

    best regards
  • @ar2rsawseen, I've just noticed that you've completely overridden the TextField class with a Sprite derivative. I imagine this is where my problem lies. Are you sure you want to do this because it seems like a bad idea to me. If it is just to get shadow support, wouldn't it be better to create a separate class called ShadowedText.

    best regards
  • ar2rsawseenar2rsawseen Maintainer
    @bowerandy most probably you're right. There was a discussion about it somewhere on the forum, and that was voted/decided to be the best solution, obviously no one thought of base line problems.
    The solution could be to override not with Sprite but empty TextField maybe?
    Unfortunately don't have much time to think about it now, I'll try to get back to it ;)
  • @ar2rsawseen, okay, I'll stick with my old version for now.

    best regards
  • NiicodemusNiicodemus Member
    edited April 2013
    @bowerandy, as far as I can tell, it's working correctly. TextFields are very strange since they are positioned based on the font metrics shown here: https://docs.google.com/document/pub?id=149g_P1YlE4_6v_hHbFx3YAaYpdU0HoM70XJSLTE38ww#h.7db690aem08u

    I updated my text project and you can see that the anchor is getting applied to the teal text. https://github.com/nshafer/AnchorSprite.

    Thanks,
    Nathan Shafer
  • @Niicodemus, Ah, I see the problem. Your setAnchor() function is only for setting the origin for scaling and rotation and doesn't affect the origin for translation. Is that the way you intended it?

    best regards
  • Hmm, yeah I'll have to look into that.
  • @bowerandy, I have updated AnchorSprite to also apply the anchor to position, so now it completely matches how Bitmap works. This actually removed a matrix multiply(), so it's got even less to calculate now.

    @atilim, would it be possible to integrate this into the base of Gideros so that these calculations don't have to happen in Lua? That would decrease overhead and also make it so that MovieClip worked with this.

    Thanks,
    Nathan Shafer
  • @Niicodemus, great!

    While waiting for @atilim to consider adding this to the base, one thing you could do is to only install the extra methods on demand by using a method called like enableAnchors() to install them on a sprite by sprite basis. This would avoid the performance hit when it's not needed.

    best regards
  • @bowerandy, GREAT idea. I've modified it now so that Sprites with anchors of 0,0 are completely untouched, and won't use this code at all. They will continue to use the built-in setPosition, setRotation, etc. They will have the setAnchorPoint function, and if that's called with anything other than 0,0, then it will start to intercept calls to setPosition, setRotation and what not. So this code can be thrown into any project without fear of causing problems. Only sprites with anchor points other than 0,0 will incur any overhead.

    Thanks,
    Nathan Shafer
  • bowerandybowerandy Guru
    edited April 2013
    @Niicodemus, that's a good idea. I can't look at it now because I'm on a bit of a deadline but earlier in the week I tried your Matrix and Sprite files and they seemed to be incompatible with GiderosCodingEasy. Any chance you could get them to play nicely together? I don't think you should change your stuff to rely on GCE (it is a bit of a beast) but if it didn't actually break it that would be good.

    best regards
  • ar2rsawseenar2rsawseen Maintainer
    @Niicodemus stuff seems to be great, let me see if we can combine it with GCE
  • @bowerandy, I didn't set out to replace this part of GCE, rather it was just an exercise in how to create alternate anchor support for Sprites using pure matrices, and specifically to answer your question about how to concatenate muliple transforms.

    That said, I just changed a couple internal variable and function names so that it will get along nicer with GCE. Now, whichever one gets executed last will determine which one is overwriting the Sprite class. Both create the function Sprite:setAnchorPoint(). If you want to use AnchorSprite's implementation, then make AnchorSprite.lua depend on GiderosCodingEasy.lua, which will make Gideros call AnchorSprite after GCE, and thus AnchorSprite will overwrite GCE's Sprite:setAnchorPoint(). However, that also means that you'll lose any other part of GCE's functionality when it comes to other things it does to Sprite. Mainly because AnchorSprite's Sprite:set() function will overwrite GCE's version. And there's a LOT of stuff that GCE is doing. So honestly, if you're using GCE, you probably want to keep doing that. If you just want anchor support that only applies to sprites you want it to, and don't want any of the other things that GCE provides, then AnchorSprite will probably fit the bill a little better. I wouldn't suggest mixing the two, unless you ripped out the Anchor support from GCE first.

    I myself have not included GCE in my projects, but rather I just cut out pieces that I want, and only after I understand exactly what they're doing. It's also been invaluable for learning Gideros. So, that said, @ar2rsawseen, I'm not sure AnchorSprite should be rolled into GCE or not, but you're more than welcome to.

    Thanks,
    Nathan Shafer
Sign In or Register to comment.