paperlab 0.6.0 → 0.7.1
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 -1
- package/dist/FxPostPass-M66QMVVC.js +166 -0
- package/dist/FxPostPass-M66QMVVC.js.map +1 -0
- package/dist/Grade-SBGYELKV.js +49 -0
- package/dist/Grade-SBGYELKV.js.map +1 -0
- package/dist/chunk-7TLIWPGM.js +36 -0
- package/dist/chunk-7TLIWPGM.js.map +1 -0
- package/dist/chunk-GB6BMHC3.js +811 -0
- package/dist/chunk-GB6BMHC3.js.map +1 -0
- package/dist/{chunk-E22ILEDO.js → chunk-NTDVOOBA.js} +1459 -197
- package/dist/chunk-NTDVOOBA.js.map +1 -0
- package/dist/damageContract-DPF1YFLZ.d.cts +129 -0
- package/dist/damageContract-DPF1YFLZ.d.ts +129 -0
- package/dist/fx.cjs +4276 -0
- package/dist/fx.cjs.map +1 -0
- package/dist/fx.d.cts +1796 -0
- package/dist/fx.d.ts +1796 -0
- package/dist/fx.js +3194 -0
- package/dist/fx.js.map +1 -0
- package/dist/index.cjs +2847 -914
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -4
- package/dist/index.d.ts +34 -4
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/stage.cjs +3063 -1091
- package/dist/stage.cjs.map +1 -1
- package/dist/stage.js +31 -51
- package/dist/stage.js.map +1 -1
- package/package.json +16 -3
- package/dist/chunk-E22ILEDO.js.map +0 -1
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DAMAGE_CHANNELS,
|
|
3
|
+
DAMAGE_LOOK_DEFAULTS
|
|
4
|
+
} from "./chunk-7TLIWPGM.js";
|
|
5
|
+
|
|
1
6
|
// src/config/merge.ts
|
|
2
7
|
function mergeConfig(base, override) {
|
|
3
8
|
if (override === void 0) return base;
|
|
@@ -4399,10 +4404,10 @@ function usePaperStates(config, enabled, instant, onAction, onStateChange) {
|
|
|
4399
4404
|
}
|
|
4400
4405
|
|
|
4401
4406
|
// src/PaperMesh.tsx
|
|
4402
|
-
import * as
|
|
4407
|
+
import * as THREE8 from "three";
|
|
4403
4408
|
import { gsap as gsap2 } from "gsap";
|
|
4404
|
-
import { useFrame, useThree } from "@react-three/fiber";
|
|
4405
|
-
import { forwardRef, useEffect as
|
|
4409
|
+
import { useFrame as useFrame3, useThree } from "@react-three/fiber";
|
|
4410
|
+
import { forwardRef, useEffect as useEffect6, useImperativeHandle, useMemo as useMemo4, useRef as useRef3 } from "react";
|
|
4406
4411
|
|
|
4407
4412
|
// src/core/normals.ts
|
|
4408
4413
|
function computeSheetNormals(geometry) {
|
|
@@ -4498,6 +4503,27 @@ function spreadByAspect(sheet2, target) {
|
|
|
4498
4503
|
if (!(long > 0)) return [target, target];
|
|
4499
4504
|
return [Math.round(sheet2.width / long * target), Math.round(sheet2.height / long * target)];
|
|
4500
4505
|
}
|
|
4506
|
+
function surfacePointAt(positions, cols, rows, u, v, out) {
|
|
4507
|
+
const fx = Math.min(1, Math.max(0, u)) * (cols - 1);
|
|
4508
|
+
const fy = (1 - Math.min(1, Math.max(0, v))) * (rows - 1);
|
|
4509
|
+
const ix = Math.min(Math.floor(fx), cols - 2);
|
|
4510
|
+
const iy = Math.min(Math.floor(fy), rows - 2);
|
|
4511
|
+
const tx = fx - ix;
|
|
4512
|
+
const ty = fy - iy;
|
|
4513
|
+
const a = (iy * cols + ix) * 3;
|
|
4514
|
+
const b = a + cols * 3;
|
|
4515
|
+
const c = b + 3;
|
|
4516
|
+
const d = a + 3;
|
|
4517
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
4518
|
+
const pa = positions[a + axis];
|
|
4519
|
+
const pb = positions[b + axis];
|
|
4520
|
+
const pc = positions[c + axis];
|
|
4521
|
+
const pd = positions[d + axis];
|
|
4522
|
+
const value = tx + ty <= 1 ? pa + tx * (pd - pa) + ty * (pb - pa) : pc + (1 - tx) * (pb - pc) + (1 - ty) * (pd - pc);
|
|
4523
|
+
out.setComponent(axis, value);
|
|
4524
|
+
}
|
|
4525
|
+
return out;
|
|
4526
|
+
}
|
|
4501
4527
|
function createSheetGeometry(sheet2, minSegments = 2, autoSegments = LEGACY_FLAT_SEGMENTS) {
|
|
4502
4528
|
const [sx, sy] = resolveSegments(sheet2, minSegments, autoSegments);
|
|
4503
4529
|
return new THREE.PlaneGeometry(sheet2.width, sheet2.height, sx, sy);
|
|
@@ -4910,9 +4936,86 @@ var ClothSim = class {
|
|
|
4910
4936
|
height;
|
|
4911
4937
|
positions;
|
|
4912
4938
|
prev;
|
|
4939
|
+
/** Where the constructor laid every particle out — what {@link restore} puts back. */
|
|
4940
|
+
laidOut;
|
|
4913
4941
|
pinned;
|
|
4914
4942
|
pinTargets;
|
|
4915
|
-
|
|
4943
|
+
/**
|
|
4944
|
+
* The constraints, one typed array per field rather than one object each.
|
|
4945
|
+
*
|
|
4946
|
+
* Because the effects layer has to vary them across the sheet, and an
|
|
4947
|
+
* object with a fixed `rest` cannot be varied cheaply. Paper that chars
|
|
4948
|
+
* SHRINKS — that is what curls it toward the flame, not softening — which
|
|
4949
|
+
* is a shorter rest length on exactly the constraints the heat reached.
|
|
4950
|
+
* Burnt-through paper has to stop pulling on what is left, which is a
|
|
4951
|
+
* broken flag. A wet corner is floppier than the dry sheet above it, which
|
|
4952
|
+
* is a per-constraint stiffness. None of that is in this file; this file
|
|
4953
|
+
* only makes it possible, and at the defaults below the solve is
|
|
4954
|
+
* bit-identical to the object version it replaces.
|
|
4955
|
+
*/
|
|
4956
|
+
constraintCount;
|
|
4957
|
+
constraintA;
|
|
4958
|
+
constraintB;
|
|
4959
|
+
/** 0 = structural, 1 = shear, 2 = bend (scaled by `stiffness` at solve time). */
|
|
4960
|
+
constraintKind;
|
|
4961
|
+
/**
|
|
4962
|
+
* The length each constraint is laid out at. Never changes; the reference
|
|
4963
|
+
* to shrink from.
|
|
4964
|
+
*
|
|
4965
|
+
* Float64, not Float32, and that is measured rather than cautious: with
|
|
4966
|
+
* single-precision rest lengths every one of them rounded, and the first
|
|
4967
|
+
* typed-array version of this solve disagreed with the object version it
|
|
4968
|
+
* replaced in 476–711 of 714 coordinates after five seconds. The object
|
|
4969
|
+
* version held a JS number, which is a double.
|
|
4970
|
+
*/
|
|
4971
|
+
naturalLength;
|
|
4972
|
+
/** The length it pulls toward now. `naturalLength` until something shrinks or stretches it. */
|
|
4973
|
+
restLength;
|
|
4974
|
+
/** A multiplier on the constraint's own strength. 1 is the paper as built. */
|
|
4975
|
+
constraintStiffness;
|
|
4976
|
+
/** Non-zero where the paper between two particles is gone. Skipped by the solve. */
|
|
4977
|
+
broken;
|
|
4978
|
+
/**
|
|
4979
|
+
* How easily each particle is moved, 0..1: one over its mass, relative to
|
|
4980
|
+
* dry paper.
|
|
4981
|
+
*
|
|
4982
|
+
* Mass matters in exactly two places here, and both are wired, because a
|
|
4983
|
+
* "heavier" that touched neither would be another knob that does nothing.
|
|
4984
|
+
* Gravity is an acceleration, so weight alone does not make wet paper fall
|
|
4985
|
+
* differently. What does: the air pushes a heavy particle less (the aero
|
|
4986
|
+
* term is divided by mass), and the constraint solve moves a heavy particle
|
|
4987
|
+
* less than a light one it is tied to. Zero is infinite mass: static, moved by nothing — not the air, not the solve, and not gravity either.
|
|
4988
|
+
*/
|
|
4989
|
+
invMass;
|
|
4990
|
+
/**
|
|
4991
|
+
* How far the middle of each BEND spring sits off the line between its ends
|
|
4992
|
+
* when relaxed, along the sheet's normal — a signed rest curvature, as a
|
|
4993
|
+
* sagitta in world units. Ignored on every other kind of constraint.
|
|
4994
|
+
*
|
|
4995
|
+
* Positive curls the paper toward its FRONT, the face a flat sheet turns to
|
|
4996
|
+
* +z — which puts the middle of each span that far BEHIND its ends. Paper
|
|
4997
|
+
* curling toward you is hollow on the side you see; the first cut of this
|
|
4998
|
+
* had the middle in front, which is a dome, and a dome's rim goes the other
|
|
4999
|
+
* way.
|
|
5000
|
+
*
|
|
5001
|
+
* The only lever here with a sign, and the sign is why it exists. A bend
|
|
5002
|
+
* spring is a distance, and a distance cannot tell paper curled toward you
|
|
5003
|
+
* from paper curled away: shorten it and the sheet buckles, but which way
|
|
5004
|
+
* is a coin toss, and gravity can flip it through later. Measured on the
|
|
5005
|
+
* first version, which pushed hot paper toward the front along its normal
|
|
5006
|
+
* and left the char to hold it — the curl held while the heat did and
|
|
5007
|
+
* snapped through to the BACK the moment the front had passed. Charred
|
|
5008
|
+
* paper stays curled toward the side that burnt because that side shrank
|
|
5009
|
+
* more: a curvature built into the material, which is what this is.
|
|
5010
|
+
*
|
|
5011
|
+
* Solved as a couple — the middle moves one way, the two ends half as far
|
|
5012
|
+
* the other — so it bends the sheet and never pushes it anywhere. A push
|
|
5013
|
+
* cannot do this job: it moves paper, and a uniformly curled sheet in still
|
|
5014
|
+
* air has no business going anywhere.
|
|
5015
|
+
*/
|
|
5016
|
+
restBend;
|
|
5017
|
+
/** For each bend spring, the particle between its ends; -1 for every other kind. */
|
|
5018
|
+
constraintMiddle;
|
|
4916
5019
|
params;
|
|
4917
5020
|
time = 0;
|
|
4918
5021
|
accumulator = 0;
|
|
@@ -4968,11 +5071,11 @@ var ClothSim = class {
|
|
|
4968
5071
|
}
|
|
4969
5072
|
}
|
|
4970
5073
|
this.prev.set(this.positions);
|
|
5074
|
+
this.laidOut = this.positions.slice();
|
|
4971
5075
|
const idx = (r, c) => r * cols + c;
|
|
5076
|
+
const links = [];
|
|
4972
5077
|
const link = (a, b, kind) => {
|
|
4973
|
-
|
|
4974
|
-
const dy = this.positions[a * 3 + 1] - this.positions[b * 3 + 1];
|
|
4975
|
-
this.constraints.push({ a, b, rest: Math.hypot(dx, dy), kind });
|
|
5078
|
+
links.push(a, b, kind);
|
|
4976
5079
|
};
|
|
4977
5080
|
for (let r = 0; r < rows; r++) {
|
|
4978
5081
|
for (let c = 0; c < cols; c++) {
|
|
@@ -4986,6 +5089,31 @@ var ClothSim = class {
|
|
|
4986
5089
|
if (r + 2 < rows) link(idx(r, c), idx(r + 2, c), 2);
|
|
4987
5090
|
}
|
|
4988
5091
|
}
|
|
5092
|
+
const m = links.length / 3;
|
|
5093
|
+
this.constraintCount = m;
|
|
5094
|
+
this.constraintA = new Uint32Array(m);
|
|
5095
|
+
this.constraintB = new Uint32Array(m);
|
|
5096
|
+
this.constraintKind = new Uint8Array(m);
|
|
5097
|
+
this.naturalLength = new Float64Array(m);
|
|
5098
|
+
this.restLength = new Float64Array(m);
|
|
5099
|
+
this.constraintStiffness = new Float64Array(m).fill(1);
|
|
5100
|
+
this.broken = new Uint8Array(m);
|
|
5101
|
+
this.restBend = new Float64Array(m);
|
|
5102
|
+
this.constraintMiddle = new Int32Array(m).fill(-1);
|
|
5103
|
+
for (let k = 0; k < m; k++) {
|
|
5104
|
+
const a = links[k * 3];
|
|
5105
|
+
const b = links[k * 3 + 1];
|
|
5106
|
+
this.constraintA[k] = a;
|
|
5107
|
+
this.constraintB[k] = b;
|
|
5108
|
+
this.constraintKind[k] = links[k * 3 + 2];
|
|
5109
|
+
if (this.constraintKind[k] === 2) this.constraintMiddle[k] = (a + b) / 2;
|
|
5110
|
+
const dx = this.positions[a * 3] - this.positions[b * 3];
|
|
5111
|
+
const dy = this.positions[a * 3 + 1] - this.positions[b * 3 + 1];
|
|
5112
|
+
const rest = Math.hypot(dx, dy);
|
|
5113
|
+
this.naturalLength[k] = rest;
|
|
5114
|
+
this.restLength[k] = rest;
|
|
5115
|
+
}
|
|
5116
|
+
this.invMass = new Float32Array(this.count).fill(1);
|
|
4989
5117
|
const pin = (r, c) => {
|
|
4990
5118
|
const i = idx(r, c);
|
|
4991
5119
|
this.pinned[i] = 1;
|
|
@@ -5061,6 +5189,22 @@ var ClothSim = class {
|
|
|
5061
5189
|
this.asleep = false;
|
|
5062
5190
|
this.stillFrames = 0;
|
|
5063
5191
|
}
|
|
5192
|
+
/**
|
|
5193
|
+
* Back to the sheet as it was laid out: flat, still, every particle where
|
|
5194
|
+
* the constructor put it. Pins, a grab and every lever are left alone.
|
|
5195
|
+
*
|
|
5196
|
+
* For a history that has been rewound — a burn played back from an earlier
|
|
5197
|
+
* moment, say, which hands back paper that had burnt away and paper that
|
|
5198
|
+
* had FALLEN, lying on the floor with its springs to the sheet suddenly
|
|
5199
|
+
* whole again. Solved from there, the solve hauls it up through the sheet
|
|
5200
|
+
* in a tangle. Laid out afresh, it is simply the sheet it is again.
|
|
5201
|
+
*/
|
|
5202
|
+
restore() {
|
|
5203
|
+
this.positions.set(this.laidOut);
|
|
5204
|
+
this.prev.set(this.laidOut);
|
|
5205
|
+
this.accumulator = 0;
|
|
5206
|
+
this.wake();
|
|
5207
|
+
}
|
|
5064
5208
|
/**
|
|
5065
5209
|
* Take hold of the sheet at one particle.
|
|
5066
5210
|
*
|
|
@@ -5103,7 +5247,7 @@ var ClothSim = class {
|
|
|
5103
5247
|
for (let c = centreCol - spanCols; c <= centreCol + spanCols; c++) {
|
|
5104
5248
|
if (c < 0 || c >= this.cols) continue;
|
|
5105
5249
|
const i = r * this.cols + c;
|
|
5106
|
-
if (this.pinned[i]) continue;
|
|
5250
|
+
if (this.pinned[i] || this.invMass[i] <= 0) continue;
|
|
5107
5251
|
const distance = Math.hypot((c - centreCol) * cellX, (r - centreRow) * cellY);
|
|
5108
5252
|
const weight = i === this.grabbedIndex ? 1 : grabFalloff(distance, radius);
|
|
5109
5253
|
if (weight <= 0) continue;
|
|
@@ -5117,6 +5261,12 @@ var ClothSim = class {
|
|
|
5117
5261
|
);
|
|
5118
5262
|
}
|
|
5119
5263
|
}
|
|
5264
|
+
if (this.grabbed.length === 0) {
|
|
5265
|
+
this.grabbedIndex = -1;
|
|
5266
|
+
this.grabOffsets = new Float32Array(0);
|
|
5267
|
+
this.wake();
|
|
5268
|
+
return -1;
|
|
5269
|
+
}
|
|
5120
5270
|
this.grabOffsets = Float32Array.from(offsets);
|
|
5121
5271
|
this.grabAt.set(this.positions.subarray(anchor, anchor + 3));
|
|
5122
5272
|
this.grabWas.set(this.grabAt);
|
|
@@ -5277,12 +5427,19 @@ var ClothSim = class {
|
|
|
5277
5427
|
const ax = nx * facing * (1 - AERO_TURBULENCE) + gust2 * 0.25 * AERO_TURBULENCE;
|
|
5278
5428
|
const ay = ny * facing * (1 - AERO_TURBULENCE);
|
|
5279
5429
|
const az = nz * facing * (1 - AERO_TURBULENCE) + gust2 * AERO_TURBULENCE;
|
|
5430
|
+
const im = this.invMass[i];
|
|
5431
|
+
if (im <= 0) {
|
|
5432
|
+
this.prev[i3] = x;
|
|
5433
|
+
this.prev[i3 + 1] = y;
|
|
5434
|
+
this.prev[i3 + 2] = z25;
|
|
5435
|
+
continue;
|
|
5436
|
+
}
|
|
5280
5437
|
this.prev[i3] = x;
|
|
5281
5438
|
this.prev[i3 + 1] = y;
|
|
5282
5439
|
this.prev[i3 + 2] = z25;
|
|
5283
|
-
p[i3] = x + vx + ax * dt2;
|
|
5284
|
-
p[i3 + 1] = y + vy + (ay - gravity * 3.2) * dt2;
|
|
5285
|
-
p[i3 + 2] = z25 + vz + az * dt2;
|
|
5440
|
+
p[i3] = x + vx + ax * im * dt2;
|
|
5441
|
+
p[i3 + 1] = y + vy + (ay * im - gravity * 3.2) * dt2;
|
|
5442
|
+
p[i3 + 2] = z25 + vz + az * im * dt2;
|
|
5286
5443
|
maxTravel = Math.max(maxTravel, vx * vx + vy * vy + vz * vz);
|
|
5287
5444
|
}
|
|
5288
5445
|
for (let k = 0; k < this.grabbed.length; k++) {
|
|
@@ -5295,21 +5452,38 @@ var ClothSim = class {
|
|
|
5295
5452
|
p[i3 + axis] = p[i3 + axis] + (target - p[i3 + axis]) * held;
|
|
5296
5453
|
}
|
|
5297
5454
|
}
|
|
5455
|
+
const {
|
|
5456
|
+
constraintA,
|
|
5457
|
+
constraintB,
|
|
5458
|
+
constraintKind,
|
|
5459
|
+
restLength,
|
|
5460
|
+
constraintStiffness,
|
|
5461
|
+
broken,
|
|
5462
|
+
invMass,
|
|
5463
|
+
restBend,
|
|
5464
|
+
constraintMiddle,
|
|
5465
|
+
normals
|
|
5466
|
+
} = this;
|
|
5467
|
+
const bend2 = 0.25 + stiffness * 0.7;
|
|
5298
5468
|
for (let iter = 0; iter < SOLVER_ITERATIONS2; iter++) {
|
|
5299
|
-
for (
|
|
5300
|
-
|
|
5301
|
-
const
|
|
5302
|
-
const
|
|
5469
|
+
for (let c = 0; c < this.constraintCount; c++) {
|
|
5470
|
+
if (broken[c]) continue;
|
|
5471
|
+
const kind = constraintKind[c];
|
|
5472
|
+
const k = (kind === 2 ? bend2 : kind === 1 ? 0.85 : 1) * constraintStiffness[c];
|
|
5473
|
+
const ia = constraintA[c];
|
|
5474
|
+
const ib = constraintB[c];
|
|
5475
|
+
const a3 = ia * 3;
|
|
5476
|
+
const b3 = ib * 3;
|
|
5303
5477
|
const dx = p[b3] - p[a3];
|
|
5304
5478
|
const dy = p[b3 + 1] - p[a3 + 1];
|
|
5305
5479
|
const dz = p[b3 + 2] - p[a3 + 2];
|
|
5306
5480
|
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
5307
5481
|
if (dist === 0) continue;
|
|
5308
|
-
const ma = this.pinned[
|
|
5309
|
-
const mb = this.pinned[
|
|
5482
|
+
const ma = this.pinned[ia] ? 0 : invMass[ia] * (1 - this.grabWeights[ia]);
|
|
5483
|
+
const mb = this.pinned[ib] ? 0 : invMass[ib] * (1 - this.grabWeights[ib]);
|
|
5310
5484
|
const total = ma + mb;
|
|
5311
5485
|
if (total <= 0) continue;
|
|
5312
|
-
const diff = (dist - c
|
|
5486
|
+
const diff = (dist - restLength[c]) / dist / total * k;
|
|
5313
5487
|
const aw = ma;
|
|
5314
5488
|
const bw = mb;
|
|
5315
5489
|
p[a3] = p[a3] + dx * diff * aw;
|
|
@@ -5318,6 +5492,31 @@ var ClothSim = class {
|
|
|
5318
5492
|
p[b3] = p[b3] - dx * diff * bw;
|
|
5319
5493
|
p[b3 + 1] = p[b3 + 1] - dy * diff * bw;
|
|
5320
5494
|
p[b3 + 2] = p[b3 + 2] - dz * diff * bw;
|
|
5495
|
+
const sag = restBend[c];
|
|
5496
|
+
if (sag !== 0) {
|
|
5497
|
+
const im = constraintMiddle[c];
|
|
5498
|
+
const m3 = im * 3;
|
|
5499
|
+
const nx = normals[m3];
|
|
5500
|
+
const ny = normals[m3 + 1];
|
|
5501
|
+
const nz = normals[m3 + 2];
|
|
5502
|
+
const offset = (p[m3] - (p[a3] + p[b3]) / 2) * nx + (p[m3 + 1] - (p[a3 + 1] + p[b3 + 1]) / 2) * ny + (p[m3 + 2] - (p[a3 + 2] + p[b3 + 2]) / 2) * nz;
|
|
5503
|
+
const mm = this.pinned[im] ? 0 : invMass[im] * (1 - this.grabWeights[im]);
|
|
5504
|
+
const weight = mm + (ma + mb) / 4;
|
|
5505
|
+
if (weight > 0) {
|
|
5506
|
+
const lambda = (-sag - offset) / weight * k;
|
|
5507
|
+
const along = lambda * mm;
|
|
5508
|
+
const back = lambda / 2;
|
|
5509
|
+
p[m3] = p[m3] + nx * along;
|
|
5510
|
+
p[m3 + 1] = p[m3 + 1] + ny * along;
|
|
5511
|
+
p[m3 + 2] = p[m3 + 2] + nz * along;
|
|
5512
|
+
p[a3] = p[a3] - nx * back * ma;
|
|
5513
|
+
p[a3 + 1] = p[a3 + 1] - ny * back * ma;
|
|
5514
|
+
p[a3 + 2] = p[a3 + 2] - nz * back * ma;
|
|
5515
|
+
p[b3] = p[b3] - nx * back * mb;
|
|
5516
|
+
p[b3 + 1] = p[b3 + 1] - ny * back * mb;
|
|
5517
|
+
p[b3 + 2] = p[b3 + 2] - nz * back * mb;
|
|
5518
|
+
}
|
|
5519
|
+
}
|
|
5321
5520
|
}
|
|
5322
5521
|
}
|
|
5323
5522
|
for (let i = 0; i < this.count; i++) {
|
|
@@ -5338,9 +5537,392 @@ var ClothSim = class {
|
|
|
5338
5537
|
}
|
|
5339
5538
|
};
|
|
5340
5539
|
|
|
5540
|
+
// src/physics/damage.ts
|
|
5541
|
+
var { char: CHAR, saturation: SATURATION, presence: PRESENCE } = DAMAGE_CHANNELS;
|
|
5542
|
+
var GONE_BELOW = 128;
|
|
5543
|
+
var CHAR_SHRINK = 0.12;
|
|
5544
|
+
var CHAR_CURL = 6;
|
|
5545
|
+
var FRONT_GRADIENT = 16 * 16;
|
|
5546
|
+
var WET_MASS = 2;
|
|
5547
|
+
var TEAR_STRETCH = 1.5;
|
|
5548
|
+
var DamageCoupling = class {
|
|
5549
|
+
sim;
|
|
5550
|
+
source = null;
|
|
5551
|
+
version = -1;
|
|
5552
|
+
size = 0;
|
|
5553
|
+
/** Each particle's texel, laid out for `size`. */
|
|
5554
|
+
particleTexel = new Int32Array(0);
|
|
5555
|
+
/** Gone, per particle, as of the last read. */
|
|
5556
|
+
gone;
|
|
5557
|
+
/** Whether any particle has ever been gone since the last reset — nothing can tear before. */
|
|
5558
|
+
holed = false;
|
|
5559
|
+
/** The gone particles with live paper beside them. See {@link follow}. */
|
|
5560
|
+
followers = [];
|
|
5561
|
+
/**
|
|
5562
|
+
* Which piece of paper each particle belongs to, named by the lowest index
|
|
5563
|
+
* in it — or -1 in the middle of a hole.
|
|
5564
|
+
*
|
|
5565
|
+
* A piece is whatever the sim's unbroken springs still hold together, which
|
|
5566
|
+
* is exactly what moves as one: two pieces are two things the solve lets
|
|
5567
|
+
* part. A gone particle on a cut takes the piece it borders MOST — a cut a
|
|
5568
|
+
* single particle wide borders two, and a particle averaged between them
|
|
5569
|
+
* would be dragged across the gap as one piece fell, stretching both edges
|
|
5570
|
+
* into it.
|
|
5571
|
+
*/
|
|
5572
|
+
piece;
|
|
5573
|
+
/** Union-find over the particles, reused on every read. */
|
|
5574
|
+
parent;
|
|
5575
|
+
/**
|
|
5576
|
+
* Per bend spring, how squarely it lies ACROSS the burn front, 0..1 — and
|
|
5577
|
+
* remembered once it has been.
|
|
5578
|
+
*
|
|
5579
|
+
* A burnt edge rolls about an axis along the front, like a scroll; it does
|
|
5580
|
+
* not dish into a bowl. Measured with the curl in every bend spring alike: a
|
|
5581
|
+
* burnt band settled into a saddle, its corners forward and its middle BACK
|
|
5582
|
+
* (+0.07, −0.06), because a surface cannot curve two ways at once without
|
|
5583
|
+
* stretching and paper does not stretch. Curled across the front only, the
|
|
5584
|
+
* same band rolled toward the viewer along its whole edge (+0.13).
|
|
5585
|
+
*
|
|
5586
|
+
* "Across the front" is the direction the char is changing fastest in, at
|
|
5587
|
+
* the moment the front passes. It has to be remembered: once the front has
|
|
5588
|
+
* gone by, the char behind it is uniform and says nothing about direction,
|
|
5589
|
+
* but the paper stays rolled the way it rolled.
|
|
5590
|
+
*/
|
|
5591
|
+
curlWeight;
|
|
5592
|
+
/** The mesh's index as it was built — what {@link tear} hides triangles from and mends them back to. */
|
|
5593
|
+
original = null;
|
|
5594
|
+
/** Per triangle of that index: torn, and hidden until the history is rewound. */
|
|
5595
|
+
torn = new Uint8Array(0);
|
|
5596
|
+
/** The triangles with a gone corner — the only ones that can tear. */
|
|
5597
|
+
rim = [];
|
|
5598
|
+
rimStale = true;
|
|
5599
|
+
/** Every torn triangle has to be put back: the history was rewound, or the source went. */
|
|
5600
|
+
mend = false;
|
|
5601
|
+
constructor(sim) {
|
|
5602
|
+
this.sim = sim;
|
|
5603
|
+
this.gone = new Uint8Array(sim.count);
|
|
5604
|
+
this.piece = new Int32Array(sim.count);
|
|
5605
|
+
this.parent = new Int32Array(sim.count);
|
|
5606
|
+
this.curlWeight = new Float32Array(sim.constraintCount);
|
|
5607
|
+
}
|
|
5608
|
+
/**
|
|
5609
|
+
* Bring the sim's levers up to date with `source`. Call before stepping.
|
|
5610
|
+
*
|
|
5611
|
+
* Returns whether anything was read. Free when the source has not changed,
|
|
5612
|
+
* and a null source hands every lever back at its default.
|
|
5613
|
+
*/
|
|
5614
|
+
update(source) {
|
|
5615
|
+
const next = source ?? null;
|
|
5616
|
+
if (next === this.source && (next === null || next.version === this.version)) return false;
|
|
5617
|
+
if (next !== this.source) {
|
|
5618
|
+
this.curlWeight.fill(0);
|
|
5619
|
+
if (next && this.holed) {
|
|
5620
|
+
this.rewind();
|
|
5621
|
+
this.gone.fill(0);
|
|
5622
|
+
}
|
|
5623
|
+
}
|
|
5624
|
+
this.source = next;
|
|
5625
|
+
if (!next) {
|
|
5626
|
+
this.reset();
|
|
5627
|
+
return true;
|
|
5628
|
+
}
|
|
5629
|
+
if (next.size !== this.size) this.layout(next.size);
|
|
5630
|
+
this.version = next.version;
|
|
5631
|
+
this.read(next.pixels);
|
|
5632
|
+
this.sim.wake();
|
|
5633
|
+
return true;
|
|
5634
|
+
}
|
|
5635
|
+
/**
|
|
5636
|
+
* Carry the gone particles at the edge of a hole along with the paper beside
|
|
5637
|
+
* them. Call after stepping.
|
|
5638
|
+
*
|
|
5639
|
+
* Each one goes where its live neighbours say it would be if the grid were
|
|
5640
|
+
* laid out flat around them — their positions plus its rest offset from
|
|
5641
|
+
* each, averaged. The triangles that straddle the burnt edge then keep
|
|
5642
|
+
* roughly their shape instead of stretching back to wherever the particle
|
|
5643
|
+
* was when it burnt. Only the neighbours in its own {@link piece} count, so
|
|
5644
|
+
* on a cut between two pieces it leaves with one of them.
|
|
5645
|
+
*/
|
|
5646
|
+
follow() {
|
|
5647
|
+
if (this.followers.length === 0) return;
|
|
5648
|
+
const { gone, sim } = this;
|
|
5649
|
+
const piece = this.piece;
|
|
5650
|
+
const p = sim.positions;
|
|
5651
|
+
const { cols, rows } = sim;
|
|
5652
|
+
const cellX = cols > 1 ? sim.width / (cols - 1) : 0;
|
|
5653
|
+
const cellY = rows > 1 ? sim.height / (rows - 1) : 0;
|
|
5654
|
+
for (const g of this.followers) {
|
|
5655
|
+
const r = g / cols | 0;
|
|
5656
|
+
const c = g % cols;
|
|
5657
|
+
const own = piece[g];
|
|
5658
|
+
let sx = 0;
|
|
5659
|
+
let sy = 0;
|
|
5660
|
+
let sz = 0;
|
|
5661
|
+
let n = 0;
|
|
5662
|
+
for (let dr = -1; dr <= 1; dr++) {
|
|
5663
|
+
const nr = r + dr;
|
|
5664
|
+
if (nr < 0 || nr >= rows) continue;
|
|
5665
|
+
for (let dc = -1; dc <= 1; dc++) {
|
|
5666
|
+
const nc = c + dc;
|
|
5667
|
+
if (dr === 0 && dc === 0 || nc < 0 || nc >= cols) continue;
|
|
5668
|
+
const j = nr * cols + nc;
|
|
5669
|
+
if (gone[j] || piece[j] !== own) continue;
|
|
5670
|
+
const j3 = j * 3;
|
|
5671
|
+
sx += p[j3] - dc * cellX;
|
|
5672
|
+
sy += p[j3 + 1] + dr * cellY;
|
|
5673
|
+
sz += p[j3 + 2];
|
|
5674
|
+
n++;
|
|
5675
|
+
}
|
|
5676
|
+
}
|
|
5677
|
+
if (n === 0) continue;
|
|
5678
|
+
const g3 = g * 3;
|
|
5679
|
+
p[g3] = sx / n;
|
|
5680
|
+
p[g3 + 1] = sy / n;
|
|
5681
|
+
p[g3 + 2] = sz / n;
|
|
5682
|
+
}
|
|
5683
|
+
}
|
|
5684
|
+
/**
|
|
5685
|
+
* Hide the triangles a piece has torn away from. Call after {@link follow},
|
|
5686
|
+
* with the sheet's index buffer: it is rewritten in place, and the return
|
|
5687
|
+
* says whether it changed, so the caller knows to upload it.
|
|
5688
|
+
*
|
|
5689
|
+
* A triangle tears when one of its edges has stretched past
|
|
5690
|
+
* {@link TEAR_STRETCH} — which, among triangles with a burnt-away corner,
|
|
5691
|
+
* only ever happens across a gap that is opening. It is then collapsed to a
|
|
5692
|
+
* point, which draws nothing, casts no shadow and adds nothing to its
|
|
5693
|
+
* corners' normals, and it stays hidden: torn paper does not mend because
|
|
5694
|
+
* the two sides swing close again. Only a rewound history puts it back.
|
|
5695
|
+
*
|
|
5696
|
+
* A sheet in one piece, held, never stretches one that far — so a burn that
|
|
5697
|
+
* cuts nothing loose leaves this index exactly as it was built.
|
|
5698
|
+
*/
|
|
5699
|
+
tear(index) {
|
|
5700
|
+
if (!this.holed && !this.mend) return false;
|
|
5701
|
+
if (!this.original || this.original.length !== index.length) {
|
|
5702
|
+
this.original = index.slice();
|
|
5703
|
+
this.torn = new Uint8Array(index.length / 3);
|
|
5704
|
+
this.rimStale = true;
|
|
5705
|
+
}
|
|
5706
|
+
const { original, torn, gone, sim } = this;
|
|
5707
|
+
let changed = false;
|
|
5708
|
+
if (this.mend) {
|
|
5709
|
+
this.mend = false;
|
|
5710
|
+
index.set(original);
|
|
5711
|
+
changed = true;
|
|
5712
|
+
}
|
|
5713
|
+
if (this.rimStale) {
|
|
5714
|
+
this.rimStale = false;
|
|
5715
|
+
const rim = [];
|
|
5716
|
+
for (let t = 0; t < torn.length; t++) {
|
|
5717
|
+
if (gone[original[t * 3]] || gone[original[t * 3 + 1]] || gone[original[t * 3 + 2]]) rim.push(t);
|
|
5718
|
+
}
|
|
5719
|
+
this.rim = rim;
|
|
5720
|
+
}
|
|
5721
|
+
const p = sim.positions;
|
|
5722
|
+
const { cols, rows } = sim;
|
|
5723
|
+
const cellX = cols > 1 ? sim.width / (cols - 1) : 0;
|
|
5724
|
+
const cellY = rows > 1 ? sim.height / (rows - 1) : 0;
|
|
5725
|
+
const limit = TEAR_STRETCH * TEAR_STRETCH;
|
|
5726
|
+
const stretched = (i, j) => {
|
|
5727
|
+
const dc = i % cols - j % cols;
|
|
5728
|
+
const dr = (i / cols | 0) - (j / cols | 0);
|
|
5729
|
+
const rest = (dc * cellX) ** 2 + (dr * cellY) ** 2;
|
|
5730
|
+
const i3 = i * 3;
|
|
5731
|
+
const j3 = j * 3;
|
|
5732
|
+
const now = (p[i3] - p[j3]) ** 2 + (p[i3 + 1] - p[j3 + 1]) ** 2 + (p[i3 + 2] - p[j3 + 2]) ** 2;
|
|
5733
|
+
return now > rest * limit;
|
|
5734
|
+
};
|
|
5735
|
+
for (const t of this.rim) {
|
|
5736
|
+
if (torn[t]) continue;
|
|
5737
|
+
const a = original[t * 3];
|
|
5738
|
+
const b = original[t * 3 + 1];
|
|
5739
|
+
const c = original[t * 3 + 2];
|
|
5740
|
+
if (!stretched(a, b) && !stretched(b, c) && !stretched(c, a)) continue;
|
|
5741
|
+
torn[t] = 1;
|
|
5742
|
+
index[t * 3 + 1] = a;
|
|
5743
|
+
index[t * 3 + 2] = a;
|
|
5744
|
+
changed = true;
|
|
5745
|
+
}
|
|
5746
|
+
return changed;
|
|
5747
|
+
}
|
|
5748
|
+
/** Map each particle to its texel. The damage grid's row 0 is v = 0, the sheet's BOTTOM. */
|
|
5749
|
+
layout(size) {
|
|
5750
|
+
const { cols, rows, count } = this.sim;
|
|
5751
|
+
const last = size - 1;
|
|
5752
|
+
this.size = size;
|
|
5753
|
+
this.particleTexel = new Int32Array(count);
|
|
5754
|
+
for (let r = 0; r < rows; r++) {
|
|
5755
|
+
for (let c = 0; c < cols; c++) {
|
|
5756
|
+
const u = cols > 1 ? c / (cols - 1) : 0.5;
|
|
5757
|
+
const v = rows > 1 ? 1 - r / (rows - 1) : 0.5;
|
|
5758
|
+
this.particleTexel[r * cols + c] = Math.round(v * last) * size + Math.round(u * last);
|
|
5759
|
+
}
|
|
5760
|
+
}
|
|
5761
|
+
}
|
|
5762
|
+
read(pixels) {
|
|
5763
|
+
const { sim, gone, particleTexel, curlWeight, size } = this;
|
|
5764
|
+
const {
|
|
5765
|
+
invMass,
|
|
5766
|
+
restLength,
|
|
5767
|
+
naturalLength,
|
|
5768
|
+
restBend,
|
|
5769
|
+
broken,
|
|
5770
|
+
constraintA,
|
|
5771
|
+
constraintB,
|
|
5772
|
+
constraintKind,
|
|
5773
|
+
constraintMiddle
|
|
5774
|
+
} = sim;
|
|
5775
|
+
let rewound = false;
|
|
5776
|
+
let holed = false;
|
|
5777
|
+
for (let i = 0; i < sim.count; i++) {
|
|
5778
|
+
const t = particleTexel[i] * 4;
|
|
5779
|
+
const isGone = pixels[t + PRESENCE] < GONE_BELOW;
|
|
5780
|
+
if (gone[i] && !isGone) rewound = true;
|
|
5781
|
+
if (isGone) holed = true;
|
|
5782
|
+
gone[i] = isGone ? 1 : 0;
|
|
5783
|
+
invMass[i] = isGone ? 0 : 1 / (1 + WET_MASS * pixels[t + SATURATION] / 255);
|
|
5784
|
+
}
|
|
5785
|
+
if (rewound) this.rewind();
|
|
5786
|
+
this.holed = holed;
|
|
5787
|
+
this.rimStale = true;
|
|
5788
|
+
for (let k = 0; k < sim.constraintCount; k++) {
|
|
5789
|
+
const a = constraintA[k];
|
|
5790
|
+
const b = constraintB[k];
|
|
5791
|
+
const m = constraintMiddle[k];
|
|
5792
|
+
if (gone[a] || gone[b] || m >= 0 && gone[m]) {
|
|
5793
|
+
broken[k] = 1;
|
|
5794
|
+
continue;
|
|
5795
|
+
}
|
|
5796
|
+
broken[k] = 0;
|
|
5797
|
+
let charSum = pixels[particleTexel[a] * 4 + CHAR] + pixels[particleTexel[b] * 4 + CHAR];
|
|
5798
|
+
let samples = 2;
|
|
5799
|
+
if (m >= 0) {
|
|
5800
|
+
charSum += pixels[particleTexel[m] * 4 + CHAR];
|
|
5801
|
+
samples = 3;
|
|
5802
|
+
}
|
|
5803
|
+
const char = charSum / (samples * 255);
|
|
5804
|
+
restLength[k] = naturalLength[k] * (1 - CHAR_SHRINK * char);
|
|
5805
|
+
if (constraintKind[k] === 2) {
|
|
5806
|
+
const t = particleTexel[m];
|
|
5807
|
+
const x = t % size;
|
|
5808
|
+
const y = t / size | 0;
|
|
5809
|
+
const gx = pixels[(y * size + Math.min(size - 1, x + 2)) * 4 + CHAR] - pixels[(y * size + Math.max(0, x - 2)) * 4 + CHAR];
|
|
5810
|
+
const gy = pixels[(Math.min(size - 1, y + 2) * size + x) * 4 + CHAR] - pixels[(Math.max(0, y - 2) * size + x) * 4 + CHAR];
|
|
5811
|
+
const steep = gx * gx + gy * gy;
|
|
5812
|
+
if (steep > FRONT_GRADIENT) {
|
|
5813
|
+
const across = (b - a === 2 ? gx * gx : gy * gy) / steep;
|
|
5814
|
+
if (across > curlWeight[k]) curlWeight[k] = across;
|
|
5815
|
+
}
|
|
5816
|
+
const span = naturalLength[k];
|
|
5817
|
+
restBend[k] = CHAR_CURL * char * curlWeight[k] * span * span / 8;
|
|
5818
|
+
}
|
|
5819
|
+
}
|
|
5820
|
+
this.findPieces();
|
|
5821
|
+
}
|
|
5822
|
+
/**
|
|
5823
|
+
* Which piece each particle is in ({@link piece}), and which gone ones
|
|
5824
|
+
* follow a piece at all ({@link followers}).
|
|
5825
|
+
*
|
|
5826
|
+
* Pieces by union-find over the springs that still hold, rooted at the
|
|
5827
|
+
* lowest index so the same burn names the same pieces every time. Then each
|
|
5828
|
+
* gone particle with live paper beside it joins the piece it has the most
|
|
5829
|
+
* neighbours in, the lower-named on a tie.
|
|
5830
|
+
*/
|
|
5831
|
+
findPieces() {
|
|
5832
|
+
const { sim, gone } = this;
|
|
5833
|
+
const parent = this.parent;
|
|
5834
|
+
const piece = this.piece;
|
|
5835
|
+
const { broken, constraintA, constraintB } = sim;
|
|
5836
|
+
for (let i = 0; i < sim.count; i++) parent[i] = i;
|
|
5837
|
+
const find = (i) => {
|
|
5838
|
+
let r = i;
|
|
5839
|
+
while (parent[r] !== r) r = parent[r];
|
|
5840
|
+
while (parent[i] !== r) {
|
|
5841
|
+
const up = parent[i];
|
|
5842
|
+
parent[i] = r;
|
|
5843
|
+
i = up;
|
|
5844
|
+
}
|
|
5845
|
+
return r;
|
|
5846
|
+
};
|
|
5847
|
+
for (let k = 0; k < sim.constraintCount; k++) {
|
|
5848
|
+
if (broken[k]) continue;
|
|
5849
|
+
const ra = find(constraintA[k]);
|
|
5850
|
+
const rb = find(constraintB[k]);
|
|
5851
|
+
if (ra < rb) parent[rb] = ra;
|
|
5852
|
+
else if (rb < ra) parent[ra] = rb;
|
|
5853
|
+
}
|
|
5854
|
+
for (let i = 0; i < sim.count; i++) piece[i] = gone[i] ? -1 : find(i);
|
|
5855
|
+
const followers = [];
|
|
5856
|
+
const { cols, rows } = sim;
|
|
5857
|
+
const seen = [];
|
|
5858
|
+
const votes = [];
|
|
5859
|
+
for (let i = 0; i < sim.count; i++) {
|
|
5860
|
+
if (!gone[i]) continue;
|
|
5861
|
+
const r = i / cols | 0;
|
|
5862
|
+
const c = i % cols;
|
|
5863
|
+
seen.length = 0;
|
|
5864
|
+
votes.length = 0;
|
|
5865
|
+
for (let dr = -1; dr <= 1; dr++) {
|
|
5866
|
+
const nr = r + dr;
|
|
5867
|
+
if (nr < 0 || nr >= rows) continue;
|
|
5868
|
+
for (let dc = -1; dc <= 1; dc++) {
|
|
5869
|
+
const nc = c + dc;
|
|
5870
|
+
if (nc < 0 || nc >= cols) continue;
|
|
5871
|
+
const j = nr * cols + nc;
|
|
5872
|
+
if (gone[j]) continue;
|
|
5873
|
+
const at = seen.indexOf(piece[j]);
|
|
5874
|
+
if (at < 0) {
|
|
5875
|
+
seen.push(piece[j]);
|
|
5876
|
+
votes.push(1);
|
|
5877
|
+
} else {
|
|
5878
|
+
votes[at]++;
|
|
5879
|
+
}
|
|
5880
|
+
}
|
|
5881
|
+
}
|
|
5882
|
+
if (seen.length === 0) continue;
|
|
5883
|
+
let best = 0;
|
|
5884
|
+
for (let k = 1; k < seen.length; k++) {
|
|
5885
|
+
if (votes[k] > votes[best] || votes[k] === votes[best] && seen[k] < seen[best]) best = k;
|
|
5886
|
+
}
|
|
5887
|
+
piece[i] = seen[best];
|
|
5888
|
+
followers.push(i);
|
|
5889
|
+
}
|
|
5890
|
+
this.followers = followers;
|
|
5891
|
+
}
|
|
5892
|
+
/**
|
|
5893
|
+
* A rewound history: the sheet starts over.
|
|
5894
|
+
*
|
|
5895
|
+
* Paper that had fallen away is paper again, with every spring back to the
|
|
5896
|
+
* sheet it was cut from — solved from where it lies, the solve would haul it
|
|
5897
|
+
* up off the floor through the sheet in a tangle. And the curl it rolled
|
|
5898
|
+
* into belonged to a front that has not passed yet.
|
|
5899
|
+
*/
|
|
5900
|
+
rewind() {
|
|
5901
|
+
this.sim.restore();
|
|
5902
|
+
this.curlWeight.fill(0);
|
|
5903
|
+
this.torn.fill(0);
|
|
5904
|
+
this.mend = true;
|
|
5905
|
+
}
|
|
5906
|
+
reset() {
|
|
5907
|
+
const { sim } = this;
|
|
5908
|
+
if (this.holed) this.rewind();
|
|
5909
|
+
sim.invMass.fill(1);
|
|
5910
|
+
sim.restBend.fill(0);
|
|
5911
|
+
sim.broken.fill(0);
|
|
5912
|
+
sim.restLength.set(sim.naturalLength);
|
|
5913
|
+
this.gone.fill(0);
|
|
5914
|
+
this.curlWeight.fill(0);
|
|
5915
|
+
this.followers = [];
|
|
5916
|
+
this.holed = false;
|
|
5917
|
+
this.version = -1;
|
|
5918
|
+
sim.wake();
|
|
5919
|
+
}
|
|
5920
|
+
};
|
|
5921
|
+
|
|
5341
5922
|
// src/surface/PaperMaterial.tsx
|
|
5342
|
-
import * as
|
|
5343
|
-
import { useEffect as
|
|
5923
|
+
import * as THREE7 from "three";
|
|
5924
|
+
import { useEffect as useEffect5, useMemo as useMemo3 } from "react";
|
|
5925
|
+
import { useFrame as useFrame2 } from "@react-three/fiber";
|
|
5344
5926
|
import CustomShaderMaterial from "three-custom-shader-material";
|
|
5345
5927
|
|
|
5346
5928
|
// src/surface/compose.ts
|
|
@@ -5753,6 +6335,518 @@ void plPerforation(inout vec4 color) {
|
|
|
5753
6335
|
}
|
|
5754
6336
|
`
|
|
5755
6337
|
);
|
|
6338
|
+
var DAMAGE_CHUNK = (
|
|
6339
|
+
/* glsl */
|
|
6340
|
+
`
|
|
6341
|
+
uniform sampler2D uDamage;
|
|
6342
|
+
uniform float uDamageDetail;
|
|
6343
|
+
// (edgeWave mm, edgeBite mm, sparkle, \u2013) \u2014 see DamageLook.
|
|
6344
|
+
uniform vec4 uLook3;
|
|
6345
|
+
|
|
6346
|
+
// The damage grid's texels sit ON the sheet \u2014 texel x at u = x / (N - 1), its
|
|
6347
|
+
// corners on the sheet's corners, which is how the field paints and how the
|
|
6348
|
+
// physics reads it. A texture puts texel x at (x + 0.5) / N. Sampled straight,
|
|
6349
|
+
// the picture of a burn sat up to half a texel off the burn the paper feels.
|
|
6350
|
+
vec2 plDamageUv(vec2 uv) {
|
|
6351
|
+
vec2 n = vec2(textureSize(uDamage, 0));
|
|
6352
|
+
// Texel space, on the field's convention: texel x at u = x / (N - 1).
|
|
6353
|
+
vec2 t = uv * (n - 1.0) + 0.5;
|
|
6354
|
+
// Quintic-smoothed texel coordinates.
|
|
6355
|
+
//
|
|
6356
|
+
// Bilinear filtering is continuous but its DERIVATIVE is not: the blend
|
|
6357
|
+
// weight is a straight line inside each texel and turns a corner at every
|
|
6358
|
+
// boundary. Magnify a 64 grid to fill a macro crop \u2014 one cell is 4.9 mm of
|
|
6359
|
+
// A4, about 58 px there \u2014 and those corners fall on a 58 px lattice, so
|
|
6360
|
+
// every ramp drawn from this field can crease along it.
|
|
6361
|
+
//
|
|
6362
|
+
// Also tried against the contour rings, and also not their cause (that was
|
|
6363
|
+
// the scorch fingers). Kept because it costs nothing and the crease is
|
|
6364
|
+
// real:
|
|
6365
|
+
// Bending the fractional part through a quintic (6t^5 - 15t^4 + 10t^3,
|
|
6366
|
+
// whose first and second derivatives vanish at both ends) makes the
|
|
6367
|
+
// interpolation C2 without a single extra tap: the hardware still does one
|
|
6368
|
+
// bilinear fetch, it is just asked for a smoothed position. The sample at a
|
|
6369
|
+
// texel's centre is unchanged, so the half-texel alignment this function
|
|
6370
|
+
// exists for still holds \u2014 and a field nothing has happened to reads 1
|
|
6371
|
+
// everywhere whatever the position, so the untouched-sheet identity is
|
|
6372
|
+
// untouched too.
|
|
6373
|
+
vec2 i = floor(t - 0.5) + 0.5;
|
|
6374
|
+
vec2 f = t - i;
|
|
6375
|
+
f = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);
|
|
6376
|
+
return (i + f) / n;
|
|
6377
|
+
}
|
|
6378
|
+
|
|
6379
|
+
/**
|
|
6380
|
+
* The four channels here, frayed finer than the grid.
|
|
6381
|
+
*
|
|
6382
|
+
* One cell of the field is ~3.3 mm of A4, and a burnt edge is jagged at the
|
|
6383
|
+
* scale of a fibre (0.3\u20132 mm) \u2014 so the grid decides roughly WHERE and this
|
|
6384
|
+
* decides exactly where. Stretched along the fibre (the field's default grain
|
|
6385
|
+
* runs along u), so a tear follows the grain. Scaled by what is already
|
|
6386
|
+
* there, so pristine paper (char 0, presence 1) is untouched to the bit, and
|
|
6387
|
+
* skipped outright at detail 0: the zones still draw on every tier, only
|
|
6388
|
+
* without the finest octaves.
|
|
6389
|
+
*/
|
|
6390
|
+
/**
|
|
6391
|
+
* Where on the grid this fragment reads its damage \u2014 warped.
|
|
6392
|
+
*
|
|
6393
|
+
* A burnt hole is never a circle: paper burns unevenly, and its edge wobbles
|
|
6394
|
+
* in long waves with small bites in and out. The field is too coarse to hold
|
|
6395
|
+
* that, so the READ is displaced instead: two octaves of noise in the sheet's
|
|
6396
|
+
* own space, a long wave of up to \xB16 mm and short bites of \xB12 mm. Every zone \u2014
|
|
6397
|
+
* the cut, the lip, the ember line, the char, the scorch \u2014 reads through the
|
|
6398
|
+
* same warp, so they all follow one uneven edge. Warping a field nothing has
|
|
6399
|
+
* happened to reads the same untouched value everywhere, so the identity
|
|
6400
|
+
* holds.
|
|
6401
|
+
*/
|
|
6402
|
+
vec2 plDamageWarpAt(vec2 paperUv) {
|
|
6403
|
+
vec2 p = (paperUv - 0.5) * uSheetSize;
|
|
6404
|
+
const float mm = 1.0 / 210.0;
|
|
6405
|
+
vec2 wave = vec2(plFbm(p * 26.0 + 3.1), plFbm(p * 26.0 + 17.7)) - 0.5;
|
|
6406
|
+
vec2 bite = vec2(plNoise(p * 95.0 + 5.3), plNoise(p * 95.0 + 41.9)) - 0.5;
|
|
6407
|
+
vec2 shift = (wave * 2.0 * uLook3.x + bite * 2.0 * uLook3.y) * mm;
|
|
6408
|
+
return plDamageUv(paperUv + shift / uSheetSize);
|
|
6409
|
+
}
|
|
6410
|
+
|
|
6411
|
+
vec2 plDamageWarp() {
|
|
6412
|
+
return plDamageWarpAt(vPaperUv);
|
|
6413
|
+
}
|
|
6414
|
+
|
|
6415
|
+
/** Presence softened over five taps \u2014 see \`plDamageRead\`. */
|
|
6416
|
+
float plSoftPresence(vec2 uv) {
|
|
6417
|
+
vec2 texel = 0.75 / vec2(textureSize(uDamage, 0));
|
|
6418
|
+
return (2.0 * texture2D(uDamage, uv).a
|
|
6419
|
+
+ texture2D(uDamage, uv + vec2(texel.x, 0.0)).a + texture2D(uDamage, uv - vec2(texel.x, 0.0)).a
|
|
6420
|
+
+ texture2D(uDamage, uv + vec2(0.0, texel.y)).a + texture2D(uDamage, uv - vec2(0.0, texel.y)).a) / 6.0;
|
|
6421
|
+
}
|
|
6422
|
+
|
|
6423
|
+
/** The fray and the fibre at a point of the sheet (local units), scaled by detail. */
|
|
6424
|
+
vec2 plFray(vec2 p) {
|
|
6425
|
+
return vec2(
|
|
6426
|
+
(plFbm(vec2(p.x * 22.0, p.y * 48.0)) - 0.47) * uDamageDetail,
|
|
6427
|
+
(plNoise(vec2(p.x * 140.0, p.y * 420.0)) - 0.5) * uDamageDetail
|
|
6428
|
+
);
|
|
6429
|
+
}
|
|
6430
|
+
|
|
6431
|
+
/**
|
|
6432
|
+
* The presence the cut is drawn from, at any point of the paper: warped,
|
|
6433
|
+
* softened and frayed exactly as \`plDamageRead\` does it for this fragment.
|
|
6434
|
+
*/
|
|
6435
|
+
float plDrawnPresenceAt(vec2 paperUv) {
|
|
6436
|
+
float a = plSoftPresence(plDamageWarpAt(paperUv));
|
|
6437
|
+
if (uDamageDetail > 0.0) {
|
|
6438
|
+
vec2 ff = plFray((paperUv - 0.5) * uSheetSize);
|
|
6439
|
+
a = clamp(a + (ff.x * 0.36 + ff.y * 0.05) * (1.0 - smoothstep(0.9, 1.0, a)), 0.0, 1.0);
|
|
6440
|
+
}
|
|
6441
|
+
return a;
|
|
6442
|
+
}
|
|
6443
|
+
|
|
6444
|
+
// The softened presence before the fray, from the last \`plDamageRead\`: what
|
|
6445
|
+
// the distance to the cut takes its slope from (see \`plDamage\`).
|
|
6446
|
+
float plDamageSmooth;
|
|
6447
|
+
|
|
6448
|
+
vec4 plDamageRead() {
|
|
6449
|
+
vec2 uv = plDamageWarp();
|
|
6450
|
+
vec4 d = texture2D(uDamage, uv);
|
|
6451
|
+
// The cut runs on a SOFTENED presence. Paper burns through within a single
|
|
6452
|
+
// texel now, so the raw field is hard-edged and its half-presence contour
|
|
6453
|
+
// is the grid's own staircase \u2014 the fray below can only move a line within
|
|
6454
|
+
// the band it is given, and a one-texel band is not enough to hide a grid.
|
|
6455
|
+
// Five taps round the contour; an untouched field is 1 everywhere and
|
|
6456
|
+
// stays exactly 1.
|
|
6457
|
+
d.a = plSoftPresence(uv);
|
|
6458
|
+
plDamageSmooth = d.a;
|
|
6459
|
+
if (uDamageDetail > 0.0) {
|
|
6460
|
+
vec2 ff = plFray(plLocal());
|
|
6461
|
+
float fray = ff.x;
|
|
6462
|
+
float fibre = ff.y;
|
|
6463
|
+
float edge = 1.0 - smoothstep(0.9, 1.0, d.a);
|
|
6464
|
+
d.r = clamp(d.r + fray * 0.3 * smoothstep(0.0, 0.15, d.r), 0.0, 1.0);
|
|
6465
|
+
d.a = clamp(d.a + (fray * 0.36 + fibre * 0.05) * edge, 0.0, 1.0);
|
|
6466
|
+
}
|
|
6467
|
+
return d;
|
|
6468
|
+
}
|
|
6469
|
+
|
|
6470
|
+
void plDamageCut(inout vec4 color, vec4 d) {
|
|
6471
|
+
// Missing paper: gone at half presence, on every stock alike.
|
|
6472
|
+
color.a *= step(0.5, d.a);
|
|
6473
|
+
}
|
|
6474
|
+
`
|
|
6475
|
+
);
|
|
6476
|
+
var DAMAGE_SHADE_CHUNK = (
|
|
6477
|
+
/* glsl */
|
|
6478
|
+
`
|
|
6479
|
+
uniform float uDamageTime;
|
|
6480
|
+
// The look, packed \u2014 see DamageLook for what each number means.
|
|
6481
|
+
uniform vec4 uLook0; // emberWidth mm, emberIntensity, emberCoverage, emberFlicker
|
|
6482
|
+
uniform vec4 uLook1; // emberGlow, lipWidth mm, lipBrightness, charWarmth
|
|
6483
|
+
uniform vec4 uLook2; // charCracks, scorchReach mm, scorchDarkness, fingers
|
|
6484
|
+
/** The light the burn gives off from the sheet itself: the ember line, and nothing else. */
|
|
6485
|
+
vec3 plDamageLight;
|
|
6486
|
+
|
|
6487
|
+
// World units per millimetre: a default sheet is one unit across, and A4 is 210 mm.
|
|
6488
|
+
const float PL_MM = 1.0 / 210.0;
|
|
6489
|
+
|
|
6490
|
+
vec3 plLinear(vec3 srgb) {
|
|
6491
|
+
return pow(srgb, vec3(2.2));
|
|
6492
|
+
}
|
|
6493
|
+
|
|
6494
|
+
/**
|
|
6495
|
+
* World up, as a direction in the sheet's UV, from how this fragment's UV and
|
|
6496
|
+
* view position change across the screen. Must be called in uniform control
|
|
6497
|
+
* flow \u2014 it is all derivatives.
|
|
6498
|
+
*/
|
|
6499
|
+
vec2 plUpUv() {
|
|
6500
|
+
vec3 dpx = dFdx(-vViewPosition);
|
|
6501
|
+
vec3 dpy = dFdy(-vViewPosition);
|
|
6502
|
+
vec2 dux = dFdx(vPaperUv);
|
|
6503
|
+
vec2 duy = dFdy(vPaperUv);
|
|
6504
|
+
float det = dux.x * duy.y - dux.y * duy.x;
|
|
6505
|
+
if (abs(det) < 1e-14) return vec2(0.0, 1.0);
|
|
6506
|
+
vec3 dPdu = (dpx * duy.y - dpy * dux.y) / det;
|
|
6507
|
+
vec3 dPdv = (dpy * dux.x - dpx * duy.x) / det;
|
|
6508
|
+
vec3 up = (viewMatrix * vec4(0.0, 1.0, 0.0, 0.0)).xyz;
|
|
6509
|
+
vec2 g = vec2(dot(up, dPdu) / max(dot(dPdu, dPdu), 1e-12), dot(up, dPdv) / max(dot(dPdv, dPdv), 1e-12));
|
|
6510
|
+
float l = length(g);
|
|
6511
|
+
return l > 1e-6 ? g / l : vec2(0.0, 1.0);
|
|
6512
|
+
}
|
|
6513
|
+
|
|
6514
|
+
/** F1 and F2 of a cell noise \u2014 the crack network in char. */
|
|
6515
|
+
vec2 plVoronoi(vec2 p) {
|
|
6516
|
+
vec2 i = floor(p);
|
|
6517
|
+
vec2 f = fract(p);
|
|
6518
|
+
float f1 = 8.0;
|
|
6519
|
+
float f2 = 8.0;
|
|
6520
|
+
for (int y = -1; y <= 1; y++) {
|
|
6521
|
+
for (int x = -1; x <= 1; x++) {
|
|
6522
|
+
vec2 o = vec2(float(x), float(y));
|
|
6523
|
+
vec2 r = o + vec2(plHash(i + o), plHash(i + o + 17.31)) - f;
|
|
6524
|
+
float dd = dot(r, r);
|
|
6525
|
+
if (dd < f1) {
|
|
6526
|
+
f2 = f1;
|
|
6527
|
+
f1 = dd;
|
|
6528
|
+
} else if (dd < f2) {
|
|
6529
|
+
f2 = dd;
|
|
6530
|
+
}
|
|
6531
|
+
}
|
|
6532
|
+
}
|
|
6533
|
+
return sqrt(vec2(f1, f2));
|
|
6534
|
+
}
|
|
6535
|
+
|
|
6536
|
+
/** The scorch ramp, sampled off Scorch.png and Hero.png, as a tint over paper. 0 is the leading edge. */
|
|
6537
|
+
vec3 plScorchTint(float t) {
|
|
6538
|
+
vec3 paper = plLinear(vec3(0.90, 0.885, 0.855));
|
|
6539
|
+
vec3 c0 = plLinear(vec3(0.843, 0.741, 0.569)); // #D7BD91
|
|
6540
|
+
vec3 c1 = plLinear(vec3(0.745, 0.514, 0.263)); // #BE8343
|
|
6541
|
+
vec3 c2 = plLinear(vec3(0.631, 0.384, 0.180)); // #A1622E
|
|
6542
|
+
vec3 c3 = plLinear(vec3(0.541, 0.310, 0.141)); // #8A4F24
|
|
6543
|
+
vec3 c4 = plLinear(vec3(0.471, 0.275, 0.149)); // #784626
|
|
6544
|
+
vec3 c5 = plLinear(vec3(0.380, 0.216, 0.133)); // #613722
|
|
6545
|
+
vec3 c6 = plLinear(vec3(0.310, 0.200, 0.137)); // #4F3323
|
|
6546
|
+
float s = clamp(t, 0.0, 1.0) * 6.0;
|
|
6547
|
+
// Each stop is blended with a SMOOTH step, not a linear one.
|
|
6548
|
+
//
|
|
6549
|
+
// Seven colours joined by six straight segments is a ramp with a corner at
|
|
6550
|
+
// every joint: the colour is continuous there but its rate of change is
|
|
6551
|
+
// not, and the eye reads a discontinuous gradient as a faint line (Mach
|
|
6552
|
+
// banding). smoothstep's derivative is zero at both ends of a segment, so
|
|
6553
|
+
// consecutive segments meet with matching slope and there is no corner.
|
|
6554
|
+
//
|
|
6555
|
+
// Cheap insurance, not a bug fix \u2014 this was tried first against the macro
|
|
6556
|
+
// crops' contour rings and did NOT remove them; the cause turned out to be
|
|
6557
|
+
// the scorch fingers below, sampled in a frame that follows the contours.
|
|
6558
|
+
float k = smoothstep(0.0, 1.0, clamp(s, 0.0, 1.0));
|
|
6559
|
+
vec3 c = mix(c0, c1, k);
|
|
6560
|
+
c = mix(c, c2, smoothstep(0.0, 1.0, clamp(s - 1.0, 0.0, 1.0)));
|
|
6561
|
+
c = mix(c, c3, smoothstep(0.0, 1.0, clamp(s - 2.0, 0.0, 1.0)));
|
|
6562
|
+
c = mix(c, c4, smoothstep(0.0, 1.0, clamp(s - 3.0, 0.0, 1.0)));
|
|
6563
|
+
c = mix(c, c5, smoothstep(0.0, 1.0, clamp(s - 4.0, 0.0, 1.0)));
|
|
6564
|
+
c = mix(c, c6, smoothstep(0.0, 1.0, clamp(s - 5.0, 0.0, 1.0)));
|
|
6565
|
+
return min(c / paper, vec3(1.0));
|
|
6566
|
+
}
|
|
6567
|
+
|
|
6568
|
+
/** The ember ramp (\xA75 zone 3): off \u2192 #870E03 \u2192 #A12108 \u2192 #F77E14 \u2192 #FEFBE0, hottest last. */
|
|
6569
|
+
vec3 plEmberRamp(float t) {
|
|
6570
|
+
vec3 c0 = plLinear(vec3(0.529, 0.055, 0.012));
|
|
6571
|
+
vec3 c1 = plLinear(vec3(0.631, 0.129, 0.031));
|
|
6572
|
+
vec3 c2 = plLinear(vec3(0.969, 0.494, 0.078));
|
|
6573
|
+
vec3 c3 = plLinear(vec3(0.996, 0.984, 0.878));
|
|
6574
|
+
vec3 c = mix(c0, c1, smoothstep(0.0, 0.3, t));
|
|
6575
|
+
c = mix(c, c2, smoothstep(0.3, 0.7, t));
|
|
6576
|
+
return mix(c, c3, smoothstep(0.75, 1.0, t));
|
|
6577
|
+
}
|
|
6578
|
+
|
|
6579
|
+
void plDamage(inout vec4 color, inout float roughness) {
|
|
6580
|
+
plDamageLight = vec3(0.0);
|
|
6581
|
+
vec4 d = plDamageRead();
|
|
6582
|
+
vec2 uv = plDamageWarp();
|
|
6583
|
+
vec2 texel = 1.0 / vec2(textureSize(uDamage, 0));
|
|
6584
|
+
|
|
6585
|
+
// Everything measured off screen derivatives, up front: derivatives are
|
|
6586
|
+
// undefined past a branch that only some fragments of a quad take.
|
|
6587
|
+
vec2 upUv = plUpUv();
|
|
6588
|
+
vec4 e = texture2D(uDamage, uv + vec2(texel.x, 0.0));
|
|
6589
|
+
vec4 w = texture2D(uDamage, uv - vec2(texel.x, 0.0));
|
|
6590
|
+
vec4 n = texture2D(uDamage, uv + vec2(0.0, texel.y));
|
|
6591
|
+
vec4 s = texture2D(uDamage, uv - vec2(0.0, texel.y));
|
|
6592
|
+
// Per world unit: one texel step is 1 / (N - 1) of the sheet.
|
|
6593
|
+
vec2 perWorld = (vec2(textureSize(uDamage, 0)) - 1.0) / uSheetSize;
|
|
6594
|
+
vec2 gradP = vec2(e.a - w.a, n.a - s.a) * 0.5 * perWorld;
|
|
6595
|
+
vec2 gradC = vec2(e.r - w.r, n.r - s.r) * 0.5 * perWorld;
|
|
6596
|
+
// Millimetres from the cut, on the paper side \u2014 measured against the field
|
|
6597
|
+
// the cut is DRAWN from (softened, frayed, warped), through its own screen
|
|
6598
|
+
// derivatives. It used to divide by the slope of the raw texels, and since
|
|
6599
|
+
// paper burns through within a texel that slope is a cliff while the drawn
|
|
6600
|
+
// edge is spread over millimetres: a point well into the char computed as a
|
|
6601
|
+
// hair from the cut, and the ember line drew there \u2014 found by the gate.
|
|
6602
|
+
// The slope, though, is the SMOOTH presence's. The fray's fibre noise
|
|
6603
|
+
// changes pixel to pixel, and its slope is a cliff everywhere it touches:
|
|
6604
|
+
// divided by that, a point deep in the char came out a hair from the cut
|
|
6605
|
+
// and the ember glow drew there \u2014 the gate's stray pixels. The fray still
|
|
6606
|
+
// moves where the edge is; it just cannot fake how steep it is.
|
|
6607
|
+
float smoothA = plDamageSmooth;
|
|
6608
|
+
vec2 cutSlope = vec2(dFdx(smoothA), dFdy(smoothA));
|
|
6609
|
+
float pxFromCut = (d.a - 0.5) / max(length(cutSlope), 1e-4);
|
|
6610
|
+
// Millimetres per pixel ALONG the way to the cut, not across the screen's
|
|
6611
|
+
// x: a sheet leaning away is foreshortened vertically, so above and below a
|
|
6612
|
+
// hole a pixel covers more paper than beside it, and a scale taken from x
|
|
6613
|
+
// alone drew the ember zone too many pixels deep on the top and bottom rims.
|
|
6614
|
+
vec2 toCutPx = cutSlope / max(length(cutSlope), 1e-6);
|
|
6615
|
+
float mmPerPx = length(dFdx(-vViewPosition) * toCutPx.x + dFdy(-vViewPosition) * toCutPx.y) / PL_MM;
|
|
6616
|
+
float mm = pxFromCut * mmPerPx;
|
|
6617
|
+
// About a pixel of antialiasing, never more: where the warp makes the
|
|
6618
|
+
// estimate jump between neighbours, fwidth(mm) is millimetres wide, and the
|
|
6619
|
+
// ember's soft tail \u2014 faint, but multiplied by its light \u2014 drew stray glow
|
|
6620
|
+
// well out into the char.
|
|
6621
|
+
float aa = min(fwidth(mm), 1.5 * mmPerPx);
|
|
6622
|
+
// Scorch reaches ahead of the char, and further UP than anywhere else:
|
|
6623
|
+
// the char a centimetre below this point, and the char a few millimetres
|
|
6624
|
+
// around it, each lent to the scorch at a discount (\xA74.2, \xA74.5).
|
|
6625
|
+
vec2 mmUv = vec2(PL_MM) / uSheetSize;
|
|
6626
|
+
float charBelow = max(texture2D(uDamage, uv - upUv * uLook2.y * mmUv).r, texture2D(uDamage, uv - upUv * 0.5 * uLook2.y * mmUv).r);
|
|
6627
|
+
// A ring of eight, averaged: four diagonal maxima drew the grid back in as
|
|
6628
|
+
// squares wherever the scorch was magnified.
|
|
6629
|
+
float charWide = 0.0;
|
|
6630
|
+
for (int k = 0; k < 8; k++) {
|
|
6631
|
+
float a = float(k) * 0.7853982;
|
|
6632
|
+
charWide += texture2D(uDamage, uv + vec2(cos(a), sin(a)) * 7.0 * mmUv).r;
|
|
6633
|
+
}
|
|
6634
|
+
charWide /= 8.0;
|
|
6635
|
+
// Only paper that has a CUT beside it has an edge: a half-eaten cell out
|
|
6636
|
+
// in the middle of the sheet reads as "half a presence" too, and without
|
|
6637
|
+
// this the ember line and the ash lip drew on it \u2014 islands of glow on
|
|
6638
|
+
// paper, the painted glow by another route.
|
|
6639
|
+
// Eased in, not switched: a hard threshold on the neighbouring texels drew
|
|
6640
|
+
// the grid back into the ember glow as small squares.
|
|
6641
|
+
float cutNear = 1.0 - smoothstep(0.3, 0.62, min(min(e.a, w.a), min(n.a, s.a)));
|
|
6642
|
+
// And the cut has to really be there. \`mm\` extrapolates this pixel's
|
|
6643
|
+
// slope to half presence, and the uneven edge's warp bends the field inside
|
|
6644
|
+
// those few millimetres \u2014 measured: with the warp off the stray ember light
|
|
6645
|
+
// the gate finds all but vanishes. So step across the PAPER toward where the
|
|
6646
|
+
// cut should be, half a millimetre past it but never beyond the ember
|
|
6647
|
+
// zone's own outer edge (the lip and the widest bead \u2014 2.5 mm at the
|
|
6648
|
+
// spec's numbers, \xA75) plus that half, and read what the cut is drawn from.
|
|
6649
|
+
// Still paper: this is not an edge, however close the slope said it was.
|
|
6650
|
+
vec2 toCutPaper = -(dFdx(vPaperUv) * toCutPx.x + dFdy(vPaperUv) * toCutPx.y);
|
|
6651
|
+
float reachPx = min(max(mm, 0.0) + 0.5, uLook1.y + uLook0.x + 0.5) / max(mmPerPx, 1e-6);
|
|
6652
|
+
float pastA = plDrawnPresenceAt(vPaperUv + toCutPaper * reachPx);
|
|
6653
|
+
// And a hole, not a pinhole: the fray opens specks of void finer than a
|
|
6654
|
+
// pixel in part-burnt paper, which the check above would take for the
|
|
6655
|
+
// edge. The void has to go on for another half millimetre.
|
|
6656
|
+
float beyondA = plDrawnPresenceAt(vPaperUv + toCutPaper * (reachPx + 0.5 / max(mmPerPx, 1e-6)));
|
|
6657
|
+
cutNear *= (1.0 - smoothstep(0.42, 0.55, pastA)) * (1.0 - smoothstep(0.42, 0.55, beyondA));
|
|
6658
|
+
|
|
6659
|
+
// An untouched texel is left exactly as it was \u2014 the identity the whole
|
|
6660
|
+
// seam is built on. Nothing here has happened to this paper.
|
|
6661
|
+
if (d.r <= 0.0 && d.g <= 0.0 && d.a >= 1.0 && charBelow <= 0.0 && charWide <= 0.0 && e.a >= 1.0 && w.a >= 1.0 && n.a >= 1.0 && s.a >= 1.0) {
|
|
6662
|
+
return;
|
|
6663
|
+
}
|
|
6664
|
+
|
|
6665
|
+
vec2 p = plLocal();
|
|
6666
|
+
|
|
6667
|
+
// Wet paper is darker and smoother: water fills the gaps between fibres
|
|
6668
|
+
// that scatter light, which is both effects from one cause.
|
|
6669
|
+
color.rgb *= 1.0 - 0.38 * d.g;
|
|
6670
|
+
roughness *= 1.0 - 0.45 * d.g;
|
|
6671
|
+
|
|
6672
|
+
// \u2500\u2500 Scorch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6673
|
+
// Fingers: irregular teeth ALONG the scorch front, plus the grain's own
|
|
6674
|
+
// streaks. Measured along the front's tangent so they point outward.
|
|
6675
|
+
vec2 tangentC = length(gradC) > 1e-4 ? normalize(vec2(-gradC.y, gradC.x)) : vec2(1.0, 0.0);
|
|
6676
|
+
float along = dot(p, tangentC) / (4.0 * PL_MM);
|
|
6677
|
+
// Only where there really IS a front.
|
|
6678
|
+
//
|
|
6679
|
+
// This noise is sampled in a frame built from the char gradient, so that
|
|
6680
|
+
// its teeth run across the front wherever the front happens to point. That
|
|
6681
|
+
// works at the front and is meaningless behind it: a few millimetres into
|
|
6682
|
+
// the char the gradient is almost flat, its direction is whatever the
|
|
6683
|
+
// eight-bit field rounded to, and the frame turns with position \u2014 which
|
|
6684
|
+
// makes the noise a function OF THE CONTOURS rather than of the sheet. It
|
|
6685
|
+
// drew closed loops following the edge, nested a cell apart: the
|
|
6686
|
+
// "concentric contour lines, like a topographic map or tree rings" the
|
|
6687
|
+
// review found at macro range and left unexplained. Turning this one term
|
|
6688
|
+
// off removes them completely and changes nothing else.
|
|
6689
|
+
//
|
|
6690
|
+
// Measured on the scripted burn's peak: the gradient runs to about 19 at
|
|
6691
|
+
// the front and under 1 a few millimetres behind it.
|
|
6692
|
+
float frontness = smoothstep(1.0, 6.0, length(gradC));
|
|
6693
|
+
float fingers = (plFbm(vec2(along, dot(p, vec2(tangentC.y, -tangentC.x)) / (12.0 * PL_MM))) - 0.5) * 0.28 * frontness
|
|
6694
|
+
+ (plFbm(vec2(p.x * 9.0, p.y * 30.0)) - 0.5) * 0.08;
|
|
6695
|
+
fingers *= uLook2.w;
|
|
6696
|
+
// Fingers on the borrowed scorch too, so the outer edge is irregular
|
|
6697
|
+
// teeth rather than a smooth offset of the char.
|
|
6698
|
+
float reach = 0.75 + fingers * 4.0;
|
|
6699
|
+
// The scorch reads a softened char \u2014 its own texel and the four around it
|
|
6700
|
+
// \u2014 so a bilinear 64\xB2 field magnified shows no contours or corners. The
|
|
6701
|
+
// char zone keeps the sharp one: its edge is meant to be crisp.
|
|
6702
|
+
float soft = (2.0 * d.r + e.r + w.r + n.r + s.r) / 6.0;
|
|
6703
|
+
float self_ = clamp(mix(d.r, soft, 0.65) + fingers * smoothstep(0.0, 0.08, d.r), 0.0, 1.0);
|
|
6704
|
+
float c = 1.0 - (1.0 - self_) * (1.0 - clamp(0.62 * charBelow * reach, 0.0, 1.0)) * (1.0 - clamp(0.4 * charWide * reach, 0.0, 1.0));
|
|
6705
|
+
// Fibre-scale grain in the scorch, so a smooth field magnified never shows
|
|
6706
|
+
// its contours \u2014 real scorch is fibrous (Scorch.png), not banded.
|
|
6707
|
+
c = clamp(c + ((plNoise(p * 300.0) - 0.5) * 0.07 + (plNoise(p * 90.0) - 0.5) * 0.06) * smoothstep(0.0, 0.1, c), 0.0, 1.0);
|
|
6708
|
+
// A dither of about one and a half of the damage texture's own levels.
|
|
6709
|
+
//
|
|
6710
|
+
// The field arrives as eight bits a channel, so char comes in steps of
|
|
6711
|
+
// 1/255 \u2014 and the leading edge below spans 0.03 of it, which is seven and a
|
|
6712
|
+
// half steps. The fibre grain above would have hidden them, but it is
|
|
6713
|
+
// multiplied by smoothstep(0, 0.1, c), which is approximately zero exactly
|
|
6714
|
+
// where the leading edge lives. This is not: it applies everywhere, and
|
|
6715
|
+
// being finer than a level it can only ever move a value into the
|
|
6716
|
+
// neighbouring one.
|
|
6717
|
+
c = clamp(c + (plNoise(p * 900.0) - 0.5) * (1.5 / 255.0), 0.0, 1.0);
|
|
6718
|
+
// A steep leading edge \u2014 paper to straw in a sliver \u2014 then the long ramp.
|
|
6719
|
+
float lead = smoothstep(0.02, 0.05, c);
|
|
6720
|
+
float ramp = smoothstep(0.05, 0.7, c);
|
|
6721
|
+
color.rgb *= mix(vec3(1.0), pow(plScorchTint(ramp), vec3(uLook2.z)), lead);
|
|
6722
|
+
|
|
6723
|
+
// \u2500\u2500 Char \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6724
|
+
// A wide, smooth hand-over from scorch to char \u2014 the gradient is the point.
|
|
6725
|
+
float charZone = smoothstep(0.5, 0.9, c);
|
|
6726
|
+
vec2 cells = plVoronoi(p / (3.6 * PL_MM));
|
|
6727
|
+
// Sparse: a real crack network is broken, not a tiled floor \u2014 most cell
|
|
6728
|
+
// borders never split.
|
|
6729
|
+
float crack = (1.0 - smoothstep(0.008, 0.03, cells.y - cells.x)) * smoothstep(0.45, 0.65, plNoise(p / (7.0 * PL_MM)));
|
|
6730
|
+
float plate = plHash(floor(p / (3.6 * PL_MM)) + 3.7);
|
|
6731
|
+
// Dark orange where the char meets the scorch, deep brown toward the cut \u2014
|
|
6732
|
+
// warm all the way, and fading smoothly into the scorch's lighter browns
|
|
6733
|
+
// rather than stopping at a grey band.
|
|
6734
|
+
// Warm (dark orange \u2192 deep brown) or, turned down, the sampled greys.
|
|
6735
|
+
vec3 body = mix(plLinear(vec3(0.255, 0.239, 0.227)), plLinear(vec3(0.165, 0.086, 0.043)), uLook1.w);
|
|
6736
|
+
vec3 under = mix(plLinear(vec3(0.275, 0.169, 0.090)), plLinear(vec3(0.42, 0.18, 0.06)), uLook1.w);
|
|
6737
|
+
vec3 cracks = plLinear(vec3(0.039, 0.024, 0.016)); // #0A0604
|
|
6738
|
+
vec3 charColor = mix(under, body, smoothstep(0.66, 1.0, c));
|
|
6739
|
+
// Mottled at the plate scale and finer, and darker than the sampled body
|
|
6740
|
+
// under a bright key \u2014 the reference's char reads near black in the frame.
|
|
6741
|
+
charColor *= (0.75 + 0.35 * plate) * (0.8 + 0.4 * plNoise(p * 260.0));
|
|
6742
|
+
charColor = mix(charColor, cracks, crack * uLook2.x);
|
|
6743
|
+
color.rgb = mix(color.rgb, charColor, charZone);
|
|
6744
|
+
roughness = mix(roughness, 0.6, charZone);
|
|
6745
|
+
// Blistered, and each plate tilted its own way, so a raking light breaks
|
|
6746
|
+
// the char into plates rather than lighting it as one sheet of black.
|
|
6747
|
+
plHeight += charZone * ((plNoise(p * 380.0) - 0.5) * 0.00008 + (plate - 0.5) * 0.00018 - crack * 0.00016);
|
|
6748
|
+
|
|
6749
|
+
// \u2500\u2500 Ash lip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6750
|
+
// Inside the spec's zones (\xA75): the lip 0.5\u20131.5 mm, then the ember line
|
|
6751
|
+
// 0.3\u20131 mm \u2014 2.5 mm from the cut at most, which the gate holds it to.
|
|
6752
|
+
// Along the rim. Both the lip and the ember line below are laid out on this,
|
|
6753
|
+
// and unlike the scorch fingers above it is safe to sample noise in: it is
|
|
6754
|
+
// used ONLY within a couple of millimetres of the cut, where the presence
|
|
6755
|
+
// gradient is the actual edge and points somewhere real. The fingers'
|
|
6756
|
+
// mistake was carrying the same idea out into flat char, where the gradient
|
|
6757
|
+
// is rounding error and a noise sampled in its frame draws the contours.
|
|
6758
|
+
vec2 tangentP = length(gradP) > 1e-4 ? normalize(vec2(-gradP.y, gradP.x)) : vec2(1.0, 0.0);
|
|
6759
|
+
|
|
6760
|
+
float lipWidth = mix(0.4, max(uLook1.y, 0.41), plFbm(p / (2.5 * PL_MM)));
|
|
6761
|
+
// Broken, not a hem. Ash flakes off a cooling edge in pieces, so the lip
|
|
6762
|
+
// comes and goes along the rim and wanders in width where it is there. It
|
|
6763
|
+
// used to be a continuous band of even width with a highlight down the
|
|
6764
|
+
// middle of it, which read as a neon tube round the hole \u2014 the single most
|
|
6765
|
+
// synthetic thing at wide view.
|
|
6766
|
+
float lipAlong = dot(p, tangentP) / (3.2 * PL_MM);
|
|
6767
|
+
float lipBreak = smoothstep(0.34, 0.56, plFbm(vec2(lipAlong, 0.9)));
|
|
6768
|
+
float lip = (1.0 - smoothstep(lipWidth - aa, lipWidth + aa, mm)) * step(d.a, 0.999) * cutNear * lipBreak;
|
|
6769
|
+
vec3 ash = plLinear(mix(vec3(0.525, 0.498, 0.490), vec3(0.741, 0.725, 0.725), plNoise(p * 520.0) * 0.6 + plNoise(p * 90.0) * 0.4)) * uLook1.z;
|
|
6770
|
+
color.rgb = mix(color.rgb, ash, lip);
|
|
6771
|
+
roughness = mix(roughness, 0.99, lip);
|
|
6772
|
+
// Barely raised. THIS is where the highlight came from: a 0.7 mm ridge
|
|
6773
|
+
// running round the hole is a cylinder, and a cylinder under a key light
|
|
6774
|
+
// has a specular line down it however rough the surface is. Ash is a
|
|
6775
|
+
// fragile crust a fraction of a millimetre proud of the paper, not a bead
|
|
6776
|
+
// of solder \u2014 and the shape of it belongs to the broken edge above, not to
|
|
6777
|
+
// a smooth ramp.
|
|
6778
|
+
plHeight += lip * (1.0 - mm / max(lipWidth, 0.1)) * 0.00018;
|
|
6779
|
+
|
|
6780
|
+
// \u2500\u2500 The ember line \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6781
|
+
// Beads along the cut: position along the edge, crawling on the burn's
|
|
6782
|
+
// clock, with a flicker of their own. Only where it is hot \u2014 and as the
|
|
6783
|
+
// heat goes the threshold rises, so the line breaks into fewer, dimmer
|
|
6784
|
+
// beads and they go out one by one.
|
|
6785
|
+
float s1 = dot(p, tangentP) / (2.4 * PL_MM) - uDamageTime * 0.55 * uLook0.w;
|
|
6786
|
+
float bead = plNoise(vec2(s1, 0.37)) * 0.7 + plNoise(vec2(s1 * 2.3, 5.1)) * 0.3;
|
|
6787
|
+
float flicker = 0.62 + 0.38 * plNoise(vec2(floor(s1) * 7.13, uDamageTime * 9.0 * uLook0.w));
|
|
6788
|
+
// Down to a smoulder's last flicker: a spent edge keeps a little heat in
|
|
6789
|
+
// the texture (\`Afterglow\`), and a bead should glow dim red on it rather
|
|
6790
|
+
// than vanish while the edge still has something to give.
|
|
6791
|
+
// Heat softened like the cut (its texel and the four around it), so the
|
|
6792
|
+
// beads and the crimson glow follow the burn rather than the grid's cells.
|
|
6793
|
+
float heatSoft = (2.0 * d.b + e.b + w.b + n.b + s.b) / 6.0;
|
|
6794
|
+
float hot = smoothstep(0.012, 0.55, heatSoft);
|
|
6795
|
+
// Coverage slides the threshold: more of the edge lit, or less.
|
|
6796
|
+
float shift = (uLook0.z - 0.5) * 0.5;
|
|
6797
|
+
float lit = smoothstep(mix(0.82, 0.45, hot) - shift, mix(0.9, 0.58, hot) - shift, bead);
|
|
6798
|
+
// Each bead tapers at its ends \u2014 a bead, not a dash cut from a strip.
|
|
6799
|
+
float width = max(mix(0.4, max(uLook0.x, 0.41), plNoise(vec2(s1 * 0.7, 9.0))) * (0.35 + 0.65 * lit), 1.4 * aa);
|
|
6800
|
+
float band = smoothstep(lipWidth - aa, lipWidth + aa, mm) * (1.0 - smoothstep(lipWidth + width - aa, lipWidth + width + aa, mm)) * cutNear;
|
|
6801
|
+
// Mostly orange: a bead is red at its ends and yellow-white only at its
|
|
6802
|
+
// hottest core, which is what Ember_line.png shows. Mapped low and pushed
|
|
6803
|
+
// high by a power, so white is the exception rather than the line.
|
|
6804
|
+
// Orange through, yellow-white at the hottest cores. Dim red alone reads
|
|
6805
|
+
// salmon once the curve desaturates it, so the ramp starts at orange and
|
|
6806
|
+
// only the cooling (low heat) falls back into red.
|
|
6807
|
+
float t = hot * flicker * (0.45 + 0.55 * lit);
|
|
6808
|
+
color.rgb = mix(color.rgb, plLinear(vec3(0.12, 0.04, 0.02)), band * lit);
|
|
6809
|
+
// Saturated orange at orange brightness; only the hottest cores are pushed
|
|
6810
|
+
// high enough for the curve to roll them to white.
|
|
6811
|
+
plDamageLight = plEmberRamp(t) * mix(0.8, 6.0, t * t * t * t) * band * lit * hot * uLook0.y;
|
|
6812
|
+
|
|
6813
|
+
// The rest of a real burning edge (Ember_line.png): a dim crimson glow in
|
|
6814
|
+
// the char beside the beads \u2014 patches of it, drifting \u2014 and specks of
|
|
6815
|
+
// glowing fibre that come and go. Both stay under the bloom threshold, so
|
|
6816
|
+
// they read as heat in the char rather than as light; both only where the
|
|
6817
|
+
// edge is hot, and inside the zone the beads are held to \u2014 the lip plus
|
|
6818
|
+
// the widest bead, measured from the cut \u2014 so the glow never reaches out
|
|
6819
|
+
// over paper the gate says heat must not light.
|
|
6820
|
+
float zoneEnd = uLook1.y + uLook0.x;
|
|
6821
|
+
float seam = smoothstep(lipWidth - aa, lipWidth + aa, mm) * (1.0 - smoothstep(zoneEnd - 0.6, zoneEnd, mm)) * cutNear;
|
|
6822
|
+
float patch_ = smoothstep(0.35, 0.72, plFbm(p / (4.0 * PL_MM) + vec2(uDamageTime * 0.15 * uLook0.w, 0.0)));
|
|
6823
|
+
plDamageLight += vec3(0.55, 0.06, 0.01) * seam * patch_ * hot * uLook1.x;
|
|
6824
|
+
float speck = step(0.985, plHash(floor(p / (0.35 * PL_MM)) + floor(uDamageTime * 8.0 * uLook0.w)));
|
|
6825
|
+
plDamageLight += plEmberRamp(0.9) * 1.6 * speck * seam * hot * uLook3.z;
|
|
6826
|
+
|
|
6827
|
+
plDamageCut(color, d);
|
|
6828
|
+
}
|
|
6829
|
+
`
|
|
6830
|
+
);
|
|
6831
|
+
function carveDepthHelpers(helpers) {
|
|
6832
|
+
const darken = "uniform float uBackDarken;\n";
|
|
6833
|
+
const relief = helpers.indexOf("/**\n * The paper's relief");
|
|
6834
|
+
const bell = helpers.lastIndexOf("/**", helpers.indexOf("float plBell("));
|
|
6835
|
+
if (!helpers.includes(darken) || relief < 0 || bell <= relief) {
|
|
6836
|
+
throw new Error("compose: HELPERS changed shape \u2014 update carveDepthHelpers");
|
|
6837
|
+
}
|
|
6838
|
+
return (helpers.slice(0, relief) + helpers.slice(bell)).replace(darken, "");
|
|
6839
|
+
}
|
|
6840
|
+
var DEPTH_HELPERS = carveDepthHelpers(HELPERS);
|
|
6841
|
+
var DEPTH_VERTEX = (
|
|
6842
|
+
/* glsl */
|
|
6843
|
+
`
|
|
6844
|
+
varying vec2 vPaperUv;
|
|
6845
|
+
void main() {
|
|
6846
|
+
vPaperUv = uv;
|
|
6847
|
+
}
|
|
6848
|
+
`
|
|
6849
|
+
);
|
|
5756
6850
|
var AGING_CHUNK = (
|
|
5757
6851
|
/* glsl */
|
|
5758
6852
|
`
|
|
@@ -5836,7 +6930,55 @@ function composeSurface(surface, stock, thickness, maps = { hasFrontMap: false,
|
|
|
5836
6930
|
calls.push("plAging(csm_DiffuseColor);");
|
|
5837
6931
|
uniforms.uAgingAmount = { value: aging };
|
|
5838
6932
|
}
|
|
5839
|
-
|
|
6933
|
+
if (maps.hasDamage) {
|
|
6934
|
+
chunks.push(DAMAGE_CHUNK, DAMAGE_SHADE_CHUNK);
|
|
6935
|
+
calls.push("plDamage(csm_DiffuseColor, csm_Roughness);");
|
|
6936
|
+
uniforms.uDamage = { value: null };
|
|
6937
|
+
uniforms.uDamageDetail = { value: 1 };
|
|
6938
|
+
uniforms.uDamageTime = { value: 0 };
|
|
6939
|
+
const look = DAMAGE_LOOK_DEFAULTS;
|
|
6940
|
+
uniforms.uLook0 = {
|
|
6941
|
+
value: new THREE5.Vector4(look.emberWidth, look.emberIntensity, look.emberCoverage, look.emberFlicker)
|
|
6942
|
+
};
|
|
6943
|
+
uniforms.uLook1 = {
|
|
6944
|
+
value: new THREE5.Vector4(look.emberGlow, look.lipWidth, look.lipBrightness, look.charWarmth)
|
|
6945
|
+
};
|
|
6946
|
+
uniforms.uLook2 = {
|
|
6947
|
+
value: new THREE5.Vector4(look.charCracks, look.scorchReach, look.scorchDarkness, look.fingers)
|
|
6948
|
+
};
|
|
6949
|
+
uniforms.uLook3 = { value: new THREE5.Vector4(look.edgeWave, look.edgeBite, look.sparkle, 0) };
|
|
6950
|
+
}
|
|
6951
|
+
const cutting = [];
|
|
6952
|
+
const cuts = [];
|
|
6953
|
+
if (deckle) {
|
|
6954
|
+
cutting.push(DECKLE_CHUNK);
|
|
6955
|
+
cuts.push("plDeckle(color);");
|
|
6956
|
+
}
|
|
6957
|
+
if (perforation) {
|
|
6958
|
+
cutting.push(PERFORATION_CHUNK);
|
|
6959
|
+
cuts.push("plPerforation(color);");
|
|
6960
|
+
}
|
|
6961
|
+
if (maps.hasDamage) {
|
|
6962
|
+
cutting.push(DAMAGE_CHUNK);
|
|
6963
|
+
cuts.push("plDamageCut(color, plDamageRead());");
|
|
6964
|
+
}
|
|
6965
|
+
const depth = cutting.length > 0 ? {
|
|
6966
|
+
vertexShader: DEPTH_VERTEX,
|
|
6967
|
+
fragmentShader: (
|
|
6968
|
+
/* glsl */
|
|
6969
|
+
`
|
|
6970
|
+
${DEPTH_HELPERS}
|
|
6971
|
+
${cutting.join("\n")}
|
|
6972
|
+
void main() {
|
|
6973
|
+
vec4 color = vec4(1.0);
|
|
6974
|
+
float roughness = 1.0;
|
|
6975
|
+
${cuts.join("\n ")}
|
|
6976
|
+
if (color.a < 0.5) discard;
|
|
6977
|
+
}
|
|
6978
|
+
`
|
|
6979
|
+
)
|
|
6980
|
+
} : null;
|
|
6981
|
+
const relief = grain !== void 0 || creases.length > 0 || Boolean(maps.hasDamage);
|
|
5840
6982
|
const frontExpr = maps.hasFrontMap ? "texture2D(uFrontMap, vPaperUv).rgb" : "uStockColor";
|
|
5841
6983
|
const backBaseExpr = stock.adhesive ? "vec3(0.965, 0.96, 0.945)" : maps.hasBackMap ? "texture2D(uBackMap, vec2(1.0 - vPaperUv.x, vPaperUv.y)).rgb" : "uStockColor";
|
|
5842
6984
|
const fragmentShader = (
|
|
@@ -5865,6 +7007,7 @@ ${relief ? " // The relief every effect above described, spent once \u2014 see
|
|
|
5865
7007
|
${stock.adhesive ? "// Adhesive underside: higher specular than the printed face.\n if (!gl_FrontFacing) csm_Roughness = 0.18;" : ""}
|
|
5866
7008
|
// What the key light pushes through the sheet, filtered by the ink on it.
|
|
5867
7009
|
csm_Emissive = plTransmission(front);
|
|
7010
|
+
${maps.hasDamage ? " // The ember line \u2014 the only light a burn gives off from the sheet itself.\n csm_Emissive += plDamageLight;" : ""}
|
|
5868
7011
|
}
|
|
5869
7012
|
`
|
|
5870
7013
|
);
|
|
@@ -5876,11 +7019,14 @@ ${relief ? " // The relief every effect above described, spent once \u2014 see
|
|
|
5876
7019
|
aging !== void 0 ? "a" : "",
|
|
5877
7020
|
perforation ? "p" : "",
|
|
5878
7021
|
stock.adhesive ? "A" : ""
|
|
5879
|
-
].join("")}:${maps.hasFrontMap ? "F" : ""}${maps.hasBackMap ? "B" : ""}`,
|
|
7022
|
+
].join("")}:${maps.hasFrontMap ? "F" : ""}${maps.hasBackMap ? "B" : ""}${maps.hasDamage ? "D" : ""}`,
|
|
5880
7023
|
vertexShader: VERTEX,
|
|
5881
7024
|
fragmentShader,
|
|
5882
7025
|
uniforms,
|
|
5883
|
-
|
|
7026
|
+
// Anything that removes paper needs fragments discarded rather than
|
|
7027
|
+
// blended — a hole has to cut the depth buffer and the shadow too.
|
|
7028
|
+
alphaTest: deckle || perforation || maps.hasDamage ? 0.5 : 0,
|
|
7029
|
+
depth
|
|
5884
7030
|
};
|
|
5885
7031
|
}
|
|
5886
7032
|
function pad(values, fill = 0) {
|
|
@@ -5889,8 +7035,43 @@ function pad(values, fill = 0) {
|
|
|
5889
7035
|
return out;
|
|
5890
7036
|
}
|
|
5891
7037
|
|
|
7038
|
+
// src/surface/useDamageTexture.ts
|
|
7039
|
+
import * as THREE6 from "three";
|
|
7040
|
+
import { useFrame } from "@react-three/fiber";
|
|
7041
|
+
import { useEffect as useEffect4, useMemo as useMemo2 } from "react";
|
|
7042
|
+
function useDamageTexture(source) {
|
|
7043
|
+
const texture = useMemo2(() => {
|
|
7044
|
+
if (!source) return null;
|
|
7045
|
+
const t = new THREE6.DataTexture(
|
|
7046
|
+
source.pixels,
|
|
7047
|
+
source.size,
|
|
7048
|
+
source.size,
|
|
7049
|
+
THREE6.RGBAFormat,
|
|
7050
|
+
THREE6.UnsignedByteType
|
|
7051
|
+
);
|
|
7052
|
+
t.magFilter = THREE6.LinearFilter;
|
|
7053
|
+
t.minFilter = THREE6.LinearFilter;
|
|
7054
|
+
t.wrapS = THREE6.ClampToEdgeWrapping;
|
|
7055
|
+
t.wrapT = THREE6.ClampToEdgeWrapping;
|
|
7056
|
+
t.generateMipmaps = false;
|
|
7057
|
+
t.colorSpace = THREE6.NoColorSpace;
|
|
7058
|
+
t.flipY = false;
|
|
7059
|
+
t.userData.version = source.version;
|
|
7060
|
+
t.needsUpdate = true;
|
|
7061
|
+
return t;
|
|
7062
|
+
}, [source]);
|
|
7063
|
+
useEffect4(() => () => texture?.dispose(), [texture]);
|
|
7064
|
+
useFrame(() => {
|
|
7065
|
+
if (!source || !texture) return;
|
|
7066
|
+
if (texture.userData.version === source.version) return;
|
|
7067
|
+
texture.userData.version = source.version;
|
|
7068
|
+
texture.needsUpdate = true;
|
|
7069
|
+
});
|
|
7070
|
+
return texture;
|
|
7071
|
+
}
|
|
7072
|
+
|
|
5892
7073
|
// src/surface/PaperMaterial.tsx
|
|
5893
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
7074
|
+
import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
5894
7075
|
function PaperMaterial({
|
|
5895
7076
|
stock,
|
|
5896
7077
|
texture,
|
|
@@ -5899,26 +7080,31 @@ function PaperMaterial({
|
|
|
5899
7080
|
thickness,
|
|
5900
7081
|
sheet: sheet2,
|
|
5901
7082
|
lighting = "studio",
|
|
5902
|
-
creases
|
|
7083
|
+
creases,
|
|
7084
|
+
damage
|
|
5903
7085
|
}) {
|
|
5904
7086
|
const rig = useLightRig(lighting);
|
|
7087
|
+
const damageTexture = useDamageTexture(damage);
|
|
5905
7088
|
const composed = composeSurface(
|
|
5906
7089
|
surface,
|
|
5907
7090
|
stock,
|
|
5908
7091
|
thickness,
|
|
5909
7092
|
{
|
|
5910
7093
|
hasFrontMap: Boolean(texture),
|
|
5911
|
-
hasBackMap: Boolean(backTexture)
|
|
7094
|
+
hasBackMap: Boolean(backTexture),
|
|
7095
|
+
hasDamage: Boolean(damageTexture)
|
|
5912
7096
|
},
|
|
5913
7097
|
sheet2,
|
|
5914
7098
|
rig,
|
|
5915
7099
|
creases ?? resolveCreases(surface, [], sheet2 ?? { width: 1, height: 1.4 })
|
|
5916
7100
|
);
|
|
5917
|
-
const bound =
|
|
5918
|
-
|
|
7101
|
+
const bound = useMemo3(() => composed.uniforms, [composed.structureKey]);
|
|
7102
|
+
useEffect5(() => {
|
|
5919
7103
|
for (const [key, uniform] of Object.entries(composed.uniforms)) {
|
|
5920
|
-
if (!bound[key] || key === "uFrontMap" || key === "uBackMap")
|
|
5921
|
-
|
|
7104
|
+
if (!bound[key] || key === "uFrontMap" || key === "uBackMap" || key === "uDamage" || key === "uDamageDetail" || key === "uDamageTime" || key.startsWith("uLook")) {
|
|
7105
|
+
continue;
|
|
7106
|
+
}
|
|
7107
|
+
if (bound[key].value instanceof THREE7.Color && uniform.value instanceof THREE7.Color) {
|
|
5922
7108
|
;
|
|
5923
7109
|
bound[key].value.copy(uniform.value);
|
|
5924
7110
|
} else {
|
|
@@ -5926,27 +7112,80 @@ function PaperMaterial({
|
|
|
5926
7112
|
}
|
|
5927
7113
|
}
|
|
5928
7114
|
});
|
|
5929
|
-
|
|
7115
|
+
useEffect5(() => {
|
|
5930
7116
|
if (bound.uFrontMap) bound.uFrontMap.value = texture;
|
|
5931
7117
|
if (bound.uBackMap) bound.uBackMap.value = backTexture ?? null;
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
7118
|
+
if (bound.uDamage) bound.uDamage.value = damageTexture;
|
|
7119
|
+
}, [bound, texture, backTexture, damageTexture]);
|
|
7120
|
+
useFrame2((state) => {
|
|
7121
|
+
if (!bound.uDamageDetail) return;
|
|
7122
|
+
if (bound.uDamageTime) {
|
|
7123
|
+
const time = damage?.time ?? state.clock.elapsedTime;
|
|
7124
|
+
bound.uDamageTime.value = Number.isFinite(time) ? time : 0;
|
|
7125
|
+
}
|
|
7126
|
+
if (bound.uLook0) {
|
|
7127
|
+
const look = { ...DAMAGE_LOOK_DEFAULTS, ...damage?.look };
|
|
7128
|
+
const v = (x, d2) => Number.isFinite(x) ? x : d2;
|
|
7129
|
+
const d = DAMAGE_LOOK_DEFAULTS;
|
|
7130
|
+
bound.uLook0.value.set(
|
|
7131
|
+
v(look.emberWidth, d.emberWidth),
|
|
7132
|
+
v(look.emberIntensity, d.emberIntensity),
|
|
7133
|
+
v(look.emberCoverage, d.emberCoverage),
|
|
7134
|
+
v(look.emberFlicker, d.emberFlicker)
|
|
7135
|
+
);
|
|
7136
|
+
bound.uLook1.value.set(
|
|
7137
|
+
v(look.emberGlow, d.emberGlow),
|
|
7138
|
+
v(look.lipWidth, d.lipWidth),
|
|
7139
|
+
v(look.lipBrightness, d.lipBrightness),
|
|
7140
|
+
v(look.charWarmth, d.charWarmth)
|
|
7141
|
+
);
|
|
7142
|
+
bound.uLook2.value.set(
|
|
7143
|
+
v(look.charCracks, d.charCracks),
|
|
7144
|
+
v(look.scorchReach, d.scorchReach),
|
|
7145
|
+
v(look.scorchDarkness, d.scorchDarkness),
|
|
7146
|
+
v(look.fingers, d.fingers)
|
|
7147
|
+
);
|
|
7148
|
+
bound.uLook3.value.set(
|
|
7149
|
+
v(look.edgeWave, d.edgeWave),
|
|
7150
|
+
v(look.edgeBite, d.edgeBite),
|
|
7151
|
+
v(look.sparkle, d.sparkle),
|
|
7152
|
+
0
|
|
7153
|
+
);
|
|
7154
|
+
}
|
|
7155
|
+
const detail = damage?.detail ?? 1;
|
|
7156
|
+
bound.uDamageDetail.value = Number.isFinite(detail) ? Math.min(1, Math.max(0, detail)) : 1;
|
|
7157
|
+
});
|
|
7158
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
7159
|
+
/* @__PURE__ */ jsx3(
|
|
7160
|
+
CustomShaderMaterial,
|
|
7161
|
+
{
|
|
7162
|
+
baseMaterial: THREE7.MeshStandardMaterial,
|
|
7163
|
+
vertexShader: composed.vertexShader,
|
|
7164
|
+
fragmentShader: composed.fragmentShader,
|
|
7165
|
+
uniforms: bound,
|
|
7166
|
+
color: "#ffffff",
|
|
7167
|
+
roughness: stock.roughness,
|
|
7168
|
+
metalness: 0,
|
|
7169
|
+
transparent: stock.opacity < 1,
|
|
7170
|
+
opacity: stock.opacity,
|
|
7171
|
+
alphaTest: composed.alphaTest,
|
|
7172
|
+
side: THREE7.DoubleSide
|
|
7173
|
+
},
|
|
7174
|
+
composed.structureKey
|
|
7175
|
+
),
|
|
7176
|
+
composed.depth ? /* @__PURE__ */ jsx3(
|
|
7177
|
+
CustomShaderMaterial,
|
|
7178
|
+
{
|
|
7179
|
+
attach: "customDepthMaterial",
|
|
7180
|
+
baseMaterial: THREE7.MeshDepthMaterial,
|
|
7181
|
+
vertexShader: composed.depth.vertexShader,
|
|
7182
|
+
fragmentShader: composed.depth.fragmentShader,
|
|
7183
|
+
uniforms: bound,
|
|
7184
|
+
depthPacking: THREE7.RGBADepthPacking
|
|
7185
|
+
},
|
|
7186
|
+
`${composed.structureKey}:depth`
|
|
7187
|
+
) : null
|
|
7188
|
+
] });
|
|
5950
7189
|
}
|
|
5951
7190
|
|
|
5952
7191
|
// src/motion/onTwos.ts
|
|
@@ -5960,7 +7199,7 @@ function quantizeProgress(p, duration, fps = ON_TWOS_FPS) {
|
|
|
5960
7199
|
}
|
|
5961
7200
|
|
|
5962
7201
|
// src/PaperMesh.tsx
|
|
5963
|
-
import { jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
7202
|
+
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
5964
7203
|
function configInputs(props) {
|
|
5965
7204
|
return [
|
|
5966
7205
|
props.preset ?? null,
|
|
@@ -5978,7 +7217,7 @@ function configInputs(props) {
|
|
|
5978
7217
|
}
|
|
5979
7218
|
function useResolvedConfig(props) {
|
|
5980
7219
|
const inputs = useStable(configInputs(props));
|
|
5981
|
-
return
|
|
7220
|
+
return useMemo4(() => resolveConfig(props), [inputs]);
|
|
5982
7221
|
}
|
|
5983
7222
|
function resolveConfig(props) {
|
|
5984
7223
|
const base = props.preset ? typeof props.preset === "string" ? getPreset(props.preset) : parsePreset(props.preset) : paperConfigSchema.parse({});
|
|
@@ -5997,12 +7236,12 @@ function resolveConfig(props) {
|
|
|
5997
7236
|
}
|
|
5998
7237
|
var CLOTH_MAX_SEGMENTS = 28;
|
|
5999
7238
|
var PROGRESS_SAMPLES = [0, 0.25, 0.5, 0.75, 1];
|
|
6000
|
-
var dragPlane = new
|
|
6001
|
-
var dragPoint = new
|
|
6002
|
-
var planeNormal = new
|
|
6003
|
-
var anchorScratch = new
|
|
6004
|
-
var worldScratch = new
|
|
6005
|
-
var quatScratch = new
|
|
7239
|
+
var dragPlane = new THREE8.Plane();
|
|
7240
|
+
var dragPoint = new THREE8.Vector3();
|
|
7241
|
+
var planeNormal = new THREE8.Vector3();
|
|
7242
|
+
var anchorScratch = new THREE8.Vector3();
|
|
7243
|
+
var worldScratch = new THREE8.Vector3();
|
|
7244
|
+
var quatScratch = new THREE8.Quaternion();
|
|
6006
7245
|
var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
6007
7246
|
const resolved = useResolvedConfig(props);
|
|
6008
7247
|
const reduced = usePrefersReducedMotion(props.reducedMotion);
|
|
@@ -6031,7 +7270,7 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6031
7270
|
const draggingRef = useRef3(null);
|
|
6032
7271
|
const configRef = useRef3(config);
|
|
6033
7272
|
configRef.current = config;
|
|
6034
|
-
const baseRotation =
|
|
7273
|
+
const baseRotation = useMemo4(() => {
|
|
6035
7274
|
const [rx, ry, rz] = props.rotation ?? [0, 0, 0];
|
|
6036
7275
|
return [rx, ry + config.scene.turn * Math.PI / 180, rz];
|
|
6037
7276
|
}, [props.rotation, config.scene.turn]);
|
|
@@ -6046,16 +7285,16 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6046
7285
|
const creasesRef = useRef3(null);
|
|
6047
7286
|
creasesRef.current ??= new CreaseTracker(config.memory.creases);
|
|
6048
7287
|
const creases = creasesRef.current;
|
|
6049
|
-
|
|
7288
|
+
useEffect6(() => {
|
|
6050
7289
|
if (!draggingRef.current && !playingRef.current) overridesRef.current = {};
|
|
6051
7290
|
creases.reset();
|
|
6052
7291
|
dirtyRef.current = true;
|
|
6053
7292
|
}, [behaviorKey, deformersKey, sheetKey, physicsKey]);
|
|
6054
|
-
|
|
7293
|
+
useEffect6(() => {
|
|
6055
7294
|
creases.adopt(configRef.current.memory.creases);
|
|
6056
7295
|
dirtyRef.current = true;
|
|
6057
7296
|
}, [creaseKey]);
|
|
6058
|
-
const { minSegments, autoSegments, animatedStack } =
|
|
7297
|
+
const { minSegments, autoSegments, animatedStack } = useMemo4(() => {
|
|
6059
7298
|
const cfg = configRef.current;
|
|
6060
7299
|
const probe2 = withMemory(buildStack(cfg, {}), cfg);
|
|
6061
7300
|
if (!probe2) {
|
|
@@ -6084,16 +7323,16 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6084
7323
|
animatedStack: animated
|
|
6085
7324
|
};
|
|
6086
7325
|
}, [behaviorKey, deformersKey, physicsKey, memoryKey]);
|
|
6087
|
-
const geometry =
|
|
7326
|
+
const geometry = useMemo4(() => {
|
|
6088
7327
|
if (isStrip) {
|
|
6089
7328
|
const strip = configRef.current.physics;
|
|
6090
7329
|
const nodes = stripNodeCount(config.sheet.height, strip.perforation);
|
|
6091
|
-
return new
|
|
7330
|
+
return new THREE8.PlaneGeometry(config.sheet.width, config.sheet.height, 1, nodes - 1);
|
|
6092
7331
|
}
|
|
6093
7332
|
if (!isCloth) return createSheetGeometry(config.sheet, minSegments, autoSegments);
|
|
6094
7333
|
const [sx, sy] = resolveSegments(config.sheet, minSegments);
|
|
6095
7334
|
const capped = Math.min(Math.max(sx, sy), CLOTH_MAX_SEGMENTS);
|
|
6096
|
-
return new
|
|
7335
|
+
return new THREE8.PlaneGeometry(config.sheet.width, config.sheet.height, capped, capped);
|
|
6097
7336
|
}, [
|
|
6098
7337
|
sheetKey,
|
|
6099
7338
|
minSegments,
|
|
@@ -6102,12 +7341,12 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6102
7341
|
isStrip,
|
|
6103
7342
|
isStrip ? config.physics.perforation : 0
|
|
6104
7343
|
]);
|
|
6105
|
-
|
|
6106
|
-
const basePositions =
|
|
7344
|
+
useEffect6(() => () => geometry.dispose(), [geometry]);
|
|
7345
|
+
const basePositions = useMemo4(
|
|
6107
7346
|
() => Float32Array.from(geometry.attributes.position.array),
|
|
6108
7347
|
[geometry]
|
|
6109
7348
|
);
|
|
6110
|
-
const stripSim =
|
|
7349
|
+
const stripSim = useMemo4(() => {
|
|
6111
7350
|
if (!isStrip) return null;
|
|
6112
7351
|
const strip = configRef.current.physics;
|
|
6113
7352
|
return new StripSim(config.sheet.height, config.sheet.width, {
|
|
@@ -6125,7 +7364,7 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6125
7364
|
});
|
|
6126
7365
|
}, [geometry, isStrip]);
|
|
6127
7366
|
const lastSimRef = useRef3(null);
|
|
6128
|
-
const sim =
|
|
7367
|
+
const sim = useMemo4(() => {
|
|
6129
7368
|
if (!isCloth) return null;
|
|
6130
7369
|
const cloth = configRef.current.physics;
|
|
6131
7370
|
const cols = geometry.parameters.widthSegments + 1;
|
|
@@ -6139,9 +7378,10 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6139
7378
|
next.adopt(lastSimRef.current);
|
|
6140
7379
|
return next;
|
|
6141
7380
|
}, [geometry, isCloth, isCloth ? config.physics.pins : ""]);
|
|
6142
|
-
|
|
7381
|
+
useEffect6(() => {
|
|
6143
7382
|
lastSimRef.current = sim;
|
|
6144
7383
|
}, [sim]);
|
|
7384
|
+
const coupling = useMemo4(() => sim ? new DamageCoupling(sim) : null, [sim]);
|
|
6145
7385
|
const stock = getStock(config.stock);
|
|
6146
7386
|
const texture = useContentTexture(config.content, config.sheet, stock);
|
|
6147
7387
|
const backTexture = useContentTexture(config.content.back, config.sheet, stock);
|
|
@@ -6183,7 +7423,7 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6183
7423
|
playingRef.current = false;
|
|
6184
7424
|
tweenRef.current?.pause();
|
|
6185
7425
|
};
|
|
6186
|
-
|
|
7426
|
+
useEffect6(() => {
|
|
6187
7427
|
if (props.autoplay && !reduced) play();
|
|
6188
7428
|
return () => {
|
|
6189
7429
|
tweenRef.current?.kill();
|
|
@@ -6226,7 +7466,22 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6226
7466
|
const index = id ? handles.findIndex((h) => h.id === id) : 0;
|
|
6227
7467
|
const mesh = handleRefs.current[index];
|
|
6228
7468
|
if (!mesh) return null;
|
|
6229
|
-
return mesh.getWorldPosition(target ?? new
|
|
7469
|
+
return mesh.getWorldPosition(target ?? new THREE8.Vector3());
|
|
7470
|
+
},
|
|
7471
|
+
surfacePoint(u, v, target) {
|
|
7472
|
+
const mesh = meshRef.current;
|
|
7473
|
+
if (!mesh) return null;
|
|
7474
|
+
const { widthSegments, heightSegments } = geometry.parameters;
|
|
7475
|
+
const out = surfacePointAt(
|
|
7476
|
+
geometry.attributes.position.array,
|
|
7477
|
+
widthSegments + 1,
|
|
7478
|
+
heightSegments + 1,
|
|
7479
|
+
u,
|
|
7480
|
+
v,
|
|
7481
|
+
target ?? new THREE8.Vector3()
|
|
7482
|
+
);
|
|
7483
|
+
mesh.updateWorldMatrix(true, false);
|
|
7484
|
+
return out.applyMatrix4(mesh.matrixWorld);
|
|
6230
7485
|
},
|
|
6231
7486
|
get state() {
|
|
6232
7487
|
return machineState;
|
|
@@ -6236,12 +7491,12 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6236
7491
|
placeProgrammatic: () => machineRef.current?.placeProgrammatic() ?? false,
|
|
6237
7492
|
returnProgrammatic: () => machineRef.current?.returnProgrammatic() ?? false
|
|
6238
7493
|
}));
|
|
6239
|
-
const shadedCreases =
|
|
7494
|
+
const shadedCreases = useMemo4(
|
|
6240
7495
|
() => resolveCreases(config.surface, config.memory.creases, config.sheet),
|
|
6241
7496
|
[config.surface, config.memory.creases, config.sheet]
|
|
6242
7497
|
);
|
|
6243
7498
|
const idlePose = useRef3({ position: [0, 0, 0], rotation: [0, 0, 0] });
|
|
6244
|
-
|
|
7499
|
+
useFrame3(({ clock }, delta) => {
|
|
6245
7500
|
const cfg = liveConfig();
|
|
6246
7501
|
const now = reduced ? 0 : cfg.onTwos ? quantizeTime(clock.elapsedTime) : clock.elapsedTime;
|
|
6247
7502
|
const hasBehaviorTransform = Boolean(behavior?.transform && cfg.behavior);
|
|
@@ -6325,8 +7580,14 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6325
7580
|
stiffness: cloth.stiffness,
|
|
6326
7581
|
floor: cloth.floor
|
|
6327
7582
|
});
|
|
7583
|
+
coupling?.update(props.damage);
|
|
6328
7584
|
sim.step(delta);
|
|
6329
7585
|
const moved = !sim.asleep;
|
|
7586
|
+
if (moved && coupling) {
|
|
7587
|
+
coupling.follow();
|
|
7588
|
+
const index = geometry.index;
|
|
7589
|
+
if (index && coupling.tear(index.array)) index.needsUpdate = true;
|
|
7590
|
+
}
|
|
6330
7591
|
if (!applyShape(sim.positions, moved) && moved) {
|
|
6331
7592
|
const position = geometry.attributes.position;
|
|
6332
7593
|
position.array.set(sim.positions);
|
|
@@ -6358,8 +7619,8 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6358
7619
|
const p = overridesRef.current[behavior.progressParam];
|
|
6359
7620
|
if (typeof p === "number") props.onProgress?.(p);
|
|
6360
7621
|
};
|
|
6361
|
-
const grabAnchor = useRef3(new
|
|
6362
|
-
const grabOffset = useRef3(new
|
|
7622
|
+
const grabAnchor = useRef3(new THREE8.Vector3());
|
|
7623
|
+
const grabOffset = useRef3(new THREE8.Vector3());
|
|
6363
7624
|
const clothDown = (e) => {
|
|
6364
7625
|
if (!isCloth || !sim || !props.interactive || !groupRef.current) return;
|
|
6365
7626
|
e.stopPropagation();
|
|
@@ -6422,7 +7683,7 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6422
7683
|
e.target.releasePointerCapture(e.pointerId);
|
|
6423
7684
|
};
|
|
6424
7685
|
const sendState = (event) => machineRef.current?.send(event);
|
|
6425
|
-
return /* @__PURE__ */
|
|
7686
|
+
return /* @__PURE__ */ jsxs2("group", { ref: groupRef, position: props.position, rotation: baseRotation, children: [
|
|
6426
7687
|
/* @__PURE__ */ jsx4(
|
|
6427
7688
|
"mesh",
|
|
6428
7689
|
{
|
|
@@ -6454,7 +7715,8 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6454
7715
|
thickness: config.sheet.thickness,
|
|
6455
7716
|
sheet: config.sheet,
|
|
6456
7717
|
lighting: config.scene.lighting,
|
|
6457
|
-
creases: shadedCreases
|
|
7718
|
+
creases: shadedCreases,
|
|
7719
|
+
damage: props.damage
|
|
6458
7720
|
}
|
|
6459
7721
|
)
|
|
6460
7722
|
}
|
|
@@ -6463,7 +7725,7 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6463
7725
|
// is no deformer stack for a handle to drive. Harmless as `!isCloth`
|
|
6464
7726
|
// only because the schema makes a sim and a behavior exclusive — the
|
|
6465
7727
|
// intent is what is written here.
|
|
6466
|
-
!simKind && behavior?.handles?.map((h, i) => /* @__PURE__ */
|
|
7728
|
+
!simKind && behavior?.handles?.map((h, i) => /* @__PURE__ */ jsxs2(
|
|
6467
7729
|
"mesh",
|
|
6468
7730
|
{
|
|
6469
7731
|
userData: { paperlabChrome: true },
|
|
@@ -6536,13 +7798,13 @@ function buildStack(config, overrides, behavior, t = 0) {
|
|
|
6536
7798
|
}
|
|
6537
7799
|
|
|
6538
7800
|
// src/scene/PaperLighting.tsx
|
|
6539
|
-
import * as
|
|
6540
|
-
import { useEffect as
|
|
6541
|
-
import { useFrame as
|
|
7801
|
+
import * as THREE10 from "three";
|
|
7802
|
+
import { useEffect as useEffect7, useMemo as useMemo5, useRef as useRef4 } from "react";
|
|
7803
|
+
import { useFrame as useFrame4, useThree as useThree2 } from "@react-three/fiber";
|
|
6542
7804
|
import { ContactShadows } from "@react-three/drei";
|
|
6543
7805
|
|
|
6544
7806
|
// src/scene/environment.ts
|
|
6545
|
-
import * as
|
|
7807
|
+
import * as THREE9 from "three";
|
|
6546
7808
|
|
|
6547
7809
|
// src/scene/color.ts
|
|
6548
7810
|
var probe;
|
|
@@ -6591,7 +7853,7 @@ function drawSky(ctx, preset) {
|
|
|
6591
7853
|
const x = skyU(angles.azimuth) * WIDTH;
|
|
6592
7854
|
const y = skyV(angles.elevation) * HEIGHT;
|
|
6593
7855
|
const radius = WIDTH * 0.3;
|
|
6594
|
-
const color = new
|
|
7856
|
+
const color = new THREE9.Color(preset.key.color);
|
|
6595
7857
|
const strength = Math.min(1, preset.key.intensity / 3);
|
|
6596
7858
|
for (const offset of [-WIDTH, 0, WIDTH]) {
|
|
6597
7859
|
const glow = ctx.createRadialGradient(x + offset, y, 0, x + offset, y, radius);
|
|
@@ -6610,14 +7872,14 @@ function makeSkyEquirect(preset) {
|
|
|
6610
7872
|
canvas.height = HEIGHT;
|
|
6611
7873
|
const ctx = canvas.getContext("2d");
|
|
6612
7874
|
drawSky(ctx, preset);
|
|
6613
|
-
const texture = new
|
|
6614
|
-
texture.mapping =
|
|
6615
|
-
texture.colorSpace =
|
|
7875
|
+
const texture = new THREE9.CanvasTexture(canvas);
|
|
7876
|
+
texture.mapping = THREE9.EquirectangularReflectionMapping;
|
|
7877
|
+
texture.colorSpace = THREE9.SRGBColorSpace;
|
|
6616
7878
|
return texture;
|
|
6617
7879
|
}
|
|
6618
7880
|
function buildEnvironment(renderer, preset) {
|
|
6619
7881
|
const equirect = makeSkyEquirect(preset);
|
|
6620
|
-
const pmrem = new
|
|
7882
|
+
const pmrem = new THREE9.PMREMGenerator(renderer);
|
|
6621
7883
|
const target = pmrem.fromEquirectangular(equirect);
|
|
6622
7884
|
pmrem.dispose();
|
|
6623
7885
|
equirect.dispose();
|
|
@@ -6628,11 +7890,11 @@ function buildEnvironment(renderer, preset) {
|
|
|
6628
7890
|
}
|
|
6629
7891
|
|
|
6630
7892
|
// src/scene/PaperLighting.tsx
|
|
6631
|
-
import { Fragment, jsx as jsx5, jsxs as
|
|
7893
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
6632
7894
|
var toneMappings = {
|
|
6633
|
-
agx:
|
|
6634
|
-
neutral:
|
|
6635
|
-
filmic:
|
|
7895
|
+
agx: THREE10.AgXToneMapping,
|
|
7896
|
+
neutral: THREE10.NeutralToneMapping,
|
|
7897
|
+
filmic: THREE10.ACESFilmicToneMapping
|
|
6636
7898
|
};
|
|
6637
7899
|
function mulberry322(seed) {
|
|
6638
7900
|
let a = seed;
|
|
@@ -6689,15 +7951,15 @@ function makeGoboTexture(kind) {
|
|
|
6689
7951
|
}
|
|
6690
7952
|
}
|
|
6691
7953
|
}
|
|
6692
|
-
const texture = new
|
|
6693
|
-
texture.wrapS = texture.wrapT =
|
|
7954
|
+
const texture = new THREE10.CanvasTexture(canvas);
|
|
7955
|
+
texture.wrapS = texture.wrapT = THREE10.RepeatWrapping;
|
|
6694
7956
|
return texture;
|
|
6695
7957
|
}
|
|
6696
7958
|
function StudioLight({ rig }) {
|
|
6697
7959
|
const gl = useThree2((s) => s.gl);
|
|
6698
7960
|
const scene = useThree2((s) => s.scene);
|
|
6699
7961
|
const sky = `${rig.sky.zenith}|${rig.sky.horizon}|${rig.sky.ground}|${rig.key.color}|${rig.key.intensity}|${rig.key.position.join()}`;
|
|
6700
|
-
|
|
7962
|
+
useEffect7(() => {
|
|
6701
7963
|
const environment = buildEnvironment(gl, rig);
|
|
6702
7964
|
const previous = scene.environment;
|
|
6703
7965
|
scene.environment = environment.texture;
|
|
@@ -6706,7 +7968,7 @@ function StudioLight({ rig }) {
|
|
|
6706
7968
|
environment.dispose();
|
|
6707
7969
|
};
|
|
6708
7970
|
}, [gl, scene, sky]);
|
|
6709
|
-
|
|
7971
|
+
useEffect7(() => {
|
|
6710
7972
|
const previous = scene.environmentIntensity;
|
|
6711
7973
|
scene.environmentIntensity = rig.studio;
|
|
6712
7974
|
return () => {
|
|
@@ -6727,13 +7989,13 @@ function PaperLighting({
|
|
|
6727
7989
|
contactShadow = true,
|
|
6728
7990
|
environment = true
|
|
6729
7991
|
}) {
|
|
6730
|
-
const p =
|
|
7992
|
+
const p = useMemo5(() => rig ?? resolveLighting(preset, light), [rig, preset, light]);
|
|
6731
7993
|
const mapSize = shadowMapSize ?? p.shadow.mapSize;
|
|
6732
7994
|
const castShadow = mapSize > 0;
|
|
6733
7995
|
const reduced = usePrefersReducedMotion(reducedMotion);
|
|
6734
7996
|
const gl = useThree2((s) => s.gl);
|
|
6735
7997
|
const scene = useThree2((s) => s.scene);
|
|
6736
|
-
|
|
7998
|
+
useEffect7(() => {
|
|
6737
7999
|
const previousExposure = gl.toneMappingExposure;
|
|
6738
8000
|
const previousFilm = gl.toneMapping;
|
|
6739
8001
|
gl.toneMappingExposure = p.exposure;
|
|
@@ -6743,23 +8005,23 @@ function PaperLighting({
|
|
|
6743
8005
|
gl.toneMapping = previousFilm;
|
|
6744
8006
|
};
|
|
6745
8007
|
}, [gl, p.exposure, p.film]);
|
|
6746
|
-
|
|
8008
|
+
useEffect7(() => {
|
|
6747
8009
|
if (!p.fog) return;
|
|
6748
8010
|
const previous = scene.fog;
|
|
6749
|
-
scene.fog = new
|
|
8011
|
+
scene.fog = new THREE10.Fog(p.fog.color, p.fog.near, p.fog.far);
|
|
6750
8012
|
return () => {
|
|
6751
8013
|
scene.fog = previous;
|
|
6752
8014
|
};
|
|
6753
8015
|
}, [scene, p.fog]);
|
|
6754
|
-
const goboMap =
|
|
6755
|
-
|
|
8016
|
+
const goboMap = useMemo5(() => p.gobo ? makeGoboTexture(p.gobo.kind) : null, [p.gobo?.kind]);
|
|
8017
|
+
useEffect7(() => () => goboMap?.dispose(), [goboMap]);
|
|
6756
8018
|
const driftRef = useRef4(0);
|
|
6757
|
-
|
|
8019
|
+
useFrame4((_, delta) => {
|
|
6758
8020
|
if (!goboMap || !p.gobo || reduced) return;
|
|
6759
8021
|
driftRef.current += delta * p.gobo.drift;
|
|
6760
8022
|
goboMap.offset.set(driftRef.current, driftRef.current * 0.6);
|
|
6761
8023
|
});
|
|
6762
|
-
return /* @__PURE__ */
|
|
8024
|
+
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
6763
8025
|
p.studio > 0 && (environment ? /* @__PURE__ */ jsx5(StudioLight, { rig: p }) : (
|
|
6764
8026
|
// The studio light degrades rather than disappearing. A hemisphere
|
|
6765
8027
|
// is the cheap half of what the room does — light from above, a
|
|
@@ -6817,15 +8079,15 @@ function PaperLighting({
|
|
|
6817
8079
|
}
|
|
6818
8080
|
|
|
6819
8081
|
// src/scene/backdrop.tsx
|
|
6820
|
-
import * as
|
|
6821
|
-
import { useEffect as
|
|
8082
|
+
import * as THREE11 from "three";
|
|
8083
|
+
import { useEffect as useEffect8, useState as useState4 } from "react";
|
|
6822
8084
|
import { useThree as useThree3 } from "@react-three/fiber";
|
|
6823
8085
|
function PaperBackdrop({ backdrop }) {
|
|
6824
8086
|
const scene = useThree3((s) => s.scene);
|
|
6825
8087
|
const size = useThree3((s) => s.size);
|
|
6826
8088
|
const [image, setImage] = useState4(null);
|
|
6827
8089
|
const src = backdrop?.image ?? "";
|
|
6828
|
-
|
|
8090
|
+
useEffect8(() => {
|
|
6829
8091
|
if (!src) {
|
|
6830
8092
|
setImage(null);
|
|
6831
8093
|
return;
|
|
@@ -6841,7 +8103,7 @@ function PaperBackdrop({ backdrop }) {
|
|
|
6841
8103
|
};
|
|
6842
8104
|
}, [src]);
|
|
6843
8105
|
const key = JSON.stringify({ backdrop: backdrop ?? null, w: size.width, h: size.height, loaded: !!image });
|
|
6844
|
-
|
|
8106
|
+
useEffect8(() => {
|
|
6845
8107
|
if (!backdrop) return;
|
|
6846
8108
|
const previous = scene.background;
|
|
6847
8109
|
const texture = paintBackdrop(backdrop, size.width, size.height, image);
|
|
@@ -6877,15 +8139,15 @@ function paintBackdrop(backdrop, width, height, image) {
|
|
|
6877
8139
|
ctx.globalAlpha = 1;
|
|
6878
8140
|
}
|
|
6879
8141
|
}
|
|
6880
|
-
const texture = new
|
|
6881
|
-
texture.colorSpace =
|
|
8142
|
+
const texture = new THREE11.CanvasTexture(canvas);
|
|
8143
|
+
texture.colorSpace = THREE11.SRGBColorSpace;
|
|
6882
8144
|
return texture;
|
|
6883
8145
|
}
|
|
6884
8146
|
|
|
6885
8147
|
// src/field/dropZones.tsx
|
|
6886
|
-
import * as
|
|
6887
|
-
import { createContext as createContext2, useContext as useContext2, useEffect as
|
|
6888
|
-
import { jsx as jsx6, jsxs as
|
|
8148
|
+
import * as THREE12 from "three";
|
|
8149
|
+
import { createContext as createContext2, useContext as useContext2, useEffect as useEffect9, useMemo as useMemo6, useRef as useRef5, useSyncExternalStore } from "react";
|
|
8150
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
6889
8151
|
var DropZoneRegistry = class {
|
|
6890
8152
|
zones = /* @__PURE__ */ new Map();
|
|
6891
8153
|
hoveredId = null;
|
|
@@ -6942,10 +8204,10 @@ function DropZone(props) {
|
|
|
6942
8204
|
const registry4 = useContext2(DropZoneContext);
|
|
6943
8205
|
const { id, accept, bounds, highlight = "glow", onPlace } = props;
|
|
6944
8206
|
const place = useRef5(onPlace);
|
|
6945
|
-
|
|
8207
|
+
useEffect9(() => {
|
|
6946
8208
|
place.current = onPlace;
|
|
6947
8209
|
}, [onPlace]);
|
|
6948
|
-
|
|
8210
|
+
useEffect9(() => {
|
|
6949
8211
|
if (!registry4) return;
|
|
6950
8212
|
return registry4.register({
|
|
6951
8213
|
id,
|
|
@@ -6963,15 +8225,15 @@ function DropZoneVisual({ registry: registry4, config }) {
|
|
|
6963
8225
|
const hovered = registry4.hovered === config.id;
|
|
6964
8226
|
const style = config.highlight ?? "glow";
|
|
6965
8227
|
const [w, h] = config.bounds.size;
|
|
6966
|
-
const edges =
|
|
6967
|
-
const plane = new
|
|
6968
|
-
const geo = new
|
|
8228
|
+
const edges = useMemo6(() => {
|
|
8229
|
+
const plane = new THREE12.PlaneGeometry(w, h);
|
|
8230
|
+
const geo = new THREE12.EdgesGeometry(plane);
|
|
6969
8231
|
plane.dispose();
|
|
6970
8232
|
return geo;
|
|
6971
8233
|
}, [w, h]);
|
|
6972
|
-
|
|
6973
|
-
return /* @__PURE__ */
|
|
6974
|
-
style !== "outline" && /* @__PURE__ */
|
|
8234
|
+
useEffect9(() => () => edges.dispose(), [edges]);
|
|
8235
|
+
return /* @__PURE__ */ jsxs4("group", { position: config.bounds.position, children: [
|
|
8236
|
+
style !== "outline" && /* @__PURE__ */ jsxs4("mesh", { children: [
|
|
6975
8237
|
/* @__PURE__ */ jsx6("planeGeometry", { args: [w, h] }),
|
|
6976
8238
|
/* @__PURE__ */ jsx6(
|
|
6977
8239
|
"meshBasicMaterial",
|
|
@@ -7572,17 +8834,17 @@ registerLayout(colonnade);
|
|
|
7572
8834
|
registerLayout(sheet);
|
|
7573
8835
|
|
|
7574
8836
|
// src/PaperField.tsx
|
|
7575
|
-
import * as
|
|
8837
|
+
import * as THREE17 from "three";
|
|
7576
8838
|
import { gsap as gsap5 } from "gsap";
|
|
7577
|
-
import { Canvas, useFrame as
|
|
7578
|
-
import { forwardRef as forwardRef2, useEffect as
|
|
8839
|
+
import { Canvas, useFrame as useFrame7, useThree as useThree6 } from "@react-three/fiber";
|
|
8840
|
+
import { forwardRef as forwardRef2, useEffect as useEffect15, useMemo as useMemo10, useRef as useRef8 } from "react";
|
|
7579
8841
|
|
|
7580
8842
|
// src/scene/release.tsx
|
|
7581
8843
|
import { useThree as useThree4 } from "@react-three/fiber";
|
|
7582
|
-
import { useEffect as
|
|
8844
|
+
import { useEffect as useEffect10 } from "react";
|
|
7583
8845
|
function ReleaseContextOnUnmount() {
|
|
7584
8846
|
const gl = useThree4((s) => s.gl);
|
|
7585
|
-
|
|
8847
|
+
useEffect10(
|
|
7586
8848
|
() => () => {
|
|
7587
8849
|
gl.forceContextLoss();
|
|
7588
8850
|
gl.dispose();
|
|
@@ -7638,15 +8900,15 @@ function resolveFieldSlotConfig(slot, fallback, index, layoutId, layoutOptions)
|
|
|
7638
8900
|
}
|
|
7639
8901
|
|
|
7640
8902
|
// src/field/fieldGroup.tsx
|
|
7641
|
-
import * as
|
|
8903
|
+
import * as THREE14 from "three";
|
|
7642
8904
|
import { gsap as gsap3 } from "gsap";
|
|
7643
|
-
import { useFrame as
|
|
7644
|
-
import { useEffect as
|
|
8905
|
+
import { useFrame as useFrame5 } from "@react-three/fiber";
|
|
8906
|
+
import { useEffect as useEffect12, useMemo as useMemo7, useRef as useRef6 } from "react";
|
|
7645
8907
|
import CustomShaderMaterial2 from "three-custom-shader-material";
|
|
7646
8908
|
|
|
7647
8909
|
// src/content/atlas.ts
|
|
7648
|
-
import * as
|
|
7649
|
-
import { useEffect as
|
|
8910
|
+
import * as THREE13 from "three";
|
|
8911
|
+
import { useEffect as useEffect11, useState as useState5 } from "react";
|
|
7650
8912
|
var MAX_ATLAS = 4096;
|
|
7651
8913
|
function atlasGrid(count, aspect = 1) {
|
|
7652
8914
|
const cols = Math.max(1, Math.min(count, Math.ceil(Math.sqrt(count * Math.max(aspect, 0.01)))));
|
|
@@ -7655,7 +8917,7 @@ function atlasGrid(count, aspect = 1) {
|
|
|
7655
8917
|
function useContentAtlas(contents, sheet2, stock) {
|
|
7656
8918
|
const [atlas, setAtlas] = useState5(null);
|
|
7657
8919
|
const stableContents = useStable(contents);
|
|
7658
|
-
|
|
8920
|
+
useEffect11(() => {
|
|
7659
8921
|
let disposed = false;
|
|
7660
8922
|
const aspect = sheet2.height / sheet2.width;
|
|
7661
8923
|
const { cols, rows } = atlasGrid(contents.length, aspect);
|
|
@@ -7671,8 +8933,8 @@ function useContentAtlas(contents, sheet2, stock) {
|
|
|
7671
8933
|
const ctx = canvas.getContext("2d");
|
|
7672
8934
|
ctx.fillStyle = stock.color;
|
|
7673
8935
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
7674
|
-
const texture = new
|
|
7675
|
-
texture.colorSpace =
|
|
8936
|
+
const texture = new THREE13.CanvasTexture(canvas);
|
|
8937
|
+
texture.colorSpace = THREE13.SRGBColorSpace;
|
|
7676
8938
|
texture.anisotropy = 4;
|
|
7677
8939
|
setAtlas({ texture, cols, rows });
|
|
7678
8940
|
const drawTile = (index, tile) => {
|
|
@@ -7841,7 +9103,7 @@ function fieldShapeStack(config, progress) {
|
|
|
7841
9103
|
|
|
7842
9104
|
// src/field/fieldGroup.tsx
|
|
7843
9105
|
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
7844
|
-
var scratchObj = new
|
|
9106
|
+
var scratchObj = new THREE14.Object3D();
|
|
7845
9107
|
var scratchAero = { position: [0, 0, 0], rotation: [0, 0, 0] };
|
|
7846
9108
|
var FIELD_SEGMENT_CAP = 48;
|
|
7847
9109
|
var FIELD_AUTO_CEILING = 128;
|
|
@@ -7864,7 +9126,7 @@ function FieldGroup({
|
|
|
7864
9126
|
behavior ? config.behavior[behavior.progressParam] : 0
|
|
7865
9127
|
);
|
|
7866
9128
|
const buildStackAt = (progress) => fieldShapeStack(config, progress);
|
|
7867
|
-
const initialStack =
|
|
9129
|
+
const initialStack = useMemo7(
|
|
7868
9130
|
() => buildStackAt(progressRef.current),
|
|
7869
9131
|
[
|
|
7870
9132
|
JSON.stringify(config.behavior ?? null),
|
|
@@ -7873,7 +9135,7 @@ function FieldGroup({
|
|
|
7873
9135
|
]
|
|
7874
9136
|
);
|
|
7875
9137
|
const structureKey = initialStack.map((i) => i.type).join("|");
|
|
7876
|
-
const geometry =
|
|
9138
|
+
const geometry = useMemo7(() => {
|
|
7877
9139
|
const floor = stackMinSegments(initialStack, config.sheet);
|
|
7878
9140
|
const want = [0, 0];
|
|
7879
9141
|
for (const p of PROGRESS_SAMPLES2) {
|
|
@@ -7902,28 +9164,28 @@ function FieldGroup({
|
|
|
7902
9164
|
atlasIdx[i] = i;
|
|
7903
9165
|
phase[i] = indices[i] * 0.618034 % 1 * 4;
|
|
7904
9166
|
}
|
|
7905
|
-
geo.setAttribute("aAtlas", new
|
|
7906
|
-
geo.setAttribute("aPhase", new
|
|
7907
|
-
geo.setAttribute("aBias", new
|
|
9167
|
+
geo.setAttribute("aAtlas", new THREE14.InstancedBufferAttribute(atlasIdx, 1));
|
|
9168
|
+
geo.setAttribute("aPhase", new THREE14.InstancedBufferAttribute(phase, 1));
|
|
9169
|
+
geo.setAttribute("aBias", new THREE14.InstancedBufferAttribute(bias, 1));
|
|
7908
9170
|
return geo;
|
|
7909
9171
|
}, [JSON.stringify(config.sheet), structureKey, count, autoCeiling]);
|
|
7910
|
-
|
|
9172
|
+
useEffect12(() => () => geometry.dispose(), [geometry]);
|
|
7911
9173
|
const atlas = useContentAtlas(contents, config.sheet, stock);
|
|
7912
|
-
const shader =
|
|
9174
|
+
const shader = useMemo7(() => {
|
|
7913
9175
|
const composed = buildDisplacementGLSL(initialStack, config.sheet);
|
|
7914
9176
|
const uniforms = {};
|
|
7915
9177
|
for (const [name, value] of Object.entries(composed.uniforms)) {
|
|
7916
9178
|
uniforms[name] = {
|
|
7917
|
-
value: Array.isArray(value) && value.length === 2 ? new
|
|
9179
|
+
value: Array.isArray(value) && value.length === 2 ? new THREE14.Vector2(...value) : value
|
|
7918
9180
|
};
|
|
7919
9181
|
}
|
|
7920
9182
|
uniforms.uPlTime = { value: 0 };
|
|
7921
9183
|
uniforms.uAtlas = { value: null };
|
|
7922
|
-
uniforms.uAtlasGrid = { value: new
|
|
9184
|
+
uniforms.uAtlasGrid = { value: new THREE14.Vector2(1, 1) };
|
|
7923
9185
|
uniforms.uBackDarken = {
|
|
7924
9186
|
value: 1 - Math.min(0.45, 0.12 + config.sheet.thickness * 0.9) * stock.opacity
|
|
7925
9187
|
};
|
|
7926
|
-
uniforms.uStockColor = { value: new
|
|
9188
|
+
uniforms.uStockColor = { value: new THREE14.Color(stock.color) };
|
|
7927
9189
|
uniforms.uShowThrough = { value: config.surface.showThrough ?? stock.showThrough };
|
|
7928
9190
|
Object.assign(uniforms, translucencyUniforms(config.surface.translucency ?? stock.translucency, rig));
|
|
7929
9191
|
return {
|
|
@@ -7938,19 +9200,19 @@ function FieldGroup({
|
|
|
7938
9200
|
config.surface.showThrough,
|
|
7939
9201
|
config.surface.translucency
|
|
7940
9202
|
]);
|
|
7941
|
-
|
|
9203
|
+
useEffect12(() => {
|
|
7942
9204
|
const values = translucencyValues(config.surface.translucency ?? stock.translucency, rig);
|
|
7943
9205
|
shader.uniforms.uTranslucency.value = values.translucency;
|
|
7944
9206
|
shader.uniforms.uBackLightDir.value.copy(values.direction);
|
|
7945
9207
|
shader.uniforms.uBackLightColor.value.copy(values.color);
|
|
7946
9208
|
shader.uniforms.uAmbientTransmission.value = values.ambient;
|
|
7947
9209
|
}, [shader, rig, config.surface.translucency, stock.translucency]);
|
|
7948
|
-
|
|
9210
|
+
useEffect12(() => {
|
|
7949
9211
|
if (!atlas) return;
|
|
7950
9212
|
shader.uniforms.uAtlas.value = atlas.texture;
|
|
7951
9213
|
shader.uniforms.uAtlasGrid.value.set(atlas.cols, atlas.rows);
|
|
7952
9214
|
}, [atlas, shader]);
|
|
7953
|
-
|
|
9215
|
+
useEffect12(() => {
|
|
7954
9216
|
if (!behavior || shared.reduced) return;
|
|
7955
9217
|
const state = { p: progressRef.current };
|
|
7956
9218
|
const tween = gsap3.to(state, {
|
|
@@ -7968,7 +9230,7 @@ function FieldGroup({
|
|
|
7968
9230
|
};
|
|
7969
9231
|
}, [structureKey, shared.reduced]);
|
|
7970
9232
|
const meshRef = useRef6(null);
|
|
7971
|
-
|
|
9233
|
+
useFrame5(({ clock }) => {
|
|
7972
9234
|
const mesh = meshRef.current;
|
|
7973
9235
|
if (!mesh) return;
|
|
7974
9236
|
const elapsed = clock.elapsedTime - Math.max(shared.mountTimeRef.current, 0);
|
|
@@ -7978,7 +9240,7 @@ function FieldGroup({
|
|
|
7978
9240
|
for (const [name, value] of Object.entries(values)) {
|
|
7979
9241
|
const uniform = shader.uniforms[name];
|
|
7980
9242
|
if (!uniform) continue;
|
|
7981
|
-
if (uniform.value instanceof
|
|
9243
|
+
if (uniform.value instanceof THREE14.Vector2 && Array.isArray(value)) {
|
|
7982
9244
|
uniform.value.set(value[0], value[1]);
|
|
7983
9245
|
} else {
|
|
7984
9246
|
uniform.value = value;
|
|
@@ -8063,13 +9325,13 @@ function FieldGroup({
|
|
|
8063
9325
|
children: /* @__PURE__ */ jsx7(
|
|
8064
9326
|
CustomShaderMaterial2,
|
|
8065
9327
|
{
|
|
8066
|
-
baseMaterial:
|
|
9328
|
+
baseMaterial: THREE14.MeshStandardMaterial,
|
|
8067
9329
|
vertexShader: shader.vertexShader,
|
|
8068
9330
|
fragmentShader: shader.fragmentShader,
|
|
8069
9331
|
uniforms: shader.uniforms,
|
|
8070
9332
|
roughness: stock.roughness,
|
|
8071
9333
|
metalness: 0,
|
|
8072
|
-
side:
|
|
9334
|
+
side: THREE14.DoubleSide
|
|
8073
9335
|
},
|
|
8074
9336
|
`${structureKey}:${count}`
|
|
8075
9337
|
)
|
|
@@ -8115,8 +9377,8 @@ var easeOut = (t) => 1 - (1 - t) ** 3;
|
|
|
8115
9377
|
var easeInOut = (t) => t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2;
|
|
8116
9378
|
|
|
8117
9379
|
// src/field/backingSheet.tsx
|
|
8118
|
-
import * as
|
|
8119
|
-
import { useEffect as
|
|
9380
|
+
import * as THREE15 from "three";
|
|
9381
|
+
import { useEffect as useEffect13, useMemo as useMemo8 } from "react";
|
|
8120
9382
|
|
|
8121
9383
|
// src/content/backing.ts
|
|
8122
9384
|
function silhouetteRects(o, count) {
|
|
@@ -8181,7 +9443,7 @@ function roundedRect(ctx, x, y, w, h, r) {
|
|
|
8181
9443
|
}
|
|
8182
9444
|
|
|
8183
9445
|
// src/field/backingSheet.tsx
|
|
8184
|
-
import { jsx as jsx8, jsxs as
|
|
9446
|
+
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
8185
9447
|
var BACKING_TINT = "#f3ecdd";
|
|
8186
9448
|
function BackingSheet({
|
|
8187
9449
|
options,
|
|
@@ -8189,7 +9451,7 @@ function BackingSheet({
|
|
|
8189
9451
|
removed
|
|
8190
9452
|
}) {
|
|
8191
9453
|
const { width, height } = sheetBackingSize(options);
|
|
8192
|
-
const canvas =
|
|
9454
|
+
const canvas = useMemo8(() => {
|
|
8193
9455
|
if (typeof document === "undefined") return null;
|
|
8194
9456
|
const c = document.createElement("canvas");
|
|
8195
9457
|
const scale = Math.min(1024, Math.round(360 * Math.max(width, height))) / Math.max(width, height);
|
|
@@ -8197,26 +9459,26 @@ function BackingSheet({
|
|
|
8197
9459
|
c.height = Math.max(2, Math.round(height * scale));
|
|
8198
9460
|
return c;
|
|
8199
9461
|
}, [width, height]);
|
|
8200
|
-
const texture =
|
|
9462
|
+
const texture = useMemo8(() => canvas ? new THREE15.CanvasTexture(canvas) : null, [canvas]);
|
|
8201
9463
|
const removedKey = [...removed].sort((a, b) => a - b).join(",");
|
|
8202
|
-
|
|
9464
|
+
useEffect13(() => {
|
|
8203
9465
|
if (!canvas || !texture) return;
|
|
8204
9466
|
drawBacking(canvas, { options, count, tint: BACKING_TINT, removed });
|
|
8205
9467
|
texture.needsUpdate = true;
|
|
8206
9468
|
}, [canvas, texture, JSON.stringify(options), count, removedKey]);
|
|
8207
|
-
|
|
8208
|
-
return /* @__PURE__ */
|
|
9469
|
+
useEffect13(() => () => texture?.dispose(), [texture]);
|
|
9470
|
+
return /* @__PURE__ */ jsxs5("mesh", { receiveShadow: true, children: [
|
|
8209
9471
|
/* @__PURE__ */ jsx8("planeGeometry", { args: [width, height] }),
|
|
8210
9472
|
/* @__PURE__ */ jsx8("meshStandardMaterial", { map: texture, color: "#ffffff", roughness: 0.92, metalness: 0 })
|
|
8211
9473
|
] });
|
|
8212
9474
|
}
|
|
8213
9475
|
|
|
8214
9476
|
// src/field/interactiveField.tsx
|
|
8215
|
-
import * as
|
|
9477
|
+
import * as THREE16 from "three";
|
|
8216
9478
|
import { gsap as gsap4 } from "gsap";
|
|
8217
|
-
import { useFrame as
|
|
8218
|
-
import { useContext as useContext3, useEffect as
|
|
8219
|
-
import { jsx as jsx9, jsxs as
|
|
9479
|
+
import { useFrame as useFrame6, useThree as useThree5 } from "@react-three/fiber";
|
|
9480
|
+
import { useContext as useContext3, useEffect as useEffect14, useMemo as useMemo9, useRef as useRef7, useState as useState6 } from "react";
|
|
9481
|
+
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
8220
9482
|
var PICK_BEHAVIORS = /* @__PURE__ */ new Set(["peel", "carry"]);
|
|
8221
9483
|
function InteractiveField(props) {
|
|
8222
9484
|
const { papers, fallback, layoutId, layoutOptions, sheetOptions } = props;
|
|
@@ -8224,14 +9486,14 @@ function InteractiveField(props) {
|
|
|
8224
9486
|
const layout = getLayout(layoutId);
|
|
8225
9487
|
const reduced = usePrefersReducedMotion(props.reducedMotion);
|
|
8226
9488
|
const contextRegistry = useContext3(DropZoneContext);
|
|
8227
|
-
const registry4 =
|
|
9489
|
+
const registry4 = useMemo9(() => contextRegistry ?? new DropZoneRegistry(), [contextRegistry]);
|
|
8228
9490
|
const camera = useThree5((s) => s.camera);
|
|
8229
9491
|
const gl = useThree5((s) => s.gl);
|
|
8230
9492
|
const controls = useThree5((s) => s.controls);
|
|
8231
9493
|
const [removed, setRemoved] = useState6(EMPTY_SET);
|
|
8232
9494
|
const [slotPatches, setSlotPatches] = useState6({});
|
|
8233
9495
|
const [slotStates, setSlotStates] = useState6({});
|
|
8234
|
-
const slotConfigs =
|
|
9496
|
+
const slotConfigs = useMemo9(
|
|
8235
9497
|
() => papers.map((slot, i) => {
|
|
8236
9498
|
const config = resolveFieldSlotConfig(slot, fallback, i, layoutId, layoutOptions);
|
|
8237
9499
|
const patch = slotPatches[i];
|
|
@@ -8245,7 +9507,7 @@ function InteractiveField(props) {
|
|
|
8245
9507
|
slotPatches
|
|
8246
9508
|
]
|
|
8247
9509
|
);
|
|
8248
|
-
const poses =
|
|
9510
|
+
const poses = useMemo9(
|
|
8249
9511
|
() => slotConfigs.map((_, i) => layout.pose(i, total, layoutOptions, 0, props.sheet)),
|
|
8250
9512
|
[layoutId, JSON.stringify(layoutOptions), total, props.sheet.width, props.sheet.height]
|
|
8251
9513
|
);
|
|
@@ -8265,10 +9527,10 @@ function InteractiveField(props) {
|
|
|
8265
9527
|
}
|
|
8266
9528
|
props.onSlotStateChange?.(i, state);
|
|
8267
9529
|
};
|
|
8268
|
-
const raycaster =
|
|
8269
|
-
const planeScratch =
|
|
8270
|
-
const pointScratch =
|
|
8271
|
-
const ndcScratch =
|
|
9530
|
+
const raycaster = useMemo9(() => new THREE16.Raycaster(), []);
|
|
9531
|
+
const planeScratch = useMemo9(() => new THREE16.Plane(new THREE16.Vector3(0, 0, 1), 0), []);
|
|
9532
|
+
const pointScratch = useMemo9(() => new THREE16.Vector3(), []);
|
|
9533
|
+
const ndcScratch = useMemo9(() => new THREE16.Vector2(), []);
|
|
8272
9534
|
const planePoint = (clientX, clientY, planeZ) => {
|
|
8273
9535
|
const rect = gl.domElement.getBoundingClientRect();
|
|
8274
9536
|
ndcScratch.set(
|
|
@@ -8436,7 +9698,7 @@ function InteractiveField(props) {
|
|
|
8436
9698
|
if (zone) settleInto(carried, zone);
|
|
8437
9699
|
else returnHome(carried);
|
|
8438
9700
|
};
|
|
8439
|
-
|
|
9701
|
+
useEffect14(() => {
|
|
8440
9702
|
const move = (e) => onPointerMoveRef.current(e);
|
|
8441
9703
|
const up = (e) => onPointerUpRef.current(e);
|
|
8442
9704
|
window.addEventListener("pointermove", move);
|
|
@@ -8446,7 +9708,7 @@ function InteractiveField(props) {
|
|
|
8446
9708
|
window.removeEventListener("pointerup", up);
|
|
8447
9709
|
};
|
|
8448
9710
|
}, []);
|
|
8449
|
-
|
|
9711
|
+
useFrame6((_, delta) => {
|
|
8450
9712
|
const carried = carriedRef.current;
|
|
8451
9713
|
if (!carried || carried.settling) return;
|
|
8452
9714
|
const group = groupRefs.current[carried.slot];
|
|
@@ -8472,8 +9734,8 @@ function InteractiveField(props) {
|
|
|
8472
9734
|
group.position.z = carried.homePose.position[2] + 0.15;
|
|
8473
9735
|
handle.set("drive", carryDrive(speed));
|
|
8474
9736
|
const lag = 0.25;
|
|
8475
|
-
group.rotation.y += (
|
|
8476
|
-
group.rotation.x += (
|
|
9737
|
+
group.rotation.y += (THREE16.MathUtils.clamp(-vx * lag, -0.6, 0.6) - group.rotation.y) * 0.12;
|
|
9738
|
+
group.rotation.x += (THREE16.MathUtils.clamp(vy * lag * 0.7, -0.5, 0.5) - group.rotation.x) * 0.12;
|
|
8477
9739
|
}
|
|
8478
9740
|
const name = slotName(carried.slot);
|
|
8479
9741
|
const zone = zonesLive().find(
|
|
@@ -8509,7 +9771,7 @@ function InteractiveField(props) {
|
|
|
8509
9771
|
zoneIds: () => zonesLive().map((z25) => z25.id),
|
|
8510
9772
|
slotState: (slot) => slotStates[slot] ?? "rest"
|
|
8511
9773
|
};
|
|
8512
|
-
|
|
9774
|
+
useEffect14(() => {
|
|
8513
9775
|
const ref = props.a11yRef;
|
|
8514
9776
|
if (!ref) return;
|
|
8515
9777
|
ref.current = {
|
|
@@ -8523,7 +9785,7 @@ function InteractiveField(props) {
|
|
|
8523
9785
|
ref.current = null;
|
|
8524
9786
|
};
|
|
8525
9787
|
}, [props.a11yRef]);
|
|
8526
|
-
return /* @__PURE__ */
|
|
9788
|
+
return /* @__PURE__ */ jsxs6("group", { children: [
|
|
8527
9789
|
sheetOptions?.backing && /* @__PURE__ */ jsx9(BackingSheet, { options: sheetOptions, count: total, removed }),
|
|
8528
9790
|
(props.zones ?? []).map((zone) => /* @__PURE__ */ jsx9(DropZoneVisual, { registry: registry4, config: zone }, zone.id)),
|
|
8529
9791
|
slotConfigs.map((config, i) => {
|
|
@@ -8563,7 +9825,7 @@ function InteractiveField(props) {
|
|
|
8563
9825
|
|
|
8564
9826
|
// src/field/keyboardMirror.tsx
|
|
8565
9827
|
import { useState as useState7 } from "react";
|
|
8566
|
-
import { jsx as jsx10, jsxs as
|
|
9828
|
+
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
8567
9829
|
function fieldKeyboardStep(carry2, slot, key, controller) {
|
|
8568
9830
|
if (!carry2) {
|
|
8569
9831
|
if (key === "Enter" || key === " ") {
|
|
@@ -8625,9 +9887,9 @@ function FieldKeyboardMirror({
|
|
|
8625
9887
|
if (handled) e.preventDefault();
|
|
8626
9888
|
if (carry2 !== carrying) setCarrying(carry2);
|
|
8627
9889
|
};
|
|
8628
|
-
return /* @__PURE__ */
|
|
9890
|
+
return /* @__PURE__ */ jsxs7("fieldset", { style: mirrorHidden, children: [
|
|
8629
9891
|
/* @__PURE__ */ jsx10("legend", { children: "Interactive papers" }),
|
|
8630
|
-
papers.map((slot, i) => /* @__PURE__ */
|
|
9892
|
+
papers.map((slot, i) => /* @__PURE__ */ jsxs7(
|
|
8631
9893
|
"button",
|
|
8632
9894
|
{
|
|
8633
9895
|
type: "button",
|
|
@@ -8636,7 +9898,7 @@ function FieldKeyboardMirror({
|
|
|
8636
9898
|
"aria-pressed": carrying?.slot === i,
|
|
8637
9899
|
children: [
|
|
8638
9900
|
paperLabel(slot, i),
|
|
8639
|
-
carrying?.slot === i && /* @__PURE__ */
|
|
9901
|
+
carrying?.slot === i && /* @__PURE__ */ jsxs7("span", { "aria-live": "polite", children: [
|
|
8640
9902
|
" ",
|
|
8641
9903
|
"\u2014 carrying; zone ",
|
|
8642
9904
|
controller.current?.zoneIds()[carrying.zoneIndex] ?? "none",
|
|
@@ -8710,17 +9972,17 @@ function fitCamera(layout, n, options, sheet2, fovDeg, aspect, margin = 1.06) {
|
|
|
8710
9972
|
}
|
|
8711
9973
|
|
|
8712
9974
|
// src/PaperField.tsx
|
|
8713
|
-
import { jsx as jsx11, jsxs as
|
|
9975
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
8714
9976
|
var PaperFieldMesh = forwardRef2(
|
|
8715
9977
|
function PaperFieldMesh2(props, ref) {
|
|
8716
9978
|
const reduced = usePrefersReducedMotion(props.reducedMotion);
|
|
8717
|
-
const papers =
|
|
9979
|
+
const papers = useMemo10(
|
|
8718
9980
|
() => effectiveFieldPapers(props.papers, props.images),
|
|
8719
9981
|
[props.papers, props.images]
|
|
8720
9982
|
);
|
|
8721
9983
|
const stablePapers = useStable(papers);
|
|
8722
9984
|
const stablePreset = useStable(props.preset ?? null);
|
|
8723
|
-
const groups =
|
|
9985
|
+
const groups = useMemo10(
|
|
8724
9986
|
() => groupFieldPapers(stablePapers, stablePreset ?? void 0),
|
|
8725
9987
|
[stablePapers, stablePreset]
|
|
8726
9988
|
);
|
|
@@ -8729,7 +9991,7 @@ var PaperFieldMesh = forwardRef2(
|
|
|
8729
9991
|
const layout = getLayout(layoutId);
|
|
8730
9992
|
const firstSheet = groups[0]?.config.sheet;
|
|
8731
9993
|
const stableLayoutOptions = useStable(props.layoutOptions ?? {});
|
|
8732
|
-
const layoutOptions =
|
|
9994
|
+
const layoutOptions = useMemo10(
|
|
8733
9995
|
// Sheet grids size their cells from the papers themselves — gutter is
|
|
8734
9996
|
// then literally the spacing between stamps (explicit cell dims win).
|
|
8735
9997
|
() => resolveLayoutOptions(layoutId, layout, stableLayoutOptions, firstSheet),
|
|
@@ -8744,7 +10006,7 @@ var PaperFieldMesh = forwardRef2(
|
|
|
8744
10006
|
const driver = reduced ? "none" : props.motion?.driver ?? "autoplay";
|
|
8745
10007
|
const speed = props.motion?.speed ?? 0.5;
|
|
8746
10008
|
const entranceType = reduced ? "none" : props.entrance?.type ?? "rise";
|
|
8747
|
-
|
|
10009
|
+
useEffect15(() => {
|
|
8748
10010
|
const prev = prevLayout.current;
|
|
8749
10011
|
if (prev.id !== layoutId || JSON.stringify(prev.options) !== JSON.stringify(layoutOptions)) {
|
|
8750
10012
|
morphRef.current = { from: prev, t: 0 };
|
|
@@ -8752,7 +10014,7 @@ var PaperFieldMesh = forwardRef2(
|
|
|
8752
10014
|
prevLayout.current = { id: layoutId, options: layoutOptions };
|
|
8753
10015
|
}
|
|
8754
10016
|
}, [layoutId, layoutOptions]);
|
|
8755
|
-
|
|
10017
|
+
useEffect15(() => {
|
|
8756
10018
|
if (driver !== "drag") return;
|
|
8757
10019
|
const el = gl.domElement;
|
|
8758
10020
|
let lastX = null;
|
|
@@ -8778,7 +10040,7 @@ var PaperFieldMesh = forwardRef2(
|
|
|
8778
10040
|
window.removeEventListener("pointerup", up);
|
|
8779
10041
|
};
|
|
8780
10042
|
}, [driver, gl]);
|
|
8781
|
-
|
|
10043
|
+
useFrame7(({ clock }, delta) => {
|
|
8782
10044
|
if (mountTimeRef.current < 0) mountTimeRef.current = clock.elapsedTime;
|
|
8783
10045
|
if (driver === "autoplay") phaseRef.current += delta * speed * 0.02;
|
|
8784
10046
|
if (driver === "drag") {
|
|
@@ -8820,7 +10082,7 @@ var PaperFieldMesh = forwardRef2(
|
|
|
8820
10082
|
}
|
|
8821
10083
|
) });
|
|
8822
10084
|
}
|
|
8823
|
-
return /* @__PURE__ */
|
|
10085
|
+
return /* @__PURE__ */ jsxs8("group", { ref, children: [
|
|
8824
10086
|
sheetOptions?.backing && /* @__PURE__ */ jsx11(BackingSheet, { options: sheetOptions, count: total, removed: EMPTY_SET }),
|
|
8825
10087
|
groups.map((group, gi) => /* @__PURE__ */ jsx11(
|
|
8826
10088
|
FieldGroup,
|
|
@@ -8843,7 +10105,7 @@ function FitCamera(meshProps) {
|
|
|
8843
10105
|
const imagesProp = useStable(meshProps.images ?? null);
|
|
8844
10106
|
const presetProp = useStable(meshProps.preset ?? null);
|
|
8845
10107
|
const optionsProp = useStable(meshProps.layoutOptions ?? {});
|
|
8846
|
-
const field =
|
|
10108
|
+
const field = useMemo10(() => {
|
|
8847
10109
|
const papers = effectiveFieldPapers(papersProp ?? void 0, imagesProp ?? void 0);
|
|
8848
10110
|
const layoutId = meshProps.layout ?? "ring";
|
|
8849
10111
|
const layout = getLayout(layoutId);
|
|
@@ -8851,8 +10113,8 @@ function FitCamera(meshProps) {
|
|
|
8851
10113
|
const options = resolveLayoutOptions(layoutId, layout, optionsProp, sheet2);
|
|
8852
10114
|
return { layout, n: papers.length, options, sheet: sheet2 ?? DEFAULT_SHEET };
|
|
8853
10115
|
}, [papersProp, imagesProp, presetProp, meshProps.layout, optionsProp]);
|
|
8854
|
-
|
|
8855
|
-
if (!(camera instanceof
|
|
10116
|
+
useEffect15(() => {
|
|
10117
|
+
if (!(camera instanceof THREE17.PerspectiveCamera)) return;
|
|
8856
10118
|
const { position, target } = fitCamera(
|
|
8857
10119
|
field.layout,
|
|
8858
10120
|
field.n,
|
|
@@ -8869,20 +10131,20 @@ function FitCamera(meshProps) {
|
|
|
8869
10131
|
}
|
|
8870
10132
|
var PaperField = forwardRef2(function PaperField2({ children, className, style, scene, ...meshProps }, ref) {
|
|
8871
10133
|
const rig = sceneSchema.parse(scene ?? {});
|
|
8872
|
-
const registry4 =
|
|
10134
|
+
const registry4 = useMemo10(() => new DropZoneRegistry(), []);
|
|
8873
10135
|
const a11yRef = useRef8(null);
|
|
8874
|
-
const papers =
|
|
10136
|
+
const papers = useMemo10(
|
|
8875
10137
|
() => effectiveFieldPapers(meshProps.papers, meshProps.images),
|
|
8876
10138
|
[meshProps.papers, meshProps.images]
|
|
8877
10139
|
);
|
|
8878
10140
|
const stablePapers = useStable(papers);
|
|
8879
10141
|
const stablePreset = useStable(meshProps.preset ?? null);
|
|
8880
|
-
const interactive =
|
|
10142
|
+
const interactive = useMemo10(
|
|
8881
10143
|
() => fieldIsInteractive(stablePapers, stablePreset ?? void 0, meshProps.interactive),
|
|
8882
10144
|
[stablePapers, stablePreset, meshProps.interactive]
|
|
8883
10145
|
);
|
|
8884
|
-
return /* @__PURE__ */ jsx11("div", { className, style: { width: "100%", height: "100%", ...style }, children: /* @__PURE__ */
|
|
8885
|
-
/* @__PURE__ */
|
|
10146
|
+
return /* @__PURE__ */ jsx11("div", { className, style: { width: "100%", height: "100%", ...style }, children: /* @__PURE__ */ jsxs8(DropZoneContext.Provider, { value: registry4, children: [
|
|
10147
|
+
/* @__PURE__ */ jsxs8(Canvas, { shadows: true, camera: { position: [0, 0.6, 5.2], fov: 45 }, dpr: [1, 2], children: [
|
|
8886
10148
|
/* @__PURE__ */ jsx11(FitCamera, { ...meshProps }),
|
|
8887
10149
|
/* @__PURE__ */ jsx11(PaperBackdrop, { backdrop: rig.backdrop }),
|
|
8888
10150
|
/* @__PURE__ */ jsx11(PaperLighting, { preset: rig.lighting, light: rig.light, floor: -2.4, scale: 14 }),
|
|
@@ -9153,4 +10415,4 @@ export {
|
|
|
9153
10415
|
describeConfig,
|
|
9154
10416
|
buildAgentPayload
|
|
9155
10417
|
};
|
|
9156
|
-
//# sourceMappingURL=chunk-
|
|
10418
|
+
//# sourceMappingURL=chunk-NTDVOOBA.js.map
|