Three.js#04#Responsive Design&Scenegraph

參考https://threejs.org/manual/#en/responsive和https://threejs.org/manual/#en/scenegraph

前者主要是說怎樣創建一個響應式的three.js應用,就是在變化屏幕大小的時候,畫面不會畸形。後者是再說,怎麼組合小的組件變成一個大的組件(依賴於一個空組件object3D)

下面是示例代碼:

index.html

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>My first three.js app</title>
		<style>
			html, body {
			   margin: 0;
			   height: 100%;
			}
			#c {
			   width: 100%;
			   height: 100%;
			   display: block;
			}
		</style>
	</head>
	<body>
		<script type="module" src="/main.js"></script>
		<canvas id="c"></canvas>
		<div id="info"></div>
	</body>
</html>

main.js

import * as THREE from 'three';
import {FontLoader} from 'three/addons/loaders/FontLoader.js';
import {TextGeometry} from 'three/addons/geometries/TextGeometry.js';

function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({antialias: true, canvas});

  const fov = 40;
  const aspect = 2;  // the canvas default
  const near = 0.1;
  const far = 1000;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.z = 120;

  const scene = new THREE.Scene();
  scene.background = new THREE.Color('white');

  {
    const color = 0xFFFFFF;
    const intensity = 1;
    const light = new THREE.DirectionalLight(color, intensity);
    light.position.set(-1, 2, 4);
    scene.add(light);
  }
  {
    const color = 0xFFFFFF;
    const intensity = 1;
    const light = new THREE.DirectionalLight(color, intensity);
    light.position.set(1, -2, -4);
    scene.add(light);
  }

  const objects = [];
  const spread = 15;

  function addObject(x, y, obj) {
    obj.position.x = x * spread;
    obj.position.y = y * spread;

    scene.add(obj);
    objects.push(obj);
  }

  const myObjs = new Map();

  function addMyObj(x, y, obj, objName) {
    obj.position.x = x * spread;
    obj.position.y = y * spread;   
    
    // scene.add(obj);

    myObjs.set(objName, obj)
  }

  function makeObject3D(x, y, objName) {
    const obj3D = new THREE.Object3D();
    obj3D.position.x = x * spread;
    obj3D.position.y = y * spread;   

    myObjs.set(objName, obj3D)
    scene.add(obj3D);
  }

  function getObj(objName) {
    return myObjs.get(objName)
  }

  function createMaterial() {
    const material = new THREE.MeshPhongMaterial({
      side: THREE.DoubleSide,
    });

    const hue = Math.random();
    const saturation = 1;
    const luminance = .5;
    material.color.setHSL(hue, saturation, luminance);

    return material;
  }

  function addSolidGeometry(x, y, geometry) {
    const mesh = new THREE.Mesh(geometry, createMaterial());
    addObject(x, y, mesh);
  }

  function addMySolidGeometry(x, y, geometry, objName) {
    const mesh = new THREE.Mesh(geometry, createMaterial());
    addMyObj(x, y, mesh, objName);
  }

  function addLineGeometry(x, y, geometry) {
    const material = new THREE.LineBasicMaterial({color: 0x000000});
    const mesh = new THREE.LineSegments(geometry, material);
    addObject(x, y, mesh);
  }

  {
    const width = 9;
    const height = 9;
    const widthSegments = 2;
    const heightSegments = 2;
    addSolidGeometry(-5.5, 2, new THREE.PlaneGeometry(width, height, widthSegments, heightSegments));
  }
  {
    const shape = new THREE.Shape();
    const x = -2.5;
    const y = -5;
    shape.moveTo(x + 2.5, y + 2.5);
    shape.bezierCurveTo(x + 2.5, y + 2.5, x + 2, y, x, y);
    shape.bezierCurveTo(x - 3, y, x - 3, y + 3.5, x - 3, y + 3.5);
    shape.bezierCurveTo(x - 3, y + 5.5, x - 1.5, y + 7.7, x + 2.5, y + 9.5);
    shape.bezierCurveTo(x + 6, y + 7.7, x + 8, y + 4.5, x + 8, y + 3.5);
    shape.bezierCurveTo(x + 8, y + 3.5, x + 8, y, x + 5, y);
    shape.bezierCurveTo(x + 3.5, y, x + 2.5, y + 2.5, x + 2.5, y + 2.5);
    addSolidGeometry(-4.5, 2, new THREE.ShapeGeometry(shape));
  }
  {
    const radius = 7;
    const widthSegments = 12;
    const heightSegments = 8;
    addSolidGeometry(-3.5, 2, new THREE.SphereGeometry(radius, widthSegments, heightSegments));
  }
  {
    const loader = new FontLoader();
    // promisify font loading
    function loadFont(url) {
      return new Promise((resolve, reject) => {
        loader.load(url, resolve, undefined, reject);
      });
    }

    async function doit() {
      const font = await loadFont('helvetiker_regular.typeface.json');  
      const geometry = new TextGeometry('three.js', {
        font: font,
        size: 3.0,
        height: .2,
        curveSegments: 12,
        bevelEnabled: true,
        bevelThickness: 0.15,
        bevelSize: .3,
        bevelSegments: 5,
      });
      const mesh = new THREE.Mesh(geometry, createMaterial());
      geometry.computeBoundingBox();
      geometry.boundingBox.getCenter(mesh.position).multiplyScalar(-1);

      const parent = new THREE.Object3D();
      parent.add(mesh);

      addObject(-2.5, 2, parent);
    }
    doit();
  }
  {
    const radius = 3.5;
    const tube = 1.5;
    const radialSegments = 8;
    const tubularSegments = 64;
    const p = 2;
    const q = 3;
    addSolidGeometry(-1.5, 2, new THREE.TorusKnotGeometry(radius, tube, tubularSegments, radialSegments, p, q));
  }
  {
    const width = 8;
    const height = 8;
    const depth = 8;
    const thresholdAngle = 15;
    addLineGeometry(-0.5, 2, new THREE.EdgesGeometry(
        new THREE.BoxGeometry(width, height, depth),
        thresholdAngle));
  }
  {
    const width = 8;
    const height = 8;
    const depth = 8;
    addLineGeometry(0.5, 2, new THREE.WireframeGeometry(new THREE.BoxGeometry(width, height, depth)));
  }

  {
    makeObject3D(0, 0, 'system')
  }
  
  {
    const objName = 'ground'
    const width = 159;
    const height = 159;
    const widthSegments = 2;
    const heightSegments = 2;
    addMySolidGeometry(0, -1, new THREE.PlaneGeometry(width, height, widthSegments, heightSegments), objName);      

    const ground = getObj(objName)
    ground.rotation.x = Math.PI * -.5;
    const system = getObj('system')
    system.add(ground)
  }
  // const groundGeometry = new THREE.PlaneGeometry(50, 50);
  // const groundMaterial = new THREE.MeshPhongMaterial({color: 0xCC8866});
  // const groundMesh = new THREE.Mesh(groundGeometry, groundMaterial);
  // groundMesh.rotation.x = Math.PI * -.5;
  // groundMesh.receiveShadow = true;
  // scene.add(groundMesh);
  {
    const objName = 'player1'
    const shape = new THREE.Shape();
    const x = -2.5;
    const y = -5;
    shape.moveTo(x + 2.5, y + 2.5);
    shape.bezierCurveTo(x + 2.5, y + 2.5, x + 2, y, x, y);
    shape.bezierCurveTo(x - 3, y, x - 3, y + 3.5, x - 3, y + 3.5);
    shape.bezierCurveTo(x - 3, y + 5.5, x - 1.5, y + 7.7, x + 2.5, y + 9.5);
    shape.bezierCurveTo(x + 6, y + 7.7, x + 8, y + 4.5, x + 8, y + 3.5);
    shape.bezierCurveTo(x + 8, y + 3.5, x + 8, y, x + 5, y);
    shape.bezierCurveTo(x + 3.5, y, x + 2.5, y + 2.5, x + 2.5, y + 2.5);
    addMySolidGeometry(-5, -0.5, new THREE.ShapeGeometry(shape), objName);

    const player1 = getObj(objName)
    const system = getObj('system')
    system.add(player1)
  }

  {
    const objName = 'player2'
    const shape = new THREE.Shape();
    const x = -2.5;
    const y = -5;
    shape.moveTo(x + 2.5, y + 2.5);
    shape.bezierCurveTo(x + 2.5, y + 2.5, x + 2, y, x, y);
    shape.bezierCurveTo(x - 3, y, x - 3, y + 3.5, x - 3, y + 3.5);
    shape.bezierCurveTo(x - 3, y + 5.5, x - 1.5, y + 7.7, x + 2.5, y + 9.5);
    shape.bezierCurveTo(x + 6, y + 7.7, x + 8, y + 4.5, x + 8, y + 3.5);
    shape.bezierCurveTo(x + 8, y + 3.5, x + 8, y, x + 5, y);
    shape.bezierCurveTo(x + 3.5, y, x + 2.5, y + 2.5, x + 2.5, y + 2.5);
    addMySolidGeometry(5, -0.5, new THREE.ShapeGeometry(shape), objName);

    const player2 = getObj(objName)
    player2.rotation.x = Math.PI * -1;
    const system = getObj('system')
    system.add(player2)
  }

  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(time) {
    time *= 0.001;

    if (resizeRendererToDisplaySize(renderer)) {
      const canvas = renderer.domElement;
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();
    }

    objects.forEach((obj, ndx) => {
      const speed = .1 + ndx * .05;
      const rot = time * speed;
      obj.rotation.x = rot;
      obj.rotation.y = rot;
    });

    const system = getObj('system')
    system.rotation.y = time * 0.05;

    const player1 = getObj('player1')
    player1.rotation.x = time * 0.5
    player1.rotation.y = time * 0.5
    player1.position.x += 0.01

    const player2 = getObj('player2')
    player2.rotation.x = time * 0.5
    player2.rotation.y = time * 0.5
    player2.position.x -= 0.01

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }

  requestAnimationFrame(render);
}

main();

下一步應該是,怎樣面向對象開發three.js應用,如何去模塊化。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章