feat: export glb

This commit is contained in:
Huh Hyeongjun 2025-02-25 13:04:51 +09:00
parent cfc19a42a2
commit f7ebf0c7a0
3 changed files with 95 additions and 14 deletions

12
src/state/exportStore.ts Normal file
View File

@ -0,0 +1,12 @@
import { create } from "zustand";
type ActionStore = {
action: boolean;
setAction: (action: boolean) => void;
};
export const useActionStore = create<ActionStore>((set) => ({
action: false,
setAction: (action) => set(() => ({ action: action })),
}));

View File

@ -1,8 +1,13 @@
import React, { useState } from "react";
import { Canvas } from "@react-three/fiber";
import React, { useEffect, useState } from "react";
import { Canvas, useThree } from "@react-three/fiber";
import { useAreaStore } from "@/state/areaStore";
import { OrbitControls, Html } from "@react-three/drei";
import * as THREE from "three";
import { NextButton } from "@/components/button/BottomButton";
import { ChevronRight } from "lucide-react";
import { css } from "@emotion/react";
import { useActionStore } from "@/state/exportStore";
import { GLTFExporter } from "three/examples/jsm/Addons.js";
function Building({
shape,
@ -121,7 +126,7 @@ export function Space() {
const buildingsData = areaData();
return (
<Canvas camera={{ fov: 90, near: 0.1, far: 4000 }}>
<Canvas camera={{ fov: 90, near: 0.1, far: 7000 }}>
<ambientLight intensity={Math.PI / 2} />
<spotLight
position={[10, 10, 10]}
@ -140,15 +145,69 @@ export function Space() {
))}
<pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />
<OrbitControls />
<Export />
</Canvas>
);
}
function Floor() {
return (
<mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
<planeGeometry args={[50, 50]} />
<meshStandardMaterial color="#ffffff" />
</mesh>
);
// function Floor() {
// return (
// <mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
// <planeGeometry args={[50, 50]} />
// <meshStandardMaterial color="#ffffff" />
// </mesh>
// );
// }
export function Export() {
const { scene } = useThree();
const action = useActionStore((state) => state.action);
const setAction = useActionStore((state) => state.setAction);
useEffect(() => {
if (action == true) {
setAction(false);
console.log(action);
exportGLB();
}
}, [action]);
const exportGLB = () => {
const sceneClone = scene.clone(true);
sceneClone.traverse((child) => {
if (child.userData && child.userData.skipExport === true) {
if (child.parent) child.parent.remove(child);
}
});
const exporter = new GLTFExporter();
const options = {
binary: true,
embedImages: true,
};
exporter.parse(
sceneClone,
(result) => {
if (result instanceof ArrayBuffer) {
const blob = new Blob([result], { type: "model/gltf-binary" });
const link = document.createElement("a");
link.style.display = "none";
document.body.appendChild(link);
link.href = URL.createObjectURL(blob);
link.download = "scene.glb";
link.click();
document.body.removeChild(link);
} else {
console.error("GLB export failed: unexpected result", result);
}
},
function () {
console.log("An error happened");
},
options
);
};
return <></>;
}

View File

@ -1,5 +1,5 @@
import { css } from "@emotion/react";
import { Space } from "../three/Space";
import { ExportButton, Space } from "../three/Space";
import { FullscreenModal } from "../components/FullscreenModal";
import { Title } from "@/components/text/Title";
import { Description } from "@/components/text/Description";
@ -8,8 +8,9 @@ import { MapComponent } from "@/components/map/SelectMap";
import { useState } from "react";
import { NextButton, PrevButton } from "@/components/button/BottomButton";
import { BuildingHeights } from "@/components/map/Processing";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { ChevronLeft, ChevronRight, Download } from "lucide-react";
import { useAreaStore } from "@/state/areaStore";
import { useActionStore } from "@/state/exportStore";
const IconSize = css({
width: "14px",
@ -22,6 +23,7 @@ function App() {
const [steps, setSteps] = useState(["front", "processing"]);
const [step, setStep] = useState(0);
const setCenter = useAreaStore((state) => state.setCenter);
const setAction = useActionStore((state) => state.setAction);
const handleDone = (data) => {
setAreaData(data);
@ -43,6 +45,10 @@ function App() {
setStep(step - 1);
};
const handleClickExport = () => {
setAction(true);
};
return (
<div css={css({ height: "100%", width: "100%" })}>
<FullscreenModal isOpen={steps[step] == "front"}>
@ -78,14 +84,18 @@ function App() {
</PrevButton>
<NextButton
isShow={true}
isShow={step != 2}
disabled={isNextButtonDisabled}
onClick={handleClickNextStep}
>
Next Step <ChevronRight css={IconSize} />
</NextButton>
<Space />
<NextButton isShow={step == 2} onClick={handleClickExport}>
Export GLB <Download css={IconSize} />
</NextButton>
<Space></Space>
</div>
);
}