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:确定鼠标位置的世界坐标的主要内容,如果未能解决你的问题,请参考以下文章

Three.js 从鼠标位置投射光线

在 THREE.js 中获取鼠标相对于 3D 空间的坐标

three.js 2D 鼠标点击到 3d 坐标使用 raycaster

Three.js 3D缩放到鼠标位置

Three.js - 推开然后在“鼠标移动”上恢复元素位置

three.js使用gpu选取物体并计算交点位置