Open DamrongGuoy opened 1 year ago
@joemasterjohn @RussTedrake
With @SeanCurtis-TRI 's help, I looked around Drake and rdeits/meshcat. This is what I found:
I think we will need to combine features from the above code together.
There's still an open question of whether we could change the mesh across time. The animation2.html uses a fixed mesh.
As I learn more about meshcat, I'll have better ideas.
I will update hydroelastic_basics.ipynb when we fix this issue of contact-patch playback.
@dmcconachie-tri said the attached mp4 in the issue description didn't play for him. I attached the GIF file of the same content here.
Drake_issue_19142.gif
@dmcconachie-tri also reported the same symptom in Animations Playbak. The contact patches drifted. They are the very thin sliver of red rectangles, which are hard to see without zooming into the GIF a lot.
There's a hope #20866 could help. Here's a prototype result from playing back a static HTML record from #20774.
There is also a better idea than #20866 that Jeremy suggested in https://drakedevelopers.slack.com/archives/C43KX47A9/p1706836298149919?thread_ts=1706810068.483669&cid=C43KX47A9
to have our
meshcat.cc
andmeshcat_animation.cc
internal logic automatically "transpile" the call sequence ofMeshcat::SetObject
andMeshcat::SetProperty
commands into a visible/invisible animation sequence, rather than teaching every Meshcat-using visualization class about the trick.
Here's an outline of how I recommend to implement this...
C++ class Meshcat can operate in three modes:
Any given operation on C++ class Meshcat comes in ~three~ two flavors:
We want to maintain the following invariants:
Now imagine the operation SetObject(path=/foo, shape=shape). I'll use the Shape overload as an example, but the same reasoning applies to the other overloads as well (e.g., point cloud, in-memory mesh, line, etc).
We want to allow the user to make timed calls to SetObject:
SetObject(path=/foo, shape=shape_t1, time=t1)
SetObject(path=/foo, shape=shape_t2, time=t2)
SetObject(path=/foo, shape=shape_t3, time=t3)
...
However, the animation concept in threejs doesn't allow timed object addition. Instead, we must add the object untimed and use timing to change the visibility property.
Here's the trick: WHEN RECORDING and a timed SetObject is requested, add it to the scene at /path/<animation>/tN
. When the user requests the three operations shown above, we transpile them to these operations instead:
SetProperty(path=/foo/<object>, property=visible, value=false, time=time_tstart)
SetProperty(path=/foo/<animation>/t1, property=visible, value=false)
SetObject(path=/foo/<animation>/t1, shape=shape_t1)
SetProperty(path=/foo/<animation>/t1, property=visible, value=true, time=time_t1)
SetProperty(path=/foo/<animation>/t2, property=visible, value=false)
SetObject(path=/foo/<animation>/t2, shape=shape_t2)
SetProperty(path=/foo/<animation>/t1, property=visible, value=false, time=time_t2)
SetProperty(path=/foo/<animation>/t2, property=visible, value=true, time=time_t2)
SetProperty(path=/foo/<animation>/t3, property=visible, value=false)
SetObject(path=/foo/<animation>/t3, shape=shape_t3)
SetProperty(path=/foo/<animation>/t2, property=visible, value=false, time=time_t3)
SetProperty(path=/foo/<animation>/t1, property=visible, value=true, time=time_t3)
SetProperty(path=/foo/<animation>/t3, property=visible, value=false, time=time_tfinal)
SetProperty(path=/foo/<object>, property=visible, value=true, time=time_final)
Remember that any calls without a time=...
are not part of the animation.
Compared to our invariants, this bends the rules a little bit: the SceneNode tree browser will be slightly different when an animation has timed setobject calls, but only in places that users do not typically unfold.
It might help to show the above sequence as untimed vs timed operations:
# Untimed initial conditions.
SetProperty(path=/foo/<animation>/t1, property=visible, value=false)
SetObject(path=/foo/<animation>/t1, shape=shape_t1)
SetProperty(path=/foo/<animation>/t2, property=visible, value=false)
SetObject(path=/foo/<animation>/t2, shape=shape_t2)
SetProperty(path=/foo/<animation>/t3, property=visible, value=false)
SetObject(path=/foo/<animation>/t3, shape=shape_t3)
# Animation.
SetProperty(path=/foo/<object>, property=visible, value=false, time=time_tstart)
SetProperty(path=/foo/<animation>/t1, property=visible, value=true, time=time_t1)
SetProperty(path=/foo/<animation>/t1, property=visible, value=false, time=time_t2)
SetProperty(path=/foo/<animation>/t2, property=visible, value=true, time=time_t2)
SetProperty(path=/foo/<animation>/t2, property=visible, value=false, time=time_t3)
SetProperty(path=/foo/<animation>/t1, property=visible, value=true, time=time_t3)
SetProperty(path=/foo/<animation>/t3, property=visible, value=false, time=time_tfinal)
SetProperty(path=/foo/<object>, property=visible, value=true, time=time_final)
Final note: remember that times are not floats. They are integers where the time is truncated to a specific frame number (based on the fps). We should plan to use those integers in the paths (not the time as a float) to avoid weird round-off and precision problems. We should also only do the property-setting operations in frames that actually change something; there's no need to reset the object in frames where it didn't actually change.
I think that proposal meets the invariants. Someone should check me.
Thank you very much, Jeremy. I confirm this typo: the third line from below in each block should be t3
instead of t1
, right? Here's the diff:
Yes, I believe you're right.
@jwnimmer-tri, thank you for the idea again. To help me understand better, I have drafted the code in #20959. I'll ask some questions in Reviewable.
Is your feature request related to a problem? Please describe. It is very helpful in dynamic simulation that we can playback animation faster or slower after the simulation finishes. Currently we can playback hydroelastic force and moment, but we do not support playback animation of contact patches. It is mentioned as a TODO here.
Describe the solution you'd like Add
time
parameter to Meshcat::SetTriangleColorMesh(..., time) , so we can change these lines toDescribe alternatives you've considered
Additional context Right now, the animation playback looks like this. The contact patch (white circle) of the last time step persists and move around the white cylinder in the playback, which is very confusing. It's like we have a ghost contact patch moving around.
https://user-images.githubusercontent.com/42557859/230230501-92fcb69b-2e85-4684-8bff-5246f3311c44.mp4