Skip to content

Commit

Permalink
feat(navmesh-website): support fbx and obj, add 'export as recast nav…
Browse files Browse the repository at this point in the history
…mesh' action
  • Loading branch information
isaac-mason committed Sep 18, 2023
1 parent 8efad82 commit 1e3fc2d
Show file tree
Hide file tree
Showing 16 changed files with 278 additions and 175 deletions.
162 changes: 104 additions & 58 deletions apps/navmesh-website/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import cityEnvironment from '@pmndrs/assets/hdri/city.exr';
import { Bounds, Environment, OrbitControls } from '@react-three/drei';
import { Canvas, ThreeEvent } from '@react-three/fiber';
import { Leva } from 'leva';
import { Suspense, useCallback, useRef, useState } from 'react';
import { Suspense, useCallback, useRef } from 'react';
import {
NavMesh,
SoloNavMeshGeneratorIntermediates,
Expand All @@ -15,45 +15,71 @@ import {
import { getPositionsAndIndices } from 'recast-navigation/three';
import { suspend } from 'suspend-react';
import { Group, Mesh } from 'three';
import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader';
import { create } from 'zustand';
import dungeonGltfUrl from './assets/dungeon.gltf?url';
import {
useActionsControls,
useDisplayOptionsControls,
useNavMeshConfigControls,
useTestAgentControls,
} from './features/controls/controls';
import { ErrorBoundary } from './features/error-handling/error-boundary';
import { ErrorMessage } from './features/error-handling/error-message';
import { download } from './features/export/download';
import { navMeshToGLTF } from './features/export/nav-mesh-to-gltf';
import { HeightfieldHelper } from './features/helpers/heightfield-helper';
import { NavMeshGeneratorInputHelper } from './features/helpers/nav-mesh-generator-input-helper';
import { NavMeshHelper } from './features/helpers/nav-mesh-helper';
} from './features/controls';
import { ErrorBoundary, ErrorMessage } from './features/error-handling';
import { download, navMeshToGLTF } from './features/export';
import {
HeightfieldHelper,
NavMeshGeneratorInputHelper,
NavMeshHelper,
} from './features/helpers';
import { RecastAgent, RecastAgentRef } from './features/recast/recast-agent';
import { CenterLayout } from './features/ui/center-layout';
import { LoadingSpinner } from './features/ui/loading-spinner';
import { GltfDropZone } from './features/upload/gltf-drop-zone';
import { gltfLoader } from './features/upload/gltf-loader';
import { readFile } from './features/upload/read-file';
import { Centered, LoadingSpinner } from './features/ui';
import {
ModelDropZone,
gltfLoader,
loadModel,
readFile,
} from './features/upload';
import { HtmlTunnel } from './tunnels';

const App = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string>();
type EditorState = {
loading: boolean;
error?: string;

const [model, setModel] = useState<Group>();
model?: Group;

const [indexedTriangleMesh, setIndexedTriangleMesh] = useState<{
indexedTriangleMesh?: {
positions: Float32Array;
indices: Uint32Array;
}>();
};

generatorIntermediates?:
| SoloNavMeshGeneratorIntermediates
| TiledNavMeshGeneratorIntermediates;

const [generatorIntermediates, setGeneratorIntermediates] = useState<
SoloNavMeshGeneratorIntermediates | TiledNavMeshGeneratorIntermediates
>();
navMesh?: NavMesh;
};

const [navMesh, setNavMesh] = useState<NavMesh>();
const useEditorState = create<
EditorState & { setEditorState: (partial: Partial<EditorState>) => void }
>((set) => ({
loading: false,
error: undefined,
model: undefined,
indexedTriangleMesh: undefined,
generatorIntermediates: undefined,
navMesh: undefined,
setEditorState: (partial) => set(partial),
}));

const App = () => {
const {
loading,
error,
model,
indexedTriangleMesh,
generatorIntermediates,
navMesh,
setEditorState,
} = useEditorState();

const recastAgent = useRef<RecastAgentRef>(null!);

Expand All @@ -64,10 +90,12 @@ const App = () => {
navMesh.destroy();
}

setError(undefined);
setLoading(true);
setNavMesh(undefined);
setIndexedTriangleMesh(undefined);
setEditorState({
loading: true,
error: undefined,
navMesh: undefined,
generatorIntermediates: undefined,
});

try {
const meshes: Mesh[] = [];
Expand All @@ -79,7 +107,9 @@ const App = () => {
});

const [positions, indices] = getPositionsAndIndices(meshes);
setIndexedTriangleMesh({ positions, indices });
setEditorState({
indexedTriangleMesh: { positions, indices },
});

const result = navMeshConfig.tileSize
? generateTiledNavMesh(positions, indices, navMeshConfig, true)
Expand All @@ -88,43 +118,56 @@ const App = () => {
console.log('nav mesh generation result', result);

if (!result.success) {
setError(result.error);
setEditorState({ error: result.error });
} else {
setNavMesh(result.navMesh);
setGeneratorIntermediates(result.intermediates);
setEditorState({
navMesh: result.navMesh,
generatorIntermediates: result.intermediates,
});
}
} catch (e) {
const message = (e as { message: string })?.message;
setError(
'Something went wrong generating the navmesh' + message
? ` - ${message}`
: ''
);
setEditorState({
error:
'Something went wrong generating the navmesh' +
(message ? ` - ${message}` : ''),
});
} finally {
setLoading(false);
setEditorState({
loading: false,
});
}
};

const onDropFile = useCallback(async (acceptedFiles: File[]) => {
if (acceptedFiles.length === 0) return;

setError(undefined);
setLoading(true);
setEditorState({
error: undefined,
loading: true,
});

try {
const { buffer } = await readFile(acceptedFiles[0]);
const modelFile = acceptedFiles[0];
const { buffer } = await readFile(modelFile);

const { scene } = await new Promise<GLTF>((resolve, reject) =>
gltfLoader.parse(buffer, '', resolve, reject)
);
const model = await loadModel(buffer, modelFile);

setModel(scene);
setEditorState({
model,
});
} catch (e) {
setError(
'Something went wrong! Please ensure the file is a valid GLTF / GLB.'
);
const message = (e as { message: string })?.message;

setEditorState({
error:
`Something went wrong! Please ensure the file is a valid GLTF, GLB, FBX or OBJ` +
(message ? ` - ${message}` : ''),
});
} finally {
setLoading(false);
setEditorState({
loading: false,
});
}
}, []);

Expand Down Expand Up @@ -166,18 +209,21 @@ const App = () => {
const selectExample = useCallback(async () => {
if (model) return;

setLoading(true);
setEditorState({
loading: true,
});

gltfLoader.load(
dungeonGltfUrl,
({ scene }) => {
setModel(scene);
setLoading(false);
setEditorState({ model: scene, loading: false });
},
undefined,
() => {
setLoading(false);
setError('Failed to load example model');
setEditorState({
loading: false,
error: 'Failed to load example model',
});
}
);
}, []);
Expand Down Expand Up @@ -268,9 +314,9 @@ const App = () => {
{loading && <LoadingSpinner />}

{!model && !loading && (
<CenterLayout>
<GltfDropZone onDrop={onDropFile} selectExample={selectExample} />
</CenterLayout>
<Centered>
<ModelDropZone onDrop={onDropFile} selectExample={selectExample} />
</Centered>
)}

{error && <ErrorMessage>{error}</ErrorMessage>}
Expand Down
20 changes: 4 additions & 16 deletions apps/navmesh-website/src/features/controls/controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,6 @@ export const useActionsControls = ({
},
[navMesh, generateNavMesh, loading]
);

useControls(
'Actions.Tips',
{
_: levaText(
`Use "Export as Recast NavMesh" to download a binary file that can be imported by 'recast-navigation-js' with 'importNavMesh', and is also compatible with the recastnavigation RecastDemo c++ application.`
),
},
{
collapsed: true,
}
);
};

export const useNavMeshConfigControls = () => {
Expand Down Expand Up @@ -224,8 +212,8 @@ export const useDisplayOptionsControls = () => {
navMeshDebugOpacity,
navMeshDebugWireframe,
displayNavMeshHelper,
}
}
};
};

