Three.js:确定鼠标位置的世界坐标
Posted
技术标签:
【中文标题】Three.js:确定鼠标位置的世界坐标【英文标题】:Three.js: Determining World coordinates of mouse position 【发布时间】:2019-02-07 23:58:31 【问题描述】:我有一个带点的 Three.js 场景,我试图弄清楚我的点的位置和屏幕坐标之间的关系。我以为我可以使用 @WestLangley 提供给 previous question 的函数,但实现这个函数引起了一些混乱。
在下面的场景中,我将左侧和最右侧点的 x 坐标存储在 world.bb.x
中,并在每次鼠标移动时记录光标的世界坐标。但是,当我将鼠标移到最左边和最右边的点时,世界坐标与 world.bb.x
中的最小或最大 x 坐标值不匹配,这是我所期望的。
其他人是否知道我可以在任何给定时间找出光标的世界坐标?非常感谢其他人可以提供的任何帮助!
function World()
this.scene = this.getScene();
this.camera = this.getCamera();
this.renderer = this.getRenderer();
this.controls = this.getControls();
this.color = new THREE.Color();
this.addPoints();
this.render();
World.prototype.getScene = function()
var scene = new THREE.Scene();
scene.background = new THREE.Color(0xefefef);
return scene;
World.prototype.getCamera = function()
var renderSize = getRenderSize(),
aspectRatio = renderSize.w / renderSize.h,
camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 100000);
camera.position.set(0, 1, -10);
return camera;
World.prototype.getRenderer = function()
var renderSize = getRenderSize(),
renderer = new THREE.WebGLRenderer(antialias: true);
renderer.setPixelRatio(window.devicePixelRatio); // retina displays
renderer.setSize(renderSize.w, renderSize.h); // set w,h
find('#gl-target').appendChild(renderer.domElement);
return renderer;
World.prototype.getControls = function()
var controls = new THREE.TrackballControls(this.camera, this.renderer.domElement);
controls.zoomSpeed = 0.4;
controls.panSpeed = 0.4;
return controls;
World.prototype.render = function()
requestAnimationFrame(this.render.bind(this));
this.renderer.render(this.scene, this.camera);
this.controls.update();
World.prototype.getMouseWorldCoords = function(e)
var vector = new THREE.Vector3(),
camera = world.camera,
x = (e.clientX / window.innerWidth) * 2 - 1,
y = (e.clientY / window.innerHeight) * 2 + 1;
vector.set(x, y, 0.5);
vector.unproject(camera);
var direction = vector.sub(camera.position).normalize(),
distance = - camera.position.z / direction.z,
scaled = direction.multiplyScalar(distance),
coords = camera.position.clone().add(scaled);
return
x: coords.x, y: coords.y,
;
World.prototype.addPoints = function()
// this geometry builds a blueprint and many copies of the blueprint
var IBG = THREE.InstancedBufferGeometry,
BA = THREE.BufferAttribute,
IBA = THREE.InstancedBufferAttribute,
Vec3 = THREE.Vector3,
Arr = Float32Array;
// add data for each observation; n = num observations
var geometry = new IBG(),
n = 10000,
rootN = n**(1/2),
// find max min for each dim to center camera
xMax = Number.NEGATIVE_INFINITY,
xMin = Number.POSITIVE_INFINITY,
yMax = Number.NEGATIVE_INFINITY,
yMin = Number.POSITIVE_INFINITY;
var translations = new Arr(n * 3),
colors = new Arr(n * 3),
uidColors = new Arr(n * 3),
translationIterator = 0,
colorIterator = 0,
uidColorIterator = 0;
var colorMap = this.getColorMap();
for (var i=0; i<n; i++)
var x = Math.sin(i) * 4,
y = Math.floor(i / (n/20)) * 0.3,
color = colorMap[ Math.floor(i / (n/20)) ],
uidColor = this.color.setHex(i + 1);
if (x > xMax) xMax = x;
if (x < xMin) xMin = x;
if (y > yMax) yMax = y;
if (y < yMin) yMin = y;
translations[translationIterator++] = x;
translations[translationIterator++] = y;
translations[translationIterator++] = 0;
colors[colorIterator++] = color.r / 255;
colors[colorIterator++] = color.g / 255;
colors[colorIterator++] = color.b / 255;
uidColors[uidColorIterator++] = uidColor.r;
uidColors[uidColorIterator++] = uidColor.g;
uidColors[uidColorIterator++] = uidColor.b;
// store the min and max coords in each dimension
this.bb =
x:
min: xMin,
max: xMax,
,
y:
min: yMin,
max: yMax,
// center the camera
this.center =
x: (xMax + xMin) / 2,
y: (yMax + yMin) / 2
this.camera.position.set(this.center.x, this.center.y, -6);
this.camera.lookAt(this.center.x, this.center.y, 0);
this.controls.target = new Vec3(this.center.x, this.center.y, 0);
// add attributes
geometry.addAttribute('position', new BA( new Arr([0, 0, 0]), 3));
geometry.addAttribute('translation', new IBA(translations, 3, 1) );
geometry.addAttribute('color', new IBA(colors, 3, 1) );
geometry.addAttribute('uidColor', new IBA(uidColors, 3, 1) );
var material = new THREE.RawShaderMaterial(
vertexShader: find('#vertex-shader').textContent,
fragmentShader: find('#fragment-shader').textContent,
);
var mesh = new THREE.Points(geometry, material);
mesh.frustumCulled = false; // prevent the mesh from being clipped on drag
this.scene.add(mesh);
World.prototype.getColorMap = function()
function toHex(c)
var hex = c.toString(16);
return hex.length == 1 ? '0' + hex : hex;
function rgbToHex(r, g, b)
return '#' + toHex(r) + toHex(g) + toHex(b);
function hexToRgb(hex)
var result = /^#?([a-f\d]2)([a-f\d]2)([a-f\d]2)$/i.exec(hex);
return result ?
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
: null;
var hexes = [
'#fe4445','#ff583b','#ff6a2f','#ff7a20','#ff8800',
'#ff9512','#ffa31f','#ffaf2a','#ffbb34',
'#cfc522','#99cc01',
'#91c14a','#85b66e','#73ac8f','#57a3ac','#0099cb',
'#14a0d1','#20a7d8','#2aaedf','#33b5e6'
]
var colorMap = ;
hexes.forEach(function(c, idx) colorMap[idx] = hexToRgb(c) )
return colorMap;
/**
* Helpers
**/
function getRenderSize()
var elem = find('#gl-target');
return
w: elem.clientWidth,
h: elem.clientHeight,
function find(selector)
return document.querySelector(selector);
/**
* Main
**/
var world = new World();
world.controls.enabled = false;
find('canvas').addEventListener('mousemove', function(e)
find('#bar').style.left = e.clientX + 'px';
var coords = world.getMouseWorldCoords(e);
console.log(coords, world.bb.x);
)
html, body
width: 100%;
height: 100%;
background: #000;
body
margin: 0;
overflow: hidden;
canvas
width: 100%;
height: 100%;
.gl-container
position: relative;
#gl-target
width:700px;
height:400px
#bar
width: 1px;
height: 100%;
display: inline-block;
position: absolute;
left: 30px;
background: red;
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/95/three.min.js'></script>
<script src='https://rawgit.com/YaleDHLab/pix-plot/master/assets/js/trackball-controls.js'></script>
<script type='x-shader/x-vertex' id='vertex-shader'>
precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
attribute vec3 position;
attribute vec3 translation;
#ifdef PICKING
attribute vec3 uidColor;
varying vec3 vUidColor;
#else
attribute vec3 color;
#endif
varying vec3 vColor;
void main()
#ifdef PICKING
vUidColor = uidColor;
#else
vColor = color;
#endif
// set point position
vec3 raw = position + translation;
vec4 pos = projectionMatrix * modelViewMatrix * vec4(raw, 1.0);
gl_Position = pos;
// set point size
gl_PointSize = 10.0;
</script>
<script type='x-shader/x-fragment' id='fragment-shader'>
precision highp float;
#ifdef PICKING
varying vec3 vUidColor;
#else
varying vec3 vColor;
#endif
void main()
// make point circular
vec2 coord = gl_PointCoord - vec2(0.5);
if (length(coord) > 0.5) discard;
// color the point
#ifdef PICKING
gl_FragColor = vec4(vUidColor, 1.0);
#else
gl_FragColor = vec4(vColor, 1.0);
#endif
</script>
<div class='gl-container'>
<div id='bar'></div>
<div id='gl-target'></div>
</div>
【问题讨论】:
【参考方案1】:啊哈,我不需要将事件 x 和 y 坐标除以窗口宽度(仅适用于延伸到整个窗口高度和宽度的画布),我需要将事件 x 和 y 坐标除以画布的宽度和身高!
function World()
this.scene = this.getScene();
this.camera = this.getCamera();
this.renderer = this.getRenderer();
this.color = new THREE.Color();
this.addPoints();
this.render();
World.prototype.getScene = function()
var scene = new THREE.Scene();
scene.background = new THREE.Color(0xefefef);
return scene;
World.prototype.getCamera = function()
var renderSize = getRenderSize(),
aspectRatio = renderSize.w / renderSize.h,
camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 100000);
camera.position.set(0, 1, -10);
return camera;
World.prototype.getRenderer = function()
var renderSize = getRenderSize(),
renderer = new THREE.WebGLRenderer(antialias: true);
renderer.setPixelRatio(window.devicePixelRatio); // retina displays
renderer.setSize(renderSize.w, renderSize.h); // set w,h
find('#gl-target').appendChild(renderer.domElement);
return renderer;
World.prototype.render = function()
requestAnimationFrame(this.render.bind(this));
this.renderer.render(this.scene, this.camera);
World.prototype.getMouseWorldCoords = function(e)
var elem = find('#gl-target'),
vector = new THREE.Vector3(),
camera = world.camera,
x = (e.clientX / elem.clientWidth) * 2 - 1,
y = (e.clientY / elem.clientHeight) * 2 + 1;
vector.set(x, y, 0.5);
vector.unproject(camera);
var direction = vector.sub(camera.position).normalize(),
distance = - camera.position.z / direction.z,
scaled = direction.multiplyScalar(distance),
coords = camera.position.clone().add(scaled);
return
x: coords.x,
y: coords.y,
;
World.prototype.addPoints = function()
// this geometry builds a blueprint and many copies of the blueprint
var IBG = THREE.InstancedBufferGeometry,
BA = THREE.BufferAttribute,
IBA = THREE.InstancedBufferAttribute,
Vec3 = THREE.Vector3,
Arr = Float32Array;
// add data for each observation; n = num observations
var geometry = new IBG(),
n = 10000,
rootN = n**(1/2),
// find max min for each dim to center camera
xMax = Number.NEGATIVE_INFINITY,
xMin = Number.POSITIVE_INFINITY,
yMax = Number.NEGATIVE_INFINITY,
yMin = Number.POSITIVE_INFINITY;
var translations = new Arr(n * 3),
colors = new Arr(n * 3),
uidColors = new Arr(n * 3),
translationIterator = 0,
colorIterator = 0,
uidColorIterator = 0;
var colorMap = this.getColorMap();
for (var i=0; i<n; i++)
var x = Math.sin(i) * 4,
y = Math.floor(i / (n/20)) * 0.3,
color = colorMap[ Math.floor(i / (n/20)) ],
uidColor = this.color.setHex(i + 1);
if (x > xMax) xMax = x;
if (x < xMin) xMin = x;
if (y > yMax) yMax = y;
if (y < yMin) yMin = y;
translations[translationIterator++] = x;
translations[translationIterator++] = y;
translations[translationIterator++] = 0;
colors[colorIterator++] = color.r / 255;
colors[colorIterator++] = color.g / 255;
colors[colorIterator++] = color.b / 255;
uidColors[uidColorIterator++] = uidColor.r;
uidColors[uidColorIterator++] = uidColor.g;
uidColors[uidColorIterator++] = uidColor.b;
// store the min and max coords in each dimension
this.bb =
x:
min: xMin,
max: xMax,
,
y:
min: yMin,
max: yMax,
// center the camera
this.center =
x: (xMax + xMin) / 2,
y: (yMax + yMin) / 2
this.camera.position.set(this.center.x, this.center.y, -6);
this.camera.lookAt(this.center.x, this.center.y, 0);
// add attributes
geometry.addAttribute('position', new BA( new Arr([0, 0, 0]), 3));
geometry.addAttribute('translation', new IBA(translations, 3, 1) );
geometry.addAttribute('color', new IBA(colors, 3, 1) );
geometry.addAttribute('uidColor', new IBA(uidColors, 3, 1) );
var material = new THREE.RawShaderMaterial(
vertexShader: find('#vertex-shader').textContent,
fragmentShader: find('#fragment-shader').textContent,
);
var mesh = new THREE.Points(geometry, material);
mesh.frustumCulled = false; // prevent the mesh from being clipped on drag
this.scene.add(mesh);
World.prototype.getColorMap = function()
function toHex(c)
var hex = c.toString(16);
return hex.length == 1 ? '0' + hex : hex;
function rgbToHex(r, g, b)
return '#' + toHex(r) + toHex(g) + toHex(b);
function hexToRgb(hex)
var result = /^#?([a-f\d]2)([a-f\d]2)([a-f\d]2)$/i.exec(hex);
return result ?
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
: null;
var hexes = [
'#fe4445','#ff583b','#ff6a2f','#ff7a20','#ff8800',
'#ff9512','#ffa31f','#ffaf2a','#ffbb34',
'#cfc522','#99cc01',
'#91c14a','#85b66e','#73ac8f','#57a3ac','#0099cb',
'#14a0d1','#20a7d8','#2aaedf','#33b5e6'
]
var colorMap = ;
hexes.forEach(function(c, idx) colorMap[idx] = hexToRgb(c) )
return colorMap;
/**
* Helpers
**/
function getRenderSize()
var elem = find('#gl-target');
return
w: elem.clientWidth,
h: elem.clientHeight,
function find(selector)
return document.querySelector(selector);
/**
* Main
**/
var world = new World();
find('canvas').addEventListener('mousemove', function(e)
find('#bar').style.left = e.clientX + 'px';
var coords = world.getMouseWorldCoords(e);
console.log(coords, world.bb.x);
)
html, body
width: 100%;
height: 100%;
background: #000;
body
margin: 0;
overflow: hidden;
canvas
width: 100%;
height: 100%;
.gl-container
position: relative;
#gl-target
width:700px;
height:400px
#bar
width: 1px;
height: 100%;
display: inline-block;
position: absolute;
left: 30px;
background: red;
<div class='gl-container'>
<div id='bar'></div>
<div id='gl-target'></div>
</div>
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/95/three.min.js'></script>
<script type='x-shader/x-vertex' id='vertex-shader'>
precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
attribute vec3 position;
attribute vec3 translation;
#ifdef PICKING
attribute vec3 uidColor;
varying vec3 vUidColor;
#else
attribute vec3 color;
#endif
varying vec3 vColor;
void main()
#ifdef PICKING
vUidColor = uidColor;
#else
vColor = color;
#endif
// set point position
vec3 raw = position + translation;
vec4 pos = projectionMatrix * modelViewMatrix * vec4(raw, 1.0);
gl_Position = pos;
// set point size
gl_PointSize = 10.0;
</script>
<script type='x-shader/x-fragment' id='fragment-shader'>
precision highp float;
#ifdef PICKING
varying vec3 vUidColor;
#else
varying vec3 vColor;
#endif
void main()
// make point circular
vec2 coord = gl_PointCoord - vec2(0.5);
if (length(coord) > 0.5) discard;
// color the point
#ifdef PICKING
gl_FragColor = vec4(vUidColor, 1.0);
#else
gl_FragColor = vec4(vColor, 1.0);
#endif
</script>
【讨论】:
以上是关于Three.js:确定鼠标位置的世界坐标的主要内容,如果未能解决你的问题,请参考以下文章