Changing the origin of a transform

LiveCode Builder is a language for extending LiveCode's capabilities, creating new object types as Widgets, and libraries that access lower-level APIs in OSes, applications, and DLLs.

Moderators: LCMark, LCfraser

Post Reply
trevordevore
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 1005
Joined: Sat Apr 08, 2006 3:06 pm
Contact:

Changing the origin of a transform

Post by trevordevore » Fri Apr 10, 2015 5:21 am

I'm trying to rotate an SVG path. The problem is that the center of the path is not the x,y coordinate around which the rotation should occur. This causes the SVG path to "jiggle" as it is rotated.

CSS has the ability to change the origin of the transform. Anyone have an idea how to mimic that? Or do we need a property added that looks something like this:

Code: Select all

set the origin of tTransform to [47%,50%]
The most basic code that gets the SVG path rotated and displayed (almost) correctly is this:

Code: Select all

rotate tPath by mRotation
translate tPath by [- (the left of the bounding box of tPath), - (the top of the bounding box of tPath)]
I've tried manually modifying the matrix of the transform (playing with elements 5 and 6) but there is something I'm missing as I can't get it to work.

I *think* what I would like to do is this:

Code: Select all

variable tTransform as Transform
put transform with origin [49%,49%] into tTransform
set the rotation rotation of tTransform to mRotation
If anyone wants to take a stab at it, here is the code that fills in a Path variable.

Code: Select all

	-- font awesome spinner
	put path "M184.7,47.3c0,14.7-12,26.6-26.6,26.6c-14.7,0-26.6-11.9-26.6-26.6c0-14.6,11.9-26.6,26.6-26.6" & \
			"	C172.6,20.7,184.7,32.7,184.7,47.3z M115,6.7c4.4,4.4,6.7,9.9,6.7,16.2c0,12.7-10.1,22.8-22.8,22.8S76,35.5,76,22.8S86.1,0,98.9,0" &\
			"	C105.2,0,110.6,2.3,115,6.7z M193.3,95.7c3,3,4.4,6.7,4.4,10.8c0,8.4-6.8,15.2-15.2,15.2s-15.2-6.8-15.2-15.2s6.8-15.2,15.2-15.2" & \
			"	C186.7,91.3,190.4,92.7,193.3,95.7z M53.1,33.9c3.7,3.7,5.6,8.2,5.6,13.4c0,10.5-8.6,19-19,19s-19-8.6-19-19s8.6-19,19-19" & \
			"	C44.9,28.3,49.4,30.2,53.1,33.9z M173.2,165.6c0,8.2-7,15.2-15.2,15.2c-8.4,0-15.2-6.8-15.2-15.2c0-8.4,6.8-15.2,15.2-15.2" & \
			"	C166.5,150.4,173.2,157.2,173.2,165.6z M26,95.7c3,3,4.4,6.7,4.4,10.8c0,8.4-6.8,15.2-15.2,15.2S0,114.9,0,106.5s6.8-15.2,15.2-15.2" & \
			"	C19.4,91.3,23.1,92.7,26,95.7z M109.7,179.3c3,3,4.4,6.7,4.4,10.8c0,8.4-6.8,15.2-15.2,15.2s-15.2-6.8-15.2-15.2" & \
			"	c0-8.4,6.8-15.2,15.2-15.2C103,174.9,106.7,176.3,109.7,179.3z M50.5,154.8c3,3,4.4,6.7,4.4,10.8c0,8.4-6.8,15.2-15.2,15.2" & \
			"	c-8.2,0-15.2-7-15.2-15.2c0-8.4,6.8-15.2,15.2-15.2C43.8,150.4,47.5,151.9,50.5,154.8z" into mSVGPath
Trevor DeVore
ScreenSteps - https://www.screensteps.com

LiveCode Repos - https://github.com/search?q=user%3Atrevordevore+topic:livecode
LiveCode Builder Repos - https://github.com/search?q=user%3Atrevordevore+topic:livecode-builder

PaulDaMacMan
Posts: 683
Joined: Wed Apr 24, 2013 4:53 pm
Contact:

Re: Changing the origin of a transform

Post by PaulDaMacMan » Fri Apr 10, 2015 11:00 pm

