Setting the file. One moment.
Logo Rig · Frost Sequence Camera Orbit · heygen-com/hyperframes · Skills Docs
ContentsBack to the top of the page Three Mesh BVH LICENSE
source/src/rig/LogoRig.ts
source/src/rig/ LogoRig.ts
TypeScript · 275 lines · 9 KB
"../core/state"
;
8 const {
9 Fn ,
10 uniform ,
11 instancedArray ,
12 instanceIndex ,
13 uint ,
14 ivec3 ,
15 vec3 ,
16 vec4 ,
17 float ,
18 If ,
19 texture3D ,
20 textureStore ,
21 clamp ,
22 abs ,
23 max ,
24 min ,
25 length ,
26 mix ,
27 smoothstep ,
28 sin ,
29 } = tsl;
30 export interface RigSample {
31 x : number ;
32 y : number ;
33 z : number ;
34 rx : number ;
35 ry : number ;
36 rz : number ;
37 breakup : number ;
38 assembly : number ;
39 target ?: number ;
40 fade ?: number ;
41 }
42 export interface LogoRigOptions {
43 duration : number ;
44 mode : "physical" | "directed" | "hybrid" ;
45 sequence ?: boolean ;
46 sample : ( t : number ) => RigSample ;
47 retarget ?: ( target : number ) => void ;
48 }
49 export class LogoRig {
50 private previousBreak = 0 ;
51 private previousAssembly = 0 ;
52 private assembling = false ;
53 private capturePending = false ;
54 private readonly from = uniform ( 0 );
55 private readonly to = uniform ( 0 );
56 private readonly progress = uniform ( 0 );
57 private readonly mode = uniform ( 0 );
58 private readonly start ;
59 private readonly captureProgress ;
60 private readonly returnRadius ;
61 private newCut = false ;
62 private readonly fracture ;
63 private readonly capture ;
64 private readonly guide ;
65 private sample !: RigSample ;
66 private target = 0 ;
67 private breakLatch = false ;
68 constructor (
69 private world : World ,
70 private options : LogoRigOptions ,
71 ) {
72 const e = world.erosion,
73 pw = world.powder,
74 B = pw.buffers,
75 u = pw.u,
76 R = e.res,
77 i = instanceIndex;
78 this .start = instancedArray (pw.count, "vec4" );
79 this .captureProgress = instancedArray (pw.count, "float" );
80 this .returnRadius = instancedArray (pw.count, "vec2" );
81 const previous = texture3D (e.tex),
82 cells = texture3D (e.cellTex);
83 this .fracture = Fn (() => {
84 const c = ivec3 (i. mod ( uint ( R )), i. div ( uint ( R )). mod ( uint ( R )), i. div ( uint ( R * R )));
85 const cell = cells. load (c). level ( 0 ). toVar (),
86 old = previous. load (c). level ( 0 ). toVar ();
87 // A diagonal front through the existing fracture cells: same shard geometry, no mesh displacement.
88 const rank = clamp (
89 abs (cell.y. add (cell.x. mul ( 0.3 )))
90 . div (e.bound * 1.3 )
91 . add (cell.w. mul ( 0.07 )),
92 0.00001 ,
93 0.99999 ,
94 );
95 const cut = rank. greaterThan ( this .from). and (rank. lessThanEqual ( this .to));
96 If (cut, () => {
97 old. assign ( vec4 ( 1 , 0 , 0 , 1 ));
98 }). Else (() => {
99 old.a. assign (old.a. mul ( 0.5 ));
100 });
101 textureStore (e.scratch, c, old). toWriteOnly ();
102 })(). compute ( R ** 3 , [ 64 ]);
103 this .capture = Fn (() => {
104 this .start. element (i). assign ( B .pos. element (i));
105 this .captureProgress. element (i). assign ( this .progress);
106 this .returnRadius
107 . element (i)
108 . assign ( length ( B .pos. element (i).xyz. sub (u.model. mul ( vec4 ( B .rest. element (i).xyz, 1 )).xyz)));
109 })(). compute (pw.count, [ 64 ]);
110 this .guide = Fn (() => {
111 const P = B .pos. element (i),
112 V = B .vel. element (i),
113 M = B .meta. element (i),
114 home = u.model. mul ( vec4 ( B .rest. element (i).xyz, 1 )).xyz;
115 const state = M .x;
116 // A landed shard must stay landed; never revive its captured size or old path.
117 If (state. greaterThan ( 4.5 ), () => {
118 P .xyz. assign (home);
119 P .w. assign ( 0 );
120 V .xyz. assign ( vec3 ( 0 ));
121 });
122 If (state. greaterThan ( 0.5 ). and (state. lessThan ( 2.5 )), () => {
123 // Shards released during an overlapping breakup begin their own continuous approach here.
124 If (state. lessThan ( 1.5 ), () => {
125 this .start. element (i). assign ( P );
126 this .captureProgress. element (i). assign ( this .progress);
127 B .heal. element (i). assign ( vec4 ( P .xyz, u.time));
128 this .returnRadius. element (i). assign ( length ( P .xyz. sub (home)));
129 });
130 const phase = max (
131 this .captureProgress. element (i),
132 assemblyFrontPhase ( B .rest. element (i).xyz, u). mul ( 0.22 ),
133 );
134 const p = clamp (
135 this .progress. sub (phase). div ( max ( 0.000001 , float ( 1 ). sub (phase))),
136 0 ,
137 1 ,
138 ). toVar ();
139 If ( this .progress. greaterThanEqual ( 0.99999 ), () => {
140 p. assign ( 1 );
141 });
142 const ease = p. mul (p). mul ( float ( 3 ). sub (p. mul ( 2 )));
143 const start = this .start. element (i).xyz;
144 const bend = sin (p. mul (Math. PI )). mul ( 0.35 );
145 const path = mix (start, home, ease). add (
146 vec3 (bend. mul ( sin ( M .y. mul ( 12 ))), bend. mul ( 0.5 ), bend),
147 );
148 If ( this .mode. greaterThan ( 0.5 ), () => {
149 P .xyz. assign (path);
150 }). Else (() => {
151 // Keep physical tangential motion, bounded by a shrinking distance to the
152 // current home. Guidance can pull inward, but cannot undo spring progress.
153 const radius = this .returnRadius. element (i);
154 const offset = P .xyz. sub (home). toVar ();
155 const distance = length (offset);
156 const allowed = min (radius.y, radius.x. mul ( float ( 1 ). sub (ease)));
157 const nextDistance = min (distance, allowed);
158 P .xyz. assign (home. add (offset. mul (nextDistance. div ( max (distance, 0.000001 )))));
159 radius.y. assign (nextDistance);
160 });
161 // AssemblyField may restore a region only when its visible shard actually reaches it.
162 M .x. assign ( 2 );
163 P .w. assign ( max ( P .w, this .start. element (i).w. mul ( float ( 1 ). sub ( smoothstep ( 0.9 , 1 , p)))));
164 If (p. greaterThanEqual ( 0.99999 ), () => {
165 P .xyz. assign (home);
166 P .w. assign ( 0 );
167 V .xyz. assign ( vec3 ( 0 ));
168 M .x. assign ( 5 );
169 });
170 });
171 })(). compute (pw.count, [ 64 ]);
172 pw. beforeIntegrate = () => {
173 u.strokeDir.value. set ( 1 , - 0.3 , 0 ). normalize ();
174 u.strokeSpeed.value = 3 ;
175 u.rigStrength.value =
176 this .options.mode === "physical" ? this .progress.value : this .progress.value > 0 ? 1 : 0 ;
177 u.returnAfter.value = 0 ;
178 u.heal.value = this .progress.value > 0 ? 1 : 0 ;
179 u.repel.value = this .progress.value > 0 ? 0 : 1 ;
180 };
181 pw. beforeAssembly = () => {
182 if ( this .capturePending) {
183 world.renderer. compute ( this .capture);
184 this .capturePending = false ;
185 }
186 if ( this .assembling && this .options.mode !== "physical" ) world.renderer. compute ( this .guide);
187 if ( this .assembling && this .newCut) pw. prepareAssembly (world.renderer);
188 };
189 pw.assemblyEnabled = false ;
190 u.rigEnabled.value = 1 ;
191 world. onBeforeSimulation = ( t , dt ) => this . update (t, dt);
192 }
193 pose ( t : number ) {
194 const s = this .options. sample (t);
195 this .sample = s;
196 this .world.motionGroup.position. set (s.x, s.y + 0.05 , s.z);
197 this .world.motionGroup.rotation. set (
198 (s.rx * Math. PI ) / 180 ,
199 (s.ry * Math. PI ) / 180 ,
200 (s.rz * Math. PI ) / 180 ,
201 "YXZ" ,
202 );
203 this .world.objectGroup.quaternion. identity ();
204 const target = s.target ?? 0 ;
205 if (target !== this .target) {
206 this .target = target;
207 this .options. retarget ?.(target);
208 this .previousBreak = 0 ;
209 this .previousAssembly = 0 ;
210 this .breakLatch = false ;
211 this .assembling = false ;
212 this .capturePending = false ;
213 this .progress.value = 0 ;
214 }
215 }
216 reset () {
217 this .target = 0 ;
218 this .breakLatch = false ;
219 this .previousBreak = 0 ;
220 this .previousAssembly = 0 ;
221 this .assembling = false ;
222 this .capturePending = false ;
223 this .world.powder.assemblyEnabled = false ;
224 this .progress.value = 0 ;
225 this .world.powder. restoreThresholds ( this .world.renderer);
226 }
227 private update ( t : number , dt : number ) {
228 if (dt <= 0 ) return ;
229 const s = this .sample || this .options. sample (t),
230 w = this .world,
231 pw = w.powder;
232 const b = Math. max ( 0 , Math. min ( 1 , s.breakup / 100 )),
233 a = Math. max ( 0 , Math. min ( 1 , s.assembly / 100 ));
234 D .performance.idleSkip = false ;
235 w.erosion.u.reconstruct.value = 1 ;
236 this .newCut = b > this .previousBreak;
237 if ( this .options.sequence && this .newCut) {
238 if ( ! this .breakLatch) pw. restoreThresholds (w.renderer);
239 this .breakLatch = true ;
240 }
241 if ( this .options.sequence && b === 0 && a === 0 ) this .breakLatch = false ;
242 this .from.value = this .previousBreak;
243 this .to.value = b;
244 w.renderer. compute ( this .fracture);
245 w.erosion. commitAssembly (w.renderer);
246 if (a > 0 && this .previousAssembly <= 0 && ! this .breakLatch) {
247 if ( ! this .options.sequence) {
248 pw. retarget (w.renderer, pw.restInit);
249 pw. restoreThresholds (w.renderer);
250 }
251 pw.assemblyEnabled = true ;
252 this .assembling = true ;
253 this .capturePending = true ;
254 pw.u.assemblyEnd.value = 1e6 ;
255 sim.lastStrokeT = t;
256 }
257 if (a === 0 || this .breakLatch) {
258 this .assembling = false ;
259 pw.assemblyEnabled = false ;
260 }
261 const returning = a > 0 && ! this .breakLatch;
262 sim.healing = returning;
263 this .progress.value = returning ? a : 0 ;
264 this .mode.value = this .options.mode === "directed" ? 1 : 0 ;
265 // Repulsion fights return; use the original eject physics only while no return is requested.
266 pw.u.repel.value = returning ? 0 : 1 ;
267 this .previousBreak = b;
268 this .previousAssembly = a;
269 }
270 dispose () {
271 this .start.value. dispose ?.();
272 this .captureProgress.value. dispose ?.();
273 this .returnRadius.value. dispose ?.();
274 }
275 }