-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
18,103 additions
and
106 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { useCallback, useMemo, useRef, useState } from "react"; | ||
|
||
export function getMeshColor() { | ||
const instanceRef = useRef<{ [key: string]: string } | null>(null); // シングルトンインスタンスを保持 | ||
const [visibility, setVisibility] = useState<{ | ||
[key: string]: string; | ||
} | null>(null); | ||
|
||
const generateMeshColor = useCallback((): { [key: string]: string } => { | ||
if (instanceRef.current) return instanceRef.current; // 既存のインスタンスを返す | ||
|
||
// 初期値は白 | ||
instanceRef.current = { | ||
hairear: "#ffffff", | ||
hairback: "#ffffff", | ||
hairfront: "#ffffff", | ||
hairtail: "#ffffff", | ||
hairsid: "#ffffff", | ||
hair: "#ffffff", | ||
}; | ||
setVisibility(instanceRef.current); // 状態を更新 | ||
return instanceRef.current; | ||
}, []); | ||
|
||
const updateVisibility = useCallback((key: string, color: string) => { | ||
if (instanceRef.current) { | ||
instanceRef.current[key] = color; // 値を更新 | ||
if (key === "hair") { | ||
instanceRef.current.hairear = color; | ||
instanceRef.current.hairback = color; | ||
instanceRef.current.hairfront = color; | ||
instanceRef.current.hairtail = color; | ||
instanceRef.current.hairsid = color; | ||
} | ||
setVisibility({ ...instanceRef.current }); // 状態を更新 | ||
} | ||
}, []); | ||
|
||
const meshColor = useMemo(() => { | ||
return visibility || generateMeshColor(); // 初回は生成したものを使用 | ||
}, [visibility, generateMeshColor]); | ||
|
||
return { meshColor, updateVisibility }; // 可視性と更新関数を返す | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { useEffect, useState } from "react"; | ||
|
||
interface ModelListProps { | ||
onModelSelect: (modelPath: string) => void; | ||
updateVisibility: (key: string, value: boolean) => void; | ||
updateColor: (key: string, value: string) => void; | ||
} | ||
|
||
export function ModelConfig({ | ||
onModelSelect, | ||
updateVisibility, | ||
updateColor, | ||
}: ModelListProps) { | ||
const [models, setModels] = useState<string[]>([]); | ||
const [containerWidth, setContainerWidth] = useState(0); | ||
const [selectedColors, setSelectedColors] = useState<Record<string, string>>( | ||
{}, | ||
); | ||
|
||
useEffect(() => { | ||
const fetchModels = async () => { | ||
const modelFiles = [ | ||
"/models/web_asuka.glb", | ||
"/models/web_jiraichan.glb", | ||
"/models/web_kaiju.glb", | ||
"/models/web_necochan.glb", | ||
"/models/web_sushong.glb", | ||
]; | ||
setModels(modelFiles); | ||
}; | ||
|
||
fetchModels(); | ||
|
||
const updateContainerWidth = () => { | ||
const containerRef = document.getElementById("model-list-container"); | ||
if (containerRef) { | ||
setContainerWidth(containerRef.offsetWidth); | ||
} | ||
}; | ||
|
||
updateContainerWidth(); | ||
window.addEventListener("resize", updateContainerWidth); | ||
|
||
return () => window.removeEventListener("resize", updateContainerWidth); | ||
}, []); | ||
|
||
// ボタンの幅を計算 | ||
const buttonWidth = `${Math.floor(containerWidth / models.length) - 16}px`; | ||
// 表示非表示のキー | ||
const visibilityKeys = [ | ||
"accessoryeyepatch", | ||
"accessoryglasses", | ||
"goggle", | ||
"goggle_1", | ||
"accessorymask", | ||
]; | ||
|
||
return ( | ||
<div> | ||
{/* モデル選択ボタン */} | ||
<div | ||
id="model-list-container" | ||
className="mb-4 flex flex-wrap justify-center space-x-2" | ||
> | ||
{models.map((model) => ( | ||
<button | ||
key={model} | ||
type="button" | ||
onClick={() => onModelSelect(model)} | ||
className="rounded bg-gray-200 px-4 py-2 font-medium text-gray-800 hover:bg-gray-300" | ||
style={{ width: buttonWidth }} | ||
> | ||
{model.split("/").pop()?.split("_")[1].split(".")[0]} | ||
</button> | ||
))} | ||
</div> | ||
{/* 表示非表示ボタン */} | ||
<div className="flex flex-col items-center"> | ||
{visibilityKeys.map((key) => ( | ||
<div key={key} className="flex items-center space-x-2"> | ||
<span>{key}</span> | ||
<button | ||
type="button" | ||
onClick={() => updateVisibility(key, true)} // 表示するボタン | ||
className="rounded bg-green-200 px-2 py-1 text-gray-800 hover:bg-green-300" | ||
> | ||
表示 | ||
</button> | ||
<button | ||
type="button" | ||
onClick={() => updateVisibility(key, false)} // 非表示にするボタン | ||
className="rounded bg-red-200 px-2 py-1 text-gray-800 hover:bg-red-300" | ||
> | ||
非表示 | ||
</button> | ||
</div> | ||
))} | ||
</div> | ||
{/* 色変更ボタン */} | ||
<div className="flex items-center justify-center space-x-2"> | ||
<span className="font-medium">Hair Color:</span> | ||
<input | ||
type="color" | ||
value={selectedColors.hair} | ||
onChange={(e) => { | ||
setSelectedColors({ ...selectedColors, hair: e.target.value }); | ||
updateColor("hair", e.target.value); | ||
}} | ||
className="h-8 w-8 cursor-pointer rounded border border-gray-300" | ||
title="髪の色を変更" | ||
/> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import { Environment, OrbitControls, useGLTF } from "@react-three/drei"; | ||
import { Canvas } from "@react-three/fiber"; | ||
import { type ReactNode, useEffect, useRef } from "react"; | ||
import type { Group, MeshStandardMaterial } from "three"; | ||
import type * as THREE from "three"; | ||
|
||
function Model({ | ||
path, | ||
colorMap, | ||
meshVisibility, | ||
}: { | ||
path: string; | ||
colorMap: { [key: string]: string }; | ||
meshVisibility: { [key: string]: boolean }; | ||
}): ReactNode { | ||
const { scene } = useGLTF(path); | ||
// モデルのグループ(オブジェクト全体)にアクセスするための参照を作成 | ||
const groupRef = useRef<Group>(null); | ||
|
||
useEffect(() => { | ||
scene.traverse((child: THREE.Object3D) => { | ||
if ((child as THREE.Mesh).isMesh) { | ||
const mesh = child as THREE.Mesh; | ||
|
||
// メッシュ名をコンソールに出力 | ||
// console.log("Mesh name:", mesh.name); | ||
|
||
// 部位名がmeshVisibilityのキーに一致する場合に表示非表示を設定 | ||
if ( | ||
Object.hasOwn(meshVisibility, mesh.name) && | ||
!meshVisibility[mesh.name] | ||
) { | ||
mesh.visible = false; | ||
return; | ||
} | ||
|
||
mesh.visible = true; | ||
|
||
const material = mesh.material as MeshStandardMaterial; | ||
|
||
// 部位名がcolorMapのキーに一致する場合に色を設定 | ||
if (Object.hasOwn(colorMap, mesh.name)) { | ||
material.color.set(colorMap[mesh.name]); | ||
} | ||
} | ||
}); | ||
}, [scene, colorMap, meshVisibility]); | ||
|
||
return ( | ||
// グループとしてシーンをレンダリング | ||
<group ref={groupRef} position={[0, -0.5, 0]}> | ||
{/* モデルのプリミティブ(生のオブジェクト)を表示 */} | ||
<primitive object={scene} /> | ||
</group> | ||
); | ||
} | ||
|
||
// 3Dモデルビューアーコンポーネント | ||
// モデルのパスを受け取って、それを表示する | ||
type ModelViewerProps = { | ||
modelPath: string; // モデルのパス | ||
colorMap: { [key: string]: string }; // 部位ごとの色マップ | ||
meshVisibility: { [key: string]: boolean }; // 部位ごとの表示非表示 | ||
}; | ||
export function ModelViewer({ | ||
modelPath, | ||
colorMap, | ||
meshVisibility, | ||
}: ModelViewerProps): ReactNode { | ||
return ( | ||
<div> | ||
{/* 3Dモデルを表示するためのCanvasエリア */} | ||
<div style={{ height: "60vh" }}> | ||
<Canvas | ||
camera={{ | ||
position: [0, 0, 4], | ||
fov: 30, | ||
}} // カメラの初期位置と視野角を設定 | ||
> | ||
{/* 環境を設定*/} | ||
<Environment preset="studio" /> | ||
{/* 環境光を追加(全体的に均一な光を当てる) */} | ||
<ambientLight intensity={0.5} /> | ||
{/* GLBモデルの読み込みと表示 */} | ||
<Model | ||
path={modelPath} | ||
colorMap={colorMap} | ||
meshVisibility={meshVisibility} | ||
/> | ||
{/* カメラコントロールの追加(ユーザーが自由にカメラを操作できるようにする) */} | ||
<OrbitControls makeDefault /> | ||
</Canvas> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { useCallback, useMemo, useRef, useState } from "react"; | ||
|
||
export function getMeshVisibility() { | ||
const instanceRef = useRef<{ [key: string]: boolean } | null>(null); // シングルトンインスタンスを保持 | ||
const [visibility, setVisibility] = useState<{ | ||
[key: string]: boolean; | ||
} | null>(null); | ||
|
||
const generateMeshVisibility = useCallback((): { [key: string]: boolean } => { | ||
if (instanceRef.current) return instanceRef.current; // 既存のインスタンスを返す | ||
|
||
// 初期値は非表示 | ||
instanceRef.current = { | ||
accessoryeyepatch: false, | ||
accessoryglasses: false, | ||
goggle: false, | ||
goggle_1: false, | ||
accessorymask: false, | ||
}; | ||
setVisibility(instanceRef.current); // 状態を更新 | ||
return instanceRef.current; | ||
}, []); | ||
|
||
const updateVisibility = useCallback((key: string, value: boolean) => { | ||
if (instanceRef.current) { | ||
instanceRef.current[key] = value; // 値を更新 | ||
setVisibility({ ...instanceRef.current }); // 状態を更新 | ||
} | ||
}, []); | ||
|
||
const meshVisibility = useMemo(() => { | ||
return visibility || generateMeshVisibility(); // 初回は生成したものを使用 | ||
}, [visibility, generateMeshVisibility]); | ||
|
||
return { meshVisibility, updateVisibility }; // 可視性と更新関数を返す | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.