From b4ba42a884704cee983689f24520862c389db9d8 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Wed, 16 Apr 2025 17:13:02 +0200 Subject: [PATCH 1/5] add Cone primitive --- src/GeometryBasics.jl | 3 +- src/primitives/Cone.jl | 102 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/primitives/Cone.jl diff --git a/src/GeometryBasics.jl b/src/GeometryBasics.jl index 89ccd370..40aaf58e 100644 --- a/src/GeometryBasics.jl +++ b/src/GeometryBasics.jl @@ -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") @@ -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 diff --git a/src/primitives/Cone.jl b/src/primitives/Cone.jl new file mode 100644 index 00000000..5f08c1b3 --- /dev/null +++ b/src/primitives/Cone.jl @@ -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) +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]) + 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) + + # 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 From b0c573a79a452a5285425e6d45565dd921a86350 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Wed, 16 Apr 2025 17:29:16 +0200 Subject: [PATCH 2/5] add tests --- test/geometrytypes.jl | 62 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/test/geometrytypes.jl b/test/geometrytypes.jl index b4c4c235..e09d5a52 100644 --- a/test/geometrytypes.jl +++ b/test/geometrytypes.jl @@ -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 \ No newline at end of file From 0f56fd6c9be8c8f3ad05ffe000ea1264c92ac3d4 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Tue, 29 Apr 2025 14:40:50 +0200 Subject: [PATCH 3/5] fix docs ci --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f9dc6f2..e5a95e99 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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: From e21b2395143a04bb09dd4f32b59d9701a3ebbdb7 Mon Sep 17 00:00:00 2001 From: ffreyer Date: Thu, 1 May 2025 18:21:54 +0200 Subject: [PATCH 4/5] fix normals --- src/primitives/Cone.jl | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/primitives/Cone.jl b/src/primitives/Cone.jl index 5f08c1b3..c655c52f 100644 --- a/src/primitives/Cone.jl +++ b/src/primitives/Cone.jl @@ -79,7 +79,19 @@ function normals(c::Cone, nvertices = 30) # cap ns[end] = Vec3f(normalize(c.origin - c.tip)) - return ns + 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) From 225381235e22d31c0c010715481745d2def9308c Mon Sep 17 00:00:00 2001 From: ffreyer Date: Thu, 1 May 2025 18:25:06 +0200 Subject: [PATCH 5/5] update test --- test/geometrytypes.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/geometrytypes.jl b/test/geometrytypes.jl index e09d5a52..bab22981 100644 --- a/test/geometrytypes.jl +++ b/test/geometrytypes.jl @@ -752,8 +752,12 @@ end (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 ns == decompose_normals(Tessellation(s, 8)) + @test FaceView(ns, fs) == decompose_normals(Tessellation(s, 8)) muv = uv_mesh(s) @test !hasproperty(muv, :uv) # not defined yet