@@ -5,7 +5,7 @@
|
|
5
5
|
import { GameObject } from "../Component.js";
|
6
6
|
import { Matrix4, Object3D } from "three";
|
7
7
|
import { RectTransform } from "./RectTransform.js";
|
8
|
-
import { ICanvas, ILayoutGroup, IRectTransform } from "./Interfaces.js";
|
8
|
+
import { ICanvas, ICanvasEventReceiver, ILayoutGroup, IRectTransform } from "./Interfaces.js";
|
9
9
|
import { Camera } from "../Camera.js";
|
10
10
|
import { EventSystem } from "./EventSystem.js";
|
11
11
|
import * as ThreeMeshUI from 'three-mesh-ui'
|
@@ -189,15 +189,26 @@
|
|
189
189
|
this._layoutGroups.delete(obj);
|
190
190
|
}
|
191
191
|
|
192
|
+
private _receivers: ICanvasEventReceiver[] = [];
|
193
|
+
registerEventReceiver(receiver: ICanvasEventReceiver) {
|
194
|
+
this._receivers.push(receiver);
|
195
|
+
}
|
196
|
+
unregisterEventReceiver(receiver: ICanvasEventReceiver) {
|
197
|
+
const index = this._receivers.indexOf(receiver);
|
198
|
+
if (index !== -1) {
|
199
|
+
this._receivers.splice(index, 1);
|
200
|
+
}
|
201
|
+
}
|
202
|
+
|
192
203
|
onBeforeRenderRoutine = () => {
|
193
|
-
|
194
204
|
if (this.context.isInVR) {
|
195
205
|
this.onUpdateRenderMode();
|
196
206
|
this.handleLayoutUpdates();
|
197
207
|
// TODO TMUI @swingingtom - For VR this is so we don't have text clipping
|
198
208
|
this.shadowComponent?.updateMatrixWorld(true);
|
199
209
|
this.shadowComponent?.updateWorldMatrix(true, true);
|
200
|
-
|
210
|
+
this.invokeBeforeRenderEvents();
|
211
|
+
EventSystem.ensureUpdateMeshUI(ThreeMeshUI, this.context, true);
|
201
212
|
return;
|
202
213
|
}
|
203
214
|
|
@@ -214,6 +225,7 @@
|
|
214
225
|
// TODO: we might need to optimize this. This is here to make sure the TMUI text clipping matrices are correct. Ideally the text does use onBeforeRender and apply the clipping matrix there so we dont have to force update all the matrices here
|
215
226
|
this.shadowComponent?.updateMatrixWorld(true);
|
216
227
|
this.shadowComponent?.updateWorldMatrix(true, true);
|
228
|
+
this.invokeBeforeRenderEvents();
|
217
229
|
EventSystem.ensureUpdateMeshUI(ThreeMeshUI, this.context);
|
218
230
|
}
|
219
231
|
}
|
@@ -236,7 +248,8 @@
|
|
236
248
|
this.handleLayoutUpdates();
|
237
249
|
this.shadowComponent?.updateMatrixWorld(true);
|
238
250
|
// this.handleLayoutUpdates();
|
239
|
-
|
251
|
+
this.invokeBeforeRenderEvents();
|
252
|
+
EventSystem.ensureUpdateMeshUI(ThreeMeshUI, this.context, true);
|
240
253
|
this.context.renderer.render(this.gameObject, this.context.mainCamera);
|
241
254
|
this.context.renderer.autoClear = prevAutoClearDepth;
|
242
255
|
this.context.renderer.autoClearColor = prevAutoClearColor;
|
@@ -245,6 +258,12 @@
|
|
245
258
|
this._lastMatrixWorld?.copy(this.gameObject.matrixWorld);
|
246
259
|
}
|
247
260
|
|
261
|
+
private invokeBeforeRenderEvents() {
|
262
|
+
for (const receiver of this._receivers) {
|
263
|
+
receiver.onBeforeCanvasRender?.(this);
|
264
|
+
}
|
265
|
+
}
|
266
|
+
|
248
267
|
private handleLayoutUpdates() {
|
249
268
|
if (this._lastMatrixWorld === null) {
|
250
269
|
this._lastMatrixWorld = new Matrix4();
|
@@ -309,8 +328,8 @@
|
|
309
328
|
let camera = this.context.mainCameraComponent;
|
310
329
|
let planeDistance: number = 10;
|
311
330
|
if (camera && camera.nearClipPlane > 0 && camera.farClipPlane > 0) {
|
312
|
-
// TODO: this is a hack/workaround for event system currently only passing events to the nearest object
|
313
|
-
planeDistance = Mathf.lerp(camera.nearClipPlane, camera.farClipPlane, .
|
331
|
+
// TODO: this is a hack/workaround for event system currently only passing events to the nearest object so we move the canvas close to the nearplane
|
332
|
+
planeDistance = Mathf.lerp(camera.nearClipPlane, camera.farClipPlane, .01);
|
314
333
|
}
|
315
334
|
if (this._renderMode === RenderMode.ScreenSpaceCamera) {
|
316
335
|
if (this.worldCamera)
|
@@ -38,21 +38,25 @@
|
|
38
38
|
let collider = this.gameObject.getComponent(CapsuleCollider);
|
39
39
|
if (!collider)
|
40
40
|
collider = this.gameObject.addNewComponent(CapsuleCollider) as CapsuleCollider;
|
41
|
-
|
41
|
+
|
42
42
|
collider.center.copy(this.center);
|
43
43
|
collider.radius = this.radius;
|
44
44
|
collider.height = this.height;
|
45
|
-
|
46
|
-
|
45
|
+
|
46
|
+
// discard any rotation besides Y axis
|
47
|
+
const wForward = new Vector3(0, 0, 1);
|
48
|
+
const wRight = new Vector3(1, 0, 0);
|
49
|
+
const wUp = new Vector3(0, 1, 0);
|
50
|
+
const fwd = this.gameObject.getWorldDirection(new Vector3());
|
51
|
+
fwd.y = 0;
|
52
|
+
|
53
|
+
const sign = wRight.dot(fwd) < 0 ? -1 : 1;
|
54
|
+
const angleY = wForward.angleTo(fwd) * sign;
|
55
|
+
this.gameObject.setRotationFromAxisAngle(wUp, angleY);
|
56
|
+
|
47
57
|
rb.lockRotationX = true;
|
48
58
|
rb.lockRotationY = true;
|
49
59
|
rb.lockRotationZ = true;
|
50
|
-
|
51
|
-
// TODO: this doesnt work yet
|
52
|
-
// setInterval(()=>{
|
53
|
-
// this.rigidbody.isKinematic = !this.rigidbody.isKinematic;
|
54
|
-
// console.log(this.rigidbody.isKinematic);
|
55
|
-
// }, 1000)
|
56
60
|
}
|
57
61
|
|
58
62
|
move(vec: Vector3) {
|
@@ -68,8 +68,8 @@
|
|
68
68
|
}
|
69
69
|
|
70
70
|
//@ts-ignore
|
71
|
-
static ensureUpdateMeshUI(instance, context: Context) {
|
72
|
-
MeshUIHelper.update(instance, context);
|
71
|
+
static ensureUpdateMeshUI(instance, context: Context, force: boolean = false) {
|
72
|
+
MeshUIHelper.update(instance, context, force);
|
73
73
|
}
|
74
74
|
static markUIDirty(_context: Context) {
|
75
75
|
MeshUIHelper.markDirty();
|
@@ -533,7 +533,11 @@
|
|
533
533
|
this.needsUpdate = true;
|
534
534
|
}
|
535
535
|
|
536
|
-
static update(threeMeshUI: any, context: Context) {
|
536
|
+
static update(threeMeshUI: any, context: Context, force: boolean = false) {
|
537
|
+
if (force) {
|
538
|
+
threeMeshUI.update();
|
539
|
+
return;
|
540
|
+
}
|
537
541
|
const currentFrame = context.time.frameCount;
|
538
542
|
for (const lu of this.lastUpdateFrame) {
|
539
543
|
if (lu.context === context) {
|
@@ -6,6 +6,8 @@
|
|
6
6
|
get screenspace(): boolean;
|
7
7
|
registerTransform(rt: IRectTransform);
|
8
8
|
unregisterTransform(rt: IRectTransform);
|
9
|
+
registerEventReceiver(receiver: ICanvasEventReceiver);
|
10
|
+
unregisterEventReceiver(receiver: ICanvasEventReceiver);
|
9
11
|
}
|
10
12
|
|
11
13
|
export interface ICanvasGroup {
|
@@ -39,6 +41,11 @@
|
|
39
41
|
updateLayout();
|
40
42
|
}
|
41
43
|
|
44
|
+
export interface ICanvasEventReceiver {
|
45
|
+
/** Called before the canvas is rendering */
|
46
|
+
onBeforeCanvasRender?(canvas: ICanvas);
|
47
|
+
}
|
48
|
+
|
42
49
|
// export abstract class LayoutGroup extends Behaviour implements IRectTransformChangedReceiver, ILayoutGroup {
|
43
50
|
// get isLayoutGroup(): boolean {
|
44
51
|
// return true;
|
@@ -52,6 +52,12 @@
|
|
52
52
|
@serializable()
|
53
53
|
minParticleSize!: number;
|
54
54
|
|
55
|
+
@serializable()
|
56
|
+
velocityScale?: number;
|
57
|
+
@serializable()
|
58
|
+
cameraVelocityScale?: number;
|
59
|
+
@serializable()
|
60
|
+
lengthScale?: number;
|
55
61
|
|
56
62
|
start() {
|
57
63
|
if (this.maxParticleSize !== .5 && this.minParticleSize !== 0) {
|
@@ -99,15 +105,8 @@
|
|
99
105
|
return material;
|
100
106
|
}
|
101
107
|
|
102
|
-
getMesh(
|
108
|
+
getMesh(_renderMode?: ParticleSystemRenderMode) {
|
103
109
|
let geo: BufferGeometry | null = null;
|
104
|
-
if (renderMode === ParticleSystemRenderMode.HorizontalBillboard) {
|
105
|
-
geo = new THREE.BoxGeometry(1, 1, 0);
|
106
|
-
}
|
107
|
-
else if (renderMode === ParticleSystemRenderMode.VerticalBillboard) {
|
108
|
-
geo = new THREE.BoxGeometry(1, 0, 1);
|
109
|
-
}
|
110
|
-
|
111
110
|
if (!geo) {
|
112
111
|
if (this.particleMesh instanceof Mesh) {
|
113
112
|
geo = this.particleMesh.geometry;
|
@@ -556,7 +555,7 @@
|
|
556
555
|
}
|
557
556
|
|
558
557
|
get prewarm() { return false; } // force disable three.quark prewarm, we have our own!
|
559
|
-
get material() { return this.system.renderer.getMaterial(this.system.trails.enabled) as Material;}
|
558
|
+
get material() { return this.system.renderer.getMaterial(this.system.trails.enabled) as Material; }
|
560
559
|
get layers() { return this.system.gameObject.layers; }
|
561
560
|
|
562
561
|
update() {
|
@@ -590,8 +589,8 @@
|
|
590
589
|
switch (this.system.renderer.renderMode) {
|
591
590
|
case ParticleSystemRenderMode.Billboard: return RenderMode.BillBoard;
|
592
591
|
case ParticleSystemRenderMode.Stretch: return RenderMode.StretchedBillBoard;
|
593
|
-
case ParticleSystemRenderMode.HorizontalBillboard: return RenderMode.
|
594
|
-
case ParticleSystemRenderMode.VerticalBillboard: return RenderMode.
|
592
|
+
case ParticleSystemRenderMode.HorizontalBillboard: return RenderMode.HorizontalBillBoard;
|
593
|
+
case ParticleSystemRenderMode.VerticalBillboard: return RenderMode.VerticalBillBoard;
|
595
594
|
case ParticleSystemRenderMode.Mesh: return RenderMode.Mesh;
|
596
595
|
}
|
597
596
|
return RenderMode.BillBoard;
|
@@ -600,7 +599,13 @@
|
|
600
599
|
startLength: new ConstantValue(220),
|
601
600
|
followLocalOrigin: false,
|
602
601
|
};
|
603
|
-
get speedFactor() {
|
602
|
+
get speedFactor() {
|
603
|
+
let factor = this.system.main.simulationSpeed;
|
604
|
+
if (this.system.renderer?.renderMode === ParticleSystemRenderMode.Stretch) {
|
605
|
+
factor *= this.system.renderer.velocityScale ?? 1;
|
606
|
+
}
|
607
|
+
return factor;
|
608
|
+
}
|
604
609
|
get texture(): THREE.Texture {
|
605
610
|
const mat = this.material;
|
606
611
|
if (mat && mat["map"]) {
|
@@ -882,7 +887,7 @@
|
|
882
887
|
awake(): void {
|
883
888
|
this._renderer = this.gameObject.getComponent(ParticleSystemRenderer) as ParticleSystemRenderer;
|
884
889
|
|
885
|
-
if (!this.main)
|
890
|
+
if (!this.main) {
|
886
891
|
throw new Error("Not Supported: ParticleSystem needs a serialized MainModule. Creating new particle systems at runtime is currently not supported.");
|
887
892
|
}
|
888
893
|
|
@@ -986,7 +991,7 @@
|
|
986
991
|
private onSimulate(dt: number) {
|
987
992
|
if (this._batchSystem) {
|
988
993
|
let needsUpdate = this.context.time.frameCount % 60 === 0;
|
989
|
-
if(this._lastBatchesCount !== this._batchSystem.batches.length) {
|
994
|
+
if (this._lastBatchesCount !== this._batchSystem.batches.length) {
|
990
995
|
this._lastBatchesCount = this._batchSystem.batches.length;
|
991
996
|
needsUpdate = true;
|
992
997
|
}
|
@@ -467,8 +467,16 @@
|
|
467
467
|
}
|
468
468
|
}
|
469
469
|
|
470
|
-
private tryGetSceneEventListener(obj: Object3D): ISceneEventListener | null {
|
470
|
+
private tryGetSceneEventListener(obj: Object3D, level: number = 0): ISceneEventListener | null {
|
471
471
|
const sceneListener = GameObject.foreachComponent(obj, c => (c as any as ISceneEventListener).sceneClosing ? c : null) as ISceneEventListener | null;
|
472
|
+
// if we didnt find any component with the listener on the root object
|
473
|
+
// we also check the first level of its children because a scene might be a group
|
474
|
+
if (level === 0 && !sceneListener && obj.children.length) {
|
475
|
+
for (const ch of obj.children) {
|
476
|
+
const res = this.tryGetSceneEventListener(ch, level + 1);
|
477
|
+
if (res) return res;
|
478
|
+
}
|
479
|
+
}
|
472
480
|
return sceneListener;
|
473
481
|
}
|
474
482
|
}
|
@@ -6,7 +6,7 @@
|
|
6
6
|
import { Canvas } from './Canvas.js';
|
7
7
|
import { serializable } from '../../engine/engine_serialization_decorator.js';
|
8
8
|
import { getParam, resolveUrl } from '../../engine/engine_utils.js';
|
9
|
-
import { ICanvas, IHasAlphaFactor } from './Interfaces.js';
|
9
|
+
import { ICanvas, ICanvasEventReceiver, IHasAlphaFactor } from './Interfaces.js';
|
10
10
|
|
11
11
|
const debug = getParam("debugtext");
|
12
12
|
|
@@ -38,7 +38,7 @@
|
|
38
38
|
BoldAndItalic = 3,
|
39
39
|
}
|
40
40
|
|
41
|
-
export class Text extends Graphic implements IHasAlphaFactor {
|
41
|
+
export class Text extends Graphic implements IHasAlphaFactor, ICanvasEventReceiver {
|
42
42
|
|
43
43
|
@serializable()
|
44
44
|
alignment: TextAnchor = TextAnchor.UpperLeft;
|
@@ -102,11 +102,15 @@
|
|
102
102
|
}
|
103
103
|
}
|
104
104
|
|
105
|
-
onBeforeRender(): void {
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
105
|
+
// onBeforeRender(): void {
|
106
|
+
// // TODO TMUI @swingingtom this is so we don't have text clipping
|
107
|
+
// if (this.uiObject && (this.Canvas?.screenspace || this.context.isInVR)) {
|
108
|
+
// this.updateOverflow();
|
109
|
+
// }
|
110
|
+
// }
|
111
|
+
onBeforeCanvasRender(_canvas: ICanvas) {
|
112
|
+
// ensure the text clipping matrix is updated (this was a problem with multiple screenspace canvases due to canvas reparenting)
|
113
|
+
this.updateOverflow();
|
110
114
|
}
|
111
115
|
|
112
116
|
private updateOverflow() {
|
@@ -220,7 +224,12 @@
|
|
220
224
|
}
|
221
225
|
|
222
226
|
setTimeout(() => this.markDirty(), 10);
|
227
|
+
this.canvas?.registerEventReceiver(this);
|
223
228
|
}
|
229
|
+
onDisable(): void {
|
230
|
+
super.onDisable();
|
231
|
+
this.canvas?.unregisterEventReceiver(this);
|
232
|
+
}
|
224
233
|
|
225
234
|
private getAlignment(opts: ThreeMeshUIEveryOptions): ThreeMeshUIEveryOptions {
|
226
235
|
|
@@ -46,6 +46,7 @@
|
|
46
46
|
private _isTouching: boolean = false;
|
47
47
|
private _rigStartPose: Matrix4 | undefined | null = null;
|
48
48
|
private _gotFirstHitTestResult: boolean = false;
|
49
|
+
private _anchor: XRAnchor | null = null;
|
49
50
|
|
50
51
|
onBegin(session: XRSession) {
|
51
52
|
this._placementPose = null;
|
@@ -54,6 +55,7 @@
|
|
54
55
|
this._startPose = this.gameObject.matrix.clone();
|
55
56
|
this._rigStartPose = this.rig?.matrix.clone();
|
56
57
|
this._gotFirstHitTestResult = false;
|
58
|
+
this._anchor = null;
|
57
59
|
session.addEventListener('selectstart', this._selectStartFn);
|
58
60
|
session.addEventListener('selectend', this._selectEndFn);
|
59
61
|
// setTimeout(() => this.gameObject.visible = false, 1000); // TODO test on phone AR and Hololens if this was still needed
|
@@ -73,8 +75,9 @@
|
|
73
75
|
this.dispatchEvent(new CustomEvent('onBeginSession'));
|
74
76
|
}
|
75
77
|
|
76
|
-
onUpdate(rig: Object3D | null, _session: XRSession, pose: XRPose | null | undefined): boolean {
|
78
|
+
onUpdate(rig: Object3D | null, _session: XRSession, hit: XRHitTestResult | null, pose: XRPose | null | undefined): boolean {
|
77
79
|
|
80
|
+
|
78
81
|
if (pose && !this._placementPose) {
|
79
82
|
|
80
83
|
if (!this._gotFirstHitTestResult) {
|
@@ -89,6 +92,8 @@
|
|
89
92
|
|
90
93
|
if (this.webAR) this.webAR.setReticleActive(false);
|
91
94
|
this.placeAt(rig, poseMatrix);
|
95
|
+
if (hit && pose)
|
96
|
+
this.onCreatePlacementAnchor(hit, pose);
|
92
97
|
return true;
|
93
98
|
}
|
94
99
|
}
|
@@ -104,11 +109,34 @@
|
|
104
109
|
// }
|
105
110
|
}
|
106
111
|
|
112
|
+
private async onCreatePlacementAnchor(hit: XRHitTestResult, pose: XRPose) {
|
113
|
+
this._anchor = null;
|
114
|
+
hit.createAnchor?.call(hit, pose.transform)?.then(anchor => {
|
115
|
+
if (this.context.isInAR)
|
116
|
+
this._anchor = anchor;
|
117
|
+
});
|
118
|
+
}
|
119
|
+
|
120
|
+
private _anchorMatrix: Matrix4 = new Matrix4();
|
121
|
+
onBeforeRender(frame: XRFrame | null): void {
|
122
|
+
if (frame && this._anchor && this._rig) {
|
123
|
+
const referenceSpace = this.context.renderer.xr.getReferenceSpace();
|
124
|
+
if (referenceSpace) {
|
125
|
+
const pose = frame.getPose(this._anchor.anchorSpace, referenceSpace);
|
126
|
+
if (pose) {
|
127
|
+
const poseMatrix = this._anchorMatrix.fromArray(pose.transform.matrix).invert();
|
128
|
+
this.placeAt(this._rig, poseMatrix);
|
129
|
+
}
|
130
|
+
}
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
private _invertedSessionRootMatrix: Matrix4 = new Matrix4();
|
107
135
|
placeAt(rig: Object3D | null, mat: Matrix4) {
|
108
136
|
if (!this._placementPose) this._placementPose = new Matrix4();
|
109
137
|
this._placementPose.copy(mat);
|
110
138
|
// apply session root offset
|
111
|
-
const invertedSessionRoot = this.gameObject.matrixWorld
|
139
|
+
const invertedSessionRoot = this._invertedSessionRootMatrix.copy(this.gameObject.matrixWorld).invert();
|
112
140
|
this._placementPose.premultiply(invertedSessionRoot);
|
113
141
|
if (rig) {
|
114
142
|
|
@@ -132,6 +160,7 @@
|
|
132
160
|
this._placementPose = null;
|
133
161
|
this.gameObject.visible = false;
|
134
162
|
this.gameObject.matrixAutoUpdate = false;
|
163
|
+
this._anchor = null;
|
135
164
|
if (this._startPose) {
|
136
165
|
this.gameObject.matrix.copy(this._startPose);
|
137
166
|
}
|
@@ -171,9 +200,10 @@
|
|
171
200
|
}
|
172
201
|
// we apply the transform to the rig because we want to move the user's position for easy networking
|
173
202
|
rig.matrixAutoUpdate = false;
|
174
|
-
rig.matrix.multiplyMatrices(
|
203
|
+
rig.matrix.multiplyMatrices(tempMatrix.makeScale(scale, scale, scale), this._placementPose);
|
175
204
|
rig.matrix.decompose(rig.position, rig.quaternion, rig.scale);
|
176
205
|
rig.updateMatrixWorld();
|
177
|
-
console.log("Place", rig.position);
|
178
206
|
}
|
179
|
-
}
|
207
|
+
}
|
208
|
+
|
209
|
+
const tempMatrix = new Matrix4();
|
@@ -122,6 +122,7 @@
|
|
122
122
|
options.domOverlay = { root: domOverlayRoot };
|
123
123
|
options.optionalFeatures.push('dom-overlay')
|
124
124
|
options.optionalFeatures.push('hit-test');
|
125
|
+
options.optionalFeatures.push('anchors');
|
125
126
|
}
|
126
127
|
else {
|
127
128
|
console.warn("No dom overlay root found, HTML overlays on top of screen-based AR will not work.");
|
@@ -712,7 +713,7 @@
|
|
712
713
|
const pose = hit.getPose(referenceSpace);
|
713
714
|
|
714
715
|
if (this.sessionRoot) {
|
715
|
-
const didPlace = this.sessionRoot.onUpdate(this.webxr.Rig, session, pose);
|
716
|
+
const didPlace = this.sessionRoot.onUpdate(this.webxr.Rig, session, hit, pose);
|
716
717
|
this.didPlaceARSessionRoot = didPlace;
|
717
718
|
}
|
718
719
|
|
@@ -730,7 +731,7 @@
|
|
730
731
|
}
|
731
732
|
|
732
733
|
} else {
|
733
|
-
this.sessionRoot?.onUpdate(this.webxr.Rig, session, null);
|
734
|
+
this.sessionRoot?.onUpdate(this.webxr.Rig, session, null, null);
|
734
735
|
if (this.reticle)
|
735
736
|
this.reticle.visible = false;
|
736
737
|
}
|