From 5ca9310d3e1948745fe1fae6e64e2cd13034a2e3 Mon Sep 17 00:00:00 2001 From: austin Date: Sun, 18 May 2025 11:24:27 -0400 Subject: [PATCH] furniture slot solver --- docs/src/progress/renderertodo.md | 6 + .../client/block/solver/FurnitureSolver.java | 222 +++++++++++++++++- .../client/block/solver/RoomSolver.java | 22 +- .../client/interact/select/AreaSelection.java | 30 +++ .../data/block/fab/FurnitureSlotMetadata.java | 32 +++ .../data/block/fab/RoomMetadata.java | 13 +- .../pipelines/debug/DebugContentPipeline.java | 5 + 7 files changed, 316 insertions(+), 14 deletions(-) create mode 100644 src/main/java/electrosphere/data/block/fab/FurnitureSlotMetadata.java diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 64bf6b44..f14ef7b5 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1854,6 +1854,12 @@ Structure metadata organization Break out room solver Solve for entrypoints to room Render entrypoints to current structure data rooms +Solve for entrypoints into room +Filter room solver to only include rooms that are enter-able + +(05/18/2025) +Solve for furniture placement inside rectangular rooms +Visualize furniture placement slots diff --git a/src/main/java/electrosphere/client/block/solver/FurnitureSolver.java b/src/main/java/electrosphere/client/block/solver/FurnitureSolver.java index 2ccda3ed..ec5162d8 100644 --- a/src/main/java/electrosphere/client/block/solver/FurnitureSolver.java +++ b/src/main/java/electrosphere/client/block/solver/FurnitureSolver.java @@ -1,17 +1,237 @@ package electrosphere.client.block.solver; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +import org.joml.Sphered; +import org.joml.Vector3d; + +import electrosphere.client.block.BlockChunkData; +import electrosphere.client.interact.select.AreaSelection; +import electrosphere.client.interact.select.AreaSelection.AreaSelectionType; +import electrosphere.data.block.fab.FurnitureSlotMetadata; import electrosphere.data.block.fab.RoomMetadata; /** * Solves for placement of furniture */ public class FurnitureSolver { + + /** + * Size of a furniture slot in blocks + */ + public static final int FURNITURE_SIZE_DIM = 4; + + /** + * Types of layouts for furniture + */ + public static enum LayoutType { + /** + * Place furniture along the walls, skipping where there are entrances to the room + */ + WALL_ALIGNED, + /** + * Place furniture in rows organized through the room, like a warehouse + */ + WAREHOUSE, + } /** * Solves for furniture placement spots for the given room */ - public static void solveFurnitureSpots(RoomMetadata room){ + public static List solveFurnitureSpots(RoomMetadata room, LayoutType layout){ + if(layout == LayoutType.WAREHOUSE){ + throw new Error("Unsupported layout type " + layout); + } + List rVal = null; + + switch(layout){ + case WALL_ALIGNED: { + rVal = FurnitureSolver.solveWallAlignedLayout(room); + } break; + default: { + throw new Error("Unsupported layout type! " + layout); + } + } + + return rVal; + } + + /** + * Solves a wall-aligned furniture layout + * @param room The room + * @return The furniture slots + */ + public static List solveWallAlignedLayout(RoomMetadata room){ + if(room.getArea().getType() != AreaSelectionType.RECTANGULAR){ + throw new Error("Unsupported room area type! " + room.getArea().getType()); + } + List rVal = new LinkedList(); + + int floorDimX = (int)((room.getArea().getRectEnd().x - room.getArea().getRectStart().x) * BlockChunkData.BLOCKS_PER_UNIT_DISTANCE); + int floorDimZ = (int)((room.getArea().getRectEnd().z - room.getArea().getRectStart().z) * BlockChunkData.BLOCKS_PER_UNIT_DISTANCE); + Vector3d roomStart = room.getArea().getRectStart(); + + //map the entrances to a list of spheres to intersection check against + List entranceColliders = room.getEntryPoints().stream().map((Vector3d entryPoint) -> { + return new Sphered(entryPoint, RoomSolver.DOORWAY_MIN_WIDTH / 2); + }).collect(Collectors.toList()); + + //scan negative z dir + for(int x = 0; x < floorDimX - FURNITURE_SIZE_DIM; x++){ + AreaSelection selection = AreaSelection.createRect( + new Vector3d(roomStart).add( + x * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + 0 * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + 0 * BlockChunkData.BLOCK_SIZE_MULTIPLIER + ), + new Vector3d(roomStart).add( + (x + FurnitureSolver.FURNITURE_SIZE_DIM) * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + FurnitureSolver.FURNITURE_SIZE_DIM * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + FurnitureSolver.FURNITURE_SIZE_DIM * BlockChunkData.BLOCK_SIZE_MULTIPLIER + ) + ); + + //verify another furniture slot doesn't already contain this one + boolean contained = false; + for(FurnitureSlotMetadata furnitureSlot : rVal){ + if(furnitureSlot.getArea().intersects(selection)){ + contained = true; + break; + } + } + + //verify isn't blocking an entryway + for(Sphered entryCollider : entranceColliders){ + if(selection.intersects(entryCollider)){ + contained = true; + break; + } + } + + if(!contained){ + FurnitureSlotMetadata newSlot = new FurnitureSlotMetadata(selection); + rVal.add(newSlot); + } + } + + //scan positive z dir + for(int x = 0; x < floorDimX - FURNITURE_SIZE_DIM; x++){ + AreaSelection selection = AreaSelection.createRect( + new Vector3d(roomStart).add( + x * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + 0 * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + (floorDimZ - FurnitureSolver.FURNITURE_SIZE_DIM) * BlockChunkData.BLOCK_SIZE_MULTIPLIER + ), + new Vector3d(roomStart).add( + (x + FurnitureSolver.FURNITURE_SIZE_DIM) * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + FurnitureSolver.FURNITURE_SIZE_DIM * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + floorDimZ * BlockChunkData.BLOCK_SIZE_MULTIPLIER + ) + ); + + //verify another furniture slot doesn't already contain this one + boolean contained = false; + for(FurnitureSlotMetadata furnitureSlot : rVal){ + if(furnitureSlot.getArea().intersects(selection)){ + contained = true; + break; + } + } + + //verify isn't blocking an entryway + for(Sphered entryCollider : entranceColliders){ + if(selection.intersects(entryCollider)){ + contained = true; + break; + } + } + + if(!contained){ + FurnitureSlotMetadata newSlot = new FurnitureSlotMetadata(selection); + rVal.add(newSlot); + } + } + + //scan negative x dir + for(int z = 0; z < floorDimZ - FURNITURE_SIZE_DIM; z++){ + AreaSelection selection = AreaSelection.createRect( + new Vector3d(roomStart).add( + 0 * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + 0 * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + z * BlockChunkData.BLOCK_SIZE_MULTIPLIER + ), + new Vector3d(roomStart).add( + FurnitureSolver.FURNITURE_SIZE_DIM * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + FurnitureSolver.FURNITURE_SIZE_DIM * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + (z + FurnitureSolver.FURNITURE_SIZE_DIM) * BlockChunkData.BLOCK_SIZE_MULTIPLIER + ) + ); + + //verify another furniture slot doesn't already contain this one + boolean contained = false; + for(FurnitureSlotMetadata furnitureSlot : rVal){ + if(furnitureSlot.getArea().intersects(selection)){ + contained = true; + break; + } + } + + //verify isn't blocking an entryway + for(Sphered entryCollider : entranceColliders){ + if(selection.intersects(entryCollider)){ + contained = true; + break; + } + } + + if(!contained){ + FurnitureSlotMetadata newSlot = new FurnitureSlotMetadata(selection); + rVal.add(newSlot); + } + } + + //scan positive x dir + for(int z = 0; z < floorDimZ - FURNITURE_SIZE_DIM; z++){ + AreaSelection selection = AreaSelection.createRect( + new Vector3d(roomStart).add( + (floorDimX - FurnitureSolver.FURNITURE_SIZE_DIM) * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + 0 * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + z * BlockChunkData.BLOCK_SIZE_MULTIPLIER + ), + new Vector3d(roomStart).add( + floorDimX * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + FurnitureSolver.FURNITURE_SIZE_DIM * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + (z + FurnitureSolver.FURNITURE_SIZE_DIM) * BlockChunkData.BLOCK_SIZE_MULTIPLIER + ) + ); + + //verify another furniture slot doesn't already contain this one + boolean contained = false; + for(FurnitureSlotMetadata furnitureSlot : rVal){ + if(furnitureSlot.getArea().intersects(selection)){ + contained = true; + break; + } + } + + //verify isn't blocking an entryway + for(Sphered entryCollider : entranceColliders){ + if(selection.intersects(entryCollider)){ + contained = true; + break; + } + } + + if(!contained){ + FurnitureSlotMetadata newSlot = new FurnitureSlotMetadata(selection); + rVal.add(newSlot); + } + } + + return rVal; } } diff --git a/src/main/java/electrosphere/client/block/solver/RoomSolver.java b/src/main/java/electrosphere/client/block/solver/RoomSolver.java index 5b18ca39..d3be0f3b 100644 --- a/src/main/java/electrosphere/client/block/solver/RoomSolver.java +++ b/src/main/java/electrosphere/client/block/solver/RoomSolver.java @@ -1,15 +1,18 @@ package electrosphere.client.block.solver; import java.util.LinkedList; +import java.util.List; import org.joml.Vector3d; import org.joml.Vector3i; import electrosphere.client.block.BlockChunkData; import electrosphere.client.block.ClientBlockSelection; +import electrosphere.client.block.solver.FurnitureSolver.LayoutType; import electrosphere.client.interact.select.AreaSelection; import electrosphere.client.interact.select.AreaSelection.AreaSelectionType; import electrosphere.client.scene.ClientWorldData; +import electrosphere.data.block.fab.FurnitureSlotMetadata; import electrosphere.data.block.fab.RoomMetadata; import electrosphere.data.block.fab.StructureMetadata; import electrosphere.engine.Globals; @@ -149,7 +152,7 @@ public class RoomSolver { } //scan for entrances to the room - LinkedList entryPoints = RoomSolver.scanForEntrances(roomArea.getRectStart(), floorDimX, floorDimZ); + List entryPoints = RoomSolver.scanForEntrances(roomArea.getRectStart(), floorDimX, floorDimZ); if(entryPoints.size() < 1){ continue; } @@ -157,6 +160,10 @@ public class RoomSolver { RoomMetadata data = new RoomMetadata(roomArea); data.setEntryPoints(entryPoints); structureMetadata.getRooms().add(data); + + //scan for furniture slots + List furnitureSlots = FurnitureSolver.solveFurnitureSpots(data, LayoutType.WALL_ALIGNED); + data.setFurnitureSlots(furnitureSlots); } } @@ -167,8 +174,8 @@ public class RoomSolver { * @param floorDimX The x dimension of the floor * @param floorDimZ The z dimension of the floor */ - private static LinkedList scanForEntrances(Vector3d roomStart, int floorDimX, int floorDimZ){ - LinkedList rVal = new LinkedList(); + private static List scanForEntrances(Vector3d roomStart, int floorDimX, int floorDimZ){ + List rVal = new LinkedList(); Vector3d tempVec = new Vector3d(); //scan for entrances to the room @@ -223,6 +230,7 @@ public class RoomSolver { } //scan positive z dir + lastSolidBlock = 0; for(int x = 0; x < floorDimX; x++){ tempVec.set(roomStart).add( x * BlockChunkData.BLOCK_SIZE_MULTIPLIER, @@ -319,7 +327,7 @@ public class RoomSolver { } } - //scan negative x dir + //scan positive x dir lastSolidBlock = 0; for(int x = 0; x < floorDimZ; x++){ tempVec.set(roomStart).add( @@ -376,7 +384,7 @@ public class RoomSolver { * @param type The type of block * @return true if it is a valid floor block, false otherwise */ - private static boolean isFloor(short type){ + public static boolean isFloor(short type){ return type != BlockChunkData.BLOCK_TYPE_EMPTY; } @@ -385,7 +393,7 @@ public class RoomSolver { * @param type The type of block * @return true if it is a valid wall block, false otherwise */ - private static boolean isWall(short type){ + public static boolean isWall(short type){ return type != BlockChunkData.BLOCK_TYPE_EMPTY; } @@ -394,7 +402,7 @@ public class RoomSolver { * @param type The type of block * @return true if is a valid doorway block, false otherwise */ - private static boolean isDoorway(short type){ + public static boolean isDoorway(short type){ return type == BlockChunkData.BLOCK_TYPE_EMPTY; } diff --git a/src/main/java/electrosphere/client/interact/select/AreaSelection.java b/src/main/java/electrosphere/client/interact/select/AreaSelection.java index f22169d0..19d297ba 100644 --- a/src/main/java/electrosphere/client/interact/select/AreaSelection.java +++ b/src/main/java/electrosphere/client/interact/select/AreaSelection.java @@ -2,6 +2,7 @@ package electrosphere.client.interact.select; import org.graalvm.polyglot.HostAccess.Export; import org.joml.AABBd; +import org.joml.Sphered; import org.joml.Vector3d; import org.joml.Vector3i; @@ -48,6 +49,11 @@ public class AreaSelection { */ private AABBd aabb; + /** + * Private constructor + */ + private AreaSelection(){ } + /** * Creates a rectangular selection * @param start The start point @@ -385,4 +391,28 @@ public class AreaSelection { return aabb.testPoint(point); } + /** + * Checks if this area intersects another area + * @param other The other area + * @return true if one intersects another, false otherwise + */ + public boolean intersects(AreaSelection other){ + if(this.type != AreaSelectionType.RECTANGULAR || other.type != AreaSelectionType.RECTANGULAR){ + throw new Error("One of the areas to test is not rectangular! " + this.type + " " + other.type); + } + return aabb.testAABB(other.aabb); + } + + /** + * Checks if this area intersects a sphere + * @param sphere The sphere to check + * @return true if intersects the sphere, false otherwise + */ + public boolean intersects(Sphered sphere){ + if(this.type != AreaSelectionType.RECTANGULAR){ + throw new Error("One of the areas to test is not rectangular! " + this.type); + } + return aabb.testSphere(sphere.x, sphere.y, sphere.z, sphere.r); + } + } diff --git a/src/main/java/electrosphere/data/block/fab/FurnitureSlotMetadata.java b/src/main/java/electrosphere/data/block/fab/FurnitureSlotMetadata.java new file mode 100644 index 00000000..8e5b4c05 --- /dev/null +++ b/src/main/java/electrosphere/data/block/fab/FurnitureSlotMetadata.java @@ -0,0 +1,32 @@ +package electrosphere.data.block.fab; + +import electrosphere.client.interact.select.AreaSelection; + +/** + * Metadata for a slot that furniture can be placed into + */ +public class FurnitureSlotMetadata { + + /** + * The area encompasing the slot + */ + AreaSelection area; + + + /** + * Constructor + * @param area The area that furniture can be placed into + */ + public FurnitureSlotMetadata(AreaSelection area){ + this.area = area; + } + + /** + * Gets the area that encompasses the furniture slot + * @return The area + */ + public AreaSelection getArea(){ + return area; + } + +} diff --git a/src/main/java/electrosphere/data/block/fab/RoomMetadata.java b/src/main/java/electrosphere/data/block/fab/RoomMetadata.java index faa35d0b..b1ab6c34 100644 --- a/src/main/java/electrosphere/data/block/fab/RoomMetadata.java +++ b/src/main/java/electrosphere/data/block/fab/RoomMetadata.java @@ -1,6 +1,7 @@ package electrosphere.data.block.fab; import java.util.LinkedList; +import java.util.List; import org.joml.Vector3d; @@ -19,12 +20,12 @@ public class RoomMetadata { /** * The list of slots that can have furniture placed on them */ - LinkedList furnitureSlots = new LinkedList(); + List furnitureSlots = new LinkedList(); /** * The list of entrypoints to the room */ - LinkedList entryPoints = new LinkedList(); + List entryPoints = new LinkedList(); /** * Constructor @@ -54,7 +55,7 @@ public class RoomMetadata { * Gets the furniture slots of the room * @return The furniture slots of the room */ - public LinkedList getFurnitureSlots() { + public List getFurnitureSlots() { return furnitureSlots; } @@ -62,7 +63,7 @@ public class RoomMetadata { * Sets the furniture slots of the room * @param furnitureSlots The furniture slots */ - public void setFurnitureSlots(LinkedList furnitureSlots) { + public void setFurnitureSlots(List furnitureSlots) { this.furnitureSlots = furnitureSlots; } @@ -70,7 +71,7 @@ public class RoomMetadata { * Gets the entry points of the room * @return The entry points of the room */ - public LinkedList getEntryPoints() { + public List getEntryPoints() { return entryPoints; } @@ -78,7 +79,7 @@ public class RoomMetadata { * Sets the entry points of the room * @param entryPoints The entry points */ - public void setEntryPoints(LinkedList entryPoints) { + public void setEntryPoints(List entryPoints) { this.entryPoints = entryPoints; } diff --git a/src/main/java/electrosphere/renderer/pipelines/debug/DebugContentPipeline.java b/src/main/java/electrosphere/renderer/pipelines/debug/DebugContentPipeline.java index 5166ee7f..9f5cebba 100644 --- a/src/main/java/electrosphere/renderer/pipelines/debug/DebugContentPipeline.java +++ b/src/main/java/electrosphere/renderer/pipelines/debug/DebugContentPipeline.java @@ -17,6 +17,7 @@ import electrosphere.client.interact.select.AreaSelection; import electrosphere.collision.CollisionEngine; import electrosphere.collision.PhysicsUtils; import electrosphere.collision.collidable.Collidable; +import electrosphere.data.block.fab.FurnitureSlotMetadata; import electrosphere.data.block.fab.RoomMetadata; import electrosphere.data.collidable.CollidableTemplate; import electrosphere.data.collidable.HitboxData; @@ -120,6 +121,10 @@ public class DebugContentPipeline implements RenderPipeline { for(Vector3d entrypoint : roomArea.getEntryPoints()){ DebugContentPipeline.renderPoint(openGLState, renderPipelineState, modelTransformMatrix, entrypoint, 0.5f, AssetDataStrings.TEXTURE_RED_TRANSPARENT); } + //render furniture slots + for(FurnitureSlotMetadata furnitureSlot : roomArea.getFurnitureSlots()){ + DebugContentPipeline.renderAreaSelection(openGLState, renderPipelineState, modelTransformMatrix, furnitureSlot.getArea(), AssetDataStrings.TEXTURE_RED_TRANSPARENT); + } } } }