rayzee 6.0.1 → 6.2.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 +5 -0
- package/dist/assets/TexturesWorker-DBqGmVdR.js.map +1 -1
- package/dist/rayzee.es.js +2421 -2078
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +55 -52
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +2 -2
- package/src/EngineDefaults.js +3 -0
- package/src/PathTracerApp.js +18 -8
- package/src/Pipeline/RenderStage.js +3 -0
- package/src/Processor/IESParser.js +340 -0
- package/src/Processor/LightSerializer.js +32 -4
- package/src/Processor/SceneProcessor.js +0 -1
- package/src/Processor/ShaderBuilder.js +40 -1
- package/src/Processor/Workers/TexturesWorker.js +1 -1
- package/src/RenderSettings.js +3 -0
- package/src/Stages/NormalDepth.js +3 -19
- package/src/Stages/PathTracer.js +15 -9
- package/src/TSL/BVHTraversal.js +4 -6
- package/src/TSL/Common.js +1 -1
- package/src/TSL/Debugger.js +0 -2
- package/src/TSL/EmissiveSampling.js +20 -22
- package/src/TSL/Environment.js +60 -14
- package/src/TSL/Fresnel.js +13 -4
- package/src/TSL/LightsCore.js +238 -5
- package/src/TSL/LightsDirect.js +16 -5
- package/src/TSL/LightsIndirect.js +4 -37
- package/src/TSL/LightsSampling.js +119 -185
- package/src/TSL/MaterialEvaluation.js +25 -14
- package/src/TSL/MaterialProperties.js +14 -34
- package/src/TSL/MaterialTransmission.js +18 -37
- package/src/TSL/PathTracer.js +25 -7
- package/src/TSL/PathTracerCore.js +144 -139
- package/src/TSL/Struct.js +7 -1
- package/src/TSL/TextureSampling.js +2 -2
- package/src/index.js +2 -0
- package/src/managers/AnimationManager.js +3 -6
- package/src/managers/DenoisingManager.js +1 -1
- package/src/managers/GoboManager.js +277 -0
- package/src/managers/IESManager.js +268 -0
- package/src/managers/LightManager.js +33 -1
- package/src/managers/TransformManager.js +3 -3
- package/src/managers/UniformManager.js +5 -5
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { Fn, float, vec3,
|
|
1
|
+
import { Fn, float, vec3, int, If, dot, max, min, sqrt, cos, exp, mix, clamp, smoothstep } from 'three/tsl';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
BRDFWeights,
|
|
5
|
-
MaterialClassification,
|
|
6
5
|
MaterialCache,
|
|
7
6
|
ImportanceSamplingInfo,
|
|
7
|
+
DFGResult,
|
|
8
8
|
|
|
9
9
|
} from './Struct.js';
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
|
-
PI, TWO_PI, EPSILON, MIN_ROUGHNESS,
|
|
13
|
-
XYZ_TO_REC709, square,
|
|
12
|
+
PI, TWO_PI, EPSILON, MIN_ROUGHNESS,
|
|
13
|
+
XYZ_TO_REC709, square,
|
|
14
14
|
} from './Common.js';
|
|
15
15
|
|
|
16
16
|
import {
|
|
@@ -68,10 +68,14 @@ export const GeometrySmith = Fn( ( [ NoV, NoL, roughness ] ) => {
|
|
|
68
68
|
// multiplicative factor for the specular BRDF that compensates for this loss.
|
|
69
69
|
// Based on: Kulla & Conty 2017 + Karis 2014 analytical DFG approximation.
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
// Single Karis DFG evaluation that returns both outputs the BRDF needs:
|
|
72
|
+
// compensation — multiscatter energy compensation factor for the specular lobe
|
|
73
|
+
// E_total — total specular directional albedo (single-scatter × compensation)
|
|
74
|
+
// Both share the same dfgScale/dfgBias/Ew polynomial, so computing them together
|
|
75
|
+
// halves the polynomial work versus calling two separate functions.
|
|
76
|
+
export const evaluateDFG = Fn( ( [ F0, NoV, roughness ] ) => {
|
|
72
77
|
|
|
73
78
|
// Analytical DFG approximation (Karis 2014)
|
|
74
|
-
// Computes scale and bias where E(F0) = F0 * scale + bias
|
|
75
79
|
const r0 = float( 1.0 ).sub( roughness );
|
|
76
80
|
const r1 = roughness.mul( - 0.0275 ).add( 0.0425 );
|
|
77
81
|
const r2 = roughness.mul( - 0.572 ).add( 1.04 );
|
|
@@ -87,35 +91,13 @@ export const multiscatterCompensation = Fn( ( [ F0, NoV, roughness ] ) => {
|
|
|
87
91
|
const Ew = max( dfgScale.add( dfgBias ), 0.1 );
|
|
88
92
|
|
|
89
93
|
// Energy compensation: 1 + F0 * (1/Ew - 1)
|
|
90
|
-
|
|
91
|
-
// At F0=0.04 (dielectrics): negligible correction
|
|
92
|
-
return vec3( 1.0 ).add( F0.mul( float( 1.0 ).div( Ew ).sub( 1.0 ) ) );
|
|
93
|
-
|
|
94
|
-
} );
|
|
95
|
-
|
|
96
|
-
// Compute the total specular directional albedo including multiscatter compensation.
|
|
97
|
-
// Returns per-channel fraction of energy captured by specular reflection,
|
|
98
|
-
// used for energy-conserving diffuse weight: kD = (1 - E_total) * (1 - metalness).
|
|
99
|
-
export const specularDirectionalAlbedo = Fn( ( [ F0, NoV, roughness ] ) => {
|
|
100
|
-
|
|
101
|
-
// Analytical DFG approximation (same as multiscatterCompensation)
|
|
102
|
-
const r0 = float( 1.0 ).sub( roughness );
|
|
103
|
-
const r1 = roughness.mul( - 0.0275 ).add( 0.0425 );
|
|
104
|
-
const r2 = roughness.mul( - 0.572 ).add( 1.04 );
|
|
105
|
-
const r3 = roughness.mul( 0.022 ).sub( 0.04 );
|
|
106
|
-
const a004 = min( r0.mul( r0 ), exp( float( - 6.4308 ).mul( NoV ) ) ).mul( r0 ).add( r1 );
|
|
107
|
-
const dfgScale = float( - 1.04 ).mul( a004 ).add( r2 );
|
|
108
|
-
const dfgBias = float( 1.04 ).mul( a004 ).add( r3 );
|
|
94
|
+
const compensation = vec3( 1.0 ).add( F0.mul( float( 1.0 ).div( Ew ).sub( 1.0 ) ) );
|
|
109
95
|
|
|
110
|
-
// Single-scatter directional albedo per channel
|
|
96
|
+
// Single-scatter directional albedo per channel, then total with compensation
|
|
111
97
|
const E_ss = max( F0.mul( dfgScale ).add( vec3( dfgBias ) ), vec3( 0.0 ) );
|
|
98
|
+
const E_total = clamp( E_ss.mul( compensation ), vec3( 0.0 ), vec3( 1.0 ) );
|
|
112
99
|
|
|
113
|
-
|
|
114
|
-
const Ew = max( dfgScale.add( dfgBias ), 0.1 );
|
|
115
|
-
|
|
116
|
-
// Apply multiscatter compensation to get total specular albedo
|
|
117
|
-
const compensation = vec3( 1.0 ).add( F0.mul( float( 1.0 ).div( Ew ).sub( 1.0 ) ) );
|
|
118
|
-
return clamp( E_ss.mul( compensation ), vec3( 0.0 ), vec3( 1.0 ) );
|
|
100
|
+
return DFGResult( { compensation, E_total } );
|
|
119
101
|
|
|
120
102
|
} );
|
|
121
103
|
|
|
@@ -329,7 +311,6 @@ export const calculateBRDFWeights = Fn( ( [ material, mc, cache ] ) => {
|
|
|
329
311
|
|
|
330
312
|
export const getImportanceSamplingInfo = Fn( ( [
|
|
331
313
|
material, bounceIndex, mc,
|
|
332
|
-
environmentIntensity, useEnvMapIS, enableEnvironmentLight
|
|
333
314
|
] ) => {
|
|
334
315
|
|
|
335
316
|
// Base BRDF weights using temporary cache
|
|
@@ -418,7 +399,6 @@ export const getImportanceSamplingInfo = Fn( ( [
|
|
|
418
399
|
specularImportance,
|
|
419
400
|
transmissionImportance,
|
|
420
401
|
clearcoatImportance,
|
|
421
|
-
envmapImportance: float( 0.0 ),
|
|
422
402
|
} );
|
|
423
403
|
|
|
424
404
|
} );
|
|
@@ -6,30 +6,25 @@ import {
|
|
|
6
6
|
wgslFn,
|
|
7
7
|
vec2,
|
|
8
8
|
vec3,
|
|
9
|
-
vec4,
|
|
10
9
|
float,
|
|
11
10
|
int,
|
|
12
11
|
bool as tslBool,
|
|
13
|
-
uint,
|
|
14
12
|
If,
|
|
15
|
-
Loop,
|
|
16
13
|
select,
|
|
17
14
|
abs,
|
|
18
15
|
dot,
|
|
19
16
|
reflect,
|
|
20
17
|
refract,
|
|
21
18
|
max,
|
|
22
|
-
min,
|
|
23
19
|
mix,
|
|
24
20
|
clamp,
|
|
25
21
|
exp,
|
|
26
22
|
} from 'three/tsl';
|
|
27
23
|
|
|
28
24
|
import { struct } from './patches.js';
|
|
29
|
-
import {
|
|
30
|
-
import { PI, EPSILON, MIN_ROUGHNESS, MIN_CLEARCOAT_ROUGHNESS, computeDotProducts } from './Common.js';
|
|
25
|
+
import { EPSILON, MIN_ROUGHNESS, MIN_PDF } from './Common.js';
|
|
31
26
|
import { iorToFresnel0, fresnelSchlickFloat } from './Fresnel.js';
|
|
32
|
-
import { DistributionGGX
|
|
27
|
+
import { DistributionGGX } from './MaterialProperties.js';
|
|
33
28
|
import { ImportanceSampleGGX } from './MaterialSampling.js';
|
|
34
29
|
import { RandomValue, pcgHash } from './Random.js';
|
|
35
30
|
|
|
@@ -78,9 +73,6 @@ export const MicrofacetTransmissionResult = struct( {
|
|
|
78
73
|
pathWavelength: 'float', // 0 if path is not yet spectral, else locked wavelength in nm
|
|
79
74
|
} );
|
|
80
75
|
|
|
81
|
-
// Maximum number of nested media
|
|
82
|
-
const MAX_MEDIA_STACK = 4;
|
|
83
|
-
|
|
84
76
|
// MediumStack as a struct with fixed-size slots
|
|
85
77
|
export const MediumStack = struct( {
|
|
86
78
|
m0_ior: 'float',
|
|
@@ -305,8 +297,14 @@ export const sampleMicrofacetTransmission = Fn( ( [
|
|
|
305
297
|
|
|
306
298
|
} );
|
|
307
299
|
|
|
308
|
-
// Compute refracted direction using the sampled half-vector
|
|
309
|
-
|
|
300
|
+
// Compute refracted direction using the sampled half-vector. HoV and NoH
|
|
301
|
+
// are needed by both the TIR and the transmission PDF branches below, so
|
|
302
|
+
// hoist them once here (VoH == HoV — same dot product). DistributionGGX D
|
|
303
|
+
// is also identical between the two branches (calculateGGXPDF builds the
|
|
304
|
+
// same D internally for the TIR branch), so share it too.
|
|
305
|
+
const HoV = clamp( dot( H, V ), 0.001, 1.0 ).toVar();
|
|
306
|
+
const NoH = clamp( dot( N, H ), 0.001, 1.0 ).toVar();
|
|
307
|
+
const D = DistributionGGX( NoH, transmissionRoughness ).toVar();
|
|
310
308
|
const refractDir = refract( V.negate(), H, etaRatio ).toVar();
|
|
311
309
|
|
|
312
310
|
// Check for total internal reflection
|
|
@@ -316,10 +314,8 @@ export const sampleMicrofacetTransmission = Fn( ( [
|
|
|
316
314
|
result.direction.assign( reflect( V.negate(), H ) );
|
|
317
315
|
result.didReflect.assign( true );
|
|
318
316
|
|
|
319
|
-
//
|
|
320
|
-
|
|
321
|
-
const VoH = clamp( dot( V, H ), 0.001, 1.0 );
|
|
322
|
-
result.pdf.assign( calculateGGXPDF( NoH, VoH, transmissionRoughness ) );
|
|
317
|
+
// Reflection PDF: D(H) * NoH / (4 * VoH) — reuses the hoisted D + dots.
|
|
318
|
+
result.pdf.assign( D.mul( NoH ).div( max( float( 4.0 ).mul( HoV ), MIN_PDF ) ) );
|
|
323
319
|
|
|
324
320
|
} ).Else( () => {
|
|
325
321
|
|
|
@@ -327,10 +323,7 @@ export const sampleMicrofacetTransmission = Fn( ( [
|
|
|
327
323
|
result.direction.assign( refractDir );
|
|
328
324
|
result.didReflect.assign( false );
|
|
329
325
|
|
|
330
|
-
// Calculate proper PDF for microfacet transmission
|
|
331
|
-
const NoH = clamp( dot( N, H ), 0.001, 1.0 );
|
|
332
326
|
const HoL = clamp( dot( H, refractDir ), 0.001, 1.0 );
|
|
333
|
-
const D = DistributionGGX( NoH, transmissionRoughness );
|
|
334
327
|
|
|
335
328
|
// Account for change of measure due to refraction (Jacobian)
|
|
336
329
|
const sqrtDenom = HoV.add( etaRatio.mul( HoL ) );
|
|
@@ -472,12 +465,9 @@ export const handleTransmission = Fn( ( [
|
|
|
472
465
|
// due to solid angle compression/expansion (cancels for round-trip enter+exit paths)
|
|
473
466
|
result.throughput.mulAssign( n1.mul( n1 ).div( max( n2.mul( n2 ), EPSILON ) ) );
|
|
474
467
|
|
|
475
|
-
//
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
result.throughput.mulAssign( calculateBeerLawAbsorption( { attenuationColor: material.attenuationColor, attenuationDistance: material.attenuationDistance, thickness: material.thickness } ) );
|
|
479
|
-
|
|
480
|
-
} );
|
|
468
|
+
// KHR_materials_volume absorption is applied per-bounce in PathTracerCore based
|
|
469
|
+
// on the actual ray path length inside the medium (driven by the medium stack).
|
|
470
|
+
// No entry-point thickness approximation here — that would double-count.
|
|
481
471
|
|
|
482
472
|
// Fresnel transmission factor with PDF compensation
|
|
483
473
|
result.throughput.mulAssign( float( 1.0 ).sub( Fr ).div( max( float( 1.0 ).sub( reflectProb ), 0.05 ) ) );
|
|
@@ -495,7 +485,7 @@ export const handleTransmission = Fn( ( [
|
|
|
495
485
|
// ================================================================================
|
|
496
486
|
|
|
497
487
|
export const handleMaterialTransparency = Fn( ( [
|
|
498
|
-
ray,
|
|
488
|
+
ray, normal, material, rngState,
|
|
499
489
|
transmissiveTraversals,
|
|
500
490
|
currentMediumIOR, previousMediumIOR,
|
|
501
491
|
pathWavelength,
|
|
@@ -513,19 +503,13 @@ export const handleMaterialTransparency = Fn( ( [
|
|
|
513
503
|
pathWavelength: pathWavelength,
|
|
514
504
|
} ).toVar();
|
|
515
505
|
|
|
516
|
-
//
|
|
517
|
-
// Step 1: Fast path for completely opaque materials
|
|
518
|
-
// -----------------------------------------------------------------
|
|
519
|
-
// Quick early exit for fully opaque materials (most common case)
|
|
506
|
+
// Fast path for fully opaque materials (most common case)
|
|
520
507
|
If( material.alphaMode.equal( int( 0 ) ).and( material.transmission.lessThanEqual( 0.0 ) ), () => {
|
|
521
508
|
|
|
522
|
-
//
|
|
509
|
+
// no interaction needed
|
|
523
510
|
|
|
524
511
|
} ).Else( () => {
|
|
525
512
|
|
|
526
|
-
// -----------------------------------------------------------------
|
|
527
|
-
// Step 2: Handle alpha modes according to glTF spec
|
|
528
|
-
// -----------------------------------------------------------------
|
|
529
513
|
const alphaRand = RandomValue( rngState );
|
|
530
514
|
const transmissionRand = RandomValue( rngState );
|
|
531
515
|
const transmissionSeed = pcgHash( { state: rngState } );
|
|
@@ -577,9 +561,6 @@ export const handleMaterialTransparency = Fn( ( [
|
|
|
577
561
|
|
|
578
562
|
} );
|
|
579
563
|
|
|
580
|
-
// -----------------------------------------------------------------
|
|
581
|
-
// Step 3: Handle transmission if present
|
|
582
|
-
// -----------------------------------------------------------------
|
|
583
564
|
If( handled.not().and( material.transmission.greaterThan( 0.0 ) ).and( transmissiveTraversals.greaterThan( int( 0 ) ) ), () => {
|
|
584
565
|
|
|
585
566
|
// Only apply transmission with probability equal to the transmission value
|
package/src/TSL/PathTracer.js
CHANGED
|
@@ -74,6 +74,16 @@ export const computeNDCDepth = /*@__PURE__*/ wgslFn( `
|
|
|
74
74
|
}
|
|
75
75
|
` );
|
|
76
76
|
|
|
77
|
+
// NaN/Inf detector for debug mode 11. x != x catches NaN; the abs threshold catches Inf.
|
|
78
|
+
const nanInfToRed = /*@__PURE__*/ wgslFn( `
|
|
79
|
+
fn nanInfToRed( c: vec3f ) -> vec3f {
|
|
80
|
+
let isNan = c.x != c.x || c.y != c.y || c.z != c.z;
|
|
81
|
+
let isInf = abs( c.x ) > 1e30f || abs( c.y ) > 1e30f || abs( c.z ) > 1e30f;
|
|
82
|
+
if ( isNan || isInf ) { return vec3f( 1.0f, 0.0f, 0.0f ); }
|
|
83
|
+
return vec3f( 0.0f );
|
|
84
|
+
}
|
|
85
|
+
` );
|
|
86
|
+
|
|
77
87
|
// Get required samples from adaptive sampling texture
|
|
78
88
|
export const getRequiredSamples = Fn( ( [
|
|
79
89
|
pixelCoord, resolution,
|
|
@@ -140,10 +150,11 @@ export const pathTracerMain = ( params ) => {
|
|
|
140
150
|
envCDFBuffer,
|
|
141
151
|
envTotalSum, envCompensationDelta, envResolution,
|
|
142
152
|
enableEnvironmentLight, useEnvMapIS,
|
|
153
|
+
groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight,
|
|
143
154
|
maxBounceCount, transmissiveBounces,
|
|
144
155
|
showBackground, transparentBackground, backgroundIntensity,
|
|
145
156
|
fireflyThreshold, globalIlluminationIntensity,
|
|
146
|
-
|
|
157
|
+
enableEmissiveTriangleSampling,
|
|
147
158
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
|
|
148
159
|
lightBVHBuffer, lightBVHNodeCount,
|
|
149
160
|
debugVisScale,
|
|
@@ -170,7 +181,6 @@ export const pathTracerMain = ( params ) => {
|
|
|
170
181
|
const pixelSamples = int( 0 ).toVar();
|
|
171
182
|
|
|
172
183
|
const baseSeed = getDecorrelatedSeed( { pixelCoord, rayIndex: int( 0 ), frame } ).toVar();
|
|
173
|
-
const pixelIndex = int( pixelCoord.y ).mul( int( resolution.x ) ).add( int( pixelCoord.x ) ).toVar();
|
|
174
184
|
|
|
175
185
|
// MRT data
|
|
176
186
|
const worldNormal = vec3( 0.0, 0.0, 1.0 ).toVar();
|
|
@@ -251,8 +261,8 @@ export const pathTracerMain = ( params ) => {
|
|
|
251
261
|
|
|
252
262
|
const sampleColor = vec4( 0.0 ).toVar();
|
|
253
263
|
|
|
254
|
-
// Debug or normal trace
|
|
255
|
-
If( visMode.greaterThan( int( 0 ) ), () => {
|
|
264
|
+
// Debug or normal trace (mode 11 runs the full path tracer so we can sniff NaN at the end)
|
|
265
|
+
If( visMode.greaterThan( int( 0 ) ).and( visMode.notEqual( int( 11 ) ) ), () => {
|
|
256
266
|
|
|
257
267
|
sampleColor.assign( TraceDebugMode(
|
|
258
268
|
ray.origin, ray.direction,
|
|
@@ -272,7 +282,7 @@ export const pathTracerMain = ( params ) => {
|
|
|
272
282
|
|
|
273
283
|
// Normal path tracing
|
|
274
284
|
const traceResult = TraceResult.wrap( Trace(
|
|
275
|
-
ray, seed, rayIndex,
|
|
285
|
+
ray, seed, rayIndex,
|
|
276
286
|
bvhBuffer,
|
|
277
287
|
triangleBuffer,
|
|
278
288
|
materialBuffer,
|
|
@@ -287,10 +297,11 @@ export const pathTracerMain = ( params ) => {
|
|
|
287
297
|
envCDFBuffer,
|
|
288
298
|
envTotalSum, envCompensationDelta, envResolution,
|
|
289
299
|
enableEnvironmentLight, useEnvMapIS,
|
|
300
|
+
groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight,
|
|
290
301
|
maxBounceCount, transmissiveBounces,
|
|
291
302
|
backgroundIntensity, showBackground, transparentBackground,
|
|
292
303
|
fireflyThreshold, globalIlluminationIntensity,
|
|
293
|
-
|
|
304
|
+
enableEmissiveTriangleSampling,
|
|
294
305
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
|
|
295
306
|
lightBVHBuffer, lightBVHNodeCount,
|
|
296
307
|
pixelCoord, resolution, frame,
|
|
@@ -343,7 +354,7 @@ export const pathTracerMain = ( params ) => {
|
|
|
343
354
|
// Output alpha: accumulated per-sample alpha when transparent, otherwise 1.0
|
|
344
355
|
const outputAlpha = select( transparentBackground, pixelAlpha, float( 1.0 ) ).toVar();
|
|
345
356
|
|
|
346
|
-
If( enableAccumulation.and( cameraIsMoving.not() ).and( frame.greaterThan( uint( 0 ) ) ).and( hasPreviousAccumulated ), () => {
|
|
357
|
+
If( enableAccumulation.and( cameraIsMoving.not() ).and( frame.greaterThan( uint( 0 ) ) ).and( hasPreviousAccumulated ).and( visMode.notEqual( int( 11 ) ) ), () => {
|
|
347
358
|
|
|
348
359
|
const prevAccumSample = texture( prevAccumTexture, prevUV, 0 ).toVar();
|
|
349
360
|
|
|
@@ -360,6 +371,13 @@ export const pathTracerMain = ( params ) => {
|
|
|
360
371
|
|
|
361
372
|
} );
|
|
362
373
|
|
|
374
|
+
// NaN/Inf debug: red where the path tracer output isn't finite, black otherwise.
|
|
375
|
+
If( visMode.equal( int( 11 ) ), () => {
|
|
376
|
+
|
|
377
|
+
finalColor.assign( nanInfToRed( finalColor ) );
|
|
378
|
+
|
|
379
|
+
} );
|
|
380
|
+
|
|
363
381
|
// Write outputs to StorageTextures
|
|
364
382
|
textureStore( writeColorTex, uintCoord, vec4( finalColor.xyz, outputAlpha ) ).toWriteOnly();
|
|
365
383
|
textureStore( writeNDTex, uintCoord, finalNormalDepth ).toWriteOnly();
|