This commit is contained in:
wass08
2026-01-21 10:03:29 +09:00
parent 14b0e6a98d
commit 5de0818633
53 changed files with 1241 additions and 1420 deletions
@@ -1,19 +1,19 @@
import { BuildingNode, useRegistry } from "@pascal-app/core";
import { useRef } from "react";
import { Group } from "three";
import { NodeRenderer } from "../node-renderer";
import { useNodeEvents } from "../../../hooks/use-node-events";
import { type BuildingNode, useRegistry } from '@pascal-app/core'
import { useRef } from 'react'
import type { Group } from 'three'
import { useNodeEvents } from '../../../hooks/use-node-events'
import { NodeRenderer } from '../node-renderer'
export const BuildingRenderer = ({ node }: { node: BuildingNode }) => {
const ref = useRef<Group>(null!);
const ref = useRef<Group>(null!)
useRegistry(node.id, node.type, ref);
const handlers = useNodeEvents(node, "building");
useRegistry(node.id, node.type, ref)
const handlers = useNodeEvents(node, 'building')
return (
<group ref={ref} {...handlers}>
{node.children.map((childId) => (
<NodeRenderer key={childId} nodeId={childId} />
))}
</group>
);
};
)
}
@@ -1,21 +1,14 @@
import {
AnyNodeId,
emitter,
ItemNode,
useRegistry,
useScene,
} from "@pascal-app/core";
import { Clone } from "@react-three/drei/core/Clone";
import { useGLTF } from "@react-three/drei/core/Gltf";
import { ThreeEvent } from "@react-three/fiber/dist/declarations/src/core/events";
import { Suspense, useCallback, useEffect, useRef } from "react";
import { Group } from "three";
import { useNodeEvents } from "../../../hooks/use-node-events";
import { type AnyNodeId, type ItemNode, useRegistry, useScene } from '@pascal-app/core'
import { Clone } from '@react-three/drei/core/Clone'
import { useGLTF } from '@react-three/drei/core/Gltf'
import { Suspense, useEffect, useRef } from 'react'
import type { Group } from 'three'
import { useNodeEvents } from '../../../hooks/use-node-events'
export const ItemRenderer = ({ node }: { node: ItemNode }) => {
const ref = useRef<Group>(null!);
const ref = useRef<Group>(null!)
useRegistry(node.id, node.type, ref);
useRegistry(node.id, node.type, ref)
return (
<group position={node.position} rotation={node.rotation} ref={ref}>
@@ -23,22 +16,22 @@ export const ItemRenderer = ({ node }: { node: ItemNode }) => {
<ModelRenderer node={node} />
</Suspense>
</group>
);
};
)
}
const ModelRenderer = ({ node }: { node: ItemNode }) => {
const { scene, nodes } = useGLTF(node.asset.src);
const { scene, nodes } = useGLTF(node.asset.src)
if (nodes.cutout) {
nodes.cutout.visible = false;
nodes.cutout.visible = false
}
const handlers = useNodeEvents(node, "item");
const handlers = useNodeEvents(node, 'item')
useEffect(() => {
if (!node.parentId) return;
useScene.getState().dirtyNodes.add(node.parentId as AnyNodeId);
}, []);
if (!node.parentId) return
useScene.getState().dirtyNodes.add(node.parentId as AnyNodeId)
}, [node.parentId])
return (
<Clone
@@ -48,5 +41,5 @@ const ModelRenderer = ({ node }: { node: ItemNode }) => {
rotation={node.asset.rotation}
{...handlers}
/>
);
};
)
}
@@ -1,12 +1,12 @@
import { LevelNode, useRegistry } from "@pascal-app/core";
import { useRef } from "react";
import { Group } from "three";
import { NodeRenderer } from "../node-renderer";
import { type LevelNode, useRegistry } from '@pascal-app/core'
import { useRef } from 'react'
import type { Group } from 'three'
import { NodeRenderer } from '../node-renderer'
export const LevelRenderer = ({ node }: { node: LevelNode }) => {
const ref = useRef<Group>(null!);
const ref = useRef<Group>(null!)
useRegistry(node.id, node.type, ref);
useRegistry(node.id, node.type, ref)
return (
<group ref={ref}>
@@ -18,5 +18,5 @@ export const LevelRenderer = ({ node }: { node: LevelNode }) => {
<NodeRenderer key={childId} nodeId={childId} />
))}
</group>
);
};
)
}
@@ -1,22 +1,22 @@
"use client";
'use client'
import { AnyNode, useScene } from "@pascal-app/core";
import { ItemRenderer } from "./item/item-renderer";
import { LevelRenderer } from "./level/level-renderer";
import { WallRenderer } from "./wall/wall-renderer";
import { BuildingRenderer } from "./building/building-renderer";
import { type AnyNode, useScene } from '@pascal-app/core'
import { BuildingRenderer } from './building/building-renderer'
import { ItemRenderer } from './item/item-renderer'
import { LevelRenderer } from './level/level-renderer'
import { WallRenderer } from './wall/wall-renderer'
export const NodeRenderer = ({ nodeId }: { nodeId: AnyNode["id"] }) => {
const node = useScene((state) => state.nodes[nodeId]);
export const NodeRenderer = ({ nodeId }: { nodeId: AnyNode['id'] }) => {
const node = useScene((state) => state.nodes[nodeId])
if (!node) return null;
if (!node) return null
return (
<>
{node.type === "building" && <BuildingRenderer node={node} />}
{node.type === "level" && <LevelRenderer node={node} />}
{node.type === "item" && <ItemRenderer node={node} />}
{node.type === "wall" && <WallRenderer node={node} />}
{node.type === 'building' && <BuildingRenderer node={node} />}
{node.type === 'level' && <LevelRenderer node={node} />}
{node.type === 'item' && <ItemRenderer node={node} />}
{node.type === 'wall' && <WallRenderer node={node} />}
</>
);
};
)
}
@@ -1,12 +1,10 @@
"use client";
'use client'
import { useScene } from "@pascal-app/core";
import { NodeRenderer } from "./node-renderer";
import { useScene } from '@pascal-app/core'
import { NodeRenderer } from './node-renderer'
export const SceneRenderer = () => {
const rootNodes = useScene((state) => state.rootNodeIds);
const rootNodes = useScene((state) => state.rootNodeIds)
return rootNodes.map((nodeId) => (
<NodeRenderer key={nodeId} nodeId={nodeId} />
));
};
return rootNodes.map((nodeId) => <NodeRenderer key={nodeId} nodeId={nodeId} />)
}
@@ -1,15 +1,15 @@
import { useRegistry, WallNode } from "@pascal-app/core";
import { useRef } from "react";
import { Mesh } from "three";
import { NodeRenderer } from "../node-renderer";
import { useNodeEvents } from "../../../hooks/use-node-events";
import { useRegistry, type WallNode } from '@pascal-app/core'
import { useRef } from 'react'
import type { Mesh } from 'three'
import { useNodeEvents } from '../../../hooks/use-node-events'
import { NodeRenderer } from '../node-renderer'
export const WallRenderer = ({ node }: { node: WallNode }) => {
const ref = useRef<Mesh>(null!);
const ref = useRef<Mesh>(null!)
useRegistry(node.id, "wall", ref);
useRegistry(node.id, 'wall', ref)
const handlers = useNodeEvents(node, "wall");
const handlers = useNodeEvents(node, 'wall')
return (
<mesh ref={ref} castShadow receiveShadow>
@@ -24,5 +24,5 @@ export const WallRenderer = ({ node }: { node: WallNode }) => {
<NodeRenderer key={childId} nodeId={childId} />
))}
</mesh>
);
};
)
}
+19 -21
View File
@@ -1,38 +1,36 @@
"use client";
'use client'
import { Bvh, Environment, OrbitControls } from "@react-three/drei";
import { Canvas, ThreeToJSXElements } from "@react-three/fiber";
import { WallSystem } from '@pascal-app/core'
import { Bvh, Environment } from '@react-three/drei'
import { Canvas, extend, type ThreeToJSXElements } from '@react-three/fiber'
import * as THREE from 'three/webgpu'
import { LevelSystem } from '../../systems/level/level-system'
import { SceneRenderer } from '../renderers/scene-renderer'
import PostProcessing from './post-processing'
import { WallSystem } from "@pascal-app/core";
import { extend } from "@react-three/fiber";
import * as THREE from "three/webgpu";
import { SceneRenderer } from "../renderers/scene-renderer";
import { LevelSystem } from "../../systems/level/level-system";
import PostProcessing from "./post-processing";
declare module "@react-three/fiber" {
declare module '@react-three/fiber' {
interface ThreeElements extends ThreeToJSXElements<typeof THREE> {}
}
extend(THREE as any);
extend(THREE as any)
interface ViewerProps {
children?: React.ReactNode;
children?: React.ReactNode
}
const Viewer: React.FC<ViewerProps> = ({ children }) => {
return (
<Canvas
className={"bg-[#303035]"}
className={'bg-[#303035]'}
gl={async (props) => {
const renderer = new THREE.WebGPURenderer(props as any);
await renderer.init();
return renderer;
const renderer = new THREE.WebGPURenderer(props as any)
await renderer.init()
return renderer
}}
shadows
camera={{ position: [50, 50, 50], fov: 50 }}
>
<color attach="background" args={["#ececec"]} />
<color attach="background" args={['#ececec']} />
<Environment preset="sunset" />
<Bvh>
@@ -46,7 +44,7 @@ const Viewer: React.FC<ViewerProps> = ({ children }) => {
{children}
</Canvas>
);
};
)
}
export default Viewer;
export default Viewer
@@ -1,99 +1,93 @@
import { useFrame, useThree } from "@react-three/fiber";
import { useEffect, useRef } from "react";
import { Color, Object3D } from "three";
import { oscSine, pass, time, uniform } from "three/tsl";
import { outline } from "three/addons/tsl/display/OutlineNode.js";
import { PostProcessing, WebGPURenderer } from "three/webgpu";
import useViewer from "../../store/use-viewer";
import { useFrame, useThree } from '@react-three/fiber'
import { useEffect, useRef } from 'react'
import { Color } from 'three'
import { outline } from 'three/addons/tsl/display/OutlineNode.js'
import { oscSine, pass, time, uniform } from 'three/tsl'
import { PostProcessing, type WebGPURenderer } from 'three/webgpu'
import useViewer from '../../store/use-viewer'
const PostProcessingPasses = ({}) => {
const { gl: renderer, scene, camera } = useThree();
const postProcessingRef = useRef<PostProcessing | null>(null);
const { gl: renderer, scene, camera } = useThree()
const postProcessingRef = useRef<PostProcessing | null>(null)
useEffect(() => {
if (!renderer || !scene || !camera) {
return;
return
}
const scenePass = pass(scene, camera);
const scenePass = pass(scene, camera)
function generateSelectedOutlinePass() {
const edgeStrength = uniform(3);
const edgeGlow = uniform(0);
const edgeThickness = uniform(1);
const visibleEdgeColor = uniform(new Color(0xffffff));
const hiddenEdgeColor = uniform(new Color(0xf3ff47));
const edgeStrength = uniform(3)
const edgeGlow = uniform(0)
const edgeThickness = uniform(1)
const visibleEdgeColor = uniform(new Color(0xffffff))
const hiddenEdgeColor = uniform(new Color(0xf3ff47))
const outlinePass = outline(scene, camera, {
selectedObjects: useViewer.getState().outliner.selectedObjects,
edgeGlow,
edgeThickness,
});
const { visibleEdge, hiddenEdge } = outlinePass;
})
const { visibleEdge, hiddenEdge } = outlinePass
const outlineColor = visibleEdge
.mul(visibleEdgeColor)
.add(hiddenEdge.mul(hiddenEdgeColor))
.mul(edgeStrength);
.mul(edgeStrength)
return outlineColor;
return outlineColor
}
function generateHoverOutlinePass() {
const edgeStrength = uniform(5);
const edgeGlow = uniform(0.5);
const edgeThickness = uniform(1.5);
const pulsePeriod = uniform(3);
const visibleEdgeColor = uniform(new Color(0x00aaff));
const hiddenEdgeColor = uniform(new Color(0xf3ff47));
const edgeStrength = uniform(5)
const edgeGlow = uniform(0.5)
const edgeThickness = uniform(1.5)
const pulsePeriod = uniform(3)
const visibleEdgeColor = uniform(new Color(0x00aaff))
const hiddenEdgeColor = uniform(new Color(0xf3ff47))
const outlinePass = outline(scene, camera, {
selectedObjects: useViewer.getState().outliner.hoveredObjects,
edgeGlow,
edgeThickness,
});
const { visibleEdge, hiddenEdge } = outlinePass;
})
const { visibleEdge, hiddenEdge } = outlinePass
const period = time.div(pulsePeriod).mul(2);
const osc = oscSine(period).mul(0.5).add(0.5); // osc [ 0.5, 1.0 ]
const period = time.div(pulsePeriod).mul(2)
const osc = oscSine(period).mul(0.5).add(0.5) // osc [ 0.5, 1.0 ]
const outlineColor = visibleEdge
.mul(visibleEdgeColor)
.add(hiddenEdge.mul(hiddenEdgeColor))
.mul(edgeStrength);
const outlinePulse = pulsePeriod
.greaterThan(0)
.select(outlineColor.mul(osc), outlineColor);
return outlinePulse;
.mul(edgeStrength)
const outlinePulse = pulsePeriod.greaterThan(0).select(outlineColor.mul(osc), outlineColor)
return outlinePulse
}
// Setup post-processing
const postProcessing = new PostProcessing(
renderer as unknown as WebGPURenderer,
);
const postProcessing = new PostProcessing(renderer as unknown as WebGPURenderer)
const selectedOutlinePass = generateSelectedOutlinePass();
const hoverOutlinePass = generateHoverOutlinePass();
const selectedOutlinePass = generateSelectedOutlinePass()
const hoverOutlinePass = generateHoverOutlinePass()
postProcessing.outputNode = selectedOutlinePass
.add(hoverOutlinePass)
.add(scenePass);
postProcessingRef.current = postProcessing;
postProcessing.outputNode = selectedOutlinePass.add(hoverOutlinePass).add(scenePass)
postProcessingRef.current = postProcessing
return () => {
if (postProcessingRef.current) {
postProcessingRef.current.dispose();
postProcessingRef.current.dispose()
}
postProcessingRef.current = null;
};
}, [renderer, scene, camera]);
postProcessingRef.current = null
}
}, [renderer, scene, camera])
useFrame(() => {
if (postProcessingRef.current) {
postProcessingRef.current.render();
postProcessingRef.current.render()
}
}, 1);
}, 1)
return null;
};
return null
}
export default PostProcessingPasses;
export default PostProcessingPasses