Setting the file. One moment.
Orbit Scene · Orbit Card · heygen-com/hyperframes · Skills Docs
ContentsBack to the top of the page assets/ orbit-scene.js
JavaScript · 257 lines · 8 KB
(v))
?
Number
(v)
:
fallback);
7 const degrees = Math. max ( - 36000 , Math. min ( 36000 , number ( V .cardOrbitDegrees, 360 )));
8 const title = String ( V .feature1Title || "Always in sync" );
9 const description = String ( V .feature1Desc || "Changes reach every screen the moment they happen." );
10 const accent = / ^ # [0-9a-f] {6}$ / i . test ( V .feature1Accent) ? V .feature1Accent : "#4cc9ff" ;
11 const stage = document. getElementById ( "oc-stage" );
12 const renderer = new THREE . WebGLRenderer ({ antialias: true , alpha: false });
13 renderer. setPixelRatio ( 1 );
14 renderer. setSize ( 1920 , 1080 );
15 renderer. setClearColor ( "#161718" );
16 renderer.outputColorSpace = THREE .SRGBColorSpace;
17 renderer.domElement. setAttribute (
18 "aria-label" ,
19 "Orbit Card: approved Blender camera and card animation" ,
20 );
21 stage. appendChild (renderer.domElement);
22 const scene = new THREE . Scene ();
23 // Saved rig: 30 mm lens, 36 mm sensor, 1920 x 1080.
24 const camera = new THREE . PerspectiveCamera (
25 ( 2 * Math. atan ( 36 / ( 1920 / 1080 ) / 60 ) * 180 ) / Math. PI ,
26 1920 / 1080 ,
27 0.1 ,
28 200 ,
29 );
30 scene. add ( new THREE . HemisphereLight ( 0xffffff , 0x414753 , 2.1 ));
31 const keyLight = new THREE . DirectionalLight ( 0xf0f5ff , 3.0 );
32 keyLight.position. set ( - 5 , 8 , 8 );
33 scene. add (keyLight);
34 const rimLight = new THREE . DirectionalLight ( 0x8fbad2 , 2.0 );
35 rimLight.position. set ( 5 , 3 , - 5 );
36 scene. add (rimLight);
37
38 const sourceRig = new THREE . Group ();
39 sourceRig.name = "Card_Rig_EDIT_ME" ;
40 scene. add (sourceRig);
41 const orbitPivot = new THREE . Group ();
42 orbitPivot.name = "Card_Orbit_EDIT_ME" ;
43 orbitPivot.position. set ( 0 , 0.94 , 0 );
44 sourceRig. add (orbitPivot);
45 const card = new THREE . Group ();
46 card.position. copy (orbitPivot.position). multiplyScalar ( - 1 );
47 orbitPivot. add (card);
48
49 function roundedRect ( w , h , r ) {
50 const s = new THREE . Shape (),
51 x = - w / 2 ,
52 y = - h / 2 ;
53 s. moveTo (x + r, y);
54 s. lineTo (x + w - r, y);
55 s. quadraticCurveTo (x + w, y, x + w, y + r);
56 s. lineTo (x + w, y + h - r);
57 s. quadraticCurveTo (x + w, y + h, x + w - r, y + h);
58 s. lineTo (x + r, y + h);
59 s. quadraticCurveTo (x, y + h, x, y + h - r);
60 s. lineTo (x, y + r);
61 s. quadraticCurveTo (x, y, x + r, y);
62 return s;
63 }
64 const shape = roundedRect ( 4.4 , 5.4 , 0.24 );
65 const hole = new THREE . Path ();
66 hole. absarc ( 0 , 0.94 , 1.08 , 0 , Math. PI * 2 , true );
67 shape.holes. push (hole);
68 const plateGeometry = new THREE . ExtrudeGeometry (shape, {
69 depth: 0.16 ,
70 bevelEnabled: true ,
71 bevelSize: 0.025 ,
72 bevelThickness: 0.02 ,
73 bevelSegments: 3 ,
74 steps: 1 ,
75 curveSegments: 64 ,
76 });
77 plateGeometry. translate ( 0 , 0 , - 0.08 );
78 const plate = new THREE . Mesh (
79 plateGeometry,
80 new THREE . MeshStandardMaterial ({ color: "#16191d" , metalness: 0.32 , roughness: 0.38 }),
81 );
82 plate.name = "Orbit_Card" ;
83 card. add (plate);
84 const outlineMaterial = new THREE . LineBasicMaterial ({
85 color: "#58616b" ,
86 transparent: true ,
87 opacity: 0.45 ,
88 });
89 const outline = new THREE . LineLoop (
90 new THREE . BufferGeometry (). setFromPoints (
91 shape. getPoints ( 96 ). map (( p ) => new THREE . Vector3 (p.x, p.y, 0.103 )),
92 ),
93 outlineMaterial,
94 );
95 card. add (outline);
96 const apertureRim = new THREE . Mesh (
97 new THREE . TorusGeometry ( 1.08 , 0.009 , 6 , 128 ),
98 new THREE . MeshBasicMaterial ({ color: accent, transparent: true , opacity: 0.3 }),
99 );
100 apertureRim.position. set ( 0 , 0.94 , 0.103 );
101 card. add (apertureRim);
102 // The aperture is open through the plate: no separate circular backplate.
103
104 const copyCanvas = document. createElement ( "canvas" );
105 copyCanvas.width = 1440 ;
106 copyCanvas.height = 500 ;
107 const copyContext = copyCanvas. getContext ( "2d" );
108 const copyTexture = new THREE . CanvasTexture (copyCanvas);
109 copyTexture.colorSpace = THREE .SRGBColorSpace;
110 copyTexture.anisotropy = Math. min ( 8 , renderer.capabilities. getMaxAnisotropy ());
111 const copy = new THREE . Mesh (
112 new THREE . PlaneGeometry ( 3.6 , 1.25 ),
113 new THREE . MeshBasicMaterial ({
114 map: copyTexture,
115 transparent: true ,
116 side: THREE .FrontSide,
117 depthWrite: false ,
118 }),
119 );
120 copy.position. set ( 0 , - 1.525 , 0.106 );
121 card. add (copy);
122 function paintCopy () {
123 const g = copyContext,
124 k = 4 ;
125 g. clearRect ( 0 , 0 , 1440 , 500 );
126 g.fillStyle = accent;
127 g. fillRect ( 0 , 10 , 30 * k, 3 * k);
128 g.fillStyle = "#f0f3f5" ;
129 g.font = "600 108px Archivo" ;
130 g. fillText (title, 0 , 165 , 1440 );
131 g.fillStyle = "#a4a8ad" ;
132 g.font = "400 66px Archivo" ;
133 const words = description. split ( / \s + / );
134 let line = "" ,
135 y = 305 ;
136 for ( const word of words) {
137 if (g. measureText (line + word).width > 1420 && line) {
138 g. fillText (line. trim (), 0 , y);
139 y += 99 ;
140 line = "" ;
141 }
142 line += word + " " ;
143 }
144 g. fillText (line. trim (), 0 , y);
145 copyTexture.needsUpdate = true ;
146 }
147 paintCopy ();
148
149 // A sibling branch of the orbit control: the sphere retains its own spin.
150 const sphereTilt = new THREE . Group ();
151 sphereTilt.name = "Sphere_Tilt_FROM_SOURCE" ;
152 sphereTilt.position. set ( 0 , 0.94 , 0 );
153 sphereTilt.rotation.x = - 0.32 ;
154 sourceRig. add (sphereTilt);
155 const sphere = new THREE . Group ();
156 sphere.name = "Dot_Sphere_Proxy" ;
157 sphereTilt. add (sphere);
158 const dotGeometry = new THREE . IcosahedronGeometry ( 0.012 , 0 );
159 const dotMaterial = new THREE . MeshStandardMaterial ({
160 color: "#ecf0f4" ,
161 roughness: 0.45 ,
162 emissive: "#c4d5df" ,
163 emissiveIntensity: 0.18 ,
164 });
165 const dots = new THREE . InstancedMesh (dotGeometry, dotMaterial, 640 );
166 const dummy = new THREE . Object3D ();
167 const golden = Math. PI * ( 3 - Math. sqrt ( 5 ));
168 for ( let i = 0 ; i < 640 ; i ++ ) {
169 const y = 1 - (i / 639 ) * 2 ,
170 radius = Math. sqrt (Math. max ( 0 , 1 - y * y)),
171 a = golden * i;
172 dummy.position. set (Math. cos (a) * radius, - y, Math. sin (a) * radius);
173 dummy. updateMatrix ();
174 dots. setMatrixAt (i, dummy.matrix);
175 }
176 sphere. add (dots);
177
178 const clamp = ( u ) => Math. max ( 0 , Math. min ( 1 , u));
179 const smooth = ( u ) => u * u * ( 3 - 2 * u);
180 // Saved Blender F-curves: linear time handles, exact cubic value handles.
181 function channelAt ( keys , frame ) {
182 if (frame <= keys[ 0 ][ 0 ][ 0 ]) return keys[ 0 ][ 0 ][ 1 ];
183 for ( let i = 1 ; i < keys. length ; i ++ ) {
184 const a = keys[i - 1 ],
185 b = keys[i];
186 if (frame <= b[ 0 ][ 0 ]) {
187 const u = (frame - a[ 0 ][ 0 ]) / (b[ 0 ][ 0 ] - a[ 0 ][ 0 ]),
188 v = 1 - u;
189 return a[ 3 ] === "LINEAR"
190 ? a[ 0 ][ 1 ] * v + b[ 0 ][ 1 ] * u
191 : v ** 3 * a[ 0 ][ 1 ] + 3 * v * v * u * a[ 2 ][ 1 ] + 3 * v * u * u * b[ 1 ][ 1 ] + u ** 3 * b[ 0 ][ 1 ];
192 }
193 }
194 return keys[keys. length - 1 ][ 0 ][ 1 ];
195 }
196 // Camera positions are the saved rig's evaluated 30 fps export, not a new orbit.
197 function cameraAt ( time ) {
198 const f = Math. min ( 300 , Math. max ( 0 , time * 30 )),
199 i = Math. min ( 299 , Math. floor (f)),
200 u = f - i;
201 const a = MOTION .cameraPositions[i],
202 b = MOTION .cameraPositions[i + 1 ];
203 camera.position. set (
204 a[ 0 ] + (b[ 0 ] - a[ 0 ]) * u,
205 a[ 2 ] + (b[ 2 ] - a[ 2 ]) * u,
206 - a[ 1 ] - (b[ 1 ] - a[ 1 ]) * u,
207 );
208 const frame = 1 + time * 30 ;
209 let aim = MOTION .aimKeys. at ( - 1 )[ 1 ];
210 for ( let j = 1 ; j < MOTION .aimKeys. length ; j ++ ) {
211 const a = MOTION .aimKeys[j - 1 ],
212 b = MOTION .aimKeys[j];
213 if (frame <= b[ 0 ]) {
214 aim = THREE .MathUtils. lerp (a[ 1 ], b[ 1 ], smooth ( clamp ((frame - a[ 0 ]) / (b[ 0 ] - a[ 0 ]))));
215 break ;
216 }
217 }
218 camera. lookAt ( 0 , aim, 0 );
219 }
220 const channels = MOTION .cardChannels;
221 let currentTime = 0 ;
222 export function renderAt ( time ) {
223 currentTime = Math. max ( 0 , Math. min ( 10 , time));
224 const frame = 1 + currentTime * 30 ;
225 const values = channels. map (( c ) => channelAt (c.keys, frame));
226 sourceRig.position. set (values[ 0 ], values[ 2 ], - values[ 1 ]);
227 sourceRig.rotation.y = values[ 3 ];
228 orbitPivot.rotation.y = THREE .MathUtils. degToRad (degrees) * smooth ( clamp ((frame - 150 ) / 30 ));
229 sphere.rotation.y = currentTime * 0.5 ;
230 sphere.scale. setScalar ( channelAt ( MOTION .sphereScale, frame));
231 cameraAt (currentTime);
232 renderer. render (scene, camera);
233 }
234
235 renderAt ( 0 );
236 document.fonts.ready. then (() => {
237 paintCopy ();
238 renderAt (currentTime);
239 });
240
241 // Read-only inspection aid for the live workbench; no extra animation clock.
242 window.__orbitCardMotion = {
243 fps: 30 ,
244 duration: 10 ,
245 orbitFrames: [ 150 , 180 ],
246 degrees,
247 get pose () {
248 const center = sphereTilt. getWorldPosition ( new THREE . Vector3 ());
249 return {
250 frame: 1 + currentTime * 30 ,
251 camera: camera.position. toArray (),
252 cardOrbitDegrees: THREE .MathUtils. radToDeg (orbitPivot.rotation.y),
253 sphereCenter: center. toArray (),
254 sphereSpin: sphere.rotation.y,
255 };
256 },
257 };