-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclipspace.js
157 lines (141 loc) · 4.74 KB
/
clipspace.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/**
* Clip space utility class
*
* Author: Andrew Lim
* https://github.com/andrew-lim
*/
class ClipSpace
{
/*
Finds the interpolation amount based off 2 homogeneous points,
1 outside the clipping plane, and 1 inside the clipping plane.
The clipping plane is based on their w components
Uses the formula for ratio between 2 signed distances
d1/(d1-d2)
Let N be inside normal of clipping plane, then
d1 is the signed distance between source point and N
d2 is the signed distance between destination point and N
Full explanation for this formula here:
https://fabiensanglard.net/polygon_codec/clippingdocument/Clipping.pdf
@param src source point to lerp from
@param dst destination point
@param ixyz Axis part - 0 or 1 or 2 for x/y/z axis respectively
@param planeSign Either 1 or -1
*/
static signedDistanceRatio(src, dst, ixyz, planeSign)
{
// const N = -planeSign
const d1 = (src.get(ixyz) - src.getW()*planeSign) // * N
const d2 = (dst.get(ixyz) - dst.getW()*planeSign) // * N
return d1/(d1-d2)
}
/**
* Clips a Triangle by one of the xyz planes
* @param triangle Triangle to clip
* @param ixyz 0/1/2 for x/y/z planes respectively
* @param planeSign 1 for positive plane, -1 for negative plane
* @return new triangles clipped from original
*/
static clipTriangle(triangle, ixyz, planeSign)
{
let triangles = []
let insidePoints = []
let outsidePoints = []
let insideIndices = []
let outsideIndices = []
for (let i=0; i<3; ++i) {
const pt = triangle.getPoint(i)
const xyz = pt.get(ixyz)
const w = pt.getW()
const outside = (planeSign<0 && xyz<-w) || (planeSign>0 && xyz>w)
if (outside) {
outsidePoints.push(pt)
outsideIndices.push(i)
}
else {
insidePoints.push(pt)
insideIndices.push(i)
}
}
if (3==insidePoints.length) {
// Triangle is completely inside this plane
triangles.push(triangle)
}
// 2 points outside, create a smaller triangle
else if (2==outsidePoints.length && 1==insidePoints.length) {
const a = insidePoints[0]
const b = outsidePoints[0]
const c = outsidePoints[1]
const ai = insideIndices[0]
const bi = outsideIndices[0]
const bt = ClipSpace.signedDistanceRatio(b, a, ixyz, planeSign)
const ct = ClipSpace.signedDistanceRatio(c, a, ixyz, planeSign)
const b1 = b.lerp(a, bt)
const c1 = c.lerp(a, ct)
// Preserve winding order
// B follows A
if ( ((ai+1)%3)==bi ) {
triangles.push(Triangle.createFromVertices(a, b1, c1))
}
// C follows A
else {
triangles.push(Triangle.createFromVertices(a, c1, b1))
}
}
// 1 point outside, create 2 smaller triangles
else if (1==outsidePoints.length && 2==insidePoints.length) {
const a = insidePoints[0]
const b = outsidePoints[0]
const c = insidePoints[1]
const ai = insideIndices[0]
const bi = outsideIndices[0]
const abt = ClipSpace.signedDistanceRatio(b, a, ixyz, planeSign)
const cbt = ClipSpace.signedDistanceRatio(b, c, ixyz, planeSign)
const a1 = b.lerp(a, abt)
const c1 = b.lerp(c, cbt)
// Preserve winding order
// B follows A
if ( ((ai+1)%3)==bi ) {
triangles.push(Triangle.createFromVertices(a, a1, c1))
triangles.push(Triangle.createFromVertices(a, c1, c))
}
// C follows A
else {
triangles.push(Triangle.createFromVertices(a, c, c1))
triangles.push(Triangle.createFromVertices(a, c1, a1))
}
}
return triangles
}
/**
* Clips the given triangles by a single plane
* @param ixyz 0/1/2 for x/y/z planes respectively
* @param planeSign 1 for positive plane, -1 for negative plane
* @return new array of clipped triangles
*/
static clipTrianglesByPlane(triangles, ixyz, planeSign)
{
let triangles2 = []
for (let t of triangles) {
triangles2.push(...ClipSpace.clipTriangle(t, ixyz, planeSign))
}
return triangles2
}
/**
* Clips the given triangles by all the X/Y/Z planes and returns
* the resulting new triangles
*
* @param triangles array of Triangles to clip
* @return new array of clipped Triangles
*/
static clipTrianglesByAllPlanes(triangles)
{
triangles = ClipSpace.clipTrianglesByPlane(triangles, 2, -1) // near
triangles = ClipSpace.clipTrianglesByPlane(triangles, 2, 1) // far
triangles = ClipSpace.clipTrianglesByPlane(triangles, 0, 1) // right
triangles = ClipSpace.clipTrianglesByPlane(triangles, 0, -1) // left
triangles = ClipSpace.clipTrianglesByPlane(triangles, 1, 1) // top
triangles = ClipSpace.clipTrianglesByPlane(triangles, 1, -1) // bottom
return triangles
}
}