2013-04-02 5 views
3

Я занимаюсь созданием игры с использованием Three.JS, и я смоделировал и успешно импортировал город, созданный в Sketchup. Теперь мне нужно динамически добавлять стрелки «следовать за мной» (согласно желтым стрелкам в макете ниже). Я считаю, что мне, возможно, понадобится использовать Three.CurvePath для достижения этого, но я не уверен, что это лучший подход. Мне нужно вручную моделировать путь и вычислять касательную для каждого из объектов стрелки, чтобы они указывали естественно вокруг углов (как за левый поворот в макете)?Three.CurvePath и пользовательские маркеры

enter image description here

Надеется, что это имеет смысл!

+0

Связанный: http://stackoverflow.com/questions/11179327/orient-objects-rotation-to-a-spline-point-tangent-in-three-js/11181366 # 11181366 – WestLangley

ответ

8

У меня могло бы быть решение. Не использовали three.js через некоторое время, поэтому не уверен, что это самое элегантное решение. Я начал с Shapes Example, так как он показывает:

  1. Как построить путь процедурно (используя команды, которые также обрабатывать кривые)
  2. Как извлечь точки из такого пути

Так что я ве разделить проблему на:

  1. Генерирование пути
  2. обходе пути, и я nterpolation (положение и вращение)

Генерирование путь Я повторно закругленный определение прямоугольника, который выглядит похожим на часть вашего экрана.

var roundedRectShape = new THREE.Shape(); 

       (function roundedRect(ctx, x, y, width, height, radius){ 

        ctx.moveTo(x, y + radius); 
        ctx.lineTo(x, y + height - radius); 
        ctx.quadraticCurveTo(x, y + height, x + radius, y + height); 
        ctx.lineTo(x + width - radius, y + height) ; 
        ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius); 
        ctx.lineTo(x + width, y + radius); 
        ctx.quadraticCurveTo(x + width, y, x + width - radius, y); 
        ctx.lineTo(x + radius, y); 
        ctx.quadraticCurveTo(x, y, x, y + radius); 

       })(roundedRectShape, 0, 0, 200, 200, 20); 

Ваш путь не может быть прямоугольник с закругленными углами, но доступные типы кривых функций (quadraticCurveTo, bezierCurveTo, splineThru) действительно полезны.

Другая идея, которая приходит на ум, - использовать скрипт Ruby для экспорта координат пути из Sketchup в three.js. Либо вы пишете это с нуля, либо используете существующие скрипты. Вот one легко найти в google.

обходе пути

К счастью Three.js уже реализует это через путь, где getPoint(t)-й т представляет собой число от 0.0 до 1.0, представляющего обхода на пути. Таким образом, получение позиции тривиально, как получение следующей интерполированной позиции на пути. Тогда это просто вопрос использования Math.atan2(), чтобы получить вращение:

t = (t + s)%1.0;//increment t while maintaining it between 0.0 and 1.0 
       var p = path.getPoint(t);//point at t 
       var pn = path.getPoint((t+s)%1.0);//point at next t iteration 

       if(p != null && pn != null){ 
        //move to current position 
        arrow.position.x = p.x; 
        arrow.position.y = p.y; 
        //get orientation based on next position 
        arrow.rotation.z = Math.atan2(pn.y-p.y,pn.x-p.x); 

       } 

В заключении сильфона простой пример (с использованием кубы вместо формы стрелки) для иллюстрации генерирования и перемещение по пути в три.JS основанные на примере фигуры:

