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" }}}
 {{{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 
 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
 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.
 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>
+