fix: use WALL_MIN_LENGTH constant to prevent ghost walls (#168)
* Increase minimum wall length from 0.01 to 0.5 * fix: use WALL_MIN_LENGTH constant in wall-tool.tsx
This commit is contained in:
@@ -1,40 +1,32 @@
|
|||||||
import { useScene, type WallNode, WallNode as WallSchema } from '@pascal-app/core'
|
import { useScene, type WallNode, WallNode as WallSchema } from '@pascal-app/core'
|
||||||
import { useViewer } from '@pascal-app/viewer'
|
import { useViewer } from '@pascal-app/viewer'
|
||||||
import { sfxEmitter } from '../../../lib/sfx-bus'
|
import { sfxEmitter } from '../../../lib/sfx-bus'
|
||||||
|
|
||||||
export type WallPlanPoint = [number, number]
|
export type WallPlanPoint = [number, number]
|
||||||
|
|
||||||
export const WALL_GRID_STEP = 0.5
|
export const WALL_GRID_STEP = 0.5
|
||||||
export const WALL_JOIN_SNAP_RADIUS = 0.35
|
export const WALL_JOIN_SNAP_RADIUS = 0.35
|
||||||
export const WALL_MIN_LENGTH = 0.01
|
export const WALL_MIN_LENGTH = 0.5
|
||||||
|
|
||||||
function distanceSquared(a: WallPlanPoint, b: WallPlanPoint): number {
|
function distanceSquared(a: WallPlanPoint, b: WallPlanPoint): number {
|
||||||
const dx = a[0] - b[0]
|
const dx = a[0] - b[0]
|
||||||
const dz = a[1] - b[1]
|
const dz = a[1] - b[1]
|
||||||
return dx * dx + dz * dz
|
return dx * dx + dz * dz
|
||||||
}
|
}
|
||||||
|
|
||||||
function snapScalarToGrid(value: number, step = WALL_GRID_STEP): number {
|
function snapScalarToGrid(value: number, step = WALL_GRID_STEP): number {
|
||||||
return Math.round(value / step) * step
|
return Math.round(value / step) * step
|
||||||
}
|
}
|
||||||
|
|
||||||
export function snapPointToGrid(point: WallPlanPoint, step = WALL_GRID_STEP): WallPlanPoint {
|
export function snapPointToGrid(point: WallPlanPoint, step = WALL_GRID_STEP): WallPlanPoint {
|
||||||
return [snapScalarToGrid(point[0], step), snapScalarToGrid(point[1], step)]
|
return [snapScalarToGrid(point[0], step), snapScalarToGrid(point[1], step)]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function snapPointTo45Degrees(start: WallPlanPoint, cursor: WallPlanPoint): WallPlanPoint {
|
export function snapPointTo45Degrees(start: WallPlanPoint, cursor: WallPlanPoint): WallPlanPoint {
|
||||||
const dx = cursor[0] - start[0]
|
const dx = cursor[0] - start[0]
|
||||||
const dz = cursor[1] - start[1]
|
const dz = cursor[1] - start[1]
|
||||||
const angle = Math.atan2(dz, dx)
|
const angle = Math.atan2(dz, dx)
|
||||||
const snappedAngle = Math.round(angle / (Math.PI / 4)) * (Math.PI / 4)
|
const snappedAngle = Math.round(angle / (Math.PI / 4)) * (Math.PI / 4)
|
||||||
const distance = Math.sqrt(dx * dx + dz * dz)
|
const distance = Math.sqrt(dx * dx + dz * dz)
|
||||||
|
|
||||||
return snapPointToGrid([
|
return snapPointToGrid([
|
||||||
start[0] + Math.cos(snappedAngle) * distance,
|
start[0] + Math.cos(snappedAngle) * distance,
|
||||||
start[1] + Math.sin(snappedAngle) * distance,
|
start[1] + Math.sin(snappedAngle) * distance,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
function projectPointOntoWall(point: WallPlanPoint, wall: WallNode): WallPlanPoint | null {
|
function projectPointOntoWall(point: WallPlanPoint, wall: WallNode): WallPlanPoint | null {
|
||||||
const [x1, z1] = wall.start
|
const [x1, z1] = wall.start
|
||||||
const [x2, z2] = wall.end
|
const [x2, z2] = wall.end
|
||||||
@@ -44,15 +36,12 @@ function projectPointOntoWall(point: WallPlanPoint, wall: WallNode): WallPlanPoi
|
|||||||
if (lengthSquared < 1e-9) {
|
if (lengthSquared < 1e-9) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const t = ((point[0] - x1) * dx + (point[1] - z1) * dz) / lengthSquared
|
const t = ((point[0] - x1) * dx + (point[1] - z1) * dz) / lengthSquared
|
||||||
if (t <= 0 || t >= 1) {
|
if (t <= 0 || t >= 1) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return [x1 + dx * t, z1 + dz * t]
|
return [x1 + dx * t, z1 + dz * t]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findWallSnapTarget(
|
export function findWallSnapTarget(
|
||||||
point: WallPlanPoint,
|
point: WallPlanPoint,
|
||||||
walls: WallNode[],
|
walls: WallNode[],
|
||||||
@@ -62,12 +51,10 @@ export function findWallSnapTarget(
|
|||||||
const radiusSquared = (options?.radius ?? WALL_JOIN_SNAP_RADIUS) ** 2
|
const radiusSquared = (options?.radius ?? WALL_JOIN_SNAP_RADIUS) ** 2
|
||||||
let bestTarget: WallPlanPoint | null = null
|
let bestTarget: WallPlanPoint | null = null
|
||||||
let bestDistanceSquared = Number.POSITIVE_INFINITY
|
let bestDistanceSquared = Number.POSITIVE_INFINITY
|
||||||
|
|
||||||
for (const wall of walls) {
|
for (const wall of walls) {
|
||||||
if (ignoreWallIds.has(wall.id)) {
|
if (ignoreWallIds.has(wall.id)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const candidates: Array<WallPlanPoint | null> = [
|
const candidates: Array<WallPlanPoint | null> = [
|
||||||
wall.start,
|
wall.start,
|
||||||
wall.end,
|
wall.end,
|
||||||
@@ -77,7 +64,6 @@ export function findWallSnapTarget(
|
|||||||
if (!candidate) {
|
if (!candidate) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const candidateDistanceSquared = distanceSquared(point, candidate)
|
const candidateDistanceSquared = distanceSquared(point, candidate)
|
||||||
if (
|
if (
|
||||||
candidateDistanceSquared > radiusSquared ||
|
candidateDistanceSquared > radiusSquared ||
|
||||||
@@ -85,15 +71,12 @@ export function findWallSnapTarget(
|
|||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
bestTarget = candidate
|
bestTarget = candidate
|
||||||
bestDistanceSquared = candidateDistanceSquared
|
bestDistanceSquared = candidateDistanceSquared
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bestTarget
|
return bestTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
export function snapWallDraftPoint(args: {
|
export function snapWallDraftPoint(args: {
|
||||||
point: WallPlanPoint
|
point: WallPlanPoint
|
||||||
walls: WallNode[]
|
walls: WallNode[]
|
||||||
@@ -103,38 +86,31 @@ export function snapWallDraftPoint(args: {
|
|||||||
}): WallPlanPoint {
|
}): WallPlanPoint {
|
||||||
const { point, walls, start, angleSnap = false, ignoreWallIds } = args
|
const { point, walls, start, angleSnap = false, ignoreWallIds } = args
|
||||||
const basePoint = start && angleSnap ? snapPointTo45Degrees(start, point) : snapPointToGrid(point)
|
const basePoint = start && angleSnap ? snapPointTo45Degrees(start, point) : snapPointToGrid(point)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
findWallSnapTarget(basePoint, walls, {
|
findWallSnapTarget(basePoint, walls, {
|
||||||
ignoreWallIds,
|
ignoreWallIds,
|
||||||
}) ?? basePoint
|
}) ?? basePoint
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isWallLongEnough(start: WallPlanPoint, end: WallPlanPoint): boolean {
|
export function isWallLongEnough(start: WallPlanPoint, end: WallPlanPoint): boolean {
|
||||||
return distanceSquared(start, end) >= WALL_MIN_LENGTH * WALL_MIN_LENGTH
|
return distanceSquared(start, end) >= WALL_MIN_LENGTH * WALL_MIN_LENGTH
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createWallOnCurrentLevel(
|
export function createWallOnCurrentLevel(
|
||||||
start: WallPlanPoint,
|
start: WallPlanPoint,
|
||||||
end: WallPlanPoint,
|
end: WallPlanPoint,
|
||||||
): WallNode | null {
|
): WallNode | null {
|
||||||
const currentLevelId = useViewer.getState().selection.levelId
|
const currentLevelId = useViewer.getState().selection.levelId
|
||||||
const { createNode, nodes } = useScene.getState()
|
const { createNode, nodes } = useScene.getState()
|
||||||
|
|
||||||
if (!(currentLevelId && isWallLongEnough(start, end))) {
|
if (!(currentLevelId && isWallLongEnough(start, end))) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const wallCount = Object.values(nodes).filter((node) => node.type === 'wall').length
|
const wallCount = Object.values(nodes).filter((node) => node.type === 'wall').length
|
||||||
const wall = WallSchema.parse({
|
const wall = WallSchema.parse({
|
||||||
name: `Wall ${wallCount + 1}`,
|
name: `Wall ${wallCount + 1}`,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
createNode(wall, currentLevelId)
|
createNode(wall, currentLevelId)
|
||||||
sfxEmitter.emit('sfx:structure-build')
|
sfxEmitter.emit('sfx:structure-build')
|
||||||
|
|
||||||
return wall
|
return wall
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { DoubleSide, type Group, type Mesh, Shape, ShapeGeometry, Vector3 } from
|
|||||||
import { EDITOR_LAYER } from '../../../lib/constants'
|
import { EDITOR_LAYER } from '../../../lib/constants'
|
||||||
import { sfxEmitter } from '../../../lib/sfx-bus'
|
import { sfxEmitter } from '../../../lib/sfx-bus'
|
||||||
import { CursorSphere } from '../shared/cursor-sphere'
|
import { CursorSphere } from '../shared/cursor-sphere'
|
||||||
import { createWallOnCurrentLevel, snapWallDraftPoint, type WallPlanPoint } from './wall-drafting'
|
import { createWallOnCurrentLevel, snapWallDraftPoint, WALL_MIN_LENGTH, type WallPlanPoint } from './wall-drafting'
|
||||||
|
|
||||||
const WALL_HEIGHT = 2.5
|
const WALL_HEIGHT = 2.5
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ const updateWallPreview = (mesh: Mesh, start: Vector3, end: Vector3) => {
|
|||||||
const direction = new Vector3(end.x - start.x, 0, end.z - start.z)
|
const direction = new Vector3(end.x - start.x, 0, end.z - start.z)
|
||||||
const length = direction.length()
|
const length = direction.length()
|
||||||
|
|
||||||
if (length < 0.01) {
|
if (length < WALL_MIN_LENGTH) {
|
||||||
mesh.visible = false
|
mesh.visible = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -142,7 +142,7 @@ export const WallTool: React.FC = () => {
|
|||||||
endingPoint.current.set(snappedEnd[0], event.position[1], snappedEnd[1])
|
endingPoint.current.set(snappedEnd[0], event.position[1], snappedEnd[1])
|
||||||
const dx = endingPoint.current.x - startingPoint.current.x
|
const dx = endingPoint.current.x - startingPoint.current.x
|
||||||
const dz = endingPoint.current.z - startingPoint.current.z
|
const dz = endingPoint.current.z - startingPoint.current.z
|
||||||
if (dx * dx + dz * dz < 0.01 * 0.01) return
|
if (dx * dx + dz * dz < WALL_MIN_LENGTH * WALL_MIN_LENGTH) return
|
||||||
createWallOnCurrentLevel(
|
createWallOnCurrentLevel(
|
||||||
[startingPoint.current.x, startingPoint.current.z],
|
[startingPoint.current.x, startingPoint.current.z],
|
||||||
[endingPoint.current.x, endingPoint.current.z],
|
[endingPoint.current.x, endingPoint.current.z],
|
||||||
|
|||||||
Reference in New Issue
Block a user