“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).
- 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
- 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
- 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
- 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.
- 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
- 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.