furniture slot solver
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2025-05-18 11:24:27 -04:00
parent 6d420a3b88
commit 5ca9310d3e
7 changed files with 316 additions and 14 deletions

View File

@ -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

View File

@ -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<FurnitureSlotMetadata> solveFurnitureSpots(RoomMetadata room, LayoutType layout){
if(layout == LayoutType.WAREHOUSE){
throw new Error("Unsupported layout type " + layout);
}
List<FurnitureSlotMetadata> 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<FurnitureSlotMetadata> solveWallAlignedLayout(RoomMetadata room){
if(room.getArea().getType() != AreaSelectionType.RECTANGULAR){
throw new Error("Unsupported room area type! " + room.getArea().getType());
}
List<FurnitureSlotMetadata> rVal = new LinkedList<FurnitureSlotMetadata>();
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<Sphered> 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;
}
}

View File

@ -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<Vector3d> entryPoints = RoomSolver.scanForEntrances(roomArea.getRectStart(), floorDimX, floorDimZ);
List<Vector3d> 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<FurnitureSlotMetadata> 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<Vector3d> scanForEntrances(Vector3d roomStart, int floorDimX, int floorDimZ){
LinkedList<Vector3d> rVal = new LinkedList<Vector3d>();
private static List<Vector3d> scanForEntrances(Vector3d roomStart, int floorDimX, int floorDimZ){
List<Vector3d> rVal = new LinkedList<Vector3d>();
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;
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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<AreaSelection> furnitureSlots = new LinkedList<AreaSelection>();
List<FurnitureSlotMetadata> furnitureSlots = new LinkedList<FurnitureSlotMetadata>();
/**
* The list of entrypoints to the room
*/
LinkedList<Vector3d> entryPoints = new LinkedList<Vector3d>();
List<Vector3d> entryPoints = new LinkedList<Vector3d>();
/**
* Constructor
@ -54,7 +55,7 @@ public class RoomMetadata {
* Gets the furniture slots of the room
* @return The furniture slots of the room
*/
public LinkedList<AreaSelection> getFurnitureSlots() {
public List<FurnitureSlotMetadata> 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<AreaSelection> furnitureSlots) {
public void setFurnitureSlots(List<FurnitureSlotMetadata> 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<Vector3d> getEntryPoints() {
public List<Vector3d> 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<Vector3d> entryPoints) {
public void setEntryPoints(List<Vector3d> entryPoints) {
this.entryPoints = entryPoints;
}

View File

@ -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);
}
}
}
}