Fix slab material reload and wall curve step

This commit is contained in:
sudhir
2026-04-20 14:13:47 +05:30
parent b715e96038
commit dd5b6e5e83
3 changed files with 43 additions and 20 deletions
@@ -21,6 +21,20 @@ function stepPrecision(s: number): number {
return Math.max(0, Math.ceil(-Math.log10(s))) return Math.max(0, Math.ceil(-Math.log10(s)))
} }
function getAdjustedStep(
baseStep: number,
modifiers: {
shiftKey?: boolean
metaKey?: boolean
ctrlKey?: boolean
altKey?: boolean
},
): number {
if (modifiers.shiftKey) return baseStep * 10
if (modifiers.metaKey || modifiers.ctrlKey || modifiers.altKey) return baseStep * 0.1
return baseStep
}
export function SliderControl({ export function SliderControl({
label, label,
value, value,
@@ -58,16 +72,14 @@ export function SliderControl({
if (isEditing) return if (isEditing) return
e.preventDefault() e.preventDefault()
const direction = e.deltaY < 0 ? 1 : -1 const direction = e.deltaY < 0 ? 1 : -1
let s = step const s = getAdjustedStep(step, e)
if (e.shiftKey) s = step * 10
else if (e.altKey) s = step * 0.1
const newValue = clamp(valueRef.current + direction * s) const newValue = clamp(valueRef.current + direction * s)
const final = Number.parseFloat(newValue.toFixed(stepPrecision(s))) const final = Number.parseFloat(newValue.toFixed(stepPrecision(s)))
if (final !== valueRef.current) onChange(final) if (final !== valueRef.current) onChange(final)
} }
el.addEventListener('wheel', handleWheel, { passive: false }) el.addEventListener('wheel', handleWheel, { passive: false })
return () => el.removeEventListener('wheel', handleWheel) return () => el.removeEventListener('wheel', handleWheel)
}, [isEditing, step, clamp, onChange, precision]) }, [isEditing, step, clamp, onChange])
// Arrow key support while hovered // Arrow key support while hovered
useEffect(() => { useEffect(() => {
@@ -78,9 +90,7 @@ export function SliderControl({
else if (e.key === 'ArrowDown' || e.key === 'ArrowLeft') direction = -1 else if (e.key === 'ArrowDown' || e.key === 'ArrowLeft') direction = -1
if (direction !== 0) { if (direction !== 0) {
e.preventDefault() e.preventDefault()
let s = step const s = getAdjustedStep(step, e)
if (e.shiftKey) s = step * 10
else if (e.metaKey || e.ctrlKey) s = step * 0.1
const newValue = clamp(valueRef.current + direction * s) const newValue = clamp(valueRef.current + direction * s)
const final = Number.parseFloat(newValue.toFixed(stepPrecision(s))) const final = Number.parseFloat(newValue.toFixed(stepPrecision(s)))
if (final !== valueRef.current) onChange(final) if (final !== valueRef.current) onChange(final)
@@ -88,7 +98,7 @@ export function SliderControl({
} }
window.addEventListener('keydown', handleKeyDown) window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown) return () => window.removeEventListener('keydown', handleKeyDown)
}, [isHovered, isEditing, step, clamp, onChange, precision]) }, [isHovered, isEditing, step, clamp, onChange])
const handleLabelPointerDown = useCallback( const handleLabelPointerDown = useCallback(
(e: React.PointerEvent<HTMLDivElement>) => { (e: React.PointerEvent<HTMLDivElement>) => {
@@ -107,16 +117,14 @@ export function SliderControl({
if (!dragRef.current) return if (!dragRef.current) return
const { startX, startValue } = dragRef.current const { startX, startValue } = dragRef.current
const dx = e.clientX - startX const dx = e.clientX - startX
let s = step const s = getAdjustedStep(step, e)
if (e.shiftKey) s = step * 10
else if (e.metaKey || e.ctrlKey) s = step * 0.1
// 4 px per step at default sensitivity // 4 px per step at default sensitivity
const newValue = clamp( const newValue = clamp(
Number.parseFloat((startValue + (dx / 4) * s).toFixed(stepPrecision(s))), Number.parseFloat((startValue + (dx / 4) * s).toFixed(stepPrecision(s))),
) )
onChange(newValue) onChange(newValue)
}, },
[step, precision, clamp, onChange], [step, clamp, onChange],
) )
const handleLabelPointerUp = useCallback( const handleLabelPointerUp = useCallback(
@@ -163,12 +171,18 @@ export function SliderControl({
setIsEditing(false) setIsEditing(false)
} else if (e.key === 'ArrowUp') { } else if (e.key === 'ArrowUp') {
e.preventDefault() e.preventDefault()
const newV = clamp(value + step) const adjustedStep = getAdjustedStep(step, e)
const newV = clamp(
Number.parseFloat((value + adjustedStep).toFixed(stepPrecision(adjustedStep))),
)
onChange(newV) onChange(newV)
setInputValue(newV.toFixed(precision)) setInputValue(newV.toFixed(precision))
} else if (e.key === 'ArrowDown') { } else if (e.key === 'ArrowDown') {
e.preventDefault() e.preventDefault()
const newV = clamp(value - step) const adjustedStep = getAdjustedStep(step, e)
const newV = clamp(
Number.parseFloat((value - adjustedStep).toFixed(stepPrecision(adjustedStep))),
)
onChange(newV) onChange(newV)
setInputValue(newV.toFixed(precision)) setInputValue(newV.toFixed(precision))
} }
@@ -228,7 +228,7 @@ export function WallPanel() {
min={-Math.max(0.01, maxCurveOffset)} min={-Math.max(0.01, maxCurveOffset)}
onChange={(v) => handleUpdate({ curveOffset: normalizeWallCurveOffset(node, v) })} onChange={(v) => handleUpdate({ curveOffset: normalizeWallCurveOffset(node, v) })}
precision={2} precision={2}
step={0.01} step={0.1}
unit="m" unit="m"
value={Math.round(curveOffset * 100) / 100} value={Math.round(curveOffset * 100) / 100}
/> />
@@ -1,11 +1,11 @@
import { type SlabNode, useRegistry } from '@pascal-app/core' import { getMaterialPresetByRef, type SlabNode, useRegistry } from '@pascal-app/core'
import { useEffect, useMemo, useRef } from 'react' import { useEffect, useMemo, useRef } from 'react'
import * as THREE from 'three' import * as THREE from 'three'
import type { Mesh } from 'three' import type { Mesh } from 'three'
import { useNodeEvents } from '../../../hooks/use-node-events' import { useNodeEvents } from '../../../hooks/use-node-events'
import { import {
applyMaterialPresetToMaterials,
createMaterial, createMaterial,
createMaterialFromPresetRef,
DEFAULT_SLAB_MATERIAL, DEFAULT_SLAB_MATERIAL,
} from '../../../lib/materials' } from '../../../lib/materials'
@@ -17,9 +17,18 @@ export const SlabRenderer = ({ node }: { node: SlabNode }) => {
const handlers = useNodeEvents(node, 'slab') const handlers = useNodeEvents(node, 'slab')
const material = useMemo(() => { const material = useMemo(() => {
const presetMaterial = createMaterialFromPresetRef(node.materialPreset) const preset = getMaterialPresetByRef(node.materialPreset)
const sourceMaterial = presetMaterial ?? (node.material ? createMaterial(node.material) : DEFAULT_SLAB_MATERIAL) const slabMaterial = preset
const slabMaterial = sourceMaterial.clone() ? new THREE.MeshStandardMaterial()
: node.material
? createMaterial(node.material).clone()
: DEFAULT_SLAB_MATERIAL.clone()
if (preset) {
// Apply the preset to the slab-owned material so async texture loads update
// the instance we actually render after refresh as well.
applyMaterialPresetToMaterials(slabMaterial, preset)
}
// Slabs participate in the WebGPU MRT scene pass. Keeping them opaque avoids // Slabs participate in the WebGPU MRT scene pass. Keeping them opaque avoids
// pipeline variants that can fail when geometry is regenerated while a // pipeline variants that can fail when geometry is regenerated while a