This example tracks the coordinates of a point in three-dimensional space using an SVG overlay.
Ideally, the callout line would be shortened by the radius of the circle plus some margin.
| license: mit |
| <html> | |
| <head> | |
| <title>Tracking</title> | |
| <style> | |
| body { margin: 0; } | |
| canvas { width: 100%; height: 100% } | |
| </style> | |
| </head> | |
| <body> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.min.js"></script> | |
| <script src="https://d3js.org/d3.v4.min.js"></script> | |
| <script> | |
| var scene = new THREE.Scene(); | |
| var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 ); | |
| camera.position.z = 2; | |
| var renderer = new THREE.WebGLRenderer(); | |
| renderer.setClearColor( 'white' ) | |
| renderer.setSize( window.innerWidth, window.innerHeight ); | |
| const appDiv = document.createElement('div'); | |
| document.body.appendChild(appDiv); | |
| appDiv.appendChild( renderer.domElement ); | |
| const canvas = document.querySelector('canvas'); | |
| const ndc_to_canvas = { | |
| x: d3.scaleLinear().domain([-1, 1]).range([0, canvas.width]), | |
| y: d3.scaleLinear().domain([-1, 1]).range([canvas.height, 0]) | |
| } | |
| const svg_ns = "http://www.w3.org/2000/svg"; | |
| var svg = document.createElementNS(svg_ns, "svg"); | |
| svg.style.position = "absolute"; | |
| svg.style.left = 0; | |
| svg.style.top = 0; | |
| svg.style.width = canvas.width; | |
| svg.style.height = canvas.height; | |
| svg.height = canvas.height; | |
| appDiv.appendChild(svg); | |
| const CIRCLE_RADIUS = 10; | |
| const FONT_SIZE = 10; | |
| const TEXT_POSITION = { | |
| x: 500, | |
| y: 100 | |
| } | |
| const circleG = document.createElementNS(svg_ns, "g"); | |
| const circle = document.createElementNS(svg_ns, "circle") | |
| circle.setAttribute('r', CIRCLE_RADIUS) | |
| circle.style.fill = "none"; | |
| circle.style.stroke = 'black' | |
| circleG.appendChild(circle) | |
| svg.appendChild(circleG) | |
| const line = document.createElementNS(svg_ns, "line") | |
| line.setAttribute('x1', TEXT_POSITION.x) | |
| line.setAttribute('y1', TEXT_POSITION.y) | |
| line.setAttribute('x2', 300) | |
| line.setAttribute('y2', 300) | |
| line.style.stroke = "black"; | |
| svg.appendChild(line) | |
| const textG = document.createElementNS(svg_ns, "g") | |
| const text = document.createElementNS(svg_ns, "text"); | |
| textG.appendChild(text) | |
| textG.setAttribute("transform", `translate(${TEXT_POSITION.x}, ${TEXT_POSITION.y})`); | |
| text.textContent = "Something We Care About" | |
| text.style.fontSize = `${FONT_SIZE}px` | |
| text.style.lineHeight = `${FONT_SIZE}px` | |
| text.style.textTransform = "uppercase" | |
| text.setAttribute('text-anchor', 'end') | |
| text.setAttribute('dx', '-5px') | |
| text.setAttribute('dy', `${FONT_SIZE/4}px`) | |
| svg.appendChild(textG) | |
| var geometry = new THREE.BoxGeometry( 1, 1, 1 ); | |
| var material = new THREE.MeshBasicMaterial( { color: 0xbbbbbb, wireframe: true } ); | |
| var cube = new THREE.Mesh( geometry, material ); | |
| function makePoint() { | |
| var geometry = new THREE.SphereGeometry( 0.02, 30, 30 ); | |
| var material = new THREE.MeshBasicMaterial( {color: 0x000000} ); | |
| var sphere = new THREE.Mesh( geometry, material ); | |
| sphere.position.y = 0.5 | |
| sphere.position.x = 0.4 | |
| return sphere; | |
| } | |
| const point = makePoint() | |
| cube.add(point) | |
| scene.add( cube ); | |
| const velocity = 0.02; | |
| const ticks = Stream(next => { | |
| function tick() { | |
| next() | |
| requestAnimationFrame(tick) | |
| } | |
| tick() | |
| }) | |
| const koob = Stream(out => { | |
| const rotation = { x: 0, y: 0 }; | |
| ticks.listen(tick => { | |
| rotation.x += 1; | |
| rotation.y += 1; | |
| out(rotation) | |
| }) | |
| }) | |
| const raycaster = new THREE.Raycaster(); | |
| ticks.listen(function () { | |
| cube.rotation.x += velocity; | |
| cube.rotation.y += velocity; | |
| renderer.render(scene, camera); | |
| const world = point.getWorldPosition(); | |
| // this is in Normalized Device Coords; | |
| const camera_ndc = world.project(camera); | |
| const screen_x = ndc_to_canvas.x(camera_ndc.x); | |
| const screen_y = ndc_to_canvas.y(camera_ndc.y); | |
| const screen = new THREE.Vector2(screen_x, screen_y); | |
| circleG.setAttribute('transform', `translate(${screen.x}, ${screen.y})`) | |
| line.setAttribute('x2', screen.x) | |
| line.setAttribute('y2', screen.y) | |
| }) | |
| function Stream(start) { | |
| return { | |
| listen: start | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> |