Google Maps 3D Overlay
The Flash Google Maps API offers what they call a 3D map. It's nothing more than a component that enables a perspective view of a classic 2D map with the associated controls. Here is how it works:
- The 3D view is engine independant and uses Flash 10 3D maths and "drawTriangles" method if available
- The API provides everything one needs to integrate 3D graphics as an overlay using the library of its choice
- It works just the same as the good old 2D Google Maps API
The first point is very important. It ensures the library is lightweight and does not include any third party software you wouldn't want to use.
The second is what makes the magic possible: the API exposes the viewport and camera data and provides methods to convert latitude/longitude into 3D world coordinates. With a little math and a few hours of debugging it is then easy to wrap it with the 3D engine of your choice.
The Experiment
You can use CTRL + mouse drag or SHIFT + mouse drag to look around.
The Code
You might want to read the official Google Maps Flash API documentation first, including the dedicated 3D maps section.
The Viewport
The Google Maps API provides the viewport width and height and the focal length. I use the field-of-view instead of the focal length but there is a simple formula to convert from one to the other:
var fieldOfView : Number = 2. * Math.atan(viewportWidth / (2. * focalLength));
Another issue is to find proper values for the near and far clipping planes. I chose 0.000001 for the near plane and 0.2 for the far plane. Those values are extremely low and might cause floating point inconsistencies. The best thing to do would be to scale the whole scene to be able to use reasonable clipping values. Maybe for a later version! This problem does not seem to affect the experiment though...
The following function presents how to get the relevant viewport values from the Google Maps API:
public function buildViewport(myMap : Map3D) : void { var g : TransformationGeometry = myMap.camera.getTransformationGeometry(); var width : Number = g.viewportSize.x; var height : Number = g.viewportSize.y; var fieldOfView : Number = 2. * Math.atan(g.viewportSize.y / (2. * g.focalLength)); // build viewport... }
The Camera
The following function retrieves the camera position and look-at vectors from the Google Maps API:
public function buildCamera(myMap : Map3D) : void { var cam : ICamera = myMap.camera; var mapCenter : Point = cam.latLngToWorld(cam.center); var g : TransformationGeometry = cam.getTransformationGeometry(); var pos : Point3D = g.cameraPosition; var zAxis : Point3D = g.cameraZAxis; var yaw : Number = cam.attitude.yaw * (Math.PI / 180.); var lookAt : Vector3D = new Vector3D(); var eye : Vector3D = new Vector3D(); eye.x = pos.x - 128; eye.y = pos.z; eye.z = 128 - pos.y; if (!zAxis.x && !zAxis.y) { lookAt.x = eye.x + EPSILON * Math.sin(yaw); lookAt.z = eye.z + EPSILON * Math.cos(yaw); } else { lookAt.x = eye.x - zAxis.x; lookAt.z = eye.z + zAxis.y; } lookAt.y = eye.y - zAxis.z; // build camera using eye position, look-at and Vector3D.Y_AXIS as the up vector }
I prefer computing the eye position and look-at vectors and using them to create the transform matrix rather than just creating the matrix directly. This way, you can expose the actual camera parameters (even if in read-only) and still use the transform matrix to do the math.
The Scene
The last step is to make it possible to position 3D objects on the map. What you actually want to do is to be able to add objects specifying their latitude/longitude instead of their actual (x, y, z) coordinates. Then again, the Google Maps API does it just fine:
public function computePosition(myLatitude : Number, myLongitude : Number) : Vector3D { var pos : Point = _map.camera.latLngToWorld(new LatLng(myLatitude, myLongitude)); return new Vector3D(pos.x - 128., 0., 128. - pos.y); }
Know Issues
As said previously, the extremely low far and near clipping plane values might introduce float point inconsistencies. Therefore, some clipping glitches might appear and frustum culling is performed only on the far clipping plane.
Credits
Credits goes to Marc Fahmi for the low-polygon 3D model of the Eiffel Tower.
Frustum Culling in Flash 10
Update: corrected a few glitches in the bounding sphere creation routine.
Optimization is always important. But when it comes to 3D for the Flash Platform, it's an everyday battle. The first ideas that come to mind are to avoid:
- redrawing the same regions : each pixel value must be set once and only once
- rendering invisible objects : objects that are out of sight still take a lot of CPU horsepower
While Flash takes care of the first one in its very renderer, the second one is not handled. But that is something we can easily address!
The Technic
The method is called "frustum culling". The big picture is that every mesh is approximated using a bounding volume (typically a sphere or a box). If the bounding volume is visible, the corresponding mesh is rendered. The two following pictures show the frustum culling caught in action:
Edit XML Namespaces with sed
Even if you are a high level programing language user, you sometimes have to use some good old tools to gain some productivity. Sed is one of those tools that might come to be handy.
I recently needed to change the xmlns attribute of a lot of XML/XSL files and doing it file by file would have been a real mess. So I wrote this little piece of script shell:
#! /bin/sh NAMESPACE_NAME=$1 NAMESPACE_URI=$(echo $2 | sed -e 's/\//\\\//g') FILE=$3 if [ x${NAMESPACE_NAME} = 'x' -o x${NAMESPACE_URI} = 'x' -o ! -f ${FILE} ]; then echo 'Usage:' ${0} 'NAMESPACE_NAME NAMESPACE_URI FILE' exit 42 fi cat $FILE | sed -e s/xmlns:${NAMESPACE_NAME}=\".*\"/xmlns:${NAMESPACE_NAME}'="'${NAMESPACE_URI}\"/g > $FILE
You can run the script with the following command line:
./change_namespace.sh myns http://my.namespace.uri my_file.xml
Where :
- myns is the namespace name (html, xsl, xhtml, etc...)
- http://my.namespace.uri is the URI of the namespace
- my_file.xml is the file to edit
