@@ -189,6 +189,7 @@
|
|
189
189
|
import { UsageMarker } from "../../engine-components/Interactable";
|
190
190
|
import { USDZExporter } from "../../engine-components/export/usdz/USDZExporter";
|
191
191
|
import { USDZText } from "../../engine-components/export/usdz/extensions/USDZText";
|
192
|
+
import { USDZUIExtension } from "../../engine-components/export/usdz/extensions/USDZUI";
|
192
193
|
import { VariantAction } from "../../engine-components/export/usdz/extensions/behavior/Actions";
|
193
194
|
import { VelocityOverLifetimeModule } from "../../engine-components/ParticleSystemModules";
|
194
195
|
import { VerticalLayoutGroup } from "../../engine-components/ui/Layout";
|
@@ -406,6 +407,7 @@
|
|
406
407
|
TypeStore.add("UsageMarker", UsageMarker);
|
407
408
|
TypeStore.add("USDZExporter", USDZExporter);
|
408
409
|
TypeStore.add("USDZText", USDZText);
|
410
|
+
TypeStore.add("USDZUIExtension", USDZUIExtension);
|
409
411
|
TypeStore.add("VariantAction", VariantAction);
|
410
412
|
TypeStore.add("VelocityOverLifetimeModule", VelocityOverLifetimeModule);
|
411
413
|
TypeStore.add("VerticalLayoutGroup", VerticalLayoutGroup);
|
@@ -116,8 +116,10 @@
|
|
116
116
|
}
|
117
117
|
}
|
118
118
|
|
119
|
-
onPointerClick(
|
119
|
+
onPointerClick(args: PointerEventData) {
|
120
120
|
if (!this.interactable) return;
|
121
|
+
// Button clicks should only run with left mouse button
|
122
|
+
if(args.pointerId !== 0) return;
|
121
123
|
if (debug) {
|
122
124
|
console.warn("Button Click", this.onClick);
|
123
125
|
showBalloonMessage("CLICKED button " + this.name + " at " + this.context.time.frameCount);
|
@@ -184,6 +184,7 @@
|
|
184
184
|
export { UsageMarker } from "../Interactable";
|
185
185
|
export { USDZExporter } from "../export/usdz/USDZExporter";
|
186
186
|
export { USDZText } from "../export/usdz/extensions/USDZText";
|
187
|
+
export { USDZUIExtension } from "../export/usdz/extensions/USDZUI";
|
187
188
|
export { VariantAction } from "../export/usdz/extensions/behavior/Actions";
|
188
189
|
export { VelocityOverLifetimeModule } from "../ParticleSystemModules";
|
189
190
|
export { VerticalLayoutGroup } from "../ui/Layout";
|
@@ -538,7 +538,7 @@
|
|
538
538
|
this._onBeforeRenderListeners.set(target.uuid, []);
|
539
539
|
target.onBeforeRender = this._createRenderCallbackWrapper(target, this._onBeforeRenderListeners);
|
540
540
|
}
|
541
|
-
this._onBeforeRenderListeners.get(target.uuid)
|
541
|
+
this._onBeforeRenderListeners.get(target.uuid)!.push(callback);
|
542
542
|
}
|
543
543
|
removeBeforeRenderListener(target: Object3D, callback: OnRenderCallback) {
|
544
544
|
if (this._onBeforeRenderListeners.has(target.uuid)) {
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Context } from "./engine_setup";
|
2
|
-
import { getParam, isMozillaXR } from "./engine_utils";
|
2
|
+
import { getParam, isMobileDevice, isMozillaXR } from "./engine_utils";
|
3
3
|
|
4
4
|
const debug = getParam("debugaroverlay");
|
5
5
|
export const arContainerClassName = "ar";
|
@@ -21,6 +21,7 @@
|
|
21
21
|
|
22
22
|
private _createdAROnlyElements: Array<any> = [];
|
23
23
|
private _reparentedObjects: Array<{ el: Element, previousParent: HTMLElement | null }> = [];
|
24
|
+
private contentElement: HTMLElement | null = null;
|
24
25
|
|
25
26
|
requestEndAR() {
|
26
27
|
this.onRequestedEndAR();
|
@@ -30,13 +31,16 @@
|
|
30
31
|
this.currentSession = session;
|
31
32
|
this.arContainer = overlayContainer;
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
34
|
+
if (isMozillaXR()) {
|
35
|
+
const arElements = context.domElement!.children;
|
36
|
+
for (let i = 0; i < arElements?.length; i++) {
|
37
|
+
const el = arElements[i];
|
38
|
+
if (!el) return;
|
39
|
+
if (el === this.arContainer) return;
|
40
|
+
this._reparentedObjects.push({ el: el, previousParent: el.parentElement });
|
41
|
+
this.arContainer?.appendChild(el);
|
42
|
+
}
|
43
|
+
}
|
40
44
|
|
41
45
|
const quit_Elements = overlayContainer.getElementsByClassName(quitARClassName);
|
42
46
|
if (!quit_Elements || quit_Elements.length <= 0) {
|
@@ -91,46 +95,20 @@
|
|
91
95
|
}
|
92
96
|
}
|
93
97
|
|
94
|
-
findOrCreateARContainer(element: HTMLElement): HTMLElement {
|
95
|
-
if(debug) console.log("findOrCreateARContainer");
|
96
|
-
// search in the needle-engine element
|
97
|
-
if (element.classList.contains(arContainerClassName)) {
|
98
|
-
if(debug) console.log("Found overlay container in needle-engine element");
|
99
|
-
return element;
|
100
|
-
}
|
101
|
-
if (element.shadowRoot) {
|
102
|
-
const el = element.shadowRoot!.querySelector(`.${arContainerClassName}`);
|
103
|
-
if (el) {
|
104
|
-
if(debug) console.log("Found overlay container in needle-engine element");
|
105
|
-
return el as HTMLElement;
|
106
|
-
};
|
107
|
-
}
|
108
98
|
|
109
|
-
|
110
|
-
|
111
|
-
if (arElements && arElements.length > 0){
|
112
|
-
if(debug) console.log("Found overlay container in document");
|
113
|
-
return arElements[0] as HTMLElement;
|
114
|
-
}
|
99
|
+
createOverlayContainer(needleEngineElement: HTMLElement): HTMLElement {
|
100
|
+
if (this.contentElement) return this.contentElement;
|
115
101
|
|
116
102
|
if (debug)
|
117
|
-
console.log("
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
display: flex;
|
127
|
-
visibility: visible;
|
128
|
-
z-index: 9999;
|
129
|
-
pointer-events: none;
|
130
|
-
// background: rgba(0,0,0,1);
|
131
|
-
`;
|
132
|
-
if(debug) this.createFallbackCloseARButton(element);
|
133
|
-
return this.appendElement(el, element) as HTMLElement;
|
103
|
+
console.log("Setup overlay container");
|
104
|
+
|
105
|
+
const contentElement = needleEngineElement.shadowRoot!.querySelector(".content") as HTMLElement;
|
106
|
+
this.contentElement = contentElement;
|
107
|
+
|
108
|
+
const overlaySlot = needleEngineElement.shadowRoot!.querySelector(".overlay-content");
|
109
|
+
if (overlaySlot) contentElement.appendChild(overlaySlot);
|
110
|
+
if (debug && !isMobileDevice()) this.createFallbackCloseARButton(contentElement);
|
111
|
+
return contentElement;
|
134
112
|
}
|
135
113
|
|
136
114
|
private onRequestedEndAR() {
|
@@ -147,30 +125,41 @@
|
|
147
125
|
private createFallbackCloseARButton(element: HTMLElement) {
|
148
126
|
const quitARSlot = document.createElement("slot");
|
149
127
|
quitARSlot.setAttribute("name", "quit-ar");
|
150
|
-
this.appendElement(quitARSlot, element);
|
151
|
-
if(debug) quitARSlot.addEventListener('click', () => console.log("Clicked fallback close button"));
|
152
|
-
quitARSlot.addEventListener('click', this.closeARCallback);
|
128
|
+
this.appendElement(quitARSlot, element);
|
129
|
+
if (debug) quitARSlot.addEventListener('click', () => console.log("Clicked fallback close button"));
|
130
|
+
quitARSlot.addEventListener('click', this.closeARCallback);
|
153
131
|
this._createdAROnlyElements.push(quitARSlot);
|
154
132
|
// for mozilla XR reparenting we have to make sure the close button is clickable so we set it on the element directly
|
155
133
|
// it's in general perhaps more safe to set it on the element to ensure it's clickable
|
156
134
|
quitARSlot.style.pointerEvents = "auto";
|
157
135
|
|
136
|
+
// we need another container to make sure the button is always on top
|
137
|
+
const fixedButtonContainer = document.createElement("div");
|
138
|
+
fixedButtonContainer.style.cssText = `
|
139
|
+
position: fixed;
|
140
|
+
top: 0;
|
141
|
+
right: 0;
|
142
|
+
z-index: 600;
|
143
|
+
pointer-events: all;
|
144
|
+
`;
|
145
|
+
this.appendElement(fixedButtonContainer, quitARSlot);
|
146
|
+
|
158
147
|
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
159
148
|
svg.classList.add("quit-ar-button");
|
160
149
|
svg.setAttribute('width', "38px");
|
161
150
|
svg.setAttribute('height', "38px");
|
162
|
-
|
151
|
+
fixedButtonContainer.appendChild(svg);
|
163
152
|
|
164
153
|
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
165
154
|
path.setAttribute('d', 'M 12,12 L 28,28 M 28,12 12,28');
|
166
|
-
path.setAttribute('stroke', '#
|
155
|
+
path.setAttribute('stroke', '#aaa');
|
167
156
|
path.setAttribute('stroke-width', "3px");
|
168
157
|
svg.appendChild(path);
|
169
|
-
if(debug) console.log("Created fallback close button", svg, element);
|
158
|
+
if (debug) console.log("Created fallback close button", svg, element);
|
170
159
|
}
|
171
160
|
|
172
161
|
private appendElement(element: Element, parent: HTMLElement) {
|
173
|
-
if(parent.shadowRoot) return parent.shadowRoot.appendChild(element);
|
162
|
+
if (parent.shadowRoot) return parent.shadowRoot.appendChild(element);
|
174
163
|
return parent.appendChild(element);
|
175
164
|
}
|
176
165
|
|
@@ -109,6 +109,17 @@
|
|
109
109
|
user-select: none;
|
110
110
|
touch-action: none;
|
111
111
|
}
|
112
|
+
:host .content {
|
113
|
+
position: fixed;
|
114
|
+
visibility: visible;
|
115
|
+
z-index: 500; /* < must be less than the webxr buttons element */
|
116
|
+
pointer-events: none;
|
117
|
+
}
|
118
|
+
:host .overlay-content {
|
119
|
+
position: absolute;
|
120
|
+
user-select: auto;
|
121
|
+
pointer-events: all;
|
122
|
+
}
|
112
123
|
:host slot[name="quit-ar"]:hover {
|
113
124
|
cursor: pointer;
|
114
125
|
}
|
@@ -120,6 +131,9 @@
|
|
120
131
|
}
|
121
132
|
</style>
|
122
133
|
<canvas></canvas>
|
134
|
+
<div class="content">
|
135
|
+
<slot class="overlay-content"></slot>
|
136
|
+
</div>
|
123
137
|
`;
|
124
138
|
|
125
139
|
if (this.shadowRoot)
|
@@ -407,7 +421,7 @@
|
|
407
421
|
}
|
408
422
|
|
409
423
|
getAROverlayContainer(): HTMLElement {
|
410
|
-
return this._overlay_ar.
|
424
|
+
return this._overlay_ar.createOverlayContainer(this);
|
411
425
|
}
|
412
426
|
|
413
427
|
getVROverlayContainer(): HTMLElement | null {
|
@@ -473,13 +487,6 @@
|
|
473
487
|
private setupElementsForMode(el: HTMLElement, currentSessionType: string, _session: XRSession | null = null) {
|
474
488
|
if (el === this._context?.renderer.domElement) return;
|
475
489
|
if (el.id === "VRButton" || el.id === "ARButton") return;
|
476
|
-
el.style.position = "absolute";
|
477
|
-
// el.style.zIndex = "100";
|
478
|
-
// ch.style.width = "100hv";
|
479
|
-
// ch.style.height = "100hv";
|
480
|
-
// set pointer events to none by default (if not explicitly declared)
|
481
|
-
// if (!el.style.pointerEvents)
|
482
|
-
// el.style.pointerEvents = "none";
|
483
490
|
|
484
491
|
const classList = el.classList;
|
485
492
|
if (classList.contains(currentSessionType)) {
|
@@ -127,6 +127,7 @@
|
|
127
127
|
MeshUIHelper.resetLastSelected();
|
128
128
|
const opts = new PointerEventData(this.context.input);
|
129
129
|
opts.inputSource = ctrl;
|
130
|
+
opts.pointerId = 0;
|
130
131
|
opts.isDown = ctrl.selectionDown;
|
131
132
|
opts.isUp = ctrl.selectionUp;
|
132
133
|
opts.isPressed = ctrl.selectionPressed;
|
@@ -136,10 +137,11 @@
|
|
136
137
|
args.grab = null;
|
137
138
|
};
|
138
139
|
}
|
139
|
-
this._selectEndFn ??= (ctrl, args: { grab: THREE.Object3D }) => {
|
140
|
+
this._selectEndFn ??= (ctrl: WebXRController, args: { grab: THREE.Object3D }) => {
|
140
141
|
if (!args.grab) return;
|
141
142
|
const opts = new PointerEventData(this.context.input);
|
142
143
|
opts.inputSource = ctrl;
|
144
|
+
opts.pointerId = 0;
|
143
145
|
opts.isDown = ctrl.selectionDown;
|
144
146
|
opts.isUp = ctrl.selectionUp;
|
145
147
|
opts.isPressed = ctrl.selectionPressed;
|
@@ -15,7 +15,7 @@
|
|
15
15
|
import { NEEDLE_progressive } from "./NEEDLE_progressive";
|
16
16
|
import { InternalUsageTrackerPlugin } from "./usage_tracker";
|
17
17
|
import { isUsageTrackingEnabled } from "../engine_assetdatabase";
|
18
|
-
import { GLTFLoaderPlugin } from "three/examples/jsm/loaders/GLTFLoader";
|
18
|
+
import { GLTFLoaderPlugin } from "three/examples/jsm/loaders/GLTFLoader.js";
|
19
19
|
// import { GLTFAnimationPointerExtension } from "three/examples/jsm/loaders/GLTFLoaderAnimationPointer";
|
20
20
|
|
21
21
|
// lazily import the GLTFAnimationPointerExtension in case it doesnt exist (e.g. using vanilla three)
|
@@ -3,7 +3,7 @@
|
|
3
3
|
import { RGBAColor } from "../js-extensions/RGBAColor"
|
4
4
|
import { BaseUIComponent } from "./BaseUIComponent";
|
5
5
|
import { serializable } from '../../engine/engine_serialization_decorator';
|
6
|
-
import { Color,
|
6
|
+
import { Color, LinearSRGBColorSpace, SRGBColorSpace, Texture } from 'three';
|
7
7
|
import { RectTransform } from './RectTransform';
|
8
8
|
import { onChange, scheduleAction } from "./Utils"
|
9
9
|
import { GameObject } from '../Component';
|
@@ -191,12 +191,12 @@
|
|
191
191
|
this.setOptions({ backgroundOpacity: 0 });
|
192
192
|
if (tex) {
|
193
193
|
// workaround for https://github.com/needle-tools/needle-engine-support/issues/109
|
194
|
-
if (tex.
|
194
|
+
if (tex.colorSpace === SRGBColorSpace) {
|
195
195
|
if (Graphic.textureCache.has(tex)) {
|
196
196
|
tex = Graphic.textureCache.get(tex)!;
|
197
197
|
} else {
|
198
198
|
const clone = tex.clone();
|
199
|
-
clone.
|
199
|
+
clone.colorSpace = LinearSRGBColorSpace;
|
200
200
|
Graphic.textureCache.set(tex, clone);
|
201
201
|
tex = clone;
|
202
202
|
}
|
@@ -352,7 +352,7 @@
|
|
352
352
|
|
353
353
|
if (this.isMultiMaterialObject(this.gameObject)) {
|
354
354
|
for (const child of this.gameObject.children) {
|
355
|
-
this.context.addBeforeRenderListener(child, this.onBeforeRenderThree
|
355
|
+
this.context.addBeforeRenderListener(child, this.onBeforeRenderThree);
|
356
356
|
child.layers.mask = this.gameObject.layers.mask;
|
357
357
|
}
|
358
358
|
|
@@ -376,14 +376,13 @@
|
|
376
376
|
}
|
377
377
|
// TODO: custom shader with sub materials
|
378
378
|
else if (this.isMeshOrSkinnedMesh(this.gameObject)) {
|
379
|
+
this.context.addBeforeRenderListener(this.gameObject, this.onBeforeRenderThree);
|
379
380
|
|
380
|
-
this.context.addBeforeRenderListener(this.gameObject, this.onBeforeRenderThree.bind(this));
|
381
|
-
|
382
381
|
if (this.renderOrder !== undefined && this.renderOrder.length > 0)
|
383
382
|
this.gameObject.renderOrder = this.renderOrder[0];
|
384
383
|
}
|
385
384
|
else {
|
386
|
-
this.context.addBeforeRenderListener(this.gameObject, this.onBeforeRenderThree
|
385
|
+
this.context.addBeforeRenderListener(this.gameObject, this.onBeforeRenderThree);
|
387
386
|
}
|
388
387
|
|
389
388
|
this.applyLightmapping();
|
@@ -415,7 +414,7 @@
|
|
415
414
|
const mat = this.gameObject["material"];
|
416
415
|
if (!mat?.isMeshBasicMaterial) {
|
417
416
|
if (this._lightmaps.length <= 0) {
|
418
|
-
const rm = new RendererLightmap(this.gameObject, this.context);
|
417
|
+
const rm = new RendererLightmap(this.gameObject as any as Mesh, this.context);
|
419
418
|
this._lightmaps.push(rm);
|
420
419
|
}
|
421
420
|
const rm = this._lightmaps[0];
|
@@ -434,16 +433,12 @@
|
|
434
433
|
if (!child["material"]?.isMeshBasicMaterial) {
|
435
434
|
let rm: RendererLightmap | undefined = undefined;
|
436
435
|
if (i >= this._lightmaps.length) {
|
437
|
-
rm = new RendererLightmap(child as
|
436
|
+
rm = new RendererLightmap(child as Mesh, this.context);
|
438
437
|
this._lightmaps.push(rm);
|
439
438
|
}
|
440
439
|
else
|
441
440
|
rm = this._lightmaps[i];
|
442
441
|
rm.init(this.lightmapIndex, this.lightmapScaleOffset, tex);
|
443
|
-
// onBeforeRender is not called when the renderer is on a group
|
444
|
-
// this is an issue we probably also need to handle for custom shaders
|
445
|
-
// and need a better solution, but for now this fixes lightmaps for multimaterial objects
|
446
|
-
rm.bindOnBeforeRender();
|
447
442
|
}
|
448
443
|
}
|
449
444
|
}
|
@@ -529,7 +524,6 @@
|
|
529
524
|
}
|
530
525
|
|
531
526
|
this.updateReflectionProbe();
|
532
|
-
|
533
527
|
}
|
534
528
|
|
535
529
|
onDisable() {
|
@@ -542,13 +536,22 @@
|
|
542
536
|
|
543
537
|
onDestroy(): void {
|
544
538
|
this.handles = null;
|
539
|
+
|
540
|
+
if (this.isMultiMaterialObject(this.gameObject)) {
|
541
|
+
for (const child of this.gameObject.children) {
|
542
|
+
this.context.removeBeforeRenderListener(child, this.onBeforeRenderThree);
|
543
|
+
}
|
544
|
+
}
|
545
|
+
else {
|
546
|
+
this.context.removeBeforeRenderListener(this.gameObject, this.onBeforeRenderThree);
|
547
|
+
}
|
548
|
+
|
545
549
|
}
|
546
550
|
|
547
551
|
applyStencil() {
|
548
552
|
NEEDLE_render_objects.applyStencil(this);
|
549
553
|
}
|
550
554
|
|
551
|
-
|
552
555
|
onBeforeRender() {
|
553
556
|
if (!this.gameObject) {
|
554
557
|
return;
|
@@ -611,8 +614,10 @@
|
|
611
614
|
}
|
612
615
|
|
613
616
|
}
|
614
|
-
onBeforeRenderThree(_renderer, _scene, _camera, _geometry, material, _group) {
|
615
617
|
|
618
|
+
|
619
|
+
private onBeforeRenderThree = (_renderer, _scene, _camera, _geometry, material, _group) => {
|
620
|
+
|
616
621
|
this.loadProgressiveTextures(material);
|
617
622
|
|
618
623
|
if (material.envMapIntensity !== undefined) {
|
@@ -656,7 +661,7 @@
|
|
656
661
|
|
657
662
|
if (this._lightmaps) {
|
658
663
|
for (const lm of this._lightmaps) {
|
659
|
-
lm.
|
664
|
+
lm.updateLightmapUniforms(material);
|
660
665
|
}
|
661
666
|
}
|
662
667
|
}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Behaviour, GameObject } from "./Component";
|
2
|
-
import { Material, Mesh, ShaderMaterial, Texture, Vector4 } from "three";
|
2
|
+
import { Material, Mesh, Shader, ShaderMaterial, Texture, Vector4 } from "three";
|
3
3
|
import { Context, OnRenderCallback } from "../engine/engine_setup";
|
4
4
|
import { getParam } from "../engine/engine_utils";
|
5
5
|
|
@@ -24,12 +24,12 @@
|
|
24
24
|
lightmapScaleOffset: THREE.Vector4 = new Vector4(1, 1, 0, 0);
|
25
25
|
|
26
26
|
private context: Context;
|
27
|
-
private gameObject:
|
27
|
+
private gameObject: Mesh;
|
28
28
|
private lightmapTexture: Texture | null = null;
|
29
29
|
private lightmapScaleOffsetUniform = { value: new Vector4(1, 1, 0, 0) };
|
30
30
|
private lightmapUniform: { value: Texture | null } = { value: null };
|
31
31
|
|
32
|
-
constructor(gameObject:
|
32
|
+
constructor(gameObject: Mesh, context: Context) {
|
33
33
|
this.gameObject = gameObject;
|
34
34
|
this.context = context;
|
35
35
|
}
|
@@ -46,19 +46,21 @@
|
|
46
46
|
this.applyLightmap();
|
47
47
|
}
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
updateLightmapUniforms(material: Material) {
|
50
|
+
const uniforms = material["uniforms"];
|
51
|
+
if (uniforms && uniforms.lightmap) {
|
52
|
+
this.lightmapScaleOffsetUniform.value = this.lightmapScaleOffset;
|
53
|
+
this.lightmapUniform.value = this.lightmapTexture;
|
54
|
+
uniforms.lightmap = this.lightmapUniform;
|
55
|
+
uniforms.lightmapScaleOffset = this.lightmapScaleOffsetUniform;
|
56
|
+
}
|
52
57
|
}
|
53
58
|
|
54
|
-
private onBeforeRenderThreeComplete = (_renderer, _scene, _camera, _geometry, material, _group) => {
|
55
|
-
this.onBeforeRenderThree(material);
|
56
|
-
}
|
57
59
|
|
58
60
|
private applyLightmap() {
|
59
|
-
|
60
61
|
if (this.gameObject.type === "Object3D") {
|
61
|
-
|
62
|
+
if (debug)
|
63
|
+
console.warn("Can not add lightmap. Is this object missing a renderer?", this.gameObject.name);
|
62
64
|
return;
|
63
65
|
}
|
64
66
|
|
@@ -70,23 +72,27 @@
|
|
70
72
|
console.assert(this.gameObject.type === "Mesh", "Lightmap only works on meshes", this);
|
71
73
|
|
72
74
|
const mesh = this.gameObject as unknown as Mesh;
|
73
|
-
// TODO: ensure uv2 exists
|
74
75
|
if (!mesh.geometry.getAttribute("uv1"))
|
75
76
|
mesh.geometry.setAttribute("uv1", mesh.geometry.getAttribute("uv"));
|
76
77
|
|
77
|
-
|
78
|
-
|
78
|
+
if (Array.isArray(this.gameObject.material)) {
|
79
|
+
const mats: Material[] = this.gameObject.material;
|
80
|
+
for (let i = 0; i < mats.length; i++) {
|
81
|
+
const mat = mats[i];
|
82
|
+
const cloned = mat.clone();
|
83
|
+
mats[i] = cloned;
|
84
|
+
cloned.onBeforeCompile = this.onBeforeCompile;
|
85
|
+
}
|
86
|
+
}
|
87
|
+
else {
|
88
|
+
const mat: Material = this.gameObject.material.clone();
|
89
|
+
this.gameObject.material = mat;
|
90
|
+
this.gameObject.material.onBeforeCompile = this.onBeforeCompile;
|
91
|
+
}
|
79
92
|
|
80
|
-
this.gameObject["material"].onBeforeCompile = (shader, _) => {
|
81
|
-
shader.lightMapUv = "uv1";
|
82
|
-
shader.uniforms.lightmap = this.lightmapUniform;
|
83
|
-
shader.uniforms.lightmapScaleOffset = this.lightmapScaleOffsetUniform;
|
84
|
-
};
|
85
|
-
|
86
|
-
|
87
93
|
if (this.lightmapIndex >= 0) {
|
88
94
|
const lightmap = this.lightmapTexture;
|
89
|
-
const mat = this.gameObject
|
95
|
+
const mat = this.gameObject.material as any;
|
90
96
|
if (mat && lightmap) {
|
91
97
|
if (!mat.uniforms) mat.uniforms = {};
|
92
98
|
mat.lightMap = lightmap;
|
@@ -95,15 +101,14 @@
|
|
95
101
|
}
|
96
102
|
}
|
97
103
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
}
|
104
|
+
private onBeforeCompile = (shader: Shader, _) => {
|
105
|
+
if(debug) console.log("Lightmaps, before compile")
|
106
|
+
//@ts-ignore
|
107
|
+
shader.lightMapUv = "uv1";
|
108
|
+
this.lightmapScaleOffsetUniform.value = this.lightmapScaleOffset;
|
109
|
+
this.lightmapUniform.value = this.lightmapTexture;
|
110
|
+
shader.uniforms.lightmap = this.lightmapUniform;
|
111
|
+
shader.uniforms.lightmapScaleOffset = this.lightmapScaleOffsetUniform;
|
107
112
|
}
|
108
113
|
|
109
114
|
private setLightmapDebugMaterial() {
|
@@ -657,7 +657,7 @@
|
|
657
657
|
|
658
658
|
if ( geometry ) {
|
659
659
|
|
660
|
-
if ( material.isMeshStandardMaterial
|
660
|
+
if ( material.isMeshStandardMaterial || material.isMeshBasicMaterial ) { // TODO convert unlit to lit+emissive
|
661
661
|
|
662
662
|
const geometryFileName = 'geometries/Geometry_' + geometry.id + '.usd';
|
663
663
|
|
@@ -1124,7 +1124,7 @@
|
|
1124
1124
|
|
1125
1125
|
}
|
1126
1126
|
|
1127
|
-
function buildMaterial( material:
|
1127
|
+
function buildMaterial( material: MeshBasicMaterial, textures, quickLookCompatible = false ) {
|
1128
1128
|
|
1129
1129
|
// https://graphics.pixar.com/usd/docs/UsdPreviewSurface-Proposal.html
|
1130
1130
|
|
@@ -1182,7 +1182,9 @@
|
|
1182
1182
|
|
1183
1183
|
const needsTextureScale = mapType !== 'normal' && (color && (color.r !== 1 || color.g !== 1 || color.b !== 1 || opacity !== 1)) || false;
|
1184
1184
|
const needsNormalScaleAndBias = mapType === 'normal';
|
1185
|
-
const normalScaleValueString =
|
1185
|
+
const normalScaleValueString = material instanceof MeshStandardMaterial
|
1186
|
+
? (material.normalScale ? material.normalScale.x * 2 : 2).toFixed( PRECISION )
|
1187
|
+
: "1";
|
1186
1188
|
|
1187
1189
|
return `
|
1188
1190
|
${needsTextureTransform ? `def Shader "Transform2d_${mapType}" (
|
@@ -1254,73 +1256,77 @@
|
|
1254
1256
|
|
1255
1257
|
}
|
1256
1258
|
|
1257
|
-
if ( material.
|
1259
|
+
if ( material.aoMap ) {
|
1258
1260
|
|
1259
|
-
inputs.push( `${pad}
|
1261
|
+
inputs.push( `${pad}float inputs:occlusion.connect = </Materials/Material_${material.id}/Texture_${material.aoMap.id}_occlusion.outputs:r>` );
|
1260
1262
|
|
1261
|
-
samplers.push( buildTexture( material.
|
1263
|
+
samplers.push( buildTexture( material.aoMap, 'occlusion' ) );
|
1262
1264
|
|
1263
|
-
}
|
1265
|
+
}
|
1264
1266
|
|
1265
|
-
|
1267
|
+
if ( material.alphaMap ) {
|
1266
1268
|
|
1269
|
+
inputs.push( `${pad}float inputs:opacity.connect = </Materials/Material_${material.id}/Texture_${material.alphaMap.id}_opacity.outputs:r>` );
|
1270
|
+
inputs.push( `${pad}float inputs:opacityThreshold = 0.0001` );
|
1271
|
+
|
1272
|
+
samplers.push( buildTexture( material.alphaMap, 'opacity' ) );
|
1273
|
+
|
1267
1274
|
} else {
|
1268
|
-
|
1269
|
-
inputs.push( `${pad}color3f inputs:emissiveColor = (0, 0, 0)` );
|
1270
1275
|
|
1276
|
+
inputs.push( `${pad}float inputs:opacity = ${effectiveOpacity}` );
|
1277
|
+
|
1271
1278
|
}
|
1272
1279
|
|
1273
|
-
if ( material
|
1280
|
+
if ( material instanceof MeshStandardMaterial ) {
|
1274
1281
|
|
1275
|
-
|
1282
|
+
if ( material.emissiveMap ) {
|
1276
1283
|
|
1277
|
-
|
1284
|
+
inputs.push( `${pad}color3f inputs:emissiveColor.connect = </Materials/Material_${material.id}/Texture_${material.emissiveMap.id}_emissive.outputs:rgb>` );
|
1278
1285
|
|
1279
|
-
|
1286
|
+
samplers.push( buildTexture( material.emissiveMap, 'emissive' ) );
|
1280
1287
|
|
1281
|
-
|
1288
|
+
} else if ( material.emissive?.getHex() > 0 ) {
|
1282
1289
|
|
1283
|
-
|
1290
|
+
inputs.push( `${pad}color3f inputs:emissiveColor = ${buildColor( material.emissive )}` );
|
1284
1291
|
|
1285
|
-
|
1292
|
+
} else {
|
1293
|
+
|
1294
|
+
inputs.push( `${pad}color3f inputs:emissiveColor = (0, 0, 0)` );
|
1286
1295
|
|
1287
|
-
|
1296
|
+
}
|
1288
1297
|
|
1289
|
-
|
1298
|
+
if ( material.normalMap ) {
|
1290
1299
|
|
1291
|
-
|
1300
|
+
inputs.push( `${pad}normal3f inputs:normal.connect = </Materials/Material_${material.id}/Texture_${material.normalMap.id}_normal.outputs:rgb>` );
|
1292
1301
|
|
1293
|
-
|
1302
|
+
samplers.push( buildTexture( material.normalMap, 'normal' ) );
|
1294
1303
|
|
1295
|
-
|
1304
|
+
}
|
1296
1305
|
|
1297
|
-
|
1306
|
+
if ( material.roughnessMap && material.roughness === 1 ) {
|
1298
1307
|
|
1299
|
-
|
1308
|
+
inputs.push( `${pad}float inputs:roughness.connect = </Materials/Material_${material.id}/Texture_${material.roughnessMap.id}_roughness.outputs:g>` );
|
1300
1309
|
|
1301
|
-
|
1310
|
+
samplers.push( buildTexture( material.roughnessMap, 'roughness' ) );
|
1302
1311
|
|
1303
|
-
|
1312
|
+
} else {
|
1304
1313
|
|
1305
|
-
|
1314
|
+
inputs.push( `${pad}float inputs:roughness = ${material.roughness !== undefined ? material.roughness : 1 }` );
|
1306
1315
|
|
1307
|
-
|
1316
|
+
}
|
1308
1317
|
|
1309
|
-
|
1318
|
+
if ( material.metalnessMap && material.metalness === 1 ) {
|
1310
1319
|
|
1311
|
-
|
1320
|
+
inputs.push( `${pad}float inputs:metallic.connect = </Materials/Material_${material.id}/Texture_${material.metalnessMap.id}_metallic.outputs:b>` );
|
1312
1321
|
|
1313
|
-
|
1322
|
+
samplers.push( buildTexture( material.metalnessMap, 'metallic' ) );
|
1314
1323
|
|
1315
|
-
|
1316
|
-
inputs.push( `${pad}float inputs:opacityThreshold = 0.0001` );
|
1324
|
+
} else {
|
1317
1325
|
|
1318
|
-
|
1326
|
+
inputs.push( `${pad}float inputs:metallic = ${material.metalness !== undefined ? material.metalness : 0 }` );
|
1319
1327
|
|
1320
|
-
|
1328
|
+
}
|
1321
1329
|
|
1322
|
-
inputs.push( `${pad}float inputs:opacity = ${effectiveOpacity}` );
|
1323
|
-
|
1324
1330
|
}
|
1325
1331
|
|
1326
1332
|
if ( material instanceof MeshPhysicalMaterial ) {
|
@@ -16,6 +16,7 @@
|
|
16
16
|
import { BehaviorExtension } from "./extensions/behavior/Behaviour";
|
17
17
|
import { AudioExtension } from "./extensions/behavior/AudioExtension";
|
18
18
|
import { TextExtension } from "./extensions/USDZText";
|
19
|
+
import { USDZUIExtension } from "./extensions/USDZUI";
|
19
20
|
import { Renderer } from "../../Renderer"
|
20
21
|
|
21
22
|
const debug = getParam("debugusdz");
|
@@ -97,6 +98,7 @@
|
|
97
98
|
this.extensions.push(new BehaviorExtension());
|
98
99
|
this.extensions.push(new AudioExtension());
|
99
100
|
this.extensions.push(new TextExtension());
|
101
|
+
this.extensions.push(new USDZUIExtension());
|
100
102
|
}
|
101
103
|
}
|
102
104
|
|
@@ -146,8 +146,13 @@
|
|
146
146
|
return "text";
|
147
147
|
}
|
148
148
|
|
149
|
-
|
149
|
+
// HACK we should clean this up, text export has moved to USDZUI.ts and is
|
150
|
+
// integrated into the hierarchy now
|
151
|
+
onExportObject(_object: Object3D, _model: USDObject, _context: USDZExporterContext) {
|
150
152
|
|
153
|
+
return;
|
154
|
+
|
155
|
+
/*
|
151
156
|
const text = GameObject.getComponent(object, Text);
|
152
157
|
if (text) {
|
153
158
|
const rt = GameObject.getComponent(object, RectTransform);
|
@@ -186,8 +191,43 @@
|
|
186
191
|
textObj.writeTo(undefined, writer);
|
187
192
|
});
|
188
193
|
}
|
194
|
+
*/
|
189
195
|
}
|
190
196
|
|
197
|
+
exportText(object: Object3D, newModel: USDObject, _context: USDZExporterContext) {
|
198
|
+
|
199
|
+
const text = GameObject.getComponent(object, Text);
|
200
|
+
if (!text) return;
|
201
|
+
|
202
|
+
const rt = GameObject.getComponent(object, RectTransform);
|
203
|
+
let width = 100;
|
204
|
+
let height = 100;
|
205
|
+
if (rt) {
|
206
|
+
width = rt.width;
|
207
|
+
height = rt.height;
|
208
|
+
}
|
209
|
+
|
210
|
+
newModel.matrix = rotateYAxisMatrix.clone();
|
211
|
+
if (rt) // Not ideal but works for now:
|
212
|
+
newModel.matrix.premultiply(invertX);
|
213
|
+
|
214
|
+
const color = new Color().copySRGBToLinear(text.color);
|
215
|
+
newModel.material = new MeshStandardMaterial({ color: color, emissive: color });
|
216
|
+
|
217
|
+
newModel.addEventListener("serialize", (writer: USDWriter, _context: USDZExporterContext) => {
|
218
|
+
let txt = text.text;
|
219
|
+
txt = txt.replace(/\n/g, "\\n");
|
220
|
+
const textObj = TextBuilder.multiLine(txt, width, height, HorizontalAlignment.center, VerticalAlignment.bottom, TextWrapMode.flowing);
|
221
|
+
this.setTextAlignment(textObj, text.alignment);
|
222
|
+
this.setOverflow(textObj, text);
|
223
|
+
if (newModel.material)
|
224
|
+
textObj.material = newModel.material;
|
225
|
+
textObj.pointSize = this.convertToTextSize(text.fontSize);
|
226
|
+
textObj.depth = .001;
|
227
|
+
textObj.writeTo(undefined, writer);
|
228
|
+
});
|
229
|
+
}
|
230
|
+
|
191
231
|
private convertToTextSize(pixel: number) {
|
192
232
|
return 1 / 0.0502 * 144 * pixel;
|
193
233
|
}
|
@@ -370,7 +370,7 @@
|
|
370
370
|
if (xrRig) {
|
371
371
|
// make it match unity forward
|
372
372
|
this.rig = xrRig.gameObject;
|
373
|
-
|
373
|
+
this.rig.rotateY(Math.PI);
|
374
374
|
// this.rig.position.copy(existing.worldPosition);
|
375
375
|
// this.rig.quaternion.premultiply(existing.worldQuaternion);
|
376
376
|
}
|
@@ -0,0 +1,120 @@
|
|
1
|
+
import { IUSDExporterExtension } from "../Extension";
|
2
|
+
import { USDObject, USDZExporterContext } from "../ThreeUSDZExporter";
|
3
|
+
import { GameObject } from "../../../Component";
|
4
|
+
import { Canvas } from "../../../ui/Canvas";
|
5
|
+
import { CanvasGroup } from "../../../ui/CanvasGroup";
|
6
|
+
import { $shadowDomOwner } from "../../../ui/BaseUIComponent";
|
7
|
+
import { RectTransform } from "../../../ui/RectTransform";
|
8
|
+
import { Color, Mesh, MeshBasicMaterial, Object3D } from "three";
|
9
|
+
import { TextExtension } from "./USDZText";
|
10
|
+
import { RenderMode } from "../../../ui/Canvas";
|
11
|
+
|
12
|
+
export class USDZUIExtension implements IUSDExporterExtension {
|
13
|
+
get extensionName(): string {
|
14
|
+
return "tmui";
|
15
|
+
}
|
16
|
+
|
17
|
+
// TODO would probably be better to export each object instead of the entire Canvas
|
18
|
+
// so that we don't export them twice (once as regular hierarchy, once as part of Canvas export)
|
19
|
+
onExportObject(object: Object3D, model: USDObject, _context: USDZExporterContext) {
|
20
|
+
const canvas = GameObject.getComponent(object, Canvas);
|
21
|
+
|
22
|
+
if (canvas && canvas.activeAndEnabled && canvas.renderMode === RenderMode.WorldSpace) {
|
23
|
+
const textExt = new TextExtension();
|
24
|
+
const rt = GameObject.getComponent(object, RectTransform);
|
25
|
+
const canvasGroup = GameObject.getComponent(object, CanvasGroup);
|
26
|
+
|
27
|
+
let width = 100;
|
28
|
+
let height = 100;
|
29
|
+
if (rt) {
|
30
|
+
width = rt.width;
|
31
|
+
height = rt.height;
|
32
|
+
|
33
|
+
const shadowRootModel = USDObject.createEmpty();
|
34
|
+
const shadowComponent = rt.shadowComponent;
|
35
|
+
model.add(shadowRootModel);
|
36
|
+
|
37
|
+
if (shadowComponent) {
|
38
|
+
const mat = shadowComponent.matrix;
|
39
|
+
shadowRootModel.matrix.copy(mat);
|
40
|
+
// shadowRootModel.matrix.premultiply(invertX);
|
41
|
+
|
42
|
+
// TODO build map of parent GOs to USDObjects so we can reparent while traversing
|
43
|
+
const usdObjectMap = new Map<Object3D, USDObject>();
|
44
|
+
const opacityMap = new Map<Object3D, number>();
|
45
|
+
usdObjectMap.set(shadowComponent, shadowRootModel);
|
46
|
+
opacityMap.set(shadowComponent, canvasGroup ? canvasGroup.alpha : 1);
|
47
|
+
|
48
|
+
shadowComponent.traverse((child) => {
|
49
|
+
if (child === shadowComponent) return;
|
50
|
+
|
51
|
+
const childModel = USDObject.createEmpty();
|
52
|
+
childModel.matrix.copy(child.matrix);
|
53
|
+
|
54
|
+
const childParent = child.parent;
|
55
|
+
const isText = childParent && typeof childParent["textContent"] === "string" && childParent["textContent"].length;
|
56
|
+
let hierarchyOpacity = opacityMap.get(childParent!) || 1;
|
57
|
+
|
58
|
+
// TODO CanvasGroup doesn't render something but modifies opacity
|
59
|
+
// get CanvasGroup and modify alpha here
|
60
|
+
|
61
|
+
const canvasGroup = GameObject.getComponent(child, CanvasGroup);
|
62
|
+
if (canvasGroup)
|
63
|
+
hierarchyOpacity *= canvasGroup.alpha;
|
64
|
+
|
65
|
+
if (child instanceof Mesh && isText) {
|
66
|
+
// get shadoDomOwner so we can export Text from the text extension directly
|
67
|
+
const shadowDomOwner = child[$shadowDomOwner].gameObject;
|
68
|
+
textExt.exportText(shadowDomOwner, childModel, _context);
|
69
|
+
}
|
70
|
+
|
71
|
+
if (child instanceof Mesh && !isText)
|
72
|
+
{
|
73
|
+
// UI behaves weird: it seems it's always flipped right now,
|
74
|
+
// and three magically fixes it when rendering
|
75
|
+
// see https://github.com/mrdoob/three.js/pull/12720#issue-275923930
|
76
|
+
// https://github.com/mrdoob/three.js/issues/17361
|
77
|
+
// So we need to fix the winding order after applying the negative scale.
|
78
|
+
const clonedGeo = child.geometry.clone();
|
79
|
+
clonedGeo.scale(1, 1, -1);
|
80
|
+
this.flipWindingOrder(clonedGeo);
|
81
|
+
childModel.geometry = clonedGeo;
|
82
|
+
|
83
|
+
const color = new Color();
|
84
|
+
const ownOpacity = child.material.opacity;
|
85
|
+
color.copy(child.material.color);
|
86
|
+
|
87
|
+
// Calculate opacity from parent chain and own alpha
|
88
|
+
childModel.material = new MeshBasicMaterial({
|
89
|
+
color: color,
|
90
|
+
opacity: ownOpacity * hierarchyOpacity,
|
91
|
+
map: child.material.map,
|
92
|
+
transparent: true,
|
93
|
+
});
|
94
|
+
}
|
95
|
+
|
96
|
+
usdObjectMap.set(child, childModel);
|
97
|
+
opacityMap.set(child, hierarchyOpacity);
|
98
|
+
|
99
|
+
const parentUsdzObject = usdObjectMap.get(childParent!);
|
100
|
+
if (!parentUsdzObject) {
|
101
|
+
console.error("Error when exporting UI: shadow component parent not found!", child, child.parent);
|
102
|
+
return;
|
103
|
+
}
|
104
|
+
parentUsdzObject.add(childModel);
|
105
|
+
});
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
private flipWindingOrder(geometry) {
|
112
|
+
const index = geometry.index.array
|
113
|
+
for (let i = 0, il = index.length / 3; i < il; i++) {
|
114
|
+
let x = index[i * 3]
|
115
|
+
index[i * 3] = index[i * 3 + 2]
|
116
|
+
index[i * 3 + 2] = x
|
117
|
+
}
|
118
|
+
geometry.index.needsUpdate = true
|
119
|
+
}
|
120
|
+
}
|