Don't know how to change the origin of the transform in LCB, but maybe the bounding box is the problem and is what is making it appear to be shifting, when the larger ball is on the left it will "shift" by the difference in size between the larger ball and the smaller ball size, so actually the bounding box size is staying the same and the art is shifting? Canvas seems to render from the top-left by default. I had a shifting problem myself with a spinner/knob control I created but I fixed the problem with Adobe Illustrator (I did my rotation/transformations manually, not with code), the bounding box somehow shifted in one of ten SVG frames I had created. I'll try to do the "manual" method on it when I get a chance. Does it shift with other paths? Maybe create a group with a circle that encompasses the bounding box? I'm not the best coder but I've worked in Graphic Design / Prepress / Print Production where things (like cross-over art) sometimes have to line up perfectly and I know you can't always trust a bounding box.
My GitHub Repos: https://github.com/PaulMcClernan/
Related YouTube Videos: PlayList

PaulDaMacMan
Posts: 683
Joined: Wed Apr 24, 2013 4:53 pm
Contact:

Re: Changing the origin of a transform

Post by PaulDaMacMan » Fri Apr 10, 2015 11:14 pm

I just checked the bounding box of the spinner char from Font Awesome 4.2.1 and it is not perfectly square, at 12pt size expanded to a path it measures 0.15 in x 0.16 in.
Screen shot 2015-04-10 at 6.15.39 PM.JPG
Screen shot 2015-04-10 at 6.15.57 PM.JPG
My GitHub Repos: https://github.com/PaulMcClernan/
Related YouTube Videos: PlayList

PaulDaMacMan
Posts: 683
Joined: Wed Apr 24, 2013 4:53 pm
Contact:

Re: Changing the origin of a transform

Post by PaulDaMacMan » Fri Apr 10, 2015 11:22 pm

Doesn't really help solve the problem though does it? :roll:
My GitHub Repos: https://github.com/PaulMcClernan/
Related YouTube Videos: PlayList

PaulDaMacMan
Posts: 683
Joined: Wed Apr 24, 2013 4:53 pm
Contact:

Re: Changing the origin of a transform

Post by PaulDaMacMan » Sat Apr 11, 2015 1:26 am

In the second picture the art has been rotated 15 degrees and no longer touches the bounding box, so I'm guessing you would have to re-check for the for a new bounding box for each frame of rotation/animation?
My GitHub Repos: https://github.com/PaulMcClernan/
Related YouTube Videos: PlayList

trevordevore
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 1005
Joined: Sat Apr 08, 2006 3:06 pm
Contact:

Re: Changing the origin of a transform

Post by trevordevore » Sat Apr 11, 2015 4:47 am

@PaulDaMacMan: Yes, the bounding box is not quite square. In addition, due to the size of some of the circles, the point in the bounding box around which the rotation would occur is not right in the middle. That is precisely the problem I am trying to address. By changing the origin of the transform that can be applied this would be pretty simple.

I came across a web site that explained how to use a matrix to rotate a shape around a specific point. I was then able to get some working code. It is very verbose so that it is clear as to what is going on. mRotateBy and mCenter are widget variables.

Code: Select all

private handler calculateRotatedPath()
		variable tX as Number
		variable tY as Number
		variable tOriginX as Number
		variable tOriginY as Number
		variable tMatrix as List
		variable tA as Number
		variable tB as Number
		variable tC as Number
		variable tD as Number
	
		-- Clockwise: sin for B, -sin for C
		-- Counterclockwise: -sin for B, sin for C
		put cos(mRotateBy) into tA -- scale the x
		put sin(mRotateBy) into tB -- scale the y
		put -sin(mRotateBy) into tC -- skew the x
		put cos(mRotateBy) into tD -- skew the y
		put the x of mCenter into tOriginX
		put the y of mCenter into tOriginY
	
		put tOriginX - tOriginX*tA - tOriginY*tC into tX
		put tOriginY - tOriginX*tB - tOriginY*tD into tY	
	
		variable tTransform as Transform
		put transform with matrix [tA,tB,tC,tD,tX,tY] into tTransform
		transform mSVGPath by tTransform
	end handler
Trevor DeVore
ScreenSteps - https://www.screensteps.com

LiveCode Repos - https://github.com/search?q=user%3Atrevordevore+topic:livecode
LiveCode Builder Repos - https://github.com/search?q=user%3Atrevordevore+topic:livecode-builder

