flag-cloth 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +19 -0
- package/README.md +417 -0
- package/dist/FlagCloth-BDCYpA5k.d.ts +451 -0
- package/dist/chunk-E5IYGF33.js +84 -0
- package/dist/chunk-E5IYGF33.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/react.d.ts +20 -0
- package/dist/react.js +2 -0
- package/dist/react.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
type Vector3Tuple = readonly [number, number, number];
|
|
2
|
+
type EdgeAttachment = "left" | "right" | "top" | "bottom";
|
|
3
|
+
type CornerAttachment = "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
|
4
|
+
/** Integer coordinates in the simulation grid, where `[0, 0]` is the top-left particle. */
|
|
5
|
+
type GridPoint = readonly [x: number, y: number];
|
|
6
|
+
/** Pins every Nth particle along one edge, starting at `offset`. */
|
|
7
|
+
interface PartialEdgeAttachment {
|
|
8
|
+
edge: EdgeAttachment;
|
|
9
|
+
/** Integer spacing between pins. Values below 1 are treated as 1. @default 1 */
|
|
10
|
+
every?: number;
|
|
11
|
+
/** Zero-based starting particle along the edge. Negative values are treated as 0. @default 0 */
|
|
12
|
+
offset?: number;
|
|
13
|
+
}
|
|
14
|
+
/** Pins only the listed simulation-grid coordinates. Out-of-range points are ignored. */
|
|
15
|
+
interface PointAttachment {
|
|
16
|
+
points: ReadonlyArray<GridPoint>;
|
|
17
|
+
}
|
|
18
|
+
type Attachment = EdgeAttachment | CornerAttachment | "none" | PartialEdgeAttachment | PointAttachment;
|
|
19
|
+
interface FlagSegments {
|
|
20
|
+
/** Horizontal cell count. The grid has `x + 1` particle columns. @default 32 */
|
|
21
|
+
x: number;
|
|
22
|
+
/** Vertical cell count. The grid has `y + 1` particle rows. @default 20 */
|
|
23
|
+
y: number;
|
|
24
|
+
}
|
|
25
|
+
interface WindOptions {
|
|
26
|
+
/** World-space wind direction. It is normalized internally. @default [1, 0.08, 0.3] */
|
|
27
|
+
direction: Vector3Tuple;
|
|
28
|
+
/** Base wind speed. @default 7 */
|
|
29
|
+
strength: number;
|
|
30
|
+
/** Amount of noisy gust and lateral variation. @default 0.35 */
|
|
31
|
+
turbulence: number;
|
|
32
|
+
/** Rate of time variation in the noise field. @default 0.5 */
|
|
33
|
+
gustFrequency: number;
|
|
34
|
+
/** Frequency of spatial wind variation. @default 0.8 */
|
|
35
|
+
spatialScale: number;
|
|
36
|
+
/** Multiplier that converts triangle pressure into particle force. @default 0.35 */
|
|
37
|
+
aerodynamicCoefficient: number;
|
|
38
|
+
}
|
|
39
|
+
interface SimulationOptions {
|
|
40
|
+
/** Velocity retained over a 1/60-second interval. @default 0.985 */
|
|
41
|
+
damping: number;
|
|
42
|
+
/** World-space acceleration applied to every unpinned particle. @default [0, 0, 0] */
|
|
43
|
+
gravity: Vector3Tuple;
|
|
44
|
+
/** Fixed accumulator step in seconds. @default 1 / 60 */
|
|
45
|
+
fixedTimeStep: number;
|
|
46
|
+
/** Maximum display-frame delta accepted by the accumulator, in seconds. @default 0.1 */
|
|
47
|
+
maxFrameDelta: number;
|
|
48
|
+
/** Integration subdivisions per fixed step. @default 2 */
|
|
49
|
+
substeps: number;
|
|
50
|
+
/** Distance-constraint passes per substep. @default 6 */
|
|
51
|
+
constraintIterations: number;
|
|
52
|
+
/** Horizontal and vertical constraint stiffness. @default 1 */
|
|
53
|
+
structuralStiffness: number;
|
|
54
|
+
/** Diagonal constraint stiffness. @default 0.9 */
|
|
55
|
+
shearStiffness: number;
|
|
56
|
+
/** Two-cell constraint stiffness. @default 0.4 */
|
|
57
|
+
bendStiffness: number;
|
|
58
|
+
/** Maximum correction per constraint; 0 disables the cap. @default 0.25 */
|
|
59
|
+
maxCorrection: number;
|
|
60
|
+
/** Total cloth mass, independent of grid density. @default 1 */
|
|
61
|
+
mass: number;
|
|
62
|
+
}
|
|
63
|
+
interface InteractionOptions {
|
|
64
|
+
/** Enables Pointer Event dragging. @default true */
|
|
65
|
+
enabled: boolean;
|
|
66
|
+
/** Grid-space radius around the selected particle. @default 1 */
|
|
67
|
+
dragRadius: number;
|
|
68
|
+
/** Strength of the temporary drag constraint. @default 0.9 */
|
|
69
|
+
dragStiffness: number;
|
|
70
|
+
/** Maximum movement from the initial grab point; 0 is unlimited. @default 4 */
|
|
71
|
+
maxDragDistance: number;
|
|
72
|
+
/** Allows attached particles to be selected temporarily. @default false */
|
|
73
|
+
allowPinned: boolean;
|
|
74
|
+
/** World-space velocity impulse applied when the pointer releases. @default [0, 0, 0] */
|
|
75
|
+
releaseImpulse: Vector3Tuple;
|
|
76
|
+
}
|
|
77
|
+
type RendererPreset = "quality" | "performance";
|
|
78
|
+
interface FlagRendererOptions {
|
|
79
|
+
/** Requests an alpha channel when creating the WebGL2 context. Construction-time only. @default true */
|
|
80
|
+
alpha: boolean;
|
|
81
|
+
/** Requests browser multisample antialiasing. Construction-time only. @default true */
|
|
82
|
+
antialias: boolean;
|
|
83
|
+
/** Requests a desynchronized context when supported. Construction-time only. @default false */
|
|
84
|
+
desynchronized: boolean;
|
|
85
|
+
/** GPU preference supplied during context creation. Construction-time only. @default "high-performance" */
|
|
86
|
+
powerPreference: WebGLPowerPreference;
|
|
87
|
+
/** Maximum backing-store device-pixel ratio. @default 1.5 */
|
|
88
|
+
maxPixelRatio: number;
|
|
89
|
+
/** Renderer profile. @default "quality" */
|
|
90
|
+
preset: RendererPreset;
|
|
91
|
+
/** Texture sampling mode. @default "linear" */
|
|
92
|
+
textureFiltering: "linear" | "nearest";
|
|
93
|
+
/** Clears with alpha instead of `backgroundColor`. @default true */
|
|
94
|
+
transparent: boolean;
|
|
95
|
+
/** CSS color used when `transparent` is false. @default "transparent" */
|
|
96
|
+
backgroundColor: string;
|
|
97
|
+
/** Enables the soft silhouette shadow pass. @default true */
|
|
98
|
+
shadows: boolean;
|
|
99
|
+
/** Recalculates normals every N rendered frames. @default 1 */
|
|
100
|
+
shadingUpdateInterval: number;
|
|
101
|
+
}
|
|
102
|
+
/** Renderer values that can change without recreating the WebGL2 context. */
|
|
103
|
+
type MutableFlagRendererOptions = Omit<FlagRendererOptions, "alpha" | "antialias" | "desynchronized" | "powerPreference">;
|
|
104
|
+
interface ClothMaterialOptions {
|
|
105
|
+
/** Cloth color used when `texture` is null. @default "#f2f4f3" */
|
|
106
|
+
baseColor: string;
|
|
107
|
+
/** Minimum material brightness. @default 0.58 */
|
|
108
|
+
ambient: number;
|
|
109
|
+
/** Directional-light strength. @default 0.55 */
|
|
110
|
+
diffuse: number;
|
|
111
|
+
/** Highlight strength in the quality preset. @default 0.16 */
|
|
112
|
+
specular: number;
|
|
113
|
+
/** Highlight concentration. @default 18 */
|
|
114
|
+
shininess: number;
|
|
115
|
+
/** View-facing fold darkening. @default 0.42 */
|
|
116
|
+
foldContrast: number;
|
|
117
|
+
/** CSS color for the silhouette shadow. @default "rgba(0, 0, 0, 0.3)" */
|
|
118
|
+
shadowColor: string;
|
|
119
|
+
/** Shadow sampling radius in CSS pixels. @default 18 */
|
|
120
|
+
shadowBlur: number;
|
|
121
|
+
/** Horizontal shadow offset in CSS pixels. @default 10 */
|
|
122
|
+
shadowOffsetX: number;
|
|
123
|
+
/** Vertical shadow offset in CSS pixels. @default 12 */
|
|
124
|
+
shadowOffsetY: number;
|
|
125
|
+
}
|
|
126
|
+
interface LightingOptions {
|
|
127
|
+
/** Enables fold lighting. @default true */
|
|
128
|
+
enabled: boolean;
|
|
129
|
+
/** Overall directional-light multiplier. @default 1 */
|
|
130
|
+
intensity: number;
|
|
131
|
+
/** World-space light direction. @default [-0.4, 0.7, 1] */
|
|
132
|
+
direction: Vector3Tuple;
|
|
133
|
+
}
|
|
134
|
+
interface CameraOptions {
|
|
135
|
+
/** Vertical perspective field of view in degrees. @default 40 */
|
|
136
|
+
fov: number;
|
|
137
|
+
/** Near clipping distance. @default 0.01 */
|
|
138
|
+
near: number;
|
|
139
|
+
/** Far clipping distance. @default 100 */
|
|
140
|
+
far: number;
|
|
141
|
+
/** World-space camera position; null automatically fits the cloth. @default null */
|
|
142
|
+
position: Vector3Tuple | null;
|
|
143
|
+
/** World-space camera target. @default [0, 0, 0] */
|
|
144
|
+
lookAt: Vector3Tuple;
|
|
145
|
+
}
|
|
146
|
+
interface VisibilityOptions {
|
|
147
|
+
/** Pauses through IntersectionObserver while the container is offscreen. @default true */
|
|
148
|
+
pauseWhenOffscreen: boolean;
|
|
149
|
+
/** Pauses while `document.visibilityState` is hidden. @default true */
|
|
150
|
+
pauseWhenDocumentHidden: boolean;
|
|
151
|
+
}
|
|
152
|
+
interface DebugOptions {
|
|
153
|
+
/** Creates the optional debug overlay. @default false */
|
|
154
|
+
enabled: boolean;
|
|
155
|
+
/** Overlay refresh interval in milliseconds, clamped to at least 100. @default 500 */
|
|
156
|
+
updateInterval: number;
|
|
157
|
+
}
|
|
158
|
+
interface AdvancedOptions {
|
|
159
|
+
/** Caller-owned canvas. It is not appended or removed by the library. */
|
|
160
|
+
canvas?: HTMLCanvasElement;
|
|
161
|
+
/** Caller-owned WebGL2 context. Its canvas is used when `canvas` is omitted. */
|
|
162
|
+
context?: WebGL2RenderingContext;
|
|
163
|
+
/** Disables the internal requestAnimationFrame scheduling. @default false */
|
|
164
|
+
externalAnimationLoop?: boolean;
|
|
165
|
+
}
|
|
166
|
+
/** A URL or a browser-native image source accepted by WebGL texture upload. */
|
|
167
|
+
type TextureSource = string | TexImageSource | null;
|
|
168
|
+
interface FlagClothOptions {
|
|
169
|
+
/** Element measured by ResizeObserver and used to host an owned canvas. */
|
|
170
|
+
container: HTMLElement;
|
|
171
|
+
/** URL, browser image source, or null for an untextured material. */
|
|
172
|
+
texture: TextureSource;
|
|
173
|
+
/** Cloth width in simulation units, not CSS pixels. @default 3 */
|
|
174
|
+
width?: number;
|
|
175
|
+
/** Cloth height in simulation units, not CSS pixels. @default 2 */
|
|
176
|
+
height?: number;
|
|
177
|
+
/** Physical grid cell counts. @default { x: 32, y: 20 } */
|
|
178
|
+
segments?: Partial<FlagSegments>;
|
|
179
|
+
/** Particle pinning configuration. @default "left" */
|
|
180
|
+
attachment?: Attachment;
|
|
181
|
+
wind?: Partial<WindOptions>;
|
|
182
|
+
simulation?: Partial<SimulationOptions>;
|
|
183
|
+
interaction?: Partial<InteractionOptions>;
|
|
184
|
+
renderer?: Partial<FlagRendererOptions>;
|
|
185
|
+
material?: Partial<ClothMaterialOptions>;
|
|
186
|
+
lighting?: Partial<LightingOptions>;
|
|
187
|
+
camera?: Partial<CameraOptions>;
|
|
188
|
+
visibility?: Partial<VisibilityOptions>;
|
|
189
|
+
debug?: boolean | Partial<DebugOptions>;
|
|
190
|
+
advanced?: AdvancedOptions;
|
|
191
|
+
/** Starts the instance immediately; external loops still require a started instance. @default true */
|
|
192
|
+
autoStart?: boolean;
|
|
193
|
+
}
|
|
194
|
+
/** Options that can be applied to an existing instance with `setOptions()`. */
|
|
195
|
+
interface FlagClothUpdateOptions {
|
|
196
|
+
texture?: TextureSource;
|
|
197
|
+
width?: number;
|
|
198
|
+
height?: number;
|
|
199
|
+
segments?: Partial<FlagSegments>;
|
|
200
|
+
attachment?: Attachment;
|
|
201
|
+
wind?: Partial<WindOptions>;
|
|
202
|
+
simulation?: Partial<SimulationOptions>;
|
|
203
|
+
interaction?: Partial<InteractionOptions>;
|
|
204
|
+
renderer?: Partial<MutableFlagRendererOptions>;
|
|
205
|
+
material?: Partial<ClothMaterialOptions>;
|
|
206
|
+
lighting?: Partial<LightingOptions>;
|
|
207
|
+
camera?: Partial<CameraOptions>;
|
|
208
|
+
visibility?: Partial<VisibilityOptions>;
|
|
209
|
+
debug?: boolean | Partial<DebugOptions>;
|
|
210
|
+
}
|
|
211
|
+
interface FlagClothStats {
|
|
212
|
+
/** Smoothed animation-loop frames per second over the latest sampling window. */
|
|
213
|
+
fps: number;
|
|
214
|
+
/** JavaScript time spent advancing the fixed-step simulation on the latest frame. */
|
|
215
|
+
simulationMs: number;
|
|
216
|
+
/** JavaScript time spent submitting the latest render. */
|
|
217
|
+
renderMs: number;
|
|
218
|
+
/** Current simulated particle count. */
|
|
219
|
+
particleCount: number;
|
|
220
|
+
/** Current structural, shear, and bend constraint count. */
|
|
221
|
+
constraintCount: number;
|
|
222
|
+
/** WebGL draw calls issued by the latest render. */
|
|
223
|
+
drawCalls: number;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
interface ConstraintSet {
|
|
227
|
+
readonly first: Uint32Array;
|
|
228
|
+
readonly second: Uint32Array;
|
|
229
|
+
readonly restLength: Float32Array;
|
|
230
|
+
readonly kind: Uint8Array;
|
|
231
|
+
readonly count: number;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
interface ClothSimulationConfig {
|
|
235
|
+
width: number;
|
|
236
|
+
height: number;
|
|
237
|
+
segmentsX: number;
|
|
238
|
+
segmentsY: number;
|
|
239
|
+
attachment: Attachment;
|
|
240
|
+
simulation: SimulationOptions;
|
|
241
|
+
wind: WindOptions;
|
|
242
|
+
}
|
|
243
|
+
declare class ClothSimulation {
|
|
244
|
+
readonly width: number;
|
|
245
|
+
readonly height: number;
|
|
246
|
+
readonly segmentsX: number;
|
|
247
|
+
readonly segmentsY: number;
|
|
248
|
+
readonly columns: number;
|
|
249
|
+
readonly rows: number;
|
|
250
|
+
readonly particleCount: number;
|
|
251
|
+
readonly positions: Float32Array;
|
|
252
|
+
readonly previousPositions: Float32Array;
|
|
253
|
+
readonly initialPositions: Float32Array;
|
|
254
|
+
readonly forces: Float32Array;
|
|
255
|
+
readonly inverseMass: Float32Array;
|
|
256
|
+
readonly pinned: Uint8Array;
|
|
257
|
+
readonly constraints: ConstraintSet;
|
|
258
|
+
private simulation;
|
|
259
|
+
private wind;
|
|
260
|
+
private windDirectionX;
|
|
261
|
+
private windDirectionY;
|
|
262
|
+
private windDirectionZ;
|
|
263
|
+
private elapsedTime;
|
|
264
|
+
private readonly dragWeights;
|
|
265
|
+
private dragParticles;
|
|
266
|
+
private dragOffsets;
|
|
267
|
+
private dragParticleCount;
|
|
268
|
+
private dragTargetX;
|
|
269
|
+
private dragTargetY;
|
|
270
|
+
private dragTargetZ;
|
|
271
|
+
private dragStiffness;
|
|
272
|
+
private dragAllowsPinned;
|
|
273
|
+
constructor(config: ClothSimulationConfig);
|
|
274
|
+
get constraintCount(): number;
|
|
275
|
+
get time(): number;
|
|
276
|
+
particleIndex(x: number, y: number): number;
|
|
277
|
+
isPinned(particleIndex: number): boolean;
|
|
278
|
+
addForce(particleIndex: number, force: Vector3Tuple): void;
|
|
279
|
+
setWind(options: Partial<WindOptions>): void;
|
|
280
|
+
setSimulationOptions(options: Partial<SimulationOptions>): void;
|
|
281
|
+
setAttachment(attachment: Attachment): void;
|
|
282
|
+
beginDrag(particleIndex: number, radius: number, stiffness: number, allowPinned: boolean): boolean;
|
|
283
|
+
setDragTarget(x: number, y: number, z: number): void;
|
|
284
|
+
endDrag(releaseImpulse?: Vector3Tuple, timeStep?: number): void;
|
|
285
|
+
step(timeStep: number): void;
|
|
286
|
+
reset(): void;
|
|
287
|
+
private initializeParticles;
|
|
288
|
+
private updateInverseMass;
|
|
289
|
+
private pinEdge;
|
|
290
|
+
private integrate;
|
|
291
|
+
private solveConstraints;
|
|
292
|
+
private solveDrag;
|
|
293
|
+
private enforcePins;
|
|
294
|
+
private applyWind;
|
|
295
|
+
private applyTriangleWind;
|
|
296
|
+
private updateWindDirection;
|
|
297
|
+
private iterationStiffness;
|
|
298
|
+
private clearDragWeights;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* A compact GPU renderer for the cloth solver. JavaScript updates two dynamic
|
|
303
|
+
* typed-array buffers; one indexed draw maps and lights the complete texture.
|
|
304
|
+
*/
|
|
305
|
+
declare class WebGLClothRenderer {
|
|
306
|
+
readonly canvas: HTMLCanvasElement;
|
|
307
|
+
readonly context: WebGL2RenderingContext;
|
|
308
|
+
projectedPositions: Float32Array;
|
|
309
|
+
projectedDepths: Float32Array;
|
|
310
|
+
private simulation;
|
|
311
|
+
private rendererOptions;
|
|
312
|
+
private material;
|
|
313
|
+
private lighting;
|
|
314
|
+
private camera;
|
|
315
|
+
private textureSource;
|
|
316
|
+
private vertexNormals;
|
|
317
|
+
private textureCoordinates;
|
|
318
|
+
private indices;
|
|
319
|
+
private cssWidth;
|
|
320
|
+
private cssHeight;
|
|
321
|
+
private pixelRatio;
|
|
322
|
+
private focalLength;
|
|
323
|
+
private cameraX;
|
|
324
|
+
private cameraY;
|
|
325
|
+
private cameraZ;
|
|
326
|
+
private forwardX;
|
|
327
|
+
private forwardY;
|
|
328
|
+
private forwardZ;
|
|
329
|
+
private rightX;
|
|
330
|
+
private rightY;
|
|
331
|
+
private rightZ;
|
|
332
|
+
private upX;
|
|
333
|
+
private upY;
|
|
334
|
+
private upZ;
|
|
335
|
+
private lightX;
|
|
336
|
+
private lightY;
|
|
337
|
+
private lightZ;
|
|
338
|
+
private frameNumber;
|
|
339
|
+
private drawCallsValue;
|
|
340
|
+
private contextLost;
|
|
341
|
+
private destroyed;
|
|
342
|
+
private readonly viewMatrix;
|
|
343
|
+
private readonly projectionMatrix;
|
|
344
|
+
private readonly viewProjectionMatrix;
|
|
345
|
+
private baseColor;
|
|
346
|
+
private shadowColor;
|
|
347
|
+
private backgroundColor;
|
|
348
|
+
private program;
|
|
349
|
+
private uniforms;
|
|
350
|
+
private vertexArray;
|
|
351
|
+
private positionBuffer;
|
|
352
|
+
private normalBuffer;
|
|
353
|
+
private textureCoordinateBuffer;
|
|
354
|
+
private indexBuffer;
|
|
355
|
+
private texture;
|
|
356
|
+
constructor(canvas: HTMLCanvasElement, context: WebGL2RenderingContext, simulation: ClothSimulation, rendererOptions: FlagRendererOptions, material: ClothMaterialOptions, lighting: LightingOptions, camera: CameraOptions);
|
|
357
|
+
get width(): number;
|
|
358
|
+
get height(): number;
|
|
359
|
+
get drawCalls(): number;
|
|
360
|
+
setSimulation(simulation: ClothSimulation): void;
|
|
361
|
+
setTexture(textureSource: TexImageSource | null): void;
|
|
362
|
+
setOptions(rendererOptions: FlagRendererOptions, material: ClothMaterialOptions, lighting: LightingOptions, camera: CameraOptions): void;
|
|
363
|
+
resize(width: number, height: number, pixelRatio: number): void;
|
|
364
|
+
render(): void;
|
|
365
|
+
findParticleAt(screenX: number, screenY: number): number;
|
|
366
|
+
unprojectToPlane(screenX: number, screenY: number, planeX: number, planeY: number, planeZ: number, output: Float32Array): boolean;
|
|
367
|
+
destroy(): void;
|
|
368
|
+
private initializeResources;
|
|
369
|
+
private uploadTopology;
|
|
370
|
+
private uploadTexture;
|
|
371
|
+
private applyTextureFiltering;
|
|
372
|
+
private rebuildTopology;
|
|
373
|
+
private updateVertexNormals;
|
|
374
|
+
private projectParticles;
|
|
375
|
+
private drawShadow;
|
|
376
|
+
private syncShaderUniforms;
|
|
377
|
+
private updateCameraBasis;
|
|
378
|
+
private updateViewProjection;
|
|
379
|
+
private updateLightDirection;
|
|
380
|
+
private updateColors;
|
|
381
|
+
private deleteResources;
|
|
382
|
+
private readonly onContextLost;
|
|
383
|
+
private readonly onContextRestored;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
declare class FlagClothInstance {
|
|
387
|
+
readonly container: HTMLElement;
|
|
388
|
+
readonly canvas: HTMLCanvasElement;
|
|
389
|
+
readonly context: WebGL2RenderingContext;
|
|
390
|
+
readonly renderer: WebGLClothRenderer;
|
|
391
|
+
ready: Promise<void>;
|
|
392
|
+
private options;
|
|
393
|
+
private simulation;
|
|
394
|
+
private fixedTimeStep;
|
|
395
|
+
private pointerController;
|
|
396
|
+
private currentTexture;
|
|
397
|
+
private readonly ownsCanvas;
|
|
398
|
+
private readonly externalAnimationLoop;
|
|
399
|
+
private canvasAppended;
|
|
400
|
+
private resizeObserver;
|
|
401
|
+
private intersectionObserver;
|
|
402
|
+
private debugPanel;
|
|
403
|
+
private textureRequest;
|
|
404
|
+
private animationFrame;
|
|
405
|
+
private lastFrameTime;
|
|
406
|
+
private started;
|
|
407
|
+
private userPaused;
|
|
408
|
+
private documentHidden;
|
|
409
|
+
private offscreen;
|
|
410
|
+
private destroyed;
|
|
411
|
+
private lastWidth;
|
|
412
|
+
private lastHeight;
|
|
413
|
+
private statsFrameCount;
|
|
414
|
+
private statsWindowStarted;
|
|
415
|
+
private lastDebugUpdate;
|
|
416
|
+
private readonly statsValue;
|
|
417
|
+
constructor(initialOptions: FlagClothOptions);
|
|
418
|
+
get stats(): Readonly<FlagClothStats>;
|
|
419
|
+
get particleCount(): number;
|
|
420
|
+
get constraintCount(): number;
|
|
421
|
+
start(): void;
|
|
422
|
+
pause(): void;
|
|
423
|
+
resume(): void;
|
|
424
|
+
update(frameDelta: number, renderFrame?: boolean): void;
|
|
425
|
+
render(): void;
|
|
426
|
+
resize(): void;
|
|
427
|
+
setTexture(source: TextureSource): Promise<void>;
|
|
428
|
+
setWind(options: Partial<WindOptions>): void;
|
|
429
|
+
setOptions(update: FlagClothUpdateOptions): Promise<void>;
|
|
430
|
+
reset(): void;
|
|
431
|
+
destroy(): void;
|
|
432
|
+
private createSimulation;
|
|
433
|
+
private rebuildCloth;
|
|
434
|
+
private setupResizeObserver;
|
|
435
|
+
private setupVisibilityObservers;
|
|
436
|
+
private syncDebugPanel;
|
|
437
|
+
private updateStats;
|
|
438
|
+
private readonly simulateStep;
|
|
439
|
+
private readonly onAnimationFrame;
|
|
440
|
+
private readonly onResizeObserved;
|
|
441
|
+
private readonly onIntersection;
|
|
442
|
+
private readonly onVisibilityChange;
|
|
443
|
+
private syncAnimationState;
|
|
444
|
+
private canAnimate;
|
|
445
|
+
private requestAnimationFrameIfNeeded;
|
|
446
|
+
private cancelAnimationFrame;
|
|
447
|
+
private assertAlive;
|
|
448
|
+
}
|
|
449
|
+
declare function createFlagCloth(options: FlagClothOptions): FlagClothInstance;
|
|
450
|
+
|
|
451
|
+
export { type AdvancedOptions as A, type CameraOptions as C, type DebugOptions as D, type EdgeAttachment as E, FlagClothInstance as F, type GridPoint as G, type InteractionOptions as I, type LightingOptions as L, type MutableFlagRendererOptions as M, type PartialEdgeAttachment as P, type RendererPreset as R, type SimulationOptions as S, type TextureSource as T, type Vector3Tuple as V, WebGLClothRenderer as W, type Attachment as a, type ClothMaterialOptions as b, ClothSimulation as c, type ClothSimulationConfig as d, type CornerAttachment as e, type FlagClothOptions as f, type FlagClothStats as g, type FlagClothUpdateOptions as h, type FlagRendererOptions as i, type FlagSegments as j, type PointAttachment as k, type VisibilityOptions as l, type WindOptions as m, createFlagCloth as n };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
var Ft=Object.defineProperty;var Rt=(a,t,e)=>t in a?Ft(a,t,{enumerable:true,configurable:true,writable:true,value:e}):a[t]=e;var r=(a,t,e)=>Rt(a,typeof t!="symbol"?t+"":t,e);var I=new Int8Array([0,0,-1,-1,0,-1,1,-1,-1,0,1,0,-1,1,0,1,1,1]),Ot=new Uint8Array([255,255,255,255]),Dt=`#version 300 es
|
|
2
|
+
precision highp float;
|
|
3
|
+
|
|
4
|
+
layout(location = 0) in vec3 aPosition;
|
|
5
|
+
layout(location = 1) in vec3 aNormal;
|
|
6
|
+
layout(location = 2) in vec2 aUv;
|
|
7
|
+
|
|
8
|
+
uniform mat4 uViewProjection;
|
|
9
|
+
uniform vec2 uScreenOffset;
|
|
10
|
+
|
|
11
|
+
out vec2 vUv;
|
|
12
|
+
out vec3 vNormal;
|
|
13
|
+
out vec3 vWorldPosition;
|
|
14
|
+
|
|
15
|
+
void main() {
|
|
16
|
+
vec4 clipPosition = uViewProjection * vec4(aPosition, 1.0);
|
|
17
|
+
clipPosition.xy += uScreenOffset * clipPosition.w;
|
|
18
|
+
gl_Position = clipPosition;
|
|
19
|
+
vUv = aUv;
|
|
20
|
+
vNormal = aNormal;
|
|
21
|
+
vWorldPosition = aPosition;
|
|
22
|
+
}
|
|
23
|
+
`,Mt=`#version 300 es
|
|
24
|
+
precision highp float;
|
|
25
|
+
|
|
26
|
+
uniform sampler2D uTexture;
|
|
27
|
+
uniform bool uHasTexture;
|
|
28
|
+
uniform bool uShadowPass;
|
|
29
|
+
uniform vec4 uBaseColor;
|
|
30
|
+
uniform vec4 uShadowColor;
|
|
31
|
+
uniform vec3 uLightDirection;
|
|
32
|
+
uniform vec3 uCameraPosition;
|
|
33
|
+
uniform bool uLightingEnabled;
|
|
34
|
+
uniform float uLightIntensity;
|
|
35
|
+
uniform float uAmbient;
|
|
36
|
+
uniform float uDiffuse;
|
|
37
|
+
uniform float uSpecular;
|
|
38
|
+
uniform float uShininess;
|
|
39
|
+
uniform float uFoldContrast;
|
|
40
|
+
|
|
41
|
+
in vec2 vUv;
|
|
42
|
+
in vec3 vNormal;
|
|
43
|
+
in vec3 vWorldPosition;
|
|
44
|
+
|
|
45
|
+
out vec4 outputColor;
|
|
46
|
+
|
|
47
|
+
void main() {
|
|
48
|
+
vec4 albedo = uHasTexture ? texture(uTexture, vUv) : uBaseColor;
|
|
49
|
+
if (albedo.a <= 0.001) discard;
|
|
50
|
+
|
|
51
|
+
if (uShadowPass) {
|
|
52
|
+
outputColor = vec4(uShadowColor.rgb, uShadowColor.a * albedo.a);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!uLightingEnabled) {
|
|
57
|
+
outputColor = albedo;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
vec3 normal = normalize(vNormal);
|
|
62
|
+
vec3 viewDirection = normalize(uCameraPosition - vWorldPosition);
|
|
63
|
+
if (dot(normal, viewDirection) < 0.0) normal = -normal;
|
|
64
|
+
|
|
65
|
+
float diffuseLight = max(dot(normal, uLightDirection), 0.0);
|
|
66
|
+
float facing = max(dot(normal, viewDirection), 0.0);
|
|
67
|
+
float brightness = uAmbient + diffuseLight * uDiffuse * uLightIntensity;
|
|
68
|
+
float foldDarkening = (1.0 - facing) * uFoldContrast * 0.28;
|
|
69
|
+
|
|
70
|
+
vec3 halfDirection = normalize(uLightDirection + viewDirection);
|
|
71
|
+
float highlight = pow(max(dot(normal, halfDirection), 0.0), max(1.0, uShininess));
|
|
72
|
+
highlight *= uSpecular * uLightIntensity;
|
|
73
|
+
|
|
74
|
+
vec3 shaded = albedo.rgb * max(0.08, brightness - foldDarkening);
|
|
75
|
+
shaded += vec3(highlight);
|
|
76
|
+
outputColor = vec4(clamp(shaded, 0.0, 1.0), albedo.a);
|
|
77
|
+
}
|
|
78
|
+
`,N=class{constructor(t,e,s,i,n,o,l){r(this,"canvas",t);r(this,"context",e);r(this,"projectedPositions");r(this,"projectedDepths");r(this,"simulation");r(this,"rendererOptions");r(this,"material");r(this,"lighting");r(this,"camera");r(this,"textureSource",null);r(this,"vertexNormals");r(this,"textureCoordinates");r(this,"indices");r(this,"cssWidth",1);r(this,"cssHeight",1);r(this,"pixelRatio",1);r(this,"focalLength",1);r(this,"cameraX",0);r(this,"cameraY",0);r(this,"cameraZ",5);r(this,"forwardX",0);r(this,"forwardY",0);r(this,"forwardZ",-1);r(this,"rightX",1);r(this,"rightY",0);r(this,"rightZ",0);r(this,"upX",0);r(this,"upY",1);r(this,"upZ",0);r(this,"lightX",0);r(this,"lightY",0);r(this,"lightZ",1);r(this,"frameNumber",0);r(this,"drawCallsValue",0);r(this,"contextLost",false);r(this,"destroyed",false);r(this,"viewMatrix",new Float32Array(16));r(this,"projectionMatrix",new Float32Array(16));r(this,"viewProjectionMatrix",new Float32Array(16));r(this,"baseColor",new Float32Array([1,1,1,1]));r(this,"shadowColor",new Float32Array([0,0,0,.3]));r(this,"backgroundColor",new Float32Array([0,0,0,0]));r(this,"program",null);r(this,"uniforms",null);r(this,"vertexArray",null);r(this,"positionBuffer",null);r(this,"normalBuffer",null);r(this,"textureCoordinateBuffer",null);r(this,"indexBuffer",null);r(this,"texture",null);r(this,"onContextLost",t=>{t.preventDefault(),this.contextLost=true;});r(this,"onContextRestored",()=>{this.destroyed||(this.contextLost=false,this.initializeResources(),this.resize(this.cssWidth,this.cssHeight,this.pixelRatio));});this.simulation=s,this.rendererOptions=i,this.material=n,this.lighting=o,this.camera=l,this.projectedPositions=new Float32Array(s.particleCount*2),this.projectedDepths=new Float32Array(s.particleCount),this.vertexNormals=new Float32Array(s.particleCount*3),this.textureCoordinates=new Float32Array(s.particleCount*2),this.indices=new Uint32Array(s.segmentsX*s.segmentsY*6),this.rebuildTopology(),this.updateCameraBasis(),this.updateLightDirection(),this.updateColors(),this.canvas.addEventListener("webglcontextlost",this.onContextLost),this.canvas.addEventListener("webglcontextrestored",this.onContextRestored),this.initializeResources();}get width(){return this.cssWidth}get height(){return this.cssHeight}get drawCalls(){return this.drawCallsValue}setSimulation(t){this.simulation=t,this.projectedPositions=new Float32Array(t.particleCount*2),this.projectedDepths=new Float32Array(t.particleCount),this.vertexNormals=new Float32Array(t.particleCount*3),this.textureCoordinates=new Float32Array(t.particleCount*2),this.indices=new Uint32Array(t.segmentsX*t.segmentsY*6),this.rebuildTopology(),this.updateCameraBasis(),this.contextLost||this.uploadTopology();}setTexture(t){this.textureSource=t,this.contextLost||this.uploadTexture();}setOptions(t,e,s,i){let n=t.textureFiltering!==this.rendererOptions.textureFiltering;this.rendererOptions=t,this.material=e,this.lighting=s,this.camera=i,this.updateCameraBasis(),this.updateLightDirection(),this.updateColors(),n&&!this.contextLost&&this.applyTextureFiltering();}resize(t,e,s){this.cssWidth=Math.max(1,t),this.cssHeight=Math.max(1,e),this.pixelRatio=Math.max(.5,s);let i=Math.max(1,Math.round(this.cssWidth*this.pixelRatio)),n=Math.max(1,Math.round(this.cssHeight*this.pixelRatio));this.canvas.width!==i&&(this.canvas.width=i),this.canvas.height!==n&&(this.canvas.height=n),this.context.viewport(0,0,i,n),this.updateCameraBasis();}render(){let t=this.context,e=this.uniforms;if(this.destroyed||this.contextLost||!this.program||!e||!this.vertexArray||!this.positionBuffer||!this.normalBuffer||!this.texture)return;t.bindBuffer(t.ARRAY_BUFFER,this.positionBuffer),t.bufferSubData(t.ARRAY_BUFFER,0,this.simulation.positions);let s=Math.max(1,Math.floor(this.rendererOptions.shadingUpdateInterval));this.frameNumber%s===0&&(this.updateVertexNormals(),t.bindBuffer(t.ARRAY_BUFFER,this.normalBuffer),t.bufferSubData(t.ARRAY_BUFFER,0,this.vertexNormals)),this.frameNumber+=1;let i=this.backgroundColor;t.clearColor(i[0]??0,i[1]??0,i[2]??0,this.rendererOptions.transparent?0:i[3]??1),t.clear(t.COLOR_BUFFER_BIT|t.DEPTH_BUFFER_BIT),t.useProgram(this.program),t.bindVertexArray(this.vertexArray),t.activeTexture(t.TEXTURE0),t.bindTexture(t.TEXTURE_2D,this.texture),t.enable(t.BLEND),t.blendFunc(t.SRC_ALPHA,t.ONE_MINUS_SRC_ALPHA),t.disable(t.CULL_FACE),this.syncShaderUniforms(e),this.drawCallsValue=0,this.rendererOptions.shadows&&this.drawShadow(e),t.enable(t.DEPTH_TEST),t.depthMask(true),t.uniform1i(e.shadowPass,0),t.uniform2f(e.screenOffset,0,0),t.drawElements(t.TRIANGLES,this.indices.length,t.UNSIGNED_INT,0),this.drawCallsValue+=1,t.bindVertexArray(null);}findParticleAt(t,e){this.projectParticles();let s=-1,i=Number.POSITIVE_INFINITY;for(let n=0;n<this.indices.length;n+=3){let o=this.indices[n]??0,l=this.indices[n+1]??0,h=this.indices[n+2]??0,c=o*2,d=l*2,m=h*2,x=this.projectedPositions[c]??Number.NaN,w=this.projectedPositions[c+1]??Number.NaN,b=this.projectedPositions[d]??Number.NaN,u=this.projectedPositions[d+1]??Number.NaN,y=this.projectedPositions[m]??Number.NaN,g=this.projectedPositions[m+1]??Number.NaN;if(!Number.isFinite(x+w+b+u+y+g))continue;let T=(u-g)*(x-y)+(y-b)*(w-g);if(Math.abs(T)<1e-8)continue;let f=((u-g)*(t-y)+(y-b)*(e-g))/T,v=((g-w)*(t-y)+(x-y)*(e-g))/T,p=1-f-v;if(f<-1e-3||v<-1e-3||p<-1e-3)continue;let C=f*(this.projectedDepths[o]??0)+v*(this.projectedDepths[l]??0)+p*(this.projectedDepths[h]??0);C>=i||(i=C,f>=v&&f>=p?s=o:s=v>=p?l:h);}return s}unprojectToPlane(t,e,s,i,n,o){let l=(t-this.cssWidth*.5)/this.focalLength,h=-(e-this.cssHeight*.5)/this.focalLength,c=this.forwardX+this.rightX*l+this.upX*h,d=this.forwardY+this.rightY*l+this.upY*h,m=this.forwardZ+this.rightZ*l+this.upZ*h,x=Math.hypot(c,d,m)||1;c/=x,d/=x,m/=x;let w=c*this.forwardX+d*this.forwardY+m*this.forwardZ;if(Math.abs(w)<1e-7)return false;let u=((s-this.cameraX)*this.forwardX+(i-this.cameraY)*this.forwardY+(n-this.cameraZ)*this.forwardZ)/w;return u<=0?false:(o[0]=this.cameraX+c*u,o[1]=this.cameraY+d*u,o[2]=this.cameraZ+m*u,true)}destroy(){this.destroyed||(this.destroyed=true,this.canvas.removeEventListener("webglcontextlost",this.onContextLost),this.canvas.removeEventListener("webglcontextrestored",this.onContextRestored),this.deleteResources(),this.context.isContextLost()||(this.context.clearColor(0,0,0,0),this.context.clear(this.context.COLOR_BUFFER_BIT|this.context.DEPTH_BUFFER_BIT)));}initializeResources(){let t=this.context,e=rt(t,t.VERTEX_SHADER,Dt),s=rt(t,t.FRAGMENT_SHADER,Mt),i=t.createProgram();if(!i)throw new Error("Unable to create the cloth shader program.");if(t.attachShader(i,e),t.attachShader(i,s),t.linkProgram(i),t.deleteShader(e),t.deleteShader(s),!t.getProgramParameter(i,t.LINK_STATUS)){let n=t.getProgramInfoLog(i)||"Unknown link error.";throw t.deleteProgram(i),new Error(`Unable to link the cloth shader: ${n}`)}if(this.program=i,this.uniforms={viewProjection:A(t,i,"uViewProjection"),screenOffset:A(t,i,"uScreenOffset"),texture:A(t,i,"uTexture"),hasTexture:A(t,i,"uHasTexture"),shadowPass:A(t,i,"uShadowPass"),baseColor:A(t,i,"uBaseColor"),shadowColor:A(t,i,"uShadowColor"),lightDirection:A(t,i,"uLightDirection"),cameraPosition:A(t,i,"uCameraPosition"),lightingEnabled:A(t,i,"uLightingEnabled"),lightIntensity:A(t,i,"uLightIntensity"),ambient:A(t,i,"uAmbient"),diffuse:A(t,i,"uDiffuse"),specular:A(t,i,"uSpecular"),shininess:A(t,i,"uShininess"),foldContrast:A(t,i,"uFoldContrast")},this.vertexArray=t.createVertexArray(),this.positionBuffer=t.createBuffer(),this.normalBuffer=t.createBuffer(),this.textureCoordinateBuffer=t.createBuffer(),this.indexBuffer=t.createBuffer(),this.texture=t.createTexture(),!this.vertexArray||!this.positionBuffer||!this.normalBuffer||!this.textureCoordinateBuffer||!this.indexBuffer||!this.texture)throw this.deleteResources(),new Error("Unable to allocate WebGL cloth resources.");t.useProgram(i),t.uniform1i(this.uniforms.texture,0),this.uploadTopology(),this.uploadTexture();}uploadTopology(){let t=this.context;!this.vertexArray||!this.positionBuffer||!this.normalBuffer||!this.textureCoordinateBuffer||!this.indexBuffer||(this.updateVertexNormals(),t.bindVertexArray(this.vertexArray),t.bindBuffer(t.ARRAY_BUFFER,this.positionBuffer),t.bufferData(t.ARRAY_BUFFER,this.simulation.positions,t.DYNAMIC_DRAW),t.enableVertexAttribArray(0),t.vertexAttribPointer(0,3,t.FLOAT,false,0,0),t.bindBuffer(t.ARRAY_BUFFER,this.normalBuffer),t.bufferData(t.ARRAY_BUFFER,this.vertexNormals,t.DYNAMIC_DRAW),t.enableVertexAttribArray(1),t.vertexAttribPointer(1,3,t.FLOAT,false,0,0),t.bindBuffer(t.ARRAY_BUFFER,this.textureCoordinateBuffer),t.bufferData(t.ARRAY_BUFFER,this.textureCoordinates,t.STATIC_DRAW),t.enableVertexAttribArray(2),t.vertexAttribPointer(2,2,t.FLOAT,false,0,0),t.bindBuffer(t.ELEMENT_ARRAY_BUFFER,this.indexBuffer),t.bufferData(t.ELEMENT_ARRAY_BUFFER,this.indices,t.STATIC_DRAW),t.bindVertexArray(null));}uploadTexture(){let t=this.context;this.texture&&(t.activeTexture(t.TEXTURE0),t.bindTexture(t.TEXTURE_2D,this.texture),t.pixelStorei(t.UNPACK_FLIP_Y_WEBGL,0),t.pixelStorei(t.UNPACK_PREMULTIPLY_ALPHA_WEBGL,0),this.textureSource?t.texImage2D(t.TEXTURE_2D,0,t.RGBA,t.RGBA,t.UNSIGNED_BYTE,this.textureSource):t.texImage2D(t.TEXTURE_2D,0,t.RGBA,1,1,0,t.RGBA,t.UNSIGNED_BYTE,Ot),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE),this.applyTextureFiltering());}applyTextureFiltering(){let t=this.context;this.texture&&(t.bindTexture(t.TEXTURE_2D,this.texture),this.rendererOptions.textureFiltering==="nearest"?(t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MIN_FILTER,t.NEAREST),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,t.NEAREST)):(t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MIN_FILTER,t.LINEAR_MIPMAP_LINEAR),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,t.LINEAR),t.generateMipmap(t.TEXTURE_2D)));}rebuildTopology(){let t=0;for(let s=0;s<this.simulation.rows;s+=1)for(let i=0;i<this.simulation.columns;i+=1){let n=t*2;this.textureCoordinates[n]=i/this.simulation.segmentsX,this.textureCoordinates[n+1]=s/this.simulation.segmentsY,t+=1;}let e=0;for(let s=0;s<this.simulation.segmentsY;s+=1){let i=s*this.simulation.columns;for(let n=0;n<this.simulation.segmentsX;n+=1){let o=i+n,l=o+1,h=o+this.simulation.columns,c=h+1;this.indices[e]=o,this.indices[e+1]=h,this.indices[e+2]=l,this.indices[e+3]=l,this.indices[e+4]=h,this.indices[e+5]=c,e+=6;}}}updateVertexNormals(){let t=this.simulation.positions,e=this.vertexNormals;e.fill(0);for(let s=0;s<this.indices.length;s+=3){let i=(this.indices[s]??0)*3,n=(this.indices[s+1]??0)*3,o=(this.indices[s+2]??0)*3,l=(t[n]??0)-(t[i]??0),h=(t[n+1]??0)-(t[i+1]??0),c=(t[n+2]??0)-(t[i+2]??0),d=(t[o]??0)-(t[i]??0),m=(t[o+1]??0)-(t[i+1]??0),x=(t[o+2]??0)-(t[i+2]??0),w=h*x-c*m,b=c*d-l*x,u=l*m-h*d;k(e,i,w,b,u),k(e,n,w,b,u),k(e,o,w,b,u);}for(let s=0;s<this.simulation.particleCount;s+=1){let i=s*3,n=e[i]??0,o=e[i+1]??0,l=e[i+2]??1,h=Math.hypot(n,o,l)||1;e[i]=n/h,e[i+1]=o/h,e[i+2]=l/h;}}projectParticles(){let t=this.simulation.positions;for(let e=0;e<this.simulation.particleCount;e+=1){let s=e*3,i=(t[s]??0)-this.cameraX,n=(t[s+1]??0)-this.cameraY,o=(t[s+2]??0)-this.cameraZ,l=i*this.rightX+n*this.rightY+o*this.rightZ,h=i*this.upX+n*this.upY+o*this.upZ,c=i*this.forwardX+n*this.forwardY+o*this.forwardZ,d=e*2;if(c<=this.camera.near||c>=this.camera.far)this.projectedPositions[d]=Number.NaN,this.projectedPositions[d+1]=Number.NaN;else {let m=this.focalLength/c;this.projectedPositions[d]=this.cssWidth*.5+l*m,this.projectedPositions[d+1]=this.cssHeight*.5-h*m;}this.projectedDepths[e]=c;}}drawShadow(t){let e=this.context,s=this.shadowColor,i=I.length/2,n=Math.max(0,this.material.shadowBlur)*.36;e.disable(e.DEPTH_TEST),e.depthMask(false),e.uniform1i(t.shadowPass,1),e.uniform4f(t.shadowColor,s[0]??0,s[1]??0,s[2]??0,(s[3]??0)/i);for(let o=0;o<I.length;o+=2){let l=this.material.shadowOffsetX+(I[o]??0)*n,h=this.material.shadowOffsetY+(I[o+1]??0)*n;e.uniform2f(t.screenOffset,l*2/this.cssWidth,-h*2/this.cssHeight),e.drawElements(e.TRIANGLES,this.indices.length,e.UNSIGNED_INT,0),this.drawCallsValue+=1;}}syncShaderUniforms(t){let e=this.context,s=this.baseColor,i=this.shadowColor;e.uniformMatrix4fv(t.viewProjection,false,this.viewProjectionMatrix),e.uniform1i(t.hasTexture,this.textureSource?1:0),e.uniform4f(t.baseColor,s[0]??1,s[1]??1,s[2]??1,s[3]??1),e.uniform4f(t.shadowColor,i[0]??0,i[1]??0,i[2]??0,i[3]??0),e.uniform3f(t.lightDirection,this.lightX,this.lightY,this.lightZ),e.uniform3f(t.cameraPosition,this.cameraX,this.cameraY,this.cameraZ),e.uniform1i(t.lightingEnabled,this.lighting.enabled?1:0),e.uniform1f(t.lightIntensity,this.lighting.intensity),e.uniform1f(t.ambient,this.material.ambient),e.uniform1f(t.diffuse,this.material.diffuse),e.uniform1f(t.specular,this.rendererOptions.preset==="quality"?this.material.specular:0),e.uniform1f(t.shininess,this.material.shininess),e.uniform1f(t.foldContrast,this.material.foldContrast);}updateCameraBasis(){let t=this.camera.position;if(t)this.cameraX=t[0],this.cameraY=t[1],this.cameraZ=t[2];else {let d=Math.max(this.simulation.width,this.simulation.height)*.65/Math.tan(this.camera.fov*Math.PI/360);this.cameraX=0,this.cameraY=0,this.cameraZ=d;}let e=this.camera.lookAt[0]-this.cameraX,s=this.camera.lookAt[1]-this.cameraY,i=this.camera.lookAt[2]-this.cameraZ,n=Math.hypot(e,s,i)||1;e/=n,s/=n,i/=n,this.forwardX=e,this.forwardY=s,this.forwardZ=i;let o=-i,l=0,h=e,c=Math.hypot(o,l,h);c<1e-7&&(o=1,l=0,h=0,c=1),this.rightX=o/c,this.rightY=l/c,this.rightZ=h/c,this.upX=this.rightY*i-this.rightZ*s,this.upY=this.rightZ*e-this.rightX*i,this.upZ=this.rightX*s-this.rightY*e,this.focalLength=this.cssHeight/(2*Math.tan(this.camera.fov*Math.PI/360)),this.updateViewProjection();}updateViewProjection(){let t=this.viewMatrix;t[0]=this.rightX,t[1]=this.upX,t[2]=-this.forwardX,t[3]=0,t[4]=this.rightY,t[5]=this.upY,t[6]=-this.forwardY,t[7]=0,t[8]=this.rightZ,t[9]=this.upZ,t[10]=-this.forwardZ,t[11]=0,t[12]=-(this.rightX*this.cameraX+this.rightY*this.cameraY+this.rightZ*this.cameraZ),t[13]=-(this.upX*this.cameraX+this.upY*this.cameraY+this.upZ*this.cameraZ),t[14]=this.forwardX*this.cameraX+this.forwardY*this.cameraY+this.forwardZ*this.cameraZ,t[15]=1;let e=this.projectionMatrix;e.fill(0);let s=1/Math.tan(this.camera.fov*Math.PI/360),i=this.cssWidth/this.cssHeight,n=Math.max(1e-4,this.camera.near),o=Math.max(n+1e-4,this.camera.far);e[0]=s/i,e[5]=s,e[10]=(o+n)/(n-o),e[11]=-1,e[14]=2*o*n/(n-o),It(this.viewProjectionMatrix,e,t);}updateLightDirection(){let t=this.lighting.direction,e=Math.hypot(t[0],t[1],t[2])||1;this.lightX=t[0]/e,this.lightY=t[1]/e,this.lightZ=t[2]/e;}updateColors(){this.baseColor=q(this.material.baseColor,[1,1,1,1]),this.shadowColor=q(this.material.shadowColor,[0,0,0,.3]),this.backgroundColor=q(this.rendererOptions.backgroundColor,[0,0,0,0]);}deleteResources(){let t=this.context;this.texture&&t.deleteTexture(this.texture),this.indexBuffer&&t.deleteBuffer(this.indexBuffer),this.textureCoordinateBuffer&&t.deleteBuffer(this.textureCoordinateBuffer),this.normalBuffer&&t.deleteBuffer(this.normalBuffer),this.positionBuffer&&t.deleteBuffer(this.positionBuffer),this.vertexArray&&t.deleteVertexArray(this.vertexArray),this.program&&t.deleteProgram(this.program),this.texture=null,this.indexBuffer=null,this.textureCoordinateBuffer=null,this.normalBuffer=null,this.positionBuffer=null,this.vertexArray=null,this.uniforms=null,this.program=null;}};function rt(a,t,e){let s=a.createShader(t);if(!s)throw new Error("Unable to create a cloth shader.");if(a.shaderSource(s,e),a.compileShader(s),!a.getShaderParameter(s,a.COMPILE_STATUS)){let i=a.getShaderInfoLog(s)||"Unknown compilation error.";throw a.deleteShader(s),new Error(`Unable to compile the cloth shader: ${i}`)}return s}function A(a,t,e){let s=a.getUniformLocation(t,e);if(!s)throw new Error(`The cloth shader is missing uniform ${e}.`);return s}function k(a,t,e,s,i){a[t]=(a[t]??0)+e,a[t+1]=(a[t+1]??0)+s,a[t+2]=(a[t+2]??0)+i;}function It(a,t,e){for(let s=0;s<4;s+=1){let i=s*4,n=e[i]??0,o=e[i+1]??0,l=e[i+2]??0,h=e[i+3]??0;a[i]=(t[0]??0)*n+(t[4]??0)*o+(t[8]??0)*l+(t[12]??0)*h,a[i+1]=(t[1]??0)*n+(t[5]??0)*o+(t[9]??0)*l+(t[13]??0)*h,a[i+2]=(t[2]??0)*n+(t[6]??0)*o+(t[10]??0)*l+(t[14]??0)*h,a[i+3]=(t[3]??0)*n+(t[7]??0)*o+(t[11]??0)*l+(t[15]??0)*h;}}function q(a,t){let e=new Float32Array(t),s=a.trim().toLowerCase();if(s==="transparent")return new Float32Array([0,0,0,0]);if(s==="black")return new Float32Array([0,0,0,1]);if(s==="white")return new Float32Array([1,1,1,1]);if(s.startsWith("#")){let o=s.slice(1);if(o.length===3||o.length===4)return e[0]=Number.parseInt(o.charAt(0)+o.charAt(0),16)/255,e[1]=Number.parseInt(o.charAt(1)+o.charAt(1),16)/255,e[2]=Number.parseInt(o.charAt(2)+o.charAt(2),16)/255,e[3]=o.length===4?Number.parseInt(o.charAt(3)+o.charAt(3),16)/255:1,e;if(o.length===6||o.length===8)return e[0]=Number.parseInt(o.slice(0,2),16)/255,e[1]=Number.parseInt(o.slice(2,4),16)/255,e[2]=Number.parseInt(o.slice(4,6),16)/255,e[3]=o.length===8?Number.parseInt(o.slice(6,8),16)/255:1,e}let i=s.match(/^rgba?\(([^)]+)\)$/);if(!i?.[1])return e;let n=i[1].split(",");return n.length<3||(e[0]=$(n[0]??"0"),e[1]=$(n[1]??"0"),e[2]=$(n[2]??"0"),e[3]=n[3]===void 0?1:Math.min(1,Math.max(0,Number(n[3])))),e}function $(a){let t=a.trim(),e=Number.parseFloat(t);return Number.isFinite(e)?t.endsWith("%")?Math.min(1,Math.max(0,e/100)):Math.min(1,Math.max(0,e/255)):0}function nt(a,t,e,s){let i=a+1,n=t+1,o=a*n+t*i,l=a*t*2,h=Math.max(0,a-1)*n+Math.max(0,t-1)*i,c=o+l+h,d=new Uint32Array(c),m=new Uint32Array(c),x=new Float32Array(c),w=new Uint8Array(c),b=e/a,u=s/t,y=Math.hypot(b,u),g=0,T=(f,v,p,C)=>{d[g]=f,m[g]=v,x[g]=p,w[g]=C,g+=1;};for(let f=0;f<n;f+=1){let v=f*i;for(let p=0;p<a;p+=1)T(v+p,v+p+1,b,0);}for(let f=0;f<t;f+=1){let v=f*i;for(let p=0;p<i;p+=1)T(v+p,v+i+p,u,0);}for(let f=0;f<t;f+=1){let v=f*i;for(let p=0;p<a;p+=1){let C=v+p,L=C+i;T(C,L+1,y,1),T(C+1,L,y,1);}}for(let f=0;f<n;f+=1){let v=f*i;for(let p=0;p<a-1;p+=1)T(v+p,v+p+2,b*2,2);}for(let f=0;f<t-1;f+=1){let v=f*i;for(let p=0;p<i;p+=1)T(v+p,v+i*2+p,u*2,2);}return {first:d,second:m,restLength:x,kind:w,count:c}}function P(a,t,e){let s=Math.imul(a,374761393)+Math.imul(t,668265263)+Math.imul(e,2147483647);return s=Math.imul(s^s>>>13,1274126177),((s^s>>>16)>>>0)/4294967295}function K(a){return a*a*(3-2*a)}function S(a,t,e){return a+(t-a)*e}function J(a,t,e){let s=Math.floor(a),i=Math.floor(t),n=Math.floor(e),o=K(a-s),l=K(t-i),h=K(e-n),c=S(P(s,i,n),P(s+1,i,n),o),d=S(P(s,i+1,n),P(s+1,i+1,n),o),m=S(P(s,i,n+1),P(s+1,i,n+1),o),x=S(P(s,i+1,n+1),P(s+1,i+1,n+1),o);return S(S(c,d,l),S(m,x,l),h)}var U=class{constructor(t){r(this,"width");r(this,"height");r(this,"segmentsX");r(this,"segmentsY");r(this,"columns");r(this,"rows");r(this,"particleCount");r(this,"positions");r(this,"previousPositions");r(this,"initialPositions");r(this,"forces");r(this,"inverseMass");r(this,"pinned");r(this,"constraints");r(this,"simulation");r(this,"wind");r(this,"windDirectionX",1);r(this,"windDirectionY",0);r(this,"windDirectionZ",0);r(this,"elapsedTime",0);r(this,"dragWeights");r(this,"dragParticles",new Uint32Array(0));r(this,"dragOffsets",new Float32Array(0));r(this,"dragParticleCount",0);r(this,"dragTargetX",0);r(this,"dragTargetY",0);r(this,"dragTargetZ",0);r(this,"dragStiffness",0);r(this,"dragAllowsPinned",false);if(t.width<=0||t.height<=0)throw new RangeError("Cloth width and height must be greater than zero.");if(!Number.isInteger(t.segmentsX)||!Number.isInteger(t.segmentsY))throw new TypeError("Cloth segment counts must be integers.");if(t.segmentsX<1||t.segmentsY<1)throw new RangeError("Cloth segment counts must be at least one.");this.width=t.width,this.height=t.height,this.segmentsX=t.segmentsX,this.segmentsY=t.segmentsY,this.columns=t.segmentsX+1,this.rows=t.segmentsY+1,this.particleCount=this.columns*this.rows,this.positions=new Float32Array(this.particleCount*3),this.previousPositions=new Float32Array(this.particleCount*3),this.initialPositions=new Float32Array(this.particleCount*3),this.forces=new Float32Array(this.particleCount*3),this.inverseMass=new Float32Array(this.particleCount),this.pinned=new Uint8Array(this.particleCount),this.dragWeights=new Float32Array(this.particleCount),this.constraints=nt(t.segmentsX,t.segmentsY,t.width,t.height),this.simulation={...t.simulation},this.wind={...t.wind},this.initializeParticles(),this.setAttachment(t.attachment),this.updateWindDirection();}get constraintCount(){return this.constraints.count}get time(){return this.elapsedTime}particleIndex(t,e){return t<0||t>=this.columns||e<0||e>=this.rows?-1:e*this.columns+t}isPinned(t){return this.pinned[t]===1}addForce(t,e){if(t<0||t>=this.particleCount)return;let s=t*3;this.forces[s]=(this.forces[s]??0)+e[0],this.forces[s+1]=(this.forces[s+1]??0)+e[1],this.forces[s+2]=(this.forces[s+2]??0)+e[2];}setWind(t){this.wind={...this.wind,...t},t.direction&&this.updateWindDirection();}setSimulationOptions(t){this.simulation={...this.simulation,...t},t.mass!==void 0&&this.updateInverseMass();}setAttachment(t){this.pinned.fill(0);let e=(s,i)=>{let n=this.particleIndex(s,i);n>=0&&(this.pinned[n]=1);};if(t==="left")this.pinEdge("left",1,0);else if(t==="right")this.pinEdge("right",1,0);else if(t==="top")this.pinEdge("top",1,0);else if(t==="bottom")this.pinEdge("bottom",1,0);else if(t==="top-left")e(0,0);else if(t==="top-right")e(this.segmentsX,0);else if(t==="bottom-left")e(0,this.segmentsY);else if(t==="bottom-right")e(this.segmentsX,this.segmentsY);else if(typeof t=="object"&&"edge"in t)this.pinEdge(t.edge,t.every??1,t.offset??0);else if(typeof t=="object"&&"points"in t)for(let s=0;s<t.points.length;s+=1){let i=t.points[s];i&&e(Math.round(i[0]),Math.round(i[1]));}this.updateInverseMass(),this.enforcePins();}beginDrag(t,e,s,i){if(t<0||t>=this.particleCount||this.isPinned(t)&&!i)return false;this.clearDragWeights();let n=t%this.columns,o=Math.floor(t/this.columns),l=Math.max(0,e),h=Math.ceil(l),c=0;for(let u=o-h;u<=o+h;u+=1)for(let y=n-h;y<=n+h;y+=1){let g=this.particleIndex(y,u);if(g<0||this.isPinned(g)&&!i)continue;(Math.hypot(y-n,u-o)<=l||g===t)&&(c+=1);}this.dragParticles=new Uint32Array(c),this.dragOffsets=new Float32Array(c*3);let d=t*3,m=this.positions[d]??0,x=this.positions[d+1]??0,w=this.positions[d+2]??0,b=0;for(let u=o-h;u<=o+h;u+=1)for(let y=n-h;y<=n+h;y+=1){let g=this.particleIndex(y,u);if(g<0||this.isPinned(g)&&!i)continue;let T=Math.hypot(y-n,u-o);if(T>l&&g!==t)continue;let f=g*3,v=b*3;this.dragParticles[b]=g,this.dragOffsets[v]=(this.positions[f]??0)-m,this.dragOffsets[v+1]=(this.positions[f+1]??0)-x,this.dragOffsets[v+2]=(this.positions[f+2]??0)-w,this.dragWeights[g]=l===0?1:Math.max(.15,1-T/(l+1)),b+=1;}return this.dragParticleCount=c,this.dragTargetX=m,this.dragTargetY=x,this.dragTargetZ=w,this.dragStiffness=Math.min(1,Math.max(0,s)),this.dragAllowsPinned=i,true}setDragTarget(t,e,s){this.dragTargetX=t,this.dragTargetY=e,this.dragTargetZ=s;}endDrag(t=[0,0,0],e=1/60){for(let s=0;s<this.dragParticleCount;s+=1){let i=this.dragParticles[s];if(i===void 0)continue;let n=this.dragWeights[i]??0,o=i*3;this.previousPositions[o]=(this.previousPositions[o]??0)-t[0]*e*n,this.previousPositions[o+1]=(this.previousPositions[o+1]??0)-t[1]*e*n,this.previousPositions[o+2]=(this.previousPositions[o+2]??0)-t[2]*e*n;}this.clearDragWeights(),this.dragParticleCount=0;}step(t){if(!Number.isFinite(t)||t<=0)return;let e=Math.max(1,Math.floor(this.simulation.substeps)),s=t/e;for(let i=0;i<e;i+=1)this.applyWind(s),this.integrate(s),this.solveConstraints(),this.elapsedTime+=s;}reset(){this.positions.set(this.initialPositions),this.previousPositions.set(this.initialPositions),this.forces.fill(0),this.clearDragWeights(),this.dragParticleCount=0,this.elapsedTime=0,this.enforcePins();}initializeParticles(){let t=this.particleCount/Math.max(1e-6,this.simulation.mass),e=0;for(let s=0;s<this.rows;s+=1){let i=this.height*.5-s/this.segmentsY*this.height;for(let n=0;n<this.columns;n+=1){let o=e*3;this.positions[o]=-this.width*.5+n/this.segmentsX*this.width,this.positions[o+1]=i,this.positions[o+2]=0,this.inverseMass[e]=t,e+=1;}}this.previousPositions.set(this.positions),this.initialPositions.set(this.positions);}updateInverseMass(){let t=this.particleCount/Math.max(1e-6,this.simulation.mass);for(let e=0;e<this.particleCount;e+=1)this.inverseMass[e]=this.pinned[e]===1?0:t;}pinEdge(t,e,s){let i=Math.max(1,Math.floor(e)),n=Math.max(0,Math.floor(s));if(t==="left"||t==="right"){let o=t==="left"?0:this.segmentsX;for(let l=n;l<this.rows;l+=i)this.pinned[l*this.columns+o]=1;}else {let l=(t==="top"?0:this.segmentsY)*this.columns;for(let h=n;h<this.columns;h+=i)this.pinned[l+h]=1;}}integrate(t){let e=t*t,s=Math.pow(Math.min(1,Math.max(0,this.simulation.damping)),t*60),i=this.simulation.gravity;for(let n=0;n<this.particleCount;n+=1){let o=n*3,l=this.inverseMass[n]??0;if(l===0){this.forces[o]=0,this.forces[o+1]=0,this.forces[o+2]=0;continue}let h=this.positions[o]??0,c=this.positions[o+1]??0,d=this.positions[o+2]??0,m=(h-(this.previousPositions[o]??0))*s,x=(c-(this.previousPositions[o+1]??0))*s,w=(d-(this.previousPositions[o+2]??0))*s;this.previousPositions[o]=h,this.previousPositions[o+1]=c,this.previousPositions[o+2]=d,this.positions[o]=h+m+(i[0]+(this.forces[o]??0)*l)*e,this.positions[o+1]=c+x+(i[1]+(this.forces[o+1]??0)*l)*e,this.positions[o+2]=d+w+(i[2]+(this.forces[o+2]??0)*l)*e,this.forces[o]=0,this.forces[o+1]=0,this.forces[o+2]=0;}}solveConstraints(){let t=Math.max(1,Math.floor(this.simulation.constraintIterations)),e=this.iterationStiffness(this.simulation.structuralStiffness,t),s=this.iterationStiffness(this.simulation.shearStiffness,t),i=this.iterationStiffness(this.simulation.bendStiffness,t),n=Math.max(0,this.simulation.maxCorrection),o=this.constraints.first,l=this.constraints.second,h=this.constraints.restLength,c=this.constraints.kind;for(let d=0;d<t;d+=1){for(let m=0;m<this.constraints.count;m+=1){let x=o[m]??0,w=l[m]??0,b=x*3,u=w*3,y=(this.positions[u]??0)-(this.positions[b]??0),g=(this.positions[u+1]??0)-(this.positions[b+1]??0),T=(this.positions[u+2]??0)-(this.positions[b+2]??0),f=y*y+g*g+T*T;if(f<1e-12)continue;let v=Math.sqrt(f),p=v-(h[m]??0);n>0&&(p=Math.max(-n,Math.min(n,p)));let C=c[m]??0,L=C===1?s:C===2?i:e,R=this.inverseMass[x]??0,M=this.inverseMass[w]??0,F=R+M;if(F===0)continue;let E=p/v*L,O=E*(R/F),D=E*(M/F);this.positions[b]=(this.positions[b]??0)+y*O,this.positions[b+1]=(this.positions[b+1]??0)+g*O,this.positions[b+2]=(this.positions[b+2]??0)+T*O,this.positions[u]=(this.positions[u]??0)-y*D,this.positions[u+1]=(this.positions[u+1]??0)-g*D,this.positions[u+2]=(this.positions[u+2]??0)-T*D;}this.solveDrag(t),this.enforcePins();}}solveDrag(t){if(this.dragParticleCount===0)return;let e=this.iterationStiffness(this.dragStiffness,t);for(let s=0;s<this.dragParticleCount;s+=1){let i=this.dragParticles[s];if(i===void 0)continue;let n=i*3,o=s*3,l=e*(this.dragWeights[i]??0),h=this.dragTargetX+(this.dragOffsets[o]??0),c=this.dragTargetY+(this.dragOffsets[o+1]??0),d=this.dragTargetZ+(this.dragOffsets[o+2]??0);this.positions[n]=(this.positions[n]??0)+(h-(this.positions[n]??0))*l,this.positions[n+1]=(this.positions[n+1]??0)+(c-(this.positions[n+1]??0))*l,this.positions[n+2]=(this.positions[n+2]??0)+(d-(this.positions[n+2]??0))*l;}}enforcePins(){for(let t=0;t<this.particleCount;t+=1){if(this.pinned[t]!==1||this.dragAllowsPinned&&(this.dragWeights[t]??0)>0)continue;let e=t*3;this.positions[e]=this.initialPositions[e]??0,this.positions[e+1]=this.initialPositions[e+1]??0,this.positions[e+2]=this.initialPositions[e+2]??0,this.previousPositions[e]=this.initialPositions[e]??0,this.previousPositions[e+1]=this.initialPositions[e+1]??0,this.previousPositions[e+2]=this.initialPositions[e+2]??0;}}applyWind(t){if(this.wind.strength===0||this.wind.aerodynamicCoefficient===0)return;let e=this.positions,s=this.previousPositions,i=this.columns;for(let n=0;n<this.segmentsY;n+=1){let o=n*i;for(let l=0;l<this.segmentsX;l+=1){let h=o+l,c=h+1,d=h+i,m=d+1;this.applyTriangleWind(h,d,c,e,s,t),this.applyTriangleWind(c,d,m,e,s,t);}}}applyTriangleWind(t,e,s,i,n,o){let l=t*3,h=e*3,c=s*3,d=i[l]??0,m=i[l+1]??0,x=i[l+2]??0,w=(i[h]??0)-d,b=(i[h+1]??0)-m,u=(i[h+2]??0)-x,y=(i[c]??0)-d,g=(i[c+1]??0)-m,T=(i[c+2]??0)-x,f=b*T-u*g,v=u*y-w*T,p=w*g-b*y,C=Math.hypot(f,v,p);if(C<1e-10)return;let L=(d+(i[h]??0)+(i[c]??0))/3,R=(m+(i[h+1]??0)+(i[c+1]??0))/3,M=(x+(i[h+2]??0)+(i[c+2]??0))/3,F=this.elapsedTime*Math.max(0,this.wind.gustFrequency),E=Math.max(1e-4,this.wind.spatialScale),O=J(L*E,R*E,F)*2-1,D=J(R*E+17.1,M*E-9.2,F+5.4)*2-1,_=1+O*this.wind.turbulence,B=this.wind.strength*this.wind.turbulence*.2*D,yt=this.windDirectionX*this.wind.strength*_+B,wt=this.windDirectionY*this.wind.strength*_-B*.35,Tt=this.windDirectionZ*this.wind.strength*_+B*.5,Z=1/Math.max(1e-6,o),At=(d-(n[l]??0)+((i[h]??0)-(n[h]??0))+((i[c]??0)-(n[c]??0)))*(Z/3),Ct=(m-(n[l+1]??0)+((i[h+1]??0)-(n[h+1]??0))+((i[c+1]??0)-(n[c+1]??0)))*(Z/3),Pt=(x-(n[l+2]??0)+((i[h+2]??0)-(n[h+2]??0))+((i[c+2]??0)-(n[c+2]??0)))*(Z/3),G=1/C,tt=f*G,et=v*G,it=p*G,Et=yt-At,St=wt-Ct,Lt=Tt-Pt,st=Et*tt+St*et+Lt*it,V=st*Math.abs(st)*this.wind.aerodynamicCoefficient*(C*.5),H=tt*V/3,j=et*V/3,z=it*V/3;this.forces[l]=(this.forces[l]??0)+H,this.forces[l+1]=(this.forces[l+1]??0)+j,this.forces[l+2]=(this.forces[l+2]??0)+z,this.forces[h]=(this.forces[h]??0)+H,this.forces[h+1]=(this.forces[h+1]??0)+j,this.forces[h+2]=(this.forces[h+2]??0)+z,this.forces[c]=(this.forces[c]??0)+H,this.forces[c+1]=(this.forces[c+1]??0)+j,this.forces[c+2]=(this.forces[c+2]??0)+z;}updateWindDirection(){let t=this.wind.direction,e=Math.hypot(t[0],t[1],t[2]);if(e<1e-8){this.windDirectionX=0,this.windDirectionY=0,this.windDirectionZ=0;return}this.windDirectionX=t[0]/e,this.windDirectionY=t[1]/e,this.windDirectionZ=t[2]/e;}iterationStiffness(t,e){let s=Math.min(1,Math.max(0,t));return 1-Math.pow(1-s,1/e)}clearDragWeights(){for(let t=0;t<this.dragParticleCount;t+=1){let e=this.dragParticles[t];e!==void 0&&(this.dragWeights[e]=0);}this.dragAllowsPinned=false;}};var X=class{constructor(t,e,s=8){r(this,"fixedTimeStep",t);r(this,"maxFrameDelta",e);r(this,"maxStepsPerFrame",s);r(this,"accumulator",0);}configure(t,e){this.fixedTimeStep=t,this.maxFrameDelta=e,this.accumulator=Math.min(this.accumulator,t);}advance(t,e){if(!Number.isFinite(t)||t<=0)return 0;let s=Math.min(t,this.maxFrameDelta);this.accumulator+=s;let i=0;for(;this.accumulator>=this.fixedTimeStep&&i<this.maxStepsPerFrame;)e(this.fixedTimeStep),this.accumulator-=this.fixedTimeStep,i+=1;return i===this.maxStepsPerFrame&&(this.accumulator=Math.min(this.accumulator,this.fixedTimeStep)),i}reset(){this.accumulator=0;}};var ot={x:32,y:20},at={direction:[1,.08,.3],strength:7,turbulence:.35,gustFrequency:.5,spatialScale:.8,aerodynamicCoefficient:.35},ht={damping:.985,gravity:[0,0,0],fixedTimeStep:1/60,maxFrameDelta:.1,substeps:2,constraintIterations:6,structuralStiffness:1,shearStiffness:.9,bendStiffness:.4,maxCorrection:.25,mass:1},lt={enabled:true,dragRadius:1,dragStiffness:.9,maxDragDistance:4,allowPinned:false,releaseImpulse:[0,0,0]},ct={alpha:true,antialias:true,desynchronized:false,powerPreference:"high-performance",maxPixelRatio:1.5,preset:"quality",textureFiltering:"linear",transparent:true,backgroundColor:"transparent",shadows:true,shadingUpdateInterval:1},dt={quality:{maxPixelRatio:1.5,shadows:true,shadingUpdateInterval:1},performance:{maxPixelRatio:1,shadows:false,shadingUpdateInterval:2}},ut={baseColor:"#f2f4f3",ambient:.58,diffuse:.55,specular:.16,shininess:18,foldContrast:.42,shadowColor:"rgba(0, 0, 0, 0.3)",shadowBlur:18,shadowOffsetX:10,shadowOffsetY:12},mt={enabled:true,intensity:1,direction:[-0.4,.7,1]},ft={fov:40,near:.01,far:100,position:null,lookAt:[0,0,0]},pt={pauseWhenOffscreen:true,pauseWhenDocumentHidden:true},gt={enabled:false,updateInterval:500};var Y=class{constructor(t){r(this,"element");this.element=document.createElement("div"),this.element.setAttribute("data-flag-cloth-debug",""),this.element.style.cssText="position:absolute;left:8px;top:8px;z-index:10;padding:7px 9px;border-radius:5px;background:rgba(0,0,0,.72);color:#bfffd2;font:11px/1.45 ui-monospace,SFMono-Regular,Consolas,monospace;pointer-events:none;white-space:pre",this.element.textContent="flag-cloth",t.appendChild(this.element);}update(t){this.element.textContent=`FPS ${t.fps.toFixed(0)}
|
|
79
|
+
simulation ${t.simulationMs.toFixed(2)} ms
|
|
80
|
+
render ${t.renderMs.toFixed(2)} ms
|
|
81
|
+
particles ${t.particleCount}
|
|
82
|
+
constraints ${t.constraintCount}
|
|
83
|
+
draw calls ${t.drawCalls}`;}destroy(){this.element.remove();}};var W=class{constructor(t,e,s,i,n){r(this,"element",t);r(this,"renderer");r(this,"simulation");r(this,"options");r(this,"fixedTimeStep");r(this,"worldTarget",new Float32Array(3));r(this,"screenPoint",new Float32Array(2));r(this,"activePointerId",-1);r(this,"selectedParticle",-1);r(this,"planeX",0);r(this,"planeY",0);r(this,"planeZ",0);r(this,"grabOffsetX",0);r(this,"grabOffsetY",0);r(this,"grabOffsetZ",0);r(this,"startTargetX",0);r(this,"startTargetY",0);r(this,"startTargetZ",0);r(this,"destroyed",false);r(this,"originalTouchAction");r(this,"onPointerDown",t=>{if(!this.options.enabled||this.activePointerId>=0||t.pointerType==="mouse"&&t.button!==0)return;this.readPointerPosition(t);let e=this.screenPoint[0]??0,s=this.screenPoint[1]??0,i=this.renderer.findParticleAt(e,s);if(!this.simulation.beginDrag(i,this.options.dragRadius,this.options.dragStiffness,this.options.allowPinned))return;let n=i*3;this.selectedParticle=i,this.planeX=this.simulation.positions[n]??0,this.planeY=this.simulation.positions[n+1]??0,this.planeZ=this.simulation.positions[n+2]??0,this.renderer.unprojectToPlane(e,s,this.planeX,this.planeY,this.planeZ,this.worldTarget)?(this.grabOffsetX=this.planeX-(this.worldTarget[0]??0),this.grabOffsetY=this.planeY-(this.worldTarget[1]??0),this.grabOffsetZ=this.planeZ-(this.worldTarget[2]??0)):(this.grabOffsetX=0,this.grabOffsetY=0,this.grabOffsetZ=0),this.startTargetX=this.planeX,this.startTargetY=this.planeY,this.startTargetZ=this.planeZ,this.activePointerId=t.pointerId,this.element.setPointerCapture(t.pointerId),t.preventDefault();});r(this,"onPointerMove",t=>{if(t.pointerId!==this.activePointerId||this.selectedParticle<0||(this.readPointerPosition(t),!this.renderer.unprojectToPlane(this.screenPoint[0]??0,this.screenPoint[1]??0,this.planeX,this.planeY,this.planeZ,this.worldTarget)))return;let e=(this.worldTarget[0]??0)+this.grabOffsetX,s=(this.worldTarget[1]??0)+this.grabOffsetY,i=(this.worldTarget[2]??0)+this.grabOffsetZ,n=e-this.startTargetX,o=s-this.startTargetY,l=i-this.startTargetZ,h=Math.hypot(n,o,l),c=Math.max(0,this.options.maxDragDistance);if(c>0&&h>c){let d=c/h;e=this.startTargetX+n*d,s=this.startTargetY+o*d,i=this.startTargetZ+l*d;}this.simulation.setDragTarget(e,s,i),t.preventDefault();});r(this,"onPointerEnd",t=>{t.pointerId===this.activePointerId&&this.releaseDrag(this.options.releaseImpulse);});r(this,"onLostPointerCapture",t=>{t.pointerId===this.activePointerId&&this.releaseDrag(this.options.releaseImpulse);});this.renderer=e,this.simulation=s,this.options=i,this.fixedTimeStep=n,this.originalTouchAction=t.style.touchAction,this.syncTouchAction(),t.addEventListener("pointerdown",this.onPointerDown),t.addEventListener("pointermove",this.onPointerMove),t.addEventListener("pointerup",this.onPointerEnd),t.addEventListener("pointercancel",this.onPointerEnd),t.addEventListener("lostpointercapture",this.onLostPointerCapture);}setTargets(t,e){this.activePointerId>=0&&this.releaseDrag(),this.renderer=t,this.simulation=e;}setOptions(t,e){this.options=t,this.fixedTimeStep=e,!t.enabled&&this.activePointerId>=0&&this.releaseDrag(),this.syncTouchAction();}destroy(){this.destroyed||(this.releaseDrag(),this.element.removeEventListener("pointerdown",this.onPointerDown),this.element.removeEventListener("pointermove",this.onPointerMove),this.element.removeEventListener("pointerup",this.onPointerEnd),this.element.removeEventListener("pointercancel",this.onPointerEnd),this.element.removeEventListener("lostpointercapture",this.onLostPointerCapture),this.element.style.touchAction=this.originalTouchAction,this.destroyed=true);}readPointerPosition(t){let e=this.element.getBoundingClientRect();this.screenPoint[0]=(t.clientX-e.left)/Math.max(1,e.width)*this.renderer.width,this.screenPoint[1]=(t.clientY-e.top)/Math.max(1,e.height)*this.renderer.height;}releaseDrag(t=[0,0,0]){if(this.activePointerId<0)return;let e=this.activePointerId;this.activePointerId=-1,this.selectedParticle=-1,this.simulation.endDrag(t,this.fixedTimeStep),this.element.hasPointerCapture(e)&&this.element.releasePointerCapture(e);}syncTouchAction(){this.element.style.touchAction=this.options.enabled?"none":this.originalTouchAction;}};async function vt(a){if(a===null||typeof a!="string")return a;let t=new Image;return t.decoding="async",t.crossOrigin="anonymous",await new Promise((e,s)=>{t.addEventListener("load",()=>e(),{once:true}),t.addEventListener("error",()=>s(new Error(`Unable to load cloth texture: ${a}`)),{once:true}),t.src=a;}),t}var Q=class{constructor(t){r(this,"container");r(this,"canvas");r(this,"context");r(this,"renderer");r(this,"ready");r(this,"options");r(this,"simulation");r(this,"fixedTimeStep");r(this,"pointerController");r(this,"currentTexture",null);r(this,"ownsCanvas");r(this,"externalAnimationLoop");r(this,"canvasAppended",false);r(this,"resizeObserver",null);r(this,"intersectionObserver",null);r(this,"debugPanel",null);r(this,"textureRequest",0);r(this,"animationFrame",0);r(this,"lastFrameTime",0);r(this,"started",false);r(this,"userPaused",false);r(this,"documentHidden",false);r(this,"offscreen",false);r(this,"destroyed",false);r(this,"lastWidth",0);r(this,"lastHeight",0);r(this,"statsFrameCount",0);r(this,"statsWindowStarted",0);r(this,"lastDebugUpdate",0);r(this,"statsValue",{fps:0,simulationMs:0,renderMs:0,particleCount:0,constraintCount:0,drawCalls:0});r(this,"simulateStep",t=>{this.simulation.step(t);});r(this,"onAnimationFrame",t=>{if(this.animationFrame=0,!this.canAnimate())return;let e=this.lastFrameTime===0?0:(t-this.lastFrameTime)/1e3;this.lastFrameTime=t,this.update(e,true),this.requestAnimationFrameIfNeeded();});r(this,"onResizeObserved",()=>this.resize());r(this,"onIntersection",t=>{let e=t[t.length-1];e&&(this.offscreen=!e.isIntersecting,this.syncAnimationState());});r(this,"onVisibilityChange",()=>{this.documentHidden=document.visibilityState==="hidden",this.syncAnimationState();});if(!t.container)throw new TypeError("createFlagCloth requires a container element.");this.container=t.container,this.options=Xt(t),this.ownsCanvas=!t.advanced?.canvas&&!t.advanced?.context,this.externalAnimationLoop=t.advanced?.externalAnimationLoop??false;let e=t.advanced?.context?.canvas;if(e&&!(e instanceof HTMLCanvasElement))throw new TypeError("advanced.context must belong to an HTMLCanvasElement.");if(this.canvas=e??t.advanced?.canvas??document.createElement("canvas"),t.advanced?.canvas&&t.advanced.context&&t.advanced.context.canvas!==t.advanced.canvas)throw new TypeError("advanced.canvas and advanced.context must refer to the same canvas.");let s=t.advanced?.context??this.canvas.getContext("webgl2",{alpha:this.options.renderer.alpha,antialias:this.options.renderer.antialias,desynchronized:this.options.renderer.desynchronized,powerPreference:this.options.renderer.powerPreference,premultipliedAlpha:true,preserveDrawingBuffer:false});if(!s)throw new Error("A WebGL2 rendering context is required.");this.context=s,this.simulation=this.createSimulation(),this.renderer=new N(this.canvas,this.context,this.simulation,this.options.renderer,this.options.material,this.options.lighting,this.options.camera),this.canvas.style.display="block",this.canvas.style.width="100%",this.canvas.style.height="100%",this.canvas.setAttribute("aria-label","flag-cloth"),this.ownsCanvas&&(this.container.appendChild(this.canvas),this.canvasAppended=true),this.fixedTimeStep=new X(this.options.simulation.fixedTimeStep,this.options.simulation.maxFrameDelta),this.pointerController=new W(this.canvas,this.renderer,this.simulation,this.options.interaction,this.options.simulation.fixedTimeStep),this.statsValue.particleCount=this.simulation.particleCount,this.statsValue.constraintCount=this.simulation.constraintCount,this.setupResizeObserver(),this.setupVisibilityObservers(),this.syncDebugPanel(),this.resize(),this.ready=this.setTexture(t.texture),t.autoStart!==false&&this.start();}get stats(){return this.statsValue}get particleCount(){return this.simulation.particleCount}get constraintCount(){return this.simulation.constraintCount}start(){this.assertAlive(),this.started=true,this.userPaused=false,this.lastFrameTime=0,this.requestAnimationFrameIfNeeded();}pause(){this.destroyed||(this.userPaused=true,this.cancelAnimationFrame());}resume(){this.assertAlive(),this.started=true,this.userPaused=false,this.lastFrameTime=0,this.requestAnimationFrameIfNeeded();}update(t,e=true){if(this.destroyed||!this.canAnimate())return;let s=performance.now();this.fixedTimeStep.advance(t,this.simulateStep);let i=performance.now();this.statsValue.simulationMs=i-s,e&&this.render(),this.updateStats(i);}render(){if(this.destroyed)return;let t=performance.now();this.renderer.render(),this.statsValue.renderMs=performance.now()-t,this.statsValue.drawCalls=this.renderer.drawCalls;}resize(){if(this.destroyed)return;let t=Math.max(0,Math.floor(this.container.clientWidth)),e=Math.max(0,Math.floor(this.container.clientHeight));if(t===0||e===0||t===this.lastWidth&&e===this.lastHeight)return;this.lastWidth=t,this.lastHeight=e;let s=typeof window>"u"?1:window.devicePixelRatio;this.renderer.resize(t,e,Math.min(s,this.options.renderer.maxPixelRatio));}async setTexture(t){this.assertAlive();let e=++this.textureRequest,s=await vt(t);this.destroyed||e!==this.textureRequest||(this.currentTexture=s,this.renderer.setTexture(s));}setWind(t){this.assertAlive(),this.options.wind={...this.options.wind,...t},this.simulation.setWind(t);}async setOptions(t){this.assertAlive();let e=t.width!==void 0&&t.width!==this.options.width||t.height!==void 0&&t.height!==this.options.height||t.segments?.x!==void 0&&t.segments.x!==this.options.segments.x||t.segments?.y!==void 0&&t.segments.y!==this.options.segments.y;t.width!==void 0&&(this.options.width=t.width),t.height!==void 0&&(this.options.height=t.height),t.segments&&(this.options.segments={...this.options.segments,...t.segments}),t.attachment!==void 0&&(this.options.attachment=t.attachment),t.wind&&(this.options.wind={...this.options.wind,...t.wind}),t.simulation&&(this.options.simulation={...this.options.simulation,...t.simulation},this.fixedTimeStep.configure(this.options.simulation.fixedTimeStep,this.options.simulation.maxFrameDelta)),t.interaction&&(this.options.interaction={...this.options.interaction,...t.interaction}),t.renderer&&(this.options.renderer=bt(this.options.renderer,t.renderer)),t.material&&(this.options.material={...this.options.material,...t.material}),t.lighting&&(this.options.lighting={...this.options.lighting,...t.lighting}),t.camera&&(this.options.camera={...this.options.camera,...t.camera}),t.visibility&&(this.options.visibility={...this.options.visibility,...t.visibility}),t.debug!==void 0&&(this.options.debug=xt(t.debug,this.options.debug)),e?this.rebuildCloth():(t.attachment!==void 0&&this.simulation.setAttachment(this.options.attachment),t.wind&&this.simulation.setWind(t.wind),t.simulation&&this.simulation.setSimulationOptions(t.simulation)),this.renderer.setOptions(this.options.renderer,this.options.material,this.options.lighting,this.options.camera),(e||t.renderer||t.camera)&&(this.lastWidth=0,this.lastHeight=0,this.resize()),this.pointerController.setOptions(this.options.interaction,this.options.simulation.fixedTimeStep),t.visibility&&this.setupVisibilityObservers(),t.debug!==void 0&&this.syncDebugPanel(),t.texture!==void 0&&await this.setTexture(t.texture);}reset(){this.assertAlive(),this.fixedTimeStep.reset(),this.simulation.reset(),this.render();}destroy(){this.destroyed||(this.destroyed=true,this.started=false,this.textureRequest+=1,this.cancelAnimationFrame(),this.resizeObserver?.disconnect(),this.resizeObserver=null,this.intersectionObserver?.disconnect(),this.intersectionObserver=null,document.removeEventListener("visibilitychange",this.onVisibilityChange),this.pointerController.destroy(),this.debugPanel?.destroy(),this.debugPanel=null,this.currentTexture=null,this.renderer.destroy(),this.ownsCanvas&&this.canvasAppended&&this.canvas.remove());}createSimulation(){return new U({width:this.options.width,height:this.options.height,segmentsX:this.options.segments.x,segmentsY:this.options.segments.y,attachment:this.options.attachment,simulation:this.options.simulation,wind:this.options.wind})}rebuildCloth(){this.simulation=this.createSimulation(),this.renderer.setSimulation(this.simulation),this.renderer.setTexture(this.currentTexture),this.pointerController.setTargets(this.renderer,this.simulation),this.statsValue.particleCount=this.simulation.particleCount,this.statsValue.constraintCount=this.simulation.constraintCount,this.fixedTimeStep.reset();}setupResizeObserver(){this.resizeObserver?.disconnect(),!(typeof ResizeObserver>"u")&&(this.resizeObserver=new ResizeObserver(this.onResizeObserved),this.resizeObserver.observe(this.container));}setupVisibilityObservers(){this.intersectionObserver?.disconnect(),this.intersectionObserver=null,document.removeEventListener("visibilitychange",this.onVisibilityChange),this.offscreen=false,this.documentHidden=false,this.options.visibility.pauseWhenOffscreen&&typeof IntersectionObserver<"u"&&(this.intersectionObserver=new IntersectionObserver(this.onIntersection),this.intersectionObserver.observe(this.container)),this.options.visibility.pauseWhenDocumentHidden&&(this.documentHidden=document.visibilityState==="hidden",document.addEventListener("visibilitychange",this.onVisibilityChange)),this.syncAnimationState();}syncDebugPanel(){this.options.debug.enabled&&!this.debugPanel?this.debugPanel=new Y(this.container):!this.options.debug.enabled&&this.debugPanel&&(this.debugPanel.destroy(),this.debugPanel=null);}updateStats(t){this.statsWindowStarted===0&&(this.statsWindowStarted=t),this.statsFrameCount+=1;let e=t-this.statsWindowStarted;e>=500&&(this.statsValue.fps=this.statsFrameCount*1e3/e,this.statsFrameCount=0,this.statsWindowStarted=t),this.debugPanel&&t-this.lastDebugUpdate>=Math.max(100,this.options.debug.updateInterval)&&(this.debugPanel.update(this.statsValue),this.lastDebugUpdate=t);}syncAnimationState(){this.canAnimate()?(this.lastFrameTime=0,this.requestAnimationFrameIfNeeded()):this.cancelAnimationFrame();}canAnimate(){return this.started&&!this.userPaused&&!this.documentHidden&&!this.offscreen}requestAnimationFrameIfNeeded(){this.externalAnimationLoop||this.animationFrame!==0||!this.canAnimate()||typeof requestAnimationFrame>"u"||(this.animationFrame=requestAnimationFrame(this.onAnimationFrame));}cancelAnimationFrame(){this.animationFrame===0||typeof cancelAnimationFrame>"u"||(cancelAnimationFrame(this.animationFrame),this.animationFrame=0);}assertAlive(){if(this.destroyed)throw new Error("This FlagCloth instance has been destroyed.")}};function he(a){return new Q(a)}function Xt(a){return {width:a.width??3,height:a.height??2,segments:{...ot,...a.segments},attachment:a.attachment??"left",wind:{...at,...a.wind},simulation:{...ht,...a.simulation},interaction:{...lt,...a.interaction},renderer:bt(ct,a.renderer??{}),material:{...ut,...a.material},lighting:{...mt,...a.lighting},camera:{...ft,...a.camera},visibility:{...pt,...a.visibility},debug:xt(a.debug??false,gt)}}function bt(a,t){let e={...a,...t};if(t.preset!==void 0){let s=dt[t.preset];t.maxPixelRatio===void 0&&(e.maxPixelRatio=s.maxPixelRatio),t.shadingUpdateInterval===void 0&&(e.shadingUpdateInterval=s.shadingUpdateInterval),t.shadows===void 0&&(e.shadows=s.shadows);}return e.maxPixelRatio=Math.max(.5,e.maxPixelRatio),e}function xt(a,t){return typeof a=="boolean"?{...t,enabled:a}:{...t,...a}}export{N as a,U as b,X as c,Q as d,he as e};//# sourceMappingURL=chunk-E5IYGF33.js.map
|
|
84
|
+
//# sourceMappingURL=chunk-E5IYGF33.js.map
|