How to get a 2D floor plan with dimensions from RoomPlan

I am in need to create a 2D floor plan with the dimensions mentioned, from the generated 3D result of RoomPlan. Is there a way to create it in a little easy-to-understand manner? Or will it require manual elaborate coding?

PS - i can try to pull out some javascript code for you, but its highly fragmented and it doesnt use any of the objects/properties that the RoomPlan or ARKit SDKs use, so i doubt it would be much help.

I've posted an article in which I go through the process of setting up an app and creating a 2D floor plan using SpriteKit: https://totheroot.io/content/create-a-2-d-floor-plan-with-room-plan-and-sprite-kit-part-i

@denniswave : I recently came across your tutorial, "Create a 2D Floor Plan with RoomPlan and SpriteKit" and just wanted to drop you a quick thank you. It was super clear and really helped me out! 😃👍

By the way, do you have any tips on how I could display wall lengths and floor areas within each room? That’d be the cherry on top! 🍒 Cheers !

Hi @denniswave ! Do you think it is possible to calculate a surface area? I couldnt find this in API

Hi @denniswave your highness. It is an honour to have you here. The sample app you provided is just as remarkable as you are. It undoubtedly paves the way for many others working in this area.

I have a follow-up question, though. Suppose I have point cloud data, like a LAS file, from a room I've scanned. How realistic do you think it would be to re-visualize this data in either 3D or 2D?

@AkshayBagekari I tried the solutions for the cross floorplan that @denniswave and @Brent185 suggested, but I couldn't solve it.. and are right, but solutions depend on how each person thinks. Everyone's thinking process may be different. I aimed for a straightforward output - So, I added a rotation gesture(UIRotationGestureRecognizer) and checked the camera angle when the user rotated it. I compared that angle with the angle of each surface, and it matched the angle of the longest continuous surface. , I set that rotation angle as the camera angle. I didn't rotate the whole surface; I just changed the camera angle. Finally, I got the correct output. I added the rotation gesture only for testing purposes to see if it appears straight at a certain angle, which means you don't need to add the gesture.

For example :- by setting camera rotation.
self.camera?.zRotation = largesurfaceAngle

@denniswave Thanks for the shared research into this!

for everyone else looking to get some dimensions and rotating the final sketch I just played around a little bit today. I have not used SpriteKit before so was just messing around and my results are below.

To rotate: self.position needs to be rotated by the largest surface (or any surface really) euler angle y in the negative direction.

Then each self.z rotation needs to compensate for this by adding the same surfaces euler y angle as well. this is because we are subtracting the y angle. So if we want the surface we select to be horizontal we need to subtract it back out. Do this for all z rotations.

code attached below.

```swift
  private func getLargestSurface() {
    largestSurface = surfaces.first
    for surface in surfaces {
      if surface.dimensions.x > largestSurface?.dimensions.x ?? 0 {
        largestSurface = surface
      }
    }
  }
```

here do the extra rotations for each wall.

