Adding floor to Entity created from RoomPlan USDZ file

I would like to add a floor to an Entity I created from a RoomPlan USDZ file. Here's my approach:

  • Recursively traverse the Entity's children to get all of its vertices.
  • Find the minimum and maximum X, Y and Z values and use those to create a plane.
  • Add the plane as a child of the room's Entity.

The resulting plane has the correct size, but not the correct orientation. Here's what it looks like:

The coordinate axes you see show the world origin. I rendered them with this option:

arView.debugOptions = [.showWorldOrigin]

That world origin matches the place and orientation where I started scanning my room.

I have tried many things to align the floor with the room, but nothing has worked. I'm not sure what I'm doing wrong. Here's my recursive function that gets the vertices (I'm pretty sure this function is correct since the floor has the correct size):

  func getVerticesOfRoom(entity: Entity, _ transformChain: simd_float4x4) {
  let modelEntity = entity as? ModelEntity
  guard let modelEntity = modelEntity else {
   // If the Entity isn't a ModelEntity, skip it and check if we can get the vertices of its children
   let updatedTransformChain = entity.transform.matrix * transformChain
   for currEntity in entity.children {
    getVerticesOfRoom(entity: currEntity, updatedTransformChain)
   }
   return
  }

  // Below we get the vertices of the ModelEntity
  let updatedTransformChain = modelEntity.transform.matrix * transformChain

  // Iterate over all instances
  var instancesIterator = modelEntity.model?.mesh.contents.instances.makeIterator()
  while let currInstance = instancesIterator?.next() {
   // Get the model of the current instance
   let currModel = modelEntity.model?.mesh.contents.models[currInstance.model]

   // Iterate over the parts of the model
   var partsIterator = currModel?.parts.makeIterator()
   while let currPart = partsIterator?.next() {
    // Iterate over the positions of the part
    var positionsIterator = currPart.positions.makeIterator()
    while let currPosition = positionsIterator.next() {
     // Transform the position and store it
     let transformedPosition = updatedTransformChain * SIMD4<Float>(currPosition.x, currPosition.y, currPosition.z, 1.0)
     modelVertices.append(SIMD3<Float>(transformedPosition.x, transformedPosition.y, transformedPosition.z))
    }
   }
  }

  // Check if we can get the vertices of the children of the ModelEntity
  for currEntity in modelEntity.children {
   getVerticesOfRoom(entity: currEntity, updatedTransformChain)
  }
 }

And here's how I call it and create the floor:

     // Get the vertices of the room
    getVerticesOfRoom(entity: roomEntity, roomEntity.transform.matrix)

    // Get the min and max X, Y and Z positions of the room
    var minVertex = SIMD3<Float>(Float.greatestFiniteMagnitude, Float.greatestFiniteMagnitude, Float.greatestFiniteMagnitude)
    var maxVertex = SIMD3<Float>(-Float.greatestFiniteMagnitude, -Float.greatestFiniteMagnitude, -Float.greatestFiniteMagnitude)
    for vertex in modelVertices {
     if vertex.x < minVertex.x { minVertex.x = vertex.x }
     if vertex.y < minVertex.y { minVertex.y = vertex.y }
     if vertex.z < minVertex.z { minVertex.z = vertex.z }
     if vertex.x > maxVertex.x { maxVertex.x = vertex.x }
     if vertex.y > maxVertex.y { maxVertex.y = vertex.y }
     if vertex.z > maxVertex.z { maxVertex.z = vertex.z }
    }

    // Compose the corners of the floor
    let upperLeftCorner: SIMD3<Float> = SIMD3<Float>(minVertex.x, minVertex.y, minVertex.z)
    let lowerLeftCorner: SIMD3<Float> = SIMD3<Float>(minVertex.x, minVertex.y, maxVertex.z)
    let lowerRightCorner: SIMD3<Float> = SIMD3<Float>(maxVertex.x, minVertex.y, maxVertex.z)
    let upperRightCorner: SIMD3<Float> = SIMD3<Float>(maxVertex.x, minVertex.y, minVertex.z)

    // Create the floor's ModelEntity
    let floorPositions: [SIMD3<Float>] = [upperLeftCorner, lowerLeftCorner, lowerRightCorner, upperRightCorner]
    var floorMeshDescriptor = MeshDescriptor(name: "floor")
    floorMeshDescriptor.positions = MeshBuffers.Positions(floorPositions)
    // Positions should be specified in CCWISE order
    floorMeshDescriptor.primitives = .triangles([0, 1, 2, 2, 3, 0])
    let simpleMaterial = SimpleMaterial(color: .gray, isMetallic: false)
    let floorModelEntity = ModelEntity(mesh: try! .generate(from: [floorMeshDescriptor]), materials: [simpleMaterial])
    guard let floorModelEntity = floorModelEntity else {
     return
    }

    // Add the floor as a child of the room
    roomEntity.addChild(floorModelEntity)

