@@ -131,6 +131,14 @@
|
|
131
131
|
Context._defaultTargetFramerate.value = val;
|
132
132
|
}
|
133
133
|
|
134
|
+
private static _defaultWebglRendererParameters: WebGLRendererParameters = {
|
135
|
+
antialias: true,
|
136
|
+
alpha: false,
|
137
|
+
};
|
138
|
+
static get DefaultWebGLRendererParameters(): WebGLRendererParameters {
|
139
|
+
return Context._defaultWebglRendererParameters;
|
140
|
+
}
|
141
|
+
|
134
142
|
/** the needle engine version */
|
135
143
|
get version() {
|
136
144
|
return VERSION;
|
@@ -324,10 +332,6 @@
|
|
324
332
|
this.renderer = args.renderer;
|
325
333
|
this.isManagedExternally = true;
|
326
334
|
}
|
327
|
-
else {
|
328
|
-
this.createRenderer();
|
329
|
-
}
|
330
|
-
|
331
335
|
if (args?.runInBackground !== undefined) this.runInBackground = args.runInBackground;
|
332
336
|
if (args?.scene) this.scene = args.scene;
|
333
337
|
else this.scene = new Scene();
|
@@ -361,15 +365,16 @@
|
|
361
365
|
ContextRegistry.register(this);
|
362
366
|
}
|
363
367
|
|
364
|
-
private
|
368
|
+
private createNewRenderer() {
|
365
369
|
this.renderer?.dispose();
|
366
370
|
|
367
|
-
const params =
|
371
|
+
const params = Context.DefaultWebGLRendererParameters;
|
372
|
+
if (!params.canvas) {
|
373
|
+
// get canvas already configured in the Needle Engine Web Component
|
374
|
+
const canvas = this.domElement?.shadowRoot?.querySelector("canvas");
|
375
|
+
if (canvas) params.canvas = canvas;
|
376
|
+
}
|
368
377
|
|
369
|
-
// get canvas already configured in the Needle Engine Web Component
|
370
|
-
const canvas = this.domElement?.shadowRoot?.querySelector("canvas");
|
371
|
-
if (canvas) params.canvas = canvas;
|
372
|
-
|
373
378
|
this.renderer = new WebGLRenderer(params);
|
374
379
|
|
375
380
|
this.renderer.debug.checkShaderErrors = isDevEnvironment() || getParam("checkshadererrors") === true;
|
@@ -389,14 +394,7 @@
|
|
389
394
|
this.renderer.useLegacyLights = false;
|
390
395
|
}
|
391
396
|
|
392
|
-
private getWebGLRendererParameters(): WebGLRendererParameters {
|
393
|
-
return {
|
394
|
-
antialias: true,
|
395
|
-
alpha: false,
|
396
|
-
};
|
397
|
-
}
|
398
397
|
|
399
|
-
|
400
398
|
private _intersectionObserver: IntersectionObserver | null = null;
|
401
399
|
private internalOnUpdateVisible() {
|
402
400
|
this._intersectionObserver?.disconnect();
|
@@ -470,9 +468,11 @@
|
|
470
468
|
this.physics?.engine?.clearCaches();
|
471
469
|
|
472
470
|
if (!this.isManagedExternally) {
|
473
|
-
this.renderer
|
474
|
-
|
475
|
-
|
471
|
+
if (this.renderer) {
|
472
|
+
this.renderer.renderLists.dispose();
|
473
|
+
this.renderer.state.reset();
|
474
|
+
this.renderer.resetState();
|
475
|
+
}
|
476
476
|
}
|
477
477
|
// We do not want to clear the renderer here because when switching src we want to keep the last rendered frame in case the loading screen is not visible
|
478
478
|
// if a user wants to see the background they can still call setClearAlpha(0) and clear manually
|
@@ -661,8 +661,10 @@
|
|
661
661
|
this.clear();
|
662
662
|
// stop the animation loop if its running during creation
|
663
663
|
// since we do not want to start enabling scripts etc before they are deserialized
|
664
|
-
if (this.isManagedExternally === false)
|
664
|
+
if (this.isManagedExternally === false) {
|
665
|
+
this.createNewRenderer();
|
665
666
|
this.renderer?.setAnimationLoop(null);
|
667
|
+
}
|
666
668
|
|
667
669
|
await delay(1);
|
668
670
|
|
@@ -764,6 +766,12 @@
|
|
764
766
|
Context.Current = this;
|
765
767
|
looputils.processNewScripts(this);
|
766
768
|
|
769
|
+
// We have to step once so that colliders that have been created in onEnable can be raycasted in start
|
770
|
+
if (this.physics.engine) {
|
771
|
+
this.physics.engine?.step(0);
|
772
|
+
this.physics.engine?.postStep();
|
773
|
+
}
|
774
|
+
|
767
775
|
// const mainCam = this.mainCameraComponent as Camera;
|
768
776
|
// if (mainCam) {
|
769
777
|
// mainCam.applyClearFlagsIfIsActiveCamera();
|
@@ -633,8 +633,13 @@
|
|
633
633
|
lf.copy(this._pointerPositions[evt.button]);
|
634
634
|
// accumulate delta (it's reset in end of frame), if we just write it here it's not correct when the browser console is open
|
635
635
|
const delta = this._pointerPositionsDelta[evt.button];
|
636
|
-
|
637
|
-
|
636
|
+
let dx = evt.clientX - lf.x;
|
637
|
+
let dy = evt.clientY - lf.y;
|
638
|
+
// if pointer is locked, clientX and Y are not changed, but Movement is.
|
639
|
+
if(dx === 0 && evt.movementX !== 0)
|
640
|
+
dx = evt.movementX || 0;
|
641
|
+
if(dy === 0 && evt.movementY !== 0)
|
642
|
+
dy = evt.movementY || 0;
|
638
643
|
delta.x += dx;
|
639
644
|
delta.y += dy;
|
640
645
|
|
@@ -234,7 +234,7 @@
|
|
234
234
|
await RAPIER.init()
|
235
235
|
}
|
236
236
|
if (debugPhysics) console.log("Physics engine initialized, creating world...");
|
237
|
-
this.
|
237
|
+
this._world = new World(this._gravity);
|
238
238
|
this.enabled = true;
|
239
239
|
this._isInitialized = true;
|
240
240
|
if (debugPhysics) console.log("Physics world created");
|
@@ -427,6 +427,8 @@
|
|
427
427
|
// physics simulation
|
428
428
|
|
429
429
|
enabled: boolean = false;
|
430
|
+
/** Get access to the rapier world */
|
431
|
+
public get world(): World | undefined { return this._world };
|
430
432
|
|
431
433
|
private _tempPosition: Vector3 = new Vector3();
|
432
434
|
private _tempQuaternion: Quaternion = new Quaternion();
|
@@ -439,7 +441,7 @@
|
|
439
441
|
get isUpdating(): boolean { return this._isUpdatingPhysicsWorld; }
|
440
442
|
|
441
443
|
|
442
|
-
private
|
444
|
+
private _world?: World;
|
443
445
|
private _hasCreatedWorld: boolean = false;
|
444
446
|
private eventQueue?: EventQueue;
|
445
447
|
private collisionHandler?: PhysicsCollisionHandler;
|
@@ -593,12 +595,20 @@
|
|
593
595
|
}
|
594
596
|
}
|
595
597
|
|
598
|
+
/** Get the rapier body for a Needle component */
|
596
599
|
getBody(obj: ICollider | IRigidbody): null | any {
|
597
600
|
if (!obj) return null;
|
598
601
|
const body = obj[$bodyKey];
|
599
602
|
return body;
|
600
603
|
}
|
601
604
|
|
605
|
+
/** Get the Needle Engine component for a rapier object */
|
606
|
+
getComponent(rapierObject:object) : IComponent | null {
|
607
|
+
if(!rapierObject) return null;
|
608
|
+
const component = rapierObject[$componentKey];
|
609
|
+
return component;
|
610
|
+
}
|
611
|
+
|
602
612
|
private createCollider(collider: ICollider, desc: ColliderDesc, center?: Vector3) {
|
603
613
|
if (!this.world) throw new Error("Physics world not initialized");
|
604
614
|
const matrix = this._tempMatrix;
|
@@ -395,9 +395,15 @@
|
|
395
395
|
clearCaches();
|
396
396
|
|
397
397
|
enabled: boolean;
|
398
|
+
get world(): any;
|
398
399
|
|
399
400
|
set gravity(vec3: Vec3);
|
400
401
|
get gravity(): Vec3;
|
402
|
+
|
403
|
+
/** Get the rapier body for a Needle component */
|
404
|
+
getBody(obj: ICollider | IRigidbody): null | any;
|
405
|
+
/** Get the Needle Engine component for a rapier object */
|
406
|
+
getComponent(rapierObject:object) : IComponent | null;
|
401
407
|
|
402
408
|
// raycasting
|
403
409
|
/** Fast raycast against physics colliders
|
@@ -1,9 +1,11 @@
|
|
1
|
+
import { Object3D, Vector3 } from "three";
|
2
|
+
import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js';
|
3
|
+
|
1
4
|
import { Behaviour, GameObject } from "../../Component.js";
|
2
|
-
import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js';
|
3
5
|
import GLTFMeshGPUInstancingExtension from '../../../include/three/EXT_mesh_gpu_instancing_exporter.js';
|
4
6
|
import { Renderer } from "../../Renderer.js";
|
5
|
-
import { Object3D, Vector3 } from "three";
|
6
7
|
import { SerializationContext } from "../../../engine/engine_serialization_core.js";
|
8
|
+
import { serializable } from "../../../engine/engine_serialization_decorator.js";
|
7
9
|
import { NEEDLE_components } from "../../../engine/extensions/NEEDLE_components.js";
|
8
10
|
import { getWorldPosition } from "../../../engine/engine_three_utils.js";
|
9
11
|
import { BoxHelperComponent } from "../../BoxHelperComponent.js";
|
@@ -36,7 +38,11 @@
|
|
36
38
|
}
|
37
39
|
|
38
40
|
export class GltfExport extends Behaviour {
|
41
|
+
|
42
|
+
@serializable()
|
39
43
|
binary: boolean = true;
|
44
|
+
|
45
|
+
@serializable(Object3D)
|
40
46
|
objects: Object3D[] = [];
|
41
47
|
|
42
48
|
private exporter?: GLTFExporter;
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { BufferAttribute, BufferGeometry, Group, Mesh, Object3D, Vector3 } from "three";
|
1
|
+
import { BufferAttribute, BufferGeometry, Group, Material, Mesh, Object3D, TypedArray, Vector3 } from "three";
|
2
2
|
import { MeshCollider } from "../Collider.js";
|
3
3
|
import { Behaviour, GameObject } from "../Component.js";
|
4
4
|
import { WebXR, WebXREvent } from "./WebXR.js";
|
@@ -9,13 +9,22 @@
|
|
9
9
|
|
10
10
|
const debug = getParam("debugplanetracking");
|
11
11
|
|
12
|
+
declare type XRMesh = {
|
13
|
+
meshSpace: XRSpace;
|
14
|
+
lastChangedTime: number;
|
15
|
+
vertices: Float32Array;
|
16
|
+
indices: Uint32Array;
|
17
|
+
semanticLabel?: string;
|
18
|
+
}
|
19
|
+
|
12
20
|
declare type XRFramePlanes = XRFrame & {
|
13
21
|
detectedPlanes?: Set<XRPlane>;
|
22
|
+
detectedMeshes?: Set<XRMesh>;
|
14
23
|
}
|
15
24
|
|
16
25
|
export declare type XRPlaneContext = {
|
17
26
|
id: number;
|
18
|
-
|
27
|
+
xrData: XRPlane | XRMesh;
|
19
28
|
timestamp: number;
|
20
29
|
mesh?: Mesh | Group;
|
21
30
|
collider?: MeshCollider;
|
@@ -28,13 +37,21 @@
|
|
28
37
|
|
29
38
|
export class WebXRPlaneTracking extends Behaviour {
|
30
39
|
|
31
|
-
/** Optional: if assigned it will be instantiated per tracked plane */
|
40
|
+
/** Optional: if assigned it will be instantiated per tracked plane/tracked mesh */
|
32
41
|
@serializable(Object3D)
|
33
|
-
|
42
|
+
dataTemplate?: Object3D;
|
43
|
+
|
34
44
|
@serializable()
|
35
|
-
|
45
|
+
initiateRoomCaptureIfNoData = true;
|
36
46
|
|
47
|
+
@serializable()
|
48
|
+
usePlaneData: boolean = true;
|
49
|
+
|
50
|
+
@serializable()
|
51
|
+
useMeshData: boolean = true;
|
52
|
+
|
37
53
|
get trackedPlanes() { return this._allPlanes.values(); }
|
54
|
+
get trackedMeshes() { return this._allMeshes.values(); }
|
38
55
|
|
39
56
|
onEnable(): void {
|
40
57
|
WebXR.addEventListener(WebXREvent.XRUpdate, this.onXRUpdate);
|
@@ -49,60 +66,40 @@
|
|
49
66
|
private onModifyAROptions = (event: any) => {
|
50
67
|
const options = event.detail;
|
51
68
|
const features = options.optionalFeatures || [];
|
52
|
-
|
69
|
+
|
70
|
+
if (this.usePlaneData && !features.includes("plane-detection"))
|
53
71
|
features.push("plane-detection");
|
72
|
+
if (this.useMeshData && !features.includes("mesh-detection"))
|
73
|
+
features.push("mesh-detection");
|
74
|
+
|
54
75
|
options.optionalFeatures = features;
|
55
76
|
}
|
56
77
|
|
57
78
|
private onXRUpdate = (evt) => {
|
58
|
-
|
59
|
-
}
|
60
|
-
|
61
|
-
private _planeId = 1;
|
62
|
-
private readonly _allPlanes = new Map<XRPlane, XRPlaneContext>();
|
63
|
-
private firstTimeNoPlanesDetected = -100;
|
64
|
-
|
65
|
-
private processPlanes(rig: Object3D, frame: XRFramePlanes) {
|
66
|
-
const renderer = this.context.renderer;
|
67
|
-
|
79
|
+
|
68
80
|
// parenting tracked planes to the XR rig ensures that they synced with the real-world user data;
|
69
81
|
// otherwise they would "swim away" when the user rotates / moves / teleports and so on.
|
70
82
|
// There may be cases where we want that! E.g. a user walks around on their own table in castle builder
|
71
|
-
if (!rig) return;
|
83
|
+
if (!evt.rig) return;
|
72
84
|
|
73
|
-
|
74
|
-
|
75
|
-
|
85
|
+
const frame = evt.frame as XRFramePlanes;
|
86
|
+
const renderer = this.context.renderer;
|
76
87
|
const referenceSpace = renderer.xr.getReferenceSpace();
|
77
88
|
if (!referenceSpace) return;
|
89
|
+
|
90
|
+
const planes = frame.detectedPlanes;
|
91
|
+
const meshes = frame.detectedMeshes;
|
92
|
+
const hasAnyPlanes = planes !== undefined && planes.size > 0;
|
93
|
+
const hasAnyMeshes = meshes !== undefined && meshes.size > 0;
|
78
94
|
|
79
|
-
for (const plane of this._allPlanes.keys()) {
|
80
|
-
if (!frame.detectedPlanes.has(plane)) {
|
81
|
-
const planeContext = this._allPlanes.get(plane)!;
|
82
|
-
// plane was removed
|
83
|
-
this._allPlanes.delete(plane);
|
84
|
-
if (debug) console.log("Plane no longer tracked, id=" + planeContext.id);
|
85
|
-
if (planeContext.mesh)
|
86
|
-
rig?.remove(planeContext.mesh);
|
87
|
-
|
88
|
-
const evt = new CustomEvent<WebXRPlaneTrackingEvent>("plane-tracking", {
|
89
|
-
detail: {
|
90
|
-
type: "plane-removed",
|
91
|
-
context: planeContext
|
92
|
-
}
|
93
|
-
})
|
94
|
-
this.dispatchEvent(evt);
|
95
|
-
}
|
96
|
-
}
|
97
|
-
|
98
95
|
// When no planes are found and we haven't already run the coroutine,
|
99
96
|
// we start it and then wait for 2s before opening the settings.
|
100
97
|
// This only works on Quest through a magic method on the frame,
|
101
98
|
// see https://developer.oculus.com/documentation/web/webxr-mixed-reality/#:~:text=Because%20few%20people,once%20per%20session.
|
102
|
-
if (this.
|
103
|
-
if (
|
99
|
+
if (this.initiateRoomCaptureIfNoData) {
|
100
|
+
if (!hasAnyPlanes && !hasAnyMeshes && this.firstTimeNoPlanesDetected < -10)
|
104
101
|
this.firstTimeNoPlanesDetected = Date.now();
|
105
|
-
if (
|
102
|
+
if (hasAnyPlanes || hasAnyMeshes)
|
106
103
|
this.firstTimeNoPlanesDetected = -1; // we're done
|
107
104
|
if (this.firstTimeNoPlanesDetected > 0 && Date.now() - this.firstTimeNoPlanesDetected > 2500) {
|
108
105
|
if ("initiateRoomCapture" in frame.session) {
|
@@ -113,31 +110,91 @@
|
|
113
110
|
}
|
114
111
|
}
|
115
112
|
|
116
|
-
|
117
|
-
|
113
|
+
if (planes !== undefined)
|
114
|
+
this.processFrameData(evt.rig, evt.frame, planes, this._allPlanes);
|
118
115
|
|
116
|
+
if (meshes !== undefined)
|
117
|
+
this.processFrameData(evt.rig, evt.frame, meshes, this._allMeshes);
|
118
|
+
}
|
119
|
+
|
120
|
+
|
121
|
+
|
122
|
+
private _dataId = 1;
|
123
|
+
private readonly _allPlanes = new Map<XRPlane, XRPlaneContext>();
|
124
|
+
private readonly _allMeshes = new Map<XRMesh, XRPlaneContext>();
|
125
|
+
private firstTimeNoPlanesDetected = -100;
|
126
|
+
|
127
|
+
private processFrameData(rig: Object3D, frame: XRFramePlanes, detected: Set<XRPlane | XRMesh>, _all: Map<XRPlane | XRMesh, XRPlaneContext>) {
|
128
|
+
const renderer = this.context.renderer;
|
129
|
+
const referenceSpace = renderer.xr.getReferenceSpace();
|
130
|
+
if (!referenceSpace) return;
|
131
|
+
|
132
|
+
for (const data of _all.keys()) {
|
133
|
+
if (!detected.has(data)) {
|
134
|
+
const dataContext = _all.get(data)!;
|
135
|
+
// plane was removed
|
136
|
+
_all.delete(data);
|
137
|
+
if (debug) console.log("Plane no longer tracked, id=" + dataContext.id);
|
138
|
+
if (dataContext.mesh)
|
139
|
+
rig?.remove(dataContext.mesh);
|
140
|
+
|
141
|
+
const evt = new CustomEvent<WebXRPlaneTrackingEvent>("plane-tracking", {
|
142
|
+
detail: {
|
143
|
+
type: "plane-removed",
|
144
|
+
context: dataContext
|
145
|
+
}
|
146
|
+
})
|
147
|
+
this.dispatchEvent(evt);
|
148
|
+
}
|
149
|
+
}
|
150
|
+
|
151
|
+
for (const data of detected) {
|
152
|
+
const space = "planeSpace" in data ? data.planeSpace
|
153
|
+
: ("meshSpace" in data ? data.meshSpace
|
154
|
+
: undefined);
|
155
|
+
if (!space) continue;
|
156
|
+
const planePose = frame.getPose(space, referenceSpace);
|
157
|
+
|
119
158
|
let planeMesh: Object3D | undefined;
|
120
159
|
|
160
|
+
const makeBlockerMaterials = (m: Material | Array<Material>) => {
|
161
|
+
if (!m) return;
|
162
|
+
if (m instanceof Array) {
|
163
|
+
for (const m0 of m)
|
164
|
+
makeBlockerMaterials(m0);
|
165
|
+
return;
|
166
|
+
}
|
167
|
+
if (!m.name.includes("Occlu")) return;
|
168
|
+
m.colorWrite = false;
|
169
|
+
m.depthWrite = true;
|
170
|
+
m.transparent = false;
|
171
|
+
m.polygonOffset = true;
|
172
|
+
m.polygonOffsetFactor = 1;
|
173
|
+
m.polygonOffsetUnits = 0.1;
|
174
|
+
m["_renderOrder"] = -1000;
|
175
|
+
}
|
176
|
+
|
121
177
|
// If the plane already existed just update it
|
122
|
-
if (
|
123
|
-
const planeContext =
|
178
|
+
if (_all.has(data)) {
|
179
|
+
const planeContext = _all.get(data)!;
|
124
180
|
planeMesh = planeContext.mesh;
|
125
|
-
if (planeContext.timestamp <
|
126
|
-
planeContext.timestamp =
|
181
|
+
if (planeContext.timestamp < data.lastChangedTime) {
|
182
|
+
planeContext.timestamp = data.lastChangedTime;
|
127
183
|
|
128
|
-
|
129
184
|
// Update the mesh geometry
|
130
185
|
if (planeContext.mesh) {
|
131
|
-
const geometry = this.createGeometry(
|
186
|
+
const geometry = this.createGeometry(data);
|
132
187
|
if (planeContext.mesh instanceof Mesh) {
|
133
188
|
planeContext.mesh.geometry.dispose();
|
134
189
|
planeContext.mesh.geometry = geometry;
|
190
|
+
makeBlockerMaterials(planeContext.mesh.material);
|
135
191
|
}
|
136
192
|
else if (planeContext.mesh instanceof Group) {
|
137
193
|
for (const ch of planeContext.mesh.children) {
|
138
194
|
if (ch instanceof Mesh) {
|
139
195
|
ch.geometry.dispose();
|
140
196
|
ch.geometry = geometry;
|
197
|
+
makeBlockerMaterials(ch.material);
|
141
198
|
}
|
142
199
|
}
|
143
200
|
}
|
@@ -164,19 +221,30 @@
|
|
164
221
|
else {
|
165
222
|
|
166
223
|
// if we don't have any template assigned we just use a simple mesh object
|
167
|
-
if (!this.
|
168
|
-
this.
|
224
|
+
if (!this.dataTemplate) {
|
225
|
+
this.dataTemplate = new Mesh();
|
169
226
|
}
|
170
227
|
|
171
|
-
if (this.
|
228
|
+
if (this.dataTemplate) {
|
172
229
|
// Create instance
|
173
|
-
const newPlane = GameObject.instantiate(this.
|
230
|
+
const newPlane = GameObject.instantiate(this.dataTemplate) as GameObject;
|
174
231
|
planeMesh = newPlane;
|
175
232
|
|
176
233
|
if (newPlane instanceof Mesh) {
|
177
234
|
disposeObjectResources(newPlane.geometry);
|
178
|
-
newPlane.geometry = this.createGeometry(
|
235
|
+
newPlane.geometry = this.createGeometry(data);
|
236
|
+
makeBlockerMaterials(newPlane.material);
|
179
237
|
}
|
238
|
+
else if (newPlane instanceof Group) {
|
239
|
+
for (const ch of newPlane.children) {
|
240
|
+
if (ch instanceof Mesh) {
|
241
|
+
disposeObjectResources(ch.geometry);
|
242
|
+
ch.geometry = this.createGeometry(data);
|
243
|
+
makeBlockerMaterials(ch.material);
|
244
|
+
}
|
245
|
+
}
|
246
|
+
}
|
247
|
+
|
180
248
|
|
181
249
|
const mc = newPlane.getComponent(MeshCollider) as MeshCollider;
|
182
250
|
if (mc) {
|
@@ -194,13 +262,13 @@
|
|
194
262
|
rig.add(newPlane);
|
195
263
|
|
196
264
|
const planeContext: XRPlaneContext = {
|
197
|
-
id: this.
|
198
|
-
|
199
|
-
timestamp:
|
265
|
+
id: this._dataId++,
|
266
|
+
xrData: data,
|
267
|
+
timestamp: data.lastChangedTime,
|
200
268
|
mesh: newPlane as unknown as Mesh,
|
201
269
|
collider: mc
|
202
270
|
};
|
203
|
-
|
271
|
+
_all.set(data, planeContext);
|
204
272
|
|
205
273
|
if (debug) console.log("New plane detected, id=" + planeContext.id);
|
206
274
|
|
@@ -230,9 +298,36 @@
|
|
230
298
|
};
|
231
299
|
}
|
232
300
|
|
233
|
-
createGeometry(
|
301
|
+
createGeometry(data: XRPlane | XRMesh) {
|
302
|
+
if ("polygon" in data) {
|
303
|
+
return this.createPlaneGeometry(data.polygon);
|
304
|
+
}
|
305
|
+
else if ("vertices" in data && "indices" in data) {
|
306
|
+
return this.createMeshGeometry(data.vertices, data.indices);
|
307
|
+
}
|
308
|
+
return new BufferGeometry();
|
309
|
+
}
|
310
|
+
|
311
|
+
createMeshGeometry(vertices: Float32Array, indices: Uint32Array) {
|
234
312
|
const geometry = new BufferGeometry();
|
313
|
+
geometry.setIndex(new BufferAttribute(indices, 1));
|
314
|
+
geometry.setAttribute('position', new BufferAttribute(vertices, 3));
|
315
|
+
// set UVs in worldspace
|
316
|
+
const uvs = Array<number>();
|
317
|
+
for (let i = 0; i < vertices.length; i+=3) {
|
318
|
+
uvs.push(vertices[i], vertices[i + 2]);
|
319
|
+
}
|
320
|
+
geometry.setAttribute('uv', new BufferAttribute(new Float32Array(uvs), 2));
|
235
321
|
|
322
|
+
geometry.computeVertexNormals();
|
323
|
+
geometry.computeTangents();
|
324
|
+
|
325
|
+
return geometry;
|
326
|
+
}
|
327
|
+
|
328
|
+
createPlaneGeometry(polygon: Vec3[]) {
|
329
|
+
const geometry = new BufferGeometry();
|
330
|
+
|
236
331
|
const vertices: number[] = [];
|
237
332
|
const uvs: number[] = [];
|
238
333
|
polygon.forEach(point => {
|
@@ -268,4 +363,4 @@
|
|
268
363
|
|
269
364
|
return geometry;
|
270
365
|
}
|
271
|
-
}
|
366
|
+
}
|