Room Builder

defining, crying, learning and applying

Agenda

  • Defining
    ​what's a room? (10 min)
  • Crying
    why graph theory didn't work (5 min)
  • Learning
    a-maze-ing grace (10-20 min)
  • Applying
    open questions/code review (rest)

Whats a room?

My event is in Grand Cafe

My event is in Penn Quarter and uses the Conference Theatre

I need the square footage of the Lower Level Dinning Space

What's a room to ST?

Points + Lines

Each line has a set length

(often doesn't match drawing we are given)

What's a room to a computer?

  • Programmatically define all spaces
  • Account for theoretically infinitely complex rooms
  • "Understands" a room in the same way we do

Graph Theory

Nodes

Have a collection of edges

Usually a data point, which is used to build edge rel.

*HAS NO OTHER DATA*

 

Edges

Can be directed (FPC=not)

Have a value (FPC=length)

https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm

Mazes

Advantages

Spacial relationships are taken into account

 

Issues

Maze must be simply connected (see Poke)

http://en.wikipedia.org/wiki/Maze_solving_algorithm#Wall_follower

Build Rooms

var pointsToCheck = [].concat(floorData.points);

while(pointsToCheck.length > 0) {
  var point = pointsToCheck[0];

  var graph = {
    points: [],
    boundaries: []
  };
  makeGraphFromPoint(point, graph); // look
  graph.points.forEach(function(node) {
    if(pointsToCheck.indexOf(node) !== -1) {
      pointsToCheck.splice(pointsToCheck.indexOf(node), 1);
    }
  });
  graphs.push(graph);
}

graphs.forEach(function(graph) {
  var possibleRooms = buildRoomsFromGraph(graph); // at these
  graph.rooms = possibleRooms;
  rooms = _.union(rooms, possibleRooms);
});

makeGraphFromPoint

function(point, graph) {
  graph.points.push(point);
  point.boundaries.forEach(function(b) {
    if(graph.boundaries.indexOf(b) === -1) {
      graph.boundaries.push(b);
      var points = [b.startPoint, b.endPoint];
      points.forEach(function(p) {
        if(graph.points.indexOf(p) === -1) {
          makeGraphFromPoint(p, graph);
        }
      });
    }
  });
};

buildRoomsFromGraph (1)

AngleUtils.sortPointBoundaryRelationshipsByAngle(graph);

graphBoundaries.forEach(function(b) {
  b.traversed = null; //set up bs to be traversed;
});

graph.points.sort(function(a, b) {
  return (a.x-b.x) || (a.y-b.y); // sort by x value with backup to y value.
});

var firstPoint = graph.points[0];
var firstBoundary = firstPoint.boundaries[0];
var lastBoundary = firstPoint.boundaries[firstPoint.boundaries.length - 1];

firstPoint.isOuterEdge = true;
firstBoundary.isOuterEdge = firstBoundary.endPoint === graph.points[0] ? -1 : 1;
lastBoundary.isOuterEdge = lastBoundary.endPoint === graph.points[0] ? 1 : -1;

buildRoomsFromGraph (2)

while(graphBoundaries.length > 0) {
  var boundary = graphBoundaries[0];
  var roomA = traverse(boundary, 1);
  var roomB = traverse(boundary, -1);
  if(roomA.length > 2) {
    rooms.push(roomA);
  }
  if(roomB.length > 2) {
    rooms.push(roomB);
  }
  graphBoundaries.forEach(function(b) {
    if(b.traversed === 0) {
      graphBoundaries.splice(graphBoundaries.indexOf(b), 1);
    }
  });
}
return rooms;

traverse (1)

function(boundary, direction) {
  var currentBoundary = boundary;
  var nextBoundaryIndex;
  var nextBoundary;
  var boundariesInRoom = [];

  var isOuterEdge = false;

  // traversed is -1, 1, or 0
  var boundaryHasNotHadThisTraversal = 
    !(boundary.traversed === direction || boundary.traversed === 0);
  var endPoint = "endPoint";
  var directionModifier = 1;

  ...traverse...

  if(isOuterEdge) {
    // may want to save this at a future date
    boundariesInRoom = [];
  }

  return boundariesInRoom;
};

traverse (2)

if(boundaryHasNotHadThisTraversal) {
  while(nextBoundary !== boundary) {

    if(nextBoundary) {
      if(currentBoundary[endPoint] !== nextBoundary.startPoint) {
        endPoint = "startPoint";
        directionModifier = -1;
      }
      else {
        endPoint = "endPoint";
        directionModifier = 1;
      }
      currentBoundary = nextBoundary;
    }
    var localDirection = direction * directionModifier;
    currentBoundary.traversed += localDirection;
    if(currentBoundary.traversed < -1 || currentBoundary.traversed > 1) {
      throw new Error("This floorplan cannot be traversed. Make sure no boundaries cross.");
    }
    // to find the hull (ILOR)
    if(currentBoundary.isOuterEdge === localDirection) {
      isOuterEdge = true;
    }

    boundariesInRoom.push(currentBoundary);

    nextBoundary = CircularArray.traverseFromValue(currentBoundary[endPoint].boundaries, currentBoundary, direction);
  }
}

createRoomRelationships

// TODO this should be a part of build rooms but we are dependent on render
var polygonUtil = PointInPolygonWithArc(floorData);

this.graphs.forEach(function(graph, index, graphs) {
  graphs.forEach(function(graphToCompare) {
    if(graph !== graphToCompare) {
      var point = graph.points[0];
      var roomIndeces = graphToCompare.rooms.map(function(room) {
        return floorData.rooms.indexOf(room);
      });
      var possibleRoom = polygonUtil.pointIsInPolygon(point, roomIndeces); // magic
      if(possibleRoom) {
        graph.outerRooms.push(possibleRoom)
      }
    }
  });
});

pointIsInPolygon

var ignoredElementString = ".st-point, .st-boundary, .st-point-circle";
for(var i = 0; i < floorData.rooms.length; i++) {
  if(indeces.indexOf(i) === -1) {
    ignoredElementString += (", .room-"+i);
  }
}

var ignoredElements = document.querySelectorAll(ignoredElementString);
var screenPoint = svgPointToScreenPoint(point, svg);

addNoPointer(ignoredElements);
var pointerElement = document.elementFromPoint(screenPoint.x, screenPoint.y);
removeNoPointer(ignoredElements);
if(pointerElement) {
  var index = null;
  var reactId = pointerElement.getAttribute("data-reactid");
  var type_parts = reactId.split("$");
  if(type_parts.length==2){
    var id_parts = type_parts[1].split("-");
    if(id_parts.length==2){
      index = parseInt(id_parts[1]);
    }
  }

  return floorData.rooms[index];
}

createRoomRelationships

var allRoomRelationShipsAdded = function(graphs) {
  return graphs.reduce(function(allRoomRelationShipsAdded, graph) {
    var hasNoOuterRooms = graph.outerRooms.length === 0;
    return allRoomRelationShipsAdded && hasNoOuterRooms;
  }, true)
}

// this is not very efficient
while(!allRoomRelationShipsAdded(this.graphs)) {
  this.graphs.forEach(function(graph, index, graphs) {
    if(graph.outerRooms.length === 1) {
      graph.rooms.forEach(function(room) {
        addRoomRelationship(room, graph.outerRooms[0]);
      });
      var outerRoomThatShouldBeRemoved = graph.outerRooms[0];
      graphs.forEach(function(graphForRemoval) {
        var index = graphForRemoval.outerRooms.indexOf(outerRoomThatShouldBeRemoved);
        var shouldRemove = graphForRemoval === graph || graphForRemoval.outerRooms.length > 1;
        if(shouldRemove && index !== -1) {
          graphForRemoval.outerRooms.splice(index, 1);
        }
      });
    }
  });
}

Thank You

http://fpc.socialtables.com/properties/1/buildings/25/floors/14510/draw/boundaries

room-builder

By rickyvetter

room-builder

  • 652