unable to get world position from mouse position using three.js

5 days ago 19
ARTICLE AD BOX

I have a WebGL canvas inside of a div and I'm trying to find the mouse position inside the "world", given the mouse position inside of the div. using that position, I'm trying to rotate a model to face the world coordinate given.

I'm using the answer provided here: Mouse / Canvas X, Y to Three.js World X, Y, Z , but it's not working for me, for some reason...

I've set it up so that when the mouse moves it will try to find out where it is in the world and then rotate one of the assets and create+place a sphere at that point, so I can visually see where it thinks the mouse is. Everything works, except getting the coordinate in the world. No matter where the mouse is, the x coordinate seems to be stuck between 20 and 75 and the y coordinate is stuck between 15 and 40. This keeps the character facing generally in the same direction, which is also not where the mouse is.

I can't figure out what I've done wrong here, I'd appreciate any help.

Here is how my camera is setup ( it's a pseudo isometric perspective )

constructor( id, env_id, env_element, assets, width, height, app_settings ) { super(); this.id = id; this.env_id = env_id; this.env_element = env_element; this.assets = assets; this.temp_assets = {}; this.width = width; this.height = height; this.scene = new THREE.Scene(); this.renderer = new THREE.WebGLRenderer(app_settings); this.camera = new THREE.PerspectiveCamera(10, (width/height), .1, 3000); this.lights = {}; this.lights["amb_light"] = new THREE.AmbientLight(0xffffff, .2); this.lights["top_light"] = new THREE.DirectionalLight(0xffffff, 1); this.camera.position.set(100, 100, 100); this.camera.lookAt(0,0,0); this.lights['top_light'].position.set(100, 1000, 0); this.lights['top_light'].castShadow = true; this.updateSize(); this.addLights(); this.env_element.appendChild(this.renderer.domElement); this.init(); };

here is how I normalize the mouse

normalizeMouse( coord ) { var mouse = new THREE.Vector3(); var rect = this.renderer.domElement.getBoundingClientRect(); mouse.x = ((coord.x-rect.left)/rect.width)*2 - 1; mouse.y = ((coord.y-rect.top)/rect.height)*2 + 1; mouse.z = .5; return mouse; }

here is where i get the world coordinate

rotateAssetToMouse( asset_id, coord ) { var vector = this.normalizeMouse(coord); var pos = new THREE.Vector3(); vector.unproject(this.camera); vector.sub(this.camera.position).normalize(); var dist = -this.camera.position.z/vector.z; pos.copy(this.camera.position).add(vector.multiplyScalar(dist)); console.log("pos: "+pos.x+", "+pos.y+", "+pos.z); this.drawPoint(new Geometry_Coordinate(pos.x, pos.y, pos.z)); var angle = Utility_Math.findAngle(this.assets[asset_id].getPosition(), new Geometry_Coordinate(pos.x, pos.y, pos.z)); console.log(angle); this.assets[asset_id]['model'].rotation.y = Utility_Math.DEGRAD*angle; };

here is how i draw the point ( this dynamically creates a small sphere and places it at the coordinate, a clean up process later removes it from the scene )

drawPoint( point ) { var id = "P"+Object.keys(this.temp_assets).length; var point_geo = new THREE.SphereGeometry(.3, 16, 16); var point_mat = new THREE.MeshBasicMaterial({ 'color': 0xFF0000, 'transparent': true, 'opacity': 1 }); var point_3D = new THREE.Mesh(point_geo, point_mat); point_3D.position.set(point.x, point.y, point.z); this.temp_assets[id] = { 'id': id, 'model': point_3D, 'start': this.animation_frames, 'duration': 40 }; this.scene.add(point_3D); };

[EDIT:]

I made this adjustment to the code, however, it still doesn't work. No matter where the mouse goes in the DIV, it's locked into a region of the world that doesn't reflect the expected result.

rotateAssetToMouse( asset_id, coord ) { var pos = this.findMousePointInWorld(coord); this.assets[asset_id]['model'].lookAt(new THREE.Vector3(pos.x, 0, pos.z); console.log("pos: "+pos.x+", "+pos.y+", "+pos.z); var settings = {"temp": true, "duration": 100, "color": Utility_Dictionary.colors['red']}; this.drawPoint(new Geometry_Coordinate(pos.x, pos.y, pos.z), settings); this.drawLine(this.assets[asset_id]['data'].getPosition(), pos, settings); }; normalizeMouse( coord ) { var mouse = new THREE.Vector2(); var rect = this.renderer.domElement.getBoundingClientRect(); mouse.x = ((coord.x-rect.left)/rect.width)*2 - 1; mouse.y = ((coord.y-rect.top)/rect.height)*2 + 1; return mouse; } findMousePointInWorld( coord ) { var mouse = this.normalizeMouse(coord); var plane = new THREE.Plane(new THREE.Vector3(0,1,0),0); var raycaster = new THREE.Raycaster(); var target = new THREE.Vector3(); raycaster.setFromCamera(mouse, this.camera); raycaster.ray.intersectPlane(plane, target); return target; }

[EDIT]

the website can be viewed here:

https://cms.smg-solutions.com/work-shop/get-help.php

here is the full code that can be copied and pasted to test it out on your own machine.

JS file

import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; class Utility_Dictionary { // #FF0000 normal color code with (100% opacity) // #FF000088 add 2 digits to the end for transparancy (~47% opacity) static colors = { "red": "#FF0000", "darkred": "#800000", "lightred": "#FF8080", "white": "#FFFFFF", "lightgrey": "#CCCCCC", "darkgrey": "#666666", "black": "#000000", "green": "#00FF00", "darkgreen": "#008000", "lightgreen": "#80FF80", "brown": "#404000", "darkbrown": "#400000", "lightbrown": "#808000", "yellow": "#FFFF00", "darkyellow": "#C0C000", "lightyellow": "#FFFF80", "blue": "#0000FF", "darkblue": "#000080", "lightblue": "#4040FF", "purple": "#800080", "darkpurple": "#400040", "lightpurple": "#FF00FF" }; class Utility_Generator_IDs { static _instance = null; #_id_padding = 8; #_last_id = 0; constructor() { if( Utility_Generator_IDs._instance != null ) { throw new Error("Singleton already declared."); } Utility_Generator_IDs._instance = this; this._map = {}; this._id_padding = 8; this._last_id = 0; }; static getInstance() { if( Utility_Generator_IDs._instance == null ) { Utility_Generator_IDs._instance = new Utility_Generator_IDs(); } return Utility_Generator_IDs._instance; }; getNextID() { this._last_id = Number(this._last_id)+1; this._last_id = String(this._last_id); while( this._last_id.length < this._id_padding ) { this._last_id = "0"+this._last_id; } return this._last_id; }; }; class Geometry_Coordinate { x_value; y_value; z_value; constructor( x, y, z ) { this.x_value = x; this.y_value = y; this.z_value = z; }; duplicate() { return new Geometry_Coordinate(this.x_value, this.y_value, this.z_value); }; adjustCoordinate( point, positive ) { if( positive ) { this.x_value += point.getX_Value(); this.y_value += point.getY_Value(); this.z_value += point.getZ_Value(); } else { this.x_value -= point.getX_Value(); this.y_value -= point.getY_Value(); this.z_value -= point.getZ_Value(); } return this; }; scaleCoordinate( point ) { this.x_value *= point.getX_Value(); this.y_value *= point.getY_Value(); this.z_value *= point.getZ_Value(); return this; }; rotateCoordinate( anchor, angle ) { this.adjustCoordinate(anchor, false); this.x_value = this.x_value*Math.cos(angle*Utility_Math.DEGRAD)-(this.y_value)*Math.sin(angle*Utility_Math.DEGRAD); this.y_value = this.x_value*Math.sin(angle*Utility_Math.DEGRAD)+(this.y_value)*Math.cos(angle*Utility_Math.DEGRAD); this.z_value = this.z_value; // implement z rotation if needed, no known required or experimental usage at the moment this.adjustCoordinate(anchor, true); return this; }; draw( view, settings ) { view.drawPoint(this, settings); return this; }; getX_Value() { return this.x_value; }; getY_Value() { return this.y_value; }; getZ_Value() { return this.z_value; }; setX_Value( x_value ) { this.x_value = x_value; return this; }; setY_Value( y_value ) { this.y_value = y_value; return this; }; setZ_Value( z_value ) { this.z_value = z_value; return this; }; cleanUp() { this.x_value = 0; this.y_value = 0; this.z_value = 0; return; }; }; class View_WebGL { id; env_id; env_element; width; height; resize; assets; temp_assets; scene; renderer; camera; lights; animation_frames; id_generator; debug; constructor( id, env_id, env_element, assets, width, height, resize, app_settings ) { this.id = id; this.env_id = env_id; this.env_element = env_element; this.assets = assets; this.temp_assets = {}; this.width = width; this.height = height; this.resize = resize; this.id_generator = Utility_Generator_IDs.getInstance(); this.debug = true; this.scene = new THREE.Scene(); this.renderer = new THREE.WebGLRenderer(app_settings); this.camera = new THREE.PerspectiveCamera(10, (width/height), .1, 3000); this.lights = {}; this.lights["amb_light"] = new THREE.AmbientLight(0xffffff, .2); this.lights["top_light"] = new THREE.DirectionalLight(0xffffff, 1); this.camera.position.set(100, 100, 100); this.camera.lookAt(0,0,0); this.lights['top_light'].position.set(100, 1000, 0); this.lights['top_light'].castShadow = true; this.lights['top_light'].shadow.bias = .0001; this.lights['top_light'].shadow.mapSize.width = 1024; this.lights['top_light'].shadow.mapSize.height = 1024; this.lights['amb_light'].castShadow = false; this.updateSize(); this.addLights(); this.env_element.appendChild(this.renderer.domElement); this.init(); }; init() { if( !this.initialized ) { this.animation_frames = 0; this.initialized = true; if( this.resize ) { this.handlers['resize'] = (event) => this.resizeEvent(event, this); this.listeners['resize'] = window.addEventListener("resize", this.handlers['resize']); } // add long term assets // ... } this.updateSize(); // place a grid on the floor if( this.debug ) { var settings = {"temp": false, "duration": 0, "color": Utility_Dictionary.colors["red"]}; this.drawLine(new Geometry_Coordinate(0, 0, 0), new Geometry_Coordinate(0, 0, -100), settings); this.drawLine(new Geometry_Coordinate(-25, 0, 0), new Geometry_Coordinate(-25, 0, -100), settings); this.drawLine(new Geometry_Coordinate(-50, 0, 0), new Geometry_Coordinate(-50, 0, -100), settings); this.drawLine(new Geometry_Coordinate(-75, 0, 0), new Geometry_Coordinate(-75, 0, -100), settings); this.drawLine(new Geometry_Coordinate(-100, 0, 0), new Geometry_Coordinate(-100, 0, -100), settings); this.drawLine(new Geometry_Coordinate(0, 0, 0), new Geometry_Coordinate(-100, 0, 0), settings); this.drawLine(new Geometry_Coordinate(0, 0, -25), new Geometry_Coordinate(-100, 0, -25), settings); this.drawLine(new Geometry_Coordinate(0, 0, -50), new Geometry_Coordinate(-100, 0, -50), settings); this.drawLine(new Geometry_Coordinate(0, 0, -75), new Geometry_Coordinate(-100, 0, -75), settings); this.drawLine(new Geometry_Coordinate(0, 0, -100), new Geometry_Coordinate(-100, 0, -100), settings); settings['color'] = Utility_Dictionary.colors["green"]; this.drawLine(new Geometry_Coordinate(100, 0, 0), new Geometry_Coordinate(0, 0, 0), settings); this.drawLine(new Geometry_Coordinate(100, 0, 25), new Geometry_Coordinate(0, 0, 25), settings); this.drawLine(new Geometry_Coordinate(100, 0, 50), new Geometry_Coordinate(0, 0, 50), settings); this.drawLine(new Geometry_Coordinate(100, 0, 75), new Geometry_Coordinate(0, 0, 75), settings); this.drawLine(new Geometry_Coordinate(100, 0, 100), new Geometry_Coordinate(0, 0, 100), settings); this.drawLine(new Geometry_Coordinate(0, 0, 100), new Geometry_Coordinate(0, 0, 0), settings); this.drawLine(new Geometry_Coordinate(25, 0, 100), new Geometry_Coordinate(25, 0, 0), settings); this.drawLine(new Geometry_Coordinate(50, 0, 100), new Geometry_Coordinate(50, 0, 0), settings); this.drawLine(new Geometry_Coordinate(75, 0, 100), new Geometry_Coordinate(75, 0, 0), settings); this.drawLine(new Geometry_Coordinate(100, 0, 100), new Geometry_Coordinate(100, 0, 0), settings); settings['color'] = Utility_Dictionary.colors["purple"]; this.drawLine(new Geometry_Coordinate(0, 0, 25), new Geometry_Coordinate(-100, 0, 25), settings); this.drawLine(new Geometry_Coordinate(0, 0, 50), new Geometry_Coordinate(-100, 0, 50), settings); this.drawLine(new Geometry_Coordinate(0, 0, 75), new Geometry_Coordinate(-100, 0, 75), settings); this.drawLine(new Geometry_Coordinate(0, 0, 100), new Geometry_Coordinate(-100, 0, 100), settings); this.drawLine(new Geometry_Coordinate(25, 0, 0), new Geometry_Coordinate(25, 0, -100), settings); this.drawLine(new Geometry_Coordinate(50, 0, 0), new Geometry_Coordinate(50, 0, -100), settings); this.drawLine(new Geometry_Coordinate(75, 0, 0), new Geometry_Coordinate(75, 0, -100), settings); this.drawLine(new Geometry_Coordinate(100, 0, 0), new Geometry_Coordinate(100, 0, -100), settings); this.drawLine(new Geometry_Coordinate(-25, 0, 100), new Geometry_Coordinate(-25, 0, 0), settings); this.drawLine(new Geometry_Coordinate(-50, 0, 100), new Geometry_Coordinate(-50, 0, 0), settings); this.drawLine(new Geometry_Coordinate(-75, 0, 100), new Geometry_Coordinate(-75, 0, 0), settings); this.drawLine(new Geometry_Coordinate(-100, 0, 100), new Geometry_Coordinate(-100, 0, 0), settings); this.drawLine(new Geometry_Coordinate(100, 0, -25), new Geometry_Coordinate(0, 0, -25), settings); this.drawLine(new Geometry_Coordinate(100, 0, -50), new Geometry_Coordinate(0, 0, -50), settings); this.drawLine(new Geometry_Coordinate(100, 0, -75), new Geometry_Coordinate(0, 0, -75), settings); this.drawLine(new Geometry_Coordinate(100, 0, -100), new Geometry_Coordinate(0, 0, -100), settings); settings['color'] = Utility_Dictionary.colors["black"]; this.drawPoint(new Geometry_Coordinate(0,0,0), settings); this.drawPoint(new Geometry_Coordinate(25,0,0), settings); this.drawPoint(new Geometry_Coordinate(50,0,0), settings); this.drawPoint(new Geometry_Coordinate(75,0,0), settings); this.drawPoint(new Geometry_Coordinate(100,0,0), settings); this.drawPoint(new Geometry_Coordinate(-25,0,0), settings); this.drawPoint(new Geometry_Coordinate(-50,0,0), settings); this.drawPoint(new Geometry_Coordinate(-75,0,0), settings); this.drawPoint(new Geometry_Coordinate(-100,0,0), settings); this.drawPoint(new Geometry_Coordinate(0,0,25), settings); this.drawPoint(new Geometry_Coordinate(0,0,50), settings); this.drawPoint(new Geometry_Coordinate(0,0,75), settings); this.drawPoint(new Geometry_Coordinate(0,0,100), settings); this.drawPoint(new Geometry_Coordinate(0,0,-25), settings); this.drawPoint(new Geometry_Coordinate(0,0,-50), settings); this.drawPoint(new Geometry_Coordinate(0,0,-75), settings); this.drawPoint(new Geometry_Coordinate(0,0,-100), settings); } return this; }; normalizeMouse( coord ) { var mouse = new THREE.Vector2(); var rect = this.renderer.domElement.getBoundingClientRect(); mouse.x = (((coord.getX_Value()-rect.left)/rect.width)*2) - 1; mouse.y = (((coord.getY_Value()-rect.top)/rect.height)*2) + 1; return mouse; } findMousePointInWorld( coord ) { // convert mouse coordinate to world coordinate var mouse = this.normalizeMouse(coord); /* // https://stackoverflow.com/questions/52141079/three-js-determining-world-coordinates-of-mouse-position mouse.unproject(this.camera); var direction = mouse.sub(this.camera.position).normalize(); var distance = -this.camera.position.z/direction.z; var scaled = direction.multiplyScalar(distance); var position = this.camera.position.clone().add(scaled); */ var raycaster = new THREE.Raycaster(); var plane_x = new THREE.Plane(new THREE.Vector3(1,0,0),0); var plane_y = new THREE.Plane(new THREE.Vector3(0,1,0),0); var plane_z = new THREE.Plane(new THREE.Vector3(0,0,1),0); var position = new THREE.Vector3(); raycaster.setFromCamera(mouse, this.camera); raycaster.ray.intersectPlane(plane_x, position); console.log("x intersect"); console.log(position); raycaster.ray.intersectPlane(plane_y, position); console.log("y intersect"); console.log(position); raycaster.ray.intersectPlane(plane_z, position); console.log("z intersect"); console.log(position); return new Geometry_Coordinate(position.x, 0, position.z); } updateSize() { if( this.resize ) { this.width = window.innerWidth-this.padding; this.height = window.innerHeight-this.padding; } this.camera.aspect = this.width/this.height; this.camera.updateProjectionMatrix(); this.renderer.setSize(this.width, this.height, false); return this; }; resizeEvent = function( e, parent_reference ) { parent_reference.updateSize(parent_reference); return this; }; rotateAssetToMouse( asset_id, coord ) { var pos = this.findMousePointInWorld(coord); //this.assets[asset_id]['model'].lookAt(new THREE.Vector3(pos.getX_Value(), 0, pos.getZ_Value())) console.log("position: "+pos.getX_Value()+", "+pos.getY_Value()+", "+pos.getZ_Value()); var settings = {"temp": true, "duration": 60, "color": Utility_Dictionary.colors["darkred"]}; this.drawPoint(pos, settings); this.drawLine(new Geometry_Coordinate(0,0,0), pos, settings); }; animate() { ++this.animation_frames; // loop through temp assets and start the fading and removing process Object.keys(this.temp_assets).forEach(key => { if( this.temp_assets[key] != null ) { var length_alive = this.animation_frames-this.temp_assets[key]['start']; if( length_alive > this.temp_assets[key]['duration'] ) { this.scene.remove(this.temp_assets[key]['model']); this.temp_assets[key] = null } else { var opacity = 1-(length_alive/this.temp_assets[key]['duration']); this.temp_assets[key]['model'].traverse((child) => { if (child.isMesh) { child.material.transparent = true; child.material.opacity = opacity.toPrecision(1); child.material.needsUpdate = true; } }); } } }); requestAnimationFrame(this.animate.bind(this)); this.renderer.render(this.scene, this.camera); return this; }; addLights() { Object.keys(this.lights).forEach(key => { this.scene.add(this.lights[key]); }); return this; }; drawPoint( point, settings ) { var id_generator var id = "DP"+this.id_generator.getNextID(); var point_geo = new THREE.SphereGeometry(.5, 16, 16); var point_mat = new THREE.MeshBasicMaterial({ 'color': settings['color'], 'transparent': true, 'opacity': 1 }); var point_3D = new THREE.Mesh(point_geo, point_mat); point_3D.position.set(point.getX_Value(), point.getY_Value(), point.getZ_Value()); var asset = { 'id': id, 'model': point_3D, 'start': this.animation_frames, 'duration': settings['duration'] }; if( settings['temp'] ) { this.temp_assets[id] = asset; } else { this.assets[id] = asset; } console.log("draw point: "+id+" ("+point.getX_Value()+", "+point.getY_Value()+", "+point.getZ_Value()+")"); this.scene.add(point_3D); }; drawLine( point_1, point_2, settings ) { var id_generator var id = "DL"+this.id_generator.getNextID(); var points = new Array(); points.push(new THREE.Vector3(point_1.getX_Value(), point_1.getY_Value(), point_1.getZ_Value())); points.push(new THREE.Vector3(point_2.getX_Value(), point_2.getY_Value(), point_2.getZ_Value())); var line_mat = new THREE.LineBasicMaterial({ 'color': settings['color'], 'transparent': true, 'opacity': 1 }); var line_geo = new THREE.BufferGeometry().setFromPoints(points); var line_3D = new THREE.Line(line_geo, line_mat); var asset = { 'id': id, 'model': line_3D, 'start': this.animation_frames, 'duration': settings['duration'] }; if( settings['temp'] ) { this.temp_assets[id] = asset; } else { this.assets[id] = asset; } console.log("draw line: "+id+" ("+point_1.getX_Value()+", "+point_1.getY_Value()+", "+point_1.getZ_Value()+") ("+point_2.getX_Value()+", "+point_2.getY_Value()+", "+point_2.getZ_Value()+")"); this.scene.add(line_3D); }; getId() { return this.id; }; getHeight() { return this.height; }; getWidth() { return this.width; }; getPadding() { return this.padding; }; setHeigh(height) { this.height = height; return this; }; setWidth(width) { this.width = width; return this; }; setPadding(padding) { this.padding = padding; return this; }; }; var env_id = "test-env"; var id_generator = Utility_Generator_IDs.getInstance(); var element = document.getElementById(env_id); var test_view = new View_WebGL("V"+id_generator.getNextID(), env_id, element, {}, 1200, 650, false, {"antialias": true, "alpha": true}); test_view.animate(); function mousemove(e, parent_ref) { test_view.rotateAssetToMouse("0", new Geometry_Coordinate(e.clientX, e.clientY, 0)); } var handlers = {}; var listeners = {}; handlers['mousemove'] = (event) => mousemove(event, this); listeners['mousemove'] = window.addEventListener("mousemove", handlers['mousemove']);

html file

<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <title>Three.js Learning Environment</title> <meta name="title" content="Three.js Learning Environment" /> <meta name="googlebot" content="noindex" /> <meta name="robots" content="noindex" /> <meta http-equiv="cache-control" content="no-cache" /> <meta http-equiv="pragma" content="no-cache" /> <script type="importmap"> { "imports": { "three": "./three.js/build/three.module.js", "three/addons/": "./three.js/examples/jsm/" } } </script> <style> body { margin: 0px; padding: 0px; } #test-env { display: block; width: 1200px; height: 650px; border: 1px solid #333; margin: 0px; padding: 0px; } </style> </head> <body> <div id="test-env"></div> <script type="module" src="get-help.js"></script> </body> </html>
Read Entire Article