import type { ObjectType, RuntimeGameObjectInstance, RuntimeObjectInstance } from "../types";
import type { VoxelGroup } from "../types/runtime/voxelGroup";
import { useEffect, useImperativeHandle, useRef, type Ref, memo, useMemo } from "react";
import { Euler, Quaternion, type Group } 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";
import { greedyMesh } from "../utils/graphics/greedyMesh";
const VoxelGroupMesh = memo(function VoxelGroupMesh({ vg }: { vg: VoxelGroup }) {
const geo = useMemo(() => greedyMesh(vg.positions), [vg]);
useEffect(() => () => { geo.dispose(); }, [geo]);
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 && }
);
};