# How to animate curved arrows in threejs?

Hi there,
I am looking for a way to animate curved arrows going from point A to B. They can be static as well. But I am not sure where I should start.
Also before I do animation I am also not sure how I can create a curved object that looks like an arrow.
If anyone can point me to right direction, I would really appreciate it.
Thank you.

If youâ€™re using `THREE.Curve`. (Which I never but this is what I see from the doc)

A simple idea is to use `curve.getPoint( t )` while t value is 0-1 (start to end of the curve)

You just need to animate the t value from 0 to 1 from time to time.

And use t value to update your arrow position

``````arrow.position = curve.getPoint(t);

// Same as above but avoid creating new Vector3 instance.
curve.getPoint(t, arrow.position);
``````
1 Like

Perhaps these examples will give you a starting point.

CurvedArrowHelper

discourse.threejs.hofk.de getPoint( ) getPointAt( )

ColorStripeChanging

2021 eXtended eXamples CurveGenerator

1 Like

Thanks for the suggestion by the question. I am close to finishing a multiform geometry (Open source ).

A test resulted in the following

Not yet perfect!

``````function  curvedArrowCenterline( h ) {
return { x: 0.1 * Math.sin(  pi * h ), z: 0 };
}

function  arrowOutline( h ) {

return{ y: h < 0.9 ? h : (  h === 0.9 ? 0.8 : h ), r: h < 0.9 ?  0.1 : (  h === 0.9 ? 0.2 : 0 ) }

}
``````

UPDATE

Multiform geometry is easier than my Addon. Produces almost infinite many time-varying geometries with functions

It is not a real space curve, but only a horizontal offset of the layers. So a shear per height segment.

In the addon, it is a correctly bent cylinder. However, the arrowhead is not centered due to the height offset. You can create the arrowhead separately and form a group.

The best solution is certainly a specially created geometry.

Here is an example of bending a generic mesh around a curve

https://threejs.org/examples/webgl_modifier_curve.html

I have it there MotionAlongCurve used.
( From the Collection of examples from discourse.threejs.org )

But it does not bend a single mesh. See the following posts.

Looks like the box needs segmentation greater, than 1, along X-axis.

1 Like

Yeah, needs more vertices in the axes its being curved in.

In fact

I had no subdivision for the example of the movement.

Then an arrow that is curved can be created easily.

1 Like

If you need many arrows there is also an instanced version which is useful for animating 100s of objects at the same time such as: XR Koi Garden

2 Likes

Everybody likes Koi fish Spline + DataTexture

I looked to see if you can make a curved arrow from the official example
three.js examples LineGeometry

Should work.

``````<!DOCTYPE html>
<!-- https://discourse.threejs.org/t/how-to-animate-curved-arrows-in-threejs/36808/10 -->
<title> WebglLinesFat </title>
<meta charset="utf-8">

<body>

<script type="module">

import * as THREE from '../jsm/three.module.139.js';
import Stats from '../jsm/stats.module.139.js';
import { OrbitControls } from '../jsm/OrbitControls.139.js';
import { LineMaterial } from '../jsm/LineMaterial.139.js';
import { LineSegments2 } from '../jsm/LineSegments2.139.js';
import { Line2 } from '../jsm/Line2.139.js';
import { LineGeometry } from '../jsm/LineGeometry.139.js';

let line, segments ;
let renderer, scene, camera,controls;

let matLine ;
let stats ;

init();
animate();

function init() {

renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
renderer.setPixelRatio( window.devicePixelRatio );
//renderer.setClearColor( 0x000000, 0.0 );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

scene = new THREE.Scene();

camera = new THREE.PerspectiveCamera( 65, window.innerWidth / window.innerHeight, 0.001, 1000 );
camera.position.set( 0, 5, 25 );

controls = new OrbitControls( camera, renderer.domElement );

// Position and THREE.Color Data

const positions = [];
//const colors = [];
const points = [];
for ( let i = - 20; i < 20; i ++ ) {

const t = i / 3;
//points.push( new THREE.Vector3( t * Math.sin( 2 * t ), t, t * Math.cos( 2 * t ) ) );
points.push( new THREE.Vector3( t * Math.sin(  t / 6 ), t, -t   ) );

}

const spline = new THREE.CatmullRomCurve3( points );
const divisions = Math.round( points.length ); //const divisions = Math.round( 3 * points.length );
const point = new THREE.Vector3();
//const color = new THREE.Color();

for ( let i = 0, l = divisions; i < l; i ++ ) {

const t = i / l;

spline.getPoint( t, point );
positions.push( point.x, point.y, point.z );

//color.setHSL( t, 1.0, 0.5 );
//colors.push( color.r, color.g, color.b );

}

const lineGeometry = new LineGeometry();
lineGeometry.setPositions( positions );
//lineGeometry.setColors( colors );

matLine = new LineMaterial( {

color:  0x0000ff, // 0xffffff,
linewidth: 0.75, // in world units with size attenuation, pixels otherwise
worldUnits: true,
//vertexColors: true,

//resolution:  // to be set by renderer, eventually
//alphaToCoverage: true,

} );

line = new Line2( lineGeometry, matLine );
// line.computeLineDistances();
// line.scale.set( 1, 1, 1 );
line.scale.set( 1.2, 1.2, 1.2 );

//const geo = new THREE.BufferGeometry();
//geo.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
//geo.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );

onWindowResize();

stats = new Stats();
document.body.appendChild( stats.dom );

}

function onWindowResize() {

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

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

}

