From b13689c09afbe8d44e343cade90da06cc68cbfcf Mon Sep 17 00:00:00 2001 From: Davide Passoni Date: Thu, 10 Oct 2024 14:35:14 +0200 Subject: [PATCH] Completed formation tool --- frontend/react/src/ui/libs/useDrag.ts | 4 +- .../react/src/ui/panels/components/menu.tsx | 6 +- .../react/src/ui/panels/formationmenu.tsx | 345 ++++++++++++++---- 3 files changed, 273 insertions(+), 82 deletions(-) diff --git a/frontend/react/src/ui/libs/useDrag.ts b/frontend/react/src/ui/libs/useDrag.ts index ccb343f3..2c1e31c8 100644 --- a/frontend/react/src/ui/libs/useDrag.ts +++ b/frontend/react/src/ui/libs/useDrag.ts @@ -44,8 +44,8 @@ export const useDrag = (props: { ref, initialPosition, count}) => { const [parentTop, parentLeft, parentWidth, parentHeight] = [parentRect.top, parentRect.left, parentRect.width, parentRect.height]; setFinalPosition({ - x: Math.max(width / 2, Math.min(mouseX - parentLeft, parentWidth - width / 2)), - y: Math.max(height / 2, Math.min(mouseY - parentTop, parentHeight - height / 2)), + x: Math.round(Math.max(width / 2, Math.min(mouseX - parentLeft, parentWidth - width / 2)) / 10) * 10, + y: Math.round(Math.max(height / 2, Math.min(mouseY - parentTop, parentHeight - height / 2)) / 10) * 10, }); }, [isDragging, props.ref] diff --git a/frontend/react/src/ui/panels/components/menu.tsx b/frontend/react/src/ui/panels/components/menu.tsx index e5697267..8477f51f 100644 --- a/frontend/react/src/ui/panels/components/menu.tsx +++ b/frontend/react/src/ui/panels/components/menu.tsx @@ -41,7 +41,7 @@ export function Menu(props: { >
{})} icon={faArrowLeft} className={` - mr-1 cursor-pointer rounded-md p-2 + mr-1 h-8 cursor-pointer rounded-md p-2 dark:text-gray-500 dark:hover:bg-gray-700 dark:hover:text-white `} /> @@ -68,7 +68,9 @@ export function Menu(props: { `} />
+
{props.children} +
{props.canBeHidden == true && (
{ + if (idx < units.length) units[idx + 1] = unit; + }); + + /* Init state variables */ const [formationType, setFormationType] = useState("echelon-lh"); const [horizontalScale, setHorizontalScale] = useState(0); const [verticalScale, setVerticalScale] = useState(30); + const [offsets, setOffsets] = useState( + units.map((unit, idx) => { + return computeFormationOffset(formationType, idx); + }) + ); + + /* The count state is used to force the reset of the initial position of the silhouettes */ + // TODO it works but I don't like it, it feels like a hack const [count, setCount] = useState(0); - let units = Array(128).fill(null) as (Unit | null)[]; - units[0] = props.leader; - props.wingmen?.forEach((unit, idx) => (units[idx + 1] = unit)); - + /* Init references and hooks */ const containerRef = useRef(null); - const silhouetteReferences = units.map((unit) => useRef(null)); - const silhouetteHandles = units.map((unit, idx) => { - let offset = computeFormationOffset(formationType, idx); + const scrollRef = useRef(null); + const silhouetteReferences = units.map((_) => useRef(null)); + const silhouetteHandles = units.map((_, idx) => { + /* Set the initial position of the unit to be centered in the drawing canvas, depending on the currently loaded formation */ + let offset = offsets[idx] ?? { x: 0, y: 0, z: 0 }; let center = { x: 0, y: 0 }; if (containerRef.current) { center.x = (containerRef.current as HTMLDivElement).getBoundingClientRect().width / 2; - center.y = 150; + center.y = (containerRef.current as HTMLDivElement).getBoundingClientRect().height / 2; } return useDrag({ ref: silhouetteReferences[idx], @@ -37,53 +53,168 @@ export function FormationMenu(props: { }); }); - let formationTypes = { - "echelon-lh": "Echelon left", - "echelon-rh": "Echelon right", - "line-abreast-rh": "Line abreast right", - "line-abreast-lh": "Line abreast left", - trail: "Trail", - front: "Front", - diamond: "Diamond", - }; + /* When the formation type is changed, reset the position to the center and the position of the silhouettes depending on the aircraft */ + useEffect(() => { + if (scrollRef.current && containerRef.current) { + const containerDiv = containerRef.current as HTMLDivElement; + const scrollDiv = scrollRef.current as HTMLDivElement; + scrollDiv.scrollTop = (containerDiv.clientHeight - scrollDiv.clientHeight) / 2 + 150; + scrollDiv.scrollLeft = (containerDiv.clientWidth - scrollDiv.clientWidth) / 2; + } + + if (formationType !== "custom") { + setOffsets( + units.map((unit, idx) => { + return computeFormationOffset(formationType, idx); + }) + ); + setCount(count + 1); + } + }, [formationType]); + + const horizontalRatio = 1 + (horizontalScale / 100) ** 2 * 100; + const verticalRatio = (verticalScale - 50) / 50; + + let referenceDistance = 200 * horizontalRatio; + if (referenceDistance < 250) { + referenceDistance = 100; + } else if (referenceDistance < 500) { + referenceDistance = 250; + } else if (referenceDistance < 1000) { + referenceDistance = 500; + } else if (referenceDistance < 3000) { + referenceDistance = 1000; + } else if (referenceDistance < 10000) { + referenceDistance = 5000; + } else { + referenceDistance = 10000; + } + + const referenceWidth = referenceDistance / horizontalRatio; return (
Formation type presets - - {Object.keys(formationTypes).map((optionFormationType) => { - return ( - { - setCount(count + 1); - setFormationType(optionFormationType); - }} - > - {formationTypes[optionFormationType]} - - ); - })} - -
- Parade +
+ + {Object.keys(formationTypes) + .filter((type) => type !== "custom") + .map((optionFormationType) => { + return ( + { + setCount(count + 1); + setFormationType(optionFormationType); + }} + > + {formationTypes[optionFormationType]} + + ); + })} + + + +
+ Formation distance +
+ + Parade + { setHorizontalScale(Number(ev.target.value)); }} /> - Tactical + Tactical
-
- Down + Vertical separation +
+ Down { setVerticalScale(Number(ev.target.value)); }} /> - Up + Up
+
+
{referenceDistance}ft
+
+
- <> - {units.map((unit, idx) => { - return ( -
- + <> + {Array(100) + .fill(0) + .map((_, idx) => { + return ( +
+ ); + })} + + <> + {Array(100) + .fill(0) + .map((_, idx) => { + return ( +
+ ); + })} + + <> + {units.map((unit, idx) => { + return ( +
-
- ); - })} - + ref={silhouetteReferences[idx]} + style={{ + top: silhouetteHandles[idx].position.y, + left: silhouetteHandles[idx].position.x, + }} + onMouseDown={(e) => { + silhouetteHandles[idx].handleMouseDown(e); + setFormationType("custom"); + }} + > + +
+ ); + })} + +
@@ -167,30 +351,24 @@ export function FormationMenu(props: { } function computeFormationOffset(formation, idx) { - let offset = { x: 0, y: 0, z: 0 }; + let offset = { x: 0, z: 0 }; if (formation === "trail") { offset.x = -50 * idx; - offset.y = -30 * idx; offset.z = 0; } else if (formation === "echelon-lh") { offset.x = -50 * idx; - offset.y = -10 * idx; offset.z = -50 * idx; } else if (formation === "echelon-rh") { offset.x = -50 * idx; - offset.y = -10 * idx; offset.z = 50 * idx; } else if (formation === "line-abreast-lh") { offset.x = 0; - offset.y = 0; offset.z = -50 * idx; } else if (formation === "line-abreast-rh") { offset.x = 0; - offset.y = 0; offset.z = 50 * idx; } else if (formation === "front") { offset.x = 100 * idx; - offset.y = 0; offset.z = 0; } else if (formation === "diamond") { var xr = 0; @@ -201,7 +379,7 @@ function computeFormationOffset(formation, idx) { for (let i = 0; i < idx; i++) { var xl = xr * Math.cos(Math.PI / 4) - yr * Math.sin(Math.PI / 4); var yl = xr * Math.sin(Math.PI / 4) + yr * Math.cos(Math.PI / 4); - offset = { x: -yl * 50, y: zr * 10, z: xl * 50 }; + offset = { x: -yl * 50, z: xl * 50 }; if (yr == 0) { layer++; xr = 0; @@ -220,3 +398,14 @@ function computeFormationOffset(formation, idx) { } return offset; } + +let formationTypes = { + "echelon-lh": "Echelon left", + "echelon-rh": "Echelon right", + "line-abreast-rh": "Line abreast right", + "line-abreast-lh": "Line abreast left", + trail: "Trail", + front: "Front", + diamond: "Diamond", + custom: "Custom", +};