Browse Source

use one canvas

Gregg Tavares 6 years ago
parent
commit
2c9e8dd148

+ 34 - 0
threejs/lessons/threejs-canvas-textures.md

@@ -365,6 +365,40 @@ and we get labels where the text is centered and scaled to fit
 
 {{{example url="../threejs-canvas-textured-labels-scale-to-fit.html" }}}
 
+Above we used a new canvas for each texture. Whether or not to use a 
+canvas per texture is up to you. If you need to up them often then 
+having one canvas per texture is probably the best option. If they are
+rarely or never updated then you can choose to use a single canvas
+for multiple textures by calling `WebGLRenderer.setTexture2D`.
+Let's change the code above to do just that.
+
+```js
++const ctx = document.createElement('canvas').getContext('2d');
+
+function makeLabelCanvas(baseWidth, size, name) {
+  const borderSize = 2;
+-  const ctx = document.createElement('canvas').getContext('2d');
+  const font =  `${size}px bold sans-serif`;
+
+  ...
+
+}
+
+function makePerson(x, labelWidth, size, name, color) {
+  const canvas = makeLabelCanvas(labelWidth, size, name);
+  const texture = new THREE.CanvasTexture(canvas);
+  // because our canvas is likely not a power of 2
+  // in both dimensions set the filtering appropriately.
+  texture.minFilter = THREE.LinearFilter;
+  texture.wrapS = THREE.ClampToEdgeWrapping;
+  texture.wrapT = THREE.ClampToEdgeWrapping;
++  renderer.setTexture2D(texture, 0);
+
+  ...
+```
+
+{{{example url="../threejs-canvas-textured-labels-one-canvas.html" }}}
+
 Another issue is that the labels don't always face the camera. If you're using 
 labels as badges that's probably a good thing. If you're using labels to put
 names over players in a 3D game maybe you want the labels to always face the camera.

+ 186 - 0
threejs/threejs-canvas-textured-labels-one-canvas.html

