@needle-tools/engine 4.12.0-next.0b39d59 → 4.12.0-next.67a7d59
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/generateMeshBVH.worker-iyfPIK6R.js +21 -0
- package/dist/{needle-engine.bundle-C6Ko3O_C.min.js → needle-engine.bundle-B5RaXnOy.min.js} +145 -145
- package/dist/needle-engine.bundle-CilNGHk-.umd.cjs +1647 -0
- package/dist/{needle-engine.bundle-BktxajaZ.js → needle-engine.bundle-DrtRnSvL.js} +8208 -7952
- package/dist/needle-engine.d.ts +41 -9
- package/dist/needle-engine.js +46 -46
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{postprocessing-BkY94MUG.min.js → postprocessing-B5ksn9-G.min.js} +61 -61
- package/dist/{postprocessing-BWZIqm3N.umd.cjs → postprocessing-DZtb9Nnn.umd.cjs} +5 -5
- package/dist/{postprocessing-CLbPDsD8.js → postprocessing-__7s9wON.js} +426 -435
- package/dist/{vendor-C_oHRUjX.js → vendor-DMZcbVO1.js} +2644 -2487
- package/dist/{vendor-BiJQtqow.min.js → vendor-sURMCFSI.min.js} +41 -41
- package/dist/{vendor-DN-NsXVB.umd.cjs → vendor-tyBvnMF-.umd.cjs} +36 -36
- package/lib/engine/debug/debug_console.js +403 -1
- package/lib/engine/debug/debug_console.js.map +1 -1
- package/lib/engine/engine_components.js +3 -3
- package/lib/engine/engine_components.js.map +1 -1
- package/lib/engine/engine_input.d.ts +5 -0
- package/lib/engine/engine_input.js +6 -0
- package/lib/engine/engine_input.js.map +1 -1
- package/lib/engine/engine_networking.js +5 -5
- package/lib/engine/engine_networking.js.map +1 -1
- package/lib/engine/engine_physics.js.map +1 -1
- package/lib/engine/engine_serialization_builtin_serializer.js +1 -1
- package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
- package/lib/engine/engine_utils.d.ts +4 -1
- package/lib/engine/engine_utils.js +28 -4
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine/extensions/extensions.d.ts +29 -7
- package/lib/engine/extensions/extensions.js.map +1 -1
- package/lib/engine/webcomponents/WebXRButtons.js +13 -5
- package/lib/engine/webcomponents/WebXRButtons.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.js +4 -5
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.ar-overlay.js +4 -0
- package/lib/engine/webcomponents/needle-engine.ar-overlay.js.map +1 -1
- package/lib/engine/xr/NeedleXRSession.d.ts +1 -1
- package/lib/engine/xr/NeedleXRSession.js +85 -22
- package/lib/engine/xr/NeedleXRSession.js.map +1 -1
- package/lib/engine/xr/TempXRContext.js +12 -2
- package/lib/engine/xr/TempXRContext.js.map +1 -1
- package/lib/engine/xr/usdz.js +6 -2
- package/lib/engine/xr/usdz.js.map +1 -1
- package/lib/engine-components/Camera.js +4 -1
- package/lib/engine-components/Camera.js.map +1 -1
- package/lib/engine-components/Component.d.ts +2 -1
- package/lib/engine-components/Component.js +3 -2
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/SpectatorCamera.js +15 -7
- package/lib/engine-components/SpectatorCamera.js.map +1 -1
- package/lib/engine-components/api.d.ts +1 -1
- package/lib/engine-components/api.js +1 -1
- package/lib/engine-components/api.js.map +1 -1
- package/lib/engine-components/webxr/Avatar.js +2 -0
- package/lib/engine-components/webxr/Avatar.js.map +1 -1
- package/lib/engine-components/webxr/WebXR.js +18 -12
- package/lib/engine-components/webxr/WebXR.js.map +1 -1
- package/package.json +3 -3
- package/plugins/vite/poster-client.js +8 -1
- package/src/engine/debug/debug_console.ts +449 -1
- package/src/engine/engine_components.ts +4 -4
- package/src/engine/engine_input.ts +7 -0
- package/src/engine/engine_networking.ts +5 -5
- package/src/engine/engine_physics.ts +3 -3
- package/src/engine/engine_serialization_builtin_serializer.ts +1 -1
- package/src/engine/engine_utils.ts +23 -4
- package/src/engine/extensions/extensions.ts +30 -6
- package/src/engine/webcomponents/WebXRButtons.ts +15 -5
- package/src/engine/webcomponents/needle menu/needle-menu.ts +4 -5
- package/src/engine/webcomponents/needle-engine.ar-overlay.ts +6 -0
- package/src/engine/xr/NeedleXRSession.ts +96 -25
- package/src/engine/xr/TempXRContext.ts +12 -2
- package/src/engine/xr/usdz.ts +6 -1
- package/src/engine-components/Camera.ts +4 -1
- package/src/engine-components/Component.ts +5 -4
- package/src/engine-components/SpectatorCamera.ts +21 -10
- package/src/engine-components/api.ts +1 -1
- package/src/engine-components/webxr/Avatar.ts +4 -0
- package/src/engine-components/webxr/WebXR.ts +19 -11
- package/dist/generateMeshBVH.worker-BvGEI0r7.js +0 -21
- package/dist/needle-engine.bundle-BZJhFSnI.umd.cjs +0 -1647
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getParam } from "../engine_utils.js";
|
|
1
|
+
import { DeviceUtilities,getParam } from "../engine_utils.js";
|
|
2
2
|
import { getErrorCount } from "./debug_overlay.js";
|
|
3
3
|
|
|
4
4
|
let consoleInstance: VConsole | null | undefined = undefined;
|
|
@@ -117,6 +117,12 @@ function createConsole(startHidden: boolean = false) {
|
|
|
117
117
|
consoleInstance.addPlugin(createInspectPlugin());
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
// Add plugin for device utilities
|
|
121
|
+
consoleInstance.addPlugin(createDeviceUtilitiesPlugin());
|
|
122
|
+
|
|
123
|
+
// Add plugin for graphics info
|
|
124
|
+
consoleInstance.addPlugin(createGraphicsInfoPlugin());
|
|
125
|
+
|
|
120
126
|
consoleHtmlElement = getConsoleElement();
|
|
121
127
|
if (consoleHtmlElement) {
|
|
122
128
|
consoleHtmlElement[$defaultConsoleParent] = consoleHtmlElement.parentElement;
|
|
@@ -291,6 +297,448 @@ function createInspectPlugin() {
|
|
|
291
297
|
return plugin;
|
|
292
298
|
}
|
|
293
299
|
|
|
300
|
+
const CONTAINER_STYLE = "padding: 10px; font-family: monospace;";
|
|
301
|
+
const HEADING_STYLE = "margin-bottom: 10px;";
|
|
302
|
+
const SUBHEADING_STYLE = "margin-bottom: 10px; margin-top: 15px;";
|
|
303
|
+
const TABLE_STYLE = "width: 100%; border-collapse: collapse; border: 1px solid rgba(0,0,0,0.1); table-layout: fixed;";
|
|
304
|
+
const CELL_STYLE = "border: 1px solid rgba(0,0,0,0.1); padding: 5px;";
|
|
305
|
+
const HEADER_CELL_STYLE = CELL_STYLE;
|
|
306
|
+
const VALUE_CELL_STYLE = CELL_STYLE + " word-break: break-all;";
|
|
307
|
+
|
|
308
|
+
function createTable(rows: Array<{label: string, value: any}>, sortByValue: boolean = false): string {
|
|
309
|
+
if (sortByValue) {
|
|
310
|
+
rows.sort((a, b) => (b.value ? 1 : 0) - (a.value ? 1 : 0));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
let html = `<table style='${TABLE_STYLE}'>`;
|
|
314
|
+
html += "<tbody>";
|
|
315
|
+
|
|
316
|
+
for (const row of rows) {
|
|
317
|
+
const value = typeof row.value === 'boolean' ? (row.value ? "✅" : "❌") : row.value;
|
|
318
|
+
html += `<tr><td style='${HEADER_CELL_STYLE}'>${row.label}</td><td style='${VALUE_CELL_STYLE}'>${value}</td></tr>`;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
html += "</tbody></table>";
|
|
322
|
+
return html;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function getWebGL2Support(): string {
|
|
326
|
+
try {
|
|
327
|
+
const canvas = document.createElement('canvas');
|
|
328
|
+
const gl = canvas.getContext('webgl2');
|
|
329
|
+
if (gl) {
|
|
330
|
+
return "✅";
|
|
331
|
+
}
|
|
332
|
+
} catch (e) {
|
|
333
|
+
// WebGL2 not supported
|
|
334
|
+
}
|
|
335
|
+
return "❌";
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function createDeviceUtilitiesPlugin() {
|
|
339
|
+
if (!globalThis.VConsole) return;
|
|
340
|
+
const plugin = new VConsole.VConsolePlugin("device-utilities", "📱 Device Info");
|
|
341
|
+
plugin.on('renderTab', function(callback) {
|
|
342
|
+
let html = `<div style='${CONTAINER_STYLE}'>`;
|
|
343
|
+
|
|
344
|
+
// Device type
|
|
345
|
+
const deviceType = getDeviceType();
|
|
346
|
+
html += `<h3 style='${HEADING_STYLE}'>Device: ${deviceType}</h3>`;
|
|
347
|
+
|
|
348
|
+
// Device capabilities table
|
|
349
|
+
html += createTable([
|
|
350
|
+
{ label: "💻 Desktop", value: DeviceUtilities.isDesktop() },
|
|
351
|
+
{ label: "📱 Mobile Device", value: DeviceUtilities.isMobileDevice() },
|
|
352
|
+
{ label: "🍎 iOS", value: DeviceUtilities.isiOS() },
|
|
353
|
+
{ label: "📱 iPad", value: DeviceUtilities.isiPad() },
|
|
354
|
+
{ label: "🤖 Android", value: DeviceUtilities.isAndroidDevice() },
|
|
355
|
+
{ label: "🦊 Mozilla XR", value: DeviceUtilities.isMozillaXR() },
|
|
356
|
+
{ label: "🌵 Needle App Clip", value: DeviceUtilities.isNeedleAppClip() },
|
|
357
|
+
{ label: "🍏 macOS", value: DeviceUtilities.isMacOS() },
|
|
358
|
+
{ label: "👓 VisionOS", value: DeviceUtilities.isVisionOS() },
|
|
359
|
+
{ label: "🧭 Safari", value: DeviceUtilities.isSafari() },
|
|
360
|
+
{ label: "🕶️ Meta Quest", value: DeviceUtilities.isQuest() },
|
|
361
|
+
{ label: "🔗 QuickLook AR Support", value: DeviceUtilities.supportsQuickLookAR() },
|
|
362
|
+
], true);
|
|
363
|
+
|
|
364
|
+
// Versions
|
|
365
|
+
const versionRows: Array<{label: string, value: any}> = [];
|
|
366
|
+
const iosVersion = DeviceUtilities.getiOSVersion();
|
|
367
|
+
if (iosVersion) versionRows.push({ label: "🍎 iOS Version", value: iosVersion });
|
|
368
|
+
const chromeVersion = DeviceUtilities.getChromeVersion();
|
|
369
|
+
if (chromeVersion) versionRows.push({ label: "🌐 Chrome Version", value: chromeVersion });
|
|
370
|
+
const safariVersion = DeviceUtilities.getSafariVersion();
|
|
371
|
+
if (safariVersion) versionRows.push({ label: "🧭 Safari Version", value: safariVersion });
|
|
372
|
+
|
|
373
|
+
if (versionRows.length > 0) {
|
|
374
|
+
html += createTable(versionRows, false);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
html += "</div>";
|
|
378
|
+
|
|
379
|
+
// User Agent table
|
|
380
|
+
html += `<div style='${CONTAINER_STYLE} margin-top: 20px;'>`;
|
|
381
|
+
html += `<h3 style='${HEADING_STYLE}'>User Agent Info</h3>`;
|
|
382
|
+
|
|
383
|
+
const userAgentRows = [
|
|
384
|
+
{ label: "User Agent", value: navigator.userAgent },
|
|
385
|
+
{ label: "Platform", value: navigator.platform },
|
|
386
|
+
{ label: "App Version", value: navigator.appVersion },
|
|
387
|
+
// @ts-ignore
|
|
388
|
+
{ label: "User Agent Data", value: navigator.userAgentData ? `Platform: ${navigator.userAgentData.platform}, Mobile: ${navigator.userAgentData.mobile}` : "Not supported" },
|
|
389
|
+
{ label: "WebXR", value: 'xr' in navigator ? "✅" : "❌" },
|
|
390
|
+
{ label: "WebGPU", value: 'gpu' in navigator ? "✅" : "❌" },
|
|
391
|
+
{ label: "WebGL 2", value: getWebGL2Support() },
|
|
392
|
+
];
|
|
393
|
+
|
|
394
|
+
html += createTable(userAgentRows, false);
|
|
395
|
+
html += "</div>";
|
|
396
|
+
|
|
397
|
+
callback(html);
|
|
398
|
+
});
|
|
399
|
+
return plugin;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function createGraphicsInfoPlugin() {
|
|
403
|
+
if (!globalThis.VConsole) return;
|
|
404
|
+
const plugin = new VConsole.VConsolePlugin("graphics-info", "🎨 Graphics Info");
|
|
405
|
+
plugin.on('renderTab', async function(callback) {
|
|
406
|
+
let html = `<div style='${CONTAINER_STYLE}'>`;
|
|
407
|
+
|
|
408
|
+
// General GPU Info Table
|
|
409
|
+
const generalInfo = getGeneralGPUInfo();
|
|
410
|
+
if (generalInfo.length > 0) {
|
|
411
|
+
html += `<h3 style='${SUBHEADING_STYLE}'>General GPU Info</h3>`;
|
|
412
|
+
html += createTable(generalInfo, false);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// WebGL Info Table
|
|
416
|
+
const webglInfo = getWebGLDetailedInfo();
|
|
417
|
+
if (webglInfo.length > 0) {
|
|
418
|
+
html += `<h3 style='${SUBHEADING_STYLE}'>WebGL</h3>`;
|
|
419
|
+
html += createTable(webglInfo, false);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// WebGL 2 Features Table
|
|
423
|
+
const webgl2Features = getWebGL2FeaturesTable();
|
|
424
|
+
if (webgl2Features.length > 0) {
|
|
425
|
+
html += `<h3 style='${SUBHEADING_STYLE}'>WebGL 2 Features</h3>`;
|
|
426
|
+
html += createTable(webgl2Features, false);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// WebGL Limits Table
|
|
430
|
+
const webglLimits = getWebGLLimitsTable();
|
|
431
|
+
if (webglLimits.length > 0) {
|
|
432
|
+
html += `<h3 style='${SUBHEADING_STYLE}'>WebGL Limits</h3>`;
|
|
433
|
+
html += createTable(webglLimits, false);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Texture Formats Table
|
|
437
|
+
const textureFormats = getTextureFormatsTable();
|
|
438
|
+
if (textureFormats.length > 0) {
|
|
439
|
+
html += `<h3 style='${SUBHEADING_STYLE}'>Texture Formats</h3>`;
|
|
440
|
+
html += createTable(textureFormats, false);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// WebGPU Info Table
|
|
444
|
+
const webgpuInfo = await getWebGPUInfoTable();
|
|
445
|
+
if (webgpuInfo.length > 0) {
|
|
446
|
+
html += `<h3 style='${SUBHEADING_STYLE}'>WebGPU</h3>`;
|
|
447
|
+
html += createTable(webgpuInfo, false);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Safari specific GPU info
|
|
451
|
+
if (DeviceUtilities.isSafari()) {
|
|
452
|
+
const safariGPUInfo = getSafariGPUInfo();
|
|
453
|
+
if (safariGPUInfo.length > 0) {
|
|
454
|
+
html += `<h3 style='${SUBHEADING_STYLE}'>Safari GPU Info</h3>`;
|
|
455
|
+
html += createTable(safariGPUInfo, false);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
html += "</div>";
|
|
460
|
+
|
|
461
|
+
callback(html);
|
|
462
|
+
});
|
|
463
|
+
return plugin;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function getGeneralGPUInfo(): Array<{label: string, value: string}> {
|
|
467
|
+
const info: Array<{label: string, value: string}> = [];
|
|
468
|
+
|
|
469
|
+
// Display and window info
|
|
470
|
+
const dpr = window.devicePixelRatio;
|
|
471
|
+
info.push({ label: "Device Pixel Ratio", value: dpr.toString() });
|
|
472
|
+
info.push({ label: "Width (px)", value: (window.innerWidth * dpr).toString() });
|
|
473
|
+
info.push({ label: "Height (px)", value: (window.innerHeight * dpr).toString() });
|
|
474
|
+
|
|
475
|
+
// Estimated physical screen size in cm (96 DPI desktop, 150 DPI mobile)
|
|
476
|
+
const isMobile = DeviceUtilities.isMobileDevice();
|
|
477
|
+
const estimatedDPI = isMobile ? 150 : 96;
|
|
478
|
+
const widthInches = screen.width / estimatedDPI;
|
|
479
|
+
const heightInches = screen.height / estimatedDPI;
|
|
480
|
+
const widthCm = widthInches * 2.54;
|
|
481
|
+
const heightCm = heightInches * 2.54;
|
|
482
|
+
info.push({ label: "Estimated Width (cm)", value: widthCm.toFixed(1) });
|
|
483
|
+
info.push({ label: "Estimated Height (cm)", value: heightCm.toFixed(1) });
|
|
484
|
+
|
|
485
|
+
const webglInfo = getWebGLInfo();
|
|
486
|
+
if (webglInfo) {
|
|
487
|
+
info.push({ label: "GPU", value: webglInfo.renderer });
|
|
488
|
+
info.push({ label: "Driver", value: webglInfo.vendor });
|
|
489
|
+
info.push({ label: "ANGLE", value: webglInfo.angle || "Not detected" });
|
|
490
|
+
|
|
491
|
+
// Enhanced GPU parsing
|
|
492
|
+
const gpuDetails = parseGPUDetails(webglInfo.renderer);
|
|
493
|
+
if (gpuDetails) {
|
|
494
|
+
if (gpuDetails.manufacturer) info.push({ label: "Manufacturer", value: gpuDetails.manufacturer });
|
|
495
|
+
if (gpuDetails.cardVersion) info.push({ label: "Card Version", value: gpuDetails.cardVersion });
|
|
496
|
+
if (gpuDetails.brand) info.push({ label: "Brand", value: gpuDetails.brand });
|
|
497
|
+
info.push({ label: "Integrated", value: gpuDetails.integrated ? "Yes" : "No" });
|
|
498
|
+
if (gpuDetails.layer) info.push({ label: "WebGL Layer", value: gpuDetails.layer });
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return info;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function parseGPUDetails(renderer: string): {manufacturer?: string, cardVersion?: string, brand?: string, integrated?: boolean, layer?: string, card?: string} | null {
|
|
506
|
+
if (!renderer) return null;
|
|
507
|
+
|
|
508
|
+
const extractValue = (reg: RegExp, str: string) => {
|
|
509
|
+
const matches = str.match(reg);
|
|
510
|
+
return matches && matches[0];
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
const layer = extractValue(/(ANGLE)/g, renderer) || undefined;
|
|
514
|
+
const card = extractValue(/((NVIDIA|AMD|Intel)[^\d]*[^\s]+)/, renderer) || renderer;
|
|
515
|
+
|
|
516
|
+
const tokens = card.split(' ');
|
|
517
|
+
tokens.shift();
|
|
518
|
+
|
|
519
|
+
const manufacturer = extractValue(/(NVIDIA|AMD|Intel)/g, card) || undefined;
|
|
520
|
+
const cardVersion = tokens.length > 0 ? tokens.pop() : undefined;
|
|
521
|
+
const brand = tokens.length > 0 ? tokens.join(' ') : undefined;
|
|
522
|
+
const integrated = manufacturer === 'Intel';
|
|
523
|
+
|
|
524
|
+
return {
|
|
525
|
+
manufacturer,
|
|
526
|
+
cardVersion,
|
|
527
|
+
brand,
|
|
528
|
+
integrated,
|
|
529
|
+
layer,
|
|
530
|
+
card
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function getWebGLDetailedInfo(): Array<{label: string, value: string}> {
|
|
535
|
+
const info: Array<{label: string, value: string}> = [];
|
|
536
|
+
|
|
537
|
+
const webglInfo = getWebGLInfo();
|
|
538
|
+
if (webglInfo) {
|
|
539
|
+
info.push({ label: "📊 WebGL Version", value: webglInfo.version });
|
|
540
|
+
info.push({ label: "🎮 WebGL 2 Available", value: getWebGL2Support() });
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return info;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function getWebGL2FeaturesTable(): Array<{label: string, value: string}> {
|
|
547
|
+
const features: Array<{label: string, value: string}> = [];
|
|
548
|
+
|
|
549
|
+
try {
|
|
550
|
+
const canvas = document.createElement('canvas');
|
|
551
|
+
const gl = canvas.getContext('webgl2');
|
|
552
|
+
if (!gl) return features;
|
|
553
|
+
|
|
554
|
+
// Check for important WebGL 2 features
|
|
555
|
+
features.push({ label: "Float Color Buffer", value: gl.getExtension('EXT_color_buffer_float') ? "✅" : "❌" });
|
|
556
|
+
features.push({ label: "Anisotropic Filtering", value: gl.getExtension('EXT_texture_filter_anisotropic') ? "✅" : "❌" });
|
|
557
|
+
features.push({ label: "Float Texture Linear", value: gl.getExtension('OES_texture_float_linear') ? "✅" : "❌" });
|
|
558
|
+
features.push({ label: "S3TC Compression", value: gl.getExtension('WEBGL_compressed_texture_s3tc') ? "✅" : "❌" });
|
|
559
|
+
features.push({ label: "ETC Compression", value: gl.getExtension('WEBGL_compressed_texture_etc') ? "✅" : "❌" });
|
|
560
|
+
features.push({ label: "PVRTC Compression", value: gl.getExtension('WEBGL_compressed_texture_pvrtc') ? "✅" : "❌" });
|
|
561
|
+
features.push({ label: "ASTC Compression", value: gl.getExtension('WEBGL_compressed_texture_astc') ? "✅" : "❌" });
|
|
562
|
+
|
|
563
|
+
} catch (e) {
|
|
564
|
+
// WebGL2 not supported
|
|
565
|
+
}
|
|
566
|
+
return features;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function getWebGLLimitsTable(): Array<{label: string, value: string}> {
|
|
570
|
+
const limits: Array<{label: string, value: string}> = [];
|
|
571
|
+
|
|
572
|
+
try {
|
|
573
|
+
// Try WebGL 2 first, fall back to WebGL 1
|
|
574
|
+
const canvas = document.createElement('canvas');
|
|
575
|
+
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
|
|
576
|
+
if (!gl) return limits;
|
|
577
|
+
|
|
578
|
+
const isWebGL2 = gl instanceof WebGL2RenderingContext;
|
|
579
|
+
|
|
580
|
+
limits.push({ label: "📏 Max Texture Size", value: gl.getParameter(gl.MAX_TEXTURE_SIZE).toString() });
|
|
581
|
+
limits.push({ label: "🎨 Max Renderbuffer Size", value: gl.getParameter(gl.MAX_RENDERBUFFER_SIZE).toString() });
|
|
582
|
+
limits.push({ label: "🔗 Max Vertex Attribs", value: gl.getParameter(gl.MAX_VERTEX_ATTRIBS).toString() });
|
|
583
|
+
limits.push({ label: "🎯 Max Texture Units", value: gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS).toString() });
|
|
584
|
+
|
|
585
|
+
if (isWebGL2) {
|
|
586
|
+
limits.push({ label: "⚡ Max Samples", value: gl.getParameter(gl.MAX_SAMPLES).toString() });
|
|
587
|
+
limits.push({ label: "🔄 Max Uniform Buffer Bindings", value: gl.getParameter(gl.MAX_UNIFORM_BUFFER_BINDINGS).toString() });
|
|
588
|
+
limits.push({ label: "📐 Max 3D Texture Size", value: gl.getParameter(gl.MAX_3D_TEXTURE_SIZE).toString() });
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
} catch (e) {
|
|
592
|
+
// WebGL not available
|
|
593
|
+
}
|
|
594
|
+
return limits;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function getTextureFormatsTable(): Array<{label: string, value: string}> {
|
|
598
|
+
const formats: Array<{label: string, value: string}> = [];
|
|
599
|
+
|
|
600
|
+
try {
|
|
601
|
+
// WebGL 1 texture formats
|
|
602
|
+
const canvas1 = document.createElement('canvas');
|
|
603
|
+
const gl1 = canvas1.getContext('webgl');
|
|
604
|
+
if (gl1) {
|
|
605
|
+
formats.push({ label: "WebGL 1 RGBA", value: "✅" });
|
|
606
|
+
formats.push({ label: "WebGL 1 RGB", value: "✅" });
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// WebGL 2 texture formats
|
|
610
|
+
const canvas2 = document.createElement('canvas');
|
|
611
|
+
const gl2 = canvas2.getContext('webgl2');
|
|
612
|
+
if (gl2) {
|
|
613
|
+
formats.push({ label: "WebGL 2 RGBA32F", value: gl2.getExtension('EXT_color_buffer_float') ? "✅" : "❌" });
|
|
614
|
+
formats.push({ label: "WebGL 2 RGB32F", value: gl2.getExtension('EXT_color_buffer_float') ? "✅" : "❌" });
|
|
615
|
+
formats.push({ label: "WebGL 2 R11F_G11F_B10F", value: "✅" });
|
|
616
|
+
formats.push({ label: "WebGL 2 RGB565", value: "✅" });
|
|
617
|
+
formats.push({ label: "WebGL 2 RGB5_A1", value: "✅" });
|
|
618
|
+
formats.push({ label: "WebGL 2 RGBA4444", value: "✅" });
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
} catch (e) {
|
|
622
|
+
// WebGL not available
|
|
623
|
+
}
|
|
624
|
+
return formats;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
async function getWebGPUInfoTable(): Promise<Array<{label: string, value: string}>> {
|
|
628
|
+
const info: Array<{label: string, value: string}> = [];
|
|
629
|
+
|
|
630
|
+
if (!('gpu' in navigator)) {
|
|
631
|
+
info.push({ label: "🚀 WebGPU Support", value: "❌ Not supported" });
|
|
632
|
+
return info;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
info.push({ label: "🚀 WebGPU Support", value: "✅ Supported" });
|
|
636
|
+
|
|
637
|
+
try {
|
|
638
|
+
const adapter = await (navigator as any).gpu.requestAdapter();
|
|
639
|
+
if (!adapter) {
|
|
640
|
+
info.push({ label: "🎯 Adapter", value: "No adapter available" });
|
|
641
|
+
return info;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
info.push({ label: "🎯 Adapter", value: adapter.name || "Unknown Adapter" });
|
|
645
|
+
|
|
646
|
+
const device = await adapter.requestDevice();
|
|
647
|
+
info.push({ label: "🔧 Device", value: device.label || "WebGPU Device" });
|
|
648
|
+
|
|
649
|
+
// WebGPU Limits
|
|
650
|
+
info.push({ label: "📏 Max Texture 2D", value: device.limits.maxTextureDimension2D.toString() });
|
|
651
|
+
info.push({ label: "📐 Max Texture 3D", value: device.limits.maxTextureDimension3D.toString() });
|
|
652
|
+
info.push({ label: "📊 Max Texture Array Layers", value: device.limits.maxTextureArrayLayers.toString() });
|
|
653
|
+
info.push({ label: "💾 Max Buffer Size", value: `${(device.limits.maxBufferSize / 1024 / 1024).toFixed(1)}MB` });
|
|
654
|
+
info.push({ label: "🔗 Max Bind Groups", value: device.limits.maxBindGroups.toString() });
|
|
655
|
+
|
|
656
|
+
} catch (e) {
|
|
657
|
+
info.push({ label: "❌ Error", value: (e as Error).message });
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
return info;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function getWebGLInfo(): {renderer: string, vendor: string, version: string, angle?: string} | null {
|
|
664
|
+
try {
|
|
665
|
+
const canvas = document.createElement('canvas');
|
|
666
|
+
// Try WebGL 2 first, fall back to WebGL 1
|
|
667
|
+
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
|
|
668
|
+
if (!gl) return null;
|
|
669
|
+
|
|
670
|
+
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
|
|
671
|
+
const renderer = debugInfo ? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER);
|
|
672
|
+
const vendor = debugInfo ? gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) : gl.getParameter(gl.VENDOR);
|
|
673
|
+
const version = gl.getParameter(gl.VERSION);
|
|
674
|
+
|
|
675
|
+
// Try to detect ANGLE
|
|
676
|
+
let angle = undefined;
|
|
677
|
+
if (renderer && renderer.includes('ANGLE')) {
|
|
678
|
+
const angleMatch = renderer.match(/ANGLE \(([^)]+)\)/);
|
|
679
|
+
if (angleMatch) {
|
|
680
|
+
angle = angleMatch[1];
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
return { renderer, vendor, version, angle };
|
|
685
|
+
} catch (e) {
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function getSafariGPUInfo(): Array<{label: string, value: string}> {
|
|
691
|
+
const info: Array<{label: string, value: string}> = [];
|
|
692
|
+
|
|
693
|
+
// Try to get Safari-specific GPU info
|
|
694
|
+
try {
|
|
695
|
+
const canvas = document.createElement('canvas');
|
|
696
|
+
const gl = canvas.getContext('webgl');
|
|
697
|
+
if (gl) {
|
|
698
|
+
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
|
|
699
|
+
if (debugInfo) {
|
|
700
|
+
const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
|
|
701
|
+
if (renderer && renderer.includes('Apple')) {
|
|
702
|
+
info.push({ label: "🍎 Apple GPU", value: renderer });
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
} catch (e) {
|
|
707
|
+
// Ignore errors
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Check for WebGL extensions specific to Safari
|
|
711
|
+
try {
|
|
712
|
+
const canvas = document.createElement('canvas');
|
|
713
|
+
const gl = canvas.getContext('webgl');
|
|
714
|
+
if (gl) {
|
|
715
|
+
const extensions = gl.getSupportedExtensions() || [];
|
|
716
|
+
if (extensions.includes('WEBGL_compressed_texture_pvrtc')) {
|
|
717
|
+
info.push({ label: "🗜️ PVRTC Support", value: "✅" });
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
} catch (e) {
|
|
721
|
+
// Ignore errors
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return info;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function getDeviceType(): string {
|
|
728
|
+
if (DeviceUtilities.isQuest()) return "Meta Quest";
|
|
729
|
+
if (DeviceUtilities.isVisionOS()) return "Vision Pro";
|
|
730
|
+
if (DeviceUtilities.isiOS()) {
|
|
731
|
+
if (DeviceUtilities.isiPad()) return "iPad";
|
|
732
|
+
return "iPhone/iPod";
|
|
733
|
+
}
|
|
734
|
+
if (DeviceUtilities.isAndroidDevice()) return "Android Device";
|
|
735
|
+
if (DeviceUtilities.isMozillaXR()) return "Mozilla XR Browser";
|
|
736
|
+
if (DeviceUtilities.isNeedleAppClip()) return "Needle App Clip";
|
|
737
|
+
if (DeviceUtilities.isMacOS()) return "Mac";
|
|
738
|
+
if (DeviceUtilities.isDesktop()) return "Desktop PC";
|
|
739
|
+
return "Unknown Device";
|
|
740
|
+
}
|
|
741
|
+
|
|
294
742
|
function getConsoleSwitchButton(): HTMLElement | null {
|
|
295
743
|
const el = document.querySelector("#__vconsole .vc-switch");
|
|
296
744
|
if (el) return el as HTMLElement;
|
|
@@ -218,13 +218,13 @@ export function getComponents<T extends IComponent>(obj: Object3D, componentType
|
|
|
218
218
|
* const myComponent = getComponentInChildren(myObject, MyComponent);
|
|
219
219
|
* ```
|
|
220
220
|
*/
|
|
221
|
-
export function getComponentInChildren<T extends IComponent>(obj: Object3D, componentType: Constructor<T>, includeInactive
|
|
222
|
-
if(includeInactive === false && obj[activeInHierarchyFieldName] === false) return null;
|
|
221
|
+
export function getComponentInChildren<T extends IComponent>(obj: Object3D, componentType: Constructor<T>, includeInactive: boolean = false): T | null {
|
|
222
|
+
if(includeInactive === false && obj[activeInHierarchyFieldName] === false) return null;
|
|
223
223
|
const res = getComponent(obj, componentType) as IComponent | null;
|
|
224
|
-
if (includeInactive === false && res?.enabled === false) return null;
|
|
224
|
+
if (includeInactive === false && (res?.enabled === false || res?.activeAndEnabled === false)) return null;
|
|
225
225
|
if (res) return res as T;
|
|
226
226
|
for (let i = 0; i < obj?.children?.length; i++) {
|
|
227
|
-
const res = getComponentInChildren(obj.children[i], componentType);
|
|
227
|
+
const res = getComponentInChildren(obj.children[i], componentType, includeInactive);
|
|
228
228
|
if (res) return res;
|
|
229
229
|
}
|
|
230
230
|
return null;
|
|
@@ -150,6 +150,12 @@ export class NEPointerEvent extends PointerEvent {
|
|
|
150
150
|
override get pointerType(): PointerTypeNames { return this._pointerType; }
|
|
151
151
|
private readonly _pointerType: PointerTypeNames;
|
|
152
152
|
|
|
153
|
+
/**
|
|
154
|
+
* The button name that raised this event (e.g. for mouse events "left", "right", "middle" or for XRTrigger "xr-standard-trigger" or "xr-standard-thumbstick")
|
|
155
|
+
* Use {@link button} to get the numeric button index (e.g. 0, 1, 2...) on the controller or mouse.
|
|
156
|
+
*/
|
|
157
|
+
readonly buttonName?: ButtonName | "none" = undefined;
|
|
158
|
+
|
|
153
159
|
// this is set via the init arguments (we override it here for intellisense to show the string options)
|
|
154
160
|
/** The input that raised this event like `pointerdown` */
|
|
155
161
|
override get type(): InputEventNames { return this._type; }
|
|
@@ -176,6 +182,7 @@ export class NEPointerEvent extends PointerEvent {
|
|
|
176
182
|
this.mode = init.mode;
|
|
177
183
|
this._ray = init.ray;
|
|
178
184
|
this.space = init.device;
|
|
185
|
+
this.buttonName = init.buttonName;
|
|
179
186
|
}
|
|
180
187
|
|
|
181
188
|
private _immediatePropagationStopped = false;
|
|
@@ -629,7 +629,7 @@ export class NetworkConnection implements INetworkConnection {
|
|
|
629
629
|
return;
|
|
630
630
|
}
|
|
631
631
|
|
|
632
|
-
console.debug("
|
|
632
|
+
console.debug("Connecting to networking backend on\n" + networkingServerUrl)
|
|
633
633
|
const pkg = await import('websocket-ts');
|
|
634
634
|
// @ts-ignore
|
|
635
635
|
const WebsocketBuilder = pkg.default?.WebsocketBuilder ?? pkg.WebsocketBuilder;
|
|
@@ -641,8 +641,8 @@ export class NetworkConnection implements INetworkConnection {
|
|
|
641
641
|
this._connectingToWebsocketPromise = null;
|
|
642
642
|
this._ws = ws;
|
|
643
643
|
this.connected = true;
|
|
644
|
-
if (isDevEnvironment() || debugNet) console.log("
|
|
645
|
-
else console.debug("
|
|
644
|
+
if (isDevEnvironment() || debugNet) console.log("Connected to networking backend\n" + networkingServerUrl);
|
|
645
|
+
else console.debug("Connected to networking backend", networkingServerUrl);
|
|
646
646
|
resolve(true);
|
|
647
647
|
this.onSendQueued(SendQueue.OnConnection);
|
|
648
648
|
})
|
|
@@ -656,10 +656,10 @@ export class NetworkConnection implements INetworkConnection {
|
|
|
656
656
|
console.error(msg);
|
|
657
657
|
})
|
|
658
658
|
.onError((_e) => {
|
|
659
|
-
console.error("
|
|
659
|
+
console.error("Websocket connection failed...");
|
|
660
660
|
resolve(false);
|
|
661
661
|
})
|
|
662
|
-
.onRetry(() => { console.log("
|
|
662
|
+
.onRetry(() => { console.log("Retry connecting to networking websocket") })
|
|
663
663
|
.build();
|
|
664
664
|
ws.addEventListener(pkg.WebsocketEvent.message, (socket, msg) => {
|
|
665
665
|
this.onMessage(socket, msg);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getRaycastMesh } from '@needle-tools/gltf-progressive';
|
|
2
2
|
import { ArrayCamera, Box3, BufferGeometry, Camera, type Intersection, Layers, Line, Matrix3, Matrix4, Mesh, Object3D, PerspectiveCamera, Plane, Ray, Raycaster, SkinnedMesh, Sphere, SphereGeometry, Vector2, Vector3 } from 'three'
|
|
3
3
|
import { GroundedSkybox } from 'three/examples/jsm/objects/GroundedSkybox.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { ComputeBVHOptions, GeometryBVH, MeshBVH, StaticGeometryGenerator } from 'three-mesh-bvh';
|
|
5
5
|
import type { GenerateMeshBVHWorker } from 'three-mesh-bvh/src/workers/GenerateMeshBVHWorker.js';
|
|
6
6
|
|
|
7
7
|
import { isDevEnvironment } from './debug/index.js';
|
|
@@ -762,7 +762,7 @@ namespace NEMeshBVH {
|
|
|
762
762
|
return true;
|
|
763
763
|
}
|
|
764
764
|
else if (method instanceof Sphere) {
|
|
765
|
-
const bvh = geom.boundsTree;
|
|
765
|
+
const bvh = geom.boundsTree as MeshBVH | undefined;
|
|
766
766
|
if (bvh) {
|
|
767
767
|
const sphere = method;
|
|
768
768
|
// Gizmos.DrawWireSphere(sphere.center, sphere.radius, 0xdddd00, 1, false);
|
|
@@ -789,7 +789,7 @@ namespace NEMeshBVH {
|
|
|
789
789
|
let _acceleratedRaycast: Function | null = null;
|
|
790
790
|
let _MeshBVH: ConstructorConcrete<MeshBVH> | null = null;
|
|
791
791
|
let _StaticGeometryGenerator: ConstructorConcrete<StaticGeometryGenerator> | null = null;
|
|
792
|
-
let _computeBoundsTree: ((
|
|
792
|
+
let _computeBoundsTree: (( options?: ComputeBVHOptions ) => GeometryBVH) | null = null;
|
|
793
793
|
|
|
794
794
|
function loadMeshBVHLibrary() {
|
|
795
795
|
if (didLoadMeshBVHLibrary) return;
|
|
@@ -337,7 +337,7 @@ class EventListSerializer extends TypeSerializer {
|
|
|
337
337
|
args = call.arguments.map(deserializeArgument);
|
|
338
338
|
}
|
|
339
339
|
const method = target[call.method];
|
|
340
|
-
if (
|
|
340
|
+
if (method === undefined) {
|
|
341
341
|
console.warn(`EventList method not found: \"${call.method}\" on ${target?.name}`);
|
|
342
342
|
}
|
|
343
343
|
else {
|
|
@@ -606,7 +606,8 @@ export namespace DeviceUtilities {
|
|
|
606
606
|
/** @returns `true` if we're currently on an iPad */
|
|
607
607
|
export function isiPad() {
|
|
608
608
|
if (__isiPad !== undefined) return __isiPad;
|
|
609
|
-
|
|
609
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
|
610
|
+
return __isiPad = /iPad/.test(navigator.userAgent) || userAgent.includes("macintosh") && "ontouchend" in document;
|
|
610
611
|
}
|
|
611
612
|
|
|
612
613
|
let __isAndroidDevice: boolean | undefined;
|
|
@@ -623,17 +624,25 @@ export namespace DeviceUtilities {
|
|
|
623
624
|
return __isMozillaXR = /WebXRViewer\//i.test(navigator.userAgent);
|
|
624
625
|
}
|
|
625
626
|
|
|
627
|
+
let __isNeedleAppClip: boolean | undefined;
|
|
628
|
+
/** @returns `true` if we're currently in the Needle App Clip */
|
|
629
|
+
export function isNeedleAppClip() {
|
|
630
|
+
if (__isNeedleAppClip !== undefined) return __isNeedleAppClip;
|
|
631
|
+
return __isNeedleAppClip = /NeedleAppClip\//i.test(navigator.userAgent);
|
|
632
|
+
}
|
|
633
|
+
|
|
626
634
|
let __isMacOS: boolean | undefined;
|
|
627
635
|
// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgentData
|
|
628
636
|
/** @returns `true` for MacOS devices */
|
|
629
637
|
export function isMacOS() {
|
|
630
638
|
if (__isMacOS !== undefined) return __isMacOS;
|
|
639
|
+
if (isiOS() || isiPad()) return __isMacOS = false;
|
|
640
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
|
631
641
|
if (navigator.userAgentData) {
|
|
632
642
|
// Use modern UA Client Hints API if available
|
|
633
643
|
return __isMacOS = navigator.userAgentData.platform === 'macOS';
|
|
634
644
|
} else {
|
|
635
645
|
// Fallback to user agent string parsing
|
|
636
|
-
const userAgent = navigator.userAgent.toLowerCase();
|
|
637
646
|
return __isMacOS = userAgent.includes('mac os x') || userAgent.includes('macintosh');
|
|
638
647
|
}
|
|
639
648
|
}
|
|
@@ -642,13 +651,13 @@ export namespace DeviceUtilities {
|
|
|
642
651
|
/** @returns `true` for VisionOS devices */
|
|
643
652
|
export function isVisionOS() {
|
|
644
653
|
if (__isVisionOS !== undefined) return __isVisionOS;
|
|
645
|
-
return __isVisionOS = (
|
|
654
|
+
return __isVisionOS = (isiPad() && "xr" in navigator && supportsQuickLookAR());
|
|
646
655
|
}
|
|
647
656
|
|
|
648
657
|
let __isiOS: boolean | undefined;
|
|
649
658
|
const iosDevices = ['iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod'];
|
|
650
659
|
|
|
651
|
-
/** @returns `true` for
|
|
660
|
+
/** @returns `true` for mobile Apple devices like iPad, iPhone, iPod, Vision Pro, ... */
|
|
652
661
|
export function isiOS() {
|
|
653
662
|
if (__isiOS !== undefined) return __isiOS;
|
|
654
663
|
// eslint-disable-next-line deprecation/deprecation
|
|
@@ -725,6 +734,16 @@ export namespace DeviceUtilities {
|
|
|
725
734
|
else __chromeVersion = null;
|
|
726
735
|
return __chromeVersion;
|
|
727
736
|
}
|
|
737
|
+
|
|
738
|
+
let __safariVersion: string | null | undefined;
|
|
739
|
+
export function getSafariVersion() {
|
|
740
|
+
if (__safariVersion !== undefined) return __safariVersion;
|
|
741
|
+
const match = navigator.userAgent.match(/Version\/(\d+\.\d+)/);
|
|
742
|
+
if (match && isSafari()) {
|
|
743
|
+
__safariVersion = match[1];
|
|
744
|
+
} else __safariVersion = null;
|
|
745
|
+
return __safariVersion;
|
|
746
|
+
}
|
|
728
747
|
}
|
|
729
748
|
|
|
730
749
|
/**
|