Fix slab material reload and wall curve step
This commit is contained in:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user