Skip to content

Commit

Permalink
Merge pull request #45 from davibarreira/arc-primitive
Browse files Browse the repository at this point in the history
Ellipse primitive
  • Loading branch information
davibarreira authored Sep 26, 2024
2 parents 927e709 + 9ea18ba commit 0898fef
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 12 deletions.
5 changes: 3 additions & 2 deletions src/Vizagrams.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ using FreeTypeAbstraction

using Hyperscript: Hyperscript, Style, m
using Librsvg_jll: Librsvg_jll, rsvg_convert
using LinearAlgebra: LinearAlgebra, UniformScaling, dot, norm, normalize,
using LinearAlgebra: LinearAlgebra, UniformScaling, dot, norm, normalize, , Diagonal
using MLStyle: MLStyle, @data, @match
using Memoize: @memoize
using NamedTupleTools: NamedTupleTools
Expand Down Expand Up @@ -51,6 +51,7 @@ include("primitives/circle.jl")
include("primitives/square.jl")
include("primitives/rectangle.jl")
include("primitives/line.jl")
include("primitives/ellipse.jl")
export intersects
include("primitives/bezier.jl")
include("primitives/polygon.jl")
Expand All @@ -59,7 +60,7 @@ include("primitives/slice.jl")
include("primitives/gradient.jl")

include("primitives/envelopes.jl")
export Circle, Square, Rectangle, Line, Polygon
export Circle, Square, Rectangle, Line, Polygon, Ellipse
export RegularPolygon, TextGeom, Bezier, QBezier, CBezier
export QBezierPolygon, CBezierPolygon, Slice, LinearAlgebra

Expand Down
10 changes: 10 additions & 0 deletions src/auxiliar/auxiliary_geometric_functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ function angle_between_vectors(v1::Vector, v2::Vector)
return angle
end

"""
atan2pi(v::Vector)
Similar to `atan`, but returns an angle between [0,2pi].
"""
function atan2pi(v::Vector)
θ = atan(v[2], v[1])
return θ >= 0 ? θ : θ + 2π
end

# """
# svector2ify(v::Vector{Vector{T}}) where {T}

Expand Down
19 changes: 15 additions & 4 deletions src/auxiliar/normalize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,26 @@ function centralize_graphic(t::TMark)::TMark
end

"""
normalize_graphic(t::TMark)::TMark
normalize_graphic(t::TMark,direction=nothing)::TMark
Takes a mark tree, centralizes it and then
scale it to 1x1 size.
scale it to 1x1 size. The direction specifies
whether the scaling prioritizes the :width, :height or nothing.
If the direction is `nothing`, then the function guarantees
that the larger (width or height) is scaled in order to give 1.
Using `direction=:width`, we guarantee a total width is 1,
and using `:height` we guarantee that the height is 1.
"""
function normalize_graphic(t::TMark)::TMark
function normalize_graphic(t::TMark, direction=nothing)::TMark
t = centralize_graphic(t)
bb = boundingbox(t)
w, h = bb[2] - bb[1]
s = 2min(1 / w, 1 / h)
if isnothing(direction)
s = 2min(1 / w, 1 / h)
elseif direction == :width
s = 2 / w
elseif direction == :height
s = 2 / h
end
return U(s) * t
end
36 changes: 32 additions & 4 deletions src/backends/svgbackend.jl
Original file line number Diff line number Diff line change
Expand Up @@ -310,13 +310,41 @@ function primtosvg(geom::LinearGradient, s::S)
)
end

