Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/GeometryBasics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ include("primitives/spheres.jl")
include("primitives/cylinders.jl")
include("primitives/pyramids.jl")
include("primitives/particles.jl")
include("primitives/Cone.jl")

include("interfaces.jl")
include("viewtypes.jl")
Expand Down Expand Up @@ -56,7 +57,7 @@ export triangle_mesh, triangle_mesh, uv_mesh
export uv_mesh, normal_mesh, uv_normal_mesh

export height, origin, radius, width, widths
export HyperSphere, Circle, Sphere
export HyperSphere, Circle, Sphere, Cone
export Cylinder, Pyramid, extremity
export HyperRectangle, Rect, Rect2, Rect3, Recti, Rect2i, Rect3i, Rectf, Rect2f, Rect3f, Rectd, Rect2d, Rect3d, RectT
export before, during, meets, overlaps, intersects, finishes
Expand Down
102 changes: 102 additions & 0 deletions src/primitives/Cone.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""
Cone{T}(origin::Point3, tip::Point3, radius)

A Cone is a cylinder where one end has a radius of 0. It is defined by an
`origin` with a finite `radius` which linearly decreases to 0 at the `tip`.
"""
struct Cone{T} <: GeometryPrimitive{3, T}
origin::Point3{T}
tip::Point3{T}
radius::T
end

function Cone(origin::Point3{T1}, tip::Point3{T2}, radius::T3) where {T1, T2, T3}
T = promote_type(T1, T2, T3)
return Cone{T}(origin, tip, radius)

Check warning on line 15 in src/primitives/Cone.jl

View check run for this annotation

Codecov / codecov/patch

src/primitives/Cone.jl#L13-L15

Added lines #L13 - L15 were not covered by tests
end

origin(c::Cone) = c.origin
extremity(c::Cone) = c.tip
radius(c::Cone) = c.radius
height(c::Cone) = norm(c.tip - c.origin)
direction(c::Cone) = (c.tip .- c.origin) ./ height(c)

function rotation(c::Cone{T}) where {T}
d3 = direction(c)
u = Vec{3, T}(d3[1], d3[2], d3[3])
if abs(u[1]) > 0 || abs(u[2]) > 0
v = Vec{3, T}(u[2], -u[1], T(0))
else
v = Vec{3, T}(T(0), -u[3], u[2])

Check warning on line 30 in src/primitives/Cone.jl

View check run for this annotation

Codecov / codecov/patch

src/primitives/Cone.jl#L30

Added line #L30 was not covered by tests
end
v = normalize(v)
w = Vec{3, T}(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 Mat{3, 3, T}(v..., w..., u...)
end

function coordinates(c::Cone{T}, nvertices=30) where {T}
nvertices += isodd(nvertices)
nhalf = div(nvertices, 2)

R = rotation(c)
step = 2pi / nhalf

ps = Vector{Point3{T}}(undef, nhalf + 2)
for i in 1:nhalf
phi = (i-1) * step
ps[i] = R * Point3{T}(c.radius * cos(phi), c.radius * sin(phi), 0) + c.origin
end
ps[end-1] = c.tip
ps[end] = c.origin

return ps
end

function normals(c::Cone, nvertices = 30)
nvertices += isodd(nvertices)
nhalf = div(nvertices, 2)

R = rotation(c)
step = 2pi / nhalf

ns = Vector{Vec3f}(undef, nhalf + 2)
# shell at origin
# normals are angled in z direction due to change in radius (from radius to 0)
# This can be calculated from triangles
z = radius(c) / height(c)
norm = 1.0 / sqrt(1 + z*z)
for i in 1:nhalf
phi = (i-1) * step
ns[i] = R * (norm * Vec3f(cos(phi), sin(phi), z))
end

# tip - this is undefined / should be all ring angles at once
# for rendering it is useful to define this as Vec3f(0), because tip normal
# has no useful value to contribute to the interpolated fragment normal
ns[end-1] = Vec3f(0)
Comment on lines +65 to +68
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This fixes seams on the mesh. The left mesh uses triangles where the tip vertex is duplicated for each triangle with a different normal (average of bottom normals). The right uses one (0,0,0) vertex at the tip:
image
You get the same seams with quads instead of triangles and even if you give the tip a finite radius, i.e. with a conical frustum. (?)


# cap
ns[end] = Vec3f(normalize(c.origin - c.tip))

return ns
end

function faces(::Cone, facets=30)
nvertices = facets + isodd(facets)
nhalf = div(nvertices, 2)

faces = Vector{GLTriangleFace}(undef, nvertices)

# shell
for i in 1:nhalf
faces[i] = GLTriangleFace(i, mod1(i+1, nhalf), nhalf+1)
end

# cap
for i in 1:nhalf
faces[i+nhalf] = GLTriangleFace(i, mod1(i+1, nhalf), nhalf+2)
end

return faces
end
62 changes: 62 additions & 0 deletions test/geometrytypes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -696,4 +696,66 @@ end
@test all(getindex.(Ref(mp), 1:10) .== ps1)
@test size(mp) == (10, ) # TODO: Does this make sense?
@test length(mp) == 10
end

@testset "Cone" begin
@testset "constructors" begin
v1 = rand(Point{3,Float64})
v2 = rand(Point{3,Float64})
R = rand()
s = Cone(v1, v2, R)
@test typeof(s) == Cone{Float64}
@test origin(s) == v1
@test extremity(s) == v2
@test radius(s) == R
@test height(s) == norm(v2 - v1)
@test isapprox(direction(s), (v2 - v1) ./ norm(v2 .- v1))
end

@testset "decompose" begin
v1 = Point{3,Float64}(1, 2, 3)
v2 = Point{3,Float64}(4, 5, 6)
R = 5.0
s = Cone(v1, v2, R)
positions = Point{3,Float64}[
(4.535533905932738, -1.5355339059327373, 3.0),
(3.0412414523193148, 4.041241452319315, -1.0824829046386295),
(-2.535533905932737, 5.535533905932738, 2.9999999999999996),
(-1.0412414523193152, -0.04124145231931431, 7.0824829046386295),
(4, 5, 6),
(1, 2, 3)
]

@test decompose(Point3{Float64}, Tessellation(s, 8)) ≈ positions

_faces = TriangleFace[
(1,2,5), (2,3,5), (3,4,5), (4,1,5),
(1,2,6), (2,3,6), (3,4,6), (4,1,6)]

@test _faces == decompose(TriangleFace{Int}, Tessellation(s, 8))

m = triangle_mesh(Tessellation(s, 8))
@test m === triangle_mesh(m)
@test GeometryBasics.faces(m) == decompose(GLTriangleFace, _faces)
@test GeometryBasics.coordinates(m) ≈ positions

m = normal_mesh(s) # just test that it works without explicit resolution parameter
@test hasproperty(m, :position)
@test hasproperty(m, :normal)
@test faces(m) isa AbstractVector{GLTriangleFace}

ns = Vec{3, Float32}[
(0.90984505, -0.10920427, 0.40032038),
(0.6944946, 0.6944946, -0.18802801),
(-0.10920427, 0.90984505, 0.40032038),
(0.106146194, 0.106146194, 0.9886688),
(0.0, 0.0, 0.0),
(-0.57735026, -0.57735026, -0.57735026),
]

@test ns == decompose_normals(Tessellation(s, 8))

muv = uv_mesh(s)
@test !hasproperty(muv, :uv) # not defined yet
end
end
Loading