@@ -0,0 +1,186 @@
+<!-- Licensed under a BSD license. See license.html for license -->
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
+    <title>Three.js - Canvas Textured Labels One Canvas</title>
+    <style>
+    body {
+        margin: 0;
+    }
+    #c {
+        width: 100vw;
+        height: 100vh;
+        display: block;
+    }
+    </style>
+  </head>
+  <body>
+    <canvas id="c"></canvas>
+  </body>
+<script src="resources/threejs/r102/three.min.js"></script>
+<script src="resources/threejs/r102/js/controls/OrbitControls.js"></script>
+<script>
+'use strict';
+
+/* global THREE */
+
+function main() {
+  const canvas = document.querySelector('#c');
+  const renderer = new THREE.WebGLRenderer({canvas: canvas});
+
+  const fov = 75;
+  const aspect = 2;  // the canvas default
+  const near = 0.1;
+  const far = 50;
+  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
+  camera.position.set(0, 2, 5);
+
+  const controls = new THREE.OrbitControls(camera, canvas);
+  controls.target.set(0, 2, 0);
+  controls.update();
+
+  const scene = new THREE.Scene();
+  scene.background = new THREE.Color('white');
+
+  function addLight(position) {
+    const color = 0xFFFFFF;
+    const intensity = 1;
+    const light = new THREE.DirectionalLight(color, intensity);
+    light.position.set(...position);
+    scene.add(light);
+    scene.add(light.target);
+  }
+  addLight([-3, 1, 1]);
+  addLight([ 2, 1, .5]);
+
+  const bodyRadiusTop = .4;
+  const bodyRadiusBottom = .2;
+  const bodyHeight = 2;
+  const bodyRadialSegments = 6;
+  const bodyGeometry = new THREE.CylinderBufferGeometry(
+      bodyRadiusTop, bodyRadiusBottom, bodyHeight, bodyRadialSegments);
+
+  const headRadius = bodyRadiusTop * 0.8;
+  const headLonSegments = 12;
+  const headLatSegments = 5;
+  const headGeometry = new THREE.SphereBufferGeometry(
+      headRadius, headLonSegments, headLatSegments);
+
+  const labelGeometry = new THREE.PlaneBufferGeometry(1, 1);
+
+  const ctx = document.createElement('canvas').getContext('2d');
+
+  function makeLabelCanvas(baseWidth, size, name) {
+    const borderSize = 2;
+    const font =  `${size}px bold sans-serif`;
+    ctx.font = font;
+    // measure how long the name will be
+    const textWidth = ctx.measureText(name).width;
+
+    const doubleBorderSize = borderSize * 2;
+    const width = baseWidth + doubleBorderSize;
+    const height = size + doubleBorderSize;
+    ctx.canvas.width = width;
+    ctx.canvas.height = height;
+
+    // need to set font again after resizing canvas
+    ctx.font = font;
+    ctx.textBaseline = 'middle';
+    ctx.textAlign = 'center';
+
+    ctx.fillStyle = 'blue';
+    ctx.fillRect(0, 0, width, height);
+
+    // scale to fit but don't stretch
+    const scaleFactor = Math.min(1, baseWidth / textWidth);
+    ctx.translate(width / 2, height / 2);
+    ctx.scale(scaleFactor, 1);
+    ctx.fillStyle = 'white';
+    ctx.fillText(name, 0, 0);
+
+    return ctx.canvas;
+  }
+
+  function makePerson(x, labelWidth, size, name, color) {
+    const canvas = makeLabelCanvas(labelWidth, size, name);
+    const texture = new THREE.CanvasTexture(canvas);
+    // because our canvas is likely not a power of 2
+    // in both dimensions set the filtering appropriately.
+    texture.minFilter = THREE.LinearFilter;
+    texture.wrapS = THREE.ClampToEdgeWrapping;
+    texture.wrapT = THREE.ClampToEdgeWrapping;
+    renderer.setTexture2D(texture, 0);
+
+    const labelMaterial = new THREE.MeshBasicMaterial({
+      map: texture,
+      side: THREE.DoubleSide,
+      transparent: true,
+    });
+    const bodyMaterial = new THREE.MeshPhongMaterial({
+      color,
+      flatShading: true,
+    });
+
+    const root = new THREE.Object3D();
+    root.position.x = x;
+
+    const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
+    root.add(body);
+    body.position.y = bodyHeight / 2;
+
+    const head = new THREE.Mesh(headGeometry, bodyMaterial);
+    root.add(head);
+    head.position.y = bodyHeight + headRadius * 1.1;
+
+    const label = new THREE.Mesh(labelGeometry, labelMaterial);
+    root.add(label);
+    label.position.y = bodyHeight * 4 / 5;
+    label.position.z = bodyRadiusTop * 1.01;
+
+    // if units are meters then 0.01 here makes size
+    // of the label into centimeters.
+    const labelBaseScale = 0.01;
+    label.scale.x = canvas.width  * labelBaseScale;
+    label.scale.y = canvas.height * labelBaseScale;
+
+    scene.add(root);
+    return root;
+  }
+
+  makePerson(-3, 150, 32, 'Purple People Eater', 'purple');
+  makePerson(-0, 150, 32, 'Green Machine', 'green');
+  makePerson(+3, 150, 32, 'Red Menace', 'red');
+
+  function resizeRendererToDisplaySize(renderer) {
+    const canvas = renderer.domElement;
+    const width = canvas.clientWidth;
+    const height = canvas.clientHeight;
+    const needResize = canvas.width !== width || canvas.height !== height;
+    if (needResize) {
+      renderer.setSize(width, height, false);
+    }
+    return needResize;
+  }
+
+
+  function render() {
+    if (resizeRendererToDisplaySize(renderer)) {
+      const canvas = renderer.domElement;
+      camera.aspect = canvas.clientWidth / canvas.clientHeight;
+      camera.updateProjectionMatrix();
+    }
+
+    renderer.render(scene, camera);
+
+    requestAnimationFrame(render);
+  }
+
+  requestAnimationFrame(render);
+}
+
+main();
+</script>
+</html>
+