Skip to content

Commit

Permalink
feat(Navigation): Adds the navigation card
Browse files Browse the repository at this point in the history
Makes it possible to browse interstellar space and solar systems.
  • Loading branch information
alexanderson1993 committed Jul 16, 2022
1 parent 296db90 commit 272626a
Show file tree
Hide file tree
Showing 20 changed files with 498 additions and 46 deletions.
35 changes: 35 additions & 0 deletions client/src/cards/Navigation/InterstellarWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {useGetStarmapStore} from "client/src/components/Starmap/starmapStore";
import {useNetRequest} from "client/src/context/useNetRequest";
import {InterstellarMap} from "client/src/components/Starmap/InterstellarMap";
import SystemMarker from "client/src/components/Starmap/SystemMarker";

export function InterstellarWrapper() {
const useStarmapStore = useGetStarmapStore();
// This netRequest comes from the starmap core.
const starmapSystems = useNetRequest("starmapSystems");

return (
<InterstellarMap>
{starmapSystems.map(sys =>
sys.components.position && sys.components.identity ? (
<SystemMarker
key={sys.id}
systemId={sys.id}
position={
[
sys.components.position.x,
sys.components.position.y,
sys.components.position.z,
] as [number, number, number]
}
name={sys.components.identity.name}
onClick={() => useStarmapStore.setState({selectedObjectId: sys.id})}
onDoubleClick={() =>
useStarmapStore.getState().setCurrentSystem(sys.id)
}
/>
) : null
)}
</InterstellarMap>
);
}
51 changes: 51 additions & 0 deletions client/src/cards/Navigation/MapControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {useGetStarmapStore} from "client/src/components/Starmap/starmapStore";
import Button from "@thorium/ui/Button";

export function MapControls() {
const useStarmapStore = useGetStarmapStore();
const systemId = useStarmapStore(state => state.currentSystem);

return (
<div className="self-end max-w-sm space-y-2">
<ZoomSliderComp />
{systemId !== null && (
<Button
className="w-full btn-primary pointer-events-auto"
onClick={() => {
useStarmapStore.setState({
currentSystem: null,
selectedObjectId: null,
});
}}
>
Interstellar View
</Button>
)}
<Button className="w-full btn-warning pointer-events-auto">
Recenter
</Button>
</div>
);
}

export const ZoomSliderComp = () => {
const useStarmapStore = useGetStarmapStore();
const cameraZoom = useStarmapStore(store => store.cameraVerticalDistance);
const cameraControls = useStarmapStore(store => store.cameraControls);
const maxDistance = cameraControls?.current?.maxDistance || 30000000000;
const minDistance = cameraControls?.current?.minDistance || 10000;
return (
<div>
<p className="text-xl">Zoom:</p>
<ZoomSlider
value={cameraZoom}
setValue={val => {
useStarmapStore.getState().cameraControls?.current?.dollyTo(val);
}}
zoomMin={minDistance}
zoomMax={maxDistance}
step={0.01}
/>
</div>
);
};
115 changes: 115 additions & 0 deletions client/src/cards/Navigation/SolarSystemWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {useGetStarmapStore} from "client/src/components/Starmap/starmapStore";
import {useNetRequest} from "client/src/context/useNetRequest";
import {useRef} from "react";
import {SolarSystemMap} from "client/src/components/Starmap/SolarSystemMap";
import {Suspense} from "react";
import {Color, Group} from "three";
import {solarRadiusToKilometers} from "server/src/utils/unitTypes";
import {StarSprite} from "client/src/components/Starmap/Star/StarMesh";
import OrbitContainer, {
OrbitLine,
} from "client/src/components/Starmap/OrbitContainer";
import {getOrbitPosition} from "server/src/utils/getOrbitPosition";
import PlanetPlugin from "server/src/classes/Plugins/Universe/Planet";
import {DEG2RAD} from "three/src/math/MathUtils";
import {planetSpriteScale} from "client/src/components/Starmap/Planet";
import {PlanetSprite} from "client/src/components/Starmap/Planet";
import SystemLabel from "client/src/components/Starmap/SystemMarker/SystemLabel";
import {useFrame} from "@react-three/fiber";

