Skip to content
This repository has been archived by the owner on Nov 22, 2023. It is now read-only.

Added capsule shape #204

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/GeometryTypes.jl
Expand Up @@ -50,6 +50,7 @@ include("polygon.jl")
include("polygon_triangulations.jl")
include("lines.jl")
include("cylinder.jl")
include("capsule.jl")

export AABB,
AbstractFlexibleGeometry,
Expand All @@ -64,6 +65,9 @@ export AABB,
Cylinder,
Cylinder2,
Cylinder3,
Capsule,
Capsule2,
Capsule3,
decompose,
direction,
eltype_or,
Expand Down
23 changes: 23 additions & 0 deletions src/capsule.jl
@@ -0,0 +1,23 @@
origin(c::Capsule{N, T}) where {N, T} = c.origin
extremity(c::Capsule{N, T}) where {N, T} = c.extremity
radius(c::Capsule{N, T}) where {N, T} = c.r
height(c::Capsule{N, T}) where {N, T} = norm(c.extremity - c.origin)
direction(c::Capsule{N, T}) where {N, T} = (c.extremity .- c.origin) ./ height(c)

function rotation(c::Capsule{2, T}) where T
d2 = direction(c); u = @SVector [d2[1], d2[2], T(0)]
v = @MVector [u[2], -u[1], T(0)]
normalize!(v)
return hcat(v, u, @SVector T[0, 0, 1])
end
function rotation(c::Capsule{3, T}) where T
d3 = direction(c); u = @SVector [d3[1], d3[2], d3[3]]
if abs(u[1]) > 0 || abs(u[2]) > 0
v = @MVector [u[2], -u[1], T(0)]
else
v = @MVector [T(0), -u[3], u[2]]
end
normalize!(v)
w = @SVector [u[2] * v[3] - u[3] * v[2], -u[1] * v[3] + u[3] * v[1], u[1] * v[2] - u[2] * v[1]]
return hcat(v, w, u)
end
70 changes: 70 additions & 0 deletions src/decompose.jl
Expand Up @@ -447,3 +447,73 @@ function decompose(::Type{FT}, c::Cylinder{3}, facets = 30) where FT <: Face
end
return indexes
end


isdecomposable(::Type{T}, ::Type{C}) where {T <:Point, C <:Capsule3} = true
isdecomposable(::Type{T}, ::Type{C}) where {T <:Face, C <:Capsule3} = true
isdecomposable(::Type{T}, ::Type{C}) where {T <:Point, C <:Capsule2} = true
isdecomposable(::Type{T}, ::Type{C}) where {T <:Face, C <:Capsule2} = true

# def of resolution + rotation
function decompose(PT::Type{Point{3, T}}, c::Capsule{2}, resolution = 10) where T
origin = length(c.origin) == 2 ? Point{3, T}(c.origin[1], c.origin[2], 0) : c.origin
nbv = 2*max.(4, resolution)
M = rotation(c); h = height(c)
vertices = Vector{PT}(undef, 2 * (nbv + 1))
for i = 0:nbv
theta = T((π * i) / nbv)
vertices[i + 1] = PT(M * Point{3, T}( cos(theta) * c.r, - sin(theta) * c.r, 0)) + origin
vertices[i + nbv + 2] = PT(M * Point{3, T}(-cos(theta) * c.r, h + sin(theta) * c.r, 0)) + origin
end
return vertices
end

