How to set a morph's heading
Last updated at 5:51 pm UTC on 31 October 2006
On January 04, 2004 Jack Johnson asked: “I was messing around with EToys and grabbed an arrow morph and a couple of ellipses and wanted to have the arrow morph automatically place itself between the two ellipses and point from one to the other. The placement was easy, but I had trouble computing the heading. I did come across a formula used for computing a bearing based on two map coordinates:
tc1=mod(atan2(sin(lon1-lon2) * cos(lat2),cos(lat1) *
sin(lat2)- sin(lat1) * cos(lat2)* cos(lon1-lon2)),2 * pi)
which would work just fine, but being relatively new to Squeak I'm at a loss for how to implement it, or how to even find out if/how Squeak implements the atan2 function. Any pointers or suggestions?
Jack got a number of answers. Listed separately below.
From: Vanessa Freudenberg Sent: Sunday, January 04, 2004 8:27 AM
Doing math with EToys is not as easy as we wish sometimes ... but Squeak provides almost everything you'll ever need. The method you are looking for probably is Point>>bearingToPoint:. It's just a bit odd how to get that into your EToy. Here is one way:
- I called my two ellipses Red and Yellow, and got an Arrow from the Object Catalogue.
- You need to rotate the Arrow once with the halo and make its forwardDirection (the tiny green arrow) match its actual direction.
- After creating a "move" script for Arrow which places it between the two ellipses,
- I made a "rotate" script for Arrow like this:
Arrow's heading - Yellow's heading + Red's heading “This is only to get references to both ellipses into the script.”
- Now you can switch the script to text mode (the checkbox near the upper-left corner). You see Smalltalk similar to this:
rotate
self setHeading: Yellow1 getHeading + Red1 getHeading
- I edited this script to use the #bearingToPoint: method (press Meta-S to save):
rotate
self setHeading: (Yellow1 costume center bearingToPoint: Red1 costume center)
It uses the ellipses' costumes directly because they provide the #center method - EToy itself does not support Points, only Numbers like x and y.
- And that's about it. You can browse the implementation of #bearingToPoint: in ArrowHeading-bf.pr to see the formula used.
(From: Frank Shearar Sent: Monday, January 05, 2004 8:10 AM Shouldn't #bearingToPoint: use #radiansToDegrees instead of a constant?
In other words,
^ ((deltaX >= 0 ifTrue: [90] ifFalse: [270])
- ((deltaY / deltaX) arcTan negated radiansToDegrees)) rounded
)
(From: Jack Johnson Sent: Monday, January 05, 2004 12:31 AM I did find that after editing the text of the script (to insert/juggle parentheses) that toggling back to the tile version undid my textual changes. I assumed it was because the changes I had made could not be adequately represented by the tiles.)
From: David T. Lewis Sent: Sunday, January 04, 2004 12:35 PM
Bert gave you a good answer for the problem you are trying to solve, but as a newcomer to Squeak you may also be interested in knowing "how do I write this math formula in Squeak?". To answer that, I'm going to drop down a level into the Smalltalk language that is working underneath the EToys envinroment.
If you were trying to write a C function to solve your problem, you would poke through the manuals and translate your formula into a function that might look more or less like this:
double get_tc1(double lat1, double lon1, double lat2, double lon2)
{
return fmod(atan2(sin(lon1-lon2) * cos(lat2), cos(lat1) *
sin(lat2)-sin(lat1) * cos(lat2) * cos(lon1-lon2)), 2 * M_PI);
}
In Squeak, you would toss out the manuals (well, there aren't any manuals to toss out, but you don't need them anyway). Go the "Tools" flap on the right side of the screen, and pull out a Workspace and a Method Finder (which opens up a "Selector Browser", don't ask me why). You can use the Selector Browser to find the math functions you need, and you can build up your formula piece by piece in the Workspace, testing it as you go.
In the upper left pane of the Selector Browser, try entering "cos" and seeing what you come up with. You will see the arcCos method right away, and the cos method a little further down the list. After you experiment a little with this, you will see that the methods that you are looking for tend to live in the classes called Number and Float, so you can open browsers on these classes and find all of the normal arithmetic and math functions that you would expect.
A couple of things are a bit tricky though. In the C program, you needed to use the fmod() function, which is specifically designed to work with double precision floating point data types. If you wanted to use a modulo function for integers, you would have to use the % operator instead of fmod(), and if you were using other kinds of numbers, you might need to use the fmodf() function or the fmodl() function. In Squeak Smalltalk, the numbers are all smart enough to figure out how to interact with one another, so once you look through the arithmetic operators and find "\\" representing the modulo function, you can just use it on any kind of number (including Float, which is a double precision floating point under the covers).
The other tricky thing is that instead of using the C macro M_PI to represent the constant value for pi, you can look for "pi" in the Selector Browser, and find out that there is a "class side method" that shows up as "Float class pi". This is where Squeak remembers the constant value of pi, and you can refer to it by evaluating the expression "Float pi".
One more small oddity: In this example, I will use ":=" as the assignment operator. When you are doing this in Squeak, you can use the underscore character "_" for assignment instead of ":=". It will show up as a nice left-arrow in Squeak, and it means exactly the same thing as ":=". You will find that most of the code in Squeak uses the left arrow (underscore) instead of ":=", but since it would look funny in this email message, I used ":=" for the example.
That's all you need to know to start building up your formula. If you go to your Workspace and enter some numbers and expressions to play around with, you might end up with something like this:
—- evaluate the following in your workspace —-
lat1 _ 203.
lon1 _ 113.
lat2 _ 479.
lon2 _ 501.
tc1 := (((lon1 - lon2) sin &star lat2 cos)
arcTan: ((lat1 cos &star lat2 sin) - (lat1 sin &star lat2 cos &star (lon1 - lon2) cos)))
\\ (2 &star Float pi)
—- end of workspace stuff —-
This produces the same result as the C program (through the first 13 significant digits, which is all you get out of double precision anyway). The values that assigned to lat1, lon1, lat2, and lon2 are just number that I made up, you can experiment with any values you want.
Of course, you can turn the Smalltalk expression into a method that you can call from other objects. This makes it equivalent to the function call in C. And it is also very interesting to try evaluating your expression in the debugger, which you can do by adding the expression "self halt." in your workspace right before the "tcl := ... " stuff. Now when you evaluate the code in your workspace, a debugger will open. Experiment with this, and you will find that you can step through the formula evaluation and see exactly how the various number objects are interacting with one another and how the math functions work.
But at this point, you should probably go back to Bert's answer, which will head you back in the direction you really wanted to go.
From: Colin Putney Sent: Sunday, January 04, 2004 12:54 PM
Just in case you're more interested in the diagram than the math, I'd also suggest having a look at Ned Konz' Connectors package. It's very good at dealing with ellipses connected by arrows. You can install Connectors via SqueakMap
From: How to save a parts bin Sent: Sunday, January 04, 2004 1:37 PM
Well, the math has been described. However, there's another strategy, and that's to use my Connectors package.
In Connectors, I had to do two things to do what you describe (say if you make a Connector and tell both of its ends to "attach to nearest point to center").
- In EllipseMorph, I added this method, which uses the ellipse formula to get the point on the border that is along the line from the center to some other point (in your case, the center of the other ellipse):
intersectionWithLineSegmentFromCenterTo: aPoint
| dx aSquared bSquared m mSquared xSquared x y dy |
(self containsPoint: aPoint)
ifTrue: [ ^aPoint ].
dx _ aPoint x - self center x.
dy _ aPoint y - self center y.
dx = 0
ifTrue: [ ^self bounds pointNearestTo: aPoint ].
m _ dy / dx.
mSquared _ m squared.
aSquared _ (self bounds width / 2) squared.
bSquared _ (self bounds height / 2) squared.
xSquared _ 1 / ((1 / aSquared) + (mSquared / bSquared)).
x _ xSquared sqrt.
dx 0 ifTrue: [ x _ x negated ].
y _ m x.
^ self center + (x @ y) asIntegerPoint.
- The Connectors themselves (well, actually their ends) use the Morphic "stepping" behavior to constantly re-adjust themselves per their attachment specification. (so for instance you could have one end attached as above, and another end attached to something else). So each one of them would be periodically computing the above (passing as an argument the location of the other one), and moving the attached line vertex if necessary.
From:Andreas Raab Sent: Sunday, January 04, 2004 5:41 PM
- EToys doesn't have to use coordinates.... You can tell a morph to point to another morph.
- Hmm, actually, you can't do that exactly. But you can tell a morph to move toward another morph. So try moving it to right place, telling it to move toward the other morph, and then moving it back to the right place again.
- Here's another way of doing it: Just make up a morph which represents the angle (e.g., set its x/y to the difference of the guys being tracked) and use its "theta" to find out what the angle is.
- The only problem you have is that theta, contrary to (I think all) other angular measures goes ccw instead of cw and therefore the x/y needs to be computed as
Angle's x - Destination's x - Source's x.
Angle's y - Source's y - Destination's y.
- (Swapping the y-computation above does the trick for turning theta to cw but understanding why and proving that this works is an interesting little exercise ;-)
- Once you've done the above Angle's theta will hand you the required heading-90 since (unfortunately) theta is measured against the positive x-axis but heading is measured against the positive y-axis, so one would need to do something like
Arrow's heading - Angle's theta + 90.
- And finally, you can put Angle into a playfield with "origin-at-center" and normalize the display using the "distance" property so you can actually see it moving. The project I had originally attached demonstrated this quite nicely.