From b69275c53d9ca8a4074995ea92d85fc1454a1f94 Mon Sep 17 00:00:00 2001 From: Owen Harvey Date: Fri, 29 Dec 2023 19:28:51 +1000 Subject: [PATCH] Adding Box Frame as a primitive. Adding a box frame primitive, and associated test. --- Graphics/Implicit/Canon.hs | 5 ++- Graphics/Implicit/Definitions.hs | 3 ++ Graphics/Implicit/Export/RayTrace.hs | 2 +- Graphics/Implicit/Export/SymbolicFormats.hs | 6 +++- Graphics/Implicit/ObjectUtil/GetBox3.hs | 3 +- Graphics/Implicit/ObjectUtil/GetImplicit3.hs | 32 ++++++++++++++++++-- Graphics/Implicit/Primitives.hs | 6 +++- tests/GoldenSpec/Spec.hs | 8 ++++- 8 files changed, 57 insertions(+), 8 deletions(-) diff --git a/Graphics/Implicit/Canon.hs b/Graphics/Implicit/Canon.hs index 71feed18..343fb2de 100644 --- a/Graphics/Implicit/Canon.hs +++ b/Graphics/Implicit/Canon.hs @@ -90,7 +90,7 @@ import Graphics.Implicit.Definitions , RotateExtrude , Shared3 , Sphere - , Transform3 + , Transform3, BoxFrame ) , hasZeroComponent ) @@ -171,6 +171,7 @@ fmapObj3 fmapObj3 f _ _ (Cube v) = f $ Cube v fmapObj3 f _ _ (Sphere r) = f $ Sphere r fmapObj3 f _ _ (Cylinder r1 r2 h) = f $ Cylinder r1 r2 h +fmapObj3 f _ _ (BoxFrame b e) = f $ BoxFrame b e fmapObj3 f g s (Rotate3 q o) = f $ Rotate3 q (fmapObj3 f g s o) fmapObj3 f g s (Transform3 m o) = f $ Transform3 m (fmapObj3 f g s o) fmapObj3 f g s (Extrude o2 h) = f $ Extrude (fmapObj2 g f s o2) h @@ -226,6 +227,7 @@ instance EqObj SymbolicObj3 where Cube a =^= Cube b = a == b Sphere a =^= Sphere b = a == b Cylinder r1a r2a ha =^= Cylinder r1b r2b hb = r1a == r1b && r2a == r2b && ha == hb + BoxFrame b1 e1 =^= BoxFrame b2 e2 = b1 == b2 && e1 == e2 Rotate3 x a =^= Rotate3 y b = x == y && a =^= b Transform3 x a =^= Transform3 y b = x == y && a =^= b Extrude a x =^= Extrude b y = x == y && a =^= b @@ -299,6 +301,7 @@ canon3 :: SymbolicObj3 -> SymbolicObj3 canon3 (Cube v) | hasZeroComponent v = emptySpace canon3 (Sphere 0) = emptySpace canon3 (Cylinder 0 _ _) = emptySpace +canon3 (BoxFrame _ 0) = emptySpace canon3 (Extrude _o2 0) = emptySpace canon3 (Rotate3 0 o) = o canon3 (RotateExtrude 0 _t _r _o) = emptySpace diff --git a/Graphics/Implicit/Definitions.hs b/Graphics/Implicit/Definitions.hs index bdfdda95..08447cc3 100644 --- a/Graphics/Implicit/Definitions.hs +++ b/Graphics/Implicit/Definitions.hs @@ -58,6 +58,7 @@ module Graphics.Implicit.Definitions ( Cylinder, Rotate3, Transform3, + BoxFrame, Extrude, ExtrudeM, ExtrudeOnEdgeOf, @@ -324,6 +325,7 @@ data SymbolicObj3 = Cube ℝ3 -- rounding, size. | Sphere ℝ -- radius | Cylinder ℝ ℝ ℝ -- + | BoxFrame ℝ3 ℝ -- b e from https://iquilezles.org/articles/distfunctions/ -- Simple transforms | Rotate3 (Quaternion ℝ) SymbolicObj3 | Transform3 (M44 ℝ) SymbolicObj3 @@ -351,6 +353,7 @@ instance Show SymbolicObj3 where -- centered. Cube sz -> showCon "cube" @| False @| sz Sphere d -> showCon "sphere" @| d + BoxFrame b e -> showCon "boxFrame" @| b @| e -- NB: The arguments to 'Cylinder' are backwards compared to 'cylinder' and -- 'cylinder2'. Cylinder h r1 r2 | r1 == r2 -> diff --git a/Graphics/Implicit/Export/RayTrace.hs b/Graphics/Implicit/Export/RayTrace.hs index 95abfdab..46abad3d 100644 --- a/Graphics/Implicit/Export/RayTrace.hs +++ b/Graphics/Implicit/Export/RayTrace.hs @@ -3,7 +3,7 @@ -- Copyright 2016, Julia Longtin (julial@turinglace.com) -- Released under the GNU AGPLV3+, see LICENSE -module Graphics.Implicit.Export.RayTrace( Color(Color), average, Camera(Camera), Light(Light), Scene(Scene), traceRay, cameraRay) where +module Graphics.Implicit.Export.RayTrace( Color(Color), average, Camera(Camera), Light(Light), Scene(Scene), traceRay, cameraRay, vectorDistance) where import Prelude(Show, RealFrac, Maybe(Just, Nothing), Bool(False, True), (-), (.), ($), (*), (/), min, fromInteger, max, round, fromIntegral, unzip, fmap, length, sum, maximum, minimum, (>), (+), (<), (==), pred, flip, not, abs, floor, toRational, otherwise, pure) diff --git a/Graphics/Implicit/Export/SymbolicFormats.hs b/Graphics/Implicit/Export/SymbolicFormats.hs index 811988fd..bf8fd0a1 100644 --- a/Graphics/Implicit/Export/SymbolicFormats.hs +++ b/Graphics/Implicit/Export/SymbolicFormats.hs @@ -12,7 +12,7 @@ module Graphics.Implicit.Export.SymbolicFormats (scad2, scad3) where import Prelude((.), fmap, Either(Left, Right), ($), (*), ($!), (-), (/), pi, error, (+), (==), take, floor, (&&), const, pure, (<>), sequenceA, (<$>)) -import Graphics.Implicit.Definitions(ℝ, SymbolicObj2(Shared2, Square, Circle, Polygon, Rotate2, Transform2), SymbolicObj3(Shared3, Cube, Sphere, Cylinder, Rotate3, Transform3, Extrude, ExtrudeM, RotateExtrude, ExtrudeOnEdgeOf), isScaleID, SharedObj(Empty, Full, Complement, UnionR, IntersectR, DifferenceR, Translate, Scale, Mirror, Outset, Shell, EmbedBoxedObj, WithRounding), quaternionToEuler) +import Graphics.Implicit.Definitions(ℝ, SymbolicObj2(Shared2, Square, Circle, Polygon, Rotate2, Transform2), SymbolicObj3(Shared3, Cube, Sphere, Cylinder, BoxFrame, Rotate3, Transform3, Extrude, ExtrudeM, RotateExtrude, ExtrudeOnEdgeOf), isScaleID, SharedObj(Empty, Full, Complement, UnionR, IntersectR, DifferenceR, Translate, Scale, Mirror, Outset, Shell, EmbedBoxedObj, WithRounding), quaternionToEuler) import Graphics.Implicit.Export.TextBuilderUtils(Text, Builder, toLazyText, fromLazyText, bf) import Control.Monad.Reader (Reader, runReader, ask) @@ -127,6 +127,10 @@ buildS3 (Cube (V3 w d h)) = call "cube" [bf w, bf d, bf h] [] buildS3 (Sphere r) = callNaked "sphere" ["r = " <> bf r] [] +buildS3 (BoxFrame (V3 w d h) e) = callNaked "boxFrame" + ["w = " <> bf w, "d = " <> bf d, "h = " <> bf h, "e = " <> bf e] + [] + buildS3 (Cylinder h r1 r2) = callNaked "cylinder" [ "r1 = " <> bf r1 ,"r2 = " <> bf r2 diff --git a/Graphics/Implicit/ObjectUtil/GetBox3.hs b/Graphics/Implicit/ObjectUtil/GetBox3.hs index 405abac5..6960f7ee 100644 --- a/Graphics/Implicit/ObjectUtil/GetBox3.hs +++ b/Graphics/Implicit/ObjectUtil/GetBox3.hs @@ -12,7 +12,7 @@ import Graphics.Implicit.Definitions ( Fastℕ, fromFastℕ, ExtrudeMScale(C2, C1), - SymbolicObj3(Shared3, Cube, Sphere, Cylinder, Rotate3, Transform3, Extrude, ExtrudeOnEdgeOf, ExtrudeM, RotateExtrude), + SymbolicObj3(Shared3, Cube, Sphere, Cylinder, Rotate3, Transform3, Extrude, ExtrudeOnEdgeOf, ExtrudeM, RotateExtrude, BoxFrame), Box3, ℝ, fromFastℕtoℝ, @@ -34,6 +34,7 @@ getBox3 (Shared3 obj) = getBoxShared obj getBox3 (Cube size) = (pure 0, size) getBox3 (Sphere r) = (pure (-r), pure r) getBox3 (Cylinder h r1 r2) = (V3 (-r) (-r) 0, V3 r r h ) where r = max r1 r2 +getBox3 (BoxFrame b _) = (-b, b) -- (Rounded) CSG -- Simple transforms getBox3 (Rotate3 q symbObj) = diff --git a/Graphics/Implicit/ObjectUtil/GetImplicit3.hs b/Graphics/Implicit/ObjectUtil/GetImplicit3.hs index 04a2b338..cc69952d 100644 --- a/Graphics/Implicit/ObjectUtil/GetImplicit3.hs +++ b/Graphics/Implicit/ObjectUtil/GetImplicit3.hs @@ -6,10 +6,20 @@ module Graphics.Implicit.ObjectUtil.GetImplicit3 (getImplicit3) where -import Prelude (id, (||), (/=), either, round, fromInteger, Either(Left, Right), abs, (-), (/), (*), sqrt, (+), atan2, max, cos, minimum, ($), sin, pi, (.), Bool(True, False), ceiling, floor, pure, (==), otherwise) +import Prelude (id, (||), (/=), either, round, fromInteger, Either(Left, Right), abs, (-), (/), (*), sqrt, (+), atan2, max, cos, minimum, ($), sin, pi, (.), Bool(True, False), ceiling, floor, pure, (==), otherwise, min) import Graphics.Implicit.Definitions - ( objectRounding, ObjectContext, ℕ, SymbolicObj3(Cube, Sphere, Cylinder, Rotate3, Transform3, Extrude, ExtrudeM, ExtrudeOnEdgeOf, RotateExtrude, Shared3), Obj3, ℝ2, ℝ, fromℕtoℝ, toScaleFn ) + ( objectRounding, + ObjectContext, + ℕ, + SymbolicObj3(Cube, Sphere, Cylinder, Rotate3, Transform3, Extrude, + ExtrudeM, ExtrudeOnEdgeOf, RotateExtrude, Shared3, BoxFrame), + Obj3, + ℝ2, + ℝ, + fromℕtoℝ, + toScaleFn, + ℝ3 ) import Graphics.Implicit.MathUtil ( rmax, rmaximum ) @@ -37,6 +47,24 @@ getImplicit3 _ (Cylinder h r1 r2) = \(V3 x y z) -> θ = atan2 (r2-r1) h in max (d * cos θ) (abs (z-h/2) - (h/2)) +getImplicit3 _ (BoxFrame b e) = \p' -> + let p@(V3 px py pz) = abs p' - b + V3 qx qy qz = abs (p + pure e) - pure e + -- Splitting out bits from https://iquilezles.org/articles/distfunctions/ + -- to make it somewhat readable. + length :: ℝ3 -> ℝ + length v = Linear.distance (abs v) $ pure 0 + -- Component wise maximum. This is what the opengl language is doing, so we need + -- it for the function as defined by the blog above. + -- See "Maximum" http://15462.courses.cs.cmu.edu/fall2019/article/20 + compMax :: ℝ3 -> ℝ3 -> ℝ3 + compMax (V3 a1 b1 c1) (V3 a2 b2 c2) = V3 (max a1 a2) (max b1 b2) (max c1 c2) + -- These names don't mean anything, and are just for splitting up the code. + x', y', z' :: ℝ + x' = length (compMax (V3 px qy qz) (pure 0)) + min (max px (max qy qz)) 0 + y' = length (compMax (V3 qx py qz) (pure 0)) + min (max qx (max py qz)) 0 + z' = length (compMax (V3 qx qy pz) (pure 0)) + min (max qx (max qy pz)) 0 + in min (min x' y') z' -- Simple transforms getImplicit3 ctx (Rotate3 q symbObj) = getImplicit3 ctx symbObj . Linear.rotate (Linear.conjugate q) diff --git a/Graphics/Implicit/Primitives.hs b/Graphics/Implicit/Primitives.hs index 3001fea3..84d173dc 100644 --- a/Graphics/Implicit/Primitives.hs +++ b/Graphics/Implicit/Primitives.hs @@ -51,7 +51,7 @@ module Graphics.Implicit.Primitives ( withRounding, _Shared, pattern Shared, - Object(Space, canonicalize)) where + Object(Space, canonicalize), boxFrame) where import Prelude(Applicative, Eq, Foldable, Num, abs, (<), otherwise, Num, (+), (-), (*), (/), (.), negate, Bool(True, False), Maybe(Just, Nothing), Either, fmap, ($), (**), sqrt, (<=), (&&), max, Ord) @@ -84,6 +84,7 @@ import Graphics.Implicit.Definitions (ObjectContext, ℝ, ℝ2, ℝ3, Box2, Cube, Sphere, Cylinder, + BoxFrame, Rotate3, Transform3, Extrude, @@ -140,6 +141,9 @@ cylinder :: -> SymbolicObj3 -- ^ Resulting cylinder cylinder r = cylinder2 r r +boxFrame :: ℝ3 -> ℝ -> SymbolicObj3 +boxFrame = BoxFrame + cone :: ℝ -- ^ Radius of the cylinder -> ℝ -- ^ Height of the cylinder diff --git a/tests/GoldenSpec/Spec.hs b/tests/GoldenSpec/Spec.hs index 03cae0c0..da13e8f0 100644 --- a/tests/GoldenSpec/Spec.hs +++ b/tests/GoldenSpec/Spec.hs @@ -10,7 +10,7 @@ import Graphics.Implicit import Graphics.Implicit.Export.OutputFormat (OutputFormat (PNG)) import Prelude import Test.Hspec ( describe, Spec ) -import Graphics.Implicit.Primitives (torus, ellipsoid, cone) +import Graphics.Implicit.Primitives (torus, ellipsoid, cone, boxFrame) default (Int) @@ -172,6 +172,12 @@ spec = describe "golden tests" $ do , ellipsoid 10 15 20 , translate (V3 0 0 25) $ cone 20 20 ] + + golden "boxFrame" 2 $ + union + [ boxFrame (V3 20 20 20) 2 + , translate (V3 0 0 10) $ boxFrame (V3 10 10 10) 2 + ] golden "closing-paths-1" 0.5 $ extrudeM