Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Star map editor #216

Merged
merged 8 commits into from
Feb 11, 2022
11 changes: 10 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@
"@mdx-js/mdx": "^2.0.0-next.9",
"@mdx-js/react": "^1.6.22",
"@msgpack/msgpack": "^2.7.1",
"@react-three/drei": "^8.6.3",
"@react-three/fiber": "^7.0.25",
"@seregpie/three.text-texture": "^3.2.1",
"@tailwindcss/typography": "^0.5.0",
"@thorium-sim/react-mde": "^11.5.0",
"@uiw/react-monacoeditor": "^3.4.5",
"@use-gesture/react": "^10.2.4",
"color": "^4.1.0",
"dot-beat-time": "^1.2.1",
"eventemitter3": "^4.0.7",
"flexsearch": "^0.7.21",
"gl-matrix": "^3.4.3",
"gray-matter": "^4.0.3",
"lodash.debounce": "^4.0.8",
"lottie-web": "^5.8.1",
Expand All @@ -44,12 +49,15 @@
"remark-mdx-images": "^1.0.3",
"remark-prism": "^1.3.6",
"remark-rehype": "^10.1.0",
"rng": "^0.2.2",
"three": "^0.135.0",
"valtio": "^1.2.7",
"vite-plugin-mdx": "^3.5.10",
"vite-tsconfig-paths": "^3.3.17"
"vite-tsconfig-paths": "^3.3.17",
"zustand": "^3.6.9"
},
"devDependencies": {
"@react-three/test-renderer": "^7.0.25",
"@types/color": "^3.0.2",
"@types/flexsearch": "^0.7.2",
"@types/lodash.debounce": "^4.0.6",
Expand All @@ -61,6 +69,7 @@
"autoprefixer": "^10.4.0",
"daisyui": "^1.16.5",
"hast-util-to-estree": "^2.0.2",
"jest-canvas-mock": "^2.3.1",
"markdown-it": "^12.3.0",
"postcss": "^8.4.5",
"postcss-cli": "^9.1.0",
Expand Down
164 changes: 164 additions & 0 deletions client/src/components/Starmap/SystemMarker/SystemCircle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import {MeshProps, useFrame} from "@react-three/fiber";
import {netSend} from "client/src/context/netSend";
import useObjectDrag from "client/src/hooks/useObjectDrag";
import * as React from "react";
import {useCallback} from "react";
import {CanvasTexture, Group, Vector3} from "three";
import {useStarmapStore} from "../starmapStore";

const size = 50;
const lineWidth = 0.07;
export const DraggableSystemCircle: React.FC<
{
hoveringDirection: React.MutableRefObject<number>;
systemId: string;
parentObject: React.MutableRefObject<Group>;
} & MeshProps
> = ({parentObject: parent, hoveringDirection, systemId, ...props}) => {
const bind = useObjectDrag(parent, {
onMouseUp: (position: Vector3) => {
useStarmapStore.getState().setCameraControlsEnabled(true);
const pluginId = useStarmapStore.getState().pluginId;
if (!pluginId) return;
netSend("pluginSolarSystemUpdate", {
pluginId,
solarSystemId: systemId,
position,
});
},
onMouseDown: () => {
useStarmapStore.setState({
selectedObjectId: systemId,
// selectedPosition: parent.current.position,
// scaledSelectedPosition: parent.current.position,
});
useStarmapStore.getState().setCameraControlsEnabled(false);
},
onDrag: (position: Vector3) => {
useStarmapStore.setState({
hoveredPosition: [position.x, position.y, position.z],
});
},
});
const setSystemId = useStarmapStore(s => s.setSystemId);

return (
<SystemCircle
systemId={systemId}
hoveringDirection={hoveringDirection}
{...(bind() as any)}
{...props}
onPointerOver={e => {
props?.onPointerOver?.(e);
const position = parent.current.position;
useStarmapStore.setState({
hoveredPosition: [position.x, position.y, position.z],
});
}}
onPointerOut={e => {
props?.onPointerOut?.(e);
useStarmapStore.setState({
hoveredPosition: null,
});
}}
onDoubleClick={() => {
setSystemId(systemId);
}}
/>
);
};

const SystemCircle: React.FC<
{
systemId: string;
hoveringDirection: React.MutableRefObject<number>;
} & MeshProps
> = ({systemId, hoveringDirection, ...props}) => {
const ctx = React.useMemo(() => {
const canvas = document.createElement("canvas");

canvas.height = size;
canvas.width = size;

const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
return ctx;
}, []);

const drawRadius = useCallback(
function drawRadius(endArc = 360) {
const selectedObjectId = useStarmapStore.getState().selectedObjectId;
const isSelected = systemId === selectedObjectId;
ctx.clearRect(0, 0, size, size);

ctx.lineWidth = size / (1 / lineWidth);
ctx.strokeStyle = isSelected ? "white" : "rgba(0,255,255,0.2)";
ctx.beginPath();
ctx.arc(
size / 2,
size / 2,
size / 2 - size / (1 / lineWidth),
0,
Math.PI * 2
);
ctx.stroke();
ctx.strokeStyle = "cyan";
ctx.beginPath();
ctx.arc(
size / 2,
size / 2,
size / 2 - size / (1 / lineWidth),
-Math.PI / 2,
endArc
);
ctx.stroke();
},
[ctx, systemId]
);
const radius = React.useRef(0);
const selected = React.useRef(false);

const texture = React.useMemo(() => {
const texture = new CanvasTexture(ctx.canvas);
texture.needsUpdate = true;
return texture;
}, [ctx]);

useFrame(() => {
const selectedObjectId = useStarmapStore.getState().selectedObjectId;
const isSelected = systemId === selectedObjectId;
if (isSelected) {
selected.current = true;
}
if (selected.current && !isSelected) {
selected.current = false;
drawRadius(radius.current - Math.PI / 2);
texture.needsUpdate = true;
}
if (hoveringDirection.current === 0) return;
radius.current += 0.5 * hoveringDirection.current;
if (radius.current >= Math.PI * 2) {
hoveringDirection.current = 0;
radius.current = Math.PI * 2;
}
if (radius.current <= 0) {
hoveringDirection.current = 0;
radius.current = 0;
}

drawRadius(radius.current - Math.PI / 2);
texture.needsUpdate = true;
});

React.useEffect(() => {
drawRadius(radius.current - Math.PI / 2);
texture.needsUpdate = true;
}, [drawRadius, texture]);

return (
<mesh {...props}>
<planeBufferGeometry args={[4, 4, 4]} attach="geometry" />
<meshBasicMaterial attach="material" map={texture} transparent />
</mesh>
);
};
export default SystemCircle;
87 changes: 87 additions & 0 deletions client/src/components/Starmap/SystemMarker/SystemLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from "react";
import TextTexture from "@seregpie/three.text-texture";
import {AdditiveBlending, Sprite} from "three";
import {useFrame} from "@react-three/fiber";
import {useStarmapStore} from "../starmapStore";

const SystemLabel: React.FC<{
systemId: string;
name: string;
color?: string;
scale?: number;
hoveringDirection: React.MutableRefObject<number>;
}> = ({
systemId,
color = "rgb(0,255,255)",
scale = 3 / 128,
name,
hoveringDirection,
}) => {
React.useEffect(() => {
if (text.current) {
text.current.material.opacity = 0.5;
}
}, []);

const textTexture = React.useMemo(() => {
let texture = new TextTexture({
color,
fontFamily: 'Electrolize, "Gill Sans", sans-serif',
fontSize: 128,
alignment: "right",
text: name,
});
texture.redraw();
return texture;
}, [name, color]);

const text = React.useRef<Sprite>();
const selected = React.useRef(false);
useFrame(({camera}) => {
const selectedObjectId = useStarmapStore.getState().selectedObjectId;
const isSelected = systemId === selectedObjectId;
if (text.current) {
if (isSelected) {
selected.current = true;
text.current.material.opacity = 1;
return;
}

if (hoveringDirection.current !== 0) {
text.current.material.opacity = Math.max(
0.5,
Math.min(
1,
text.current.material.opacity + 0.05 * hoveringDirection.current
)
);
} else if (selected.current) {
text.current.material.opacity = 0.5;
}
}
});

const spriteWidth = textTexture.width / textTexture.height;
return (
<mesh
scale={[scale, scale, scale]}
position={[-spriteWidth * (115 * scale) - 2, 0, 0]}
ref={text}
renderOrder={1000}
>
<planeBufferGeometry
args={[textTexture.width, textTexture.height, 1]}
attach="geometry"
/>
<meshBasicMaterial
attach="material"
map={textTexture}
transparent
blending={AdditiveBlending}
depthTest={false}
depthWrite={false}
/>
</mesh>
);
};
export default SystemLabel;
75 changes: 75 additions & 0 deletions client/src/components/Starmap/SystemMarker/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React from "react";
import {Group} from "three";
import SystemLabel from "./SystemLabel";
import SystemCircle, {DraggableSystemCircle} from "./SystemCircle";
import {MeshProps, useFrame} from "@react-three/fiber";
const SystemMarker: React.FC<
{
systemId: string;
name: string;
position: [number, number, number];
draggable?: boolean;
onPointerDown?: () => void;
} & MeshProps
> = ({systemId, name, position, draggable, ...props}) => {
const group = React.useRef<Group>(new Group());

const direction = React.useRef(0);

useFrame(({camera}) => {
const zoom = group.current?.position
? camera.position.distanceTo(group.current?.position)
: 1;
let zoomedScale = (zoom / 2) * 0.015;
group.current?.scale.set(zoomedScale, zoomedScale, zoomedScale);
group.current?.quaternion.copy(camera.quaternion);
});

return (
<>
<group position={position} ref={group}>
{draggable ? (
<DraggableSystemCircle
systemId={systemId}
hoveringDirection={direction}
parentObject={group}
{...props}
onPointerOver={e => {
props?.onPointerOver?.(e);
direction.current = 1;
document.body.style.cursor = "pointer";
}}
onPointerOut={e => {
props?.onPointerOut?.(e);
direction.current = -1;
document.body.style.cursor = "auto";
}}
/>
) : (
<SystemCircle
systemId={systemId}
hoveringDirection={direction}
{...props}
onPointerOver={e => {
props?.onPointerOver?.(e);
direction.current = 1;
document.body.style.cursor = "pointer";
}}
onPointerOut={e => {
props?.onPointerOut?.(e);
direction.current = -1;
document.body.style.cursor = "auto";
}}
/>
)}
<SystemLabel
systemId={systemId}
hoveringDirection={direction}
name={name}
/>
</group>
</>
);
};

export default SystemMarker;
26 changes: 26 additions & 0 deletions client/src/components/Starmap/starmapStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {Vector3} from "three";
import create from "zustand";

interface StarmapStore {
viewingMode: "editor" | "core" | "station" | "viewscreen";
skyboxKey: string;
selectedObjectId: string | null;
cameraControlsEnabled: boolean;
setCameraControlsEnabled: (enabled: boolean) => void;
pluginId: string | null;
systemId: string | null;
setSystemId: (systemId: string | null) => void;
hoveredPosition: [number, number, number] | null;
}
export const useStarmapStore = create<StarmapStore>(set => ({
skyboxKey: "blank",
viewingMode: "editor",
selectedObjectId: null,
cameraControlsEnabled: true,
setCameraControlsEnabled: (enabled: boolean) =>
set({cameraControlsEnabled: enabled}),
pluginId: null,
systemId: null,
setSystemId: (systemId: string | null) => set({systemId: systemId}),
hoveredPosition: null,
}));
Loading