export function SolarSystemWrapper() {
const labelRef = useRef<Group>(null);

const useStarmapStore = useGetStarmapStore();
const currentSystem = useStarmapStore(store => store.currentSystem);

if (currentSystem === null) throw new Error("No current system");
const system = useNetRequest("starmapSystem", {systemId: currentSystem});
const starmapEntities = useNetRequest("starmapSystemEntities", {
systemId: currentSystem,
});
return (
<SolarSystemMap
skyboxKey={system?.components.isSolarSystem?.skyboxKey || "Blank"}
>
{starmapEntities.map(entity => {
if (entity.components.isStar) {
if (!entity.components.satellite) return null;
const size = solarRadiusToKilometers(entity.components.isStar.radius);

const color = new Color(
`hsl(${entity.components.isStar.hue}, 100%, ${
entity.components.isStar.isWhite ? 100 : 50
}%)`
);
return (
<OrbitContainer key={entity.id} {...entity.components.satellite}>
<group scale={[size, size, size]}>
<StarSprite size={size} color1={color} />
</group>
</OrbitContainer>
);
}
if (entity.components.isPlanet) {
if (!entity.components.satellite) return null;
const position = getOrbitPosition(entity.components.satellite);
const size = entity.components.isPlanet.radius;
const satellites: PlanetPlugin[] = [];
const {semiMajorAxis, eccentricity, inclination} =
entity.components.satellite;
const radiusY = semiMajorAxis - semiMajorAxis * eccentricity;

useFrame(({camera}) => {
if (labelRef.current) {
const zoom = camera.position.distanceTo(position) + 500;
let zoomedScale = (zoom / 2) * 0.01;
labelRef.current.scale.set(zoomedScale, zoomedScale, zoomedScale);
labelRef.current.quaternion.copy(camera.quaternion);
}
});

return (
<group key={entity.id}>
<group rotation={[0, 0, inclination * DEG2RAD]}>
<OrbitLine radiusX={semiMajorAxis} radiusY={radiusY} />
</group>
<group position={position}>
<Suspense fallback={null}>
<group
scale={[
planetSpriteScale,
planetSpriteScale,
planetSpriteScale,
]}
>
<PlanetSprite />
</group>
</Suspense>
{entity.components.identity && (
<group ref={labelRef}>
<SystemLabel
systemId=""
name={entity.components.identity.name}
hoveringDirection={{current: 0}}
scale={5 / 128}
/>
</group>
)}
</group>
{/* TODO June 20, 2022 - Figure out all of the stuff around moons */}
{/* {satellites?.map((s, i) => (
<Planet
key={`orbit-${s.name}`}
isSatellite
origin={position}
planet={s}
/>
))} */}
</group>
);
}
return null;
})}
</SolarSystemMap>
);
}
3 changes: 3 additions & 0 deletions client/src/cards/Navigation/data.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {DataContext} from "server/src/utils/DataContext";

export const requests = {};
61 changes: 61 additions & 0 deletions client/src/cards/Navigation/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {
StarmapStoreProvider,
useCalculateVerticalDistance,
useGetStarmapStore,
} from "client/src/components/Starmap/starmapStore";
import {useEffect, useState} from "react";
import StarmapCanvas from "client/src/components/Starmap/StarmapCanvas";
import Input from "@thorium/ui/Input";
import {CardProps} from "client/src/components/Station/CardProps";
import {MapControls} from "./MapControls";
import {InterstellarWrapper} from "./InterstellarWrapper";
import {SolarSystemWrapper} from "./SolarSystemWrapper";

export function Navigation(props: CardProps) {
return (
<StarmapStoreProvider>
<div className="mx-auto h-full bg-black/70 border border-white/50 relative">
<CanvasWrapper shouldRender={props.cardLoaded} />
<div className="grid grid-cols-2 grid-rows-2 absolute inset-0 pointer-events-none p-4">
<Input
label="Search"
labelHidden
placeholder="Search..."
className="pointer-events-all max-w-sm"
/>
<div></div>
<MapControls />
</div>
</div>
</StarmapStoreProvider>
);
}

function CanvasWrapper({shouldRender}: {shouldRender: boolean}) {
const useStarmapStore = useGetStarmapStore();
const currentSystem = useStarmapStore(store => store.currentSystem);
const [firstRender, setFirstRender] = useState(true);

useEffect(() => {
useStarmapStore.setState({viewingMode: "station", cameraView: "2d"});
setFirstRender(false);
}, []);

return (
<StarmapCanvas shouldRender={firstRender || shouldRender}>
<ambientLight intensity={0.2} />
<pointLight position={[10, 10, 10]} />
<StarmapHooks />
{currentSystem === null ? (
<InterstellarWrapper />
) : (
<SolarSystemWrapper />
)}
</StarmapCanvas>
);
}

