Compare commits
No commits in common. "50a553b24d9a4a8ffeb51b933754313241741740" and "487a894fe5dabc86ac09e593847644271798a0b9" have entirely different histories.
50a553b24d
...
487a894fe5
|
|
@ -12,7 +12,6 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-three/drei": "^10.7.7",
|
"@react-three/drei": "^10.7.7",
|
||||||
"@react-three/fiber": "^9.6.1",
|
"@react-three/fiber": "^9.6.1",
|
||||||
"@react-three/rapier": "^2.2.0",
|
|
||||||
"@types/three": "^0.184.1",
|
"@types/three": "^0.184.1",
|
||||||
"install": "^0.13.0",
|
"install": "^0.13.0",
|
||||||
"mobx": "^6.15.4",
|
"mobx": "^6.15.4",
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,6 @@ importers:
|
||||||
'@react-three/fiber':
|
'@react-three/fiber':
|
||||||
specifier: ^9.6.1
|
specifier: ^9.6.1
|
||||||
version: 9.6.1(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(three@0.184.0)
|
version: 9.6.1(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(three@0.184.0)
|
||||||
'@react-three/rapier':
|
|
||||||
specifier: ^2.2.0
|
|
||||||
version: 2.2.0(@react-three/fiber@9.6.1(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(three@0.184.0))(react@19.2.6)(three@0.184.0)
|
|
||||||
'@types/three':
|
'@types/three':
|
||||||
specifier: ^0.184.1
|
specifier: ^0.184.1
|
||||||
version: 0.184.1
|
version: 0.184.1
|
||||||
|
|
@ -164,9 +161,6 @@ packages:
|
||||||
'@dimforge/rapier3d-compat@0.12.0':
|
'@dimforge/rapier3d-compat@0.12.0':
|
||||||
resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==}
|
resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==}
|
||||||
|
|
||||||
'@dimforge/rapier3d-compat@0.19.2':
|
|
||||||
resolution: {integrity: sha512-AZHL1jqUF55QJkJyU1yKeh4ImX2J93bVLIezT1+o0FZqTix6O06MOaqpKoJ4MmbDCsoZmwO+qc471/SDMDm2AA==}
|
|
||||||
|
|
||||||
'@emnapi/core@1.10.0':
|
'@emnapi/core@1.10.0':
|
||||||
resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==}
|
resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==}
|
||||||
|
|
||||||
|
|
@ -392,13 +386,6 @@ packages:
|
||||||
react-native:
|
react-native:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@react-three/rapier@2.2.0':
|
|
||||||
resolution: {integrity: sha512-mVsqbKXlGZoN+XrqdhzFZUQmy8pibEOVzl4k7LC+LHe84bQnYBSagy1Hvbda6bL1PJDdTFyiDiBk5buKFinNIQ==}
|
|
||||||
peerDependencies:
|
|
||||||
'@react-three/fiber': ^9.0.4
|
|
||||||
react: ^19
|
|
||||||
three: '>=0.159.0'
|
|
||||||
|
|
||||||
'@rolldown/binding-android-arm64@1.0.2':
|
'@rolldown/binding-android-arm64@1.0.2':
|
||||||
resolution: {integrity: sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==}
|
resolution: {integrity: sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
|
|
@ -1545,8 +1532,6 @@ snapshots:
|
||||||
|
|
||||||
'@dimforge/rapier3d-compat@0.12.0': {}
|
'@dimforge/rapier3d-compat@0.12.0': {}
|
||||||
|
|
||||||
'@dimforge/rapier3d-compat@0.19.2': {}
|
|
||||||
|
|
||||||
'@emnapi/core@1.10.0':
|
'@emnapi/core@1.10.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/wasi-threads': 1.2.1
|
'@emnapi/wasi-threads': 1.2.1
|
||||||
|
|
@ -1762,15 +1747,6 @@ snapshots:
|
||||||
- '@types/react'
|
- '@types/react'
|
||||||
- immer
|
- immer
|
||||||
|
|
||||||
'@react-three/rapier@2.2.0(@react-three/fiber@9.6.1(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(three@0.184.0))(react@19.2.6)(three@0.184.0)':
|
|
||||||
dependencies:
|
|
||||||
'@dimforge/rapier3d-compat': 0.19.2
|
|
||||||
'@react-three/fiber': 9.6.1(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(three@0.184.0)
|
|
||||||
react: 19.2.6
|
|
||||||
suspend-react: 0.1.3(react@19.2.6)
|
|
||||||
three: 0.184.0
|
|
||||||
three-stdlib: 2.36.1(three@0.184.0)
|
|
||||||
|
|
||||||
'@rolldown/binding-android-arm64@1.0.2':
|
'@rolldown/binding-android-arm64@1.0.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
allowBuilds:
|
allowBuilds:
|
||||||
'@parcel/watcher': false
|
'@parcel/watcher': set this to true or false
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,22 @@
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import type { Character } from "../types";
|
import type { Character } from "../types";
|
||||||
import { SyncRigidBody } from "./SyncRigidBody";
|
|
||||||
import { state } from "../state";
|
|
||||||
|
|
||||||
export const CharacterView = observer(function ({ character }: { character: Character }) {
|
export const CharacterView = observer(function ({ character }: { character: Character }) {
|
||||||
|
|
||||||
const pos = character.transform.position;
|
const pos = character.transform.position;
|
||||||
|
|
||||||
return (
|
return <group
|
||||||
<SyncRigidBody
|
position={[pos[0] + 0.5, pos[1] + 0.5, pos[2] + 0.5]}
|
||||||
colliders="hull"
|
rotation={character.transform.look}
|
||||||
restitution={2}
|
>
|
||||||
onSync={(data) => {
|
{/* <mesh>
|
||||||
state.game?.setCharacterTransform(
|
<boxGeometry args={[0.8, 0.8, 0.8]} />
|
||||||
{
|
<meshStandardMaterial color="yellow" />
|
||||||
position: data.position,
|
</mesh> */}
|
||||||
look: character.transform.look,
|
<mesh position={[0, 0, 0]} rotation={[-Math.PI / 2, -Math.PI / 4, 0]}>
|
||||||
},
|
<coneGeometry args={[0.55, 0.8, 4]} />
|
||||||
data.linearVelocity,
|
<meshStandardMaterial color="yellow" />
|
||||||
undefined, // do not change radial velocity
|
</mesh>
|
||||||
);
|
</group>
|
||||||
}}
|
|
||||||
>
|
|
||||||
<group
|
|
||||||
position={[pos[0] + 0.5, pos[1] + 0.5, pos[2] + 0.5]}
|
|
||||||
rotation={character.transform.look}
|
|
||||||
>
|
|
||||||
<mesh position={[0, 0, 0]} rotation={[-Math.PI / 2, -Math.PI / 4, 0]}>
|
|
||||||
<coneGeometry args={[0.55, 0.8, 4]} />
|
|
||||||
<meshStandardMaterial color="yellow" />
|
|
||||||
</mesh>
|
|
||||||
</group>
|
|
||||||
</SyncRigidBody>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
import type { ObjectInstance, Runtime } from "../types";
|
|
||||||
import { state } from "../state";
|
|
||||||
import { ObjectViewInternal } from "./ObjectViewInternal";
|
|
||||||
|
|
||||||
export type GameObjectViewProps = {
|
|
||||||
object: Runtime<ObjectInstance>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GameObjectView = observer(function (props: GameObjectViewProps) {
|
|
||||||
|
|
||||||
const objectType = state.world.getObjectTypeById(props.object.typeId);
|
|
||||||
if (!objectType)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return <ObjectViewInternal {...props} objectType={objectType} />
|
|
||||||
});
|
|
||||||
|
|
@ -3,35 +3,28 @@ import { SceneView } from "./SceneView";
|
||||||
import { state } from "../state";
|
import { state } from "../state";
|
||||||
import { useFrame, useThree } from "@react-three/fiber";
|
import { useFrame, useThree } from "@react-three/fiber";
|
||||||
import { PointerLockControls, useKeyboardControls } from "@react-three/drei";
|
import { PointerLockControls, useKeyboardControls } from "@react-three/drei";
|
||||||
import { Suspense, useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { Physics } from "@react-three/rapier";
|
|
||||||
|
|
||||||
function PlayerMovement() {
|
function PlayerMovement() {
|
||||||
const [, get] = useKeyboardControls();
|
const [, get] = useKeyboardControls();
|
||||||
const dirty = useRef(false);
|
const dirty = useRef(false);
|
||||||
|
|
||||||
useFrame(({ camera }, dt) => {
|
useFrame(({ camera }, dt) => {
|
||||||
if (state.game?.isPaused)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const { forward, backward, left, right } = get();
|
const { forward, backward, left, right } = get();
|
||||||
const speed = 5 * dt;
|
const speed = 5 * dt;
|
||||||
if (forward) { camera.translateZ(-speed); dirty.current = true; }
|
if (forward) { camera.translateZ(-speed); dirty.current = true; }
|
||||||
if (backward) { camera.translateZ(speed); dirty.current = true; }
|
if (backward) { camera.translateZ( speed); dirty.current = true; }
|
||||||
if (left) { camera.translateX(-speed); dirty.current = true; }
|
if (left) { camera.translateX(-speed); dirty.current = true; }
|
||||||
if (right) { camera.translateX(speed); dirty.current = true; }
|
if (right) { camera.translateX( speed); dirty.current = true; }
|
||||||
|
|
||||||
if (!dirty.current) return;
|
if (!dirty.current) return;
|
||||||
dirty.current = false;
|
dirty.current = false;
|
||||||
|
|
||||||
const [rx, ry, rz] = camera.rotation.toArray();
|
const [rx, ry, rz] = camera.rotation.toArray();
|
||||||
state.game?.setCharacterTransform(
|
state.game?.setCharacterTransform({
|
||||||
{
|
position: camera.position.toArray(),
|
||||||
position: camera.position.toArray(),
|
look: [rx, ry, rz],
|
||||||
look: [rx, ry, rz],
|
});
|
||||||
},
|
|
||||||
// do not change velocities
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return <PointerLockControls onChange={() => { dirty.current = true; }} />;
|
return <PointerLockControls onChange={() => { dirty.current = true; }} />;
|
||||||
|
|
@ -57,11 +50,7 @@ export const GameView = observer(function () {
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<Suspense>
|
<PlayerMovement />
|
||||||
{/* <PlayerMovement /> */}
|
<SceneView scene={game.scene} renderCharacter={false} />
|
||||||
<Physics paused={game.isPaused}>
|
|
||||||
<SceneView scene={game.scene} />
|
|
||||||
</Physics>
|
|
||||||
</Suspense>
|
|
||||||
</>);
|
</>);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
import type { ObjectInstance, R3, Runtime } from "../types";
|
|
||||||
import { useRef, type RefObject } from "react";
|
|
||||||
import type { Group } from "three";
|
|
||||||
import { TransformControls, useHelper } from "@react-three/drei";
|
|
||||||
import { BoxHelper } from "three";
|
|
||||||
import type { ThreeEvent } from "@react-three/fiber";
|
|
||||||
import { state } from "../state";
|
|
||||||
import { nextSelectionEditMode } from "../state/worldEditorState";
|
|
||||||
import { ObjectViewInternal } from "./ObjectViewInternal";
|
|
||||||
|
|
||||||
type ObjectEditorViewProps = {
|
|
||||||
object: Runtime<ObjectInstance>;
|
|
||||||
}
|
|
||||||
|
|
||||||
type SelectionOverlayProps = {
|
|
||||||
objectId: string;
|
|
||||||
groupRef: RefObject<Group | null>;
|
|
||||||
onTransformEnd: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Separate observer so only the 2 affected instances (selected/deselected)
|
|
||||||
// re-render on selection change, not all N objects in the scene.
|
|
||||||
const SelectionOverlay = observer(function ({ objectId, groupRef, onTransformEnd }: SelectionOverlayProps) {
|
|
||||||
const isSelected = state.worldEditor.isEnabled &&
|
|
||||||
state.worldEditor.selectedObjectId === objectId;
|
|
||||||
const selectionMode = isSelected ? state.worldEditor.selectedObjectMode : undefined;
|
|
||||||
|
|
||||||
useHelper(isSelected ? groupRef : { current: null } as any, BoxHelper, 'white');
|
|
||||||
|
|
||||||
if (!isSelected || selectionMode === undefined || !groupRef.current)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TransformControls
|
|
||||||
object={groupRef as RefObject<Group>}
|
|
||||||
mode={selectionMode}
|
|
||||||
onMouseUp={onTransformEnd}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ObjectEditorView = observer(function ({ object }: ObjectEditorViewProps) {
|
|
||||||
const groupRef = useRef<Group>(null);
|
|
||||||
|
|
||||||
// Only observes world object types — not selection state — so won't
|
|
||||||
// re-render when a different object is selected.
|
|
||||||
const objectType = state.world.getObjectTypeById(object.typeId);
|
|
||||||
if (!objectType)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
function handleClick(e: ThreeEvent<MouseEvent>) {
|
|
||||||
if (e.delta > 5) return;
|
|
||||||
e.stopPropagation();
|
|
||||||
// Reading selection state inside an event handler: not tracked by observer.
|
|
||||||
const currentMode = state.worldEditor.isEnabled &&
|
|
||||||
state.worldEditor.selectedObjectId === object.id
|
|
||||||
? state.worldEditor.selectedObjectMode
|
|
||||||
: undefined;
|
|
||||||
state.worldEditor.setSelectedObject(object.id, nextSelectionEditMode(currentMode));
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleTransformEnd() {
|
|
||||||
const group = groupRef.current;
|
|
||||||
if (group)
|
|
||||||
state.worldEditor.setObjectTransform(
|
|
||||||
object.id,
|
|
||||||
group.position.toArray(),
|
|
||||||
group.rotation.toArray().slice(0, 3) as R3,
|
|
||||||
group.scale.toArray(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (<>
|
|
||||||
<SelectionOverlay
|
|
||||||
objectId={object.id}
|
|
||||||
groupRef={groupRef}
|
|
||||||
onTransformEnd={handleTransformEnd}
|
|
||||||
/>
|
|
||||||
<ObjectViewInternal
|
|
||||||
ref={groupRef}
|
|
||||||
object={object}
|
|
||||||
objectType={objectType}
|
|
||||||
onClick={handleClick}
|
|
||||||
/>
|
|
||||||
</>);
|
|
||||||
});
|
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import type { ObjectInstance, ObjectType } from "../types";
|
||||||
|
import { useRef, type RefObject } from "react";
|
||||||
|
import type { Group } from "three";
|
||||||
|
import { Instance, Instances, TransformControls, useHelper } from "@react-three/drei";
|
||||||
|
import { BoxHelper } from "three";
|
||||||
|
import type { ThreeEvent } from "@react-three/fiber";
|
||||||
|
import { state } from "../state";
|
||||||
|
import { nextSelectionEditMode } from "../state/worldEditorState";
|
||||||
|
import type { R3 } from "../types/3d";
|
||||||
|
|
||||||
|
type VoxelGroup = {
|
||||||
|
id: string;
|
||||||
|
color: string;
|
||||||
|
opacity: number;
|
||||||
|
positions: [number, number, number][];
|
||||||
|
};
|
||||||
|
|
||||||
|
function voxelGroups(objectType: ObjectType): VoxelGroup[] {
|
||||||
|
const map = new Map<string, VoxelGroup>();
|
||||||
|
for (const idx in objectType.voxels) {
|
||||||
|
const v = objectType.voxels[idx];
|
||||||
|
const vt = state.world.getVoxelTypeById(v.typeId);
|
||||||
|
const color = (v.color ?? vt?.color) ?? 'white';
|
||||||
|
const opacity = (v.opacity ?? vt?.opacity) ?? 1;
|
||||||
|
const key = `${color}-${opacity}`;
|
||||||
|
if (!map.has(key))
|
||||||
|
map.set(key, { id: key, color, opacity, positions: [] });
|
||||||
|
const p = v.position;
|
||||||
|
map.get(key)!.positions.push([p[0] + 0.5, p[1] + 0.5, p[2] + 0.5]);
|
||||||
|
}
|
||||||
|
return [...map.values()];
|
||||||
|
}
|
||||||
|
|
||||||
|
type ObjectViewProps = {
|
||||||
|
object: ObjectInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ObjectView = observer(function ({ object }: ObjectViewProps) {
|
||||||
|
const groupRef = useRef<Group>(null);
|
||||||
|
|
||||||
|
const isSelected = state.worldEditor.isEnabled &&
|
||||||
|
state.worldEditor.selectedObjectId === object.id;
|
||||||
|
|
||||||
|
useHelper(isSelected ? groupRef : { current: null } as any, BoxHelper, 'white');
|
||||||
|
|
||||||
|
const objectType = state.world.getObjectTypeById(object.typeId);
|
||||||
|
if (!objectType)
|
||||||
|
return null;
|
||||||
|
const selectionMode = isSelected
|
||||||
|
? state.worldEditor.selectedObjectMode
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
function handleClick(e: ThreeEvent<MouseEvent>) {
|
||||||
|
if (!state.worldEditor.isEnabled)
|
||||||
|
return;
|
||||||
|
if (e.delta > 5)
|
||||||
|
return;
|
||||||
|
e.stopPropagation();
|
||||||
|
state.worldEditor.setSelectedObject(object.id, nextSelectionEditMode(selectionMode));
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleTransformEnd() {
|
||||||
|
const group = groupRef.current;
|
||||||
|
if (group)
|
||||||
|
state.worldEditor.setObjectTransform(
|
||||||
|
object.id,
|
||||||
|
group.position.toArray(),
|
||||||
|
group.rotation.toArray().slice(0, 3) as R3,
|
||||||
|
group.scale.toArray(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (<>
|
||||||
|
{
|
||||||
|
(isSelected && (selectionMode !== undefined) && groupRef.current) &&
|
||||||
|
<TransformControls
|
||||||
|
object={groupRef as RefObject<Group>}
|
||||||
|
mode={selectionMode}
|
||||||
|
onMouseUp={handleTransformEnd}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
<group
|
||||||
|
ref={groupRef}
|
||||||
|
name={`${object.id} (${object.typeId} instance)`}
|
||||||
|
position={object.position}
|
||||||
|
rotation={object.rotation}
|
||||||
|
scale={object.scale}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
voxelGroups(objectType).map((vg) => {
|
||||||
|
return <Instances key={vg.id} limit={vg.positions.length}>
|
||||||
|
<boxGeometry args={[1, 1, 1]} />
|
||||||
|
<meshStandardMaterial color={vg.color} opacity={vg.opacity} transparent={vg.opacity < 1} />
|
||||||
|
{
|
||||||
|
vg.positions
|
||||||
|
.map((pos, i) => <Instance key={i} position={pos} />)
|
||||||
|
}
|
||||||
|
</Instances>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</group>
|
||||||
|
</>);
|
||||||
|
});
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
import type { ObjectType, RuntimeObjectInstance } from "../types";
|
|
||||||
import { forwardRef, useMemo } from "react";
|
|
||||||
import type { Group } from "three";
|
|
||||||
import { Instance, Instances } from "@react-three/drei";
|
|
||||||
import type { ThreeEvent } from "@react-three/fiber";
|
|
||||||
import { state } from "../state";
|
|
||||||
import { TrimeshCollider } from "@react-three/rapier";
|
|
||||||
import { SyncRigidBody } from "./SyncRigidBody";
|
|
||||||
import { runInAction } from "mobx";
|
|
||||||
import { buildObjectTrimesh } from "../utils/graphics/mesh";
|
|
||||||
|
|
||||||
type ObjectViewInternalProps = {
|
|
||||||
object: Omit<RuntimeObjectInstance, 'typeId'>;
|
|
||||||
objectType: ObjectType;
|
|
||||||
onClick?: (e: ThreeEvent<MouseEvent>) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ObjectViewInternal = observer(forwardRef<Group | null, ObjectViewInternalProps>(
|
|
||||||
function ({ object, objectType, onClick }, ref) {
|
|
||||||
const trimeshArgs = useMemo(
|
|
||||||
() => buildObjectTrimesh(objectType, state.world.data.voxelTypes),
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[objectType.id]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SyncRigidBody
|
|
||||||
colliders={false}
|
|
||||||
gravityScale={object.physics ? 1 : 0.1}
|
|
||||||
onSync={(data) => {
|
|
||||||
runInAction(() => {
|
|
||||||
const obj = state.game?.scene.objects[object.id];
|
|
||||||
if (obj) {
|
|
||||||
obj.position = data.position;
|
|
||||||
obj.rotation = data.rotation;
|
|
||||||
obj.linearVelocity = data.linearVelocity;
|
|
||||||
obj.radialVelocity = data.radialVelocity;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<group
|
|
||||||
ref={ref}
|
|
||||||
name={`${object.id} (${objectType.id} instance)`}
|
|
||||||
position={object.position}
|
|
||||||
rotation={object.rotation}
|
|
||||||
scale={object.scale}
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
object.cache.voxelGroups.map((vg) =>
|
|
||||||
<Instances key={vg.id} limit={vg.positions.length}>
|
|
||||||
<boxGeometry args={[1, 1, 1]} />
|
|
||||||
<meshStandardMaterial color={vg.color} opacity={vg.opacity} transparent={vg.opacity < 1} />
|
|
||||||
{vg.positions.map((pos, i) => <Instance key={i} position={pos} />)}
|
|
||||||
</Instances>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{trimeshArgs && <TrimeshCollider args={trimeshArgs} />}
|
|
||||||
</group>
|
|
||||||
</SyncRigidBody>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
));
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Suspense, useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { Grid, OrbitControls } from '@react-three/drei';
|
import { Grid, OrbitControls } from '@react-three/drei';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
|
@ -6,7 +6,6 @@ import { state } from '../state';
|
||||||
import { SceneView } from './SceneView';
|
import { SceneView } from './SceneView';
|
||||||
import { type OrthographicCamera, type PerspectiveCamera } from 'three';
|
import { type OrthographicCamera, type PerspectiveCamera } from 'three';
|
||||||
import { CameraSync } from './tools/CameraSync';
|
import { CameraSync } from './tools/CameraSync';
|
||||||
import { Physics } from '@react-three/rapier';
|
|
||||||
|
|
||||||
|
|
||||||
export const SceneEditorView = observer(function () {
|
export const SceneEditorView = observer(function () {
|
||||||
|
|
@ -43,24 +42,20 @@ export const SceneEditorView = observer(function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<Suspense>
|
<OrbitControls
|
||||||
<OrbitControls
|
ref={controlsRef}
|
||||||
ref={controlsRef}
|
onStart={handleStart}
|
||||||
onStart={handleStart}
|
onEnd={handleEnd}
|
||||||
onEnd={handleEnd}
|
makeDefault
|
||||||
makeDefault
|
enableDamping={false}
|
||||||
enableDamping={false}
|
/>
|
||||||
/>
|
<CameraSync camera={state.worldEditor.camera} />
|
||||||
<CameraSync camera={state.worldEditor.camera} />
|
<Grid
|
||||||
<Grid
|
sectionThickness={0.5}
|
||||||
sectionThickness={0.5}
|
cellThickness={0}
|
||||||
cellThickness={0}
|
sectionColor="white"
|
||||||
sectionColor="white"
|
infiniteGrid
|
||||||
infiniteGrid
|
/>
|
||||||
/>
|
<SceneView scene={state.worldEditor.scene} renderCharacter={true} />
|
||||||
<Physics colliders={false} paused={true}>
|
|
||||||
<SceneView scene={state.worldEditor.scene} editMode />
|
|
||||||
</Physics>
|
|
||||||
</Suspense>
|
|
||||||
</>);
|
</>);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,19 @@
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import type { RuntimeScene } from "../types";
|
import type { Scene } from "../types";
|
||||||
import { CharacterView } from "./CharacterView";
|
import { CharacterView } from "./CharacterView";
|
||||||
import { GameObjectView } from "./GameObjectView";
|
import { ObjectView } from "./ObjectView";
|
||||||
import { useFrame } from "@react-three/fiber";
|
|
||||||
import { useRapier } from "@react-three/rapier";
|
|
||||||
import { ObjectEditorView } from "./ObjectEditorView";
|
|
||||||
|
|
||||||
type SceneViewProps = {
|
type SceneViewProps = {
|
||||||
scene: RuntimeScene,
|
scene: Scene,
|
||||||
editMode?: boolean;
|
renderCharacter: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SceneView = observer(function (props: SceneViewProps) {
|
export const SceneView = observer(function ({ scene, renderCharacter }: SceneViewProps) {
|
||||||
const rapier = useRapier();
|
|
||||||
|
|
||||||
useFrame((_, dt) => {
|
|
||||||
// if (props.editMode)
|
|
||||||
// return;
|
|
||||||
|
|
||||||
// const game = state.game;
|
|
||||||
// if (!game || game.isPaused)
|
|
||||||
// return;
|
|
||||||
|
|
||||||
// rapier.step(dt);
|
|
||||||
})
|
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<ambientLight intensity={0.5} />
|
<ambientLight intensity={0.5} />
|
||||||
<directionalLight position={[5, 5, 5]} intensity={1} />
|
<directionalLight position={[5, 5, 5]} intensity={1} />
|
||||||
{
|
{Object.values(scene.objects).map((obj) =>
|
||||||
Object.values(props.scene.objects).map((obj) => (
|
<ObjectView key={obj.id} object={obj} />)}
|
||||||
props.editMode
|
{renderCharacter && <CharacterView character={scene.character} />}
|
||||||
? <ObjectEditorView key={obj.id} object={obj} />
|
|
||||||
: <GameObjectView key={obj.id} object={obj} />
|
|
||||||
))
|
|
||||||
}
|
|
||||||
{/* {props.editMode && <CharacterView character={props.scene.character} />} */}
|
|
||||||
{<CharacterView character={props.scene.character} />}
|
|
||||||
</>);
|
</>);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
import { RigidBody, type RigidBodyProps, type RapierRigidBody } from "@react-three/rapier";
|
|
||||||
import { useFrame } from "@react-three/fiber";
|
|
||||||
import { useRef } from "react";
|
|
||||||
import { Euler, Quaternion } from "three";
|
|
||||||
import type { R3, V3 } from "../types";
|
|
||||||
|
|
||||||
export type SyncRigidBodyData = {
|
|
||||||
position: V3;
|
|
||||||
rotation: R3;
|
|
||||||
linearVelocity: V3;
|
|
||||||
radialVelocity: R3;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SyncRigidBodyOnSyncFunction = (data: SyncRigidBodyData) => void;
|
|
||||||
|
|
||||||
type SyncRigidBodyProps = RigidBodyProps & {
|
|
||||||
onSync: SyncRigidBodyOnSyncFunction;
|
|
||||||
};
|
|
||||||
|
|
||||||
const _q = new Quaternion();
|
|
||||||
const _e = new Euler();
|
|
||||||
const EPS = 1e-6;
|
|
||||||
|
|
||||||
// indices: 0-2 position, 3-5 rotation, 6-8 linearVelocity, 9-11 radialVelocity
|
|
||||||
const PREV_INIT = new Float64Array(12).fill(Infinity);
|
|
||||||
|
|
||||||
function syncRigidBodyDataToArray(data: SyncRigidBodyData, arr: Float64Array): void {
|
|
||||||
arr[0] = data.position[0]; arr[1] = data.position[1]; arr[2] = data.position[2];
|
|
||||||
arr[3] = data.rotation[0]; arr[4] = data.rotation[1]; arr[5] = data.rotation[2];
|
|
||||||
arr[6] = data.linearVelocity[0]; arr[7] = data.linearVelocity[1]; arr[8] = data.linearVelocity[2];
|
|
||||||
arr[9] = data.radialVelocity[0]; arr[10] = data.radialVelocity[1]; arr[11] = data.radialVelocity[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
function compareTwoFloatArrays(a: Float64Array, b: Float64Array, epsilon: number): boolean {
|
|
||||||
for (let i = 0; i < a.length; i++)
|
|
||||||
if (Math.abs(a[i] - b[i]) > epsilon)
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SyncRigidBody({ onSync, children, ...props }: SyncRigidBodyProps) {
|
|
||||||
const rbRef = useRef<RapierRigidBody>(null);
|
|
||||||
const prevData = useRef<Float64Array>(PREV_INIT.slice());
|
|
||||||
const currentData = useRef<Float64Array>(PREV_INIT.slice());
|
|
||||||
|
|
||||||
useFrame(() => {
|
|
||||||
const body = rbRef.current;
|
|
||||||
if (!body)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const { x, y, z } = body.translation();
|
|
||||||
const rot = body.rotation();
|
|
||||||
_q.set(rot.x, rot.y, rot.z, rot.w);
|
|
||||||
_e.setFromQuaternion(_q);
|
|
||||||
const lv = body.linvel();
|
|
||||||
const av = body.angvel();
|
|
||||||
|
|
||||||
const data: SyncRigidBodyData = {
|
|
||||||
position: [x, y, z],
|
|
||||||
rotation: [_e.x, _e.y, _e.z],
|
|
||||||
linearVelocity: [lv.x, lv.y, lv.z],
|
|
||||||
radialVelocity: [av.x, av.y, av.z],
|
|
||||||
};
|
|
||||||
|
|
||||||
syncRigidBodyDataToArray(data, currentData.current);
|
|
||||||
|
|
||||||
if (compareTwoFloatArrays(currentData.current, prevData.current, EPS)) {
|
|
||||||
prevData.current.set(currentData.current);
|
|
||||||
onSync(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return <RigidBody ref={rbRef} {...props}>{children}</RigidBody>;
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useThree } from "@react-three/fiber";
|
import { useThree } from "@react-three/fiber";
|
||||||
import type { Pos3 } from "../../types";
|
import type { Pos3 } from "../../types/3d";
|
||||||
import { useLayoutEffect } from "react";
|
import { useLayoutEffect } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { VoxelType } from "../types";
|
import type { VoxelType } from "../types/voxel";
|
||||||
|
|
||||||
const stone: VoxelType = {
|
const stone: VoxelType = {
|
||||||
id: 'stone',
|
id: 'stone',
|
||||||
|
|
@ -11,7 +11,7 @@ const stone: VoxelType = {
|
||||||
const dirt: VoxelType = {
|
const dirt: VoxelType = {
|
||||||
id: 'dirt',
|
id: 'dirt',
|
||||||
name: 'Dirt',
|
name: 'Dirt',
|
||||||
opacity: 0.8,
|
opacity: 1,
|
||||||
collidable: true,
|
collidable: true,
|
||||||
color: '#302520',
|
color: '#302520',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,13 @@
|
||||||
import type { Game, RuntimeGameScene, RuntimeScene, World } from "../types";
|
import type { Game, World } from "../types";
|
||||||
import { clone } from "../utils";
|
import { clone } from "../utils";
|
||||||
import { populateRuntimeScene } from "../utils/runtime";
|
|
||||||
|
|
||||||
export class GameFactory {
|
export class GameFactory {
|
||||||
|
|
||||||
public static create(world: World): Game {
|
public static create(world: World): Game {
|
||||||
const scene = populateRuntimeScene(clone(world.initialScene), world);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
paused: false,
|
paused: false,
|
||||||
time: 0,
|
time: 0,
|
||||||
scene: GameFactory.initGameScene(scene),
|
scene: clone(world.initialScene),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,8 +21,4 @@ export class GameFactory {
|
||||||
public static save(game: Game): void {
|
public static save(game: Game): void {
|
||||||
localStorage.setItem("game", JSON.stringify(game));
|
localStorage.setItem("game", JSON.stringify(game));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static initGameScene(scene: RuntimeScene): RuntimeGameScene {
|
|
||||||
return scene as RuntimeGameScene;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { Voxel } from "../../types";
|
import type { Voxel } from "../../types/voxel";
|
||||||
|
|
||||||
export function terrainXZ(width: number, length: number): Voxel[] {
|
export function terrainXZ(width: number, length: number): Voxel[] {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { Voxel } from "../../types";
|
import type { Voxel } from "../../types/voxel";
|
||||||
|
|
||||||
const G = '#808080'; // gray fur
|
const G = '#808080'; // gray fur
|
||||||
const L = '#d0c8b8'; // light beige-gray (snout / face)
|
const L = '#d0c8b8'; // light beige-gray (snout / face)
|
||||||
|
|
@ -11,52 +11,52 @@ function v(x: number, y: number, z: number, color: string): Voxel {
|
||||||
return { typeId: 'stone', position: [x, y, z], color };
|
return { typeId: 'stone', position: [x, y, z], color };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wolf faces +Z (nose at Z=3). Head spans X:-3..3, Y:0..5, Z:-3..3.
|
// Wolf faces +Z (nose at Z=6). Head spans 7×7×7.
|
||||||
export const wolf: Voxel[] = [
|
export const wolf: Voxel[] = [
|
||||||
// ── Back of skull Z=-3 ──────────────────────────────────────────────
|
// ── Back of skull Z=0 ───────────────────────────────────────────
|
||||||
v(-1,2,-3,G), v(0,2,-3,G), v(1,2,-3,G),
|
v(2,3,0,G), v(3,3,0,G), v(4,3,0,G),
|
||||||
v(-1,3,-3,G), v(0,3,-3,G), v(1,3,-3,G),
|
v(2,4,0,G), v(3,4,0,G), v(4,4,0,G),
|
||||||
v(-1,4,-3,G), v(0,4,-3,G), v(1,4,-3,G),
|
v(2,5,0,G), v(3,5,0,G), v(4,5,0,G),
|
||||||
|
|
||||||
// ── Skull Z=-2 ──────────────────────────────────────────────────────
|
// ── Skull Z=1 ───────────────────────────────────────────────────
|
||||||
v(-2,2,-2,G), v(-1,2,-2,G), v(0,2,-2,G), v(1,2,-2,G), v(2,2,-2,G),
|
v(1,3,1,G), v(2,3,1,G), v(3,3,1,G), v(4,3,1,G), v(5,3,1,G),
|
||||||
v(-2,3,-2,G), v(-1,3,-2,G), v(0,3,-2,G), v(1,3,-2,G), v(2,3,-2,G),
|
v(1,4,1,G), v(2,4,1,G), v(3,4,1,G), v(4,4,1,G), v(5,4,1,G),
|
||||||
v(-2,4,-2,G), v(-1,4,-2,G), v(0,4,-2,G), v(1,4,-2,G), v(2,4,-2,G),
|
v(1,5,1,G), v(2,5,1,G), v(3,5,1,G), v(4,5,1,G), v(5,5,1,G),
|
||||||
v(-1,5,-2,G), v(0,5,-2,G), v(1,5,-2,G),
|
v(2,6,1,G), v(3,6,1,G), v(4,6,1,G),
|
||||||
|
|
||||||
// ── Skull + ear bases Z=-1 ───────────────────────────────────────────
|
// ── Skull + ear bases Z=2 ───────────────────────────────────────
|
||||||
v(-2,2,-1,G), v(-1,2,-1,G), v(0,2,-1,G), v(1,2,-1,G), v(2,2,-1,G),
|
v(1,3,2,G), v(2,3,2,G), v(3,3,2,G), v(4,3,2,G), v(5,3,2,G),
|
||||||
v(-2,3,-1,G), v(-1,3,-1,G), v(0,3,-1,G), v(1,3,-1,G), v(2,3,-1,G),
|
v(1,4,2,G), v(2,4,2,G), v(3,4,2,G), v(4,4,2,G), v(5,4,2,G),
|
||||||
v(-3,4,-1,G), v(-2,4,-1,G), v(-1,4,-1,G), v(0,4,-1,G), v(1,4,-1,G), v(2,4,-1,G), v(3,4,-1,G),
|
v(0,5,2,G), v(1,5,2,G), v(2,5,2,G), v(3,5,2,G), v(4,5,2,G), v(5,5,2,G), v(6,5,2,G),
|
||||||
v(-2,5,-1,G), v(-1,5,-1,G), v(0,5,-1,G), v(1,5,-1,G), v(2,5,-1,G),
|
v(1,6,2,G), v(2,6,2,G), v(3,6,2,G), v(4,6,2,G), v(5,6,2,G),
|
||||||
v(-3,5,-1,D), v(3,5,-1,D),
|
v(0,6,2,D), v(6,6,2,D),
|
||||||
|
|
||||||
// ── Face + ears Z=0 ──────────────────────────────────────────────────
|
// ── Face + ears Z=3 ─────────────────────────────────────────────
|
||||||
v(-2,2,0,L), v(-1,2,0,L), v(0,2,0,L), v(1,2,0,L), v(2,2,0,L),
|
v(1,3,3,L), v(2,3,3,L), v(3,3,3,L), v(4,3,3,L), v(5,3,3,L),
|
||||||
v(-2,3,0,G), v(-1,3,0,G), v(0,3,0,G), v(1,3,0,G), v(2,3,0,G),
|
v(1,4,3,G), v(2,4,3,G), v(3,4,3,G), v(4,4,3,G), v(5,4,3,G),
|
||||||
// ears: dark outer, pink inner
|
// ears: dark outer, pink inner
|
||||||
v(-3,4,0,D), v(-2,4,0,P), v(-1,4,0,G), v(0,4,0,G), v(1,4,0,G), v(2,4,0,P), v(3,4,0,D),
|
v(0,5,3,D), v(1,5,3,P), v(2,5,3,G), v(3,5,3,G), v(4,5,3,G), v(5,5,3,P), v(6,5,3,D),
|
||||||
v(-3,5,0,D), v(-2,5,0,P), v(-1,5,0,G), v(0,5,0,G), v(1,5,0,G), v(2,5,0,P), v(3,5,0,D),
|
v(0,6,3,D), v(1,6,3,P), v(2,6,3,G), v(3,6,3,G), v(4,6,3,G), v(5,6,3,P), v(6,6,3,D),
|
||||||
// snout start
|
// snout start
|
||||||
v(-1,0,0,L), v(0,0,0,L), v(1,0,0,L),
|
v(2,1,3,L), v(3,1,3,L), v(4,1,3,L),
|
||||||
v(-1,1,0,L), v(0,1,0,L), v(1,1,0,L),
|
v(2,2,3,L), v(3,2,3,L), v(4,2,3,L),
|
||||||
|
|
||||||
// ── Face + amber eyes + snout Z=1 ────────────────────────────────────
|
// ── Face + amber eyes + snout Z=4 ───────────────────────────────
|
||||||
v(-2,2,1,L), v(-1,2,1,L), v(0,2,1,L), v(1,2,1,L), v(2,2,1,L),
|
v(1,3,4,L), v(2,3,4,L), v(3,3,4,L), v(4,3,4,L), v(5,3,4,L),
|
||||||
v(-2,3,1,G), v(-1,3,1,E), v(0,3,1,G), v(1,3,1,E), v(2,3,1,G),
|
v(1,4,4,G), v(2,4,4,E), v(3,4,4,G), v(4,4,4,E), v(5,4,4,G),
|
||||||
v(-2,4,1,G), v(-1,4,1,G), v(0,4,1,G), v(1,4,1,G), v(2,4,1,G),
|
v(1,5,4,G), v(2,5,4,G), v(3,5,4,G), v(4,5,4,G), v(5,5,4,G),
|
||||||
v(-1,0,1,L), v(0,0,1,L), v(1,0,1,L),
|
v(2,1,4,L), v(3,1,4,L), v(4,1,4,L),
|
||||||
v(-1,1,1,L), v(0,1,1,L), v(1,1,1,L),
|
v(2,2,4,L), v(3,2,4,L), v(4,2,4,L),
|
||||||
|
|
||||||
// ── Front face + pupils + snout Z=2 ──────────────────────────────────
|
// ── Front face + pupils + snout Z=5 ─────────────────────────────
|
||||||
v(-2,2,2,L), v(-1,2,2,L), v(0,2,2,L), v(1,2,2,L), v(2,2,2,L),
|
v(1,3,5,L), v(2,3,5,L), v(3,3,5,L), v(4,3,5,L), v(5,3,5,L),
|
||||||
v(-2,3,2,G), v(-1,3,2,N), v(0,3,2,G), v(1,3,2,N), v(2,3,2,G),
|
v(1,4,5,G), v(2,4,5,N), v(3,4,5,G), v(4,4,5,N), v(5,4,5,G),
|
||||||
v(-2,4,2,G), v(-1,4,2,G), v(0,4,2,G), v(1,4,2,G), v(2,4,2,G),
|
v(1,5,5,G), v(2,5,5,G), v(3,5,5,G), v(4,5,5,G), v(5,5,5,G),
|
||||||
v(-1,0,2,L), v(0,0,2,L), v(1,0,2,L),
|
v(2,1,5,L), v(3,1,5,L), v(4,1,5,L),
|
||||||
v(-1,1,2,L), v(0,1,2,L), v(1,1,2,L),
|
v(2,2,5,L), v(3,2,5,L), v(4,2,5,L),
|
||||||
|
|
||||||
// ── Snout tip + nose Z=3 ─────────────────────────────────────────────
|
// ── Snout tip + nose Z=6 ────────────────────────────────────────
|
||||||
v(-1,1,3,L), v(0,1,3,L), v(1,1,3,L),
|
v(2,2,6,L), v(3,2,6,L), v(4,2,6,L),
|
||||||
v(-1,0,3,L), v(0,0,3,L), v(1,0,3,L),
|
v(2,1,6,L), v(3,1,6,L), v(4,1,6,L),
|
||||||
v(-1,2,3,N), v(0,2,3,N), v(1,2,3,N), // nose
|
v(2,3,6,N), v(3,3,6,N), v(4,3,6,N), // nose
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
import { type RuntimeWorld, type World } from "../types";
|
import type { World } from "../types";
|
||||||
import { depopulateRuntimeWorld, populateRuntimeWorld } from "../utils/runtime";
|
|
||||||
import { DEFAULT_VOXEL_TYPES } from "./defaultVoxelTypes";
|
import { DEFAULT_VOXEL_TYPES } from "./defaultVoxelTypes";
|
||||||
|
|
||||||
export class WorldFactory {
|
export class WorldFactory {
|
||||||
|
|
||||||
public static create(): RuntimeWorld {
|
public static create(): World {
|
||||||
return populateRuntimeWorld({
|
return {
|
||||||
objectTypes: {},
|
objectTypes: {},
|
||||||
voxelTypes: DEFAULT_VOXEL_TYPES,
|
voxelTypes: DEFAULT_VOXEL_TYPES,
|
||||||
initialScene: {
|
initialScene: {
|
||||||
|
|
@ -24,17 +23,17 @@ export class WorldFactory {
|
||||||
position: [0, 5, 10],
|
position: [0, 5, 10],
|
||||||
look: [0, 0, 0],
|
look: [0, 0, 0],
|
||||||
},
|
},
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static load(): RuntimeWorld | undefined {
|
|
||||||
const json = localStorage.getItem("world");
|
|
||||||
if (json) {
|
|
||||||
return populateRuntimeWorld(JSON.parse(json) as World);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static save(world: RuntimeWorld): void {
|
public static load(): World | undefined {
|
||||||
localStorage.setItem("world", JSON.stringify(depopulateRuntimeWorld(world)));
|
const json = localStorage.getItem("world");
|
||||||
|
if (json) {
|
||||||
|
return JSON.parse(json) as World;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static save(world: World): void {
|
||||||
|
localStorage.setItem("world", JSON.stringify(world));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { makeAutoObservable, reaction, toJS } from "mobx";
|
import { makeAutoObservable, reaction, toJS } from "mobx";
|
||||||
import type { WorldState } from "./worldState";
|
import type { WorldState } from "./worldState";
|
||||||
import type { Game, Pos3, V3, R3, RuntimeGameScene } from "../types";
|
import type { Game, Scene } from "../types";
|
||||||
|
import type { Pos3, R3, V3 } from "../types/3d";
|
||||||
import type { CameraProps } from "@react-three/fiber";
|
import type { CameraProps } from "@react-three/fiber";
|
||||||
import { GameFactory } from "../model/gameFactory";
|
import { GameFactory } from "../model/gameFactory";
|
||||||
|
|
||||||
|
|
@ -33,12 +34,11 @@ export class GameState {
|
||||||
return this.data.paused;
|
return this.data.paused;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get scene(): RuntimeGameScene {
|
public get scene(): Scene {
|
||||||
return this.data.scene;
|
return this.data.scene;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get camera(): Pos3 {
|
public get camera(): Pos3 {
|
||||||
return this.world.data.editorCamera;
|
|
||||||
return this.scene.character.transform;
|
return this.scene.character.transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,21 +78,11 @@ export class GameState {
|
||||||
this.data.paused = true;
|
this.data.paused = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setCharacterTransform(
|
public setCharacterTransform(transform: Pos3): void {
|
||||||
transform: Pos3,
|
|
||||||
linearVelocity?: V3,
|
|
||||||
radialVelocity?: R3,
|
|
||||||
): void {
|
|
||||||
if (this.isPaused)
|
if (this.isPaused)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.scene.character.transform = transform;
|
this.scene.character.transform = transform;
|
||||||
if (linearVelocity)
|
|
||||||
this.scene.character.linearVelocity = linearVelocity;
|
|
||||||
if (radialVelocity)
|
|
||||||
this.scene.character.radialVelocity = radialVelocity;
|
|
||||||
|
|
||||||
// console.log(`changed character to ${JSON.stringify(this.scene.character)}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public tick(deltaTime: number): void {
|
public tick(deltaTime: number): void {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { makeAutoObservable, runInAction } from "mobx";
|
import { makeAutoObservable, runInAction } from "mobx";
|
||||||
import type { WorldState } from "./worldState";
|
import type { WorldState } from "./worldState";
|
||||||
import { type ObjectInstance, type Pos3, type R3, type V3, type RuntimeScene } from "../types";
|
import type { ObjectInstance, Scene } from "../types";
|
||||||
import { createObjectInstance } from "../utils/object";
|
import { createObjectInstance } from "../utils/object";
|
||||||
import { randomId } from "../utils";
|
import { randomId } from "../utils";
|
||||||
|
import type { Pos3, R3, V3 } from "../types/3d";
|
||||||
import { state } from "./rootState";
|
import { state } from "./rootState";
|
||||||
import { populateRuntimeObject } from "../utils/runtime";
|
|
||||||
|
|
||||||
export const SelectionEditModeEnum = [
|
export const SelectionEditModeEnum = [
|
||||||
'translate',
|
'translate',
|
||||||
|
|
@ -51,7 +51,7 @@ export class WorldEditorState {
|
||||||
this.selectedObjectMode = undefined;
|
this.selectedObjectMode = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get scene(): RuntimeScene {
|
public get scene(): Scene {
|
||||||
return this.world.data.initialScene;
|
return this.world.data.initialScene;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,7 +82,7 @@ export class WorldEditorState {
|
||||||
typeId: string,
|
typeId: string,
|
||||||
pos: { x: number; y: number; z: number; },
|
pos: { x: number; y: number; z: number; },
|
||||||
): ObjectInstance {
|
): ObjectInstance {
|
||||||
const obj = populateRuntimeObject(createObjectInstance(randomId(), typeId), this.world.data);
|
const obj = createObjectInstance(randomId(), typeId);
|
||||||
obj.position = [pos.x, pos.y, pos.z];
|
obj.position = [pos.x, pos.y, pos.z];
|
||||||
obj.rotation = [0, 0, 0];
|
obj.rotation = [0, 0, 0];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import { makeAutoObservable, reaction, toJS } from "mobx";
|
import { makeAutoObservable, reaction, toJS } from "mobx";
|
||||||
import { WorldFactory } from "../model/worldFactory";
|
import { WorldFactory } from "../model/worldFactory";
|
||||||
import { type ObjectInstance, type ObjectType, type World, type VoxelType, type RuntimeWorld } from "../types";
|
import type { ObjectInstance, ObjectType, World } from "../types";
|
||||||
|
import type { VoxelType } from "../types/voxel";
|
||||||
import { state } from "./rootState";
|
import { state } from "./rootState";
|
||||||
import { DEFAULT_VOXEL_TYPES } from "../model/defaultVoxelTypes";
|
import { DEFAULT_VOXEL_TYPES } from "../model/defaultVoxelTypes";
|
||||||
import { wolf } from "../model/objectPrefabs/wolf";
|
import { wolf } from "../model/objectPrefabs/wolf";
|
||||||
import { terrainXZ } from "../model/objectPrefabs/terrain";
|
import { terrainXZ } from "../model/objectPrefabs/terrain";
|
||||||
import { populateRuntimeWorld } from "../utils/runtime";
|
|
||||||
|
|
||||||
export class WorldState {
|
export class WorldState {
|
||||||
public data: RuntimeWorld = WorldFactory.create();
|
public data: World = WorldFactory.create();
|
||||||
|
|
||||||
private startAutoSave() {
|
private startAutoSave() {
|
||||||
return reaction(() => toJS(this.data), (data) => this.saveData(data), { delay: 500 });
|
return reaction(() => toJS(this.data), (data) => this.saveData(data), { delay: 500 });
|
||||||
|
|
@ -43,16 +43,15 @@ export class WorldState {
|
||||||
.map((_, idx) => ({
|
.map((_, idx) => ({
|
||||||
id: `obj${idx}`,
|
id: `obj${idx}`,
|
||||||
typeId: 'wolf',
|
typeId: 'wolf',
|
||||||
gravityScale: 1,
|
|
||||||
position: [idx * 10 - 10, 0, 0],
|
position: [idx * 10 - 10, 0, 0],
|
||||||
rotation: [0, 0, 0],
|
rotation: [0, 0, 0],
|
||||||
scale: [1, 1, 1],
|
scale: [1, 1, 1],
|
||||||
} as ObjectInstance));
|
}));
|
||||||
const objectMap = Object.fromEntries(
|
const objectMap = Object.fromEntries(
|
||||||
objects.map((obj) => [obj.id, obj]),
|
objects.map((obj) => [obj.id, obj]),
|
||||||
) as Record<string, ObjectInstance>
|
) as Record<string, ObjectInstance>
|
||||||
|
|
||||||
this.data = populateRuntimeWorld({
|
this.data = {
|
||||||
objectTypes: {
|
objectTypes: {
|
||||||
wolf: {
|
wolf: {
|
||||||
id: 'wolf',
|
id: 'wolf',
|
||||||
|
|
@ -75,14 +74,12 @@ export class WorldState {
|
||||||
transform: {
|
transform: {
|
||||||
position: [0, 5, 20],
|
position: [0, 5, 20],
|
||||||
look: [0, 0, 0],
|
look: [0, 0, 0],
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
objects: {
|
objects: {
|
||||||
terrain: {
|
terrain: {
|
||||||
id: 'terrain',
|
id: 'terrain',
|
||||||
typeId: 'terrain',
|
typeId: 'terrain',
|
||||||
physics: false, // pinned
|
|
||||||
gravityScale: 0, // pinned
|
|
||||||
position: [0, -1, 0],
|
position: [0, -1, 0],
|
||||||
rotation: [0, 0, 0],
|
rotation: [0, 0, 0],
|
||||||
scale: [1, 1, 1],
|
scale: [1, 1, 1],
|
||||||
|
|
@ -93,8 +90,7 @@ export class WorldState {
|
||||||
gameRules: {
|
gameRules: {
|
||||||
gravity: true,
|
gravity: true,
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
console.log(objects);
|
console.log(objects);
|
||||||
state.worldEditor.resetSelectedObject();
|
state.worldEditor.resetSelectedObject();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import type { Pos3 } from "./3d";
|
||||||
|
|
||||||
|
export type Character = {
|
||||||
|
transform: Pos3,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
import type { Scene } from "./scene";
|
||||||
|
|
||||||
|
export type Game = {
|
||||||
|
paused: boolean;
|
||||||
|
time: number;
|
||||||
|
scene: Scene;
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,9 @@
|
||||||
export * from './3d';
|
export * from './object';
|
||||||
export * from './model';
|
export * from './scene';
|
||||||
export * from './runtime';
|
export * from './world';
|
||||||
|
export * from './gameRules';
|
||||||
|
export * from './game';
|
||||||
|
export * from './character';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import type { Pos3 } from "../3d";
|
|
||||||
import type { GameObjectData } from "./object";
|
|
||||||
|
|
||||||
export type Character = {
|
|
||||||
transform: Pos3,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GameCharacter = Character & GameObjectData;
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
import type { RuntimeGameScene } from "./scene";
|
|
||||||
|
|
||||||
export type Game = {
|
|
||||||
paused: boolean;
|
|
||||||
time: number;
|
|
||||||
scene: RuntimeGameScene;
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
export * from './object';
|
|
||||||
export * from './scene';
|
|
||||||
export * from './world';
|
|
||||||
export * from './gameRules';
|
|
||||||
export * from './game';
|
|
||||||
export * from './character';
|
|
||||||
export * from './voxel';
|
|
||||||
export * from './runtime';
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
import type { R3, V3 } from "../3d";
|
|
||||||
import type { Runtime } from "./runtime";
|
|
||||||
import type { Voxel } from "./voxel";
|
|
||||||
|
|
||||||
export type ObjectType = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
voxels: Voxel[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ObjectInstance = {
|
|
||||||
id: string;
|
|
||||||
typeId: string;
|
|
||||||
physics: boolean;
|
|
||||||
gravityScale: number;
|
|
||||||
position: V3;
|
|
||||||
rotation: R3;
|
|
||||||
scale: V3;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GameObjectData = {
|
|
||||||
linearVelocity: V3;
|
|
||||||
radialVelocity: R3;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GameObjectInstance = ObjectInstance & GameObjectData;
|
|
||||||
|
|
||||||
export type RuntimeObjectInstance = Runtime<ObjectInstance>;
|
|
||||||
export type RuntimeGameObjectInstance = Runtime<GameObjectInstance>;
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import type { VoxelGroup } from "../runtime";
|
|
||||||
import type { ObjectInstance } from "./object";
|
|
||||||
|
|
||||||
export type ObjectInstanceRuntimeData = {
|
|
||||||
cache: {
|
|
||||||
voxelGroups: VoxelGroup[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Runtime<T extends ObjectInstance> = T & ObjectInstanceRuntimeData;
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
import type { Character, GameCharacter } from "./character";
|
|
||||||
import type { GameObjectInstance, ObjectInstance, RuntimeGameObjectInstance, RuntimeObjectInstance } from "./object";
|
|
||||||
|
|
||||||
export type Scene = {
|
|
||||||
character: Character;
|
|
||||||
objects: Record<string, ObjectInstance>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RuntimeScene = {
|
|
||||||
character: Character;
|
|
||||||
objects: Record<string, RuntimeObjectInstance>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GameScene = {
|
|
||||||
character: GameCharacter;
|
|
||||||
objects: Record<string, GameObjectInstance>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RuntimeGameScene = {
|
|
||||||
character: GameCharacter;
|
|
||||||
objects: Record<string, RuntimeGameObjectInstance>;
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import type { R3, V3 } from "./3d";
|
||||||
|
import type { Voxel } from "./voxel";
|
||||||
|
|
||||||
|
export type ObjectType = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
voxels: Voxel[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ObjectInstance = {
|
||||||
|
id: string;
|
||||||
|
typeId: string;
|
||||||
|
position: V3;
|
||||||
|
rotation: R3;
|
||||||
|
scale: V3;
|
||||||
|
}
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from './voxelGroup';
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
import type { V3 } from "../3d";
|
|
||||||
|
|
||||||
export type VoxelGroup = {
|
|
||||||
id: string;
|
|
||||||
color: string;
|
|
||||||
opacity: number;
|
|
||||||
positions: V3[];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
import type { Character } from "./character";
|
||||||
|
import type { ObjectInstance } from "./object";
|
||||||
|
|
||||||
|
export type Scene = {
|
||||||
|
character: Character;
|
||||||
|
objects: Record<string, ObjectInstance>;
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { V3 } from "../3d";
|
import type { V3 } from "./3d";
|
||||||
|
|
||||||
export type VoxelType = {
|
export type VoxelType = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type { GameRules } from "./gameRules";
|
import type { GameRules } from "./gameRules";
|
||||||
import type { ObjectType } from "./object";
|
import type { ObjectType } from "./object";
|
||||||
import type { RuntimeScene, Scene } from "./scene";
|
import type { Scene } from "./scene";
|
||||||
import type { Pos3 } from "../3d";
|
import type { Pos3 } from "./3d";
|
||||||
import type { VoxelType } from "./voxel";
|
import type { VoxelType } from "./voxel";
|
||||||
|
|
||||||
export type World = {
|
export type World = {
|
||||||
|
|
@ -11,7 +11,3 @@ export type World = {
|
||||||
initialScene: Scene;
|
initialScene: Scene;
|
||||||
gameRules: GameRules;
|
gameRules: GameRules;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RuntimeWorld = World & {
|
|
||||||
initialScene: RuntimeScene;
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
import type { ObjectType, VoxelType } from "../../types";
|
|
||||||
|
|
||||||
export function buildObjectTrimesh(objectType: ObjectType, voxelTypes: Record<string, VoxelType>): [Float32Array, Uint32Array] | null {
|
|
||||||
const collidable = objectType.voxels.filter((v) => voxelTypes[v.typeId]?.collidable !== false);
|
|
||||||
|
|
||||||
if (!collidable.length)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
const n = collidable.length;
|
|
||||||
const verts = new Float32Array(n * 8 * 3);
|
|
||||||
const idxs = new Uint32Array(n * 36);
|
|
||||||
|
|
||||||
for (let i = 0; i < n; i++) {
|
|
||||||
const p = collidable[i].position;
|
|
||||||
const vb = i * 8;
|
|
||||||
|
|
||||||
// 8 corners of unit box at position p (no +0.5 — group transform handles offset)
|
|
||||||
verts.set([
|
|
||||||
p[0], p[1], p[2],
|
|
||||||
p[0] + 1, p[1], p[2],
|
|
||||||
p[0], p[1] + 1, p[2],
|
|
||||||
p[0] + 1, p[1] + 1, p[2],
|
|
||||||
p[0], p[1], p[2] + 1,
|
|
||||||
p[0] + 1, p[1], p[2] + 1,
|
|
||||||
p[0], p[1] + 1, p[2] + 1,
|
|
||||||
p[0] + 1, p[1] + 1, p[2] + 1,
|
|
||||||
], vb * 3);
|
|
||||||
|
|
||||||
// 12 triangles (CCW outward normals)
|
|
||||||
idxs.set([
|
|
||||||
vb, vb + 2, vb + 3, vb, vb + 3, vb + 1, // -Z
|
|
||||||
vb + 4, vb + 5, vb + 7, vb + 4, vb + 7, vb + 6, // +Z
|
|
||||||
vb, vb + 1, vb + 5, vb, vb + 5, vb + 4, // -Y
|
|
||||||
vb + 2, vb + 6, vb + 7, vb + 2, vb + 7, vb + 3, // +Y
|
|
||||||
vb, vb + 4, vb + 6, vb, vb + 6, vb + 2, // -X
|
|
||||||
vb + 1, vb + 3, vb + 7, vb + 1, vb + 7, vb + 5, // +X
|
|
||||||
], i * 36);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [verts, idxs];
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import type { ObjectType, VoxelGroup, VoxelType } from "../../types";
|
|
||||||
|
|
||||||
export function getObjectVoxelGroups(
|
|
||||||
object: ObjectType,
|
|
||||||
voxelTypes: Record<string, VoxelType>,
|
|
||||||
): VoxelGroup[] {
|
|
||||||
const map = new Map<string, VoxelGroup>();
|
|
||||||
for (const idx in object.voxels) {
|
|
||||||
const v = object.voxels[idx];
|
|
||||||
const vt = voxelTypes[v.typeId];
|
|
||||||
const color = (v.color ?? vt?.color) ?? 'white';
|
|
||||||
const opacity = (v.opacity ?? vt?.opacity) ?? 1;
|
|
||||||
const key = `${color}-${opacity}`;
|
|
||||||
if (!map.has(key))
|
|
||||||
map.set(key, { id: key, color, opacity, positions: [] });
|
|
||||||
const p = v.position;
|
|
||||||
map.get(key)!.positions.push([p[0] + 0.5, p[1] + 0.5, p[2] + 0.5]);
|
|
||||||
}
|
|
||||||
return [...map.values()];
|
|
||||||
}
|
|
||||||
|
|
@ -4,10 +4,7 @@ export function createObjectInstance(id: string, typeId: string): ObjectInstance
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
typeId: typeId,
|
typeId: typeId,
|
||||||
physics: false,
|
|
||||||
gravityScale: 1,
|
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0],
|
rotation: [0, 0, 0],
|
||||||
scale: [1, 1, 1],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
export * from './world';
|
|
||||||
export * from './scene';
|
|
||||||
export * from './object';
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
import { getObjectVoxelGroups } from "../graphics/voxelGroup";
|
|
||||||
import type { ObjectInstance, RuntimeObjectInstance, World } from "../../types";
|
|
||||||
|
|
||||||
export function populateRuntimeObject(object: ObjectInstance, world: World): RuntimeObjectInstance {
|
|
||||||
const objectType = world.objectTypes[object.typeId];
|
|
||||||
|
|
||||||
return {
|
|
||||||
...object,
|
|
||||||
cache: {
|
|
||||||
voxelGroups: getObjectVoxelGroups(objectType, world.voxelTypes),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export function depopulateRuntimeObject(object: RuntimeObjectInstance, _world: World): ObjectInstance {
|
|
||||||
const { cache, ...result } = object;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import type { RuntimeScene, Scene, World } from "../../types";
|
|
||||||
import { depopulateRuntimeObject, populateRuntimeObject } from "./object";
|
|
||||||
|
|
||||||
export function populateRuntimeScene(scene: Scene, world: World): RuntimeScene {
|
|
||||||
return {
|
|
||||||
...scene,
|
|
||||||
objects: Object.fromEntries(Object.entries(scene.objects).map(([id, obj]) => [id, populateRuntimeObject(obj, world)])),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function depopulateRuntimeScene(scene: RuntimeScene, world: World): Scene {
|
|
||||||
return {
|
|
||||||
...scene,
|
|
||||||
objects: Object.fromEntries(Object.entries(scene.objects).map(([id, obj]) => [id, depopulateRuntimeObject(obj, world)])),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import type { RuntimeWorld, World } from "../../types";
|
|
||||||
import { depopulateRuntimeScene, populateRuntimeScene } from "./scene";
|
|
||||||
|
|
||||||
export function populateRuntimeWorld(world: World): RuntimeWorld {
|
|
||||||
return {
|
|
||||||
...world,
|
|
||||||
initialScene: populateRuntimeScene(world.initialScene, world),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function depopulateRuntimeWorld(world: RuntimeWorld): World {
|
|
||||||
return {
|
|
||||||
...world,
|
|
||||||
initialScene: depopulateRuntimeScene(world.initialScene, world),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue