game-primitives.tsx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796
  1. "use client";
  2. import { useRef, useEffect } from "react";
  3. import * as THREE from "three";
  4. import { RigidBody } from "@react-three/rapier";
  5. import type { RapierRigidBody } from "@react-three/rapier";
  6. import { useEditorStore } from "@/lib/store";
  7. interface PhysicsProps {
  8. mass?: number | null;
  9. isStatic?: boolean | null;
  10. restitution?: number | null;
  11. friction?: number | null;
  12. colliderType?: string | null;
  13. }
  14. interface DamageProps {
  15. amount?: number | null;
  16. enabled?: boolean | null;
  17. }
  18. interface MaterialProps {
  19. color?: string | null;
  20. metalness?: number | null;
  21. roughness?: number | null;
  22. emissive?: string | null;
  23. emissiveIntensity?: number | null;
  24. opacity?: number | null;
  25. transparent?: boolean | null;
  26. wireframe?: boolean | null;
  27. }
  28. interface GamePrimitiveProps {
  29. position?: [number, number, number] | null;
  30. rotation?: [number, number, number] | null;
  31. scale?: [number, number, number] | null;
  32. castShadow?: boolean | null;
  33. receiveShadow?: boolean | null;
  34. material?: MaterialProps | null;
  35. physics?: PhysicsProps | null;
  36. damage?: DamageProps | null;
  37. objectId?: string | null;
  38. children?: React.ReactNode;
  39. }
  40. function buildMaterialProps(mat?: MaterialProps | null) {
  41. if (!mat) return { color: "#888888" };
  42. return {
  43. color: mat.color ?? "#888888",
  44. metalness: mat.metalness ?? 0,
  45. roughness: mat.roughness ?? 0.5,
  46. ...(mat.emissive ? { emissive: mat.emissive } : {}),
  47. ...(mat.emissiveIntensity
  48. ? { emissiveIntensity: mat.emissiveIntensity }
  49. : {}),
  50. ...(mat.opacity != null ? { opacity: mat.opacity, transparent: true } : {}),
  51. ...(mat.wireframe ? { wireframe: true } : {}),
  52. };
  53. }
  54. function PhysicsWrapper({
  55. physics,
  56. damage,
  57. position,
  58. rotation,
  59. scale,
  60. children,
  61. }: {
  62. physics?: PhysicsProps | null;
  63. damage?: DamageProps | null;
  64. position: [number, number, number];
  65. rotation: [number, number, number];
  66. scale: [number, number, number];
  67. children: React.ReactNode;
  68. }) {
  69. const bodyRef = useRef<RapierRigidBody>(null);
  70. const takeDamage = useEditorStore((s) => s.takeDamage);
  71. const isPlaying = useEditorStore((s) => s.isPlaying);
  72. const hasPhysics =
  73. physics && physics.colliderType && physics.colliderType !== "none";
  74. const hasDamage = damage?.enabled && (damage.amount ?? 0) > 0;
  75. if (!hasPhysics || !isPlaying) {
  76. return (
  77. <group position={position} rotation={rotation} scale={scale}>
  78. {children}
  79. </group>
  80. );
  81. }
  82. return (
  83. <RigidBody
  84. ref={bodyRef}
  85. type={physics.isStatic ? "fixed" : "dynamic"}
  86. position={position}
  87. rotation={rotation}
  88. mass={physics.mass ?? 1}
  89. restitution={physics.restitution ?? 0.2}
  90. friction={physics.friction ?? 0.5}
  91. onCollisionEnter={
  92. hasDamage
  93. ? () => {
  94. takeDamage(damage!.amount!);
  95. }
  96. : undefined
  97. }
  98. >
  99. <group scale={scale}>{children}</group>
  100. </RigidBody>
  101. );
  102. }
  103. export function GameBox({
  104. position,
  105. rotation,
  106. scale,
  107. castShadow,
  108. receiveShadow,
  109. material,
  110. physics,
  111. damage,
  112. width,
  113. height,
  114. depth,
  115. }: GamePrimitiveProps & {
  116. width?: number | null;
  117. height?: number | null;
  118. depth?: number | null;
  119. }) {
  120. const pos: [number, number, number] = position ?? [0, 0, 0];
  121. const rot: [number, number, number] = rotation ?? [0, 0, 0];
  122. const scl: [number, number, number] = scale ?? [1, 1, 1];
  123. return (
  124. <PhysicsWrapper
  125. physics={physics}
  126. damage={damage}
  127. position={pos}
  128. rotation={rot}
  129. scale={scl}
  130. >
  131. <mesh
  132. castShadow={castShadow ?? false}
  133. receiveShadow={receiveShadow ?? false}
  134. >
  135. <boxGeometry args={[width ?? 1, height ?? 1, depth ?? 1]} />
  136. <meshStandardMaterial {...buildMaterialProps(material)} />
  137. </mesh>
  138. </PhysicsWrapper>
  139. );
  140. }
  141. export function GameSphere({
  142. position,
  143. rotation,
  144. scale,
  145. castShadow,
  146. receiveShadow,
  147. material,
  148. physics,
  149. damage,
  150. radius,
  151. widthSegments,
  152. heightSegments,
  153. }: GamePrimitiveProps & {
  154. radius?: number | null;
  155. widthSegments?: number | null;
  156. heightSegments?: number | null;
  157. }) {
  158. const pos: [number, number, number] = position ?? [0, 0, 0];
  159. const rot: [number, number, number] = rotation ?? [0, 0, 0];
  160. const scl: [number, number, number] = scale ?? [1, 1, 1];
  161. return (
  162. <PhysicsWrapper
  163. physics={physics}
  164. damage={damage}
  165. position={pos}
  166. rotation={rot}
  167. scale={scl}
  168. >
  169. <mesh
  170. castShadow={castShadow ?? false}
  171. receiveShadow={receiveShadow ?? false}
  172. >
  173. <sphereGeometry
  174. args={[radius ?? 1, widthSegments ?? 32, heightSegments ?? 16]}
  175. />
  176. <meshStandardMaterial {...buildMaterialProps(material)} />
  177. </mesh>
  178. </PhysicsWrapper>
  179. );
  180. }
  181. export function GameCylinder({
  182. position,
  183. rotation,
  184. scale,
  185. castShadow,
  186. receiveShadow,
  187. material,
  188. physics,
  189. damage,
  190. radiusTop,
  191. radiusBottom,
  192. height,
  193. radialSegments,
  194. }: GamePrimitiveProps & {
  195. radiusTop?: number | null;
  196. radiusBottom?: number | null;
  197. height?: number | null;
  198. radialSegments?: number | null;
  199. }) {
  200. const pos: [number, number, number] = position ?? [0, 0, 0];
  201. const rot: [number, number, number] = rotation ?? [0, 0, 0];
  202. const scl: [number, number, number] = scale ?? [1, 1, 1];
  203. return (
  204. <PhysicsWrapper
  205. physics={physics}
  206. damage={damage}
  207. position={pos}
  208. rotation={rot}
  209. scale={scl}
  210. >
  211. <mesh
  212. castShadow={castShadow ?? false}
  213. receiveShadow={receiveShadow ?? false}
  214. >
  215. <cylinderGeometry
  216. args={[
  217. radiusTop ?? 1,
  218. radiusBottom ?? 1,
  219. height ?? 1,
  220. radialSegments ?? 32,
  221. ]}
  222. />
  223. <meshStandardMaterial {...buildMaterialProps(material)} />
  224. </mesh>
  225. </PhysicsWrapper>
  226. );
  227. }
  228. export function GameCone({
  229. position,
  230. rotation,
  231. scale,
  232. castShadow,
  233. receiveShadow,
  234. material,
  235. physics,
  236. damage,
  237. radius,
  238. height,
  239. radialSegments,
  240. }: GamePrimitiveProps & {
  241. radius?: number | null;
  242. height?: number | null;
  243. radialSegments?: number | null;
  244. }) {
  245. const pos: [number, number, number] = position ?? [0, 0, 0];
  246. const rot: [number, number, number] = rotation ?? [0, 0, 0];
  247. const scl: [number, number, number] = scale ?? [1, 1, 1];
  248. return (
  249. <PhysicsWrapper
  250. physics={physics}
  251. damage={damage}
  252. position={pos}
  253. rotation={rot}
  254. scale={scl}
  255. >
  256. <mesh
  257. castShadow={castShadow ?? false}
  258. receiveShadow={receiveShadow ?? false}
  259. >
  260. <coneGeometry args={[radius ?? 1, height ?? 1, radialSegments ?? 32]} />
  261. <meshStandardMaterial {...buildMaterialProps(material)} />
  262. </mesh>
  263. </PhysicsWrapper>
  264. );
  265. }
  266. export function GameTorus({
  267. position,
  268. rotation,
  269. scale,
  270. castShadow,
  271. receiveShadow,
  272. material,
  273. physics,
  274. damage,
  275. radius,
  276. tube,
  277. radialSegments,
  278. tubularSegments,
  279. }: GamePrimitiveProps & {
  280. radius?: number | null;
  281. tube?: number | null;
  282. radialSegments?: number | null;
  283. tubularSegments?: number | null;
  284. }) {
  285. const pos: [number, number, number] = position ?? [0, 0, 0];
  286. const rot: [number, number, number] = rotation ?? [0, 0, 0];
  287. const scl: [number, number, number] = scale ?? [1, 1, 1];
  288. return (
  289. <PhysicsWrapper
  290. physics={physics}
  291. damage={damage}
  292. position={pos}
  293. rotation={rot}
  294. scale={scl}
  295. >
  296. <mesh
  297. castShadow={castShadow ?? false}
  298. receiveShadow={receiveShadow ?? false}
  299. >
  300. <torusGeometry
  301. args={[
  302. radius ?? 1,
  303. tube ?? 0.4,
  304. radialSegments ?? 16,
  305. tubularSegments ?? 48,
  306. ]}
  307. />
  308. <meshStandardMaterial {...buildMaterialProps(material)} />
  309. </mesh>
  310. </PhysicsWrapper>
  311. );
  312. }
  313. export function GamePlane({
  314. position,
  315. rotation,
  316. scale,
  317. castShadow,
  318. receiveShadow,
  319. material,
  320. physics,
  321. damage,
  322. width,
  323. height,
  324. }: GamePrimitiveProps & { width?: number | null; height?: number | null }) {
  325. const pos: [number, number, number] = position ?? [0, 0, 0];
  326. const rot: [number, number, number] = rotation ?? [0, 0, 0];
  327. const scl: [number, number, number] = scale ?? [1, 1, 1];
  328. return (
  329. <PhysicsWrapper
  330. physics={physics}
  331. damage={damage}
  332. position={pos}
  333. rotation={rot}
  334. scale={scl}
  335. >
  336. <mesh
  337. castShadow={castShadow ?? false}
  338. receiveShadow={receiveShadow ?? true}
  339. >
  340. <planeGeometry args={[width ?? 1, height ?? 1]} />
  341. <meshStandardMaterial
  342. {...buildMaterialProps(material)}
  343. side={THREE.DoubleSide}
  344. />
  345. </mesh>
  346. </PhysicsWrapper>
  347. );
  348. }
  349. export function GameCapsule({
  350. position,
  351. rotation,
  352. scale,
  353. castShadow,
  354. receiveShadow,
  355. material,
  356. physics,
  357. damage,
  358. radius,
  359. length,
  360. }: GamePrimitiveProps & {
  361. radius?: number | null;
  362. length?: number | null;
  363. capSegments?: number | null;
  364. radialSegments?: number | null;
  365. }) {
  366. const pos: [number, number, number] = position ?? [0, 0, 0];
  367. const rot: [number, number, number] = rotation ?? [0, 0, 0];
  368. const scl: [number, number, number] = scale ?? [1, 1, 1];
  369. return (
  370. <PhysicsWrapper
  371. physics={physics}
  372. damage={damage}
  373. position={pos}
  374. rotation={rot}
  375. scale={scl}
  376. >
  377. <mesh
  378. castShadow={castShadow ?? false}
  379. receiveShadow={receiveShadow ?? false}
  380. >
  381. <capsuleGeometry args={[radius ?? 0.5, length ?? 1, 16, 32]} />
  382. <meshStandardMaterial {...buildMaterialProps(material)} />
  383. </mesh>
  384. </PhysicsWrapper>
  385. );
  386. }
  387. export function GameKnot({
  388. position,
  389. rotation,
  390. scale,
  391. castShadow,
  392. receiveShadow,
  393. material,
  394. physics,
  395. damage,
  396. radius,
  397. tube,
  398. tubularSegments,
  399. radialSegments,
  400. p,
  401. q,
  402. }: GamePrimitiveProps & {
  403. radius?: number | null;
  404. tube?: number | null;
  405. tubularSegments?: number | null;
  406. radialSegments?: number | null;
  407. p?: number | null;
  408. q?: number | null;
  409. }) {
  410. const pos: [number, number, number] = position ?? [0, 0, 0];
  411. const rot: [number, number, number] = rotation ?? [0, 0, 0];
  412. const scl: [number, number, number] = scale ?? [1, 1, 1];
  413. return (
  414. <PhysicsWrapper
  415. physics={physics}
  416. damage={damage}
  417. position={pos}
  418. rotation={rot}
  419. scale={scl}
  420. >
  421. <mesh
  422. castShadow={castShadow ?? false}
  423. receiveShadow={receiveShadow ?? false}
  424. >
  425. <torusKnotGeometry
  426. args={[
  427. radius ?? 1,
  428. tube ?? 0.3,
  429. tubularSegments ?? 64,
  430. radialSegments ?? 8,
  431. p ?? 2,
  432. q ?? 3,
  433. ]}
  434. />
  435. <meshStandardMaterial {...buildMaterialProps(material)} />
  436. </mesh>
  437. </PhysicsWrapper>
  438. );
  439. }
  440. function PolyhedronPrimitive({
  441. Geometry,
  442. ...props
  443. }: GamePrimitiveProps & {
  444. Geometry: "tetrahedron" | "octahedron" | "dodecahedron" | "icosahedron";
  445. radius?: number | null;
  446. }) {
  447. const pos: [number, number, number] = props.position ?? [0, 0, 0];
  448. const rot: [number, number, number] = props.rotation ?? [0, 0, 0];
  449. const scl: [number, number, number] = props.scale ?? [1, 1, 1];
  450. const r = props.radius ?? 1;
  451. const geoElement = {
  452. tetrahedron: <tetrahedronGeometry args={[r]} />,
  453. octahedron: <octahedronGeometry args={[r]} />,
  454. dodecahedron: <dodecahedronGeometry args={[r]} />,
  455. icosahedron: <icosahedronGeometry args={[r]} />,
  456. }[Geometry];
  457. return (
  458. <PhysicsWrapper
  459. physics={props.physics}
  460. damage={props.damage}
  461. position={pos}
  462. rotation={rot}
  463. scale={scl}
  464. >
  465. <mesh
  466. castShadow={props.castShadow ?? false}
  467. receiveShadow={props.receiveShadow ?? false}
  468. >
  469. {geoElement}
  470. <meshStandardMaterial {...buildMaterialProps(props.material)} />
  471. </mesh>
  472. </PhysicsWrapper>
  473. );
  474. }
  475. export function GameTetrahedron(
  476. props: GamePrimitiveProps & { radius?: number | null },
  477. ) {
  478. return <PolyhedronPrimitive {...props} Geometry="tetrahedron" />;
  479. }
  480. export function GameOctahedron(
  481. props: GamePrimitiveProps & { radius?: number | null },
  482. ) {
  483. return <PolyhedronPrimitive {...props} Geometry="octahedron" />;
  484. }
  485. export function GameDodecahedron(
  486. props: GamePrimitiveProps & { radius?: number | null },
  487. ) {
  488. return <PolyhedronPrimitive {...props} Geometry="dodecahedron" />;
  489. }
  490. export function GameIcosahedron(
  491. props: GamePrimitiveProps & { radius?: number | null },
  492. ) {
  493. return <PolyhedronPrimitive {...props} Geometry="icosahedron" />;
  494. }
  495. export function GameExtrude({
  496. position,
  497. rotation,
  498. scale,
  499. castShadow,
  500. receiveShadow,
  501. material,
  502. physics,
  503. damage,
  504. shapeData,
  505. depth,
  506. }: GamePrimitiveProps & {
  507. shapeData?: {
  508. points: [number, number][];
  509. holes?: [number, number][][];
  510. } | null;
  511. depth?: number | null;
  512. }) {
  513. const pos: [number, number, number] = position ?? [0, 0, 0];
  514. const rot: [number, number, number] = rotation ?? [0, 0, 0];
  515. const scl: [number, number, number] = scale ?? [1, 1, 1];
  516. const shape = new THREE.Shape();
  517. const pts = shapeData?.points ?? [
  518. [-0.5, -0.5],
  519. [0.5, -0.5],
  520. [0.5, 0.5],
  521. [-0.5, 0.5],
  522. ];
  523. if (pts.length > 0) {
  524. shape.moveTo(pts[0]![0], pts[0]![1]);
  525. for (let i = 1; i < pts.length; i++) {
  526. shape.lineTo(pts[i]![0], pts[i]![1]);
  527. }
  528. shape.closePath();
  529. }
  530. if (shapeData?.holes) {
  531. for (const hole of shapeData.holes) {
  532. const holePath = new THREE.Path();
  533. if (hole.length > 0) {
  534. holePath.moveTo(hole[0]![0], hole[0]![1]);
  535. for (let i = 1; i < hole.length; i++) {
  536. holePath.lineTo(hole[i]![0], hole[i]![1]);
  537. }
  538. holePath.closePath();
  539. shape.holes.push(holePath);
  540. }
  541. }
  542. }
  543. return (
  544. <PhysicsWrapper
  545. physics={physics}
  546. damage={damage}
  547. position={pos}
  548. rotation={rot}
  549. scale={scl}
  550. >
  551. <mesh
  552. castShadow={castShadow ?? false}
  553. receiveShadow={receiveShadow ?? false}
  554. >
  555. <extrudeGeometry
  556. args={[
  557. shape as THREE.Shape,
  558. { depth: depth ?? 1, bevelEnabled: false },
  559. ]}
  560. />
  561. <meshStandardMaterial {...buildMaterialProps(material)} />
  562. </mesh>
  563. </PhysicsWrapper>
  564. );
  565. }
  566. export function GameTube({
  567. position,
  568. rotation,
  569. scale,
  570. castShadow,
  571. receiveShadow,
  572. material,
  573. physics,
  574. damage,
  575. radius,
  576. tubularSegments,
  577. radialSegments,
  578. }: GamePrimitiveProps & {
  579. radius?: number | null;
  580. tubularSegments?: number | null;
  581. radialSegments?: number | null;
  582. }) {
  583. const pos: [number, number, number] = position ?? [0, 0, 0];
  584. const rot: [number, number, number] = rotation ?? [0, 0, 0];
  585. const scl: [number, number, number] = scale ?? [1, 1, 1];
  586. const curve = new THREE.CatmullRomCurve3([
  587. new THREE.Vector3(-1, 0, 0),
  588. new THREE.Vector3(-0.5, 0.5, 0),
  589. new THREE.Vector3(0.5, -0.5, 0),
  590. new THREE.Vector3(1, 0, 0),
  591. ]);
  592. return (
  593. <PhysicsWrapper
  594. physics={physics}
  595. damage={damage}
  596. position={pos}
  597. rotation={rot}
  598. scale={scl}
  599. >
  600. <mesh
  601. castShadow={castShadow ?? false}
  602. receiveShadow={receiveShadow ?? false}
  603. >
  604. <tubeGeometry
  605. args={[
  606. curve as THREE.Curve<THREE.Vector3>,
  607. tubularSegments ?? 64,
  608. radius ?? 0.1,
  609. radialSegments ?? 8,
  610. false,
  611. ]}
  612. />
  613. <meshStandardMaterial {...buildMaterialProps(material)} />
  614. </mesh>
  615. </PhysicsWrapper>
  616. );
  617. }
  618. export function GameShape({
  619. position,
  620. rotation,
  621. scale,
  622. castShadow,
  623. receiveShadow,
  624. material,
  625. physics,
  626. damage,
  627. shapeData,
  628. }: GamePrimitiveProps & {
  629. shapeData?: {
  630. points: [number, number][];
  631. holes?: [number, number][][];
  632. } | null;
  633. }) {
  634. const pos: [number, number, number] = position ?? [0, 0, 0];
  635. const rot: [number, number, number] = rotation ?? [0, 0, 0];
  636. const scl: [number, number, number] = scale ?? [1, 1, 1];
  637. const shape = new THREE.Shape();
  638. const pts = shapeData?.points ?? [
  639. [-0.5, -0.5],
  640. [0.5, -0.5],
  641. [0, 0.5],
  642. ];
  643. if (pts.length > 0) {
  644. shape.moveTo(pts[0]![0], pts[0]![1]);
  645. for (let i = 1; i < pts.length; i++) {
  646. shape.lineTo(pts[i]![0], pts[i]![1]);
  647. }
  648. shape.closePath();
  649. }
  650. if (shapeData?.holes) {
  651. for (const hole of shapeData.holes) {
  652. const holePath = new THREE.Path();
  653. if (hole.length > 0) {
  654. holePath.moveTo(hole[0]![0], hole[0]![1]);
  655. for (let i = 1; i < hole.length; i++) {
  656. holePath.lineTo(hole[i]![0], hole[i]![1]);
  657. }
  658. holePath.closePath();
  659. shape.holes.push(holePath);
  660. }
  661. }
  662. }
  663. return (
  664. <PhysicsWrapper
  665. physics={physics}
  666. damage={damage}
  667. position={pos}
  668. rotation={rot}
  669. scale={scl}
  670. >
  671. <mesh
  672. castShadow={castShadow ?? false}
  673. receiveShadow={receiveShadow ?? false}
  674. >
  675. <shapeGeometry args={[shape as THREE.Shape]} />
  676. <meshStandardMaterial
  677. {...buildMaterialProps(material)}
  678. side={THREE.DoubleSide}
  679. />
  680. </mesh>
  681. </PhysicsWrapper>
  682. );
  683. }
  684. export function GameMesh({
  685. position,
  686. rotation,
  687. scale,
  688. castShadow,
  689. receiveShadow,
  690. material,
  691. physics,
  692. damage,
  693. meshData,
  694. }: GamePrimitiveProps & {
  695. meshData?: {
  696. vertices: number[];
  697. indices: number[];
  698. normals?: number[];
  699. uvs?: number[];
  700. } | null;
  701. }) {
  702. const pos: [number, number, number] = position ?? [0, 0, 0];
  703. const rot: [number, number, number] = rotation ?? [0, 0, 0];
  704. const scl: [number, number, number] = scale ?? [1, 1, 1];
  705. const meshRef = useRef<THREE.Mesh>(null);
  706. useEffect(() => {
  707. if (!meshRef.current || !meshData) return;
  708. const geometry = new THREE.BufferGeometry();
  709. geometry.setAttribute(
  710. "position",
  711. new THREE.Float32BufferAttribute(meshData.vertices, 3),
  712. );
  713. if (meshData.indices.length > 0) {
  714. geometry.setIndex(meshData.indices);
  715. }
  716. if (meshData.normals) {
  717. geometry.setAttribute(
  718. "normal",
  719. new THREE.Float32BufferAttribute(meshData.normals, 3),
  720. );
  721. } else {
  722. geometry.computeVertexNormals();
  723. }
  724. if (meshData.uvs) {
  725. geometry.setAttribute(
  726. "uv",
  727. new THREE.Float32BufferAttribute(meshData.uvs, 2),
  728. );
  729. }
  730. meshRef.current.geometry = geometry;
  731. }, [meshData]);
  732. return (
  733. <PhysicsWrapper
  734. physics={physics}
  735. damage={damage}
  736. position={pos}
  737. rotation={rot}
  738. scale={scl}
  739. >
  740. <mesh
  741. ref={meshRef}
  742. castShadow={castShadow ?? false}
  743. receiveShadow={receiveShadow ?? false}
  744. >
  745. <boxGeometry args={[1, 1, 1]} />
  746. <meshStandardMaterial {...buildMaterialProps(material)} />
  747. </mesh>
  748. </PhysicsWrapper>
  749. );
  750. }