function StarmapHooks() {
useCalculateVerticalDistance();
return null;
}
1 change: 1 addition & 0 deletions client/src/cards/dataList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
export * as Timer from "./timer/data";
export * as Pilot from "./Pilot/data";
export * as OfficersLog from "./OfficersLog/data";
export * as Navigation from "./Navigation/data";

// This is a special set of data that is available
// on all cards.
Expand Down
1 change: 1 addition & 0 deletions client/src/cards/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export {default as Login} from "./Login";
export {Pilot} from "./Pilot";
export {default as OfficersLog} from "./OfficersLog";
export {default as ComponentDemo} from "./ComponentDemo";
export {Navigation} from "./Navigation";
16 changes: 16 additions & 0 deletions client/src/components/Starmap/CameraControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
Overwrite,
} from "@react-three/fiber";
import CameraControlsDefault from "camera-controls";
import {useGetStarmapStore} from "./starmapStore";

declare global {
namespace JSX {
Expand Down Expand Up @@ -61,6 +62,20 @@ const subsetOfTHREE = {
CameraControlsDefault.install({THREE: subsetOfTHREE});
extend({CameraControlsDefault});

export function useExternalCameraControl(
controls: React.MutableRefObject<CameraControlsDefault | null>
) {
const useStarmapStore = useGetStarmapStore();

useEffect(() => {
if (controls) {
useStarmapStore.setState({
cameraControls: controls,
});
}
}, [controls]);
}

export const CameraControls = forwardRef<
CameraControlsDefault,
ExtendedColors<
Expand All @@ -75,6 +90,7 @@ export const CameraControls = forwardRef<
const renderer = useThree(state => state.gl);
useFrame((_, delta) => cameraControls.current?.update(delta));
useEffect(() => () => cameraControls.current?.dispose(), []);

return (
<cameraControlsDefault
ref={mergeRefs<CameraControlsDefault>(cameraControls, ref)}
Expand Down
3 changes: 2 additions & 1 deletion client/src/components/Starmap/InterstellarMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {toast} from "client/src/context/ToastContext";
import {netSend} from "client/src/context/netSend";
import {useConfirm} from "@thorium/ui/AlertDialog";
import Button from "@thorium/ui/Button";
import {CameraControls} from "./CameraControls";
import {CameraControls, useExternalCameraControl} from "./CameraControls";
import CameraControlsClass from "camera-controls";
import debounce from "lodash.debounce";
import Input from "@thorium/ui/Input";
Expand Down Expand Up @@ -54,6 +54,7 @@ export function InterstellarMap({children}: {children: React.ReactNode}) {
useEffect(() => {
useStarmapStore.setState({skyboxKey: "blank"});
}, []);
useExternalCameraControl(orbitControls);

return (
<Suspense fallback={null}>
Expand Down
16 changes: 8 additions & 8 deletions client/src/components/Starmap/OrbitContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,23 @@ export const OrbitLine: React.FC<{radiusX: number; radiusY: number}> = ({

interface OrbitContainerProps {
children: React.ReactNode;
radius: number;
semiMajorAxis: number;
eccentricity: number;
orbitalArc: number;
orbitalInclination: number;
inclination: number;
showOrbit: boolean;
}

const OrbitContainer: React.FC<OrbitContainerProps> = ({
radius,
semiMajorAxis,
eccentricity,
orbitalArc,
orbitalInclination,
inclination,
showOrbit,
children,
}) => {
const radiusY = radius - radius * eccentricity;
const X = radius * Math.cos(MathUtils.DEG2RAD * orbitalArc);
const radiusY = semiMajorAxis - semiMajorAxis * eccentricity;
const X = semiMajorAxis * Math.cos(MathUtils.DEG2RAD * orbitalArc);
const Z = radiusY * Math.sin(MathUtils.DEG2RAD * orbitalArc);
const position = [X, 0, Z];
const childrenWithProps = React.Children.map(children, child => {
Expand All @@ -67,8 +67,8 @@ const OrbitContainer: React.FC<OrbitContainerProps> = ({
return child;
});
return (
<group rotation={[0, 0, orbitalInclination * MathUtils.DEG2RAD]}>
{showOrbit && <OrbitLine radiusX={radius} radiusY={radiusY} />}
<group rotation={[0, 0, inclination * MathUtils.DEG2RAD]}>
{showOrbit && <OrbitLine radiusX={semiMajorAxis} radiusY={radiusY} />}
{childrenWithProps}
</group>
);
Expand Down
Loading

0 comments on commit 272626a

Please sign in to comment.