trevordevore
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 1005
Joined: Sat Apr 08, 2006 3:06 pm
Contact:

Re: Changing the origin of a transform

Post by trevordevore » Sat Apr 11, 2015 7:35 am

Slight modification to the code. I wasn't calculating the angle properly. I also modified it so that there is a baseline path which is put into a working path which in turn has the transformation applied to it. I've added the final widget to my github repository. It allows you to select a couple of different Font Awesome icons and it animates them. You can also display your own icon.

https://github.com/trevordevore/livecod ... tating_svg

Code: Select all

private handler calculateRotatedPath(in pRotation as Number)
		variable tX as Number
		variable tY as Number
		variable tOriginX as Number
		variable tOriginY as Number
		variable tMatrix as List
		variable tA as Number
		variable tB as Number
		variable tC as Number
		variable tD as Number
	
		-- Clockwise: sin for B, -sin for C
		-- Counterclockwise: -sin for B, sin for C
		variable tAngle as Number
		put (pRotation mod 360)/360*2*pi into tAngle
		
		put cos(tAngle) into tA -- scale the x
		put sin(tAngle) into tB -- scale the y
		put -sin(tAngle) into tC -- skew the x
		put cos(tAngle) into tD -- skew the y
		put the x of mCenter into tOriginX
		put the y of mCenter into tOriginY
	
		put tOriginX - tOriginX*tA - tOriginY*tC into tX
		put tOriginY - tOriginX*tB - tOriginY*tD into tY	
	
		variable tTransform as Transform
		put transform with matrix [tA,tB,tC,tD,tX,tY] into tTransform
		
		put mSVGPath into mWorkingPath
		transform mWorkingPath by tTransform
	end handler
Trevor DeVore
ScreenSteps - https://www.screensteps.com

LiveCode Repos - https://github.com/search?q=user%3Atrevordevore+topic:livecode
LiveCode Builder Repos - https://github.com/search?q=user%3Atrevordevore+topic:livecode-builder

PaulDaMacMan
Posts: 683
Joined: Wed Apr 24, 2013 4:53 pm
Contact:

Re: Changing the origin of a transform

Post by PaulDaMacMan » Sat Apr 11, 2015 5:11 pm

Thanks for sharing!
I'm very interested in your path rotation code because of my desire to build a spinner-control (and general interest in learning lcb). Unfortunately I wasn't able to try it out because you seem to be using a newer build of LiveCode 8 and I couldn't easily back-port your code (replaced 'returns' with 'as', but then hit some variable declaration problems). I'll have to either reinstall x-code and get the latest build of LC8 or wait until DP2 comes out.

Here's a shot of what I've done so-far just by using the SVG icon widget and setting the iconPath property to 10 different paths exported from Illustrator. Since all of the paths are perfectly round (distorted here by the widget/engine), wobble/shifting wasn't a problem, but what I really wanted was to have an outward facing notch and not have to make separate paths/frames for each position and therefore be able to have an arbitrary amount of positions by dividing 360 by some number and setting a property to that number. For example 360 / 2.8125 would yield 128 positions (perfect for 7-bit MIDI music data) or a full byte, 256 positions with 360 / 1.40625 for more general purpose use.
snap.jpg
Examples
Very basic but it feels as exciting as when I built some simple XCMDs for HyperCard using FutureBasic way back in the day!
My GitHub Repos: https://github.com/PaulMcClernan/
Related YouTube Videos: PlayList

trevordevore
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 1005
Joined: Sat Apr 08, 2006 3:06 pm
Contact:

Re: Changing the origin of a transform

Post by trevordevore » Sat Apr 11, 2015 5:33 pm

Below is a modified version of the lcb file that will compile in DP-1. I tried to upload a file but the forums don't allow lcb or txt files. I am using the latest code from the "develop" branch as I want to be testing and providing feedback on the latest work the dev team is doing. One of the functions you may be interested in is the scaleAndMaintainAspectRatioTransform() handler. It will scale your paths and maintain the aspect ratio. That way you don't have to have your widget just the right size in order to have the paths display properly. Your control looks cool!

Code: Select all

