Advertisement
 
Tutorial
  1 <!-- -------------------------------------------------------------------------- 
  2   Roughly based (or inspired by) NeHe Tutorial 13 
  3   Original:  http://nehe.gamedev.net/tutorial/bitmap_fonts/17002/ 
  4    
  5   This demo based on work by Lee Stemkoski, see  
  6   https://stemkoski.github.io/Three.js 
  7  
  8   @author: rkwright@geofx.com 
  9 --------------------------------------------------------------------------- --> 
 10 <html xmlns="http://www.w3.org/1999/xhtml"> 
 11 <head>
 12     <title>Sprite Text Labels</title> 
 13     <meta charset="utf-8"> 
 14     <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> 
 15     <link rel=stylesheet href="../css/NEHE.css"/> 
 16  
 17     <script src="../js/r69/three.js" type="text/javascript"></script> 
 18     <script src="../js/r69/Detector.js" type="text/javascript"></script> 
 19     <script src="../js/r69/OrbitControls.js" type="text/javascript"></script> 
 20     <script src="../js/r69/Stats.js" type="text/javascript"></script> 
 21     <script src="../js/r69/Scene.js" type="text/javascript"></script> 
 22 </head> 
 23 <body>
 24  
 25 <script> 
 26 //custom global variables 
 27 var cube; 
 28 var projector, mouse = { x: 0, y: 0 }, INTERSECTED; 
 29 var sprite1; 
 30 var canvas1, context1, texture1; 
 31 var DESCENDER_ADJUST = 1.28; 
 32  
 33 // allocate the Scene object, request orbitControls, some of 3D axes 10 units high and the stats 
 34 var nScene = new Scene( { axisHeight:10, controls:true, displayStats:true }); 
 35  
 36 // set up the THREE.js scene via our Scene object 
 37 nScene.initialize(); 
 38  
 39 // then initialize our demo's stuff 
 40 initializeDemo(); 
 41  
 42 // Animate the scene 
 43 animateScene(); 
 44  
 45 /** 
 46  * Initialize the Demo.  This is roughly equivalent to NeHe #12: 
 47  */ 
 48      
 49 function initializeDemo()  
 50 { 
 51     var cubeGeometry = new THREE.BoxGeometry( 1, 1, 1 ); 
 52     var cubeMaterial = new THREE.MeshNormalMaterial( { wireframe:true } ); 
 53     cube = new THREE.Mesh( cubeGeometry, cubeMaterial ); 
 54     cube.position.set(0,0,0); 
 55     cube.name = "Cube"; 
 56     nScene.addToScene(cube); 
 57   
 58     var txSprite = makeTextSprite( "Bottom-Left", -1, 1, 0.25, { fontsize: 72, fontface: "Georgia", borderColor: {r:0, g:0, b:255, a:1.0},  
 59         borderThickness:4, fillColor: {r:255, g:255, b:255, a:1.0}, radius:0, vAlign:"bottom", hAlign:"left" } ); 
 60     nScene.addToScene( txSprite ); 
 61  
 62     var txSprite = makeTextSprite( "Center-Left", -1, 0, 0.25, { fontsize: 72, fontface: "Georgia", borderColor: {r:0, g:0, b:255, a:1.0},  
 63         borderThickness:6, fillColor: {r:255, g:255, b:255, a:1.0}, radius:12, vAlign:"center", hAlign:"left" } ); 
 64     nScene.addToScene( txSprite ); 
 65  
 66     var txSprite = makeTextSprite( "Top-Left", -1, -1, 0.25, { fontsize: 72, fontface: "Georgia",  
 67         borderColor: {r:0, g:0, b:255, a:1.0}, borderThickness:8, fillColor: {r:255, g:255, b:255, a:1.0}, radius:8, vAlign:"top", hAlign:"left" } ); 
 68     nScene.addToScene( txSprite ); 
 69          
 70     var txSprite = makeTextSprite( "Bottom-Center", 0, 1, 0.25, { fontsize: 72, fontface: "Georgia", borderColor: {r:0, g:0, b:255, a:1.0},  
 71         borderThickness:4, radius:2, fillColor: {r:255, g:255, b:255, a:1.0}, vAlign:"bottom", hAlign:"center" } ); 
 72     nScene.addToScene( txSprite ); 
 73  
 74     var txSprite = makeTextSprite( "Center-Center", 0, 0, 0.25, { fontsize: 72, fontface: "Georgia", borderColor: {r:0, g:0, b:255, a:1.0},  
 75         borderThickness:10, radius:10, fillColor: {r:255, g:255, b:255, a:1.0}, vAlign:"center", hAlign:"center" } ); 
 76     nScene.addToScene( txSprite ); 
 77  
 78     var txSprite = makeTextSprite( "Top-Center", 0, -1, 0.25, { fontsize: 72, fontface: "Georgia",  borderColor: {r:0, g:0, b:255, a:1.0}, 
 79         borderThickness:20, fillColor: {r:255, g:255, b:255, a:1.0}, radius:10, vAlign:"top", hAlign:"center" } ); 
 80     nScene.addToScene( txSprite ); 
 81      
 82     var txSprite = makeTextSprite( "Bottom-Right-NoFill", 1, 1, 0.25, { fontsize: 72, fontface: "Georgia", radius:8,  
 83         textColor: {r:0, g:255, b:0, a:1.0}, vAlign:"bottom", hAlign:"right" } ); 
 84     nScene.addToScene( txSprite ); 
 85  
 86     var txSprite = makeTextSprite( "Center-Right", 1, 0, 0.25, { fontsize: 72, fontface: "Georgia", borderColor: {r:0, g:0, b:255, a:1.0},  
 87         borderThickness:8, fillColor: {r:255, g:255, b:255, a:1.0}, radius:0, vAlign:"center", hAlign:"right" } ); 
 88     nScene.addToScene( txSprite ); 
 89  
 90     var txSprite = makeTextSprite( "Top-Right", 1, -1, 0.25, { fontsize: 72, fontface: "Georgia", borderColor: {r:0, g:0, b:255, a:1.0},  
 91         borderThickness:0, radius:8, fillColor: {r:255, g:255, b:255, a:1.0}, vAlign:"top", hAlign:"right" } ); 
 92     nScene.addToScene( txSprite ); 
 93  
 94 } 
 95  
 96 /** 
 97  * Build a text sprite.  We use canvas to write the label in 2D then create a texture
 98  * from the canvas.  Three.js extracts the raster from the canvas and composites that
 99  * into the center of the texture. 
100  */ 
101 function makeTextSprite( message, x, y, z, parameters ) 
102 { 
103     if ( parameters === undefined ) parameters = {}; 
104      
105     var fontface = parameters.hasOwnProperty("fontface") ?  
106         parameters["fontface"] : "Arial"; 
107      
108     var fontsize = parameters.hasOwnProperty("fontsize") ?  
109         parameters["fontsize"] : 18; 
110      
111     var borderThickness = parameters.hasOwnProperty("borderThickness") ?  
112         parameters["borderThickness"] : 4; 
113      
114     var borderColor = parameters.hasOwnProperty("borderColor") ? 
115         parameters["borderColor"] : { r:0, g:0, b:0, a:1.0 }; 
116      
117     var fillColor = parameters.hasOwnProperty("fillColor") ? 
118         parameters["fillColor"] : undefined; 
119  
120     var textColor = parameters.hasOwnProperty("textColor") ? 
121         parameters["textColor"] : { r:0, g:0, b:255, a:1.0 }; 
122  
123     var radius = parameters.hasOwnProperty("radius") ? 
124                 parameters["radius"] : 6; 
125  
126     var vAlign = parameters.hasOwnProperty("vAlign") ? 
127                         parameters["vAlign"] : "center"; 
128  
129     var hAlign = parameters.hasOwnProperty("hAlign") ? 
130                         parameters["hAlign"] : "center"; 
131  
132     var canvas = document.createElement('canvas'); 
133     var context = canvas.getContext('2d'); 
134      
135     // set a large-enough fixed-size canvas  
136     canvas.width = 1800; 
137     canvas.height = 900; 
138      
139     context.font = fontsize + "px " + fontface; 
140     context.textBaseline = "alphabetic"; 
141     context.textAlign = "left"; 
142      
143     // get size data (height depends only on font size) 
144     var metrics = context.measureText( message ); 
145     var textWidth = metrics.width; 
146      
147     /* 
148     // need to ensure that our canvas is always large enough 
149     // to support the borders and justification, if any 
150     // Note that this will fail for vertical text (e.g. Japanese)
151     // The other problem with this approach is that the size of the canvas 
152     // varies with the length of the text, so 72-point text is different 
153     // sizes for different text strings.  There are ways around this 
154     // by dynamically adjust the sprite scale etc. but not in this demo...
155     var larger = textWidth > fontsize ? textWidth : fontsize;
156     canvas.width = larger * 4; 
157     canvas.height = larger * 2; 
158     // need to re-fetch and refresh the context after resizing the canvas 
159     context = canvas.getContext('2d'); 
160     context.font = fontsize + "px " + fontface; 
161     context.textBaseline = "alphabetic"; 
162     context.textAlign = "left"; 
163      metrics = context.measureText( message ); 
164     textWidth = metrics.width; 
165  
166      console.log("canvas: " + canvas.width + ", " + canvas.height + ", texW: " + textWidth);
167     */ 
168      
169     // find the center of the canvas and the half of the font width and height 
170     // we do it this way because the sprite's position is the CENTER of the sprite 
171     var cx = canvas.width / 2; 
172     var cy = canvas.height / 2; 
173     var tx = textWidth/ 2.0; 
174     var ty = fontsize / 2.0; 
175  
176     // then adjust for the justification 
177     if ( vAlign == "bottom") 
178         ty = 0; 
179     else if (vAlign == "top") 
180         ty = fontsize; 
181      
182     if (hAlign == "left") 
183         tx = textWidth; 
184     else if (hAlign == "right") 
185         tx = 0; 
186      
187     // the DESCENDER_ADJUST is extra height factor for text below baseline: g,j,p,q. since we don't know the true bbox 
188     roundRect(context, cx - tx , cy + ty + 0.28 * fontsize,  
189             textWidth, fontsize * DESCENDER_ADJUST, radius, borderThickness, borderColor, fillColor); 
190      
191     // text color.  Note that we have to do this AFTER the round-rect as it also uses the "fillstyle" of the canvas 
192     context.fillStyle = getCanvasColor(textColor); 
193  
194     context.fillText( message, cx - tx, cy + ty); 
195   
196     // draw some visual references - debug only 
197     drawCrossHairs( context, cx, cy );     
198     // outlineCanvas(context, canvas); 
199     addSphere(x,y,z); 
200     
201     // canvas contents will be used for a texture 
202     var texture = new THREE.Texture(canvas) 
203     texture.needsUpdate = true; 
204  
205     var spriteMaterial = new THREE.SpriteMaterial( { map: texture } ); 
206     var sprite = new THREE.Sprite( spriteMaterial ); 
207      
208     // we MUST set the scale to 2:1.  The canvas is already at a 2:1 scale, 
209     // but the sprite itself is square: 1.0 by 1.0 
210     // Note also that the size of the scale factors controls the actual size of the text-label 
211     sprite.scale.set(4,2,1); 
212      
213     // set the sprite's position.  Note that this position is in the CENTER of the sprite 
214     sprite.position.set(x, y, z); 
215      
216     return sprite;     
217 } 
218  
219 /** 
220  *  function for drawing rounded rectangles 
221  */ 
222 function roundRect(ctx, x, y, w, h, r, borderThickness, borderColor, fillColor)  
223 { 
224     // no point in drawing it if it isn't going to be rendered 
225     if (fillColor == undefined && borderColor == undefined)  
226         return; 
227  
228     x -= borderThickness + r; 
229     y += borderThickness + r; 
230     w += borderThickness * 2 + r * 2; 
231     h += borderThickness * 2 + r * 2; 
232      
233     ctx.beginPath(); 
234     ctx.moveTo(x+r, y); 
235     ctx.lineTo(x+w-r, y); 
236     ctx.quadraticCurveTo(x+w, y, x+w, y-r); 
237     ctx.lineTo(x+w, y-h+r); 
238     ctx.quadraticCurveTo(x+w, y-h, x+w-r, y-h); 
239     ctx.lineTo(x+r, y-h); 
240     ctx.quadraticCurveTo(x, y-h, x, y-h+r); 
241     ctx.lineTo(x, y-r); 
242     ctx.quadraticCurveTo(x, y, x+r, y); 
243     ctx.closePath(); 
244      
245     ctx.lineWidth = borderThickness; 
246  
247     // background color 
248     // border color 
249  
250     // if the fill color is defined, then fill it 
251     if (fillColor != undefined) { 
252         ctx.fillStyle = getCanvasColor(fillColor); 
253         ctx.fill(); 
254     } 
255      
256     if (borderThickness > 0 && borderColor != undefined) { 
257         ctx.strokeStyle = getCanvasColor(borderColor); 
258         ctx.stroke(); 
259     } 
260 } 
261  
262 /** 
263  * Just add a little sphere for visual reference 
264  */ 
265 function addSphere( x, y, z, size ) { 
266     if (size == undefined) 
267         size = 0.01; 
268      
269     var sphere = new THREE.Mesh( new THREE.SphereGeometry(size, 32, 32), new THREE.MeshNormalMaterial()); 
270     sphere.position.set(x, y, z); 
271     nScene.addToScene(sphere); 
272 } 
273  
274 /** 
275  * Just a debug feature to draw cross-hair for visual reference 
276  */ 
277 function drawCrossHairs ( context, cx, cy ) { 
278      
279     context.strokeStyle = "rgba(0,255,0,1)"; 
280     context.lineWidth = 2; 
281     context.beginPath();  
282     context.moveTo(cx-150,cy); 
283     context.lineTo(cx+150,cy); 
284     context.stroke(); 
285      
286     context.strokeStyle = "rgba(0,255,0,1)"; 
287     context.lineWidth = 2; 
288     context.beginPath();  
289     context.moveTo(cx,cy-150); 
290     context.lineTo(cx,cy+150); 
291     context.stroke(); 
292     context.strokeStyle = "rgba(0,255,0,1)"; 
293     context.lineWidth = 2; 
294     context.beginPath();  
295     context.moveTo(cx-150,cy); 
296     context.lineTo(cx+150,cy); 
297     context.stroke(); 
298      
299     context.strokeStyle = "rgba(0,255,0,1)"; 
300     context.lineWidth = 2; 
301     context.beginPath();  
302     context.moveTo(cx,cy-150); 
303     context.lineTo(cx,cy+150); 
304     context.stroke(); 
305 } 
306  
307 /** 
308  * Just a debug feature to outline and cross the canvas for visual reference
309  */ 
310 function outlineCanvas (context, canvas ) { 
311      
312     context.strokeStyle = "rgba(255,0,0,1)"; 
313     context.lineWidth = 2; 
314      
315     context.beginPath();  
316     context.moveTo(0,0); 
317     context.lineTo(canvas.width, canvas.height); 
318     context.stroke(); 
319  
320     context.beginPath();  
321     context.moveTo(0,canvas.height); 
322     context.lineTo(canvas.width, 0); 
323     context.stroke(); 
324      
325     context.strokeRect(0, 0, canvas.width, canvas.height); 
326 } 
327 /** 
328  * convenience for converting JSON color to rgba that canvas wants
329  * Be nice to handle different forms (e.g. no alpha, CSS style, etc.)
330  */ 
331 function getCanvasColor ( color ) { 
332     return "rgba(" + color.r + "," + color.g + "," + color.b + "," + color.a + ")"; 
333 } 
334  
335 /** 
336  * Animate the scene and call rendering. 
337  */ 
338 function animateScene() { 
339  
340     // Tell the browser to call this function when page is visible 
341     requestAnimationFrame(animateScene); 
342      
343     // Map the 3D scene down to the 2D screen (render the frame) 
344     nScene.renderScene(); 
345 } 
346  
347 </script> 
348  
349 </body> 
350 </html> 
351 
Scene.js
  1     //some constants 
  2     var        X_AXIS = 0; 
  3     var        Y_AXIS = 1; 
  4     var        Z_AXIS = 2; 
  5      
  6 Scene = function ( parameters ) { 
  7      
  8     this.scene; 
  9     this.renderer; 
 10     this.camera; 
 11  
 12     this.controls = false; 
 13     this.orbitControls; 
 14  
 15     this.displayStats = false; 
 16     this.stats; 
 17  
 18     this.ambientLight; 
 19     this.directionalLight; 
 20  
 21     this.axisHeight = 0; 
 22  
 23     this.setParameters( parameters ); 
 24 }; 
 25  
 26 // the scene's parameters from the values JSON object 
 27 // lifted from MrDoob's implementation in three.js 
 28 Scene.prototype = { 
 29          
 30     setParameters: function( values ) { 
 31  
 32         if ( values === undefined ) return; 
 33      
 34         for ( var key in values ) { 
 35      
 36             var newValue = values[ key ]; 
 37      
 38             if ( newValue === undefined ) { 
 39                 console.warn( "NEHE: '" + key + "' parameter is undefined." ); 
 40                 continue; 
 41             } 
 42      
 43             if ( key in this ) { 
 44                 var currentValue = this[ key ]; 
 45      
 46                 if ( currentValue instanceof THREE.Color ) { 
 47                     currentValue.set( newValue ); 
 48                 } else if ( currentValue instanceof THREE.Vector3 && newValue instanceof THREE.Vector3 ) { 
 49                     currentValue.copy( newValue ); 
 50                 } else if ( key == 'overdraw' ) { 
 51                     // ensure overdraw is backwards-compatible with legacy boolean type 
 52                     this[ key ] = Number( newValue ); 
 53                 } else { 
 54                     this[ key ] = newValue; 
 55                 } 
 56             } 
 57         } 
 58     }, 
 59  
 60     initialize: function () { 
 61         // Check whether the browser supports WebGL.  
 62         if ( !Detector.webgl ) Detector.addGetWebGLMessage(); 
 63      
 64         // Create the scene, in which all objects are stored (e. g. camera, lights, geometries, ...) 
 65         this.scene = new THREE.Scene(); 
 66      
 67         // Get the size of the inner window (content area) 
 68         var canvasWidth = window.innerWidth; 
 69         var canvasHeight = window.innerHeight; 
 70      
 71         // if the caller supplied the container elm ID try to find it 
 72         var container; 
 73         var containerID; 
 74         if (containerID != null && typeof containerID != 'undefined') 
 75             container = document.getElementById(containerID); 
 76          
 77         // couldn't find it, so create it ourselves 
 78         if (container == null || typeof container == 'undefined') { 
 79             container = document.createElement( 'div' ); 
 80             document.body.appendChild( container ); 
 81         } 
 82         else { 
 83             canvasWidth = container.clientWidth; 
 84             canvasHeight = container.clientHeight; 
 85         } 
 86      
 87         // set up the camera 
 88         this.camera = new THREE.PerspectiveCamera(45, canvasWidth / canvasHeight, 0.1, 1000); 
 89         this.camera.position.set(0, 6, 6); 
 90         this.camera.lookAt(this.scene.position); 
 91         this.scene.add(this.camera); 
 92      
 93         // allocate the THREE.js renderer 
 94         this.renderer = new THREE.WebGLRenderer({antialias:true}); 
 95      
 96         // Set the background color of the renderer to black, with full opacity 
 97         this.renderer.setClearColor(0x000000, 1); 
 98      
 99         // Set the renderers size to the content areas size 
100         this.renderer.setSize(canvasWidth, canvasHeight); 
101      
102         // Get the DIV element from the HTML document by its ID and append the renderer's DOM object 
103         container.appendChild(this.renderer.domElement); 
104          
105         // Ambient light has no direction, it illuminates every object with the same 
106         // intensity. If only ambient light is used, no shading effects will occur. 
107         this.ambientLight = new THREE.AmbientLight(0x404040); 
108         this.scene.add(this.ambientLight); 
109      
110         // Directional light has a source and shines in all directions, like the sun. 
111         // This behaviour creates shading effects. 
112         this.directionalLight = new THREE.PointLight(0xffffff); 
113         this.directionalLight.position.set(250,250,250);  
114         this.scene.add(this.directionalLight); 
115          
116         // request the orbitControls be created and enabled 
117         // add the controls 
118         if (this.controls == true) 
119             this.orbitControls = new THREE.OrbitControls( this.camera, this.renderer.domElement ); 
120          
121         if ( this.axisHeight != 0 ) 
122             this.drawAxes(this.axisHeight); 
123          
124         //------ STATS --------------------     
125         // displays current and past frames per second attained by scene 
126         if (this.displayStats == true) { 
127             this.stats = new Stats(); 
128             this.stats.domElement.style.position = 'absolute'; 
129             this.stats.domElement.style.bottom = '0px'; 
130             this.stats.domElement.style.zIndex = 100; 
131             container.appendChild( this.stats.domElement ); 
132         } 
133     }, 
134  
135     addToScene: function ( obj ) { 
136         this.scene.add(obj); 
137     }, 
138  
139 /** 
140  * Render the scene. Map the 3D world to the 2D screen. 
141  */ 
142     renderScene: function() { 
143          
144         this.renderer.render(this.scene, this.camera); 
145  
146         // the orbit controls, if used, have to be updated as well 
147         if (this.orbitControls != null && typeof this.orbitControls != 'undefined')  
148             this.orbitControls.update(); 
149  
150         if (this.stats != null && typeof this.stats != 'undefined')  
151             this.stats.update(); 
152  
153 }, 
154  
155 // draw some axes 
156     drawAxis: function( axis, axisColor, axisHeight ) { 
157         var        AXIS_RADIUS   =    axisHeight/200.0; 
158         var        AXIS_HEIGHT   =    axisHeight; 
159         var        AXIS_STEP     =    axisHeight/20.0; 
160         var        AXIS_SEGMENTS = 32; 
161         var        AXIS_GRAY     = 0x777777; 
162         var        AXIS_WHITE    = 0xEEEEEE; 
163          
164         //console.log("drawAxis " + axis + " ht: " +  AXIS_HEIGHT + ", " + AXIS_STEP + " color: " + axisColor); 
165      
166         for ( i=0; i<(AXIS_HEIGHT/AXIS_STEP); i++ ) 
167         { 
168             //console.log("loop " +  i); 
169              
170             var pos = -AXIS_HEIGHT / 2 + i * AXIS_STEP; 
171      
172             if ((i & 1) == 0) 
173                 curColor = axisColor; 
174             else if (pos < 0) 
175                 curColor = AXIS_GRAY; 
176             else 
177                 curColor = AXIS_WHITE; 
178              
179             //console.log(i + " pos: " + pos + " color: " + curColor); 
180              
181             var geometry = new THREE.CylinderGeometry( AXIS_RADIUS, AXIS_RADIUS, AXIS_STEP, AXIS_SEGMENTS );  
182             var material = new THREE.MeshLambertMaterial( {color: curColor} );  
183             var cylinder = new THREE.Mesh( geometry, material );  
184              
185             pos += AXIS_STEP/2.0; 
186             if (axis == X_AXIS) 
187             { 
188                 cylinder.position.x = pos; 
189                 cylinder.rotation.z = Math.PI/2; 
190             } 
191             else if (axis == Y_AXIS) 
192             { 
193                 cylinder.rotation.y = Math.PI/2; 
194                 cylinder.position.y = pos; 
195             } 
196             else 
197             {     
198                 cylinder.position.z = pos; 
199                 cylinder.rotation.x = Math.PI/2; 
200             } 
201              
202             this.scene.add( cylinder ); 
203         }; 
204     }, 
205  
206     drawAxes: function( height ) { 
207      
208         this.drawAxis(X_AXIS, 0xff0000, height); 
209         this.drawAxis(Y_AXIS, 0x00ff00, height); 
210         this.drawAxis(Z_AXIS, 0x0000ff, height); 
211     } 
212 }
NEHE.css
  1 /* 
  2  * @author rkwright   /  www.geofx.com 
  3  */ 
  4   
  5  body { 
  6     /* Set the background color of the HTML page to black */ 
  7     background-color: #000000; 
  8  
  9     /* Hide oversized content. This prevents the scroll bars. */ 
 10     overflow: hidden; 
 11  
 12     /* Define the font and the color for the usage, which is an ordinary HTML overlay. */ 
 13     font-family: Monospace; 
 14     color: white; 
 15 }
Live example