Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add merge! for animation types, insert! for AnimationTrack. #115

Merged
merged 11 commits into from
Aug 5, 2019
1 change: 1 addition & 0 deletions src/MeshCat.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export ArrowVisualizer
abstract type AbstractObject end
abstract type AbstractMaterial end

include("util.jl")
include("trees.jl")
using .SceneTrees
include("mesh_files.jl")
Expand Down
51 changes: 49 additions & 2 deletions src/animations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,37 @@
struct AnimationTrack{T}
name::String
jstype::String
frames::Vector{Int}
values::Vector{T}
events::Vector{Pair{Int, T}} # frame => value
end

AnimationTrack{T}(name::String, jstype::String) where {T} = AnimationTrack(name, jstype, Pair{Int, T}[])

wider_js_type(::Type{<:Integer}) = Float64 # Javascript thinks everything is a `double`
wider_js_type(::Type{Float64}) = Float64
wider_js_type(x) = x

function Base.insert!(track::AnimationTrack, frame::Integer, value)
i = searchsortedfirst(track.events, frame; by=first)
if i <= length(track.events) && first(track.events[i]) == frame
track.events[i] = frame => value
else
insert!(track.events, i, frame => value)
end
return track
end

function Base.merge!(a::AnimationTrack{T}, others::AnimationTrack{T}...) where T
l = length(a)
events = copy(a.events)
for other in others
@assert other.name == a.name
@assert other.jstype == a.jstype
events_temp = similar(events, length(events) + length(other.events))
mergesorted!(events_temp, events, other.events; by=first)
events = events_temp
end
a.events = unique(first, a.events) # TODO: use unique! in ≥ 1.1
return a
end

@with_kw struct AnimationClip
Expand All @@ -12,13 +41,31 @@ end
name::String = "default"
end

function Base.merge!(a::AnimationClip, others::AnimationClip...)
for other in others
@assert other.fps == a.fps
merge!(merge!, a.tracks, other.tracks) # merge tracks recursively
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just thinking: there could potentially be two animation tracks with the same name but different jstypes. That would currently error here. Again, not with the current atframe implementation,

track = get!(clip.tracks, prop) do
AnimationTrack(prop, jstype, Int[], wider_js_type(typeof(value))[])
end

but still.

end
return a
end

struct Animation
clips::Dict{Path, AnimationClip}
default_framerate::Int
end

Animation(fps::Int=30) = Animation(Dict{Path, AnimationClip}(), fps)

function Base.merge!(a::Animation, others::Animation...)
for other in others
@assert a.default_framerate == other.default_framerate
tkoolen marked this conversation as resolved.
Show resolved Hide resolved
merge!(merge!, a.clips, other.clips) # merge clips recursively
end
return a
end

Base.merge(a::Animation, others::Animation...) = merge!(Animation(a.default_framerate), a, others...)

function convert_frames_to_video(tar_file_path::AbstractString, output_path::AbstractString="output.mp4"; framerate=60, overwrite=false)
output_path = abspath(output_path)
if !isfile(tar_file_path)
Expand Down
16 changes: 4 additions & 12 deletions src/atframe.jl
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
function _setprop!(track::AnimationTrack, frame::Integer, value)
i = searchsortedfirst(track.frames, frame)
insert!(track.frames, i, frame)
insert!(track.values, i, value)
end

wider_js_type(::Type{<:Integer}) = Float64 # Javascript thinks everything is a `double`
wider_js_type(::Type{Float64}) = Float64
wider_js_type(x) = x

function _setprop!(clip::AnimationClip, frame::Integer, prop::AbstractString, jstype::AbstractString, value)
T = wider_js_type(typeof(value))
track = get!(clip.tracks, prop) do
AnimationTrack(prop, jstype, Int[], wider_js_type(typeof(value))[])
AnimationTrack{T}(prop, jstype)
end
_setprop!(track, frame, value)
insert!(track, frame, value)
return nothing
end

