jupyter-widgets / pythreejs

A Jupyter - Three.js bridge
https://pythreejs.readthedocs.io
Other
942 stars 188 forks source link

Can't animate Arrow length / direction #325

Open Boomer91 opened 4 years ago

Boomer91 commented 4 years ago

I'm trying to animate length and direction of an ArrowHelper, but these attributes don't seem animateable. Why is this the case?

Alternatively, I can use position, rotation and scale attributes (those inherited from Object3D) to do what I want. However I am still interested to know if this could be done using the other arrow attributes.

Related; How can I make a custom arrow class (I want an animateable circular arrow), and how can I make its attributes animateable?

Related 2: How can I animate the endpoints a of Line segment?

from pythreejs import *

scene = Scene()

camera = PerspectiveCamera(position=[5, 0, -2], up=[0,0,1])
scene.add(camera)

control = OrbitControls(controlling=camera)

renderer = Renderer(camera=camera, scene=scene,
controls=[control],
width=400, height=400
)

axes = AxesHelper(size=1)
scene.add(axes)

scene.add(AmbientLight(intensity=0.3))

arrow = ArrowHelper(color='red', dir=[0.5,0.5,0.5], length=3, name='arrow')
scene.add(arrow)

arrow_track = NumberKeyframeTrack(
    name='scene/arrow.length', times=[0,0.5], values=[1,2])

movement = AnimationClip(tracks=[arrow_track])
animation = AnimationAction(mixer=AnimationMixer(scene), clip=movement, localRoot=scene)

animation
vidartf commented 4 years ago

The animation system might not be fully compliant with the behavior of threejs at the moment, but do you have any luck following the examples in https://github.com/jupyter-widgets/pythreejs/blob/master/examples/Animation.ipynb more closely? E.g. not using scene as the local root?

Boomer91 commented 4 years ago

No luck, see below:

from pythreejs import *

scene = Scene()

camera = PerspectiveCamera(position=[5, 0, -2], up=[0, 0, 1])
scene.add(camera)

control = OrbitControls(controlling=camera)

renderer = Renderer(camera=camera,
                    scene=scene,
                    controls=[control],
                    width=400,
                    height=400)

axes = AxesHelper(size=1)
scene.add(axes)

scene.add(AmbientLight(intensity=0.3))

arrow = ArrowHelper(color='red', dir=[0.5, 0.5, 0.5], length=3)
scene.add(arrow)

arrow_track = NumberKeyframeTrack(name='.length',
                                  times=[0, 0.5],
                                  values=[1, 2])

movement = AnimationClip(tracks=[arrow_track])
animation = AnimationAction(mixer=AnimationMixer(arrow),
                            clip=movement,
                            localRoot=arrow)

display(renderer)
display(animation)
Boomer91 commented 4 years ago

For reference, here is my solution for drawing and animating an arrow between two points. One strange thing I noticed was that the arrow.quaternion is not always the unit quaternion after initialization unless dir = [0, 1, 0]

This could also be used to animate lines with moving endpoints, but feels rather convoluted. Is there no way to animate the line/arrow endpoints directly (without kernel roundtrips) instead of using position/quaternion/scale transformations?

from pythreejs import *
import numpy as np

scene = Scene()

camera = PerspectiveCamera(position=[8, 5, 2], up=[0, 0, 1])
scene.add(camera)

control = OrbitControls(controlling=camera)

renderer = Renderer(camera=camera,
                    scene=scene,
                    controls=[control],
                    width=600,
                    height=600)

axes = AxesHelper(size=1)
scene.add(axes)

scene.add(AmbientLight(intensity=0.3))

origin = [2, 1, -2]  # origin of arrow
vector = [-1, 1, 5]  # vector (direction & length) of arrow

n_origin = Mesh(
    SphereBufferGeometry(radius=0.05),
    MeshStandardMaterial(color='red'),
    position=origin,
)

scene.add(n_origin)

n_vector = Mesh(
    SphereBufferGeometry(radius=0.05),
    MeshStandardMaterial(color='red'),
    position=list(np.array(origin) + np.array(vector)),
)

scene.add(n_vector)

arrow = ArrowHelper(
    dir=[0, 1, 0],
    origin=[0, 0, 0],
    length=1,
    color='red',
    name='arrow',
)

scene.add(arrow)
print(arrow.quaternion)

positions_track = NumberKeyframeTrack(name='scene/arrow.position',
                                      times=[0, 1],
                                      values=[[0, 0, 0], origin])

scale_array = [[1, 1, 1], 3 * [np.linalg.norm(vector)],
               3 * [np.linalg.norm(vector)]]
scale_track = NumberKeyframeTrack(name='scene/arrow.scale',
                                  times=[2, 3, 4],
                                  values=scale_array)

# https://stackoverflow.com/questions/1171849/finding-quaternion-representing-the-rotation-from-one-vector-to-another
vector = vector / np.linalg.norm(vector)
v0 = [0, 1, 0]  # initial arrow direction
xyz = np.cross(v0, vector)
w = 1 + np.dot(v0, vector)
q = [*xyz, w]
q = q / np.linalg.norm(q)

print(q)

quat_array = [(0, 0, 0, 1), q]

print(q[0]**2 + q[1]**2 + q[2]**2 + q[3]**2)

quat_track = NumberKeyframeTrack(name='scene/arrow.quaternion',
                                 times=[1, 2],
                                 values=quat_array)

movement = AnimationClip(tracks=[positions_track, quat_track, scale_track])
animation = AnimationAction(mixer=AnimationMixer(scene),
                            clip=movement,
                            localRoot=scene,
                            timeScale=1)

display(renderer)
display(animation)