Skip to content

Commit

Permalink
feat(Pilot): Add direction and rotation thruster joysticks.
Browse files Browse the repository at this point in the history
  • Loading branch information
alexanderson1993 committed Aug 20, 2022
1 parent 9a6b6b7 commit f9eaf0f
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 6 deletions.
59 changes: 54 additions & 5 deletions client/src/cards/Pilot/index.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,71 @@
import {useDataStream} from "client/src/context/useDataStream";
import {useNetRequest} from "client/src/context/useNetRequest";
import {Joystick, LinearJoystick} from "@thorium/ui/Joystick";
import {ReactNode} from "react";
import type {Coordinates} from "server/src/utils/unitTypes";
import {netSend} from "client/src/context/netSend";

async function rotation({x = 0, y = 0, z = 0}: Partial<Coordinates<number>>) {
await netSend("thrustersSetRotationDelta", {rotation: {x, y, z}});
}
async function direction({x = 0, y = 0, z = 0}: Partial<Coordinates<number>>) {
await netSend("thrustersSetDirection", {direction: {x, y, z}});
}

function UntouchableLabel({
children,
className,
}: {
children: ReactNode;
className?: string;
}) {
return (
<p className={`select-none pointer-events-none absolute ${className}`}>
{children}
</p>
);
}
export function Pilot() {
useDataStream({systemId: null});

return (
<div className="grid grid-cols-4 h-full place-content-center gap-4">
<div className="bg-purple-500 h-full">
<div className="flex flex-col justify-between">
<div>Impulse controls here</div>
<div>Thruster direction here here</div>
<div className="flex gap-4 w-full">
<LinearJoystick onDrag={({y}) => direction({z: -y})} vertical>
<UntouchableLabel className="top-1">Fore</UntouchableLabel>
<UntouchableLabel className="bottom-1">Aft</UntouchableLabel>
</LinearJoystick>
<Joystick
className="flex-1"
onDrag={({x, y}) => direction({y: -y, x: -x})}
>
<UntouchableLabel className="bottom-1">Down</UntouchableLabel>
<UntouchableLabel className="top-1">Up</UntouchableLabel>
<UntouchableLabel className="right-1">Starboard</UntouchableLabel>
<UntouchableLabel className="left-1">Port</UntouchableLabel>
</Joystick>
</div>
</div>
<div className="col-span-2 h-full">
<div className="aspect-square w-full max-h-full bg-orange-400"></div>
</div>
<div className="bg-purple-500 h-full">
<div className="h-full flex flex-col justify-between gap-4">
<div>Course controls here</div>
<div>Camera controls here</div>
<div>Thruster rotation here</div>
<div className="flex-1"></div>
<Joystick onDrag={({x, y}) => rotation({z: x, x: y})}>
<UntouchableLabel className="bottom-1">Pitch Down</UntouchableLabel>
<UntouchableLabel className="top-1">Pitch Up</UntouchableLabel>
<UntouchableLabel className="right-1">
Starboard Roll
</UntouchableLabel>
<UntouchableLabel className="left-1">Port Roll</UntouchableLabel>
</Joystick>
<LinearJoystick onDrag={({x}) => rotation({y: -x})}>
<UntouchableLabel className="left-1">Port Yaw</UntouchableLabel>
<UntouchableLabel className="right-1">Starboard Yaw</UntouchableLabel>
</LinearJoystick>
</div>
</div>
);
Expand Down
64 changes: 63 additions & 1 deletion client/src/components/ui/Joystick.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,63 @@
export {};
import {animated as a} from "@react-spring/web";
import {useJoystick} from "client/src/hooks/useJoystick";
import {ReactNode} from "react";

export const Joystick = ({
children,
className,
onDrag,
}: {
onDrag: (dir: {x: number; y: number}) => void;
className?: string;
children?: ReactNode;
}) => {
const [xy, bind, containerRef] = useJoystick({axisSnap: true, onDrag});

return (
<div className={`relative aspect-square ${className}`}>
<div
ref={containerRef}
className="top-0 absolute bg-black/50 border-2 border-white/50 rounded-full w-full h-full flex justify-center items-center"
>
<a.div
{...bind()}
style={{transform: xy?.to((x, y) => `translate3d(${x}px,${y}px,0)`)}}
className="z-10 w-10 h-10 rounded-full border-black/50 border-2 bg-gray-500 shadow-md cursor-pointer"
></a.div>
{children}
</div>
</div>
);
};

export const LinearJoystick = ({
className,
onDrag,
children,
vertical,
}: {
className?: string;
onDrag: (dirs: {x: number; y: number}) => void;
children: ReactNode;
vertical?: boolean;
}) => {
const [xy, bind, containerRef] = useJoystick({
axis: vertical ? "y" : "x",
onDrag,
});
return (
<div
ref={containerRef}
className={`${
vertical ? "h-full" : "w-full"
} relative bg-black/50 border-2 border-white/50 rounded-full flex justify-center items-center ${className}`}
>
<a.div
{...bind()}
style={{transform: xy?.to((x, y) => `translate3d(${x}px,${y}px,0)`)}}
className="z-10 w-10 h-10 rounded-full border-black/50 border-2 bg-gray-500 shadow-md cursor-pointer"
></a.div>
{children}
</div>
);
};
65 changes: 65 additions & 0 deletions client/src/hooks/useJoystick.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import throttle from "lodash.throttle";
import {useSpring} from "@react-spring/web";
import {useDrag} from "@use-gesture/react";
import {useRef} from "react";

function distance(x1: number, y1: number, x2 = 0, y2 = 0) {
const a = x1 - x2;
const b = y1 - y2;

return Math.sqrt(a * a + b * b);
}
const minDistance = 10;
export function useJoystick({
axisSnap,
axis,
onDrag = () => {},
throttleMs = 100,
}: {
axisSnap?: boolean;
axis?: "x" | "y" | undefined;
onDrag?: (values: {x: number; y: number}) => void;
throttleMs?: number;
} = {}) {
const callback = useRef(throttle(onDrag, throttleMs));
const containerRef = useRef<HTMLDivElement>(null);
const maxDistance = useRef(0);
const [{xy}, set] = useSpring(() => ({
xy: [0, 0],
config: {mass: 1, tension: 280, friction: 30},
}));
const bind = useDrag(
({down, first, movement: [x, y]}) => {
if (first) {
maxDistance.current =
(containerRef.current?.getBoundingClientRect()[
axis === "y" ? "height" : "width"
] || 0) / 2;
}
const dist = distance(x, y);
if (dist > maxDistance.current) {
const theta = Math.abs(Math.atan(y / x));
x = maxDistance.current * Math.cos(theta) * (x > 0 ? 1 : -1);
y = maxDistance.current * Math.sin(theta) * (y > 0 ? 1 : -1);
}
if (
axisSnap &&
(Math.abs(x) > minDistance || Math.abs(y) > minDistance)
) {
if (x < minDistance && x > minDistance * -1) x = 0;
if (y < minDistance && y > minDistance * -1) y = 0;
}
set({
xy: down ? [x, y] : [0, 0],
immediate: down,
});
callback.current?.(
down
? {x: x / maxDistance.current, y: y / maxDistance.current}
: {x: 0, y: 0}
);
},
{axis}
);
return [xy, bind, containerRef] as const;
}
1 change: 1 addition & 0 deletions server/src/inputs/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ export {thrustersPluginInput} from "./plugins/shipSystems/thrusters";
export {impulseEnginesInputs} from "./shipSystems/impulseEngines";
export {warpEnginesInputs} from "./shipSystems/warpEngines";
export {pluginInventoryInputs} from "./plugins/inventory";
export {thrustersInputs} from "./shipSystems/thrusters";
1 change: 1 addition & 0 deletions server/src/inputs/shipSystems/thrusters.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {DataContext} from "server/src/utils/DataContext";
import {pubsub} from "server/src/utils/pubsub";
import {
Coordinates,
MetersPerSecond,
Expand Down

0 comments on commit f9eaf0f

Please sign in to comment.