Skip to content

Commit 8e04685

Browse files
authored
Merge pull request #144 from JuliaComputing/optimize_rendering
Optimize rendering
2 parents 8fab3a7 + c2bd425 commit 8e04685

File tree

7 files changed

+111
-23
lines changed

7 files changed

+111
-23
lines changed

docs/src/examples/free_motion.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ eqs = [connect(bar2.frame_a, world.frame_b)
6868
spring1,
6969
spring2,
7070
])
71+
model = complete(model)
7172
ssys = structural_simplify(IRSystem(model))
7273
prob = ODEProblem(ssys, [
7374
collect(body.body.v_0 .=> 0);

docs/src/examples/pendulum.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ nothing # hide
4242
With all components and connections defined, we can create an `ODESystem` like so:
4343
```@example pendulum
4444
@named model = ODESystem(connections, t, systems=[world, joint, body])
45+
model = complete(model)
4546
nothing # hide
4647
```
4748
The `ODESystem` is the fundamental model type in ModelingToolkit used for multibody-type models.
@@ -88,6 +89,7 @@ connections = [connect(world.frame_b, joint.frame_a)
8889
connect(body.frame_a, joint.frame_b)]
8990
9091
@named model = ODESystem(connections, t, systems = [world, joint, body, damper])
92+
model = complete(model)
9193
ssys = structural_simplify(IRSystem(model))
9294
9395
prob = ODEProblem(ssys, [damper.phi_rel => 1], (0, 10))
@@ -120,6 +122,7 @@ connections = [connect(world.frame_b, joint.frame_a)
120122
connect(body_0.frame_a, joint.frame_b)]
121123
122124
@named model = ODESystem(connections, t, systems = [world, joint, body_0, damper, spring])
125+
model = complete(model)
123126
ssys = structural_simplify(IRSystem(model))
124127
125128
prob = ODEProblem(ssys, [], (0, 10))
@@ -146,6 +149,7 @@ connections = [connect(world.frame_b, multibody_spring.frame_a)
146149
connect(root_body.frame_a, multibody_spring.frame_b)]
147150
148151
@named model = ODESystem(connections, t, systems = [world, multibody_spring, root_body])
152+
model = complete(model)
149153
ssys = structural_simplify(IRSystem(model))
150154
151155
defs = Dict(collect(root_body.r_0) .=> [0, 1e-3, 0]) # The spring has a singularity at zero length, so we start some distance away
@@ -163,6 +167,7 @@ push!(connections, connect(multibody_spring.spring2d.flange_a, damper.flange_a))
163167
push!(connections, connect(multibody_spring.spring2d.flange_b, damper.flange_b))
164168
165169
@named model = ODESystem(connections, t, systems = [world, multibody_spring, root_body, damper])
170+
model = complete(model)
166171
ssys = structural_simplify(IRSystem(model))
167172
prob = ODEProblem(ssys, defs, (0, 10))
168173

docs/src/examples/quad.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,16 @@ function RotorCraft(; closed_loop = true, addload=true)
8282
end
8383
8484
thrusters = [Thruster(name = Symbol("thruster$i")) for i = 1:num_arms]
85-
@named body = Body(m = body_mass, state_priority = 0, I_11=0.01, I_22=0.01, I_33=0.01, air_resistance=1)
86-
@named freemotion = FreeMotion(state=true, isroot=true, quat=false) # We use Euler angles to describe the orientation of the rotorcraft.
85+
@named body = Body(m = body_mass, state_priority = 0, I_11=0.01, I_22=0.01, I_33=0.01, air_resistance=1, isroot=true)
8786
8887
connections = [
89-
connect(world.frame_b, freemotion.frame_a)
90-
connect(freemotion.frame_b, body.frame_a)
9188
y_alt ~ body.r_0[2]
92-
y_roll ~ freemotion.phi[3]
93-
y_pitch ~ freemotion.phi[1]
89+
y_roll ~ body.phi[3]
90+
y_pitch ~ body.phi[1]
9491
[connect(body.frame_a, arms[i].frame_a) for i = 1:num_arms]
9592
[connect(arms[i].frame_b, thrusters[i].frame_b) for i = 1:num_arms]
9693
]
97-
systems = [world; arms; body; thrusters; freemotion]
94+
systems = [world; arms; body; thrusters]
9895
if addload
9996
@named load = Body(m = load_mass, air_resistance=0.1)
10097
@named cable = Rope(
@@ -142,15 +139,16 @@ function RotorCraft(; closed_loop = true, addload=true)
142139
# append!(connections, [feedback_gain.input.u[i] ~ arms[i].frame_b.r_0[2] for i = 1:num_arms ]) # Connect positions to controller
143140
# append!(connections, [feedback_gain.input.u[i+num_arms] ~ D(arms[i].frame_b.r_0[2]) for i = 1:num_arms]) # Connect velocities to controller
144141
# append!(connections, [feedback_gain.input.u[i+2num_arms] ~ Ie[i] for i = 1:num_arms]) #
145-
# append!(connections, [feedback_gain.input.u[i] ~ freemotion.phi[[1,3][i]] for i = 1:2 ]) # Connect positions to controller
146-
# append!(connections, [feedback_gain.input.u[i+2] ~ freemotion.phid[[1,3][i]] for i = 1:2]) # Connect velocities to controller
142+
# append!(connections, [feedback_gain.input.u[i] ~ body.phi[[1,3][i]] for i = 1:2 ]) # Connect positions to controller
143+
# append!(connections, [feedback_gain.input.u[i+2] ~ body.phid[[1,3][i]] for i = 1:2]) # Connect velocities to controller
147144
# push!(systems, feedback_gain)
148145
=#
149146
end
150147
@named model = ODESystem(connections, t; systems)
151148
complete(model)
152149
end
153150
model = RotorCraft(closed_loop=true, addload=true)
151+
model = complete(model)
154152
ssys = structural_simplify(IRSystem(model))
155153
156154
op = [

docs/src/examples/ropes_and_cables.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ connections = [connect(world.frame_b, rope.frame_a)
3030
3131
@named stiff_rope = ODESystem(connections, t, systems = [world, body, rope])
3232
33+
stiff_rope = complete(stiff_rope)
3334
ssys = structural_simplify(IRSystem(stiff_rope))
3435
prob = ODEProblem(ssys, [], (0, 5))
3536
sol = solve(prob, Rodas4(autodiff=false))
@@ -53,6 +54,7 @@ connections = [connect(world.frame_b, rope.frame_a)
5354
5455
@named flexible_rope = ODESystem(connections, t, systems = [world, body, rope])
5556
57+
flexible_rope = complete(flexible_rope)
5658
ssys = structural_simplify(IRSystem(flexible_rope))
5759
prob = ODEProblem(ssys, [], (0, 8))
5860
sol = solve(prob, Rodas4(autodiff=false));
@@ -85,7 +87,7 @@ connections = [connect(world.frame_b, fixed.frame_a, chain.frame_a)
8587
connect(spring.frame_b, fixed.frame_b)]
8688
8789
@named mounted_chain = ODESystem(connections, t, systems = [systems; world])
88-
90+
mounted_chain = complete(mounted_chain)
8991
ssys = structural_simplify(IRSystem(mounted_chain))
9092
prob = ODEProblem(ssys, [
9193
collect(chain.link_8.body.w_a) .=> [0,0,0];

docs/src/examples/spherical_pendulum.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ connections = [connect(world.frame_b, joint.frame_a)
2727
connect(bar.frame_b, body.frame_a)]
2828
2929
@named model = ODESystem(connections, t, systems = [world; systems])
30+
model = complete(model)
3031
ssys = structural_simplify(IRSystem(model))
3132
3233
prob = ODEProblem(ssys, [], (0, 5))

docs/src/examples/wheel.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ prob = ODEProblem(ssys, [
332332
D(model.prismatic.r0[2]) => 0,
333333
], (0.0, 15.0))
334334
sol = solve(prob, Rodas5Pr())
335-
render(model, sol, show_axis=false, x=0, y=0, z=4, traces=[model.slipBasedWheelJoint.frame_a], filename="slipwheel.gif")
335+
render(model, sol, show_axis=false, x=0, y=0, z=4, traces=[model.slipBasedWheelJoint.frame_a], filename="slipwheel.gif", cache=fale)
336336
nothing # hide
337337
```
338338

@@ -452,7 +452,7 @@ defs = merge(
452452
prob = ODEProblem(ssys, defs, (0.0, 5.0))
453453
sol = solve(prob, Rodas5P(autodiff=false))
454454
@test SciMLBase.successful_retcode(sol)
455-
Multibody.render(model, sol, show_axis=false, x=0, y=0, z=5, filename="twotrack.gif")
455+
Multibody.render(model, sol, show_axis=false, x=0, y=0, z=5, filename="twotrack.gif", cache=false)
456456
nothing # hide
457457
```
458458

ext/Render.jl

Lines changed: 92 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ function recursive_extract(sol, s, depth=0)
4040
end
4141
end
4242

43-
4443
function getu(sol::FakeSol, syms)
4544
t->[recursive_extract(sol, s) for s in syms]
4645
end
@@ -75,6 +74,82 @@ function Base.getproperty(sol::FakeSol, s::Symbol)
7574
end
7675
end
7776

77+
mutable struct CacheSol
78+
model
79+
sol
80+
cache::Dict{Any, Any}
81+
vars
82+
last_t::Float64
83+
function CacheSol(model, sol)
84+
vars = get_all_vars(model) |> unique
85+
Main.vars = vars
86+
values = sol(0.0, idxs=vars)
87+
new(model, sol, Dict(vars .=> values), vars, 0)
88+
end
89+
end
90+
91+
function get_all_vars(model, vars = Multibody.collect_all(unknowns(model)))
92+
for sys in model.systems
93+
if ModelingToolkit.isframe(sys)
94+
newvars = Multibody.ModelingToolkit.renamespace.(model.name, Multibody.Symbolics.unwrap.(vec(ori(sys).R)))
95+
append!(vars, newvars)
96+
else
97+
subsys_ns = getproperty(model, sys.name)
98+
get_all_vars(subsys_ns, vars)
99+
end
100+
end
101+
vars
102+
end
103+
104+
105+
get_cached(cs::CacheSol, t::AbstractArray, idxs) = cs.sol(t; idxs)
106+
function get_cached(cs::CacheSol, t::Real, idxs)
107+
if ModelingToolkit.isparameter(idxs[1])
108+
return cs.prob.ps[idxs]
109+
end
110+
if idxs isa AbstractArray{Num}
111+
idxs = Multibody.Symbolics.unwrap.(idxs)
112+
end
113+
if !haskey(cs.cache, idxs[1])
114+
# Fallback for things not in cache
115+
return cs.sol(t; idxs)
116+
end
117+
if t != cs.last_t
118+
values = cs.sol(t, idxs=cs.vars)
119+
cs.cache = Dict(cs.vars .=> values)
120+
cs.last_t = t
121+
end
122+
if idxs isa Real
123+
return cs.cache[idxs]
124+
else
125+
return [cs.cache[i] for i in idxs]
126+
end
127+
end
128+
129+
130+
function getu(cs::CacheSol, syms)
131+
t->get_cached(cs::CacheSol, t.t, syms)
132+
end
133+
134+
function ModelingToolkit.parameter_values(cs::CacheSol)
135+
ModelingToolkit.parameter_values(cs.sol)
136+
# pars = Multibody.collect_all(parameters(cs.model))
137+
# cs.prob.ps[pars]
138+
end
139+
140+
function (cs::CacheSol)(t; idxs=nothing)
141+
if idxs === nothing
142+
cs.sol(t)
143+
else
144+
get_cached(cs, t, idxs)
145+
end
146+
end
147+
148+
function Base.getproperty(cs::CacheSol, s::Symbol)
149+
s fieldnames(typeof(cs)) && return getfield(cs, s)
150+
return getproperty(getfield(cs, :sol), s)
151+
end
152+
78153

79154

80155
"""
@@ -199,11 +274,15 @@ function render(model, sol,
199274
traces = nothing,
200275
display = false,
201276
loop = 1,
277+
cache = true,
202278
kwargs...
203279
)
280+
ModelingToolkit.iscomplete(model) || (model = complete(model))
204281
if sol isa ODEProblem
205282
sol = FakeSol(model, sol)
206283
return render(model, sol, 0; x, y, z, lookat, up, show_axis, kwargs...)[1]
284+
elseif cache
285+
sol = CacheSol(model, sol)
207286
end
208287
scene, fig = default_scene(x,y,z; lookat,up,show_axis)
209288
if timevec === nothing
@@ -258,14 +337,19 @@ function render(model, sol, time::Real;
258337
x = 2,
259338
y = 0.5,
260339
z = 2,
340+
cache = true,
261341
kwargs...,
262342
)
263343

344+
ModelingToolkit.iscomplete(model) || (model = complete(model))
264345
slider = !(sol isa Union{ODEProblem, FakeSol})
265346

266347
if sol isa ODEProblem
267348
sol = FakeSol(model, sol)
268349
end
350+
if cache
351+
sol = CacheSol(model, sol)
352+
end
269353

270354
# fig = Figure()
271355
# scene = LScene(fig[1, 1]).scene
@@ -332,13 +416,11 @@ end
332416
render!(scene, ::Any, args...) = false # Fallback for systems that have no rendering
333417

334418
function render!(scene, ::typeof(Body), sys, sol, t)
335-
sol(sol.t[1], idxs=sys.render)==true || return true # yes, == true
419+
render, radius, length_fraction, cylinder_radius = sol(sol.t[1], idxs=[sys.render, sys.radius, sys.length_fraction, sys.cylinder_radius]) .|> Float32
420+
render==true || return true # yes, == true
336421
color = get_color(sys, sol, :purple)
337422
r_cm = get_fun(sol, collect(sys.r_cm))
338423
framefun = get_frame_fun(sol, sys.frame_a)
339-
radius = sol(sol.t[1], idxs=sys.radius) |> Float32
340-
length_fraction = sol(sol.t[1], idxs=sys.length_fraction) |> Float32
341-
cylinder_radius = sol(sol.t[1], idxs=sys.cylinder_radius) |> Float32
342424
thing = @lift begin # Sphere
343425
Ta = framefun($t)
344426
coords = (Ta*[r_cm($t); 1])[1:3] # TODO: make use of a proper transformation library instead of rolling own?
@@ -504,8 +586,6 @@ end
504586
function render!(scene, ::typeof(BodyShape), sys, sol, t)
505587
color = get_color(sys, sol, :purple)
506588
shapepath = get_shape(sys, sol)
507-
Tshape = reshape(sol(sol.t[1], idxs=sys.shape_transform), 4, 4)
508-
scale = Vec3f(Float32(sol(sol.t[1], idxs=sys.shape_scale))*ones(Float32, 3))
509589
if isempty(shapepath)
510590
radius = Float32(sol(sol.t[1], idxs=sys.radius))
511591
r_0a = get_fun(sol, collect(sys.frame_a.r_0))
@@ -520,7 +600,9 @@ function render!(scene, ::typeof(BodyShape), sys, sol, t)
520600
mesh!(scene, thing; color, specular = Vec3f(1.5), shininess=20f0, diffuse=Vec3f(1), transparency=true)
521601
else
522602
T = get_frame_fun(sol, sys.frame_a)
523-
603+
scale = Vec3f(Float32(sol(sol.t[1], idxs=sys.shape_scale))*ones(Float32, 3))
604+
Tshape = reshape(sol(sol.t[1], idxs=sys.shape_transform), 4, 4)
605+
524606
@info "Loading shape mesh $shapepath"
525607
shapemesh = FileIO.load(shapepath)
526608
m = mesh!(scene, shapemesh; color, specular = Vec3f(1.5))
@@ -570,9 +652,7 @@ function render!(scene, ::typeof(BodyBox), sys, sol, t)
570652

571653
# NOTE: This draws a solid box without the hole in the middle. Cannot figure out how to render a hollow box
572654
color = get_color(sys, sol, [1, 0.2, 1, 0.9])
573-
width = Float32(sol(sol.t[1], idxs=sys.width))
574-
height = Float32(sol(sol.t[1], idxs=sys.height))
575-
length = Float32(sol(sol.t[1], idxs=sys.render_length))
655+
width, height, length = Float32.(sol(sol.t[1], idxs=[sys.width, sys.height, sys.render_length]))
576656

577657
length_dir = sol(sol.t[1], idxs=collect(sys.render_length_dir))
578658
width_dir = sol(sol.t[1], idxs=collect(sys.render_width_dir))
@@ -782,6 +862,7 @@ end
782862

783863
Multibody.render!(scene, ::typeof(Multibody.URDFRevolute), sys, sol, t) = false
784864
Multibody.render!(scene, ::typeof(Multibody.URDFPrismatic), sys, sol, t) = false
865+
Multibody.render!(scene, ::typeof(Multibody.NullJoint), sys, sol, t) = false
785866

786867
# ==============================================================================
787868
## PlanarMechanics

0 commit comments

Comments
 (0)