function animate() {

requestAnimationFrame( animate );

stats.update();

// main scene

//renderer.setClearColor( 0x000000, 0 );

//renderer.setViewport( 0, 0, window.innerWidth, window.innerHeight );

// renderer will set this eventually
//matLine.resolution.set( window.innerWidth, window.innerHeight ); // resolution of the viewport

renderer.render( scene, camera );
}

</script>

</body>

</html>
``````
1 Like

And here is the arrow.

## But you have to create the peak separately, otherwise it is only correct from a distance.

``````for ( let i = - 25; i <= 0.4; i ++ ) {
points.push( new THREE.Vector3( i / 4 * Math.sin(  i / 20 ), 0, -i / 2 ) );
}

for ( let i = - 5; i <=  5; i ++ ) {
points.push( new THREE.Vector3( i / 5 , 0, Math.abs( i / 5 ) ) );
}

const matLine = new LineMaterial( {
color:  0xff00ff,
linewidth: 0.01,
} );
``````

UPDATE:
I just noticed that the visible arrow thickness remains constant as the camera moves further away. I must have removed something from the template that is obviously important.

1 Like

Try to uncomment this line.

Then the complete line disappears. Iâ€™ll probably have to simplify the original example again piece by piece to find the spot.

UPDATE:
I obviously got confused with my various test versions. In one it works. Must bring order into it.

This is how it works
ArrowLinesFat

I didnâ€™t get, why does it have to be fat lines?
Is usual `THREE.Line()` not an option?

â€¦ me neither.
Looked at how to do it with a simple line.

CurvedArrowWithTip

With it you can build a beautiful net.

You can make the balls as transparent bubbles and position things in them.
Here only as a wireframe.

The arrow hits the balls on the straight line connecting the centers.

``````
<!DOCTYPE html>
<!-- https://discourse.threejs.org/t/how-to-animate-curved-arrows-in-threejs/36808/17 -->
<meta charset="utf-8">
<title> CurvedArrowWithTip </title>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>

<body></body>

<script src='../js/three.139.js'></script>
<script src='../js/OrbitControls.139.js'></script>

<script>

// @author hofk

const scene = new THREE.Scene( );

const renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setClearColor( 0xdedede, 1.0 );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

const camera = new THREE.PerspectiveCamera( 65, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.set( -1, 3, 4 );

const controls = new THREE.OrbitControls( camera, renderer.domElement );

window.addEventListener( "resize", event => { camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix( );
renderer.setSize(innerWidth, innerHeight ); } );

const gridHelper = new THREE.GridHelper( 5, 10 );

// input data .................................
const v0 = new THREE.Vector3( -1.9, 0.2, 0.4 );
const r0 = 0.16;
const v1 = new THREE.Vector3(  1.7, 0.5, 0.1 );
const r1 = 0.35;
const bend = 0.8; // 0 .. +-1 .. +-2 ...
const Ï†  = -0.9;  // radiant,  test with 0
//.............................................

const mesh0 = new THREE.Mesh( new THREE.SphereGeometry( r0 ), new THREE.MeshBasicMaterial( { color:  0x11ab22 , wireframe: true } ) );
mesh0.position.set( v0.x, v0.y, v0.z ) ;

const mesh1 = new THREE.Mesh( new THREE.SphereGeometry( r1 ), new THREE.MeshBasicMaterial( { color:  0x11ab22 , wireframe: true } ) );
mesh1.position.set( v1.x, v1.y, v1.z ) ;

const  cpc = 100; // bended: curve points count
let points = [];  // curve points
const points3 = [];
let k, b, n, r;

const v = new THREE.Vector3( ).subVectors( v1, v0 );
const vn = v.clone( ).normalize( );
const vlen = v.length( );
v0.add( v.normalize( ).multiplyScalar( r0 ) );
v1.sub( v.normalize( ).multiplyScalar( r1 ) );
const vv = new THREE.Vector3( ).subVectors( v1, v0 ).divideScalar( 2 );

// orthogonal see   http://lolengine.net/blog/2013/09/21/picking-orthogonal-vector-combing-coconuts
k = ( Math.abs( vn.x ) + 0. ) % 1; // fract
b = new THREE.Vector3( -vn.y, vn.x - k * vn.z, k * vn.y ).normalize( ); // binormal

if( Ï† !== 0 ) {

const n = new THREE.Vector3( ).crossVectors( vn, b ); // normal
r = new THREE.Vector3( ).addVectors( b.multiplyScalar( Math.cos( Ï† ) ), n.multiplyScalar( Math.sin( Ï† ) ) ); // rotated

}

const vm = new THREE.Vector3( ).add( v0 ).add( vv ).add( ( Ï† === 0 ? b : r ).multiplyScalar( vlen / 4 ).multiplyScalar( bend ) );

points3.push( v0, vm, v1 );
points = new THREE.CatmullRomCurve3( points3 ).getPoints( cpc );

const line = new THREE.Line( new THREE.BufferGeometry( ).setFromPoints( points ), new THREE.LineBasicMaterial( { color: 0xff1122 } ) );

const tipHeight = 0.2;
const tipGeometry =  new THREE.ConeGeometry(  tipHeight / 4, tipHeight );
tipGeometry.translate( 0, -tipHeight / 2, 0 );

const tipMesh = new THREE.Mesh( tipGeometry , new THREE.MeshBasicMaterial( { color:  0xff1122 , wireframe: true } ) );

const vt = new THREE.Vector3( ).subVectors( points[ cpc ], points[ cpc - Math.floor( 0.05 * cpc ) ] ).normalize( );

tipMesh.quaternion.setFromUnitVectors( new THREE.Vector3( 0, 1, 0 ), vt );
tipMesh.position.set( v1.x, v1.y, v1.z );