function decompose(PT::Type{Point{3, T}}, c::Capsule{3}, resolution = (30, 10)) where T
nbv = max.(4, resolution)
M = rotation(c); h = height(c)
position = 1; vertices = Vector{PT}(undef, nbv[1] * (2 * nbv[2]) + 2)
for j = 1:nbv[1]
phi = T((2π * (j - 1)) / nbv[1])
for i = 0:(nbv[2]-1)
theta = T((π/2 * i) / nbv[2])
vertices[position + i] = PT(M * Point{3, T}(cos(theta) * cos(phi) * c.r, cos(theta) * sin(phi) * c.r, - sin(theta) * c.r)) + PT(c.origin)
vertices[position + i + nbv[2]] = PT(M * Point{3, T}(cos(theta) * cos(phi) * c.r, cos(theta) * sin(phi) * c.r, h + sin(theta) * c.r)) + PT(c.origin)
end
position += nbv[2] * 2
end
vertices[end-1] = PT(c.origin) + PT(M * Point{3, T}(0, 0, -c.r))
vertices[end] = PT(c.extremity) + PT(M * Point{3, T}(0, 0, c.r))
return vertices
end

function decompose(::Type{FT}, c::Capsule{2}, facets = 10) where FT <: Face
nbv = 4*max.(4, facets)
indexes = Vector{FT}(undef, nbv)
for j = 1:nbv
indexes[j] = (1, j + 2, j + 1)
end
return indexes
end

function decompose(::Type{FT}, c::Capsule{3}, facets = (30, 10)) where FT <: Face
nbv = max.(4, facets)
indexes = Vector{FT}(undef, nbv[1] * (4 * (nbv[2]-1) + 4))
index = 1
slice = nbv[2] * 2
last = nbv[1] * (2 * nbv[2]) + 2
for j = 0:(nbv[1]-1)
j_next = (j+1) % nbv[1]
indexes[index+0] = (j_next * slice + nbv[2] + 1, j_next * slice + 1, j * slice + 1)
indexes[index+1] = (j * slice + nbv[2] + 1, j_next * slice + nbv[2] + 1, j * slice + 1)
indexes[index+2] = (j * slice + nbv[2], j_next * slice + nbv[2], last - 1)
indexes[index+3] = (j_next * slice + 2 * nbv[2], j * slice + 2 * nbv[2], last)
for i = 0:(nbv[2]-2)
indexes[index + 4 + i*4 + 0] = (j_next * slice + i + 1, j_next * slice + i + 2, j * slice + i + 1)
indexes[index + 4 + i*4 + 1] = (j_next * slice + i + 2, j * slice + i + 2, j * slice + i + 1)
indexes[index + 4 + i*4 + 2] = (j_next * slice + nbv[2] + i + 2, j_next * slice + nbv[2] + i + 1, j * slice + nbv[2] + i + 1)
indexes[index + 4 + i*4 + 3] = (j_next * slice + nbv[2] + i + 2, j * slice + nbv[2] + i + 1, j * slice + nbv[2] + i + 2)
end
index += 4 * (nbv[2]-1) + 4
end
return indexes
end
7 changes: 6 additions & 1 deletion src/typealias.jl
Expand Up @@ -80,7 +80,12 @@ its extremity and a radius. `origin`, `extremity` and `r`, must be specified.
const Cylinder2{T} = Cylinder{2, T}
const Cylinder3{T} = Cylinder{3, T}


"""
A `Capsule2` or `Capsule3` is a 2D/3D capsule defined by its origin point,
its extremity and a radius. `origin`, `extremity` and `r`, must be specified.
"""
const Capsule2{T} = Capsule{2, T}
const Capsule3{T} = Capsule{3, T}

const UV{T} = TextureCoordinate{2, T}
const UVW{T} = TextureCoordinate{3, T}
Expand Down
10 changes: 10 additions & 0 deletions src/types.jl
Expand Up @@ -233,6 +233,16 @@ struct Cylinder{N,T<: AbstractFloat} <: GeometryPrimitive{N,T}
r::T
end

"""
A `Capsule` is a 2D line segment with a radius or a 3D Capsule defined by its origin point,
its extremity and a radius. `origin`, `extremity` and `r`, must be specified.
"""
struct Capsule{N,T<: AbstractFloat} <: GeometryPrimitive{N,T}
origin::Point{N,T}
extremity::Point{N,T}
r::T
end