function getclip!(animation::Animation, path::Path)
Expand Down
6 changes: 3 additions & 3 deletions src/lowering.jl
Original file line number Diff line number Diff line change
Expand Up @@ -323,9 +323,9 @@ function lower(track::AnimationTrack)
"name" => string(".", track.name),
"type" => track.jstype,
"keys" => [Dict{String, Any}(
"time" => track.frames[i],
"value" => lower(track.values[i])
) for i in eachindex(track.frames)]
"time" => frame,
"value" => lower(value)
) for (frame, value) in track.events]
)
end

Expand Down
33 changes: 33 additions & 0 deletions src/util.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Port of possible implementation of std::merge (second version),
# https://en.cppreference.com/w/cpp/algorithm/merge
# TODO: contribute to base.
@inline function mergesorted!(dest::AbstractVector, a, b; by=identity, lt=isless)
i = 1
it_a = iterate(a)
it_b = iterate(b)
while it_a !== nothing
val_a, state_a = it_a
if it_b === nothing
dest[i] = val_a
i += 1
copyto!(dest, i, Iterators.rest(a, state_a))
return dest
end
val_b, state_b = it_b
if lt(by(val_b), by(val_a))
dest[i] = val_b
it_b = iterate(b, state_b)
else
dest[i] = val_a
it_a = iterate(a, state_a)
end
i += 1
end
if it_b !== nothing
val_b, state_b = it_b
dest[i] = val_b
i += 1
copyto!(dest, i, Iterators.rest(b, state_b))
end
return dest
end
2 changes: 2 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ using CoordinateTransformations
using Colors
using MeshIO, FileIO

include("util.jl")

@testset "MeshCat" begin
include("video_rendering.jl")
include("paths.jl")
Expand Down
35 changes: 35 additions & 0 deletions test/util.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module UtilTest

using Test
using Random
using MeshCat: mergesorted!

@testset "mergesorted!" begin
@testset "basic" begin
rng = MersenneTwister(1)
for i = 1 : 1000
a = sort(rand(1 : 100, rand(0 : 20)))
b = sort(rand(1 : 100, rand(0 : 20)))
result = similar(a, length(a) + length(b))
mergesorted!(result, a, b)
@test issorted(result)
@test a ⊆ result
@test b ⊆ result
end
end

@testset "by" begin
rng = MersenneTwister(1)
for i = 1 : 1000
a = sort([rand(1 : 100) => rand(1 : 100) for _ in rand(0 : 20)]; dims=1, by=first)
b = sort([rand(1 : 100) => rand(1 : 100) for _ in rand(0 : 20)]; dims=1, by=first)
result = similar(a, length(a) + length(b))
mergesorted!(result, a, b; by=first)
@test issorted(result; by=first)
@test a ⊆ result
@test b ⊆ result
end
end
end

end
17 changes: 11 additions & 6 deletions test/visualizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -248,20 +248,25 @@ end
end

@testset "Animation (new style)" begin
anim = Animation()
atframe(anim, 0) do
anim1 = Animation()
atframe(anim1, 0) do
settransform!(vis[:shapes][:box], Translation(0., 0, 0))
end
atframe(anim, 30) do
atframe(anim1, 30) do
settransform!(vis[:shapes][:box], Translation(2., 0, 0) ∘ LinearMap(RotZ(π/2)))
end
atframe(anim, 0) do
setanimation!(vis, anim1)
anim2 = Animation()
atframe(anim2, 0) do
setprop!(vis["/Cameras/default/rotated/<object>"], "zoom", 1)
end
atframe(anim, 30) do
atframe(anim2, 30) do
setprop!(vis["/Cameras/default/rotated/<object>"], "zoom", 0.5)
end
setanimation!(vis, anim)
setanimation!(vis, anim2)
anim_combined = merge(anim1, anim2)
@test Set(Iterators.flatten((keys(anim1.clips), keys(anim2.clips)))) == Set(keys(anim_combined.clips))
setanimation!(vis, anim_combined)
end
end

Expand Down