Compare commits
No commits in common. "90162d47cf19f755c64e5e2c8911b9250e234865" and "409664e67dfa016aa7657c90f0a7c9df9adebe25" have entirely different histories.
90162d47cf
...
409664e67d
|
|
@ -2,7 +2,7 @@ import { BrowserRouter, Link, Outlet, Route, Routes, useNavigate, useParams } fr
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { reaction } from 'mobx';
|
import { reaction } from 'mobx';
|
||||||
import { ThreeView } from './components/ThreeView';
|
import { ThreeView } from './components/ThreeView';
|
||||||
import { Panel } from './components/Panel';
|
import { Toolbar } from './components/Toolbar';
|
||||||
import { state } from './state';
|
import { state } from './state';
|
||||||
import './App.scss';
|
import './App.scss';
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ function EditorLayout() {
|
||||||
<>
|
<>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<ThreeView />
|
<ThreeView />
|
||||||
<Panel side="left" />
|
<Toolbar />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
.panel {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
width: 30%;
|
|
||||||
height: 100vh;
|
|
||||||
padding: 10px;
|
|
||||||
z-index: 10;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
&.left {
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.right {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&>.container {
|
|
||||||
flex: 1;
|
|
||||||
background: rgba(0, 0, 0, 0.3);
|
|
||||||
backdrop-filter: blur(4px);
|
|
||||||
padding: 4px;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.25em;
|
|
||||||
|
|
||||||
&>*:not(.debug):not(.gap) {
|
|
||||||
pointer-events: all;
|
|
||||||
}
|
|
||||||
|
|
||||||
&>.gap {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&>.debug {
|
|
||||||
border: 1px solid #ffffff20;
|
|
||||||
padding: 2px;
|
|
||||||
font-size: 75%;
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5em;
|
|
||||||
|
|
||||||
&>.chart {
|
|
||||||
display: inline;
|
|
||||||
|
|
||||||
& div {
|
|
||||||
display: inline;
|
|
||||||
position: relative !important;
|
|
||||||
z-index: unset !important;
|
|
||||||
opacity: 1 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&>.metrics {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.25em 0.5em;
|
|
||||||
|
|
||||||
&>* {
|
|
||||||
white-space: pre;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
import './Panel.scss';
|
|
||||||
import { RenderInfoView } from "./RenderInfoView";
|
|
||||||
import { state } from "../state";
|
|
||||||
|
|
||||||
export type PanelProps = {
|
|
||||||
side?: 'left' | 'right';
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Panel = observer(function ({ side = 'left' }: PanelProps) {
|
|
||||||
|
|
||||||
function handleCloneTest1Object(): void {
|
|
||||||
state.worldEditor.addObjectCloneAtRandomPosition('test1');
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleLoadWorld(): void {
|
|
||||||
state.world.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleLoadMockWorld(): void {
|
|
||||||
state.world.loadMock();
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div className={`panel ${side}`}>
|
|
||||||
<div className="container">
|
|
||||||
<button onClick={handleLoadWorld}>Load</button>
|
|
||||||
<button onClick={handleLoadMockWorld}>Load mock world</button>
|
|
||||||
<button onClick={handleCloneTest1Object}>Clone test1</button>
|
|
||||||
<div className="gap" />
|
|
||||||
<div className="debug"><RenderInfoView /></div>
|
|
||||||
</div>
|
|
||||||
</div >
|
|
||||||
});
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
import { useEffect, useRef } from "react";
|
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
import { state } from "../state";
|
|
||||||
import { chartRef } from "./chartRef";
|
|
||||||
|
|
||||||
export const RenderInfoView = observer(function () {
|
|
||||||
const info = state.renderInfo;
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const el = containerRef.current;
|
|
||||||
if (!el) return;
|
|
||||||
el.appendChild(chartRef.current);
|
|
||||||
return () => { el.removeChild(chartRef.current); };
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return <>
|
|
||||||
<div className="chart" ref={containerRef} />
|
|
||||||
{info != null && (
|
|
||||||
<div className="metrics">
|
|
||||||
<span>draw: {info.calls}</span>
|
|
||||||
<span>tri: {info.triangles}</span>
|
|
||||||
<span>shaders: {info.shaders}</span>
|
|
||||||
<span>geometries: {info.geometries}</span>
|
|
||||||
<span>textures: {info.textures}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
});
|
|
||||||
|
|
@ -1,22 +1,19 @@
|
||||||
import { Canvas, useFrame, useThree } from '@react-three/fiber';
|
import { Canvas, useFrame, useThree } from '@react-three/fiber';
|
||||||
import { KeyboardControls, Stats } from '@react-three/drei';
|
import { KeyboardControls, Stats } from '@react-three/drei';
|
||||||
import { action } from 'mobx';
|
import { useRef } from 'react';
|
||||||
import { chartRef } from './chartRef';
|
|
||||||
|
|
||||||
function RenderInfoUpdater() {
|
function RenderInfoUpdater({ domRef }: { domRef: React.RefObject<HTMLDivElement | null> }) {
|
||||||
const { gl } = useThree();
|
const { gl } = useThree();
|
||||||
useFrame(action(() => {
|
useFrame(() => {
|
||||||
|
if (!domRef.current)
|
||||||
|
return;
|
||||||
|
|
||||||
const { calls, triangles } = gl.info.render;
|
const { calls, triangles } = gl.info.render;
|
||||||
const shaders = gl.info.programs?.length ?? 0;
|
const shaders = gl.info.programs?.length ?? 0;
|
||||||
const { geometries, textures } = gl.info.memory;
|
const { geometries, textures } = gl.info.memory;
|
||||||
state.setRenderInfo({
|
|
||||||
calls,
|
domRef.current.textContent = `draw: ${calls} | tri: ${triangles} | shaders: ${shaders} | geometries: ${geometries} | textures: ${textures}`;
|
||||||
triangles,
|
});
|
||||||
shaders,
|
|
||||||
geometries,
|
|
||||||
textures,
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,7 +21,6 @@ import { observer } from 'mobx-react-lite';
|
||||||
import { state } from '../state';
|
import { state } from '../state';
|
||||||
import { GameView } from './GameView';
|
import { GameView } from './GameView';
|
||||||
import { SceneEditorView } from './SceneEditorView';
|
import { SceneEditorView } from './SceneEditorView';
|
||||||
import type { RefObject } from 'react';
|
|
||||||
|
|
||||||
const IconStop = () => <svg viewBox="0 0 14 14"><rect x="2" y="2" width="10" height="10" fill="currentColor" /></svg>;
|
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 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 >;
|
||||||
|
|
@ -32,6 +28,7 @@ const IconPlay = () => <svg viewBox="0 0 14 14"><polygon points="3,1 13,7 3,13"
|
||||||
|
|
||||||
export const ThreeView = observer(function () {
|
export const ThreeView = observer(function () {
|
||||||
const isGame = state.isGamePlaying;
|
const isGame = state.isGamePlaying;
|
||||||
|
const infoRef = useRef<HTMLDivElement>(null);
|
||||||
return (
|
return (
|
||||||
<KeyboardControls map={[
|
<KeyboardControls map={[
|
||||||
{ name: 'forward', keys: ['ArrowUp', 'w', 'W'] },
|
{ name: 'forward', keys: ['ArrowUp', 'w', 'W'] },
|
||||||
|
|
@ -40,15 +37,21 @@ export const ThreeView = observer(function () {
|
||||||
{ name: 'right', keys: ['ArrowRight', 'd', 'D'] },
|
{ name: 'right', keys: ['ArrowRight', 'd', 'D'] },
|
||||||
{ name: 'jump', keys: ['Space'] },
|
{ name: 'jump', keys: ['Space'] },
|
||||||
]}>
|
]}>
|
||||||
<div style={{ position: 'fixed', inset: 0, overflow: 'hidden' }}>
|
<div style={{ position: 'relative', width: '800px', height: '600px', border: '1px solid #333', borderRadius: '8px', overflow: 'hidden' }}>
|
||||||
<Canvas
|
<Canvas
|
||||||
// camera={state.world.character.camera}
|
// camera={state.world.character.camera}
|
||||||
onPointerMissed={() => state.worldEditor.resetSelectedObject()}
|
onPointerMissed={() => state.worldEditor.resetSelectedObject()}
|
||||||
>
|
>
|
||||||
<Stats parent={chartRef as RefObject<HTMLElement>} />
|
<Stats />
|
||||||
<RenderInfoUpdater />
|
<RenderInfoUpdater domRef={infoRef} />
|
||||||
{isGame ? <GameView /> : <SceneEditorView />}
|
{isGame ? <GameView /> : <SceneEditorView />}
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
<div ref={infoRef} style={{
|
||||||
|
position: 'absolute', bottom: 8, left: 8,
|
||||||
|
color: 'white', fontSize: 11, fontFamily: 'monospace',
|
||||||
|
background: 'rgba(0,0,0,0.5)', padding: '2px 6px', borderRadius: 3,
|
||||||
|
pointerEvents: 'none',
|
||||||
|
}} />
|
||||||
<div style={{ position: 'absolute', top: 8, right: 8, display: 'flex', gap: 4 }}>
|
<div style={{ position: 'absolute', top: 8, right: 8, display: 'flex', gap: 4 }}>
|
||||||
{
|
{
|
||||||
state.game
|
state.game
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
|
import { state } from "../state";
|
||||||
|
|
||||||
|
export const Toolbar = observer(function () {
|
||||||
|
|
||||||
|
function handleCloneTest1Object(): void {
|
||||||
|
state.worldEditor.addObjectCloneAtRandomPosition('test1');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLoadWorld(): void {
|
||||||
|
state.world.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLoadMockWorld(): void {
|
||||||
|
state.world.loadMock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="toolbar">
|
||||||
|
<button onClick={handleLoadWorld}>Load</button>
|
||||||
|
<button onClick={handleLoadMockWorld}>Load mock world</button>
|
||||||
|
<button onClick={handleCloneTest1Object}>Clone test1</button>
|
||||||
|
</div>
|
||||||
|
});
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export const chartRef = { current: document.createElement('span') };
|
|
||||||
|
|
@ -48,9 +48,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#root {
|
#root {
|
||||||
width: 100%;
|
width: 1126px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
border-inline: 1px solid var(--border);
|
border-inline: 1px solid var(--border);
|
||||||
min-height: 100svh;
|
min-height: 100svh;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -58,10 +59,8 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overscroll-behavior: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,10 @@ import { WorldState } from "./worldState";
|
||||||
import { WorldEditorState } from "./worldEditorState";
|
import { WorldEditorState } from "./worldEditorState";
|
||||||
import { GameState } from "./gameState";
|
import { GameState } from "./gameState";
|
||||||
|
|
||||||
export type RenderInfo = {
|
|
||||||
calls: number,
|
|
||||||
triangles: number,
|
|
||||||
shaders: number,
|
|
||||||
geometries: number,
|
|
||||||
textures: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class RootState {
|
export class RootState {
|
||||||
public readonly world = new WorldState();
|
public readonly world = new WorldState();
|
||||||
public readonly worldEditor: WorldEditorState;
|
public readonly worldEditor: WorldEditorState;
|
||||||
public game: GameState | undefined;
|
public game: GameState | undefined;
|
||||||
public renderInfo: RenderInfo | undefined;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.worldEditor = new WorldEditorState(this.world);
|
this.worldEditor = new WorldEditorState(this.world);
|
||||||
|
|
@ -46,10 +37,6 @@ export class RootState {
|
||||||
this.game.pause();
|
this.game.pause();
|
||||||
this.game = undefined;
|
this.game = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setRenderInfo(value: RenderInfo | undefined) {
|
|
||||||
this.renderInfo = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const state = new RootState();
|
export const state = new RootState();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue