Compare commits
No commits in common. "e56f92a4ac32d96a84c5528f08448c55d6efbc99" and "e520eb07c5a050029567f83bc91582809d9a962d" have entirely different histories.
e56f92a4ac
...
e520eb07c5
|
|
@ -32,7 +32,6 @@
|
|||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.2",
|
||||
"globals": "^17.6.0",
|
||||
"sass": "^1.100.0",
|
||||
"typescript": "~6.0.2",
|
||||
"typescript-eslint": "^8.59.2",
|
||||
"vite": "^8.0.12"
|
||||
|
|
|
|||
201
pnpm-lock.yaml
201
pnpm-lock.yaml
|
|
@ -56,7 +56,7 @@ importers:
|
|||
version: 19.2.3(@types/react@19.2.15)
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.2(vite@8.0.14(@types/node@24.12.4)(sass@1.100.0))
|
||||
version: 6.0.2(vite@8.0.14(@types/node@24.12.4))
|
||||
eslint:
|
||||
specifier: ^10.3.0
|
||||
version: 10.4.1
|
||||
|
|
@ -69,9 +69,6 @@ importers:
|
|||
globals:
|
||||
specifier: ^17.6.0
|
||||
version: 17.6.0
|
||||
sass:
|
||||
specifier: ^1.100.0
|
||||
version: 1.100.0
|
||||
typescript:
|
||||
specifier: ~6.0.2
|
||||
version: 6.0.3
|
||||
|
|
@ -80,7 +77,7 @@ importers:
|
|||
version: 8.60.0(eslint@10.4.1)(typescript@6.0.3)
|
||||
vite:
|
||||
specifier: ^8.0.12
|
||||
version: 8.0.14(@types/node@24.12.4)(sass@1.100.0)
|
||||
version: 8.0.14(@types/node@24.12.4)
|
||||
|
||||
packages:
|
||||
|
||||
|
|
@ -259,94 +256,6 @@ packages:
|
|||
'@oxc-project/types@0.132.0':
|
||||
resolution: {integrity: sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==}
|
||||
|
||||
'@parcel/watcher-android-arm64@2.5.6':
|
||||
resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@parcel/watcher-darwin-arm64@2.5.6':
|
||||
resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@parcel/watcher-darwin-x64@2.5.6':
|
||||
resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@parcel/watcher-freebsd-x64@2.5.6':
|
||||
resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@parcel/watcher-linux-arm-glibc@2.5.6':
|
||||
resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm-musl@2.5.6':
|
||||
resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-arm64-glibc@2.5.6':
|
||||
resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-arm64-musl@2.5.6':
|
||||
resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-linux-x64-glibc@2.5.6':
|
||||
resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@parcel/watcher-linux-x64-musl@2.5.6':
|
||||
resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@parcel/watcher-win32-arm64@2.5.6':
|
||||
resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@parcel/watcher-win32-ia32@2.5.6':
|
||||
resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@parcel/watcher-win32-x64@2.5.6':
|
||||
resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@parcel/watcher@2.5.6':
|
||||
resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
'@react-three/drei@10.7.7':
|
||||
resolution: {integrity: sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==}
|
||||
peerDependencies:
|
||||
|
|
@ -656,10 +565,6 @@ packages:
|
|||
caniuse-lite@1.0.30001793:
|
||||
resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==}
|
||||
|
||||
chokidar@5.0.0:
|
||||
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
|
||||
convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
||||
|
|
@ -843,9 +748,6 @@ packages:
|
|||
immediate@3.0.6:
|
||||
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
|
||||
|
||||
immutable@5.1.6:
|
||||
resolution: {integrity: sha512-q1swsS8K7L8usSHuOqF2TAoCCkonYz0SG38wLAggaa4Wml70zixIvt2ql4coQ2C2B3hTjltJry4r6bULwgAXLQ==}
|
||||
|
||||
imurmurhash@0.1.4:
|
||||
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
|
||||
engines: {node: '>=0.8.19'}
|
||||
|
|
@ -1031,9 +933,6 @@ packages:
|
|||
natural-compare@1.4.0:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
|
||||
node-addon-api@7.1.1:
|
||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||
|
||||
node-releases@2.0.46:
|
||||
resolution: {integrity: sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -1172,10 +1071,6 @@ packages:
|
|||
resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
readdirp@5.0.0:
|
||||
resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
|
||||
require-from-string@2.0.2:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -1185,11 +1080,6 @@ packages:
|
|||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
|
||||
sass@1.100.0:
|
||||
resolution: {integrity: sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
hasBin: true
|
||||
|
||||
scheduler@0.27.0:
|
||||
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
|
||||
|
||||
|
|
@ -1625,67 +1515,6 @@ snapshots:
|
|||
|
||||
'@oxc-project/types@0.132.0': {}
|
||||
|
||||
'@parcel/watcher-android-arm64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-darwin-arm64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-darwin-x64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-freebsd-x64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm-glibc@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm-musl@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm64-glibc@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-arm64-musl@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-x64-glibc@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-linux-x64-musl@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-win32-arm64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-win32-ia32@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-win32-x64@2.5.6':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher@2.5.6':
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
is-glob: 4.0.3
|
||||
node-addon-api: 7.1.1
|
||||
picomatch: 4.0.4
|
||||
optionalDependencies:
|
||||
'@parcel/watcher-android-arm64': 2.5.6
|
||||
'@parcel/watcher-darwin-arm64': 2.5.6
|
||||
'@parcel/watcher-darwin-x64': 2.5.6
|
||||
'@parcel/watcher-freebsd-x64': 2.5.6
|
||||
'@parcel/watcher-linux-arm-glibc': 2.5.6
|
||||
'@parcel/watcher-linux-arm-musl': 2.5.6
|
||||
'@parcel/watcher-linux-arm64-glibc': 2.5.6
|
||||
'@parcel/watcher-linux-arm64-musl': 2.5.6
|
||||
'@parcel/watcher-linux-x64-glibc': 2.5.6
|
||||
'@parcel/watcher-linux-x64-musl': 2.5.6
|
||||
'@parcel/watcher-win32-arm64': 2.5.6
|
||||
'@parcel/watcher-win32-ia32': 2.5.6
|
||||
'@parcel/watcher-win32-x64': 2.5.6
|
||||
optional: true
|
||||
|
||||
'@react-three/drei@10.7.7(@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))(@types/react@19.2.15)(@types/three@0.184.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(three@0.184.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.7
|
||||
|
|
@ -1934,10 +1763,10 @@ snapshots:
|
|||
'@use-gesture/core': 10.3.1
|
||||
react: 19.2.6
|
||||
|
||||
'@vitejs/plugin-react@6.0.2(vite@8.0.14(@types/node@24.12.4)(sass@1.100.0))':
|
||||
'@vitejs/plugin-react@6.0.2(vite@8.0.14(@types/node@24.12.4))':
|
||||
dependencies:
|
||||
'@rolldown/pluginutils': 1.0.1
|
||||
vite: 8.0.14(@types/node@24.12.4)(sass@1.100.0)
|
||||
vite: 8.0.14(@types/node@24.12.4)
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.16.0):
|
||||
dependencies:
|
||||
|
|
@ -1985,10 +1814,6 @@ snapshots:
|
|||
|
||||
caniuse-lite@1.0.30001793: {}
|
||||
|
||||
chokidar@5.0.0:
|
||||
dependencies:
|
||||
readdirp: 5.0.0
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
cross-env@7.0.3:
|
||||
|
|
@ -2161,8 +1986,6 @@ snapshots:
|
|||
|
||||
immediate@3.0.6: {}
|
||||
|
||||
immutable@5.1.6: {}
|
||||
|
||||
imurmurhash@0.1.4: {}
|
||||
|
||||
install@0.13.0: {}
|
||||
|
|
@ -2297,9 +2120,6 @@ snapshots:
|
|||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
node-addon-api@7.1.1:
|
||||
optional: true
|
||||
|
||||
node-releases@2.0.46: {}
|
||||
|
||||
npm@11.16.0: {}
|
||||
|
|
@ -2359,8 +2179,6 @@ snapshots:
|
|||
|
||||
react@19.2.6: {}
|
||||
|
||||
readdirp@5.0.0: {}
|
||||
|
||||
require-from-string@2.0.2: {}
|
||||
|
||||
rolldown@1.0.2:
|
||||
|
|
@ -2384,14 +2202,6 @@ snapshots:
|
|||
'@rolldown/binding-win32-arm64-msvc': 1.0.2
|
||||
'@rolldown/binding-win32-x64-msvc': 1.0.2
|
||||
|
||||
sass@1.100.0:
|
||||
dependencies:
|
||||
chokidar: 5.0.0
|
||||
immutable: 5.1.6
|
||||
source-map-js: 1.2.1
|
||||
optionalDependencies:
|
||||
'@parcel/watcher': 2.5.6
|
||||
|
||||
scheduler@0.27.0: {}
|
||||
|
||||
semver@6.3.1: {}
|
||||
|
|
@ -2504,7 +2314,7 @@ snapshots:
|
|||
|
||||
uuid@14.0.0: {}
|
||||
|
||||
vite@8.0.14(@types/node@24.12.4)(sass@1.100.0):
|
||||
vite@8.0.14(@types/node@24.12.4):
|
||||
dependencies:
|
||||
lightningcss: 1.32.0
|
||||
picomatch: 4.0.4
|
||||
|
|
@ -2514,7 +2324,6 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@types/node': 24.12.4
|
||||
fsevents: 2.3.3
|
||||
sass: 1.100.0
|
||||
|
||||
webgl-constants@1.1.1: {}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
allowBuilds:
|
||||
'@parcel/watcher': set this to true or false
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import { ThreeView } from './components/ThreeView';
|
||||
import './App.scss';
|
||||
import './App.css';
|
||||
import { Toolbar } from './components/Toolbar';
|
||||
|
||||
export const App = function () {
|
||||
return (
|
||||
<>
|
||||
<ThreeView />
|
||||
<Toolbar />
|
||||
<ThreeView />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { observer } from "mobx-react-lite";
|
||||
import type { Character, Scene } from "../types";
|
||||
import { state } from "../state";
|
||||
|
||||
export const CharacterView = observer(function ({ character }: { character: Character }) {
|
||||
export const CharacterView = observer(function () {
|
||||
const character = state.world.currentScene.character;
|
||||
|
||||
return <mesh position={character.position} rotation={character.look}>
|
||||
<boxGeometry args={[0.8, 0.8, 0.8]} />
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
import { observer } from "mobx-react-lite";
|
||||
import { SceneView } from "./SceneView";
|
||||
import { state } from "../state";
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
|
||||
export const GameView = observer(function () {
|
||||
const game = state.game;
|
||||
|
||||
useFrame((_, delta) => {
|
||||
state.game?.tick(delta);
|
||||
});
|
||||
|
||||
if (!game)
|
||||
return null;
|
||||
|
||||
return <SceneView scene={game.scene} />;
|
||||
});
|
||||
|
|
@ -5,7 +5,7 @@ import type { Group } from "three";
|
|||
import { Edges, TransformControls } from "@react-three/drei";
|
||||
import type { ThreeEvent } from "@react-three/fiber";
|
||||
import { state } from "../state";
|
||||
import { nextSelectionEditMode } from "../state/worldEditorState";
|
||||
import { nextSelectionEditMode } from "../state/worldEditor";
|
||||
import type { R3 } from "../types/3d";
|
||||
|
||||
type ObjectViewProps = {
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
import { useLayoutEffect, useRef } from 'react';
|
||||
import type React from 'react';
|
||||
import { useThree } from '@react-three/fiber';
|
||||
import { OrbitControls } from '@react-three/drei';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { state } from '../state';
|
||||
import { SceneView } from './SceneView';
|
||||
import type { Pos3 } from '../types/3d';
|
||||
|
||||
const CameraSync = observer(function ({ camera }: { camera: Pos3 }) {
|
||||
const { camera: threeCamera } = useThree();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
threeCamera.position.set(camera.position[0], camera.position[1], camera.position[2]);
|
||||
threeCamera.rotation.set(camera.look[0], camera.look[1], camera.look[2]);
|
||||
threeCamera.updateProjectionMatrix();
|
||||
});
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
export const SceneEditorView = observer(function () {
|
||||
const controlsRef = useRef<React.ComponentRef<typeof OrbitControls>>(null);
|
||||
|
||||
const handleEnd = () => {
|
||||
const controls = controlsRef.current;
|
||||
if (!controls)
|
||||
return;
|
||||
const [x, y, z] = controls.object.rotation.toArray();
|
||||
state.worldEditor.setCamera({
|
||||
position: controls.object.position.toArray(),
|
||||
look: [x, y, z],
|
||||
});
|
||||
};
|
||||
|
||||
return (<>
|
||||
<OrbitControls
|
||||
ref={controlsRef}
|
||||
enabled={!state.worldEditor.isDragging}
|
||||
onEnd={handleEnd}
|
||||
makeDefault
|
||||
enableDamping={false}
|
||||
/>
|
||||
<CameraSync camera={state.worldEditor.camera} />
|
||||
<SceneView scene={state.worldEditor.scene} />
|
||||
</>);
|
||||
});
|
||||
|
|
@ -13,6 +13,6 @@ export const SceneView = observer(function ({ scene }: SceneViewProps) {
|
|||
<directionalLight position={[5, 5, 5]} intensity={1} />
|
||||
{Object.values(scene.objects).map((obj) =>
|
||||
<ObjectView key={obj.id} object={obj} />)}
|
||||
<CharacterView character={scene.character} />
|
||||
<CharacterView />
|
||||
</>);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,37 +1,17 @@
|
|||
import { Canvas } from '@react-three/fiber';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { state } from '../state';
|
||||
import { GameView } from './GameView';
|
||||
import { SceneEditorView } from './SceneEditorView';
|
||||
|
||||
const IconStop = () => <svg viewBox="0 0 14 14"><rect x="2" y="2" width="10" height="10" fill="currentColor" /></svg>;
|
||||
const IconPause = () => <svg viewBox="0 0 14 14"><rect x="2" y="2" width="4" height="10" fill="currentColor" /><rect x="8" y="2" width="4" height="10" fill="currentColor" /></svg >;
|
||||
const IconPlay = () => <svg viewBox="0 0 14 14"><polygon points="3,1 13,7 3,13" fill="currentColor" /></svg>;
|
||||
import { WorldView } from './WorldView';
|
||||
|
||||
export const ThreeView = observer(function () {
|
||||
const isGame = state.isGamePlaying;
|
||||
return (
|
||||
<div style={{ position: 'relative', width: '800px', height: '600px', border: '1px solid #333', borderRadius: '8px', overflow: 'hidden' }}>
|
||||
<div style={{ width: '800px', height: '600px', border: '1px solid #333', borderRadius: '8px', overflow: 'hidden' }}>
|
||||
<Canvas
|
||||
// camera={state.world.character.camera}
|
||||
camera={state.world.character.camera}
|
||||
onPointerMissed={() => state.worldEditor.resetSelectedObject()}
|
||||
>
|
||||
{isGame ? <GameView /> : <SceneEditorView />}
|
||||
<WorldView />
|
||||
</Canvas>
|
||||
<div style={{ position: 'absolute', top: 8, right: 8, display: 'flex', gap: 4 }}>
|
||||
{
|
||||
state.game
|
||||
? <>
|
||||
<button onClick={() => state.game!.stop()}><IconStop /></button>
|
||||
{
|
||||
state.game!.isPaused
|
||||
? <button onClick={() => state.game!.resume()}><IconPlay /></button>
|
||||
: <button onClick={() => state.game!.pause()}><IconPause /></button>
|
||||
}
|
||||
</>
|
||||
: <button onClick={() => state.world.play()}><IconPlay /></button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,6 +14,18 @@ export const Toolbar = observer(function () {
|
|||
}
|
||||
|
||||
return <div className="toolbar">
|
||||
{state.worldEditor.isEnabled && <div>EDITOR MODE</div>}
|
||||
{state.world.isPlaying
|
||||
? <>
|
||||
<button onClick={() => state.world.stop()}>Stop</button>
|
||||
{
|
||||
(state.world.data.state as RunningGameState).paused
|
||||
? <button onClick={() => state.world.play()}>Play</button>
|
||||
: <button onClick={() => state.world.pause()}>Pause</button>
|
||||
}
|
||||
</>
|
||||
: <button onClick={() => state.world.play()}>Play</button>
|
||||
}
|
||||
<button onClick={handleLoadMockWorld}>Load mock world</button>
|
||||
<button onClick={handleCloneTest1Object}>Clone test1</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
import { useLayoutEffect, useRef } from 'react';
|
||||
import type React from 'react';
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import { OrbitControls } from '@react-three/drei';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { state } from '../state';
|
||||
import { SceneView } from './SceneView';
|
||||
|
||||
const CameraSync = observer(function () {
|
||||
const { camera } = useThree();
|
||||
const cam = state.world.currentCamera; // MobX tracks this; re-renders on change
|
||||
|
||||
useLayoutEffect(() => {
|
||||
camera.position.set(cam.position[0], cam.position[1], cam.position[2]);
|
||||
camera.rotation.set(cam.look[0], cam.look[1], cam.look[2]);
|
||||
camera.updateProjectionMatrix();
|
||||
});
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
export const WorldView = observer(function () {
|
||||
const world = state.world;
|
||||
const controlsRef = useRef<React.ComponentRef<typeof OrbitControls>>(null);
|
||||
|
||||
useFrame((_, delta) => {
|
||||
world.tick(delta);
|
||||
});
|
||||
|
||||
const handleEnd = () => {
|
||||
const controls = controlsRef.current;
|
||||
if (!controls || world.isPlaying)
|
||||
return;
|
||||
const [x, y, z] = controls.object.rotation.toArray();
|
||||
state.worldEditor.setCamera({
|
||||
position: controls.object.position.toArray(),
|
||||
look: [x, y, z],
|
||||
});
|
||||
};
|
||||
|
||||
return (<>
|
||||
<OrbitControls
|
||||
ref={controlsRef}
|
||||
enabled={!world.isPlaying && !state.worldEditor.isDragging}
|
||||
onEnd={handleEnd}
|
||||
makeDefault
|
||||
enableDamping={false}
|
||||
/>
|
||||
<CameraSync />
|
||||
<SceneView scene={state.world.currentScene} />
|
||||
</>)
|
||||
});
|
||||
|
|
@ -15,8 +15,7 @@
|
|||
--heading: system-ui, 'Segoe UI', Roboto, sans-serif;
|
||||
--mono: ui-monospace, Consolas, monospace;
|
||||
|
||||
font: 16px/100% var(--sans);
|
||||
line-height: 1;
|
||||
font: 18px/145% var(--sans);
|
||||
letter-spacing: 0.18px;
|
||||
color-scheme: light dark;
|
||||
color: var(--text);
|
||||
|
|
@ -25,6 +24,10 @@
|
|||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
|
|
@ -63,17 +66,46 @@ body {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
display: inline-block;
|
||||
height: 1em;
|
||||
top: 0.125em;
|
||||
vertical-align: bottom;
|
||||
h1,
|
||||
h2 {
|
||||
font-family: var(--heading);
|
||||
font-weight: 500;
|
||||
color: var(--text-h);
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font: inherit;
|
||||
h1 {
|
||||
font-size: 56px;
|
||||
letter-spacing: -1.68px;
|
||||
margin: 32px 0;
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 36px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
}
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
line-height: 118%;
|
||||
letter-spacing: -0.24px;
|
||||
margin: 0 0 8px;
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code,
|
||||
.counter {
|
||||
font-family: var(--mono);
|
||||
display: inline-flex;
|
||||
border-radius: 4px;
|
||||
color: var(--text-h);
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 15px;
|
||||
line-height: 135%;
|
||||
padding: 4px 8px;
|
||||
background: var(--code-bg);
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { App } from './App.tsx'
|
||||
import './index.scss'
|
||||
import './index.css'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
import { makeAutoObservable } from "mobx";
|
||||
import type { WorldState } from "./world";
|
||||
import type { CameraProps } from "@react-three/fiber";
|
||||
|
||||
export class CharacterState {
|
||||
private readonly world: WorldState;
|
||||
|
||||
constructor(world: WorldState) {
|
||||
this.world = world;
|
||||
makeAutoObservable(
|
||||
this,
|
||||
{
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public get camera(): CameraProps {
|
||||
const cam = this.world.currentCamera;
|
||||
return {
|
||||
position: cam.position,
|
||||
fov: 50,
|
||||
rotation: cam.look,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
import { makeAutoObservable } from "mobx";
|
||||
import type { WorldState } from "./worldState";
|
||||
import type { RunningGameState, Scene } from "../types";
|
||||
import { clone } from "../utils";
|
||||
import { state } from "./rootState";
|
||||
import type { Pos3 } from "../types/3d";
|
||||
import type { CameraProps } from "@react-three/fiber";
|
||||
|
||||
export class GameState {
|
||||
private readonly world: WorldState;
|
||||
|
||||
constructor(world: WorldState) {
|
||||
this.world = world;
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
public get state(): RunningGameState {
|
||||
return this.world.data.state as RunningGameState;
|
||||
}
|
||||
|
||||
public get isPaused(): boolean {
|
||||
return this.state.paused;
|
||||
}
|
||||
|
||||
public get scene(): Scene {
|
||||
return this.state.scene;
|
||||
}
|
||||
|
||||
public get camera(): Pos3 {
|
||||
return this.scene.character;
|
||||
}
|
||||
|
||||
public get cameraAsThree(): CameraProps {
|
||||
const cam = this.camera;
|
||||
return {
|
||||
position: cam.position,
|
||||
fov: 50,
|
||||
rotation: cam.look,
|
||||
}
|
||||
}
|
||||
|
||||
public resume(): void {
|
||||
const state = clone(this.world.data.state) as RunningGameState;
|
||||
state.paused = false;
|
||||
this.world.data.state = state;
|
||||
}
|
||||
|
||||
public pause(): void {
|
||||
const state = clone(this.world.data.state) as RunningGameState;
|
||||
state.paused = true;
|
||||
this.world.data.state = state;
|
||||
}
|
||||
|
||||
public stop(): void {
|
||||
this.world.data.state = { playing: false };
|
||||
}
|
||||
|
||||
public tick(deltaTime: number): void {
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
export * from './rootState';
|
||||
export * from './root';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { makeAutoObservable } from "mobx";
|
||||
import { WorldState } from "./worldState";
|
||||
import { WorldEditorState } from "./worldEditorState";
|
||||
import { GameState } from "./gameState";
|
||||
import { WorldState } from "./world";
|
||||
import { WorldEditorState } from "./worldEditor";
|
||||
|
||||
export class RootState {
|
||||
public readonly world = new WorldState();
|
||||
|
|
@ -17,15 +16,6 @@ export class RootState {
|
|||
},
|
||||
);
|
||||
}
|
||||
|
||||
public get isGamePlaying(): boolean {
|
||||
return this.world.data.state.playing;
|
||||
}
|
||||
|
||||
public get game(): GameState | undefined {
|
||||
if (this.isGamePlaying)
|
||||
return new GameState(this.world);
|
||||
}
|
||||
}
|
||||
|
||||
export const state = new RootState();
|
||||
|
|
@ -1,15 +1,19 @@
|
|||
import { makeAutoObservable } from "mobx";
|
||||
import { WorldFactory } from "../model/worldFactory";
|
||||
import type { ObjectType, World } from "../types";
|
||||
import type { ObjectType, RunningGameState, Scene, World } from "../types";
|
||||
import { CharacterState } from "./character";
|
||||
import type { Pos3 } from "../types/3d";
|
||||
import { clone } from "../utils";
|
||||
import type { VoxelType } from "../types/voxel";
|
||||
import { state } from "./rootState";
|
||||
import { state } from "./root";
|
||||
import { DEFAULT_VOXEL_TYPES } from "../model/defaultVoxelTypes";
|
||||
import { wolf } from "../model/objectPrefabs/wolf";
|
||||
import { clone } from "../utils";
|
||||
|
||||
export class WorldState {
|
||||
public data: World = WorldFactory.create();
|
||||
|
||||
public character = new CharacterState(this);
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
|
@ -71,17 +75,35 @@ export class WorldState {
|
|||
WorldFactory.save(this.data);
|
||||
}
|
||||
|
||||
public getObjectTypeById(id: string): ObjectType | undefined {
|
||||
return this.data.objectTypes[id];
|
||||
public get isPlaying(): boolean {
|
||||
return this.data.state.playing;
|
||||
}
|
||||
|
||||
public getVoxelTypeById(id: string): VoxelType | undefined {
|
||||
return this.data.voxelTypes[id];
|
||||
public get currentScene(): Scene {
|
||||
return this.isPlaying
|
||||
? (this.data.state as RunningGameState).scene
|
||||
: this.data.initialScene;
|
||||
}
|
||||
|
||||
public get currentCamera(): Pos3 {
|
||||
return this.isPlaying
|
||||
? (this.data.state as RunningGameState).scene.character
|
||||
: this.data.editorCamera;
|
||||
}
|
||||
|
||||
public tick(_delta: number) {
|
||||
if (!this.isPlaying)
|
||||
return;
|
||||
|
||||
//TODO
|
||||
}
|
||||
|
||||
public play(): void {
|
||||
if (state.game)
|
||||
state.game.resume();
|
||||
if (this.isPlaying) {
|
||||
const state = clone(this.data.state) as RunningGameState;
|
||||
state.paused = false;
|
||||
this.data.state = state;
|
||||
}
|
||||
else {
|
||||
this.data.state = {
|
||||
playing: true,
|
||||
|
|
@ -89,7 +111,29 @@ export class WorldState {
|
|||
time: 0,
|
||||
scene: clone(this.data.initialScene),
|
||||
}
|
||||
state.worldEditor.resetSelectedObject();
|
||||
}
|
||||
state.worldEditor.resetSelectedObject();
|
||||
}
|
||||
|
||||
public pause(): void {
|
||||
if (!this.isPlaying)
|
||||
return;
|
||||
const state = clone(this.data.state) as RunningGameState;
|
||||
state.paused = true;
|
||||
this.data.state = state;
|
||||
}
|
||||
|
||||
public stop(): void {
|
||||
if (!this.isPlaying)
|
||||
return;
|
||||
this.data.state = { playing: false };
|
||||
}
|
||||
|
||||
public getObjectTypeById(id: string): ObjectType | undefined {
|
||||
return this.data.objectTypes[id];
|
||||
}
|
||||
|
||||
public getVoxelTypeById(id: string): VoxelType | undefined {
|
||||
return this.data.voxelTypes[id];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
import { makeAutoObservable } from "mobx";
|
||||
import type { WorldState } from "./worldState";
|
||||
import type { WorldState } from "./world";
|
||||
import type { ObjectInstance, Scene, World } from "../types";
|
||||
import { createObjectInstance } from "../utils/object";
|
||||
import { randomId } from "../utils";
|
||||
import type { Pos3, R3, V3 } from "../types/3d";
|
||||
import { state } from "./rootState";
|
||||
|
||||
export const SelectionEditModeEnum = [
|
||||
'translate',
|
||||
|
|
@ -35,7 +34,7 @@ export class WorldEditorState {
|
|||
}
|
||||
|
||||
public get isEnabled(): boolean {
|
||||
return !state.isGamePlaying;
|
||||
return !this.world.isPlaying;
|
||||
}
|
||||
|
||||
public setCamera(value: Pos3): void {
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
import type { Pos3 } from "./3d";
|
||||
|
||||
export type Character = Pos3 & {
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import type { Pos3 } from "./3d";
|
||||
|
||||
export type CharacterState = Pos3 & {
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ export * from './scene';
|
|||
export * from './world';
|
||||
export * from './gameRules';
|
||||
export * from './gameState';
|
||||
export * from './character';
|
||||
export * from './characterState';
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { Character } from "./character";
|
||||
import type { CharacterState } from "./characterState";
|
||||
import type { ObjectInstance } from "./object";
|
||||
|
||||
export type Scene = {
|
||||
character: Character;
|
||||
character: CharacterState;
|
||||
objects: Record<string, ObjectInstance>;
|
||||
}
|
||||
Loading…
Reference in New Issue