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?

I've been tinkering with it too. I'm a newbie and I can get it to scan and stop but I can't figure out how to export to floor plan eerier. If you figure let me know !

I also investigate this. And I have an idea, we have simd_float4x4 matrix, and we can get coordinate our walls, but it will be just point, need to draw a line from this and other points, and find edges, just ignore Y axis, but I don't understand in which direction need to draw a line. Probably I don't know how works simd_float4x4 matrix, and how need to decode the value. Do you have some ideas?

Would love to collaborate on this. I too think that going to a simple 2D plan will be important. Nate

I've found a solution for this. As suggested, the simd_float4x4 matrix is the key to the solution here. Using SpriteKit I've created my 2D map using RoomPlan data.

To start with, I've created a couple of extensions for simd_float4x4. These extract the data we need from the metrix so it's easier to work with:

extension simd_float4x4 {
    var eulerAngles: simd_float3 {
        simd_float3(
            x: asin(-self[2][1]),
            y: atan2(self[2][0], self[2][2]),
            z: atan2(self[0][1], self[1][1])
        )
    }

    var position: simd_float3 {
        simd_float3(x: self.columns.3.x,
                    y: self.columns.3.y,
                    z: self.columns.3.z
        )
    }

    var positionVector: SCNVector3 {
        SCNVector3(x: self.columns.3.x,
                   y: self.columns.3.y,
                   z: self.columns.3.z
        )
    }
}

To build my 2D map in SpriteKit, I created a separate SKNode for every RoomPlan surface. In each node I set the correct position and rotation using the sims_float4x4 matrix, create a path using the dimensions and draw the surface using an SKShapeNode.

Using the eulerAngles and position extensions, I set the correct rotation and position of every surface:

// Position surface
position.x = -CGFloat(transform.position.x) * mapScale
position.y = CGFloat(transform.position.z) * mapScale
zRotation = -CGFloat(transform.eulerAngles.z - transform.eulerAngles.y)

The mapScale is just a scaling factor I applied. The measurements that come out of RoomPlan are in meters. In SpriteKit they're in points. If you don't apply a scale, the whole map will display within a few pixels. Setting the mapScale to 100-300 will do fine, depending on the device's screen size and SpriteKit camera.

The x position is the same in both RoomPlan's and SpriteKit's coordinate system. In RoomPlan however, the x- and y-axis are horizontal and vertical and the z-axis is the "depth" axis. We're interested in the "top" view, ignoring RoomPlan's y-axis so we'll need to use the z value for SpriteKit's y component (I hope my explanation makes any sense :p).

The surfaceRotation is a weird one. I still don't fully get why this works like this, but it does xd.

As pointed out by @ArthurPhilipenko the matrix only tells us about the position and rotation of the surface which will result in a point in space, but not a line, like we want to draw. For this, we also need the "dimension" component from the RoomPlan surface. This is a simd_float3. Using this dimension component, we can draw a line using the width of the surface (or the dimension.x) and positioning it equally on both sides of out position and rotation point of the node.

// Create surface path
let surfacePath = CGMutablePath()
let span = CGFloat(dimensions.x) * mapScale / 2
surfacePath.move(to: CGPoint(x: -span, y: 0))
surfacePath.addLine(to: CGPoint(x: span, y: 0))

Finally, I create a SKShapeNode to draw the line on the screen:

// Draw surface path
func draw() { 
    let surfaceShape = SKShapeNode(path: surfacePath)
    surfaceShape.strokeColor = surfaceColor
    surfaceShape.lineWidth = wallThickness
    surfaceShape.lineCap = .square
    addChild(surfaceShape)
}

If you do this for every surface you'll get a nice 2D map using the 3D RoomPlan data.

To clarify, the code below will generate SKShapeNodes for all walls captured in the CapturedRoom attribute. It uses the extensions mentioned in my previous answer. Hope this'll help.

class Walls: SKNode {

