@kite3d/engine 0.18.0 → 0.19.0-alpha.1
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/authoring.d.ts +1 -1
- package/dist/authoring.js +7 -7
- package/dist/authoring.js.map +1 -1
- package/dist/authoringValidation.d.ts +2 -5
- package/dist/authoringValidation.js +266 -289
- package/dist/authoringValidation.js.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +1145 -1440
- package/dist/index.js.map +1 -1
- package/dist/projectFormat.js +70 -81
- package/dist/projectFormat.js.map +1 -1
- package/dist/runtime/createGame.d.ts +1 -1
- package/dist/runtime/index.d.ts +0 -2
- package/dist/runtime/projectFormat.d.ts +7 -14
- package/dist/runtime.js +14086 -15497
- package/dist/runtime.js.map +1 -1
- package/dist/sceneSerialization.js +57 -52
- package/dist/sceneSerialization.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
- package/src/authoring.ts +3 -3
- package/src/authoringValidation.ts +3 -40
- package/src/index.ts +0 -2
- package/src/runtime/createGame.ts +1 -5
- package/src/runtime/index.ts +0 -2
- package/src/runtime/projectFormat.ts +20 -47
- package/src/sceneSerialization.ts +10 -2
- package/dist/generatorParams.d.ts +0 -23
- package/dist/plugins/GeneratorComponent.d.ts +0 -44
- package/src/generatorParams.ts +0 -182
- package/src/plugins/GeneratorComponent.ts +0 -287
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {ThreeViewer} from 'threepipe'
|
|
1
|
+
import type {IObject3D, ThreeViewer} from 'threepipe'
|
|
2
2
|
|
|
3
3
|
export interface SerializeSceneGltfOptions {
|
|
4
4
|
scenePath?: string
|
|
@@ -51,13 +51,21 @@ export async function serializeSceneGltf(
|
|
|
51
51
|
preserveUUIDs: true,
|
|
52
52
|
viewerConfig: true,
|
|
53
53
|
embedUrlImages: false,
|
|
54
|
-
onlyVisible:
|
|
54
|
+
onlyVisible: false,
|
|
55
|
+
shouldExportObject: isAuthoredSceneObject,
|
|
55
56
|
jsonSpaces: 2,
|
|
56
57
|
}, false)
|
|
57
58
|
if (!blob) throw new Error('The scene exporter returned no glTF data')
|
|
58
59
|
return serializeSceneGltfDocument(JSON.parse(await blob.text()), options)
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
function isAuthoredSceneObject(object: IObject3D): boolean {
|
|
63
|
+
return object.userData?.excludeFromExport !== true
|
|
64
|
+
&& object.userData?.isWidgetRoot !== true
|
|
65
|
+
&& object.isWidget !== true
|
|
66
|
+
&& object.assetType !== 'widget'
|
|
67
|
+
}
|
|
68
|
+
|
|
61
69
|
/** Canonicalize JSON glTF and extract every embedded resource. */
|
|
62
70
|
export async function serializeSceneGltfDocument(
|
|
63
71
|
input: unknown,
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export declare const generatorParamTypes: readonly ["boolean", "number", "integer", "string", "select", "vector", "color", "json"];
|
|
2
|
-
export type GeneratorParamType = typeof generatorParamTypes[number];
|
|
3
|
-
export type GeneratorParamOption = string | number | {
|
|
4
|
-
value: string | number;
|
|
5
|
-
label: string;
|
|
6
|
-
};
|
|
7
|
-
export interface GeneratorParamDefinition {
|
|
8
|
-
type?: GeneratorParamType;
|
|
9
|
-
label?: string;
|
|
10
|
-
help?: string;
|
|
11
|
-
default?: unknown;
|
|
12
|
-
options?: GeneratorParamOption[];
|
|
13
|
-
min?: number;
|
|
14
|
-
max?: number;
|
|
15
|
-
step?: number;
|
|
16
|
-
}
|
|
17
|
-
export interface GeneratorParamSchemaEntry extends Omit<GeneratorParamDefinition, 'type'> {
|
|
18
|
-
type: GeneratorParamType;
|
|
19
|
-
}
|
|
20
|
-
export type GeneratorParamSchema = Record<string, GeneratorParamSchemaEntry>;
|
|
21
|
-
export declare function parseGeneratorParamsSchema(exported: unknown, module: string, warn?: (message: string) => void): GeneratorParamSchema;
|
|
22
|
-
export declare function inferGeneratorParam(value: unknown): GeneratorParamSchemaEntry;
|
|
23
|
-
export declare function validateGeneratorParam(value: unknown, entry: GeneratorParamSchemaEntry): string | undefined;
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { ComponentDefn, IObject3D, Object3DComponent, ThreeViewer } from 'threepipe';
|
|
2
|
-
import { GeneratorParamDefinition, GeneratorParamSchema } from '../generatorParams.ts';
|
|
3
|
-
export interface GeneratorParams {
|
|
4
|
-
[key: string]: unknown;
|
|
5
|
-
}
|
|
6
|
-
export interface GeneratorContext {
|
|
7
|
-
node: IObject3D;
|
|
8
|
-
params: GeneratorParams;
|
|
9
|
-
viewer: ThreeViewer;
|
|
10
|
-
engine: Record<string, unknown>;
|
|
11
|
-
}
|
|
12
|
-
export interface GeneratorModule {
|
|
13
|
-
default?: (context: GeneratorContext) => unknown | Promise<unknown>;
|
|
14
|
-
params?: Record<string, GeneratorParamDefinition>;
|
|
15
|
-
}
|
|
16
|
-
export interface GeneratorViewerOptions {
|
|
17
|
-
base: string | URL;
|
|
18
|
-
onError?: (error: unknown) => void;
|
|
19
|
-
}
|
|
20
|
-
export declare class GeneratorComponent extends Object3DComponent {
|
|
21
|
-
static ComponentType: string;
|
|
22
|
-
static StateProperties: ComponentDefn['StateProperties'];
|
|
23
|
-
module: string;
|
|
24
|
-
params: GeneratorParams;
|
|
25
|
-
schema: GeneratorParamSchema;
|
|
26
|
-
private runRevision;
|
|
27
|
-
static configureViewer(viewer: ThreeViewer, options: GeneratorViewerOptions): void;
|
|
28
|
-
static waitForViewer(viewer: ThreeViewer): Promise<void>;
|
|
29
|
-
init(object: IObject3D, state: Record<string, unknown>): void;
|
|
30
|
-
run(): Promise<void>;
|
|
31
|
-
bake(): Promise<number>;
|
|
32
|
-
destroy(): Record<string, unknown>;
|
|
33
|
-
}
|
|
34
|
-
export interface RunGeneratorOptions extends Omit<GeneratorContext, 'engine'> {
|
|
35
|
-
module: string;
|
|
36
|
-
base: URL;
|
|
37
|
-
revision?: number;
|
|
38
|
-
isCurrent?: () => boolean;
|
|
39
|
-
onSchema?: (schema: GeneratorParamSchema) => void;
|
|
40
|
-
}
|
|
41
|
-
export declare function runGenerator({ node, params, viewer, module, base, revision, isCurrent, onSchema, }: RunGeneratorOptions): Promise<IObject3D[]>;
|
|
42
|
-
export declare function removeGeneratedChildren(node: IObject3D): void;
|
|
43
|
-
export declare function markGenerated(object: IObject3D, sourceId?: string, outputIndex?: number): void;
|
|
44
|
-
export declare function resolveGeneratorModule(module: string, base: URL): URL;
|
package/src/generatorParams.ts
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
export const generatorParamTypes = [
|
|
2
|
-
'boolean',
|
|
3
|
-
'number',
|
|
4
|
-
'integer',
|
|
5
|
-
'string',
|
|
6
|
-
'select',
|
|
7
|
-
'vector',
|
|
8
|
-
'color',
|
|
9
|
-
'json',
|
|
10
|
-
] as const
|
|
11
|
-
|
|
12
|
-
export type GeneratorParamType = typeof generatorParamTypes[number]
|
|
13
|
-
|
|
14
|
-
export type GeneratorParamOption = string | number | {
|
|
15
|
-
value: string | number
|
|
16
|
-
label: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface GeneratorParamDefinition {
|
|
20
|
-
type?: GeneratorParamType
|
|
21
|
-
label?: string
|
|
22
|
-
help?: string
|
|
23
|
-
default?: unknown
|
|
24
|
-
options?: GeneratorParamOption[]
|
|
25
|
-
min?: number
|
|
26
|
-
max?: number
|
|
27
|
-
step?: number
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface GeneratorParamSchemaEntry extends Omit<GeneratorParamDefinition, 'type'> {
|
|
31
|
-
type: GeneratorParamType
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export type GeneratorParamSchema = Record<string, GeneratorParamSchemaEntry>
|
|
35
|
-
|
|
36
|
-
export function parseGeneratorParamsSchema(
|
|
37
|
-
exported: unknown,
|
|
38
|
-
module: string,
|
|
39
|
-
warn: (message: string) => void = console.warn,
|
|
40
|
-
): GeneratorParamSchema {
|
|
41
|
-
if (exported === undefined) return {}
|
|
42
|
-
if (!isRecord(exported)) {
|
|
43
|
-
warn(`[kite3d] Ignoring invalid generator params export from ${module}: expected an object`)
|
|
44
|
-
return {}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const schema: GeneratorParamSchema = {}
|
|
48
|
-
for (const [key, value] of Object.entries(exported)) {
|
|
49
|
-
const parsed = parseEntry(value)
|
|
50
|
-
if (typeof parsed === 'string') {
|
|
51
|
-
warn(`[kite3d] Ignoring invalid generator param "${key}" from ${module}: ${parsed}`)
|
|
52
|
-
continue
|
|
53
|
-
}
|
|
54
|
-
schema[key] = parsed
|
|
55
|
-
}
|
|
56
|
-
return schema
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function inferGeneratorParam(value: unknown): GeneratorParamSchemaEntry {
|
|
60
|
-
if (typeof value === 'boolean') return {type: 'boolean'}
|
|
61
|
-
if (typeof value === 'number') return {type: 'number'}
|
|
62
|
-
if (isNumberVector(value)) return {type: 'vector'}
|
|
63
|
-
if (typeof value === 'string') return {type: 'string'}
|
|
64
|
-
return {type: 'json'}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function validateGeneratorParam(value: unknown, entry: GeneratorParamSchemaEntry): string | undefined {
|
|
68
|
-
const name = entry.label || 'Value'
|
|
69
|
-
switch (entry.type) {
|
|
70
|
-
case 'boolean':
|
|
71
|
-
return typeof value === 'boolean' ? undefined : `${name} must be true or false.`
|
|
72
|
-
case 'number':
|
|
73
|
-
case 'integer': {
|
|
74
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) return `${name} must be a number.`
|
|
75
|
-
if (entry.type === 'integer' && !Number.isInteger(value)) return `${name} must be an integer.`
|
|
76
|
-
if (entry.min !== undefined && value < entry.min) return `${name} must be at least ${entry.min}.`
|
|
77
|
-
if (entry.max !== undefined && value > entry.max) return `${name} must be at most ${entry.max}.`
|
|
78
|
-
return undefined
|
|
79
|
-
}
|
|
80
|
-
case 'string':
|
|
81
|
-
return typeof value === 'string' ? undefined : `${name} must be text.`
|
|
82
|
-
case 'select': {
|
|
83
|
-
if (typeof value !== 'string' && typeof value !== 'number') {
|
|
84
|
-
return `${name} must be one of the available options.`
|
|
85
|
-
}
|
|
86
|
-
if (entry.options?.length && !entry.options.some((option) => Object.is(optionValue(option), value))) {
|
|
87
|
-
return `${name} must be one of the available options.`
|
|
88
|
-
}
|
|
89
|
-
return undefined
|
|
90
|
-
}
|
|
91
|
-
case 'vector':
|
|
92
|
-
return isNumberVector(value) ? undefined : `${name} must be an array of 2 to 4 numbers.`
|
|
93
|
-
case 'color':
|
|
94
|
-
return typeof value === 'string' && /^#[\da-f]{6}$/i.test(value)
|
|
95
|
-
? undefined
|
|
96
|
-
: `${name} must be a six-digit hex color.`
|
|
97
|
-
case 'json':
|
|
98
|
-
return isJsonValue(value) ? undefined : `${name} must be a JSON value.`
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function parseEntry(value: unknown): GeneratorParamSchemaEntry | string {
|
|
103
|
-
if (!isRecord(value)) return 'expected an object'
|
|
104
|
-
if (value.type !== undefined && !generatorParamTypes.includes(value.type as GeneratorParamType)) {
|
|
105
|
-
return `unknown type ${JSON.stringify(value.type)}`
|
|
106
|
-
}
|
|
107
|
-
if (value.label !== undefined && typeof value.label !== 'string') return 'label must be a string'
|
|
108
|
-
if (value.help !== undefined && typeof value.help !== 'string') return 'help must be a string'
|
|
109
|
-
if (value.options !== undefined && !isOptions(value.options)) return 'options must contain strings, numbers, or labeled values'
|
|
110
|
-
for (const bound of ['min', 'max', 'step'] as const) {
|
|
111
|
-
if (value[bound] !== undefined && !isFiniteNumber(value[bound])) return `${bound} must be a finite number`
|
|
112
|
-
}
|
|
113
|
-
if (isFiniteNumber(value.step) && value.step <= 0) return 'step must be greater than zero'
|
|
114
|
-
if (isFiniteNumber(value.min) && isFiniteNumber(value.max) && value.min > value.max) {
|
|
115
|
-
return 'min must not be greater than max'
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const type = value.type as GeneratorParamType | undefined
|
|
119
|
-
?? (value.options !== undefined ? 'select' : inferGeneratorParam(value.default).type)
|
|
120
|
-
if ((value.min !== undefined || value.max !== undefined || value.step !== undefined)
|
|
121
|
-
&& type !== 'number' && type !== 'integer') {
|
|
122
|
-
return 'min, max, and step require a number or integer type'
|
|
123
|
-
}
|
|
124
|
-
if (value.options !== undefined && type !== 'select') return 'options require the select type'
|
|
125
|
-
|
|
126
|
-
const entry: GeneratorParamSchemaEntry = {
|
|
127
|
-
type,
|
|
128
|
-
...(value.label !== undefined ? {label: value.label} : {}),
|
|
129
|
-
...(value.help !== undefined ? {help: value.help} : {}),
|
|
130
|
-
...(Object.prototype.hasOwnProperty.call(value, 'default') ? {default: value.default} : {}),
|
|
131
|
-
...(value.options !== undefined ? {options: value.options} : {}),
|
|
132
|
-
...(isFiniteNumber(value.min) ? {min: value.min} : {}),
|
|
133
|
-
...(isFiniteNumber(value.max) ? {max: value.max} : {}),
|
|
134
|
-
...(isFiniteNumber(value.step) ? {step: value.step} : {}),
|
|
135
|
-
}
|
|
136
|
-
if (Object.prototype.hasOwnProperty.call(entry, 'default')) {
|
|
137
|
-
const error = validateGeneratorParam(entry.default, entry)
|
|
138
|
-
if (error) return `invalid default: ${error}`
|
|
139
|
-
}
|
|
140
|
-
return entry
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function isOptions(value: unknown): value is GeneratorParamOption[] {
|
|
144
|
-
return Array.isArray(value) && value.every((option) => {
|
|
145
|
-
if (typeof option === 'string' || typeof option === 'number') return true
|
|
146
|
-
return isRecord(option)
|
|
147
|
-
&& (typeof option.value === 'string' || typeof option.value === 'number')
|
|
148
|
-
&& typeof option.label === 'string'
|
|
149
|
-
})
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function optionValue(option: GeneratorParamOption): string | number {
|
|
153
|
-
return typeof option === 'object' ? option.value : option
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function isNumberVector(value: unknown): value is number[] {
|
|
157
|
-
return Array.isArray(value)
|
|
158
|
-
&& value.length >= 2
|
|
159
|
-
&& value.length <= 4
|
|
160
|
-
&& value.every(isFiniteNumber)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function isFiniteNumber(value: unknown): value is number {
|
|
164
|
-
return typeof value === 'number' && Number.isFinite(value)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function isJsonValue(value: unknown, seen = new Set<object>()): boolean {
|
|
168
|
-
if (value === null || typeof value === 'string' || typeof value === 'boolean') return true
|
|
169
|
-
if (typeof value === 'number') return Number.isFinite(value)
|
|
170
|
-
if (!value || typeof value !== 'object' || seen.has(value)) return false
|
|
171
|
-
seen.add(value)
|
|
172
|
-
const valid = Array.isArray(value)
|
|
173
|
-
? value.every((item) => isJsonValue(item, seen))
|
|
174
|
-
: Object.getPrototypeOf(value) === Object.prototype
|
|
175
|
-
&& Object.values(value).every((item) => isJsonValue(item, seen))
|
|
176
|
-
seen.delete(value)
|
|
177
|
-
return valid
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
181
|
-
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
|
182
|
-
}
|
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
import * as ThreePipe from 'threepipe'
|
|
2
|
-
import {
|
|
3
|
-
type ComponentDefn,
|
|
4
|
-
type IObject3D,
|
|
5
|
-
Object3DComponent,
|
|
6
|
-
type ThreeViewer,
|
|
7
|
-
} from 'threepipe'
|
|
8
|
-
import {getAuthoringMetadata, setAuthoringMetadata, type AuthoringMetadata} from '../authoring.ts'
|
|
9
|
-
import {
|
|
10
|
-
parseGeneratorParamsSchema,
|
|
11
|
-
type GeneratorParamDefinition,
|
|
12
|
-
type GeneratorParamSchema,
|
|
13
|
-
} from '../generatorParams.ts'
|
|
14
|
-
|
|
15
|
-
export interface GeneratorParams {
|
|
16
|
-
[key: string]: unknown
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface GeneratorContext {
|
|
20
|
-
node: IObject3D
|
|
21
|
-
params: GeneratorParams
|
|
22
|
-
viewer: ThreeViewer
|
|
23
|
-
engine: Record<string, unknown>
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface GeneratorModule {
|
|
27
|
-
default?: (context: GeneratorContext) => unknown | Promise<unknown>
|
|
28
|
-
params?: Record<string, GeneratorParamDefinition>
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface GeneratorViewerOptions {
|
|
32
|
-
base: string | URL
|
|
33
|
-
onError?: (error: unknown) => void
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface GeneratorViewerConfig {
|
|
37
|
-
base: URL
|
|
38
|
-
onError?: (error: unknown) => void
|
|
39
|
-
pending: Set<Promise<void>>
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const viewerConfigs = new WeakMap<ThreeViewer, GeneratorViewerConfig>()
|
|
43
|
-
let generatorImportRevision = 0
|
|
44
|
-
|
|
45
|
-
export class GeneratorComponent extends Object3DComponent {
|
|
46
|
-
static ComponentType = 'Generator'
|
|
47
|
-
static StateProperties: ComponentDefn['StateProperties'] = ['module', 'params']
|
|
48
|
-
|
|
49
|
-
module = ''
|
|
50
|
-
params: GeneratorParams = {}
|
|
51
|
-
schema: GeneratorParamSchema = {}
|
|
52
|
-
private runRevision = 0
|
|
53
|
-
|
|
54
|
-
static configureViewer(viewer: ThreeViewer, options: GeneratorViewerOptions): void {
|
|
55
|
-
viewerConfigs.set(viewer, {
|
|
56
|
-
base: typeof options.base === 'string' ? new URL(options.base) : options.base,
|
|
57
|
-
onError: options.onError,
|
|
58
|
-
pending: new Set(),
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
static async waitForViewer(viewer: ThreeViewer): Promise<void> {
|
|
63
|
-
const config = viewerConfigs.get(viewer)
|
|
64
|
-
while (config?.pending.size) await Promise.all([...config.pending])
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
init(object: IObject3D, state: Record<string, unknown>): void {
|
|
68
|
-
super.init(object, state)
|
|
69
|
-
const run = () => { void this.run().catch((error) => reportGeneratorError(this.ctx.viewer, error)) }
|
|
70
|
-
this.onStateChange('module', run)
|
|
71
|
-
this.onStateChange('params', run)
|
|
72
|
-
run()
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async run(): Promise<void> {
|
|
76
|
-
const viewer = this.ctx.viewer
|
|
77
|
-
const config = viewerConfigs.get(viewer)
|
|
78
|
-
if (!config) throw new Error('Generator viewer is not configured')
|
|
79
|
-
const revision = ++this.runRevision
|
|
80
|
-
const task = runGenerator({
|
|
81
|
-
node: this.object,
|
|
82
|
-
params: this.params,
|
|
83
|
-
viewer,
|
|
84
|
-
module: this.module,
|
|
85
|
-
base: config.base,
|
|
86
|
-
revision: ++generatorImportRevision,
|
|
87
|
-
isCurrent: () => revision === this.runRevision,
|
|
88
|
-
onSchema: (schema) => { this.schema = schema },
|
|
89
|
-
}).then(() => {
|
|
90
|
-
if (revision === this.runRevision) viewer.setDirty(this)
|
|
91
|
-
}).finally(() => config.pending.delete(task))
|
|
92
|
-
config.pending.add(task)
|
|
93
|
-
await task
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async bake(): Promise<number> {
|
|
97
|
-
await this.run()
|
|
98
|
-
const node = this.object
|
|
99
|
-
const generated = node.children.filter((child) => child.userData.kite3dGenerated === true)
|
|
100
|
-
const bakedFrom = {
|
|
101
|
-
module: this.module,
|
|
102
|
-
params: JSON.parse(JSON.stringify(this.params)) as GeneratorParams,
|
|
103
|
-
ts: new Date().toISOString(),
|
|
104
|
-
}
|
|
105
|
-
for (const child of generated) unmarkGenerated(child as IObject3D)
|
|
106
|
-
this.ctx.ecp.removeComponent(node, this.uuid)
|
|
107
|
-
const metadata = getAuthoringMetadata(node)
|
|
108
|
-
if (metadata?.role === 'generator' && !metadata.sourceId) {
|
|
109
|
-
setAuthoringMetadata(node, {
|
|
110
|
-
role: 'direct',
|
|
111
|
-
id: metadata.id,
|
|
112
|
-
...(metadata.allowCameraInside !== undefined ? {allowCameraInside: metadata.allowCameraInside} : {}),
|
|
113
|
-
})
|
|
114
|
-
}
|
|
115
|
-
node.userData.kite3dBakedFrom = bakedFrom
|
|
116
|
-
node._sChildren = [...node.children]
|
|
117
|
-
node.setDirty?.({change: 'userData.kite3dBakedFrom', source: 'kite3d bake'})
|
|
118
|
-
return generated.length
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
destroy(): Record<string, unknown> {
|
|
122
|
-
this.runRevision += 1
|
|
123
|
-
removeGeneratedChildren(this.object)
|
|
124
|
-
return super.destroy()
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export interface RunGeneratorOptions extends Omit<GeneratorContext, 'engine'> {
|
|
129
|
-
module: string
|
|
130
|
-
base: URL
|
|
131
|
-
revision?: number
|
|
132
|
-
isCurrent?: () => boolean
|
|
133
|
-
onSchema?: (schema: GeneratorParamSchema) => void
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export async function runGenerator({
|
|
137
|
-
node,
|
|
138
|
-
params,
|
|
139
|
-
viewer,
|
|
140
|
-
module,
|
|
141
|
-
base,
|
|
142
|
-
revision = 0,
|
|
143
|
-
isCurrent,
|
|
144
|
-
onSchema,
|
|
145
|
-
}: RunGeneratorOptions): Promise<IObject3D[]> {
|
|
146
|
-
removeGeneratedChildren(node)
|
|
147
|
-
onSchema?.({})
|
|
148
|
-
if (!module) return []
|
|
149
|
-
const source = ensureGeneratorMetadata(node)
|
|
150
|
-
const moduleUrl = resolveGeneratorModule(module, base)
|
|
151
|
-
if (revision) moduleUrl.searchParams.set('kite3d-generator', String(revision))
|
|
152
|
-
const loaded = await importGeneratorModule(moduleUrl.href)
|
|
153
|
-
if (!isCurrent || isCurrent()) onSchema?.(parseGeneratorParamsSchema(loaded.params, module))
|
|
154
|
-
if (typeof loaded.default !== 'function') {
|
|
155
|
-
throw new Error(`Generator module must have a default generate function: ${module}`)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const existingChildren = new Set(node.children)
|
|
159
|
-
const returned = await loaded.default({
|
|
160
|
-
node,
|
|
161
|
-
params,
|
|
162
|
-
viewer,
|
|
163
|
-
engine: ThreePipe as unknown as Record<string, unknown>,
|
|
164
|
-
})
|
|
165
|
-
const returnedObjects = normalizeGeneratedResult(returned)
|
|
166
|
-
for (const [index, child] of returnedObjects.entries()) {
|
|
167
|
-
if (child.parent === node) continue
|
|
168
|
-
markGenerated(child, source.id, index)
|
|
169
|
-
node.add(child)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const generated = node.children.filter((child) => !existingChildren.has(child)) as IObject3D[]
|
|
173
|
-
if (isCurrent && !isCurrent()) {
|
|
174
|
-
for (const child of generated) removeGeneratedObject(child)
|
|
175
|
-
return []
|
|
176
|
-
}
|
|
177
|
-
generated.forEach((child, index) => markGenerated(child, source.id, index))
|
|
178
|
-
return generated
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export function removeGeneratedChildren(node: IObject3D): void {
|
|
182
|
-
for (const child of [...node.children] as IObject3D[]) {
|
|
183
|
-
if (child.userData.kite3dGenerated !== true) continue
|
|
184
|
-
removeGeneratedObject(child)
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export function markGenerated(object: IObject3D, sourceId?: string, outputIndex = 0): void {
|
|
189
|
-
let descendantIndex = 0
|
|
190
|
-
object.traverse((child: IObject3D) => {
|
|
191
|
-
child.userData.kite3dGenerated = true
|
|
192
|
-
child.userData.excludeFromExport = true
|
|
193
|
-
if (sourceId) {
|
|
194
|
-
child.userData.kite3dAuthoring = {
|
|
195
|
-
role: 'generator',
|
|
196
|
-
id: `${sourceId}:preview:${outputIndex}:${descendantIndex++}`,
|
|
197
|
-
sourceId,
|
|
198
|
-
} satisfies AuthoringMetadata
|
|
199
|
-
}
|
|
200
|
-
})
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function unmarkGenerated(object: IObject3D): void {
|
|
204
|
-
object.traverse((child: IObject3D) => {
|
|
205
|
-
delete child.userData.kite3dGenerated
|
|
206
|
-
delete child.userData.excludeFromExport
|
|
207
|
-
const metadata = getAuthoringMetadata(child)
|
|
208
|
-
if (metadata?.role === 'generator' && metadata.sourceId) delete child.userData.kite3dAuthoring
|
|
209
|
-
})
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function ensureGeneratorMetadata(node: IObject3D): AuthoringMetadata {
|
|
213
|
-
const current = getAuthoringMetadata(node)
|
|
214
|
-
if (current?.role === 'generator' && !current.sourceId) return current
|
|
215
|
-
const savedId = typeof node.userData.gltfUUID === 'string' && node.userData.gltfUUID.trim()
|
|
216
|
-
? node.userData.gltfUUID.trim()
|
|
217
|
-
: current?.id || generatorPathId(node)
|
|
218
|
-
setAuthoringMetadata(node, {role: 'generator', id: savedId})
|
|
219
|
-
return getAuthoringMetadata(node)!
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function generatorPathId(node: IObject3D): string {
|
|
223
|
-
const parts: string[] = []
|
|
224
|
-
for (let current: IObject3D | null = node; current?.parent; current = current.parent as IObject3D) {
|
|
225
|
-
const index = current.parent.children.indexOf(current)
|
|
226
|
-
parts.push(`${current.name || current.type}:${index}`)
|
|
227
|
-
if (current.parent.userData?.rootSceneModelRoot) break
|
|
228
|
-
}
|
|
229
|
-
return `generator:${parts.reverse().join('/')}`
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function removeGeneratedObject(object: IObject3D): void {
|
|
233
|
-
object.removeFromParent()
|
|
234
|
-
const disposed = new Set<object>()
|
|
235
|
-
object.traverse((child) => {
|
|
236
|
-
const renderable = child as IObject3D & {geometry?: {dispose?(): void}, material?: unknown | unknown[]}
|
|
237
|
-
disposeResource(renderable.geometry, disposed)
|
|
238
|
-
const materials = Array.isArray(renderable.material) ? renderable.material : [renderable.material]
|
|
239
|
-
for (const material of materials) {
|
|
240
|
-
if (!material || typeof material !== 'object') continue
|
|
241
|
-
for (const value of Object.values(material)) {
|
|
242
|
-
if (value && typeof value === 'object' && 'isTexture' in value) disposeResource(value, disposed)
|
|
243
|
-
}
|
|
244
|
-
disposeResource(material, disposed)
|
|
245
|
-
}
|
|
246
|
-
})
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function disposeResource(resource: unknown, disposed: Set<object>): void {
|
|
250
|
-
if (!resource || typeof resource !== 'object' || disposed.has(resource)) return
|
|
251
|
-
disposed.add(resource)
|
|
252
|
-
;(resource as {dispose?(): void}).dispose?.()
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
export function resolveGeneratorModule(module: string, base: URL): URL {
|
|
256
|
-
if (!module || module.startsWith('/') || /^[a-z][a-z\d+.-]*:/i.test(module)) {
|
|
257
|
-
throw new Error(`Generator module must be a project-relative path: ${module}`)
|
|
258
|
-
}
|
|
259
|
-
const resolved = new URL(module, base)
|
|
260
|
-
if (resolved.origin !== base.origin) {
|
|
261
|
-
throw new Error(`Generator module must be same-origin: ${resolved.href}`)
|
|
262
|
-
}
|
|
263
|
-
return resolved
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function normalizeGeneratedResult(value: unknown): IObject3D[] {
|
|
267
|
-
if (!value) return []
|
|
268
|
-
const values = Array.isArray(value) ? value : [value]
|
|
269
|
-
const objects: IObject3D[] = []
|
|
270
|
-
for (const candidate of values) {
|
|
271
|
-
if (!(candidate as IObject3D | undefined)?.isObject3D) {
|
|
272
|
-
throw new Error('Generator output must be an Object3D, an array of Object3D values, or undefined')
|
|
273
|
-
}
|
|
274
|
-
objects.push(candidate as IObject3D)
|
|
275
|
-
}
|
|
276
|
-
return objects
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
async function importGeneratorModule(url: string): Promise<GeneratorModule> {
|
|
280
|
-
return import(/* @vite-ignore */ url) as Promise<GeneratorModule>
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function reportGeneratorError(viewer: ThreeViewer, error: unknown): void {
|
|
284
|
-
const onError = viewerConfigs.get(viewer)?.onError
|
|
285
|
-
if (onError) onError(error)
|
|
286
|
-
else console.error('[kite3d] Generator error', error)
|
|
287
|
-
}
|