@aura3d/product-studio 1.4.0 → 1.4.2
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/README.md +27 -0
- package/package.json +37 -5
- package/src/ProductAsset.ts +0 -8
- package/src/ProductAssetLoader.ts +0 -83
- package/src/ProductCamera.ts +0 -48
- package/src/ProductDiagnostics.ts +0 -19
- package/src/ProductExport.ts +0 -43
- package/src/ProductFloor.ts +0 -17
- package/src/ProductLighting.ts +0 -52
- package/src/ProductMaterials.ts +0 -45
- package/src/ProductRenderScene.ts +0 -49
- package/src/ProductShowcaseLayout.ts +0 -90
- package/src/ProductStudio.ts +0 -72
- package/src/ProductTypes.ts +0 -158
- package/src/index.ts +0 -12
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @aura3d/product-studio
|
|
2
|
+
|
|
3
|
+
`@aura3d/product-studio` owns product asset loading, camera framing,
|
|
4
|
+
diagnostics, export, floor, lighting, material modes, render scenes, and
|
|
5
|
+
showcase layouts for Aura3D product workflows.
|
|
6
|
+
|
|
7
|
+
## Public API
|
|
8
|
+
|
|
9
|
+
- `loadProductAsset`: product asset loading boundary.
|
|
10
|
+
- `createProductCameraFrame`, `validateProductCameraFrame`: product camera
|
|
11
|
+
framing helpers.
|
|
12
|
+
- `createProductDiagnostics`: product scene diagnostics.
|
|
13
|
+
- `exportProductRender`, `exportProductSceneManifest`: product export helpers.
|
|
14
|
+
- `createProductFloor`, `createProductLightingPreset`: floor and lighting
|
|
15
|
+
presets for product scenes.
|
|
16
|
+
- `createProductMaterialMode`, `applyProductMaterialMode`: material mode
|
|
17
|
+
selection and application.
|
|
18
|
+
- `createProductRenderScene`, `updateProductRenderScene`: product render scene
|
|
19
|
+
construction and updates.
|
|
20
|
+
- `createProductShowcaseLayout`: product showcase layout helpers.
|
|
21
|
+
- `createProductStudio`: product studio workflow assembly.
|
|
22
|
+
|
|
23
|
+
## Package Boundary
|
|
24
|
+
|
|
25
|
+
This package is the product workflow layer. Public examples should still use
|
|
26
|
+
typed GLB/glTF assets from the CLI pipeline and only claim product-rendering
|
|
27
|
+
capabilities that have route-health, screenshot, and browser evidence.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aura3d/product-studio",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -11,9 +11,41 @@
|
|
|
11
11
|
"main": "./dist/index.js",
|
|
12
12
|
"types": "./dist/index.d.ts",
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@aura3d/assets": "1.4.
|
|
15
|
-
"@aura3d/rendering": "1.4.
|
|
16
|
-
"@aura3d/scene": "1.4.
|
|
14
|
+
"@aura3d/assets": "1.4.2",
|
|
15
|
+
"@aura3d/rendering": "1.4.2",
|
|
16
|
+
"@aura3d/scene": "1.4.2"
|
|
17
17
|
},
|
|
18
|
-
"sideEffects": false
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"description": "Product asset loading, camera framing, diagnostics, export, floor, lighting, material modes, render scenes, and showcase layouts for Aura3D.",
|
|
20
|
+
"keywords": [
|
|
21
|
+
"aura3d",
|
|
22
|
+
"3d",
|
|
23
|
+
"browser-3d",
|
|
24
|
+
"web-3d",
|
|
25
|
+
"typescript",
|
|
26
|
+
"product-studio",
|
|
27
|
+
"product-viewer",
|
|
28
|
+
"product-configurator",
|
|
29
|
+
"3d-commerce",
|
|
30
|
+
"product-rendering",
|
|
31
|
+
"materials",
|
|
32
|
+
"lighting"
|
|
33
|
+
],
|
|
34
|
+
"homepage": "https://aura3d.auraone.ai",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/auraoneai/aura3d.git",
|
|
38
|
+
"directory": "packages/product-studio"
|
|
39
|
+
},
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/auraoneai/aura3d/issues"
|
|
42
|
+
},
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"README.md",
|
|
49
|
+
"dist"
|
|
50
|
+
]
|
|
19
51
|
}
|
package/src/ProductAsset.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { createGLTFRenderResources, GLTFLoader, LoadContext, type DecodedGLTFImage } from "@aura3d/assets";
|
|
2
|
-
import type { ProductAsset, ProductAssetLoadOptions, ProductManifest } from "./ProductTypes";
|
|
3
|
-
|
|
4
|
-
export async function loadProductAsset(options: ProductAssetLoadOptions): Promise<ProductAsset> {
|
|
5
|
-
const loader = new GLTFLoader();
|
|
6
|
-
const context = new LoadContext();
|
|
7
|
-
const gltf = await loader.load({ url: options.url }, context);
|
|
8
|
-
const manifest = await loadManifest(options, gltf);
|
|
9
|
-
const resources = await createGLTFRenderResources(gltf, nativeImageDecodeAvailable() ? {} : { imageDecoder: fallbackImageDecoder });
|
|
10
|
-
const parts = manifest.parts.length > 0
|
|
11
|
-
? manifest.parts
|
|
12
|
-
: gltf.meshes.map((mesh) => ({ name: mesh.name, material: mesh.material, shape: "mesh" }));
|
|
13
|
-
const materials = manifest.materials.length > 0
|
|
14
|
-
? manifest.materials
|
|
15
|
-
: gltf.materials.map((material) => ({
|
|
16
|
-
name: material.name,
|
|
17
|
-
metallic: material.metallicFactor,
|
|
18
|
-
roughness: material.roughnessFactor,
|
|
19
|
-
alphaMode: material.alphaMode
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
id: manifest.id,
|
|
24
|
-
title: manifest.title,
|
|
25
|
-
category: manifest.category,
|
|
26
|
-
url: options.url,
|
|
27
|
-
manifest,
|
|
28
|
-
gltf,
|
|
29
|
-
resources,
|
|
30
|
-
parts,
|
|
31
|
-
materials
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function nativeImageDecodeAvailable(): boolean {
|
|
36
|
-
return typeof createImageBitmap === "function" || typeof globalThis.Image === "function";
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function fallbackImageDecoder(): DecodedGLTFImage {
|
|
40
|
-
return {
|
|
41
|
-
width: 1,
|
|
42
|
-
height: 1,
|
|
43
|
-
colorSpace: "srgb",
|
|
44
|
-
data: new Uint8Array([220, 220, 220, 255])
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function loadManifest(options: ProductAssetLoadOptions, gltf: Awaited<ReturnType<GLTFLoader["load"]>>): Promise<ProductManifest> {
|
|
49
|
-
const manifestUrl = options.manifestUrl ?? options.url.replace(/\/[^/]+\.gltf(?:\?.*)?$/i, "/manifest.json");
|
|
50
|
-
if (typeof fetch === "function") {
|
|
51
|
-
const response = await fetch(manifestUrl);
|
|
52
|
-
if (response.ok) {
|
|
53
|
-
const manifest = await response.json() as ProductManifest;
|
|
54
|
-
return normalizeManifest(manifest, options);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return normalizeManifest({
|
|
58
|
-
schema: "a3d-product-manifest/fallback",
|
|
59
|
-
id: options.id ?? "product",
|
|
60
|
-
title: options.title ?? gltf.scenes[gltf.defaultScene]?.name ?? "Product",
|
|
61
|
-
category: options.category ?? "product",
|
|
62
|
-
gltf: options.url,
|
|
63
|
-
parts: gltf.meshes.map((mesh) => ({ name: mesh.name, material: mesh.material, shape: "mesh" })),
|
|
64
|
-
materials: gltf.materials.map((material) => ({
|
|
65
|
-
name: material.name,
|
|
66
|
-
metallic: material.metallicFactor,
|
|
67
|
-
roughness: material.roughnessFactor,
|
|
68
|
-
alphaMode: material.alphaMode
|
|
69
|
-
}))
|
|
70
|
-
}, options);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function normalizeManifest(manifest: ProductManifest, options: ProductAssetLoadOptions): ProductManifest {
|
|
74
|
-
return {
|
|
75
|
-
...manifest,
|
|
76
|
-
id: options.id ?? manifest.id,
|
|
77
|
-
title: options.title ?? manifest.title,
|
|
78
|
-
category: options.category ?? manifest.category,
|
|
79
|
-
gltf: manifest.gltf ?? options.url,
|
|
80
|
-
parts: manifest.parts ?? [],
|
|
81
|
-
materials: manifest.materials ?? []
|
|
82
|
-
};
|
|
83
|
-
}
|
package/src/ProductCamera.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { computePerspectiveCameraFrame } from "@aura3d/rendering";
|
|
2
|
-
import type { ProductAsset, ProductCameraFrame, ProductCameraOptions, ProductCameraPreset } from "./ProductTypes";
|
|
3
|
-
|
|
4
|
-
const CAMERA_PRESETS: Record<ProductCameraPreset, { readonly yaw: number; readonly pitch: number; readonly padding: number }> = {
|
|
5
|
-
"front-three-quarter": { yaw: -0.42, pitch: -0.16, padding: 0.22 },
|
|
6
|
-
"side-profile": { yaw: Math.PI / 2, pitch: -0.08, padding: 0.28 },
|
|
7
|
-
"top-detail": { yaw: -0.22, pitch: -0.92, padding: 0.2 },
|
|
8
|
-
"macro-detail": { yaw: -0.5, pitch: -0.12, padding: 0.02 }
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export function createProductCameraFrame(asset: ProductAsset, options: ProductCameraOptions = {}): ProductCameraFrame {
|
|
12
|
-
const preset = options.preset ?? "front-three-quarter";
|
|
13
|
-
const viewport = options.viewport ?? { width: 1280, height: 900 };
|
|
14
|
-
const config = CAMERA_PRESETS[preset];
|
|
15
|
-
const frame = computePerspectiveCameraFrame(asset.resources.bounds, viewport, {
|
|
16
|
-
paddingRatio: options.paddingRatio ?? config.padding,
|
|
17
|
-
yawRadians: config.yaw,
|
|
18
|
-
pitchRadians: config.pitch,
|
|
19
|
-
nearPadding: preset === "macro-detail" ? 0.04 : 0.16,
|
|
20
|
-
farPadding: 2.4,
|
|
21
|
-
minDistance: preset === "macro-detail" ? 0.35 : 1
|
|
22
|
-
});
|
|
23
|
-
const camera = {
|
|
24
|
-
projectionMatrix: frame.projectionMatrix,
|
|
25
|
-
viewMatrix: frame.viewMatrix,
|
|
26
|
-
viewProjectionMatrix: frame.viewProjectionMatrix,
|
|
27
|
-
cameraPosition: frame.cameraPosition
|
|
28
|
-
};
|
|
29
|
-
validateProductCameraFrame({ preset, frame, camera });
|
|
30
|
-
return { preset, frame, camera };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function validateProductCameraFrame(frame: ProductCameraFrame): void {
|
|
34
|
-
const numbers = [
|
|
35
|
-
...frame.frame.center,
|
|
36
|
-
...frame.frame.cameraPosition,
|
|
37
|
-
frame.frame.near,
|
|
38
|
-
frame.frame.far,
|
|
39
|
-
frame.frame.aspect,
|
|
40
|
-
...Array.from(frame.frame.viewProjectionMatrix)
|
|
41
|
-
];
|
|
42
|
-
if (numbers.some((value) => !Number.isFinite(value))) {
|
|
43
|
-
throw new RangeError(`Product camera frame ${frame.preset} contains a non-finite value.`);
|
|
44
|
-
}
|
|
45
|
-
if (frame.frame.far <= frame.frame.near) {
|
|
46
|
-
throw new RangeError(`Product camera frame ${frame.preset} far plane must exceed near plane.`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { RenderDeviceDiagnostics } from "@aura3d/rendering";
|
|
2
|
-
import type { ProductAsset, ProductDiagnostics } from "./ProductTypes";
|
|
3
|
-
|
|
4
|
-
export function createProductDiagnostics(asset: ProductAsset, renderDiagnostics?: RenderDeviceDiagnostics): ProductDiagnostics {
|
|
5
|
-
const warnings: string[] = [];
|
|
6
|
-
if (asset.parts.length < 8) warnings.push(`Product asset ${asset.id} has too few named parts for Product Studio.`);
|
|
7
|
-
if (asset.materials.length < 3) warnings.push(`Product asset ${asset.id} has too few named materials for Product Studio.`);
|
|
8
|
-
if (asset.gltf.images.length === 0 || asset.gltf.textures.length === 0) warnings.push(`Product asset ${asset.id} is missing texture-backed materials.`);
|
|
9
|
-
if (asset.gltf.meshes.some((mesh) => !mesh.name)) warnings.push(`Product asset ${asset.id} contains unnamed meshes.`);
|
|
10
|
-
return {
|
|
11
|
-
assetId: asset.id,
|
|
12
|
-
partCount: asset.parts.length,
|
|
13
|
-
materialCount: asset.materials.length,
|
|
14
|
-
textureCount: asset.gltf.textures.length,
|
|
15
|
-
meshCount: asset.gltf.meshes.length,
|
|
16
|
-
warnings,
|
|
17
|
-
...(renderDiagnostics ? { renderDiagnostics } : {})
|
|
18
|
-
};
|
|
19
|
-
}
|
package/src/ProductExport.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { ProductExportResult, ProductRenderScene, ProductSceneManifest } from "./ProductTypes";
|
|
2
|
-
|
|
3
|
-
export async function exportProductRender(canvas: HTMLCanvasElement | OffscreenCanvas, scene: ProductRenderScene): Promise<ProductExportResult> {
|
|
4
|
-
const dataUrl = await canvasToDataUrl(canvas);
|
|
5
|
-
return {
|
|
6
|
-
mimeType: "image/png",
|
|
7
|
-
dataUrl,
|
|
8
|
-
byteLength: dataUrl.length,
|
|
9
|
-
manifest: exportProductSceneManifest(scene)
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function exportProductSceneManifest(scene: ProductRenderScene): ProductSceneManifest {
|
|
14
|
-
return {
|
|
15
|
-
schema: "a3d-product-studio-scene",
|
|
16
|
-
assetId: scene.asset.id,
|
|
17
|
-
title: scene.asset.title,
|
|
18
|
-
partCount: scene.asset.parts.length,
|
|
19
|
-
materialCount: scene.asset.materials.length,
|
|
20
|
-
materialMode: scene.materialMode.id,
|
|
21
|
-
lightingPreset: scene.lighting.preset,
|
|
22
|
-
cameraPreset: scene.cameraFrame.preset,
|
|
23
|
-
generatedAt: new Date().toISOString()
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function canvasToDataUrl(canvas: HTMLCanvasElement | OffscreenCanvas): Promise<string> {
|
|
28
|
-
if ("toDataURL" in canvas && typeof canvas.toDataURL === "function") {
|
|
29
|
-
return canvas.toDataURL("image/png");
|
|
30
|
-
}
|
|
31
|
-
if ("convertToBlob" in canvas && typeof canvas.convertToBlob === "function") {
|
|
32
|
-
const blob = await canvas.convertToBlob({ type: "image/png" });
|
|
33
|
-
const buffer = await blob.arrayBuffer();
|
|
34
|
-
return `data:image/png;base64,${base64(buffer)}`;
|
|
35
|
-
}
|
|
36
|
-
throw new Error("Product render export requires HTMLCanvasElement.toDataURL or OffscreenCanvas.convertToBlob.");
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function base64(buffer: ArrayBuffer): string {
|
|
40
|
-
let binary = "";
|
|
41
|
-
for (const byte of new Uint8Array(buffer)) binary += String.fromCharCode(byte);
|
|
42
|
-
return btoa(binary);
|
|
43
|
-
}
|
package/src/ProductFloor.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { composeMat4 } from "@aura3d/scene";
|
|
2
|
-
import { Geometry, PBRMaterial } from "@aura3d/rendering";
|
|
3
|
-
import type { RenderItem } from "@aura3d/rendering";
|
|
4
|
-
import type { ProductAsset } from "./ProductTypes";
|
|
5
|
-
|
|
6
|
-
export function createProductFloor(asset: ProductAsset): RenderItem {
|
|
7
|
-
const bounds = asset.resources.bounds;
|
|
8
|
-
const width = Math.max(bounds.max[0] - bounds.min[0], bounds.max[2] - bounds.min[2], 1) * 3.2;
|
|
9
|
-
const y = bounds.min[1] - 0.04;
|
|
10
|
-
return {
|
|
11
|
-
label: "product-studio-floor",
|
|
12
|
-
geometry: Geometry.texturedCube(1),
|
|
13
|
-
material: new PBRMaterial({ name: "product-studio-floor-material", baseColor: [0.72, 0.73, 0.72, 1], metallic: 0, roughness: 0.62 }),
|
|
14
|
-
includeInAutoFrame: false,
|
|
15
|
-
modelMatrix: composeMat4([0, y, 0], [0, 0, 0, 1], [width, 0.035, width])
|
|
16
|
-
};
|
|
17
|
-
}
|
package/src/ProductLighting.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { DirectionalLight } from "@aura3d/scene";
|
|
2
|
-
import { createLightingDefault } from "@aura3d/rendering";
|
|
3
|
-
import type { CollectedLight } from "@aura3d/rendering";
|
|
4
|
-
import type { ProductLightingConfig, ProductLightingPreset } from "./ProductTypes";
|
|
5
|
-
|
|
6
|
-
export function createProductLightingPreset(preset: ProductLightingPreset = "catalog-softbox"): ProductLightingConfig {
|
|
7
|
-
const base = createLightingDefault(preset === "inspection-bay" ? "interiorGallery" : preset === "hero-contrast" ? "gameNight" : "studioProduct");
|
|
8
|
-
const key = makeDirectionalLight("product-key-light", [1, 0.92, 0.82], preset === "hero-contrast" ? 3.6 : 2.8, [0.42, -0.72, -0.55], true);
|
|
9
|
-
const rim = makeDirectionalLight("product-rim-light", [0.58, 0.68, 1], preset === "inspection-bay" ? 0.8 : 1.15, [-0.56, -0.28, 0.78], false);
|
|
10
|
-
return {
|
|
11
|
-
preset,
|
|
12
|
-
environmentLighting: base.environmentLighting,
|
|
13
|
-
postprocess: base.postprocess,
|
|
14
|
-
shadow: {
|
|
15
|
-
...base.shadow,
|
|
16
|
-
light: key.source,
|
|
17
|
-
strength: preset === "inspection-bay" ? 0.32 : preset === "hero-contrast" ? 0.5 : 0.38
|
|
18
|
-
},
|
|
19
|
-
lights: [key, rim]
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function makeDirectionalLight(
|
|
24
|
-
name: string,
|
|
25
|
-
color: readonly [number, number, number],
|
|
26
|
-
intensity: number,
|
|
27
|
-
direction: readonly [number, number, number],
|
|
28
|
-
castsShadow: boolean
|
|
29
|
-
): CollectedLight {
|
|
30
|
-
const source = new DirectionalLight(name);
|
|
31
|
-
source.color = [...color] as [number, number, number];
|
|
32
|
-
source.intensity = intensity;
|
|
33
|
-
source.castsShadow = castsShadow;
|
|
34
|
-
return {
|
|
35
|
-
kind: "directional",
|
|
36
|
-
color,
|
|
37
|
-
intensity,
|
|
38
|
-
position: [0, 0, 0],
|
|
39
|
-
direction: normalize(direction),
|
|
40
|
-
range: 0,
|
|
41
|
-
spotAngle: 0,
|
|
42
|
-
penumbra: 0,
|
|
43
|
-
castsShadow,
|
|
44
|
-
layerMask: 0xffffffff,
|
|
45
|
-
source
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function normalize(value: readonly [number, number, number]): readonly [number, number, number] {
|
|
50
|
-
const length = Math.hypot(value[0], value[1], value[2]);
|
|
51
|
-
return length === 0 ? [0, -1, -1] : [value[0] / length, value[1] / length, value[2] / length];
|
|
52
|
-
}
|
package/src/ProductMaterials.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { PBRMaterial } from "@aura3d/rendering";
|
|
2
|
-
import type { Material } from "@aura3d/rendering";
|
|
3
|
-
import type { ProductAsset, ProductMaterialMode, ProductMaterialModeId } from "./ProductTypes";
|
|
4
|
-
|
|
5
|
-
export function createProductMaterialMode(id: ProductMaterialModeId = "asset"): ProductMaterialMode {
|
|
6
|
-
switch (id) {
|
|
7
|
-
case "asset":
|
|
8
|
-
return { id, label: "Asset", description: "Use product-authored glTF materials." };
|
|
9
|
-
case "clay":
|
|
10
|
-
return { id, label: "Clay", description: "Neutral clay material for shape review." };
|
|
11
|
-
case "matte":
|
|
12
|
-
return { id, label: "Matte", description: "Low-glare product material pass." };
|
|
13
|
-
case "metal-check":
|
|
14
|
-
return { id, label: "Metal", description: "High-metallic inspection material pass." };
|
|
15
|
-
case "contrast":
|
|
16
|
-
return { id, label: "Contrast", description: "High contrast material separation pass." };
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function applyProductMaterialMode(asset: ProductAsset, mode: ProductMaterialMode): ReadonlyMap<string, Material> {
|
|
21
|
-
if (mode.id === "asset") {
|
|
22
|
-
return asset.resources.materialLibrary;
|
|
23
|
-
}
|
|
24
|
-
const output = new Map<string, Material>();
|
|
25
|
-
let index = 0;
|
|
26
|
-
for (const key of asset.resources.materialLibrary.keys()) {
|
|
27
|
-
output.set(key, materialForMode(mode.id, index));
|
|
28
|
-
index += 1;
|
|
29
|
-
}
|
|
30
|
-
return output;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function materialForMode(mode: ProductMaterialModeId, index: number): PBRMaterial {
|
|
34
|
-
if (mode === "metal-check") {
|
|
35
|
-
return new PBRMaterial({ name: `product-metal-check-${index}`, baseColor: [0.72, 0.72, 0.68, 1], metallic: 1, roughness: 0.18 });
|
|
36
|
-
}
|
|
37
|
-
if (mode === "contrast") {
|
|
38
|
-
const palette = [[0.9, 0.16, 0.12, 1], [0.1, 0.42, 0.86, 1], [0.96, 0.76, 0.18, 1], [0.16, 0.72, 0.38, 1]] as const;
|
|
39
|
-
return new PBRMaterial({ name: `product-contrast-${index}`, baseColor: palette[index % palette.length], metallic: 0.15, roughness: 0.42 });
|
|
40
|
-
}
|
|
41
|
-
if (mode === "matte") {
|
|
42
|
-
return new PBRMaterial({ name: `product-matte-${index}`, baseColor: [0.56, 0.6, 0.58, 1], metallic: 0, roughness: 0.86 });
|
|
43
|
-
}
|
|
44
|
-
return new PBRMaterial({ name: `product-clay-${index}`, baseColor: [0.74, 0.7, 0.64, 1], metallic: 0, roughness: 0.74 });
|
|
45
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { createGLTFRenderSource } from "@aura3d/assets";
|
|
2
|
-
import { createProductCameraFrame } from "./ProductCamera";
|
|
3
|
-
import { createProductFloor } from "./ProductFloor";
|
|
4
|
-
import { createProductLightingPreset } from "./ProductLighting";
|
|
5
|
-
import { applyProductMaterialMode, createProductMaterialMode } from "./ProductMaterials";
|
|
6
|
-
import type { ProductAsset, ProductRenderScene, ProductRenderSceneOptions } from "./ProductTypes";
|
|
7
|
-
|
|
8
|
-
export function createProductRenderScene(asset: ProductAsset, options: ProductRenderSceneOptions = {}): ProductRenderScene {
|
|
9
|
-
const lighting = options.lighting ?? createProductLightingPreset();
|
|
10
|
-
const camera = options.camera ?? createProductCameraFrame(asset);
|
|
11
|
-
const materialMode = options.materialMode ?? createProductMaterialMode("asset");
|
|
12
|
-
const renderItems = options.floor === false ? [] : [createProductFloor(asset)];
|
|
13
|
-
const baseSource = createGLTFRenderSource({
|
|
14
|
-
scene: asset.resources.scene,
|
|
15
|
-
geometryLibrary: asset.resources.geometryLibrary,
|
|
16
|
-
materialLibrary: applyProductMaterialMode(asset, materialMode),
|
|
17
|
-
morphTargetLibrary: asset.resources.morphTargetLibrary,
|
|
18
|
-
bounds: asset.resources.bounds
|
|
19
|
-
}, {
|
|
20
|
-
qualityPreset: "hdr-studio-preview",
|
|
21
|
-
environmentLighting: lighting.environmentLighting,
|
|
22
|
-
postprocess: lighting.postprocess,
|
|
23
|
-
shadow: lighting.shadow,
|
|
24
|
-
renderItems,
|
|
25
|
-
cameraPolicy: "require",
|
|
26
|
-
cameraPosition: camera.frame.cameraPosition,
|
|
27
|
-
frustumCulling: true
|
|
28
|
-
});
|
|
29
|
-
const source = { ...baseSource, collectedLights: lighting.lights };
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
asset,
|
|
33
|
-
source,
|
|
34
|
-
camera: camera.camera,
|
|
35
|
-
cameraFrame: camera,
|
|
36
|
-
lighting,
|
|
37
|
-
materialMode,
|
|
38
|
-
renderItems
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function updateProductRenderScene(scene: ProductRenderScene, options: ProductRenderSceneOptions = {}): ProductRenderScene {
|
|
43
|
-
return createProductRenderScene(scene.asset, {
|
|
44
|
-
lighting: options.lighting ?? scene.lighting,
|
|
45
|
-
camera: options.camera ?? scene.cameraFrame,
|
|
46
|
-
materialMode: options.materialMode ?? scene.materialMode,
|
|
47
|
-
floor: options.floor
|
|
48
|
-
});
|
|
49
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
export type ProductShowcaseSlot = "hero" | "left-detail" | "left-transparent" | "right-variant";
|
|
2
|
-
|
|
3
|
-
export interface ProductShowcaseSlotInput {
|
|
4
|
-
readonly assetId: string;
|
|
5
|
-
readonly slot: ProductShowcaseSlot;
|
|
6
|
-
readonly materialVariantControl?: string;
|
|
7
|
-
readonly defaultMaterialVariant?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface ProductShowcaseLayoutItem {
|
|
11
|
-
readonly assetId: string;
|
|
12
|
-
readonly slot: ProductShowcaseSlot;
|
|
13
|
-
readonly position: readonly [number, number, number];
|
|
14
|
-
readonly scale: readonly [number, number, number];
|
|
15
|
-
readonly targetHeight: number;
|
|
16
|
-
readonly yawRadians: number;
|
|
17
|
-
readonly turntableSpeedRadiansPerSecond: number;
|
|
18
|
-
readonly materialVariantControl?: string;
|
|
19
|
-
readonly defaultMaterialVariant?: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface ProductShowcaseFrame {
|
|
23
|
-
readonly boundsMin: readonly [number, number, number];
|
|
24
|
-
readonly boundsMax: readonly [number, number, number];
|
|
25
|
-
readonly heroPaddingRatio: number;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface ProductShowcaseLayout {
|
|
29
|
-
readonly schema: "a3d-product-showcase-layout";
|
|
30
|
-
readonly mode: "hero-product-with-secondary-detail";
|
|
31
|
-
readonly items: readonly ProductShowcaseLayoutItem[];
|
|
32
|
-
readonly frame: ProductShowcaseFrame;
|
|
33
|
-
readonly limitations: readonly string[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const COMPACT_SLOT_LAYOUT: Record<ProductShowcaseSlot, Omit<ProductShowcaseLayoutItem, "assetId" | "slot" | "materialVariantControl" | "defaultMaterialVariant">> = {
|
|
37
|
-
hero: {
|
|
38
|
-
position: [-0.28, -0.88, 0.0],
|
|
39
|
-
scale: [1, 1, 1],
|
|
40
|
-
targetHeight: 0.92,
|
|
41
|
-
yawRadians: -0.38,
|
|
42
|
-
turntableSpeedRadiansPerSecond: 0.001
|
|
43
|
-
},
|
|
44
|
-
"left-detail": {
|
|
45
|
-
position: [-2.28, -0.86, -0.72],
|
|
46
|
-
scale: [1, 1, 1],
|
|
47
|
-
targetHeight: 0.24,
|
|
48
|
-
yawRadians: 0.34,
|
|
49
|
-
turntableSpeedRadiansPerSecond: 0.1
|
|
50
|
-
},
|
|
51
|
-
"left-transparent": {
|
|
52
|
-
position: [-2.36, -0.87, 0.68],
|
|
53
|
-
scale: [1, 1, 1],
|
|
54
|
-
targetHeight: 0.11,
|
|
55
|
-
yawRadians: 0.58,
|
|
56
|
-
turntableSpeedRadiansPerSecond: 0.06
|
|
57
|
-
},
|
|
58
|
-
"right-variant": {
|
|
59
|
-
position: [2.34, -0.86, 0.58],
|
|
60
|
-
scale: [1, 1, 1],
|
|
61
|
-
targetHeight: 0.24,
|
|
62
|
-
yawRadians: -0.82,
|
|
63
|
-
turntableSpeedRadiansPerSecond: 0.08
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const COMPACT_FRAME: ProductShowcaseFrame = {
|
|
68
|
-
boundsMin: [-1.3, -1.0, -0.78],
|
|
69
|
-
boundsMax: [1.3, 0.5, 0.86],
|
|
70
|
-
heroPaddingRatio: 0.02
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export function createProductShowcaseLayout(slots: readonly ProductShowcaseSlotInput[]): ProductShowcaseLayout {
|
|
74
|
-
return {
|
|
75
|
-
schema: "a3d-product-showcase-layout",
|
|
76
|
-
mode: "hero-product-with-secondary-detail",
|
|
77
|
-
items: slots.map((slot) => ({
|
|
78
|
-
assetId: slot.assetId,
|
|
79
|
-
slot: slot.slot,
|
|
80
|
-
...COMPACT_SLOT_LAYOUT[slot.slot],
|
|
81
|
-
...(slot.materialVariantControl ? { materialVariantControl: slot.materialVariantControl } : {}),
|
|
82
|
-
...(slot.defaultMaterialVariant ? { defaultMaterialVariant: slot.defaultMaterialVariant } : {})
|
|
83
|
-
})),
|
|
84
|
-
frame: COMPACT_FRAME,
|
|
85
|
-
limitations: [
|
|
86
|
-
"This layout helper owns deterministic hero-first product staging and camera bounds; secondary products remain loaded but are kept outside the default hero frame so they cannot read as debris attached to the car.",
|
|
87
|
-
"The layout does not infer a semantic part graph from imported GLBs; imported mesh triangle picking, authored exploded timelines, and physical contact shadows remain separate engine/runtime capabilities."
|
|
88
|
-
]
|
|
89
|
-
};
|
|
90
|
-
}
|
package/src/ProductStudio.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { Renderer } from "@aura3d/rendering";
|
|
2
|
-
import { createProductCameraFrame } from "./ProductCamera";
|
|
3
|
-
import { exportProductRender, exportProductSceneManifest } from "./ProductExport";
|
|
4
|
-
import { createProductLightingPreset } from "./ProductLighting";
|
|
5
|
-
import { createProductMaterialMode } from "./ProductMaterials";
|
|
6
|
-
import type {
|
|
7
|
-
ProductAsset,
|
|
8
|
-
ProductCameraFrame,
|
|
9
|
-
ProductCameraPreset,
|
|
10
|
-
ProductExportResult,
|
|
11
|
-
ProductLightingConfig,
|
|
12
|
-
ProductLightingPreset,
|
|
13
|
-
ProductMaterialMode,
|
|
14
|
-
ProductMaterialModeId,
|
|
15
|
-
ProductRenderScene,
|
|
16
|
-
ProductSceneManifest,
|
|
17
|
-
ProductStudio,
|
|
18
|
-
ProductStudioOptions
|
|
19
|
-
} from "./ProductTypes";
|
|
20
|
-
|
|
21
|
-
export async function createProductStudio(options: ProductStudioOptions = {}): Promise<ProductStudio> {
|
|
22
|
-
const renderer = await Renderer.create({
|
|
23
|
-
clearColor: [0.03, 0.035, 0.038, 1],
|
|
24
|
-
width: options.width ?? 1280,
|
|
25
|
-
height: options.height ?? 900,
|
|
26
|
-
...options
|
|
27
|
-
});
|
|
28
|
-
return new ProductStudioRuntime(renderer, options.canvas);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
class ProductStudioRuntime implements ProductStudio {
|
|
32
|
-
constructor(
|
|
33
|
-
public readonly renderer: Renderer,
|
|
34
|
-
private readonly canvas?: HTMLCanvasElement | OffscreenCanvas
|
|
35
|
-
) {}
|
|
36
|
-
|
|
37
|
-
render(scene: ProductRenderScene) {
|
|
38
|
-
return this.renderer.render(scene.source, scene.camera);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
resize(width: number, height: number): void {
|
|
42
|
-
this.renderer.resize(width, height);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
setLighting(preset: ProductLightingPreset): ProductLightingConfig {
|
|
46
|
-
return createProductLightingPreset(preset);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
setCamera(asset: ProductAsset, preset: ProductCameraPreset, viewport = { width: 1280, height: 900 }): ProductCameraFrame {
|
|
50
|
-
return createProductCameraFrame(asset, { preset, viewport });
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
setMaterialMode(mode: ProductMaterialModeId): ProductMaterialMode {
|
|
54
|
-
return createProductMaterialMode(mode);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async exportPng(scene: ProductRenderScene): Promise<ProductExportResult> {
|
|
58
|
-
if (!this.canvas) {
|
|
59
|
-
throw new Error("ProductStudio exportPng requires a canvas-backed studio.");
|
|
60
|
-
}
|
|
61
|
-
this.render(scene);
|
|
62
|
-
return exportProductRender(this.canvas, scene);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
exportSceneManifest(scene: ProductRenderScene): ProductSceneManifest {
|
|
66
|
-
return exportProductSceneManifest(scene);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
dispose(): void {
|
|
70
|
-
this.renderer.dispose();
|
|
71
|
-
}
|
|
72
|
-
}
|
package/src/ProductTypes.ts
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import type { GLTFAsset, GLTFRenderResources } from "@aura3d/assets";
|
|
2
|
-
import type {
|
|
3
|
-
CameraLike,
|
|
4
|
-
CollectedLight,
|
|
5
|
-
EnvironmentLightingOptions,
|
|
6
|
-
PerspectiveCameraFrame,
|
|
7
|
-
RenderDeviceDiagnostics,
|
|
8
|
-
RenderItem,
|
|
9
|
-
Renderer,
|
|
10
|
-
RendererOptions,
|
|
11
|
-
RendererPostProcessOptions,
|
|
12
|
-
RendererShadowOptions,
|
|
13
|
-
RenderSource
|
|
14
|
-
} from "@aura3d/rendering";
|
|
15
|
-
|
|
16
|
-
export type ProductAssetId = "camera-kit" | "speaker" | "watch" | (string & {});
|
|
17
|
-
export type ProductLightingPreset = "catalog-softbox" | "inspection-bay" | "hero-contrast";
|
|
18
|
-
export type ProductCameraPreset = "front-three-quarter" | "side-profile" | "top-detail" | "macro-detail";
|
|
19
|
-
export type ProductMaterialModeId = "asset" | "clay" | "matte" | "metal-check" | "contrast";
|
|
20
|
-
|
|
21
|
-
export interface ProductPartDescriptor {
|
|
22
|
-
readonly name: string;
|
|
23
|
-
readonly material: string;
|
|
24
|
-
readonly shape?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface ProductMaterialDescriptor {
|
|
28
|
-
readonly name: string;
|
|
29
|
-
readonly metallic: number;
|
|
30
|
-
readonly roughness: number;
|
|
31
|
-
readonly alphaMode: "OPAQUE" | "BLEND" | "MASK";
|
|
32
|
-
readonly textureSet?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface ProductManifest {
|
|
36
|
-
readonly schema: string;
|
|
37
|
-
readonly id: ProductAssetId;
|
|
38
|
-
readonly title: string;
|
|
39
|
-
readonly category: string;
|
|
40
|
-
readonly gltf: string;
|
|
41
|
-
readonly parts: readonly ProductPartDescriptor[];
|
|
42
|
-
readonly materials: readonly ProductMaterialDescriptor[];
|
|
43
|
-
readonly rejectedInputs?: readonly string[];
|
|
44
|
-
readonly requirements?: Readonly<Record<string, unknown>>;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface ProductAsset {
|
|
48
|
-
readonly id: ProductAssetId;
|
|
49
|
-
readonly title: string;
|
|
50
|
-
readonly category: string;
|
|
51
|
-
readonly url: string;
|
|
52
|
-
readonly manifest: ProductManifest;
|
|
53
|
-
readonly gltf: GLTFAsset;
|
|
54
|
-
readonly resources: GLTFRenderResources;
|
|
55
|
-
readonly parts: readonly ProductPartDescriptor[];
|
|
56
|
-
readonly materials: readonly ProductMaterialDescriptor[];
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface ProductAssetLoadOptions {
|
|
60
|
-
readonly url: string;
|
|
61
|
-
readonly manifestUrl?: string;
|
|
62
|
-
readonly id?: ProductAssetId;
|
|
63
|
-
readonly title?: string;
|
|
64
|
-
readonly category?: string;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export interface ProductLightingConfig {
|
|
68
|
-
readonly preset: ProductLightingPreset;
|
|
69
|
-
readonly environmentLighting: EnvironmentLightingOptions;
|
|
70
|
-
readonly postprocess: RendererPostProcessOptions;
|
|
71
|
-
readonly shadow: RendererShadowOptions;
|
|
72
|
-
readonly lights: readonly CollectedLight[];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export interface ProductCameraFrame {
|
|
76
|
-
readonly preset: ProductCameraPreset;
|
|
77
|
-
readonly frame: PerspectiveCameraFrame;
|
|
78
|
-
readonly camera: CameraLike;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface ProductCameraOptions {
|
|
82
|
-
readonly preset?: ProductCameraPreset;
|
|
83
|
-
readonly viewport?: {
|
|
84
|
-
readonly width: number;
|
|
85
|
-
readonly height: number;
|
|
86
|
-
};
|
|
87
|
-
readonly paddingRatio?: number;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export interface ProductMaterialMode {
|
|
91
|
-
readonly id: ProductMaterialModeId;
|
|
92
|
-
readonly label: string;
|
|
93
|
-
readonly description: string;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export interface ProductRenderSceneOptions {
|
|
97
|
-
readonly lighting?: ProductLightingConfig;
|
|
98
|
-
readonly camera?: ProductCameraFrame;
|
|
99
|
-
readonly materialMode?: ProductMaterialMode;
|
|
100
|
-
readonly floor?: boolean;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export interface ProductRenderScene {
|
|
104
|
-
readonly asset: ProductAsset;
|
|
105
|
-
readonly source: RenderSource;
|
|
106
|
-
readonly camera: CameraLike;
|
|
107
|
-
readonly cameraFrame: ProductCameraFrame;
|
|
108
|
-
readonly lighting: ProductLightingConfig;
|
|
109
|
-
readonly materialMode: ProductMaterialMode;
|
|
110
|
-
readonly renderItems: readonly RenderItem[];
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export interface ProductDiagnostics {
|
|
114
|
-
readonly assetId: ProductAssetId;
|
|
115
|
-
readonly partCount: number;
|
|
116
|
-
readonly materialCount: number;
|
|
117
|
-
readonly textureCount: number;
|
|
118
|
-
readonly meshCount: number;
|
|
119
|
-
readonly warnings: readonly string[];
|
|
120
|
-
readonly renderDiagnostics?: RenderDeviceDiagnostics;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export interface ProductSceneManifest {
|
|
124
|
-
readonly schema: "a3d-product-studio-scene";
|
|
125
|
-
readonly assetId: ProductAssetId;
|
|
126
|
-
readonly title: string;
|
|
127
|
-
readonly partCount: number;
|
|
128
|
-
readonly materialCount: number;
|
|
129
|
-
readonly materialMode: ProductMaterialModeId;
|
|
130
|
-
readonly lightingPreset: ProductLightingPreset;
|
|
131
|
-
readonly cameraPreset: ProductCameraPreset;
|
|
132
|
-
readonly generatedAt: string;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export interface ProductExportResult {
|
|
136
|
-
readonly mimeType: "image/png";
|
|
137
|
-
readonly dataUrl: string;
|
|
138
|
-
readonly byteLength: number;
|
|
139
|
-
readonly manifest: ProductSceneManifest;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export interface ProductStudioOptions extends RendererOptions {
|
|
143
|
-
readonly canvas?: HTMLCanvasElement | OffscreenCanvas;
|
|
144
|
-
readonly width?: number;
|
|
145
|
-
readonly height?: number;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export interface ProductStudio {
|
|
149
|
-
readonly renderer: Renderer;
|
|
150
|
-
render(scene: ProductRenderScene): RenderDeviceDiagnostics;
|
|
151
|
-
resize(width: number, height: number): void;
|
|
152
|
-
setLighting(preset: ProductLightingPreset): ProductLightingConfig;
|
|
153
|
-
setCamera(asset: ProductAsset, preset: ProductCameraPreset, viewport?: { readonly width: number; readonly height: number }): ProductCameraFrame;
|
|
154
|
-
setMaterialMode(mode: ProductMaterialModeId): ProductMaterialMode;
|
|
155
|
-
exportPng(scene: ProductRenderScene): Promise<ProductExportResult>;
|
|
156
|
-
exportSceneManifest(scene: ProductRenderScene): ProductSceneManifest;
|
|
157
|
-
dispose(): void;
|
|
158
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export { loadProductAsset } from "./ProductAssetLoader";
|
|
2
|
-
export { createProductCameraFrame, validateProductCameraFrame } from "./ProductCamera";
|
|
3
|
-
export { createProductDiagnostics } from "./ProductDiagnostics";
|
|
4
|
-
export { exportProductRender, exportProductSceneManifest } from "./ProductExport";
|
|
5
|
-
export { createProductFloor } from "./ProductFloor";
|
|
6
|
-
export { createProductLightingPreset } from "./ProductLighting";
|
|
7
|
-
export { applyProductMaterialMode, createProductMaterialMode } from "./ProductMaterials";
|
|
8
|
-
export { createProductRenderScene, updateProductRenderScene } from "./ProductRenderScene";
|
|
9
|
-
export { createProductShowcaseLayout } from "./ProductShowcaseLayout";
|
|
10
|
-
export { createProductStudio } from "./ProductStudio";
|
|
11
|
-
export type * from "./ProductTypes";
|
|
12
|
-
export type * from "./ProductShowcaseLayout";
|