export const useTestAgentControls = () => {
const {
Expand Down Expand Up @@ -270,5 +258,5 @@ export const useTestAgentControls = () => {
agentHeight,
agentMaxAcceleration,
agentMaxSpeed,
}
}
};
};
2 changes: 2 additions & 0 deletions apps/navmesh-website/src/features/controls/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './controls';
export * from './leva-text';
2 changes: 2 additions & 0 deletions apps/navmesh-website/src/features/error-handling/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './error-boundary';
export * from './error-message';
2 changes: 2 additions & 0 deletions apps/navmesh-website/src/features/export/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './download';
export * from './nav-mesh-to-gltf';
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,5 @@ export const HeightfieldHelper = ({
return heightfieldHelper;
}, [navMesh, enabled]);

console.log(enabled);

return enabled && helper && <primitive object={helper} />;
};
3 changes: 3 additions & 0 deletions apps/navmesh-website/src/features/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './heightfield-helper';
export * from './nav-mesh-generator-input-helper';
export * from './nav-mesh-helper';
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled from 'styled-components';

export const CenterLayout = styled.div`
export const Centered = styled.div`
position: absolute;
top: 0;
left: 0;
Expand All @@ -12,11 +12,5 @@ export const CenterLayout = styled.div`
width: calc(100% - 4em);
height: calc(100vh - 4em);
padding: 2em;
font-weight: 600;
line-height: 1.3;
text-align: center;
color: #fff;
padding: 2em;
`;
2 changes: 2 additions & 0 deletions apps/navmesh-website/src/features/ui/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './centered';
export * from './loading-spinner';
6 changes: 3 additions & 3 deletions apps/navmesh-website/src/features/ui/loading-spinner.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import styled, { keyframes } from 'styled-components';
import { CenterLayout } from './center-layout';
import { Centered } from './centered';

const SpinnerKeyframes = keyframes`
from {
Expand All @@ -20,7 +20,7 @@ export const Spinner = styled.div`
`;

export const LoadingSpinner = () => (
<CenterLayout>
<Centered>
<Spinner />
</CenterLayout>
</Centered>
)

0 comments on commit 1e3fc2d

Please sign in to comment.