Skip to content

Commit

Permalink
Support linear animation transitions.
Browse files Browse the repository at this point in the history
  • Loading branch information
ztangent committed Oct 19, 2023
1 parent fd7ae2f commit ae71b0a
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 4 deletions.
78 changes: 78 additions & 0 deletions src/renderers/graphworld/animate.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""
Transition
Abstract animation transition type.
"""
abstract type Transition end

"""
StepTransition
Transition that immediately steps to the next state.
"""
struct StepTransition <: Transition end

"""
LinearTransition
Transition that linearly interpolates between node positions.
"""
struct LinearTransition <: Transition end

function anim_transition!(
canvas::Canvas, renderer::GraphworldRenderer, domain::Domain,
state::State, action::Term = PDDL.no_op, t::Int = 1;
transition=StepTransition(), options...
)
options = merge(renderer.anim_options, options)
return anim_transition!(
transition, canvas, renderer, domain, state, action, t;
options...
)
end

function anim_transition!(
trans::StepTransition,
canvas::Canvas, renderer::GraphworldRenderer, domain::Domain,
state::State, action::Term = PDDL.no_op, t::Int = 1;
callback=nothing, overlay=nothing, options...
)
# Update canvas with new state
canvas.state[] = state
# Run callback
overlay !== nothing && overlay(canvas)
callback !== nothing && callback(canvas)
return canvas
end

function anim_transition!(
trans::LinearTransition,
canvas::Canvas, renderer::GraphworldRenderer, domain::Domain,
state::State, action::Term = PDDL.no_op, t::Int = 1;
callback=nothing, overlay=nothing, options...
)
options = merge(renderer.anim_options, options)
# Copy starting node positions
node_start_pos = copy(canvas.observables[:node_pos][])
# Update canvas with new state
canvas.state[] = state
# Copy ending node positions
node_stop_pos = copy(canvas.observables[:node_pos][])
# Compute differences
node_diffs = node_stop_pos .- node_start_pos
# Temporarily disconnect graph layout from node positions
layout = canvas.observables[:layout][]
canvas.observables[:layout][] = node_start_pos
# Linearly interpolate between start and stop positions
frames_per_step = get(options, :frames_per_step, 10)
for t in 1:frames_per_step
node_pos = node_start_pos .+ node_diffs .* t / frames_per_step
canvas.observables[:layout][] = node_pos
# Run callbacks
overlay !== nothing && overlay(canvas)
callback !== nothing && callback(canvas)
end
# Reconnect graph layout to node positions
canvas.observables[:layout].val = layout
return canvas
end
3 changes: 2 additions & 1 deletion src/renderers/graphworld/graphworld.jl
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ function BlocksworldRenderer(;
:show_movable_graphics => true
),
graph_options = Dict{Symbol, Any}(
:show_arrow => false,
:arrow_show => false,
:node_size => 0.0,
:edge_width => 0.0,
:node_attr => (markerspace=:data,),
Expand Down Expand Up @@ -200,6 +200,7 @@ function new_canvas(renderer::GraphworldRenderer)
end

include("state.jl")
include("animate.jl")

# Add documentation for auxiliary options
Base.with_logger(Base.NullLogger()) do
Expand Down
4 changes: 2 additions & 2 deletions src/renderers/graphworld/layouts.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ function StressLocSpringMov(;
dim = 2,
Ptype = Float64,
n_locs = 0,
stress_kwargs = Dict{Symbol, Any}(),
spring_kwargs = Dict{Symbol, Any}(:C => 0.3)
stress_kwargs = Dict{Symbol, Any}(:seed => 1),
spring_kwargs = Dict{Symbol, Any}(:C => 0.3, :seed => 1)
)
return StressLocSpringMov{dim, Ptype}(n_locs, stress_kwargs, spring_kwargs)
end
Expand Down
4 changes: 3 additions & 1 deletion src/renderers/graphworld/state.jl
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ function render_state!(
gp = graphplot!(ax, graph; layout=layout, node_color=node_colors,
nlabels=node_labels, elabels=edge_labels,
edge_color=edge_colors, renderer.graph_options...)
canvas.plots[:graph] = gp
canvas.plots[:graphplot] = gp
canvas.observables[:graph] = graph
canvas.observables[:layout] = gp[:layout]
canvas.observables[:node_pos] = gp[:node_pos]
# Update node label offsets
label_offset = get(options, :label_offset, 0.15)
Expand Down
7 changes: 7 additions & 0 deletions test/graphworld/zeno_travel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,10 @@ storyboard = render_storyboard(
"(v) Person 1 debarks plane", "(vi) Plane flies back to city 2"],
xlabelsize=18, subtitlesize=22
)

# Render animation with linearly interpolated transitions
canvas = renderer(domain, state)
anim = anim_plan!(canvas, renderer, domain, state, plan,
transition=PDDLViz.LinearTransition(),
framerate=30, frames_per_step=30)
save("zeno_travel_smooth.mp4", anim)

0 comments on commit ae71b0a

Please sign in to comment.