<!DOCTYPE html> 
<html lang="en"> 
    <head> 
     <title>path interpolation</title> 
     <meta charset="utf-8"> 
     <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> 
     <style> 
      body { 
       font-family: Monospace; 
       background-color: #f0f0f0; 
       margin: 0px; 
       overflow: hidden; 
      } 
     </style> 
    </head> 
    <body> 
     <canvas id="debug" style="position:absolute; left:100px"></canvas> 

     <script src="../build/three.min.js"></script> 

     <script src="js/libs/stats.min.js"></script> 


     <script> 

      var container, stats; 

      var camera, scene, renderer; 

      var text, plane; 

      var targetRotation = 0; 
      var targetRotationOnMouseDown = 0; 

      var mouseX = 0; 
      var mouseXOnMouseDown = 0; 

      var windowHalfX = window.innerWidth/2; 
      var windowHalfY = window.innerHeight/2; 

      init(); 
      animate(); 

      var t = 0.0;//traversal on path 
      var s = 0.001;//speed of traversal 
      var arrow;//mesh to move/rotate on path 
      var path;//Path object to traverse 

      function init() { 

       container = document.createElement('div'); 
       document.body.appendChild(container); 

       camera = new THREE.PerspectiveCamera(50, window.innerWidth/window.innerHeight, 1, 1000); 
       camera.position.set(0, 150, 500); 

       scene = new THREE.Scene(); 

       parent = new THREE.Object3D(); 
       parent.position.y = 50; 
       scene.add(parent); 

       arrow = new THREE.Mesh(new THREE.CubeGeometry(20,10,10),new THREE.MeshBasicMaterial({color: 0x009900})); 
       parent.add(arrow); 
       //this is helpful as a visual aid but not crucial 
       function addShape(shape, extrudeSettings, color, x, y, z, rx, ry, rz, s) { 

        var points = shape.createPointsGeometry(); 
        var spacedPoints = shape.createSpacedPointsGeometry(50); 

        // transparent line from equidistance sampled points 

        var line = new THREE.Line(spacedPoints, new THREE.LineBasicMaterial({ color: color, opacity: 0.2 })); 
        line.rotation.set(rx, ry, rz); 
        parent.add(line); 

        // equidistance sampled points 

        var pgeo = spacedPoints.clone(); 
        var particles2 = new THREE.ParticleSystem(pgeo, new THREE.ParticleBasicMaterial({ color: color, size: 2, opacity: 0.5 })); 
        particles2.rotation.set(rx, ry, rz); 
        parent.add(particles2); 

       } 


       // Rounded rectangle 
       //generating the path and populating it is crucial tough 
       var roundedRectShape = new THREE.Shape(); 

       (function roundedRect(ctx, x, y, width, height, radius){ 

        ctx.moveTo(x, y + radius); 
        ctx.lineTo(x, y + height - radius); 
        ctx.quadraticCurveTo(x, y + height, x + radius, y + height); 
        ctx.lineTo(x + width - radius, y + height) ; 
        ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius); 
        ctx.lineTo(x + width, y + radius); 
        ctx.quadraticCurveTo(x + width, y, x + width - radius, y); 
        ctx.lineTo(x + radius, y); 
        ctx.quadraticCurveTo(x, y, x, y + radius); 

       })(roundedRectShape, 0, 0, 200, 200, 20); 
       path = roundedRectShape; 

       var extrudeSettings = { amount: 20 }; // bevelSegments: 2, steps: 2 , bevelSegments: 5, bevelSize: 8, bevelThickness:5 
       extrudeSettings.bevelEnabled = true; 
       extrudeSettings.bevelSegments = 2; 
       extrudeSettings.steps = 2; 

       addShape(roundedRectShape, extrudeSettings, 0x000000, -150, 150, 0, 0, 0, 0, 1); 

       renderer = new THREE.WebGLRenderer({ antialias: true }); 
       renderer.setSize(window.innerWidth, window.innerHeight); 

       container.appendChild(renderer.domElement); 

       stats = new Stats(); 
       stats.domElement.style.position = 'absolute'; 
       stats.domElement.style.top = '0px'; 
       container.appendChild(stats.domElement); 

       document.addEventListener('mousedown', onDocumentMouseDown, false); 
       document.addEventListener('touchstart', onDocumentTouchStart, false); 
       document.addEventListener('touchmove', onDocumentTouchMove, false); 

       // 

       window.addEventListener('resize', onWindowResize, false); 

      } 

      function onWindowResize() { 

       windowHalfX = window.innerWidth/2; 
       windowHalfY = window.innerHeight/2; 

       camera.aspect = window.innerWidth/window.innerHeight; 
       camera.updateProjectionMatrix(); 

       renderer.setSize(window.innerWidth, window.innerHeight); 

      } 

      // 

      function onDocumentMouseDown(event) { 

       event.preventDefault(); 

       document.addEventListener('mousemove', onDocumentMouseMove, false); 
       document.addEventListener('mouseup', onDocumentMouseUp, false); 
       document.addEventListener('mouseout', onDocumentMouseOut, false); 

       mouseXOnMouseDown = event.clientX - windowHalfX; 
       targetRotationOnMouseDown = targetRotation; 

      } 

      function onDocumentMouseMove(event) { 

       mouseX = event.clientX - windowHalfX; 

       targetRotation = targetRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.02; 

      } 

      function onDocumentMouseUp(event) { 

       document.removeEventListener('mousemove', onDocumentMouseMove, false); 
       document.removeEventListener('mouseup', onDocumentMouseUp, false); 
       document.removeEventListener('mouseout', onDocumentMouseOut, false); 

      } 

      function onDocumentMouseOut(event) { 

       document.removeEventListener('mousemove', onDocumentMouseMove, false); 
       document.removeEventListener('mouseup', onDocumentMouseUp, false); 
       document.removeEventListener('mouseout', onDocumentMouseOut, false); 

      } 

      function onDocumentTouchStart(event) { 

       if (event.touches.length == 1) { 

        event.preventDefault(); 

        mouseXOnMouseDown = event.touches[ 0 ].pageX - windowHalfX; 
        targetRotationOnMouseDown = targetRotation; 

       } 

      } 

      function onDocumentTouchMove(event) { 

       if (event.touches.length == 1) { 

        event.preventDefault(); 

        mouseX = event.touches[ 0 ].pageX - windowHalfX; 
        targetRotation = targetRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.05; 

       } 

      } 

      // 

      function animate() { 

       requestAnimationFrame(animate); 

       render(); 
       stats.update(); 

      } 

      function render() { 
       t = (t + s)%1.0;//increment t while maintaining it between 0.0 and 1.0 
       var p = path.getPoint(t);//point at t 
       var pn = path.getPoint((t+s)%1.0);//point at next t iteration 

       if(p != null && pn != null){ 
        //move to current position 
        arrow.position.x = p.x; 
        arrow.position.y = p.y; 
        //get orientation based on next position 
        arrow.rotation.z = Math.atan2(pn.y-p.y,pn.x-p.x); 

       } 

       parent.rotation.y += (targetRotation - parent.rotation.y) * 0.05; 
       renderer.render(scene, camera); 

      } 

     </script> 

    </body> 
