Skip to content

Commit 2dde592

Browse files
authored
Merge pull request #95 from JuliaComputing/traces
add path tracing and loop rendering option to animations
2 parents 51a8052 + 6b768ad commit 2dde592

File tree

5 files changed

+86
-12
lines changed

5 files changed

+86
-12
lines changed

docs/src/examples/pendulum.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,9 +220,10 @@ sol = solve(prob, Rodas4())
220220
plot(sol, layout=4)
221221
```
222222

223+
In the animation below, we visualize the path that the origin of the pendulum tip traces by providing the tip frame in a vector of frames passed to `traces`
223224
```@example pendulum
224225
import GLMakie
225-
Multibody.render(model, sol, filename = "furuta.gif")
226+
Multibody.render(model, sol, filename = "furuta.gif", traces=[model.tip.frame_a])
226227
nothing # hide
227228
```
228229
![furuta](furuta.gif)

docs/src/rendering.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ Many components allows the user to select with which color it is rendered. This
2424
## Rendering the world frame
2525
The display of the world frame can be turned off by setting `world.render => false` in the variable map.
2626

27+
## Tracing the path of a frame in 3D visualizations
28+
The path that a frame traces out during simulation can be visualized by passing a vector of frames to the `render` function using the `traces` keyword, e.g., `render(..., traces=[frame1, frame2])`.
29+
See the Furuta-pendulum demonstration [Going 3D](@ref) for an example of this.
30+
2731

2832
## Rendering API
2933

ext/Render.jl

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
module Render
22
using Makie
33
using Multibody
4-
import Multibody: render, render!, encode, decode, get_rot, get_trans, get_frame
4+
import Multibody: render, render!, loop_render, encode, decode, get_rot, get_trans, get_frame
55
using Rotations
66
using LinearAlgebra
77
using ModelingToolkit
8-
export render
8+
export render, loop_render
99
using MeshIO, FileIO
1010
using StaticArrays
1111

@@ -129,6 +129,9 @@ function render(model, sol,
129129
up = Vec3f(0,1,0),
130130
show_axis = false,
131131
timescale = 1.0,
132+
traces = nothing,
133+
display = false,
134+
loop = 1,
132135
kwargs...
133136
)
134137
scene, fig = default_scene(x,y,z; lookat,up,show_axis)
@@ -139,30 +142,85 @@ function render(model, sol,
139142
t = Observable(timevec[1])
140143

141144
recursive_render!(scene, complete(model), sol, t)
142-
fn = record(fig, filename, timevec; framerate) do time
143-
t[] = time/timescale
145+
146+
if traces !== nothing
147+
tvec = range(sol.t[1], stop=sol.t[end], length=500)
148+
for frame in traces
149+
(frame.metadata !== nothing && get(frame.metadata, :frame, false)) || error("Only frames can be traced in animations.")
150+
points = get_trans(sol, frame, tvec) |> Matrix
151+
Makie.lines!(scene, points)
152+
end
153+
end
154+
if loop > 1
155+
timevec = repeat(timevec, loop)
156+
end
157+
if display
158+
Base.display(fig)
159+
sleep(2)
160+
fnt = @async begin
161+
record(fig, filename, timevec; framerate) do time
162+
if time == timevec[1]
163+
Base.display(fig)
164+
end
165+
t[] = time/timescale
166+
sleep(max(0, 1/framerate))
167+
end
168+
end
169+
fn = fetch(fnt)
170+
else
171+
fn = record(fig, filename, timevec; framerate) do time
172+
t[] = time/timescale
173+
end
144174
end
175+
145176
fn, scene, fig
146177
end
147178

148179
function render(model, sol, time::Real;
180+
traces = nothing,
181+
x = 2,
182+
y = 0.5,
183+
z = 2,
149184
kwargs...,
150185
)
151186

152187
# fig = Figure()
153188
# scene = LScene(fig[1, 1]).scene
154189
# cam3d!(scene)
155-
scene, fig = default_scene(0,0,10; kwargs...)
190+
scene, fig = default_scene(x,y,z; kwargs...)
156191
# mesh!(scene, Rect3f(Vec3f(-5, -3.6, -5), Vec3f(10, 0.1, 10)), color=:gray) # Floor
157192

158193
steps = range(sol.t[1], sol.t[end], length=3000)
159194

160195
t = Slider(fig[2, 1], range = steps, startvalue = time).value
161-
162196
recursive_render!(scene, complete(model), sol, t)
197+
198+
if traces !== nothing
199+
tvec = range(sol.t[1], stop=sol.t[end], length=500)
200+
for frame in traces
201+
(frame.metadata !== nothing && get(frame.metadata, :frame, false)) || error("Only frames can be traced in animations.")
202+
points = get_trans(sol, frame, tvec) |> Matrix
203+
Makie.lines!(scene, points)
204+
end
205+
end
163206
fig, t
164207
end
165208

209+
function Multibody.loop_render(model, sol; timescale = 1.0, framerate = 30, max_loop = 5, kwargs...)
210+
fig, t = render(model, sol, sol.t[1]; kwargs...)
211+
sleeptime = 1/framerate
212+
timevec = range(sol.t[1], sol.t[end]*timescale, step=sleeptime)
213+
display(fig)
214+
@async begin
215+
for i = 1:max_loop
216+
for ti in timevec
217+
execution_time = @elapsed t[] = ti
218+
sleep(max(0, sleeptime - execution_time))
219+
end
220+
end
221+
end
222+
end
223+
166224
"""
167225
Internal function: Recursively render all subsystem components of a multibody system. If a particular component returns `true` from its `render!` method, indicating that the component performaed rendering, the recursion stops.
168226
"""

src/Multibody.jl

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ export Rotational, Translational
1111
export render, render!
1212

1313
"""
14-
scene, time = render(model, sol, t::Real; framerate = 30)
15-
path = render(model, sol, timevec = range(sol.t[1], sol.t[end], step = 1 / framerate); framerate = 30, timescale=1)
14+
scene, time = render(model, sol, t::Real; framerate = 30, traces = [])
15+
path = render(model, sol, timevec = range(sol.t[1], sol.t[end], step = 1 / framerate); framerate = 30, timescale=1, display=false, loop=1)
1616
1717
Create a 3D animation of a multibody system
1818
@@ -23,7 +23,9 @@ Create a 3D animation of a multibody system
2323
- `timevec`: If a vector of times is provided, an animation is created and the path to the file on disk is returned.
2424
- `framerate`: Number of frames per second.
2525
- `timescale`: Scaling of the time vector. This argument can be made to speed up animations (`timescale < 1`) or slow them down (`timescale > 1`). A value of `timescale = 2` will be 2x slower than real time.
26+
- `loop`: The animation will be looped this many times. Please note: looping the animation using this argument is only recommended when `display = true` for camera manipulation purposes. When the camera is not manipulated, looping the animation by other means is recommended to avoid an increase in the file size.
2627
- `filename` controls the name and the file type of the resulting animation
28+
- `traces`: An optional array of frames to show the trace of.
2729
2830
# Camera control
2931
The following keyword arguments are available to control the camera pose:
@@ -32,9 +34,19 @@ The following keyword arguments are available to control the camera pose:
3234
- `z = 2`
3335
- `lookat = [0,0,0]`: a three-vector of coordinates indicating the point at which the camera looks.
3436
- `up = [0,1,0]`: A vector indicating the direction that is up.
37+
- `display`: if `true`, the figure will be displayed during the recording process and time will advance in real-time. This allows the user to manipulate the camera options using the mouse during the recording.
38+
39+
See also [`loop_render`](@ref)
3540
"""
3641
function render end
3742

43+
"""
44+
loop_render(model, sol; framerate = 30, timescale = 1, max_loop = 5, kwargs...)
45+
46+
Similar to the method of [`render`](@ref) that produces an animation, but instead opens an interactive window where the time is automatically advanced in real time. This allows the user to manually manipulate the camera using the mouse is a live animation.
47+
"""
48+
function loop_render end
49+
3850
"""
3951
did_render::Bool = render!(scene, ::typeof(ComponentConstructor), sys, sol, t)
4052

src/orientation.jl

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -381,9 +381,8 @@ end
381381
Extract the translational part of a frame from a solution at time `t`.
382382
See also [`get_rot`](@ref), [`get_frame`](@ref), [Orientations and directions](@ref) (docs section).
383383
"""
384-
function get_trans(sol, frame, t)
385-
SVector{3}(sol(t, idxs = collect(frame.r_0)))
386-
end
384+
get_trans(sol, frame, t::Number) = SVector{3}(sol(t, idxs = collect(frame.r_0)))
385+
get_trans(sol, frame, t::AbstractArray) = sol(t, idxs = collect(frame.r_0))
387386

388387
"""
389388
T_W_F = get_frame(sol, frame, t)

0 commit comments

Comments
 (0)