-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathDOMFrustum.ts
182 lines (161 loc) · 7.17 KB
/
DOMFrustum.ts
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import { Box3 } from '../../math/Box3'
import { Mat4 } from '../../math/Mat4'
import { DOMElementBoundingRect, RectCoords } from './DOMElement'
/**
* An object defining all possible {@link DOMFrustum} class instancing parameters
*/
export interface DOMFrustumParams {
/** our 3D Object bounding box, i.e. size in world space before any transform. Usually defined by a {@link core/geometries/Geometry.Geometry | Geometry} */
boundingBox?: Box3
/** {@link core/objects3D/ProjectedObject3D.ProjectedObject3D#modelViewProjectionMatrix | model view projection matrix} to use for frustum calculations */
modelViewProjectionMatrix?: Mat4
/** the {@link DOMElementBoundingRect | bounding rectangle} to check against */
containerBoundingRect?: DOMElementBoundingRect
/** additional margins to add to {@link containerBoundingRect} */
DOMFrustumMargins?: RectCoords
/** callback to run when the {@link DOMFrustum#projectedBoundingRect | projectedBoundingRect} reenters the view frustum */
onReEnterView?: () => void
/** callback to run when the {@link DOMFrustum#projectedBoundingRect | projectedBoundingRect} leaves the view frustum */
onLeaveView?: () => void
}
/** @constant {RectCoords} - default {@link DOMFrustum#DOMFrustumMargins | DOMFrustumMargins} */
const defaultDOMFrustumMargins: RectCoords = {
top: 0,
right: 0,
bottom: 0,
left: 0,
}
/**
* Used to check if a {@link core/objects3D/ProjectedObject3D.ProjectedObject3D | ProjectedObject3D} is currently contained inside a DOM bounding rectangle.
*
* Uses a {@link core/objects3D/ProjectedObject3D.ProjectedObject3D#modelViewProjectionMatrix | model view projection matrix} that contains both useful {@link core/objects3D/ProjectedObject3D.ProjectedObject3D#transforms | Object3D transforms} and {@link core/camera/Camera.Camera | Camera} projection information.
* The DOM bounding rectangle to check against usually is the {@link core/renderers/GPURenderer.GPURenderer | renderer}'s {@link core/DOM/DOMElement.DOMElement | DOMElement} bounding rectangle, unless frustum margins are specified.
*/
export class DOMFrustum {
/** Our 3D Object bounding box, i.e. size in world space before any transform. Usually defined by a {@link core/geometries/Geometry.Geometry | Geometry} */
boundingBox: Box3
/** A model view projection matrix defining transformations, usually from a {@link core/objects3D/ProjectedObject3D.ProjectedObject3D | ProjectedObject3D}, to use for frustum calculations */
modelViewProjectionMatrix: Mat4
/** The DOM bounding rectangle to check against, usually the renderer DOM Element bounding rectangle */
containerBoundingRect: DOMElementBoundingRect
/** Additional margins to add to {@link containerBoundingRect} */
DOMFrustumMargins: RectCoords
/** A DOM Element bounding rectangle representing the result of our {@link boundingBox} with the {@link modelViewProjectionMatrix} applied */
projectedBoundingRect: DOMElementBoundingRect
/** Callback to run when the {@link projectedBoundingRect} reenters the view frustum */
onReEnterView: () => void
/** Callback to run when the {@link projectedBoundingRect} leaves the view frustum */
onLeaveView: () => void
/** Flag to indicate whether the given {@link projectedBoundingRect} is intersecting our view frustum */
isIntersecting: boolean
/** Flag to indicate whether we should update our {@link projectedBoundingRect} */
shouldUpdate: boolean
/**
* DOMFrustum constructor
* @param {DOMFrustumParams} parameters - {@link DOMFrustumParams | parameters} used to create our {@link DOMFrustum}
*/
constructor({
boundingBox = new Box3(),
modelViewProjectionMatrix = new Mat4(),
containerBoundingRect = {
top: 0,
right: 0,
bottom: 0,
left: 0,
width: 0,
height: 0,
x: 0,
y: 0,
},
DOMFrustumMargins = defaultDOMFrustumMargins,
onReEnterView = () => {
/* allow empty callbacks */
},
onLeaveView = () => {
/* allow empty callbacks */
},
}: DOMFrustumParams) {
this.boundingBox = boundingBox
this.modelViewProjectionMatrix = modelViewProjectionMatrix
this.containerBoundingRect = containerBoundingRect
this.DOMFrustumMargins = { ...defaultDOMFrustumMargins, ...DOMFrustumMargins }
this.projectedBoundingRect = {
top: 0,
right: 0,
bottom: 0,
left: 0,
width: 0,
height: 0,
x: 0,
y: 0,
}
this.onReEnterView = onReEnterView
this.onLeaveView = onLeaveView
this.isIntersecting = false
this.shouldUpdate = false
}
/**
* Set our {@link containerBoundingRect} (called on resize)
* @param boundingRect - new bounding rectangle
*/
setContainerBoundingRect(boundingRect: DOMElementBoundingRect) {
this.containerBoundingRect = boundingRect
}
/**
* Get our DOM frustum bounding rectangle, i.e. our {@link containerBoundingRect} with the {@link DOMFrustumMargins} applied
* @readonly
*/
get DOMFrustumBoundingRect(): RectCoords {
return {
top: this.projectedBoundingRect.top - this.DOMFrustumMargins.top,
right: this.projectedBoundingRect.right + this.DOMFrustumMargins.right,
bottom: this.projectedBoundingRect.bottom + this.DOMFrustumMargins.bottom,
left: this.projectedBoundingRect.left - this.DOMFrustumMargins.left,
}
}
/**
* Applies all {@link modelViewProjectionMatrix} transformations to our {@link boundingBox} and then check against intersections
*/
computeProjectedToDocumentCoords() {
const projectedBox = this.boundingBox.applyMat4(this.modelViewProjectionMatrix)
// normalize [-1, 1] coords to [0, 1]
projectedBox.min.x = (projectedBox.min.x + 1) * 0.5
projectedBox.max.x = (projectedBox.max.x + 1) * 0.5
projectedBox.min.y = 1 - (projectedBox.min.y + 1) * 0.5
projectedBox.max.y = 1 - (projectedBox.max.y + 1) * 0.5
const { width, height, top, left } = this.containerBoundingRect
this.projectedBoundingRect = {
left: projectedBox.min.x * width + left,
x: projectedBox.min.x * width + left,
top: projectedBox.max.y * height + top,
y: projectedBox.max.y * height + top,
right: projectedBox.max.x * width + left,
bottom: projectedBox.min.y * height + top,
width: projectedBox.max.x * width + left - (projectedBox.min.x * width + left),
height: projectedBox.min.y * height + top - (projectedBox.max.y * height + top),
}
this.intersectsContainer()
}
/**
* Check whether our {@link projectedBoundingRect} intersects with our {@link DOMFrustumBoundingRect}
*/
intersectsContainer() {
if (
Math.round(this.DOMFrustumBoundingRect.right) <= this.containerBoundingRect.left ||
Math.round(this.DOMFrustumBoundingRect.left) >=
this.containerBoundingRect.left + this.containerBoundingRect.width ||
Math.round(this.DOMFrustumBoundingRect.bottom) <= this.containerBoundingRect.top ||
Math.round(this.DOMFrustumBoundingRect.top) >= this.containerBoundingRect.top + this.containerBoundingRect.height
) {
if (this.isIntersecting) {
this.onLeaveView()
}
this.isIntersecting = false
} else {
if (!this.isIntersecting) {
this.onReEnterView()
}
this.isIntersecting = true
}
}
}