“This is the 12th day of my participation in the First Challenge 2022. For details: First Challenge 2022”

Introduction to the

This section focuses on how to combine geometry into a tank-shaped object. Keep the object moving in the direction we want it to, and keep the head forward. Add multiple cameras to show different scenes according to camera switching.

Building foundation

  • Importing components, instantiating renderers.
      // Official website address
      import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r132/build/three.module.js'
      import { OrbitControls } from 'https://threejsfundamentals.org/threejs/resources/threejs/r132/examples/jsm/controls/OrbitControls.js'

      const canvas = document.querySelector('#c2d')
      / / the renderer
      const renderer = new THREE.WebGLRenderer({ canvas, antialias: true })
Copy the code
  • Instantiate the global camera. This section switches scenarios and creates common methods for camera instantiation.
    /** * Create camera public method ** /
    function makeCamera(fov = 40) {
    const aspect = 2 // the canvas default
    const zNear = 0.1
    const zFar = 1000
    return new THREE.PerspectiveCamera(fov, aspect, zNear, zFar)
    }
    const camera = makeCamera()
    // multiplyScalar() multiplies each element of the matrix by the parameters.
    camera.position.set(8.4.10).multiplyScalar(3)
    / / towards
    camera.lookAt(0.0.0)
    // Control the camera
    const controls = new OrbitControls(camera, canvas)
    controls.update()
Copy the code
  • Instantiate scenarios
    / / the scene
    const scene = new THREE.Scene()
Copy the code
  • Initialize light
    {
        / / direction of the light
        const light = new THREE.DirectionalLight(0xffffff.1)
        light.position.set(0.20.0)
        scene.add(light)
    }
    {
        / / direction of the light
        const light = new THREE.DirectionalLight(0xffffff.1)
        light.position.set(1.2.4)
        scene.add(light)
    }
Copy the code
  • Rendering function
      function render(time) {
        time *= 0.001

        // Load the renderer
        renderer.render(scene, camera)
        // Start animation
        requestAnimationFrame(render)
      }
      // Start rendering
      requestAnimationFrame(render)
Copy the code

Draw the object

Draw the ground

  • Create a plane and rotate it.
      {
        // Plane geometry
        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 * -0.5
        scene.add(groundMesh)
      }
Copy the code

Draw the tanks

  • When drawing multiple geometries to combine one object, we move the tank so that all geometries move. And here we’re going to analyze the object, and those parts are going to change together. Put all geometry that needs to change together into the local space (the child node changes after the parent node changes).
  1. Create a tank local space where all the geometry goes. To move this local space in global space is to move the entire tank.
  const tank = new THREE.Object3D();
  scene.add(tank);
Copy the code
  1. Create tires and chassis.
      // Create chassis
      const carWidth = 4
      const carHeight = 1
      const carLength = 8
      / / geometry
      const bodyGeometry = new THREE.BoxGeometry(carWidth, carHeight, carLength)
      const bodyMaterial = new THREE.MeshPhongMaterial({ color: 0x6688aa })
      const bodyMesh = new THREE.Mesh(bodyGeometry, bodyMaterial)
      bodyMesh.position.y = 1.4
      tank.add(bodyMesh)

      const wheelRadius = 1
      const wheelThickness = 0.5
      const wheelSegments = 36
      / / cylinder
      const wheelGeometry = new THREE.CylinderGeometry(
        wheelRadius, // The radius of the top circle of the cylinder
        wheelRadius, // The radius of the circle at the bottom of the cylinder
        wheelThickness, / / height
        wheelSegments // How many segments does the X-axis divide into
      )
      const wheelMaterial = new THREE.MeshPhongMaterial({ color: 0x888888 })
      // Determine the tire position according to the chassis
      const wheelPositions = [
        [-carWidth / 2 - wheelThickness / 2, -carHeight / 2, carLength / 3],
        [carWidth / 2 + wheelThickness / 2, -carHeight / 2, carLength / 3],
        [-carWidth / 2 - wheelThickness / 2, -carHeight / 2.0],
        [carWidth / 2 + wheelThickness / 2, -carHeight / 2.0],
        [-carWidth / 2 - wheelThickness / 2, -carHeight / 2, -carLength / 3],
        [carWidth / 2 + wheelThickness / 2, -carHeight / 2, -carLength / 3]]const wheelMeshes = wheelPositions.map((position) = > {
        const mesh = newTHREE.Mesh(wheelGeometry, wheelMaterial) mesh.position.set(... position) mesh.rotation.z =Math.PI * 0.5
        bodyMesh.add(mesh)
        return mesh
      })
Copy the code

  1. Added partial camera to chassis. Its parent node is the chassis, so its displacement and rotation are local space changes in the chassis.
      // Chassis local camera
      const tankCameraFov = 75
      const tankCamera = makeCamera(tankCameraFov)
      tankCamera.position.y = 3
      tankCamera.position.z = -6
      tankCamera.rotation.y = Math.PI
      bodyMesh.add(tankCamera)
Copy the code
    // Temporarily modify render chassis local camera
    // renderer.render(scene, camera)
    renderer.render(scene, tankCamera)
Copy the code

  1. Draw tank head
      / / tanks
      const domeRadius = 2
      const domeWidthSubdivisions = 12
      const domeHeightSubdivisions = 12
      const domePhiStart = 0
      const domePhiEnd = Math.PI * 2
      const domeThetaStart = 0
      const domeThetaEnd = Math.PI * 0.5
      const domeGeometry = new THREE.SphereGeometry(
        domeRadius,
        domeWidthSubdivisions,
        domeHeightSubdivisions,
        domePhiStart,
        domePhiEnd,
        domeThetaStart,
        domeThetaEnd
      )
      const domeMesh = new THREE.Mesh(domeGeometry, bodyMaterial)
      bodyMesh.add(domeMesh)
      domeMesh.position.y = 0.5

      / / dry
      const turretWidth = 0.5
      const turretHeight = 0.5
      const turretLength = 5
      const turretGeometry = new THREE.BoxGeometry(turretWidth, turretHeight, turretLength)
      const turretMesh = new THREE.Mesh(turretGeometry, bodyMaterial)
      const turretPivot = new THREE.Object3D()
      turretPivot.position.y = 0.5
      turretMesh.position.z = turretLength * 0.5
      turretPivot.add(turretMesh)
      bodyMesh.add(turretPivot)
Copy the code

Draw the target

  • The target is another object in the global scene. After the target is drawn we point the gun stem at the target.
/ / target
const targetGeometry = new THREE.SphereGeometry(0.5.36.36)
const targetMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00.flatShading: true })
const targetMesh = new THREE.Mesh(targetGeometry, targetMaterial)
const targetElevation = new THREE.Object3D()
const targetBob = new THREE.Object3D()
scene.add(targetElevation)
targetElevation.position.z = carLength * 2
targetElevation.position.y = 8
targetElevation.add(targetBob)
targetBob.add(targetMesh)

// Get the target global coordinates
const targetPosition = new THREE.Vector3()
targetMesh.getWorldPosition(targetPosition)
// The battery aims at the target
turretPivot.lookAt(targetPosition)

// Camera on target
const targetCamera = makeCamera()
targetCamera.position.y = 1
targetCamera.position.z = -2
targetCamera.rotation.y = Math.PI
targetBob.add(targetCamera)
Copy the code

Draw movement path

  • Create a smooth 2d spline using.splinecurve ().
      // Draw the moving path
      const curve = new THREE.SplineCurve([
        new THREE.Vector2(-10.20),
        new THREE.Vector2(-5.5),
        new THREE.Vector2(0.0),
        new THREE.Vector2(5, -5),
        new THREE.Vector2(10.0),
        new THREE.Vector2(5.10),
        new THREE.Vector2(-5.10),
        new THREE.Vector2(-10, -10),
        new THREE.Vector2(-15, -8),
        new THREE.Vector2(-10.20)])const points = curve.getPoints(50)
      const geometry = new THREE.BufferGeometry().setFromPoints(points)
      const material = new THREE.LineBasicMaterial({ color: 0xff0000 })
      const splineObject = new THREE.Line(geometry, material)
      splineObject.rotation.x = Math.PI * 0.5
      splineObject.position.y = 0.05
      scene.add(splineObject)
Copy the code

Add animation

  • In the loop rendering function, the tank is moved by changing the position of the object and rolling the tires.
  • First put the camera into the array and get the camera rendering switching scene with the corresponding subscript according to the number of cycles. You can also manually switch the render camera.
  1. Mobile tanks
    const targetPosition2 = new THREE.Vector3()
    const tankPosition = new THREE.Vector2()
    const tankTarget = new THREE.Vector2()
    function render(time) {...// Move the target up and down
        targetBob.position.y = Math.sin(time * 2) * 4
        targetMaterial.emissive.setHSL((time * 10) % 1.1.0.25)
        targetMaterial.color.setHSL((time * 10) % 1.1.0.25)
        // Get the target global coordinates
        targetMesh.getWorldPosition(targetPosition2)
        // The battery aims at the target
        turretPivot.lookAt(targetPosition2)

        // Move tanks according to the route
        const tankTime = time * 0.05
        curve.getPointAt(tankTime % 1, tankPosition)
        // Get the point in front of the tank in the path for the tank head forward
        curve.getPointAt((tankTime + 0.01) % 1, tankTarget)
        / / displacement
        tank.position.set(tankPosition.x, 0, tankPosition.y)
        tank.lookAt(tankTarget.x, 0, tankTarget.y)
        ...
    }
Copy the code

  1. Switch scenarios.
const cameras = [
    { cam: camera, desc: 'Global camera' },
    { cam: targetCamera, desc: 'Camera on target' },
    { cam: tankCamera, desc: 'Chassis local camera'}]function render(time) {...// Switch the camera
    const camera1 = cameras[time % cameras.length | 0]
    // Get the global coordinates of the tank
    tank.getWorldPosition(targetPosition2)
    // Look at the tank
    targetCamera.lookAt(targetPosition2)

    // Load the renderer tankCamera targetCamera
    renderer.render(scene, camera1.cam)
    ...
}
Copy the code

conclusion

In three.js, Object3D and other object class nodes should be flexibly used to synchronize the movement, rotation and scaling of objects to child nodes. For example, in this section, if Object3D is not used, we need to calculate the global coordinate displacement of each combination, which requires some complicated mathematics to achieve, but Object3D can reduce this series of troublesome calculation.