function reducesvg(x::Vector{SVG}; kwargs...)
# reduce(⋄, x; init=m("svg"; kwargs...))
return reduce(
, x; init=m("svg"; xmlns="http://www.w3.org/2000/svg", version="1.1", kwargs...)
"""
primtosvg(g::Ellipse, s::Style) =
m("ellipse", cx=p.geom.c[1], cy=p.geom.c[2], rx=p.geom.rx, ry=p.geom.ry, transform="rotate(\$(r.ang))", style=dicttostring(p.s.d))
"""
function primtosvg(geom::Ellipse, s::S)
sty, attr = split_style_attributes(s)
return m(
"ellipse";
cx=geom.c[1],
cy=geom.c[2],
rx=geom.rx,
ry=geom.ry,
style=dicttostring(sty),
transform="scale(1,-1) rotate($(rad2deg(geom.ang)) $(geom.c[1]) $(geom.c[2]))",
attr...,
)
end

function reducesvg(x::Vector{SVG}; style="", kwargs...)
reduce(, x; init=m("svg"; kwargs...))
# tag = m("svg"; xmlns="http://www.w3.org/2000/svg", version="1.1", kwargs...)
# style_tag = m(
# "style",
# """
# .mycircle {
# fill: gold !important;
# stroke: maroon;
# stroke-width: 2px;
# }
# """,
# )
# tag = tag ⋄ style_tag
# return reduce(⋄, x; init=tag)
end

"""
tosvg(dprim::TDiagram; height=200, width=1000)
Expand Down
42 changes: 42 additions & 0 deletions src/primitives/ellipse.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
struct Ellipse <: GeometricPrimitive
rx::Real # Semi-major axis
ry::Real # Semi-minor axis
c::Vector # Center of the ellipse
ang::Real
end

# Constructor for Ellipse with default values
Ellipse(; rx=2, ry=1, c=[0, 0], ang=0) = Ellipse(rx, ry, c, ang)

# Covariant representation of an Ellipse (CovEllipse)
# <rx_vec, ry_vec> = 0 (inner product is zero, both vectors are perpendicular)
struct CovEllipse
_1::Vector # rx_vec
_2::Vector # ry_vec
_3::Vector # center
end

# Action of a transformation group on CovEllipse
act(g::G, x::CovEllipse) = CovEllipse(g(x._1), g(x._2), g(x._3))

# Mapping from CovEllipse to Ellipse
ψ(p::CovEllipse) = Ellipse(norm(p._1 - p._3), norm(p._2 - p._3), p._3, atan2pi(p._1 - p._3))

# Mapping from Ellipse to CovEllipse
function ϕ(p::Ellipse)
return CovEllipse(
rotatevec([p.rx, 0], p.ang) + p.c, rotatevec([0, p.ry], p.ang) + p.c, p.c
)
end

# """
# coordinates(p::Ellipse)
# Generate points in the ellipse.
# """
# function coordinates(p::Ellipse)
# a = p.rx
# b = p.ry
# c = p.c
# ang = p.ang
# return pts = [R(ang)([a * cos(θ), b * sin(θ)]) + c for θ in 0:(2π / 100):(2π)]
# end
36 changes: 36 additions & 0 deletions src/primitives/envelopes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,42 @@ function envelope(t::LinearGradient, s::S, v::Vector)
return nothing
end

function envelope(e::Ellipse, s::S, v::Vector)
# Normalize the direction vector
v_global = normalize(v)

# Compute the rotation matrix for the ellipse
cosθ = cos(e.ang)
sinθ = sin(e.ang)
Rot = [cosθ -sinθ; sinθ cosθ]

# Compute the covariance matrix of the ellipse
D = Diagonal([e.rx^2, e.ry^2])
Se = Rot * D * Rot'

# Compute the support function (envelope)
s = sqrt(v_global' * Se * v_global)

# Return the envelope value
return dot(e.c, v_global) + s
end

# function envelope(e::Ellipse, s::S, v::Vector)
# # Normalize the direction vector
# v = normalize(v)

# vlocal = R(-e.ang)(normalize(v))

# # Compute the envelope by scaling the vector according to the axes of the ellipse
# vlocal = [e.rx * vlocal[1], e.ry * vlocal[2]]

# # p = e.c + [e.rx * v[1], e.ry * v[2]]
# p = e.c + R(e.ang)(vlocal)

# # Return the dot product of the resulting point and the vector v
# return dot(p, v)
# end

envelope(p::Prim, v::Vector) = envelope(p.geom, p.s, v);
envelope(p::GeometricPrimitive, v::Vector) = envelope(p, S(), v);

Expand Down
38 changes: 38 additions & 0 deletions test/primitives/test_ellipse.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Vizagrams: CovEllipse, ϕ, ψ, coordinates

# Test default constructor
@testset "Ellipse Default Constructor" begin
e = Ellipse()
@test e.rx == 2
@test e.ry == 1
@test e.c == [0, 0]
@test e.ang == 0
end

# Test custom constructor
@testset "Ellipse Custom Constructor" begin
e = Ellipse(rx=3, ry=4, c=[1, 2], ang=π/4)
@test e.rx == 3
@test e.ry == 4
@test e.c == [1, 2]
@test e.ang == π/4
end

# Test CovEllipse to Ellipse mapping
@testset "CovEllipse to Ellipse Mapping" begin
cov_e = CovEllipse([3, 0], [0, 4], [1, 2])
e = ψ(cov_e)
@test e.rx 3
@test e.ry 4
@test e.c == [1, 2]
@test e.ang 0
end

# Test Ellipse to CovEllipse mapping
@testset "Ellipse to CovEllipse Mapping" begin
e = Ellipse(rx=3, ry=4, c=[1, 2], ang=π/4)
cov_e = ϕ(e)
@test cov_e._1 rotatevec([3, 0], π/4) + [1, 2]
@test cov_e._2 rotatevec([0, 4], π/4) + [1, 2]
@test cov_e._3 == [1, 2]
end
7 changes: 5 additions & 2 deletions test/primitives/test_envelopes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ using Vizagrams
CBezierPolygon(
[[0, 0], [0, 1], [1, 2], [1, 1]],
[[0, 0], [0, 1], [0.5, 1], [0.5, 2], [1, 2], [1, 1], [0.5, 1], [0.5, 0]]),
TextGeom(pos=[0, 0], fontsize=1, text="Hello")
TextGeom(pos=[0, 0], fontsize=1, text="Hello"),
Ellipse(ang=π / 4)
]
envs = [
[1.5, 1.0, 1.5, 1.0],
Expand All @@ -22,7 +23,9 @@ using Vizagrams
[3.0, 1.0, 0.0, 0.0],
[1.0, 0.5, -0.2, 0.49999999999999994],
[1.0, 2.0, 0.0, 0.0],
[2.9850260416666665, 0.9563802083333333, -0.10481770833333333, 0.025390625]]
[2.9850260416666665, 0.9563802083333333, -0.10481770833333333, 0.025390625],
[1.5811388300841898, 1.5811388300841895, 1.5811388300841898, 1.5811388300841895]
]
@testset "envelope" begin
for (i, g) in enumerate(geoms)
for (j, v) in enumerate([[1, 0], [0, 1], [-1, 0], [0, -1]])
Expand Down

0 comments on commit 0898fef

Please sign in to comment.