
Positioning things in 3D can be difficult if you don’t have a good understanding of, well, 3D. I often focus on cool new experiments, or some new feature I’ve added to the repository, but after getting some questions about positioning things on a globe, I decided to take a step back and do something more functional and basic than normal. Don’t worry, I’ve put my usual spin on things, so hopefully there will some fun stuff you can pick up from this demo as well if you already know how to use spherical coordinates.
Check out the demo. It will show you where I live (not too specifically), then where you live in relation. If it can’t find you, it will pick a random spot on the globe… but that hasn’t happened yet.
I have divided this post into what i consider to be the main points of interest of this demo. It isn’t as sequential as normal, but hopefully will help you jump to something you are interested in and read specifically about that. Believe it or not, this little demo has a lot packed into it.
Geolocation
Geolocation simply means we find where you currently are in the world. This demo uses your IP address to figure it out. I’m using the free, and incredibly accurate GeoLite City database and php module to figure out what city (or area) you are currently in. This is all done on the server side, but don’t worry, I have included the php I used to get the information that is passed into flash. As a fallback, I have left in my original method, www.hostip.info – an online API that has flakey results and often has no information. The XML returned is unparsable by flash, so you will notice in my code there is some interesting workarounds to format the XML correctly. I’ve left it in just in case, and as a reference, but I woudln’t recommend it for people to use on anything commercial. GeoLite City on the other hand is fantastic, and the full version is even more complete. There is also IP2Location, which doesn’t have a free version, but seems to have the lions share of commercial geolocation.
GeoLite City includes the most pertinant information we need for geolocation in this application, which is longitude and latitude. Once we have those two peices of information, we can figure out where that point is in both 2D and 3D space. Keep in mind, these equations only work with equirectangular projections, so if your map has a different projection, you will need to use the correct formulas.
Adding a Marker in 2D
You will notice in the demo that under the 3D marker there is a bulls eye added to the texture. I put this in for all those who don’t want 3D markers, but would rather like to know how to add a marker in 2D.
Actionscript:
-
var w2:Number = movieMaterial.rect.width/2;
-
var h2:Number = movieMaterial.rect.height/2;
-
-
var m:Bitmap = new bullseye() as Bitmap;
-
textureContainer.addChild(m);
-
-
m.y = (90-lat)/90*h2-m.height/2;
-
m.x = (lon+180)/180*w2-m.width/2;
-
-
m.alpha = 0.5;
-
-
movieMaterial.drawBitmap();
You will notice that the first thing done is to find half the size of our world texture. This will let us put the lon/lat in the same scale the texture is in. The longitude and latitude we recieve range from (-180, 180) and (-90, 90). This code: (90-lat)/90 gives us a percentage our latitude is from the top of the sphere. We then multiply that by half the texture height to bring it into our 2D coordinates. We then do the same thing to find the longitudes 2D coordinate. I subtract half the bulls eye’s width and height to center it on the postion. The last thing we do is update the earth material, movieMaterial, so the texture changes become visible.
Adding a Marker in 3D
More people are probably interested in how to add a marker in 3D space, as it is a little more involved. But keep in mind that the general concept is the same as adding a marker to 2D. To begin, we simply get the spherical coordinates:
var phi:Number = (90-lat)*Math.PI/180;
var theta:Number = (lon+180)*Math.PI/180;
phi represents the angle from the top of the sphere to the current position. theta is how far “around” the sphere our point is. Both are in radians so we can use them for the next part: converting spherical to cartesian coordinates:
marker.x = (earthRadius+10) * Math.sin(phi)*Math.cos(theta);
marker.z = (earthRadius+10) * Math.sin(phi)*Math.sin(theta);
marker.y = (earthRadius+10) * Math.cos(phi);
The common misconception that people have is that if you know how far from the top, and how far around the radius a point is, you can simply use something like sin and cos on the XY and then XZ planes. Don’t fall for it – it doesn’t work. The three dimensions are tied together – and when dealing with spherical coordinates (rather than 2 planes of circular), you have to consider all 3.
Figuring Out The Rotation
I enjoy math – if you haven’t figured it out over the past year of my blog – so this part of the demo is something I really enjoyed working on. I used quaternions for this application, partly because I enjoy using them, and partly because it was a good opportunity to show some additional techniques for using quaternions.
I don’t really feel like doing a complete math tutorial, so here is all I will say about finding the angles:
- cross = perpendicular vector
- dot = scalar magnitude
- acos(dot) = angle
Now that angles is taken care of, take a look at the getRotatedQuat function:
Actionscript:
-
private function getRotatedQuat(axis:Number3D, startQuat:Quaternion, angle:Number, resultOffset:Number):Quaternion{
-
-
var rotQuat:Quaternion = Quaternion.createFromAxisAngle(axis.x, axis.y, axis.z, angle);
-
rotQuat.normalize();
-
-
var result:Quaternion = Quaternion.multiply(Quaternion.multiply(rotQuat, startQuat), Quaternion.conjugate(rotQuat));
-
-
…
You notice that we have an axis we want to rotate around, the starting Quaternion (representing our start vector), and how far around the axis we want to go from that start point. Quaternions make rotating vectors pretty easy, so don’t be discouraged if you aren’t sure what the code above is talking about. I’m going to try and make it a little more clear.
You might be wondering about the startQuat, which is defined as:
var startQuat:Quaternion = new Quaternion(marker1.x, marker1.y, marker1.z, 0);
You will notice that this quaternion is representing a vector, rather than a rotation. I don’t want to get into some crazy proofs, but to put it simply, you can represent a vector, or a point, with a quaternion – and this is known as a pure quaternion. When you multiply a pure quaternion with a rotation quaternion and its inverse (also equal to the conjugate for rotation quaternions), you get another pure quaternion out of it. This resulting pure quaternion is the now rotated vector.
Lets put it into a form that is easier to read:
The first thing we do is convert our axis of rotation and amount of rotation (angle) into a rotation quaternion. We will call it R from now on. Once we have R, we can use it with our pure quaternion (startQuat), which i will call V, to get a rotated vector from V’s initial position. The formula to do this is:
result = R*V*R`
You might be wondering, why do I have to use R` in this equation. Simply put, by multiplying the inverse of the rotation, we can get our resulting quaternion back into a “pure” state. Now, we have a quaternion that contains our rotated vector! You will notice I store these in an array, which then define the bezier curve that my dotted line follows. Keep in mind there are other ways you an derive these intermediate points, but this way is fun and hopefully you have learned something useful!
In Conclusion
I think that is enough for now – you can look over the code for any other things that might be useful.
Check out the demo
and
Get the Source
As A Side Note
There is a competition for you to compete in! Jim Foley over at http://www.madvertices.com is hosting a Pv3D competition with some nice prizes for the guy(s) with the coolest soccer ball related application. You can check out the competition details at http://madvertices.yuku.com/topic/11 – it should be fun! Thanks for putting that together Jim!