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?

Post not yet marked as solved Up vote post of bishusikder Down vote post of bishusikder
8.9k views

Replies

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.

  • Hi, Thanks so much for your solution. But can you provide the full version and source code? Because I do not understand some points:)

  • Hey I've posted a follow up answer. Hope this clears thinks up. If not, I'm planning on writing something better and more elaborate but I'll have to do a lot more refactoring in order to have something to post since my code is quite split up into different functionalities.

  • @ArthurPhilipenko - wondering if you got this working and/or got the full source code for this... I am also working on understanding the specifics of how this can be accomplished, but the snippets have left me with many questions (I am new to swift and xcode). Ideally, I would be able to step through this as I debug to better understand the end-to-end process. Thanks!

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)
    }
  }
}
Add a Comment

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

  • Hey Brent,

    Don't be sorry ;)

    Basically, the dimensions give me the span (that is indeed the length) of the wall. This doesn't tell me anything about the position or rotation of the wall, just it's length or x value. I believe the direction you're after is the zRotation of my wallNode. It is calculated from the eulerAngles out of the transformation matrix.

  • To summarise: The transform contains all the information about the position and rotation of the wall. The dimensions only holds the length of the wall. I don't calculate the two points of the wall, but rather create a line that is the correct length and place that line in correct position at the right rotation. Hope this helps ;)

  • I'm currently writing a full length article about this which is about half way done. I hope this clears everything up as I find it quite difficult to explain with little words :p. It'll most likely take a couple more weeks to be published though, sorry for the wait.

Add a Comment

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.

Add a Comment

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 had the same problem with the relative position of the walls

Add a Comment

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, great work. How would I go about finding the length of each plane so I can find the dimensions of the room?

Add a Comment

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

  • Hey @Brent185,

    Glad it cleared things up!

    The scalingFactor is just an arbitrary number to make sure the size of the surfaces and objects is big enough to see in the SKScene. The actual number inside dimensions and transform is in meters, so to convert to 'actual dimensions' you have to do nothing! :D

    I apply a scalingFactor, otherwise a wall of 5 meters would be 5 points long and in most SpriteKit scenes, this is so small, you would barely see it.

Add a Comment

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

  • Hey @AnjaliNirmale, you can take the rotation of the longest surface and rotate the entire floor plan the same amount in the opposite direction. This way the longest line will be straight and your floor plan will appear to be straight.

  • @denniswave how to rotate the entire floorplan means SKScene.? Can you please explain to me with an example of how to do that..? whenever I tried to generate the floorplan by using https://github.com/denniswave/RoomPlan-2D your solution.it is always the cross floorplan.

  • If you've added all the walls and object to a parent node, you can just rotate that node. Technically the SKScene is the root node but I wouldn't advise rotating that, if possible at all.

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
  • @Brent185 Hi, Finding the longest continuous line means the wall?? I tried that but didn't get the correct output.? can you please explain me in detail with an example.?

  • @AnjaliNirmale hi, the longest continuous line is really arbitrary - I use the longest wall only because for my use case the longest wall will likely result in the best looking floor plan on a desktop screen (imaging your floor plan image above rotated so it is wide, and not tall), and its prob the easiest reference to get - just iterate your collection of walls and grab the one that has the longest length. on a mobile device, you may want to take that longest wall and rotate it to be vertical.

  • Hello @AnjaliNirmale , did you found any solution to the cross floor plan ?