fix(viewer): 修复墙体材质切换后缩放旋转时颜色恢复问题

问题描述:
选中墙体并切换材质颜色后,在界面上进行缩放、旋转或移动操作时,
墙体实际渲染的颜色会恢复为原来的白色,但颜色选项卡显示仍为选中的颜色。

根本原因:
WallCutout 系统在每次相机移动时,直接使用固定的白色材质覆盖墙体材质,
完全忽略了用户在节点上设置的自定义材质。

修复内容:
- 为每个墙体创建独立的材质对象,支持用户自定义颜色
- 支持预设颜色(brick, concrete, wood 等)和自定义颜色属性
- 添加材质哈希值跟踪,检测材质变化并重新创建材质
- 提取 getWallHideState() 辅助函数,优化代码结构
- 添加预设颜色映射的类型安全改进,使用 as const 和 keyof 类型操作
This commit is contained in:
Developer
2026-03-30 20:21:48 +08:00
parent 12b6292623
commit d0cdeb2ac4
8 changed files with 180 additions and 68 deletions
@@ -40,13 +40,14 @@ export const CeilingRenderer = ({ node }: { node: CeilingNode }) => {
const handlers = useNodeEvents(node, 'ceiling')
const materials = useMemo(() => {
if (node.material) {
const props = node.material.properties
const mat = node.material
if (mat) {
const props = mat.properties
const color = props?.color || '#999999'
return createCeilingMaterials(color)
}
return { topMaterial: createCeilingMaterials().topMaterial, bottomMaterial: DEFAULT_CEILING_MATERIAL }
}, [node.material])
}, [node.material, node.material?.preset, node.material?.properties, node.material?.texture])
return (
<mesh material={materials.bottomMaterial} ref={ref}>
@@ -12,8 +12,10 @@ export const DoorRenderer = ({ node }: { node: DoorNode }) => {
const isTransient = !!(node.metadata as Record<string, unknown> | null)?.isTransient
const material = useMemo(() => {
return node.material ? createMaterial(node.material) : DEFAULT_DOOR_MATERIAL
}, [node.material])
const mat = node.material
if (!mat) return DEFAULT_DOOR_MATERIAL
return createMaterial(mat)
}, [node.material, node.material?.preset, node.material?.properties, node.material?.texture])
return (
<mesh
@@ -1,7 +1,8 @@
import { type RoofSegmentNode, useRegistry } from '@pascal-app/core'
import { useRef } from 'react'
import { useMemo, useRef } from 'react'
import type * as THREE from 'three'
import { useNodeEvents } from '../../../hooks/use-node-events'
import { createMaterial } from '../../../lib/materials'
import useViewer from '../../../store/use-viewer'
import { roofDebugMaterials, roofMaterials } from '../roof/roof-materials'
@@ -13,9 +14,17 @@ export const RoofSegmentRenderer = ({ node }: { node: RoofSegmentNode }) => {
const handlers = useNodeEvents(node, 'roof-segment')
const debugColors = useViewer((s) => s.debugColors)
const customMaterial = useMemo(() => {
const mat = node.material
if (!mat) return null
return createMaterial(mat)
}, [node.material, node.material?.preset, node.material?.properties, node.material?.texture])
const material = debugColors ? roofDebugMaterials : customMaterial || roofMaterials
return (
<mesh
material={debugColors ? roofDebugMaterials : roofMaterials}
material={material}
position={node.position}
ref={ref}
rotation-y={node.rotation}
@@ -26,4 +35,4 @@ export const RoofSegmentRenderer = ({ node }: { node: RoofSegmentNode }) => {
<boxGeometry args={[0, 0, 0]} />
</mesh>
)
}
}
@@ -16,8 +16,10 @@ export const RoofRenderer = ({ node }: { node: RoofNode }) => {
const debugColors = useViewer((s) => s.debugColors)
const customMaterial = useMemo(() => {
return node.material ? createMaterial(node.material) : null
}, [node.material])
const mat = node.material
if (!mat) return null
return createMaterial(mat)
}, [node.material, node.material?.preset, node.material?.properties, node.material?.texture])
const material = debugColors ? roofDebugMaterials : customMaterial || roofMaterials
@@ -12,8 +12,10 @@ export const SlabRenderer = ({ node }: { node: SlabNode }) => {
const handlers = useNodeEvents(node, 'slab')
const material = useMemo(() => {
return node.material ? createMaterial(node.material) : DEFAULT_SLAB_MATERIAL
}, [node.material])
const mat = node.material
if (!mat) return DEFAULT_SLAB_MATERIAL
return createMaterial(mat)
}, [node.material, node.material?.preset, node.material?.properties, node.material?.texture])
return (
<mesh castShadow receiveShadow ref={ref} {...handlers} visible={node.visible} material={material}>
@@ -17,8 +17,10 @@ export const WallRenderer = ({ node }: { node: WallNode }) => {
const handlers = useNodeEvents(node, 'wall')
const material = useMemo(() => {
return node.material ? createMaterial(node.material) : DEFAULT_WALL_MATERIAL
}, [node.material])
const mat = node.material
if (!mat) return DEFAULT_WALL_MATERIAL
return createMaterial(mat)
}, [node.material, node.material?.preset, node.material?.properties, node.material?.texture])
return (
<mesh castShadow receiveShadow ref={ref} visible={node.visible} material={material}>
@@ -12,8 +12,10 @@ export const WindowRenderer = ({ node }: { node: WindowNode }) => {
const isTransient = !!(node.metadata as Record<string, unknown> | null)?.isTransient
const material = useMemo(() => {
return node.material ? createMaterial(node.material) : DEFAULT_WINDOW_MATERIAL
}, [node.material])
const mat = node.material
if (!mat) return DEFAULT_WINDOW_MATERIAL
return createMaterial(mat)
}, [node.material, node.material?.preset, node.material?.properties, node.material?.texture])
return (
<mesh