paperlab 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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-MZCAN3AR.js} +1264 -197
- package/dist/chunk-MZCAN3AR.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 +2652 -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 +2864 -1087
- 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);
|
|
@@ -4912,7 +4938,82 @@ var ClothSim = class {
|
|
|
4912
4938
|
prev;
|
|
4913
4939
|
pinned;
|
|
4914
4940
|
pinTargets;
|
|
4915
|
-
|
|
4941
|
+
/**
|
|
4942
|
+
* The constraints, one typed array per field rather than one object each.
|
|
4943
|
+
*
|
|
4944
|
+
* Because the effects layer has to vary them across the sheet, and an
|
|
4945
|
+
* object with a fixed `rest` cannot be varied cheaply. Paper that chars
|
|
4946
|
+
* SHRINKS — that is what curls it toward the flame, not softening — which
|
|
4947
|
+
* is a shorter rest length on exactly the constraints the heat reached.
|
|
4948
|
+
* Burnt-through paper has to stop pulling on what is left, which is a
|
|
4949
|
+
* broken flag. A wet corner is floppier than the dry sheet above it, which
|
|
4950
|
+
* is a per-constraint stiffness. None of that is in this file; this file
|
|
4951
|
+
* only makes it possible, and at the defaults below the solve is
|
|
4952
|
+
* bit-identical to the object version it replaces.
|
|
4953
|
+
*/
|
|
4954
|
+
constraintCount;
|
|
4955
|
+
constraintA;
|
|
4956
|
+
constraintB;
|
|
4957
|
+
/** 0 = structural, 1 = shear, 2 = bend (scaled by `stiffness` at solve time). */
|
|
4958
|
+
constraintKind;
|
|
4959
|
+
/**
|
|
4960
|
+
* The length each constraint is laid out at. Never changes; the reference
|
|
4961
|
+
* to shrink from.
|
|
4962
|
+
*
|
|
4963
|
+
* Float64, not Float32, and that is measured rather than cautious: with
|
|
4964
|
+
* single-precision rest lengths every one of them rounded, and the first
|
|
4965
|
+
* typed-array version of this solve disagreed with the object version it
|
|
4966
|
+
* replaced in 476–711 of 714 coordinates after five seconds. The object
|
|
4967
|
+
* version held a JS number, which is a double.
|
|
4968
|
+
*/
|
|
4969
|
+
naturalLength;
|
|
4970
|
+
/** The length it pulls toward now. `naturalLength` until something shrinks or stretches it. */
|
|
4971
|
+
restLength;
|
|
4972
|
+
/** A multiplier on the constraint's own strength. 1 is the paper as built. */
|
|
4973
|
+
constraintStiffness;
|
|
4974
|
+
/** Non-zero where the paper between two particles is gone. Skipped by the solve. */
|
|
4975
|
+
broken;
|
|
4976
|
+
/**
|
|
4977
|
+
* How easily each particle is moved, 0..1: one over its mass, relative to
|
|
4978
|
+
* dry paper.
|
|
4979
|
+
*
|
|
4980
|
+
* Mass matters in exactly two places here, and both are wired, because a
|
|
4981
|
+
* "heavier" that touched neither would be another knob that does nothing.
|
|
4982
|
+
* Gravity is an acceleration, so weight alone does not make wet paper fall
|
|
4983
|
+
* differently. What does: the air pushes a heavy particle less (the aero
|
|
4984
|
+
* term is divided by mass), and the constraint solve moves a heavy particle
|
|
4985
|
+
* 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.
|
|
4986
|
+
*/
|
|
4987
|
+
invMass;
|
|
4988
|
+
/**
|
|
4989
|
+
* How far the middle of each BEND spring sits off the line between its ends
|
|
4990
|
+
* when relaxed, along the sheet's normal — a signed rest curvature, as a
|
|
4991
|
+
* sagitta in world units. Ignored on every other kind of constraint.
|
|
4992
|
+
*
|
|
4993
|
+
* Positive curls the paper toward its FRONT, the face a flat sheet turns to
|
|
4994
|
+
* +z — which puts the middle of each span that far BEHIND its ends. Paper
|
|
4995
|
+
* curling toward you is hollow on the side you see; the first cut of this
|
|
4996
|
+
* had the middle in front, which is a dome, and a dome's rim goes the other
|
|
4997
|
+
* way.
|
|
4998
|
+
*
|
|
4999
|
+
* The only lever here with a sign, and the sign is why it exists. A bend
|
|
5000
|
+
* spring is a distance, and a distance cannot tell paper curled toward you
|
|
5001
|
+
* from paper curled away: shorten it and the sheet buckles, but which way
|
|
5002
|
+
* is a coin toss, and gravity can flip it through later. Measured on the
|
|
5003
|
+
* first version, which pushed hot paper toward the front along its normal
|
|
5004
|
+
* and left the char to hold it — the curl held while the heat did and
|
|
5005
|
+
* snapped through to the BACK the moment the front had passed. Charred
|
|
5006
|
+
* paper stays curled toward the side that burnt because that side shrank
|
|
5007
|
+
* more: a curvature built into the material, which is what this is.
|
|
5008
|
+
*
|
|
5009
|
+
* Solved as a couple — the middle moves one way, the two ends half as far
|
|
5010
|
+
* the other — so it bends the sheet and never pushes it anywhere. A push
|
|
5011
|
+
* cannot do this job: it moves paper, and a uniformly curled sheet in still
|
|
5012
|
+
* air has no business going anywhere.
|
|
5013
|
+
*/
|
|
5014
|
+
restBend;
|
|
5015
|
+
/** For each bend spring, the particle between its ends; -1 for every other kind. */
|
|
5016
|
+
constraintMiddle;
|
|
4916
5017
|
params;
|
|
4917
5018
|
time = 0;
|
|
4918
5019
|
accumulator = 0;
|
|
@@ -4969,10 +5070,9 @@ var ClothSim = class {
|
|
|
4969
5070
|
}
|
|
4970
5071
|
this.prev.set(this.positions);
|
|
4971
5072
|
const idx = (r, c) => r * cols + c;
|
|
5073
|
+
const links = [];
|
|
4972
5074
|
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 });
|
|
5075
|
+
links.push(a, b, kind);
|
|
4976
5076
|
};
|
|
4977
5077
|
for (let r = 0; r < rows; r++) {
|
|
4978
5078
|
for (let c = 0; c < cols; c++) {
|
|
@@ -4986,6 +5086,31 @@ var ClothSim = class {
|
|
|
4986
5086
|
if (r + 2 < rows) link(idx(r, c), idx(r + 2, c), 2);
|
|
4987
5087
|
}
|
|
4988
5088
|
}
|
|
5089
|
+
const m = links.length / 3;
|
|
5090
|
+
this.constraintCount = m;
|
|
5091
|
+
this.constraintA = new Uint32Array(m);
|
|
5092
|
+
this.constraintB = new Uint32Array(m);
|
|
5093
|
+
this.constraintKind = new Uint8Array(m);
|
|
5094
|
+
this.naturalLength = new Float64Array(m);
|
|
5095
|
+
this.restLength = new Float64Array(m);
|
|
5096
|
+
this.constraintStiffness = new Float64Array(m).fill(1);
|
|
5097
|
+
this.broken = new Uint8Array(m);
|
|
5098
|
+
this.restBend = new Float64Array(m);
|
|
5099
|
+
this.constraintMiddle = new Int32Array(m).fill(-1);
|
|
5100
|
+
for (let k = 0; k < m; k++) {
|
|
5101
|
+
const a = links[k * 3];
|
|
5102
|
+
const b = links[k * 3 + 1];
|
|
5103
|
+
this.constraintA[k] = a;
|
|
5104
|
+
this.constraintB[k] = b;
|
|
5105
|
+
this.constraintKind[k] = links[k * 3 + 2];
|
|
5106
|
+
if (this.constraintKind[k] === 2) this.constraintMiddle[k] = (a + b) / 2;
|
|
5107
|
+
const dx = this.positions[a * 3] - this.positions[b * 3];
|
|
5108
|
+
const dy = this.positions[a * 3 + 1] - this.positions[b * 3 + 1];
|
|
5109
|
+
const rest = Math.hypot(dx, dy);
|
|
5110
|
+
this.naturalLength[k] = rest;
|
|
5111
|
+
this.restLength[k] = rest;
|
|
5112
|
+
}
|
|
5113
|
+
this.invMass = new Float32Array(this.count).fill(1);
|
|
4989
5114
|
const pin = (r, c) => {
|
|
4990
5115
|
const i = idx(r, c);
|
|
4991
5116
|
this.pinned[i] = 1;
|
|
@@ -5103,7 +5228,7 @@ var ClothSim = class {
|
|
|
5103
5228
|
for (let c = centreCol - spanCols; c <= centreCol + spanCols; c++) {
|
|
5104
5229
|
if (c < 0 || c >= this.cols) continue;
|
|
5105
5230
|
const i = r * this.cols + c;
|
|
5106
|
-
if (this.pinned[i]) continue;
|
|
5231
|
+
if (this.pinned[i] || this.invMass[i] <= 0) continue;
|
|
5107
5232
|
const distance = Math.hypot((c - centreCol) * cellX, (r - centreRow) * cellY);
|
|
5108
5233
|
const weight = i === this.grabbedIndex ? 1 : grabFalloff(distance, radius);
|
|
5109
5234
|
if (weight <= 0) continue;
|
|
@@ -5117,6 +5242,12 @@ var ClothSim = class {
|
|
|
5117
5242
|
);
|
|
5118
5243
|
}
|
|
5119
5244
|
}
|
|
5245
|
+
if (this.grabbed.length === 0) {
|
|
5246
|
+
this.grabbedIndex = -1;
|
|
5247
|
+
this.grabOffsets = new Float32Array(0);
|
|
5248
|
+
this.wake();
|
|
5249
|
+
return -1;
|
|
5250
|
+
}
|
|
5120
5251
|
this.grabOffsets = Float32Array.from(offsets);
|
|
5121
5252
|
this.grabAt.set(this.positions.subarray(anchor, anchor + 3));
|
|
5122
5253
|
this.grabWas.set(this.grabAt);
|
|
@@ -5277,12 +5408,19 @@ var ClothSim = class {
|
|
|
5277
5408
|
const ax = nx * facing * (1 - AERO_TURBULENCE) + gust2 * 0.25 * AERO_TURBULENCE;
|
|
5278
5409
|
const ay = ny * facing * (1 - AERO_TURBULENCE);
|
|
5279
5410
|
const az = nz * facing * (1 - AERO_TURBULENCE) + gust2 * AERO_TURBULENCE;
|
|
5411
|
+
const im = this.invMass[i];
|
|
5412
|
+
if (im <= 0) {
|
|
5413
|
+
this.prev[i3] = x;
|
|
5414
|
+
this.prev[i3 + 1] = y;
|
|
5415
|
+
this.prev[i3 + 2] = z25;
|
|
5416
|
+
continue;
|
|
5417
|
+
}
|
|
5280
5418
|
this.prev[i3] = x;
|
|
5281
5419
|
this.prev[i3 + 1] = y;
|
|
5282
5420
|
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;
|
|
5421
|
+
p[i3] = x + vx + ax * im * dt2;
|
|
5422
|
+
p[i3 + 1] = y + vy + (ay * im - gravity * 3.2) * dt2;
|
|
5423
|
+
p[i3 + 2] = z25 + vz + az * im * dt2;
|
|
5286
5424
|
maxTravel = Math.max(maxTravel, vx * vx + vy * vy + vz * vz);
|
|
5287
5425
|
}
|
|
5288
5426
|
for (let k = 0; k < this.grabbed.length; k++) {
|
|
@@ -5295,21 +5433,38 @@ var ClothSim = class {
|
|
|
5295
5433
|
p[i3 + axis] = p[i3 + axis] + (target - p[i3 + axis]) * held;
|
|
5296
5434
|
}
|
|
5297
5435
|
}
|
|
5436
|
+
const {
|
|
5437
|
+
constraintA,
|
|
5438
|
+
constraintB,
|
|
5439
|
+
constraintKind,
|
|
5440
|
+
restLength,
|
|
5441
|
+
constraintStiffness,
|
|
5442
|
+
broken,
|
|
5443
|
+
invMass,
|
|
5444
|
+
restBend,
|
|
5445
|
+
constraintMiddle,
|
|
5446
|
+
normals
|
|
5447
|
+
} = this;
|
|
5448
|
+
const bend2 = 0.25 + stiffness * 0.7;
|
|
5298
5449
|
for (let iter = 0; iter < SOLVER_ITERATIONS2; iter++) {
|
|
5299
|
-
for (
|
|
5300
|
-
|
|
5301
|
-
const
|
|
5302
|
-
const
|
|
5450
|
+
for (let c = 0; c < this.constraintCount; c++) {
|
|
5451
|
+
if (broken[c]) continue;
|
|
5452
|
+
const kind = constraintKind[c];
|
|
5453
|
+
const k = (kind === 2 ? bend2 : kind === 1 ? 0.85 : 1) * constraintStiffness[c];
|
|
5454
|
+
const ia = constraintA[c];
|
|
5455
|
+
const ib = constraintB[c];
|
|
5456
|
+
const a3 = ia * 3;
|
|
5457
|
+
const b3 = ib * 3;
|
|
5303
5458
|
const dx = p[b3] - p[a3];
|
|
5304
5459
|
const dy = p[b3 + 1] - p[a3 + 1];
|
|
5305
5460
|
const dz = p[b3 + 2] - p[a3 + 2];
|
|
5306
5461
|
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
5307
5462
|
if (dist === 0) continue;
|
|
5308
|
-
const ma = this.pinned[
|
|
5309
|
-
const mb = this.pinned[
|
|
5463
|
+
const ma = this.pinned[ia] ? 0 : invMass[ia] * (1 - this.grabWeights[ia]);
|
|
5464
|
+
const mb = this.pinned[ib] ? 0 : invMass[ib] * (1 - this.grabWeights[ib]);
|
|
5310
5465
|
const total = ma + mb;
|
|
5311
5466
|
if (total <= 0) continue;
|
|
5312
|
-
const diff = (dist - c
|
|
5467
|
+
const diff = (dist - restLength[c]) / dist / total * k;
|
|
5313
5468
|
const aw = ma;
|
|
5314
5469
|
const bw = mb;
|
|
5315
5470
|
p[a3] = p[a3] + dx * diff * aw;
|
|
@@ -5318,6 +5473,31 @@ var ClothSim = class {
|
|
|
5318
5473
|
p[b3] = p[b3] - dx * diff * bw;
|
|
5319
5474
|
p[b3 + 1] = p[b3 + 1] - dy * diff * bw;
|
|
5320
5475
|
p[b3 + 2] = p[b3 + 2] - dz * diff * bw;
|
|
5476
|
+
const sag = restBend[c];
|
|
5477
|
+
if (sag !== 0) {
|
|
5478
|
+
const im = constraintMiddle[c];
|
|
5479
|
+
const m3 = im * 3;
|
|
5480
|
+
const nx = normals[m3];
|
|
5481
|
+
const ny = normals[m3 + 1];
|
|
5482
|
+
const nz = normals[m3 + 2];
|
|
5483
|
+
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;
|
|
5484
|
+
const mm = this.pinned[im] ? 0 : invMass[im] * (1 - this.grabWeights[im]);
|
|
5485
|
+
const weight = mm + (ma + mb) / 4;
|
|
5486
|
+
if (weight > 0) {
|
|
5487
|
+
const lambda = (-sag - offset) / weight * k;
|
|
5488
|
+
const along = lambda * mm;
|
|
5489
|
+
const back = lambda / 2;
|
|
5490
|
+
p[m3] = p[m3] + nx * along;
|
|
5491
|
+
p[m3 + 1] = p[m3 + 1] + ny * along;
|
|
5492
|
+
p[m3 + 2] = p[m3 + 2] + nz * along;
|
|
5493
|
+
p[a3] = p[a3] - nx * back * ma;
|
|
5494
|
+
p[a3 + 1] = p[a3 + 1] - ny * back * ma;
|
|
5495
|
+
p[a3 + 2] = p[a3 + 2] - nz * back * ma;
|
|
5496
|
+
p[b3] = p[b3] - nx * back * mb;
|
|
5497
|
+
p[b3 + 1] = p[b3 + 1] - ny * back * mb;
|
|
5498
|
+
p[b3 + 2] = p[b3 + 2] - nz * back * mb;
|
|
5499
|
+
}
|
|
5500
|
+
}
|
|
5321
5501
|
}
|
|
5322
5502
|
}
|
|
5323
5503
|
for (let i = 0; i < this.count; i++) {
|
|
@@ -5338,9 +5518,220 @@ var ClothSim = class {
|
|
|
5338
5518
|
}
|
|
5339
5519
|
};
|
|
5340
5520
|
|
|
5521
|
+
// src/physics/damage.ts
|
|
5522
|
+
var { char: CHAR, saturation: SATURATION, presence: PRESENCE } = DAMAGE_CHANNELS;
|
|
5523
|
+
var GONE_BELOW = 128;
|
|
5524
|
+
var CHAR_SHRINK = 0.12;
|
|
5525
|
+
var CHAR_CURL = 6;
|
|
5526
|
+
var FRONT_GRADIENT = 16 * 16;
|
|
5527
|
+
var WET_MASS = 2;
|
|
5528
|
+
var DamageCoupling = class {
|
|
5529
|
+
sim;
|
|
5530
|
+
source = null;
|
|
5531
|
+
version = -1;
|
|
5532
|
+
size = 0;
|
|
5533
|
+
/** Each particle's texel, laid out for `size`. */
|
|
5534
|
+
particleTexel = new Int32Array(0);
|
|
5535
|
+
/** Gone, per particle, as of the last read. */
|
|
5536
|
+
gone;
|
|
5537
|
+
/** The gone particles with live paper beside them. See {@link follow}. */
|
|
5538
|
+
followers = [];
|
|
5539
|
+
/**
|
|
5540
|
+
* Per bend spring, how squarely it lies ACROSS the burn front, 0..1 — and
|
|
5541
|
+
* remembered once it has been.
|
|
5542
|
+
*
|
|
5543
|
+
* A burnt edge rolls about an axis along the front, like a scroll; it does
|
|
5544
|
+
* not dish into a bowl. Measured with the curl in every bend spring alike: a
|
|
5545
|
+
* burnt band settled into a saddle, its corners forward and its middle BACK
|
|
5546
|
+
* (+0.07, −0.06), because a surface cannot curve two ways at once without
|
|
5547
|
+
* stretching and paper does not stretch. Curled across the front only, the
|
|
5548
|
+
* same band rolled toward the viewer along its whole edge (+0.13).
|
|
5549
|
+
*
|
|
5550
|
+
* "Across the front" is the direction the char is changing fastest in, at
|
|
5551
|
+
* the moment the front passes. It has to be remembered: once the front has
|
|
5552
|
+
* gone by, the char behind it is uniform and says nothing about direction,
|
|
5553
|
+
* but the paper stays rolled the way it rolled.
|
|
5554
|
+
*/
|
|
5555
|
+
curlWeight;
|
|
5556
|
+
constructor(sim) {
|
|
5557
|
+
this.sim = sim;
|
|
5558
|
+
this.gone = new Uint8Array(sim.count);
|
|
5559
|
+
this.curlWeight = new Float32Array(sim.constraintCount);
|
|
5560
|
+
}
|
|
5561
|
+
/**
|
|
5562
|
+
* Bring the sim's levers up to date with `source`. Call before stepping.
|
|
5563
|
+
*
|
|
5564
|
+
* Returns whether anything was read. Free when the source has not changed,
|
|
5565
|
+
* and a null source hands every lever back at its default.
|
|
5566
|
+
*/
|
|
5567
|
+
update(source) {
|
|
5568
|
+
const next = source ?? null;
|
|
5569
|
+
if (next === this.source && (next === null || next.version === this.version)) return false;
|
|
5570
|
+
if (next !== this.source) this.curlWeight.fill(0);
|
|
5571
|
+
this.source = next;
|
|
5572
|
+
if (!next) {
|
|
5573
|
+
this.reset();
|
|
5574
|
+
return true;
|
|
5575
|
+
}
|
|
5576
|
+
if (next.size !== this.size) this.layout(next.size);
|
|
5577
|
+
this.version = next.version;
|
|
5578
|
+
this.read(next.pixels);
|
|
5579
|
+
this.sim.wake();
|
|
5580
|
+
return true;
|
|
5581
|
+
}
|
|
5582
|
+
/**
|
|
5583
|
+
* Carry the gone particles at the edge of a hole along with the paper beside
|
|
5584
|
+
* them. Call after stepping.
|
|
5585
|
+
*
|
|
5586
|
+
* Each one goes where its live neighbours say it would be if the grid were
|
|
5587
|
+
* laid out flat around them — their positions plus its rest offset from
|
|
5588
|
+
* each, averaged. The triangles that straddle the burnt edge then keep
|
|
5589
|
+
* roughly their shape instead of stretching back to wherever the particle
|
|
5590
|
+
* was when it burnt. Only a band of a single particle's width, with live
|
|
5591
|
+
* paper pulling it both ways, is left averaging between them.
|
|
5592
|
+
*/
|
|
5593
|
+
follow() {
|
|
5594
|
+
if (this.followers.length === 0) return;
|
|
5595
|
+
const { gone, sim } = this;
|
|
5596
|
+
const p = sim.positions;
|
|
5597
|
+
const { cols, rows } = sim;
|
|
5598
|
+
const cellX = cols > 1 ? sim.width / (cols - 1) : 0;
|
|
5599
|
+
const cellY = rows > 1 ? sim.height / (rows - 1) : 0;
|
|
5600
|
+
for (const g of this.followers) {
|
|
5601
|
+
const r = g / cols | 0;
|
|
5602
|
+
const c = g % cols;
|
|
5603
|
+
let sx = 0;
|
|
5604
|
+
let sy = 0;
|
|
5605
|
+
let sz = 0;
|
|
5606
|
+
let n = 0;
|
|
5607
|
+
for (let dr = -1; dr <= 1; dr++) {
|
|
5608
|
+
const nr = r + dr;
|
|
5609
|
+
if (nr < 0 || nr >= rows) continue;
|
|
5610
|
+
for (let dc = -1; dc <= 1; dc++) {
|
|
5611
|
+
const nc = c + dc;
|
|
5612
|
+
if (dr === 0 && dc === 0 || nc < 0 || nc >= cols) continue;
|
|
5613
|
+
const j = nr * cols + nc;
|
|
5614
|
+
if (gone[j]) continue;
|
|
5615
|
+
const j3 = j * 3;
|
|
5616
|
+
sx += p[j3] - dc * cellX;
|
|
5617
|
+
sy += p[j3 + 1] + dr * cellY;
|
|
5618
|
+
sz += p[j3 + 2];
|
|
5619
|
+
n++;
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5622
|
+
if (n === 0) continue;
|
|
5623
|
+
const g3 = g * 3;
|
|
5624
|
+
p[g3] = sx / n;
|
|
5625
|
+
p[g3 + 1] = sy / n;
|
|
5626
|
+
p[g3 + 2] = sz / n;
|
|
5627
|
+
}
|
|
5628
|
+
}
|
|
5629
|
+
/** Map each particle to its texel. The damage grid's row 0 is v = 0, the sheet's BOTTOM. */
|
|
5630
|
+
layout(size) {
|
|
5631
|
+
const { cols, rows, count } = this.sim;
|
|
5632
|
+
const last = size - 1;
|
|
5633
|
+
this.size = size;
|
|
5634
|
+
this.particleTexel = new Int32Array(count);
|
|
5635
|
+
for (let r = 0; r < rows; r++) {
|
|
5636
|
+
for (let c = 0; c < cols; c++) {
|
|
5637
|
+
const u = cols > 1 ? c / (cols - 1) : 0.5;
|
|
5638
|
+
const v = rows > 1 ? 1 - r / (rows - 1) : 0.5;
|
|
5639
|
+
this.particleTexel[r * cols + c] = Math.round(v * last) * size + Math.round(u * last);
|
|
5640
|
+
}
|
|
5641
|
+
}
|
|
5642
|
+
}
|
|
5643
|
+
read(pixels) {
|
|
5644
|
+
const { sim, gone, particleTexel, curlWeight, size } = this;
|
|
5645
|
+
const {
|
|
5646
|
+
invMass,
|
|
5647
|
+
restLength,
|
|
5648
|
+
naturalLength,
|
|
5649
|
+
restBend,
|
|
5650
|
+
broken,
|
|
5651
|
+
constraintA,
|
|
5652
|
+
constraintB,
|
|
5653
|
+
constraintKind,
|
|
5654
|
+
constraintMiddle
|
|
5655
|
+
} = sim;
|
|
5656
|
+
for (let i = 0; i < sim.count; i++) {
|
|
5657
|
+
const t = particleTexel[i] * 4;
|
|
5658
|
+
const isGone = pixels[t + PRESENCE] < GONE_BELOW;
|
|
5659
|
+
gone[i] = isGone ? 1 : 0;
|
|
5660
|
+
invMass[i] = isGone ? 0 : 1 / (1 + WET_MASS * pixels[t + SATURATION] / 255);
|
|
5661
|
+
}
|
|
5662
|
+
for (let k = 0; k < sim.constraintCount; k++) {
|
|
5663
|
+
const a = constraintA[k];
|
|
5664
|
+
const b = constraintB[k];
|
|
5665
|
+
const m = constraintMiddle[k];
|
|
5666
|
+
if (gone[a] || gone[b] || m >= 0 && gone[m]) {
|
|
5667
|
+
broken[k] = 1;
|
|
5668
|
+
continue;
|
|
5669
|
+
}
|
|
5670
|
+
broken[k] = 0;
|
|
5671
|
+
let charSum = pixels[particleTexel[a] * 4 + CHAR] + pixels[particleTexel[b] * 4 + CHAR];
|
|
5672
|
+
let samples = 2;
|
|
5673
|
+
if (m >= 0) {
|
|
5674
|
+
charSum += pixels[particleTexel[m] * 4 + CHAR];
|
|
5675
|
+
samples = 3;
|
|
5676
|
+
}
|
|
5677
|
+
const char = charSum / (samples * 255);
|
|
5678
|
+
restLength[k] = naturalLength[k] * (1 - CHAR_SHRINK * char);
|
|
5679
|
+
if (constraintKind[k] === 2) {
|
|
5680
|
+
const t = particleTexel[m];
|
|
5681
|
+
const x = t % size;
|
|
5682
|
+
const y = t / size | 0;
|
|
5683
|
+
const gx = pixels[(y * size + Math.min(size - 1, x + 2)) * 4 + CHAR] - pixels[(y * size + Math.max(0, x - 2)) * 4 + CHAR];
|
|
5684
|
+
const gy = pixels[(Math.min(size - 1, y + 2) * size + x) * 4 + CHAR] - pixels[(Math.max(0, y - 2) * size + x) * 4 + CHAR];
|
|
5685
|
+
const steep = gx * gx + gy * gy;
|
|
5686
|
+
if (steep > FRONT_GRADIENT) {
|
|
5687
|
+
const across = (b - a === 2 ? gx * gx : gy * gy) / steep;
|
|
5688
|
+
if (across > curlWeight[k]) curlWeight[k] = across;
|
|
5689
|
+
}
|
|
5690
|
+
const span = naturalLength[k];
|
|
5691
|
+
restBend[k] = CHAR_CURL * char * curlWeight[k] * span * span / 8;
|
|
5692
|
+
}
|
|
5693
|
+
}
|
|
5694
|
+
const followers = [];
|
|
5695
|
+
const { cols, rows } = sim;
|
|
5696
|
+
for (let i = 0; i < sim.count; i++) {
|
|
5697
|
+
if (!gone[i]) continue;
|
|
5698
|
+
const r = i / cols | 0;
|
|
5699
|
+
const c = i % cols;
|
|
5700
|
+
let beside = false;
|
|
5701
|
+
for (let dr = -1; dr <= 1 && !beside; dr++) {
|
|
5702
|
+
const nr = r + dr;
|
|
5703
|
+
if (nr < 0 || nr >= rows) continue;
|
|
5704
|
+
for (let dc = -1; dc <= 1; dc++) {
|
|
5705
|
+
const nc = c + dc;
|
|
5706
|
+
if (nc < 0 || nc >= cols) continue;
|
|
5707
|
+
if (!gone[nr * cols + nc]) {
|
|
5708
|
+
beside = true;
|
|
5709
|
+
break;
|
|
5710
|
+
}
|
|
5711
|
+
}
|
|
5712
|
+
}
|
|
5713
|
+
if (beside) followers.push(i);
|
|
5714
|
+
}
|
|
5715
|
+
this.followers = followers;
|
|
5716
|
+
}
|
|
5717
|
+
reset() {
|
|
5718
|
+
const { sim } = this;
|
|
5719
|
+
sim.invMass.fill(1);
|
|
5720
|
+
sim.restBend.fill(0);
|
|
5721
|
+
sim.broken.fill(0);
|
|
5722
|
+
sim.restLength.set(sim.naturalLength);
|
|
5723
|
+
this.gone.fill(0);
|
|
5724
|
+
this.curlWeight.fill(0);
|
|
5725
|
+
this.followers = [];
|
|
5726
|
+
this.version = -1;
|
|
5727
|
+
sim.wake();
|
|
5728
|
+
}
|
|
5729
|
+
};
|
|
5730
|
+
|
|
5341
5731
|
// src/surface/PaperMaterial.tsx
|
|
5342
|
-
import * as
|
|
5343
|
-
import { useEffect as
|
|
5732
|
+
import * as THREE7 from "three";
|
|
5733
|
+
import { useEffect as useEffect5, useMemo as useMemo3 } from "react";
|
|
5734
|
+
import { useFrame as useFrame2 } from "@react-three/fiber";
|
|
5344
5735
|
import CustomShaderMaterial from "three-custom-shader-material";
|
|
5345
5736
|
|
|
5346
5737
|
// src/surface/compose.ts
|
|
@@ -5753,6 +6144,518 @@ void plPerforation(inout vec4 color) {
|
|
|
5753
6144
|
}
|
|
5754
6145
|
`
|
|
5755
6146
|
);
|
|
6147
|
+
var DAMAGE_CHUNK = (
|
|
6148
|
+
/* glsl */
|
|
6149
|
+
`
|
|
6150
|
+
uniform sampler2D uDamage;
|
|
6151
|
+
uniform float uDamageDetail;
|
|
6152
|
+
// (edgeWave mm, edgeBite mm, sparkle, \u2013) \u2014 see DamageLook.
|
|
6153
|
+
uniform vec4 uLook3;
|
|
6154
|
+
|
|
6155
|
+
// The damage grid's texels sit ON the sheet \u2014 texel x at u = x / (N - 1), its
|
|
6156
|
+
// corners on the sheet's corners, which is how the field paints and how the
|
|
6157
|
+
// physics reads it. A texture puts texel x at (x + 0.5) / N. Sampled straight,
|
|
6158
|
+
// the picture of a burn sat up to half a texel off the burn the paper feels.
|
|
6159
|
+
vec2 plDamageUv(vec2 uv) {
|
|
6160
|
+
vec2 n = vec2(textureSize(uDamage, 0));
|
|
6161
|
+
// Texel space, on the field's convention: texel x at u = x / (N - 1).
|
|
6162
|
+
vec2 t = uv * (n - 1.0) + 0.5;
|
|
6163
|
+
// Quintic-smoothed texel coordinates.
|
|
6164
|
+
//
|
|
6165
|
+
// Bilinear filtering is continuous but its DERIVATIVE is not: the blend
|
|
6166
|
+
// weight is a straight line inside each texel and turns a corner at every
|
|
6167
|
+
// boundary. Magnify a 64 grid to fill a macro crop \u2014 one cell is 4.9 mm of
|
|
6168
|
+
// A4, about 58 px there \u2014 and those corners fall on a 58 px lattice, so
|
|
6169
|
+
// every ramp drawn from this field can crease along it.
|
|
6170
|
+
//
|
|
6171
|
+
// Also tried against the contour rings, and also not their cause (that was
|
|
6172
|
+
// the scorch fingers). Kept because it costs nothing and the crease is
|
|
6173
|
+
// real:
|
|
6174
|
+
// Bending the fractional part through a quintic (6t^5 - 15t^4 + 10t^3,
|
|
6175
|
+
// whose first and second derivatives vanish at both ends) makes the
|
|
6176
|
+
// interpolation C2 without a single extra tap: the hardware still does one
|
|
6177
|
+
// bilinear fetch, it is just asked for a smoothed position. The sample at a
|
|
6178
|
+
// texel's centre is unchanged, so the half-texel alignment this function
|
|
6179
|
+
// exists for still holds \u2014 and a field nothing has happened to reads 1
|
|
6180
|
+
// everywhere whatever the position, so the untouched-sheet identity is
|
|
6181
|
+
// untouched too.
|
|
6182
|
+
vec2 i = floor(t - 0.5) + 0.5;
|
|
6183
|
+
vec2 f = t - i;
|
|
6184
|
+
f = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);
|
|
6185
|
+
return (i + f) / n;
|
|
6186
|
+
}
|
|
6187
|
+
|
|
6188
|
+
/**
|
|
6189
|
+
* The four channels here, frayed finer than the grid.
|
|
6190
|
+
*
|
|
6191
|
+
* One cell of the field is ~3.3 mm of A4, and a burnt edge is jagged at the
|
|
6192
|
+
* scale of a fibre (0.3\u20132 mm) \u2014 so the grid decides roughly WHERE and this
|
|
6193
|
+
* decides exactly where. Stretched along the fibre (the field's default grain
|
|
6194
|
+
* runs along u), so a tear follows the grain. Scaled by what is already
|
|
6195
|
+
* there, so pristine paper (char 0, presence 1) is untouched to the bit, and
|
|
6196
|
+
* skipped outright at detail 0: the zones still draw on every tier, only
|
|
6197
|
+
* without the finest octaves.
|
|
6198
|
+
*/
|
|
6199
|
+
/**
|
|
6200
|
+
* Where on the grid this fragment reads its damage \u2014 warped.
|
|
6201
|
+
*
|
|
6202
|
+
* A burnt hole is never a circle: paper burns unevenly, and its edge wobbles
|
|
6203
|
+
* in long waves with small bites in and out. The field is too coarse to hold
|
|
6204
|
+
* that, so the READ is displaced instead: two octaves of noise in the sheet's
|
|
6205
|
+
* own space, a long wave of up to \xB16 mm and short bites of \xB12 mm. Every zone \u2014
|
|
6206
|
+
* the cut, the lip, the ember line, the char, the scorch \u2014 reads through the
|
|
6207
|
+
* same warp, so they all follow one uneven edge. Warping a field nothing has
|
|
6208
|
+
* happened to reads the same untouched value everywhere, so the identity
|
|
6209
|
+
* holds.
|
|
6210
|
+
*/
|
|
6211
|
+
vec2 plDamageWarpAt(vec2 paperUv) {
|
|
6212
|
+
vec2 p = (paperUv - 0.5) * uSheetSize;
|
|
6213
|
+
const float mm = 1.0 / 210.0;
|
|
6214
|
+
vec2 wave = vec2(plFbm(p * 26.0 + 3.1), plFbm(p * 26.0 + 17.7)) - 0.5;
|
|
6215
|
+
vec2 bite = vec2(plNoise(p * 95.0 + 5.3), plNoise(p * 95.0 + 41.9)) - 0.5;
|
|
6216
|
+
vec2 shift = (wave * 2.0 * uLook3.x + bite * 2.0 * uLook3.y) * mm;
|
|
6217
|
+
return plDamageUv(paperUv + shift / uSheetSize);
|
|
6218
|
+
}
|
|
6219
|
+
|
|
6220
|
+
vec2 plDamageWarp() {
|
|
6221
|
+
return plDamageWarpAt(vPaperUv);
|
|
6222
|
+
}
|
|
6223
|
+
|
|
6224
|
+
/** Presence softened over five taps \u2014 see \`plDamageRead\`. */
|
|
6225
|
+
float plSoftPresence(vec2 uv) {
|
|
6226
|
+
vec2 texel = 0.75 / vec2(textureSize(uDamage, 0));
|
|
6227
|
+
return (2.0 * texture2D(uDamage, uv).a
|
|
6228
|
+
+ texture2D(uDamage, uv + vec2(texel.x, 0.0)).a + texture2D(uDamage, uv - vec2(texel.x, 0.0)).a
|
|
6229
|
+
+ texture2D(uDamage, uv + vec2(0.0, texel.y)).a + texture2D(uDamage, uv - vec2(0.0, texel.y)).a) / 6.0;
|
|
6230
|
+
}
|
|
6231
|
+
|
|
6232
|
+
/** The fray and the fibre at a point of the sheet (local units), scaled by detail. */
|
|
6233
|
+
vec2 plFray(vec2 p) {
|
|
6234
|
+
return vec2(
|
|
6235
|
+
(plFbm(vec2(p.x * 22.0, p.y * 48.0)) - 0.47) * uDamageDetail,
|
|
6236
|
+
(plNoise(vec2(p.x * 140.0, p.y * 420.0)) - 0.5) * uDamageDetail
|
|
6237
|
+
);
|
|
6238
|
+
}
|
|
6239
|
+
|
|
6240
|
+
/**
|
|
6241
|
+
* The presence the cut is drawn from, at any point of the paper: warped,
|
|
6242
|
+
* softened and frayed exactly as \`plDamageRead\` does it for this fragment.
|
|
6243
|
+
*/
|
|
6244
|
+
float plDrawnPresenceAt(vec2 paperUv) {
|
|
6245
|
+
float a = plSoftPresence(plDamageWarpAt(paperUv));
|
|
6246
|
+
if (uDamageDetail > 0.0) {
|
|
6247
|
+
vec2 ff = plFray((paperUv - 0.5) * uSheetSize);
|
|
6248
|
+
a = clamp(a + (ff.x * 0.36 + ff.y * 0.05) * (1.0 - smoothstep(0.9, 1.0, a)), 0.0, 1.0);
|
|
6249
|
+
}
|
|
6250
|
+
return a;
|
|
6251
|
+
}
|
|
6252
|
+
|
|
6253
|
+
// The softened presence before the fray, from the last \`plDamageRead\`: what
|
|
6254
|
+
// the distance to the cut takes its slope from (see \`plDamage\`).
|
|
6255
|
+
float plDamageSmooth;
|
|
6256
|
+
|
|
6257
|
+
vec4 plDamageRead() {
|
|
6258
|
+
vec2 uv = plDamageWarp();
|
|
6259
|
+
vec4 d = texture2D(uDamage, uv);
|
|
6260
|
+
// The cut runs on a SOFTENED presence. Paper burns through within a single
|
|
6261
|
+
// texel now, so the raw field is hard-edged and its half-presence contour
|
|
6262
|
+
// is the grid's own staircase \u2014 the fray below can only move a line within
|
|
6263
|
+
// the band it is given, and a one-texel band is not enough to hide a grid.
|
|
6264
|
+
// Five taps round the contour; an untouched field is 1 everywhere and
|
|
6265
|
+
// stays exactly 1.
|
|
6266
|
+
d.a = plSoftPresence(uv);
|
|
6267
|
+
plDamageSmooth = d.a;
|
|
6268
|
+
if (uDamageDetail > 0.0) {
|
|
6269
|
+
vec2 ff = plFray(plLocal());
|
|
6270
|
+
float fray = ff.x;
|
|
6271
|
+
float fibre = ff.y;
|
|
6272
|
+
float edge = 1.0 - smoothstep(0.9, 1.0, d.a);
|
|
6273
|
+
d.r = clamp(d.r + fray * 0.3 * smoothstep(0.0, 0.15, d.r), 0.0, 1.0);
|
|
6274
|
+
d.a = clamp(d.a + (fray * 0.36 + fibre * 0.05) * edge, 0.0, 1.0);
|
|
6275
|
+
}
|
|
6276
|
+
return d;
|
|
6277
|
+
}
|
|
6278
|
+
|
|
6279
|
+
void plDamageCut(inout vec4 color, vec4 d) {
|
|
6280
|
+
// Missing paper: gone at half presence, on every stock alike.
|
|
6281
|
+
color.a *= step(0.5, d.a);
|
|
6282
|
+
}
|
|
6283
|
+
`
|
|
6284
|
+
);
|
|
6285
|
+
var DAMAGE_SHADE_CHUNK = (
|
|
6286
|
+
/* glsl */
|
|
6287
|
+
`
|
|
6288
|
+
uniform float uDamageTime;
|
|
6289
|
+
// The look, packed \u2014 see DamageLook for what each number means.
|
|
6290
|
+
uniform vec4 uLook0; // emberWidth mm, emberIntensity, emberCoverage, emberFlicker
|
|
6291
|
+
uniform vec4 uLook1; // emberGlow, lipWidth mm, lipBrightness, charWarmth
|
|
6292
|
+
uniform vec4 uLook2; // charCracks, scorchReach mm, scorchDarkness, fingers
|
|
6293
|
+
/** The light the burn gives off from the sheet itself: the ember line, and nothing else. */
|
|
6294
|
+
vec3 plDamageLight;
|
|
6295
|
+
|
|
6296
|
+
// World units per millimetre: a default sheet is one unit across, and A4 is 210 mm.
|
|
6297
|
+
const float PL_MM = 1.0 / 210.0;
|
|
6298
|
+
|
|
6299
|
+
vec3 plLinear(vec3 srgb) {
|
|
6300
|
+
return pow(srgb, vec3(2.2));
|
|
6301
|
+
}
|
|
6302
|
+
|
|
6303
|
+
/**
|
|
6304
|
+
* World up, as a direction in the sheet's UV, from how this fragment's UV and
|
|
6305
|
+
* view position change across the screen. Must be called in uniform control
|
|
6306
|
+
* flow \u2014 it is all derivatives.
|
|
6307
|
+
*/
|
|
6308
|
+
vec2 plUpUv() {
|
|
6309
|
+
vec3 dpx = dFdx(-vViewPosition);
|
|
6310
|
+
vec3 dpy = dFdy(-vViewPosition);
|
|
6311
|
+
vec2 dux = dFdx(vPaperUv);
|
|
6312
|
+
vec2 duy = dFdy(vPaperUv);
|
|
6313
|
+
float det = dux.x * duy.y - dux.y * duy.x;
|
|
6314
|
+
if (abs(det) < 1e-14) return vec2(0.0, 1.0);
|
|
6315
|
+
vec3 dPdu = (dpx * duy.y - dpy * dux.y) / det;
|
|
6316
|
+
vec3 dPdv = (dpy * dux.x - dpx * duy.x) / det;
|
|
6317
|
+
vec3 up = (viewMatrix * vec4(0.0, 1.0, 0.0, 0.0)).xyz;
|
|
6318
|
+
vec2 g = vec2(dot(up, dPdu) / max(dot(dPdu, dPdu), 1e-12), dot(up, dPdv) / max(dot(dPdv, dPdv), 1e-12));
|
|
6319
|
+
float l = length(g);
|
|
6320
|
+
return l > 1e-6 ? g / l : vec2(0.0, 1.0);
|
|
6321
|
+
}
|
|
6322
|
+
|
|
6323
|
+
/** F1 and F2 of a cell noise \u2014 the crack network in char. */
|
|
6324
|
+
vec2 plVoronoi(vec2 p) {
|
|
6325
|
+
vec2 i = floor(p);
|
|
6326
|
+
vec2 f = fract(p);
|
|
6327
|
+
float f1 = 8.0;
|
|
6328
|
+
float f2 = 8.0;
|
|
6329
|
+
for (int y = -1; y <= 1; y++) {
|
|
6330
|
+
for (int x = -1; x <= 1; x++) {
|
|
6331
|
+
vec2 o = vec2(float(x), float(y));
|
|
6332
|
+
vec2 r = o + vec2(plHash(i + o), plHash(i + o + 17.31)) - f;
|
|
6333
|
+
float dd = dot(r, r);
|
|
6334
|
+
if (dd < f1) {
|
|
6335
|
+
f2 = f1;
|
|
6336
|
+
f1 = dd;
|
|
6337
|
+
} else if (dd < f2) {
|
|
6338
|
+
f2 = dd;
|
|
6339
|
+
}
|
|
6340
|
+
}
|
|
6341
|
+
}
|
|
6342
|
+
return sqrt(vec2(f1, f2));
|
|
6343
|
+
}
|
|
6344
|
+
|
|
6345
|
+
/** The scorch ramp, sampled off Scorch.png and Hero.png, as a tint over paper. 0 is the leading edge. */
|
|
6346
|
+
vec3 plScorchTint(float t) {
|
|
6347
|
+
vec3 paper = plLinear(vec3(0.90, 0.885, 0.855));
|
|
6348
|
+
vec3 c0 = plLinear(vec3(0.843, 0.741, 0.569)); // #D7BD91
|
|
6349
|
+
vec3 c1 = plLinear(vec3(0.745, 0.514, 0.263)); // #BE8343
|
|
6350
|
+
vec3 c2 = plLinear(vec3(0.631, 0.384, 0.180)); // #A1622E
|
|
6351
|
+
vec3 c3 = plLinear(vec3(0.541, 0.310, 0.141)); // #8A4F24
|
|
6352
|
+
vec3 c4 = plLinear(vec3(0.471, 0.275, 0.149)); // #784626
|
|
6353
|
+
vec3 c5 = plLinear(vec3(0.380, 0.216, 0.133)); // #613722
|
|
6354
|
+
vec3 c6 = plLinear(vec3(0.310, 0.200, 0.137)); // #4F3323
|
|
6355
|
+
float s = clamp(t, 0.0, 1.0) * 6.0;
|
|
6356
|
+
// Each stop is blended with a SMOOTH step, not a linear one.
|
|
6357
|
+
//
|
|
6358
|
+
// Seven colours joined by six straight segments is a ramp with a corner at
|
|
6359
|
+
// every joint: the colour is continuous there but its rate of change is
|
|
6360
|
+
// not, and the eye reads a discontinuous gradient as a faint line (Mach
|
|
6361
|
+
// banding). smoothstep's derivative is zero at both ends of a segment, so
|
|
6362
|
+
// consecutive segments meet with matching slope and there is no corner.
|
|
6363
|
+
//
|
|
6364
|
+
// Cheap insurance, not a bug fix \u2014 this was tried first against the macro
|
|
6365
|
+
// crops' contour rings and did NOT remove them; the cause turned out to be
|
|
6366
|
+
// the scorch fingers below, sampled in a frame that follows the contours.
|
|
6367
|
+
float k = smoothstep(0.0, 1.0, clamp(s, 0.0, 1.0));
|
|
6368
|
+
vec3 c = mix(c0, c1, k);
|
|
6369
|
+
c = mix(c, c2, smoothstep(0.0, 1.0, clamp(s - 1.0, 0.0, 1.0)));
|
|
6370
|
+
c = mix(c, c3, smoothstep(0.0, 1.0, clamp(s - 2.0, 0.0, 1.0)));
|
|
6371
|
+
c = mix(c, c4, smoothstep(0.0, 1.0, clamp(s - 3.0, 0.0, 1.0)));
|
|
6372
|
+
c = mix(c, c5, smoothstep(0.0, 1.0, clamp(s - 4.0, 0.0, 1.0)));
|
|
6373
|
+
c = mix(c, c6, smoothstep(0.0, 1.0, clamp(s - 5.0, 0.0, 1.0)));
|
|
6374
|
+
return min(c / paper, vec3(1.0));
|
|
6375
|
+
}
|
|
6376
|
+
|
|
6377
|
+
/** The ember ramp (\xA75 zone 3): off \u2192 #870E03 \u2192 #A12108 \u2192 #F77E14 \u2192 #FEFBE0, hottest last. */
|
|
6378
|
+
vec3 plEmberRamp(float t) {
|
|
6379
|
+
vec3 c0 = plLinear(vec3(0.529, 0.055, 0.012));
|
|
6380
|
+
vec3 c1 = plLinear(vec3(0.631, 0.129, 0.031));
|
|
6381
|
+
vec3 c2 = plLinear(vec3(0.969, 0.494, 0.078));
|
|
6382
|
+
vec3 c3 = plLinear(vec3(0.996, 0.984, 0.878));
|
|
6383
|
+
vec3 c = mix(c0, c1, smoothstep(0.0, 0.3, t));
|
|
6384
|
+
c = mix(c, c2, smoothstep(0.3, 0.7, t));
|
|
6385
|
+
return mix(c, c3, smoothstep(0.75, 1.0, t));
|
|
6386
|
+
}
|
|
6387
|
+
|
|
6388
|
+
void plDamage(inout vec4 color, inout float roughness) {
|
|
6389
|
+
plDamageLight = vec3(0.0);
|
|
6390
|
+
vec4 d = plDamageRead();
|
|
6391
|
+
vec2 uv = plDamageWarp();
|
|
6392
|
+
vec2 texel = 1.0 / vec2(textureSize(uDamage, 0));
|
|
6393
|
+
|
|
6394
|
+
// Everything measured off screen derivatives, up front: derivatives are
|
|
6395
|
+
// undefined past a branch that only some fragments of a quad take.
|
|
6396
|
+
vec2 upUv = plUpUv();
|
|
6397
|
+
vec4 e = texture2D(uDamage, uv + vec2(texel.x, 0.0));
|
|
6398
|
+
vec4 w = texture2D(uDamage, uv - vec2(texel.x, 0.0));
|
|
6399
|
+
vec4 n = texture2D(uDamage, uv + vec2(0.0, texel.y));
|
|
6400
|
+
vec4 s = texture2D(uDamage, uv - vec2(0.0, texel.y));
|
|
6401
|
+
// Per world unit: one texel step is 1 / (N - 1) of the sheet.
|
|
6402
|
+
vec2 perWorld = (vec2(textureSize(uDamage, 0)) - 1.0) / uSheetSize;
|
|
6403
|
+
vec2 gradP = vec2(e.a - w.a, n.a - s.a) * 0.5 * perWorld;
|
|
6404
|
+
vec2 gradC = vec2(e.r - w.r, n.r - s.r) * 0.5 * perWorld;
|
|
6405
|
+
// Millimetres from the cut, on the paper side \u2014 measured against the field
|
|
6406
|
+
// the cut is DRAWN from (softened, frayed, warped), through its own screen
|
|
6407
|
+
// derivatives. It used to divide by the slope of the raw texels, and since
|
|
6408
|
+
// paper burns through within a texel that slope is a cliff while the drawn
|
|
6409
|
+
// edge is spread over millimetres: a point well into the char computed as a
|
|
6410
|
+
// hair from the cut, and the ember line drew there \u2014 found by the gate.
|
|
6411
|
+
// The slope, though, is the SMOOTH presence's. The fray's fibre noise
|
|
6412
|
+
// changes pixel to pixel, and its slope is a cliff everywhere it touches:
|
|
6413
|
+
// divided by that, a point deep in the char came out a hair from the cut
|
|
6414
|
+
// and the ember glow drew there \u2014 the gate's stray pixels. The fray still
|
|
6415
|
+
// moves where the edge is; it just cannot fake how steep it is.
|
|
6416
|
+
float smoothA = plDamageSmooth;
|
|
6417
|
+
vec2 cutSlope = vec2(dFdx(smoothA), dFdy(smoothA));
|
|
6418
|
+
float pxFromCut = (d.a - 0.5) / max(length(cutSlope), 1e-4);
|
|
6419
|
+
// Millimetres per pixel ALONG the way to the cut, not across the screen's
|
|
6420
|
+
// x: a sheet leaning away is foreshortened vertically, so above and below a
|
|
6421
|
+
// hole a pixel covers more paper than beside it, and a scale taken from x
|
|
6422
|
+
// alone drew the ember zone too many pixels deep on the top and bottom rims.
|
|
6423
|
+
vec2 toCutPx = cutSlope / max(length(cutSlope), 1e-6);
|
|
6424
|
+
float mmPerPx = length(dFdx(-vViewPosition) * toCutPx.x + dFdy(-vViewPosition) * toCutPx.y) / PL_MM;
|
|
6425
|
+
float mm = pxFromCut * mmPerPx;
|
|
6426
|
+
// About a pixel of antialiasing, never more: where the warp makes the
|
|
6427
|
+
// estimate jump between neighbours, fwidth(mm) is millimetres wide, and the
|
|
6428
|
+
// ember's soft tail \u2014 faint, but multiplied by its light \u2014 drew stray glow
|
|
6429
|
+
// well out into the char.
|
|
6430
|
+
float aa = min(fwidth(mm), 1.5 * mmPerPx);
|
|
6431
|
+
// Scorch reaches ahead of the char, and further UP than anywhere else:
|
|
6432
|
+
// the char a centimetre below this point, and the char a few millimetres
|
|
6433
|
+
// around it, each lent to the scorch at a discount (\xA74.2, \xA74.5).
|
|
6434
|
+
vec2 mmUv = vec2(PL_MM) / uSheetSize;
|
|
6435
|
+
float charBelow = max(texture2D(uDamage, uv - upUv * uLook2.y * mmUv).r, texture2D(uDamage, uv - upUv * 0.5 * uLook2.y * mmUv).r);
|
|
6436
|
+
// A ring of eight, averaged: four diagonal maxima drew the grid back in as
|
|
6437
|
+
// squares wherever the scorch was magnified.
|
|
6438
|
+
float charWide = 0.0;
|
|
6439
|
+
for (int k = 0; k < 8; k++) {
|
|
6440
|
+
float a = float(k) * 0.7853982;
|
|
6441
|
+
charWide += texture2D(uDamage, uv + vec2(cos(a), sin(a)) * 7.0 * mmUv).r;
|
|
6442
|
+
}
|
|
6443
|
+
charWide /= 8.0;
|
|
6444
|
+
// Only paper that has a CUT beside it has an edge: a half-eaten cell out
|
|
6445
|
+
// in the middle of the sheet reads as "half a presence" too, and without
|
|
6446
|
+
// this the ember line and the ash lip drew on it \u2014 islands of glow on
|
|
6447
|
+
// paper, the painted glow by another route.
|
|
6448
|
+
// Eased in, not switched: a hard threshold on the neighbouring texels drew
|
|
6449
|
+
// the grid back into the ember glow as small squares.
|
|
6450
|
+
float cutNear = 1.0 - smoothstep(0.3, 0.62, min(min(e.a, w.a), min(n.a, s.a)));
|
|
6451
|
+
// And the cut has to really be there. \`mm\` extrapolates this pixel's
|
|
6452
|
+
// slope to half presence, and the uneven edge's warp bends the field inside
|
|
6453
|
+
// those few millimetres \u2014 measured: with the warp off the stray ember light
|
|
6454
|
+
// the gate finds all but vanishes. So step across the PAPER toward where the
|
|
6455
|
+
// cut should be, half a millimetre past it but never beyond the ember
|
|
6456
|
+
// zone's own outer edge (the lip and the widest bead \u2014 2.5 mm at the
|
|
6457
|
+
// spec's numbers, \xA75) plus that half, and read what the cut is drawn from.
|
|
6458
|
+
// Still paper: this is not an edge, however close the slope said it was.
|
|
6459
|
+
vec2 toCutPaper = -(dFdx(vPaperUv) * toCutPx.x + dFdy(vPaperUv) * toCutPx.y);
|
|
6460
|
+
float reachPx = min(max(mm, 0.0) + 0.5, uLook1.y + uLook0.x + 0.5) / max(mmPerPx, 1e-6);
|
|
6461
|
+
float pastA = plDrawnPresenceAt(vPaperUv + toCutPaper * reachPx);
|
|
6462
|
+
// And a hole, not a pinhole: the fray opens specks of void finer than a
|
|
6463
|
+
// pixel in part-burnt paper, which the check above would take for the
|
|
6464
|
+
// edge. The void has to go on for another half millimetre.
|
|
6465
|
+
float beyondA = plDrawnPresenceAt(vPaperUv + toCutPaper * (reachPx + 0.5 / max(mmPerPx, 1e-6)));
|
|
6466
|
+
cutNear *= (1.0 - smoothstep(0.42, 0.55, pastA)) * (1.0 - smoothstep(0.42, 0.55, beyondA));
|
|
6467
|
+
|
|
6468
|
+
// An untouched texel is left exactly as it was \u2014 the identity the whole
|
|
6469
|
+
// seam is built on. Nothing here has happened to this paper.
|
|
6470
|
+
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) {
|
|
6471
|
+
return;
|
|
6472
|
+
}
|
|
6473
|
+
|
|
6474
|
+
vec2 p = plLocal();
|
|
6475
|
+
|
|
6476
|
+
// Wet paper is darker and smoother: water fills the gaps between fibres
|
|
6477
|
+
// that scatter light, which is both effects from one cause.
|
|
6478
|
+
color.rgb *= 1.0 - 0.38 * d.g;
|
|
6479
|
+
roughness *= 1.0 - 0.45 * d.g;
|
|
6480
|
+
|
|
6481
|
+
// \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
|
|
6482
|
+
// Fingers: irregular teeth ALONG the scorch front, plus the grain's own
|
|
6483
|
+
// streaks. Measured along the front's tangent so they point outward.
|
|
6484
|
+
vec2 tangentC = length(gradC) > 1e-4 ? normalize(vec2(-gradC.y, gradC.x)) : vec2(1.0, 0.0);
|
|
6485
|
+
float along = dot(p, tangentC) / (4.0 * PL_MM);
|
|
6486
|
+
// Only where there really IS a front.
|
|
6487
|
+
//
|
|
6488
|
+
// This noise is sampled in a frame built from the char gradient, so that
|
|
6489
|
+
// its teeth run across the front wherever the front happens to point. That
|
|
6490
|
+
// works at the front and is meaningless behind it: a few millimetres into
|
|
6491
|
+
// the char the gradient is almost flat, its direction is whatever the
|
|
6492
|
+
// eight-bit field rounded to, and the frame turns with position \u2014 which
|
|
6493
|
+
// makes the noise a function OF THE CONTOURS rather than of the sheet. It
|
|
6494
|
+
// drew closed loops following the edge, nested a cell apart: the
|
|
6495
|
+
// "concentric contour lines, like a topographic map or tree rings" the
|
|
6496
|
+
// review found at macro range and left unexplained. Turning this one term
|
|
6497
|
+
// off removes them completely and changes nothing else.
|
|
6498
|
+
//
|
|
6499
|
+
// Measured on the scripted burn's peak: the gradient runs to about 19 at
|
|
6500
|
+
// the front and under 1 a few millimetres behind it.
|
|
6501
|
+
float frontness = smoothstep(1.0, 6.0, length(gradC));
|
|
6502
|
+
float fingers = (plFbm(vec2(along, dot(p, vec2(tangentC.y, -tangentC.x)) / (12.0 * PL_MM))) - 0.5) * 0.28 * frontness
|
|
6503
|
+
+ (plFbm(vec2(p.x * 9.0, p.y * 30.0)) - 0.5) * 0.08;
|
|
6504
|
+
fingers *= uLook2.w;
|
|
6505
|
+
// Fingers on the borrowed scorch too, so the outer edge is irregular
|
|
6506
|
+
// teeth rather than a smooth offset of the char.
|
|
6507
|
+
float reach = 0.75 + fingers * 4.0;
|
|
6508
|
+
// The scorch reads a softened char \u2014 its own texel and the four around it
|
|
6509
|
+
// \u2014 so a bilinear 64\xB2 field magnified shows no contours or corners. The
|
|
6510
|
+
// char zone keeps the sharp one: its edge is meant to be crisp.
|
|
6511
|
+
float soft = (2.0 * d.r + e.r + w.r + n.r + s.r) / 6.0;
|
|
6512
|
+
float self_ = clamp(mix(d.r, soft, 0.65) + fingers * smoothstep(0.0, 0.08, d.r), 0.0, 1.0);
|
|
6513
|
+
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));
|
|
6514
|
+
// Fibre-scale grain in the scorch, so a smooth field magnified never shows
|
|
6515
|
+
// its contours \u2014 real scorch is fibrous (Scorch.png), not banded.
|
|
6516
|
+
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);
|
|
6517
|
+
// A dither of about one and a half of the damage texture's own levels.
|
|
6518
|
+
//
|
|
6519
|
+
// The field arrives as eight bits a channel, so char comes in steps of
|
|
6520
|
+
// 1/255 \u2014 and the leading edge below spans 0.03 of it, which is seven and a
|
|
6521
|
+
// half steps. The fibre grain above would have hidden them, but it is
|
|
6522
|
+
// multiplied by smoothstep(0, 0.1, c), which is approximately zero exactly
|
|
6523
|
+
// where the leading edge lives. This is not: it applies everywhere, and
|
|
6524
|
+
// being finer than a level it can only ever move a value into the
|
|
6525
|
+
// neighbouring one.
|
|
6526
|
+
c = clamp(c + (plNoise(p * 900.0) - 0.5) * (1.5 / 255.0), 0.0, 1.0);
|
|
6527
|
+
// A steep leading edge \u2014 paper to straw in a sliver \u2014 then the long ramp.
|
|
6528
|
+
float lead = smoothstep(0.02, 0.05, c);
|
|
6529
|
+
float ramp = smoothstep(0.05, 0.7, c);
|
|
6530
|
+
color.rgb *= mix(vec3(1.0), pow(plScorchTint(ramp), vec3(uLook2.z)), lead);
|
|
6531
|
+
|
|
6532
|
+
// \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
|
|
6533
|
+
// A wide, smooth hand-over from scorch to char \u2014 the gradient is the point.
|
|
6534
|
+
float charZone = smoothstep(0.5, 0.9, c);
|
|
6535
|
+
vec2 cells = plVoronoi(p / (3.6 * PL_MM));
|
|
6536
|
+
// Sparse: a real crack network is broken, not a tiled floor \u2014 most cell
|
|
6537
|
+
// borders never split.
|
|
6538
|
+
float crack = (1.0 - smoothstep(0.008, 0.03, cells.y - cells.x)) * smoothstep(0.45, 0.65, plNoise(p / (7.0 * PL_MM)));
|
|
6539
|
+
float plate = plHash(floor(p / (3.6 * PL_MM)) + 3.7);
|
|
6540
|
+
// Dark orange where the char meets the scorch, deep brown toward the cut \u2014
|
|
6541
|
+
// warm all the way, and fading smoothly into the scorch's lighter browns
|
|
6542
|
+
// rather than stopping at a grey band.
|
|
6543
|
+
// Warm (dark orange \u2192 deep brown) or, turned down, the sampled greys.
|
|
6544
|
+
vec3 body = mix(plLinear(vec3(0.255, 0.239, 0.227)), plLinear(vec3(0.165, 0.086, 0.043)), uLook1.w);
|
|
6545
|
+
vec3 under = mix(plLinear(vec3(0.275, 0.169, 0.090)), plLinear(vec3(0.42, 0.18, 0.06)), uLook1.w);
|
|
6546
|
+
vec3 cracks = plLinear(vec3(0.039, 0.024, 0.016)); // #0A0604
|
|
6547
|
+
vec3 charColor = mix(under, body, smoothstep(0.66, 1.0, c));
|
|
6548
|
+
// Mottled at the plate scale and finer, and darker than the sampled body
|
|
6549
|
+
// under a bright key \u2014 the reference's char reads near black in the frame.
|
|
6550
|
+
charColor *= (0.75 + 0.35 * plate) * (0.8 + 0.4 * plNoise(p * 260.0));
|
|
6551
|
+
charColor = mix(charColor, cracks, crack * uLook2.x);
|
|
6552
|
+
color.rgb = mix(color.rgb, charColor, charZone);
|
|
6553
|
+
roughness = mix(roughness, 0.6, charZone);
|
|
6554
|
+
// Blistered, and each plate tilted its own way, so a raking light breaks
|
|
6555
|
+
// the char into plates rather than lighting it as one sheet of black.
|
|
6556
|
+
plHeight += charZone * ((plNoise(p * 380.0) - 0.5) * 0.00008 + (plate - 0.5) * 0.00018 - crack * 0.00016);
|
|
6557
|
+
|
|
6558
|
+
// \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
|
|
6559
|
+
// Inside the spec's zones (\xA75): the lip 0.5\u20131.5 mm, then the ember line
|
|
6560
|
+
// 0.3\u20131 mm \u2014 2.5 mm from the cut at most, which the gate holds it to.
|
|
6561
|
+
// Along the rim. Both the lip and the ember line below are laid out on this,
|
|
6562
|
+
// and unlike the scorch fingers above it is safe to sample noise in: it is
|
|
6563
|
+
// used ONLY within a couple of millimetres of the cut, where the presence
|
|
6564
|
+
// gradient is the actual edge and points somewhere real. The fingers'
|
|
6565
|
+
// mistake was carrying the same idea out into flat char, where the gradient
|
|
6566
|
+
// is rounding error and a noise sampled in its frame draws the contours.
|
|
6567
|
+
vec2 tangentP = length(gradP) > 1e-4 ? normalize(vec2(-gradP.y, gradP.x)) : vec2(1.0, 0.0);
|
|
6568
|
+
|
|
6569
|
+
float lipWidth = mix(0.4, max(uLook1.y, 0.41), plFbm(p / (2.5 * PL_MM)));
|
|
6570
|
+
// Broken, not a hem. Ash flakes off a cooling edge in pieces, so the lip
|
|
6571
|
+
// comes and goes along the rim and wanders in width where it is there. It
|
|
6572
|
+
// used to be a continuous band of even width with a highlight down the
|
|
6573
|
+
// middle of it, which read as a neon tube round the hole \u2014 the single most
|
|
6574
|
+
// synthetic thing at wide view.
|
|
6575
|
+
float lipAlong = dot(p, tangentP) / (3.2 * PL_MM);
|
|
6576
|
+
float lipBreak = smoothstep(0.34, 0.56, plFbm(vec2(lipAlong, 0.9)));
|
|
6577
|
+
float lip = (1.0 - smoothstep(lipWidth - aa, lipWidth + aa, mm)) * step(d.a, 0.999) * cutNear * lipBreak;
|
|
6578
|
+
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;
|
|
6579
|
+
color.rgb = mix(color.rgb, ash, lip);
|
|
6580
|
+
roughness = mix(roughness, 0.99, lip);
|
|
6581
|
+
// Barely raised. THIS is where the highlight came from: a 0.7 mm ridge
|
|
6582
|
+
// running round the hole is a cylinder, and a cylinder under a key light
|
|
6583
|
+
// has a specular line down it however rough the surface is. Ash is a
|
|
6584
|
+
// fragile crust a fraction of a millimetre proud of the paper, not a bead
|
|
6585
|
+
// of solder \u2014 and the shape of it belongs to the broken edge above, not to
|
|
6586
|
+
// a smooth ramp.
|
|
6587
|
+
plHeight += lip * (1.0 - mm / max(lipWidth, 0.1)) * 0.00018;
|
|
6588
|
+
|
|
6589
|
+
// \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
|
|
6590
|
+
// Beads along the cut: position along the edge, crawling on the burn's
|
|
6591
|
+
// clock, with a flicker of their own. Only where it is hot \u2014 and as the
|
|
6592
|
+
// heat goes the threshold rises, so the line breaks into fewer, dimmer
|
|
6593
|
+
// beads and they go out one by one.
|
|
6594
|
+
float s1 = dot(p, tangentP) / (2.4 * PL_MM) - uDamageTime * 0.55 * uLook0.w;
|
|
6595
|
+
float bead = plNoise(vec2(s1, 0.37)) * 0.7 + plNoise(vec2(s1 * 2.3, 5.1)) * 0.3;
|
|
6596
|
+
float flicker = 0.62 + 0.38 * plNoise(vec2(floor(s1) * 7.13, uDamageTime * 9.0 * uLook0.w));
|
|
6597
|
+
// Down to a smoulder's last flicker: a spent edge keeps a little heat in
|
|
6598
|
+
// the texture (\`Afterglow\`), and a bead should glow dim red on it rather
|
|
6599
|
+
// than vanish while the edge still has something to give.
|
|
6600
|
+
// Heat softened like the cut (its texel and the four around it), so the
|
|
6601
|
+
// beads and the crimson glow follow the burn rather than the grid's cells.
|
|
6602
|
+
float heatSoft = (2.0 * d.b + e.b + w.b + n.b + s.b) / 6.0;
|
|
6603
|
+
float hot = smoothstep(0.012, 0.55, heatSoft);
|
|
6604
|
+
// Coverage slides the threshold: more of the edge lit, or less.
|
|
6605
|
+
float shift = (uLook0.z - 0.5) * 0.5;
|
|
6606
|
+
float lit = smoothstep(mix(0.82, 0.45, hot) - shift, mix(0.9, 0.58, hot) - shift, bead);
|
|
6607
|
+
// Each bead tapers at its ends \u2014 a bead, not a dash cut from a strip.
|
|
6608
|
+
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);
|
|
6609
|
+
float band = smoothstep(lipWidth - aa, lipWidth + aa, mm) * (1.0 - smoothstep(lipWidth + width - aa, lipWidth + width + aa, mm)) * cutNear;
|
|
6610
|
+
// Mostly orange: a bead is red at its ends and yellow-white only at its
|
|
6611
|
+
// hottest core, which is what Ember_line.png shows. Mapped low and pushed
|
|
6612
|
+
// high by a power, so white is the exception rather than the line.
|
|
6613
|
+
// Orange through, yellow-white at the hottest cores. Dim red alone reads
|
|
6614
|
+
// salmon once the curve desaturates it, so the ramp starts at orange and
|
|
6615
|
+
// only the cooling (low heat) falls back into red.
|
|
6616
|
+
float t = hot * flicker * (0.45 + 0.55 * lit);
|
|
6617
|
+
color.rgb = mix(color.rgb, plLinear(vec3(0.12, 0.04, 0.02)), band * lit);
|
|
6618
|
+
// Saturated orange at orange brightness; only the hottest cores are pushed
|
|
6619
|
+
// high enough for the curve to roll them to white.
|
|
6620
|
+
plDamageLight = plEmberRamp(t) * mix(0.8, 6.0, t * t * t * t) * band * lit * hot * uLook0.y;
|
|
6621
|
+
|
|
6622
|
+
// The rest of a real burning edge (Ember_line.png): a dim crimson glow in
|
|
6623
|
+
// the char beside the beads \u2014 patches of it, drifting \u2014 and specks of
|
|
6624
|
+
// glowing fibre that come and go. Both stay under the bloom threshold, so
|
|
6625
|
+
// they read as heat in the char rather than as light; both only where the
|
|
6626
|
+
// edge is hot, and inside the zone the beads are held to \u2014 the lip plus
|
|
6627
|
+
// the widest bead, measured from the cut \u2014 so the glow never reaches out
|
|
6628
|
+
// over paper the gate says heat must not light.
|
|
6629
|
+
float zoneEnd = uLook1.y + uLook0.x;
|
|
6630
|
+
float seam = smoothstep(lipWidth - aa, lipWidth + aa, mm) * (1.0 - smoothstep(zoneEnd - 0.6, zoneEnd, mm)) * cutNear;
|
|
6631
|
+
float patch_ = smoothstep(0.35, 0.72, plFbm(p / (4.0 * PL_MM) + vec2(uDamageTime * 0.15 * uLook0.w, 0.0)));
|
|
6632
|
+
plDamageLight += vec3(0.55, 0.06, 0.01) * seam * patch_ * hot * uLook1.x;
|
|
6633
|
+
float speck = step(0.985, plHash(floor(p / (0.35 * PL_MM)) + floor(uDamageTime * 8.0 * uLook0.w)));
|
|
6634
|
+
plDamageLight += plEmberRamp(0.9) * 1.6 * speck * seam * hot * uLook3.z;
|
|
6635
|
+
|
|
6636
|
+
plDamageCut(color, d);
|
|
6637
|
+
}
|
|
6638
|
+
`
|
|
6639
|
+
);
|
|
6640
|
+
function carveDepthHelpers(helpers) {
|
|
6641
|
+
const darken = "uniform float uBackDarken;\n";
|
|
6642
|
+
const relief = helpers.indexOf("/**\n * The paper's relief");
|
|
6643
|
+
const bell = helpers.lastIndexOf("/**", helpers.indexOf("float plBell("));
|
|
6644
|
+
if (!helpers.includes(darken) || relief < 0 || bell <= relief) {
|
|
6645
|
+
throw new Error("compose: HELPERS changed shape \u2014 update carveDepthHelpers");
|
|
6646
|
+
}
|
|
6647
|
+
return (helpers.slice(0, relief) + helpers.slice(bell)).replace(darken, "");
|
|
6648
|
+
}
|
|
6649
|
+
var DEPTH_HELPERS = carveDepthHelpers(HELPERS);
|
|
6650
|
+
var DEPTH_VERTEX = (
|
|
6651
|
+
/* glsl */
|
|
6652
|
+
`
|
|
6653
|
+
varying vec2 vPaperUv;
|
|
6654
|
+
void main() {
|
|
6655
|
+
vPaperUv = uv;
|
|
6656
|
+
}
|
|
6657
|
+
`
|
|
6658
|
+
);
|
|
5756
6659
|
var AGING_CHUNK = (
|
|
5757
6660
|
/* glsl */
|
|
5758
6661
|
`
|
|
@@ -5836,7 +6739,55 @@ function composeSurface(surface, stock, thickness, maps = { hasFrontMap: false,
|
|
|
5836
6739
|
calls.push("plAging(csm_DiffuseColor);");
|
|
5837
6740
|
uniforms.uAgingAmount = { value: aging };
|
|
5838
6741
|
}
|
|
5839
|
-
|
|
6742
|
+
if (maps.hasDamage) {
|
|
6743
|
+
chunks.push(DAMAGE_CHUNK, DAMAGE_SHADE_CHUNK);
|
|
6744
|
+
calls.push("plDamage(csm_DiffuseColor, csm_Roughness);");
|
|
6745
|
+
uniforms.uDamage = { value: null };
|
|
6746
|
+
uniforms.uDamageDetail = { value: 1 };
|
|
6747
|
+
uniforms.uDamageTime = { value: 0 };
|
|
6748
|
+
const look = DAMAGE_LOOK_DEFAULTS;
|
|
6749
|
+
uniforms.uLook0 = {
|
|
6750
|
+
value: new THREE5.Vector4(look.emberWidth, look.emberIntensity, look.emberCoverage, look.emberFlicker)
|
|
6751
|
+
};
|
|
6752
|
+
uniforms.uLook1 = {
|
|
6753
|
+
value: new THREE5.Vector4(look.emberGlow, look.lipWidth, look.lipBrightness, look.charWarmth)
|
|
6754
|
+
};
|
|
6755
|
+
uniforms.uLook2 = {
|
|
6756
|
+
value: new THREE5.Vector4(look.charCracks, look.scorchReach, look.scorchDarkness, look.fingers)
|
|
6757
|
+
};
|
|
6758
|
+
uniforms.uLook3 = { value: new THREE5.Vector4(look.edgeWave, look.edgeBite, look.sparkle, 0) };
|
|
6759
|
+
}
|
|
6760
|
+
const cutting = [];
|
|
6761
|
+
const cuts = [];
|
|
6762
|
+
if (deckle) {
|
|
6763
|
+
cutting.push(DECKLE_CHUNK);
|
|
6764
|
+
cuts.push("plDeckle(color);");
|
|
6765
|
+
}
|
|
6766
|
+
if (perforation) {
|
|
6767
|
+
cutting.push(PERFORATION_CHUNK);
|
|
6768
|
+
cuts.push("plPerforation(color);");
|
|
6769
|
+
}
|
|
6770
|
+
if (maps.hasDamage) {
|
|
6771
|
+
cutting.push(DAMAGE_CHUNK);
|
|
6772
|
+
cuts.push("plDamageCut(color, plDamageRead());");
|
|
6773
|
+
}
|
|
6774
|
+
const depth = cutting.length > 0 ? {
|
|
6775
|
+
vertexShader: DEPTH_VERTEX,
|
|
6776
|
+
fragmentShader: (
|
|
6777
|
+
/* glsl */
|
|
6778
|
+
`
|
|
6779
|
+
${DEPTH_HELPERS}
|
|
6780
|
+
${cutting.join("\n")}
|
|
6781
|
+
void main() {
|
|
6782
|
+
vec4 color = vec4(1.0);
|
|
6783
|
+
float roughness = 1.0;
|
|
6784
|
+
${cuts.join("\n ")}
|
|
6785
|
+
if (color.a < 0.5) discard;
|
|
6786
|
+
}
|
|
6787
|
+
`
|
|
6788
|
+
)
|
|
6789
|
+
} : null;
|
|
6790
|
+
const relief = grain !== void 0 || creases.length > 0 || Boolean(maps.hasDamage);
|
|
5840
6791
|
const frontExpr = maps.hasFrontMap ? "texture2D(uFrontMap, vPaperUv).rgb" : "uStockColor";
|
|
5841
6792
|
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
6793
|
const fragmentShader = (
|
|
@@ -5865,6 +6816,7 @@ ${relief ? " // The relief every effect above described, spent once \u2014 see
|
|
|
5865
6816
|
${stock.adhesive ? "// Adhesive underside: higher specular than the printed face.\n if (!gl_FrontFacing) csm_Roughness = 0.18;" : ""}
|
|
5866
6817
|
// What the key light pushes through the sheet, filtered by the ink on it.
|
|
5867
6818
|
csm_Emissive = plTransmission(front);
|
|
6819
|
+
${maps.hasDamage ? " // The ember line \u2014 the only light a burn gives off from the sheet itself.\n csm_Emissive += plDamageLight;" : ""}
|
|
5868
6820
|
}
|
|
5869
6821
|
`
|
|
5870
6822
|
);
|
|
@@ -5876,11 +6828,14 @@ ${relief ? " // The relief every effect above described, spent once \u2014 see
|
|
|
5876
6828
|
aging !== void 0 ? "a" : "",
|
|
5877
6829
|
perforation ? "p" : "",
|
|
5878
6830
|
stock.adhesive ? "A" : ""
|
|
5879
|
-
].join("")}:${maps.hasFrontMap ? "F" : ""}${maps.hasBackMap ? "B" : ""}`,
|
|
6831
|
+
].join("")}:${maps.hasFrontMap ? "F" : ""}${maps.hasBackMap ? "B" : ""}${maps.hasDamage ? "D" : ""}`,
|
|
5880
6832
|
vertexShader: VERTEX,
|
|
5881
6833
|
fragmentShader,
|
|
5882
6834
|
uniforms,
|
|
5883
|
-
|
|
6835
|
+
// Anything that removes paper needs fragments discarded rather than
|
|
6836
|
+
// blended — a hole has to cut the depth buffer and the shadow too.
|
|
6837
|
+
alphaTest: deckle || perforation || maps.hasDamage ? 0.5 : 0,
|
|
6838
|
+
depth
|
|
5884
6839
|
};
|
|
5885
6840
|
}
|
|
5886
6841
|
function pad(values, fill = 0) {
|
|
@@ -5889,8 +6844,43 @@ function pad(values, fill = 0) {
|
|
|
5889
6844
|
return out;
|
|
5890
6845
|
}
|
|
5891
6846
|
|
|
6847
|
+
// src/surface/useDamageTexture.ts
|
|
6848
|
+
import * as THREE6 from "three";
|
|
6849
|
+
import { useFrame } from "@react-three/fiber";
|
|
6850
|
+
import { useEffect as useEffect4, useMemo as useMemo2 } from "react";
|
|
6851
|
+
function useDamageTexture(source) {
|
|
6852
|
+
const texture = useMemo2(() => {
|
|
6853
|
+
if (!source) return null;
|
|
6854
|
+
const t = new THREE6.DataTexture(
|
|
6855
|
+
source.pixels,
|
|
6856
|
+
source.size,
|
|
6857
|
+
source.size,
|
|
6858
|
+
THREE6.RGBAFormat,
|
|
6859
|
+
THREE6.UnsignedByteType
|
|
6860
|
+
);
|
|
6861
|
+
t.magFilter = THREE6.LinearFilter;
|
|
6862
|
+
t.minFilter = THREE6.LinearFilter;
|
|
6863
|
+
t.wrapS = THREE6.ClampToEdgeWrapping;
|
|
6864
|
+
t.wrapT = THREE6.ClampToEdgeWrapping;
|
|
6865
|
+
t.generateMipmaps = false;
|
|
6866
|
+
t.colorSpace = THREE6.NoColorSpace;
|
|
6867
|
+
t.flipY = false;
|
|
6868
|
+
t.userData.version = source.version;
|
|
6869
|
+
t.needsUpdate = true;
|
|
6870
|
+
return t;
|
|
6871
|
+
}, [source]);
|
|
6872
|
+
useEffect4(() => () => texture?.dispose(), [texture]);
|
|
6873
|
+
useFrame(() => {
|
|
6874
|
+
if (!source || !texture) return;
|
|
6875
|
+
if (texture.userData.version === source.version) return;
|
|
6876
|
+
texture.userData.version = source.version;
|
|
6877
|
+
texture.needsUpdate = true;
|
|
6878
|
+
});
|
|
6879
|
+
return texture;
|
|
6880
|
+
}
|
|
6881
|
+
|
|
5892
6882
|
// src/surface/PaperMaterial.tsx
|
|
5893
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
6883
|
+
import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
5894
6884
|
function PaperMaterial({
|
|
5895
6885
|
stock,
|
|
5896
6886
|
texture,
|
|
@@ -5899,26 +6889,31 @@ function PaperMaterial({
|
|
|
5899
6889
|
thickness,
|
|
5900
6890
|
sheet: sheet2,
|
|
5901
6891
|
lighting = "studio",
|
|
5902
|
-
creases
|
|
6892
|
+
creases,
|
|
6893
|
+
damage
|
|
5903
6894
|
}) {
|
|
5904
6895
|
const rig = useLightRig(lighting);
|
|
6896
|
+
const damageTexture = useDamageTexture(damage);
|
|
5905
6897
|
const composed = composeSurface(
|
|
5906
6898
|
surface,
|
|
5907
6899
|
stock,
|
|
5908
6900
|
thickness,
|
|
5909
6901
|
{
|
|
5910
6902
|
hasFrontMap: Boolean(texture),
|
|
5911
|
-
hasBackMap: Boolean(backTexture)
|
|
6903
|
+
hasBackMap: Boolean(backTexture),
|
|
6904
|
+
hasDamage: Boolean(damageTexture)
|
|
5912
6905
|
},
|
|
5913
6906
|
sheet2,
|
|
5914
6907
|
rig,
|
|
5915
6908
|
creases ?? resolveCreases(surface, [], sheet2 ?? { width: 1, height: 1.4 })
|
|
5916
6909
|
);
|
|
5917
|
-
const bound =
|
|
5918
|
-
|
|
6910
|
+
const bound = useMemo3(() => composed.uniforms, [composed.structureKey]);
|
|
6911
|
+
useEffect5(() => {
|
|
5919
6912
|
for (const [key, uniform] of Object.entries(composed.uniforms)) {
|
|
5920
|
-
if (!bound[key] || key === "uFrontMap" || key === "uBackMap")
|
|
5921
|
-
|
|
6913
|
+
if (!bound[key] || key === "uFrontMap" || key === "uBackMap" || key === "uDamage" || key === "uDamageDetail" || key === "uDamageTime" || key.startsWith("uLook")) {
|
|
6914
|
+
continue;
|
|
6915
|
+
}
|
|
6916
|
+
if (bound[key].value instanceof THREE7.Color && uniform.value instanceof THREE7.Color) {
|
|
5922
6917
|
;
|
|
5923
6918
|
bound[key].value.copy(uniform.value);
|
|
5924
6919
|
} else {
|
|
@@ -5926,27 +6921,80 @@ function PaperMaterial({
|
|
|
5926
6921
|
}
|
|
5927
6922
|
}
|
|
5928
6923
|
});
|
|
5929
|
-
|
|
6924
|
+
useEffect5(() => {
|
|
5930
6925
|
if (bound.uFrontMap) bound.uFrontMap.value = texture;
|
|
5931
6926
|
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
|
-
|
|
6927
|
+
if (bound.uDamage) bound.uDamage.value = damageTexture;
|
|
6928
|
+
}, [bound, texture, backTexture, damageTexture]);
|
|
6929
|
+
useFrame2((state) => {
|
|
6930
|
+
if (!bound.uDamageDetail) return;
|
|
6931
|
+
if (bound.uDamageTime) {
|
|
6932
|
+
const time = damage?.time ?? state.clock.elapsedTime;
|
|
6933
|
+
bound.uDamageTime.value = Number.isFinite(time) ? time : 0;
|
|
6934
|
+
}
|
|
6935
|
+
if (bound.uLook0) {
|
|
6936
|
+
const look = { ...DAMAGE_LOOK_DEFAULTS, ...damage?.look };
|
|
6937
|
+
const v = (x, d2) => Number.isFinite(x) ? x : d2;
|
|
6938
|
+
const d = DAMAGE_LOOK_DEFAULTS;
|
|
6939
|
+
bound.uLook0.value.set(
|
|
6940
|
+
v(look.emberWidth, d.emberWidth),
|
|
6941
|
+
v(look.emberIntensity, d.emberIntensity),
|
|
6942
|
+
v(look.emberCoverage, d.emberCoverage),
|
|
6943
|
+
v(look.emberFlicker, d.emberFlicker)
|
|
6944
|
+
);
|
|
6945
|
+
bound.uLook1.value.set(
|
|
6946
|
+
v(look.emberGlow, d.emberGlow),
|
|
6947
|
+
v(look.lipWidth, d.lipWidth),
|
|
6948
|
+
v(look.lipBrightness, d.lipBrightness),
|
|
6949
|
+
v(look.charWarmth, d.charWarmth)
|
|
6950
|
+
);
|
|
6951
|
+
bound.uLook2.value.set(
|
|
6952
|
+
v(look.charCracks, d.charCracks),
|
|
6953
|
+
v(look.scorchReach, d.scorchReach),
|
|
6954
|
+
v(look.scorchDarkness, d.scorchDarkness),
|
|
6955
|
+
v(look.fingers, d.fingers)
|
|
6956
|
+
);
|
|
6957
|
+
bound.uLook3.value.set(
|
|
6958
|
+
v(look.edgeWave, d.edgeWave),
|
|
6959
|
+
v(look.edgeBite, d.edgeBite),
|
|
6960
|
+
v(look.sparkle, d.sparkle),
|
|
6961
|
+
0
|
|
6962
|
+
);
|
|
6963
|
+
}
|
|
6964
|
+
const detail = damage?.detail ?? 1;
|
|
6965
|
+
bound.uDamageDetail.value = Number.isFinite(detail) ? Math.min(1, Math.max(0, detail)) : 1;
|
|
6966
|
+
});
|
|
6967
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6968
|
+
/* @__PURE__ */ jsx3(
|
|
6969
|
+
CustomShaderMaterial,
|
|
6970
|
+
{
|
|
6971
|
+
baseMaterial: THREE7.MeshStandardMaterial,
|
|
6972
|
+
vertexShader: composed.vertexShader,
|
|
6973
|
+
fragmentShader: composed.fragmentShader,
|
|
6974
|
+
uniforms: bound,
|
|
6975
|
+
color: "#ffffff",
|
|
6976
|
+
roughness: stock.roughness,
|
|
6977
|
+
metalness: 0,
|
|
6978
|
+
transparent: stock.opacity < 1,
|
|
6979
|
+
opacity: stock.opacity,
|
|
6980
|
+
alphaTest: composed.alphaTest,
|
|
6981
|
+
side: THREE7.DoubleSide
|
|
6982
|
+
},
|
|
6983
|
+
composed.structureKey
|
|
6984
|
+
),
|
|
6985
|
+
composed.depth ? /* @__PURE__ */ jsx3(
|
|
6986
|
+
CustomShaderMaterial,
|
|
6987
|
+
{
|
|
6988
|
+
attach: "customDepthMaterial",
|
|
6989
|
+
baseMaterial: THREE7.MeshDepthMaterial,
|
|
6990
|
+
vertexShader: composed.depth.vertexShader,
|
|
6991
|
+
fragmentShader: composed.depth.fragmentShader,
|
|
6992
|
+
uniforms: bound,
|
|
6993
|
+
depthPacking: THREE7.RGBADepthPacking
|
|
6994
|
+
},
|
|
6995
|
+
`${composed.structureKey}:depth`
|
|
6996
|
+
) : null
|
|
6997
|
+
] });
|
|
5950
6998
|
}
|
|
5951
6999
|
|
|
5952
7000
|
// src/motion/onTwos.ts
|
|
@@ -5960,7 +7008,7 @@ function quantizeProgress(p, duration, fps = ON_TWOS_FPS) {
|
|
|
5960
7008
|
}
|
|
5961
7009
|
|
|
5962
7010
|
// src/PaperMesh.tsx
|
|
5963
|
-
import { jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
7011
|
+
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
5964
7012
|
function configInputs(props) {
|
|
5965
7013
|
return [
|
|
5966
7014
|
props.preset ?? null,
|
|
@@ -5978,7 +7026,7 @@ function configInputs(props) {
|
|
|
5978
7026
|
}
|
|
5979
7027
|
function useResolvedConfig(props) {
|
|
5980
7028
|
const inputs = useStable(configInputs(props));
|
|
5981
|
-
return
|
|
7029
|
+
return useMemo4(() => resolveConfig(props), [inputs]);
|
|
5982
7030
|
}
|
|
5983
7031
|
function resolveConfig(props) {
|
|
5984
7032
|
const base = props.preset ? typeof props.preset === "string" ? getPreset(props.preset) : parsePreset(props.preset) : paperConfigSchema.parse({});
|
|
@@ -5997,12 +7045,12 @@ function resolveConfig(props) {
|
|
|
5997
7045
|
}
|
|
5998
7046
|
var CLOTH_MAX_SEGMENTS = 28;
|
|
5999
7047
|
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
|
|
7048
|
+
var dragPlane = new THREE8.Plane();
|
|
7049
|
+
var dragPoint = new THREE8.Vector3();
|
|
7050
|
+
var planeNormal = new THREE8.Vector3();
|
|
7051
|
+
var anchorScratch = new THREE8.Vector3();
|
|
7052
|
+
var worldScratch = new THREE8.Vector3();
|
|
7053
|
+
var quatScratch = new THREE8.Quaternion();
|
|
6006
7054
|
var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
6007
7055
|
const resolved = useResolvedConfig(props);
|
|
6008
7056
|
const reduced = usePrefersReducedMotion(props.reducedMotion);
|
|
@@ -6031,7 +7079,7 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6031
7079
|
const draggingRef = useRef3(null);
|
|
6032
7080
|
const configRef = useRef3(config);
|
|
6033
7081
|
configRef.current = config;
|
|
6034
|
-
const baseRotation =
|
|
7082
|
+
const baseRotation = useMemo4(() => {
|
|
6035
7083
|
const [rx, ry, rz] = props.rotation ?? [0, 0, 0];
|
|
6036
7084
|
return [rx, ry + config.scene.turn * Math.PI / 180, rz];
|
|
6037
7085
|
}, [props.rotation, config.scene.turn]);
|
|
@@ -6046,16 +7094,16 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6046
7094
|
const creasesRef = useRef3(null);
|
|
6047
7095
|
creasesRef.current ??= new CreaseTracker(config.memory.creases);
|
|
6048
7096
|
const creases = creasesRef.current;
|
|
6049
|
-
|
|
7097
|
+
useEffect6(() => {
|
|
6050
7098
|
if (!draggingRef.current && !playingRef.current) overridesRef.current = {};
|
|
6051
7099
|
creases.reset();
|
|
6052
7100
|
dirtyRef.current = true;
|
|
6053
7101
|
}, [behaviorKey, deformersKey, sheetKey, physicsKey]);
|
|
6054
|
-
|
|
7102
|
+
useEffect6(() => {
|
|
6055
7103
|
creases.adopt(configRef.current.memory.creases);
|
|
6056
7104
|
dirtyRef.current = true;
|
|
6057
7105
|
}, [creaseKey]);
|
|
6058
|
-
const { minSegments, autoSegments, animatedStack } =
|
|
7106
|
+
const { minSegments, autoSegments, animatedStack } = useMemo4(() => {
|
|
6059
7107
|
const cfg = configRef.current;
|
|
6060
7108
|
const probe2 = withMemory(buildStack(cfg, {}), cfg);
|
|
6061
7109
|
if (!probe2) {
|
|
@@ -6084,16 +7132,16 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6084
7132
|
animatedStack: animated
|
|
6085
7133
|
};
|
|
6086
7134
|
}, [behaviorKey, deformersKey, physicsKey, memoryKey]);
|
|
6087
|
-
const geometry =
|
|
7135
|
+
const geometry = useMemo4(() => {
|
|
6088
7136
|
if (isStrip) {
|
|
6089
7137
|
const strip = configRef.current.physics;
|
|
6090
7138
|
const nodes = stripNodeCount(config.sheet.height, strip.perforation);
|
|
6091
|
-
return new
|
|
7139
|
+
return new THREE8.PlaneGeometry(config.sheet.width, config.sheet.height, 1, nodes - 1);
|
|
6092
7140
|
}
|
|
6093
7141
|
if (!isCloth) return createSheetGeometry(config.sheet, minSegments, autoSegments);
|
|
6094
7142
|
const [sx, sy] = resolveSegments(config.sheet, minSegments);
|
|
6095
7143
|
const capped = Math.min(Math.max(sx, sy), CLOTH_MAX_SEGMENTS);
|
|
6096
|
-
return new
|
|
7144
|
+
return new THREE8.PlaneGeometry(config.sheet.width, config.sheet.height, capped, capped);
|
|
6097
7145
|
}, [
|
|
6098
7146
|
sheetKey,
|
|
6099
7147
|
minSegments,
|
|
@@ -6102,12 +7150,12 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6102
7150
|
isStrip,
|
|
6103
7151
|
isStrip ? config.physics.perforation : 0
|
|
6104
7152
|
]);
|
|
6105
|
-
|
|
6106
|
-
const basePositions =
|
|
7153
|
+
useEffect6(() => () => geometry.dispose(), [geometry]);
|
|
7154
|
+
const basePositions = useMemo4(
|
|
6107
7155
|
() => Float32Array.from(geometry.attributes.position.array),
|
|
6108
7156
|
[geometry]
|
|
6109
7157
|
);
|
|
6110
|
-
const stripSim =
|
|
7158
|
+
const stripSim = useMemo4(() => {
|
|
6111
7159
|
if (!isStrip) return null;
|
|
6112
7160
|
const strip = configRef.current.physics;
|
|
6113
7161
|
return new StripSim(config.sheet.height, config.sheet.width, {
|
|
@@ -6125,7 +7173,7 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6125
7173
|
});
|
|
6126
7174
|
}, [geometry, isStrip]);
|
|
6127
7175
|
const lastSimRef = useRef3(null);
|
|
6128
|
-
const sim =
|
|
7176
|
+
const sim = useMemo4(() => {
|
|
6129
7177
|
if (!isCloth) return null;
|
|
6130
7178
|
const cloth = configRef.current.physics;
|
|
6131
7179
|
const cols = geometry.parameters.widthSegments + 1;
|
|
@@ -6139,9 +7187,10 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6139
7187
|
next.adopt(lastSimRef.current);
|
|
6140
7188
|
return next;
|
|
6141
7189
|
}, [geometry, isCloth, isCloth ? config.physics.pins : ""]);
|
|
6142
|
-
|
|
7190
|
+
useEffect6(() => {
|
|
6143
7191
|
lastSimRef.current = sim;
|
|
6144
7192
|
}, [sim]);
|
|
7193
|
+
const coupling = useMemo4(() => sim ? new DamageCoupling(sim) : null, [sim]);
|
|
6145
7194
|
const stock = getStock(config.stock);
|
|
6146
7195
|
const texture = useContentTexture(config.content, config.sheet, stock);
|
|
6147
7196
|
const backTexture = useContentTexture(config.content.back, config.sheet, stock);
|
|
@@ -6183,7 +7232,7 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6183
7232
|
playingRef.current = false;
|
|
6184
7233
|
tweenRef.current?.pause();
|
|
6185
7234
|
};
|
|
6186
|
-
|
|
7235
|
+
useEffect6(() => {
|
|
6187
7236
|
if (props.autoplay && !reduced) play();
|
|
6188
7237
|
return () => {
|
|
6189
7238
|
tweenRef.current?.kill();
|
|
@@ -6226,7 +7275,22 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6226
7275
|
const index = id ? handles.findIndex((h) => h.id === id) : 0;
|
|
6227
7276
|
const mesh = handleRefs.current[index];
|
|
6228
7277
|
if (!mesh) return null;
|
|
6229
|
-
return mesh.getWorldPosition(target ?? new
|
|
7278
|
+
return mesh.getWorldPosition(target ?? new THREE8.Vector3());
|
|
7279
|
+
},
|
|
7280
|
+
surfacePoint(u, v, target) {
|
|
7281
|
+
const mesh = meshRef.current;
|
|
7282
|
+
if (!mesh) return null;
|
|
7283
|
+
const { widthSegments, heightSegments } = geometry.parameters;
|
|
7284
|
+
const out = surfacePointAt(
|
|
7285
|
+
geometry.attributes.position.array,
|
|
7286
|
+
widthSegments + 1,
|
|
7287
|
+
heightSegments + 1,
|
|
7288
|
+
u,
|
|
7289
|
+
v,
|
|
7290
|
+
target ?? new THREE8.Vector3()
|
|
7291
|
+
);
|
|
7292
|
+
mesh.updateWorldMatrix(true, false);
|
|
7293
|
+
return out.applyMatrix4(mesh.matrixWorld);
|
|
6230
7294
|
},
|
|
6231
7295
|
get state() {
|
|
6232
7296
|
return machineState;
|
|
@@ -6236,12 +7300,12 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6236
7300
|
placeProgrammatic: () => machineRef.current?.placeProgrammatic() ?? false,
|
|
6237
7301
|
returnProgrammatic: () => machineRef.current?.returnProgrammatic() ?? false
|
|
6238
7302
|
}));
|
|
6239
|
-
const shadedCreases =
|
|
7303
|
+
const shadedCreases = useMemo4(
|
|
6240
7304
|
() => resolveCreases(config.surface, config.memory.creases, config.sheet),
|
|
6241
7305
|
[config.surface, config.memory.creases, config.sheet]
|
|
6242
7306
|
);
|
|
6243
7307
|
const idlePose = useRef3({ position: [0, 0, 0], rotation: [0, 0, 0] });
|
|
6244
|
-
|
|
7308
|
+
useFrame3(({ clock }, delta) => {
|
|
6245
7309
|
const cfg = liveConfig();
|
|
6246
7310
|
const now = reduced ? 0 : cfg.onTwos ? quantizeTime(clock.elapsedTime) : clock.elapsedTime;
|
|
6247
7311
|
const hasBehaviorTransform = Boolean(behavior?.transform && cfg.behavior);
|
|
@@ -6325,8 +7389,10 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6325
7389
|
stiffness: cloth.stiffness,
|
|
6326
7390
|
floor: cloth.floor
|
|
6327
7391
|
});
|
|
7392
|
+
coupling?.update(props.damage);
|
|
6328
7393
|
sim.step(delta);
|
|
6329
7394
|
const moved = !sim.asleep;
|
|
7395
|
+
if (moved) coupling?.follow();
|
|
6330
7396
|
if (!applyShape(sim.positions, moved) && moved) {
|
|
6331
7397
|
const position = geometry.attributes.position;
|
|
6332
7398
|
position.array.set(sim.positions);
|
|
@@ -6358,8 +7424,8 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6358
7424
|
const p = overridesRef.current[behavior.progressParam];
|
|
6359
7425
|
if (typeof p === "number") props.onProgress?.(p);
|
|
6360
7426
|
};
|
|
6361
|
-
const grabAnchor = useRef3(new
|
|
6362
|
-
const grabOffset = useRef3(new
|
|
7427
|
+
const grabAnchor = useRef3(new THREE8.Vector3());
|
|
7428
|
+
const grabOffset = useRef3(new THREE8.Vector3());
|
|
6363
7429
|
const clothDown = (e) => {
|
|
6364
7430
|
if (!isCloth || !sim || !props.interactive || !groupRef.current) return;
|
|
6365
7431
|
e.stopPropagation();
|
|
@@ -6422,7 +7488,7 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6422
7488
|
e.target.releasePointerCapture(e.pointerId);
|
|
6423
7489
|
};
|
|
6424
7490
|
const sendState = (event) => machineRef.current?.send(event);
|
|
6425
|
-
return /* @__PURE__ */
|
|
7491
|
+
return /* @__PURE__ */ jsxs2("group", { ref: groupRef, position: props.position, rotation: baseRotation, children: [
|
|
6426
7492
|
/* @__PURE__ */ jsx4(
|
|
6427
7493
|
"mesh",
|
|
6428
7494
|
{
|
|
@@ -6454,7 +7520,8 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6454
7520
|
thickness: config.sheet.thickness,
|
|
6455
7521
|
sheet: config.sheet,
|
|
6456
7522
|
lighting: config.scene.lighting,
|
|
6457
|
-
creases: shadedCreases
|
|
7523
|
+
creases: shadedCreases,
|
|
7524
|
+
damage: props.damage
|
|
6458
7525
|
}
|
|
6459
7526
|
)
|
|
6460
7527
|
}
|
|
@@ -6463,7 +7530,7 @@ var PaperMesh = forwardRef(function PaperMesh2(props, ref) {
|
|
|
6463
7530
|
// is no deformer stack for a handle to drive. Harmless as `!isCloth`
|
|
6464
7531
|
// only because the schema makes a sim and a behavior exclusive — the
|
|
6465
7532
|
// intent is what is written here.
|
|
6466
|
-
!simKind && behavior?.handles?.map((h, i) => /* @__PURE__ */
|
|
7533
|
+
!simKind && behavior?.handles?.map((h, i) => /* @__PURE__ */ jsxs2(
|
|
6467
7534
|
"mesh",
|
|
6468
7535
|
{
|
|
6469
7536
|
userData: { paperlabChrome: true },
|
|
@@ -6536,13 +7603,13 @@ function buildStack(config, overrides, behavior, t = 0) {
|
|
|
6536
7603
|
}
|
|
6537
7604
|
|
|
6538
7605
|
// src/scene/PaperLighting.tsx
|
|
6539
|
-
import * as
|
|
6540
|
-
import { useEffect as
|
|
6541
|
-
import { useFrame as
|
|
7606
|
+
import * as THREE10 from "three";
|
|
7607
|
+
import { useEffect as useEffect7, useMemo as useMemo5, useRef as useRef4 } from "react";
|
|
7608
|
+
import { useFrame as useFrame4, useThree as useThree2 } from "@react-three/fiber";
|
|
6542
7609
|
import { ContactShadows } from "@react-three/drei";
|
|
6543
7610
|
|
|
6544
7611
|
// src/scene/environment.ts
|
|
6545
|
-
import * as
|
|
7612
|
+
import * as THREE9 from "three";
|
|
6546
7613
|
|
|
6547
7614
|
// src/scene/color.ts
|
|
6548
7615
|
var probe;
|
|
@@ -6591,7 +7658,7 @@ function drawSky(ctx, preset) {
|
|
|
6591
7658
|
const x = skyU(angles.azimuth) * WIDTH;
|
|
6592
7659
|
const y = skyV(angles.elevation) * HEIGHT;
|
|
6593
7660
|
const radius = WIDTH * 0.3;
|
|
6594
|
-
const color = new
|
|
7661
|
+
const color = new THREE9.Color(preset.key.color);
|
|
6595
7662
|
const strength = Math.min(1, preset.key.intensity / 3);
|
|
6596
7663
|
for (const offset of [-WIDTH, 0, WIDTH]) {
|
|
6597
7664
|
const glow = ctx.createRadialGradient(x + offset, y, 0, x + offset, y, radius);
|
|
@@ -6610,14 +7677,14 @@ function makeSkyEquirect(preset) {
|
|
|
6610
7677
|
canvas.height = HEIGHT;
|
|
6611
7678
|
const ctx = canvas.getContext("2d");
|
|
6612
7679
|
drawSky(ctx, preset);
|
|
6613
|
-
const texture = new
|
|
6614
|
-
texture.mapping =
|
|
6615
|
-
texture.colorSpace =
|
|
7680
|
+
const texture = new THREE9.CanvasTexture(canvas);
|
|
7681
|
+
texture.mapping = THREE9.EquirectangularReflectionMapping;
|
|
7682
|
+
texture.colorSpace = THREE9.SRGBColorSpace;
|
|
6616
7683
|
return texture;
|
|
6617
7684
|
}
|
|
6618
7685
|
function buildEnvironment(renderer, preset) {
|
|
6619
7686
|
const equirect = makeSkyEquirect(preset);
|
|
6620
|
-
const pmrem = new
|
|
7687
|
+
const pmrem = new THREE9.PMREMGenerator(renderer);
|
|
6621
7688
|
const target = pmrem.fromEquirectangular(equirect);
|
|
6622
7689
|
pmrem.dispose();
|
|
6623
7690
|
equirect.dispose();
|
|
@@ -6628,11 +7695,11 @@ function buildEnvironment(renderer, preset) {
|
|
|
6628
7695
|
}
|
|
6629
7696
|
|
|
6630
7697
|
// src/scene/PaperLighting.tsx
|
|
6631
|
-
import { Fragment, jsx as jsx5, jsxs as
|
|
7698
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
6632
7699
|
var toneMappings = {
|
|
6633
|
-
agx:
|
|
6634
|
-
neutral:
|
|
6635
|
-
filmic:
|
|
7700
|
+
agx: THREE10.AgXToneMapping,
|
|
7701
|
+
neutral: THREE10.NeutralToneMapping,
|
|
7702
|
+
filmic: THREE10.ACESFilmicToneMapping
|
|
6636
7703
|
};
|
|
6637
7704
|
function mulberry322(seed) {
|
|
6638
7705
|
let a = seed;
|
|
@@ -6689,15 +7756,15 @@ function makeGoboTexture(kind) {
|
|
|
6689
7756
|
}
|
|
6690
7757
|
}
|
|
6691
7758
|
}
|
|
6692
|
-
const texture = new
|
|
6693
|
-
texture.wrapS = texture.wrapT =
|
|
7759
|
+
const texture = new THREE10.CanvasTexture(canvas);
|
|
7760
|
+
texture.wrapS = texture.wrapT = THREE10.RepeatWrapping;
|
|
6694
7761
|
return texture;
|
|
6695
7762
|
}
|
|
6696
7763
|
function StudioLight({ rig }) {
|
|
6697
7764
|
const gl = useThree2((s) => s.gl);
|
|
6698
7765
|
const scene = useThree2((s) => s.scene);
|
|
6699
7766
|
const sky = `${rig.sky.zenith}|${rig.sky.horizon}|${rig.sky.ground}|${rig.key.color}|${rig.key.intensity}|${rig.key.position.join()}`;
|
|
6700
|
-
|
|
7767
|
+
useEffect7(() => {
|
|
6701
7768
|
const environment = buildEnvironment(gl, rig);
|
|
6702
7769
|
const previous = scene.environment;
|
|
6703
7770
|
scene.environment = environment.texture;
|
|
@@ -6706,7 +7773,7 @@ function StudioLight({ rig }) {
|
|
|
6706
7773
|
environment.dispose();
|
|
6707
7774
|
};
|
|
6708
7775
|
}, [gl, scene, sky]);
|
|
6709
|
-
|
|
7776
|
+
useEffect7(() => {
|
|
6710
7777
|
const previous = scene.environmentIntensity;
|
|
6711
7778
|
scene.environmentIntensity = rig.studio;
|
|
6712
7779
|
return () => {
|
|
@@ -6727,13 +7794,13 @@ function PaperLighting({
|
|
|
6727
7794
|
contactShadow = true,
|
|
6728
7795
|
environment = true
|
|
6729
7796
|
}) {
|
|
6730
|
-
const p =
|
|
7797
|
+
const p = useMemo5(() => rig ?? resolveLighting(preset, light), [rig, preset, light]);
|
|
6731
7798
|
const mapSize = shadowMapSize ?? p.shadow.mapSize;
|
|
6732
7799
|
const castShadow = mapSize > 0;
|
|
6733
7800
|
const reduced = usePrefersReducedMotion(reducedMotion);
|
|
6734
7801
|
const gl = useThree2((s) => s.gl);
|
|
6735
7802
|
const scene = useThree2((s) => s.scene);
|
|
6736
|
-
|
|
7803
|
+
useEffect7(() => {
|
|
6737
7804
|
const previousExposure = gl.toneMappingExposure;
|
|
6738
7805
|
const previousFilm = gl.toneMapping;
|
|
6739
7806
|
gl.toneMappingExposure = p.exposure;
|
|
@@ -6743,23 +7810,23 @@ function PaperLighting({
|
|
|
6743
7810
|
gl.toneMapping = previousFilm;
|
|
6744
7811
|
};
|
|
6745
7812
|
}, [gl, p.exposure, p.film]);
|
|
6746
|
-
|
|
7813
|
+
useEffect7(() => {
|
|
6747
7814
|
if (!p.fog) return;
|
|
6748
7815
|
const previous = scene.fog;
|
|
6749
|
-
scene.fog = new
|
|
7816
|
+
scene.fog = new THREE10.Fog(p.fog.color, p.fog.near, p.fog.far);
|
|
6750
7817
|
return () => {
|
|
6751
7818
|
scene.fog = previous;
|
|
6752
7819
|
};
|
|
6753
7820
|
}, [scene, p.fog]);
|
|
6754
|
-
const goboMap =
|
|
6755
|
-
|
|
7821
|
+
const goboMap = useMemo5(() => p.gobo ? makeGoboTexture(p.gobo.kind) : null, [p.gobo?.kind]);
|
|
7822
|
+
useEffect7(() => () => goboMap?.dispose(), [goboMap]);
|
|
6756
7823
|
const driftRef = useRef4(0);
|
|
6757
|
-
|
|
7824
|
+
useFrame4((_, delta) => {
|
|
6758
7825
|
if (!goboMap || !p.gobo || reduced) return;
|
|
6759
7826
|
driftRef.current += delta * p.gobo.drift;
|
|
6760
7827
|
goboMap.offset.set(driftRef.current, driftRef.current * 0.6);
|
|
6761
7828
|
});
|
|
6762
|
-
return /* @__PURE__ */
|
|
7829
|
+
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
6763
7830
|
p.studio > 0 && (environment ? /* @__PURE__ */ jsx5(StudioLight, { rig: p }) : (
|
|
6764
7831
|
// The studio light degrades rather than disappearing. A hemisphere
|
|
6765
7832
|
// is the cheap half of what the room does — light from above, a
|
|
@@ -6817,15 +7884,15 @@ function PaperLighting({
|
|
|
6817
7884
|
}
|
|
6818
7885
|
|
|
6819
7886
|
// src/scene/backdrop.tsx
|
|
6820
|
-
import * as
|
|
6821
|
-
import { useEffect as
|
|
7887
|
+
import * as THREE11 from "three";
|
|
7888
|
+
import { useEffect as useEffect8, useState as useState4 } from "react";
|
|
6822
7889
|
import { useThree as useThree3 } from "@react-three/fiber";
|
|
6823
7890
|
function PaperBackdrop({ backdrop }) {
|
|
6824
7891
|
const scene = useThree3((s) => s.scene);
|
|
6825
7892
|
const size = useThree3((s) => s.size);
|
|
6826
7893
|
const [image, setImage] = useState4(null);
|
|
6827
7894
|
const src = backdrop?.image ?? "";
|
|
6828
|
-
|
|
7895
|
+
useEffect8(() => {
|
|
6829
7896
|
if (!src) {
|
|
6830
7897
|
setImage(null);
|
|
6831
7898
|
return;
|
|
@@ -6841,7 +7908,7 @@ function PaperBackdrop({ backdrop }) {
|
|
|
6841
7908
|
};
|
|
6842
7909
|
}, [src]);
|
|
6843
7910
|
const key = JSON.stringify({ backdrop: backdrop ?? null, w: size.width, h: size.height, loaded: !!image });
|
|
6844
|
-
|
|
7911
|
+
useEffect8(() => {
|
|
6845
7912
|
if (!backdrop) return;
|
|
6846
7913
|
const previous = scene.background;
|
|
6847
7914
|
const texture = paintBackdrop(backdrop, size.width, size.height, image);
|
|
@@ -6877,15 +7944,15 @@ function paintBackdrop(backdrop, width, height, image) {
|
|
|
6877
7944
|
ctx.globalAlpha = 1;
|
|
6878
7945
|
}
|
|
6879
7946
|
}
|
|
6880
|
-
const texture = new
|
|
6881
|
-
texture.colorSpace =
|
|
7947
|
+
const texture = new THREE11.CanvasTexture(canvas);
|
|
7948
|
+
texture.colorSpace = THREE11.SRGBColorSpace;
|
|
6882
7949
|
return texture;
|
|
6883
7950
|
}
|
|
6884
7951
|
|
|
6885
7952
|
// src/field/dropZones.tsx
|
|
6886
|
-
import * as
|
|
6887
|
-
import { createContext as createContext2, useContext as useContext2, useEffect as
|
|
6888
|
-
import { jsx as jsx6, jsxs as
|
|
7953
|
+
import * as THREE12 from "three";
|
|
7954
|
+
import { createContext as createContext2, useContext as useContext2, useEffect as useEffect9, useMemo as useMemo6, useRef as useRef5, useSyncExternalStore } from "react";
|
|
7955
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
6889
7956
|
var DropZoneRegistry = class {
|
|
6890
7957
|
zones = /* @__PURE__ */ new Map();
|
|
6891
7958
|
hoveredId = null;
|
|
@@ -6942,10 +8009,10 @@ function DropZone(props) {
|
|
|
6942
8009
|
const registry4 = useContext2(DropZoneContext);
|
|
6943
8010
|
const { id, accept, bounds, highlight = "glow", onPlace } = props;
|
|
6944
8011
|
const place = useRef5(onPlace);
|
|
6945
|
-
|
|
8012
|
+
useEffect9(() => {
|
|
6946
8013
|
place.current = onPlace;
|
|
6947
8014
|
}, [onPlace]);
|
|
6948
|
-
|
|
8015
|
+
useEffect9(() => {
|
|
6949
8016
|
if (!registry4) return;
|
|
6950
8017
|
return registry4.register({
|
|
6951
8018
|
id,
|
|
@@ -6963,15 +8030,15 @@ function DropZoneVisual({ registry: registry4, config }) {
|
|
|
6963
8030
|
const hovered = registry4.hovered === config.id;
|
|
6964
8031
|
const style = config.highlight ?? "glow";
|
|
6965
8032
|
const [w, h] = config.bounds.size;
|
|
6966
|
-
const edges =
|
|
6967
|
-
const plane = new
|
|
6968
|
-
const geo = new
|
|
8033
|
+
const edges = useMemo6(() => {
|
|
8034
|
+
const plane = new THREE12.PlaneGeometry(w, h);
|
|
8035
|
+
const geo = new THREE12.EdgesGeometry(plane);
|
|
6969
8036
|
plane.dispose();
|
|
6970
8037
|
return geo;
|
|
6971
8038
|
}, [w, h]);
|
|
6972
|
-
|
|
6973
|
-
return /* @__PURE__ */
|
|
6974
|
-
style !== "outline" && /* @__PURE__ */
|
|
8039
|
+
useEffect9(() => () => edges.dispose(), [edges]);
|
|
8040
|
+
return /* @__PURE__ */ jsxs4("group", { position: config.bounds.position, children: [
|
|
8041
|
+
style !== "outline" && /* @__PURE__ */ jsxs4("mesh", { children: [
|
|
6975
8042
|
/* @__PURE__ */ jsx6("planeGeometry", { args: [w, h] }),
|
|
6976
8043
|
/* @__PURE__ */ jsx6(
|
|
6977
8044
|
"meshBasicMaterial",
|
|
@@ -7572,17 +8639,17 @@ registerLayout(colonnade);
|
|
|
7572
8639
|
registerLayout(sheet);
|
|
7573
8640
|
|
|
7574
8641
|
// src/PaperField.tsx
|
|
7575
|
-
import * as
|
|
8642
|
+
import * as THREE17 from "three";
|
|
7576
8643
|
import { gsap as gsap5 } from "gsap";
|
|
7577
|
-
import { Canvas, useFrame as
|
|
7578
|
-
import { forwardRef as forwardRef2, useEffect as
|
|
8644
|
+
import { Canvas, useFrame as useFrame7, useThree as useThree6 } from "@react-three/fiber";
|
|
8645
|
+
import { forwardRef as forwardRef2, useEffect as useEffect15, useMemo as useMemo10, useRef as useRef8 } from "react";
|
|
7579
8646
|
|
|
7580
8647
|
// src/scene/release.tsx
|
|
7581
8648
|
import { useThree as useThree4 } from "@react-three/fiber";
|
|
7582
|
-
import { useEffect as
|
|
8649
|
+
import { useEffect as useEffect10 } from "react";
|
|
7583
8650
|
function ReleaseContextOnUnmount() {
|
|
7584
8651
|
const gl = useThree4((s) => s.gl);
|
|
7585
|
-
|
|
8652
|
+
useEffect10(
|
|
7586
8653
|
() => () => {
|
|
7587
8654
|
gl.forceContextLoss();
|
|
7588
8655
|
gl.dispose();
|
|
@@ -7638,15 +8705,15 @@ function resolveFieldSlotConfig(slot, fallback, index, layoutId, layoutOptions)
|
|
|
7638
8705
|
}
|
|
7639
8706
|
|
|
7640
8707
|
// src/field/fieldGroup.tsx
|
|
7641
|
-
import * as
|
|
8708
|
+
import * as THREE14 from "three";
|
|
7642
8709
|
import { gsap as gsap3 } from "gsap";
|
|
7643
|
-
import { useFrame as
|
|
7644
|
-
import { useEffect as
|
|
8710
|
+
import { useFrame as useFrame5 } from "@react-three/fiber";
|
|
8711
|
+
import { useEffect as useEffect12, useMemo as useMemo7, useRef as useRef6 } from "react";
|
|
7645
8712
|
import CustomShaderMaterial2 from "three-custom-shader-material";
|
|
7646
8713
|
|
|
7647
8714
|
// src/content/atlas.ts
|
|
7648
|
-
import * as
|
|
7649
|
-
import { useEffect as
|
|
8715
|
+
import * as THREE13 from "three";
|
|
8716
|
+
import { useEffect as useEffect11, useState as useState5 } from "react";
|
|
7650
8717
|
var MAX_ATLAS = 4096;
|
|
7651
8718
|
function atlasGrid(count, aspect = 1) {
|
|
7652
8719
|
const cols = Math.max(1, Math.min(count, Math.ceil(Math.sqrt(count * Math.max(aspect, 0.01)))));
|
|
@@ -7655,7 +8722,7 @@ function atlasGrid(count, aspect = 1) {
|
|
|
7655
8722
|
function useContentAtlas(contents, sheet2, stock) {
|
|
7656
8723
|
const [atlas, setAtlas] = useState5(null);
|
|
7657
8724
|
const stableContents = useStable(contents);
|
|
7658
|
-
|
|
8725
|
+
useEffect11(() => {
|
|
7659
8726
|
let disposed = false;
|
|
7660
8727
|
const aspect = sheet2.height / sheet2.width;
|
|
7661
8728
|
const { cols, rows } = atlasGrid(contents.length, aspect);
|
|
@@ -7671,8 +8738,8 @@ function useContentAtlas(contents, sheet2, stock) {
|
|
|
7671
8738
|
const ctx = canvas.getContext("2d");
|
|
7672
8739
|
ctx.fillStyle = stock.color;
|
|
7673
8740
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
7674
|
-
const texture = new
|
|
7675
|
-
texture.colorSpace =
|
|
8741
|
+
const texture = new THREE13.CanvasTexture(canvas);
|
|
8742
|
+
texture.colorSpace = THREE13.SRGBColorSpace;
|
|
7676
8743
|
texture.anisotropy = 4;
|
|
7677
8744
|
setAtlas({ texture, cols, rows });
|
|
7678
8745
|
const drawTile = (index, tile) => {
|
|
@@ -7841,7 +8908,7 @@ function fieldShapeStack(config, progress) {
|
|
|
7841
8908
|
|
|
7842
8909
|
// src/field/fieldGroup.tsx
|
|
7843
8910
|
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
7844
|
-
var scratchObj = new
|
|
8911
|
+
var scratchObj = new THREE14.Object3D();
|
|
7845
8912
|
var scratchAero = { position: [0, 0, 0], rotation: [0, 0, 0] };
|
|
7846
8913
|
var FIELD_SEGMENT_CAP = 48;
|
|
7847
8914
|
var FIELD_AUTO_CEILING = 128;
|
|
@@ -7864,7 +8931,7 @@ function FieldGroup({
|
|
|
7864
8931
|
behavior ? config.behavior[behavior.progressParam] : 0
|
|
7865
8932
|
);
|
|
7866
8933
|
const buildStackAt = (progress) => fieldShapeStack(config, progress);
|
|
7867
|
-
const initialStack =
|
|
8934
|
+
const initialStack = useMemo7(
|
|
7868
8935
|
() => buildStackAt(progressRef.current),
|
|
7869
8936
|
[
|
|
7870
8937
|
JSON.stringify(config.behavior ?? null),
|
|
@@ -7873,7 +8940,7 @@ function FieldGroup({
|
|
|
7873
8940
|
]
|
|
7874
8941
|
);
|
|
7875
8942
|
const structureKey = initialStack.map((i) => i.type).join("|");
|
|
7876
|
-
const geometry =
|
|
8943
|
+
const geometry = useMemo7(() => {
|
|
7877
8944
|
const floor = stackMinSegments(initialStack, config.sheet);
|
|
7878
8945
|
const want = [0, 0];
|
|
7879
8946
|
for (const p of PROGRESS_SAMPLES2) {
|
|
@@ -7902,28 +8969,28 @@ function FieldGroup({
|
|
|
7902
8969
|
atlasIdx[i] = i;
|
|
7903
8970
|
phase[i] = indices[i] * 0.618034 % 1 * 4;
|
|
7904
8971
|
}
|
|
7905
|
-
geo.setAttribute("aAtlas", new
|
|
7906
|
-
geo.setAttribute("aPhase", new
|
|
7907
|
-
geo.setAttribute("aBias", new
|
|
8972
|
+
geo.setAttribute("aAtlas", new THREE14.InstancedBufferAttribute(atlasIdx, 1));
|
|
8973
|
+
geo.setAttribute("aPhase", new THREE14.InstancedBufferAttribute(phase, 1));
|
|
8974
|
+
geo.setAttribute("aBias", new THREE14.InstancedBufferAttribute(bias, 1));
|
|
7908
8975
|
return geo;
|
|
7909
8976
|
}, [JSON.stringify(config.sheet), structureKey, count, autoCeiling]);
|
|
7910
|
-
|
|
8977
|
+
useEffect12(() => () => geometry.dispose(), [geometry]);
|
|
7911
8978
|
const atlas = useContentAtlas(contents, config.sheet, stock);
|
|
7912
|
-
const shader =
|
|
8979
|
+
const shader = useMemo7(() => {
|
|
7913
8980
|
const composed = buildDisplacementGLSL(initialStack, config.sheet);
|
|
7914
8981
|
const uniforms = {};
|
|
7915
8982
|
for (const [name, value] of Object.entries(composed.uniforms)) {
|
|
7916
8983
|
uniforms[name] = {
|
|
7917
|
-
value: Array.isArray(value) && value.length === 2 ? new
|
|
8984
|
+
value: Array.isArray(value) && value.length === 2 ? new THREE14.Vector2(...value) : value
|
|
7918
8985
|
};
|
|
7919
8986
|
}
|
|
7920
8987
|
uniforms.uPlTime = { value: 0 };
|
|
7921
8988
|
uniforms.uAtlas = { value: null };
|
|
7922
|
-
uniforms.uAtlasGrid = { value: new
|
|
8989
|
+
uniforms.uAtlasGrid = { value: new THREE14.Vector2(1, 1) };
|
|
7923
8990
|
uniforms.uBackDarken = {
|
|
7924
8991
|
value: 1 - Math.min(0.45, 0.12 + config.sheet.thickness * 0.9) * stock.opacity
|
|
7925
8992
|
};
|
|
7926
|
-
uniforms.uStockColor = { value: new
|
|
8993
|
+
uniforms.uStockColor = { value: new THREE14.Color(stock.color) };
|
|
7927
8994
|
uniforms.uShowThrough = { value: config.surface.showThrough ?? stock.showThrough };
|
|
7928
8995
|
Object.assign(uniforms, translucencyUniforms(config.surface.translucency ?? stock.translucency, rig));
|
|
7929
8996
|
return {
|
|
@@ -7938,19 +9005,19 @@ function FieldGroup({
|
|
|
7938
9005
|
config.surface.showThrough,
|
|
7939
9006
|
config.surface.translucency
|
|
7940
9007
|
]);
|
|
7941
|
-
|
|
9008
|
+
useEffect12(() => {
|
|
7942
9009
|
const values = translucencyValues(config.surface.translucency ?? stock.translucency, rig);
|
|
7943
9010
|
shader.uniforms.uTranslucency.value = values.translucency;
|
|
7944
9011
|
shader.uniforms.uBackLightDir.value.copy(values.direction);
|
|
7945
9012
|
shader.uniforms.uBackLightColor.value.copy(values.color);
|
|
7946
9013
|
shader.uniforms.uAmbientTransmission.value = values.ambient;
|
|
7947
9014
|
}, [shader, rig, config.surface.translucency, stock.translucency]);
|
|
7948
|
-
|
|
9015
|
+
useEffect12(() => {
|
|
7949
9016
|
if (!atlas) return;
|
|
7950
9017
|
shader.uniforms.uAtlas.value = atlas.texture;
|
|
7951
9018
|
shader.uniforms.uAtlasGrid.value.set(atlas.cols, atlas.rows);
|
|
7952
9019
|
}, [atlas, shader]);
|
|
7953
|
-
|
|
9020
|
+
useEffect12(() => {
|
|
7954
9021
|
if (!behavior || shared.reduced) return;
|
|
7955
9022
|
const state = { p: progressRef.current };
|
|
7956
9023
|
const tween = gsap3.to(state, {
|
|
@@ -7968,7 +9035,7 @@ function FieldGroup({
|
|
|
7968
9035
|
};
|
|
7969
9036
|
}, [structureKey, shared.reduced]);
|
|
7970
9037
|
const meshRef = useRef6(null);
|
|
7971
|
-
|
|
9038
|
+
useFrame5(({ clock }) => {
|
|
7972
9039
|
const mesh = meshRef.current;
|
|
7973
9040
|
if (!mesh) return;
|
|
7974
9041
|
const elapsed = clock.elapsedTime - Math.max(shared.mountTimeRef.current, 0);
|
|
@@ -7978,7 +9045,7 @@ function FieldGroup({
|
|
|
7978
9045
|
for (const [name, value] of Object.entries(values)) {
|
|
7979
9046
|
const uniform = shader.uniforms[name];
|
|
7980
9047
|
if (!uniform) continue;
|
|
7981
|
-
if (uniform.value instanceof
|
|
9048
|
+
if (uniform.value instanceof THREE14.Vector2 && Array.isArray(value)) {
|
|
7982
9049
|
uniform.value.set(value[0], value[1]);
|
|
7983
9050
|
} else {
|
|
7984
9051
|
uniform.value = value;
|
|
@@ -8063,13 +9130,13 @@ function FieldGroup({
|
|
|
8063
9130
|
children: /* @__PURE__ */ jsx7(
|
|
8064
9131
|
CustomShaderMaterial2,
|
|
8065
9132
|
{
|
|
8066
|
-
baseMaterial:
|
|
9133
|
+
baseMaterial: THREE14.MeshStandardMaterial,
|
|
8067
9134
|
vertexShader: shader.vertexShader,
|
|
8068
9135
|
fragmentShader: shader.fragmentShader,
|
|
8069
9136
|
uniforms: shader.uniforms,
|
|
8070
9137
|
roughness: stock.roughness,
|
|
8071
9138
|
metalness: 0,
|
|
8072
|
-
side:
|
|
9139
|
+
side: THREE14.DoubleSide
|
|
8073
9140
|
},
|
|
8074
9141
|
`${structureKey}:${count}`
|
|
8075
9142
|
)
|
|
@@ -8115,8 +9182,8 @@ var easeOut = (t) => 1 - (1 - t) ** 3;
|
|
|
8115
9182
|
var easeInOut = (t) => t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2;
|
|
8116
9183
|
|
|
8117
9184
|
// src/field/backingSheet.tsx
|
|
8118
|
-
import * as
|
|
8119
|
-
import { useEffect as
|
|
9185
|
+
import * as THREE15 from "three";
|
|
9186
|
+
import { useEffect as useEffect13, useMemo as useMemo8 } from "react";
|
|
8120
9187
|
|
|
8121
9188
|
// src/content/backing.ts
|
|
8122
9189
|
function silhouetteRects(o, count) {
|
|
@@ -8181,7 +9248,7 @@ function roundedRect(ctx, x, y, w, h, r) {
|
|
|
8181
9248
|
}
|
|
8182
9249
|
|
|
8183
9250
|
// src/field/backingSheet.tsx
|
|
8184
|
-
import { jsx as jsx8, jsxs as
|
|
9251
|
+
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
8185
9252
|
var BACKING_TINT = "#f3ecdd";
|
|
8186
9253
|
function BackingSheet({
|
|
8187
9254
|
options,
|
|
@@ -8189,7 +9256,7 @@ function BackingSheet({
|
|
|
8189
9256
|
removed
|
|
8190
9257
|
}) {
|
|
8191
9258
|
const { width, height } = sheetBackingSize(options);
|
|
8192
|
-
const canvas =
|
|
9259
|
+
const canvas = useMemo8(() => {
|
|
8193
9260
|
if (typeof document === "undefined") return null;
|
|
8194
9261
|
const c = document.createElement("canvas");
|
|
8195
9262
|
const scale = Math.min(1024, Math.round(360 * Math.max(width, height))) / Math.max(width, height);
|
|
@@ -8197,26 +9264,26 @@ function BackingSheet({
|
|
|
8197
9264
|
c.height = Math.max(2, Math.round(height * scale));
|
|
8198
9265
|
return c;
|
|
8199
9266
|
}, [width, height]);
|
|
8200
|
-
const texture =
|
|
9267
|
+
const texture = useMemo8(() => canvas ? new THREE15.CanvasTexture(canvas) : null, [canvas]);
|
|
8201
9268
|
const removedKey = [...removed].sort((a, b) => a - b).join(",");
|
|
8202
|
-
|
|
9269
|
+
useEffect13(() => {
|
|
8203
9270
|
if (!canvas || !texture) return;
|
|
8204
9271
|
drawBacking(canvas, { options, count, tint: BACKING_TINT, removed });
|
|
8205
9272
|
texture.needsUpdate = true;
|
|
8206
9273
|
}, [canvas, texture, JSON.stringify(options), count, removedKey]);
|
|
8207
|
-
|
|
8208
|
-
return /* @__PURE__ */
|
|
9274
|
+
useEffect13(() => () => texture?.dispose(), [texture]);
|
|
9275
|
+
return /* @__PURE__ */ jsxs5("mesh", { receiveShadow: true, children: [
|
|
8209
9276
|
/* @__PURE__ */ jsx8("planeGeometry", { args: [width, height] }),
|
|
8210
9277
|
/* @__PURE__ */ jsx8("meshStandardMaterial", { map: texture, color: "#ffffff", roughness: 0.92, metalness: 0 })
|
|
8211
9278
|
] });
|
|
8212
9279
|
}
|
|
8213
9280
|
|
|
8214
9281
|
// src/field/interactiveField.tsx
|
|
8215
|
-
import * as
|
|
9282
|
+
import * as THREE16 from "three";
|
|
8216
9283
|
import { gsap as gsap4 } from "gsap";
|
|
8217
|
-
import { useFrame as
|
|
8218
|
-
import { useContext as useContext3, useEffect as
|
|
8219
|
-
import { jsx as jsx9, jsxs as
|
|
9284
|
+
import { useFrame as useFrame6, useThree as useThree5 } from "@react-three/fiber";
|
|
9285
|
+
import { useContext as useContext3, useEffect as useEffect14, useMemo as useMemo9, useRef as useRef7, useState as useState6 } from "react";
|
|
9286
|
+
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
8220
9287
|
var PICK_BEHAVIORS = /* @__PURE__ */ new Set(["peel", "carry"]);
|
|
8221
9288
|
function InteractiveField(props) {
|
|
8222
9289
|
const { papers, fallback, layoutId, layoutOptions, sheetOptions } = props;
|
|
@@ -8224,14 +9291,14 @@ function InteractiveField(props) {
|
|
|
8224
9291
|
const layout = getLayout(layoutId);
|
|
8225
9292
|
const reduced = usePrefersReducedMotion(props.reducedMotion);
|
|
8226
9293
|
const contextRegistry = useContext3(DropZoneContext);
|
|
8227
|
-
const registry4 =
|
|
9294
|
+
const registry4 = useMemo9(() => contextRegistry ?? new DropZoneRegistry(), [contextRegistry]);
|
|
8228
9295
|
const camera = useThree5((s) => s.camera);
|
|
8229
9296
|
const gl = useThree5((s) => s.gl);
|
|
8230
9297
|
const controls = useThree5((s) => s.controls);
|
|
8231
9298
|
const [removed, setRemoved] = useState6(EMPTY_SET);
|
|
8232
9299
|
const [slotPatches, setSlotPatches] = useState6({});
|
|
8233
9300
|
const [slotStates, setSlotStates] = useState6({});
|
|
8234
|
-
const slotConfigs =
|
|
9301
|
+
const slotConfigs = useMemo9(
|
|
8235
9302
|
() => papers.map((slot, i) => {
|
|
8236
9303
|
const config = resolveFieldSlotConfig(slot, fallback, i, layoutId, layoutOptions);
|
|
8237
9304
|
const patch = slotPatches[i];
|
|
@@ -8245,7 +9312,7 @@ function InteractiveField(props) {
|
|
|
8245
9312
|
slotPatches
|
|
8246
9313
|
]
|
|
8247
9314
|
);
|
|
8248
|
-
const poses =
|
|
9315
|
+
const poses = useMemo9(
|
|
8249
9316
|
() => slotConfigs.map((_, i) => layout.pose(i, total, layoutOptions, 0, props.sheet)),
|
|
8250
9317
|
[layoutId, JSON.stringify(layoutOptions), total, props.sheet.width, props.sheet.height]
|
|
8251
9318
|
);
|
|
@@ -8265,10 +9332,10 @@ function InteractiveField(props) {
|
|
|
8265
9332
|
}
|
|
8266
9333
|
props.onSlotStateChange?.(i, state);
|
|
8267
9334
|
};
|
|
8268
|
-
const raycaster =
|
|
8269
|
-
const planeScratch =
|
|
8270
|
-
const pointScratch =
|
|
8271
|
-
const ndcScratch =
|
|
9335
|
+
const raycaster = useMemo9(() => new THREE16.Raycaster(), []);
|
|
9336
|
+
const planeScratch = useMemo9(() => new THREE16.Plane(new THREE16.Vector3(0, 0, 1), 0), []);
|
|
9337
|
+
const pointScratch = useMemo9(() => new THREE16.Vector3(), []);
|
|
9338
|
+
const ndcScratch = useMemo9(() => new THREE16.Vector2(), []);
|
|
8272
9339
|
const planePoint = (clientX, clientY, planeZ) => {
|
|
8273
9340
|
const rect = gl.domElement.getBoundingClientRect();
|
|
8274
9341
|
ndcScratch.set(
|
|
@@ -8436,7 +9503,7 @@ function InteractiveField(props) {
|
|
|
8436
9503
|
if (zone) settleInto(carried, zone);
|
|
8437
9504
|
else returnHome(carried);
|
|
8438
9505
|
};
|
|
8439
|
-
|
|
9506
|
+
useEffect14(() => {
|
|
8440
9507
|
const move = (e) => onPointerMoveRef.current(e);
|
|
8441
9508
|
const up = (e) => onPointerUpRef.current(e);
|
|
8442
9509
|
window.addEventListener("pointermove", move);
|
|
@@ -8446,7 +9513,7 @@ function InteractiveField(props) {
|
|
|
8446
9513
|
window.removeEventListener("pointerup", up);
|
|
8447
9514
|
};
|
|
8448
9515
|
}, []);
|
|
8449
|
-
|
|
9516
|
+
useFrame6((_, delta) => {
|
|
8450
9517
|
const carried = carriedRef.current;
|
|
8451
9518
|
if (!carried || carried.settling) return;
|
|
8452
9519
|
const group = groupRefs.current[carried.slot];
|
|
@@ -8472,8 +9539,8 @@ function InteractiveField(props) {
|
|
|
8472
9539
|
group.position.z = carried.homePose.position[2] + 0.15;
|
|
8473
9540
|
handle.set("drive", carryDrive(speed));
|
|
8474
9541
|
const lag = 0.25;
|
|
8475
|
-
group.rotation.y += (
|
|
8476
|
-
group.rotation.x += (
|
|
9542
|
+
group.rotation.y += (THREE16.MathUtils.clamp(-vx * lag, -0.6, 0.6) - group.rotation.y) * 0.12;
|
|
9543
|
+
group.rotation.x += (THREE16.MathUtils.clamp(vy * lag * 0.7, -0.5, 0.5) - group.rotation.x) * 0.12;
|
|
8477
9544
|
}
|
|
8478
9545
|
const name = slotName(carried.slot);
|
|
8479
9546
|
const zone = zonesLive().find(
|
|
@@ -8509,7 +9576,7 @@ function InteractiveField(props) {
|
|
|
8509
9576
|
zoneIds: () => zonesLive().map((z25) => z25.id),
|
|
8510
9577
|
slotState: (slot) => slotStates[slot] ?? "rest"
|
|
8511
9578
|
};
|
|
8512
|
-
|
|
9579
|
+
useEffect14(() => {
|
|
8513
9580
|
const ref = props.a11yRef;
|
|
8514
9581
|
if (!ref) return;
|
|
8515
9582
|
ref.current = {
|
|
@@ -8523,7 +9590,7 @@ function InteractiveField(props) {
|
|
|
8523
9590
|
ref.current = null;
|
|
8524
9591
|
};
|
|
8525
9592
|
}, [props.a11yRef]);
|
|
8526
|
-
return /* @__PURE__ */
|
|
9593
|
+
return /* @__PURE__ */ jsxs6("group", { children: [
|
|
8527
9594
|
sheetOptions?.backing && /* @__PURE__ */ jsx9(BackingSheet, { options: sheetOptions, count: total, removed }),
|
|
8528
9595
|
(props.zones ?? []).map((zone) => /* @__PURE__ */ jsx9(DropZoneVisual, { registry: registry4, config: zone }, zone.id)),
|
|
8529
9596
|
slotConfigs.map((config, i) => {
|
|
@@ -8563,7 +9630,7 @@ function InteractiveField(props) {
|
|
|
8563
9630
|
|
|
8564
9631
|
// src/field/keyboardMirror.tsx
|
|
8565
9632
|
import { useState as useState7 } from "react";
|
|
8566
|
-
import { jsx as jsx10, jsxs as
|
|
9633
|
+
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
8567
9634
|
function fieldKeyboardStep(carry2, slot, key, controller) {
|
|
8568
9635
|
if (!carry2) {
|
|
8569
9636
|
if (key === "Enter" || key === " ") {
|
|
@@ -8625,9 +9692,9 @@ function FieldKeyboardMirror({
|
|
|
8625
9692
|
if (handled) e.preventDefault();
|
|
8626
9693
|
if (carry2 !== carrying) setCarrying(carry2);
|
|
8627
9694
|
};
|
|
8628
|
-
return /* @__PURE__ */
|
|
9695
|
+
return /* @__PURE__ */ jsxs7("fieldset", { style: mirrorHidden, children: [
|
|
8629
9696
|
/* @__PURE__ */ jsx10("legend", { children: "Interactive papers" }),
|
|
8630
|
-
papers.map((slot, i) => /* @__PURE__ */
|
|
9697
|
+
papers.map((slot, i) => /* @__PURE__ */ jsxs7(
|
|
8631
9698
|
"button",
|
|
8632
9699
|
{
|
|
8633
9700
|
type: "button",
|
|
@@ -8636,7 +9703,7 @@ function FieldKeyboardMirror({
|
|
|
8636
9703
|
"aria-pressed": carrying?.slot === i,
|
|
8637
9704
|
children: [
|
|
8638
9705
|
paperLabel(slot, i),
|
|
8639
|
-
carrying?.slot === i && /* @__PURE__ */
|
|
9706
|
+
carrying?.slot === i && /* @__PURE__ */ jsxs7("span", { "aria-live": "polite", children: [
|
|
8640
9707
|
" ",
|
|
8641
9708
|
"\u2014 carrying; zone ",
|
|
8642
9709
|
controller.current?.zoneIds()[carrying.zoneIndex] ?? "none",
|
|
@@ -8710,17 +9777,17 @@ function fitCamera(layout, n, options, sheet2, fovDeg, aspect, margin = 1.06) {
|
|
|
8710
9777
|
}
|
|
8711
9778
|
|
|
8712
9779
|
// src/PaperField.tsx
|
|
8713
|
-
import { jsx as jsx11, jsxs as
|
|
9780
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
8714
9781
|
var PaperFieldMesh = forwardRef2(
|
|
8715
9782
|
function PaperFieldMesh2(props, ref) {
|
|
8716
9783
|
const reduced = usePrefersReducedMotion(props.reducedMotion);
|
|
8717
|
-
const papers =
|
|
9784
|
+
const papers = useMemo10(
|
|
8718
9785
|
() => effectiveFieldPapers(props.papers, props.images),
|
|
8719
9786
|
[props.papers, props.images]
|
|
8720
9787
|
);
|
|
8721
9788
|
const stablePapers = useStable(papers);
|
|
8722
9789
|
const stablePreset = useStable(props.preset ?? null);
|
|
8723
|
-
const groups =
|
|
9790
|
+
const groups = useMemo10(
|
|
8724
9791
|
() => groupFieldPapers(stablePapers, stablePreset ?? void 0),
|
|
8725
9792
|
[stablePapers, stablePreset]
|
|
8726
9793
|
);
|
|
@@ -8729,7 +9796,7 @@ var PaperFieldMesh = forwardRef2(
|
|
|
8729
9796
|
const layout = getLayout(layoutId);
|
|
8730
9797
|
const firstSheet = groups[0]?.config.sheet;
|
|
8731
9798
|
const stableLayoutOptions = useStable(props.layoutOptions ?? {});
|
|
8732
|
-
const layoutOptions =
|
|
9799
|
+
const layoutOptions = useMemo10(
|
|
8733
9800
|
// Sheet grids size their cells from the papers themselves — gutter is
|
|
8734
9801
|
// then literally the spacing between stamps (explicit cell dims win).
|
|
8735
9802
|
() => resolveLayoutOptions(layoutId, layout, stableLayoutOptions, firstSheet),
|
|
@@ -8744,7 +9811,7 @@ var PaperFieldMesh = forwardRef2(
|
|
|
8744
9811
|
const driver = reduced ? "none" : props.motion?.driver ?? "autoplay";
|
|
8745
9812
|
const speed = props.motion?.speed ?? 0.5;
|
|
8746
9813
|
const entranceType = reduced ? "none" : props.entrance?.type ?? "rise";
|
|
8747
|
-
|
|
9814
|
+
useEffect15(() => {
|
|
8748
9815
|
const prev = prevLayout.current;
|
|
8749
9816
|
if (prev.id !== layoutId || JSON.stringify(prev.options) !== JSON.stringify(layoutOptions)) {
|
|
8750
9817
|
morphRef.current = { from: prev, t: 0 };
|
|
@@ -8752,7 +9819,7 @@ var PaperFieldMesh = forwardRef2(
|
|
|
8752
9819
|
prevLayout.current = { id: layoutId, options: layoutOptions };
|
|
8753
9820
|
}
|
|
8754
9821
|
}, [layoutId, layoutOptions]);
|
|
8755
|
-
|
|
9822
|
+
useEffect15(() => {
|
|
8756
9823
|
if (driver !== "drag") return;
|
|
8757
9824
|
const el = gl.domElement;
|
|
8758
9825
|
let lastX = null;
|
|
@@ -8778,7 +9845,7 @@ var PaperFieldMesh = forwardRef2(
|
|
|
8778
9845
|
window.removeEventListener("pointerup", up);
|
|
8779
9846
|
};
|
|
8780
9847
|
}, [driver, gl]);
|
|
8781
|
-
|
|
9848
|
+
useFrame7(({ clock }, delta) => {
|
|
8782
9849
|
if (mountTimeRef.current < 0) mountTimeRef.current = clock.elapsedTime;
|
|
8783
9850
|
if (driver === "autoplay") phaseRef.current += delta * speed * 0.02;
|
|
8784
9851
|
if (driver === "drag") {
|
|
@@ -8820,7 +9887,7 @@ var PaperFieldMesh = forwardRef2(
|
|
|
8820
9887
|
}
|
|
8821
9888
|
) });
|
|
8822
9889
|
}
|
|
8823
|
-
return /* @__PURE__ */
|
|
9890
|
+
return /* @__PURE__ */ jsxs8("group", { ref, children: [
|
|
8824
9891
|
sheetOptions?.backing && /* @__PURE__ */ jsx11(BackingSheet, { options: sheetOptions, count: total, removed: EMPTY_SET }),
|
|
8825
9892
|
groups.map((group, gi) => /* @__PURE__ */ jsx11(
|
|
8826
9893
|
FieldGroup,
|
|
@@ -8843,7 +9910,7 @@ function FitCamera(meshProps) {
|
|
|
8843
9910
|
const imagesProp = useStable(meshProps.images ?? null);
|
|
8844
9911
|
const presetProp = useStable(meshProps.preset ?? null);
|
|
8845
9912
|
const optionsProp = useStable(meshProps.layoutOptions ?? {});
|
|
8846
|
-
const field =
|
|
9913
|
+
const field = useMemo10(() => {
|
|
8847
9914
|
const papers = effectiveFieldPapers(papersProp ?? void 0, imagesProp ?? void 0);
|
|
8848
9915
|
const layoutId = meshProps.layout ?? "ring";
|
|
8849
9916
|
const layout = getLayout(layoutId);
|
|
@@ -8851,8 +9918,8 @@ function FitCamera(meshProps) {
|
|
|
8851
9918
|
const options = resolveLayoutOptions(layoutId, layout, optionsProp, sheet2);
|
|
8852
9919
|
return { layout, n: papers.length, options, sheet: sheet2 ?? DEFAULT_SHEET };
|
|
8853
9920
|
}, [papersProp, imagesProp, presetProp, meshProps.layout, optionsProp]);
|
|
8854
|
-
|
|
8855
|
-
if (!(camera instanceof
|
|
9921
|
+
useEffect15(() => {
|
|
9922
|
+
if (!(camera instanceof THREE17.PerspectiveCamera)) return;
|
|
8856
9923
|
const { position, target } = fitCamera(
|
|
8857
9924
|
field.layout,
|
|
8858
9925
|
field.n,
|
|
@@ -8869,20 +9936,20 @@ function FitCamera(meshProps) {
|
|
|
8869
9936
|
}
|
|
8870
9937
|
var PaperField = forwardRef2(function PaperField2({ children, className, style, scene, ...meshProps }, ref) {
|
|
8871
9938
|
const rig = sceneSchema.parse(scene ?? {});
|
|
8872
|
-
const registry4 =
|
|
9939
|
+
const registry4 = useMemo10(() => new DropZoneRegistry(), []);
|
|
8873
9940
|
const a11yRef = useRef8(null);
|
|
8874
|
-
const papers =
|
|
9941
|
+
const papers = useMemo10(
|
|
8875
9942
|
() => effectiveFieldPapers(meshProps.papers, meshProps.images),
|
|
8876
9943
|
[meshProps.papers, meshProps.images]
|
|
8877
9944
|
);
|
|
8878
9945
|
const stablePapers = useStable(papers);
|
|
8879
9946
|
const stablePreset = useStable(meshProps.preset ?? null);
|
|
8880
|
-
const interactive =
|
|
9947
|
+
const interactive = useMemo10(
|
|
8881
9948
|
() => fieldIsInteractive(stablePapers, stablePreset ?? void 0, meshProps.interactive),
|
|
8882
9949
|
[stablePapers, stablePreset, meshProps.interactive]
|
|
8883
9950
|
);
|
|
8884
|
-
return /* @__PURE__ */ jsx11("div", { className, style: { width: "100%", height: "100%", ...style }, children: /* @__PURE__ */
|
|
8885
|
-
/* @__PURE__ */
|
|
9951
|
+
return /* @__PURE__ */ jsx11("div", { className, style: { width: "100%", height: "100%", ...style }, children: /* @__PURE__ */ jsxs8(DropZoneContext.Provider, { value: registry4, children: [
|
|
9952
|
+
/* @__PURE__ */ jsxs8(Canvas, { shadows: true, camera: { position: [0, 0.6, 5.2], fov: 45 }, dpr: [1, 2], children: [
|
|
8886
9953
|
/* @__PURE__ */ jsx11(FitCamera, { ...meshProps }),
|
|
8887
9954
|
/* @__PURE__ */ jsx11(PaperBackdrop, { backdrop: rig.backdrop }),
|
|
8888
9955
|
/* @__PURE__ */ jsx11(PaperLighting, { preset: rig.lighting, light: rig.light, floor: -2.4, scale: 14 }),
|
|
@@ -9153,4 +10220,4 @@ export {
|
|
|
9153
10220
|
describeConfig,
|
|
9154
10221
|
buildAgentPayload
|
|
9155
10222
|
};
|
|
9156
|
-
//# sourceMappingURL=chunk-
|
|
10223
|
+
//# sourceMappingURL=chunk-MZCAN3AR.js.map
|