@volter/blender-engine 0.1.147 → 0.1.149
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/LICENSE +16 -0
- package/README.md +4 -11
- package/browser/addons/vrm/LICENSE_(OPTION1)_MIT.txt +22 -0
- package/browser/addons/vrm/LICENSE_(OPTION2)_GPL-3_0.txt +674 -0
- package/browser/addons/vrm/LICENSE_MAIN.txt +1 -0
- package/browser/addons/vrm/README.txt +3 -0
- package/browser/addons/vrm/__init__.py +41 -0
- package/browser/addons/vrm/blender_manifest.toml +16 -0
- package/browser/addons/vrm/common/__init__.py +1 -0
- package/browser/addons/vrm/common/animation.py +58 -0
- package/browser/addons/vrm/common/blender_manifest.py +82 -0
- package/browser/addons/vrm/common/char.py +16 -0
- package/browser/addons/vrm/common/convert.py +287 -0
- package/browser/addons/vrm/common/convert_any.py +48 -0
- package/browser/addons/vrm/common/debug.py +40 -0
- package/browser/addons/vrm/common/deep.py +162 -0
- package/browser/addons/vrm/common/error_dialog.py +274 -0
- package/browser/addons/vrm/common/fs.py +71 -0
- package/browser/addons/vrm/common/gl.py +20 -0
- package/browser/addons/vrm/common/gltf.py +1149 -0
- package/browser/addons/vrm/common/human_bone_mapper/__init__.py +1 -0
- package/browser/addons/vrm/common/human_bone_mapper/biped_mapping.py +84 -0
- package/browser/addons/vrm/common/human_bone_mapper/cats_blender_plugin_fix_model_mapping.py +63 -0
- package/browser/addons/vrm/common/human_bone_mapper/human_bone_mapper.py +203 -0
- package/browser/addons/vrm/common/human_bone_mapper/microsoft_rocketbox_mapping.py +69 -0
- package/browser/addons/vrm/common/human_bone_mapper/mixamo_mapping.py +62 -0
- package/browser/addons/vrm/common/human_bone_mapper/mmd_mapping.py +115 -0
- package/browser/addons/vrm/common/human_bone_mapper/mpfb_mapping.py +120 -0
- package/browser/addons/vrm/common/human_bone_mapper/ready_player_me_mapping.py +65 -0
- package/browser/addons/vrm/common/human_bone_mapper/reallusion_mapping.py +94 -0
- package/browser/addons/vrm/common/human_bone_mapper/rigify_meta_rig_mapping.py +65 -0
- package/browser/addons/vrm/common/human_bone_mapper/structure_based_mapping.py +891 -0
- package/browser/addons/vrm/common/human_bone_mapper/unreal_mapping.py +62 -0
- package/browser/addons/vrm/common/human_bone_mapper/vrm_addon_mapping.py +112 -0
- package/browser/addons/vrm/common/human_bone_mapper/vroid_mapping.py +89 -0
- package/browser/addons/vrm/common/legacy_gltf.py +16 -0
- package/browser/addons/vrm/common/logger.py +84 -0
- package/browser/addons/vrm/common/mtoon0.blend +0 -0
- package/browser/addons/vrm/common/mtoon1.blend +0 -0
- package/browser/addons/vrm/common/mtoon1_outline.blend +0 -0
- package/browser/addons/vrm/common/mtoon_auto_setup.blend +0 -0
- package/browser/addons/vrm/common/mtoon_unversioned.py +62 -0
- package/browser/addons/vrm/common/ops/__init__.py +10 -0
- package/browser/addons/vrm/common/ops/export_scene.py +67 -0
- package/browser/addons/vrm/common/ops/icyp.py +53 -0
- package/browser/addons/vrm/common/ops/import_scene.py +57 -0
- package/browser/addons/vrm/common/ops/vrm.py +2380 -0
- package/browser/addons/vrm/common/ops/wm.py +167 -0
- package/browser/addons/vrm/common/preferences.py +362 -0
- package/browser/addons/vrm/common/progress.py +87 -0
- package/browser/addons/vrm/common/rotation.py +89 -0
- package/browser/addons/vrm/common/safe_removal.py +46 -0
- package/browser/addons/vrm/common/scene_watcher.py +230 -0
- package/browser/addons/vrm/common/shader.py +2243 -0
- package/browser/addons/vrm/common/shape_key_mapper/__init__.py +1 -0
- package/browser/addons/vrm/common/shape_key_mapper/arkit_mapping.py +168 -0
- package/browser/addons/vrm/common/shape_key_mapper/mmd_mapping.py +62 -0
- package/browser/addons/vrm/common/shape_key_mapper/ready_player_me_mapping.py +52 -0
- package/browser/addons/vrm/common/shape_key_mapper/vrchat_mapping.py +17 -0
- package/browser/addons/vrm/common/third_party_user_extension.py +214 -0
- package/browser/addons/vrm/common/transparent_z_write.blend +0 -0
- package/browser/addons/vrm/common/version.py +161 -0
- package/browser/addons/vrm/common/vrm0/__init__.py +1 -0
- package/browser/addons/vrm/common/vrm0/human_bone.py +662 -0
- package/browser/addons/vrm/common/vrm0/material_property.py +346 -0
- package/browser/addons/vrm/common/vrm1/__init__.py +1 -0
- package/browser/addons/vrm/common/vrm1/expression_preset.py +207 -0
- package/browser/addons/vrm/common/vrm1/human_bone.py +822 -0
- package/browser/addons/vrm/common/workspace.py +227 -0
- package/browser/addons/vrm/common/writable_context.py +77 -0
- package/browser/addons/vrm/documentation/en-us/animation/index.md +63 -0
- package/browser/addons/vrm/documentation/en-us/create-humanoid-vrm-from-scratch/index.md +131 -0
- package/browser/addons/vrm/documentation/en-us/create-simple-vrm-from-scratch/index.md +30 -0
- package/browser/addons/vrm/documentation/en-us/development/index.md +136 -0
- package/browser/addons/vrm/documentation/en-us/index.md +41 -0
- package/browser/addons/vrm/documentation/en-us/installation/index.md +152 -0
- package/browser/addons/vrm/documentation/en-us/material-mtoon/index.md +122 -0
- package/browser/addons/vrm/documentation/en-us/material-pbr/index.md +94 -0
- package/browser/addons/vrm/documentation/en-us/scripting-api/index.md +391 -0
- package/browser/addons/vrm/documentation/en-us/ui/export_scene.vrm/index.md +57 -0
- package/browser/addons/vrm/documentation/en-us/ui/export_scene.vrma/index.md +17 -0
- package/browser/addons/vrm/documentation/en-us/ui/import_scene.vrm/index.md +61 -0
- package/browser/addons/vrm/documentation/en-us/ui/import_scene.vrma/index.md +13 -0
- package/browser/addons/vrm/documentation/ja-jp/animation/index.md +57 -0
- package/browser/addons/vrm/documentation/ja-jp/create-humanoid-vrm-from-scratch/index.md +129 -0
- package/browser/addons/vrm/documentation/ja-jp/create-simple-vrm-from-scratch/index.md +29 -0
- package/browser/addons/vrm/documentation/ja-jp/development/index.md +137 -0
- package/browser/addons/vrm/documentation/ja-jp/index.md +37 -0
- package/browser/addons/vrm/documentation/ja-jp/installation/index.md +150 -0
- package/browser/addons/vrm/documentation/ja-jp/material-mtoon/index.md +113 -0
- package/browser/addons/vrm/documentation/ja-jp/material-pbr/index.md +90 -0
- package/browser/addons/vrm/documentation/ja-jp/scripting-api/index.md +389 -0
- package/browser/addons/vrm/documentation/ja-jp/ui/export_scene.vrm/index.md +55 -0
- package/browser/addons/vrm/documentation/ja-jp/ui/export_scene.vrma/index.md +19 -0
- package/browser/addons/vrm/documentation/ja-jp/ui/import_scene.vrm/index.md +56 -0
- package/browser/addons/vrm/documentation/ja-jp/ui/import_scene.vrma/index.md +14 -0
- package/browser/addons/vrm/editor/__init__.py +1 -0
- package/browser/addons/vrm/editor/extension.py +425 -0
- package/browser/addons/vrm/editor/extension_accessor.py +75 -0
- package/browser/addons/vrm/editor/handler.py +84 -0
- package/browser/addons/vrm/editor/khr_character/__init__.py +1 -0
- package/browser/addons/vrm/editor/khr_character/panel.py +69 -0
- package/browser/addons/vrm/editor/khr_character/property_group.py +27 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/__init__.py +1 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/ops.py +464 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/panel.py +136 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/property_group.py +83 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/ui_list.py +198 -0
- package/browser/addons/vrm/editor/make_armature.py +709 -0
- package/browser/addons/vrm/editor/menu.py +147 -0
- package/browser/addons/vrm/editor/migration.py +297 -0
- package/browser/addons/vrm/editor/mtoon1/__init__.py +1 -0
- package/browser/addons/vrm/editor/mtoon1/handler.py +20 -0
- package/browser/addons/vrm/editor/mtoon1/migration.py +536 -0
- package/browser/addons/vrm/editor/mtoon1/ops.py +304 -0
- package/browser/addons/vrm/editor/mtoon1/panel.py +442 -0
- package/browser/addons/vrm/editor/mtoon1/property_group.py +4977 -0
- package/browser/addons/vrm/editor/mtoon1/scene_watcher.py +381 -0
- package/browser/addons/vrm/editor/node_constraint1/__init__.py +1 -0
- package/browser/addons/vrm/editor/node_constraint1/panel.py +317 -0
- package/browser/addons/vrm/editor/node_constraint1/property_group.py +25 -0
- package/browser/addons/vrm/editor/ops.py +471 -0
- package/browser/addons/vrm/editor/panel.py +315 -0
- package/browser/addons/vrm/editor/property_group.py +888 -0
- package/browser/addons/vrm/editor/search.py +682 -0
- package/browser/addons/vrm/editor/spring_bone1/__init__.py +1 -0
- package/browser/addons/vrm/editor/spring_bone1/handler.py +994 -0
- package/browser/addons/vrm/editor/spring_bone1/menu.py +246 -0
- package/browser/addons/vrm/editor/spring_bone1/migration.py +86 -0
- package/browser/addons/vrm/editor/spring_bone1/ops.py +1679 -0
- package/browser/addons/vrm/editor/spring_bone1/panel.py +605 -0
- package/browser/addons/vrm/editor/spring_bone1/property_group.py +1457 -0
- package/browser/addons/vrm/editor/spring_bone1/ui_list.py +408 -0
- package/browser/addons/vrm/editor/subscription.py +123 -0
- package/browser/addons/vrm/editor/t_pose.py +713 -0
- package/browser/addons/vrm/editor/validation.py +1271 -0
- package/browser/addons/vrm/editor/vrm0/__init__.py +1 -0
- package/browser/addons/vrm/editor/vrm0/gizmo_group.py +63 -0
- package/browser/addons/vrm/editor/vrm0/handler.py +23 -0
- package/browser/addons/vrm/editor/vrm0/menu.py +145 -0
- package/browser/addons/vrm/editor/vrm0/migration.py +761 -0
- package/browser/addons/vrm/editor/vrm0/ops.py +2145 -0
- package/browser/addons/vrm/editor/vrm0/panel.py +1333 -0
- package/browser/addons/vrm/editor/vrm0/property_group.py +1818 -0
- package/browser/addons/vrm/editor/vrm0/ui_list.py +688 -0
- package/browser/addons/vrm/editor/vrm1/__init__.py +1 -0
- package/browser/addons/vrm/editor/vrm1/handler.py +28 -0
- package/browser/addons/vrm/editor/vrm1/menu.py +76 -0
- package/browser/addons/vrm/editor/vrm1/migration.py +321 -0
- package/browser/addons/vrm/editor/vrm1/ops.py +2679 -0
- package/browser/addons/vrm/editor/vrm1/panel.py +1243 -0
- package/browser/addons/vrm/editor/vrm1/property_group.py +2087 -0
- package/browser/addons/vrm/editor/vrm1/scene_watcher.py +90 -0
- package/browser/addons/vrm/editor/vrm1/ui_list.py +533 -0
- package/browser/addons/vrm/exporter/__init__.py +1 -0
- package/browser/addons/vrm/exporter/abstract_base_vrm_exporter.py +628 -0
- package/browser/addons/vrm/exporter/export_scene.py +930 -0
- package/browser/addons/vrm/exporter/gltf2_export_user_extension.py +45 -0
- package/browser/addons/vrm/exporter/khr_character_exporter.py +34 -0
- package/browser/addons/vrm/exporter/vrm0_exporter.py +4020 -0
- package/browser/addons/vrm/exporter/vrm1_exporter.py +3682 -0
- package/browser/addons/vrm/exporter/vrm_animation_exporter.py +1035 -0
- package/browser/addons/vrm/external/__init__.py +1 -0
- package/browser/addons/vrm/external/auto_rig_pro_support.py +3 -0
- package/browser/addons/vrm/external/io_scene_gltf2_support.py +418 -0
- package/browser/addons/vrm/external/rigify_support.py +1 -0
- package/browser/addons/vrm/importer/__init__.py +1 -0
- package/browser/addons/vrm/importer/abstract_base_vrm_importer.py +1548 -0
- package/browser/addons/vrm/importer/file_handler.py +34 -0
- package/browser/addons/vrm/importer/gltf2_import_user_extension.py +268 -0
- package/browser/addons/vrm/importer/import_scene.py +646 -0
- package/browser/addons/vrm/importer/license_validation.py +198 -0
- package/browser/addons/vrm/importer/vrm0_importer.py +1723 -0
- package/browser/addons/vrm/importer/vrm1_importer.py +2014 -0
- package/browser/addons/vrm/importer/vrm_animation_importer.py +763 -0
- package/browser/addons/vrm/importer/vrm_diff.py +316 -0
- package/browser/addons/vrm/locale/__init__.py +1 -0
- package/browser/addons/vrm/locale/ja_jp.py +826 -0
- package/browser/addons/vrm/locale/ruff.toml +8 -0
- package/browser/addons/vrm/locale/translation_dictionary.py +10 -0
- package/browser/addons/vrm/locale/zh_hans.py +928 -0
- package/browser/addons/vrm/registration.py +670 -0
- package/browser/addons/vrm.json +18 -0
- package/browser/blender-emscripten-engine.mts +60 -128
- package/browser/blender-engine.mts +20 -186
- package/browser/blender-wali-engine.mts +35 -81
- package/browser/describe-thrown.mts +3 -6
- package/browser/document-chunks.mts +4 -16
- package/browser/gltf_export.py +3161 -0
- package/browser/heap-growth.mts +23 -0
- package/browser/index.ts +2 -0
- package/browser/protocol.ts +119 -35
- package/browser/pull-job.mts +2 -10
- package/browser/rna.ts +190 -73
- package/browser/runtime.ts +197 -82
- package/browser/session-addons.mts +33 -0
- package/browser/session-frame.mts +5 -24
- package/browser/session.py +1338 -1427
- package/browser/three/attach-presenter.ts +11 -39
- package/browser/three/blender-base64.ts +1 -36
- package/browser/three/blender-corner-normals.ts +1 -23
- package/browser/three/blender-display-shaders.json +1 -1
- package/browser/three/blender-display-shaders.lut +0 -0
- package/browser/three/blender-display-transform.ts +2 -1
- package/browser/three/blender-frame-columns.ts +5 -13
- package/browser/three/blender-graph-material.ts +18 -3
- package/browser/three/blender-node-graph.ts +10 -34
- package/browser/three/blender-physical-material.ts +2 -31
- package/browser/three/blender-runtime-armature.ts +7 -60
- package/browser/three/blender-runtime-frame.ts +9 -23
- package/browser/three/blender-runtime-geometry.ts +22 -26
- package/browser/three/blender-runtime-lighting.ts +48 -178
- package/browser/three/blender-runtime-shadows.ts +187 -1
- package/browser/three/blender-runtime-skeleton.ts +53 -1
- package/browser/three/blender-runtime-view.ts +505 -283
- package/browser/three/blender-runtime-weights.ts +9 -72
- package/browser/three/blender-sky.ts +13 -33
- package/browser/three/blender-triangulate.ts +7 -35
- package/browser/three/blender-viewport-sampling.ts +235 -0
- package/browser/three/blender-workbench-material.ts +25 -29
- package/browser/three/blender-workbench-outline.ts +199 -0
- package/browser/three/blender-world-volume.ts +62 -10
- package/browser/three/presenter.ts +10 -26
- package/browser/three/release.ts +5 -21
- package/browser/three/sky-precompute-worker.ts +1 -18
- package/browser/three/sky-worker.ts +5 -23
- package/browser/three/world-field-sampler.ts +12 -26
- package/browser/volter_three.py +9 -105
- package/browser/worker.ts +156 -151
- package/package.json +5 -2
- package/wasm/BUNDLE.json +17 -17
- package/wasm/DEPENDENCY-LICENSES.txt +14 -0
- package/wasm/blender_browser.data.br +0 -0
- package/wasm/blender_browser.js +1 -1
- package/wasm/blender_browser.wasm.br +0 -0
- package/browser/frame-transfer.mts +0 -19
- package/browser/three/agx-base-srgb.lut +0 -0
- package/browser/three/agx-look-medium-high-contrast.lut +0 -0
- package/browser/three/agx-look-punchy.lut +0 -0
- package/browser/three/blender-agx.ts +0 -235
- package/browser/three/blender-display-lut.ts +0 -145
- package/browser/three/blender-filmic.ts +0 -49
- package/browser/three/blender-standard.ts +0 -68
- package/browser/three/blender-world-visibility.ts +0 -278
- package/browser/three/filmic-srgb.lut +0 -0
|
Binary file
|
|
@@ -13,7 +13,8 @@ export interface BlenderDisplaySettings {
|
|
|
13
13
|
|
|
14
14
|
export function blenderDisplaySettingsForRender(render: Pick<RenderRequest, 'toneMapping' | 'look' | 'exposure' | 'gamma'>): BlenderDisplaySettings {
|
|
15
15
|
return {
|
|
16
|
-
transform: {none: 'Standard', filmic: 'Filmic', agx: 'AgX', neutral: 'Khronos PBR Neutral'
|
|
16
|
+
transform: {none: 'Standard', filmic: 'Filmic', agx: 'AgX', neutral: 'Khronos PBR Neutral',
|
|
17
|
+
aces13: 'ACES 1.3', aces20: 'ACES 2.0'}[render.toneMapping],
|
|
17
18
|
look: render.look ?? 'None', exposure: render.exposure, gamma: render.gamma ?? 1,
|
|
18
19
|
};
|
|
19
20
|
}
|
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* ships and `drawArraysFromColumns` draws from.
|
|
2
|
+
* Typed columns transferred in a Blender frame and drawn by `drawArraysFromColumns`.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* against the retired mesh kit's `mesh-store.ts` because that store happened to
|
|
10
|
-
* hold the same layout — Blender's — and the two were in one package. They
|
|
11
|
-
* are not the same contract: the kit's store is MODELING STATE it mutates
|
|
12
|
-
* (selection, hide flags, creases, an allocator per attribute type), while
|
|
13
|
-
* this is the READ-ONLY shape of one presented frame. This file is the second
|
|
14
|
-
* half only, so `@volter/editor-blender` names no modeling package (WORK.md §Blender in
|
|
15
|
-
* the tab is Blender, "The mesh kit retires", M1).
|
|
4
|
+
* The columns are Blender's own export arena (`@volter/blender-engine`'s
|
|
5
|
+
* `browser/session-frame.mts`, `columnsToTypedArrays`): `{offset, length, dtype, count, stride}`
|
|
6
|
+
* descriptors into one transferable buffer, decoded on this side into the arrays below. This is
|
|
7
|
+
* the read-only shape of one presented frame, independent of editable modeling state.
|
|
16
8
|
*
|
|
17
9
|
* Layout (Blender's own): positions `co` (3 per vertex); faces as CSR —
|
|
18
10
|
* `faceStart` (nf+1) into `corner` (one vertex index per loop), a loop's edge
|
|
@@ -28,7 +28,7 @@ interface Binding {
|
|
|
28
28
|
borrowedFrom?: Binding;
|
|
29
29
|
borrowedSource?: THREE.MeshPhysicalMaterial;
|
|
30
30
|
objectInfoUniforms?: {color: THREE.IUniform<THREE.Vector4>; indexRandom: THREE.IUniform<THREE.Vector2>; particleRandom: THREE.IUniform<number>};
|
|
31
|
-
palette?:
|
|
31
|
+
palette?: true;
|
|
32
32
|
}
|
|
33
33
|
const bindings = new WeakMap<THREE.MeshPhysicalMaterial, Binding>();
|
|
34
34
|
/** Graph structures whose program the GPU refused. A material whose graph is
|
|
@@ -88,7 +88,7 @@ export function bindPaletteGraph(material: THREE.MeshPhysicalMaterial, compiled:
|
|
|
88
88
|
if (source[name]) values[`bp${index}_${name}`] = {value: source[name]};
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
-
bindings.set(material, {compiled, uniforms: values, ramps: [], channels: channelsFor(compiled, geometry), palette:
|
|
91
|
+
bindings.set(material, {compiled, uniforms: values, ramps: [], channels: channelsFor(compiled, geometry), palette: true});
|
|
92
92
|
material.needsUpdate = true;
|
|
93
93
|
return true;
|
|
94
94
|
}
|
|
@@ -108,6 +108,15 @@ interface Pending {
|
|
|
108
108
|
started: boolean;
|
|
109
109
|
}
|
|
110
110
|
const pendings = new WeakMap<THREE.MeshPhysicalMaterial, Pending>();
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* For a viewport photograph, bind and compile pending graph materials on the capture draw.
|
|
114
|
+
* prepareDraw’s photograph flag selects this synchronous path.
|
|
115
|
+
*/
|
|
116
|
+
let photographing = false;
|
|
117
|
+
export function photographGraphs(on: boolean): void {
|
|
118
|
+
photographing = on;
|
|
119
|
+
}
|
|
111
120
|
/** The proxy material a swapped-in program was compiled through, disposed
|
|
112
121
|
* once the material itself holds that program (its second draw after). */
|
|
113
122
|
const retiring = new WeakMap<THREE.MeshPhysicalMaterial, {shadow: THREE.Material; draws: number}>();
|
|
@@ -324,7 +333,13 @@ export function bindGraphDraw(material: THREE.MeshPhysicalMaterial, geometry: TH
|
|
|
324
333
|
retiring.delete(material);
|
|
325
334
|
}
|
|
326
335
|
const waiting = pendings.get(material);
|
|
327
|
-
if (waiting &&
|
|
336
|
+
if (waiting && photographing) {
|
|
337
|
+
pendings.delete(material);
|
|
338
|
+
for (const ramp of bindings.get(material)?.ramps ?? []) ramp.dispose();
|
|
339
|
+
waiting.binding.channels = channelsFor(waiting.binding.compiled, geometry);
|
|
340
|
+
bindings.set(material, waiting.binding);
|
|
341
|
+
material.needsUpdate = true;
|
|
342
|
+
} else if (waiting && !waiting.started) {
|
|
328
343
|
waiting.started = true;
|
|
329
344
|
waiting.binding.channels = channelsFor(waiting.binding.compiled, geometry);
|
|
330
345
|
const shadow = shadowOf(material);
|
|
@@ -212,20 +212,12 @@ function prop<T>(node: GraphNode, name: string): T {
|
|
|
212
212
|
return node.props[name] as T;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
/**
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
|
|
221
|
-
* with its derivative flag set (`gpu_shader_codegen_lib.glsl`).
|
|
222
|
-
* BLENDER'S WORLD IS Z-UP: the presenter's model root maps it into three's
|
|
223
|
-
* Y-up world by the exact permutation (x, y, z) -> (x, z, -y)
|
|
224
|
-
* (`blender-runtime-view.ts`), so every world-space quantity handed to the
|
|
225
|
-
* library goes back through `blender_from_three`, and a normal the graph
|
|
226
|
-
* returns comes out through its transpose. A transmissive material's fragment shader already
|
|
227
|
-
* declares `modelMatrix` (three's `transmission_pars_fragment`), so the prelude yields it there:
|
|
228
|
-
* declared twice, the Stoneguard file's water failed to compile ("'modelMatrix' : redefinition"). */
|
|
215
|
+
/**
|
|
216
|
+
* Provide the EEVEE node library’s shading globals, object and view accessors, scale flags,
|
|
217
|
+
* incoming coordinates and derivative helpers. Transform world quantities through the model root’s
|
|
218
|
+
* exact Z-up to Y-up permutation before passing them to Blender’s code, and transform returned
|
|
219
|
+
* normals back. Yield modelMatrix when three’s transmission shader already declares it.
|
|
220
|
+
*/
|
|
229
221
|
const EEVEE_GLOBALS = `
|
|
230
222
|
#define GPU_FRAGMENT_SHADER
|
|
231
223
|
#define FrontFacing gl_FrontFacing
|
|
@@ -837,8 +829,7 @@ class Compiler {
|
|
|
837
829
|
uniform, node: key, name: image.name, revision: image.revision, extension,
|
|
838
830
|
closest: interpolation === 'Closest',
|
|
839
831
|
mipmap: projection !== 'SPHERE' && projection !== 'TUBE',
|
|
840
|
-
// EEVEE samples tiles only through
|
|
841
|
-
// treats the first tile as the image (#141776).
|
|
832
|
+
// EEVEE samples tiles only through flat projection; other projections sample the first tile.
|
|
842
833
|
tiled: projection === 'FLAT' && node.props['image_tiled'] === true,
|
|
843
834
|
});
|
|
844
835
|
const tiled = this.s.images.at(-1)!.tiled;
|
|
@@ -866,13 +857,7 @@ class Compiler {
|
|
|
866
857
|
if (extension === 'CLIP')
|
|
867
858
|
body.push(`if (any(lessThan(${co}.xy, vec2(0.0))) || any(greaterThan(${co}.xy, vec2(1.0)))) { ${color} = vec4(0.0); ${alpha} = 0.0; }`);
|
|
868
859
|
}
|
|
869
|
-
// node_shader_gpu_tex_image
|
|
870
|
-
// premultiplied for a PREMUL image, as Blender's GPU texture holds them): alpha cleared for
|
|
871
|
-
// ignored, packed or data alpha; otherwise straight when Alpha is used, so a factor does
|
|
872
|
-
// not multiply it in twice, and premultiplied when it is not. Measured in Blender 5.2's
|
|
873
|
-
// EEVEE: a straight red texel at alpha 0.5 emits 0.502 with Alpha unused, 1.0 with it used.
|
|
874
|
-
// A PREMUL sRGB image differs: the sampler decodes the premultiplied bytes, `decode(r·a)`,
|
|
875
|
-
// where Blender's texture holds `linear(r)·a`, so its partly transparent texels read darker.
|
|
860
|
+
// Match node_shader_gpu_tex_image alpha handling: ignore packed/data alpha; use straight colour when Alpha is linked and premultiplied colour otherwise. PREMUL sRGB decoding retains the file’s premultiplied-byte convention.
|
|
876
861
|
const mode = prop<string>(node, 'image_alpha_mode');
|
|
877
862
|
const alphaUsed = this.uses(key, 1);
|
|
878
863
|
const c = outs([0]);
|
|
@@ -1007,16 +992,7 @@ class Compiler {
|
|
|
1007
992
|
}
|
|
1008
993
|
}
|
|
1009
994
|
|
|
1010
|
-
/**
|
|
1011
|
-
* ONLY THE FUNCTIONS THE GRAPH REACHES, as Blender's codegen links only the
|
|
1012
|
-
* functions a material calls. A node's file holds every variant of it (each
|
|
1013
|
-
* Noise type and dimension, each Voronoi feature), and a program carrying all
|
|
1014
|
-
* of them measured ~155 KB of GLSL for a 30-line graph -- compile time the
|
|
1015
|
-
* viewport spent blocked. Everything that is not a plain function definition
|
|
1016
|
-
* (macros, which may generate or call functions; structs; constants) is kept,
|
|
1017
|
-
* and a function is kept when its name appears in kept text, to a fixpoint.
|
|
1018
|
-
* Overloads of a kept name are all kept.
|
|
1019
|
-
*/
|
|
995
|
+
/** Keep GLSL definitions reached from the graph to a name-reference fixpoint, retaining overloads together. Keep macros, structs and constants because they may define or call functions. */
|
|
1020
996
|
function shakeLibrary(library: string, root: string): string {
|
|
1021
997
|
type Chunk = {text: string; name: string | null};
|
|
1022
998
|
const chunks: Chunk[] = [];
|
|
@@ -1060,12 +1036,12 @@ function shakeLibrary(library: string, root: string): string {
|
|
|
1060
1036
|
return chunks.filter(c => keep.has(c)).map(c => c.text).join('');
|
|
1061
1037
|
}
|
|
1062
1038
|
|
|
1063
|
-
/** The varying a UV layer arrives in; `''` is the render UV. */
|
|
1064
1039
|
/** The varying a graph reads Blender attribute `name` through. */
|
|
1065
1040
|
export function attributeVarying(name: string): string {
|
|
1066
1041
|
return `v${graphAttributeName(name)}`;
|
|
1067
1042
|
}
|
|
1068
1043
|
|
|
1044
|
+
/** The varying a UV layer arrives in; `''` is the render UV. */
|
|
1069
1045
|
export function uvVarying(name: string): string {
|
|
1070
1046
|
return name === '' ? 'vBlenderUvRender' : `vBlenderUv_${[...name].map(c => c.charCodeAt(0).toString(16)).join('')}`;
|
|
1071
1047
|
}
|
|
@@ -7,7 +7,6 @@ import {z} from 'zod';
|
|
|
7
7
|
import {applyGraphShader, bindGraphDraw, bindPaletteGraph, borrowGraphDrawBinding, graphProgramKey, graphUvChannels, setGraphViewport} from './blender-graph-material';
|
|
8
8
|
import type {CompiledGraph} from './blender-node-graph';
|
|
9
9
|
import {bindNamedUvChannels} from './blender-texture-samplers';
|
|
10
|
-
import {WORLD_VISIBILITY,copyWorldVisibility,worldVisibilityShader,worldVisibilityUniforms} from './blender-world-visibility';
|
|
11
10
|
|
|
12
11
|
const scalar = z.number().finite();
|
|
13
12
|
const color = z.tuple([scalar, scalar, scalar]);
|
|
@@ -56,7 +55,6 @@ function graphShadow(material: THREE.MeshPhysicalMaterial): THREE.MeshPhysicalMa
|
|
|
56
55
|
// which `copy` replaces with the material's own (it leaves hooks alone).
|
|
57
56
|
applyPhysicalMaterial(shadow);
|
|
58
57
|
shadow.copy(material);
|
|
59
|
-
copyWorldVisibility(material,shadow);
|
|
60
58
|
const values = uniforms.get(shadow)!;
|
|
61
59
|
if (held) {
|
|
62
60
|
values.blenderCoatIor.value = held.blenderCoatIor.value;
|
|
@@ -151,7 +149,6 @@ export function syncPhysicalDrawMaterial(source: THREE.Material, target?: THREE.
|
|
|
151
149
|
return null;
|
|
152
150
|
}
|
|
153
151
|
copy.copy(source);
|
|
154
|
-
copyWorldVisibility(source,copy);
|
|
155
152
|
const held = uniforms.get(source)!;
|
|
156
153
|
const values = uniforms.get(copy)!;
|
|
157
154
|
values.blenderCoatIor.value = held.blenderCoatIor.value;
|
|
@@ -163,16 +160,6 @@ export function syncPhysicalDrawMaterial(source: THREE.Material, target?: THREE.
|
|
|
163
160
|
return copy;
|
|
164
161
|
}
|
|
165
162
|
|
|
166
|
-
/** A copy of a presented material that ONE object can wear and a script can recolour (Model
|
|
167
|
-
* Play's `tint`/`setOpacity`): the same hooks, physical values, maps and private uniforms,
|
|
168
|
-
* drawn from the material's constants -- `color`, `opacity`, `map`, `emissive` -- with no
|
|
169
|
-
* node graph, since a graph's Base Color and Alpha replace those constants in the shader.
|
|
170
|
-
* Null for a material that is not one of the presenter's. The caller owns and disposes it. */
|
|
171
|
-
export function ownedSurfaceMaterial(source: THREE.Material): THREE.MeshPhysicalMaterial | null {
|
|
172
|
-
if (!(source instanceof THREE.MeshPhysicalMaterial) || !uniforms.has(source) || !materialDrawHooksSupported(source)) return null;
|
|
173
|
-
return graphShadow(source);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
163
|
export function applyPhysicalMaterial(material: THREE.MeshPhysicalMaterial, input?: Physical,
|
|
177
164
|
clips: {map?: boolean; roughness?: boolean; normal?: boolean} = {}): void {
|
|
178
165
|
const data = input ?? defaults;
|
|
@@ -195,7 +182,7 @@ export function applyPhysicalMaterial(material: THREE.MeshPhysicalMaterial, inpu
|
|
|
195
182
|
blenderWorldExtinction: {value: new THREE.Vector3()}};
|
|
196
183
|
uniforms.set(material, values);
|
|
197
184
|
const held = values;
|
|
198
|
-
material.customProgramCacheKey = () => `blender-principled-physical-v6${
|
|
185
|
+
material.customProgramCacheKey = () => `blender-principled-physical-v6${graphProgramKey(material)}`;
|
|
199
186
|
material.onBeforeRender = (renderer, scene, camera, geometry, object) => {
|
|
200
187
|
bindNamedUvChannels(material, geometry);
|
|
201
188
|
bindGraphDraw(material, geometry, renderer, scene, camera, object, graphShadow);
|
|
@@ -213,16 +200,6 @@ export function applyPhysicalMaterial(material: THREE.MeshPhysicalMaterial, inpu
|
|
|
213
200
|
.map(i => (i <= 3 ? `#ifndef USE_UV${i}\nattribute vec2 uv${i};\n#endif` : `attribute vec2 uv${i};`))
|
|
214
201
|
.join('\n') + '\n' + shader.vertexShader;
|
|
215
202
|
Object.assign(shader.uniforms, held);
|
|
216
|
-
// The sky-light atlas exists in the shader only while it is on: its three
|
|
217
|
-
// samplers would count against every material's sixteen (WORLD_VISIBILITY).
|
|
218
|
-
if (WORLD_VISIBILITY) {
|
|
219
|
-
worldVisibilityUniforms(material,shader.uniforms);
|
|
220
|
-
shader.vertexShader='varying vec3 blenderWorldPosition;\n'+shader.vertexShader;
|
|
221
|
-
shader.vertexShader=shader.vertexShader.replace('#include <worldpos_vertex>',
|
|
222
|
-
'#include <worldpos_vertex>\n#if defined(USE_ENVMAP) || defined(USE_SHADOWMAP) || defined(USE_TRANSMISSION)\nblenderWorldPosition=worldPosition.xyz;\n#else\nblenderWorldPosition=(modelMatrix*vec4(transformed,1.)).xyz;\n#endif');
|
|
223
|
-
shader.fragmentShader=shader.fragmentShader.replace('#include <envmap_physical_pars_fragment>',
|
|
224
|
-
'#include <envmap_physical_pars_fragment>\n'+worldVisibilityShader);
|
|
225
|
-
}
|
|
226
203
|
shader.fragmentShader = `uniform float blenderCoatIor;
|
|
227
204
|
uniform vec3 blenderWorldExtinction;
|
|
228
205
|
vec3 blenderInfiniteTransmission() { return vec3(
|
|
@@ -267,13 +244,7 @@ export function applyPhysicalMaterial(material: THREE.MeshPhysicalMaterial, inpu
|
|
|
267
244
|
.replace('rectAreaLight = rectAreaLights[ i ];',
|
|
268
245
|
'rectAreaLight = rectAreaLights[ i ]; rectAreaLight.color *= exp(-blenderWorldExtinction * length(rectAreaLight.position-geometryPosition));')
|
|
269
246
|
.replace('getAmbientLightIrradiance( ambientLightColor )', 'getAmbientLightIrradiance( ambientLightColor ) * blenderInfiniteTransmission()');
|
|
270
|
-
const
|
|
271
|
-
.replace('getIBLIrradiance( geometryNormal )',
|
|
272
|
-
'blenderWorldIrradiance( geometryNormal, getIBLIrradiance( geometryNormal ) )')
|
|
273
|
-
.replaceAll(/(getIBL(?:Radiance|AnisotropyRadiance)\([^;]+\))/g,
|
|
274
|
-
'$1 * blenderWorldReflectionVisibility(geometryViewDir,geometryNormal,material.roughness)')
|
|
275
|
-
: THREE.ShaderChunk.lights_fragment_maps;
|
|
276
|
-
const environment = visible
|
|
247
|
+
const environment = THREE.ShaderChunk.lights_fragment_maps
|
|
277
248
|
.replaceAll(/(getIBL(?:Irradiance|Radiance|AnisotropyRadiance)\([^;]+\))/g, '$1 * blenderInfiniteTransmission()');
|
|
278
249
|
shader.fragmentShader = shader.fragmentShader.replace('#include <lights_physical_fragment>', physical)
|
|
279
250
|
.replace('#include <lights_fragment_begin>', lighting)
|
|
@@ -1,31 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* THE ARMATURE OVERLAY — Blender's bones, drawn in three.js over the engine's
|
|
3
|
-
* own data (ARCHITECTURE-CORE §Blender north star, "Inspection parity, not
|
|
4
|
-
* editing parity" and "The reference is Blender's SOURCE as well as its
|
|
5
|
-
* frames"; WORK.md §Blender in the tab is Blender, "Inspection parity", I4).
|
|
6
|
-
*
|
|
7
|
-
* Blender's overlay ENGINE is never run, ported or recorded: what crosses is
|
|
8
|
-
* the data its draw functions read — per bone a pose matrix, a length, a
|
|
9
|
-
* parent, its hide/select/active flags — and this file is our own drawing of
|
|
10
|
-
* the same shapes with Blender's own vertex tables and Blender's own theme
|
|
11
|
-
* colours. Every metric below cites the file and constant it came from, at the
|
|
12
|
-
* engine's pin (Blender 5.2.0, `fbe6228777e7`).
|
|
13
|
-
*
|
|
14
|
-
* WHAT IS DRAWN, and what is not. `display_type` OCTAHEDRAL and STICK are
|
|
15
|
-
* drawn (`bone_draw_octa` / `bone_draw_line`, `overlay_armature.cc:1415-1500`).
|
|
16
|
-
* BBONE, ENVELOPE and WIRE fall back to the octahedron and say so through a
|
|
17
|
-
* frame warning — Blender's B-Bone needs the per-segment matrices
|
|
18
|
-
* (`draw_bone_update_disp_matrix_bbone`, `:1186`) and its envelope needs the
|
|
19
|
-
* head/tail radii, neither of which the door carries yet. A standing named
|
|
20
|
-
* warning, never a silent degrade.
|
|
21
|
-
*
|
|
22
|
-
* EDIT MODE IS NOT DRAWN EITHER, for a reason worth stating: in edit mode
|
|
23
|
-
* Blender draws the EDIT bones (`ED_armature_ebone_to_mat4`), which are a
|
|
24
|
-
* different set of matrices from `pose.bones`, and drawing the pose there
|
|
25
|
-
* would be a confident picture of the wrong thing. The door reports the mode;
|
|
26
|
-
* an armature in EDIT mode draws its pose with the object-mode colours and the
|
|
27
|
-
* frame carries the warning.
|
|
28
|
-
*/
|
|
1
|
+
|
|
29
2
|
import * as THREE from 'three';
|
|
30
3
|
import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
|
|
31
4
|
import { LineSegments2 } from 'three/addons/lines/LineSegments2.js';
|
|
@@ -51,7 +24,7 @@ export const boneSchema = z
|
|
|
51
24
|
active: z.boolean(),
|
|
52
25
|
lockedWeight: z.boolean(),
|
|
53
26
|
/** `Bone.matrix_local`, the rest matrix in the armature's space. */
|
|
54
|
-
rest: matrixSchema
|
|
27
|
+
rest: matrixSchema,
|
|
55
28
|
/** The channels' own values: what a channel no action animates keeps. */
|
|
56
29
|
channels: z.object({
|
|
57
30
|
mode: z.string(),
|
|
@@ -60,7 +33,7 @@ export const boneSchema = z
|
|
|
60
33
|
rotation_euler: z.array(scalar).length(3),
|
|
61
34
|
rotation_axis_angle: z.array(scalar).length(4),
|
|
62
35
|
scale: z.array(scalar).length(3),
|
|
63
|
-
}).strict()
|
|
36
|
+
}).strict(),
|
|
64
37
|
/** Parenting flags that are not Blender's default, by name. */
|
|
65
38
|
inherit: z.array(z.string()).optional(),
|
|
66
39
|
/** The bone's constraint stack, in order. */
|
|
@@ -114,7 +87,7 @@ export const armatureAnimationSchema = z.object({
|
|
|
114
87
|
blendType: z.string(),
|
|
115
88
|
extrapolation: z.string(),
|
|
116
89
|
/** The NLA is in tweak mode (`use_tweak_mode`), which the presenter does not play. */
|
|
117
|
-
tweak: z.boolean()
|
|
90
|
+
tweak: z.boolean(),
|
|
118
91
|
tracks: z.array(z.object({ name: z.string(), mute: z.boolean(), solo: z.boolean(), strips: z.array(nlaStripSchema) }).strict()),
|
|
119
92
|
/** Bones whose channels a driver sets. */
|
|
120
93
|
drivers: z.array(z.string()).optional(),
|
|
@@ -130,7 +103,7 @@ export const armatureSchema = z
|
|
|
130
103
|
mode: z.string(),
|
|
131
104
|
bones: z.array(boneSchema),
|
|
132
105
|
/** `animation_data.action`, the action it plays (`session.py` `_armatures`). */
|
|
133
|
-
action: z.string().nullable()
|
|
106
|
+
action: z.string().nullable(),
|
|
134
107
|
animation: armatureAnimationSchema.optional(),
|
|
135
108
|
})
|
|
136
109
|
.strict();
|
|
@@ -265,18 +238,7 @@ function wireThickness(bone: Bone): number {
|
|
|
265
238
|
* (`overlay_shape.cc:368-375`): a 10 CSS px bar at UI scale 1. */
|
|
266
239
|
const STICK_WIDTH = 10;
|
|
267
240
|
|
|
268
|
-
/**
|
|
269
|
-
* The CORE of that bar, in the bone's own colour.
|
|
270
|
-
*
|
|
271
|
-
* Blender's stick fragment is a gradient, not two bands:
|
|
272
|
-
* `fac = smoothstep(1.0, 0.2, color_fac)` then `mix(inner, wire, fac)`
|
|
273
|
-
* (`overlay_armature_stick_frag.glsl:13-14`), where `color_fac` runs 1 at the
|
|
274
|
-
* centre line to 0 at either edge. The blend passes 50% at `color_fac` 0.6,
|
|
275
|
-
* i.e. 0.4 of the half-width — so the band that is more bone than wire is
|
|
276
|
-
* 0.4 × 10 = 4 px. `LineMaterial` draws a flat width, so the bar is two
|
|
277
|
-
* lines, 10 px of wire colour with a 4 px core, and that 4 is this
|
|
278
|
-
* measurement rather than a choice.
|
|
279
|
-
*/
|
|
241
|
+
/** Bone-colour core spans 0.4 × STICK_WIDTH, matching the midpoint of Blender's stick-fragment gradient. */
|
|
280
242
|
const STICK_CORE_WIDTH = 4;
|
|
281
243
|
|
|
282
244
|
/**
|
|
@@ -300,22 +262,7 @@ const BONE_SOLID_VERTEX = /* glsl */ `
|
|
|
300
262
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
301
263
|
}
|
|
302
264
|
`;
|
|
303
|
-
/**
|
|
304
|
-
* THE OUTPUT TRANSFORM HAS TO BE ASKED FOR, and forgetting it is what the walk
|
|
305
|
-
* caught: a `ShaderMaterial` writes `gl_FragColor` RAW. three's own materials
|
|
306
|
-
* end with `#include <colorspace_fragment>`, which is what converts the linear
|
|
307
|
-
* working value to the renderer's `outputColorSpace`; without it the theme's
|
|
308
|
-
* `bone_solid` (#b2b2b2, linear 0.44) reached the framebuffer as 0.44 and the
|
|
309
|
-
* bones photographed at byte 112 against Blender's 178 — visibly dark grey
|
|
310
|
-
* where Blender's are light. Tone mapping is deliberately NOT included: an
|
|
311
|
-
* overlay is drawn after the view transform in Blender and is not a
|
|
312
|
-
* scene-referred value.
|
|
313
|
-
*
|
|
314
|
-
* ONLY THE `_fragment` HALF: three PREPENDS `colorspace_pars_fragment` to every
|
|
315
|
-
* `ShaderMaterial` already, so including it here declared `LinearTransferOETF`,
|
|
316
|
-
* `sRGBTransferEOTF` and `sRGBTransferOETF` twice and the whole program refused
|
|
317
|
-
* to compile ("function already has a body", measured live in `cyclotron console`).
|
|
318
|
-
*/
|
|
265
|
+
/** Apply colorspace_fragment to overlay colours without tone mapping. ShaderMaterial already supplies colorspace_pars_fragment. */
|
|
319
266
|
const BONE_SOLID_FRAGMENT = /* glsl */ `
|
|
320
267
|
varying vec3 vColor;
|
|
321
268
|
void main() {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* (`blender-runtime-view.ts`) both have to agree on.
|
|
1
|
+
/** Incremental Blender frames transferred by the worker and applied by
|
|
2
|
+
* `blender-runtime-view.ts`.
|
|
4
3
|
*
|
|
5
4
|
* A present ships the columns of the meshes whose store revision moved since
|
|
6
5
|
* the last frame that session sent, and a bare REFERENCE for the rest; the
|
|
@@ -9,31 +8,18 @@
|
|
|
9
8
|
* a marker string.
|
|
10
9
|
*/
|
|
11
10
|
|
|
12
|
-
/** A mesh the presenter is expected to already hold, named by the store and
|
|
13
|
-
* revision it was last sent as. `store`/`revision` are diagnostic — they are
|
|
14
|
-
* what a stale-geometry report would quote — and `unchanged` is the
|
|
15
|
-
* discriminator. */
|
|
16
|
-
export interface UnchangedMesh {
|
|
17
|
-
store: number;
|
|
18
|
-
revision: number;
|
|
19
|
-
unchanged: true;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
11
|
/** The presenter's refusal when a reference names geometry it does not hold.
|
|
23
12
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* the
|
|
27
|
-
* answers by re-sending the whole frame in full; a page that reloaded under a
|
|
28
|
-
* still-running Python session is the case this exists for. */
|
|
13
|
+
* The message marker survives the worker response boundary. The worker
|
|
14
|
+
* answers by re-sending the whole frame in full, restoring geometry after
|
|
15
|
+
* the presenter has lost its retained frame. */
|
|
29
16
|
export const UNKNOWN_GEOMETRY = 'RUNTIME_FRAME_UNKNOWN_GEOMETRY';
|
|
30
17
|
|
|
31
18
|
/** The presenter's refusal when a material names a PICTURE it does not hold.
|
|
32
19
|
*
|
|
33
20
|
* The same contract one level up from the meshes: a frame carries an image's
|
|
34
|
-
* bytes once per `(name, revision)` and after that its materials name it
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* drops its record and presents the frame again in full. */
|
|
21
|
+
* bytes once per `(name, revision)` and after that its materials name it.
|
|
22
|
+
* The worker cannot supply Python's image bytes, so the refusal reaches
|
|
23
|
+
* `browser/session.py`'s `Session`, which clears its image-carry record and
|
|
24
|
+
* retries the present with the image data. */
|
|
39
25
|
export const UNKNOWN_IMAGE = 'RUNTIME_FRAME_UNKNOWN_IMAGE';
|
|
@@ -8,12 +8,6 @@
|
|
|
8
8
|
* crosses as transferable buffers, never as nested number arrays. The
|
|
9
9
|
* boundary JSON door (`drawRuntimeGeometry`) remains for a frame that
|
|
10
10
|
* arrives as JSON.
|
|
11
|
-
*
|
|
12
|
-
* A drawn geometry carried a `userData.authoredUv` mark until 2026-09-19.
|
|
13
|
-
* MEASURED when this file became `@volter/editor-blender`'s: its only readers were the
|
|
14
|
-
* kit's own `bmesh.ts` and `unwrap.ts`, and a presented frame's geometry is
|
|
15
|
-
* never handed to the kit — it is the Model document's scene. Data with no
|
|
16
|
-
* reader is not authored, so the mark is gone.
|
|
17
11
|
*/
|
|
18
12
|
import * as THREE from 'three';
|
|
19
13
|
import { customCornerNormals, type Normal } from './blender-corner-normals';
|
|
@@ -83,10 +77,10 @@ interface DrawView {
|
|
|
83
77
|
sharp: Iterable<[number, number]>;
|
|
84
78
|
/** A corner's UV, or undefined when the mesh has no map. */
|
|
85
79
|
uv: ((corner: number) => [number, number]) | null;
|
|
86
|
-
uvLayers
|
|
80
|
+
uvLayers: {name: string; read: (corner: number) => [number, number]}[];
|
|
87
81
|
/** A corner's value of each generic attribute layer, by vertex, corner and
|
|
88
82
|
* face; `corner` and `face` are undefined for a loose vertex. */
|
|
89
|
-
attributeLayers
|
|
83
|
+
attributeLayers: {name: string; domain: string;
|
|
90
84
|
read: (vertex: number, corner: number | undefined, face: number | undefined) => ArrayLike<number>}[];
|
|
91
85
|
}
|
|
92
86
|
|
|
@@ -115,7 +109,7 @@ function attributeFloat4(type: string, values: ArrayLike<number>, i: number): [n
|
|
|
115
109
|
function graphAttributeLayers(
|
|
116
110
|
attributes: readonly {name: string; domain: string; type: string; data: ArrayLike<unknown>}[],
|
|
117
111
|
flat: (data: ArrayLike<unknown>, type: string) => ArrayLike<number>,
|
|
118
|
-
):
|
|
112
|
+
): DrawView['attributeLayers'] {
|
|
119
113
|
return attributes
|
|
120
114
|
.filter(a => GRAPH_ATTRIBUTE_TYPES.has(a.type) && a.domain !== 'EDGE' && !a.name.startsWith('.')
|
|
121
115
|
&& a.name !== 'position' && a.name !== 'custom_normal' && !(a.type === 'FLOAT2' && a.domain === 'CORNER'))
|
|
@@ -205,8 +199,8 @@ function drawCore(view: DrawView): Omit<DrawArrays, 'hash'> {
|
|
|
205
199
|
const positions = new Float32Array(capacity * 3),
|
|
206
200
|
texcoords = new Float32Array(capacity * 2);
|
|
207
201
|
const sourceVertex = new Uint32Array(capacity);
|
|
208
|
-
const uvLayers =
|
|
209
|
-
const attributeLayers =
|
|
202
|
+
const uvLayers = view.uvLayers.map(layer => ({...layer, data: new Float32Array(capacity * 2)}));
|
|
203
|
+
const attributeLayers = view.attributeLayers.map(layer => ({...layer, data: new Float32Array(capacity * 4)}));
|
|
210
204
|
const orco = view.orco ? new Float32Array(capacity * 3) : null;
|
|
211
205
|
const indices = new Uint32Array(capacity),
|
|
212
206
|
shared = new Map<string, number>();
|
|
@@ -310,15 +304,23 @@ export function geometryFromDrawArrays(
|
|
|
310
304
|
geometry.addGroup(group.start, group.count, group.materialIndex);
|
|
311
305
|
if (arrays.normals) geometry.setAttribute('normal', new THREE.BufferAttribute(arrays.normals, 3));
|
|
312
306
|
else geometry.computeVertexNormals();
|
|
313
|
-
// The draw
|
|
314
|
-
// because a frame may arrive ALREADY DRAWN from the worker, whose buffers
|
|
315
|
-
// predate this attribute; a reader that needs it says so by name rather than
|
|
316
|
-
// colouring by a guessed index.
|
|
307
|
+
// The draw’s source-vertex map. A reader that needs the map refuses an absent attribute instead of substituting an index.
|
|
317
308
|
if (arrays.sourceVertex)
|
|
318
309
|
geometry.setAttribute('blenderVertex', new THREE.BufferAttribute(arrays.sourceVertex, 1));
|
|
319
310
|
return geometry;
|
|
320
311
|
}
|
|
321
312
|
|
|
313
|
+
/** Choose a mesh’s render UV, then active UV, then first UV; return -1 when none exist. Only CORNER FLOAT2 layers qualify, so stale names and generic attributes are skipped. */
|
|
314
|
+
export function renderMapIndex(maps: readonly { name: string }[], renderUv: string | null | undefined,
|
|
315
|
+
activeUv: string | null | undefined): number {
|
|
316
|
+
for (const name of [renderUv, activeUv]) {
|
|
317
|
+
if (!name) continue;
|
|
318
|
+
const index = maps.findIndex((map) => map.name === name);
|
|
319
|
+
if (index >= 0) return index;
|
|
320
|
+
}
|
|
321
|
+
return maps.length ? 0 : -1;
|
|
322
|
+
}
|
|
323
|
+
|
|
322
324
|
/** Blender already supplied corner normals and triangulation. Read those
|
|
323
325
|
* columns directly: no polygon triangulation, adjacency graph, vector objects
|
|
324
326
|
* or stringified floating-point keys. Two passes size final buffers exactly. */
|
|
@@ -326,9 +328,7 @@ function drawNativeColumns(c: MeshColumns, hash: string): DrawArrays {
|
|
|
326
328
|
const triangles = c.cornerTri!, normal = c.cornerNormal!;
|
|
327
329
|
const nv = c.co.length / 3, nc = c.corner.length, nf = c.faceStart.length - 1;
|
|
328
330
|
const maps = c.attributes.filter(a => a.type === 'FLOAT2' && a.domain === 'CORNER');
|
|
329
|
-
const
|
|
330
|
-
const activeMap = maps.findIndex(a => a.name === mapName);
|
|
331
|
-
if (mapName && activeMap < 0) throw new Error(`Runtime UV map ${mapName} is missing`);
|
|
331
|
+
const activeMap = renderMapIndex(maps, c.renderUv, c.activeUv);
|
|
332
332
|
const attributes = graphAttributeLayers(c.attributes, data => data as ArrayLike<number>);
|
|
333
333
|
const width = 3 + maps.length * 2 + attributes.length * 4;
|
|
334
334
|
const scratch = new Float32Array(width), comparison = new Float32Array(width);
|
|
@@ -428,9 +428,7 @@ export function drawArraysFromColumns(c: MeshColumns, hash: string): DrawArrays
|
|
|
428
428
|
if (c.cornerTri && c.cornerNormal) return withIslandRandom(drawNativeColumns(c, hash),c.co.length/3,storedMeshEdges(c.edge));
|
|
429
429
|
const nv = c.co.length / 3;
|
|
430
430
|
const maps = c.attributes.filter((a) => a.type === 'FLOAT2' && a.domain === 'CORNER');
|
|
431
|
-
const
|
|
432
|
-
const map = maps.find((a) => a.name === mapName);
|
|
433
|
-
if (mapName && !map) throw new Error(`Runtime UV map ${mapName} is missing`);
|
|
431
|
+
const map = maps[renderMapIndex(maps, c.renderUv, c.activeUv)];
|
|
434
432
|
const uvData = map ? (map.data as Float32Array) : null;
|
|
435
433
|
const ne = c.edge.length / 2;
|
|
436
434
|
const edges: [number, number][] = new Array(ne);
|
|
@@ -536,9 +534,7 @@ export function drawRuntimeGeometry(data: MeshData): THREE.BufferGeometry {
|
|
|
536
534
|
`Runtime attribute ${attribute.name} has ${attribute.data.length} values for ${counts[attribute.domain]} ${attribute.domain} elements`,
|
|
537
535
|
);
|
|
538
536
|
const maps = (data.attributes ?? []).filter((a) => a.type === 'FLOAT2' && a.domain === 'CORNER');
|
|
539
|
-
const
|
|
540
|
-
const map = maps.find((a) => a.name === mapName);
|
|
541
|
-
if (mapName && !map) throw new Error(`Runtime UV map ${mapName} is missing`);
|
|
537
|
+
const map = maps[renderMapIndex(maps, data.render_uv, data.active_uv)];
|
|
542
538
|
const uv = map?.data as [number, number][] | undefined;
|
|
543
539
|
const view: DrawView = {
|
|
544
540
|
vertexCount: vertices.length,
|
|
@@ -560,8 +556,8 @@ export function drawRuntimeGeometry(data: MeshData): THREE.BufferGeometry {
|
|
|
560
556
|
attributeLayers: graphAttributeLayers(data.attributes ?? [],
|
|
561
557
|
values => (Array.from(values) as (number | number[])[]).flat()),
|
|
562
558
|
};
|
|
563
|
-
// Native frames always supply stored edges.
|
|
564
|
-
// order
|
|
559
|
+
// Native frames always supply stored edges. A JSON frame without that
|
|
560
|
+
// order keeps an unsupported zero input rather than guessing IDs.
|
|
565
561
|
const arrays=drawCore(view);
|
|
566
562
|
return geometryFromDrawArrays(view.edges?withIslandRandom(arrays,vertices.length,view.edges):arrays);
|
|
567
563
|
}
|