rayzee 6.3.0 → 6.4.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 -1
- package/dist/rayzee.es.js +3273 -2008
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +50 -48
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/Processor/AssetLoader.js +151 -1
- package/src/Processor/PBRT/PBRTMaterials.js +350 -0
- package/src/Processor/PBRT/PBRTMath.js +173 -0
- package/src/Processor/PBRT/PBRTParser.js +642 -0
- package/src/Processor/PBRT/PBRTSceneBuilder.js +578 -0
- package/src/Processor/PBRT/PBRTTokenizer.js +141 -0
- package/src/Processor/PBRT/index.js +179 -0
- package/src/TSL/PathTracerCore.js +12 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PBRT-v4 scene loader.
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates: virtual filesystem (from a zip) → tokenize/parse the entry
|
|
5
|
+
* .pbrt (following Include/Import) → build a THREE scene graph. Geometry,
|
|
6
|
+
* image, and HDR decoding are injected by the host (AssetLoader owns the
|
|
7
|
+
* three/examples loaders) so this module stays dependency-light.
|
|
8
|
+
*
|
|
9
|
+
* Usage (from AssetLoader):
|
|
10
|
+
* const { group, environment, warnings } = await loadPBRTScene({
|
|
11
|
+
* vfs, entryPath, plyParser, imageFromBytes, envFromBytes
|
|
12
|
+
* });
|
|
13
|
+
* scene.environment = environment?.texture ?? scene.environment;
|
|
14
|
+
* await loadObject3D(group);
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { PBRTParser } from './PBRTParser.js';
|
|
18
|
+
import { PBRTSceneBuilder } from './PBRTSceneBuilder.js';
|
|
19
|
+
|
|
20
|
+
export { PBRTParser } from './PBRTParser.js';
|
|
21
|
+
export { PBRTSceneBuilder } from './PBRTSceneBuilder.js';
|
|
22
|
+
export { tokenize } from './PBRTTokenizer.js';
|
|
23
|
+
|
|
24
|
+
const decoder = new TextDecoder();
|
|
25
|
+
|
|
26
|
+
/** Normalize a path: forward slashes, collapse "./" and "../". */
|
|
27
|
+
function normalizePath( p ) {
|
|
28
|
+
|
|
29
|
+
const parts = p.replace( /\\/g, '/' ).split( '/' );
|
|
30
|
+
const out = [];
|
|
31
|
+
for ( const part of parts ) {
|
|
32
|
+
|
|
33
|
+
if ( part === '' || part === '.' ) continue;
|
|
34
|
+
if ( part === '..' ) out.pop();
|
|
35
|
+
else out.push( part );
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return out.join( '/' );
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Join a base directory with a relative path. */
|
|
44
|
+
function joinPath( dir, rel ) {
|
|
45
|
+
|
|
46
|
+
if ( ! dir ) return normalizePath( rel );
|
|
47
|
+
if ( rel.startsWith( '/' ) ) return normalizePath( rel );
|
|
48
|
+
return normalizePath( `${dir}/${rel}` );
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Wraps the zip contents with tolerant, case-insensitive lookup that falls back
|
|
54
|
+
* to a basename match — pbrt scenes are inconsistent about path roots.
|
|
55
|
+
*/
|
|
56
|
+
class VirtualFS {
|
|
57
|
+
|
|
58
|
+
constructor( entries ) {
|
|
59
|
+
|
|
60
|
+
// entries: { path: Uint8Array }
|
|
61
|
+
this.byPath = new Map(); // normalized lowercase -> { norm, bytes }
|
|
62
|
+
this.byBase = new Map(); // basename lowercase -> [ { norm, bytes } ] (insertion order)
|
|
63
|
+
for ( const key in entries ) {
|
|
64
|
+
|
|
65
|
+
const norm = normalizePath( key ).toLowerCase();
|
|
66
|
+
const rec = { norm, bytes: entries[ key ] };
|
|
67
|
+
this.byPath.set( norm, rec );
|
|
68
|
+
const base = norm.split( '/' ).pop();
|
|
69
|
+
const bucket = this.byBase.get( base );
|
|
70
|
+
if ( bucket ) bucket.push( rec );
|
|
71
|
+
else this.byBase.set( base, [ rec ] );
|
|
72
|
+
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
find( path ) {
|
|
78
|
+
|
|
79
|
+
const norm = normalizePath( path ).toLowerCase();
|
|
80
|
+
if ( this.byPath.has( norm ) ) return this.byPath.get( norm ).bytes;
|
|
81
|
+
|
|
82
|
+
// Resolve by basename (O(1)); among collisions prefer a path-suffix match,
|
|
83
|
+
// else fall back to the first entry with that name. pbrt scenes are
|
|
84
|
+
// inconsistent about path roots, so this tolerates relative/absolute drift.
|
|
85
|
+
const bucket = this.byBase.get( norm.split( '/' ).pop() );
|
|
86
|
+
if ( ! bucket ) return null;
|
|
87
|
+
const suffixHit = bucket.find( rec => rec.norm.endsWith( '/' + norm ) );
|
|
88
|
+
return ( suffixHit || bucket[ 0 ] ).bytes;
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Pick the top-level .pbrt entry: shallowest path, preferring scene/main names. */
|
|
95
|
+
export function pickEntryPath( entries ) {
|
|
96
|
+
|
|
97
|
+
const pbrts = Object.keys( entries ).filter( k => k.toLowerCase().endsWith( '.pbrt' ) );
|
|
98
|
+
if ( pbrts.length === 0 ) return null;
|
|
99
|
+
|
|
100
|
+
const preferred = pbrts.filter( k => /(^|\/)(scene|main)\.pbrt$/i.test( k ) );
|
|
101
|
+
const pool = preferred.length ? preferred : pbrts;
|
|
102
|
+
|
|
103
|
+
// Shallowest (fewest path segments), then shortest name.
|
|
104
|
+
pool.sort( ( a, b ) => {
|
|
105
|
+
|
|
106
|
+
const da = a.split( '/' ).length, db = b.split( '/' ).length;
|
|
107
|
+
return da !== db ? da - db : a.length - b.length;
|
|
108
|
+
|
|
109
|
+
} );
|
|
110
|
+
|
|
111
|
+
return pool[ 0 ];
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {object} args
|
|
117
|
+
* @param {Object<string,Uint8Array>} args.vfs - zip entries (path → bytes)
|
|
118
|
+
* @param {string} [args.entryPath] - top .pbrt; auto-detected if omitted
|
|
119
|
+
* @param {(buf:ArrayBuffer)=>import('three').BufferGeometry} args.plyParser
|
|
120
|
+
* @param {(bytes:Uint8Array, filename:string)=>Promise<import('three').Texture>} args.imageFromBytes
|
|
121
|
+
* @param {(bytes:Uint8Array, filename:string)=>Promise<import('three').Texture>} [args.envFromBytes]
|
|
122
|
+
* @param {boolean} [args.convertHandedness=true]
|
|
123
|
+
* @returns {Promise<{group, camera, environment, warnings, entryPath}>}
|
|
124
|
+
*/
|
|
125
|
+
export async function loadPBRTScene( args ) {
|
|
126
|
+
|
|
127
|
+
const { vfs: rawEntries, plyParser, imageFromBytes, envFromBytes, convertHandedness } = args;
|
|
128
|
+
const vfs = new VirtualFS( rawEntries );
|
|
129
|
+
|
|
130
|
+
const entryPath = args.entryPath || pickEntryPath( rawEntries );
|
|
131
|
+
if ( ! entryPath ) throw new Error( 'PBRT loader: no .pbrt file found in archive' );
|
|
132
|
+
|
|
133
|
+
const entryBytes = vfs.find( entryPath );
|
|
134
|
+
if ( ! entryBytes ) throw new Error( `PBRT loader: entry "${entryPath}" not readable` );
|
|
135
|
+
|
|
136
|
+
const baseDir = entryPath.includes( '/' ) ? entryPath.slice( 0, entryPath.lastIndexOf( '/' ) ) : '';
|
|
137
|
+
|
|
138
|
+
// Parse (with Include resolution)
|
|
139
|
+
const parser = new PBRTParser( {
|
|
140
|
+
resolveInclude: ( path, currentDir ) => {
|
|
141
|
+
|
|
142
|
+
const bytes = vfs.find( joinPath( currentDir, path ) ) || vfs.find( path );
|
|
143
|
+
return bytes ? decoder.decode( bytes ) : null;
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
} );
|
|
147
|
+
const ir = parser.parse( decoder.decode( entryBytes ), baseDir );
|
|
148
|
+
|
|
149
|
+
// Build scene graph
|
|
150
|
+
const sliceBuf = ( bytes ) => bytes.buffer.slice( bytes.byteOffset, bytes.byteOffset + bytes.byteLength );
|
|
151
|
+
const builder = new PBRTSceneBuilder( {
|
|
152
|
+
convertHandedness,
|
|
153
|
+
resolvePLY: async ( filename ) => {
|
|
154
|
+
|
|
155
|
+
const bytes = vfs.find( filename );
|
|
156
|
+
if ( ! bytes ) return null;
|
|
157
|
+
return plyParser( sliceBuf( bytes ) );
|
|
158
|
+
|
|
159
|
+
},
|
|
160
|
+
resolveImage: async ( filename ) => {
|
|
161
|
+
|
|
162
|
+
const bytes = vfs.find( filename );
|
|
163
|
+
if ( ! bytes ) return null;
|
|
164
|
+
return imageFromBytes( bytes, filename );
|
|
165
|
+
|
|
166
|
+
},
|
|
167
|
+
resolveEnvironment: async ( filename ) => {
|
|
168
|
+
|
|
169
|
+
const bytes = vfs.find( filename );
|
|
170
|
+
if ( ! bytes ) return null;
|
|
171
|
+
return ( envFromBytes || imageFromBytes )( bytes, filename );
|
|
172
|
+
|
|
173
|
+
}
|
|
174
|
+
} );
|
|
175
|
+
|
|
176
|
+
const result = await builder.build( ir );
|
|
177
|
+
return { ...result, entryPath };
|
|
178
|
+
|
|
179
|
+
}
|
|
@@ -918,6 +918,18 @@ export const Trace = Fn( ( [
|
|
|
918
918
|
const randomSample = getRandomSample( pixelCoord, rayIndex, bounceIndex, rngState, int( - 1 ), resolution, frame ).toVar();
|
|
919
919
|
|
|
920
920
|
const V = rayDirection.negate().toVar();
|
|
921
|
+
|
|
922
|
+
// Two-sided shading: flip the shading normal into the viewer's hemisphere.
|
|
923
|
+
// This is the opaque reflection path (transmissive rays already Continue'd),
|
|
924
|
+
// so it never disturbs dielectric entering/exiting. No-op when N already
|
|
925
|
+
// faces V; rescues meshes with inward-facing normals (common in imported
|
|
926
|
+
// scenes, e.g. pbrt PLY assets) that would otherwise shade black.
|
|
927
|
+
If( dot( N, V ).lessThan( 0.0 ), () => {
|
|
928
|
+
|
|
929
|
+
N.assign( N.negate() );
|
|
930
|
+
|
|
931
|
+
} );
|
|
932
|
+
|
|
921
933
|
material.sheenRoughness.assign( clamp( material.sheenRoughness, MIN_ROUGHNESS, MAX_ROUGHNESS ) );
|
|
922
934
|
|
|
923
935
|
// Sync material classification cache up front — the materialCache, BRDF
|