**New here?**Learn about Bountify and follow @bountify to get notified of new bounties! Follow @bountify x

I have a Float32Array of points (x,y,z) coordinates of where the line should be drawn.

In a threejs scene, I need you draw a line using those points, and then animate it on scroll from start position to end position.

The camera should also follow along the line as it animates.

To clarity, it doesn't have to be a line object, it's okay if it's geometry. Bonus points if I can configure a shape (eg not just a "tube", but a flat plane (like a road) etc.

I think the spline example from three.js documentation is a good place to start?

For context, I'm trying to recreate this in 3D: https://tympanus.net/Development/StorytellingMap/

This is what I already have: https://imgur.com/a/G56nd69

Important is that everything is well-commented, so I can implement into my own project.

Bonus points if you help me implement it within my own codebase at https://github.com/elfensky/storymap

## 1 Solution

This is using an older version of your project so I can't do a pull request right now, but it works and the line is a geometric tube so you can change the thickness.

```
import { GLOBAL as $ } from './globals'
import * as THREE from 'three'
import * as GEOLIB from 'geolib'
import { Vector3 } from 'three'
import { Vector2 } from 'three'
let clock = new THREE.Clock()
//on scroll event listener. well, it's not scroll but just mousewheel, you get the idea though.
//can replace it with 'scroll'
document.addEventListener('mousewheel', (event) => {
$.drawRange += event.deltaY / 1000
if ($.drawRange < 2) $.drawRange = 2
})
//
async function animatePath() {
$.data = await loadPath()
$.lineArray = new Float32Array(($.data.length + 1) * 3)
let drawRange = 0
$.data.forEach((element, index) => {
$.lineArray[index * 3 + 0] = element[0]
$.lineArray[index * 3 + 1] = element[1]
$.lineArray[index * 3 + 2] = element[2]
drawRange += 1
})
$.camera.up = new THREE.Vector3(0, 1, 0)
drawPath()
}
async function loadPath() {
return await fetch($.config.path).then((response) => {
return response.json().then((data) => {
return data
})
})
}
// this is exported and imported in the initiate.js and put inside the render function.
//basically, recreates the tubegeometry and updates it based on the event listener on top of this class
function updatePath() {
drawPath()
}
//this (re)drawns the tube. Instead of just using a WebGL line I used a Tube, so you can change the thickness.
function drawPath() {
if ($.line) {
$.scene.remove($.line)
}
if ($.lineArray.length == 0) return
if ($.drawRange <= 0) return
let drawRange = Math.floor($.drawRange) * 3
let diff = $.drawRange - Math.floor($.drawRange)
let copyLineArray = []
if (diff > 0 && drawRange >= 3 && drawRange <= $.lineArray.length - 3) {
let x1 = $.lineArray[drawRange - 3]
let y1 = $.lineArray[drawRange - 2]
let z1 = $.lineArray[drawRange - 1]
let x2 = $.lineArray[drawRange]
let y2 = $.lineArray[drawRange + 1]
let z2 = $.lineArray[drawRange + 2]
let dis = new Vector3((x2 - x1) * diff + x1, (y2 - y1) * diff + y1, (z2 - z1) * diff + z1)
copyLineArray = new Float32Array(drawRange + 3)
for (var i = 0; i < drawRange; i++) copyLineArray[i] = $.lineArray[i]
copyLineArray[i] = dis.x
copyLineArray[i + 1] = dis.y
copyLineArray[i + 2] = dis.z
} else {
copyLineArray = $.lineArray.filter((a, i) => i < drawRange)
}
if (drawRange >= 3) {
let xLenth = copyLineArray.length
$.camera.lookAt(
copyLineArray[xLenth - 3],
copyLineArray[xLenth - 2],
copyLineArray[xLenth - 1],
)
$.camera.position.set(
copyLineArray[xLenth - 3] + 2,
copyLineArray[xLenth - 2] + 2,
copyLineArray[xLenth - 1],
)
}
let vecs = []
for (var i = 0; i < copyLineArray.length; i += 3)
vecs.push(new Vector3(copyLineArray[i], copyLineArray[i + 1], copyLineArray[i + 2]))
const path = new THREE.CatmullRomCurve3(vecs)
var geometry = new THREE.TubeGeometry(path, 100, 0.01, 100, false)
... (12 lines left)
Collapse
ANSWER.md
4 KB
This is using an older version of your project so I can't do a pull request right now, but it works and the line is a geometric tube so you can change the thickness.
```js
import { GLOBAL as $ } from './globals'
import * as THREE from 'three'
import * as GEOLIB from 'geolib'
import { Vector3 } from 'three'
import { Vector2 } from 'three'
let clock = new THREE.Clock()
//on scroll event listener. well, it's not scroll but just mousewheel, you get the idea though.
//can replace it with 'scroll'
document.addEventListener('mousewheel', (event) => {
$.drawRange += event.deltaY / 1000
if ($.drawRange < 2) $.drawRange = 2
})
//
async function animatePath() {
$.data = await loadPath()
$.lineArray = new Float32Array(($.data.length + 1) * 3)
let drawRange = 0
$.data.forEach((element, index) => {
$.lineArray[index * 3 + 0] = element[0]
$.lineArray[index * 3 + 1] = element[1]
$.lineArray[index * 3 + 2] = element[2]
drawRange += 1
})
$.camera.up = new THREE.Vector3(0, 1, 0)
drawPath()
}
async function loadPath() {
return await fetch($.config.path).then((response) => {
return response.json().then((data) => {
return data
})
})
}
// this is exported and imported in the initiate.js and put inside the render function.
//basically, recreates the tubegeometry and updates it based on the event listener on top of this class
function updatePath() {
drawPath()
}
//this (re)drawns the tube. Instead of just using a WebGL line I used a Tube, so you can change the thickness.
function drawPath() {
if ($.line) {
$.scene.remove($.line)
}
if ($.lineArray.length == 0) return
if ($.drawRange <= 0) return
let drawRange = Math.floor($.drawRange) * 3
let diff = $.drawRange - Math.floor($.drawRange)
let copyLineArray = []
if (diff > 0 && drawRange >= 3 && drawRange <= $.lineArray.length - 3) {
let x1 = $.lineArray[drawRange - 3]
let y1 = $.lineArray[drawRange - 2]
let z1 = $.lineArray[drawRange - 1]
let x2 = $.lineArray[drawRange]
let y2 = $.lineArray[drawRange + 1]
let z2 = $.lineArray[drawRange + 2]
let dis = new Vector3((x2 - x1) * diff + x1, (y2 - y1) * diff + y1, (z2 - z1) * diff + z1)
copyLineArray = new Float32Array(drawRange + 3)
for (var i = 0; i < drawRange; i++) copyLineArray[i] = $.lineArray[i]
copyLineArray[i] = dis.x
copyLineArray[i + 1] = dis.y
copyLineArray[i + 2] = dis.z
} else {
copyLineArray = $.lineArray.filter((a, i) => i < drawRange)
}
if (drawRange >= 3) {
let xLenth = copyLineArray.length
$.camera.lookAt(
copyLineArray[xLenth - 3],
copyLineArray[xLenth - 2],
copyLineArray[xLenth - 1],
)
$.camera.position.set(
copyLineArray[xLenth - 3] + 2,
copyLineArray[xLenth - 2] + 2,
copyLineArray[xLenth - 1],
)
}
let vecs = []
for (var i = 0; i < copyLineArray.length; i += 3)
vecs.push(new Vector3(copyLineArray[i], copyLineArray[i + 1], copyLineArray[i + 2]))
const path = new THREE.CatmullRomCurve3(vecs)
var geometry = new THREE.TubeGeometry(path, 100, 0.01, 100, false)
let material = new THREE.MeshNormalMaterial({
flatShading: true,
})
$.line = new THREE.Mesh(geometry, material)
$.scene.add($.line)
}
export { animatePath, updatePath }
```