I also added a basic dimension calculator to demonstrate how this might look converting from the RoomPlan dimensions. This is not super useful in its current state but I was just playing around and figured I would share my findings.
```
//
//  FloorPlanSurface.swift
//  RoomPlan 2D
//
//  Created by Dennis van Oosten on 12/03/2023.
//

import SpriteKit
import RoomPlan

class FloorPlanSurface: SKNode {
  
  private let capturedSurface: CapturedRoom.Surface
  
  // MARK: - Computed properties
  
  private var halfLength: CGFloat {
    return CGFloat(capturedSurface.dimensions.x) * scalingFactor / 2
  }
  
  private var pointA: CGPoint {
    return CGPoint(x: -halfLength, y: 0)
  }
  
  private var pointB: CGPoint {
    return CGPoint(x: halfLength, y: 0)
  }
  
  private var pointADim: CGPoint {
    return CGPoint(x: -halfLength, y: -dimensionLineDistFromSurface)
  }
  
  private var pointBDim: CGPoint {
    return CGPoint(x: halfLength, y: -dimensionLineDistFromSurface)
  }
  
  private var pointC: CGPoint {
    return pointB.rotateAround(point: pointA, by: 0.25 * .pi)
  }
  
  // MARK: - Init
  
  init(capturedSurface: CapturedRoom.Surface) {
    self.capturedSurface = capturedSurface
    
    super.init()
    
    // Set the surface's position using the transform matrix
    let surfacePositionX = -CGFloat(capturedSurface.transform.position.x) * scalingFactor
    let surfacePositionY = CGFloat(capturedSurface.transform.position.z) * scalingFactor
    self.position = CGPoint(x: surfacePositionX, y: surfacePositionY)
      .rotateAround(
        point: CGPointZero,
        by: -CGFloat(largestSurface?.transform.eulerAngles.y ?? 0)
      ) //this rotation will rotate each point around an arbitrary point to get the roration of midpoints in the correct location
    
    // Set the surface's zRotation using the transform matrix
    // the additional largest surface rotation here will get the new angle offset for each wall. i.e. if this is the largest wall the rotation will be 0 - largest wall rotation + largest wall rotation. this essentially brings it back to a horizontal angle.
    self.zRotation = -CGFloat(capturedSurface.transform.eulerAngles.z - capturedSurface.transform.eulerAngles.y + (largestSurface?.transform.eulerAngles.y ?? 0))
    
    // Draw the right surface
    switch capturedSurface.category {
    case .door:
      drawDoor()
    case .opening:
      drawOpening()
    case .wall:
      drawWall()
    case .window:
      drawWindow()
    @unknown default:
      drawWall()
    }
  }
  
  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  
  // MARK: - Draw
  
  private func drawDoor() {
    let hideWallPath = createPath(from: pointA, to: pointB)
    let doorPath = createPath(from: pointA, to: pointC)
    
    // Hide the wall underneath the door
    let hideWallShape = createShapeNode(from: hideWallPath)
    hideWallShape.strokeColor = floorPlanBackgroundColor
    hideWallShape.lineWidth = hideSurfaceWith
    hideWallShape.zPosition = hideSurfaceZPosition
    
    // The door itself
    let doorShape = createShapeNode(from: doorPath)
    doorShape.lineCap = .round
    doorShape.zPosition = doorZPosition
    
    // The door's arc
    let doorArcPath = CGMutablePath()
    doorArcPath.addArc(
      center: pointA,
      radius: halfLength * 2,
      startAngle: 0.25 * .pi,
      endAngle: 0,
      clockwise: true
    )
    
    // Create a dashed path
    let dashPattern: [CGFloat] = [24.0, 8.0]
    let dashedArcPath = doorArcPath.copy(dashingWithPhase: 1, lengths: dashPattern)
    
    let doorArcShape = createShapeNode(from: dashedArcPath)
    doorArcShape.lineWidth = doorArcWidth
    doorArcShape.zPosition = doorArcZPosition
    
    addChild(hideWallShape)
    addChild(doorShape)
    addChild(doorArcShape)
    drawBackWallCaps()
  }
  
  private func drawOpening() {
    let openingPath = createPath(from: pointA, to: pointB)
    
    // Hide the wall underneath the opening
    let hideWallShape = createShapeNode(from: openingPath)
    hideWallShape.strokeColor = floorPlanBackgroundColor
    hideWallShape.lineWidth = hideSurfaceWith
    hideWallShape.zPosition = hideSurfaceZPosition
    
    addChild(hideWallShape)
    drawBackWallCaps()
  }
  
  private func drawWall() {
    let wallPath = createPath(from: pointA, to: pointB)
    let wallShape = createShapeNode(from: wallPath)
    wallShape.lineCap = .round
    
    let dimensionsPath = createDimPath(from: pointADim, to: pointBDim)
    let dimensionsShape = createDimNode(from: dimensionsPath)
    dimensionsShape.lineCap = .round
    
    let dimensionsLabel = createDimLabel()
    
    addChild(wallShape)
    addChild(dimensionsShape)
    addChild(dimensionsLabel)
  }
  
  private func drawWindow() {
    let windowPath = createPath(from: pointA, to: pointB)
    
    // Hide the wall underneath the window
    let hideWallShape = createShapeNode(from: windowPath)
    hideWallShape.strokeColor = floorPlanBackgroundColor
    hideWallShape.lineWidth = hideSurfaceWith
    hideWallShape.zPosition = hideSurfaceZPosition
    
    // The window itself
    let windowShape = createShapeNode(from: windowPath)
    windowShape.lineWidth = windowWidth
    windowShape.zPosition = windowZPosition
    
    addChild(hideWallShape)
    addChild(windowShape)
  
    drawBackWallCaps()
  }
  
  // MARK: - Helper functions
  
  private func createPath(from pointA: CGPoint, to pointB: CGPoint) -> CGMutablePath {
    let path = CGMutablePath()
    path.move(to: pointA)
    path.addLine(to: pointB)
    
    return path
  }
  
  private func createDimPath(from pointA: CGPoint, to pointB: CGPoint) -> CGMutablePath {
    let path = CGMutablePath()
    // edges of dimension line
    path.move(to: CGPoint(x: pointA.x, y: pointA.y-surfacedWidth))
    path.addLine(to: CGPoint(x: pointA.x, y: pointA.y+surfacedWidth))
    path.move(to: CGPoint(x: pointB.x, y: pointB.y-surfacedWidth))
    path.addLine(to: CGPoint(x: pointB.x, y: pointB.y+surfacedWidth))
    
    // main line with gap for label
    path.move(to: pointA)
    path.addLine(to: CGPoint(x: -dimensionLabelWidth/2, y: -dimensionLineDistFromSurface))
    path.move(to: pointB)
    path.addLine(to: CGPoint(x: dimensionLabelWidth/2, y: -dimensionLineDistFromSurface))
    
    return path
  }
  
  
  private func createDimLabel() -> SKLabelNode {
    let dimTotalInches = self.capturedSurface.dimensions.x*metersToInchesFactor
    let feet = Int(dimTotalInches/12)
    let inches = Int(round(dimTotalInches)) % 12
    
    let label = SKLabelNode(text: "\(feet)' \(inches)''")
    label.fontColor = floorPlanSurfaceColor
    label.position.y = -dimensionLineDistFromSurface - labelFontSize/2
    label.fontSize = labelFontSize
    label.fontName = labelFont
    
    return label
  }
  
  private func createShapeNode(from path: CGPath) -> SKShapeNode {
    let shapeNode = SKShapeNode(path: path)
    shapeNode.strokeColor = floorPlanSurfaceColor
    shapeNode.lineWidth = surfacedWidth
    
    return shapeNode
  }
  
  private func createDimNode(from path: CGPath) -> SKShapeNode {
    let shapeNode = SKShapeNode(path: path)
    shapeNode.strokeColor = floorPlanSurfaceColor
    shapeNode.lineWidth = dimensionWidth
    
    return shapeNode
  }
  
  private func drawBackWallCaps() {
    let lineWidth: CGFloat = 1
    let capA = SKShapeNode(circleOfRadius: surfacedWidth/2-lineWidth/2)
    let capB = SKShapeNode(circleOfRadius: surfacedWidth/2-lineWidth/2)
    capA.lineWidth = lineWidth
    capB.lineWidth = lineWidth
    capA.position = pointA
    capB.position = pointB
    capA.fillColor = floorPlanSurfaceColor
    capB.fillColor = floorPlanSurfaceColor
    capA.strokeColor = floorPlanSurfaceColor
    capB.strokeColor = floorPlanSurfaceColor
    capA.zPosition = hideSurfaceWallCapZPosition
    capB.zPosition = hideSurfaceWallCapZPosition
    
    addChild(capA)
    addChild(capB)
  }
}
```

To get a 2D floor plan with dimensions from RoomPlan:

  1. With RoomPlan's intuitive tools, you can easily draw the walls, doors, and windows of your room.

  2. You can add dimensions by clicking on the "Dimensions" tool and selecting the elements you want to measure. RoomPlan calculates and displays the dimensions automatically.

  3. Choosing the "Export" option and selecting the desired format (e.g., PDF, PNG, JPEG) will export your plan. Your added dimensions will be included in the exported plan.

Has anyone managed to draw the floor? iOS17 introduced the floor as polygonCorners. I have managed to draw it but it's in the wrong rotation (and I can't find how to calculate the rotation).

How to get a 2D floor plan with dimensions from RoomPlan
 
 
Q