Can you think of a transformation that I could apply to the vertices or the plane to align them?

Thanks for any help.

If you find the corner vertices of your walls you can create the floor using an extruded SCNShape. The tricky part is to make sure your vertices are in logical order, but after that, you can draw a bezier it using the x and z coordinates of your points. You'll have to rotate it by 90 degrees and position it but it should already have the correct rotation. This will help if your room is not rectangular. I'd add code but it's still on my list of things to do I'm afraid. But broadly my plan is to pick a wall and choose a corner, find the wall that's got a corner in the same position, presume it's the next in line and iterate through all the outside walls.

But a quick and dirty way if your room is always rectangular is to find the rotation of the longest wall and rotate your floor by the same angle.

Your code is incredibly well-thought, creative, and a great example of how to build procedural geometries in RealityKit. Really great work! I'm wondering why it is necessary to build the floor geometry based on the individual sizing and vertices of the components of the RoomPlan scan. More specifically, if you build the RoomPlan sample project, the RoomCaptureView experience builds what appears to be a rectangle matching (or slightly extending past) the bounds of the scan as a floor, but the shape never changes from a rectangle, nor is it custom based on a uniquely shaped room. Based on this, and to negate having to worry about rotations, it might be easier to build a rectangle as the floor, using the bounds of the RoomPlan model, and add it as a child to the loaded Entity. You could do something like;

let entityBounds = myRoomPlanEntity.visualBounds(relativeTo: nil) // Get the bounds of the RoomPlan scan Entity.
let width = entityBounds.extents.x + 0.025 // Slightly extend the width of the "floor" past the model, adjust to your preference.
let height = Float(0.002) // Set the "height" of the floor, or its thickness, to your preference.
let depth = entityBounds.extents.z + 0.0125 // Set the length/depth of the floor slightly past the model, adjust to your preference.

let boxResource = MeshResource.generateBox(size: SIMD3<Float>(width, height, depth))
let material = SimpleMaterial(color: .white, roughness: 0, isMetallic: true)
let floorEntity = ModelEntity(mesh: boxResource, materials: [material])

let yCenter = (entityBounds.center.y * 100) - 1.0 // Set the offset of the floor slightly from the mode, adjust to your preference.
floorEntity.scale = [100.0, 100.0, 100.0] // Scale the model by a factor of 100, as noted in the [release notes](https://developer.apple.com/documentation/ios-ipados-release-notes/ios-ipados-16-release-notes) for working with RoomPlan entities.
floorEntity.position = [entityBounds.center.x * 100, yCenter, entityBounds.center.z * 100]
myRoomPlanEntity.addChild(floorEntity)

Not sure if this works for your needs, but might be a way to avoid having to worry about the rotation offset when building a custom geometry for the floor.

Was anyone able to add the same floor as Apple did? Why won't they give as access to this info, that's just rude

Hello guys. Do you have some ideas on how to find the corner edges of the wall? Because I want to draw a 2d map of the room. And I decided firstly to find corner edges on each wall, but I don't understand how can I do it.

Adding floor to Entity created from RoomPlan USDZ file
 
 
Q