Skip to content

Commit 5a72865

Browse files
committed
add go-play3d logic
1 parent cc03f9f commit 5a72865

File tree

10 files changed

+337
-56
lines changed

10 files changed

+337
-56
lines changed

internal/internalmath.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package internal
22

33
const (
4-
Smallfloat32 = 1e-5
5-
Smallfloat64 = 1e-8
4+
Smallfloat32 float32 = 1e-5
5+
Smallfloat64 float64 = 1e-8
6+
Largefloat32 float32 = 1e23
7+
Largefloat64 float64 = 1e53
68
)

ms2/line.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package ms2
2+
3+
import (
4+
math "github.com/chewxy/math32"
5+
)
6+
7+
// Line can be used to represent a line between two points
8+
// or an infinite line.
9+
type Line [2]Vec
10+
11+
// Interpolate takes a value between 0 and 1 to linearly
12+
// interpolate a point on the line.
13+
//
14+
// Interpolate(0) returns l[0]
15+
// Interpolate(1) returns l[1]
16+
func (ln Line) Interpolate(t float32) Vec {
17+
lineDir := Sub(ln[1], ln[0])
18+
return Add(ln[0], Scale(t, lineDir))
19+
}
20+
21+
// DistanceInfinite returns the minimum euclidean distance of point p to the infinite line represented by l.
22+
func (ln Line) DistanceInfinite(point Vec) float32 {
23+
// https://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html
24+
p1 := ln[0]
25+
p2 := ln[1]
26+
num := math.Abs((p2.X-p1.X)*(p1.Y-point.Y) - (p1.X-point.X)*(p2.Y-p1.Y))
27+
return num / math.Hypot(p2.X-p1.X, p2.Y-p1.Y)
28+
}
29+
30+
// ClosestInfinite returns the point on the infinite line closest to the argument point.
31+
func (ln Line) ClosestInfinite(point Vec) Vec {
32+
// https://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html
33+
t := -Dot(Sub(ln[0], point), Sub(ln[1], point)) / Norm2(Sub(ln[1], ln[0]))
34+
return ln.Interpolate(t)
35+
}
36+
37+
// Closest returns the closest point on the line l[0]..l[1] to the argument point.
38+
// The integer return param is 0 or 1 when closest to vertex l[0] or l[1], respectively,
39+
// and is -1 for when the point is closest to the line segment.
40+
func (ln Line) Closest(point Vec) (closest Vec, vertexOrSegment int8) {
41+
lineDir := Sub(ln[1], ln[0])
42+
perpendicular := Vec{-lineDir.Y, lineDir.X}
43+
perpend2 := Add(ln[1], perpendicular)
44+
e2 := Line{ln[1], perpend2}.edgeEquation(point)
45+
if e2 > 0 {
46+
return ln[1], 0
47+
}
48+
perpend1 := Add(ln[0], perpendicular)
49+
e1 := Line{ln[0], perpend1}.edgeEquation(point)
50+
if e1 < 0 {
51+
return ln[0], 1
52+
}
53+
e3 := ln.DistanceInfinite(point)
54+
toPoint := Scale(-e3, Unit(perpendicular))
55+
return Sub(point, toPoint), -1
56+
}
57+
58+
// edgeEquation returns a signed distance of a point to
59+
// an infinite line defined by two points
60+
// Edge equation for a line passing through (X,Y)
61+
// with gradient dY/dX
62+
// E ( x; y ) =(x-X)*dY - (y-Y)*dX
63+
func (ln Line) edgeEquation(p Vec) float32 {
64+
dxy := Sub(ln[1], ln[0])
65+
return (p.X-ln[0].X)*dxy.Y - (p.Y-ln[0].Y)*dxy.X
66+
}

ms2/triangle.go

Lines changed: 59 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ms2
22

33
import (
44
math "github.com/chewxy/math32"
5+
"github.com/soypat/geometry/internal"
56
)
67

78
// Triangle represents a triangle in 2D space and
@@ -17,6 +18,17 @@ func (t Triangle) Centroid() Vec {
1718
return Scale(1.0/3.0, Add(Add(t[0], t[1]), t[2]))
1819
}
1920

21+
// Sides returns the triangle's sides as lines:
22+
//
23+
// [(t[0],t[1]), (t[1],t[2]), (t[2],t[0])]
24+
func (t Triangle) Sides() [3]Line {
25+
return [3]Line{
26+
{t[0], t[1]},
27+
{t[1], t[2]},
28+
{t[2], t[0]},
29+
}
30+
}
31+
2032
// Area returns the surface area of the triangle.
2133
func (t Triangle) Area() float32 {
2234
// Heron's Formula, see https://en.wikipedia.org/wiki/Heron%27s_formula.
@@ -64,33 +76,10 @@ func (t Triangle) IsDegenerate(tol float32) bool {
6476
}
6577
// calculate vertex distance from longest side
6678
ln := Line{t[longIdx], t[(longIdx+1)%3]}
67-
dist := ln.Distance(t[(longIdx+2)%3])
79+
dist := ln.DistanceInfinite(t[(longIdx+2)%3])
6880
return dist <= tol
6981
}
7082

