This commit is contained in:
Nordi98 2025-07-14 15:59:33 +02:00
parent 4c47f97c2a
commit 096c4ffcaf
22 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,23 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View file

@ -0,0 +1 @@
body{overflow:hidden}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<script type="module" crossorigin src="./assets/index.4b31427f.js"></script>
<link rel="stylesheet" href="./assets/index.09731cda.css">
</head>
<body>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
{
"name": "gizmo",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@react-three/drei": "^9.65.5",
"@react-three/fiber": "^8.12.2",
"axios": "^1.3.6",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"three": "^0.151.3"
},
"devDependencies": {
"@types/react": "^18.0.22",
"@types/react-dom": "^18.0.7",
"@vitejs/plugin-react": "^2.2.0",
"typescript": "^4.6.4",
"vite": "^3.2.0"
}
}

View file

@ -0,0 +1,11 @@
import { ThreeComponent } from "./components/ThreeComponent";
function App() {
return (
<div style={{ width: "100vw", height: "100vh" }}>
<ThreeComponent />
</div>
);
}
export default App;

View file

@ -0,0 +1,34 @@
import { PerspectiveCamera } from "@react-three/drei";
import { useNuiEvent } from "../nui-events";
import { useThree } from "@react-three/fiber";
import { MathUtils } from "three";
export const CameraComponent = () => {
const { camera } = useThree();
const zRotationHandler = (t: number, e: number) => {
return t > 0 && t < 90 ? e : (t > -180 && t < -90) || t > 0 ? -e : e;
};
useNuiEvent("setCameraPosition", ({ position, rotation }: any) => {
camera.position.set(position.x, position.z, -position.y);
camera.rotation.order = "YZX";
rotation &&
camera.rotation.set(
MathUtils.degToRad(rotation.x),
MathUtils.degToRad(zRotationHandler(rotation.x, rotation.z)),
MathUtils.degToRad(rotation.y)
);
camera.updateProjectionMatrix();
});
return (
<PerspectiveCamera
position={[0, 0, 10]}
makeDefault
onUpdate={(self: any) => self.updateProjectionMatrix()}
/>
);
};

View file

@ -0,0 +1,12 @@
import { Canvas } from "@react-three/fiber";
import { CameraComponent } from "./CameraComponent";
import { TransformComponent } from "./TransformComponent";
export const ThreeComponent = () => {
return (
<Canvas style={{ zIndex: 1 }}>
<CameraComponent />
<TransformComponent />
</Canvas>
);
};

View file

@ -0,0 +1,91 @@
import { Suspense, useRef, useState, useEffect } from "react";
import { TransformControls } from "@react-three/drei";
import { useNuiEvent, fetchNui } from "../nui-events";
import { Mesh, MathUtils } from "three";
export const TransformComponent = () => {
const mesh = useRef<Mesh>(null!);
const [currentEntity, setCurrentEntity] = useState<number>();
const [editorMode, setEditorMode] = useState<
"translate" | "rotate" | undefined
>("translate");
const handleObjectDataUpdate = () => {
const entity = {
handle: currentEntity,
position: {
x: mesh.current.position.x,
y: -mesh.current.position.z,
z: mesh.current.position.y,
},
rotation: {
x: MathUtils.radToDeg(mesh.current.rotation.x),
y: MathUtils.radToDeg(-mesh.current.rotation.z),
z: MathUtils.radToDeg(mesh.current.rotation.y),
},
};
fetchNui("moveEntity", entity);
};
useNuiEvent("setGizmoEntity", (entity: any) => {
setCurrentEntity(entity.handle);
if (!entity.handle) {
return;
}
mesh.current.position.set(
entity.position.x,
entity.position.z,
-entity.position.y
);
mesh.current.rotation.order = "YZX";
mesh.current.rotation.set(
MathUtils.degToRad(entity.rotation.x),
MathUtils.degToRad(entity.rotation.z),
MathUtils.degToRad(entity.rotation.y)
);
});
useEffect(() => {
const keyHandler = (e: KeyboardEvent) => {
switch (e.code) {
case "KeyR":
if (editorMode == "rotate") return;
setEditorMode("rotate");
fetchNui("swapMode", { mode: "Rotate" });
break;
case "KeyW":
if (editorMode == "translate") return;
setEditorMode("translate");
fetchNui("swapMode", { mode: "Translate" });
break;
case "Escape":
fetchNui("finishEdit");
break;
case "KeyQ":
fetchNui("cam");
break;
default:
break;
}
};
window.addEventListener("keyup", keyHandler);
return () => window.removeEventListener("keyup", keyHandler);
});
return (
<>
<Suspense fallback={<p>Loading Gizmo</p>}>
{currentEntity != null && (
<TransformControls
size={0.5}
object={mesh}
mode={editorMode}
onObjectChange={handleObjectDataUpdate}
/>
)}
<mesh ref={mesh} />
</Suspense>
</>
);
};

View file

@ -0,0 +1,3 @@
body {
overflow: hidden;
}

View file

@ -0,0 +1,7 @@
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<App />
);

View file

@ -0,0 +1,57 @@
import { MutableRefObject, useEffect, useRef } from "react";
const isEnvBrowser = (): boolean => !(window as any).invokeNative
interface NuiMessageData<T = unknown> {
action: string;
data: T;
}
type NuiHandlerSignature<T> = (data: T) => void;
export const useNuiEvent = <T = any>(
action: string,
handler: (data: T) => void
) => {
const savedHandler: MutableRefObject<NuiHandlerSignature<T>> = useRef(() => {});
useEffect(() => {
savedHandler.current = handler;
}, [handler]);
useEffect(() => {
const eventListener = (event: MessageEvent<NuiMessageData<T>>) => {
const { action: eventAction, data } = event.data;
if (savedHandler.current) {
if (eventAction === action) {
savedHandler.current(data);
}
}
};
window.addEventListener("message", eventListener);
return () => window.removeEventListener("message", eventListener);
}, [action]);
};
export async function fetchNui<T = any>(eventName: string, data?: any, mockData?: T): Promise<T> {
const options = {
method: 'post',
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: JSON.stringify(data),
};
if (isEnvBrowser() && mockData) return mockData;
const resourceName = (window as any).GetParentResourceName ? (window as any).GetParentResourceName() : 'nui-frame-app';
const resp = await fetch(`https://${resourceName}/${eventName}`, options);
const respFormatted = await resp.json()
return respFormatted
}

View file

@ -0,0 +1 @@
/// <reference types="vite/client" />

View file

@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View file

@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View file

@ -0,0 +1,8 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
base: "./",
plugins: [react()]
})