Setting the file. One moment.
Deform · Frost Sequence Camera Orbit · heygen-com/hyperframes · Skills Docs
ContentsBack to the top of the page Three Mesh BVH LICENSE
7 KB source/src/shape/ deform.ts
TypeScript · 190 lines · 7 KB
7 import { logoRefinementSettings } from "./logoRefine" ;
8
9 export interface Deformation {
10 strength : number ;
11 scale : number ;
12 seed : number ;
13 }
14 export const DEFORMATION_DEFAULTS : Readonly < Deformation > = Object. freeze ({
15 strength: approvedPreset.deformStrength,
16 scale: approvedPreset.deformScale,
17 seed: approvedPreset.deformSeed,
18 });
19 export function validateDeformationScale ( value : number ) {
20 if ( ! Number. isFinite (value) || value < 0.001 || value > 0.5 )
21 throw new RangeError (
22 `Noise feature size ${ value } is outside 0.001–0.5. Edit this value to load the preset; other settings have not been changed.` ,
23 );
24 return value;
25 }
26 export function resolveDeformation ( input ?: Partial < Deformation >) : Deformation {
27 const number = ( key : keyof Deformation , min : number , max : number ) => {
28 const v = input?.[key];
29 return typeof v === "number" && Number. isFinite (v)
30 ? Math. max (min, Math. min (max, v))
31 : DEFORMATION_DEFAULTS [key];
32 };
33 return {
34 strength: number ( "strength" , 0 , 0.08 ),
35 scale: validateDeformationScale (input?.scale ?? DEFORMATION_DEFAULTS .scale),
36 seed: Math. round ( number ( "seed" , 0 , 65535 )),
37 };
38 }
39
40 /** Three octave Perlin noise, like the original shader FBM, baked in object space.
41 * Sequential noise shears keep a one-to-one continuous map (each shear has determinant 1),
42 * so opposite sides of thin strokes/counters move together instead of inflating into each other.
43 */
44 export function makeDeformation ( input : Deformation ) {
45 const { strength , scale , seed } = resolveDeformation (input);
46 const noise = new ImprovedNoise (),
47 rand = rng (seed);
48 const offsets = Array. from ({ length: 3 }, () => [ rand () * 256 , rand () * 256 , rand () * 256 ]);
49 function fbm ( a : number , b : number , channel : number ) {
50 const o = offsets[channel];
51 let x = a / scale + o[ 0 ],
52 y = b / scale + o[ 1 ],
53 z = o[ 2 ],
54 sum = 0 ,
55 amp = 1 ;
56 for ( let i = 0 ; i < 3 ; i ++ ) {
57 sum += amp * noise. noise (x, y, z);
58 x = x * 2 + 17.3 ;
59 y = y * 2 + 9.1 ;
60 z = z * 2 + 31.7 ;
61 amp *= 0.5 ;
62 }
63 return sum / 1.75 ;
64 }
65 return ( x : number , y : number , z : number ) : [ number , number , number ] => {
66 if (strength === 0 ) return [x, y, z];
67 x += strength * fbm (y, z, 0 );
68 y += strength * fbm (z, x, 1 );
69 z += strength * fbm (x, y, 2 );
70 return [x, y, z];
71 };
72 }
73
74 /** Conforming shared-edge subdivision. Stop before exceeding a fixed extra-triangle budget.
75 * Every marked edge is split in ALL adjacent triangles, including cap/side seams.
76 */
77 function refine ( positions : number [], initial : number [], maxEdge : number ) {
78 let triangles = initial;
79 const limit = initial. length / 3 + 6000 ,
80 maxLength2 = maxEdge * maxEdge;
81 const key = ( a : number , b : number ) => (a < b ? `${ a }:${ b }` : `${ b }:${ a }` );
82 for ( let pass = 0 ; pass < 8 ; pass ++ ) {
83 const marked = new Map < string , number >();
84 let extra = 0 ;
85 for ( let i = 0 ; i < triangles. length ; i += 3 ) {
86 const a = triangles[i],
87 b = triangles[i + 1 ],
88 c = triangles[i + 2 ];
89 for ( const [ u , v ] of [
90 [a, b],
91 [b, c],
92 [c, a],
93 ]) {
94 const dx = positions[u * 3 ] - positions[v * 3 ],
95 dy = positions[u * 3 + 1 ] - positions[v * 3 + 1 ],
96 dz = positions[u * 3 + 2 ] - positions[v * 3 + 2 ];
97 if (dx * dx + dy * dy + dz * dz > maxLength2) {
98 marked. set ( key (u, v), - 1 );
99 extra ++ ;
100 }
101 }
102 }
103 if ( ! marked.size || triangles. length / 3 + extra > limit) break ;
104 for ( const [ edge ] of marked) {
105 const [ a , b ] = edge. split ( ":" ). map (Number),
106 m = positions. length / 3 ;
107 positions. push (
108 (positions[a * 3 ] + positions[b * 3 ]) / 2 ,
109 (positions[a * 3 + 1 ] + positions[b * 3 + 1 ]) / 2 ,
110 (positions[a * 3 + 2 ] + positions[b * 3 + 2 ]) / 2 ,
111 );
112 marked. set (edge, m);
113 }
114 const out : number [] = [];
115 for ( let i = 0 ; i < triangles. length ; i += 3 ) {
116 const a = triangles[i],
117 b = triangles[i + 1 ],
118 c = triangles[i + 2 ];
119 const ab = marked. get ( key (a, b)),
120 bc = marked. get ( key (b, c)),
121 ca = marked. get ( key (c, a));
122 if (ab !== undefined && bc !== undefined && ca !== undefined )
123 out. push (a, ab, ca, ab, b, bc, ca, bc, c, ab, bc, ca);
124 else if (ab !== undefined && bc !== undefined ) out. push (b, bc, ab, a, ab, c, ab, bc, c);
125 else if (bc !== undefined && ca !== undefined ) out. push (c, ca, bc, b, bc, a, bc, ca, a);
126 else if (ca !== undefined && ab !== undefined ) out. push (a, ab, ca, c, ca, b, ca, ab, b);
127 else if (ab !== undefined ) out. push (a, ab, c, ab, b, c);
128 else if (bc !== undefined ) out. push (b, bc, a, bc, c, a);
129 else if (ca !== undefined ) out. push (c, ca, b, ca, a, b);
130 else out. push (a, b, c);
131 }
132 triangles = out;
133 }
134 return triangles;
135 }
136
137 /** Zero is an exact identity, including original buffers/normals. Caller owns the returned geometry. */
138 export function deformGeometry (
139 source : THREE . BufferGeometry ,
140 input : Deformation ,
141 creaseAngle = 40 ,
142 textMeshDetail ?: number ,
143 logoMeshDetail ?: number ,
144 ) : THREE . BufferGeometry {
145 const options = resolveDeformation (input);
146 if (options.strength === 0 ) return source;
147 const copy = source. clone ();
148 // The material uses object-space fields, not UVs. Weld geometric seams before refinement.
149 for ( const name of Object. keys (copy.attributes))
150 if (name !== "position" ) copy. deleteAttribute (name);
151 copy. clearGroups ();
152 const welded = mergeVertices (copy, 1e-7 );
153 copy. dispose ();
154 const positions = Array. from (welded. getAttribute ( "position" ).array);
155 const original = Array. from (welded.index ! .array);
156 welded. dispose ();
157 // Independent text/logo budgets share conforming surface refinement. Legacy callers
158 // without a mesh-detail argument retain the original refinement path.
159 const settings =
160 textMeshDetail !== undefined
161 ? textRefinementSettings (options.scale, textMeshDetail)
162 : logoMeshDetail !== undefined
163 ? logoRefinementSettings (source, logoMeshDetail)
164 : null ;
165 const triangles = settings
166 ? refineText (
167 positions,
168 repairTextSeams (positions, original),
169 settings.maxEdge,
170 settings.extraTriangles,
171 )
172 : refine (positions, original, options.scale * 0.3 );
173 const warp = makeDeformation (options);
174 for ( let i = 0 ; i < positions. length ; i += 3 ) {
175 const q = warp (positions[i], positions[i + 1 ], positions[i + 2 ]);
176 positions[i] = q[ 0 ];
177 positions[i + 1 ] = q[ 1 ];
178 positions[i + 2 ] = q[ 2 ];
179 }
180 const geometry = new THREE . BufferGeometry ();
181 geometry. setAttribute ( "position" , new THREE . Float32BufferAttribute (positions, 3 ));
182 geometry. setIndex (triangles);
183 const result = toCreasedNormals (geometry, THREE .MathUtils. degToRad (creaseAngle));
184 if (result !== geometry) geometry. dispose ();
185 // Keep the centered-geometry contract used by bounds and shard-home sampling.
186 result. center ();
187 result. computeBoundingBox ();
188 result. computeBoundingSphere ();
189 return result;
190 }