diff --git a/go.mod b/go.mod index c402d7b..f5e360f 100644 --- a/go.mod +++ b/go.mod @@ -7,20 +7,25 @@ require ( github.com/deadsy/sdfx v0.0.0-20220428051248-ab3af168a1af github.com/fogleman/fauxgl v0.0.0-20200818143847-27cddc103802 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 - gonum.org/v1/gonum v0.11.0 + gonum.org/v1/gonum v0.11.1-0.20220625074215-67f3e1dbfccc gonum.org/v1/plot v0.11.0 ) require ( + git.sr.ht/~sbinet/gg v0.3.1 // indirect github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect github.com/dhconnelly/rtreego v1.1.0 // indirect github.com/fogleman/simplify v0.0.0-20170216171241-d32f302d5046 // indirect + github.com/go-fonts/liberation v0.2.0 // indirect + github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81 // indirect + github.com/go-pdf/fpdf v0.6.0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/hschendel/stl v1.0.4 // indirect github.com/llgcode/draw2d v0.0.0-20200930101115-bfaf5d914d1e // indirect github.com/yofu/dxf v0.0.0-20190710012328-5a6d1e83f16c // indirect - golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3 // indirect - golang.org/x/image v0.0.0-20220302094943-723b81ca9867 // indirect + golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d // indirect + golang.org/x/image v0.0.0-20220617043117-41969df76e82 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/tools v0.1.11 // indirect rsc.io/pdf v0.1.1 // indirect ) - diff --git a/go.sum b/go.sum index 7859ca4..bb15cb7 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +git.sr.ht/~sbinet/gg v0.3.1 h1:LNhjNn8DerC8f9DHLz6lS0YYul/b602DUxDgGkd/Aik= git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -28,14 +29,17 @@ github.com/fogleman/simplify v0.0.0-20170216171241-d32f302d5046/go.mod h1:KDwyDq github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/liberation v0.2.0 h1:jAkAWJP4S+OsrPLZM4/eC9iW7CtHy+HBXrEwZXWo5VM= github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= github.com/go-gl/gl v0.0.0-20180407155706-68e253793080/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= github.com/go-gl/glfw v0.0.0-20180426074136-46a8d530c326/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= +github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81 h1:6zl3BbBhdnMkpSj2YY30qV3gDcVBGtFgVsV3+/i+mKQ= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-pdf/fpdf v0.6.0 h1:MlgtGIfsdMEEQJr2le6b/HNr1ZlQwxyWr77r2aj2U/8= github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= @@ -78,6 +82,8 @@ golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3 h1:n9HxLrNxWWtEb1cA950nuEEj3QnKbtsCJ6KjcgisNUs= golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= +golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d h1:vtUKgx8dahOomfFzLREU8nSv25YHnTgLBn4rDnWZdU0= +golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -92,6 +98,8 @@ golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeap golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20220302094943-723b81ca9867 h1:TcHcE0vrmgzNH1v3ppjcMGbhG5+9fMuvOmUYwNEF4q4= golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20220617043117-41969df76e82 h1:KpZB5pUSBvrHltNEdK/tw0xlPeD13M6M6aGP32gKqiw= +golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -117,6 +125,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -125,6 +134,8 @@ golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY= +golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -134,6 +145,8 @@ gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E= gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +gonum.org/v1/gonum v0.11.1-0.20220625074215-67f3e1dbfccc h1:ADX7LJ8SknE89dXBcPhZGGV60cMUzqB9XcaNKuHZxy4= +gonum.org/v1/gonum v0.11.1-0.20220625074215-67f3e1dbfccc/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= diff --git a/helpers/sdfexp/import.go b/helpers/sdfexp/import.go index 0ec7630..10fcbc7 100644 --- a/helpers/sdfexp/import.go +++ b/helpers/sdfexp/import.go @@ -6,7 +6,6 @@ import ( "math" "github.com/soypat/sdf/internal/d3" - "github.com/soypat/sdf/render" "gonum.org/v1/gonum/spatial/kdtree" "gonum.org/v1/gonum/spatial/r3" ) @@ -17,7 +16,7 @@ import ( // using vertexTol. // vertexTol should be of the order of 1/1000th of the size of the smallest // triangle in the model. If set to 0 then it is inferred automatically. -func ImportModel(model []render.Triangle3, vertexTolOrZero float64) (ImportedSDF3, error) { +func ImportModel(model []r3.Triangle, vertexTolOrZero float64) (ImportedSDF3, error) { m, err := newMesh(model, vertexTolOrZero) if err != nil { return ImportedSDF3{}, err @@ -61,7 +60,7 @@ type pseudoVertex struct { N r3.Vec // Vertex Normal } -func newMesh(triangles []render.Triangle3, tol float64) (*mesh, error) { +func newMesh(triangles []r3.Triangle, tol float64) (*mesh, error) { bb := d3.Box{d3.Elem(math.MaxFloat64), d3.Elem(-math.MaxFloat64)} minDist2 := math.MaxFloat64 maxDist2 := -math.MaxFloat64 @@ -102,7 +101,7 @@ func newMesh(triangles []render.Triangle3, tol float64) (*mesh, error) { cache := make(map[[3]int64]int) ri := 1 / tol for i, tri := range triangles { - norm := tri.Normal() + norm := r3.Unit(tri.Normal()) Tform := canalisTransform(tri) InvT := Tform.Inv() sdfT := meshTriangle{ diff --git a/helpers/sdfexp/spatial3.go b/helpers/sdfexp/spatial3.go index 4d70895..7882f5f 100644 --- a/helpers/sdfexp/spatial3.go +++ b/helpers/sdfexp/spatial3.go @@ -4,7 +4,6 @@ import ( "math" "github.com/soypat/sdf/internal/d3" - "github.com/soypat/sdf/render" "gonum.org/v1/gonum/spatial/kdtree" "gonum.org/v1/gonum/spatial/r2" "gonum.org/v1/gonum/spatial/r3" @@ -83,8 +82,8 @@ func (t *meshTriangle) CopySign(p r3.Vec, dist float64) (signed float64) { return math.Copysign(dist, signed) } -func (t *meshTriangle) triangle() render.Triangle3 { - return render.Triangle3{ +func (t *meshTriangle) triangle() r3.Triangle { + return r3.Triangle{ t.m.vertices[t.Vertices[0]].V, t.m.vertices[t.Vertices[1]].V, t.m.vertices[t.Vertices[2]].V, @@ -96,7 +95,7 @@ func (t *meshTriangle) triangle() render.Triangle3 { // - the triangle's first edge (t_0,t_1) is on the X axis // - the triangle's first vertex t_0 is at the origin // - the triangle's last vertex t_2 is in the XY plane. -func canalisTransform(t render.Triangle3) d3.Transform { +func canalisTransform(t r3.Triangle) d3.Transform { u2 := r3.Sub(t[1], t[0]) u3 := r3.Sub(t[2], t[0]) @@ -124,7 +123,7 @@ func lowerVec(v r3.Vec) r2.Vec { return r2.Vec{X: v.X, Y: v.Y} } -func centroid(t render.Triangle3) r3.Vec { +func centroid(t r3.Triangle) r3.Vec { return r3.Scale(1./3., r3.Add(r3.Add(t[0], t[1]), t[2])) } diff --git a/render/3mf.go b/render/3mf.go index 670dd45..feea79c 100644 --- a/render/3mf.go +++ b/render/3mf.go @@ -109,7 +109,7 @@ type mf3Triangle struct { V3 int `xml:"v3,attr"` } -func (m *model) AddObject(name string, src []Triangle3) error { +func (m *model) AddObject(name string, src []r3.Triangle) error { id := len(m.Resources.Objects) + 1 type vertexEntry struct { idx int diff --git a/render/internal_test.go b/render/internal_test.go index 69f1fd0..8eb90e1 100644 --- a/render/internal_test.go +++ b/render/internal_test.go @@ -60,7 +60,7 @@ func TestSTLWriteReadback(t *testing.T) { mismatches := 0 for iface, expect := range input { got := output[iface] - if got.Degenerate(1e-12) { + if got.IsDegenerate(1e-12) { t.Fatalf("triangle degenerate: %+v", got) } for i := range expect { @@ -78,10 +78,10 @@ func TestSTLWriteReadback(t *testing.T) { func TestOctreeMultithread(t *testing.T) { oct := NewOctreeRenderer(must3.Sphere(20), 100) oct.concurrent = 2 - buf := make([]Triangle3, oct.concurrent*100) + buf := make([]r3.Triangle, oct.concurrent*100) var err error var nt int - var model []Triangle3 + var model []r3.Triangle for err == nil { nt, err = oct.ReadTriangles(buf) model = append(model, buf[:nt]...) diff --git a/render/io.go b/render/io.go index 27caf0a..57653ad 100644 --- a/render/io.go +++ b/render/io.go @@ -1,14 +1,18 @@ package render -import "io" +import ( + "io" + + "gonum.org/v1/gonum/spatial/r3" +) // RenderAll reads the full contents of a Renderer and returns the slice read. // It does not return error on io.EOF, like the io.RenderAll implementation. -func RenderAll(r Renderer) ([]Triangle3, error) { +func RenderAll(r Renderer) ([]r3.Triangle, error) { var err error var nt int - result := make([]Triangle3, 0, 1<<12) - buf := make([]Triangle3, 1024) + result := make([]r3.Triangle, 0, 1<<12) + buf := make([]r3.Triangle, 1024) for { nt, err = r.ReadTriangles(buf) if err != nil { @@ -22,21 +26,21 @@ func RenderAll(r Renderer) ([]Triangle3, error) { return result, err } -type triangle3Buffer struct { - buf []Triangle3 +type TriangleBuffer struct { + buf []r3.Triangle } // Read reads from this buffer. -func (b *triangle3Buffer) Read(t []Triangle3) int { +func (b *TriangleBuffer) Read(t []r3.Triangle) int { n := copy(t, b.buf) b.buf = b.buf[n:] return n } // Write appends triangles to this buffer. -func (b *triangle3Buffer) Write(t []Triangle3) int { +func (b *TriangleBuffer) Write(t []r3.Triangle) int { b.buf = append(b.buf, t...) return len(t) } -func (b *triangle3Buffer) Len() int { return len(b.buf) } +func (b *TriangleBuffer) Len() int { return len(b.buf) } diff --git a/render/kdrender_test.go b/render/kdrender_test.go index e79f924..3fc1165 100644 --- a/render/kdrender_test.go +++ b/render/kdrender_test.go @@ -50,7 +50,7 @@ var ( _ kdtree.Bounder = kdTriangles{} ) -func NewKDSDF(model []Triangle3) sdf.SDF3 { +func NewKDSDF(model []r3.Triangle) sdf.SDF3 { mykd := make(kdTriangles, len(model)) // var min, max r3.Vec for i := range mykd { @@ -93,7 +93,7 @@ func (s kdSDF) Evaluate(v r3.Vec) float64 { return 0 } pointDir := r3.Sub(v, closest) - n := triangle.Normal() + n := r3.Unit(triangle.Normal()) alpha := math.Acos(r3.Cos(n, pointDir)) return math.Copysign(minDist, math.Pi/2-alpha) } @@ -120,7 +120,7 @@ func (s kdSDF) Bounds() r3.Box { type kdTriangles []kdTriangle -type kdTriangle Triangle3 +type kdTriangle r3.Triangle func (k kdTriangles) Index(i int) kdtree.Comparable { return k[i] @@ -187,7 +187,7 @@ func (a kdTriangle) Bounds() *kdtree.Bounding { } func (a kdTriangle) Normal() r3.Vec { - v := Triangle3(a) + v := r3.Triangle(a) return v.Normal() } diff --git a/render/marchingcubes.go b/render/marchingcubes.go index 3fe975b..c187565 100644 --- a/render/marchingcubes.go +++ b/render/marchingcubes.go @@ -12,7 +12,7 @@ const ( marchingCubesMaxTriangles = 5 ) -func mcToTriangles(dst []Triangle3, p [8]r3.Vec, v [8]float64, x float64) (n int) { +func mcToTriangles(dst []r3.Triangle, p [8]r3.Vec, v [8]float64, x float64) (n int) { if len(dst) < marchingCubesMaxTriangles { panic("destination triangle buffer must be greater than 5") } @@ -41,12 +41,12 @@ func mcToTriangles(dst []Triangle3, p [8]r3.Vec, v [8]float64, x float64) (n int table := mcTriangleTable[index] count := len(table) / 3 // max count is 5, a.k.a marchingCubesMaxTriangles for i := 0; i < count; i++ { - t := Triangle3{ + t := r3.Triangle{ points[table[i*3+2]], points[table[i*3+1]], points[table[i*3+0]], } - if !t.Degenerate(1e-12) { + if !t.IsDegenerate(1e-12) { dst[n] = t n++ } diff --git a/render/octree_renderer.go b/render/octree_renderer.go index c136c17..eea2b56 100644 --- a/render/octree_renderer.go +++ b/render/octree_renderer.go @@ -15,7 +15,7 @@ type octree struct { dc dc3 mu sync.Mutex todo []cube - unwritten triangle3Buffer + unwritten TriangleBuffer // concurrent goroutine processing. concurrent int // Number of triangles generated. @@ -61,7 +61,7 @@ func NewOctreeRenderer(s sdf.SDF3, meshCells int) *octree { cubes[0] = cube{sdf.V3i{0, 0, 0}, levels - 1} // process the octree, start at the top level return &octree{ dc: *newDc3(s, bb.Min, resolution, levels), - unwritten: triangle3Buffer{buf: make([]Triangle3, 0, 1024)}, + unwritten: TriangleBuffer{buf: make([]r3.Triangle, 0, 1024)}, todo: cubes, cubes: 1, } @@ -69,7 +69,7 @@ func NewOctreeRenderer(s sdf.SDF3, meshCells int) *octree { // ReadTriangles writes triangles rendered from the model into the argument buffer. // returns number of triangles written and an error if present. -func (oc *octree) ReadTriangles(dst []Triangle3) (n int, err error) { +func (oc *octree) ReadTriangles(dst []r3.Triangle) (n int, err error) { if len(dst) == 0 { panic("cannot write to empty triangle slice") } @@ -104,7 +104,7 @@ func (oc *octree) ReadTriangles(dst []Triangle3) (n int, err error) { // Returned newCubes are non-empty cubes that should be processed in future calls to readTriangles. // Triangles that were not succesfully written to dst are stored in octree unwritten buffer. // This function is safe to call concurrently. -func (oc *octree) readTriangles(dst []Triangle3, todo []cube) (n, cubesProcessed int, newCubes []cube) { +func (oc *octree) readTriangles(dst []r3.Triangle, todo []cube) (n, cubesProcessed int, newCubes []cube) { for _, cube := range todo { if n == len(dst) { // Finished writing all the buffer @@ -112,7 +112,7 @@ func (oc *octree) readTriangles(dst []Triangle3, todo []cube) (n, cubesProcessed } if n+marchingCubesMaxTriangles > len(dst) { // Not enough room in buffer to write all triangles that could be found by marching cubes. - tmp := make([]Triangle3, marchingCubesMaxTriangles) + tmp := make([]r3.Triangle, marchingCubesMaxTriangles) tri, cubes := oc.processCube(tmp, cube) oc.mu.Lock() oc.unwritten.Write(tmp[:tri]) @@ -131,10 +131,10 @@ func (oc *octree) readTriangles(dst []Triangle3, todo []cube) (n, cubesProcessed // readTrianglesThreaded is a multithreaded triangle reader implementation for octree. // It writes nt triangles into dst. -func (oc *octree) readTrianglesThreaded(dst []Triangle3) (nt int) { +func (oc *octree) readTrianglesThreaded(dst []r3.Triangle) (nt int) { var wg sync.WaitGroup div := len(dst) / oc.concurrent - work := make([][]Triangle3, oc.concurrent) + work := make([][]r3.Triangle, oc.concurrent) cubeWork := make([][]cube, oc.concurrent) newCubesC := make([][]cube, oc.concurrent) divC := len(oc.todo) / oc.concurrent @@ -183,7 +183,7 @@ func (oc *octree) readTrianglesThreaded(dst []Triangle3) (nt int) { // Process a cube. Generate triangles, or more cubes. // Safe to call concurrently. -func (oc *octree) processCube(dst []Triangle3, c cube) (writtenTriangles int, newCubes []cube) { +func (oc *octree) processCube(dst []r3.Triangle, c cube) (writtenTriangles int, newCubes []cube) { if c.n == 1 { // this cube is at the required resolution c0, d0 := oc.dc.Evaluate(c.Add(sdf.V3i{0, 0, 0})) diff --git a/render/render.go b/render/render.go index 594ad54..90c832b 100644 --- a/render/render.go +++ b/render/render.go @@ -1,34 +1,9 @@ package render import ( - "github.com/soypat/sdf/internal/d3" - "gonum.org/v1/gonum/spatial/r2" "gonum.org/v1/gonum/spatial/r3" ) type Renderer interface { - ReadTriangles(t []Triangle3) (int, error) -} - -// Triangle2 is a 2D triangle -type Triangle2 [3]r2.Vec - -// Triangle3 is a 3D triangle -type Triangle3 [3]r3.Vec - -// Normal returns the normal vector to the plane defined by the 3D triangle. -func (t *Triangle3) Normal() r3.Vec { - e1 := t[1].Sub(t[0]) - e2 := t[2].Sub(t[0]) - - return r3.Unit(r3.Cross(e1, e2)) -} - -// Degenerate returns true if the triangle is degenerate. -func (t *Triangle3) Degenerate(tolerance float64) bool { - // check for identical vertices. - // TODO more tests needed. - return d3.EqualWithin(t[0], t[1], tolerance) || - d3.EqualWithin(t[1], t[2], tolerance) || - d3.EqualWithin(t[2], t[0], tolerance) + ReadTriangles(t []r3.Triangle) (int, error) } diff --git a/render/render_test.go b/render/render_test.go index 0ba87e4..1027a34 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -1,6 +1,7 @@ package render_test import ( + "math/rand" "os" "runtime/pprof" "testing" @@ -10,6 +11,7 @@ import ( "github.com/soypat/sdf/form3/obj3/thread" "github.com/soypat/sdf/internal/d3" "github.com/soypat/sdf/render" + "gonum.org/v1/gonum/spatial/r2" "gonum.org/v1/gonum/spatial/r3" ) @@ -92,3 +94,54 @@ func startProf(t testing.TB, name string) { t.Fatal(err) } } + +func TestTriangle(t *testing.T) { + const tol = 1e-12 + rng := rand.New(rand.NewSource(1)) + for i := 0; i < 100; i++ { + tri := randTriangle(rng) + golden := Triangle3{} + for j := range tri { + golden[j] = tri[j] + } + got := tri.Normal() + expect := golden.Normal() + if !d3.EqualWithin(got, expect, tol) { + t.Errorf("expect %f, got %f", expect, got) + } + } +} + +func randVec(rng *rand.Rand) r3.Vec { + return r3.Vec{X: 20 * (rng.Float64() - .5), Y: 20 * (rng.Float64() - .5), Z: 20 * (rng.Float64() - .5)} +} + +func randTriangle(rng *rand.Rand) r3.Triangle { + return r3.Triangle{ + randVec(rng), + randVec(rng), + randVec(rng), + } +} + +// Triangle2 is a 2D triangle +type Triangle2 [3]r2.Vec + +// Triangle3 is a 3D triangle +type Triangle3 [3]r3.Vec + +// Normal returns the normal vector to the plane defined by the 3D triangle. +func (t *Triangle3) Normal() r3.Vec { + e1 := t[1].Sub(t[0]) + e2 := t[2].Sub(t[0]) + return r3.Cross(e1, e2) +} + +// Degenerate returns true if the triangle is degenerate. +func (t *Triangle3) Degenerate(tolerance float64) bool { + // check for identical vertices. + // TODO more tests needed. + return d3.EqualWithin(t[0], t[1], tolerance) || + d3.EqualWithin(t[1], t[2], tolerance) || + d3.EqualWithin(t[2], t[0], tolerance) +} diff --git a/render/stl.go b/render/stl.go index da4eefb..949f760 100644 --- a/render/stl.go +++ b/render/stl.go @@ -19,7 +19,7 @@ func CreateSTL(path string, r Renderer) error { } // WriteSTL writes model triangles to a writer in STL file format. -func WriteSTL(w io.Writer, model []Triangle3) error { +func WriteSTL(w io.Writer, model []r3.Triangle) error { if len(model) == 0 { return errors.New("empty triangle slice") } @@ -43,7 +43,7 @@ func WriteSTL(w io.Writer, model []Triangle3) error { var d stlTriangle for _, triangle := range model { var b [50]byte - n := triangle.Normal() + n := r3.Unit(triangle.Normal()) d.Normal[0] = float32(n.X) d.Normal[1] = float32(n.Y) d.Normal[2] = float32(n.Z) @@ -80,7 +80,7 @@ const trianglesInBuffer = 1 << 14 type stlReader struct { r Renderer - buf [trianglesInBuffer]Triangle3 + buf [trianglesInBuffer]r3.Triangle } func (w *stlReader) Read(b []byte) (int, error) { @@ -109,7 +109,7 @@ func (w *stlReader) Read(b []byte) (int, error) { panic("bug: buffer overflow") } for _, triangle := range w.buf[:nt] { - n := triangle.Normal() + n := r3.Unit(triangle.Normal()) d.Normal[0] = float32(n.X) d.Normal[1] = float32(n.Y) d.Normal[2] = float32(n.Z) @@ -169,7 +169,7 @@ func min(a, b int) int { return b } -func readBinarySTL(r io.Reader) (output []Triangle3, readErr error) { +func readBinarySTL(r io.Reader) (output []r3.Triangle, readErr error) { var header stlHeader if err := binary.Read(r, binary.LittleEndian, &header); err != nil { if errors.Is(err, io.ErrUnexpectedEOF) { @@ -213,7 +213,7 @@ func readBinarySTL(r io.Reader) (output []Triangle3, readErr error) { return nil, err } } - output = append(output, d.toTriangle3()) + output = append(output, d.Triangle()) } // NormalMismatch error validation may be returned. // For high resolution models this error may be incorrectly returned. @@ -324,8 +324,8 @@ func equalWithin3F32(a, b [3]float32, tol float32) bool { math32.Abs(a[2]-b[2]) <= tol } -func (d stlTriangle) toTriangle3() Triangle3 { - return Triangle3{ +func (d stlTriangle) Triangle() r3.Triangle { + return r3.Triangle{ r3From3F32(d.Vertex1), r3From3F32(d.Vertex2), r3From3F32(d.Vertex3),