"""
AbstractConvexHull

Expand Down
185 changes: 185 additions & 0 deletions test/capsule.jl
@@ -0,0 +1,185 @@
@testset "Capsule" begin
@testset "constructors" begin
o, extr, r = Point2f0(1, 2), Point2f0(3, 4), 5f0
s = Capsule(o, extr, r)
@test typeof(s) == Capsule{2,Float32}
@test typeof(s) == Capsule2{Float32}
@test origin(s) == o
@test extremity(s) == extr
@test radius(s) == r
#@test abs(height(s)- norm([1,2]-[3,4]))<1e-5
h = norm(o - extr)
@test isapprox(height(s), h)
#@test norm(direction(s) - Point{2,Float32}([2,2]./norm([1,2]-[3,4])))<1e-5
@test isapprox(direction(s), Point2f0(2, 2) ./ h)
v1 = rand(Point{3, Float64}); v2 = rand(Point{3, Float64}); R = rand()
s = Capsule(v1, v2, R)
@test typeof(s) == Capsule{3, Float64}
@test typeof(s) == Capsule3{Float64}
@test origin(s) == v1
@test extremity(s) == v2
@test radius(s) == R
@test height(s) == norm(v2 - v1)
#@test norm(direction(s) - Point{3,Float64}((v2-v1)./norm(v2-v1)))<1e-10
@test isapprox(direction(s), (v2-v1) ./ norm(v2 .- v1))
end

@testset "decompose" begin
o, extr, r = Point2f0(1, 2), Point2f0(3, 4), 5f0
s = Capsule(o, extr, r)
positions = Point{3, Float32}[
(4.535534, -1.5355341, 0.0),
(2.9134173, -2.619398, 0.0),
(1.0000002, -3.0, 0.0),
(-0.913417, -2.6193976, 0.0),
(-2.5355341, -1.5355337, 0.0),
(-3.6193976, 0.08658302, 0.0),
(-4.0, 2.0000002, 0.0),
(-3.619398, 3.9134173, 0.0),
(-2.535534, 5.5355344, 0.0),
(-0.53553426, 7.535534, 0.0),
(1.0865824, 8.619398, 0.0),
(2.9999998, 9.0, 0.0),
(4.913417, 8.619397, 0.0),
(6.535534, 7.535534, 0.0),
(7.619397, 5.913417, 0.0),
(8.0, 3.9999998, 0.0),
(7.619398, 2.0865827, 0.0),
(6.535534, 0.4644655, 0.0)
]
@test decompose(Point3f0, s, 4) ≈ positions
FT = Face{3, Int}
faces = FT[
(1, 3, 2),
(1, 4, 3),
(1, 5, 4),
(1, 6, 5),
(1, 7, 6),
(1, 8, 7),
(1, 9, 8),
(1, 10, 9),
(1, 11, 10),
(1, 12, 11),
(1, 13, 12),
(1, 14, 13),
(1, 15, 14),
(1, 16, 15),
(1, 17, 16),
(1, 18, 17)
]
@test faces == decompose(FT, s, 4)

v1 = Point{3, Float64}(1,2,3); v2 = Point{3, Float64}(4,5,6); R = 5.0
s = Capsule(v1, v2, R)
positions = Point{3,Float64}[
(4.535533905932738, -1.5355339059327373, 3.0),
(3.1616954987389683, -2.3711193256429137, 1.8952880865480273),
(1.4587585476806852, -2.5412414523193148, 0.9587585476806852),
(-0.31402023360539255, -2.020000734336378, 0.332989516029115),
(7.535533905932738, 1.4644660940672627, 6.0),
(8.371119325642914, 2.8383045012610317, 7.104711913451973),
(8.541241452319316, 4.541241452319315, 8.041241452319316),
(8.020000734336378, 6.314020233605392, 8.667010483970884),
(3.0412414523193148, 4.041241452319315, -1.0824829046386295),
(1.7811492852594561, 2.7811492852594557, -1.8764343108748296),
(0.4021342206547498, 1.4021342206547494, -1.927992798267443),
(-0.8858611987114287, 0.11413880128857112, -1.2293090544897973),
(6.041241452319315, 7.041241452319315, 1.9175170953613705),
(6.990573112163402, 7.990573112163402, 3.3329895160291154),
(7.484617125293379, 8.48461712529338, 5.1544901063711865),
(7.448159769230341, 8.44815976923034, 7.104711913451973),
(-2.535533905932737, 5.535533905932738, 2.9999999999999996),
(-3.3711193256429137, 4.161695498738968, 1.8952880865480268),
(-3.5412414523193148, 2.4587585476806852, 0.9587585476806848),
(-3.020000734336378, 0.6859797663946074, 0.332989516029115),
(0.46446609406726314, 8.535533905932738, 6.0),
(1.8383045012610322, 9.371119325642914, 7.104711913451972),
(3.5412414523193148, 9.541241452319316, 8.041241452319316),
(5.314020233605392, 9.020000734336378, 8.667010483970884),
(-1.0412414523193152, -0.04124145231931431, 7.0824829046386295),
(-1.990573112163402, -0.9905731121634007, 5.667010483970884),
(-2.4846171252933793, -1.4846171252933784, 3.845509893628814),
(-2.4481597692303416, -1.4481597692303412, 1.8952880865480275),
(1.9587585476806848, 2.9587585476806857, 10.08248290463863),
(3.2188507147405434, 4.218850714740545, 10.87643431087483),
(4.59786577934525, 5.597865779345251, 10.927992798267443),
(5.885861198711429, 6.885861198711429, 10.229309054489796),
(-1.8867513459481287, -0.8867513459481287, 0.11324865405187134),
(6.886751345948129, 7.886751345948129, 8.886751345948129)
]
@test decompose(Point3{Float64},s,(4, 4)) ≈ positions
faces = Face[
(13, 9, 1),
(5, 13, 1),
(4, 12, 33),
(16, 8, 34),
(9, 10, 1),
(10, 2, 1),
(14, 13, 5),
(14, 5, 6),
(10, 11, 2),
(11, 3, 2),
(15, 14, 6),
(15, 6, 7),
(11, 12, 3),
(12, 4, 3),
(16, 15, 7),
(16, 7, 8),
(21, 17, 9),
(13, 21, 9),
(12, 20, 33),
(24, 16, 34),
(17, 18, 9),
(18, 10, 9),
(22, 21, 13),
(22, 13, 14),
(18, 19, 10),
(19, 11, 10),
(23, 22, 14),
(23, 14, 15),
(19, 20, 11),
(20, 12, 11),
(24, 23, 15),
(24, 15, 16),
(29, 25, 17),
(21, 29, 17),
(20, 28, 33),
(32, 24, 34),
(25, 26, 17),
(26, 18, 17),
(30, 29, 21),
(30, 21, 22),
(26, 27, 18),
(27, 19, 18),
(31, 30, 22),
(31, 22, 23),
(27, 28, 19),
(28, 20, 19),
(32, 31, 23),
(32, 23, 24),
(5, 1, 25),
(29, 5, 25),
(28, 4, 33),
(8, 32, 34),
(1, 2, 25),
(2, 26, 25),
(6, 5, 29),
(6, 29, 30),
(2, 3, 26),
(3, 27, 26),
(7, 6, 30),
(7, 30, 31),
(3, 4, 27),
(4, 28, 27),
(8, 7, 31),
(8, 31, 32)
]
@test faces == decompose(Face{3, Int}, s, (4, 4))
m = GLPlainMesh(s, (4, 4))
@test m.faces == faces
@test m.vertices ≈ positions
m = GLNormalMesh(s)# just test that it works without explicit resolution parameter
@test m isa GLNormalMesh

end
end
1 change: 1 addition & 0 deletions test/runtests.jl
Expand Up @@ -27,5 +27,6 @@ using Test: @inferred
include("lines.jl")
include("polygons.jl")
include("cylinder.jl")
include("capsule.jl")
include("algorithms.jl")
end