@volter/blender-engine 0.1.148 → 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
|
@@ -1,30 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
* THE WEIGHT OVERLAY — the active vertex group's weights, coloured with
|
|
3
|
-
* Blender's own ramp (ARCHITECTURE-CORE §Blender north star, "Inspection
|
|
4
|
-
* parity, not editing parity" and "The reference is Blender's SOURCE as well
|
|
5
|
-
* as its frames"; WORK.md §Blender in the tab is Blender, "Inspection parity",
|
|
6
|
-
* I4).
|
|
7
|
-
*
|
|
8
|
-
* IT IS INSPECTION, NOT PAINT MODE. Blender shows these colours by entering
|
|
9
|
-
* Weight Paint; here it is a viewport overlay toggle beside Bones, because the
|
|
10
|
-
* Model document has no brushes to enter a paint mode for and looking at the
|
|
11
|
-
* weights is the whole point.
|
|
12
|
-
*
|
|
13
|
-
* WHICH RAMP, and the correction it cost. The brief for this unit named
|
|
14
|
-
* `BKE_defvert_weight_to_rgb` / `weight_to_rgb`
|
|
15
|
-
* (`blenkernel/intern/deform.cc:1559-1590`) — the blue → cyan → green →
|
|
16
|
-
* yellow → red piecewise ramp. MEASURED at the engine's pin, that function is
|
|
17
|
-
* NOT what paints a vertex group: its only callers in the whole tree are
|
|
18
|
-
* `blenkernel/intern/particle.cc:3602,3624-3625`, the HAIR KEY weight colour.
|
|
19
|
-
* The weight-paint viewport samples a 256-texel 1D table built in
|
|
20
|
-
* `draw/engines/overlay/overlay_instance.cc:186-221`, and that table is an HSV
|
|
21
|
-
* sweep with a gamma correction — a different curve with the same endpoints,
|
|
22
|
-
* which is why the two look alike until you compare mid-tones. This file is
|
|
23
|
-
* the table Blender actually samples; the piecewise ramp is named here so the
|
|
24
|
-
* next reader does not "fix" it back.
|
|
25
|
-
*/
|
|
1
|
+
|
|
26
2
|
import * as THREE from 'three';
|
|
27
3
|
import { z } from 'zod';
|
|
4
|
+
import { bytesFromBase64 } from './blender-base64';
|
|
28
5
|
|
|
29
6
|
/** What `session.py`'s `_weights` answers. `weightsBase64` / `alertBase64` are
|
|
30
7
|
* absent on a REFERENCE (`unchanged`), which is the same contract the meshes
|
|
@@ -36,10 +13,8 @@ export const weightsSchema = z
|
|
|
36
13
|
group: z.string(),
|
|
37
14
|
groupIndex: z.number().int().nonnegative(),
|
|
38
15
|
count: z.number().int().nonnegative(),
|
|
39
|
-
/** sha1 of the packed weight
|
|
40
|
-
*
|
|
41
|
-
* NOT move when only deform weights change (`session.py::_weights` states
|
|
42
|
-
* the measurement). */
|
|
16
|
+
/** sha1 of the packed weight and alert bytes: the reference key and
|
|
17
|
+
* staleness signal, independent of mesh geometry revisions. */
|
|
43
18
|
digest: z.string(),
|
|
44
19
|
/** `scene.tool_settings.vertex_group_user` (`rna_scene.cc:3428-3434`). */
|
|
45
20
|
alertMode: z.enum(['NONE', 'ACTIVE', 'ALL']),
|
|
@@ -126,27 +101,9 @@ function hueToRgb(hue: number): [number, number, number] {
|
|
|
126
101
|
}
|
|
127
102
|
}
|
|
128
103
|
|
|
129
|
-
/**
|
|
130
|
-
* `TH_VERTEX_UNREFERENCED`, the colour a zero-weight vertex is painted
|
|
131
|
-
* (`overlay_paint_weight_frag.glsl:98-100`, `mix(weight_color,
|
|
132
|
-
* color_unreferenced, alert * alert)` at alert 1).
|
|
133
|
-
*
|
|
134
|
-
* MEASURED: `vertex_unreferenced` appears NOWHERE in
|
|
135
|
-
* `release/datafiles/userdef/userdef_default_theme.c` — the only mention of
|
|
136
|
-
* the member in the whole checkout is its RNA declaration
|
|
137
|
-
* (`rna_userdef.cc:3104`). The default theme therefore leaves the struct's
|
|
138
|
-
* zero standing, which is BLACK, and black is what Blender draws for an
|
|
139
|
-
* unweighted vertex. Stated rather than eyedropped.
|
|
140
|
-
*/
|
|
104
|
+
/** TH_VERTEX_UNREFERENCED is black: Blender’s default theme leaves that member zero. */
|
|
141
105
|
export const VERTEX_UNREFERENCED = 0x000000;
|
|
142
106
|
|
|
143
|
-
function bytesFrom(base64: string): Uint8Array {
|
|
144
|
-
const binary = atob(base64);
|
|
145
|
-
const bytes = new Uint8Array(binary.length);
|
|
146
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
147
|
-
return bytes;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
107
|
/**
|
|
151
108
|
* THE OVERLAY: the painted object's own geometry, drawn again in the weight
|
|
152
109
|
* colours.
|
|
@@ -173,34 +130,14 @@ export class WeightOverlay {
|
|
|
173
130
|
this.group.name = 'BlenderWeightOverlay';
|
|
174
131
|
}
|
|
175
132
|
|
|
176
|
-
/**
|
|
177
|
-
* Draw the weights over `source`, the presented mesh of the painted object.
|
|
178
|
-
* `null` weights (no active vertex group, no active mesh) clears it.
|
|
179
|
-
*
|
|
180
|
-
* `objectMatrix` is the object's matrix in BLENDER's frame — the frame's own
|
|
181
|
-
* `objects[].matrix`, exactly as the armature overlay takes it, and NOT the
|
|
182
|
-
* presented mesh's `matrixWorld`. Measured live 2026-09-19: `matrixWorld`
|
|
183
|
-
* already carries the model root's Z-up permutation, and this group carries
|
|
184
|
-
* it too, so the drawing landed with the permutation applied TWICE — the
|
|
185
|
-
* Body's colours lay flat on the floor beside the standing cylinder.
|
|
186
|
-
*
|
|
187
|
-
* Returns the warning this drawing could not honour, or null.
|
|
188
|
-
*/
|
|
133
|
+
/** Draw active-group weights over source, or clear the drawing for null weights. objectMatrix is objects[].matrix in Blender coordinates, before the model root’s Z-up conversion. Return an unsupported-drawing warning or null. */
|
|
189
134
|
apply(
|
|
190
135
|
weights: BlenderWeights | null,
|
|
191
136
|
source: THREE.Mesh | null,
|
|
192
137
|
objectMatrix: THREE.Matrix4 | null,
|
|
193
138
|
): string | null {
|
|
194
139
|
if (weights === null || source === null || !source.isMesh || objectMatrix === null) {
|
|
195
|
-
//
|
|
196
|
-
// walk caught (2026-09-19): `_weights` answers null whenever the active
|
|
197
|
-
// object is not a mesh — entering POSE MODE on the rig is enough — while
|
|
198
|
-
// the session's `_known` still records the array as sent, so the next
|
|
199
|
-
// frame that names the Body again ships a REFERENCE to bytes this side
|
|
200
|
-
// had just thrown away, and the colours never came back. The array is
|
|
201
|
-
// still the truth about that object's group; only the drawing is stale.
|
|
202
|
-
// It is the same rule the meshes follow: a presenter holds its geometry
|
|
203
|
-
// until the SESSION replaces it, never until a frame stops mentioning it.
|
|
140
|
+
// Clear the drawing while retaining received weight arrays. The session may refer to those bytes again until it replaces them.
|
|
204
141
|
this.clear();
|
|
205
142
|
this.signature = '';
|
|
206
143
|
return null;
|
|
@@ -222,11 +159,11 @@ export class WeightOverlay {
|
|
|
222
159
|
this.clear();
|
|
223
160
|
return `weights: ${key} carried neither an array nor a reference`;
|
|
224
161
|
}
|
|
225
|
-
const raw =
|
|
162
|
+
const raw = bytesFromBase64(weights.weightsBase64);
|
|
226
163
|
this.held = {
|
|
227
164
|
key,
|
|
228
165
|
weights: new Float32Array(raw.buffer, raw.byteOffset, weights.count),
|
|
229
|
-
alerts:
|
|
166
|
+
alerts: bytesFromBase64(weights.alertBase64),
|
|
230
167
|
};
|
|
231
168
|
}
|
|
232
169
|
const held = this.held;
|
|
@@ -1,19 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Blender's scattering skies, transcribed from the pinned source.
|
|
3
|
-
*
|
|
4
|
-
* `intern/sky/source/sky_multiple_scattering.cpp` precomputes a 512x256 XYZ
|
|
5
|
-
* texture and `gpu_shader_material_tex_sky.glsl`'s `node_tex_sky_nishita` reads
|
|
6
|
-
* it back per direction; both halves are here because both are what a script
|
|
7
|
-
* sees through `ShaderNodeTexSky`. The single-scattering atmosphere is in
|
|
8
|
-
* `blender-single-scattering-sky.ts`; model selection, caching and the shared
|
|
9
|
-
* GPU lookup convention live here. Each model keeps its own native elevation
|
|
10
|
-
* and azimuth conventions.
|
|
11
|
-
*
|
|
12
|
-
* The constants and the arithmetic are the source's. The XYZ-to-scene-linear
|
|
13
|
-
* matrix is not in the source -- it comes from the colour configuration -- and
|
|
14
|
-
* was measured off the oracle by round-tripping an EXR through
|
|
15
|
-
* `Linear CIE-XYZ D65`.
|
|
16
|
-
*/
|
|
1
|
+
/** Blender scattering skies from the pinned source. Multiple scattering uses a precomputed XYZ texture; single scattering lives in blender-single-scattering-sky.ts. This module selects models, caches results and converts XYZ into scene-linear colour. */
|
|
17
2
|
import * as THREE from 'three';
|
|
18
3
|
import {precomputeSingleScatteringSky, precomputeSingleScatteringSun} from './blender-single-scattering-sky';
|
|
19
4
|
|
|
@@ -53,8 +38,7 @@ const SPECTRAL_XYZ = [
|
|
|
53
38
|
[20.762668673810577145, 2.3614213523314368527, 110.48009643252140334],
|
|
54
39
|
];
|
|
55
40
|
|
|
56
|
-
/**
|
|
57
|
-
* loading an EXR of the three basis vectors as `Linear CIE-XYZ D65`. */
|
|
41
|
+
/** XYZ-to-scene-linear matrix from EXR basis-vector conversion through Linear CIE-XYZ D65. */
|
|
58
42
|
const XYZ_TO_RGB = [
|
|
59
43
|
[3.2409698963165283, -1.5373831987380981, -0.49861079454421997],
|
|
60
44
|
[-0.969243586063385, 1.8759675025939941, 0.041555099189281464],
|
|
@@ -102,11 +86,11 @@ const aerosolDensity = (h: number): number =>
|
|
|
102
86
|
(Math.exp(-h / AEROSOL_HEIGHT_SCALE) + AEROSOL_BACKGROUND_DENSITY / AEROSOL_BASE_DENSITY);
|
|
103
87
|
|
|
104
88
|
export interface SkyParameters {
|
|
105
|
-
/**
|
|
106
|
-
model
|
|
107
|
-
sunDisc
|
|
108
|
-
sunSize
|
|
109
|
-
sunIntensity
|
|
89
|
+
/** Sky model selected by the frame. */
|
|
90
|
+
model: 'SINGLE_SCATTERING' | 'MULTIPLE_SCATTERING';
|
|
91
|
+
sunDisc: boolean;
|
|
92
|
+
sunSize: number;
|
|
93
|
+
sunIntensity: number;
|
|
110
94
|
sunElevation: number;
|
|
111
95
|
sunRotation: number;
|
|
112
96
|
altitude: number;
|
|
@@ -357,13 +341,9 @@ function precomputeTexture(p: SkyParameters, sunElevation: number): Float32Array
|
|
|
357
341
|
}
|
|
358
342
|
|
|
359
343
|
/**
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
* that is where a sky's cost lives; reading it back (below) is 32,768 bilinear
|
|
364
|
-
* lookups and costs milliseconds. Split out because the expensive half takes
|
|
365
|
-
* numbers and returns numbers -- no THREE, no DOM -- so a WORKER can run it and
|
|
366
|
-
* post the Float32Array back. See `contributions/sky-precompute-worker.ts`.
|
|
344
|
+
* Precompute sky radiance from numeric parameters without Three or DOM access.
|
|
345
|
+
* `sky-precompute-worker.ts` runs this work off the main thread
|
|
346
|
+
* and returns the Float32Array for synchronous field sampling.
|
|
367
347
|
*/
|
|
368
348
|
export function precomputeSkyTexture(p: SkyParameters): Float32Array {
|
|
369
349
|
if (p.model === 'SINGLE_SCATTERING') return precomputeSingleScatteringSky(p);
|
|
@@ -376,7 +356,7 @@ export function precomputeSkyTexture(p: SkyParameters): Float32Array {
|
|
|
376
356
|
* single scattering keeps it as supplied to Blender's precompute. */
|
|
377
357
|
export function skyTextureKey(p: SkyParameters): string {
|
|
378
358
|
const elevation = p.model === 'SINGLE_SCATTERING' ? p.sunElevation : simplify(p.sunElevation, p.sunRotation)[0];
|
|
379
|
-
return JSON.stringify([p.model
|
|
359
|
+
return JSON.stringify([p.model, elevation, p.altitude, p.airDensity, p.aerosolDensity, p.ozoneDensity]);
|
|
380
360
|
}
|
|
381
361
|
|
|
382
362
|
/** Precomputed sky textures, however they were derived. `skyField` is
|
|
@@ -433,7 +413,7 @@ export function skyField(p: SkyParameters): (direction: THREE.Vector3) => THREE.
|
|
|
433
413
|
primeSkyTexture(p, pixels);
|
|
434
414
|
}
|
|
435
415
|
const colour = new THREE.Vector3();
|
|
436
|
-
const diameter = p.sunSize
|
|
416
|
+
const diameter = p.sunSize;
|
|
437
417
|
const solar = p.model === 'SINGLE_SCATTERING' && p.sunDisc && diameter > 0
|
|
438
418
|
? precomputeSingleScatteringSun(p, diameter) : null;
|
|
439
419
|
const sun = skySunDirection(p);
|
|
@@ -478,7 +458,7 @@ export function skyField(p: SkyParameters): (direction: THREE.Vector3) => THREE.
|
|
|
478
458
|
if (angle < diameter / 2) {
|
|
479
459
|
const limb = 1 - .6 * (1 - Math.sqrt(Math.max(0, 1 - (angle / (diameter / 2)) ** 2)));
|
|
480
460
|
const t = (dirElevation - p.sunElevation) / diameter + .5;
|
|
481
|
-
for (let i = 0; i < 3; i++) xyz[i]! += mix(solar[0][i]!, solar[1][i]!, t) *
|
|
461
|
+
for (let i = 0; i < 3; i++) xyz[i]! += mix(solar[0][i]!, solar[1][i]!, t) * p.sunIntensity * limb;
|
|
482
462
|
}
|
|
483
463
|
}
|
|
484
464
|
return colour.set(
|
|
@@ -1,33 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* A `BMesh` face is an n-gon; a `BufferGeometry` is triangles. The conversion
|
|
6
|
-
* used to be a naive fan (`0,1,2`, `0,2,3`, `0,3,4`, …), which is correct for
|
|
7
|
-
* a CONVEX polygon and silently wrong for a concave one: the fan's later
|
|
8
|
-
* triangles fold back over the polygon's own reflex corner, so they overlap
|
|
9
|
-
* each other and face BACKWARDS. That renders as a hole. It shipped as one —
|
|
10
|
-
* the treasure chest's crescent lid end-cap (docs/BLENDER-PARITY.md §Driver
|
|
11
|
-
* ladder, "Chest-measured gaps") — and, worse, `validate()` had nothing to say
|
|
12
|
-
* about it, because every count and every winding on the BMesh side was
|
|
13
|
-
* perfectly sound. The defect only existed in the export.
|
|
14
|
-
*
|
|
15
|
-
* So the export's triangulation is now a real one, and it is the SAME
|
|
16
|
-
* function `validate` checks, which is what closes the gap: there is no way
|
|
17
|
-
* for the exporter to fan something the validator did not look at.
|
|
18
|
-
*
|
|
19
|
-
* THE RUNG. Ear clipping is not hand-rolled here: `THREE.ShapeUtils
|
|
20
|
-
* .triangulateShape` is three's own Earcut and ships in the dependency the
|
|
21
|
-
* kit already has. This module supplies only the two things Earcut cannot do
|
|
22
|
-
* for a 3D face — pick the plane to flatten onto, and decide whether the
|
|
23
|
-
* result is trustworthy.
|
|
24
|
-
*
|
|
25
|
-
* THE FAN IS STILL THE FAST PATH, and deliberately so. Every face is fanned
|
|
26
|
-
* FIRST and the fan is kept whenever it is sound, so every convex face — which
|
|
27
|
-
* is nearly all of them, quads included — exports with byte-identical indices
|
|
28
|
-
* to before this module existed. Earcut runs only where the fan actually
|
|
29
|
-
* fails. That is not an optimization: it is what lets an existing model be
|
|
30
|
-
* rebuilt through the fixed kit and change ONLY where it was broken.
|
|
2
|
+
* Triangulate BMesh n-gons for export and validation. Keep a fan when it covers the polygon without
|
|
3
|
+
* overlap or reversed triangles; otherwise flatten onto a suitable plane and use three’s Earcut.
|
|
4
|
+
* Both paths validate the resulting triangles against the face.
|
|
31
5
|
*/
|
|
32
6
|
|
|
33
7
|
import * as THREE from 'three';
|
|
@@ -52,8 +26,8 @@ export interface PolygonTriangulation {
|
|
|
52
26
|
* no flattening of it is simple, and neither the fan nor Earcut can fix
|
|
53
27
|
* that. `validate` reports it as `overlappingTriangulation`. */
|
|
54
28
|
readonly sound: boolean;
|
|
55
|
-
/** Which path produced `triangles` — `'fan'` is the
|
|
56
|
-
*
|
|
29
|
+
/** Which path produced `triangles` — `'fan'` is the fan from the first
|
|
30
|
+
* corner, `'earcut'` is `THREE.ShapeUtils.triangulateShape`. */
|
|
57
31
|
readonly method: 'fan' | 'earcut';
|
|
58
32
|
}
|
|
59
33
|
|
|
@@ -64,8 +38,7 @@ export interface PolygonTriangulation {
|
|
|
64
38
|
const AREA_TOLERANCE = 1e-6;
|
|
65
39
|
|
|
66
40
|
/** The polygon's Newell area-weighted normal — the standard best-fit plane
|
|
67
|
-
* for a possibly non-planar face,
|
|
68
|
-
* projected area. */
|
|
41
|
+
* for a possibly non-planar face, scaled so its length is the projected area. */
|
|
69
42
|
function newellNormal(points: readonly THREE.Vector3[]): THREE.Vector3 {
|
|
70
43
|
const n = new THREE.Vector3();
|
|
71
44
|
for (let i = 0; i < points.length; i++) {
|
|
@@ -82,8 +55,7 @@ function newellNormal(points: readonly THREE.Vector3[]): THREE.Vector3 {
|
|
|
82
55
|
const cross2 = (a: THREE.Vector2, b: THREE.Vector2, c: THREE.Vector2): number =>
|
|
83
56
|
(b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
|
|
84
57
|
|
|
85
|
-
/**
|
|
86
|
-
* this module, reproduced exactly so a sound fan stays byte-identical. */
|
|
58
|
+
/** Consecutive triangle fan from corner 0. */
|
|
87
59
|
const fanOf = (n: number): TriangleIndices[] =>
|
|
88
60
|
Array.from({ length: n - 2 }, (_, i) => [0, i + 1, i + 2] as const);
|
|
89
61
|
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/** Viewport anti-aliasing from Blender 5.2: Workbench uses its jitter table and Blackman-Harris resolve; EEVEE uses Halton jitter and Gaussian film accumulation. The luma weight is constant at exposureScale 0. Photographs cap samples at PHOTOGRAPH_SAMPLE_MAX. */
|
|
2
|
+
|
|
3
|
+
/** The most samples a photograph draws. Workbench's own maximum is 32; EEVEE's viewport can be
|
|
4
|
+
* set past it, and 0 means it never stops. */
|
|
5
|
+
export const PHOTOGRAPH_SAMPLE_MAX = 64;
|
|
6
|
+
|
|
7
|
+
/** What the stage's `PhotographSampling` reads (`@volter/editor-threejs/capture/photograph-sampling`). */
|
|
8
|
+
export interface BlenderPhotographSampling {
|
|
9
|
+
readonly offsets: readonly (readonly [number, number])[];
|
|
10
|
+
readonly projectionScale: number;
|
|
11
|
+
readonly reach: number;
|
|
12
|
+
readonly filter: 'blackman-harris' | 'gaussian';
|
|
13
|
+
readonly radius: number;
|
|
14
|
+
readonly accumulation: 'log2' | 'luma';
|
|
15
|
+
readonly exposureScale: number;
|
|
16
|
+
readonly backdropMerge: boolean;
|
|
17
|
+
/** Show Workbench accumulation from sample 1 and EEVEE accumulation from sample 2, where their measured edges improve on the ordinary MSAA frame. */
|
|
18
|
+
readonly liveFrom: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const f32 = Math.fround;
|
|
22
|
+
|
|
23
|
+
/** `blender::RandomNumberGenerator` (drand48's constants), as `BLI_rng_new(seed)` seeds it. */
|
|
24
|
+
class BlenderRng {
|
|
25
|
+
private x: bigint;
|
|
26
|
+
constructor(seed: number) {
|
|
27
|
+
this.x = (BigInt(seed >>> 0) << 16n) | 0x330en;
|
|
28
|
+
}
|
|
29
|
+
private step(): void {
|
|
30
|
+
this.x = (0x5deece66dn * this.x + 0xbn) & 0xffffffffffffn;
|
|
31
|
+
}
|
|
32
|
+
getDouble(): number {
|
|
33
|
+
this.step();
|
|
34
|
+
return Number(BigInt.asIntN(32, this.x >> 17n)) / 0x80000000;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function jitterate1(jit1: [number, number][], jit2: [number, number][], radius1: number): void {
|
|
39
|
+
const num = jit1.length;
|
|
40
|
+
for (let i = num - 1; i >= 0; i--) {
|
|
41
|
+
let dvecx = 0;
|
|
42
|
+
let dvecy = 0;
|
|
43
|
+
const x0 = jit1[i]![0];
|
|
44
|
+
const y0 = jit1[i]![1];
|
|
45
|
+
for (let j = num - 1; j >= 0; j--) {
|
|
46
|
+
if (i === j) continue;
|
|
47
|
+
// Single-precision, rounded after each operation as C's float arithmetic is.
|
|
48
|
+
let vecx = f32(f32(jit1[j]![0] - x0) - 1);
|
|
49
|
+
let vecy = f32(f32(jit1[j]![1] - y0) - 1);
|
|
50
|
+
for (let k = 3; k > 0; k--) {
|
|
51
|
+
for (const step of [1, 1, -2]) {
|
|
52
|
+
if (Math.abs(vecx) < radius1 && Math.abs(vecy) < radius1) {
|
|
53
|
+
let len = f32(Math.sqrt(f32(f32(vecx * vecx) + f32(vecy * vecy))));
|
|
54
|
+
if (len > 0 && len < radius1) {
|
|
55
|
+
len = f32(len / radius1);
|
|
56
|
+
dvecx = f32(dvecx + f32(vecx / len));
|
|
57
|
+
dvecy = f32(dvecy + f32(vecy / len));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
vecx = f32(vecx + step);
|
|
61
|
+
}
|
|
62
|
+
vecy = f32(vecy + 1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
let x = f32(x0 - f32(dvecx / 18));
|
|
66
|
+
let y = f32(y0 - f32(dvecy / 18));
|
|
67
|
+
x = f32(x - Math.floor(x));
|
|
68
|
+
y = f32(y - Math.floor(y));
|
|
69
|
+
jit2[i] = [x, y];
|
|
70
|
+
}
|
|
71
|
+
for (let i = 0; i < num; i++) jit1[i] = [jit2[i]![0], jit2[i]![1]];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function jitterate2(jit1: [number, number][], jit2: [number, number][], radius2: number): void {
|
|
75
|
+
const num = jit1.length;
|
|
76
|
+
for (let i = num - 1; i >= 0; i--) {
|
|
77
|
+
let dvecx = 0;
|
|
78
|
+
let dvecy = 0;
|
|
79
|
+
const x0 = jit1[i]![0];
|
|
80
|
+
const y0 = jit1[i]![1];
|
|
81
|
+
for (let j = num - 1; j >= 0; j--) {
|
|
82
|
+
if (i === j) continue;
|
|
83
|
+
// Single-precision, rounded after each operation as C's float arithmetic is.
|
|
84
|
+
let vecx = f32(f32(jit1[j]![0] - x0) - 1);
|
|
85
|
+
let vecy = f32(f32(jit1[j]![1] - y0) - 1);
|
|
86
|
+
for (let k = 0; k < 3; k++) {
|
|
87
|
+
if (Math.abs(vecx) < radius2) dvecx = f32(dvecx + f32(vecx * radius2));
|
|
88
|
+
vecx = f32(vecx + 1);
|
|
89
|
+
}
|
|
90
|
+
for (let k = 0; k < 3; k++) {
|
|
91
|
+
if (Math.abs(vecy) < radius2) dvecy = f32(dvecy + f32(vecy * radius2));
|
|
92
|
+
vecy = f32(vecy + 1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
let x = f32(x0 - f32(dvecx / 2));
|
|
96
|
+
let y = f32(y0 - f32(dvecy / 2));
|
|
97
|
+
x = f32(x - Math.floor(x));
|
|
98
|
+
y = f32(y - Math.floor(y));
|
|
99
|
+
jit2[i] = [x, y];
|
|
100
|
+
}
|
|
101
|
+
for (let i = 0; i < num; i++) jit1[i] = [jit2[i]![0], jit2[i]![1]];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** `BLI_jitter_init`: `num` points in the unit square, centred on (0, 0). */
|
|
105
|
+
function bliJitterInit(num: number): [number, number][] {
|
|
106
|
+
const numFl = f32(num);
|
|
107
|
+
const numFlSqrt = f32(Math.sqrt(numFl));
|
|
108
|
+
const rad1 = f32(1 / numFlSqrt);
|
|
109
|
+
const rad2 = f32(1 / numFl);
|
|
110
|
+
const rad3 = f32(numFlSqrt / numFl);
|
|
111
|
+
const rng = new BlenderRng(31415926 + num);
|
|
112
|
+
const jit: [number, number][] = [];
|
|
113
|
+
let x = 0;
|
|
114
|
+
for (let i = 0; i < num; i++) {
|
|
115
|
+
const a = f32(x + f32(rad1 * f32(0.5 - rng.getDouble())));
|
|
116
|
+
const b = f32(f32(f32(i) / numFl) + f32(rad1 * f32(0.5 - rng.getDouble())));
|
|
117
|
+
jit.push([a, b]);
|
|
118
|
+
x = f32(x + rad3);
|
|
119
|
+
x = f32(x - Math.floor(x));
|
|
120
|
+
}
|
|
121
|
+
const jit2: [number, number][] = jit.map(() => [0, 0]);
|
|
122
|
+
for (let i = 0; i < 24; i++) {
|
|
123
|
+
jitterate1(jit, jit2, rad1);
|
|
124
|
+
jitterate1(jit, jit2, rad1);
|
|
125
|
+
jitterate2(jit, jit2, rad2);
|
|
126
|
+
}
|
|
127
|
+
return jit.map(([a, b]) => [f32(a - 0.5), f32(b - 0.5)]);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** C's `fmodf`: the remainder carries the dividend's sign. */
|
|
131
|
+
const fmod = (a: number, b: number) => a - b * Math.trunc(a / b);
|
|
132
|
+
|
|
133
|
+
/** `TaaSamples::init_samples`: one of Workbench's tables, in [-1, 1) pixels-as-weighed. */
|
|
134
|
+
function workbenchTable(num: number): (readonly [number, number])[] {
|
|
135
|
+
const samples = bliJitterInit(num);
|
|
136
|
+
let closest = 0;
|
|
137
|
+
let closestDistance = 1;
|
|
138
|
+
samples.forEach(([x, y], i) => {
|
|
139
|
+
const d = f32(f32(x * x) + f32(y * y));
|
|
140
|
+
if (d < closestDistance) {
|
|
141
|
+
closestDistance = d;
|
|
142
|
+
closest = i;
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
const [cx, cy] = samples[closest]!;
|
|
146
|
+
const moved = samples.map(([x, y]): [number, number] => {
|
|
147
|
+
const sx = f32(x - cx);
|
|
148
|
+
const sy = f32(y - cy);
|
|
149
|
+
return [f32(f32(fmod(f32(sx + 0.5), 1) * 2) - 1), f32(f32(fmod(f32(sy + 0.5), 1) * 2) - 1)];
|
|
150
|
+
});
|
|
151
|
+
if (closest !== 0) [moved[0], moved[closest]] = [moved[closest]!, moved[0]!];
|
|
152
|
+
for (let i = 0; i < moved.length - 2; i++) {
|
|
153
|
+
let farthest = 0;
|
|
154
|
+
let index = i;
|
|
155
|
+
for (let j = i + 1; j < moved.length; j++) {
|
|
156
|
+
const dx = moved[i]![0] - moved[j]![0];
|
|
157
|
+
const dy = moved[i]![1] - moved[j]![1];
|
|
158
|
+
const d = dx * dx + dy * dy;
|
|
159
|
+
if (d > farthest) {
|
|
160
|
+
farthest = d;
|
|
161
|
+
index = j;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
[moved[i + 1], moved[index]] = [moved[index]!, moved[i + 1]!];
|
|
165
|
+
}
|
|
166
|
+
return moved;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const workbenchTables = new Map<number, (readonly [number, number])[]>();
|
|
170
|
+
|
|
171
|
+
/** Workbench's sampling for a sample count (`viewport_aa` / `render_aa` as a number: 0 off,
|
|
172
|
+
* 1 the single sample its SMAA resolves alone, else 5, 8, 11, 16 or 32). Off is one centred
|
|
173
|
+
* sample kept as it is: no accumulation, no filter (`workbench_state.cc`, `draw_aa` false). */
|
|
174
|
+
export function workbenchSampling(count: number, backdropMerge: boolean): BlenderPhotographSampling {
|
|
175
|
+
if (count <= 0) {
|
|
176
|
+
return {
|
|
177
|
+
offsets: [[0, 0]], projectionScale: 0.5, reach: 0, filter: 'blackman-harris', radius: 0,
|
|
178
|
+
accumulation: 'log2', exposureScale: 1, backdropMerge, liveFrom: 1,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
// One sample is the centre (`TaaSamples` always starts there); Workbench then SMAAs it.
|
|
182
|
+
const size = count <= 1 ? 1 : count <= 5 ? 5 : count <= 8 ? 8 : count <= 11 ? 11 : count <= 16 ? 16 : 32;
|
|
183
|
+
let offsets = workbenchTables.get(size);
|
|
184
|
+
if (!offsets) {
|
|
185
|
+
offsets = size === 1 ? [[0, 0]] : workbenchTable(size);
|
|
186
|
+
workbenchTables.set(size, offsets);
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
offsets, projectionScale: 0.5, reach: 1, filter: 'blackman-harris', radius: 1,
|
|
190
|
+
accumulation: 'log2', exposureScale: 1, backdropMerge, liveFrom: 1,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** `Sampling::sample_disk`. */
|
|
195
|
+
function sampleDisk(u: number, v: number): [number, number] {
|
|
196
|
+
const omega = v * 2 * Math.PI;
|
|
197
|
+
const r = Math.sqrt(u);
|
|
198
|
+
return [r * Math.cos(omega), r * Math.sin(omega)];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Radical inverse in `base` (`BLI_halton_2d` iterates to the same value for index `n`). */
|
|
202
|
+
function halton(n: number, base: number): number {
|
|
203
|
+
let result = 0;
|
|
204
|
+
let fraction = 1 / base;
|
|
205
|
+
for (let i = n; i > 0; i = Math.floor(i / base)) {
|
|
206
|
+
result += (i % base) * fraction;
|
|
207
|
+
fraction /= base;
|
|
208
|
+
}
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** EEVEE's film sampling over its viewport `taa_samples` (0: never stops) and `filter_size`. */
|
|
213
|
+
export function eeveeSampling(taaSamples: number, filterSize: number): BlenderPhotographSampling {
|
|
214
|
+
const count = Math.min(PHOTOGRAPH_SAMPLE_MAX, taaSamples > 0 ? taaSamples : PHOTOGRAPH_SAMPLE_MAX);
|
|
215
|
+
// A single sample is not filtered (`Film::init`), nor is a filter under 0.01, whose one texel
|
|
216
|
+
// is taken whole (`update_sample_table`).
|
|
217
|
+
const clamped = Math.min(100, Math.max(0, filterSize));
|
|
218
|
+
const radius = count === 1 || clamped < 0.01 ? 0 : clamped;
|
|
219
|
+
const fract = (x: number) => x - Math.floor(x);
|
|
220
|
+
const offsets: (readonly [number, number])[] = [];
|
|
221
|
+
for (let s = 0; s < count; s++) {
|
|
222
|
+
const u = fract(f32(halton(s + 1, 2) + f32(1 / 2)));
|
|
223
|
+
const v = fract(f32(halton(s + 1, 3) + f32(2 / 3)));
|
|
224
|
+
// A filter under 1/sqrt(2) (0 included) jitters over a disk of half its radius (`pixel_jitter_get`).
|
|
225
|
+
if (radius < Math.SQRT1_2) {
|
|
226
|
+
const [x, y] = sampleDisk(u, v);
|
|
227
|
+
offsets.push([x * 0.5 * radius, y * 0.5 * radius]);
|
|
228
|
+
} else offsets.push([u - 0.5, v - 0.5]);
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
offsets, projectionScale: 1, reach: Math.min(3, Math.ceil(radius)), filter: 'gaussian', radius,
|
|
232
|
+
// Exposure scale zero gives a constant luma weight.
|
|
233
|
+
accumulation: 'luma', exposureScale: 0, backdropMerge: false, liveFrom: Math.min(2, count),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
@@ -1,26 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BLENDER'S SOLID SHADING, as Blender computes it: `get_world_lighting` from
|
|
3
|
-
* `draw/engines/workbench/shaders/workbench_world_light_lib.glsl`, transcribed line for line,
|
|
4
|
-
* over the four studio lights of the factory `Default` (`SOLID_LIGHTS`, read back from Blender
|
|
5
|
-
* 5.2's preferences with their specular colours) and no ambient (`light_ambient` is zero).
|
|
6
|
-
*
|
|
7
|
-
* Per light: a wrapped diffuse term, `(N·L + wrap) / (1 + wrap)²`; a normalized Blinn specular
|
|
8
|
-
* whose gloss the wrap softens, blended toward a wrapped "environment" term by `wrap²`; a
|
|
9
|
-
* Fresnel lift of the specular colour (`brdf_approx`); and energy conservation that takes the
|
|
10
|
-
* specular's grey level off the diffuse. The surface is the material's VIEWPORT DISPLAY
|
|
11
|
-
* (`Material.diffuse_color`, `roughness`, `metallic`), which is what Solid's MATERIAL colour type
|
|
12
|
-
* reads — never its node graph.
|
|
13
|
-
*
|
|
14
|
-
* WHY A FUNCTION AND NOT A LIGHT RIG: three's lambert and BRDF cannot express the wrap, the
|
|
15
|
-
* coupled specular or the per-light specular colour, so the four lights plus one fitted gain
|
|
16
|
-
* could match the default view's three faces only by absorbing the difference, and missed a
|
|
17
|
-
* face square to the view by 18 levels (143 against Blender's 161). This function, evaluated
|
|
18
|
-
* offline for that face, gives 161.
|
|
19
|
-
*
|
|
20
|
-
* Built on `MeshLambertMaterial` so skinning, morphs and the view-space normal come with it;
|
|
21
|
-
* only the final colour is replaced. Blender uses a fast reciprocal (`fast_rcp`); this uses the
|
|
22
|
-
* exact one.
|
|
23
|
-
*/
|
|
1
|
+
/** Blender Solid shading from workbench_world_light_lib.glsl with the factory studio lights and viewport display colours. Pack roughness as sqrt(Material.roughness), use Blender’s fast_rcp and respect the Specular Lighting toggle. MeshLambertMaterial supplies skinning, morphs and view-space normals. */
|
|
24
2
|
import * as THREE from 'three';
|
|
25
3
|
import { SOLID_LIGHTS } from './blender-runtime-lighting';
|
|
26
4
|
|
|
@@ -31,8 +9,12 @@ export interface ViewportDisplay {
|
|
|
31
9
|
readonly metallic: number;
|
|
32
10
|
}
|
|
33
11
|
|
|
34
|
-
/**
|
|
35
|
-
|
|
12
|
+
/** `Material::pack_data`'s remap of the display roughness to Disney roughness. */
|
|
13
|
+
export function workbenchRoughness(display: ViewportDisplay): number {
|
|
14
|
+
return Math.sqrt(Math.min(1, Math.max(0, display.roughness)));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Default material display values: grey 0.8, roughness 0.5, metallic 0. */
|
|
36
18
|
export const DEFAULT_VIEWPORT_DISPLAY: ViewportDisplay = { color: [0.8, 0.8, 0.8, 1], roughness: 0.5, metallic: 0 };
|
|
37
19
|
|
|
38
20
|
const WORKBENCH_GLSL = /* glsl */ `
|
|
@@ -42,13 +24,25 @@ uniform vec3 wbSpecular[4];
|
|
|
42
24
|
uniform vec4 wbWrap;
|
|
43
25
|
uniform float wbRoughness;
|
|
44
26
|
uniform float wbMetallic;
|
|
27
|
+
uniform bool wbSpecularOn;
|
|
28
|
+
|
|
29
|
+
// [Drobot2014a] as Blender has it (\`workbench_world_light_lib.glsl\`): an approximate reciprocal.
|
|
30
|
+
vec4 wbFastRcp(vec4 v) {
|
|
31
|
+
return intBitsToFloat(0x7eef370b - floatBitsToInt(v));
|
|
32
|
+
}
|
|
45
33
|
|
|
46
34
|
vec4 wbWrapped(vec4 NL, vec4 w) {
|
|
47
35
|
vec4 w1 = w + 1.0;
|
|
48
|
-
return clamp((NL + w)
|
|
36
|
+
return clamp((NL + w) * wbFastRcp(w1 * w1), 0.0, 1.0);
|
|
49
37
|
}
|
|
50
38
|
|
|
51
39
|
vec3 volterWorkbenchLighting(vec3 baseColor, vec3 N, vec3 I) {
|
|
40
|
+
if (!wbSpecularOn) {
|
|
41
|
+
vec4 plainNL = vec4(dot(wbDir[0], N), dot(wbDir[1], N), dot(wbDir[2], N), dot(wbDir[3], N));
|
|
42
|
+
vec4 plainLight = wbWrapped(plainNL, wbWrap);
|
|
43
|
+
return (plainLight.x * wbDiffuse[0] + plainLight.y * wbDiffuse[1] +
|
|
44
|
+
plainLight.z * wbDiffuse[2] + plainLight.w * wbDiffuse[3]) * baseColor;
|
|
45
|
+
}
|
|
52
46
|
vec3 diffuseColor = mix(baseColor, vec3(0.0), wbMetallic);
|
|
53
47
|
vec3 specularColor = mix(vec3(0.05), baseColor, wbMetallic);
|
|
54
48
|
vec3 R = -reflect(I, N);
|
|
@@ -89,8 +83,9 @@ const LIGHT_DIFFUSE = vec3s((light) => light.diffuse);
|
|
|
89
83
|
const LIGHT_SPECULAR = vec3s((light) => light.specular);
|
|
90
84
|
const LIGHT_WRAP = new THREE.Vector4(...SOLID_LIGHTS.map((light) => light.smooth));
|
|
91
85
|
|
|
92
|
-
/** One Solid-shaded surface: Blender's function over this viewport display
|
|
93
|
-
|
|
86
|
+
/** One Solid-shaded surface: Blender's function over this viewport display, with or without the
|
|
87
|
+
* specular term (`View3DShading.show_specular_highlight`). */
|
|
88
|
+
export function workbenchMaterial(display: ViewportDisplay, side: THREE.Side, specular = true): THREE.MeshLambertMaterial {
|
|
94
89
|
const [r, g, b] = display.color;
|
|
95
90
|
const material = new THREE.MeshLambertMaterial({ side });
|
|
96
91
|
material.color.setRGB(r, g, b, THREE.LinearSRGBColorSpace);
|
|
@@ -100,8 +95,9 @@ export function workbenchMaterial(display: ViewportDisplay, side: THREE.Side): T
|
|
|
100
95
|
shader.uniforms['wbDiffuse'] = { value: LIGHT_DIFFUSE };
|
|
101
96
|
shader.uniforms['wbSpecular'] = { value: LIGHT_SPECULAR };
|
|
102
97
|
shader.uniforms['wbWrap'] = { value: LIGHT_WRAP };
|
|
103
|
-
shader.uniforms['wbRoughness'] = { value: display
|
|
98
|
+
shader.uniforms['wbRoughness'] = { value: workbenchRoughness(display) };
|
|
104
99
|
shader.uniforms['wbMetallic'] = { value: display.metallic };
|
|
100
|
+
shader.uniforms['wbSpecularOn'] = { value: specular };
|
|
105
101
|
shader.fragmentShader = shader.fragmentShader
|
|
106
102
|
.replace('void main() {', `${WORKBENCH_GLSL}\nvoid main() {`)
|
|
107
103
|
.replace(
|