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.
Post
Replies
Boosts
Views
Activity
I've posted a solution to this in this similar post: https://developer.apple.com/forums/thread/713409?page=1#734583022
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)
}
}
}
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.
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
I’ve updated the solution with a sample project: https://developer.apple.com/forums/thread/713409?page=1#734583022
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