diff --git a/src/ConstructiveSolidGeometry/SurfacePrimitives/EllipsoidMantle.jl b/src/ConstructiveSolidGeometry/SurfacePrimitives/EllipsoidMantle.jl index 50919e1d0..ed4f96694 100644 --- a/src/ConstructiveSolidGeometry/SurfacePrimitives/EllipsoidMantle.jl +++ b/src/ConstructiveSolidGeometry/SurfacePrimitives/EllipsoidMantle.jl @@ -98,7 +98,7 @@ function normal(em::EllipsoidMantle{T,NTuple{3,T},TP,TT,:outwards}, pt::Cartesia # Or wrap this into somehting like `normal(em, pt) = normalize(direction(em, pt))` ? p = _transform_into_object_coordinate_system(pt, em) obj_normal = CartesianVector{T}(sign(p.x)*(p.x/em.r[1])^2, sign(p.y)*(p.y/em.r[2])^2, sign(p.z)*(p.z/em.r[3])^2) # We might want to store the inv(em.r) in the struct? - transform_into_global_coordinate_system(obj_normal, em) + _transform_into_global_coordinate_system(obj_normal, em) end function normal(em::EllipsoidMantle{T,T,TP,TT,:outwards}, pt::CartesianPoint{T})::CartesianVector{T} where {T,TP,TT} # not normalized, do we want this? diff --git a/src/ConstructiveSolidGeometry/VolumePrimitives/Cone.jl b/src/ConstructiveSolidGeometry/VolumePrimitives/Cone.jl index a1884630f..5b051bf20 100644 --- a/src/ConstructiveSolidGeometry/VolumePrimitives/Cone.jl +++ b/src/ConstructiveSolidGeometry/VolumePrimitives/Cone.jl @@ -139,17 +139,12 @@ const Cylinder{T,CO} = Cone{T,CO,T,Nothing} # Full in φ const PartialCylinder{T,CO} = Cone{T,CO,T,T} ### Cylinder - function _in(pt::CartesianPoint, c::Cylinder{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - az = abs(pt.z) - az <= c.hZ + csgtol && begin - r = hypot(pt.x, pt.y) - r <= c.r + csgtol - end + hypot(max(zero(T), hypot(pt.x, pt.y) - c.r), max(zero(T), pt.z - c.hZ, - pt.z - c.hZ)) <= csgtol end + function _in(pt::CartesianPoint, c::Cylinder{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - abs(pt.z) < c.hZ - csgtol && - hypot(pt.x, pt.y) < c.r - csgtol + abs(pt.z) < c.hZ - csgtol && hypot(pt.x, pt.y) < c.r - csgtol end function surfaces(t::Cylinder{T}) where {T} @@ -163,21 +158,26 @@ function surfaces(t::Cylinder{T}) where {T} end ### PartialCylinder - -function _in(pt::CartesianPoint, c::PartialCylinder{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - az = abs(pt.z) - az <= c.hZ + csgtol && begin - r = hypot(pt.x, pt.y) - r <= c.r + csgtol && - _in_angular_interval_closed(atan(pt.y, pt.x), c.φ, csgtol = csgtol / r) +function _in(pt::CartesianPoint, c::PartialCylinder{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} + r::T = hypot(pt.x, pt.y) + φ::T = mod(atan(pt.y, pt.x), T(2π)) + z::T = pt.z + abs(z) <= c.hZ + csgtol && begin + det::T = csgtol^2 - max(zero(T), z - c.hZ, - z - c.hZ)^2 + det >= 0 && (_in_angular_interval_closed(φ, c.φ, csgtol = zero(T)) && r <= c.r + sqrt(det) || let Δφ = min(T(2π)-φ, φ-c.φ) + if Δφ <= atan(sqrt(det), c.r) + det >= c.r^2 * sin(Δφ)^2 && r <= c.r * cos(Δφ) + sqrt(det - c.r^2 * sin(Δφ)^2) + else + r <= sqrt(det)/sin(min(Δφ, T(π/2))) + end + end) end end -function _in(pt::CartesianPoint, c::PartialCylinder{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - abs(pt.z) + csgtol < c.hZ && begin - r = hypot(pt.x, pt.y) - csgtol + r < c.r && - _in_angular_interval_open(atan(pt.y, pt.x), c.φ, csgtol = csgtol / r) - end + +function _in(pt::CartesianPoint, c::PartialCylinder{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} + r::T = hypot(pt.x, pt.y) + φ::T = mod(atan(pt.y, pt.x), T(2π)) + abs(pt.z) + csgtol < c.hZ && r < c.r - csgtol && _in_angular_interval_closed(φ, c.φ, csgtol = zero(T)) && r * sin(min(φ, c.φ - φ, T(π/2))) > csgtol end function surfaces(t::PartialCylinder{T}) where {T} @@ -213,17 +213,22 @@ _____\ const VaryingCylinder{T,CO} = Cone{T,CO,Tuple{Tuple{T},Tuple{T}},Nothing} # Full in φ const PartialVaryingCylinder{T,CO} = Cone{T,CO,Tuple{Tuple{T},Tuple{T}},T} + +### VaryingCylinder function _in(pt::CartesianPoint, c::VaryingCylinder{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - az = abs(pt.z) - az <= c.hZ + csgtol && begin - r = hypot(pt.x, pt.y) - rz = radius_at_z(c.hZ, c.r[1][1], c.r[2][1], pt.z) - r <= rz + csgtol - end + r::T = hypot(pt.x, pt.y) + z::T = pt.z + rz::T = radius_at_z(c.hZ, c.r[1][1], c.r[2][1], z) + Δr::T = c.r[2][1] - c.r[1][1] + Δz::T = iszero(c.hZ) ? zero(T) : -csgtol * Δr / hypot(2*c.hZ, Δr) + return (abs(z - Δz) < c.hZ && r <= rz + csgtol * hypot(2*c.hZ, Δr) / (2*c.hZ)) || + ((z + c.hZ)^2 <= csgtol^2 && r <= c.r[1][1] + sqrt(csgtol^2 - (z + c.hZ)^2)) || + ((z - c.hZ)^2 <= csgtol^2 && r <= c.r[2][1] + sqrt(csgtol^2 - (z - c.hZ)^2)) end + function _in(pt::CartesianPoint, c::VaryingCylinder{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - abs(pt.z) + csgtol < c.hZ && - csgtol + hypot(pt.x, pt.y) < radius_at_z(c.hZ, c.r[1][1], c.r[2][1], pt.z) + abs(pt.z) < c.hZ - csgtol && + hypot(pt.x, pt.y) < radius_at_z(c.hZ, c.r[1][1], c.r[2][1], pt.z) - csgtol * hypot(2*c.hZ, c.r[2][1] - c.r[1][1]) / (2*c.hZ) end function surfaces(t::VaryingCylinder{T}) where {T} @@ -236,21 +241,40 @@ function surfaces(t::VaryingCylinder{T}) where {T} e_top, e_bot, mantle end +### PartialVaryingCylinder function _in(pt::CartesianPoint, c::PartialVaryingCylinder{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - az = abs(pt.z) - az <= c.hZ + csgtol && begin - r = hypot(pt.x, pt.y) - rz = radius_at_z(c.hZ, c.r[1][1], c.r[2][1], pt.z) - r <= rz + csgtol && - _in_angular_interval_closed(atan(pt.y, pt.x), c.φ, csgtol = csgtol / r) + r::T = hypot(pt.x, pt.y) + φ::T = mod(atan(pt.y, pt.x), T(2π)) + z::T = pt.z + rz::T = radius_at_z(c.hZ, c.r[1][1], c.r[2][1], z) + Δr::T = c.r[2][1] - c.r[1][1] + abs(z) <= c.hZ + csgtol && if _in_angular_interval_closed(φ, c.φ, csgtol = zero(T)) + _in(pt, Cone{T,ClosedPrimitive}(c.r, nothing, c.hZ, c.origin, c.rotation); csgtol) + else + t::T = ((r - c.r[1][1]) * Δr + (z + c.hZ) * 2c.hZ) / (Δr^2 + 4c.hZ^2) + s::T = ((r - c.r[1][1]) * 2c.hZ - (z + c.hZ) * Δr) / (Δr^2 + 4c.hZ^2) + d::T = if r <= rz && abs(z) <= c.hZ + zero(T) + elseif 0 <= t <= 1 && s >= 0 + s * hypot(Δr, 2c.hZ) + elseif t > 1 || (t >= 0 && s <= 0 && Δr > 0) + hypot(abs(z - c.hZ), max(zero(T), r - c.r[2][1])) + elseif t < 0 || (t <= 1 && s <= 0 && Δr < 0) + hypot(abs(-z - c.hZ), max(zero(T), r - c.r[1][1])) + else + hypot(r, max(zero(T), pt.z - c.hZ, - pt.z - c.hZ)) + end + (r * sin(min(T(2π)-φ, φ-c.φ, T(π/2))))^2 + d^2 <= csgtol^2 end end + function _in(pt::CartesianPoint, c::PartialVaryingCylinder{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - abs(pt.z) + csgtol < c.hZ && begin - r = hypot(pt.x, pt.y) - csgtol + r < radius_at_z(c.hZ, c.r[1][1], c.r[2][1], pt.z) && - _in_angular_interval_open(atan(pt.y, pt.x), c.φ, csgtol = csgtol / r) - end + r::T = hypot(pt.x, pt.y) + φ::T = mod(atan(pt.y, pt.x), T(2π)) + Δr::T = c.r[2][1] - c.r[1][1] + abs(pt.z) + csgtol < c.hZ && + r < radius_at_z(c.hZ, c.r[1][1], c.r[2][1], pt.z) - csgtol * hypot(2c.hZ, Δr) / 2c.hZ && + _in_angular_interval_open(atan(pt.y, pt.x), c.φ, csgtol = zero(T)) && r * sin(min(φ, c.φ - φ, T(π/2))) > csgtol end function surfaces(t::PartialVaryingCylinder{T}) where {T} @@ -289,19 +313,29 @@ const PartialVaryingTube{T,CO} = Cone{T,CO,Tuple{Tuple{T,T},Tuple{T,T}},T} #(r_bot_in = r[1][1], r_bot_out = r[1][2], r_top_in = r[2][1], r_top_out = r[2][2]) -function _in(pt::CartesianPoint, c::VaryingTube{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - az = abs(pt.z) - az <= c.hZ + csgtol && begin - r = hypot(pt.x, pt.y) - r_in = radius_at_z(c.hZ, c.r[1][1], c.r[2][1], pt.z) - r_out = radius_at_z(c.hZ, c.r[1][2], c.r[2][2], pt.z) - r_in - csgtol <= r && - r <= r_out + csgtol - end -end -function _in(pt::CartesianPoint, c::VaryingTube{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - abs(pt.z) + csgtol < c.hZ && - csgtol + radius_at_z(c.hZ, c.r[1][1], c.r[2][1], pt.z) < hypot(pt.x, pt.y) < radius_at_z(c.hZ, c.r[1][2], c.r[2][2], pt.z) - csgtol +### VaryingTube +function _in(pt::CartesianPoint, c::VaryingTube{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} + r::T = hypot(pt.x, pt.y) + z::T = pt.z + r_in::T = radius_at_z(c.hZ, c.r[1][1], c.r[2][1], z) + r_out::T = radius_at_z(c.hZ, c.r[1][2], c.r[2][2], z) + Δr_in::T = c.r[2][1] - c.r[1][1] + Δr_out::T = c.r[2][2] - c.r[1][2] + Δz_in::T = iszero(c.hZ) ? zero(T) : csgtol * Δr_in / hypot(2*c.hZ, Δr_in) + Δz_out::T = iszero(c.hZ) ? zero(T) : csgtol * Δr_out / hypot(2*c.hZ, Δr_out) + return (abs(z - Δz_in) < c.hZ && r >= r_in - csgtol * hypot(2*c.hZ, Δr_in) / (2*c.hZ) || + ((z + c.hZ)^2 <= csgtol^2 && r >= c.r[1][1] - sqrt(csgtol^2 - (z + c.hZ)^2)) || + ((z - c.hZ)^2 <= csgtol^2 && r >= c.r[2][1] - sqrt(csgtol^2 - (z - c.hZ)^2))) && + (abs(z + Δz_out) < c.hZ && r <= r_out + csgtol * hypot(2*c.hZ, Δr_out) / (2*c.hZ) || + ((z + c.hZ)^2 <= csgtol^2 && r <= c.r[1][2] + sqrt(csgtol^2 - (z + c.hZ)^2)) || + ((z - c.hZ)^2 <= csgtol^2 && r <= c.r[2][2] + sqrt(csgtol^2 - (z - c.hZ)^2))) +end + +function _in(pt::CartesianPoint, c::VaryingTube{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} + r::T = hypot(pt.x, pt.y) + return abs(pt.z) + csgtol < c.hZ && + r > radius_at_z(c.hZ, c.r[1][1], c.r[2][1], pt.z) + csgtol * hypot(2*c.hZ, c.r[2][1] - c.r[1][1]) / (2*c.hZ) && + r < radius_at_z(c.hZ, c.r[1][2], c.r[2][2], pt.z) - csgtol * hypot(2*c.hZ, c.r[2][2] - c.r[1][2]) / (2*c.hZ) end function surfaces(t::VaryingTube{T}) where {T} @@ -315,23 +349,44 @@ function surfaces(t::VaryingTube{T}) where {T} e_top, e_bot, inner_mantle, outer_mantle end +### PartialVaryingTube function _in(pt::CartesianPoint, c::PartialVaryingTube{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - az = abs(pt.z) - az <= c.hZ + csgtol && begin - r = hypot(pt.x, pt.y) - r_in = radius_at_z(c.hZ, c.r[1][1], c.r[2][1], pt.z) - r_out = radius_at_z(c.hZ, c.r[1][2], c.r[2][2], pt.z) - r_in - csgtol <= r && - r <= r_out + csgtol && - _in_angular_interval_closed(atan(pt.y, pt.x), c.φ, csgtol = csgtol / r) + r::T = hypot(pt.x, pt.y) + φ::T = mod(atan(pt.y, pt.x), T(2π)) + z::T = pt.z + abs(z) <= c.hZ + csgtol && if _in_angular_interval_closed(atan(pt.y, pt.x), c.φ, csgtol = zero(T)) + _in(pt, Cone{T,ClosedPrimitive}(c.r, nothing, c.hZ, c.origin, c.rotation); csgtol) + else + r_in = clamp(radius_at_z(c.hZ, c.r[1][1], c.r[2][1], z), extrema((c.r[1][1], c.r[2][1]))...) + r_out = clamp(radius_at_z(c.hZ, c.r[1][2], c.r[2][2], z), extrema((c.r[1][2], c.r[2][2]))...) + Δr_in = c.r[2][1] - c.r[1][1] + Δr_out = c.r[2][2] - c.r[1][2] + t_in = ((r - c.r[1][1]) * Δr_in + (z + c.hZ) * 2c.hZ) / (Δr_in^2 + 4c.hZ^2) + s_in = -((r - c.r[1][1]) * 2c.hZ - (z + c.hZ) * Δr_in) / (Δr_in^2 + 4c.hZ^2) + t_out = ((r - c.r[1][2]) * Δr_out + (z + c.hZ) * 2c.hZ) / (Δr_out^2 + 4c.hZ^2) + s_out = ((r - c.r[1][2]) * 2c.hZ - (z + c.hZ) * Δr_out) / (Δr_out^2 + 4c.hZ^2) + d::T = if r_in <= r <= r_out && abs(z) <= c.hZ + zero(T) + elseif 0 <= t_in <= 1 && s_in >= 0 + s_in * hypot(Δr_in, 2c.hZ) + elseif 0 <= t_out <= 1 && s_out >= 0 + s_out * hypot(Δr_out, 2c.hZ) + elseif t_out > 1 || t_in > 1 + hypot(abs(z - c.hZ), max(0, r - c.r[2][2], c.r[2][1] - r)) + else # t_out < 0 || t_in < 0 + hypot(abs(-z - c.hZ), max(0, r - c.r[1][2], c.r[1][1] - r)) + end + (r * sin(min(T(2π)-φ, φ-c.φ, T(π/2))))^2 + d^2 <= csgtol^2 end end + function _in(pt::CartesianPoint, c::PartialVaryingTube{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - abs(pt.z) + csgtol < c.hZ && begin - r = hypot(pt.x, pt.y) - csgtol + radius_at_z(c.hZ, c.r[1][1], c.r[2][1], pt.z) < r < radius_at_z(c.hZ, c.r[1][2], c.r[2][2], pt.z) - csgtol && - _in_angular_interval_open(atan(pt.y, pt.x), c.φ, csgtol = csgtol / r) - end + r::T = hypot(pt.x, pt.y) + φ::T = mod(atan(pt.y, pt.x), T(2π)) + abs(pt.z) + csgtol < c.hZ && + _in_angular_interval_open(atan(pt.y, pt.x), c.φ, csgtol = zero(T)) && r * sin(min(φ, c.φ - φ, T(π/2))) > csgtol && + r > radius_at_z(c.hZ, c.r[1][1], c.r[2][1], pt.z) + csgtol * hypot(2c.hZ, c.r[2][1] - c.r[1][1]) / 2c.hZ && + r < radius_at_z(c.hZ, c.r[1][2], c.r[2][2], pt.z) - csgtol * hypot(2c.hZ, c.r[2][2] - c.r[1][2]) / 2c.hZ end function surfaces(t::PartialVaryingTube{T}) where {T} @@ -368,17 +423,8 @@ __\ const UpwardCone{T,CO} = Cone{T,CO,Tuple{Tuple{Nothing,T},Nothing},Nothing} # Full in φ const PartialUpwardCone{T,CO} = Cone{T,CO,Tuple{Tuple{Nothing,T},Nothing},T} -function _in(pt::CartesianPoint, c::UpwardCone{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - az = abs(pt.z) - az <= c.hZ + csgtol && begin - r = hypot(pt.x, pt.y) - r_out = radius_at_z(c.hZ, c.r[1][2], zero(T), pt.z) - r <= r_out + csgtol - end -end -function _in(pt::CartesianPoint, c::UpwardCone{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - abs(pt.z) + csgtol < c.hZ && - hypot(pt.x, pt.y) + csgtol < radius_at_z(c.hZ, c.r[1][2], zero(T), pt.z) +function _in(pt::CartesianPoint, c::Cone{T,CO,Tuple{Tuple{Nothing,T},Nothing}}; csgtol::T = csg_default_tol(T)) where {T,CO} + _in(pt, Cone{T,CO}(((c.r[1][2],), (zero(T),)), c.φ, c.hZ, c.origin, c.rotation); csgtol) end function surfaces(t::UpwardCone{T}) where {T} @@ -389,23 +435,6 @@ function surfaces(t::UpwardCone{T}) where {T} e_bot, outer_mantle end -function _in(pt::CartesianPoint, c::PartialUpwardCone{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - az = abs(pt.z) - az <= c.hZ + csgtol && begin - r = hypot(pt.x, pt.y) - r_out = radius_at_z(c.hZ, c.r[1][2], zero(T), pt.z) - r <= r_out + csgtol && - _in_angular_interval_closed(atan(pt.y, pt.x), c.φ, csgtol = csgtol / r) - end -end -function _in(pt::CartesianPoint, c::PartialUpwardCone{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - abs(pt.z) + csgtol < c.hZ && begin - r = hypot(pt.x, pt.y) - csgtol + r < radius_at_z(c.hZ, c.r[1][2], zero(T), pt.z) && - _in_angular_interval_open(atan(pt.y, pt.x), c.φ, csgtol = csgtol / r) - end -end - function surfaces(t::PartialUpwardCone{T}) where {T} bot_center_pt = _transform_into_global_coordinate_system(CartesianPoint{T}(zero(T), zero(T), -t.hZ), t) outer_mantle = PartialConeMantle{T,:inwards}( (t.r[1][2], zero(T)), t.φ, t.hZ, t.origin, t.rotation) @@ -436,18 +465,8 @@ ___ const DownwardCone{T,CO} = Cone{T,CO,Tuple{Nothing,Tuple{Nothing,T}},Nothing} # Full in φ const PartialDownwardCone{T,CO} = Cone{T,CO,Tuple{Nothing,Tuple{Nothing,T}},T} - -function _in(pt::CartesianPoint, c::DownwardCone{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - az = abs(pt.z) - az <= c.hZ + csgtol && begin - r = hypot(pt.x, pt.y) - r_out = radius_at_z(c.hZ, zero(T), c.r[2][2], pt.z) - r <= r_out + csgtol - end -end -function _in(pt::CartesianPoint, c::DownwardCone{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - abs(pt.z) + csgtol < c.hZ && - hypot(pt.x, pt.y) + csgtol < radius_at_z(c.hZ, zero(T), c.r[2][2], pt.z) +function _in(pt::CartesianPoint, c::Cone{T,CO,Tuple{Nothing,Tuple{Nothing,T}}}; csgtol::T = csg_default_tol(T)) where {T,CO} + _in(pt, Cone{T,CO}(((zero(T),), (c.r[2][2],)), c.φ, c.hZ, c.origin, c.rotation); csgtol) end function surfaces(t::DownwardCone{T}) where {T} @@ -458,23 +477,6 @@ function surfaces(t::DownwardCone{T}) where {T} e_top, outer_mantle end -function _in(pt::CartesianPoint, c::PartialDownwardCone{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - az = abs(pt.z) - az <= c.hZ + csgtol && begin - r = hypot(pt.x, pt.y) - r_out = radius_at_z(c.hZ, zero(T), c.r[2][2], pt.z) - r <= r_out + csgtol && - _in_angular_interval_closed(atan(pt.y, pt.x), c.φ, csgtol = csgtol / r) - end -end -function _in(pt::CartesianPoint, c::PartialDownwardCone{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - abs(pt.z) + csgtol < c.hZ && begin - r = hypot(pt.x, pt.y) - csgtol + r < radius_at_z(c.hZ, zero(T), c.r[2][2], pt.z) && - _in_angular_interval_open(atan(pt.y, pt.x), c.φ, csgtol = csgtol / r) - end -end - function surfaces(t::PartialDownwardCone{T}) where {T} bot_center_pt = _transform_into_global_coordinate_system(CartesianPoint{T}(zero(T), zero(T), -t.hZ), t) outer_mantle = PartialConeMantle{T,:inwards}( (t.r[2][2], zero(T)), t.φ, t.hZ, t.origin, t.rotation) @@ -506,20 +508,8 @@ end const TopClosedTube{T,CO} = Cone{T,CO,Tuple{Tuple{T,T},T},Nothing} # Full in φ const PartialTopClosedTube{T,CO} = Cone{T,CO,Tuple{Tuple{T,T},T},T} - -function _in(pt::CartesianPoint, c::TopClosedTube{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - az = abs(pt.z) - az <= c.hZ + csgtol && begin - r = hypot(pt.x, pt.y) - r_in = radius_at_z(c.hZ, c.r[1][1], c.r[2], pt.z) - r_out = radius_at_z(c.hZ, c.r[1][2], c.r[2], pt.z) - r_in - csgtol <= r && - r <= r_out + csgtol - end -end -function _in(pt::CartesianPoint, c::TopClosedTube{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - abs(pt.z) + csgtol < c.hZ && - csgtol + radius_at_z(c.hZ, c.r[1][1], c.r[2], pt.z) < hypot(pt.x, pt.y) < radius_at_z(c.hZ, c.r[1][2], c.r[2], pt.z) - csgtol +function _in(pt::CartesianPoint, c::Cone{T,CO,Tuple{Tuple{T,T},T}}; csgtol::T = csg_default_tol(T)) where {T,CO} + _in(pt, Cone{T,CO}((c.r[1], (c.r[2], c.r[2])), c.φ, c.hZ, c.origin, c.rotation); csgtol) end function surfaces(t::TopClosedTube{T}) where {T} @@ -533,25 +523,6 @@ function surfaces(t::TopClosedTube{T}) where {T} e_bot, inner_mantle, outer_mantle end -function _in(pt::CartesianPoint, c::PartialTopClosedTube{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - az = abs(pt.z) - az <= c.hZ + csgtol && begin - r = hypot(pt.x, pt.y) - r_in = radius_at_z(c.hZ, c.r[1][1], c.r[2], pt.z) - r_out = radius_at_z(c.hZ, c.r[1][2], c.r[2], pt.z) - r_in - csgtol <= r && - r <= r_out + csgtol && - _in_angular_interval_closed(atan(pt.y, pt.x), c.φ, csgtol = csgtol / r) - end -end -function _in(pt::CartesianPoint, c::PartialTopClosedTube{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - abs(pt.z) + csgtol < c.hZ && begin - r = hypot(pt.x, pt.y) - csgtol + radius_at_z(c.hZ, c.r[1][1], c.r[2], pt.z) < r < radius_at_z(c.hZ, c.r[1][2], c.r[2], pt.z) - csgtol && - _in_angular_interval_open(atan(pt.y, pt.x), c.φ, csgtol = csgtol / r) - end -end - function surfaces(t::PartialTopClosedTube{T}) where {T} bot_center_pt = _transform_into_global_coordinate_system(CartesianPoint{T}(zero(T), zero(T), -t.hZ), t) # top_center_pt = _transform_into_global_coordinate_system(CartesianPoint{T}(zero(T), zero(T), +t.hZ), t) @@ -587,20 +558,8 @@ ______ const BottomClosedTube{T,CO} = Cone{T,CO,Tuple{T,Tuple{T,T}},Nothing} # Full in φ const PartialBottomClosedTube{T,CO} = Cone{T,CO,Tuple{T,Tuple{T,T}},T} - -function _in(pt::CartesianPoint, c::BottomClosedTube{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - az = abs(pt.z) - az <= c.hZ + csgtol && begin - r = hypot(pt.x, pt.y) - r_in = radius_at_z(c.hZ, c.r[1], c.r[2][1], pt.z) - r_out = radius_at_z(c.hZ, c.r[1], c.r[2][2], pt.z) - r_in - csgtol <= r && - r <= r_out + csgtol - end -end -function _in(pt::CartesianPoint, c::BottomClosedTube{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - abs(pt.z) + csgtol < c.hZ && - csgtol + radius_at_z(c.hZ, c.r[1], c.r[2][1], pt.z) < hypot(pt.x, pt.y) < radius_at_z(c.hZ, c.r[1], c.r[2][2], pt.z) - csgtol +function _in(pt::CartesianPoint, c::Cone{T,CO,Tuple{T,Tuple{T,T}}}; csgtol::T = csg_default_tol(T)) where {T,CO} + _in(pt, Cone{T,CO}(((c.r[1], c.r[1]), c.r[2]), c.φ, c.hZ, c.origin, c.rotation); csgtol) end function surfaces(t::BottomClosedTube{T}) where {T} @@ -614,25 +573,6 @@ function surfaces(t::BottomClosedTube{T}) where {T} e_top, inner_mantle, outer_mantle end -function _in(pt::CartesianPoint, c::PartialBottomClosedTube{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - az = abs(pt.z) - az <= c.hZ + csgtol && begin - r = hypot(pt.x, pt.y) - r_in = radius_at_z(c.hZ, c.r[1], c.r[2][1], pt.z) - r_out = radius_at_z(c.hZ, c.r[1], c.r[2][2], pt.z) - r_in - csgtol <= r && - r <= r_out + csgtol && - _in_angular_interval_closed(atan(pt.y, pt.x), c.φ, csgtol = csgtol / r) - end -end -function _in(pt::CartesianPoint, c::PartialBottomClosedTube{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} - abs(pt.z) + csgtol < c.hZ && begin - r = hypot(pt.x, pt.y) - csgtol + radius_at_z(c.hZ, c.r[1], c.r[2][1], pt.z) < r < radius_at_z(c.hZ, c.r[1], c.r[2][2], pt.z) - csgtol && - _in_angular_interval_open(atan(pt.y, pt.x), c.φ, csgtol = csgtol / r) - end -end - function surfaces(t::PartialBottomClosedTube{T}) where {T} # bot_center_pt = _transform_into_global_coordinate_system(CartesianPoint{T}(zero(T), zero(T), -t.hZ), t) top_center_pt = _transform_into_global_coordinate_system(CartesianPoint{T}(zero(T), zero(T), +t.hZ), t) @@ -678,6 +618,8 @@ function Geometry(::Type{T}, t::Type{Cone}, dict::AbstractDict, input_units::Nam _parse_value(T, dict["h"], length_unit) / 2 end + # TODO: throw error if hZ == 0 and different radii at top and bottom ? + cone = Cone{T}(ClosedPrimitive; r = r, φ = φ, @@ -729,103 +671,4 @@ function Dictionary(c::Cone{T, <:Any, TR})::OrderedDict{String, Any} where {T, T OrderedDict{String, Any}(name => dict) end -extremum(c::Cone{T}) where {T} = hypot(c.hZ, max((c.r...)...)) - -# Cylinder - - -# PartialCylinder - - - - - - -# Tube -# function _in(pt::CartesianPoint, c::Tube{T,ClosedPrimitive}) where {T} -# abs(pt.z) <= c.hZ && -# c.r[1] <= hypot(pt.x, pt.y) <= c.r[2] -# end -# function _in(pt::CartesianPoint, c::Tube{T,OpenPrimitive}) where {T} -# abs(pt.z) < c.hZ && -# c.r[1] < hypot(pt.x, pt.y) < c.r[2] -# end - -# function surfaces(t::Tube{T}) where {T} -# bot_center_pt = _transform_into_global_coordinate_system(CartesianPoint{T}(zero(T), zero(T), -t.hZ), t) -# top_center_pt = _transform_into_global_coordinate_system(CartesianPoint{T}(zero(T), zero(T), +t.hZ), t) -# inner_mantle = ConeMantle{T}((t.r[1], t.r[1]), t.φ, t.hZ, t.origin, t.rotation) -# outer_mantle = ConeMantle{T}((t.r[2], t.r[2]), t.φ, t.hZ, t.origin, t.rotation) -# e_bot = Annulus{T}(r = t.r, φ = t.φ, origin = bot_center_pt, rotation = t.rotation) -# e_top = Annulus{T}(r = t.r, φ = t.φ, origin = top_center_pt, rotation = -t.rotation * RotZ{T}(π)) -# # normals of the surfaces show inside the volume primitives. -# e_top, e_bot, inner_mantle, outer_mantle -# end - -# PartialTube -# function _in(pt::CartesianPoint, c::PartialTube{T,ClosedPrimitive}) where {T} -# abs(pt.z) <= c.hZ && -# c.r[1] <= hypot(pt.x, pt.y) <= c.r[2] && -# _in_angular_interval_closed(atan(pt.y, pt.x), c.φ) -# end -# function _in(pt::CartesianPoint, c::PartialTube{T,OpenPrimitive}) where {T} -# abs(pt.z) < c.hZ && -# c.r[1] < hypot(pt.x, pt.y) < c.r && -# _in_angular_interval_open(atan(pt.y, pt.x), c.φ) -# end - -# function surfaces(t::PartialTube{T}) where {T} -# bot_center_pt = _transform_into_global_coordinate_system(CartesianPoint{T}(zero(T), zero(T), -t.hZ), t) -# top_center_pt = _transform_into_global_coordinate_system(CartesianPoint{T}(zero(T), zero(T), +t.hZ), t) -# inner_mantle = PartialConeMantle{T}((t.r[1], t.r[1]), t.φ, t.hZ, t.origin, t.rotation) -# outer_mantle = PartialConeMantle{T}((t.r[2], t.r[2]), t.φ, t.hZ, t.origin, t.rotation) -# e_bot = PartialAnnulus{T}(r = t.r, φ = t.φ, origin = bot_center_pt, rotation = t.rotation) -# e_top = PartialAnnulus{T}(r = t.r, φ = t.φ, origin = top_center_pt, rotation = -t.rotation * RotZ{T}(π)) -# poly_l = _transform_into_global_coordinate_system(Quadrangle{T}(( -# CartesianPoint(CylindricalPoint{T}( t.r[1], t.φ[1], -t.hZ)), -# CartesianPoint(CylindricalPoint{T}( t.r[2], t.φ[1], -t.hZ)), -# CartesianPoint(CylindricalPoint{T}( t.r[2], t.φ[1], +t.hZ)), -# CartesianPoint(CylindricalPoint{T}( t.r[1], t.φ[1], +t.hZ)) )), t) -# poly_r = _transform_into_global_coordinate_system(Quadrangle{T}(( -# CartesianPoint(CylindricalPoint{T}( t.r[2], t.φ[2], -t.hZ)), -# CartesianPoint(CylindricalPoint{T}( t.r[2], t.φ[2], +t.hZ)), -# CartesianPoint(CylindricalPoint{T}( t.r[1], t.φ[2], +t.hZ)), -# CartesianPoint(CylindricalPoint{T}( t.r[1], t.φ[2], -t.hZ)) )), t) -# # normals of the surfaces show inside the volume primitives. -# e_top, e_bot, inner_mantle, outer_mantle, poly_l, poly_r -# end - -# VaryingTube -# function _in(pt::CartesianPoint, c::VaryingTube{T,ClosedPrimitive}) where {T} -# abs(pt.z) <= c.hZ && -# c.r[1] <= hypot(pt.x, pt.y) <= c.r[2] -# end -# function _in(pt::CartesianPoint, c::VaryingTube{T,OpenPrimitive}) where {T} -# abs(pt.z) < c.hZ && -# c.r[1] < hypot(pt.x, pt.y) < c.r[2] -# end - -# function surfaces(t::VaryingTube{T}) where {T} -# bot_center_pt = _transform_into_global_coordinate_system(CartesianPoint{T}(zero(T), zero(T), -t.hZ), t) -# top_center_pt = _transform_into_global_coordinate_system(CartesianPoint{T}(zero(T), zero(T), +t.hZ), t) -# inner_mantle = CylinderMantle{T}(t.r[1], t.φ, t.hZ, t.origin, t.rotation) -# outer_mantle = CylinderMantle{T}(t.r[2], t.φ, t.hZ, t.origin, t.rotation) -# e_bot = Annulus{T}(r = t.r, φ = t.φ, origin = bot_center_pt, rotation = t.rotation) -# e_top = Annulus{T}(r = t.r, φ = t.φ, origin = top_center_pt, rotation = -t.rotation * RotZ{T}(π)) -# # normals of the surfaces show inside the volume primitives. -# e_top, e_bot, inner_mantle, outer_mantle -# end - - -# function surfaces(t::VaryingTube{T,CO}) where {T,CO} -# bot_center_pt = _transform_into_global_coordinate_system(CartesianPoint{T}(zero(T), zero(T), -t.hZ), t) -# top_center_pt = _transform_into_global_coordinate_system(CartesianPoint{T}(zero(T), zero(T), +t.hZ), t) -# in_mantle = ConeMantle{T,Tuple{T,T},Nothing}((t.r[1][1], t.r[2][1]), t.φ, t.hZ, t.origin, t.rotation) -# out_mantle = ConeMantle{T,Tuple{T,T},Nothing}((t.r[1][2], t.r[2][2]), t.φ, t.hZ, t.origin, t.rotation) -# e_bot = EllipticalSurface{T,Tuple{T,T},Nothing}(r = t.r[1], φ = nothing, origin = bot_center_pt, rotation = t.rotation) -# e_top = EllipticalSurface{T,Tuple{T,T},Nothing}(r = t.r[2], φ = nothing, origin = top_center_pt, rotation = -t.rotation * RotZ{T}(π)) -# # normals of the surfaces show inside the volume primitives. -# e_top, e_bot, in_mantle, out_mantle -# end - -# PartialVaryingTube +extremum(c::Cone{T}) where {T} = hypot(c.hZ, max((c.r...)...)) \ No newline at end of file diff --git a/src/ConstructiveSolidGeometry/VolumePrimitives/Torus.jl b/src/ConstructiveSolidGeometry/VolumePrimitives/Torus.jl index 679d1fe76..5a6a19807 100644 --- a/src/ConstructiveSolidGeometry/VolumePrimitives/Torus.jl +++ b/src/ConstructiveSolidGeometry/VolumePrimitives/Torus.jl @@ -299,59 +299,88 @@ function surfaces(t::Torus{T,OpenPrimitive,Tuple{T,T},T,Tuple{T,T},TT1,TT2}) whe (tm_in, tm_out, cm1, cm2, es1, es2) end -# HollowTorus -function _in(pt::CartesianPoint{T}, t::Torus{T,ClosedPrimitive,Tuple{T,T}}; csgtol::T = csg_default_tol(T)) where {T} - _r = hypot(hypot(pt.x, pt.y) - t.r_torus, pt.z) - rmin::T, rmax::T = _radial_endpoints(t.r_tube) - return rmin - csgtol <= _r <= rmax + csgtol && - (isnothing(t.φ) || _in_angular_interval_closed(atan(pt.y, pt.x), t.φ, csgtol = csgtol)) && - (isnothing(t.θ) || _in_angular_interval_closed(atan(pt.z, hypot(pt.x, pt.y) - t.r_torus), t.θ, csgtol = csgtol)) +# FullTorus +function _in(pt::CartesianPoint{T}, t::FullTorus{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} + hypot(hypot(pt.x, pt.y) - t.r_torus, pt.z) <= t.r_tube + csgtol end -function _in(pt::CartesianPoint{T}, t::Torus{T,OpenPrimitive,Tuple{T,T}}; csgtol::T = csg_default_tol(T)) where {T} - _r = hypot(hypot(pt.x, pt.y) - t.r_torus, pt.z) - rmin::T, rmax::T = _radial_endpoints(t.r_tube) - return rmin + csgtol < _r < rmax - csgtol && - (isnothing(t.φ) || _in_angular_interval_open(atan(pt.y, pt.x), t.φ, csgtol = csgtol)) && - (isnothing(t.θ) || _in_angular_interval_open(atan(pt.z, hypot(pt.x, pt.y) - t.r_torus), t.θ, csgtol = csgtol)) + +function _in(pt::CartesianPoint{T}, t::FullTorus{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} + hypot(hypot(pt.x, pt.y) - t.r_torus, pt.z) < t.r_tube - csgtol end -# FullTorus -function _in(pt::CartesianPoint{T}, t::Torus{T,ClosedPrimitive,T}; csgtol::T = csg_default_tol(T)) where {T} - _r = hypot(hypot(pt.x, pt.y) - t.r_torus, pt.z) - rmax::T = t.r_tube - return _r <= rmax + csgtol && - (isnothing(t.φ) || _in_angular_interval_closed(atan(pt.y, pt.x), t.φ, csgtol = csgtol)) && - (isnothing(t.θ) || _in_angular_interval_closed(atan(pt.z, hypot(pt.x, pt.y) - t.r_torus), t.θ, csgtol = csgtol)) -end -function _in(pt::CartesianPoint{T}, t::Torus{T,OpenPrimitive,T}; csgtol::T = csg_default_tol(T)) where {T} - _r = hypot(hypot(pt.x, pt.y) - t.r_torus, pt.z) - rmax::T = t.r_tube - return _r < rmax - csgtol && - (isnothing(t.φ) || _in_angular_interval_open(atan(pt.y, pt.x), t.φ, csgtol = csgtol)) && - (isnothing(t.θ) || _in_angular_interval_open(atan(pt.z, hypot(pt.x, pt.y) - t.r_torus), t.θ, csgtol = csgtol)) -end - -# #Constructors -# function Torus(;r_torus = 1, r_tubeMin = 0, r_tubeMax = 1, φMin = 0, φMax = 2π, θMin = 0, θMax = 2π, z = 0) -# T = float(promote_type(typeof.((r_torus, r_tubeMin, r_tubeMax, φMin, φMax, θMin, θMax, z))...)) -# r_tube = r_tubeMin == 0 ? T(r_tubeMax) : T(r_tubeMin)..T(r_tubeMax) -# φ = mod(T(φMax) - T(φMin), T(2π)) == 0 ? nothing : T(φMin)..T(φMax) -# θ = mod(T(θMax) - T(θMin), T(2π)) == 0 ? nothing : T(θMin)..T(θMax) -# Torus( T, T(r_torus), r_tube, φ, θ, T(z)) -# end +# FullThetaTorus +function _in(pt::CartesianPoint{T}, t::FullThetaTorus{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} + φ::T = mod(atan(pt.y, pt.x), T(2π)) + Δφ::T = max(min(T(2π)-φ, φ-t.φ), zero(T)) + _y::T, _x::T = hypot(pt.x, pt.y) .* sincos(Δφ) + _r::T = hypot(_x - t.r_torus, pt.z) + hypot(max(_r - t.r_tube, zero(T)), _y) <= csgtol +end -# Torus(r_torus, r_tubeMin, r_tubeMax, φMin, φMax, θMin, θMax, z) = Torus(;r_torus = r_torus, r_tubeMin = r_tubeMin, r_tubeMax = r_tubeMax, φMin = φMin, φMax = φMax, θMin = θMin, θMax = θMax, z = z) +function _in(pt::CartesianPoint{T}, t::FullThetaTorus{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} + r::T = hypot(pt.x, pt.y) + φ::T = mod(atan(pt.y, pt.x), T(2π)) + hypot(r - t.r_torus, pt.z) < t.r_tube - csgtol && + _in_angular_interval_closed(φ, t.φ, csgtol = zero(T)) && + r * sin(min(φ, t.φ - φ, T(π/2))) > csgtol +end -# function Torus(r_torus::R1, r_tube::R2, z::TZ) where {R1<:Real, R2<:Real, TZ<:Real} -# T = float(promote_type(R1, R2, TZ)) -# Torus( T, T(r_torus), T(r_tube), nothing, nothing, T(z)) -# end +# FullPhiTorus +function _in(pt::CartesianPoint{T}, t::FullPhiTorus{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} + _r::T = hypot(hypot(pt.x, pt.y) - t.r_torus, pt.z) + θ::T = mod(atan(pt.z, hypot(pt.x, pt.y) - t.r_torus) - t.θ[1], T(2π)) + Δθ::T = max(min(T(2π) - θ, θ - t.θ[2] + t.θ[1]), zero(T)) + Δθ <= atan(csgtol, t.r_tube) ? + csgtol^2 - t.r_tube^2 * sin(Δθ)^2 >= 0 && _r <= t.r_tube * cos(Δθ) + sqrt(csgtol^2 - t.r_tube^2 * sin(Δθ)^2) : + _r <= csgtol / sin(min(Δθ, T(π/2))) +end -# function RoundChamfer(r_torus::R1, r_tube::R2, z::TZ) where {R1<:Real, R2<:Real, TZ<:Real} -# T = float(promote_type(R1, R2, TZ)) -# Torus( T, T(r_torus), T(r_tube), nothing, T(0)..T(π/2), T(z)) -# end +function _in(pt::CartesianPoint{T}, t::FullPhiTorus{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} + _r::T = hypot(hypot(pt.x, pt.y) - t.r_torus, pt.z) + θ::T = mod(atan(pt.z, hypot(pt.x, pt.y) - t.r_torus) - t.θ[1], T(2π)) + Δθ::T = max(min(T(2π) - θ, θ - t.θ[2] + t.θ[1]), zero(T)) + _r < t.r_tube - csgtol && _in_angular_interval_closed(θ, t.θ[2] - t.θ[1], csgtol = zero(T)) && _r * sin(min(θ, t.θ[2] - t.θ[1] - θ, T(π/2))) > csgtol +end +# HollowTorus +function _in(pt::CartesianPoint{T}, t::HollowTorus{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} + t.r_tube[1] - csgtol <= hypot(hypot(pt.x, pt.y) - t.r_torus, pt.z) <= t.r_tube[2] + csgtol +end + +function _in(pt::CartesianPoint{T}, t::HollowTorus{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} + t.r_tube[1] + csgtol < hypot(hypot(pt.x, pt.y) - t.r_torus, pt.z) < t.r_tube[2] - csgtol +end + +# HollowPhiTorus +function _in(pt::CartesianPoint{T}, t::HollowPhiTorus{T,ClosedPrimitive}; csgtol::T = csg_default_tol(T)) where {T} + _r::T = hypot(hypot(pt.x, pt.y) - t.r_torus, pt.z) + θ::T = mod(atan(pt.z, hypot(pt.x, pt.y) - t.r_torus) - t.θ[1], T(2π)) + Δθ::T = max(min(T(2π) - θ, θ - t.θ[2] + t.θ[1]), zero(T)) + return csgtol^2 - t.r_tube[1]^2 * sin(Δθ)^2 >=0 && + _r >= t.r_tube[1] * cos(Δθ) - sqrt(csgtol^2 - t.r_tube[1]^2 * sin(Δθ)^2) && + if Δθ <= atan(csgtol, t.r_tube[2]) + csgtol^2 - t.r_tube[2]^2 * sin(Δθ)^2 >= 0 && + _r <= t.r_tube[2] * cos(Δθ) + sqrt(csgtol^2 - t.r_tube[2]^2 * sin(Δθ)^2) + else + _r <= max(csgtol / sin(min(Δθ, T(π/2))), t.r_tube[1] * cos(Δθ) + sqrt(csgtol^2 - t.r_tube[1]^2 * sin(Δθ)^2)) + end +end + +function _in(pt::CartesianPoint{T}, t::HollowPhiTorus{T,OpenPrimitive}; csgtol::T = csg_default_tol(T)) where {T} + _r::T = hypot(hypot(pt.x, pt.y) - t.r_torus, pt.z) + θ::T = mod(atan(pt.z, hypot(pt.x, pt.y) - t.r_torus) - t.θ[1], T(2π)) + Δθ::T = max(min(T(2π) - θ, θ - t.θ[2] + t.θ[1]), zero(T)) + return t.r_tube[1] + csgtol < _r < t.r_tube[2] - csgtol && + _in_angular_interval_closed(θ, t.θ[2] - t.θ[1], csgtol = zero(T)) && + _r * sin(min(θ, t.θ[2] - t.θ[1] - θ, T(π/2))) > csgtol +end + +# Generic Torus (combination of HollowPhiTorus and FullThetaTorus) +function _in(pt::CartesianPoint{T}, t::Torus{T,CO}; csgtol::T = csg_default_tol(T)) where {T,CO} + rmax::T = _radial_endpoints(t.r_tube)[2] + _in(pt, Torus{T,CO}(t.r_torus, t.r_tube, nothing, t.θ, t.origin, t.rotation); csgtol) && + (isnothing(t.φ) || _in(pt, Torus{T,CO}(t.r_torus, rmax, t.φ, nothing, t.origin, t.rotation); csgtol)) +end extremum(t::Torus{T}) where {T} = t.r_torus + max(t.r_tube...) diff --git a/test/ConstructiveSolidGeometry/CSG_primitives.jl b/test/ConstructiveSolidGeometry/CSG_primitives.jl index 723b52981..f222c4093 100644 --- a/test/ConstructiveSolidGeometry/CSG_primitives.jl +++ b/test/ConstructiveSolidGeometry/CSG_primitives.jl @@ -18,17 +18,17 @@ no_translations = (rotation = one(SMatrix{3, 3, T, 9}), translation = zero(Carte for bot in (2.0, Dict("from" => 1.0, "to" => 2.0)), top in (2.0, 1.0, Dict("from" => 1.0, "to" => 2.0), Dict("from" => 2.0, "to" => 4.0)), φ in ((30,180),(0,360)) - + dict = Dict("difference" => [ Dict("cone" => Dict( "r" => Dict("bottom" => bot, "top" => top), "phi" => Dict("from" => "$(φ[1])°", "to" => "$(φ[2])°"), "h" => 1.0)) for i in 1:2 - ]) + ]) c = Geometry(T, dict, default_units, no_translations) - + # Conversion from Geometry -> Dict and Dict -> Geometry should result in the same geometry output = Dictionary(c) name = collect(keys(output["difference"][1]))[1] # "tube" or "cone" @@ -92,13 +92,31 @@ no_translations = (rotation = one(SMatrix{3, 3, T, 9}), translation = zero(Carte tuple_cone = @inferred CSG.Cone{Float64}(φ=(π/4,3*π/4), rotation=SMatrix{3}(0,0,-1,0,1,0,1,0,0)) rot_cone = @inferred CSG.Cone{Float64}(φ=π/2, rotation=SMatrix{3}(0,0,-1,0,1,0,1,0,0) * SMatrix{3}(cos(π/4),sin(π/4),0,-sin(π/4),cos(π/4),0,0,0,1)) @test tuple_cone ==rot_cone - + # PartialCylinder (OpenPrimitive) pc_open = CSG.Cone(CSG.OpenPrimitive, r = convert(T, 1.0), φ = convert(T, π/2), hZ = convert(T, 1.0)) @test CSG._in(CartesianPoint{T}(0.5, 0.2, 0.0), pc_open) @test !CSG._in(CartesianPoint{T}(1.1, 0.0, 0.0), pc_open) @test !CSG._in(CartesianPoint{T}(-0.5, -0.2, 0.0), pc_open) @test !CSG._in(CartesianPoint{T}(0.5, 0.0, 1.0), pc_open) + @test !CSG._in(CartesianPoint{T}(0.0, 0.0, 0.0), pc_open) + @test !CSG._in(CartesianPoint{T}(0.0, 0.0, -1.0), pc_open) + @test !CSG._in(CartesianPoint{T}(1.0, 0.0, -1.0), pc_open) + @test !CSG._in(CartesianPoint{T}(0.0, 1.0, -1.0), pc_open) + @test !CSG._in(CartesianPoint{T}(-1.0, 0.0, -1.0), pc_open) + @test !CSG._in(CartesianPoint{T}(0.0, 0.0, 1.0), pc_open) + + # PartialCylinder (ClosedPrimitive) + pc_closed = CSG.Cone(CSG.ClosedPrimitive, r = convert(T, 1.0), φ = convert(T, π/2), hZ = convert(T, 1.0)) + @test CSG._in(CartesianPoint{T}(0.5, 0.2, 0.0), pc_closed) + @test !CSG._in(CartesianPoint{T}(1.1, 0.0, 0.0), pc_closed) + @test !CSG._in(CartesianPoint{T}(-0.5, -0.2, 0.0), pc_closed) + @test CSG._in(CartesianPoint{T}(0.0, 0.0, 0.0), pc_closed) + @test CSG._in(CartesianPoint{T}(0.0, 0.0, -1.0), pc_closed) + @test CSG._in(CartesianPoint{T}(1.0, 0.0, -1.0), pc_closed) + @test CSG._in(CartesianPoint{T}(0.0, 1.0, -1.0), pc_closed) + @test !CSG._in(CartesianPoint{T}(-1.0, 0.0, -1.0), pc_closed) + @test CSG._in(CartesianPoint{T}(0.0, 0.0, 1.0), pc_closed) # VaryingCylinder (ClosedPrimitive) vc_closed = CSG.Cone{T}(r = ((convert(T,1.0),), (convert(T,3.0),)), φ = nothing, hZ = convert(T,2.0)) @@ -111,18 +129,58 @@ no_translations = (rotation = one(SMatrix{3, 3, T, 9}), translation = zero(Carte vc_open = CSG.Cone(CSG.OpenPrimitive, r = ((convert(T,1.0),), (convert(T,3.0),)), φ = nothing, hZ = convert(T,2.0)) @test !CSG._in(CartesianPoint{T}(2.0, 0.0, 0.0), vc_open) @test CSG._in(CartesianPoint{T}(1.9, 0.0, 0.0), vc_open) + @test !CSG._in(CartesianPoint{T}(1.0, 0.0, -2.0), vc_open) @test !CSG._in(CartesianPoint{T}(3.0, 0.0, 2.0), vc_open) # PartialVaryingCylinder (ClosedPrimitive) - pvc_closed = CSG.Cone{T}(r = ((convert(T,1.0),), (convert(T,2.0),)), φ = convert(T, π), hZ = convert(T, 1.0)) + pvc_closed = CSG.Cone{T}(CSG.ClosedPrimitive, r = ((convert(T,1.0),), (convert(T,2.0),)), φ = convert(T, π), hZ = convert(T, 1.0)) @test CSG._in(CartesianPoint{T}(1.4, 0.1, 0.0), pvc_closed) @test !CSG._in(CartesianPoint{T}(1.6, 0.0, 0.0), pvc_closed) @test !CSG._in(CartesianPoint{T}(1.0, -0.1, 0.0), pvc_closed) + @test CSG._in(CartesianPoint{T}(0.0, 0.0, -1.0), pvc_closed) + @test CSG._in(CartesianPoint{T}(0.0, 1.0, -1.0), pvc_closed) + @test CSG._in(CartesianPoint{T}(1.0, 0.0, -1.0), pvc_closed) + @test CSG._in(CartesianPoint{T}(-1.0, 0.0, -1.0), pvc_closed) + @test !CSG._in(CartesianPoint{T}(0.0, -1.0, -1.0), pvc_closed) + @test CSG._in(CartesianPoint{T}(0.0, 0.0, 1.0), pvc_closed) + @test CSG._in(CartesianPoint{T}(0.0, 0.0, 0.0), pvc_closed) + @test CSG._in(CartesianPoint{T}(1.5, 0.0, 1.0), pvc_closed) + @test CSG._in(CartesianPoint{T}(0.0, 2.0, 1.0), pvc_closed) + @test CSG._in(CartesianPoint{T}(2.0, 0.0, 1.0), pvc_closed) + @test CSG._in(CartesianPoint{T}(-2.0, 0.0, 1.0), pvc_closed) + @test !CSG._in(CartesianPoint{T}(0.0, -2.0, 1.0), pvc_closed) # PartialVaryingCylinder (OpenPrimitive) pvc_open = CSG.Cone(CSG.OpenPrimitive, r = ((convert(T,1.0),), (convert(T,2.0),)), φ = convert(T, π), hZ = convert(T, 1.0)) @test !CSG._in(CartesianPoint{T}(1.5, 0.0, 0.0), pvc_open) @test CSG._in(CartesianPoint{T}(1.4, 0.1, 0.0), pvc_open) + @test !CSG._in(CartesianPoint{T}(0.0, 0.0, -1.0), pvc_open) + @test !CSG._in(CartesianPoint{T}(0.0, 1.0, -1.0), pvc_open) + @test !CSG._in(CartesianPoint{T}(1.0, 0.0, -1.0), pvc_open) + @test !CSG._in(CartesianPoint{T}(-1.0, 0.0, -1.0), pvc_open) + @test !CSG._in(CartesianPoint{T}(0.0, -1.0, -1.0), pvc_open) + @test !CSG._in(CartesianPoint{T}(0.0, 0.0, 1.0), pvc_open) + @test !CSG._in(CartesianPoint{T}(1.5, 0.0, 1.0), pvc_open) + @test !CSG._in(CartesianPoint{T}(0.0, 2.0, 1.0), pvc_open) + @test !CSG._in(CartesianPoint{T}(2.0, 0.0, 1.0), pvc_open) + @test !CSG._in(CartesianPoint{T}(-2.0, 0.0, 1.0), pvc_open) + @test !CSG._in(CartesianPoint{T}(0.0, -2.0, 1.0), pvc_open) + + # PartialVaryingTube (ClosedPrimitive) + pvt_closed = CSG.Cone(CSG.ClosedPrimitive, r = ((convert(T,1.0), convert(T,2.0)), (convert(T,1.5), convert(T,2.5))), φ = convert(T, π/2), hZ = convert(T, 1.0)) + @test CSG._in(CartesianPoint{T}(1.6, 0.2, 0.0), pvt_closed) + @test !CSG._in(CartesianPoint{T}(1.1, 0.0, 0.0), pvt_closed) + @test !CSG._in(CartesianPoint{T}(2.6, 0.0, 0.0), pvt_closed) + @test !CSG._in(CartesianPoint{T}(1.6, -0.2, 0.0), pvt_closed) + @test CSG._in(CartesianPoint{T}(1.0, 0.0, -1.0), pvt_closed) + @test CSG._in(CartesianPoint{T}(2.0, 0.0, -1.0), pvt_closed) + @test CSG._in(CartesianPoint{T}(0.0, 1.0, -1.0), pvt_closed) + @test CSG._in(CartesianPoint{T}(0.0, 2.0, -1.0), pvt_closed) + @test CSG._in(CartesianPoint{T}(0.0, 2.5, 1.0), pvt_closed) + @test CSG._in(CartesianPoint{T}(0.0, 1.5, 1.0), pvt_closed) + @test CSG._in(CartesianPoint{T}(1.5, 0.0, 1.0), pvt_closed) + @test CSG._in(CartesianPoint{T}(2.5, 0.0, 1.0), pvt_closed) + @test !CSG._in(CartesianPoint{T}(-2.5, 0.0, 1.0), pvt_closed) # PartialVaryingTube (OpenPrimitive) pvt_open = CSG.Cone(CSG.OpenPrimitive, r = ((convert(T,1.0), convert(T,2.0)), (convert(T,1.5), convert(T,2.5))), φ = convert(T, π/2), hZ = convert(T, 1.0)) @@ -130,15 +188,24 @@ no_translations = (rotation = one(SMatrix{3, 3, T, 9}), translation = zero(Carte @test !CSG._in(CartesianPoint{T}(1.1, 0.0, 0.0), pvt_open) @test !CSG._in(CartesianPoint{T}(2.6, 0.0, 0.0), pvt_open) @test !CSG._in(CartesianPoint{T}(1.6, -0.2, 0.0), pvt_open) + @test !CSG._in(CartesianPoint{T}(1.0, 0.0, -1.0), pvt_open) + @test !CSG._in(CartesianPoint{T}(2.0, 0.0, -1.0), pvt_open) + @test !CSG._in(CartesianPoint{T}(0.0, 1.0, -1.0), pvt_open) + @test !CSG._in(CartesianPoint{T}(0.0, 2.0, -1.0), pvt_open) + @test !CSG._in(CartesianPoint{T}(0.0, 2.5, 1.0), pvt_open) + @test !CSG._in(CartesianPoint{T}(0.0, 1.5, 1.0), pvt_open) + @test !CSG._in(CartesianPoint{T}(1.5, 0.0, 1.0), pvt_open) + @test !CSG._in(CartesianPoint{T}(2.5, 0.0, 1.0), pvt_open) + @test !CSG._in(CartesianPoint{T}(-2.5, 0.0, 1.0), pvt_open) # Upward cones r_up_closed = ((nothing, convert(T,0.0)), nothing) r_up_open = ((nothing, convert(T,0.1)), nothing) φ_up = nothing h_up = convert(T, 1.0) - origin = CartesianPoint{T}(0.0, 0.0, 0.0) + origin = zero(CartesianPoint{Float64}()) rotation = SMatrix{3,3,T}(I) - + # Closed UpwardCone upward_closed = CSG.UpwardCone{T, CSG.ClosedPrimitive}(r_up_closed, φ_up, h_up, origin, rotation) @test CSG._in(CartesianPoint{T}(0.0, 0.0, 0.5), upward_closed) @@ -153,39 +220,222 @@ no_translations = (rotation = one(SMatrix{3, 3, T, 9}), translation = zero(Carte @test !CSG._in(CartesianPoint{T}(2.0, 0.0, 0.5), upward_open) @test length(CSG.surfaces(upward_open)) > 0 - # PartialUpwardCone - φ_puc = convert(T, π) - h_puc = convert(T, 1.0) - r_puc_closed = ((nothing, convert(T,0.1)), nothing) - r_puc_open = ((nothing, convert(T,0.5)), nothing) + r_bot = (nothing, 2.0) + r_cone = (r_bot, nothing) - puc_closed = CSG.PartialUpwardCone{T, CSG.ClosedPrimitive}(r_puc_closed, φ_puc, h_puc, origin, rotation) - puc_open = CSG.PartialUpwardCone{T, CSG.OpenPrimitive}(r_puc_open, φ_puc, h_puc, origin, rotation) + # PartialUpwardCone + puc_closed = CSG.PartialUpwardCone{T, CSG.ClosedPrimitive}(r_cone, T(π), 1.0, origin, rotation) + @test CSG._in(CartesianPoint{T}(0.5, 0.5, -0.5), puc_closed) + @test CSG._in(CartesianPoint{T}(0.0, 0.0, -1.0), puc_closed) + @test CSG._in(CartesianPoint{T}(0.0, 0.0, 0.0), puc_closed) + @test CSG._in(CartesianPoint{T}(0.0, 0.0, 1.0), puc_closed) + @test CSG._in(CartesianPoint{T}(2.0, 0.0, -1.0), puc_closed) + @test CSG._in(CartesianPoint{T}(0.0, 2.0, -1.0), puc_closed) + @test CSG._in(CartesianPoint{T}(-2.0, 0.0, -1.0), puc_closed) + @test !CSG._in(CartesianPoint{T}(0.0, -2.0, -1.0), puc_closed) + @test !CSG._in(CartesianPoint{T}(0.5, -0.6, -2), puc_closed) - z_test = convert(T, 0.1) - r_max_closed = CSG.radius_at_z(puc_closed.hZ, puc_closed.r[1][2], zero(T), z_test) - r_max_open = CSG.radius_at_z(puc_open.hZ, puc_open.r[1][2], zero(T), z_test) - - # Points inside the cone - r_inside_closed = r_max_closed * 0.5 - r_inside_open = r_max_open * 0.5 - - # ---- Closed cone tests ---- - @test CSG._in(CartesianPoint{T}(r_inside_closed, 0.0, z_test), puc_closed) - @test !CSG._in(CartesianPoint{T}(r_max_closed * 1.1, 0.0, z_test), puc_closed) - @test !CSG._in(CartesianPoint{T}(r_inside_closed, -0.1, z_test), puc_closed) - - # ---- Open cone tests ---- - x_inside_open = r_inside_open * cos(T(0.1)) # small angle inside φ range - y_inside_open = r_inside_open * sin(T(0.1)) - @test CSG._in(CartesianPoint{T}(x_inside_open, y_inside_open, z_test), puc_open) - @test !CSG._in(CartesianPoint{T}(r_max_open * 1.1, 0.0, z_test), puc_open) - @test !CSG._in(CartesianPoint{T}(r_inside_open/2, -0.1, z_test), puc_open) + puc_open = CSG.PartialUpwardCone{T, CSG.OpenPrimitive}(r_cone, T(π), 1.0, origin, rotation) + @test CSG._in(CartesianPoint{T}(0.5, 0.5, -0.5), puc_open) + @test !CSG._in(CartesianPoint{T}(0.0, 0.0, -1.0), puc_open) + @test !CSG._in(CartesianPoint{T}(0.0, 0.0, 0.0), puc_open) + @test !CSG._in(CartesianPoint{T}(0.0, 0.0, 1.0), puc_open) + @test !CSG._in(CartesianPoint{T}(2.0, 0.0, -1.0), puc_open) + @test !CSG._in(CartesianPoint{T}(0.0, 2.0, -1.0), puc_open) + @test !CSG._in(CartesianPoint{T}(-2.0, 0.0, -1.0), puc_open) + @test !CSG._in(CartesianPoint{T}(0.0, -2.0, -1.0), puc_open) + @test !CSG._in(CartesianPoint{T}(0.5, -0.6, -2), puc_open) surfs = CSG.surfaces(puc_open) @test length(surfs) > 0 @test all(x -> x !== nothing, surfs) + + # Radii must match type: Tuple{Nothing, Tuple{Nothing,T}} + r_top = (nothing, 2.0) + r_cone = (nothing, r_top) + + # DownwardCone ClosedPrimitive + dc_closed = CSG.DownwardCone{T, CSG.ClosedPrimitive}(r_cone, nothing, 1.0, origin, rotation) + @test CSG._in(CartesianPoint{T}(0.2, 0.2, -0.5), dc_closed) + @test CSG._in(CartesianPoint{T}(1, 0, 0), dc_closed) + @test !CSG._in(CartesianPoint{T}(1.5, 0.0, -0.5), dc_closed) + @test CSG._in(CartesianPoint{T}(0, 0, -1), dc_closed) + + # DownwardCone OpenPrimitive + dc_open = CSG.DownwardCone{T, CSG.OpenPrimitive}(r_cone, nothing, 1.0, origin, rotation) + @test CSG._in(CartesianPoint{T}(0.2, 0.2, -0.5), dc_open) + @test !CSG._in(CartesianPoint{T}(1, 0, 0), dc_open) + @test !CSG._in(CartesianPoint{T}(1.5, 0.0, -0.5), dc_open) + @test !CSG._in(CartesianPoint{T}(0, 0, -1), dc_open) + + # PartialDownwardCone ClosedPrimitive + pdc_closed = CSG.PartialDownwardCone{T, CSG.ClosedPrimitive}(r_cone, T(π), 1.0, origin, rotation) + @test CSG._in(CartesianPoint{T}(0.5, 0.0, -0.5), pdc_closed) + @test CSG._in(CartesianPoint{T}(0.0, 0.0, -1.0), pdc_closed) + @test CSG._in(CartesianPoint{T}(0.0, 0.0, 0.0), pdc_closed) + @test CSG._in(CartesianPoint{T}(0.0, 0.0, 1.0), pdc_closed) + @test CSG._in(CartesianPoint{T}(2.0, 0.0, 1.0), pdc_closed) + @test CSG._in(CartesianPoint{T}(0.0, 2.0, 1.0), pdc_closed) + @test CSG._in(CartesianPoint{T}(-2.0, 0.0, 1.0), pdc_closed) + @test !CSG._in(CartesianPoint{T}(0.0, -2.0, 1.0), pdc_closed) + @test !CSG._in(CartesianPoint{T}(0.5, -0.6, -0.5), pdc_closed) + + # PartialDownwardCone OpenPrimitive + pdc_open = CSG.PartialDownwardCone{T, CSG.OpenPrimitive}(r_cone, T(π), 1.0, origin, rotation) + @test CSG._in(CartesianPoint{T}(0.2, 0.2, -0.5), pdc_open) + @test !CSG._in(CartesianPoint{T}(0.0, 0.0, -1.0), pdc_open) + @test !CSG._in(CartesianPoint{T}(1.5, 0.0, -0.5), pdc_open) + @test !CSG._in(CartesianPoint{T}(0.0, 0.0, 0.0), pdc_open) + @test !CSG._in(CartesianPoint{T}(0.0, 0.0, 1.0), pdc_open) + @test !CSG._in(CartesianPoint{T}(2.0, 0.0, 1.0), pdc_open) + @test !CSG._in(CartesianPoint{T}(0.0, 2.0, 1.0), pdc_open) + @test !CSG._in(CartesianPoint{T}(-2.0, 0.0, 1.0), pdc_open) + + # Surfaces tests + surfs_dc_closed = CSG.surfaces(dc_closed) + @test length(surfs_dc_closed) == 2 + @test surfs_dc_closed[1] isa CSG.EllipticalSurface + @test surfs_dc_closed[2] isa CSG.FullConeMantle + + surfs_pdc_closed = CSG.surfaces(pdc_closed) + @test length(surfs_pdc_closed) == 4 + @test surfs_pdc_closed[1] isa CSG.EllipticalSurface + @test surfs_pdc_closed[2] isa CSG.PartialConeMantle + @test surfs_pdc_closed[3] isa CSG.Triangle + @test surfs_pdc_closed[4] isa CSG.Triangle + + # TopClosedTube + r_inner = (0.3, 0.5) + r_top = 1.0 + r_cone = (r_inner, r_top) + hZ = 1.0 + tube_closed = CSG.TopClosedTube{Float64, CSG.ClosedPrimitive}(r_cone, nothing, hZ, origin, rotation) + @test CSG._in(CartesianPoint(0.5, 0.0, -1), tube_closed) # surface limit + @test CSG._in(CartesianPoint(1.0, 0.0, 1.0), tube_closed) # on outer boundary + @test !CSG._in(CartesianPoint(0.0, 0.0, -1.5), tube_closed) # below bottom + + tube_open = CSG.TopClosedTube{Float64, CSG.OpenPrimitive}(r_cone, nothing, hZ, origin, rotation) + @test !CSG._in(CartesianPoint(0.5, 0.0, -1), tube_open) + @test !CSG._in(CartesianPoint(1.0, 0.0, 1.0), tube_open) + @test !CSG._in(CartesianPoint(0.0, 0.0, -1.5), tube_open) + + surfs_closed = CSG.surfaces(tube_closed) + @test length(surfs_closed) == 3 + @test surfs_closed[1] isa CSG.EllipticalSurface + @test surfs_closed[2] isa CSG.FullConeMantle + @test surfs_closed[3] isa CSG.FullConeMantle + + surfs_open = CSG.surfaces(tube_open) + @test length(surfs_open) == 3 + @test surfs_open[1] isa CSG.EllipticalSurface + @test surfs_open[2] isa CSG.FullConeMantle + @test surfs_open[3] isa CSG.FullConeMantle + + phi_partial = π/2 + + # PartialTopClosedTube (ClosedPrimitive) + p_tube_closed = CSG.PartialTopClosedTube{Float64, CSG.ClosedPrimitive}(r_cone, phi_partial, hZ, origin, rotation) + @test CSG._in(CartesianPoint(1.0, 0.0, 1.0), p_tube_closed) + @test CSG._in(CartesianPoint(1.0, 0.0, 1.0), p_tube_closed) + @test !CSG._in(CartesianPoint(-1.0, 0.0, 1.0), p_tube_closed) + @test CSG._in(CartesianPoint(0.5, 0.0, -1.0), p_tube_closed) + @test CSG._in(CartesianPoint(0.3, 0.0, -1.0), p_tube_closed) + @test !CSG._in(CartesianPoint(0.0, 0.0, -1.5), p_tube_closed) + @test !CSG._in(CartesianPoint(0.6, 0.6, 0.0), p_tube_closed) + + # PartialTopClosedTube (OpenPrimitive) + p_tube_open = CSG.PartialTopClosedTube{Float64, CSG.OpenPrimitive}(r_cone, phi_partial, hZ, origin, rotation) + @test CSG._in(CartesianPoint(0.4, 0.1, -0.9), p_tube_open) + @test !CSG._in(CartesianPoint(0.5, 0.0, -1.0), p_tube_open) + @test !CSG._in(CartesianPoint(1.0, 0.0, 1.0), p_tube_open) + @test !CSG._in(CartesianPoint(0.0, 0.0, -1.5), p_tube_open) + @test !CSG._in(CartesianPoint(0.5, 0.0, -1.0), p_tube_open) + @test !CSG._in(CartesianPoint(0.3, 0.0, -1.0), p_tube_open) + + surfs_closed = CSG.surfaces(p_tube_closed) + @test surfs_closed[1] isa CSG.EllipticalSurface + @test surfs_closed[2] isa CSG.PartialConeMantle + @test surfs_closed[3] isa CSG.PartialConeMantle + @test surfs_closed[4] isa CSG.Triangle + @test surfs_closed[5] isa CSG.Triangle + + surfs_open = CSG.surfaces(p_tube_open) + @test length(surfs_open) == 5 + @test surfs_open[1] isa CSG.EllipticalSurface + @test surfs_open[2] isa CSG.PartialConeMantle + @test surfs_open[3] isa CSG.PartialConeMantle + @test surfs_open[4] isa CSG.Triangle + @test surfs_open[5] isa CSG.Triangle + + # BottomClosedTube and PartialBottomClosedTube + r_inner = 0.5 + r_outer = (0.8, 1.0) + r_cone = (r_inner, r_outer) + + tube_closed = CSG.BottomClosedTube{Float64, CSG.ClosedPrimitive}(r_cone, nothing, hZ, origin, rotation) + tube_open = CSG.BottomClosedTube{Float64, CSG.OpenPrimitive}(r_cone, nothing, hZ, origin, rotation) + p_tube_closed = CSG.PartialBottomClosedTube{Float64, CSG.ClosedPrimitive}(r_cone, phi_partial, hZ, origin, rotation) + p_tube_open = CSG.PartialBottomClosedTube{Float64, CSG.OpenPrimitive}(r_cone, phi_partial, hZ, origin, rotation) + + @test CSG._in(CartesianPoint(0.5, 0.0, -1.0), tube_closed) + @test CSG._in(CartesianPoint(0.8, 0.0, 1.0), tube_closed) + @test CSG._in(CartesianPoint(-0.9, 0.0, 0.9), tube_closed) + @test !CSG._in(CartesianPoint(1.1, 0.0, 0.0), tube_closed) + @test !CSG._in(CartesianPoint(0.5, 0.0, 1.1), tube_closed) + + @test !CSG._in(CartesianPoint(0.5, 0.0, -1.0), tube_open) + @test !CSG._in(CartesianPoint(0.8, 0.0, 1.0), tube_open) + @test CSG._in(CartesianPoint(-0.9, 0.0, 0.9), tube_open) + @test !CSG._in(CartesianPoint(1.1, 0.0, 0.0), tube_open) + @test !CSG._in(CartesianPoint(0.5, 0.0, 1.1), tube_open) + + @test CSG._in(CartesianPoint(0.5, 0.0, -1.0), p_tube_closed) + @test CSG._in(CartesianPoint(0.8, 0.0, 1.0), p_tube_closed) + @test CSG._in(CartesianPoint(1.0, 0.0, 1.0), p_tube_closed) + @test CSG._in(CartesianPoint(0.5, 0.0, -1.0), p_tube_closed) + @test !CSG._in(CartesianPoint(-0.9, 0.0, 0.9), p_tube_closed) + @test CSG._in(CartesianPoint(0.9, 0.1, 0.9), p_tube_closed) + @test !CSG._in(CartesianPoint(1.1, 0.0, 0.0), p_tube_closed) + @test !CSG._in(CartesianPoint(0.5, 0.0, 1.1), p_tube_closed) + + @test !CSG._in(CartesianPoint(0.5, 0.0, -1.0), p_tube_open) + @test !CSG._in(CartesianPoint(0.8, 0.0, 1.0), p_tube_open) + @test !CSG._in(CartesianPoint(1.0, 0.0, 1.0), p_tube_open) + @test !CSG._in(CartesianPoint(0.0, 0.0, -1.0), p_tube_open) + @test !CSG._in(CartesianPoint(-0.9, 0.0, 0.9), p_tube_open) + @test CSG._in(CartesianPoint(0.9, 0.1, 0.9), p_tube_open) + @test !CSG._in(CartesianPoint(1.1, 0.0, 0.0), p_tube_open) + @test !CSG._in(CartesianPoint(0.5, 0.0, 1.1), p_tube_open) + + surfs_closed = CSG.surfaces(tube_closed) + @test length(surfs_closed) == 3 + @test surfs_closed[1] isa CSG.EllipticalSurface + @test surfs_closed[2] isa CSG.FullConeMantle + @test surfs_closed[3] isa CSG.FullConeMantle + + surfs_open = CSG.surfaces(tube_open) + @test length(surfs_open) == 3 + @test surfs_open[1] isa CSG.EllipticalSurface + @test surfs_open[2] isa CSG.FullConeMantle + @test surfs_open[3] isa CSG.FullConeMantle + + surfs_p_closed = CSG.surfaces(p_tube_closed) + @test length(surfs_p_closed) == 5 + @test surfs_p_closed[1] isa CSG.EllipticalSurface + @test surfs_p_closed[2] isa CSG.PartialConeMantle + @test surfs_p_closed[3] isa CSG.PartialConeMantle + @test surfs_p_closed[4] isa CSG.Triangle + @test surfs_p_closed[5] isa CSG.Triangle + + surfs_p_open = CSG.surfaces(p_tube_open) + @test length(surfs_p_open) == 5 + @test surfs_p_open[1] isa CSG.EllipticalSurface + @test surfs_p_open[2] isa CSG.PartialConeMantle + @test surfs_p_open[3] isa CSG.PartialConeMantle + @test surfs_p_open[4] isa CSG.Triangle + @test surfs_p_open[5] isa CSG.Triangle end + @testset "Torus" begin for r_tube in (2.0, Dict("from" => 1.0, "to" => 2.0)), φ in ((30,180),(0,360)), @@ -261,7 +511,8 @@ no_translations = (rotation = one(SMatrix{3, 3, T, 9}), translation = zero(Carte @test tuple_torus ==rot_torus @testset "_in" begin - r_torus, r_tube = 5.0, (1.0, 2.0) + for r_tube in (2.0, (1.0, 2.0)) + r_torus = 5.0 origin = CartesianPoint(0.0, 0.0, 0.0) rot = RotX(0.0) @@ -269,7 +520,7 @@ no_translations = (rotation = one(SMatrix{3, 3, T, 9}), translation = zero(Carte t_open = CSG.Torus{Float64, CSG.OpenPrimitive}(r_torus, r_tube, nothing, nothing, origin, rot) # Point safely inside tube (avoid boundaries due to csgtol) - rmin, rmax = r_tube + rmin, rmax = CSG._radial_endpoints(r_tube) safe_r = (rmin + rmax) / 2 pt_inside = CartesianPoint(r_torus + safe_r, 0.0, 0.0) @test CSG._in(pt_inside, t_open) @@ -277,43 +528,125 @@ no_translations = (rotation = one(SMatrix{3, 3, T, 9}), translation = zero(Carte # Point outside radially pt_out_rad = CartesianPoint(r_torus + rmax + 1.0, 0.0, 0.0) @test !CSG._in(pt_out_rad, t_open) + # Hollow OpenPrimitive with φ restriction t_phi = CSG.Torus{Float64, CSG.OpenPrimitive}(r_torus, r_tube, (0.0, π/2), nothing, origin, rot) pt_out_phi = CartesianPoint(-r_torus - safe_r, 0.0, 0.0) # φ = π, outside @test !CSG._in(pt_out_phi, t_phi) + t_phi = CSG.Torus{Float64, CSG.OpenPrimitive}(r_torus, r_tube, (0.0, 3π/2), nothing, origin, rot) + pt_out_phi = CartesianPoint(-r_torus - safe_r, 0.0, 0.0) # φ = π, outside + @test CSG._in(pt_out_phi, t_phi) + # Hollow OpenPrimitive with θ restriction t_theta = CSG.Torus{Float64, CSG.OpenPrimitive}(r_torus, r_tube, nothing, (0.0, π/4), origin, rot) # Point with z above allowed θ pt_out_theta = CartesianPoint(r_torus + safe_r, 0.0, rmax + 1.0) @test !CSG._in(pt_out_theta, t_theta) + + t_theta = CSG.Torus{Float64, CSG.OpenPrimitive}(r_torus, r_tube, nothing, (0.0, 3π/2), origin, rot) + # Point with z above allowed θ + pt_out_theta = CartesianPoint(r_torus + safe_r, 0.0, rmax + 1.0) + @test !CSG._in(pt_out_theta, t_theta) + end end @testset "surfaces" begin - r_torus, r_tube = 5.0, 1.0 - φ, θ = π/2, π/4 + r_torus = 5.0 + φ, θ = π/2, π/4 origin = CartesianPoint(0.0, 0.0, 0.0) rot = RotX(0.0) # FullThetaTorus ClosedPrimitive + r_tube = 1.0 + t_closed = CSG.Torus{Float64, CSG.ClosedPrimitive}(r_torus, r_tube, nothing, nothing, origin, rot) s = CSG.surfaces(t_closed) @test length(s) == 1 + t = CSG.Torus{Float64, CSG.ClosedPrimitive}(r_torus, r_tube, (0.0, φ), (0.0, θ), origin, rot) + tm, es1, es2 = CSG.surfaces(t) + + @test tm isa CSG.TorusMantle{Float64,Float64,Tuple{Float64,Float64},:inwards} + @test es1 isa CSG.EllipticalSurface + @test es2 isa CSG.ConeMantle + # FullThetaTorus OpenPrimitive t_open = CSG.Torus{Float64, CSG.OpenPrimitive}(r_torus, r_tube, nothing, nothing, origin, rot) s_open = CSG.surfaces(t_open) @test length(s_open) == 1 + + t = CSG.Torus{Float64, CSG.OpenPrimitive}(r_torus, r_tube, (0.0, φ), (0.0, θ), origin, rot) + tm, es1, es2 = CSG.surfaces(t) + + @test tm isa CSG.TorusMantle{Float64,Float64,Tuple{Float64,Float64},:outwards} + @test es1 isa CSG.EllipticalSurface + @test es2 isa CSG.ConeMantle + # HollowThetaTorus ClosedPrimitive - t_hollow = CSG.Torus{Float64, CSG.ClosedPrimitive}(r_torus, (0.5,1.0), (0.0, φ), (0.0, θ), origin, rot) + r_tube = (0.5, 1.0) + + t_hollow = CSG.Torus{Float64, CSG.ClosedPrimitive}(r_torus, r_tube, (0.0, φ), (0.0, θ), origin, rot) s_hollow = CSG.surfaces(t_hollow) @test length(s_hollow) == 6 + tm_in, tm_out, es1, es2 = CSG.surfaces(t_hollow) + + @test tm_in isa CSG.TorusMantle{Float64, Float64, Tuple{Float64, Float64}, :outwards} + @test tm_out isa CSG.TorusMantle{Float64, Float64, Tuple{Float64, Float64}, :inwards} + @test es1 isa CSG.EllipticalSurface + @test es2 isa CSG.ConeMantle # HollowThetaTorus OpenPrimitive - t_hollow_open = CSG.Torus{Float64, CSG.OpenPrimitive}(r_torus, (0.5,1.0), (0.0, φ), (0.0, θ), origin, rot) + t_hollow_open = CSG.Torus{Float64, CSG.OpenPrimitive}(r_torus, r_tube, (0.0, φ), (0.0, θ), origin, rot) s_hollow_open = CSG.surfaces(t_hollow_open) @test length(s_hollow_open) == 6 + tm_in, tm_out, es1, es2 = CSG.surfaces(t_hollow_open) + + @test tm_in isa CSG.TorusMantle{Float64, Float64, Tuple{Float64, Float64}, :inwards} + @test tm_out isa CSG.TorusMantle{Float64, Float64, Tuple{Float64, Float64}, :outwards} + @test es1 isa CSG.EllipticalSurface + @test es2 isa CSG.ConeMantle end end + @testset "TorusMantle" begin + r_torus, r_tube = 5.0, 1.0 + origin = CartesianPoint{Float64}(0.0, 0.0, 0.0) + rot = SMatrix{3,3,Float64}([1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0]) + tm_out = CSG.TorusMantle{Float64}(:outwards; r_torus=r_torus, r_tube=r_tube, origin=origin, rotation=rot) + tm_in = CSG.TorusMantle{Float64}(:inwards; r_torus=r_torus, r_tube=r_tube, origin=origin, rotation=rot) + + # Test normal + pt = CartesianPoint{Float64}(6.0, 0.0, 0.0) + n_out = CSG.normal(tm_out, pt) + n_in = CSG.normal(tm_in, pt) + + @test n_out isa CartesianVector{Float64} + @test n_in isa CartesianVector{Float64} + @test dot(n_out, n_in) < -0.999 + + # Test vertices + verts = CSG.vertices(tm_out, 8) + @test verts isa Vector{CartesianPoint{Float64}} + @test length(verts) > 0 + + # Test sample + samples = CSG.sample(tm_out, 0.1) + @test samples isa Vector{CartesianPoint{Float64}} + @test length(samples) > 0 + + # Test TorusThetaSurface + r = 1.0 + φ = π/4 + hZ = 0.5 + + ts_flat = CSG.TorusThetaSurface(r, φ, hZ, origin, rot, Val(:flat)) + @test ts_flat isa CSG.EllipticalSurface + + ts_in = CSG.TorusThetaSurface(r, φ, hZ, origin, rot, Val(:inwards)) + @test ts_in isa CSG.ConeMantle + + ts_out = CSG.TorusThetaSurface(r, φ, hZ, origin, rot, Val(:outwards)) + @test ts_out isa CSG.ConeMantle + end @testset "Polycone" begin polycone1 = CSG.Polycone(CSG.ClosedPrimitive,r=Float32[0,2,4,0,0],z=Float32[0,1,2,3,0],origin=zero(CartesianPoint{Float16}),rotation=one(SMatrix{3, 3, Float16, 9})) polycone2 = CSG.Polycone{Float32}(r = [0,2,4,0], z = [0,1,2,3]) # omit last entry @@ -432,17 +765,48 @@ no_translations = (rotation = one(SMatrix{3, 3, T, 9}), translation = zero(Carte # Test normal normal_vec = CSG.normal(em, pt) - @test isa(normal_vec, CartesianVector{Float32}) + @test normal_vec isa CartesianVector{Float32} @test abs(norm(normal_vec) - 1f0) < 1e-5 # normalized + em_out = CSG.EllipsoidMantle{Float32}(:outwards; r=(1f0, 2f0, 3f0)) + em_in = CSG.EllipsoidMantle{Float32}(:inwards; r=(1f0, 2f0, 3f0)) + pt2 = CartesianPoint{Float32}(0.6f0, 0.4f0, 0.2f0) + + n_out = CSG.normal(em_out, pt2) + n_in = CSG.normal(em_in, pt2) + + @test n_out isa CartesianVector{Float32} + @test n_in isa CartesianVector{Float32} + v_pt2 = CartesianVector(pt2.x, pt2.y, pt2.z) + # Outward normal must point away from center + @test dot(n_out, v_pt2) > 0 + # Inward normal must point toward center + @test dot(n_in, v_pt2) < 0 + + p_local = CSG._transform_into_object_coordinate_system(pt2, em_out) + x = p_local.x + y = p_local.y + z = p_local.z + + expected_local = CartesianVector( + sign(x) * (x / em_out.r[1])^2, + sign(y) * (y / em_out.r[2])^2, + sign(z) * (z / em_out.r[3])^2 + ) + + expected_global = CSG._transform_into_global_coordinate_system(expected_local, em_out) + + @test norm(n_out - expected_global) < 1e-6 + @test norm(n_in + expected_global) < 1e-6 + # Test vertices verts = CSG.vertices(em, 8) - @test isa(verts, Vector{CartesianPoint{Float32}}) + @test verts isa Vector{CartesianPoint{Float32}} @test length(verts) > 0 # Test sample samples = CSG.sample(em, 0.1f0) - @test isa(samples, Vector{CartesianPoint{Float32}}) + @test samples isa Vector{CartesianPoint{Float32}} @test length(samples) > 0 end @testset "EllipticalSurface" begin @@ -455,23 +819,23 @@ no_translations = (rotation = one(SMatrix{3, 3, T, 9}), translation = zero(Carte es_annulus = CSG.EllipticalSurface{Float32}(r=(0.5f0, 1f0)) verts_full = CSG.vertices(es_full, 8) - @test isa(verts_full, Vector{CartesianPoint{Float32}}) + @test verts_full isa Vector{CartesianPoint{Float32}} @test length(verts_full) == 10 verts_annulus = CSG.vertices(es_annulus, 8) - @test isa(verts_annulus, Vector{CartesianPoint{Float32}}) + @test verts_annulus isa Vector{CartesianPoint{Float32}} @test length(verts_annulus) == 2*9 samples_full = CSG.sample(es_full, 0.1f0) - @test isa(samples_full, Vector{CartesianPoint{Float32}}) + @test samples_full isa Vector{CartesianPoint{Float32}} @test length(samples_full) > 0 samples_annulus = CSG.sample(es_annulus, 0.1f0) - @test isa(samples_annulus, Vector{CartesianPoint{Float32}}) + @test samples_annulus isa Vector{CartesianPoint{Float32}} @test length(samples_annulus) > 0 circ, edges = CSG.lines(CSG.EllipticalSurface(r=1f0, φ=Float32(π/2))) - @test isa(circ, CSG.Ellipse{Float32, Float32, Float32}) + @test circ isa CSG.Ellipse{Float32, Float32, Float32} @test all(isa.(edges, CSG.Edge{Float32})) p_inside = CartesianPoint{Float32}(0.5f0, 0f0, 0f0) @@ -483,12 +847,12 @@ no_translations = (rotation = one(SMatrix{3, 3, T, 9}), translation = zero(Carte pt = CartesianPoint{Float32}(1f0, 0f0, 0f0) dist_full = CSG.distance_to_surface(pt, es_full) - @test isa(dist_full, Float32) + @test dist_full isa Float32 @test dist_full ≈ 0f0 atol=1e-5 pt_off = CartesianPoint{Float32}(1.5f0, 0f0, 0f0) dist_annulus = CSG.distance_to_surface(pt_off, es_annulus) - @test isa(dist_annulus, Float32) + @test dist_annulus isa Float32 @test dist_annulus > 0f0 end @testset "Plane" begin @@ -502,23 +866,24 @@ no_translations = (rotation = one(SMatrix{3, 3, T, 9}), translation = zero(Carte CSG.ConeMantle(φ=(1.,2.)) cm_out = CSG.ConeMantle{Float32}( :outwards; r=(1f0, 2f0), φ=nothing, hZ=1f0 ) - cm_in = CSG.flip(cm_out) - - pt_top = CartesianPoint{Float32}(0, 1.5f0, 1.0f0) # near top radius - pt_bottom = CartesianPoint{Float32}(0, 0.5f0, -1.0f0) # near bottom radius - pt_mid = CartesianPoint{Float32}(1.0f0, 0, 0.0f0) # mid cone height + cm_in = CSG.ConeMantle{Float32}( :inwards; r=(1f0, 2f0), φ=nothing, hZ=1f0 ) + pt = CartesianPoint{Float32}(0.7f0, 0.4f0, 0.0f0) - normal_out = CSG.normal(cm_out, pt_mid) - normal_in = CSG.normal(cm_in, pt_mid) + normal_out = CSG.normal(cm_out, pt) + normal_in = CSG.normal(cm_in, pt) - @test isa(normal_out, CartesianVector{Float32}) - @test isa(normal_in, CartesianVector{Float32}) + @test normal_out isa CartesianVector{Float32} + @test normal_in isa CartesianVector{Float32} # inwards vs outwards should point opposite @test dot(normal_out, normal_in) ≈ -1f0 atol=1e-5 + + v_pt = CartesianVector(pt.x, pt.y, pt.z) + @test dot(normal_out, v_pt) > 0 + @test dot(normal_in, v_pt) < 0 n_arc = 8 verts = CSG.vertices(cm_out, n_arc) - @test isa(verts, Vector{CartesianPoint{Float32}}) + @test verts isa Vector{CartesianPoint{Float32}} @test length(verts) == 2*(n_arc + 1) # bottom + top circle points # All vertices should lie approximately on their respective radius circles @@ -533,9 +898,9 @@ no_translations = (rotation = one(SMatrix{3, 3, T, 9}), translation = zero(Carte end pts = CSG.sample(cm_out, Float32(1)) - @test isa(pts, Vector{CartesianPoint{Float32}}) + @test pts isa Vector{CartesianPoint{Float32}} @test length(pts) >= 2 # must have at least 2 points - + # Check that all points are within height bounds for pt in pts @test -cm_out.hZ - 1e-5 ≤ pt.z ≤ cm_out.hZ + 1e-5