71-
// Line is an infinite 3D Line
72-
// defined by two points on the Line.
73-
type Line [2]Vec
74-
75-
// Interpolate takes a value between 0 and 1 to linearly
76-
// interpolate a point on the line.
77-
//
78-
// Interpolate(0) returns l[0]
79-
// Interpolate(1) returns l[1]
80-
func (l Line) Interpolate(t float32) Vec {
81-
lineDir := Sub(l[1], l[0])
82-
return Add(l[0], Scale(t, lineDir))
83-
}
84-
85-
// Distance returns the minimum euclidean Distance of point p to the line.
86-
func (l Line) Distance(p Vec) float32 {
87-
// https://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html
88-
p1 := l[0]
89-
p2 := l[1]
90-
num := math.Abs((p2.X-p1.X)*(p1.Y-p.Y) - (p1.X-p.X)*(p2.Y-p1.Y))
91-
return num / math.Hypot(p2.X-p1.X, p2.Y-p1.Y)
92-
}
93-
9483
// sort performs the sort-3 algorithm and returns
9584
// l1, l2, l3 such that l1 ≤ l2 ≤ l3.
9685
func sort(a, b, c float32) (l1, l2, l3 float32) {
@@ -110,14 +99,57 @@ func sort(a, b, c float32) (l1, l2, l3 float32) {
11099
// orderedLengths returns the lengths of the sides of the triangle such that
111100
// a ≤ b ≤ c.
112101
func (t Triangle) orderedLengths() (a, b, c float32) {
113-
s1, s2, s3 := t.sides()
102+
s1, s2, s3 := t.edges()
114103
l1 := Norm(s1)
115104
l2 := Norm(s2)
116105
l3 := Norm(s3)
117106
return sort(l1, l2, l3)
118107
}
119108

120-
// sides returns vectors for each of the sides of t.
121-
func (t Triangle) sides() (Vec, Vec, Vec) {
109+
// edges returns vectors for each of the edges of t.
110+
func (t Triangle) edges() (Vec, Vec, Vec) {
122111
return Sub(t[1], t[0]), Sub(t[2], t[1]), Sub(t[0], t[2])
123112
}
113+
114+
// Contains returns true if point is contained within the triangle's surface.
115+
func (t Triangle) Contains(point Vec) bool {
116+
d1 := d2Sign(point, t[0], t[1])
117+
d2 := d2Sign(point, t[1], t[2])
118+
d3 := d2Sign(point, t[2], t[0])
119+
has_neg := (d1 < 0) || (d2 < 0) || (d3 < 0)
120+
has_pos := (d1 > 0) || (d2 > 0) || (d3 > 0)
121+
return !(has_neg && has_pos)
122+
}
123+
124+
func d2Sign(p1, p2, p3 Vec) float32 {
125+
// TODO: replace with CopyOrientation?
126+
return (p1.X-p3.X)*(p2.Y-p3.Y) - (p2.X-p3.X)*(p1.Y-p3.Y)
127+
}
128+
129+
// Closest returns the point on the triangle closest to the argument point p.
130+
// side and vertex are non negative to flag the point is closest the non-negative side or vertex index.
131+
// If the point lies on the triangle it returns the same point and side=vertex=-1. side and vertex cannot be both non-negative.
132+
func (t Triangle) Closest(p Vec) (closest Vec, side int8, vertex int8) {
133+
if t.Contains(p) {
134+
return p, -1, -1
135+
}
136+
minDist := internal.Largefloat32
137+
for j := range t {
138+
nxt := (j + 1) % 3
139+
edge := Line{{X: t[j].X, Y: t[j].Y}, {X: t[nxt].X, Y: t[nxt].Y}}
140+
pointOnTriangle, maybeVertex := edge.Closest(p)
141+
d2 := Norm2(Sub(p, pointOnTriangle))
142+
if d2 < minDist {
143+
if vertex < 0 {
144+
vertex = -1
145+
side = int8(j)
146+
} else {
147+
side = -1
148+
vertex = (int8(j) + maybeVertex) % 3
149+
}
150+
minDist = d2
151+
closest = pointOnTriangle
152+
}
153+
}
154+
return closest, side, vertex
155+
}

ms2/vec.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ func CopyOrientation(f float32, p1, p2, p3 Vec) float32 {
236236
if slope1 == slope2 {
237237
return 0
238238
}
239+
239240
return math.Copysign(f, slope2-slope1)
240241
}
241242

ms3/line.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package ms3
2+
3+
// Line can be used to represent a line between two points
4+
// or an infinite line.
5+
type Line [2]Vec
6+
7+
// Interpolate takes a value between 0 and 1 to linearly
8+
// interpolate a point on the line.
9+
//
10+
// Interpolate(0) returns l[0]
11+
// Interpolate(1) returns l[1]
12+
func (ln Line) Interpolate(t float32) Vec {
13+
lineDir := Sub(ln[1], ln[0])
14+
return Add(ln[0], Scale(t, lineDir))
15+
}
16+
17+
// DistanceInfinite returns the minimum euclidean Distance of point p
18+
// to the infinite line represented by l.
19+
func (ln Line) DistanceInfinite(point Vec) float32 {
20+
// https://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html
21+
num := Norm(Cross(Sub(point, ln[0]), Sub(point, ln[1])))
22+
return num / Norm(Sub(ln[1], ln[0]))
23+
}
24+
25+
// ClosestInfinite returns the point on the infinite line closest to the argument point.
26+
func (ln Line) ClosestInfinite(point Vec) Vec {
27+
// https://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html
28+
t := -Dot(Sub(ln[0], point), Sub(ln[1], point)) / Norm2(Sub(ln[1], ln[0]))
29+
return ln.Interpolate(t)
30+
}

ms3/plane.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package ms3
2+
3+
import math "github.com/chewxy/math32"
4+
5+
// Plane represents a plane in 3D space. To safely construct one use [Triangle.Plane]
6+
type Plane struct {
7+
// p is a point on the plane
8+
p Vec
9+
// n is the unit vector normal to the plane.
10+
n Vec
11+
}
12+
13+
func newPlane(p, n Vec) Plane {
14+
return Plane{p: p, n: Unit(n)}
15+
}
16+
17+
func (p Plane) distanceTo(q Vec) float32 {
18+
v := Sub(q, p.p)
19+
return math.Abs(Dot(v, p.n))
20+
}

ms3/quat.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ const (
5353
type Quat struct {
5454
// V contains I, J and K imaginary parts.
5555
I, J, K float32
56-
W float32
56+
// W is the quaternion's real part.
57+
W float32
5758
}
5859

5960
// IJK returns I,J,K fields of q as a vector with set fields X,Y,Z, respectively.

ms3/tetrahedron.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package ms3
2+
3+
import (
4+
math "github.com/chewxy/math32"
5+
)
6+
7+
// Tetra represents a tetrahedron in 3D space.
8+
type Tetra [4]Vec
9+
10+
// Centroid returns the tetrahedron's centroid.
11+
func (t Tetra) Centroid() Vec {
12+
return Scale(1./4., Add(t[0], Add(t[1], Add(t[2], t[3]))))
13+
}
14+
15+
// Sides returns the sides as lines of the tetrahedron:
16+
//
17+
// [(t[0],t[1]),(t[1],t[2]),(t[2],t[0]),
18+
// (t[3],t[2]),(t[0],t[3]),(t[1],t[3])]
19+
func (t Tetra) Sides() [6]Line {
20+
return [6]Line{
21+
{t[0], t[1]}, {t[1], t[2]}, {t[2], t[0]},
22+
{t[3], t[2]}, {t[0], t[3]}, {t[1], t[3]},
23+
}
24+
}
25+
26+
// Edges returns the directional vectors composing the sides of the tetrahedron as returned by [Tetra.Sides].
27+
func (t Tetra) Edges() [6]Vec {
28+
return [6]Vec{
29+
Sub(t[1], t[0]), Sub(t[2], t[1]), Sub(t[0], t[2]),
30+
Sub(t[2], t[3]), Sub(t[3], t[0]), Sub(t[3], t[1]),
31+
}
32+
}
33+
34+
// Volume returns the volume contained in the tetrahedron.
35+
func (t Tetra) Volume() float32 {
36+
const third = 1.0 / 3.0
37+
base := Triangle{t[0], t[1], t[2]}
38+
area := base.Area()
39+
height := base.Plane().distanceTo(t[3])
40+
return third * area * height
41+
}
42+
43+
// Aspect returns the aspect ratio of the tetrahedron calculated
44+
// as: longestEdge/minHeight.
45+
func (t Tetra) Aspect() float32 {
46+
e := t.longestEdge()
47+
heights := t.heights()
48+
hmin, _, _ := Sort(heights[0], heights[1], heights[2])
49+
if heights[3] < hmin {
50+
hmin = heights[3]
51+
}
52+
return e / hmin
53+
54+
}
55+
56+
func (t Tetra) longestEdge() float32 {
57+
edges := t.Edges()
58+
_, _, e1 := Sort(Norm2(edges[0]), Norm2(edges[1]), Norm2(edges[2]))
59+
_, _, e2 := Sort(Norm2(edges[3]), Norm2(edges[4]), Norm2(edges[5]))
60+
if e1 > e2 {
61+
return math.Sqrt(e1)
62+
}
63+
return math.Sqrt(e2)
64+
}
65+
66+
// heights returns the "heights" of the tetrahedron,
67+
// which are basically the shortest normal dropped from a vertex to the opposite face.
68+
func (t Tetra) heights() (alt [4]float32) {
69+
for i := range t {
70+
j := (i + 1) % 4
71+
k := (i + 2) % 4
72+
l := (i + 3) % 4
73+
e1 := Sub(t[k], t[j])
74+
e2 := Sub(t[l], t[j])
75+
p := newPlane(t[l], Cross(e1, e2))
76+
alt[i] = p.distanceTo(t[i])
77+
}
78+
return alt
79+
}

0 commit comments

Comments
 (0)