Getting aligned with three.js
and react-three-fiber
In the world of three.js everything is aligned centred on each axis by default. This means that if you place an object at a position of interest it will likely overlap something else in a way you did not intend it to. In the above example I'm rendering a simple box at position [0, 0, 0]
and, as you can see, it appears to have sunk in to the floor. I suspect this is expected if you think in 3d but I don't so let's see if we can fix it.
To fix this I'm using an <Alignment />
component and withAxisAlignment
hook that can shift the position of a child component to align it how you want
const ExampleTwo = () => {
const { CustomProperties, customProperties } = useCustomProperties([
'--🎨-background',
'--🎨-art-accent',
])
return (
<CustomProperties>
<ExampleScene>
<DebugBox
color={customProperties?.['--🎨-background'] || 'green'}
position={new Vector3(0, 0, 0)}
size={[0.5, 0.5, 0.5]}
/>
<Alignment
x={AxisAlignment.CENTER}
y={AxisAlignment.CENTER}
z={AxisAlignment.CENTER}
>
<DebugBox
color={customProperties?.['--🎨-art-accent'] || 'red'}
position={new Vector3(0.25, 0.25, 0.25)}
size={[0.5, 0.5, 0.5]}
/>
</Alignment>
</ExampleScene>
</CustomProperties>
)
}
const Alignment = ({
children,
x = AxisAlignment.CENTER,
y = AxisAlignment.CENTER,
z = AxisAlignment.CENTER,
offsets,
}: AlignmentProps) => {
if (!isValidElement(children)) {
throw new Error('Alignment requires a single React element as child')
}
const { position = new Vector3(0, 0, 0), size } = children.props
if (!size) {
throw new Error('Child component must have a size prop')
}
const alignedPosition = withAxisAlignment({
position,
size,
x,
y,
z,
offsets,
})
return cloneElement(children, {
position: alignedPosition,
})
}
const withAxisAlignment = ({
position = new Vector3(0, 0, 0),
size,
x = AxisAlignment.CENTER,
y = AxisAlignment.CENTER,
z = AxisAlignment.CENTER,
offsets = new Vector3(0, 0, 0),
}: {
position?: Vector3
size: BoxSize
x?: AxisAlignment
y?: AxisAlignment
z?: AxisAlignment
offsets?: Vector3
}) => {
const [width, height, depth] = size
const positionCopy = position.clone()
// Center alignment is the default
let alignedX = 0
let alignedY = 0
let alignedZ = 0
if (x === AxisAlignment.START) alignedX = width / 2
if (x === AxisAlignment.END) alignedX = -width / 2
if (y === AxisAlignment.START) alignedY = -height / 2
if (y === AxisAlignment.END) alignedY = height / 2
if (z === AxisAlignment.START) alignedZ = -depth / 2
if (z === AxisAlignment.END) alignedZ = depth / 2
return positionCopy
.add(new Vector3(alignedX, alignedY, alignedZ))
.add(offsets)
}