  init() {
    for wall in CapturedRoom.walls {
      let wallNode = SKNode()
      addChild(wallNode)

      // Position wallNode
      wallNode.position.x = -CGFloat(wall.transform.position.x) * 200
      wallNode.position.y = CGFloat(wall.transform.position.z) * 200
      wallNode.zRotation = -CGFloat(wall.transform.eulerAngles.z - wall.transform.eulerAngles.y)

      // Create the path for the wall
      let surfacePath = CGMutablePath()
      let span = CGFloat(wall.dimensions.x) * 200 / 2
      surfacePath.move(to: CGPoint(x: -span, y: 0))
      surfacePath.addLine(to: CGPoint(x: span, y: 0))

      // Draw the wall using an SKShapeNode and the path
      let wallShape = SKShapeNode(path: surfacePath)
      wallShape.strokeColor = .white
      wallShape.lineWidth = 5
      wallShape.lineCap = .square
      
      wallNode.addChild(wallShape)
    }
  }
}

Hey did you found the solution? If yes then can you link a test project?

I'm aware my explanation is not the clearest. I'll write an article about this to explain things more clearly and provide a sample project.

Great, i'd also be interested in a sample project!

hi Dennis,

in lieu of getting a working solution, which I believe would answer this question, I am hoping you may be able to answer a question about the code you posted, per my understanding of building a 2D representation using the data from CapturedRoom.walls.

In a cartesian coordinate space, a wall would have, at a minimum, two points, with each point represented by an x and y value (which would be a single number). So a wall that is a 100 units long spanning 'left to right' would look like this (p is a wall point) [p:{x:0,y:0},p:{x:100,y:0}]

In a 2d space, in its simplest form, this wall would look like a horizontal line.

How do you map the data you show in your code to this type of cartesian data? I see you pulling out the x and y positions, and i see the span which i assume is the length, but how do you know which direction the line travels in cartesian space?

Sorry if this is a dumb question, I am just trying to distill down the code you thoughtfully provided to what I understand about 2D. Thanks

Use every node of your usdz model and sort out their matrix. I create a clean floor plan graph structure in 2d with all matrix of nodes.

hi @denniswave I started converting the 3D floorplan to 2D by using your solution..but getting the cross floorplan as result.I.e walls are cross instead of horizontal,vertical means

I've created a project that creates a 2D floor plan out of RoomPlan data. I hope this answers anyone's questions. An article explaining every step is on its way. https://github.com/denniswave/RoomPlan-2D

hi @denniswave, this is really cool - well thought through and executed. thanks for the guidance and repo!

quick question about scale and 'actual dimensions'. How does one acquire the real dimensions that are acquired by the lidar related processes on the device? I assume that the CapturedRoom surface objects store this in a unit of measure or format that can be converted easily to meters or feet/inches etc, but this is not clear in the code. I do see this in your constants:

let scalingFactor: CGFloat = 200

but I am not sure how the value 200 is related to the real world dimensions.

Ideally, I can take whatever unit of measure the device produces and scale it appropriately to units required on the system with which i integrate.

thanks! Brent

hi@denniswave,it has generated the cross floorplan..how to make that straight.?

hey guys, I ran into this same issue and was able to resolve it with excellent results. Dennis is right - but note that the solution depends on what you're attempting to accomplish and how you're managing the data.

I am exporting the data generated by room plan (in addition to the 2D data) to another app/platform. For the rotation use case, I take the longest continuous line, find its angle in radians, and then get the difference in radians relative to that line being horizontal (you can use 0, or Math.Pi or Math.Pi*2, etc - ... I am using javascript to do this btw). Once you have that delta in radians, you then rotate all points accordingly. It's important that I do this rotation before anything else, as I generate a ton of additional data that is derived from wall points.

Also, as a side note, I create wall depths, miters, faces, etc that make these walls much more than a thick drawn line. So I take the data that Dennis' process provides (essentially the midpoint of wall, its length and angle) and from there create two points to start from - the start and end points of the wall. For my purposes these endpoints make it easier to work from and to create the downstream data structures I require - and it makes it easier in my opinion to do things like rotate walls, but again, totally depends on your use cases.

@AnjaiNimale - per your question (as I eluded to above), if you're getting 'crosses' as a result, its likely that you are doing what I did when i first worked with the data, which is using the x and y position as a wall end point. Use it as a midpoint and find the start and end point using PointA and PointB. If you look at FloorPlanSurface, you'll see that these two points are derived from taking half the width of the wall and going in opposite directions. Confusing at first.

  • Brent

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.

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