widget com.livecode.extensions.trevordevore.rotatingSVG

	use com.livecode.canvas
	use com.livecode.widget
	use com.livecode.engine
	use com.livecode.math

	metadata title is "Rotating SVG"
	metadata author is "Trevor DeVore"
	metadata version is "1.0.0"

	-- public
	private variable mRotateBy          as Number
	private variable mFrequency         as Number
	private variable mSVGPath           as Path
	private variable mColor             as Color
	private variable mIconOrigin        as List
	private variable mIconSize          as Number
	private variable mIsAnimating       as Boolean
	private variable mIconStyle         as String

	-- private
	private variable mCenter            as Point
	private variable mWorkingPath       as Path
	private variable mRotation          as Number

	property rotateBy           get mRotateBy            set setRotateBy
	property currentRotation    get mRotateBy            set setRotation -- we don't keep track of current rotation. Only set it.
	property frequency          get mFrequency           set mFrequency
	property iconStyle          get mIconStyle           set setIconStyle
	property iconSVGPath        get getIconPath          set setIconPath
	property iconColor        	get getColor             set setColor
	property iconOrigin         get getIconOrigin        set setIconOrigin
	property iconSize           get mIconSize            set setIconSize
	property animating          get mIsAnimating         set setIsAnimating

	metadata iconSVGPath.editor is "com.livecode.pi.text"
	metadata iconColor.editor is "com.livecode.pi.color"
	metadata currentRotation.user_visble is "false"
	metadata iconStyle.editor is "com.livecode.pi.enum"
	metadata iconStyle.options is "circle notch,cog,pulse,spinner,refresh"

	private handler setIconSize(in pSize as Number)
		put pSize into mIconSize
		initializationCalculations() 
		redraw all
	end handler
	
	private handler setIsAnimating(in pBoolean as Boolean)
		if pBoolean is not mIsAnimating then
			put pBoolean into mIsAnimating
			
			cancel timer
		
			if mIsAnimating then
				schedule timer in mFrequency seconds -- add function to keep timer firing exactly right?
			end if
		end if
	end handler
	
	private handler setRotation(in pRotation as Number)
		initializationCalculations()
		calculateRotatedPath(pRotation)
		redraw all
	end handler
	
	private handler setRotateBy(in pRotateBy as Number)
		put pRotateBy into mRotateBy
		initializationCalculations()
		redraw all
	end handler

	private handler getIconPath() as String
		return the instructions of mSVGPath
	end handler

	private handler setIconPath(in pPath as String)
		put path pPath into mSVGPath
		put "custom" into mIconStyle
		initializationCalculations()
		redraw all
	end handler
	
	private handler getColor() as String
		return colorToString(mColor, false)
	end handler

	private handler setColor(in pColor as String)
		put stringToColor(pColor) into mColor
		redraw all
	end handler
	
	private handler getIconOrigin() as String
		return (element 1 of mIconOrigin) formatted as string & "," & (element 2 of mIconOrigin) formatted as string
	end handler

	private handler setIconOrigin(in pPoint as String)
		variable tList as List
				
		split pPoint by "," into tList
		put tList parsed as list of number into tList
		if the number of elements in tList is 2 then
			put tList into mIconOrigin
			initializationCalculations()
			redraw all
		end if	
	end handler
	
	private handler setIconStyle(in pStyle as String)
		_setIconStyle(pStyle)
		initializationCalculations()
		redraw all
	end handler
		
	private handler _setIconStyle(in pStyle as String)
		put 3 into mRotateBy
		put 0.01 into mFrequency
		put [0.50,0.50] into mIconOrigin
		put pStyle into mIconStyle
					
		if pStyle is "circle notch" then
			put path "M355.8,176.1c0,48.3-20.3,94.1-52,125.9s-77.6,52-125.9,52S83.8,333.8,52,302S0,224.4,0,176.1C0,117.4,28.6,63.7,75.1,30.8 c23.2-16.5,49-26.6,77.4-30.8v51.6C93.9,63.5,50.8,115.2,50.8,176.1c0,34.4,14.7,67.1,37.3,89.8c22.6,22.6,55.4,37.3,89.8,37.3 s67.3-14.7,90-37.3c22.6-22.6,37.1-55.4,37.1-89.8c0-61-43.1-112.6-101.7-124.5V0c57,8.1,105.6,42.9,132.4,93.7 C349.1,119.3,355.8,146.7,355.8,176.1z" into mSVGPath
		else if pStyle is "refresh" then
			put path "M181.2,15.1V68c0,4.1-3.4,7.6-7.6,7.6h-52.9c-4.1,0-7.6-3.4-7.6-7.6c0-2,0.7-3.8,2.2-5.3l16.3-16.3 c-11.7-10.7-25.4-16.2-41.2-16.2c-21.1,0-40.5,10.9-51.4,28.8c-0.8,1.3-2.9,5.9-6.3,13.8c-0.6,1.8-1.8,2.7-3.5,2.7H5.9 c-2,0-3.8-1.8-3.8-3.8v-0.8C12.4,28.8,47.3,0,90.6,0c23,0,45.5,9.1,62.4,25l15.3-15.2c1.5-1.5,3.3-2.2,5.3-2.2 C177.8,7.6,181.2,11,181.2,15.1z M178.3,109.5c0,0.4,0,0.7-0.1,0.8c-10,42.1-44.9,70.9-88,70.9c-23,0-45.2-9-62.1-25l-15.2,15.2 c-1.5,1.5-3.3,2.2-5.3,2.2c-4.1,0-7.6-3.4-7.6-7.6v-52.9c0-4.1,3.4-7.6,7.6-7.6h52.9c4.1,0,7.6,3.4,7.6,7.6c0,2-0.7,3.8-2.2,5.3 l-16.2,16.2C60.8,145.1,75.4,151,90.6,151c21.1,0,40.5-10.9,51.4-28.8c0.8-1.3,2.9-5.9,6.3-13.8c0.6-1.8,1.8-2.7,3.5-2.7h22.7 C176.5,105.7,178.3,107.5,178.3,109.5z" into mSVGPath
		else if pStyle is "cog" then
			put path "M322.8,138.5v46.7c0,3.4-2.5,6.9-5.9,7.6l-38.9,5.9c-2.7,7.6-5.5,13.9-8.2,19.1c4.8,6.9,12.4,16.6,22.5,29 c1.5,1.7,2.1,3.4,2.1,5.3c0,1.9-0.6,3.4-1.9,4.8c-7.6,10.3-34.5,37.6-40.6,37.6c-1.7,0-3.6-0.6-5.5-1.9l-29-22.7 c-6.1,3.2-12.6,5.9-19.1,8c-2.3,19.1-4.2,32.2-6.1,39.1c-1.1,4-3.6,5.9-7.6,5.9h-46.7c-4,0-7.4-2.7-7.6-6.3l-5.9-38.7 c-6.9-2.3-13.2-4.8-18.9-7.8l-29.6,22.5c-1.5,1.3-3.2,1.9-5.3,1.9c-1.9,0-3.8-0.8-5.3-2.3c-17.7-16-29.2-27.7-34.7-35.3 c-1.1-1.5-1.5-2.9-1.5-4.8c0-1.7,0.6-3.4,1.7-4.8c2.1-2.9,5.7-7.6,10.7-14.1c5-6.3,8.8-11.1,11.3-14.7c-3.8-6.9-6.7-13.9-8.6-20.8 l-38.5-5.7c-3.6-0.6-6.1-4-6.1-7.6v-46.7c0-3.4,2.5-6.9,5.7-7.6l39.1-5.9c1.9-6.5,4.6-12.8,8.2-19.3c-5.7-8-13-17.7-22.5-29 c-1.5-1.7-2.1-3.4-2.1-5c0-1.5,0.6-2.9,1.9-4.8c7.4-10.1,34.5-37.6,40.6-37.6c1.9,0,3.6,0.6,5.5,2.1l29,22.5 c6.1-3.2,12.6-5.9,19.1-8c2.3-19.1,4.2-32.2,6.1-39.1c1.1-4,3.6-5.9,7.6-5.9h46.7c4,0,7.4,2.7,7.6,6.3l5.9,38.7 c6.9,2.3,13.2,4.8,18.9,7.8L247,30.3c1.3-1.3,2.9-1.9,5-1.9c1.9,0,3.6,0.6,5.3,2.1c18.1,16.6,29.6,28.6,34.7,35.7 c1.1,1.1,1.5,2.7,1.5,4.6c0,1.7-0.6,3.4-1.7,4.8c-2.1,2.9-5.7,7.6-10.7,13.9c-5,6.5-8.8,11.3-11.3,14.9c3.6,6.9,6.5,13.9,8.6,20.6 l38.5,5.9C320.3,131.6,322.8,134.9,322.8,138.5z M199.5,199.5c10.5-10.5,15.8-23.1,15.8-38c0-29.6-24.2-53.8-53.8-53.8 s-53.8,24.2-53.8,53.8s24.2,53.8,53.8,53.8C176.3,215.2,189,210,199.5,199.5z" into mSVGPath
		else
			put path "M297.3,76.2c0,23.7-19.3,42.9-42.9,42.9c-23.7,0-42.9-19.1-42.9-42.9c0-23.5,19.1-42.9,42.9-42.9 C278,33.3,297.3,52.6,297.3,76.2z M185.2,10.7c7.1,7.1,10.7,15.9,10.7,26c0,20.5-16.3,36.7-36.7,36.7s-36.7-16.3-36.7-36.7 S138.7,0,159.2,0C169.3,0,178.1,3.6,185.2,10.7z M311.3,154c4.8,4.8,7.1,10.7,7.1,17.4c0,13.6-10.9,24.5-24.5,24.5 c-13.6,0-24.5-10.9-24.5-24.5s10.9-24.5,24.5-24.5C300.6,146.9,306.5,149.2,311.3,154z M85.5,54.5c5.9,5.9,9,13.2,9,21.6 c0,16.8-13.8,30.6-30.6,30.6S33.3,93,33.3,76.2s13.8-30.6,30.6-30.6C72.3,45.5,79.6,48.6,85.5,54.5z M279,266.7 c0,13.2-11.3,24.5-24.5,24.5c-13.6,0-24.5-10.9-24.5-24.5c0-13.6,10.9-24.5,24.5-24.5S279,253.1,279,266.7z M41.9,154 c4.8,4.8,7.1,10.7,7.1,17.4c0,13.6-10.9,24.5-24.5,24.5S0,185,0,171.4s10.9-24.5,24.5-24.5C31.2,146.9,37.1,149.2,41.9,154z  M176.6,288.7c4.8,4.8,7.1,10.7,7.1,17.4c0,13.6-10.9,24.5-24.5,24.5s-24.5-10.9-24.5-24.5s10.9-24.5,24.5-24.5 C165.9,281.6,171.8,283.9,176.6,288.7z M81.3,249.3c4.8,4.8,7.1,10.7,7.1,17.4c0,13.6-10.9,24.5-24.5,24.5 c-13.2,0-24.5-11.3-24.5-24.5c0-13.6,10.9-24.5,24.5-24.5C70.6,242.2,76.5,244.5,81.3,249.3z" into mSVGPath
			put [0.50,0.52] into mIconOrigin
			
			if pStyle is "spinner" then
				put 3 into mRotateBy
				put 0.01 into mFrequency
			else -- pulse
				put "pulse" into mIconStyle -- set style in case it was a bad type
				put 45 into mRotateBy
				put 0.125 into mFrequency
			end if
		end if
	end handler

	public handler OnCreate()
		_setIconStyle("pulse")
		put color [0/255, 0/255, 0/255] into mColor
		put [0.50,0.52] into mIconOrigin
		put path "" into mWorkingPath
		put true into mIsAnimating
		put 31 into mIconSize
		
		put 0 into mRotation
	end handler


	public handler OnSave(out rProperties as Array)
		put the empty array into rProperties
	
		put colorToString(mColor, true) into rProperties["color"]
		put mRotateBy into rProperties["rotateBy"]
		put mFrequency into rProperties["frequency"]
		put the instructions of mSVGPath into rProperties["svgPath"]
		put mIconOrigin into rProperties["iconOrigin"]
		put mIconSize into rProperties["iconSize"]
		put mIconStyle into rProperties["iconStyle"]
		put mIsAnimating into rProperties["animating"]
	end handler


	public handler OnLoad(in pProperties as Array)
		put path pProperties["svgPath"] into mSVGPath
		put stringToColor(pProperties["color"]) into mColor
		put pProperties["rotateBy"] into mRotateBy
		put pProperties["frequency"] into mFrequency
		put pProperties["iconOrigin"] into mIconOrigin
		put pProperties["iconSize"] into mIconSize
		put pProperties["iconStyle"] into mIconStyle
		put pProperties["animating"] into mIsAnimating
	end handler


	public handler OnOpen()
		initializationCalculations()
					
		if mIsAnimating then						
			schedule timer in mFrequency seconds
		end if
	end handler


	public handler OnGeometryChanged()
		initializationCalculations()
	end handler
	
	-- this message doesn't seem to be firing yet
	public handler OnVisibilityChanged(in pVisible as Boolean)
		setIsAnimating(pVisible)
	end handler


	private handler initializationCalculations() as undefined	
		-- Scale 
		transform mSVGPath by scaleAndMaintainAspectRatioTransform(the bounding box of mSVGPath, rectangle [0,0,mIconSize,mIconSize])
		-- Reset to 0,0
		translate mSVGPath by [- the x of the bounding box of mSVGPath, \
												- the y of the bounding box of mSVGPath]
		-- Center
		variable tCenterX as Number
		variable tCenterY as Number
		
		put (the width of the bounding box of mSVGPath) * element 1 of mIconOrigin into tCenterX
		put (the height of the bounding box of mSVGPath) * element 2 of mIconOrigin into tCenterY
		translate mSVGPath by [my width/2-tCenterX, my height/2-tCenterY]
		put point [(the left of the bounding box of mSVGPath) + tCenterX, \
								(the top of the bounding box of mSVGPath) + tCenterY] into mCenter
								
		put mSVGPath into mWorkingPath
		
		put 0 into mRotation
	end handler

	----------
	-- called whenever LiveCode needs to redraw the widget
	public handler OnPaint()
		set the paint of this canvas to solid paint with mColor
		fill mWorkingPath on this canvas
	end handler
	
	
	public handler OnClose()
		cancel timer
	end handler


	-- Very useful article: http://docs.rainmeter.net/tips/transformation-matrix-guide
	private handler calculateRotatedPath(in pRotation as Number)
		variable tX as Number
		variable tY as Number
		variable tOriginX as Number
		variable tOriginY as Number
		variable tMatrix as List
		variable tA as Number
		variable tB as Number
		variable tC as Number
		variable tD as Number
	
		-- Clockwise: sin for B, -sin for C
		-- Counterclockwise: -sin for B, sin for C
		variable tAngle as Number
		put (pRotation mod 360)/360*2*pi into tAngle
		
		put cos(tAngle) into tA -- scale the x
		put sin(tAngle) into tB -- scale the y
		put -sin(tAngle) into tC -- skew the x
		put cos(tAngle) into tD -- skew the y
		put the x of mCenter into tOriginX
		put the y of mCenter into tOriginY
	
		put tOriginX - tOriginX*tA - tOriginY*tC into tX
		put tOriginY - tOriginX*tB - tOriginY*tD into tY	
	
		variable tTransform as Transform
		put transform with matrix [tA,tB,tC,tD,tX,tY] into tTransform
		
		put mSVGPath into mWorkingPath
		transform mWorkingPath by tTransform
	end handler

	----------
	-- this handler is called when the timer scheduled with 'schedule timer' fires
	public handler OnTimer()
		put (mRotation + mRotateBy) mod 360 into mRotation
		calculateRotatedPath(mRotation)
		redraw all
		
		if mIsAnimating then
			--cancel timer
			schedule timer in mFrequency seconds -- add function to keep timer firing exactly right?
		end if
	end handler
	----------

	-- this handler as the amount of time until the seconds change
	handler computeNextTimer() as Number
		-- use the 'execute script' command
		--execute script "return 1 - (the long seconds - round(the long seconds - 0.5))"
		--return the result
	end handler


	-- Translated from some Skia code
	private handler scaleAndMaintainAspectRatioTransform(in pSrcBounds as Rectangle, in pDestBounds as Rectangle) as Transform
		// Prepare values for matrix transformation
		variable isLarger as Boolean
		variable sX as Number
		variable sY as Number
	
		put false into isLarger
		put the width of pDestBounds / the width of pSrcBounds into sX
		put the height of pDestBounds / the height of pSrcBounds into sY
	
		if sX > sY then
			put true into isLarger
			put sY into sX
		else
			put sX into sY
		end if
	
		variable tX as Number
		variable tY as Number
	
		put the left of pDestBounds - (the left of pSrcBounds*sX) into tX
		put the top of pDestBounds - (the top of pSrcBounds*sY) into tY
	
		variable tDiff as Number
	
		if isLarger then
			put my width - (the width of pSrcBounds*sY) into tDiff
		else
			put my height - (the height of pSrcBounds*sY) into tDiff
		end if
	
		// align center
		divide tDiff by 2
	
		if isLarger then
			add tDiff to tX
		else
			add tDiff to tY
		end if
	
		// create transformation matrix and apply
		variable tTransform as Transform
		put transform with matrix [sX, 0, 0, sY, tX, tY] into tTransform
	
		return tTransform
	end handler
	
	-- Needed until dp-2
	handler FormatInt(in pNumber as Number) as String
		variable tNumberString as String

		put pNumber formatted as string into tNumberString

		if "." is in tNumberString then
			variable tDotOffset
			put the first offset of "." in tNumberString into tDotOffset
			delete char tDotOffset to (the number of chars in tNumberString) of tNumberString
		end if

		return tNumberString
	end handler
	
	private handler colorToString(in pColor as Color, in pIncludeAlpha as Boolean) as String
		variable tColor as String
	
		put FormatInt(the rounded of ((the red of pColor) * 255)) into tColor
		put "," & FormatInt(the rounded of ((the green of pColor) * 255)) after tColor
		put "," & FormatInt(the rounded of ((the blue of pColor) * 255)) after tColor
	
		if pIncludeAlpha then
			put "," & FormatInt(the rounded of ((the alpha of pColor) * 255)) after tColor
		end if
	
		return tColor
	end handler

	----------
	-- this handler converts a String of numbers to an RGBA color
	private handler stringToColor(in pString as String) as Color
		variable tRed as Real
		variable tGreen as Real
		variable tBlue as Real
		variable tAlpha as Real

		variable tComponentList as List
		split pString by "," into tComponentList

		variable tComponentCount
		put the number of elements in tComponentList into tComponentCount
		if tComponentCount is not 3 and tComponentCount is not 4 then
			// Invalid number of components detected
			throw "Invalid color"
		end if

		put (element 1 of tComponentList) parsed as number into tRed
		put (element 2 of tComponentList) parsed as number into tGreen
		put (element 3 of tComponentList) parsed as number into tBlue

		if tComponentCount is 4 then
			put (element 4 of tComponentList) parsed as number into tAlpha
		else
			put 255 into tAlpha
		end if

		return color [ tRed/255, tGreen/255, tBlue/255, tAlpha/255 ]
	end handler

