rayzee 9.4.2 → 9.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +223 -21
- package/dist/ActiveColor-YvuQq44O.js +13 -0
- package/dist/ArchiveImporter-xuUJiklP.js +4301 -0
- package/dist/AssetConfig-DSEntE65.js +30 -0
- package/dist/BidirectionalIntegrator-C5PKjJoT.js +934 -0
- package/dist/ColorManagement-CheBCPaW.js +1488 -0
- package/dist/EngineDefaults-DH3bI5t-.js +844 -0
- package/dist/EngineEvents-Ci8dVWCP.js +73 -0
- package/dist/EngineIssues-CIZnXHsF.js +72 -0
- package/dist/FileFormats-sPGJeCRc.js +102 -0
- package/dist/Logger-BnHghprG.js +97 -0
- package/dist/{NeuralRetouch-vkdXXyK2.js → NeuralRetouch-CW11yLZ_.js} +8 -6
- package/dist/{NeuralSuperRes-FVqRJKQV.js → NeuralSuperRes-BekaecYg.js} +55 -120
- package/dist/PathTracer-Ccl9KOuF.js +29729 -0
- package/dist/PhysicalSky-Dv2PMFGW.js +1219 -0
- package/dist/RayzeeRenderer-BQxnzCTD.js +3542 -0
- package/dist/SolarLimb-ken-JbDK.js +721 -0
- package/dist/ToneMapGPU-BHi5JzIq.js +177 -0
- package/dist/ViewTransforms-D0rTLBsX.js +255 -0
- package/dist/WorkingMatrix-C6y233hT.js +27 -0
- package/dist/ZipReader-gDSWr0I5.js +98 -0
- package/dist/addons/archives.es.js +3 -0
- package/dist/addons/bidirectional.es.js +2 -0
- package/dist/addons/color.es.js +3 -0
- package/dist/addons/formats.es.js +2 -0
- package/dist/addons/physical-sky.es.js +2 -0
- package/dist/addons/storage.es.js +3 -0
- package/dist/archiveFormats-BZw7TiDp.js +75 -0
- package/dist/identity-DrQsGT2o.js +39 -0
- package/dist/openStorage-DwcBRUj7.js +820 -0
- package/dist/rayzee-core.es.js +12 -0
- package/dist/rayzee.es.js +2266 -44079
- package/dist/rayzee.umd.js +485 -473
- package/dist/sceneCachePolicy-BmwYOYDu.js +183 -0
- package/dist/shared-BGBcSxBQ.js +17 -0
- package/package.json +23 -2
- package/src/Color/ActiveColor.js +34 -0
- package/src/Color/BasicColor.js +147 -0
- package/src/Color/ColorManagement.js +7 -31
- package/src/EngineDefaults.js +22 -8
- package/src/EngineEvents.js +2 -0
- package/src/EngineIssues.js +1 -0
- package/src/PathTracerApp.js +617 -4016
- package/src/Pipeline/PipelineContext.js +0 -4
- package/src/Platform.js +36 -0
- package/src/Processor/ArchiveCache.js +1 -1
- package/src/Processor/ArchiveImporter.js +1259 -0
- package/src/Processor/AssetLoader.js +322 -1746
- package/src/Processor/AtmosphereModel.js +1 -2
- package/src/Processor/BVHBuilder.js +4 -95
- package/src/Processor/EnvironmentExactTable.js +187 -0
- package/src/Processor/EquirectHDRInfo.js +30 -223
- package/src/Processor/FileFormats.js +116 -0
- package/src/Processor/GLTFDecoders.js +85 -0
- package/src/Processor/GLTFDiffuseTransmission.js +63 -0
- package/src/Processor/GeometryExtractor.js +12 -1
- package/src/Processor/IESParser.js +47 -35
- package/src/Processor/KernelManager.js +35 -2
- package/src/Processor/MaterialPacking.js +8 -7
- package/src/Processor/PBRT/PBRTMaterials.js +129 -41
- package/src/Processor/PBRT/PBRTParser.js +30 -17
- package/src/Processor/PBRT/PBRTSceneBuilder.js +548 -37
- package/src/Processor/PBRT/PBRTTextureBake.js +154 -0
- package/src/Processor/PBRT/PFM.js +59 -0
- package/src/Processor/PackedRayBuffer.js +116 -9
- package/src/Processor/ParallelBVHBuilder.js +3 -6
- package/src/Processor/PhysicalSky.js +5 -4
- package/src/Processor/PlatformImageLoader.js +50 -6
- package/src/Processor/QueueManager.js +12 -3
- package/src/Processor/ReinsertionOptimizer.js +2 -2
- package/src/Processor/SceneProcessor.js +15 -55
- package/src/Processor/ShaderBuilder.js +6 -16
- package/src/Processor/SolarLimb.js +3 -0
- package/src/Processor/TextureCreator.js +1 -1
- package/src/Processor/Workers/BVHSubtreeWorker.js +1 -34
- package/src/Processor/Workers/BVHWorker.js +5 -18
- package/src/Processor/Workers/CDFWorker.js +6 -183
- package/src/Processor/archiveFormats.js +7 -0
- package/src/Processor/utils.js +2 -1
- package/src/RayzeeRenderer.js +3902 -0
- package/src/RenderSettings.js +49 -41
- package/src/Stages/ASVGF.js +5 -7
- package/src/Stages/AutoExposure.js +3 -12
- package/src/Stages/Compositor.js +8 -23
- package/src/Stages/NRD.js +9 -2
- package/src/Stages/NormalDepth.js +9 -6
- package/src/Stages/PathTracer.js +254 -38
- package/src/Stages/PathTracerStage.js +11 -16
- package/src/Storage/BLASCache.js +1 -1
- package/src/Storage/CDFCache.js +18 -17
- package/src/Storage/DownloadCache.js +1 -1
- package/src/Storage/SceneGraphCodec.js +46 -3
- package/src/Storage/SpillStore.js +1 -1
- package/src/Storage/StorageManager.js +2 -4
- package/src/Storage/areas.js +4 -0
- package/src/TSL/BVHTraversal.js +62 -24
- package/src/TSL/Bidirectional.js +204 -0
- package/src/TSL/BidirectionalLamps.js +132 -0
- package/src/TSL/Clearcoat.js +15 -6
- package/src/TSL/Common.js +19 -14
- package/src/TSL/ConnectKernel.js +147 -0
- package/src/TSL/Debugger.js +1 -1
- package/src/TSL/Displacement.js +4 -3
- package/src/TSL/EmissiveSampling.js +64 -17
- package/src/TSL/Environment.js +77 -106
- package/src/TSL/EnvironmentCDF.js +85 -39
- package/src/TSL/ExtendKernel.js +8 -3
- package/src/TSL/FinalWriteKernel.js +4 -4
- package/src/TSL/GenerateKernel.js +18 -4
- package/src/TSL/HitFacet.js +35 -5
- package/src/TSL/LightBVHSampling.js +40 -9
- package/src/TSL/LightGenerateKernel.js +293 -0
- package/src/TSL/LightGuide.js +198 -0
- package/src/TSL/LightSplatKernel.js +165 -0
- package/src/TSL/LightsCore.js +13 -38
- package/src/TSL/LightsDirect.js +28 -29
- package/src/TSL/LightsIndirect.js +3 -1
- package/src/TSL/LightsSampling.js +90 -38
- package/src/TSL/MaterialEvaluation.js +113 -31
- package/src/TSL/MaterialProperties.js +44 -17
- package/src/TSL/MaterialTransmission.js +52 -47
- package/src/TSL/MergeKernel.js +218 -0
- package/src/TSL/PathTracerCore.js +65 -29
- package/src/TSL/Random.js +4 -0
- package/src/TSL/SceneResources.js +51 -0
- package/src/TSL/ShadeKernel.js +881 -103
- package/src/TSL/Struct.js +10 -0
- package/src/TSL/Sun.js +1 -1
- package/src/TSL/TextureSampling.js +65 -60
- package/src/addons/archives.js +14 -0
- package/src/addons/bidirectional.js +13 -0
- package/src/addons/color.js +32 -0
- package/src/addons/formats.js +16 -0
- package/src/addons/physicalSky.js +14 -0
- package/src/addons/storage.js +19 -0
- package/src/core.js +35 -0
- package/src/index.js +3 -1
- package/src/integrators/BidirectionalIntegrator.js +832 -0
- package/src/managers/EnvironmentManager.js +85 -33
- package/src/managers/MaterialDataManager.js +42 -0
- package/src/managers/UniformManager.js +0 -1
- package/dist/ToneMapGPU-DKQM1pMd.js +0 -552
- package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
- package/src/Processor/TreeletOptimizer.js +0 -574
|
@@ -1,574 +0,0 @@
|
|
|
1
|
-
// No external dependencies — uses inline float fields for bounds (no Vector3)
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Treelet optimization for BVH trees (Bittner et al. 2013).
|
|
5
|
-
*
|
|
6
|
-
* For each small subtree ("treelet") of N leaves, enumerates all
|
|
7
|
-
* Catalan(N-1) distinct binary tree topologies, tries all leaf
|
|
8
|
-
* permutations (or greedy assignment for N > 5), and replaces the
|
|
9
|
-
* subtree with the arrangement that minimises SAH cost.
|
|
10
|
-
*/
|
|
11
|
-
export class TreeletOptimizer {
|
|
12
|
-
|
|
13
|
-
constructor( traversalCost, intersectionCost ) {
|
|
14
|
-
|
|
15
|
-
this.traversalCost = traversalCost;
|
|
16
|
-
this.intersectionCost = intersectionCost;
|
|
17
|
-
this.maxTreeletLeaves = 7;
|
|
18
|
-
this.minImprovement = 0.02; // relative improvement threshold
|
|
19
|
-
|
|
20
|
-
// Precompute topologies for leaf counts 3..maxTreeletLeaves
|
|
21
|
-
this.topologyCache = new Map();
|
|
22
|
-
for ( let n = 3; n <= this.maxTreeletLeaves; n ++ ) {
|
|
23
|
-
|
|
24
|
-
this.topologyCache.set( n, this.generateTopologies( n ) );
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Stats
|
|
29
|
-
this.stats = {
|
|
30
|
-
treeletsProcessed: 0,
|
|
31
|
-
treeletsImproved: 0,
|
|
32
|
-
totalSAHImprovement: 0,
|
|
33
|
-
averageSAHImprovement: 0,
|
|
34
|
-
optimizationTime: 0
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// ------------------------------------------------------------------
|
|
40
|
-
// Topology generation — nested binary trees via Catalan decomposition
|
|
41
|
-
// ------------------------------------------------------------------
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Generate all distinct binary tree topologies for `n` leaves.
|
|
45
|
-
* Leaves are represented as integers 0..n-1 (slot indices).
|
|
46
|
-
* Internal nodes are 2-element arrays [left, right].
|
|
47
|
-
*
|
|
48
|
-
* Examples for n=3 (Catalan(2)=2):
|
|
49
|
-
* [[0,1],2] and [0,[1,2]]
|
|
50
|
-
*/
|
|
51
|
-
generateTopologies( n ) {
|
|
52
|
-
|
|
53
|
-
if ( n === 1 ) return [ 0 ];
|
|
54
|
-
if ( n === 2 ) return [[ 0, 1 ]];
|
|
55
|
-
|
|
56
|
-
const results = [];
|
|
57
|
-
|
|
58
|
-
// Split n leaves into left (k) and right (n-k) groups.
|
|
59
|
-
// Base cases return arrays of topologies:
|
|
60
|
-
// n=1 → [0] (one topology: leaf 0)
|
|
61
|
-
// n=2 → [[0,1]] (one topology: pair [0,1])
|
|
62
|
-
// So `for (const t of topos)` yields individual topologies in all cases.
|
|
63
|
-
for ( let k = 1; k < n; k ++ ) {
|
|
64
|
-
|
|
65
|
-
const leftTopos = this.generateTopologies( k );
|
|
66
|
-
const rightTopos = this.generateTopologies( n - k );
|
|
67
|
-
|
|
68
|
-
for ( const lt of leftTopos ) {
|
|
69
|
-
|
|
70
|
-
for ( const rt of rightTopos ) {
|
|
71
|
-
|
|
72
|
-
results.push( [ lt, this.offsetTopology( rt, k ) ] );
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return results;
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Offset all leaf indices in a topology by `offset`.
|
|
86
|
-
*/
|
|
87
|
-
offsetTopology( topo, offset ) {
|
|
88
|
-
|
|
89
|
-
if ( typeof topo === 'number' ) return topo + offset;
|
|
90
|
-
return [ this.offsetTopology( topo[ 0 ], offset ), this.offsetTopology( topo[ 1 ], offset ) ];
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// ------------------------------------------------------------------
|
|
95
|
-
// Main entry point
|
|
96
|
-
// ------------------------------------------------------------------
|
|
97
|
-
|
|
98
|
-
optimizeBVH( bvhRoot ) {
|
|
99
|
-
|
|
100
|
-
const startTime = performance.now();
|
|
101
|
-
const maxTime = 30000; // 30s safety timeout
|
|
102
|
-
|
|
103
|
-
this.stats = {
|
|
104
|
-
treeletsProcessed: 0,
|
|
105
|
-
treeletsImproved: 0,
|
|
106
|
-
totalSAHImprovement: 0,
|
|
107
|
-
averageSAHImprovement: 0,
|
|
108
|
-
optimizationTime: 0
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
// Identify all treelet roots (bottom-up, non-overlapping)
|
|
112
|
-
const treeletRoots = this.identifyTreeletRoots( bvhRoot );
|
|
113
|
-
|
|
114
|
-
for ( let i = 0; i < treeletRoots.length; i ++ ) {
|
|
115
|
-
|
|
116
|
-
if ( performance.now() - startTime > maxTime ) {
|
|
117
|
-
|
|
118
|
-
console.warn( `TreeletOptimizer: timeout after ${i}/${treeletRoots.length} treelets` );
|
|
119
|
-
break;
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
this.optimizeTreelet( treeletRoots[ i ] );
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
this.stats.optimizationTime = performance.now() - startTime;
|
|
128
|
-
this.stats.averageSAHImprovement = this.stats.treeletsProcessed > 0
|
|
129
|
-
? this.stats.totalSAHImprovement / this.stats.treeletsProcessed
|
|
130
|
-
: 0;
|
|
131
|
-
|
|
132
|
-
return bvhRoot;
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// ------------------------------------------------------------------
|
|
137
|
-
// Treelet identification — bottom-up, non-overlapping
|
|
138
|
-
// ------------------------------------------------------------------
|
|
139
|
-
|
|
140
|
-
identifyTreeletRoots( bvhRoot ) {
|
|
141
|
-
|
|
142
|
-
const roots = [];
|
|
143
|
-
const processed = new Set();
|
|
144
|
-
|
|
145
|
-
// Post-order traversal: children before parents so we process
|
|
146
|
-
// bottom-up and skip subtrees already covered by a deeper treelet.
|
|
147
|
-
const stack = [ { node: bvhRoot, visited: false } ];
|
|
148
|
-
|
|
149
|
-
while ( stack.length > 0 ) {
|
|
150
|
-
|
|
151
|
-
const top = stack[ stack.length - 1 ];
|
|
152
|
-
|
|
153
|
-
if ( top.visited ) {
|
|
154
|
-
|
|
155
|
-
stack.pop();
|
|
156
|
-
const node = top.node;
|
|
157
|
-
|
|
158
|
-
// Skip leaves
|
|
159
|
-
if ( node.triangleCount > 0 ) continue;
|
|
160
|
-
// Skip if already inside a processed treelet
|
|
161
|
-
if ( processed.has( node ) ) continue;
|
|
162
|
-
|
|
163
|
-
const leafCount = this.countLeaves( node );
|
|
164
|
-
if ( leafCount >= 3 && leafCount <= this.maxTreeletLeaves ) {
|
|
165
|
-
|
|
166
|
-
roots.push( node );
|
|
167
|
-
this.markSubtree( node, processed );
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
} else {
|
|
172
|
-
|
|
173
|
-
top.visited = true;
|
|
174
|
-
const node = top.node;
|
|
175
|
-
if ( node.triangleCount > 0 ) continue;
|
|
176
|
-
if ( node.rightChild ) stack.push( { node: node.rightChild, visited: false } );
|
|
177
|
-
if ( node.leftChild ) stack.push( { node: node.leftChild, visited: false } );
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return roots;
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
countLeaves( node ) {
|
|
188
|
-
|
|
189
|
-
if ( ! node ) return 0;
|
|
190
|
-
if ( node.triangleCount > 0 ) return 1;
|
|
191
|
-
return this.countLeaves( node.leftChild ) + this.countLeaves( node.rightChild );
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
markSubtree( node, set ) {
|
|
196
|
-
|
|
197
|
-
if ( ! node ) return;
|
|
198
|
-
set.add( node );
|
|
199
|
-
if ( node.triangleCount > 0 ) return;
|
|
200
|
-
this.markSubtree( node.leftChild, set );
|
|
201
|
-
this.markSubtree( node.rightChild, set );
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// ------------------------------------------------------------------
|
|
206
|
-
// Single treelet optimization
|
|
207
|
-
// ------------------------------------------------------------------
|
|
208
|
-
|
|
209
|
-
optimizeTreelet( treeletRoot ) {
|
|
210
|
-
|
|
211
|
-
// Extract leaves
|
|
212
|
-
const leaves = [];
|
|
213
|
-
this.extractLeaves( treeletRoot, leaves );
|
|
214
|
-
const n = leaves.length;
|
|
215
|
-
|
|
216
|
-
if ( n < 3 || n > this.maxTreeletLeaves ) return;
|
|
217
|
-
|
|
218
|
-
this.stats.treeletsProcessed ++;
|
|
219
|
-
|
|
220
|
-
const originalCost = this.evaluateSubtreeSAH( treeletRoot );
|
|
221
|
-
const topologies = this.topologyCache.get( n );
|
|
222
|
-
if ( ! topologies || topologies.length === 0 ) return;
|
|
223
|
-
|
|
224
|
-
let bestCost = originalCost;
|
|
225
|
-
let bestTopo = null;
|
|
226
|
-
let bestPerm = null;
|
|
227
|
-
|
|
228
|
-
if ( n <= 5 ) {
|
|
229
|
-
|
|
230
|
-
// Full permutation search — feasible for n<=5 (max 120 perms × 14 topos)
|
|
231
|
-
const perms = this.generatePermutations( n );
|
|
232
|
-
|
|
233
|
-
for ( const topo of topologies ) {
|
|
234
|
-
|
|
235
|
-
for ( const perm of perms ) {
|
|
236
|
-
|
|
237
|
-
const cost = this.evaluateTopology( topo, leaves, perm );
|
|
238
|
-
if ( cost < bestCost ) {
|
|
239
|
-
|
|
240
|
-
bestCost = cost;
|
|
241
|
-
bestTopo = topo;
|
|
242
|
-
bestPerm = perm;
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
} else {
|
|
251
|
-
|
|
252
|
-
// Greedy assignment for n>5: for each topology, use identity perm
|
|
253
|
-
// plus a few SAH-guided swaps
|
|
254
|
-
const identityPerm = Array.from( { length: n }, ( _, i ) => i );
|
|
255
|
-
|
|
256
|
-
for ( const topo of topologies ) {
|
|
257
|
-
|
|
258
|
-
// Try identity permutation
|
|
259
|
-
const cost = this.evaluateTopology( topo, leaves, identityPerm );
|
|
260
|
-
if ( cost < bestCost ) {
|
|
261
|
-
|
|
262
|
-
bestCost = cost;
|
|
263
|
-
bestTopo = topo;
|
|
264
|
-
bestPerm = identityPerm;
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Try greedy swap improvements
|
|
269
|
-
const result = this.greedySwapOptimize( topo, leaves, identityPerm, cost );
|
|
270
|
-
if ( result.cost < bestCost ) {
|
|
271
|
-
|
|
272
|
-
bestCost = result.cost;
|
|
273
|
-
bestTopo = topo;
|
|
274
|
-
bestPerm = result.perm;
|
|
275
|
-
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Apply if relative improvement exceeds threshold
|
|
283
|
-
const relativeImprovement = ( originalCost - bestCost ) / originalCost;
|
|
284
|
-
if ( bestTopo && relativeImprovement > this.minImprovement ) {
|
|
285
|
-
|
|
286
|
-
this.reconstructTreelet( treeletRoot, bestTopo, leaves, bestPerm );
|
|
287
|
-
this.stats.treeletsImproved ++;
|
|
288
|
-
this.stats.totalSAHImprovement += relativeImprovement;
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
extractLeaves( node, leaves ) {
|
|
295
|
-
|
|
296
|
-
if ( ! node ) return;
|
|
297
|
-
|
|
298
|
-
if ( node.triangleCount > 0 ) {
|
|
299
|
-
|
|
300
|
-
// BVHNode uses inline floats (minX/maxX etc.)
|
|
301
|
-
leaves.push( {
|
|
302
|
-
minX: node.minX, minY: node.minY, minZ: node.minZ,
|
|
303
|
-
maxX: node.maxX, maxY: node.maxY, maxZ: node.maxZ,
|
|
304
|
-
triangleOffset: node.triangleOffset,
|
|
305
|
-
triangleCount: node.triangleCount
|
|
306
|
-
} );
|
|
307
|
-
return;
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
this.extractLeaves( node.leftChild, leaves );
|
|
312
|
-
this.extractLeaves( node.rightChild, leaves );
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// ------------------------------------------------------------------
|
|
317
|
-
// SAH evaluation
|
|
318
|
-
// ------------------------------------------------------------------
|
|
319
|
-
|
|
320
|
-
evaluateSubtreeSAH( node ) {
|
|
321
|
-
|
|
322
|
-
if ( ! node ) return 0;
|
|
323
|
-
|
|
324
|
-
if ( node.triangleCount > 0 ) {
|
|
325
|
-
|
|
326
|
-
return this.surfaceAreaFlat( node.minX, node.minY, node.minZ, node.maxX, node.maxY, node.maxZ ) * node.triangleCount * this.intersectionCost;
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const leftCost = this.evaluateSubtreeSAH( node.leftChild );
|
|
331
|
-
const rightCost = this.evaluateSubtreeSAH( node.rightChild );
|
|
332
|
-
const sa = this.surfaceAreaFlat( node.minX, node.minY, node.minZ, node.maxX, node.maxY, node.maxZ );
|
|
333
|
-
return sa * this.traversalCost + leftCost + rightCost;
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Evaluate SAH cost for a topology with a given leaf permutation.
|
|
339
|
-
* Returns { cost, boundsMin, boundsMax } for internal use, or just cost number.
|
|
340
|
-
*/
|
|
341
|
-
evaluateTopology( topo, leaves, perm ) {
|
|
342
|
-
|
|
343
|
-
const result = this.evalTopoRecursive( topo, leaves, perm );
|
|
344
|
-
return result.cost;
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
evalTopoRecursive( topo, leaves, perm ) {
|
|
349
|
-
|
|
350
|
-
if ( typeof topo === 'number' ) {
|
|
351
|
-
|
|
352
|
-
// Leaf slot — look up actual leaf via permutation
|
|
353
|
-
const leaf = leaves[ perm[ topo ] ];
|
|
354
|
-
return {
|
|
355
|
-
cost: this.surfaceAreaFlat( leaf.minX, leaf.minY, leaf.minZ, leaf.maxX, leaf.maxY, leaf.maxZ ) * leaf.triangleCount * this.intersectionCost,
|
|
356
|
-
minX: leaf.minX, minY: leaf.minY, minZ: leaf.minZ,
|
|
357
|
-
maxX: leaf.maxX, maxY: leaf.maxY, maxZ: leaf.maxZ
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const left = this.evalTopoRecursive( topo[ 0 ], leaves, perm );
|
|
363
|
-
const right = this.evalTopoRecursive( topo[ 1 ], leaves, perm );
|
|
364
|
-
|
|
365
|
-
const mnX = Math.min( left.minX, right.minX );
|
|
366
|
-
const mnY = Math.min( left.minY, right.minY );
|
|
367
|
-
const mnZ = Math.min( left.minZ, right.minZ );
|
|
368
|
-
const mxX = Math.max( left.maxX, right.maxX );
|
|
369
|
-
const mxY = Math.max( left.maxY, right.maxY );
|
|
370
|
-
const mxZ = Math.max( left.maxZ, right.maxZ );
|
|
371
|
-
|
|
372
|
-
const sa = this.surfaceAreaFlat( mnX, mnY, mnZ, mxX, mxY, mxZ );
|
|
373
|
-
return {
|
|
374
|
-
cost: sa * this.traversalCost + left.cost + right.cost,
|
|
375
|
-
minX: mnX, minY: mnY, minZ: mnZ,
|
|
376
|
-
maxX: mxX, maxY: mxY, maxZ: mxZ
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
surfaceAreaFlat( minX, minY, minZ, maxX, maxY, maxZ ) {
|
|
382
|
-
|
|
383
|
-
const dx = maxX - minX;
|
|
384
|
-
const dy = maxY - minY;
|
|
385
|
-
const dz = maxZ - minZ;
|
|
386
|
-
return 2 * ( dx * dy + dy * dz + dz * dx );
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// ------------------------------------------------------------------
|
|
391
|
-
// Permutation helpers
|
|
392
|
-
// ------------------------------------------------------------------
|
|
393
|
-
|
|
394
|
-
generatePermutations( n ) {
|
|
395
|
-
|
|
396
|
-
const result = [];
|
|
397
|
-
const arr = Array.from( { length: n }, ( _, i ) => i );
|
|
398
|
-
|
|
399
|
-
const permute = ( start ) => {
|
|
400
|
-
|
|
401
|
-
if ( start === n ) {
|
|
402
|
-
|
|
403
|
-
result.push( [ ...arr ] );
|
|
404
|
-
return;
|
|
405
|
-
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
for ( let i = start; i < n; i ++ ) {
|
|
409
|
-
|
|
410
|
-
[ arr[ start ], arr[ i ] ] = [ arr[ i ], arr[ start ] ];
|
|
411
|
-
permute( start + 1 );
|
|
412
|
-
[ arr[ start ], arr[ i ] ] = [ arr[ i ], arr[ start ] ];
|
|
413
|
-
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
};
|
|
417
|
-
|
|
418
|
-
permute( 0 );
|
|
419
|
-
return result;
|
|
420
|
-
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Greedy pairwise swap optimization for large treelet sizes.
|
|
425
|
-
* Starting from an initial permutation, try all pairwise swaps
|
|
426
|
-
* and accept any that improve cost. Repeat until no improvement.
|
|
427
|
-
*/
|
|
428
|
-
greedySwapOptimize( topo, leaves, initialPerm, initialCost ) {
|
|
429
|
-
|
|
430
|
-
const perm = [ ...initialPerm ];
|
|
431
|
-
let cost = initialCost;
|
|
432
|
-
let improved = true;
|
|
433
|
-
|
|
434
|
-
while ( improved ) {
|
|
435
|
-
|
|
436
|
-
improved = false;
|
|
437
|
-
|
|
438
|
-
for ( let i = 0; i < perm.length - 1; i ++ ) {
|
|
439
|
-
|
|
440
|
-
for ( let j = i + 1; j < perm.length; j ++ ) {
|
|
441
|
-
|
|
442
|
-
// Swap
|
|
443
|
-
[ perm[ i ], perm[ j ] ] = [ perm[ j ], perm[ i ] ];
|
|
444
|
-
const newCost = this.evaluateTopology( topo, leaves, perm );
|
|
445
|
-
|
|
446
|
-
if ( newCost < cost ) {
|
|
447
|
-
|
|
448
|
-
cost = newCost;
|
|
449
|
-
improved = true;
|
|
450
|
-
|
|
451
|
-
} else {
|
|
452
|
-
|
|
453
|
-
// Swap back
|
|
454
|
-
[ perm[ i ], perm[ j ] ] = [ perm[ j ], perm[ i ] ];
|
|
455
|
-
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
return { perm, cost };
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// ------------------------------------------------------------------
|
|
469
|
-
// Reconstruction — builds proper BVHNode instances
|
|
470
|
-
// ------------------------------------------------------------------
|
|
471
|
-
|
|
472
|
-
reconstructTreelet( treeletRoot, topo, leaves, perm ) {
|
|
473
|
-
|
|
474
|
-
const built = this.buildSubtree( topo, leaves, perm );
|
|
475
|
-
|
|
476
|
-
// Copy built structure into the existing treelet root node (inline floats)
|
|
477
|
-
treeletRoot.minX = built.minX; treeletRoot.minY = built.minY; treeletRoot.minZ = built.minZ;
|
|
478
|
-
treeletRoot.maxX = built.maxX; treeletRoot.maxY = built.maxY; treeletRoot.maxZ = built.maxZ;
|
|
479
|
-
treeletRoot.leftChild = built.leftChild;
|
|
480
|
-
treeletRoot.rightChild = built.rightChild;
|
|
481
|
-
treeletRoot.triangleOffset = built.triangleOffset;
|
|
482
|
-
treeletRoot.triangleCount = built.triangleCount;
|
|
483
|
-
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* Recursively build a BVHNode subtree from a topology + leaf permutation.
|
|
488
|
-
*/
|
|
489
|
-
buildSubtree( topo, leaves, perm ) {
|
|
490
|
-
|
|
491
|
-
if ( typeof topo === 'number' ) {
|
|
492
|
-
|
|
493
|
-
// Leaf — create a lightweight node with inline bounds
|
|
494
|
-
const leaf = leaves[ perm[ topo ] ];
|
|
495
|
-
const node = new TreeletBVHNode();
|
|
496
|
-
node.minX = leaf.minX; node.minY = leaf.minY; node.minZ = leaf.minZ;
|
|
497
|
-
node.maxX = leaf.maxX; node.maxY = leaf.maxY; node.maxZ = leaf.maxZ;
|
|
498
|
-
node.triangleOffset = leaf.triangleOffset;
|
|
499
|
-
node.triangleCount = leaf.triangleCount;
|
|
500
|
-
return node;
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
const left = this.buildSubtree( topo[ 0 ], leaves, perm );
|
|
505
|
-
const right = this.buildSubtree( topo[ 1 ], leaves, perm );
|
|
506
|
-
|
|
507
|
-
const node = new TreeletBVHNode();
|
|
508
|
-
node.leftChild = left;
|
|
509
|
-
node.rightChild = right;
|
|
510
|
-
node.minX = Math.min( left.minX, right.minX );
|
|
511
|
-
node.minY = Math.min( left.minY, right.minY );
|
|
512
|
-
node.minZ = Math.min( left.minZ, right.minZ );
|
|
513
|
-
node.maxX = Math.max( left.maxX, right.maxX );
|
|
514
|
-
node.maxY = Math.max( left.maxY, right.maxY );
|
|
515
|
-
node.maxZ = Math.max( left.maxZ, right.maxZ );
|
|
516
|
-
|
|
517
|
-
return node;
|
|
518
|
-
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// ------------------------------------------------------------------
|
|
522
|
-
// Configuration
|
|
523
|
-
// ------------------------------------------------------------------
|
|
524
|
-
|
|
525
|
-
setTreeletSize( size ) {
|
|
526
|
-
|
|
527
|
-
this.maxTreeletLeaves = Math.max( 3, Math.min( 7, size ) );
|
|
528
|
-
|
|
529
|
-
// Regenerate topology cache if needed
|
|
530
|
-
for ( let n = 3; n <= this.maxTreeletLeaves; n ++ ) {
|
|
531
|
-
|
|
532
|
-
if ( ! this.topologyCache.has( n ) ) {
|
|
533
|
-
|
|
534
|
-
this.topologyCache.set( n, this.generateTopologies( n ) );
|
|
535
|
-
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
setMinImprovement( threshold ) {
|
|
543
|
-
|
|
544
|
-
this.minImprovement = Math.max( 0.001, threshold );
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
getStatistics() {
|
|
549
|
-
|
|
550
|
-
return { ...this.stats };
|
|
551
|
-
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
/**
|
|
557
|
-
* Lightweight BVH node used during reconstruction.
|
|
558
|
-
* Duck-type compatible with the main BVHNode class —
|
|
559
|
-
* uses inline float fields (minX/maxX etc.).
|
|
560
|
-
*/
|
|
561
|
-
class TreeletBVHNode {
|
|
562
|
-
|
|
563
|
-
constructor() {
|
|
564
|
-
|
|
565
|
-
this.minX = 0; this.minY = 0; this.minZ = 0;
|
|
566
|
-
this.maxX = 0; this.maxY = 0; this.maxZ = 0;
|
|
567
|
-
this.leftChild = null;
|
|
568
|
-
this.rightChild = null;
|
|
569
|
-
this.triangleOffset = 0;
|
|
570
|
-
this.triangleCount = 0;
|
|
571
|
-
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
}
|