Skip to content

Add Cone primitive #257

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

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
token: ${{ secrets.CODECOV_TOKEN }}
docs:
name: Documentation
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
env:
JULIA_PKG_SERVER: ""
steps:
Expand Down
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
114 changes: 114 additions & 0 deletions src/primitives/Cone.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""
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 +74 to +77
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))

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(nhalf + 2)
end

return FaceView(ns, faces)
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
66 changes: 66 additions & 0 deletions test/geometrytypes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -696,4 +696,70 @@ 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),
]
fs = [
GLTriangleFace(1, 2, 5), GLTriangleFace(2, 3, 5), GLTriangleFace(3, 4, 5), GLTriangleFace(4, 1, 5),
GLTriangleFace(6, 6, 6), GLTriangleFace(6, 6, 6), GLTriangleFace(6, 6, 6), GLTriangleFace(6, 6, 6)
]

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

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