end widget
Trevor DeVore
ScreenSteps - https://www.screensteps.com

LiveCode Repos - https://github.com/search?q=user%3Atrevordevore+topic:livecode
LiveCode Builder Repos - https://github.com/search?q=user%3Atrevordevore+topic:livecode-builder

PaulDaMacMan
Posts: 683
Joined: Wed Apr 24, 2013 4:53 pm
Contact:

Re: Changing the origin of a transform

Post by PaulDaMacMan » Mon Apr 13, 2015 7:46 am

Hey thanks for the compliment and thanks for the back-port as I haven't kept up on the changes in LCB syntax.

I just needed to add the lcb to get the mouseDown firing:

Code: Select all

public handler OnMouseDown()
	post "mouseDown" to my script object with [FormatInt(the click button)]
end handler
...and now it works well for what I wanted to do!

The only weird thing is sometimes paths seem to be getting corrupted on saving and then reopening a stack or pasting a copy of the widget:
should look like.JPG
should look like.JPG (6.27 KiB) Viewed 10801 times
corrupt.JPG
corrupt.JPG (6.12 KiB) Viewed 10801 times
Here's my test / proof of concept stack (you can attach .zip files)
SVGRotateTest.livecode.zip
Proof of concept stack - Needs Work
(2.1 KiB) Downloaded 382 times
Click the 'knob' and drag while keeping the mouse button down, If you hold the option/alt key the values increment faster.
My GitHub Repos: https://github.com/PaulMcClernan/
Related YouTube Videos: PlayList

livecodeali
Livecode Staff Member
Livecode Staff Member
Posts: 194
Joined: Thu Apr 18, 2013 2:48 pm

Re: Changing the origin of a transform

Post by livecodeali » Mon Apr 13, 2015 9:45 am

Hi Paul,

Hopefully the corruption you're seeing is a result of this bug, which will be fixed in DP 2.
http://quality.runrev.com/show_bug.cgi?id=15035

It certainly looks like what I was seeing anyway. Are you saving the path by saving the instructions string?

Post Reply