</html> 

Думал бы добавить работоспособный фрагмент прямо на этой странице:

  var container; 
 

 
      var camera, scene, renderer; 
 

 
      var text, plane; 
 

 
      var targetRotation = 0; 
 
      var targetRotationOnMouseDown = 0; 
 

 
      var mouseX = 0; 
 
      var mouseXOnMouseDown = 0; 
 

 
      var windowHalfX = window.innerWidth/2; 
 
      var windowHalfY = window.innerHeight/2; 
 

 
      init(); 
 
      animate(); 
 

 
      var t = 0.0;//traversal on path 
 
      var s = 0.001;//speed of traversal 
 
      var arrows;//mesh to move/rotate on path 
 
      var path;//Path object to traverse 
 

 
      function init() { 
 

 
       container = document.createElement('div'); 
 
       document.body.appendChild(container); 
 

 
       camera = new THREE.PerspectiveCamera(50, window.innerWidth/window.innerHeight, 1, 1000); 
 
       camera.position.set(0, 150, 500); 
 

 
       scene = new THREE.Scene(); 
 

 
       parent = new THREE.Object3D(); 
 
       parent.position.y = 50; 
 
       scene.add(parent); 
 
       
 
       arrows = []; 
 
       for(var i = 0 ; i < 50; i++){ 
 
       arrows[i] = new THREE.Mesh(new THREE.CubeGeometry(10,5,5),new THREE.MeshBasicMaterial({color: 0x009900})); 
 
       parent.add(arrows[i]); 
 
       } 
 
       //this is helpful as a visual aid but not crucial 
 
       function addShape(shape, extrudeSettings, color, x, y, z, rx, ry, rz, s) { 
 

 
        var points = shape.createPointsGeometry(); 
 
        var spacedPoints = shape.createSpacedPointsGeometry(50); 
 

 
        // transparent line from equidistance sampled points 
 

 
        var line = new THREE.Line(spacedPoints, new THREE.LineBasicMaterial({ color: color, opacity: 0.2 })); 
 
        line.rotation.set(rx, ry, rz); 
 
        parent.add(line); 
 

 
        // equidistance sampled points 
 

 
        var pgeo = spacedPoints.clone(); 
 
        var particles2 = new THREE.ParticleSystem(pgeo, new THREE.ParticleBasicMaterial({ color: color, size: 2, opacity: 0.5 })); 
 
        particles2.rotation.set(rx, ry, rz); 
 
        parent.add(particles2); 
 

 
       } 
 

 

 
       // Rounded rectangle 
 
       //generating the path and populating it is crucial tough 
 
       var roundedRectShape = new THREE.Shape(); 
 

 
       (function roundedRect(ctx, x, y, width, height, radius){ 
 

 
        ctx.moveTo(x, y + radius); 
 
        ctx.lineTo(x, y + height - radius); 
 
        ctx.quadraticCurveTo(x, y + height, x + radius, y + height); 
 
        ctx.lineTo(x + width - radius, y + height) ; 
 
        ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius); 
 
        ctx.lineTo(x + width, y + radius); 
 
        ctx.quadraticCurveTo(x + width, y, x + width - radius, y); 
 
        ctx.lineTo(x + radius, y); 
 
        ctx.quadraticCurveTo(x, y, x, y + radius); 
 

 
       })(roundedRectShape, 0, 0, 200, 200, 20); 
 
       path = roundedRectShape; 
 

 
       var extrudeSettings = { amount: 20 }; // bevelSegments: 2, steps: 2 , bevelSegments: 5, bevelSize: 8, bevelThickness:5 
 
       extrudeSettings.bevelEnabled = true; 
 
       extrudeSettings.bevelSegments = 2; 
 
       extrudeSettings.steps = 2; 
 

 
       addShape(roundedRectShape, extrudeSettings, 0x000000, -150, 150, 0, 0, 0, 0, 1); 
 

 
       renderer = new THREE.WebGLRenderer({ antialias: true }); 
 
       renderer.setSize(window.innerWidth, window.innerHeight); 
 

 
       container.appendChild(renderer.domElement); 
 

 
       
 

 
       document.addEventListener('mousedown', onDocumentMouseDown, false); 
 
       document.addEventListener('touchstart', onDocumentTouchStart, false); 
 
       document.addEventListener('touchmove', onDocumentTouchMove, false); 
 

 
       // 
 

 
       window.addEventListener('resize', onWindowResize, false); 
 

 
      } 
 

 
      function onWindowResize() { 
 

 
       windowHalfX = window.innerWidth/2; 
 
       windowHalfY = window.innerHeight/2; 
 

 
       camera.aspect = window.innerWidth/window.innerHeight; 
 
       camera.updateProjectionMatrix(); 
 

 
       renderer.setSize(window.innerWidth, window.innerHeight); 
 

 
      } 
 

 
      // 
 

 
      function onDocumentMouseDown(event) { 
 

 
       event.preventDefault(); 
 

 
       document.addEventListener('mousemove', onDocumentMouseMove, false); 
 
       document.addEventListener('mouseup', onDocumentMouseUp, false); 
 
       document.addEventListener('mouseout', onDocumentMouseOut, false); 
 

 
       mouseXOnMouseDown = event.clientX - windowHalfX; 
 
       targetRotationOnMouseDown = targetRotation; 
 

 
      } 
 

 
      function onDocumentMouseMove(event) { 
 

 
       mouseX = event.clientX - windowHalfX; 
 

 
       targetRotation = targetRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.02; 
 

 
      } 
 

 
      function onDocumentMouseUp(event) { 
 

 
       document.removeEventListener('mousemove', onDocumentMouseMove, false); 
 
       document.removeEventListener('mouseup', onDocumentMouseUp, false); 
 
       document.removeEventListener('mouseout', onDocumentMouseOut, false); 
 

 
      } 
 

 
      function onDocumentMouseOut(event) { 
 

 
       document.removeEventListener('mousemove', onDocumentMouseMove, false); 
 
       document.removeEventListener('mouseup', onDocumentMouseUp, false); 
 
       document.removeEventListener('mouseout', onDocumentMouseOut, false); 
 

 
      } 
 

 
      function onDocumentTouchStart(event) { 
 

 
       if (event.touches.length == 1) { 
 

 
        event.preventDefault(); 
 

 
        mouseXOnMouseDown = event.touches[ 0 ].pageX - windowHalfX; 
 
        targetRotationOnMouseDown = targetRotation; 
 

 
       } 
 

 
      } 
 

 
      function onDocumentTouchMove(event) { 
 

 
       if (event.touches.length == 1) { 
 

 
        event.preventDefault(); 
 

 
        mouseX = event.touches[ 0 ].pageX - windowHalfX; 
 
        targetRotation = targetRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.05; 
 

 
       } 
 

 
      } 
 

 
      // 
 

 
      function animate() { 
 

 
       requestAnimationFrame(animate); 
 

 
       render(); 
 
    
 
      } 
 

 
      function render() { 
 
       t = (t + s)%1.0;//increment t while maintaining it between 0.0 and 1.0 - could map mouse x position/window width for fun :) 
 
\t \t \t \t for(var i = 0 ; i < 50; i++){//for each box 
 
\t \t \t \t \t var ti = ((i/50.0)+t)%1.0;//compute the traversval including each box's own offset on the path 
 
\t \t \t \t \t 
 
\t \t \t \t \t var p = path.getPoint(ti);//point at t 
 
\t \t \t \t \t var pn = path.getPoint((ti+s)%1.0);//point at next t iteration 
 
\t \t \t \t 
 
\t \t \t \t \t if(p != null && pn != null){ 
 
\t \t \t \t \t \t //move to current position 
 
\t \t \t \t \t \t arrows[i].position.x = p.x; 
 
\t \t \t \t \t \t arrows[i].position.y = p.y; 
 
\t \t \t \t \t \t //get orientation based on next position 
 
\t \t \t \t \t \t arrows[i].rotation.z = Math.atan2(pn.y-p.y,pn.x-p.x); 
 
\t \t \t \t \t 
 
\t \t \t \t \t } 
 
\t \t \t \t } 
 
       parent.rotation.y += (targetRotation - parent.rotation.y) * 0.05; 
 
       renderer.render(scene, camera); 
 

 
      }
  body { 
 
       font-family: Monospace; 
 
       background-color: #f0f0f0; 
 
       margin: 0px; 
 
       overflow: hidden; 
 
      }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r71/three.min.js"></script>

