implicit-0.0.3: Graphics/Implicit/Export/SymbolicObj3.hs
-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Released under the GNU GPL, see LICENSE
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances #-}
-- The purpose of this function is to symbolicaly compute triangle meshes where possible.
-- Otherwise we coerce it into an implicit function and apply our modified marching cubes algorithm.
-- We just want to export the instance...
module Graphics.Implicit.Export.SymbolicObj3 (symbolicGetMesh) where
import Graphics.Implicit.Definitions
import Graphics.Implicit.Export.Definitions
import Graphics.Implicit.Export.Render (getMesh)
import Graphics.Implicit.Primitives
import Graphics.Implicit.ObjectUtil
import Graphics.Implicit.MathUtil
import Graphics.Implicit.Export.SymbolicObj2
import qualified Data.Maybe as Maybe
import Graphics.Implicit.Export.Symbolic.Rebound2
import Graphics.Implicit.Export.Symbolic.Rebound3
--import Graphics.Implicit.Export.Util (divideMeshTo, dividePolylineTo)
import Graphics.Implicit.Export.Util (normTriangle)
import Data.VectorSpace
instance DiscreteAproxable SymbolicObj3 TriangleMesh where
discreteAprox res obj = symbolicGetMesh res obj
instance DiscreteAproxable SymbolicObj3 NormedTriangleMesh where
discreteAprox res obj = map (normTriangle res (getImplicit3 obj)) $ symbolicGetMesh res obj
symbolicGetMesh :: ℝ -> SymbolicObj3 -> [(ℝ3, ℝ3, ℝ3)]
-- A translated objects mesh is its mesh translated.
symbolicGetMesh res (Translate3 v obj) =
map (\(a,b,c) -> (a ^+^ v, b ^+^ v, c ^+^ v) ) (symbolicGetMesh res obj)
-- A scaled objects mesh is its mesh scaled
symbolicGetMesh res (Scale3 s obj) =
let
mesh :: [(ℝ3, ℝ3, ℝ3)]
mesh = symbolicGetMesh res obj
scaleTriangle :: (ℝ3, ℝ3, ℝ3) -> (ℝ3, ℝ3, ℝ3)
scaleTriangle (a,b,c) = (s ⋯* a, s ⋯* b, s ⋯* c)
in map scaleTriangle mesh
-- A couple triangles make a cube...
symbolicGetMesh _ (Rect3R 0 (x1,y1,z1) (x2,y2,z2)) =
let
square a b c d = [(a,b,c),(d,a,c)]
rsquare a b c d = [(c,b,a),(c,a,d)]
in
rsquare (x1,y1,z1) (x2,y1,z1) (x2,y2,z1) (x1,y2,z1)
++ square (x1,y1,z2) (x2,y1,z2) (x2,y2,z2) (x1,y2,z2)
++ square (x1,y1,z1) (x2,y1,z1) (x2,y1,z2) (x1,y1,z2)
++ rsquare (x1,y2,z1) (x2,y2,z1) (x2,y2,z2) (x1,y2,z2)
++ square (x1,y1,z1) (x1,y1,z2) (x1,y2,z2) (x1,y2,z1)
++ rsquare (x2,y1,z1) (x2,y1,z2) (x2,y2,z2) (x2,y2,z1)
-- Use spherical coordinates to create an easy tesselation of a sphere
symbolicGetMesh res (Sphere r) = half1 ++ half2
where
-- Convenience functions for mesh generation
square a b c d = [(a,b,c),(d,a,c)]
rsquare a b c d = [(c,b,a),(c,a,d)]
-- Number of steps of φ and θ respectivly
m = max 3 (fromIntegral $ ceiling $ 1.5*r/res)
n = 2*m
-- Spherical coordinates
spherical θ φ = (r*cos(θ), r*sin(θ)*cos(φ), r*sin(θ)*sin(φ))
-- Function placing steps on sphere
f n' m' = spherical (2*pi*n'/n) (pi*m'/m)
-- Mesh in two pieces..
half1 = concat [ square (f m1 m2) (f (m1+1) m2) (f (m1+1) (m2+1)) (f m1 (m2+1))
| m1 <- [0.. m-1], m2 <- [0.. m-1] ]
half2 = concat [ rsquare (f m1 m2) (f (m1+1) m2) (f (m1+1) (m2+1)) (f m1 (m2+1))
| m1 <- [m.. n-1], m2 <- [0.. m-1] ]
{-symbolicGetMesh res (UnionR3 r [ExtrudeR ra obja ha, ExtrudeR rb objb hb])
| ha == hb && ra == rb = symbolicGetMesh res $ ExtrudeR ra (UnionR2 r [obja, objb]) ha
symbolicGetMesh res (UnionR3 r [ExtrudeR ra obja ha, ExtrudeR rb objb hb, ExtrudeR rc objc hc])
| ha == hb && ha == hc && ra == rb && ra == rc =
symbolicGetMesh res $ ExtrudeR ra (UnionR2 r [obja, objb, objc]) ha-}
-- We can compute a mesh of a rounded, extruded object from it contour,
-- contour filling trinagles, and magic.
-- General approach:
-- - generate sides by basically cross producting the contour.
-- - generate the the top by taking the contour fill and
-- calculating an appropriate z height.
symbolicGetMesh res (ExtrudeR r obj2 h) =
let
-- Get a Obj2 (magnitude descriptor object)
obj2mag :: ℝ2 -> ℝ -- Obj2
obj2mag = getImplicit2 obj2
-- The amount that a point (x,y) on the top should be lifted
-- from h-r. Because of rounding, the edges should be h-r,
-- but it should increase inwards.
dh x y = sqrt (r^2 - ( max 0 $ min r $ r+obj2mag (x,y))^2)
-- Turn a polyline into a list of its segments
segify (a:b:xs) = (a,b):(segify $ b:xs)
segify _ = []
-- Flip a triangle. It's the same triangle with opposite handedness.
flipTri (a,b,c) = (a,c,b)
-- Turn a segment a--b into a list of triangles forming (a--b)×(r,h-r)
-- The dh stuff is to compensate for rounding errors, etc, and ensure that
-- the sides meet the top and bottom
segToSide (x1,y1) (x2,y2) =
[((x1,y1,r-dh x1 y1), (x2,y2,r-dh x2 y2), (x2,y2,h-r+dh x2 y2)),
((x1,y1,r-dh x1 y1), (x2,y2,h-r+dh x2 y2), (x1,y1,h-r+dh x1 y1)) ]
-- Get a contour polyline for obj2, turn it into a list of segments
segs = concat $ map segify $ symbolicGetOrientedContour res obj2
-- Create sides for the main body of our object = segs × (r,h-r)
side_tris = concat $ map (\(a,b) -> segToSide a b) segs
-- Triangles that fill the contour. Make sure the mesh is at least (res/5) fine.
-- --res/5 because xyres won't always match up with normal res and we need to compensate.
fill_tris = {-divideMeshTo (res/5) $-} symbolicGetContourMesh res obj2
-- The bottom. Use dh to determine the z coordinates
bottom_tris = map flipTri $ [((a1,a2,r-dh a1 a2), (b1,b2,r - dh b1 b2), (c1,c2,r - dh c1 c2))
| ((a1,a2),(b1,b2),(c1,c2)) <- fill_tris]
-- Same idea at the top.
top_tris = [((a1,a2,h-r+dh a1 a2), (b1,b2,h-r+dh b1 b2), (c1,c2,h-r+dh c1 c2))
| ((a1,a2),(b1,b2),(c1,c2)) <- fill_tris]
in
-- Merge them all together! :)
side_tris ++ bottom_tris ++ top_tris
symbolicGetMesh res (ExtrudeRM r@0 twist scale translate obj2 h@(Left _)) =
let
-- Get a Obj2 (magnitude descriptor object)
obj2mag :: Obj2 -- = ℝ2 -> ℝ
obj2mag = getImplicit2 obj2
-- cleanup twist, scale, etc
twist' = Maybe.fromMaybe (const 0) twist
scale' = Maybe.fromMaybe (const 1) scale
translate' = Maybe.fromMaybe (const (0,0)) translate
h' = case h of
Left n -> const n
Right f -> f
-- The amount that a point (x,y) on the top should be lifted
-- from h-r. Because of rounding, the edges should be h-r,
-- but it should increase inwards.
dh x y = sqrt (r^2 - ( max 0 $ min r $ r+obj2mag (x,y))^2)
-- Turn a polyline into a list of its segments
segify (a:b:xs) = (a,b):(segify $ b:xs)
segify _ = []
-- Flip a triangle. It's the same triangle with opposite handedness.
flipTri (a,b,c) = (a,c,b)
-- The number of steps we're going to do the sides in:
n = max 4 $ fromIntegral $ ceiling $ h' (0,0)/res
-- Turn a segment a--b into a list of triangles forming
-- (a--b)×(r+(h-2r)*m/n,r+(h-2r)*(m+1)/n)
-- The dh stuff is to compensate for rounding errors, etc, and ensure that
-- the sides meet the top and bottom
-- m is the number of n steps we are up from the base of the main section
segToSide m (x1,y1) (x2,y2) =
let
-- Change across the main body of the object,
-- at (x1,y1) and (x2,y2) respectivly
mainH1 = h' (x1, y1) - 2*r + 2*dh x1 y1
mainH2 = h' (x2, y2) - 2*r + 2*dh x2 y2
-- level a (lower) and level b (upper)
la1 = r-dh x1 y1 + mainH1*m/n
lb1 = r-dh x1 y1 + mainH1*(m+1)/n
la2 = r-dh x2 y2 + mainH2*m/n
lb2 = r-dh x2 y2 + mainH2*(m+1)/n
in
-- Resulting triangles:
[((x1,y1,la1), (x2,y2,la2), (x2,y2,lb2)),
((x1,y1,la1), (x2,y2,lb2), (x1,y1,lb1)) ]
-- Get a contour polyline for obj2, turn it into a list of segments
segs = concat $ map segify $ symbolicGetOrientedContour res obj2
-- Create sides for the main body of our object = segs × (r,h-r)
-- Many layers...
side_tris = map flipTri $ concat $
[concat $ map (\(a,b) -> segToSide m a b) segs | m <- [0.. n-1] ]
-- Triangles that fill the contour. Make sure the mesh is at least (res/5) fine.
-- --res/5 because xyres won't always match up with normal res and we need to compensate.
fill_tris = {-divideMeshTo (res/5) $-} symbolicGetContourMesh res obj2
-- The bottom. Use dh to determine the z coordinates
bottom_tris = [((a1,a2,r-dh a1 a2), (b1,b2,r - dh b1 b2), (c1,c2,r - dh c1 c2))
| ((a1,a2),(b1,b2),(c1,c2)) <- fill_tris]
-- Same idea at the top.
top_tris = map flipTri $ [((a1,a2,h' (a1,a2) -r+dh a1 a2), (b1,b2,h' (b1,b2) -r+dh b1 b2), (c1,c2,h' (c1,c2)-r+dh c1 c2))
| ((a1,a2),(b1,b2),(c1,c2)) <- fill_tris]
-- Mesh modifiers in individual components
k = 2*pi/360
fx :: ℝ3 -> ℝ
fx (x,y,z) = let (tx,ty) = translate' z in
scale' z *((x+tx)*cos(k*twist' z) + (y+ty)*sin(k*twist' z))
fy :: ℝ3 -> ℝ
fy (x,y,z) =let (tx,ty) = translate' z in
scale' z *((x+tx)*sin(k*twist' z) - (y+ty)*cos(k*twist' z))
-- function to transform a triangle
transformTriangle :: (ℝ3,ℝ3,ℝ3) -> (ℝ3,ℝ3,ℝ3)
transformTriangle (a@(_,_,z1), b@(_,_,z2), c@(_,_,z3)) =
((fx a, fy a, z1), (fx b, fy b, z2), (fx c, fy c, z3))
in
map transformTriangle (side_tris ++ bottom_tris ++ top_tris)
symbolicGetMesh res inputObj@(UnionR3 r objs) =
let
boxes = map getBox3 objs
boxedObjs = zip boxes objs
sepFree ((box,obj):others) =
if length (filter (box3sWithin r box) boxes) > 1
then (\(a,b) -> (obj:a,b)) $ sepFree others
else (\(a,b) -> (a,obj:b)) $ sepFree others
sepFree [] = ([],[])
(dependants, independents) = sepFree boxedObjs
in if null independents
then case rebound3 (getImplicit3 inputObj, getBox3 inputObj) of
(obj, (a,b)) -> getMesh a b res obj
else if null dependants
then concat $ map (symbolicGetMesh res) independents
else concat $
map (symbolicGetMesh res) independents
++ [symbolicGetMesh res (UnionR3 r dependants)]
-- If all that fails, coerce and apply marching cubes :(
-- (rebound is for being safe about the bounding box --
-- it slightly streches it to make sure nothing will
-- have problems because it is right at the edge )
symbolicGetMesh res obj =
case rebound3 (getImplicit3 obj, getBox3 obj) of
(obj, (a,b)) -> getMesh a b res obj