diff --git a/src/animations.jl b/src/animations.jl index 33060cd..e94c884 100644 --- a/src/animations.jl +++ b/src/animations.jl @@ -7,10 +7,6 @@ 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 @@ -22,16 +18,17 @@ function Base.insert!(track::AnimationTrack, frame::Integer, value) end function Base.merge!(a::AnimationTrack{T}, others::AnimationTrack{T}...) where T - l = length(a) - events = copy(a.events) + l = length(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 + l += length(other.events) end - a.events = unique(first, a.events) # TODO: use unique! in ≥ 1.1 + events = similar(a.events, l) + mergesorted!(events, a.events, Iterators.flatten((other.events for other in others))) + combine!(events, by=first, combine=(a, b) -> b) + resize!(a.events, length(events)) + copyto!(a.events, events) return a end diff --git a/src/atframe.jl b/src/atframe.jl index 70b7472..23fc033 100644 --- a/src/atframe.jl +++ b/src/atframe.jl @@ -1,3 +1,7 @@ +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 diff --git a/src/util.jl b/src/util.jl index 07399bd..d31216e 100644 --- a/src/util.jl +++ b/src/util.jl @@ -1,6 +1,25 @@ # Port of possible implementation of std::merge (second version), # https://en.cppreference.com/w/cpp/algorithm/merge # TODO: contribute to base. + +""" + mergesorted!(dest::AbstractVector, a, b; by=identity, lt=isless) + +Merge sorted iterators `a` and `b`, storing the result in `dest`. +`dest` should be of the appropriate length to store all elements of `a` and `b`; +it is not resized. + +For equivalent elements in `a` and `b`, the elements from `a` +(preserving their original order) precede the elements from `b` +(preserving their original order). + +Elements are compared by `(x, y) -> lt(by(x), by(y))` + +The behavior is undefined if `dest` overlaps `a` or `b` (though the `a` and `b` +may overlap each other) or if `a` or `b` are not sorted. + +Adapted from https://en.cppreference.com/w/cpp/algorithm/merge. +""" @inline function mergesorted!(dest::AbstractVector, a, b; by=identity, lt=isless) i = 1 it_a = iterate(a) @@ -31,3 +50,29 @@ end return dest end + +# from https://github.com/JuliaOpt/MathOptInterface.jl/blob/d3106f434293a2aae7c664a22a30a7bd4069111a/src/Utilities/functions.jl#L390. +# TODO: contribute to Base +function combine!(x::AbstractVector; by=identity, keep=x->true, combine) + @boundscheck issorted(x; by=by) || throw(ArgumentError("Input is not sorted.")) + if length(x) > 0 + i1 = firstindex(x) + for i2 in eachindex(x)[2:end] + if by(x[i1]) == by(x[i2]) + x[i1] = combine(x[i1], x[i2]) + else + if !keep(x[i1]) + x[i1] = x[i2] + else + x[i1 + 1] = x[i2] + i1 += 1 + end + end + end + if !keep(x[i1]) + i1 -= 1 + end + resize!(x, i1) + end + return x +end diff --git a/test/visualizer.jl b/test/visualizer.jl index 45fd3d6..420f0dd 100644 --- a/test/visualizer.jl +++ b/test/visualizer.jl @@ -270,6 +270,26 @@ end end end +@testset "AnimationTrack" begin + track1 = MeshCat.AnimationTrack{Float64}("foo", "bar") + @test track1.name == "foo" + @test track1.jstype == "bar" + insert!(track1, 0, 32.0) + @test track1.events == [0 => 32.0] + insert!(track1, 2, 64.0) + @test track1.events == [0 => 32.0, 2 => 64.0] + insert!(track1, 2, 65.0) + @test track1.events == [0 => 32.0, 2 => 65.0] + + track2 = MeshCat.AnimationTrack{Float64}("foo", "bar") + insert!(track2, 1, 17.0) + insert!(track2, 2, 66.0) + insert!(track2, 5, 1.0) + + merge!(track1, track2) + @test track1.events == [0 => 32.0, 1 => 17.0, 2 => 66.0, 5 => 1.0] +end + @testset "setvisible!" begin v = vis[:box_to_hide] setobject!(v, HyperRectangle(Vec(0., 0, 0), Vec(0.1, 0.2, 0.3)))