Raycaster Mouse Picking
Description
This is a basic example of using the Raycaster to mouse pick objects in the scene.
The sphere and plane are not added to the pickableObjects array that is used by the Raycaster, so they will not be mouse picked.
The plane also receives shadows while everything else only casts shadows.
This examples demonstrates,
- Loading a GLB scene, traversing the child objects to add or copy properties for later use.
- Using the Raycaster to detect if the mouse is over certain objects and changing there material.
Code
./src/server/server.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | import express from "express" import path from "path" import http from "http" const port: number = 3000 class App { private server: http.Server private port: number constructor(port: number) { this.port = port const app = express() app.use(express.static(path.join(__dirname, '../client'))) app.use('/build/three.module.js', express.static(path.join(__dirname, '../../node_modules/three/build/three.module.js'))) app.use('/jsm/controls/OrbitControls', express.static(path.join(__dirname, '../../node_modules/three/examples/jsm/controls/OrbitControls.js'))) app.use('/jsm/loaders/GLTFLoader', express.static(path.join(__dirname, '../../node_modules/three/examples/jsm/loaders/GLTFLoader.js'))) app.use('/jsm/libs/stats.module', express.static(path.join(__dirname, '../../node_modules/three/examples/jsm/libs/stats.module.js'))) this.server = new http.Server(app); } public Start() { this.server.listen(this.port, () => { console.log(`Server listening on port ${this.port}.`) }) } } new App(port).Start() |
./src/client/client.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | import * as THREE from '/build/three.module.js' import { OrbitControls } from '/jsm/controls/OrbitControls' import { GLTFLoader } from '/jsm/loaders/GLTFLoader' import Stats from '/jsm/libs/stats.module' const scene: THREE.Scene = new THREE.Scene() const axesHelper = new THREE.AxesHelper(5) scene.add(axesHelper) var light = new THREE.SpotLight() light.position.set(12.5, 12.5, 12.5) light.castShadow = true light.shadow.mapSize.width = 1024; light.shadow.mapSize.height = 1024; scene.add(light) const camera: THREE.PerspectiveCamera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000) camera.position.x = 15 camera.position.y = 15 camera.position.z = 15 const renderer: THREE.WebGLRenderer = new THREE.WebGLRenderer() renderer.shadowMap.enabled = true renderer.outputEncoding = THREE.sRGBEncoding renderer.setSize(window.innerWidth, window.innerHeight) document.body.appendChild(renderer.domElement) const controls = new OrbitControls(camera, renderer.domElement) const pickableObjects: THREE.Mesh[] = new Array() let intersectedObject: THREE.Object3D | null let originalMaterials: { [id: string]: THREE.Material | THREE.Material[] } = {} const highlightedMaterial = new THREE.MeshBasicMaterial({ wireframe: true, color: 0x00ff00 }) const loader = new GLTFLoader() loader.load( 'models/simplescene.glb', function (gltf) { gltf.scene.traverse(function (child) { if ((<THREE.Mesh>child).isMesh) { let m = <THREE.Mesh>child //the sphere and plane will not be mouse picked. THe plane will receive shadows while everything else casts shadows. switch (m.name) { case "Plane": m.receiveShadow = true break case "Sphere": m.castShadow = true break default: m.castShadow = true pickableObjects.push(m) //store reference to original materials for later originalMaterials[m.name] = (m as THREE.Mesh).material; } } }) scene.add(gltf.scene) }, (xhr) => { console.log((xhr.loaded / xhr.total * 100) + '% loaded') }, (error) => { console.log(error); } ); window.addEventListener('resize', onWindowResize, false) function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight camera.updateProjectionMatrix() renderer.setSize(window.innerWidth, window.innerHeight) render() } const raycaster = new THREE.Raycaster() let intersects: THREE.Intersection[] document.addEventListener('mousemove', onDocumentMouseMove, false); function onDocumentMouseMove(event: MouseEvent) { raycaster.setFromCamera({ x: (event.clientX / renderer.domElement.clientWidth) * 2 - 1, y: -(event.clientY / renderer.domElement.clientHeight) * 2 + 1 }, camera); intersects = raycaster.intersectObjects(pickableObjects, false); if (intersects.length > 0) { intersectedObject = intersects[0].object } else { intersectedObject = null } pickableObjects.forEach((o: THREE.Mesh, i) => { if (intersectedObject && intersectedObject.name === o.name) { pickableObjects[i].material = highlightedMaterial } else { pickableObjects[i].material = originalMaterials[o.name] } }) } const stats = Stats() document.body.appendChild(stats.dom) var animate = function () { requestAnimationFrame(animate) controls.update() render() stats.update() }; function render() { renderer.render(scene, camera) } animate(); |