Cango3D Rotating Globe
Creating the 3D globe
The page shows an animated globe, demonstrating a more complex application created with the Cango3D graphics library. The input geographic data comes from a file containing the borders of each country of the world defined as a series of latitude and longitude pairs. These data are converted to Cgo3D (SVG style) x,y,z coordinates that are then used to make a Shape3D for each country.
The set of Shape3D representing the countries that will be visible for each frame are separated and further processed to hide the parts of the country that don't lie within the front facing hemisphere. It is task is computationally intensive and requires a dedicated 'paint' method replacing the standard Shape3D.paint. Some of the notable code snippets are shown below the canvas.
The animation has been made interactive, the rotation can be paused and resumes with the buttons and the globe axis can be tiled side to side or backward and forward using the sliders.
Animated 3D Globe
Features of the source code
The conversion from longitude, latitude pair to Cartesian [x,y,z]:
function sphericalToCart(p) // p = [long, lat] in degrees { const toRad = Math.PI/180.0; const lon = toRad*p[0]; // convert to radians const lat = toRad*p[1]; const sinLon = Math.sin(lon); const cosLon = Math.cos(lon); const sinLat = Math.sin(lat); const cosLat = Math.cos(lat); return [radius*cosLat*sinLon, radius*sinLat, radius*cosLat*cosLon]; // [x, y, z] }
To ensure land segments that are around the back of the globe in its current orientation aren't render, they must be mapped outside the the globe circle and then when the land is filled they are clipped by a global composition operation as follows:
function paintFrontFacing(gc) // 'paint' method for 'land3D' Shape3D { // only draw this country if at least one point visible (transformed z coord >0) for (let i=0; i<this.drawCmds.length; i++) { const dc = this.drawCmds[i]; if ((dc.drawFn === "moveTo" || dc.drawFn === "lineTo") && (dc.ep.tz > 0)) { gc.ctx.save(); gc.ctx.beginPath(); // step through the drawCmds array and draw each one for (let j=0; j < this.drawCmds.length; j++) { const dc = this.drawCmds[j]; // convert all parms to pixel coords if (dc.ep.tz < 0) // line ends on back of globe, map it to outside the circle { const l = Math.sqrt(dc.parms[0]*dc.parms[0] + dc.parms[1]*dc.parms[1]); const scl = (2*radius - l)/l; dc.parms[0] = scl*dc.parms[0]; dc.parms[1] = scl*dc.parms[1]; } // map world coords to pixels dc.parmsPx[0] = gc.vpLLx+gc.xoffset+dc.parms[0]*gc.xscl; dc.parmsPx[1] = gc.vpLLy+gc.yoffset+dc.parms[1]*gc.yscl; // now actually draw the path onto the canvas gc.ctx[dc.drawFn].apply(gc.ctx, dc.parmsPx); } gc.ctx.closePath(); // fill and stroke the path gc.ctx.fillStyle = this.fillColor.toRGBA(); gc.ctx.fill(); gc.ctx.restore(); // put things back the way they were break; // country has been drawn we are done } } }