rayzee 7.31.1 → 8.1.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 +164 -0
- package/dist/rayzee.es.js +6728 -4735
- package/dist/rayzee.umd.js +121 -73
- package/package.json +2 -2
- package/src/EngineDefaults.js +168 -13
- package/src/EngineIssues.js +5 -0
- package/src/PathTracerApp.js +151 -25
- package/src/Processor/ArchiveReader.js +538 -0
- package/src/Processor/AssetLoader.js +265 -23
- package/src/Processor/BVHBuilder.js +48 -32
- package/src/Processor/BVHRefitter.js +244 -99
- package/src/Processor/ChunkedRecords.js +302 -0
- package/src/Processor/EmissiveTriangleBuilder.js +48 -21
- package/src/Processor/GeometryExtractor.js +580 -93
- package/src/Processor/HostMemory.js +222 -0
- package/src/Processor/InstanceTable.js +548 -97
- package/src/Processor/PBRT/EqualAreaOctahedral.js +32 -8
- package/src/Processor/PBRT/PBRTCurves.js +249 -0
- package/src/Processor/PBRT/PBRTMath.js +19 -0
- package/src/Processor/PBRT/PBRTParser.js +187 -41
- package/src/Processor/PBRT/PBRTSceneBuilder.js +586 -42
- package/src/Processor/PBRT/PBRTTokenizer.js +205 -65
- package/src/Processor/PBRT/index.js +257 -50
- package/src/Processor/PackedRayBuffer.js +29 -18
- package/src/Processor/ParallelBVHBuilder.js +9 -8
- package/src/Processor/QueueManager.js +5 -3
- package/src/Processor/SceneProcessor.js +1046 -365
- package/src/Processor/TLASBuilder.js +546 -201
- package/src/Processor/TextureCreator.js +11 -9
- package/src/Processor/VRAMTracker.js +12 -3
- package/src/Processor/Workers/BVHRefitWorker.js +16 -11
- package/src/Processor/Workers/BVHWorker.js +12 -12
- package/src/Processor/Workers/TLASWorker.js +37 -0
- package/src/Processor/utils.js +8 -20
- package/src/Stages/NormalDepth.js +6 -2
- package/src/Stages/PathTracer.js +5 -5
- package/src/Stages/PathTracerStage.js +163 -41
- package/src/TSL/BVHTraversal.js +208 -90
- package/src/TSL/Common.js +135 -2
- package/src/TSL/Displacement.js +21 -11
- package/src/TSL/EmissiveSampling.js +49 -25
- package/src/TSL/ExtendKernel.js +3 -2
- package/src/TSL/LightBVHSampling.js +7 -4
- package/src/TSL/LightsDirect.js +30 -29
- package/src/TSL/ShadeKernel.js +8 -6
- package/src/TSL/SortGlobalKernels.js +3 -4
- package/src/TSL/Struct.js +3 -0
- package/src/TSL/TextureSampling.js +24 -9
- package/src/TSL/patches.js +149 -84
- package/src/index.js +3 -0
- package/src/managers/AnimationManager.js +86 -76
- package/src/managers/MaterialDataManager.js +29 -23
- package/src/managers/TransformManager.js +9 -192
|
@@ -52,8 +52,26 @@ export function equalAreaSphereToSquare( dx, dy, dz ) {
|
|
|
52
52
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
/**
|
|
56
|
-
|
|
55
|
+
/** 8-bit sRGB → linear, tabulated so a per-tap decode costs a lookup rather than a pow(). */
|
|
56
|
+
const SRGB_TO_LINEAR = ( () => {
|
|
57
|
+
|
|
58
|
+
const t = new Float32Array( 256 );
|
|
59
|
+
for ( let i = 0; i < 256; i ++ ) {
|
|
60
|
+
|
|
61
|
+
const c = i / 255;
|
|
62
|
+
t[ i ] = c <= 0.04045 ? c / 12.92 : Math.pow( ( c + 0.055 ) / 1.055, 2.4 );
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return t;
|
|
67
|
+
|
|
68
|
+
} )();
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Bilinear fetch from a tightly packed image, clamped at the edges.
|
|
72
|
+
* `table` maps stored values to linear before filtering; null reads them as-is.
|
|
73
|
+
*/
|
|
74
|
+
function sampleBilinear( data, width, height, channels, u, v, out, table ) {
|
|
57
75
|
|
|
58
76
|
const fx = Math.min( Math.max( u * width - 0.5, 0 ), width - 1 );
|
|
59
77
|
const fy = Math.min( Math.max( v * height - 0.5, 0 ), height - 1 );
|
|
@@ -68,8 +86,12 @@ function sampleBilinear( data, width, height, channels, u, v, out ) {
|
|
|
68
86
|
|
|
69
87
|
for ( let c = 0; c < 3; c ++ ) {
|
|
70
88
|
|
|
71
|
-
const
|
|
72
|
-
const
|
|
89
|
+
const a = table ? table[ data[ i00 + c ] ] : data[ i00 + c ];
|
|
90
|
+
const b = table ? table[ data[ i10 + c ] ] : data[ i10 + c ];
|
|
91
|
+
const d = table ? table[ data[ i01 + c ] ] : data[ i01 + c ];
|
|
92
|
+
const e = table ? table[ data[ i11 + c ] ] : data[ i11 + c ];
|
|
93
|
+
const top = a * ( 1 - tx ) + b * tx;
|
|
94
|
+
const bottom = d * ( 1 - tx ) + e * tx;
|
|
73
95
|
out[ c ] = top * ( 1 - ty ) + bottom * ty;
|
|
74
96
|
|
|
75
97
|
}
|
|
@@ -83,9 +105,10 @@ function sampleBilinear( data, width, height, channels, u, v, out ) {
|
|
|
83
105
|
* v = 1 - acos(y)/pi, with row 0 at v = 1 so "up" lands at the top like any other HDRI.
|
|
84
106
|
*
|
|
85
107
|
* @param {{data: ArrayLike<number>, width: number, height: number, channels: number,
|
|
86
|
-
* bottomUp?: boolean}} src - `bottomUp` when row 0 holds the BOTTOM of the
|
|
87
|
-
* what three's EXR/HDR loaders produce; pbrt indexes the square top-down, and
|
|
88
|
-
* the wrong way round turns out to rotate the sky 180° about the light's z axis.
|
|
108
|
+
* bottomUp?: boolean, srgb?: boolean}} src - `bottomUp` when row 0 holds the BOTTOM of the
|
|
109
|
+
* image, which is what three's EXR/HDR loaders produce; pbrt indexes the square top-down, and
|
|
110
|
+
* reading it the wrong way round turns out to rotate the sky 180° about the light's z axis.
|
|
111
|
+
* `srgb` when the samples are 8-bit sRGB, as a PNG read back through a canvas is.
|
|
89
112
|
* @param {number[]} lightToWorld - the light's pbrt CTM (column-major 16), or null
|
|
90
113
|
* @param {number} scale - the light's `scale` parameter, baked into the output
|
|
91
114
|
* @param {number} [outWidth] - defaults to the source width, capped at 4096
|
|
@@ -100,6 +123,7 @@ export function octahedralToEquirect( src, lightToWorld, scale = 1, outWidth = 0
|
|
|
100
123
|
// Directions go world → light, the opposite of the CTM, matching pbrt's
|
|
101
124
|
// renderFromLight.ApplyInverse(ray.d) before the square lookup.
|
|
102
125
|
const worldToLight = lightToWorld ? M.invert( lightToWorld ) : null;
|
|
126
|
+
const table = src.srgb ? SRGB_TO_LINEAR : null;
|
|
103
127
|
const rgb = [ 0, 0, 0 ];
|
|
104
128
|
|
|
105
129
|
for ( let j = 0; j < height; j ++ ) {
|
|
@@ -132,7 +156,7 @@ export function octahedralToEquirect( src, lightToWorld, scale = 1, outWidth = 0
|
|
|
132
156
|
}
|
|
133
157
|
|
|
134
158
|
const [ su, sv ] = equalAreaSphereToSquare( dx, dy, dz );
|
|
135
|
-
sampleBilinear( src.data, src.width, src.height, src.channels, su, src.bottomUp ? 1 - sv : sv, rgb );
|
|
159
|
+
sampleBilinear( src.data, src.width, src.height, src.channels, su, src.bottomUp ? 1 - sv : sv, rgb, table );
|
|
136
160
|
|
|
137
161
|
const o = ( j * width + i ) * 4;
|
|
138
162
|
out[ o ] = rgb[ 0 ] * scale;
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tessellation for pbrt-v4's `Shape "curve"`.
|
|
3
|
+
*
|
|
4
|
+
* pbrt intersects curves analytically as swept cubic splines. The engine only traces
|
|
5
|
+
* triangles, so each curve becomes a strip: one ribbon for `flat`/`ribbon`, a crossed
|
|
6
|
+
* pair for `cylinder` (view-independent and a quarter the cost of a real tube), or a
|
|
7
|
+
* closed tube when asked for one.
|
|
8
|
+
*
|
|
9
|
+
* Cost is the whole story here — Moana's isMountainB ground cover alone declares 5.18
|
|
10
|
+
* million curve shapes, so `steps` and `sides` are the knobs that decide whether a
|
|
11
|
+
* scene is 20 million triangles or 200 million.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const DEFAULT_STEPS = 2;
|
|
15
|
+
|
|
16
|
+
/** Uniform cubic B-spline basis at t for the four control points of one span. */
|
|
17
|
+
function bsplineWeights( t, out ) {
|
|
18
|
+
|
|
19
|
+
const t2 = t * t;
|
|
20
|
+
const t3 = t2 * t;
|
|
21
|
+
out[ 0 ] = ( 1 - 3 * t + 3 * t2 - t3 ) / 6;
|
|
22
|
+
out[ 1 ] = ( 4 - 6 * t2 + 3 * t3 ) / 6;
|
|
23
|
+
out[ 2 ] = ( 1 + 3 * t + 3 * t2 - 3 * t3 ) / 6;
|
|
24
|
+
out[ 3 ] = t3 / 6;
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Cubic Bezier basis at t. */
|
|
29
|
+
function bezierWeights( t, out ) {
|
|
30
|
+
|
|
31
|
+
const u = 1 - t;
|
|
32
|
+
out[ 0 ] = u * u * u;
|
|
33
|
+
out[ 1 ] = 3 * u * u * t;
|
|
34
|
+
out[ 2 ] = 3 * u * t * t;
|
|
35
|
+
out[ 3 ] = t * t * t;
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function normalize( v ) {
|
|
40
|
+
|
|
41
|
+
const l = Math.hypot( v[ 0 ], v[ 1 ], v[ 2 ] );
|
|
42
|
+
if ( l > 0 ) {
|
|
43
|
+
|
|
44
|
+
v[ 0 ] /= l; v[ 1 ] /= l; v[ 2 ] /= l;
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return v;
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function cross( a, b, out ) {
|
|
53
|
+
|
|
54
|
+
out[ 0 ] = a[ 1 ] * b[ 2 ] - a[ 2 ] * b[ 1 ];
|
|
55
|
+
out[ 1 ] = a[ 2 ] * b[ 0 ] - a[ 0 ] * b[ 2 ];
|
|
56
|
+
out[ 2 ] = a[ 0 ] * b[ 1 ] - a[ 1 ] * b[ 0 ];
|
|
57
|
+
return out;
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Sample a curve's centreline.
|
|
63
|
+
* @returns {{points: Float64Array, count: number}} count points, 3 floats each
|
|
64
|
+
*/
|
|
65
|
+
function sampleCenterline( P, basis, steps ) {
|
|
66
|
+
|
|
67
|
+
const cps = P.length / 3;
|
|
68
|
+
// bspline: every window of 4 control points is one span. bezier: 4 points per span,
|
|
69
|
+
// sharing an endpoint between consecutive spans.
|
|
70
|
+
const spans = basis === 'bspline' ? cps - 3 : Math.max( 1, ( cps - 1 ) / 3 );
|
|
71
|
+
if ( spans < 1 ) return null;
|
|
72
|
+
|
|
73
|
+
const total = Math.max( 1, Math.round( steps * spans ) );
|
|
74
|
+
const points = new Float64Array( ( total + 1 ) * 3 );
|
|
75
|
+
const w = [ 0, 0, 0, 0 ];
|
|
76
|
+
|
|
77
|
+
for ( let i = 0; i <= total; i ++ ) {
|
|
78
|
+
|
|
79
|
+
const u = ( i / total ) * spans;
|
|
80
|
+
let span = Math.floor( u );
|
|
81
|
+
if ( span >= spans ) span = spans - 1;
|
|
82
|
+
const t = u - span;
|
|
83
|
+
|
|
84
|
+
if ( basis === 'bspline' ) bsplineWeights( t, w );
|
|
85
|
+
else bezierWeights( t, w );
|
|
86
|
+
|
|
87
|
+
const base = basis === 'bspline' ? span : span * 3;
|
|
88
|
+
let x = 0, y = 0, z = 0;
|
|
89
|
+
for ( let k = 0; k < 4; k ++ ) {
|
|
90
|
+
|
|
91
|
+
const o = ( base + k ) * 3;
|
|
92
|
+
x += w[ k ] * P[ o ];
|
|
93
|
+
y += w[ k ] * P[ o + 1 ];
|
|
94
|
+
z += w[ k ] * P[ o + 2 ];
|
|
95
|
+
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
points[ i * 3 ] = x; points[ i * 3 + 1 ] = y; points[ i * 3 + 2 ] = z;
|
|
99
|
+
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { points, count: total + 1 };
|
|
103
|
+
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @param {object} spec
|
|
108
|
+
* @param {ArrayLike<number>} spec.P - control points, 3 floats each
|
|
109
|
+
* @param {string} [spec.basis='bezier'] - 'bezier' | 'bspline'
|
|
110
|
+
* @param {number} [spec.width0=1]
|
|
111
|
+
* @param {number} [spec.width1=1]
|
|
112
|
+
* @param {ArrayLike<number>} [spec.N] - ribbon normals (start, end), 3 floats each
|
|
113
|
+
* @param {number} [spec.steps] - samples per spline span
|
|
114
|
+
* @param {number} [spec.sides=1] - 1 ribbon, 2 crossed ribbons, >=3 closed tube
|
|
115
|
+
* @returns {{positions: Float32Array, indices: Uint32Array}|null}
|
|
116
|
+
*/
|
|
117
|
+
export function tessellateCurve( spec ) {
|
|
118
|
+
|
|
119
|
+
const { P, basis = 'bezier', width0 = 1, width1 = 1, N = null } = spec;
|
|
120
|
+
const steps = Math.max( 1, Math.round( spec.steps ?? DEFAULT_STEPS ) );
|
|
121
|
+
const sides = Math.max( 1, Math.round( spec.sides ?? 1 ) );
|
|
122
|
+
|
|
123
|
+
if ( ! P || P.length < 12 ) return null;
|
|
124
|
+
|
|
125
|
+
const line = sampleCenterline( P, basis, steps );
|
|
126
|
+
if ( ! line ) return null;
|
|
127
|
+
|
|
128
|
+
const { points, count } = line;
|
|
129
|
+
const rings = sides >= 3 ? sides : sides * 2;
|
|
130
|
+
const positions = new Float32Array( count * rings * 3 );
|
|
131
|
+
|
|
132
|
+
const tangent = [ 0, 0, 0 ];
|
|
133
|
+
const side = [ 0, 0, 0 ];
|
|
134
|
+
const up = [ 0, 0, 0 ];
|
|
135
|
+
const tmp = [ 0, 0, 0 ];
|
|
136
|
+
let haveSide = false;
|
|
137
|
+
|
|
138
|
+
for ( let i = 0; i < count; i ++ ) {
|
|
139
|
+
|
|
140
|
+
const a = Math.max( 0, i - 1 ), b = Math.min( count - 1, i + 1 );
|
|
141
|
+
tangent[ 0 ] = points[ b * 3 ] - points[ a * 3 ];
|
|
142
|
+
tangent[ 1 ] = points[ b * 3 + 1 ] - points[ a * 3 + 1 ];
|
|
143
|
+
tangent[ 2 ] = points[ b * 3 + 2 ] - points[ a * 3 + 2 ];
|
|
144
|
+
normalize( tangent );
|
|
145
|
+
|
|
146
|
+
if ( ! haveSide ) {
|
|
147
|
+
|
|
148
|
+
// Seed from the ribbon normal when the scene gives one, else from whichever
|
|
149
|
+
// axis the tangent leans on least.
|
|
150
|
+
if ( N && N.length >= 3 ) {
|
|
151
|
+
|
|
152
|
+
side[ 0 ] = N[ 0 ]; side[ 1 ] = N[ 1 ]; side[ 2 ] = N[ 2 ];
|
|
153
|
+
cross( tangent, side, tmp );
|
|
154
|
+
side[ 0 ] = tmp[ 0 ]; side[ 1 ] = tmp[ 1 ]; side[ 2 ] = tmp[ 2 ];
|
|
155
|
+
|
|
156
|
+
} else {
|
|
157
|
+
|
|
158
|
+
const ax = Math.abs( tangent[ 0 ] ), ay = Math.abs( tangent[ 1 ] ), az = Math.abs( tangent[ 2 ] );
|
|
159
|
+
const ref = ax < ay && ax < az ? [ 1, 0, 0 ] : ay < az ? [ 0, 1, 0 ] : [ 0, 0, 1 ];
|
|
160
|
+
cross( tangent, ref, side );
|
|
161
|
+
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if ( Math.hypot( side[ 0 ], side[ 1 ], side[ 2 ] ) < 1e-9 ) cross( tangent, [ 0, 1, 0 ], side );
|
|
165
|
+
normalize( side );
|
|
166
|
+
haveSide = true;
|
|
167
|
+
|
|
168
|
+
} else {
|
|
169
|
+
|
|
170
|
+
// Rotation-minimising: drop the previous side back onto the new normal plane,
|
|
171
|
+
// so a coiled curve does not twist its ribbon.
|
|
172
|
+
const d = side[ 0 ] * tangent[ 0 ] + side[ 1 ] * tangent[ 1 ] + side[ 2 ] * tangent[ 2 ];
|
|
173
|
+
side[ 0 ] -= d * tangent[ 0 ];
|
|
174
|
+
side[ 1 ] -= d * tangent[ 1 ];
|
|
175
|
+
side[ 2 ] -= d * tangent[ 2 ];
|
|
176
|
+
if ( Math.hypot( side[ 0 ], side[ 1 ], side[ 2 ] ) < 1e-9 ) cross( tangent, [ 0, 1, 0 ], side );
|
|
177
|
+
normalize( side );
|
|
178
|
+
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
cross( tangent, side, up );
|
|
182
|
+
normalize( up );
|
|
183
|
+
|
|
184
|
+
const u = i / ( count - 1 || 1 );
|
|
185
|
+
const radius = ( width0 + ( width1 - width0 ) * u ) * 0.5;
|
|
186
|
+
const cx = points[ i * 3 ], cy = points[ i * 3 + 1 ], cz = points[ i * 3 + 2 ];
|
|
187
|
+
|
|
188
|
+
for ( let r = 0; r < rings; r ++ ) {
|
|
189
|
+
|
|
190
|
+
let dx, dy, dz;
|
|
191
|
+
if ( sides >= 3 ) {
|
|
192
|
+
|
|
193
|
+
const a2 = ( r / sides ) * Math.PI * 2;
|
|
194
|
+
const c = Math.cos( a2 ), s = Math.sin( a2 );
|
|
195
|
+
dx = side[ 0 ] * c + up[ 0 ] * s;
|
|
196
|
+
dy = side[ 1 ] * c + up[ 1 ] * s;
|
|
197
|
+
dz = side[ 2 ] * c + up[ 2 ] * s;
|
|
198
|
+
|
|
199
|
+
} else {
|
|
200
|
+
|
|
201
|
+
// Ribbon r spans -1..+1 along its own axis: `side` first, `up` for the cross.
|
|
202
|
+
const axis = r < 2 ? side : up;
|
|
203
|
+
const sign = r % 2 === 0 ? - 1 : 1;
|
|
204
|
+
dx = axis[ 0 ] * sign; dy = axis[ 1 ] * sign; dz = axis[ 2 ] * sign;
|
|
205
|
+
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const o = ( i * rings + r ) * 3;
|
|
209
|
+
positions[ o ] = cx + dx * radius;
|
|
210
|
+
positions[ o + 1 ] = cy + dy * radius;
|
|
211
|
+
positions[ o + 2 ] = cz + dz * radius;
|
|
212
|
+
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const quads = sides >= 3 ? sides : sides;
|
|
218
|
+
const indices = new Uint32Array( ( count - 1 ) * quads * 6 );
|
|
219
|
+
let w = 0;
|
|
220
|
+
|
|
221
|
+
for ( let i = 0; i < count - 1; i ++ ) {
|
|
222
|
+
|
|
223
|
+
for ( let q = 0; q < quads; q ++ ) {
|
|
224
|
+
|
|
225
|
+
const r0 = sides >= 3 ? q : q * 2;
|
|
226
|
+
const r1 = sides >= 3 ? ( q + 1 ) % sides : q * 2 + 1;
|
|
227
|
+
const a = i * rings + r0, b = i * rings + r1;
|
|
228
|
+
const c = ( i + 1 ) * rings + r1, d = ( i + 1 ) * rings + r0;
|
|
229
|
+
indices[ w ++ ] = a; indices[ w ++ ] = b; indices[ w ++ ] = c;
|
|
230
|
+
indices[ w ++ ] = a; indices[ w ++ ] = c; indices[ w ++ ] = d;
|
|
231
|
+
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return { positions, indices: w === indices.length ? indices : indices.slice( 0, w ) };
|
|
237
|
+
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Triangles `tessellateCurve` would produce, without building anything. */
|
|
241
|
+
export function curveTriangleCount( controlPoints, basis, steps, sides ) {
|
|
242
|
+
|
|
243
|
+
const cps = controlPoints / 3;
|
|
244
|
+
const spans = basis === 'bspline' ? cps - 3 : Math.max( 1, ( cps - 1 ) / 3 );
|
|
245
|
+
if ( spans < 1 ) return 0;
|
|
246
|
+
const segments = Math.max( 1, Math.round( Math.max( 1, steps ) * spans ) );
|
|
247
|
+
return segments * Math.max( 1, Math.round( sides ) ) * 2;
|
|
248
|
+
|
|
249
|
+
}
|
|
@@ -27,6 +27,25 @@ export function determinant3( m ) {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/** Matrix product a*b (applies b first, then a, to a column vector). */
|
|
30
|
+
/** a*b written into `out`, so a hot loop over millions of placements allocates nothing. */
|
|
31
|
+
export function multiplyInto( out, a, aOff, b ) {
|
|
32
|
+
|
|
33
|
+
for ( let c = 0; c < 4; c ++ ) {
|
|
34
|
+
|
|
35
|
+
const b0 = b[ c * 4 ], b1 = b[ c * 4 + 1 ], b2 = b[ c * 4 + 2 ], b3 = b[ c * 4 + 3 ];
|
|
36
|
+
for ( let r = 0; r < 4; r ++ ) {
|
|
37
|
+
|
|
38
|
+
out[ c * 4 + r ] = a[ aOff + r ] * b0 + a[ aOff + 4 + r ] * b1
|
|
39
|
+
+ a[ aOff + 8 + r ] * b2 + a[ aOff + 12 + r ] * b3;
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return out;
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
30
49
|
export function multiply( a, b ) {
|
|
31
50
|
|
|
32
51
|
const a11 = a[ 0 ], a21 = a[ 1 ], a31 = a[ 2 ], a41 = a[ 3 ];
|