components.tsx 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967
  1. // @ts-nocheck
  2. import React, { type ReactNode, Suspense, useRef, useMemo } from "react";
  3. import { useFrame } from "@react-three/fiber";
  4. import {
  5. Environment as DreiEnvironment,
  6. OrbitControls as DreiOrbitControls,
  7. PerspectiveCamera as DreiPerspectiveCamera,
  8. Text as DreiText,
  9. Text3D as DreiText3D,
  10. Center as DreiCenter,
  11. Gltf,
  12. Splat as DreiSplat,
  13. Sparkles as DreiSparkles,
  14. Stars as DreiStars,
  15. Sky as DreiSky,
  16. Cloud as DreiCloud,
  17. Clouds as DreiClouds,
  18. Float as DreiFloat,
  19. ContactShadows as DreiContactShadows,
  20. MeshTransmissionMaterial,
  21. MeshDistortMaterial,
  22. MeshReflectorMaterial,
  23. MeshPortalMaterial as DreiMeshPortalMaterial,
  24. Html as DreiHtml,
  25. RoundedBox as DreiRoundedBox,
  26. Backdrop as DreiBackdrop,
  27. CameraShake as DreiCameraShake,
  28. } from "@react-three/drei";
  29. // @ts-ignore
  30. import * as postprocessing from "@react-three/postprocessing";
  31. import * as THREE from "three";
  32. import type { BaseComponentProps } from "@json-render/react";
  33. import type { ThreeProps } from "./catalog";
  34. // =============================================================================
  35. // Helpers
  36. // =============================================================================
  37. type Vec3 = [number, number, number];
  38. const DEFAULT_POS: Vec3 = [0, 0, 0];
  39. const DEFAULT_ROT: Vec3 = [0, 0, 0];
  40. const DEFAULT_SCALE: Vec3 = [1, 1, 1];
  41. function pos(v: Vec3 | null | undefined): Vec3 {
  42. return v ?? DEFAULT_POS;
  43. }
  44. function rot(v: Vec3 | null | undefined): Vec3 {
  45. return v ?? DEFAULT_ROT;
  46. }
  47. function scl(v: Vec3 | null | undefined): Vec3 {
  48. return v ?? DEFAULT_SCALE;
  49. }
  50. function MaterialComponent({
  51. material,
  52. }: {
  53. material: ThreeProps<"Box">["material"] | null | undefined;
  54. }) {
  55. if (!material) return <meshStandardMaterial />;
  56. return (
  57. <meshStandardMaterial
  58. color={material.color ?? "#ffffff"}
  59. metalness={material.metalness ?? 0}
  60. roughness={material.roughness ?? 1}
  61. emissive={material.emissive ?? "#000000"}
  62. emissiveIntensity={material.emissiveIntensity ?? 1}
  63. opacity={material.opacity ?? 1}
  64. transparent={material.transparent ?? false}
  65. wireframe={material.wireframe ?? false}
  66. />
  67. );
  68. }
  69. // =============================================================================
  70. // Component Implementations
  71. // =============================================================================
  72. /**
  73. * React Three Fiber component implementations for json-render.
  74. *
  75. * Pass to `defineRegistry()` from `@json-render/react` to create a
  76. * component registry for rendering JSON specs as 3D scenes.
  77. *
  78. * @example
  79. * ```ts
  80. * import { defineRegistry } from "@json-render/react";
  81. * import { threeComponents } from "@json-render/react-three-fiber";
  82. *
  83. * const { registry } = defineRegistry(catalog, {
  84. * components: { ...threeComponents },
  85. * });
  86. * ```
  87. */
  88. export const threeComponents = {
  89. // ── Primitives ─────────────────────────────────────────────────────────
  90. Box: ({ props }: BaseComponentProps<ThreeProps<"Box">>) => (
  91. <mesh
  92. position={pos(props.position)}
  93. rotation={rot(props.rotation)}
  94. scale={scl(props.scale)}
  95. castShadow={props.castShadow ?? false}
  96. receiveShadow={props.receiveShadow ?? false}
  97. >
  98. <boxGeometry
  99. args={[props.width ?? 1, props.height ?? 1, props.depth ?? 1]}
  100. />
  101. <MaterialComponent material={props.material} />
  102. </mesh>
  103. ),
  104. Sphere: ({ props }: BaseComponentProps<ThreeProps<"Sphere">>) => (
  105. <mesh
  106. position={pos(props.position)}
  107. rotation={rot(props.rotation)}
  108. scale={scl(props.scale)}
  109. castShadow={props.castShadow ?? false}
  110. receiveShadow={props.receiveShadow ?? false}
  111. >
  112. <sphereGeometry
  113. args={[
  114. props.radius ?? 1,
  115. props.widthSegments ?? 32,
  116. props.heightSegments ?? 16,
  117. ]}
  118. />
  119. <MaterialComponent material={props.material} />
  120. </mesh>
  121. ),
  122. Cylinder: ({ props }: BaseComponentProps<ThreeProps<"Cylinder">>) => (
  123. <mesh
  124. position={pos(props.position)}
  125. rotation={rot(props.rotation)}
  126. scale={scl(props.scale)}
  127. castShadow={props.castShadow ?? false}
  128. receiveShadow={props.receiveShadow ?? false}
  129. >
  130. <cylinderGeometry
  131. args={[
  132. props.radiusTop ?? 1,
  133. props.radiusBottom ?? 1,
  134. props.height ?? 1,
  135. props.radialSegments ?? 32,
  136. ]}
  137. />
  138. <MaterialComponent material={props.material} />
  139. </mesh>
  140. ),
  141. Cone: ({ props }: BaseComponentProps<ThreeProps<"Cone">>) => (
  142. <mesh
  143. position={pos(props.position)}
  144. rotation={rot(props.rotation)}
  145. scale={scl(props.scale)}
  146. castShadow={props.castShadow ?? false}
  147. receiveShadow={props.receiveShadow ?? false}
  148. >
  149. <coneGeometry
  150. args={[
  151. props.radius ?? 1,
  152. props.height ?? 1,
  153. props.radialSegments ?? 32,
  154. ]}
  155. />
  156. <MaterialComponent material={props.material} />
  157. </mesh>
  158. ),
  159. Torus: ({ props }: BaseComponentProps<ThreeProps<"Torus">>) => (
  160. <mesh
  161. position={pos(props.position)}
  162. rotation={rot(props.rotation)}
  163. scale={scl(props.scale)}
  164. castShadow={props.castShadow ?? false}
  165. receiveShadow={props.receiveShadow ?? false}
  166. >
  167. <torusGeometry
  168. args={[
  169. props.radius ?? 1,
  170. props.tube ?? 0.4,
  171. props.radialSegments ?? 16,
  172. props.tubularSegments ?? 48,
  173. ]}
  174. />
  175. <MaterialComponent material={props.material} />
  176. </mesh>
  177. ),
  178. Plane: ({
  179. props,
  180. children,
  181. }: BaseComponentProps<ThreeProps<"Plane">> & { children?: ReactNode }) => (
  182. <mesh
  183. position={pos(props.position)}
  184. rotation={rot(props.rotation)}
  185. scale={scl(props.scale)}
  186. castShadow={props.castShadow ?? false}
  187. receiveShadow={props.receiveShadow ?? false}
  188. >
  189. <planeGeometry args={[props.width ?? 1, props.height ?? 1]} />
  190. {children ?? <MaterialComponent material={props.material} />}
  191. </mesh>
  192. ),
  193. Capsule: ({ props }: BaseComponentProps<ThreeProps<"Capsule">>) => (
  194. <mesh
  195. position={pos(props.position)}
  196. rotation={rot(props.rotation)}
  197. scale={scl(props.scale)}
  198. castShadow={props.castShadow ?? false}
  199. receiveShadow={props.receiveShadow ?? false}
  200. >
  201. <capsuleGeometry
  202. args={[
  203. props.radius ?? 0.5,
  204. props.length ?? 1,
  205. props.capSegments ?? 4,
  206. props.radialSegments ?? 16,
  207. ]}
  208. />
  209. <MaterialComponent material={props.material} />
  210. </mesh>
  211. ),
  212. // ── Lights ─────────────────────────────────────────────────────────────
  213. AmbientLight: ({ props }: BaseComponentProps<ThreeProps<"AmbientLight">>) => (
  214. <ambientLight
  215. color={props.color ?? "#ffffff"}
  216. intensity={props.intensity ?? 1}
  217. />
  218. ),
  219. DirectionalLight: ({
  220. props,
  221. }: BaseComponentProps<ThreeProps<"DirectionalLight">>) => (
  222. <directionalLight
  223. position={pos(props.position)}
  224. rotation={rot(props.rotation)}
  225. color={props.color ?? "#ffffff"}
  226. intensity={props.intensity ?? 1}
  227. castShadow={props.castShadow ?? false}
  228. />
  229. ),
  230. PointLight: ({ props }: BaseComponentProps<ThreeProps<"PointLight">>) => (
  231. <pointLight
  232. position={pos(props.position)}
  233. color={props.color ?? "#ffffff"}
  234. intensity={props.intensity ?? 1}
  235. distance={props.distance ?? 0}
  236. decay={props.decay ?? 2}
  237. castShadow={props.castShadow ?? false}
  238. />
  239. ),
  240. SpotLight: ({ props }: BaseComponentProps<ThreeProps<"SpotLight">>) => (
  241. <spotLight
  242. position={pos(props.position)}
  243. color={props.color ?? "#ffffff"}
  244. intensity={props.intensity ?? 1}
  245. distance={props.distance ?? 0}
  246. decay={props.decay ?? 2}
  247. angle={props.angle ?? Math.PI / 3}
  248. penumbra={props.penumbra ?? 0}
  249. castShadow={props.castShadow ?? false}
  250. />
  251. ),
  252. // ── Container ──────────────────────────────────────────────────────────
  253. Group: ({
  254. props,
  255. children,
  256. }: BaseComponentProps<ThreeProps<"Group">> & { children?: ReactNode }) => (
  257. <group
  258. position={pos(props.position)}
  259. rotation={rot(props.rotation)}
  260. scale={scl(props.scale)}
  261. >
  262. {children}
  263. </group>
  264. ),
  265. // ── Model ──────────────────────────────────────────────────────────────
  266. Model: ({ props }: BaseComponentProps<ThreeProps<"Model">>) => (
  267. <Suspense fallback={null}>
  268. <Gltf
  269. src={props.url}
  270. position={pos(props.position)}
  271. rotation={rot(props.rotation)}
  272. scale={scl(props.scale)}
  273. castShadow={props.castShadow ?? false}
  274. receiveShadow={props.receiveShadow ?? false}
  275. />
  276. </Suspense>
  277. ),
  278. // ── Environment ────────────────────────────────────────────────────────
  279. Environment: ({ props }: BaseComponentProps<ThreeProps<"Environment">>) => (
  280. <DreiEnvironment
  281. preset={props.preset ?? "sunset"}
  282. background={props.background ?? false}
  283. blur={props.blur ?? 0}
  284. environmentIntensity={props.intensity ?? 1}
  285. />
  286. ),
  287. Fog: ({ props }: BaseComponentProps<ThreeProps<"Fog">>) => (
  288. <fog
  289. attach="fog"
  290. args={[props.color ?? "#cccccc", props.near ?? 10, props.far ?? 50]}
  291. />
  292. ),
  293. GridHelper: ({ props }: BaseComponentProps<ThreeProps<"GridHelper">>) => (
  294. <group
  295. position={pos(props.position)}
  296. rotation={rot(props.rotation)}
  297. scale={scl(props.scale)}
  298. >
  299. <gridHelper
  300. args={[
  301. props.size ?? 10,
  302. props.divisions ?? 10,
  303. props.color ?? "#888888",
  304. props.secondaryColor ?? "#444444",
  305. ]}
  306. />
  307. </group>
  308. ),
  309. // ── Text ───────────────────────────────────────────────────────────────
  310. Text3D: ({ props }: BaseComponentProps<ThreeProps<"Text3D">>) => (
  311. <DreiText
  312. position={pos(props.position)}
  313. rotation={rot(props.rotation)}
  314. scale={scl(props.scale)}
  315. fontSize={props.fontSize ?? 1}
  316. color={props.color ?? "#ffffff"}
  317. anchorX={props.anchorX ?? "center"}
  318. anchorY={props.anchorY ?? "middle"}
  319. maxWidth={props.maxWidth ?? undefined}
  320. >
  321. {props.text}
  322. </DreiText>
  323. ),
  324. ExtrudedText: ({ props }: BaseComponentProps<ThreeProps<"ExtrudedText">>) => {
  325. const font =
  326. props.font ??
  327. "https://cdn.jsdelivr.net/npm/three/examples/fonts/helvetiker_regular.typeface.json";
  328. const inner = (
  329. <DreiText3D
  330. font={font}
  331. size={props.size ?? 1}
  332. height={props.depth ?? 0.2}
  333. curveSegments={props.curveSegments ?? 12}
  334. bevelEnabled={props.bevelEnabled ?? false}
  335. bevelThickness={props.bevelThickness ?? 0.02}
  336. bevelSize={props.bevelSize ?? 0.02}
  337. bevelSegments={props.bevelSegments ?? 3}
  338. castShadow={props.castShadow ?? false}
  339. receiveShadow={props.receiveShadow ?? false}
  340. >
  341. {props.text}
  342. <MaterialComponent material={props.material} />
  343. </DreiText3D>
  344. );
  345. return (
  346. <Suspense fallback={null}>
  347. <group
  348. position={pos(props.position)}
  349. rotation={rot(props.rotation)}
  350. scale={scl(props.scale)}
  351. >
  352. {(props.centered ?? true) ? <DreiCenter>{inner}</DreiCenter> : inner}
  353. </group>
  354. </Suspense>
  355. );
  356. },
  357. // ── Effects / Atmosphere ──────────────────────────────────────────────
  358. Sparkles: ({ props }: BaseComponentProps<ThreeProps<"Sparkles">>) => (
  359. <DreiSparkles
  360. position={pos(props.position)}
  361. rotation={rot(props.rotation)}
  362. scale={scl(props.scale)}
  363. count={props.count ?? 50}
  364. speed={props.speed ?? 0.4}
  365. opacity={props.opacity ?? 1}
  366. color={props.color ?? "#ffffff"}
  367. size={props.size ?? 1}
  368. noise={props.noise ?? 1}
  369. />
  370. ),
  371. Stars: ({ props }: BaseComponentProps<ThreeProps<"Stars">>) => (
  372. <DreiStars
  373. radius={props.radius ?? 100}
  374. depth={props.depth ?? 50}
  375. count={props.count ?? 5000}
  376. factor={props.factor ?? 4}
  377. saturation={props.saturation ?? 0}
  378. fade={props.fade ?? true}
  379. speed={props.speed ?? 1}
  380. />
  381. ),
  382. Sky: ({ props }: BaseComponentProps<ThreeProps<"Sky">>) => (
  383. <DreiSky
  384. distance={props.distance ?? 450000}
  385. sunPosition={props.sunPosition ?? [0, 1, 0]}
  386. inclination={props.inclination ?? undefined}
  387. azimuth={props.azimuth ?? undefined}
  388. mieCoefficient={props.mieCoefficient ?? 0.005}
  389. mieDirectionalG={props.mieDirectionalG ?? 0.8}
  390. rayleigh={props.rayleigh ?? 0.5}
  391. turbidity={props.turbidity ?? 10}
  392. />
  393. ),
  394. Cloud: ({ props }: BaseComponentProps<ThreeProps<"Cloud">>) => (
  395. <DreiClouds limit={200}>
  396. <DreiCloud
  397. position={pos(props.position)}
  398. seed={props.seed ?? undefined}
  399. segments={props.segments ?? 20}
  400. bounds={props.bounds ?? [10, 2, 10]}
  401. volume={props.volume ?? 6}
  402. speed={props.speed ?? 0.2}
  403. fade={props.fade ?? 30}
  404. opacity={props.opacity ?? 0.6}
  405. color={props.color ?? "#ffffff"}
  406. growth={props.growth ?? 4}
  407. />
  408. </DreiClouds>
  409. ),
  410. // ── Special Materials ───────────────────────────────────────────────
  411. GlassSphere: ({ props }: BaseComponentProps<ThreeProps<"GlassSphere">>) => (
  412. <mesh
  413. position={pos(props.position)}
  414. rotation={rot(props.rotation)}
  415. scale={scl(props.scale)}
  416. castShadow={props.castShadow ?? false}
  417. receiveShadow={props.receiveShadow ?? false}
  418. >
  419. <sphereGeometry
  420. args={[
  421. props.radius ?? 1,
  422. props.widthSegments ?? 64,
  423. props.heightSegments ?? 32,
  424. ]}
  425. />
  426. <MeshTransmissionMaterial
  427. color={props.color ?? "#ffffff"}
  428. transmission={props.transmission ?? 1}
  429. thickness={props.thickness ?? 0.5}
  430. roughness={props.roughness ?? 0}
  431. chromaticAberration={props.chromaticAberration ?? 0.06}
  432. ior={props.ior ?? 1.5}
  433. distortion={props.distortion ?? 0}
  434. distortionScale={props.distortionScale ?? 0.3}
  435. temporalDistortion={props.temporalDistortion ?? 0.5}
  436. samples={props.samples ?? 10}
  437. resolution={props.resolution ?? 256}
  438. backside
  439. />
  440. </mesh>
  441. ),
  442. GlassBox: ({ props }: BaseComponentProps<ThreeProps<"GlassBox">>) => (
  443. <mesh
  444. position={pos(props.position)}
  445. rotation={rot(props.rotation)}
  446. scale={scl(props.scale)}
  447. castShadow={props.castShadow ?? false}
  448. receiveShadow={props.receiveShadow ?? false}
  449. >
  450. <boxGeometry
  451. args={[props.width ?? 1, props.height ?? 1, props.depth ?? 1]}
  452. />
  453. <MeshTransmissionMaterial
  454. color={props.color ?? "#ffffff"}
  455. transmission={props.transmission ?? 1}
  456. thickness={props.thickness ?? 0.5}
  457. roughness={props.roughness ?? 0}
  458. chromaticAberration={props.chromaticAberration ?? 0.06}
  459. ior={props.ior ?? 1.5}
  460. distortion={props.distortion ?? 0}
  461. distortionScale={props.distortionScale ?? 0.3}
  462. temporalDistortion={props.temporalDistortion ?? 0.5}
  463. samples={props.samples ?? 10}
  464. resolution={props.resolution ?? 256}
  465. backside
  466. />
  467. </mesh>
  468. ),
  469. DistortSphere: ({
  470. props,
  471. }: BaseComponentProps<ThreeProps<"DistortSphere">>) => (
  472. <mesh
  473. position={pos(props.position)}
  474. rotation={rot(props.rotation)}
  475. scale={scl(props.scale)}
  476. castShadow={props.castShadow ?? false}
  477. receiveShadow={props.receiveShadow ?? false}
  478. >
  479. <sphereGeometry
  480. args={[
  481. props.radius ?? 1,
  482. props.widthSegments ?? 64,
  483. props.heightSegments ?? 32,
  484. ]}
  485. />
  486. <MeshDistortMaterial
  487. color={props.color ?? "#ff6600"}
  488. speed={props.speed ?? 2}
  489. distort={props.distort ?? 0.5}
  490. metalness={props.metalness ?? 0}
  491. roughness={props.roughness ?? 0.2}
  492. />
  493. </mesh>
  494. ),
  495. // ── Extended Geometry ───────────────────────────────────────────────
  496. TorusKnot: ({ props }: BaseComponentProps<ThreeProps<"TorusKnot">>) => (
  497. <mesh
  498. position={pos(props.position)}
  499. rotation={rot(props.rotation)}
  500. scale={scl(props.scale)}
  501. castShadow={props.castShadow ?? false}
  502. receiveShadow={props.receiveShadow ?? false}
  503. >
  504. <torusKnotGeometry
  505. args={[
  506. props.radius ?? 1,
  507. props.tube ?? 0.4,
  508. props.tubularSegments ?? 64,
  509. props.radialSegments ?? 8,
  510. props.p ?? 2,
  511. props.q ?? 3,
  512. ]}
  513. />
  514. <MaterialComponent material={props.material} />
  515. </mesh>
  516. ),
  517. RoundedBox: ({ props }: BaseComponentProps<ThreeProps<"RoundedBox">>) => (
  518. <DreiRoundedBox
  519. position={pos(props.position)}
  520. rotation={rot(props.rotation)}
  521. scale={scl(props.scale)}
  522. castShadow={props.castShadow ?? false}
  523. receiveShadow={props.receiveShadow ?? false}
  524. args={[props.width ?? 1, props.height ?? 1, props.depth ?? 1]}
  525. radius={props.radius ?? 0.05}
  526. smoothness={props.smoothness ?? 4}
  527. >
  528. <MaterialComponent material={props.material} />
  529. </DreiRoundedBox>
  530. ),
  531. // ── Shadows / Staging ───────────────────────────────────────────────
  532. ContactShadows: ({
  533. props,
  534. }: BaseComponentProps<ThreeProps<"ContactShadows">>) => (
  535. <DreiContactShadows
  536. position={pos(props.position)}
  537. rotation={rot(props.rotation)}
  538. opacity={props.opacity ?? 0.5}
  539. width={props.width ?? 10}
  540. height={props.height ?? 10}
  541. blur={props.blur ?? 2}
  542. near={props.near ?? undefined}
  543. far={props.far ?? 10}
  544. smooth={props.smooth ?? true}
  545. resolution={props.resolution ?? 256}
  546. frames={props.frames ?? undefined}
  547. color={props.color ?? "#000000"}
  548. />
  549. ),
  550. Float: ({
  551. props,
  552. children,
  553. }: BaseComponentProps<ThreeProps<"Float">> & { children?: ReactNode }) => (
  554. <DreiFloat
  555. position={pos(props.position)}
  556. rotation={rot(props.rotation)}
  557. scale={scl(props.scale)}
  558. speed={props.speed ?? 1}
  559. rotationIntensity={props.rotationIntensity ?? 1}
  560. floatIntensity={props.floatIntensity ?? 1}
  561. enabled={props.enabled ?? true}
  562. >
  563. {children}
  564. </DreiFloat>
  565. ),
  566. ReflectorPlane: ({
  567. props,
  568. }: BaseComponentProps<ThreeProps<"ReflectorPlane">>) => (
  569. <mesh
  570. position={pos(props.position)}
  571. rotation={rot(props.rotation)}
  572. scale={scl(props.scale)}
  573. >
  574. <planeGeometry args={[props.width ?? 10, props.height ?? 10]} />
  575. <MeshReflectorMaterial
  576. color={props.color ?? "#888888"}
  577. resolution={props.resolution ?? 1024}
  578. blur={props.blur ? [props.blur, props.blur] : [300, 100]}
  579. mirror={props.mirror ?? 0.5}
  580. mixBlur={props.mixBlur ?? 10}
  581. mixStrength={props.mixStrength ?? 2}
  582. depthScale={props.depthScale ?? 0.1}
  583. metalness={props.metalness ?? 0.5}
  584. roughness={props.roughness ?? 1}
  585. />
  586. </mesh>
  587. ),
  588. Backdrop: ({
  589. props,
  590. children,
  591. }: BaseComponentProps<ThreeProps<"Backdrop">> & { children?: ReactNode }) => (
  592. <DreiBackdrop
  593. position={pos(props.position)}
  594. rotation={rot(props.rotation)}
  595. scale={scl(props.scale)}
  596. floor={props.floor ?? 0.25}
  597. segments={props.segments ?? 20}
  598. receiveShadow={props.receiveShadow ?? true}
  599. >
  600. {children}
  601. </DreiBackdrop>
  602. ),
  603. // ── Crazy / Magic ─────────────────────────────────────────────────────
  604. MeshPortalMaterial: ({
  605. props,
  606. children,
  607. }: BaseComponentProps<ThreeProps<"MeshPortalMaterial">> & {
  608. children?: ReactNode;
  609. }) => (
  610. <DreiMeshPortalMaterial
  611. blend={props.blend ?? 0}
  612. blur={props.blur ?? 0}
  613. resolution={props.resolution ?? 512}
  614. >
  615. {children}
  616. </DreiMeshPortalMaterial>
  617. ),
  618. HtmlLabel: ({ props }: BaseComponentProps<ThreeProps<"HtmlLabel">>) => (
  619. <DreiHtml
  620. position={pos(props.position)}
  621. transform={props.transform ?? true}
  622. distanceFactor={props.distanceFactor ?? 10}
  623. center={props.center ?? true}
  624. style={{
  625. color: props.color ?? "#ffffff",
  626. fontSize: props.fontSize ? `${props.fontSize}px` : "16px",
  627. fontFamily: "system-ui, sans-serif",
  628. whiteSpace: "nowrap",
  629. pointerEvents: "none",
  630. }}
  631. >
  632. {props.text}
  633. </DreiHtml>
  634. ),
  635. // ── Post-Processing ────────────────────────────────────────────────────
  636. EffectComposer: ({
  637. props,
  638. children,
  639. }: BaseComponentProps<ThreeProps<"EffectComposer">> & {
  640. children?: ReactNode;
  641. }) => {
  642. if (props.enabled === false) return null;
  643. return (
  644. <postprocessing.EffectComposer multisampling={props.multisampling ?? 8}>
  645. {children}
  646. </postprocessing.EffectComposer>
  647. );
  648. },
  649. Bloom: ({ props }: BaseComponentProps<ThreeProps<"Bloom">>) => (
  650. <postprocessing.Bloom
  651. intensity={props.intensity ?? 1.5}
  652. luminanceThreshold={props.luminanceThreshold ?? 0.1}
  653. luminanceSmoothing={props.luminanceSmoothing ?? 0.025}
  654. mipmapBlur={props.mipmapBlur ?? true}
  655. />
  656. ),
  657. Glitch: ({ props }: BaseComponentProps<ThreeProps<"Glitch">>) => {
  658. const delay = props.delay ? new THREE.Vector2(...props.delay) : undefined;
  659. const duration = props.duration
  660. ? new THREE.Vector2(...props.duration)
  661. : undefined;
  662. const strength = props.strength
  663. ? new THREE.Vector2(...props.strength)
  664. : undefined;
  665. return (
  666. <postprocessing.Glitch
  667. delay={delay}
  668. duration={duration}
  669. strength={strength}
  670. active={props.active ?? true}
  671. ratio={props.ratio ?? 0.85}
  672. />
  673. );
  674. },
  675. Vignette: ({ props }: BaseComponentProps<ThreeProps<"Vignette">>) => (
  676. <postprocessing.Vignette
  677. offset={props.offset ?? 0.5}
  678. darkness={props.darkness ?? 0.5}
  679. />
  680. ),
  681. // ── Animation Wrappers ──────────────────────────────────────────────
  682. WarpTunnel: (() => {
  683. function TunnelRing({
  684. index,
  685. ringCount,
  686. radius,
  687. length,
  688. speed,
  689. tubeRadius,
  690. color,
  691. }: {
  692. index: number;
  693. ringCount: number;
  694. radius: number;
  695. length: number;
  696. speed: number;
  697. tubeRadius: number;
  698. color: THREE.Color;
  699. }) {
  700. const meshRef = useRef<THREE.Mesh>(null);
  701. const spacing = length / ringCount;
  702. useFrame((state) => {
  703. if (!meshRef.current) return;
  704. const t = state.clock.elapsedTime;
  705. const z =
  706. ((((-index * spacing + t * speed) % length) + length) % length) -
  707. length;
  708. meshRef.current.position.z = z;
  709. const distFactor = 1 - Math.abs(z) / length;
  710. const s = 0.3 + distFactor * 0.7;
  711. meshRef.current.scale.set(s, s, s);
  712. });
  713. return (
  714. <mesh ref={meshRef} rotation={[0, 0, (index * Math.PI) / 12]}>
  715. <torusGeometry args={[radius, tubeRadius, 16, 64]} />
  716. <meshStandardMaterial
  717. color={color}
  718. emissive={color}
  719. emissiveIntensity={3}
  720. transparent
  721. opacity={0.7}
  722. side={THREE.DoubleSide}
  723. />
  724. </mesh>
  725. );
  726. }
  727. return ({ props }: BaseComponentProps<ThreeProps<"WarpTunnel">>) => {
  728. const ringCount = props.ringCount ?? 80;
  729. const radius = props.radius ?? 3;
  730. const length = props.length ?? 40;
  731. const speed = props.speed ?? 8;
  732. const tubeRadius = props.tubeRadius ?? 0.02;
  733. const color1 = props.color1 ?? "#00ffff";
  734. const color2 = props.color2 ?? "#ff00ff";
  735. const colors = useMemo(() => {
  736. const c1 = new THREE.Color(color1);
  737. const c2 = new THREE.Color(color2);
  738. return Array.from({ length: ringCount }, (_, i) =>
  739. new THREE.Color().lerpColors(c1, c2, i / ringCount),
  740. );
  741. }, [ringCount, color1, color2]);
  742. return (
  743. <group
  744. position={pos(props.position)}
  745. rotation={rot(props.rotation)}
  746. scale={scl(props.scale)}
  747. >
  748. {colors.map((color, i) => (
  749. <TunnelRing
  750. key={i}
  751. index={i}
  752. ringCount={ringCount}
  753. radius={radius}
  754. length={length}
  755. speed={speed}
  756. tubeRadius={tubeRadius}
  757. color={color}
  758. />
  759. ))}
  760. </group>
  761. );
  762. };
  763. })(),
  764. Spin: ({
  765. props,
  766. children,
  767. }: BaseComponentProps<ThreeProps<"Spin">> & { children?: ReactNode }) => {
  768. const ref = useRef<THREE.Group>(null);
  769. const speed = props.speed ?? 1;
  770. const axis = props.axis ?? "y";
  771. useFrame((_, delta) => {
  772. if (!ref.current) return;
  773. if (axis === "x") ref.current.rotation.x += delta * speed;
  774. else if (axis === "z") ref.current.rotation.z += delta * speed;
  775. else ref.current.rotation.y += delta * speed;
  776. });
  777. return (
  778. <group
  779. ref={ref}
  780. position={pos(props.position)}
  781. rotation={rot(props.rotation)}
  782. scale={scl(props.scale)}
  783. >
  784. {children}
  785. </group>
  786. );
  787. },
  788. Orbit: ({
  789. props,
  790. children,
  791. }: BaseComponentProps<ThreeProps<"Orbit">> & { children?: ReactNode }) => {
  792. const ref = useRef<THREE.Group>(null);
  793. const speed = props.speed ?? 1;
  794. const radius = props.radius ?? 3;
  795. const tilt = props.tilt ?? 0;
  796. const baseY = props.position?.[1] ?? 0;
  797. useFrame((state) => {
  798. if (!ref.current) return;
  799. const t = state.clock.elapsedTime * speed;
  800. ref.current.position.x = Math.cos(t) * radius;
  801. ref.current.position.z = Math.sin(t) * radius;
  802. ref.current.position.y = baseY + Math.sin(t * 0.7) * tilt;
  803. });
  804. return <group ref={ref}>{children}</group>;
  805. },
  806. Pulse: ({
  807. props,
  808. children,
  809. }: BaseComponentProps<ThreeProps<"Pulse">> & { children?: ReactNode }) => {
  810. const ref = useRef<THREE.Group>(null);
  811. const speed = props.speed ?? 1;
  812. const min = props.min ?? 0.8;
  813. const max = props.max ?? 1.2;
  814. useFrame((state) => {
  815. if (!ref.current) return;
  816. const t = (Math.sin(state.clock.elapsedTime * speed) + 1) / 2;
  817. const s = min + t * (max - min);
  818. ref.current.scale.setScalar(s);
  819. });
  820. return (
  821. <group
  822. ref={ref}
  823. position={pos(props.position)}
  824. rotation={rot(props.rotation)}
  825. >
  826. {children}
  827. </group>
  828. );
  829. },
  830. CameraShake: ({ props }: BaseComponentProps<ThreeProps<"CameraShake">>) => (
  831. <DreiCameraShake
  832. intensity={props.intensity ?? 0.5}
  833. maxYaw={props.maxYaw ?? 0.1}
  834. maxPitch={props.maxPitch ?? 0.1}
  835. maxRoll={props.maxRoll ?? 0.1}
  836. />
  837. ),
  838. // ── Camera / Controls ──────────────────────────────────────────────
  839. PerspectiveCamera: ({
  840. props,
  841. }: BaseComponentProps<ThreeProps<"PerspectiveCamera">>) => (
  842. <DreiPerspectiveCamera
  843. position={pos(props.position)}
  844. rotation={rot(props.rotation)}
  845. fov={props.fov ?? 50}
  846. near={props.near ?? 0.1}
  847. far={props.far ?? 1000}
  848. makeDefault={props.makeDefault ?? false}
  849. />
  850. ),
  851. OrbitControls: ({
  852. props,
  853. }: BaseComponentProps<ThreeProps<"OrbitControls">>) => (
  854. <DreiOrbitControls
  855. enableDamping={props.enableDamping ?? true}
  856. dampingFactor={props.dampingFactor ?? 0.05}
  857. enableZoom={props.enableZoom ?? true}
  858. enablePan={props.enablePan ?? true}
  859. enableRotate={props.enableRotate ?? true}
  860. minDistance={props.minDistance ?? undefined}
  861. maxDistance={props.maxDistance ?? undefined}
  862. minPolarAngle={props.minPolarAngle ?? undefined}
  863. maxPolarAngle={props.maxPolarAngle ?? undefined}
  864. autoRotate={props.autoRotate ?? false}
  865. autoRotateSpeed={props.autoRotateSpeed ?? 2}
  866. target={props.target ?? undefined}
  867. />
  868. ),
  869. // ── Gaussian Splatting ──────────────────────────────────────────────
  870. GaussianSplat: ({
  871. props,
  872. }: BaseComponentProps<ThreeProps<"GaussianSplat">>) => (
  873. <Suspense fallback={null}>
  874. <DreiSplat
  875. src={props.src}
  876. position={pos(props.position)}
  877. rotation={rot(props.rotation)}
  878. scale={scl(props.scale)}
  879. castShadow={props.castShadow ?? false}
  880. receiveShadow={props.receiveShadow ?? false}
  881. alphaHash={props.alphaHash ?? undefined}
  882. toneMapped={props.toneMapped ?? undefined}
  883. visible={props.visible ?? true}
  884. />
  885. </Suspense>
  886. ),
  887. };