@needle-tools/engine 4.8.7-next.3c34e8b → 4.8.7-next.830076f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{needle-engine.bundle-BthqMTPI.min.js → needle-engine.bundle-DW7OUhMH.min.js} +129 -129
- package/dist/{needle-engine.bundle-Fj5SzBaF.js → needle-engine.bundle-DlMlXTv_.js} +5700 -5657
- package/dist/{needle-engine.bundle-BgFqEj5W.umd.cjs → needle-engine.bundle-XMeUns-Z.umd.cjs} +131 -131
- package/dist/needle-engine.js +2 -2
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/lib/engine/engine_camera.d.ts +8 -1
- package/lib/engine/engine_camera.js +25 -0
- package/lib/engine/engine_camera.js.map +1 -1
- package/lib/engine/engine_context.d.ts +9 -0
- package/lib/engine/engine_context.js +15 -0
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.attributes.d.ts +1 -0
- package/lib/engine/webcomponents/needle-engine.js +11 -6
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine-components/export/usdz/Extension.d.ts +1 -1
- package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +1 -1
- package/lib/engine-components/export/usdz/ThreeUSDZExporter.js.map +1 -1
- package/lib/engine-components/export/usdz/USDZExporter.d.ts +7 -0
- package/lib/engine-components/export/usdz/USDZExporter.js +8 -1
- package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
- package/lib/engine-components/webxr/WebXRImageTracking.d.ts +3 -1
- package/lib/engine-components/webxr/WebXRImageTracking.js +115 -81
- package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
- package/package.json +1 -1
- package/src/engine/engine_camera.ts +40 -1
- package/src/engine/engine_context.ts +21 -1
- package/src/engine/webcomponents/needle-engine.attributes.ts +2 -0
- package/src/engine/webcomponents/needle-engine.ts +13 -6
- package/src/engine-components/export/usdz/Extension.ts +1 -1
- package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +1 -1
- package/src/engine-components/export/usdz/USDZExporter.ts +21 -12
- package/src/engine-components/webxr/WebXRImageTracking.ts +136 -90
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { Matrix4, Object3D, Quaternion, Vector3 } from "three";
|
|
1
|
+
import { ImageBitmapLoader, Matrix4, Object3D, Quaternion, Vector3 } from "three";
|
|
2
2
|
import { Object3DEventMap } from "three";
|
|
3
3
|
|
|
4
4
|
import { isDevEnvironment, showBalloonWarning } from "../../engine/debug/index.js";
|
|
5
5
|
import { AssetReference } from "../../engine/engine_addressables.js";
|
|
6
6
|
import { serializable } from "../../engine/engine_serialization.js";
|
|
7
7
|
import { IGameObject } from "../../engine/engine_types.js";
|
|
8
|
-
import { CircularBuffer, DeviceUtilities, getParam } from "../../engine/engine_utils.js";
|
|
8
|
+
import { CircularBuffer, delay, DeviceUtilities, getParam } from "../../engine/engine_utils.js";
|
|
9
9
|
import { type NeedleXREventArgs, NeedleXRSession } from "../../engine/xr/api.js";
|
|
10
10
|
import { IUSDExporterExtension } from "../../engine-components/export/usdz/Extension.js";
|
|
11
11
|
import { imageToCanvas, USDObject, USDWriter, USDZExporterContext } from "../../engine-components/export/usdz/ThreeUSDZExporter.js";
|
|
@@ -148,91 +148,122 @@ export class WebXRImageTrackingModel {
|
|
|
148
148
|
|
|
149
149
|
class ImageTrackingExtension implements IUSDExporterExtension {
|
|
150
150
|
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
readonly isImageTrackingExtension = true;
|
|
151
154
|
get extensionName() { return "image-tracking"; }
|
|
152
155
|
|
|
153
|
-
private filename: string;
|
|
154
|
-
private widthInMeters: number;
|
|
155
|
-
private imageData: Uint8Array;
|
|
156
156
|
|
|
157
|
-
constructor(
|
|
158
|
-
|
|
159
|
-
this.
|
|
160
|
-
this.widthInMeters = widthInMeters;
|
|
157
|
+
constructor(private readonly exporter: USDZExporter, private readonly component: WebXRImageTracking) {
|
|
158
|
+
if (debug) console.log(this);
|
|
159
|
+
this.exporter.anchoringType = "image";
|
|
161
160
|
}
|
|
162
161
|
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
// set during export
|
|
163
|
+
private shouldExport: boolean = true;
|
|
164
|
+
|
|
165
|
+
private filename: string = "marker.png";
|
|
166
|
+
private imageModel: WebXRImageTrackingModel | null = null;
|
|
167
|
+
|
|
168
|
+
onBeforeBuildDocument(_context: USDZExporterContext) {
|
|
169
|
+
|
|
170
|
+
// check if this extension is the first image tracking extension in the list
|
|
171
|
+
// since iOS can only track one image at a time we only allow one image tracking extension to be active
|
|
172
|
+
// we have to determine this at the earlierst export callback
|
|
173
|
+
// all subsequent export callbacks should then check is shouldExport is set to true
|
|
174
|
+
// this should only be the case for exactly one extension
|
|
175
|
+
const index = this.exporter.extensions
|
|
176
|
+
.filter(e => {
|
|
177
|
+
const ext = (e as ImageTrackingExtension);
|
|
178
|
+
return ext.isImageTrackingExtension && ext.component.activeAndEnabled && ext.component.trackedImages?.length > 0;
|
|
179
|
+
})
|
|
180
|
+
.indexOf(this);
|
|
181
|
+
this.shouldExport = index === 0;
|
|
182
|
+
if (!this.shouldExport) return;
|
|
183
|
+
|
|
184
|
+
// Warn if more than one tracked image is used for USDZ; that's not supported at the moment.
|
|
185
|
+
if (this.component.trackedImages?.length > 1) {
|
|
186
|
+
if (debug || isDevEnvironment()) {
|
|
187
|
+
showBalloonWarning("USDZ: Only one tracked image is supported.");
|
|
188
|
+
console.warn("USDZ: Only one tracked image is supported. Will choose the first one in the trackedImages list");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
165
191
|
}
|
|
166
192
|
|
|
167
193
|
onAfterHierarchy(_context: USDZExporterContext, writer: USDWriter) {
|
|
194
|
+
if (!this.shouldExport) return;
|
|
195
|
+
|
|
168
196
|
const iOSVersion = DeviceUtilities.getiOSVersion();
|
|
169
197
|
const majorVersion = iOSVersion ? parseInt(iOSVersion.split(".")[0]) : 18;
|
|
170
198
|
const workaroundForFB16119331 = majorVersion >= 18;
|
|
171
199
|
const multiplier = workaroundForFB16119331 ? 1 : 100;
|
|
172
200
|
writer.beginBlock(`def Preliminary_ReferenceImage "AnchoringReferenceImage"`);
|
|
173
201
|
writer.appendLine(`uniform asset image = @image_tracking/` + this.filename + `@`);
|
|
174
|
-
writer.appendLine(`uniform double physicalWidth = ` + (this.widthInMeters * multiplier).toFixed(8));
|
|
202
|
+
writer.appendLine(`uniform double physicalWidth = ` + (this.imageModel!.widthInMeters * multiplier).toFixed(8));
|
|
175
203
|
writer.closeBlock();
|
|
204
|
+
|
|
176
205
|
}
|
|
177
206
|
|
|
178
|
-
|
|
179
|
-
|
|
207
|
+
async onAfterSerialize(context: USDZExporterContext) {
|
|
208
|
+
if (!this.shouldExport) return;
|
|
180
209
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (isDevEnvironment()) showBalloonWarning("USDZ: Only one tracked image is supported.");
|
|
184
|
-
console.warn("USDZ: Only one tracked image is supported.");
|
|
185
|
-
}
|
|
186
|
-
}
|
|
210
|
+
const imageModel = this.imageModel;
|
|
211
|
+
const img = _imageElements.get(imageModel!.image!)!;
|
|
187
212
|
|
|
188
|
-
|
|
189
|
-
|
|
213
|
+
const canvas = await imageToCanvas(img);
|
|
214
|
+
const blob = await canvas.convertToBlob({ type: 'image/png' });
|
|
215
|
+
const arrayBuffer = await blob.arrayBuffer();
|
|
216
|
+
context.files['image_tracking/' + this.filename] = new Uint8Array(arrayBuffer);
|
|
190
217
|
}
|
|
191
218
|
|
|
192
219
|
onExportObject(object: Object3D<Object3DEventMap>, model: USDObject, _context: USDZExporterContext) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
220
|
+
if (!this.shouldExport) return;
|
|
221
|
+
|
|
222
|
+
const imageTracking = this.component;
|
|
223
|
+
if (!imageTracking || !imageTracking.trackedImages?.length || !imageTracking.activeAndEnabled) return;
|
|
224
|
+
|
|
225
|
+
// we only care about the first image
|
|
226
|
+
const trackedImage = imageTracking.trackedImages[0];
|
|
227
|
+
|
|
228
|
+
if (trackedImage.object?.asset === object) {
|
|
229
|
+
this.imageModel = trackedImage;
|
|
230
|
+
|
|
231
|
+
const { scale, target } = this.exporter.getARScaleAndTarget();
|
|
232
|
+
|
|
233
|
+
// We have to reset the image tracking object's position and rotation, because QuickLook applies them.
|
|
234
|
+
// On Android WebXR they're replaced by the tracked data
|
|
235
|
+
let parent = object;
|
|
236
|
+
|
|
237
|
+
const relativeMatrix = new Matrix4();
|
|
238
|
+
if (object !== target) {
|
|
239
|
+
while (parent && parent.parent && parent.parent !== target) {
|
|
240
|
+
parent = parent.parent;
|
|
241
|
+
relativeMatrix.premultiply(parent.matrix);
|
|
212
242
|
}
|
|
213
|
-
const mat = relativeMatrix
|
|
214
|
-
.clone()
|
|
215
|
-
.invert()
|
|
216
|
-
// apply session root scale again after undoing the world transformation
|
|
217
|
-
model.setMatrix(mat.scale(new Vector3(scale, scale, scale)));
|
|
218
|
-
|
|
219
|
-
// Unfortunately looks like Apple's docs are incomplete:
|
|
220
|
-
// https://developer.apple.com/documentation/realitykit/preliminary_anchoringapi#Nest-and-Layer-Anchorable-Prims
|
|
221
|
-
// In practice, it seems that nesting is not allowed – no image tracking will be applied to nested objects.
|
|
222
|
-
// Thus, we can't have separate transforms for "regularly placing content" and "placing content with an image marker".
|
|
223
|
-
// model.extraSchemas.push("Preliminary_AnchoringAPI");
|
|
224
|
-
// model.addEventListener("serialize", (_writer: USDWriter, _context: USDZExporterContext) => {
|
|
225
|
-
// writer.appendLine( `token preliminary:anchoring:type = "image"` );
|
|
226
|
-
// writer.appendLine( `rel preliminary:imageAnchoring:referenceImage = </${context.document.name}/Scenes/Scene/AnchoringReferenceImage>` );
|
|
227
|
-
// });
|
|
228
|
-
|
|
229
|
-
// We can only apply this to the first tracked image, more are not supported by QuickLook.
|
|
230
|
-
break;
|
|
231
243
|
}
|
|
244
|
+
const mat = relativeMatrix
|
|
245
|
+
.clone()
|
|
246
|
+
.invert()
|
|
247
|
+
// apply session root scale again after undoing the world transformation
|
|
248
|
+
model.setMatrix(mat.scale(new Vector3(scale, scale, scale)));
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
// Unfortunately looks like Apple's docs are incomplete:
|
|
252
|
+
// https://developer.apple.com/documentation/realitykit/preliminary_anchoringapi#Nest-and-Layer-Anchorable-Prims
|
|
253
|
+
// In practice, it seems that nesting is not allowed – no image tracking will be applied to nested objects.
|
|
254
|
+
// Thus, we can't have separate transforms for "regularly placing content" and "placing content with an image marker".
|
|
255
|
+
// model.extraSchemas.push("Preliminary_AnchoringAPI");
|
|
256
|
+
// model.addEventListener("serialize", (_writer: USDWriter, _context: USDZExporterContext) => {
|
|
257
|
+
// writer.appendLine( `token preliminary:anchoring:type = "image"` );
|
|
258
|
+
// writer.appendLine( `rel preliminary:imageAnchoring:referenceImage = </${context.document.name}/Scenes/Scene/AnchoringReferenceImage>` );
|
|
259
|
+
// });
|
|
260
|
+
|
|
261
|
+
// We can only apply this to the first tracked image, more are not supported by QuickLook.
|
|
232
262
|
}
|
|
233
263
|
}
|
|
234
264
|
}
|
|
235
265
|
|
|
266
|
+
|
|
236
267
|
/**
|
|
237
268
|
* @category XR
|
|
238
269
|
* @group Components
|
|
@@ -247,7 +278,6 @@ export class WebXRImageTracking extends Behaviour {
|
|
|
247
278
|
smooth: boolean = true;
|
|
248
279
|
|
|
249
280
|
private readonly trackedImageIndexMap: Map<number, WebXRImageTrackingModel> = new Map();
|
|
250
|
-
private static _imageElements: Map<string, ImageBitmap | null> = new Map();
|
|
251
281
|
|
|
252
282
|
/** @returns true if image tracking is supported on this device. This may return false at runtime if the user's browser did not enable webxr incubations */
|
|
253
283
|
get supported() { return this._supported; }
|
|
@@ -259,38 +289,24 @@ export class WebXRImageTracking extends Behaviour {
|
|
|
259
289
|
if (!this.trackedImages) return;
|
|
260
290
|
for (const trackedImage of this.trackedImages) {
|
|
261
291
|
if (trackedImage.image) {
|
|
262
|
-
|
|
263
|
-
// already loaded
|
|
264
|
-
}
|
|
265
|
-
else {
|
|
266
|
-
const url = trackedImage.image;
|
|
267
|
-
WebXRImageTracking._imageElements.set(url, null);
|
|
268
|
-
const imageElement = document.createElement("img") as HTMLImageElement;
|
|
269
|
-
imageElement.src = url;
|
|
270
|
-
imageElement.addEventListener("load", async () => {
|
|
271
|
-
const img = await createImageBitmap(imageElement);
|
|
272
|
-
WebXRImageTracking._imageElements.set(url, img);
|
|
273
|
-
|
|
274
|
-
// read back Uint8Array to use in USDZ -
|
|
275
|
-
// TODO better would be to do that once we actually need it
|
|
276
|
-
const canvas = await imageToCanvas(img);
|
|
277
|
-
if (canvas) {
|
|
278
|
-
const blob = await canvas.convertToBlob({ type: 'image/png' });
|
|
279
|
-
const arrayBuffer = await blob.arrayBuffer();
|
|
280
|
-
|
|
281
|
-
const exporter = GameObject.findObjectOfType(USDZExporter);
|
|
282
|
-
if (exporter && this.trackedImages) {
|
|
283
|
-
exporter.extensions.push(
|
|
284
|
-
new ImageTrackingExtension("marker.png", new Uint8Array(arrayBuffer), this.trackedImages[0].widthInMeters)
|
|
285
|
-
);
|
|
286
|
-
exporter.anchoringType = "image";
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
});
|
|
290
|
-
}
|
|
292
|
+
loadImage(trackedImage.image);
|
|
291
293
|
}
|
|
292
294
|
}
|
|
293
295
|
}
|
|
296
|
+
onEnable() {
|
|
297
|
+
USDZExporter.beforeExport.addEventListener(this.onBeforeUSDZExport);
|
|
298
|
+
}
|
|
299
|
+
onDisable(): void {
|
|
300
|
+
USDZExporter.beforeExport.removeEventListener(this.onBeforeUSDZExport);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private onBeforeUSDZExport = (args: { exporter: USDZExporter }) => {
|
|
304
|
+
if (this.activeAndEnabled && this.trackedImages?.length) {
|
|
305
|
+
args.exporter.extensions.push(new ImageTrackingExtension(args.exporter, this));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
|
|
294
310
|
|
|
295
311
|
onBeforeXR(_mode: XRSessionMode, args: XRSessionInit & { trackedImages: Array<any> }): void {
|
|
296
312
|
// console.log("onXRRequested", args, this.trackedImages)
|
|
@@ -302,7 +318,7 @@ export class WebXRImageTracking extends Behaviour {
|
|
|
302
318
|
args.trackedImages = [];
|
|
303
319
|
for (const trackedImage of this.trackedImages) {
|
|
304
320
|
if (trackedImage.image?.length && trackedImage.widthInMeters > 0) {
|
|
305
|
-
const bitmap =
|
|
321
|
+
const bitmap = _imageElements.get(trackedImage.image);
|
|
306
322
|
if (bitmap) {
|
|
307
323
|
this.trackedImageIndexMap.set(args.trackedImages.length, trackedImage);
|
|
308
324
|
args.trackedImages.push({
|
|
@@ -519,4 +535,34 @@ export class WebXRImageTracking extends Behaviour {
|
|
|
519
535
|
}
|
|
520
536
|
}
|
|
521
537
|
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
const _imageElements: Map<string, ImageBitmap | null> = new Map();
|
|
544
|
+
const _imageLoadingPromises: Map<string, Promise<boolean>> = new Map();
|
|
545
|
+
|
|
546
|
+
async function loadImage(url: string) {
|
|
547
|
+
if (_imageElements.has(url)) {
|
|
548
|
+
if (_imageLoadingPromises.has(url)) return _imageLoadingPromises.get(url);
|
|
549
|
+
return Promise.resolve(true);
|
|
550
|
+
}
|
|
551
|
+
const promise = new Promise<boolean>(res => {
|
|
552
|
+
_imageElements.set(url, null);
|
|
553
|
+
const imageElement = document.createElement("img") as HTMLImageElement;
|
|
554
|
+
imageElement.src = url;
|
|
555
|
+
imageElement.addEventListener("load", async () => {
|
|
556
|
+
const img = await createImageBitmap(imageElement);
|
|
557
|
+
_imageElements.set(url, img);
|
|
558
|
+
res(true);
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
_imageLoadingPromises.set(url, promise);
|
|
563
|
+
promise.finally(() => {
|
|
564
|
+
_imageLoadingPromises.delete(url);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
return promise;
|
|
522
568
|
}
|