import type { ObjectType, RuntimeGameObjectInstance, RuntimeObjectInstance } from "../types"; import type { VoxelGroup } from "../types/runtime/voxelGroup"; import { useEffect, useImperativeHandle, useLayoutEffect, useRef, type Ref, memo } from "react"; import { Euler, Matrix4, Quaternion, type Group, type InstancedMesh } from "three"; import type { ThreeEvent } from "@react-three/fiber"; import { ConvexHullCollider, RapierRigidBody } from "@react-three/rapier"; import { SyncRigidBody } from "./SyncRigidBody"; import { reaction, runInAction } from "mobx"; import { v3toRapier } from "../utils"; import { state } from "../state"; const _m4 = new Matrix4(); const VoxelGroupMesh = memo(function VoxelGroupMesh({ vg }: { vg: VoxelGroup }) { const ref = useRef(null); useLayoutEffect(() => { const mesh = ref.current; if (!mesh) return; vg.positions.forEach(([x, y, z], i) => { mesh.setMatrixAt(i, _m4.makeTranslation(x, y, z)); }); mesh.instanceMatrix.needsUpdate = true; mesh.computeBoundingSphere(); }, [vg]); return ( ); }); export type ObjectViewInternalHandle = { group: Group | null; rb: RapierRigidBody | null; } type ObjectViewInternalProps = { object: Omit; objectType: ObjectType; isEditor?: boolean; isPlayer?: boolean; onClick?: () => void; ref?: Ref; } // not observer, instead reacts only to object.version field export const ObjectViewInternal = function ({ object, objectType, ref, ...props }: ObjectViewInternalProps) { const rbRef = useRef(null); const groupRef = useRef(null); useImperativeHandle(ref, () => ({ group: groupRef.current, rb: rbRef.current })); useEffect( () => { if (props.isPlayer) return; return reaction( () => 'version' in object ? object.version : 0, () => { if (!rbRef.current) return; const gameObj = object as RuntimeGameObjectInstance; rbRef.current.setTranslation(v3toRapier(gameObj.position), true); const euler = new Euler(gameObj.rotation[0], gameObj.rotation[1], gameObj.rotation[2]); const q = new Quaternion().setFromEuler(euler); rbRef.current.setRotation({ x: q.x, y: q.y, z: q.z, w: q.w }, true); groupRef.current?.scale.set(...gameObj.scale); rbRef.current.setLinvel(v3toRapier(gameObj.linearVelocity), true); rbRef.current.setAngvel(v3toRapier(gameObj.angularVelocity), true); }, ); }, [object, object.id, props.isPlayer] ); const gameObj = object as RuntimeGameObjectInstance; useEffect( () => { runInAction(() => { gameObj.rb = () => rbRef.current; }); return () => { runInAction(() => { gameObj.rb = () => null; }); }; }, // eslint-disable-next-line react-hooks/exhaustive-deps [object.id], ); function handleClick(e: ThreeEvent) { if (e.delta > 5) return; e.stopPropagation(); props.onClick?.(); } return ( { const game = state.game; if (!game) return; const otherHandle = payload.other.rigidBody?.handle; if (otherHandle === undefined) return; const otherObj = Object.values(game.scene.objects) .find((o) => o.rb()?.handle === otherHandle); game.emitEvent('objectTouchStart', { object: gameObj, other: otherObj }); if (otherObj?.id === game.scene.playerObjectId) game.emitEvent('playerTouchStart', { object: gameObj, other: otherObj }); }} onSync={(data) => { runInAction(() => { object.position = data.position; object.rotation = data.rotation; if (!props.isEditor) { (object as RuntimeGameObjectInstance).linearVelocity = data.linearVelocity; (object as RuntimeGameObjectInstance).angularVelocity = data.radialVelocity; } // do not change object.version to not trigger rerender }); }} > { object.cache.voxelGroups.map((vg) => ) } {object.cache.colliderMesh && } ); };