rayzee 8.6.0 → 8.7.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 +1 -53
- package/dist/{DLSSNeural-BhBCBgKf.js → NeuralRetouch-CYOJ21Wn.js} +31 -31
- package/dist/{DLSSSuperRes-BCA7F_k5.js → NeuralSuperRes-Dc6jRYke.js} +23 -23
- package/dist/{ToneMapGPU-BzcLheoR.js → ToneMapGPU-BVE852iN.js} +2 -2
- package/dist/rayzee.es.js +3552 -2990
- package/dist/rayzee.umd.js +17 -17
- package/package.json +1 -1
- package/src/AssetConfig.js +6 -5
- package/src/Passes/OIDNDenoiser.js +13 -5
- package/src/PathTracerApp.js +97 -2
- package/src/Processor/AssetLoader.js +14 -8
- package/src/Processor/EmissiveTriangleBuilder.js +70 -0
- package/src/Processor/PBRT/PBRTAnimation.js +646 -0
- package/src/Processor/PBRT/PBRTParser.js +179 -52
- package/src/Processor/PBRT/PBRTSceneBuilder.js +181 -25
- package/src/Processor/PBRT/index.js +73 -14
- package/src/Processor/SceneProcessor.js +22 -0
- package/src/Stages/PathTracerStage.js +16 -1
- package/src/index.js +3 -3
- package/src/managers/AnimationManager.js +202 -8
- package/src/managers/DenoisingManager.js +60 -44
- package/src/{dlss/DLSSNeural.js → neural/NeuralRetouch.js} +42 -41
- package/src/{dlss/DLSSSuperRes.js → neural/NeuralSuperRes.js} +27 -26
package/package.json
CHANGED
package/src/AssetConfig.js
CHANGED
|
@@ -45,12 +45,13 @@ const config = {
|
|
|
45
45
|
// AI upscaler ONNX model base URL. Quality presets resolve relative paths against this.
|
|
46
46
|
upscalerModelBaseUrl: 'https://huggingface.co/notaneimu/onnx-image-models/resolve/main/',
|
|
47
47
|
|
|
48
|
-
//
|
|
49
|
-
// `globalThis.
|
|
50
|
-
|
|
48
|
+
// Neural post-pass runtime. A plain script the host serves, not a module: it installs
|
|
49
|
+
// `globalThis.NeuralRuntime`, so it cannot be imported and the host must publish it somewhere.
|
|
50
|
+
neuralRuntimeUrl: '/neural/neural-runtime.js',
|
|
51
51
|
|
|
52
|
-
// Base for the
|
|
53
|
-
|
|
52
|
+
// Base for the neural weight manifests the runtime fetches (`generated/...` beneath this). No
|
|
53
|
+
// default: the weights are not redistributable, so a host supplies its own copy.
|
|
54
|
+
neuralAssetBaseUrl: null,
|
|
54
55
|
|
|
55
56
|
// Prefix used when the engine writes to client-side stores (IndexedDB, etc).
|
|
56
57
|
// Set to a unique value to avoid collisions when multiple apps embed the engine on the same origin.
|
|
@@ -688,6 +688,14 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
688
688
|
this._computeInputScale( device, width * height );
|
|
689
689
|
this._applyColorScale( device, width * height );
|
|
690
690
|
|
|
691
|
+
// A final denoise is shown tile by tile, so the tiles still to come must show this render
|
|
692
|
+
// rather than the blank or previous-view picture the output holds between runs.
|
|
693
|
+
if ( ! continuous && ! this._hasOutput ) {
|
|
694
|
+
|
|
695
|
+
this._unpackToTexture( this._gpuInputBuffers.color, width, { x: 0, y: 0, width, height }, false );
|
|
696
|
+
|
|
697
|
+
}
|
|
698
|
+
|
|
691
699
|
// Pass GPU storage buffers to oidn-web (GPUBuffer path, well-tested)
|
|
692
700
|
const config = {
|
|
693
701
|
color: { data: this._gpuInputBuffers.color, width, height },
|
|
@@ -996,11 +1004,12 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
996
1004
|
/**
|
|
997
1005
|
* Writes a rectangle of the denoised buffer into the output picture, on the card.
|
|
998
1006
|
*
|
|
999
|
-
* @param {GPUBuffer} src -
|
|
1007
|
+
* @param {GPUBuffer} src - rgba32float, full image, scaled by the input autoexposure
|
|
1000
1008
|
* @param {number} srcWidth
|
|
1001
1009
|
* @param {{x: number, y: number, width: number, height: number}} rect
|
|
1010
|
+
* @param {boolean} [denoised=true] - false for the raw render laid under a tiled run
|
|
1002
1011
|
*/
|
|
1003
|
-
_unpackToTexture( src, srcWidth, rect ) {
|
|
1012
|
+
_unpackToTexture( src, srcWidth, rect, denoised = true ) {
|
|
1004
1013
|
|
|
1005
1014
|
const device = this.gpuDevice;
|
|
1006
1015
|
if ( ! device || ! this._ensureScalePipelines( device ) ) return;
|
|
@@ -1047,7 +1056,7 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
1047
1056
|
// Set here rather than when the run resolves: 'end' is dispatched from execute()'s
|
|
1048
1057
|
// finally, which runs first, so a listener that waits for the run would see the very
|
|
1049
1058
|
// first denoise as having produced nothing.
|
|
1050
|
-
this._hasOutput = true;
|
|
1059
|
+
if ( denoised ) this._hasOutput = true;
|
|
1051
1060
|
|
|
1052
1061
|
} finally {
|
|
1053
1062
|
|
|
@@ -1060,8 +1069,7 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
1060
1069
|
|
|
1061
1070
|
/**
|
|
1062
1071
|
* Promise wrapper around tileExecute for the GPU path. Each tile is written straight into the
|
|
1063
|
-
* output picture on the card
|
|
1064
|
-
* being visible.
|
|
1072
|
+
* output picture on the card; 'tileProgress' is what puts it on screen.
|
|
1065
1073
|
*/
|
|
1066
1074
|
_executeWithAbortGPU( config, continuous = false ) {
|
|
1067
1075
|
|
package/src/PathTracerApp.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { WebGPURenderer, RectAreaLightNode, SRGBColorSpace, LinearSRGBColorSpace } from 'three/webgpu';
|
|
2
2
|
import { texture as _tslTexture, cubeTexture as _tslCubeTexture } from 'three/tsl';
|
|
3
3
|
import {
|
|
4
|
-
Scene, EventDispatcher, Box3
|
|
4
|
+
Scene, EventDispatcher, Box3, Vector3
|
|
5
5
|
} from 'three';
|
|
6
6
|
import { RectAreaLightTexturesLib } from 'three/addons/lights/RectAreaLightTexturesLib.js';
|
|
7
7
|
import { SceneHelpers } from './SceneHelpers.js';
|
|
@@ -195,6 +195,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
195
195
|
this.assetLoader = null;
|
|
196
196
|
this._sdf = null;
|
|
197
197
|
this._animRefitInFlight = false;
|
|
198
|
+
this._emittersMoved = false;
|
|
198
199
|
// Max material-texture dimension (longest edge); applied on each scene build.
|
|
199
200
|
this._maxTextureSize = DEFAULT_STATE.maxTextureSize;
|
|
200
201
|
|
|
@@ -1165,6 +1166,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1165
1166
|
// Stop any running animation before rebuilding scene data
|
|
1166
1167
|
this.animationManager.dispose();
|
|
1167
1168
|
this._animRefitInFlight = false;
|
|
1169
|
+
this._emittersMoved = false;
|
|
1168
1170
|
|
|
1169
1171
|
// Tag the primary (replace-loaded) model so it appears in the scene-object list.
|
|
1170
1172
|
this._tagPrimarySceneObject();
|
|
@@ -1654,6 +1656,95 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1654
1656
|
|
|
1655
1657
|
}
|
|
1656
1658
|
|
|
1659
|
+
/** Apply a pose from AnimationManager: placements, visibility, and a followed camera. @private */
|
|
1660
|
+
_applyAnimationPose( { meshIndices, visibilityChanged, cameras } ) {
|
|
1661
|
+
|
|
1662
|
+
if ( ! this._sdf?.instanceTable ) return;
|
|
1663
|
+
let changed = false;
|
|
1664
|
+
|
|
1665
|
+
if ( meshIndices.length > 0 ) {
|
|
1666
|
+
|
|
1667
|
+
this._sdf.updateMeshTransforms( meshIndices );
|
|
1668
|
+
this.stages.pathTracer?.updateBufferRanges( [], [ this._sdf.computeTLASDirtyRange() ] );
|
|
1669
|
+
if ( this._sdf.movesEmitters( meshIndices ) ) {
|
|
1670
|
+
|
|
1671
|
+
this._emittersMoved = true;
|
|
1672
|
+
// Deferred while playing: the rebuild uploads a scene-sized map, and sampling
|
|
1673
|
+
// already reads each emitter through its placement.
|
|
1674
|
+
if ( ! this.animationManager.isPlaying ) this._refreshMovedEmitters();
|
|
1675
|
+
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
changed = true;
|
|
1679
|
+
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
if ( visibilityChanged ) {
|
|
1683
|
+
|
|
1684
|
+
this.stages.pathTracer?.updateAllMeshVisibility();
|
|
1685
|
+
this._refreshEmissiveForVisibility();
|
|
1686
|
+
changed = true;
|
|
1687
|
+
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
if ( cameras.length > 0 && this._followAnimatedCamera( cameras ) ) changed = true;
|
|
1691
|
+
|
|
1692
|
+
if ( changed ) this.reset();
|
|
1693
|
+
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
/** @private */
|
|
1697
|
+
_refreshMovedEmitters() {
|
|
1698
|
+
|
|
1699
|
+
if ( ! this._emittersMoved ) return;
|
|
1700
|
+
this._emittersMoved = false;
|
|
1701
|
+
this._uploadEmissivePayload( this._sdf?.refreshEmissiveTransforms() ?? null );
|
|
1702
|
+
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
/**
|
|
1706
|
+
* Move the view with the selected camera's animated original — the camera list holds copies.
|
|
1707
|
+
* @returns {boolean} whether the view moved
|
|
1708
|
+
* @private
|
|
1709
|
+
*/
|
|
1710
|
+
_followAnimatedCamera( cameras ) {
|
|
1711
|
+
|
|
1712
|
+
const cm = this.cameraManager;
|
|
1713
|
+
const selected = cm.currentCameraIndex > 0 ? cm.cameras[ cm.currentCameraIndex ] : null;
|
|
1714
|
+
const uuid = selected?.userData?.__rayzeeSourceUuid;
|
|
1715
|
+
const source = uuid && cameras.find( c => c.uuid === uuid );
|
|
1716
|
+
if ( ! source ) return false;
|
|
1717
|
+
|
|
1718
|
+
source.updateWorldMatrix( true, false );
|
|
1719
|
+
const worldScale = new Vector3();
|
|
1720
|
+
source.matrixWorld.decompose( selected.position, selected.quaternion, worldScale );
|
|
1721
|
+
// Only the mirror survives.
|
|
1722
|
+
selected.scale.set( Math.sign( worldScale.x ) || 1, Math.sign( worldScale.y ) || 1, Math.sign( worldScale.z ) || 1 );
|
|
1723
|
+
if ( source.isPerspectiveCamera ) selected.fov = source.fov;
|
|
1724
|
+
|
|
1725
|
+
const camera = cm.camera;
|
|
1726
|
+
const controls = cm.controls;
|
|
1727
|
+
const distance = controls ? controls.target.distanceTo( camera.position ) : 0;
|
|
1728
|
+
|
|
1729
|
+
camera.position.copy( selected.position );
|
|
1730
|
+
camera.quaternion.copy( selected.quaternion );
|
|
1731
|
+
camera.scale.copy( selected.scale );
|
|
1732
|
+
if ( camera.isPerspectiveCamera && selected.isPerspectiveCamera ) camera.fov = selected.fov;
|
|
1733
|
+
camera.updateProjectionMatrix();
|
|
1734
|
+
camera.updateMatrixWorld( true );
|
|
1735
|
+
|
|
1736
|
+
// Pivot ahead at the same distance, so controls.update() keeps the pose.
|
|
1737
|
+
if ( controls ) {
|
|
1738
|
+
|
|
1739
|
+
const forward = new Vector3( 0, 0, - 1 ).applyQuaternion( camera.quaternion );
|
|
1740
|
+
controls.target.copy( camera.position ).addScaledVector( forward, distance || 1 );
|
|
1741
|
+
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
return true;
|
|
1745
|
+
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1657
1748
|
/**
|
|
1658
1749
|
* Apply new transforms for objects that moved, without touching their geometry.
|
|
1659
1750
|
*
|
|
@@ -2873,10 +2964,11 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2873
2964
|
const table = this._sdf?.instanceTable;
|
|
2874
2965
|
if ( ! table ) return;
|
|
2875
2966
|
|
|
2967
|
+
// The emitter list names meshes, the table placements.
|
|
2876
2968
|
const hidden = new Set();
|
|
2877
2969
|
for ( let i = 0; i < table.count; i ++ ) {
|
|
2878
2970
|
|
|
2879
|
-
if ( table.isSet[ i ] && ! table.visible[ i ] ) hidden.add( i );
|
|
2971
|
+
if ( table.isSet[ i ] && ! table.visible[ i ] ) hidden.add( table.sourceMesh[ i ] );
|
|
2880
2972
|
|
|
2881
2973
|
}
|
|
2882
2974
|
|
|
@@ -3410,6 +3502,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3410
3502
|
|
|
3411
3503
|
// Animation lifecycle → wake + refit flag
|
|
3412
3504
|
this.animationManager.wakeCallback = () => this.wake();
|
|
3505
|
+
this.animationManager.applyPoseCallback = ( pose ) => this._applyAnimationPose( pose );
|
|
3413
3506
|
this._forwardEvents( this.animationManager, [
|
|
3414
3507
|
EngineEvents.ANIMATION_STARTED,
|
|
3415
3508
|
EngineEvents.ANIMATION_PAUSED,
|
|
@@ -3418,6 +3511,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3418
3511
|
this._addTrackedListener( this.animationManager, EngineEvents.ANIMATION_PAUSED, () => {
|
|
3419
3512
|
|
|
3420
3513
|
this._animRefitInFlight = false;
|
|
3514
|
+
this._refreshMovedEmitters();
|
|
3421
3515
|
|
|
3422
3516
|
} );
|
|
3423
3517
|
this._addTrackedListener( this.animationManager, EngineEvents.ANIMATION_STOPPED, () => {
|
|
@@ -3538,6 +3632,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3538
3632
|
this.animationManager.onFinished = () => {
|
|
3539
3633
|
|
|
3540
3634
|
this._animRefitInFlight = false;
|
|
3635
|
+
this._refreshMovedEmitters();
|
|
3541
3636
|
this.dispatchEvent( { type: EngineEvents.ANIMATION_FINISHED } );
|
|
3542
3637
|
|
|
3543
3638
|
};
|
|
@@ -787,10 +787,11 @@ export class AssetLoader extends EventDispatcher {
|
|
|
787
787
|
};
|
|
788
788
|
|
|
789
789
|
const pbrtStart = performance.now();
|
|
790
|
-
const { group, environment, report, warnings, meshCount, entryPath: loadedEntry, candidates,
|
|
791
|
-
parseMs, buildMs, triangleCount, placementCount, mergedShapes, skippedForBudget,
|
|
790
|
+
const { group, environment, animations, report, warnings, meshCount, entryPath: loadedEntry, candidates,
|
|
791
|
+
frames, parseMs, buildMs, triangleCount, placementCount, mergedShapes, skippedForBudget,
|
|
792
792
|
droppedNoTemplate } = await loadPBRTScene( {
|
|
793
793
|
vfs: zip, source, entryPath, plyParser, imageFromBytes, envFromBytes,
|
|
794
|
+
animation: options.animation,
|
|
794
795
|
maxTriangles: options.maxTriangles,
|
|
795
796
|
maxPlacements: options.maxPlacements,
|
|
796
797
|
mergeShapesAbove: options.mergeShapesAbove,
|
|
@@ -802,15 +803,17 @@ export class AssetLoader extends EventDispatcher {
|
|
|
802
803
|
// SceneProcessor.performanceMetrics.
|
|
803
804
|
this.lastPBRTStats = {
|
|
804
805
|
parseMs, buildMs, loaderMs: performance.now() - pbrtStart,
|
|
805
|
-
triangleCount, placementCount, mergedShapes, skippedForBudget, droppedNoTemplate, meshCount
|
|
806
|
+
triangleCount, placementCount, mergedShapes, skippedForBudget, droppedNoTemplate, meshCount,
|
|
807
|
+
frames: frames?.length ?? 1
|
|
806
808
|
};
|
|
807
809
|
|
|
808
|
-
// An archive can hold several independent scenes (
|
|
809
|
-
//
|
|
810
|
-
//
|
|
811
|
-
|
|
810
|
+
// An archive can hold several independent scenes (bistro ships three views). Only one is
|
|
811
|
+
// loaded, so name it and the alternatives rather than leave the user comparing against a
|
|
812
|
+
// reference of a different scene. A frame sequence loads whole, as its animation.
|
|
813
|
+
const loaded = new Set( frames ?? [ loadedEntry ] );
|
|
814
|
+
const others = candidates.filter( p => ! loaded.has( p ) );
|
|
815
|
+
if ( others.length > 0 ) {
|
|
812
816
|
|
|
813
|
-
const others = candidates.filter( p => p !== loadedEntry );
|
|
814
817
|
console.warn( `PBRT archive holds ${candidates.length} scenes; loaded "${loadedEntry}". Others: ${others.join( ', ' )}` );
|
|
815
818
|
this._issues?.record(
|
|
816
819
|
ISSUE_CODES.ASSET_AMBIGUOUS_ENTRY,
|
|
@@ -849,6 +852,7 @@ export class AssetLoader extends EventDispatcher {
|
|
|
849
852
|
group.name = loadedEntry || filename;
|
|
850
853
|
this.releaseTargetModel();
|
|
851
854
|
this.targetModel = group;
|
|
855
|
+
this.animations = animations ?? [];
|
|
852
856
|
|
|
853
857
|
// The light's own orientation and `scale` are already baked into the texture, so the
|
|
854
858
|
// scene is only correct at rotation 0 / intensity 1 — pinned, whatever the host's defaults.
|
|
@@ -2032,6 +2036,8 @@ export class AssetLoader extends EventDispatcher {
|
|
|
2032
2036
|
|
|
2033
2037
|
// Clone the camera to avoid modifying the original
|
|
2034
2038
|
const camera = object.clone();
|
|
2039
|
+
// The clip animates the original; this lets the copy follow it.
|
|
2040
|
+
camera.userData.__rayzeeSourceUuid = object.uuid;
|
|
2035
2041
|
|
|
2036
2042
|
// Apply world transforms — cameras may be children of
|
|
2037
2043
|
// transformed nodes, so local position/quaternion != world.
|
|
@@ -72,6 +72,7 @@ export class EmissiveTriangleBuilder {
|
|
|
72
72
|
extractEmissiveTriangles( triangleData, materials, triangleCount, table = null ) {
|
|
73
73
|
|
|
74
74
|
this.emissiveTriangles = [];
|
|
75
|
+
this._emissiveMeshes = null;
|
|
75
76
|
this.totalEmissivePower = 0;
|
|
76
77
|
this._totalTriangleCount = triangleCount;
|
|
77
78
|
|
|
@@ -172,6 +173,7 @@ export class EmissiveTriangleBuilder {
|
|
|
172
173
|
triangleIndex: i,
|
|
173
174
|
materialIndex: materialIndex,
|
|
174
175
|
meshIndex: meshIndex,
|
|
176
|
+
sideSign,
|
|
175
177
|
instanceLeaf: hasInstance ? table.tlasLeafIndex[ placement ] : - 1,
|
|
176
178
|
power: power,
|
|
177
179
|
area: area,
|
|
@@ -203,6 +205,74 @@ export class EmissiveTriangleBuilder {
|
|
|
203
205
|
|
|
204
206
|
}
|
|
205
207
|
|
|
208
|
+
/** Mesh indices owning at least one emissive triangle. */
|
|
209
|
+
get emissiveMeshes() {
|
|
210
|
+
|
|
211
|
+
return this._emissiveMeshes ??= new Set( this.emissiveTriangles.map( t => t.meshIndex ) );
|
|
212
|
+
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Re-measure every emitter at its placement's current matrix. Call buildLightBVH() after.
|
|
217
|
+
* @param {Uint32Array|object} triangleData - packed triangle records, flat or chunked
|
|
218
|
+
* @param {import('./InstanceTable.js').InstanceTable} table
|
|
219
|
+
*/
|
|
220
|
+
refreshTransforms( triangleData, table ) {
|
|
221
|
+
|
|
222
|
+
const chunked = triangleData && triangleData.chunks ? triangleData : null;
|
|
223
|
+
const chunkedF = chunked ? chunked.viewAs( Float32Array ) : null;
|
|
224
|
+
const flatF = chunked ? null : new Float32Array( triangleData.buffer, triangleData.byteOffset, triangleData.length );
|
|
225
|
+
const FLOATS_PER_TRIANGLE = TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE;
|
|
226
|
+
const m = table.world;
|
|
227
|
+
|
|
228
|
+
this.totalEmissivePower = 0;
|
|
229
|
+
|
|
230
|
+
for ( const tri of this.emissiveTriangles ) {
|
|
231
|
+
|
|
232
|
+
const f = chunked ? chunkedF.chunkFor( tri.triangleIndex ) : flatF;
|
|
233
|
+
const b = chunked ? chunked.baseOf( tri.triangleIndex ) : tri.triangleIndex * FLOATS_PER_TRIANGLE;
|
|
234
|
+
const v = [ f[ b ], f[ b + 1 ], f[ b + 2 ], f[ b + 4 ], f[ b + 5 ], f[ b + 6 ], f[ b + 8 ], f[ b + 9 ], f[ b + 10 ] ];
|
|
235
|
+
|
|
236
|
+
// As extraction resolves it.
|
|
237
|
+
const placement = table.placementRunOf?.( tri.meshIndex )?.start ?? - 1;
|
|
238
|
+
if ( placement >= 0 && table.isSet[ placement ] ) {
|
|
239
|
+
|
|
240
|
+
const o = placement * 16;
|
|
241
|
+
for ( let k = 0; k < 9; k += 3 ) {
|
|
242
|
+
|
|
243
|
+
const x = v[ k ], y = v[ k + 1 ], z = v[ k + 2 ];
|
|
244
|
+
v[ k ] = m[ o ] * x + m[ o + 4 ] * y + m[ o + 8 ] * z + m[ o + 12 ];
|
|
245
|
+
v[ k + 1 ] = m[ o + 1 ] * x + m[ o + 5 ] * y + m[ o + 9 ] * z + m[ o + 13 ];
|
|
246
|
+
v[ k + 2 ] = m[ o + 2 ] * x + m[ o + 6 ] * y + m[ o + 10 ] * z + m[ o + 14 ];
|
|
247
|
+
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const [ v0x, v0y, v0z, v1x, v1y, v1z, v2x, v2y, v2z ] = v;
|
|
253
|
+
const area = this._calculateTriangleArea( v0x, v0y, v0z, v1x, v1y, v1z, v2x, v2y, v2z );
|
|
254
|
+
const e = tri.emissive;
|
|
255
|
+
tri.power = ( 0.2126 * e.r + 0.7152 * e.g + 0.0722 * e.b ) * tri.emissiveIntensity * area;
|
|
256
|
+
tri.area = area;
|
|
257
|
+
|
|
258
|
+
const nx = ( v1y - v0y ) * ( v2z - v0z ) - ( v1z - v0z ) * ( v2y - v0y );
|
|
259
|
+
const ny = ( v1z - v0z ) * ( v2x - v0x ) - ( v1x - v0x ) * ( v2z - v0z );
|
|
260
|
+
const nz = ( v1x - v0x ) * ( v2y - v0y ) - ( v1y - v0y ) * ( v2x - v0x );
|
|
261
|
+
const nl = ( Math.sqrt( nx * nx + ny * ny + nz * nz ) || 1 ) / tri.sideSign;
|
|
262
|
+
tri.nx = nx / nl; tri.ny = ny / nl; tri.nz = nz / nl;
|
|
263
|
+
|
|
264
|
+
tri.cx = ( v0x + v1x + v2x ) / 3; tri.cy = ( v0y + v1y + v2y ) / 3; tri.cz = ( v0z + v1z + v2z ) / 3;
|
|
265
|
+
tri.bMinX = Math.min( v0x, v1x, v2x ); tri.bMinY = Math.min( v0y, v1y, v2y ); tri.bMinZ = Math.min( v0z, v1z, v2z );
|
|
266
|
+
tri.bMaxX = Math.max( v0x, v1x, v2x ); tri.bMaxY = Math.max( v0y, v1y, v2y ); tri.bMaxZ = Math.max( v0z, v1z, v2z );
|
|
267
|
+
|
|
268
|
+
this.totalEmissivePower += tri.power;
|
|
269
|
+
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
this._buildDataArrays();
|
|
273
|
+
|
|
274
|
+
}
|
|
275
|
+
|
|
206
276
|
/**
|
|
207
277
|
* Calculate triangle area using cross product
|
|
208
278
|
*/
|