@@ -9,6 +9,8 @@ import Foundation
99import UIKit
1010import RoomPlan
1111import ARKit
12+ import SpriteKit
13+ import simd
1214
1315@objc ( CDVRoomPlan)
1416class CDVRoomPlan : CDVPlugin , RoomCaptureSessionDelegate , RoomCaptureViewDelegate , UIDocumentPickerDelegate {
@@ -125,7 +127,19 @@ class CDVRoomPlan: CDVPlugin, RoomCaptureSessionDelegate, RoomCaptureViewDelegat
125127 try jsonData. write ( to: jsonFile)
126128 try self . processedResult? . export ( to: modelFile, exportOptions: . parametric)
127129 if ( self . processedResult != nil ) && isCapturedRoomNil ( capturedRoom: self . processedResult!) {
128- let result = [ " model " : modelFile. absoluteString, " json " : jsonFile. absoluteString, " message " : " Scanning completed successfully " ]
130+ // Generate 2D floor plan
131+ let floorPlanImagePath = generate2DFloorPlan ( capturedRoom: self . processedResult!, outputDirectory: documentsDirectory, uuid: uuid)
132+
133+ var result : [ String : Any ] = [
134+ " model " : modelFile. absoluteString,
135+ " json " : jsonFile. absoluteString,
136+ " message " : " Scanning completed successfully "
137+ ]
138+
139+ if let floorPlanPath = floorPlanImagePath {
140+ result [ " floorPlan " ] = floorPlanPath. absoluteString
141+ }
142+
129143 let pluginResult = CDVPluginResult ( status: CDVCommandStatus_OK, messageAs: result)
130144 pluginResult? . keepCallback = true
131145 self . commandDelegate. send ( pluginResult, callbackId: self . command. callbackId)
@@ -143,6 +157,228 @@ class CDVRoomPlan: CDVPlugin, RoomCaptureSessionDelegate, RoomCaptureViewDelegat
143157 }
144158 }
145159
160+ func generate2DFloorPlan( capturedRoom: CapturedRoom , outputDirectory: URL , uuid: String ) -> URL ? {
161+ guard #available( iOS 17 . 0 , * ) else {
162+ return nil
163+ }
164+
165+ // Create SpriteKit scene for 2D floor plan
166+ let sceneSize = CGSize ( width: 2048 , height: 2048 )
167+ let scene = SKScene ( size: sceneSize)
168+ scene. backgroundColor = . white
169+
170+ // Calculate bounds to center the floor plan
171+ var minX : Float = Float . greatestFiniteMagnitude
172+ var maxX : Float = - Float. greatestFiniteMagnitude
173+ var minZ : Float = Float . greatestFiniteMagnitude
174+ var maxZ : Float = - Float. greatestFiniteMagnitude
175+
176+ // Collect all points to determine bounds
177+ var allPoints : [ ( x: Float , z: Float ) ] = [ ]
178+
179+ // Extract all surfaces to calculate bounds
180+ for wall in capturedRoom. walls {
181+ let transform = wall. transform
182+ let position = transform. position
183+ let dimensions = wall. dimensions
184+ let halfLength = dimensions. x / 2.0
185+ let forward = simd_float3 ( transform. columns. 0 . x, transform. columns. 0 . y, transform. columns. 0 . z)
186+ let wallStart = position - forward * halfLength
187+ let wallEnd = position + forward * halfLength
188+ allPoints. append ( ( x: wallStart. x, z: wallStart. z) )
189+ allPoints. append ( ( x: wallEnd. x, z: wallEnd. z) )
190+ }
191+
192+ for door in capturedRoom. doors {
193+ let transform = door. transform
194+ let position = transform. position
195+ allPoints. append ( ( x: position. x, z: position. z) )
196+ }
197+
198+ for window in capturedRoom. windows {
199+ let transform = window. transform
200+ let position = transform. position
201+ allPoints. append ( ( x: position. x, z: position. z) )
202+ }
203+
204+ for obj in capturedRoom. objects {
205+ let transform = obj. transform
206+ let position = transform. position
207+ allPoints. append ( ( x: position. x, z: position. z) )
208+ }
209+
210+ // Calculate bounds
211+ for point in allPoints {
212+ minX = min ( minX, point. x)
213+ maxX = max ( maxX, point. x)
214+ minZ = min ( minZ, point. z)
215+ maxZ = max ( maxZ, point. z)
216+ }
217+
218+ if allPoints. isEmpty {
219+ return nil
220+ }
221+
222+ // Calculate scale and offset to fit in the scene
223+ let width = maxX - minX
224+ let height = maxZ - minZ
225+ let maxDimension = max ( width, height)
226+ let scale : Float = maxDimension > 0 ? 1800.0 / maxDimension : 1.0
227+ let offsetX = ( minX + maxX) / 2.0
228+ let offsetZ = ( minZ + maxZ) / 2.0
229+
230+ // Helper function to convert 3D coordinates to 2D scene coordinates
231+ func convertToScene( x: Float , z: Float ) -> CGPoint {
232+ let sceneX = CGFloat ( ( x - offsetX) * scale) + sceneSize. width / 2
233+ let sceneY = CGFloat ( ( z - offsetZ) * scale) + sceneSize. height / 2
234+ return CGPoint ( x: sceneX, y: sceneY)
235+ }
236+
237+ // Draw walls using SpriteKit
238+ for wall in capturedRoom. walls {
239+ let transform = wall. transform
240+ let position = transform. position
241+ let dimensions = wall. dimensions
242+ let eulerAngles = transform. eulerAngles
243+
244+ let halfLength = dimensions. x / 2.0
245+ let forward = simd_float3 ( transform. columns. 0 . x, transform. columns. 0 . y, transform. columns. 0 . z)
246+
247+ let wallStart = position - forward * halfLength
248+ let wallEnd = position + forward * halfLength
249+
250+ let startPoint = convertToScene ( x: wallStart. x, z: wallStart. z)
251+ let endPoint = convertToScene ( x: wallEnd. x, z: wallEnd. z)
252+
253+ // Create path for wall
254+ let path = CGMutablePath ( )
255+ path. move ( to: startPoint)
256+ path. addLine ( to: endPoint)
257+
258+ // Create SKShapeNode for wall
259+ let wallNode = SKShapeNode ( path: path)
260+ wallNode. strokeColor = . black
261+ wallNode. lineWidth = max ( 2 , CGFloat ( dimensions. y * scale * 0.1 ) )
262+ wallNode. lineCap = . round
263+ scene. addChild ( wallNode)
264+ }
265+
266+ // Draw doors using SpriteKit
267+ for door in capturedRoom. doors {
268+ let transform = door. transform
269+ let position = transform. position
270+ let dimensions = door. dimensions
271+ let eulerAngles = transform. eulerAngles
272+
273+ let scenePoint = convertToScene ( x: position. x, z: position. z)
274+ let width = CGFloat ( dimensions. x * scale)
275+ let depth = CGFloat ( dimensions. z * scale)
276+
277+ // Create rectangle path for door
278+ let doorRect = CGRect (
279+ x: scenePoint. x - width / 2 ,
280+ y: scenePoint. y - depth / 2 ,
281+ width: width,
282+ height: depth
283+ )
284+ let path = CGPath ( rect: doorRect, transform: nil )
285+
286+ // Create SKShapeNode for door
287+ let doorNode = SKShapeNode ( path: path)
288+ doorNode. fillColor = . lightGray
289+ doorNode. strokeColor = . blue
290+ doorNode. lineWidth = 2
291+ doorNode. zRotation = CGFloat ( eulerAngles. y) // Rotate based on Y-axis rotation
292+ scene. addChild ( doorNode)
293+ }
294+
295+ // Draw windows using SpriteKit
296+ for window in capturedRoom. windows {
297+ let transform = window. transform
298+ let position = transform. position
299+ let dimensions = window. dimensions
300+ let eulerAngles = transform. eulerAngles
301+
302+ let scenePoint = convertToScene ( x: position. x, z: position. z)
303+ let width = CGFloat ( dimensions. x * scale)
304+ let depth = CGFloat ( dimensions. z * scale)
305+
306+ // Create rectangle path for window
307+ let windowRect = CGRect (
308+ x: scenePoint. x - width / 2 ,
309+ y: scenePoint. y - depth / 2 ,
310+ width: width,
311+ height: depth
312+ )
313+ let path = CGPath ( rect: windowRect, transform: nil )
314+
315+ // Create SKShapeNode for window
316+ let windowNode = SKShapeNode ( path: path)
317+ windowNode. fillColor = . white
318+ windowNode. strokeColor = . cyan
319+ windowNode. lineWidth = 2
320+ windowNode. zRotation = CGFloat ( eulerAngles. y)
321+ scene. addChild ( windowNode)
322+ }
323+
324+ // Draw objects using SpriteKit
325+ for obj in capturedRoom. objects {
326+ let transform = obj. transform
327+ let position = transform. position
328+ let dimensions = obj. dimensions
329+ let eulerAngles = transform. eulerAngles
330+
331+ let scenePoint = convertToScene ( x: position. x, z: position. z)
332+ let width = CGFloat ( dimensions. x * scale)
333+ let depth = CGFloat ( dimensions. z * scale)
334+
335+ // Create rectangle path for object
336+ let objectRect = CGRect (
337+ x: scenePoint. x - width / 2 ,
338+ y: scenePoint. y - depth / 2 ,
339+ width: width,
340+ height: depth
341+ )
342+ let path = CGPath ( rect: objectRect, transform: nil )
343+
344+ // Create SKShapeNode for object
345+ let objectNode = SKShapeNode ( path: path)
346+ objectNode. fillColor = . lightGray
347+ objectNode. strokeColor = . brown
348+ objectNode. lineWidth = 2
349+ objectNode. zRotation = CGFloat ( eulerAngles. y)
350+ scene. addChild ( objectNode)
351+ }
352+
353+ // Render the SpriteKit scene to an image
354+ let imageFile = outputDirectory. appendingPathComponent ( uuid + " _floorplan.png " )
355+
356+ // Create SKView to render the scene
357+ let skView = SKView ( frame: CGRect ( origin: . zero, size: sceneSize) )
358+ skView. presentScene ( scene)
359+
360+ // Get texture from the scene
361+ guard let texture = skView. texture ( from: scene) else {
362+ return nil
363+ }
364+
365+ // Convert texture to UIImage
366+ let cgImage = texture. cgImage ( )
367+ let uiImage = UIImage ( cgImage: cgImage)
368+
369+ // Save the image
370+ guard let imageData = uiImage. pngData ( ) else {
371+ return nil
372+ }
373+
374+ do {
375+ try imageData. write ( to: imageFile)
376+ return imageFile
377+ } catch {
378+ return nil
379+ }
380+ }
381+
146382 private func addButtons( ) {
147383 cancelButton = createButton ( title: " Cancel " , backgroundColor: UIColor ( hex: " #D65745 " ) )
148384 doneButton = createButton ( title: " Done " , backgroundColor: UIColor ( hex: " #00A885 " ) )
@@ -194,6 +430,33 @@ class CDVRoomPlan: CDVPlugin, RoomCaptureSessionDelegate, RoomCaptureViewDelegat
194430 }
195431}
196432
433+ // Extension for simd_float4x4 to extract position and euler angles (as per article reference)
434+ extension simd_float4x4 {
435+ var position : simd_float3 {
436+ return simd_float3 ( self . columns. 3 . x, self . columns. 3 . y, self . columns. 3 . z)
437+ }
438+
439+ var eulerAngles : simd_float3 {
440+ // Extract rotation angles from the transform matrix
441+ let sy = sqrt ( self . columns. 0 . x * self . columns. 0 . x + self . columns. 1 . x * self . columns. 1 . x)
442+ let singular = sy < 1e-6
443+
444+ var x : Float , y : Float , z : Float
445+
446+ if !singular {
447+ x = atan2 ( self . columns. 2 . y, self . columns. 2 . z)
448+ y = atan2 ( - self . columns. 2 . x, sy)
449+ z = atan2 ( self . columns. 1 . x, self . columns. 0 . x)
450+ } else {
451+ x = atan2 ( - self . columns. 1 . z, self . columns. 1 . y)
452+ y = atan2 ( - self . columns. 2 . x, sy)
453+ z = 0
454+ }
455+
456+ return simd_float3 ( x, y, z)
457+ }
458+ }
459+
197460extension UIColor {
198461 convenience init ( hex: String , alpha: CGFloat = 1.0 ) {
199462 let hexString = hex. trimmingCharacters ( in: CharacterSet . alphanumerics. inverted)
0 commit comments