diff --git a/CLAUDE.md b/CLAUDE.md index 373911c..b518a54 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,6 +8,10 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co quite high. This *requires* that you use the MCP server to avoid starting a new Julia session each time. +- Run tests by using the MCP server and `include(test/runtests.jl)` rather than `Pkg.test()`. This will +avoid lengthy recompilation and will work even if you iterate changes on the package due to +`Revise`. + - Exploit Julia packages and macros for evaluating performance issues: `BenchmarkTools.jl` for micro-benchmarks, `Profile` for CPU profiling, and `Cthulhu.jl` for method analysis (or `@code_warntype`). These tools are in diff --git a/NEWS.md b/NEWS.md index 2275ad3..3bf35bf 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,11 @@ We started keeping track of changes in the `NEWS.md` file after version 0.1.0. +# PlantGeomTurtle 1.0.2 (2026-06-17) + +* Add `Ellipsoid!` and `Ellipsoid` turtle constructors to generate solid ellipsoids + in front of the turtle. Requires PlantGeomPrimitives 1.0.2. + # PlantGeomTurtle 1.0.0 (2026-06-10) No actual changes. We move to version 1.0.0 because: diff --git a/Project.toml b/Project.toml index c45bdd4..a5ebc82 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "PlantGeomTurtle" uuid = "7d6e2781-1c99-4c66-97ec-106669f3e96e" authors = ["Alejandro Morales Sierra and contributors"] -version = "1.0.0" +version = "1.0.2" [deps] CoordinateTransformations = "150eb455-5306-5404-9cee-2592286d6298" @@ -15,7 +15,7 @@ Unrolled = "9602ed7d-8fef-5bc8-8597-8f21381861e8" [compat] CoordinateTransformations = "0.6.3" LinearAlgebra = "1.11" -PlantGeomPrimitives = "1.0.0" +PlantGeomPrimitives = "1.0.3" PlantGraphs = "1.0.0" Rotations = "1.5.1" StaticArrays = "1.9.13" diff --git a/src/Primitives.jl b/src/Primitives.jl index fb6c0e9..e58e929 100644 --- a/src/Primitives.jl +++ b/src/Primitives.jl @@ -1064,16 +1064,87 @@ function PGP.SolidFrustum(turtle::Turtle{FT,UT}; length::FT = one(FT), width::FT end -function Ellipsoid!(turtle::Turtle{FT,UT}; length::FT = one(FT), width::FT = one(FT), - height::FT = one(FT), n::Int = 20, move = false, kwargs...) where {FT,UT} - @error "Ellipsoid not implemented yet" +""" + Ellipsoid!(turtle; length = 1.0, width = 1.0, height = 1.0, n = 20, move = false, kwargs...) + +Generate a solid ellipsoid in front of the turtle and feed it to a turtle. + +## Arguments +- `turtle`: The turtle that we feed the solid ellipsoid to. +- `length`: Length of the solid ellipsoid along the head axis. +- `width`: Width of the solid ellipsoid along the arm axis. +- `height`: Height of the solid ellipsoid along the up axis. +- `n`: Number of latitude and azimuth subdivisions. The mesh has `2n(n-1)` triangles. +- `move`: Whether to move the turtle forward or not (`true` or `false`). +- `kwargs`: Properties to be set per triangle in the mesh. + +## Details +A mesh will be generated with `2n(n-1)` triangles that approximate the solid ellipsoid. +The ellipsoid will be generated in front of the turtle, with the base centered at the +turtle's current position. The `length` argument refers to the axis aligned with the head +axis of the turtle, whereas `width` refers to the arm axis and `height` to the up axis. + +When `move = true`, the turtle will be moved forward by a distance equal to `length`. + +## Return +Returns `nothing` but modifies the `turtle` as a side effect. + +## Examples +```jldoctest +julia> turtle = Turtle(); + +julia> e = Ellipsoid!(turtle; length = 1.0, width = 0.5, height = 0.5, n = 20); +``` +""" +function PGP.Ellipsoid!(turtle::Turtle{FT,UT}; length::FT = one(FT), width::FT = one(FT), + height::FT = one(FT), n::Int = 20, move = false, kwargs...) where {FT,UT} + trans = transformation(turtle, PGP.Vec(height / FT(2), width / FT(2), length / FT(2))) + PGP.Ellipsoid!(PGP.Mesh(turtle), trans; n = n) + ntri = 2n * (n - 1) + for (k, v) in kwargs + PGP.add_property!(PGP.Mesh(turtle), k, v, ntri) + end + move && f!(turtle, length) return nothing end -function Ellipsoid(turtle::Turtle{FT,UT}; length::FT = one(FT), width::FT = one(FT), - height::FT = one(FT), n::Int = 20, move = false) where {FT,UT} - @error "Ellipsoid not implemented yet" - return nothing +""" + Ellipsoid(turtle; length = 1.0, width = 1.0, height = 1.0, n = 20, move = false) + +Generate a solid ellipsoid in front of the turtle and return it. + +## Arguments +- `turtle`: The turtle which state is used to create the solid ellipsoid. +- `length`: Length of the solid ellipsoid along the head axis. +- `width`: Width of the solid ellipsoid along the arm axis. +- `height`: Height of the solid ellipsoid along the up axis. +- `n`: Number of latitude and azimuth subdivisions. The mesh has `2n(n-1)` triangles. +- `move`: Whether to move the turtle forward or not (`true` or `false`). + +## Details +A mesh will be generated with `2n(n-1)` triangles that approximate the solid ellipsoid. +The ellipsoid will be generated in front of the turtle, with the base centered at the +turtle's current position. The `length` argument refers to the axis aligned with the head +axis of the turtle, whereas `width` refers to the arm axis and `height` to the up axis. + +When `move = true`, the turtle will be moved forward by a distance equal to `length`. + +## Return +Returns a triangular mesh (object of type `Mesh`). + +## Examples +```jldoctest +julia> turtle = Turtle(); + +julia> e = Ellipsoid(turtle; length = 1.0, width = 0.5, height = 0.5, n = 20); +``` +""" +function PGP.Ellipsoid(turtle::Turtle{FT,UT}; length::FT = one(FT), width::FT = one(FT), + height::FT = one(FT), n::Int = 20, move = false) where {FT,UT} + trans = transformation(turtle, PGP.Vec(height / FT(2), width / FT(2), length / FT(2))) + e = PGP.Ellipsoid(trans; n = n) + move && f!(turtle, length) + return e end diff --git a/test/runtests.jl b/test/runtests.jl index 0b070c3..99e3623 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -68,6 +68,9 @@ end @testset "solid_cone" begin include("test_solid_cone.jl") end +@testset "ellipsoid" begin + include("test_ellipsoid.jl") +end @testset "mesh" begin include("test_mesh.jl") end diff --git a/test/test_ellipsoid.jl b/test/test_ellipsoid.jl new file mode 100644 index 0000000..430b2f3 --- /dev/null +++ b/test/test_ellipsoid.jl @@ -0,0 +1,33 @@ +import PlantGeomTurtle as PGT +import PlantGeomPrimitives as PGP +using Test + +let + + n = 20 + + # Construct solid ellipsoid using a turtle + e = PGP.Ellipsoid(length = 2.0, width = 1.0, height = 0.5, n = n) + t = PGT.Turtle(Float64) + PGP.Ellipsoid!(t; length = 2.0, width = 1.0, height = 0.5, n = n, move = true) + @test PGP.Mesh(t) == e + @test PGT.pos(t) == PGP.Vec{Float64}(0, 0, 2) + + t = PGT.Turtle(Float64) + e2 = PGP.Ellipsoid(t; length = 2.0, width = 1.0, height = 0.5, n = n, move = false) + @test e2 == e + @test PGT.pos(t) == PGP.Vec{Float64}(0, 0, 0) + + # Check properties + ntri = 2n * (n - 1) + t = PGT.Turtle(Float64) + PGP.Ellipsoid!(t; length = 2.0, width = 1.0, height = 0.5, n = n, move = false, colors = rand(RGB)) + @test length(get_colors(t)) == ntri + @test get_colors(t)[1] == get_colors(t)[4] + + t = PGT.Turtle(Float64) + PGP.Ellipsoid!(t; length = 2.0, width = 1.0, height = 0.5, n = n, move = false, colors = [rand(RGB) for _ in 1:ntri]) + @test length(get_colors(t)) == ntri + @test get_colors(t)[1] != get_colors(t)[4] + +end