+1

Хороший ответ. Я дал вам взнос и поместил версию вашего примера в jsfiddle, чтобы люди могли ее запустить. http://jsfiddle.net/crossphire/DAktM/ –

+0

Великолепно тщательный ответ - спасибо. Я мог бы что-то игнорировать, но при использовании системы частиц я могу контролировать направление частиц? Я хотел бы иметь возможность отображать в виде серии стрелок, которые отслеживают путь (подобно пути, составленному из стрелок в макете). Принимая это как ответ в любом случае, так как это отличный стартер. – Sidebp

+0

@Sidebp Не использовали ParticleSystem много, но я предполагаю, что он может быть динамическим (там должен быть образец где-то), чтобы вы могли обновлять позиции. Спасибо за jsfiddle от Crossphire Development, я создал обновление, чтобы проиллюстрировать, как вы перемещаете несколько объектов на пути (будь то пользовательские меши/частицы/и т. Д.): Посмотрите [здесь] (http://jsfiddle.net/orgicus/XuUFs/4 /). Что касается фигур стрелок, они могут быть экземплярами «Shape», частицами в виде билбордов/спрайтов или, поскольку все находится в одной плоскости, все может быть 2D-текстурой, либо процедурно генерируемой в элементе холста, либо, может быть, спрайтом. –

Смежные вопросы