-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathPlane.ts
168 lines (139 loc) · 6.13 KB
/
Plane.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
import { isCurtainsRenderer } from '../../core/renderers/utils'
import { PlaneGeometry, PlaneGeometryParams } from '../../core/geometries/PlaneGeometry'
import { DOMMesh, DOMMeshBaseParams, DOMMeshParams } from './DOMMesh'
import { Vec3 } from '../../math/Vec3'
import { Vec2 } from '../../math/Vec2'
import { cacheManager } from '../../utils/CacheManager'
import { GPUCurtainsRenderer } from '../renderers/GPUCurtainsRenderer'
import { GPUCurtains } from '../GPUCurtains'
import { DOMElementParams } from '../../core/DOM/DOMElement'
/**
* Parameters used to create a {@link Plane}
*/
export interface PlaneParams extends DOMMeshBaseParams, PlaneGeometryParams {
/** Optional {@link PlaneGeometry} to use */
geometry?: PlaneGeometry
}
/** @const - default {@link Plane} parameters */
const defaultPlaneParams = {
label: 'Plane',
// geometry
instancesCount: 1,
vertexBuffers: [],
} as PlaneParams
/**
* Used to create a special {@link DOMMesh} class object using a {@link PlaneGeometry}.
* This means a quad that looks like an ordinary {@link HTMLElement} but with WebGPU rendering capabilities.
*
* @example
* ```javascript
* // set our main GPUCurtains instance
* const gpuCurtains = new GPUCurtains({
* container: '#canvas' // selector of our WebGPU canvas container
* })
*
* // set the GPU device
* // note this is asynchronous
* await gpuCurtains.setDevice()
*
* // create a Plane,
* // assuming there's a HTML element with the "plane" ID in the DOM
* // will use the normals colors as default shading
* const plane = new Plane(gpuCurtains, '#plane', {
* label: 'My plane',
* })
* ```
*/
export class Plane extends DOMMesh {
/**
* Plane constructor
* @param renderer - {@link GPUCurtainsRenderer} object or {@link GPUCurtains} class object used to create this {@link Plane}
* @param element - {@link HTMLElement} or string representing an {@link HTMLElement} selector used to scale and position the {@link Plane}
* @param parameters - {@link PlaneParams | parameters} used to create this {@link Plane}
*/
constructor(
renderer: GPUCurtainsRenderer | GPUCurtains,
element: DOMElementParams['element'],
parameters = {} as PlaneParams
) {
// we could pass our curtains object OR our curtains renderer object
renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as GPUCurtainsRenderer)
isCurtainsRenderer(renderer, parameters.label ? parameters.label + ' Plane' : 'Plane')
// assign default params if needed
const params = { ...defaultPlaneParams, ...parameters }
let { geometry, widthSegments, heightSegments, ...DOMMeshParams } = params
const { instancesCount, vertexBuffers, ...materialParams } = DOMMeshParams
// can we get a cached geometry?
if (!geometry || geometry.type !== 'PlaneGeometry') {
widthSegments = widthSegments ?? 1
heightSegments = heightSegments ?? 1
const geometryID = widthSegments * heightSegments + widthSegments
// if there's no additional vertex buffers, try to get a geometry from cache
if (!vertexBuffers.length) {
geometry = cacheManager.getPlaneGeometryByID(geometryID)
}
if (!geometry) {
// no cached plane geometry, we need to create a new one
geometry = new PlaneGeometry({ widthSegments, heightSegments, instancesCount, vertexBuffers })
cacheManager.addPlaneGeometry(geometry as PlaneGeometry)
} else {
// if geometry comes from cache, force instances count
geometry.instancesCount = instancesCount
}
}
// get DOMMesh params
super(renderer, element, { geometry, ...materialParams } as DOMMeshParams)
this.type = 'Plane'
}
/**
* Take the pointer {@link Vec2 | vector} position relative to the document and returns it relative to our {@link Plane}
* It ranges from -1 to 1 on both axis
* @param mouseCoords - pointer {@link Vec2 | vector} coordinates
* @returns - raycasted {@link Vec2 | vector} coordinates relative to the {@link Plane}
*/
mouseToPlaneCoords(mouseCoords: Vec2 = new Vec2()): Vec2 {
// TODO simplify if no rotation set?
// raycasting
// based on https://people.cs.clemson.edu/~dhouse/courses/405/notes/raycast.pdf
// convert mouse position to 3d normalised device coordinates (from [-1, -1] to [1, 1])
const worldMouse = {
x: 2 * (mouseCoords.x / this.renderer.displayBoundingRect.width) - 1,
y: 2 * (1 - mouseCoords.y / this.renderer.displayBoundingRect.height) - 1,
}
const rayOrigin = this.camera.position.clone()
// ray direction based on normalised coordinates and plane translation
const rayDirection = new Vec3(worldMouse.x, worldMouse.y, -0.5)
// unproject ray direction
rayDirection.unproject(this.camera)
rayDirection.sub(rayOrigin).normalize()
// plane normals (could also be [0, 0, -1], makes no difference, raycasting lands the same result for both face)
const planeNormals = new Vec3(0, 0, 1)
// apply plane quaternion to plane normals
planeNormals.applyQuat(this.quaternion).normalize()
const result = new Vec3(0, 0, 0)
const denominator = planeNormals.dot(rayDirection)
if (Math.abs(denominator) >= 0.0001) {
const inverseViewMatrix = this.modelMatrix.getInverse().premultiply(this.camera.viewMatrix)
// get the plane's center coordinates
// start with our transform origin point
const planeOrigin = this.worldTransformOrigin.clone().add(this.worldPosition)
// rotate our transform origin about world center
const rotatedOrigin = new Vec3(
this.worldPosition.x - planeOrigin.x,
this.worldPosition.y - planeOrigin.y,
this.worldPosition.z - planeOrigin.z
)
rotatedOrigin.applyQuat(this.quaternion)
// add it to our plane origin
planeOrigin.add(rotatedOrigin)
// distance from ray origin to plane
const distance = planeNormals.dot(planeOrigin.clone().sub(rayOrigin)) / denominator
result.copy(rayOrigin.add(rayDirection.multiplyScalar(distance)))
result.applyMat4(inverseViewMatrix)
} else {
// no intersection!
result.set(Infinity, Infinity, Infinity)
}
return new Vec2(result.x, result.y)
}
}