-
Notifications
You must be signed in to change notification settings - Fork 2.7k
feat(viewer): per-level base elevation parameter #312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,7 +24,7 @@ export function getStoredLevelHeight(level: Pick<LevelNode, 'height'>): number { | |
| } | ||
|
|
||
| export type LevelElevation = { | ||
| /** World Y of the level's floor: prefix sum of the storey heights below it. */ | ||
| /** World Y of the level's floor: cumulative heights and level offsets through this level. */ | ||
| baseY: number | ||
| /** Stored storey height of this level (fallback applied). */ | ||
| height: number | ||
|
|
@@ -49,10 +49,10 @@ function resolveLevelBuildingId( | |
| } | ||
|
|
||
| /** | ||
| * Per-building stacked elevations from stored storey heights: levels are | ||
| * sorted by ordinal ascending within each building, the lowest level's floor | ||
| * sits at 0, and each next floor sits on top of the previous storey height. | ||
| * Levels with no resolvable building share one legacy stack from 0. | ||
| * Per-building stacked elevations from stored storey heights and additive | ||
| * base-elevation offsets: levels are sorted by ordinal ascending within each | ||
| * building, and each offset shifts its level plus every higher level in the | ||
| * same stack. Levels with no resolvable building share one legacy stack. | ||
| * | ||
| * Pure — operates on the serialized nodes record only. | ||
| */ | ||
|
|
@@ -61,12 +61,13 @@ export function getLevelElevations(nodes: Record<AnyNodeId, AnyNode>): Map<strin | |
| (node): node is BuildingNode => node?.type === 'building', | ||
| ) | ||
|
|
||
| const entries: Array<{ levelId: string } & LevelElevation> = [] | ||
| const entries: Array<{ baseElevation: number; levelId: string } & LevelElevation> = [] | ||
| for (const node of Object.values(nodes)) { | ||
| if (node?.type !== 'level') continue | ||
| const level = node as LevelNode | ||
| entries.push({ | ||
| levelId: level.id, | ||
| baseElevation: level.baseElevation ?? 0, | ||
| baseY: 0, | ||
| height: getStoredLevelHeight(level), | ||
| buildingId: resolveLevelBuildingId(level.id, level.parentId, buildings), | ||
|
|
@@ -77,7 +78,7 @@ export function getLevelElevations(nodes: Record<AnyNodeId, AnyNode>): Map<strin | |
| const elevations = new Map<string, LevelElevation>() | ||
| const cumulativeYByBuilding = new Map<string | null, number>() | ||
| for (const entry of entries.sort((a, b) => a.ordinal - b.ordinal)) { | ||
| const baseY = cumulativeYByBuilding.get(entry.buildingId) ?? 0 | ||
| const baseY = (cumulativeYByBuilding.get(entry.buildingId) ?? 0) + entry.baseElevation | ||
| elevations.set(entry.levelId, { | ||
| baseY, | ||
| height: entry.height, | ||
|
|
@@ -90,6 +91,26 @@ export function getLevelElevations(nodes: Record<AnyNodeId, AnyNode>): Map<strin | |
| return elevations | ||
| } | ||
|
|
||
| function resolveLevelFloorToFloorHeight( | ||
| levelId: string, | ||
| elevations: Map<string, LevelElevation>, | ||
| ): number | null { | ||
| const current = elevations.get(levelId) | ||
| if (!current) return null | ||
|
|
||
| const aboveId = findLevelAboveId(levelId, elevations) | ||
| if (!aboveId) return current.height | ||
| const above = elevations.get(aboveId) | ||
| return above ? above.baseY - current.baseY : current.height | ||
| } | ||
|
|
||
| export function getLevelFloorToFloorHeight( | ||
| levelId: string, | ||
| nodes: Record<AnyNodeId, AnyNode>, | ||
| ): number { | ||
| return resolveLevelFloorToFloorHeight(levelId, getLevelElevations(nodes)) ?? DEFAULT_LEVEL_HEIGHT | ||
| } | ||
|
|
||
| /** | ||
| * The id of the level directly above `levelId` in its own stack (same | ||
| * resolved building, or the shared legacy stack for building-less levels): | ||
|
|
@@ -170,8 +191,8 @@ export function getLevelBelow( | |
| } | ||
|
|
||
| type CoveringSlabContext = { | ||
| /** Stored storey height of the QUERIED level. */ | ||
| storeyHeight: number | ||
| /** Offset-aware distance from the queried floor to the floor above. */ | ||
| floorToFloorHeight: number | ||
| /** Non-recessed slab children of the level above. */ | ||
| slabs: SlabNode[] | ||
| } | ||
|
|
@@ -189,7 +210,10 @@ function resolveCoveringSlabContext( | |
| const level = nodes[levelId as LevelNode['id']] | ||
| if (level?.type !== 'level') return null | ||
|
|
||
| const above = getLevelAbove(levelId, nodes) | ||
| const elevations = getLevelElevations(nodes) | ||
| const aboveId = findLevelAboveId(levelId, elevations) | ||
| const aboveNode = aboveId ? nodes[aboveId as LevelNode['id']] : null | ||
| const above = aboveNode?.type === 'level' ? (aboveNode as LevelNode) : null | ||
| const slabs: SlabNode[] = [] | ||
| for (const childId of above?.children ?? []) { | ||
| const child = nodes[childId as keyof typeof nodes] | ||
|
|
@@ -201,16 +225,21 @@ function resolveCoveringSlabContext( | |
| slabs.push(slab) | ||
| } | ||
|
|
||
| return { storeyHeight: getStoredLevelHeight(level as LevelNode), slabs } | ||
| return { | ||
| floorToFloorHeight: | ||
| resolveLevelFloorToFloorHeight(levelId, elevations) ?? | ||
| getStoredLevelHeight(level as LevelNode), | ||
| slabs, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Base elevation skips geometry rebuildHigh Severity Walls and ceilings now derive their plane from offset-aware floor-to-floor spacing via Additional Locations (1)Reviewed by Cursor Bugbot for commit ac82cf8. Configure here. |
||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Underside of `slab`'s solid in the QUERIED level's local Y. The solid | ||
| * occupies `[elevation - thickness, elevation]` in ITS level's local Y, | ||
| * which sits `storeyHeight` above the queried level's floor. | ||
| * which sits `floorToFloorHeight` above the queried level's floor. | ||
| */ | ||
| function coveringUndersideY(storeyHeight: number, slab: SlabNode): number { | ||
| return storeyHeight + ((slab.elevation ?? 0.05) - (slab.thickness ?? 0.05)) | ||
| function coveringUndersideY(floorToFloorHeight: number, slab: SlabNode): number { | ||
| return floorToFloorHeight + ((slab.elevation ?? 0.05) - (slab.thickness ?? 0.05)) | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -248,7 +277,7 @@ function lowestCoveringUndersideAt( | |
| let lowest: number | null = null | ||
| for (const slab of context.slabs) { | ||
| if (!slabCoversPoint(slab, x, z)) continue | ||
| const underside = coveringUndersideY(context.storeyHeight, slab) | ||
| const underside = coveringUndersideY(context.floorToFloorHeight, slab) | ||
| if (lowest === null || underside < lowest) lowest = underside | ||
| } | ||
| return lowest | ||
|
|
@@ -257,7 +286,7 @@ function lowestCoveringUndersideAt( | |
| /** | ||
| * Underside of the LOWEST slab from the level above that covers | ||
| * level-local point `[x, z]`, expressed in the queried level's local Y: | ||
| * `storeyHeight + (slab.elevation - slab.thickness)`. `recessed` slabs | ||
| * `floorToFloorHeight + (slab.elevation - slab.thickness)`. `recessed` slabs | ||
| * (pools) never cover. `null` when no covering slab (or no level above). | ||
| * | ||
| * Coordinate spaces: levels stack in Y only (`LevelNode` carries no XZ | ||
|
|
@@ -304,9 +333,9 @@ export function getWallPlaneTop( | |
| const context = resolveCoveringSlabContext(levelId, nodes) | ||
| if (!context) return DEFAULT_LEVEL_HEIGHT | ||
|
|
||
| let plane = context.storeyHeight | ||
| let plane = context.floorToFloorHeight | ||
| for (const slab of context.slabs) { | ||
| const underside = coveringUndersideY(context.storeyHeight, slab) | ||
| const underside = coveringUndersideY(context.floorToFloorHeight, slab) | ||
| if (underside >= plane) continue | ||
| if (!wallOverlapsSlabFootprint(wall, slab.polygon, slab.holes)) continue | ||
| plane = underside | ||
|
|
@@ -336,7 +365,7 @@ export function getCeilingClampBound( | |
| const context = resolveCoveringSlabContext(levelId, nodes) | ||
| if (!context) return Number.POSITIVE_INFINITY | ||
|
|
||
| let bound = context.storeyHeight | ||
| let bound = context.floorToFloorHeight | ||
| if (polygon.length > 0) { | ||
| let cx = 0 | ||
| let cz = 0 | ||
|
|
||


Uh oh!
There was an error while loading. Please reload this page.