diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..1216a73 Binary files /dev/null and b/.DS_Store differ diff --git a/iOS App/.DS_Store b/iOS App/.DS_Store new file mode 100644 index 0000000..498df6c Binary files /dev/null and b/iOS App/.DS_Store differ diff --git a/iOS App/ARPen.xcodeproj/project.pbxproj b/iOS App/ARPen.xcodeproj/project.pbxproj index 1d01f1f..22b0949 100644 --- a/iOS App/ARPen.xcodeproj/project.pbxproj +++ b/iOS App/ARPen.xcodeproj/project.pbxproj @@ -7,9 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 4A27E27D239FCEA800C4BF75 /* NotificationName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A27E27C239FCEA800C4BF75 /* NotificationName.swift */; }; - 4A27E27F239FF36D00C4BF75 /* PassthroughView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A27E27E239FF36D00C4BF75 /* PassthroughView.swift */; }; - 4A27E28123A0F9E300C4BF75 /* SecondButton.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4A27E28023A0F9E300C4BF75 /* SecondButton.xib */; }; 4A56952F2058186800C8065E /* aruco.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A56952D2058186800C8065E /* aruco.framework */; }; 4A5695302058186800C8065E /* opencv2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A56952E2058186800C8065E /* opencv2.framework */; }; 4A5695322058192B00C8065E /* UserStudyIDListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A5695312058192B00C8065E /* UserStudyIDListTableViewController.swift */; }; @@ -21,50 +18,13 @@ 4A59BA262244E69400EA23E7 /* CombinationPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A59BA252244E69400EA23E7 /* CombinationPlugin.swift */; }; 4A895F0A2052791100CC2C8E /* CubeByDraggingPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A895F092052791100CC2C8E /* CubeByDraggingPlugin.swift */; }; 4A895F0C2052D46000CC2C8E /* CubeByExtractionPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A895F0B2052D46000CC2C8E /* CubeByExtractionPlugin.swift */; }; - 4AA09F8C24DD508B00B59F9E /* HeatmapPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA09F8724DD508A00B59F9E /* HeatmapPlugin.swift */; }; - 4AA09F8D24DD508B00B59F9E /* ShadowPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA09F8824DD508A00B59F9E /* ShadowPlugin.swift */; }; - 4AA09F8E24DD508B00B59F9E /* BubblePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA09F8924DD508A00B59F9E /* BubblePlugin.swift */; }; - 4AA09F8F24DD508B00B59F9E /* MinVisPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA09F8A24DD508A00B59F9E /* MinVisPlugin.swift */; }; - 4AA09F9024DD508B00B59F9E /* DepthRayPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA09F8B24DD508A00B59F9E /* DepthRayPlugin.swift */; }; - 4AAC4AE823A24A20003B6E83 /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4ADA23A24A1F003B6E83 /* Util.swift */; }; - 4AAC4AE923A24A20003B6E83 /* ARPSphere.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4ADC23A24A1F003B6E83 /* ARPSphere.swift */; }; - 4AAC4AEA23A24A20003B6E83 /* ARPGeomNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4ADD23A24A1F003B6E83 /* ARPGeomNode.swift */; }; - 4AAC4AEB23A24A20003B6E83 /* ARPRevolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4ADE23A24A1F003B6E83 /* ARPRevolution.swift */; }; - 4AAC4AEC23A24A20003B6E83 /* ARPBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4ADF23A24A1F003B6E83 /* ARPBox.swift */; }; - 4AAC4AED23A24A20003B6E83 /* ARPNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4AE023A24A1F003B6E83 /* ARPNode.swift */; }; - 4AAC4AEE23A24A20003B6E83 /* ARPSweep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4AE123A24A1F003B6E83 /* ARPSweep.swift */; }; - 4AAC4AEF23A24A20003B6E83 /* ARPCylinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4AE223A24A1F003B6E83 /* ARPCylinder.swift */; }; - 4AAC4AF023A24A20003B6E83 /* ARPLoft.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4AE323A24A20003B6E83 /* ARPLoft.swift */; }; - 4AAC4AF123A24A20003B6E83 /* ARPPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4AE423A24A20003B6E83 /* ARPPath.swift */; }; - 4AAC4AF223A24A20003B6E83 /* ARPBoolNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4AE523A24A20003B6E83 /* ARPBoolNode.swift */; }; - 4AAC4AF323A24A20003B6E83 /* OCCTAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4AE723A24A20003B6E83 /* OCCTAPI.swift */; }; - 4AAC4AFA23A24A3F003B6E83 /* ButtonEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4AF523A24A3F003B6E83 /* ButtonEvents.swift */; }; - 4AAC4AFB23A24A3F003B6E83 /* TaskScenes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4AF623A24A3F003B6E83 /* TaskScenes.swift */; }; - 4AAC4AFC23A24A3F003B6E83 /* Arranger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4AF723A24A3F003B6E83 /* Arranger.swift */; }; - 4AAC4AFD23A24A3F003B6E83 /* CurveDesigner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4AF823A24A3F003B6E83 /* CurveDesigner.swift */; }; - 4AAC4AFE23A24A3F003B6E83 /* TaskTimeLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4AF923A24A3F003B6E83 /* TaskTimeLogger.swift */; }; - 4AAC4B0A23A24A6E003B6E83 /* Helpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4B0123A24A6E003B6E83 /* Helpers.mm */; }; - 4AAC4B0B23A24A6E003B6E83 /* Meshing.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4B0323A24A6E003B6E83 /* Meshing.mm */; }; - 4AAC4B0C23A24A6E003B6E83 /* OCCT.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4B0523A24A6E003B6E83 /* OCCT.mm */; }; - 4AAC4B0D23A24A6E003B6E83 /* Builders.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4B0623A24A6E003B6E83 /* Builders.mm */; }; - 4AAC4B0E23A24A6E003B6E83 /* Registry.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4B0923A24A6E003B6E83 /* Registry.mm */; }; - 4AAC4B1323A24C2A003B6E83 /* DateExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4B1223A24C29003B6E83 /* DateExtensions.swift */; }; - 4AAC4B1523A24DF6003B6E83 /* occt.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AAC4B1423A24DF5003B6E83 /* occt.framework */; }; - 4AAC4B1723A27DFE003B6E83 /* ModelingPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4B1623A27DFE003B6E83 /* ModelingPlugin.swift */; }; - 4AAC4B1923A27FC4003B6E83 /* AllButtonsAndUndo.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4AAC4B1823A27FC4003B6E83 /* AllButtonsAndUndo.xib */; }; - 4AAC4B1B23A28600003B6E83 /* SweepPluginProfileAndPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4B1A23A28600003B6E83 /* SweepPluginProfileAndPath.swift */; }; - 4AAC4B1D23A290FE003B6E83 /* SweepPluginTwoProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4B1C23A290FE003B6E83 /* SweepPluginTwoProfiles.swift */; }; - 4AAC4B1F23A29189003B6E83 /* SweepPluginTutorial.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4B1E23A29189003B6E83 /* SweepPluginTutorial.swift */; }; - 4AAC4B2123A2949E003B6E83 /* CombinePluginTutorial.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4B2023A2949D003B6E83 /* CombinePluginTutorial.swift */; }; - 4AAC4B2823A37D17003B6E83 /* CombinePluginFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4B2223A37D15003B6E83 /* CombinePluginFunction.swift */; }; - 4AAC4B2923A37D17003B6E83 /* LoftPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4B2323A37D16003B6E83 /* LoftPlugin.swift */; }; - 4AAC4B2A23A37D17003B6E83 /* RevolvePluginTwoProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4B2423A37D16003B6E83 /* RevolvePluginTwoProfiles.swift */; }; - 4AAC4B2B23A37D17003B6E83 /* CombinePluginSolidHole.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4B2523A37D16003B6E83 /* CombinePluginSolidHole.swift */; }; - 4AAC4B2C23A37D17003B6E83 /* RevolvePluginProfileAndCircle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4B2623A37D16003B6E83 /* RevolvePluginProfileAndCircle.swift */; }; - 4AAC4B2D23A37D17003B6E83 /* RevolvePluginProfileAndAxis.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AAC4B2723A37D16003B6E83 /* RevolvePluginProfileAndAxis.swift */; }; 4AC711C7204EDCBE00CD4239 /* SettingsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC711C6204EDCBE00CD4239 /* SettingsTableViewController.swift */; }; 4AC711C9204FDCE800CD4239 /* BluetoothDeviceListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC711C8204FDCE800CD4239 /* BluetoothDeviceListTableViewController.swift */; }; - 4ACE473324659B450037F9EE /* PositionFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ACE473224659B450037F9EE /* PositionFilter.swift */; }; + 5B16C84C241BF7C300111D94 /* StudyFreehandPainting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B16C84B241BF7C300111D94 /* StudyFreehandPainting.swift */; }; + 5B16C84E241BF7CC00111D94 /* ClosestPointPainting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B16C84D241BF7CC00111D94 /* ClosestPointPainting.swift */; }; + 5B16C850241BF7D500111D94 /* StudyObjectGeneration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B16C84F241BF7D500111D94 /* StudyObjectGeneration.swift */; }; + 5B16C852241BF7E800111D94 /* RaycastPainting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B16C851241BF7E700111D94 /* RaycastPainting.swift */; }; + 5BA0DAD3239BC0860000D90A /* StudyObjectPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA0DAD2239BC0850000D90A /* StudyObjectPlugin.swift */; }; AA93510A224245CE002AEB00 /* ARMenusPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA935109224245CE002AEB00 /* ARMenusPlugin.swift */; }; AA93510C2242460D002AEB00 /* ARMenusSupportFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA93510B2242460D002AEB00 /* ARMenusSupportFile.swift */; }; DC18B2BC202384D700884920 /* UserDefaultsKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC18B2BB202384D700884920 /* UserDefaultsKeys.swift */; }; @@ -87,12 +47,10 @@ DC28B471200E592C008B77AB /* DegreesToRadians.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC28B470200E592C008B77AB /* DegreesToRadians.swift */; }; DC28B473200E6025008B77AB /* SCNVector3Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC28B472200E6025008B77AB /* SCNVector3Extensions.swift */; }; DC28B475200E64B7008B77AB /* ObjectCreationPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC28B474200E64B7008B77AB /* ObjectCreationPlugin.swift */; }; - E906324522E3174B008E320F /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = E906324422E3174B008E320F /* Utilities.swift */; }; - E906324722E31770008E320F /* SnapshotAnchor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E906324622E31770008E320F /* SnapshotAnchor.swift */; }; + DC28B47A200E7E8A008B77AB /* MarkerBox.m in Sources */ = {isa = PBXBuildFile; fileRef = DC28B478200E7E89008B77AB /* MarkerBox.m */; }; E983D24622ABEDBB0000B8D5 /* SphereByDraggingPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = E983D24522ABEDBB0000B8D5 /* SphereByDraggingPlugin.swift */; }; E983D24822ABF3830000B8D5 /* CylinderByDraggingPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = E983D24722ABF3830000B8D5 /* CylinderByDraggingPlugin.swift */; }; E983D24A22ABFE680000B8D5 /* PyramidByDraggingPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = E983D24922ABFE680000B8D5 /* PyramidByDraggingPlugin.swift */; }; - E9B530632487965A0051C0C3 /* MultipeerSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B530622487965A0051C0C3 /* MultipeerSession.swift */; }; EA0C795F22F078EE00E820E8 /* ARPenStudyNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0C795922F078EE00E820E8 /* ARPenStudyNode.swift */; }; EA0C796022F078EE00E820E8 /* ARPenSceneConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0C795A22F078EE00E820E8 /* ARPenSceneConstructor.swift */; }; EA0C796122F078EE00E820E8 /* ARPenGridSceneConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0C795B22F078EE00E820E8 /* ARPenGridSceneConstructor.swift */; }; @@ -115,9 +73,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 4A27E27C239FCEA800C4BF75 /* NotificationName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationName.swift; sourceTree = ""; }; - 4A27E27E239FF36D00C4BF75 /* PassthroughView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassthroughView.swift; sourceTree = ""; }; - 4A27E28023A0F9E300C4BF75 /* SecondButton.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SecondButton.xib; sourceTree = ""; }; 4A56952D2058186800C8065E /* aruco.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = aruco.framework; sourceTree = ""; }; 4A56952E2058186800C8065E /* opencv2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = opencv2.framework; sourceTree = ""; }; 4A5695312058192B00C8065E /* UserStudyIDListTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserStudyIDListTableViewController.swift; sourceTree = ""; }; @@ -129,56 +84,14 @@ 4A59BA252244E69400EA23E7 /* CombinationPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombinationPlugin.swift; sourceTree = ""; }; 4A895F092052791100CC2C8E /* CubeByDraggingPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CubeByDraggingPlugin.swift; sourceTree = ""; }; 4A895F0B2052D46000CC2C8E /* CubeByExtractionPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CubeByExtractionPlugin.swift; sourceTree = ""; }; - 4AA09F8724DD508A00B59F9E /* HeatmapPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeatmapPlugin.swift; sourceTree = ""; }; - 4AA09F8824DD508A00B59F9E /* ShadowPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowPlugin.swift; sourceTree = ""; }; - 4AA09F8924DD508A00B59F9E /* BubblePlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubblePlugin.swift; sourceTree = ""; }; - 4AA09F8A24DD508A00B59F9E /* MinVisPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MinVisPlugin.swift; sourceTree = ""; }; - 4AA09F8B24DD508A00B59F9E /* DepthRayPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DepthRayPlugin.swift; sourceTree = ""; }; - 4AAC4ADA23A24A1F003B6E83 /* Util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Util.swift; sourceTree = ""; }; - 4AAC4ADC23A24A1F003B6E83 /* ARPSphere.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARPSphere.swift; sourceTree = ""; }; - 4AAC4ADD23A24A1F003B6E83 /* ARPGeomNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARPGeomNode.swift; sourceTree = ""; }; - 4AAC4ADE23A24A1F003B6E83 /* ARPRevolution.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARPRevolution.swift; sourceTree = ""; }; - 4AAC4ADF23A24A1F003B6E83 /* ARPBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARPBox.swift; sourceTree = ""; }; - 4AAC4AE023A24A1F003B6E83 /* ARPNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARPNode.swift; sourceTree = ""; }; - 4AAC4AE123A24A1F003B6E83 /* ARPSweep.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARPSweep.swift; sourceTree = ""; }; - 4AAC4AE223A24A1F003B6E83 /* ARPCylinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARPCylinder.swift; sourceTree = ""; }; - 4AAC4AE323A24A20003B6E83 /* ARPLoft.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARPLoft.swift; sourceTree = ""; }; - 4AAC4AE423A24A20003B6E83 /* ARPPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARPPath.swift; sourceTree = ""; }; - 4AAC4AE523A24A20003B6E83 /* ARPBoolNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARPBoolNode.swift; sourceTree = ""; }; - 4AAC4AE723A24A20003B6E83 /* OCCTAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OCCTAPI.swift; sourceTree = ""; }; - 4AAC4AF523A24A3F003B6E83 /* ButtonEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonEvents.swift; sourceTree = ""; }; - 4AAC4AF623A24A3F003B6E83 /* TaskScenes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskScenes.swift; sourceTree = ""; }; - 4AAC4AF723A24A3F003B6E83 /* Arranger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Arranger.swift; sourceTree = ""; }; - 4AAC4AF823A24A3F003B6E83 /* CurveDesigner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurveDesigner.swift; sourceTree = ""; }; - 4AAC4AF923A24A3F003B6E83 /* TaskTimeLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskTimeLogger.swift; sourceTree = ""; }; - 4AAC4B0023A24A6E003B6E83 /* OCCT.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCCT.h; sourceTree = ""; }; - 4AAC4B0123A24A6E003B6E83 /* Helpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Helpers.mm; sourceTree = ""; }; - 4AAC4B0223A24A6E003B6E83 /* Meshing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Meshing.h; sourceTree = ""; }; - 4AAC4B0323A24A6E003B6E83 /* Meshing.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Meshing.mm; sourceTree = ""; }; - 4AAC4B0423A24A6E003B6E83 /* Registry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Registry.h; sourceTree = ""; }; - 4AAC4B0523A24A6E003B6E83 /* OCCT.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = OCCT.mm; sourceTree = ""; }; - 4AAC4B0623A24A6E003B6E83 /* Builders.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Builders.mm; sourceTree = ""; }; - 4AAC4B0723A24A6E003B6E83 /* Builders.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Builders.h; sourceTree = ""; }; - 4AAC4B0823A24A6E003B6E83 /* Helpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Helpers.h; sourceTree = ""; }; - 4AAC4B0923A24A6E003B6E83 /* Registry.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Registry.mm; sourceTree = ""; }; - 4AAC4B1223A24C29003B6E83 /* DateExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateExtensions.swift; sourceTree = ""; }; - 4AAC4B1423A24DF5003B6E83 /* occt.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = occt.framework; path = "../../191016 ARPen + OpenCascade/iOS App/occt.framework"; sourceTree = ""; }; - 4AAC4B1623A27DFE003B6E83 /* ModelingPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelingPlugin.swift; sourceTree = ""; }; - 4AAC4B1823A27FC4003B6E83 /* AllButtonsAndUndo.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AllButtonsAndUndo.xib; sourceTree = ""; }; - 4AAC4B1A23A28600003B6E83 /* SweepPluginProfileAndPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SweepPluginProfileAndPath.swift; sourceTree = ""; }; - 4AAC4B1C23A290FE003B6E83 /* SweepPluginTwoProfiles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SweepPluginTwoProfiles.swift; sourceTree = ""; }; - 4AAC4B1E23A29189003B6E83 /* SweepPluginTutorial.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SweepPluginTutorial.swift; sourceTree = ""; }; - 4AAC4B2023A2949D003B6E83 /* CombinePluginTutorial.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombinePluginTutorial.swift; sourceTree = ""; }; - 4AAC4B2223A37D15003B6E83 /* CombinePluginFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombinePluginFunction.swift; sourceTree = ""; }; - 4AAC4B2323A37D16003B6E83 /* LoftPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoftPlugin.swift; sourceTree = ""; }; - 4AAC4B2423A37D16003B6E83 /* RevolvePluginTwoProfiles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RevolvePluginTwoProfiles.swift; sourceTree = ""; }; - 4AAC4B2523A37D16003B6E83 /* CombinePluginSolidHole.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombinePluginSolidHole.swift; sourceTree = ""; }; - 4AAC4B2623A37D16003B6E83 /* RevolvePluginProfileAndCircle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RevolvePluginProfileAndCircle.swift; sourceTree = ""; }; - 4AAC4B2723A37D16003B6E83 /* RevolvePluginProfileAndAxis.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RevolvePluginProfileAndAxis.swift; sourceTree = ""; }; 4AC0B97D226F2B3F001B591D /* ARPen.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ARPen.entitlements; sourceTree = ""; }; 4AC711C6204EDCBE00CD4239 /* SettingsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTableViewController.swift; sourceTree = ""; }; 4AC711C8204FDCE800CD4239 /* BluetoothDeviceListTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothDeviceListTableViewController.swift; sourceTree = ""; }; - 4ACE473224659B450037F9EE /* PositionFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionFilter.swift; sourceTree = ""; }; + 5B16C84B241BF7C300111D94 /* StudyFreehandPainting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StudyFreehandPainting.swift; sourceTree = ""; }; + 5B16C84D241BF7CC00111D94 /* ClosestPointPainting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosestPointPainting.swift; sourceTree = ""; }; + 5B16C84F241BF7D500111D94 /* StudyObjectGeneration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StudyObjectGeneration.swift; sourceTree = ""; }; + 5B16C851241BF7E700111D94 /* RaycastPainting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RaycastPainting.swift; sourceTree = ""; }; + 5BA0DAD2239BC0850000D90A /* StudyObjectPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyObjectPlugin.swift; sourceTree = ""; }; AA935109224245CE002AEB00 /* ARMenusPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARMenusPlugin.swift; sourceTree = ""; }; AA93510B2242460D002AEB00 /* ARMenusSupportFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARMenusSupportFile.swift; sourceTree = ""; }; DC18B2BB202384D700884920 /* UserDefaultsKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsKeys.swift; sourceTree = ""; }; @@ -205,13 +118,12 @@ DC28B470200E592C008B77AB /* DegreesToRadians.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DegreesToRadians.swift; sourceTree = ""; }; DC28B472200E6025008B77AB /* SCNVector3Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SCNVector3Extensions.swift; sourceTree = ""; }; DC28B474200E64B7008B77AB /* ObjectCreationPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectCreationPlugin.swift; sourceTree = ""; }; + DC28B478200E7E89008B77AB /* MarkerBox.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MarkerBox.m; sourceTree = ""; }; + DC28B479200E7E89008B77AB /* MarkerBox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MarkerBox.h; sourceTree = ""; }; DCE5A37E202F504B005F4186 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; - E906324422E3174B008E320F /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; - E906324622E31770008E320F /* SnapshotAnchor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnapshotAnchor.swift; sourceTree = ""; }; E983D24522ABEDBB0000B8D5 /* SphereByDraggingPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SphereByDraggingPlugin.swift; sourceTree = ""; }; E983D24722ABF3830000B8D5 /* CylinderByDraggingPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CylinderByDraggingPlugin.swift; sourceTree = ""; }; E983D24922ABFE680000B8D5 /* PyramidByDraggingPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PyramidByDraggingPlugin.swift; sourceTree = ""; }; - E9B530622487965A0051C0C3 /* MultipeerSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipeerSession.swift; sourceTree = ""; }; EA0C795922F078EE00E820E8 /* ARPenStudyNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARPenStudyNode.swift; sourceTree = ""; }; EA0C795A22F078EE00E820E8 /* ARPenSceneConstructor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARPenSceneConstructor.swift; sourceTree = ""; }; EA0C795B22F078EE00E820E8 /* ARPenGridSceneConstructor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARPenGridSceneConstructor.swift; sourceTree = ""; }; @@ -225,7 +137,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 4AAC4B1523A24DF6003B6E83 /* occt.framework in Frameworks */, 4A56952F2058186800C8065E /* aruco.framework in Frameworks */, 4A5695302058186800C8065E /* opencv2.framework in Frameworks */, ); @@ -234,15 +145,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 4A27E28223A1032400C4BF75 /* PluginUIs */ = { - isa = PBXGroup; - children = ( - 4A27E28023A0F9E300C4BF75 /* SecondButton.xib */, - 4AAC4B1823A27FC4003B6E83 /* AllButtonsAndUndo.xib */, - ); - path = PluginUIs; - sourceTree = ""; - }; 4A4B7F1B205817A200331810 /* SettingsViewControllers */ = { isa = PBXGroup; children = ( @@ -264,116 +166,9 @@ path = UserStudyData; sourceTree = ""; }; - 4AA09F8624DD504600B59F9E /* DepthVisualizationPlugins */ = { - isa = PBXGroup; - children = ( - 4AA09F8A24DD508A00B59F9E /* MinVisPlugin.swift */, - 4AA09F8B24DD508A00B59F9E /* DepthRayPlugin.swift */, - 4AA09F8924DD508A00B59F9E /* BubblePlugin.swift */, - 4AA09F8824DD508A00B59F9E /* ShadowPlugin.swift */, - 4AA09F8724DD508A00B59F9E /* HeatmapPlugin.swift */, - ); - path = DepthVisualizationPlugins; - sourceTree = ""; - }; - 4AAC4AD923A24A1F003B6E83 /* Modeling */ = { - isa = PBXGroup; - children = ( - 4AAC4ADA23A24A1F003B6E83 /* Util.swift */, - 4AAC4ADB23A24A1F003B6E83 /* GeometryNodes */, - 4AAC4AE623A24A20003B6E83 /* OCCTAPI */, - ); - path = Modeling; - sourceTree = ""; - }; - 4AAC4ADB23A24A1F003B6E83 /* GeometryNodes */ = { - isa = PBXGroup; - children = ( - 4AAC4ADC23A24A1F003B6E83 /* ARPSphere.swift */, - 4AAC4ADD23A24A1F003B6E83 /* ARPGeomNode.swift */, - 4AAC4ADE23A24A1F003B6E83 /* ARPRevolution.swift */, - 4AAC4ADF23A24A1F003B6E83 /* ARPBox.swift */, - 4AAC4AE023A24A1F003B6E83 /* ARPNode.swift */, - 4AAC4AE123A24A1F003B6E83 /* ARPSweep.swift */, - 4AAC4AE223A24A1F003B6E83 /* ARPCylinder.swift */, - 4AAC4AE323A24A20003B6E83 /* ARPLoft.swift */, - 4AAC4AE423A24A20003B6E83 /* ARPPath.swift */, - 4AAC4AE523A24A20003B6E83 /* ARPBoolNode.swift */, - ); - path = GeometryNodes; - sourceTree = ""; - }; - 4AAC4AE623A24A20003B6E83 /* OCCTAPI */ = { - isa = PBXGroup; - children = ( - 4AAC4AE723A24A20003B6E83 /* OCCTAPI.swift */, - ); - path = OCCTAPI; - sourceTree = ""; - }; - 4AAC4AF423A24A3F003B6E83 /* Util */ = { - isa = PBXGroup; - children = ( - 4AAC4AF523A24A3F003B6E83 /* ButtonEvents.swift */, - 4AAC4AF623A24A3F003B6E83 /* TaskScenes.swift */, - 4AAC4AF723A24A3F003B6E83 /* Arranger.swift */, - 4AAC4AF823A24A3F003B6E83 /* CurveDesigner.swift */, - 4AAC4AF923A24A3F003B6E83 /* TaskTimeLogger.swift */, - ); - path = Util; - sourceTree = ""; - }; - 4AAC4AFF23A24A6E003B6E83 /* Open CASCADE */ = { - isa = PBXGroup; - children = ( - 4AAC4B0023A24A6E003B6E83 /* OCCT.h */, - 4AAC4B0123A24A6E003B6E83 /* Helpers.mm */, - 4AAC4B0223A24A6E003B6E83 /* Meshing.h */, - 4AAC4B0323A24A6E003B6E83 /* Meshing.mm */, - 4AAC4B0423A24A6E003B6E83 /* Registry.h */, - 4AAC4B0523A24A6E003B6E83 /* OCCT.mm */, - 4AAC4B0623A24A6E003B6E83 /* Builders.mm */, - 4AAC4B0723A24A6E003B6E83 /* Builders.h */, - 4AAC4B0823A24A6E003B6E83 /* Helpers.h */, - 4AAC4B0923A24A6E003B6E83 /* Registry.mm */, - ); - path = "Open CASCADE"; - sourceTree = ""; - }; - 4AAC4B2E23A37D3D003B6E83 /* SweepPlugins */ = { - isa = PBXGroup; - children = ( - 4AAC4B1E23A29189003B6E83 /* SweepPluginTutorial.swift */, - 4AAC4B1A23A28600003B6E83 /* SweepPluginProfileAndPath.swift */, - 4AAC4B1C23A290FE003B6E83 /* SweepPluginTwoProfiles.swift */, - ); - path = SweepPlugins; - sourceTree = ""; - }; - 4AAC4B2F23A37D5E003B6E83 /* RevolvePlugins */ = { - isa = PBXGroup; - children = ( - 4AAC4B2723A37D16003B6E83 /* RevolvePluginProfileAndAxis.swift */, - 4AAC4B2623A37D16003B6E83 /* RevolvePluginProfileAndCircle.swift */, - 4AAC4B2423A37D16003B6E83 /* RevolvePluginTwoProfiles.swift */, - ); - path = RevolvePlugins; - sourceTree = ""; - }; - 4AAC4B3023A37D6F003B6E83 /* BooleanOperationPlugins */ = { - isa = PBXGroup; - children = ( - 4AAC4B2023A2949D003B6E83 /* CombinePluginTutorial.swift */, - 4AAC4B2223A37D15003B6E83 /* CombinePluginFunction.swift */, - 4AAC4B2523A37D16003B6E83 /* CombinePluginSolidHole.swift */, - ); - path = BooleanOperationPlugins; - sourceTree = ""; - }; DC18B2B820237A7200884920 /* Frameworks */ = { isa = PBXGroup; children = ( - 4AAC4B1423A24DF5003B6E83 /* occt.framework */, 4A56952D2058186800C8065E /* aruco.framework */, 4A56952E2058186800C8065E /* opencv2.framework */, ); @@ -404,8 +199,6 @@ 4AC0B97D226F2B3F001B591D /* ARPen.entitlements */, DC28B43F200E4353008B77AB /* AppDelegate.swift */, DC28B443200E4353008B77AB /* ViewController.swift */, - E906324622E31770008E320F /* SnapshotAnchor.swift */, - E9B530622487965A0051C0C3 /* MultipeerSession.swift */, 4A4B7F1B205817A200331810 /* SettingsViewControllers */, DC18B2BB202384D700884920 /* UserDefaultsKeys.swift */, DC28B464200E4825008B77AB /* PenScene.swift */, @@ -415,12 +208,10 @@ DC28B46C200E4E6E008B77AB /* MarkerBox.swift */, DC28B462200E471F008B77AB /* PluginManager.swift */, DC28B476200E64C0008B77AB /* Plugins */, - 4AAC4AD923A24A1F003B6E83 /* Modeling */, 4A56953520581AF300C8065E /* UserStudyData */, EA0C795822F0787300E820E8 /* UserStudySetup */, - DC28B477200E6640008B77AB /* Extensions And Helpers */, + DC28B477200E6640008B77AB /* Extensions */, DC28B45D200E4476008B77AB /* OpenCV */, - 4AAC4AFF23A24A6E003B6E83 /* Open CASCADE */, DC28B445200E4353008B77AB /* Main.storyboard */, DC28B448200E4353008B77AB /* Assets.xcassets */, DC28B441200E4353008B77AB /* art.scnassets */, @@ -434,6 +225,8 @@ DC28B45D200E4476008B77AB /* OpenCV */ = { isa = PBXGroup; children = ( + DC28B479200E7E89008B77AB /* MarkerBox.h */, + DC28B478200E7E89008B77AB /* MarkerBox.m */, DC28B45A200E4470008B77AB /* OpenCVWrapper.h */, DC28B45B200E4470008B77AB /* OpenCVWrapper.mm */, ); @@ -453,33 +246,25 @@ 4A59BA252244E69400EA23E7 /* CombinationPlugin.swift */, E983D24522ABEDBB0000B8D5 /* SphereByDraggingPlugin.swift */, E983D24722ABF3830000B8D5 /* CylinderByDraggingPlugin.swift */, + 5BA0DAD2239BC0850000D90A /* StudyObjectPlugin.swift */, + 5B16C84F241BF7D500111D94 /* StudyObjectGeneration.swift */, + 5B16C84D241BF7CC00111D94 /* ClosestPointPainting.swift */, + 5B16C851241BF7E700111D94 /* RaycastPainting.swift */, + 5B16C84B241BF7C300111D94 /* StudyFreehandPainting.swift */, E983D24922ABFE680000B8D5 /* PyramidByDraggingPlugin.swift */, - 4AAC4B1623A27DFE003B6E83 /* ModelingPlugin.swift */, - 4AAC4B2E23A37D3D003B6E83 /* SweepPlugins */, - 4AAC4B2323A37D16003B6E83 /* LoftPlugin.swift */, - 4AAC4B2F23A37D5E003B6E83 /* RevolvePlugins */, - 4AAC4B3023A37D6F003B6E83 /* BooleanOperationPlugins */, - 4AA09F8624DD504600B59F9E /* DepthVisualizationPlugins */, - 4A27E28223A1032400C4BF75 /* PluginUIs */, - 4AAC4AF423A24A3F003B6E83 /* Util */, ); path = Plugins; sourceTree = ""; }; - DC28B477200E6640008B77AB /* Extensions And Helpers */ = { + DC28B477200E6640008B77AB /* Extensions */ = { isa = PBXGroup; children = ( DC28B468200E4C43008B77AB /* CylinderLine.swift */, DC28B472200E6025008B77AB /* SCNVector3Extensions.swift */, DC28B470200E592C008B77AB /* DegreesToRadians.swift */, AA93510B2242460D002AEB00 /* ARMenusSupportFile.swift */, - E906324422E3174B008E320F /* Utilities.swift */, - 4A27E27C239FCEA800C4BF75 /* NotificationName.swift */, - 4A27E27E239FF36D00C4BF75 /* PassthroughView.swift */, - 4AAC4B1223A24C29003B6E83 /* DateExtensions.swift */, - 4ACE473224659B450037F9EE /* PositionFilter.swift */, ); - path = "Extensions And Helpers"; + path = Extensions; sourceTree = ""; }; EA0C795822F0787300E820E8 /* UserStudySetup */ = { @@ -502,7 +287,6 @@ isa = PBXNativeTarget; buildConfigurationList = DC28B450200E4353008B77AB /* Build configuration list for PBXNativeTarget "ARPen" */; buildPhases = ( - 4AAC4B1123A24B1C003B6E83 /* ShellScript */, DC28B438200E4353008B77AB /* Sources */, DC28B439200E4353008B77AB /* Frameworks */, DC28B43A200E4353008B77AB /* Resources */, @@ -524,12 +308,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1130; + LastUpgradeCheck = 0920; ORGANIZATIONNAME = "RWTH Aachen"; TargetAttributes = { DC28B43B200E4353008B77AB = { CreatedOnToolsVersion = 9.2; - LastSwiftMigration = 1130; + LastSwiftMigration = 0920; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.iCloud = { @@ -563,108 +347,54 @@ buildActionMask = 2147483647; files = ( DC28B442200E4353008B77AB /* art.scnassets in Resources */, - 4A27E28123A0F9E300C4BF75 /* SecondButton.xib in Resources */, DC28B44C200E4353008B77AB /* LaunchScreen.storyboard in Resources */, DC28B449200E4353008B77AB /* Assets.xcassets in Resources */, - 4AAC4B1923A27FC4003B6E83 /* AllButtonsAndUndo.xib in Resources */, DC28B447200E4353008B77AB /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - 4AAC4B1123A24B1C003B6E83 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nif [ ! -d \"${PROJECT_DIR}/occt.framework\" ]; then\n # Re-assemble the split zip archive\n for i in `ls \"${PROJECT_DIR}/Open CASCADE\" | sort -V`; do cat \"${PROJECT_DIR}/Open CASCADE/$i\" >> \"${PROJECT_DIR}/occt.framework.zip\"; done;\n # Unzip\n unzip \"${PROJECT_DIR}/occt.framework.zip\"\n # Remove assembled version\n rm \"${PROJECT_DIR}/occt.framework.zip\"\nfi\n"; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ DC28B438200E4353008B77AB /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4AAC4B0C23A24A6E003B6E83 /* OCCT.mm in Sources */, DC28B461200E46DC008B77AB /* Plugin.swift in Sources */, - 4ACE473324659B450037F9EE /* PositionFilter.swift in Sources */, E983D24622ABEDBB0000B8D5 /* SphereByDraggingPlugin.swift in Sources */, EA0C796122F078EE00E820E8 /* ARPenGridSceneConstructor.swift in Sources */, 4A56953B20581B9000C8065E /* UserStudyRecordPluginProtocol.swift in Sources */, - 4AAC4B0D23A24A6E003B6E83 /* Builders.mm in Sources */, + 5B16C850241BF7D500111D94 /* StudyObjectGeneration.swift in Sources */, 4A895F0A2052791100CC2C8E /* CubeByDraggingPlugin.swift in Sources */, - 4AAC4AFB23A24A3F003B6E83 /* TaskScenes.swift in Sources */, + DC28B47A200E7E8A008B77AB /* MarkerBox.m in Sources */, 4A5695342058195200C8065E /* UserStudyRecordsListTableViewController.swift in Sources */, - 4AA09F8C24DD508B00B59F9E /* HeatmapPlugin.swift in Sources */, DC28B45F200E4561008B77AB /* PenManager.swift in Sources */, EA0C796222F078EE00E820E8 /* ARPenWireBoxNode.swift in Sources */, - 4AAC4AE823A24A20003B6E83 /* Util.swift in Sources */, 4A56953920581B6700C8065E /* UserStudyRecordManager.swift in Sources */, - 4AAC4AF123A24A20003B6E83 /* ARPPath.swift in Sources */, - 4AAC4B2B23A37D17003B6E83 /* CombinePluginSolidHole.swift in Sources */, - 4AAC4B1323A24C2A003B6E83 /* DateExtensions.swift in Sources */, DC28B465200E4825008B77AB /* PenScene.swift in Sources */, 4AC711C9204FDCE800CD4239 /* BluetoothDeviceListTableViewController.swift in Sources */, - 4AAC4AEC23A24A20003B6E83 /* ARPBox.swift in Sources */, - 4A27E27F239FF36D00C4BF75 /* PassthroughView.swift in Sources */, - 4AAC4B2823A37D17003B6E83 /* CombinePluginFunction.swift in Sources */, DC18B2BC202384D700884920 /* UserDefaultsKeys.swift in Sources */, E983D24A22ABFE680000B8D5 /* PyramidByDraggingPlugin.swift in Sources */, DC28B45C200E4470008B77AB /* OpenCVWrapper.mm in Sources */, DC28B444200E4353008B77AB /* ViewController.swift in Sources */, EA0C796422F078EE00E820E8 /* ARPenBoxNode.swift in Sources */, - 4AAC4B1B23A28600003B6E83 /* SweepPluginProfileAndPath.swift in Sources */, - 4AAC4B2C23A37D17003B6E83 /* RevolvePluginProfileAndCircle.swift in Sources */, - 4AA09F8F24DD508B00B59F9E /* MinVisPlugin.swift in Sources */, - 4AAC4B0A23A24A6E003B6E83 /* Helpers.mm in Sources */, - 4AAC4AEE23A24A20003B6E83 /* ARPSweep.swift in Sources */, AA93510A224245CE002AEB00 /* ARMenusPlugin.swift in Sources */, 4A59BA1E2244E66D00EA23E7 /* TranslationDemoPlugin.swift in Sources */, - 4AA09F8D24DD508B00B59F9E /* ShadowPlugin.swift in Sources */, - E906324522E3174B008E320F /* Utilities.swift in Sources */, - E906324722E31770008E320F /* SnapshotAnchor.swift in Sources */, 4A59BA262244E69400EA23E7 /* CombinationPlugin.swift in Sources */, DC28B46D200E4E6E008B77AB /* MarkerBox.swift in Sources */, DC28B469200E4C43008B77AB /* CylinderLine.swift in Sources */, - E9B530632487965A0051C0C3 /* MultipeerSession.swift in Sources */, - 4AAC4B0E23A24A6E003B6E83 /* Registry.mm in Sources */, - 4AAC4B1723A27DFE003B6E83 /* ModelingPlugin.swift in Sources */, - 4AAC4AEF23A24A20003B6E83 /* ARPCylinder.swift in Sources */, - 4AAC4B1F23A29189003B6E83 /* SweepPluginTutorial.swift in Sources */, - 4AAC4B2A23A37D17003B6E83 /* RevolvePluginTwoProfiles.swift in Sources */, - 4AAC4AF323A24A20003B6E83 /* OCCTAPI.swift in Sources */, AA93510C2242460D002AEB00 /* ARMenusSupportFile.swift in Sources */, - 4AA09F9024DD508B00B59F9E /* DepthRayPlugin.swift in Sources */, - 4AAC4B2123A2949E003B6E83 /* CombinePluginTutorial.swift in Sources */, - 4AAC4AEB23A24A20003B6E83 /* ARPRevolution.swift in Sources */, - 4A27E27D239FCEA800C4BF75 /* NotificationName.swift in Sources */, EA0C796322F078EE00E820E8 /* ARPenDropTargetNode.swift in Sources */, - 4AAC4AE923A24A20003B6E83 /* ARPSphere.swift in Sources */, - 4AAC4AFE23A24A3F003B6E83 /* TaskTimeLogger.swift in Sources */, - 4AAC4B2923A37D17003B6E83 /* LoftPlugin.swift in Sources */, - 4AAC4AFA23A24A3F003B6E83 /* ButtonEvents.swift in Sources */, 4A895F0C2052D46000CC2C8E /* CubeByExtractionPlugin.swift in Sources */, DC28B454200E4389008B77AB /* ARManager.swift in Sources */, 4A5695322058192B00C8065E /* UserStudyIDListTableViewController.swift in Sources */, + 5B16C84C241BF7C300111D94 /* StudyFreehandPainting.swift in Sources */, + 5B16C84E241BF7CC00111D94 /* ClosestPointPainting.swift in Sources */, 4A56953720581B4500C8065E /* UserStudyRecord.swift in Sources */, - 4AAC4AF023A24A20003B6E83 /* ARPLoft.swift in Sources */, + 5BA0DAD3239BC0860000D90A /* StudyObjectPlugin.swift in Sources */, DC28B475200E64B7008B77AB /* ObjectCreationPlugin.swift in Sources */, - 4AAC4B0B23A24A6E003B6E83 /* Meshing.mm in Sources */, + 5B16C852241BF7E800111D94 /* RaycastPainting.swift in Sources */, DC28B473200E6025008B77AB /* SCNVector3Extensions.swift in Sources */, - 4AAC4AEA23A24A20003B6E83 /* ARPGeomNode.swift in Sources */, DC28B440200E4353008B77AB /* AppDelegate.swift in Sources */, DC28B463200E471F008B77AB /* PluginManager.swift in Sources */, DC28B467200E4BF1008B77AB /* PaintPlugin.swift in Sources */, @@ -672,15 +402,8 @@ EA0C795F22F078EE00E820E8 /* ARPenStudyNode.swift in Sources */, 4AC711C7204EDCBE00CD4239 /* SettingsTableViewController.swift in Sources */, DC28B46B200E4C78008B77AB /* Button.swift in Sources */, - 4AAC4AED23A24A20003B6E83 /* ARPNode.swift in Sources */, - 4AAC4B2D23A37D17003B6E83 /* RevolvePluginProfileAndAxis.swift in Sources */, EA0C796022F078EE00E820E8 /* ARPenSceneConstructor.swift in Sources */, - 4AAC4AFD23A24A3F003B6E83 /* CurveDesigner.swift in Sources */, - 4AA09F8E24DD508B00B59F9E /* BubblePlugin.swift in Sources */, - 4AAC4AFC23A24A3F003B6E83 /* Arranger.swift in Sources */, DC28B471200E592C008B77AB /* DegreesToRadians.swift in Sources */, - 4AAC4B1D23A290FE003B6E83 /* SweepPluginTwoProfiles.swift in Sources */, - 4AAC4AF223A24A20003B6E83 /* ARPBoolNode.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -720,7 +443,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -728,7 +450,6 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -756,7 +477,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.2; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -779,7 +500,6 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -787,7 +507,6 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -809,7 +528,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.2; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; @@ -823,22 +542,19 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 286L2EXR23; - ENABLE_BITCODE = NO; + DEVELOPMENT_TEAM = 6U59XMBW6T; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/ARPen", "$(PROJECT_DIR)", ); INFOPLIST_FILE = ARPen/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 1.2; - PRODUCT_BUNDLE_IDENTIFIER = "de.rwth-aachen.hci.arpen"; + PRODUCT_BUNDLE_IDENTIFIER = "de.rwth-aachen.hci.ARPenApp1"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "ARPen/ARPen-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 1; }; name = Debug; @@ -849,21 +565,18 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 286L2EXR23; - ENABLE_BITCODE = NO; + DEVELOPMENT_TEAM = 6U59XMBW6T; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/ARPen", "$(PROJECT_DIR)", ); INFOPLIST_FILE = ARPen/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.4; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 1.2; - PRODUCT_BUNDLE_IDENTIFIER = "de.rwth-aachen.hci.arpen"; + PRODUCT_BUNDLE_IDENTIFIER = "de.rwth-aachen.hci.ARPenApp1"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "ARPen/ARPen-Bridging-Header.h"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 1; }; name = Release; diff --git a/iOS App/ARPen.xcodeproj/xcshareddata/xcschemes/ARPen.xcscheme b/iOS App/ARPen.xcodeproj/xcshareddata/xcschemes/ARPen.xcscheme new file mode 100644 index 0000000..9a02b1b --- /dev/null +++ b/iOS App/ARPen.xcodeproj/xcshareddata/xcschemes/ARPen.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS App/ARPen/.DS_Store b/iOS App/ARPen/.DS_Store new file mode 100644 index 0000000..fd68b5c Binary files /dev/null and b/iOS App/ARPen/.DS_Store differ diff --git a/iOS App/ARPen/ARManager.swift b/iOS App/ARPen/ARManager.swift index 037a215..45104b9 100644 --- a/iOS App/ARPen/ARManager.swift +++ b/iOS App/ARPen/ARManager.swift @@ -29,22 +29,15 @@ class ARManager: NSObject, ARSessionDelegate, ARSessionObserver, OpenCVWrapperDe self.opencvWrapper = OpenCVWrapper() super.init() self.opencvWrapper.delegate = self + } func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) { self.delegate?.didChangeTrackingState(cam: camera) - - // create a dictionary literal to pass currentFrame and trackingState - let informationPackage: [String : Any] = ["currentFrame": session.currentFrame!, "trackingState": camera.trackingState] - NotificationCenter.default.post(name: .cameraDidChangeTrackingState, object: nil, userInfo: informationPackage) } func session(_ session: ARSession, didUpdate frame: ARFrame) { self.opencvWrapper.findMarker(frame.capturedImage, withCameraIntrinsics: frame.camera.intrinsics, cameraSize: frame.camera.imageResolution) - - // create a dictionary literal to pass frame and trackingState - let informationPackage: [String : Any] = ["frame": frame, "trackingState": frame.camera.trackingState] - NotificationCenter.default.post(name: .sessionDidUpdate, object: nil, userInfo: informationPackage) } func session(_ session: ARSession, didFailWithError error: Error) { @@ -57,6 +50,7 @@ class ARManager: NSObject, ARSessionDelegate, ARSessionObserver, OpenCVWrapperDe func sessionInterruptionEnded(_ session: ARSession) { // Reset tracking and/or remove existing anchors if consistent tracking is required + } // MARK: - OpenCVWrapperDelegate @@ -82,8 +76,9 @@ class ARManager: NSObject, ARSessionDelegate, ARSessionObserver, OpenCVWrapperDe } scene.markerFound = true //self.scene.pencilPoint.position = self.scene.markerBox.position(withIds: UnsafeMutablePointer(mutating: ids), count: Int32(ids.count)) - let markerBoxNode = scene.markerBox.positionWith(ids: ids) - scene.pencilPoint.transform = markerBoxNode.transform + scene.pencilPoint.position = scene.markerBox.posititonWith(ids: ids) + //scene.directionNode.position = scene.markerBox.posititonMarkerOrigin(ids: ids) + scene.directionNode.position = scene.markerBox.posititonDirectionWith(ids: ids) self.delegate?.finishedCalculation() diff --git a/iOS App/ARPen/ARPen-Bridging-Header.h b/iOS App/ARPen/ARPen-Bridging-Header.h index d9b2086..a67a268 100644 --- a/iOS App/ARPen/ARPen-Bridging-Header.h +++ b/iOS App/ARPen/ARPen-Bridging-Header.h @@ -3,5 +3,4 @@ // #import "OpenCVWrapper.h" -#import "OCCT.h" //#import "MarkerBox.h" diff --git a/iOS App/ARPen/AppDelegate.swift b/iOS App/ARPen/AppDelegate.swift index 80ed5d5..edb617f 100644 --- a/iOS App/ARPen/AppDelegate.swift +++ b/iOS App/ARPen/AppDelegate.swift @@ -17,7 +17,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var userStudyRecordManager: UserStudyRecordManager? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. application.isIdleTimerDisabled = true diff --git a/iOS App/ARPen/Assets.xcassets/.DS_Store b/iOS App/ARPen/Assets.xcassets/.DS_Store new file mode 100644 index 0000000..1355758 Binary files /dev/null and b/iOS App/ARPen/Assets.xcassets/.DS_Store differ diff --git a/iOS App/ARPen/Assets.xcassets/AR Resources.arresourcegroup/Contents.json b/iOS App/ARPen/Assets.xcassets/AR Resources.arresourcegroup/Contents.json new file mode 100644 index 0000000..8beb732 --- /dev/null +++ b/iOS App/ARPen/Assets.xcassets/AR Resources.arresourcegroup/Contents.json @@ -0,0 +1,14 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "resources" : [ + { + "filename" : "macbookpro-15.arreferenceimage" + }, + { + "filename" : "stones.arreferenceimage" + } + ] +} \ No newline at end of file diff --git a/iOS App/ARPen/Assets.xcassets/AR Resources.arresourcegroup/macbookpro-15.arreferenceimage/Contents.json b/iOS App/ARPen/Assets.xcassets/AR Resources.arresourcegroup/macbookpro-15.arreferenceimage/Contents.json new file mode 100644 index 0000000..8345f52 --- /dev/null +++ b/iOS App/ARPen/Assets.xcassets/AR Resources.arresourcegroup/macbookpro-15.arreferenceimage/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "macbookpro-15.jpg" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "width" : 13.090999999999999, + "unit" : "inches" + } +} \ No newline at end of file diff --git a/iOS App/ARPen/Assets.xcassets/AR Resources.arresourcegroup/macbookpro-15.arreferenceimage/macbookpro-15.jpg b/iOS App/ARPen/Assets.xcassets/AR Resources.arresourcegroup/macbookpro-15.arreferenceimage/macbookpro-15.jpg new file mode 100644 index 0000000..434bc93 Binary files /dev/null and b/iOS App/ARPen/Assets.xcassets/AR Resources.arresourcegroup/macbookpro-15.arreferenceimage/macbookpro-15.jpg differ diff --git a/iOS App/ARPen/Assets.xcassets/AR Resources.arresourcegroup/stones.arreferenceimage/Contents.json b/iOS App/ARPen/Assets.xcassets/AR Resources.arresourcegroup/stones.arreferenceimage/Contents.json new file mode 100644 index 0000000..3d6ad46 --- /dev/null +++ b/iOS App/ARPen/Assets.xcassets/AR Resources.arresourcegroup/stones.arreferenceimage/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "stones.jpg" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "width" : 13.300000000000001, + "unit" : "centimeters" + } +} \ No newline at end of file diff --git a/iOS App/ARPen/Assets.xcassets/AR Resources.arresourcegroup/stones.arreferenceimage/stones.jpg b/iOS App/ARPen/Assets.xcassets/AR Resources.arresourcegroup/stones.arreferenceimage/stones.jpg new file mode 100644 index 0000000..e770604 Binary files /dev/null and b/iOS App/ARPen/Assets.xcassets/AR Resources.arresourcegroup/stones.arreferenceimage/stones.jpg differ diff --git a/iOS App/ARPen/Assets.xcassets/Bool(Function).imageset/Bool(Function).png b/iOS App/ARPen/Assets.xcassets/Bool(Function).imageset/Bool(Function).png deleted file mode 100644 index 9589511..0000000 Binary files a/iOS App/ARPen/Assets.xcassets/Bool(Function).imageset/Bool(Function).png and /dev/null differ diff --git a/iOS App/ARPen/Assets.xcassets/Bool(Function).imageset/Contents.json b/iOS App/ARPen/Assets.xcassets/Bool(Function).imageset/Contents.json deleted file mode 100644 index a1040e8..0000000 --- a/iOS App/ARPen/Assets.xcassets/Bool(Function).imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "Bool(Function).png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/iOS App/ARPen/Assets.xcassets/Bool(Hole).imageset/Bool(Hole).png b/iOS App/ARPen/Assets.xcassets/Bool(Hole).imageset/Bool(Hole).png deleted file mode 100644 index 9d9d54e..0000000 Binary files a/iOS App/ARPen/Assets.xcassets/Bool(Hole).imageset/Bool(Hole).png and /dev/null differ diff --git a/iOS App/ARPen/Assets.xcassets/Bool(Hole).imageset/Contents.json b/iOS App/ARPen/Assets.xcassets/Bool(Hole).imageset/Contents.json deleted file mode 100644 index af21a41..0000000 --- a/iOS App/ARPen/Assets.xcassets/Bool(Hole).imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "Bool(Hole).png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/iOS App/ARPen/Assets.xcassets/Contents.json b/iOS App/ARPen/Assets.xcassets/Contents.json index 73c0059..da4a164 100644 --- a/iOS App/ARPen/Assets.xcassets/Contents.json +++ b/iOS App/ARPen/Assets.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "author" : "xcode", - "version" : 1 + "version" : 1, + "author" : "xcode" } -} +} \ No newline at end of file diff --git a/iOS App/ARPen/Assets.xcassets/Loft.imageset/Contents.json b/iOS App/ARPen/Assets.xcassets/DrawButton.imageset/Contents.json similarity index 87% rename from iOS App/ARPen/Assets.xcassets/Loft.imageset/Contents.json rename to iOS App/ARPen/Assets.xcassets/DrawButton.imageset/Contents.json index 259047a..d923c26 100644 --- a/iOS App/ARPen/Assets.xcassets/Loft.imageset/Contents.json +++ b/iOS App/ARPen/Assets.xcassets/DrawButton.imageset/Contents.json @@ -10,7 +10,7 @@ }, { "idiom" : "universal", - "filename" : "Loft.png", + "filename" : "DrawButton.png", "scale" : "3x" } ], diff --git a/iOS App/ARPen/Assets.xcassets/DrawButton.imageset/DrawButton.png b/iOS App/ARPen/Assets.xcassets/DrawButton.imageset/DrawButton.png new file mode 100644 index 0000000..145d618 Binary files /dev/null and b/iOS App/ARPen/Assets.xcassets/DrawButton.imageset/DrawButton.png differ diff --git a/iOS App/ARPen/Assets.xcassets/Load Model Button.imageset/Contents.json b/iOS App/ARPen/Assets.xcassets/Load Model Button.imageset/Contents.json deleted file mode 100644 index 58d3265..0000000 --- a/iOS App/ARPen/Assets.xcassets/Load Model Button.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "LoadModelButton.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/iOS App/ARPen/Assets.xcassets/Load Model Button.imageset/LoadModelButton.png b/iOS App/ARPen/Assets.xcassets/Load Model Button.imageset/LoadModelButton.png deleted file mode 100644 index 9a58607..0000000 Binary files a/iOS App/ARPen/Assets.xcassets/Load Model Button.imageset/LoadModelButton.png and /dev/null differ diff --git a/iOS App/ARPen/Assets.xcassets/Loft.imageset/Loft.png b/iOS App/ARPen/Assets.xcassets/Loft.imageset/Loft.png deleted file mode 100644 index 7489404..0000000 Binary files a/iOS App/ARPen/Assets.xcassets/Loft.imageset/Loft.png and /dev/null differ diff --git a/iOS App/ARPen/Assets.xcassets/Move1PluginInstruction.imageset/Contents.json b/iOS App/ARPen/Assets.xcassets/Move1PluginInstruction.imageset/Contents.json index f022861..7e35692 100644 --- a/iOS App/ARPen/Assets.xcassets/Move1PluginInstruction.imageset/Contents.json +++ b/iOS App/ARPen/Assets.xcassets/Move1PluginInstruction.imageset/Contents.json @@ -9,13 +9,13 @@ "scale" : "2x" }, { - "filename" : "Move1PluginInstructions.png", "idiom" : "universal", + "filename" : "Move1PluginInstructions.png", "scale" : "3x" } ], "info" : { - "author" : "xcode", - "version" : 1 + "version" : 1, + "author" : "xcode" } -} +} \ No newline at end of file diff --git a/iOS App/ARPen/Assets.xcassets/Move1PluginInstruction.imageset/Move1PluginInstructions.png b/iOS App/ARPen/Assets.xcassets/Move1PluginInstruction.imageset/Move1PluginInstructions.png index a9e1ae2..550fb9b 100644 Binary files a/iOS App/ARPen/Assets.xcassets/Move1PluginInstruction.imageset/Move1PluginInstructions.png and b/iOS App/ARPen/Assets.xcassets/Move1PluginInstruction.imageset/Move1PluginInstructions.png differ diff --git a/iOS App/ARPen/Assets.xcassets/Sweep(Path).imageset/Contents.json b/iOS App/ARPen/Assets.xcassets/NextTrialButton.imageset/Contents.json similarity index 86% rename from iOS App/ARPen/Assets.xcassets/Sweep(Path).imageset/Contents.json rename to iOS App/ARPen/Assets.xcassets/NextTrialButton.imageset/Contents.json index f432b32..cb71fa9 100644 --- a/iOS App/ARPen/Assets.xcassets/Sweep(Path).imageset/Contents.json +++ b/iOS App/ARPen/Assets.xcassets/NextTrialButton.imageset/Contents.json @@ -10,7 +10,7 @@ }, { "idiom" : "universal", - "filename" : "Sweep(Path).png", + "filename" : "NextTrialButton.png", "scale" : "3x" } ], diff --git a/iOS App/ARPen/Assets.xcassets/NextTrialButton.imageset/NextTrialButton.png b/iOS App/ARPen/Assets.xcassets/NextTrialButton.imageset/NextTrialButton.png new file mode 100644 index 0000000..58db568 Binary files /dev/null and b/iOS App/ARPen/Assets.xcassets/NextTrialButton.imageset/NextTrialButton.png differ diff --git a/iOS App/ARPen/Assets.xcassets/Revolve(2Profile).imageset/Contents.json b/iOS App/ARPen/Assets.xcassets/Revolve(2Profile).imageset/Contents.json deleted file mode 100644 index 52bce09..0000000 --- a/iOS App/ARPen/Assets.xcassets/Revolve(2Profile).imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "Revolve(2Profile).png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/iOS App/ARPen/Assets.xcassets/Revolve(2Profile).imageset/Revolve(2Profile).png b/iOS App/ARPen/Assets.xcassets/Revolve(2Profile).imageset/Revolve(2Profile).png deleted file mode 100644 index b93208b..0000000 Binary files a/iOS App/ARPen/Assets.xcassets/Revolve(2Profile).imageset/Revolve(2Profile).png and /dev/null differ diff --git a/iOS App/ARPen/Assets.xcassets/Revolve(Profile+Axis).imageset/Contents.json b/iOS App/ARPen/Assets.xcassets/Revolve(Profile+Axis).imageset/Contents.json deleted file mode 100644 index bf36db5..0000000 --- a/iOS App/ARPen/Assets.xcassets/Revolve(Profile+Axis).imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "Revolve(Profile+Axis).png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/iOS App/ARPen/Assets.xcassets/Revolve(Profile+Axis).imageset/Revolve(Profile+Axis).png b/iOS App/ARPen/Assets.xcassets/Revolve(Profile+Axis).imageset/Revolve(Profile+Axis).png deleted file mode 100644 index 59d2e4c..0000000 Binary files a/iOS App/ARPen/Assets.xcassets/Revolve(Profile+Axis).imageset/Revolve(Profile+Axis).png and /dev/null differ diff --git a/iOS App/ARPen/Assets.xcassets/Revolve(Profile+Circle).imageset/Contents.json b/iOS App/ARPen/Assets.xcassets/Revolve(Profile+Circle).imageset/Contents.json deleted file mode 100644 index 94656e2..0000000 --- a/iOS App/ARPen/Assets.xcassets/Revolve(Profile+Circle).imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "Revolve(Profile+Circle).png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/iOS App/ARPen/Assets.xcassets/Revolve(Profile+Circle).imageset/Revolve(Profile+Circle).png b/iOS App/ARPen/Assets.xcassets/Revolve(Profile+Circle).imageset/Revolve(Profile+Circle).png deleted file mode 100644 index f734c2e..0000000 Binary files a/iOS App/ARPen/Assets.xcassets/Revolve(Profile+Circle).imageset/Revolve(Profile+Circle).png and /dev/null differ diff --git a/iOS App/ARPen/Assets.xcassets/Save Model Button.imageset/Contents.json b/iOS App/ARPen/Assets.xcassets/Save Model Button.imageset/Contents.json deleted file mode 100644 index 70af5e1..0000000 --- a/iOS App/ARPen/Assets.xcassets/Save Model Button.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "SaveModelButton.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/iOS App/ARPen/Assets.xcassets/Save Model Button.imageset/SaveModelButton.png b/iOS App/ARPen/Assets.xcassets/Save Model Button.imageset/SaveModelButton.png deleted file mode 100644 index 6cd6680..0000000 Binary files a/iOS App/ARPen/Assets.xcassets/Save Model Button.imageset/SaveModelButton.png and /dev/null differ diff --git a/iOS App/ARPen/Assets.xcassets/Share Model Button.imageset/Contents.json b/iOS App/ARPen/Assets.xcassets/Share Model Button.imageset/Contents.json deleted file mode 100644 index 81c75f0..0000000 --- a/iOS App/ARPen/Assets.xcassets/Share Model Button.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "ShareModelButton.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/iOS App/ARPen/Assets.xcassets/Share Model Button.imageset/ShareModelButton.png b/iOS App/ARPen/Assets.xcassets/Share Model Button.imageset/ShareModelButton.png deleted file mode 100644 index 23670e4..0000000 Binary files a/iOS App/ARPen/Assets.xcassets/Share Model Button.imageset/ShareModelButton.png and /dev/null differ diff --git a/iOS App/ARPen/Assets.xcassets/SonarPlugin.imageset/Contents.json b/iOS App/ARPen/Assets.xcassets/SonarPlugin.imageset/Contents.json deleted file mode 100644 index 3f1b483..0000000 --- a/iOS App/ARPen/Assets.xcassets/SonarPlugin.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "SonarPlugin.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/iOS App/ARPen/Assets.xcassets/SonarPlugin.imageset/SonarPlugin.png b/iOS App/ARPen/Assets.xcassets/SonarPlugin.imageset/SonarPlugin.png deleted file mode 100644 index a1a6fd3..0000000 Binary files a/iOS App/ARPen/Assets.xcassets/SonarPlugin.imageset/SonarPlugin.png and /dev/null differ diff --git a/iOS App/ARPen/Assets.xcassets/Sweep(2Profiles).imageset/Contents.json b/iOS App/ARPen/Assets.xcassets/Sweep(2Profiles).imageset/Contents.json deleted file mode 100644 index ae4d6fd..0000000 --- a/iOS App/ARPen/Assets.xcassets/Sweep(2Profiles).imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "Sweep(2Profiles).png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/iOS App/ARPen/Assets.xcassets/Sweep(2Profiles).imageset/Sweep(2Profiles).png b/iOS App/ARPen/Assets.xcassets/Sweep(2Profiles).imageset/Sweep(2Profiles).png deleted file mode 100644 index b02df37..0000000 Binary files a/iOS App/ARPen/Assets.xcassets/Sweep(2Profiles).imageset/Sweep(2Profiles).png and /dev/null differ diff --git a/iOS App/ARPen/Assets.xcassets/Sweep(Path).imageset/Sweep(Path).png b/iOS App/ARPen/Assets.xcassets/Sweep(Path).imageset/Sweep(Path).png deleted file mode 100644 index 268b8ea..0000000 Binary files a/iOS App/ARPen/Assets.xcassets/Sweep(Path).imageset/Sweep(Path).png and /dev/null differ diff --git a/iOS App/ARPen/Base.lproj/Main.storyboard b/iOS App/ARPen/Base.lproj/Main.storyboard index 0a4bf6b..ab025d7 100644 --- a/iOS App/ARPen/Base.lproj/Main.storyboard +++ b/iOS App/ARPen/Base.lproj/Main.storyboard @@ -1,9 +1,8 @@ - + - - + @@ -19,10 +18,6 @@ - - - - @@ -60,21 +55,8 @@ - - - - - - - - - - - - @@ -243,31 +178,72 @@ + + + - + - - - - - - - @@ -277,25 +253,13 @@ - - - - - - - - + - - - - - @@ -308,22 +272,16 @@ - - - - - - - + @@ -509,7 +467,7 @@ - + @@ -545,14 +503,14 @@ - + - + - + - + - - - - - - - - - - - @@ -892,10 +801,9 @@ - + - - + diff --git a/iOS App/ARPen/Extensions And Helpers/DateExtensions.swift b/iOS App/ARPen/Extensions And Helpers/DateExtensions.swift deleted file mode 100755 index 4c65ff7..0000000 --- a/iOS App/ARPen/Extensions And Helpers/DateExtensions.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// DateExtensions.swift -// ARPen -// -// Created by Jan Benscheid on 19.04.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation - -extension Date { - - var millisecondsSince1970:Int64 { - return Int64((self.timeIntervalSince1970 * 1000.0).rounded()) - } - - init(milliseconds:Int64) { - self = Date(timeIntervalSince1970: TimeInterval(milliseconds) / 1000) - } - - static func - (lhs: Date, rhs: Date) -> TimeInterval { - return lhs.timeIntervalSinceReferenceDate - rhs.timeIntervalSinceReferenceDate - } - -} diff --git a/iOS App/ARPen/Extensions And Helpers/NotificationName.swift b/iOS App/ARPen/Extensions And Helpers/NotificationName.swift deleted file mode 100644 index 2228c53..0000000 --- a/iOS App/ARPen/Extensions And Helpers/NotificationName.swift +++ /dev/null @@ -1,11 +0,0 @@ -// -// NotificationName.swift -// ARPen -// -// Created by Philipp Wacker on 10.12.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -extension Notification.Name { - static let softwarePenButtonEvent = Notification.Name("softwarePenButtonEvent") -} diff --git a/iOS App/ARPen/Extensions And Helpers/PassthroughView.swift b/iOS App/ARPen/Extensions And Helpers/PassthroughView.swift deleted file mode 100644 index c4df668..0000000 --- a/iOS App/ARPen/Extensions And Helpers/PassthroughView.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// PassthroughView.swift -// ARPen -// -// Created by Philipp Wacker on 10.12.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -// View to let through all touch events that are not on UI elements in this view -// Code taken from: https://medium.com/@nguyenminhphuc/how-to-pass-ui-events-through-views-in-ios-c1be9ab1626b - -import UIKit - -class PassthroughView: UIView { - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - let view = super.hitTest(point, with: event) - return view == self ? nil : view - } - - /* - // Only override draw() if you perform custom drawing. - // An empty implementation adversely affects performance during animation. - override func draw(_ rect: CGRect) { - // Drawing code - } - */ - -} diff --git a/iOS App/ARPen/Extensions And Helpers/PositionFilter.swift b/iOS App/ARPen/Extensions And Helpers/PositionFilter.swift deleted file mode 100644 index 157df4f..0000000 --- a/iOS App/ARPen/Extensions And Helpers/PositionFilter.swift +++ /dev/null @@ -1,118 +0,0 @@ -// -// PositionFilter.swift -// ARPen -// -// Created by Philipp Wacker on 08.05.20. -// Copyright © 2020 RWTH Aachen. All rights reserved. -// - -import Foundation - -class PositionFilter { - //specify filter weights - var alphaValue : Float = 0.5 - var gammaValue : Float = 0.5 - var slerpFactor : Float = 0.5 - - //specify previous position & trend - var previousFilteredPosition : SCNVector3? - var trend : SCNVector3? - - var previousFilteredOrientation : simd_quatf? - - var penTipPositionHistory: [SCNVector3] = [] - - init(alphaValue:Float, gammaValue:Float, slerpFactor:Float) { - self.alphaValue = alphaValue - self.gammaValue = gammaValue - self.slerpFactor = slerpFactor - } - - init(alphaValue:Float, gammaValue:Float){ - self.alphaValue = alphaValue - self.gammaValue = gammaValue - } - - init(slerpFactor:Float){ - self.slerpFactor = slerpFactor - } - - //smooth the position - func filteredPositionAfter(newPosition position:SCNVector3) -> SCNVector3 { - - //return slidingWindowFilteringWith(newPosition: position) - //return exponentialMovingAverageWith(newPosition: position) - return doubleExponentialMovingAverageWith(newPosition: position) - - } - - func slidingWindowFilteringWith(newPosition position : SCNVector3) -> SCNVector3 { - var penTipPosition = position - - //Average with past n tip positions - let n = 1 - for pastPenTip in penTipPositionHistory { - penTipPosition += pastPenTip - } - penTipPosition /= Float(penTipPositionHistory.count + 1) - penTipPositionHistory.append(penTipPosition) - - //Remove latest item if too much items are in penTipPositionHistory - if penTipPositionHistory.count > n { - penTipPositionHistory.remove(at: 0) - } - - return penTipPosition - } - - func exponentialMovingAverageWith(newPosition position : SCNVector3) -> SCNVector3 { - //check if there are previous positions available, otherwise return the current position - guard let previousFilteredPosition = self.previousFilteredPosition else { - self.previousFilteredPosition = position - return position - } - - //calculate new filtered position (split into two parts since the compiler can't check it otherwise -.-) - let firstPartOfEquation = alphaValue * position - let secondPartOfEquation = (1.0 - alphaValue) * previousFilteredPosition - let newPosition = firstPartOfEquation + secondPartOfEquation - - self.previousFilteredPosition = newPosition - - return newPosition - } - - func doubleExponentialMovingAverageWith(newPosition position : SCNVector3) -> SCNVector3 { - //check if there are previous positions available, otherwise return the current position - guard let previousFilteredPosition = self.previousFilteredPosition, let trend = self.trend else { - self.previousFilteredPosition = position - self.trend = SCNVector3Zero - return position - } - - //calculate new filtered position - var firstPartOfEquation = alphaValue * position - var secondPartOfEquation = (1.0 - alphaValue) * (previousFilteredPosition + trend) - let newPosition = firstPartOfEquation + secondPartOfEquation - - //calculate new trend - firstPartOfEquation = gammaValue * (newPosition - previousFilteredPosition) - secondPartOfEquation = (1.0 - gammaValue) * trend - let newTrend = firstPartOfEquation + secondPartOfEquation - - self.previousFilteredPosition = newPosition - self.trend = newTrend - - return newPosition - } - - //smooth the orientation - func filteredOrientationAfter(newOrientation orientation : simd_quatf) -> simd_quatf { - guard let previousOrientation = self.previousFilteredOrientation else { - self.previousFilteredOrientation = orientation - return orientation - } - return simd_slerp(previousOrientation, orientation, self.slerpFactor) - } - -} diff --git a/iOS App/ARPen/Extensions And Helpers/Utilities.swift b/iOS App/ARPen/Extensions And Helpers/Utilities.swift deleted file mode 100644 index 0e270a9..0000000 --- a/iOS App/ARPen/Extensions And Helpers/Utilities.swift +++ /dev/null @@ -1,126 +0,0 @@ -// -// Utilities.swift -// ARPen -// -// Created by Krishna Subramanian on 20.07.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation -import simd -import ARKit - -extension UIViewController { - func showAlert(title: String, - message: String, - buttonTitle: String = "OK", - showCancel: Bool = false, - buttonHandler: ((UIAlertAction) -> Void)? = nil) { - print(title + "\n" + message) - let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) - alertController.addAction(UIAlertAction(title: buttonTitle, style: .default, handler: buttonHandler)) - if showCancel { - alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel)) - } - DispatchQueue.main.async { - self.present(alertController, animated: true, completion: nil) - } - } - - func makeRoundedCorners(button: UIButton!) { - button.layer.masksToBounds = true - button.layer.cornerRadius = button.frame.width/2 - } -} - -extension CGImagePropertyOrientation { - /// Preferred image presentation orientation respecting the native sensor orientation of iOS device camera. - init(cameraOrientation: UIDeviceOrientation) { - switch cameraOrientation { - case .portrait: - self = .right - case .portraitUpsideDown: - self = .left - case .landscapeLeft: - self = .up - case .landscapeRight: - self = .down - default: - self = .right - } - } -} - -@available(iOS 12.0, *) -extension ARWorldMap { - var snapshotAnchor: SnapshotAnchor? { - return anchors.compactMap { $0 as? SnapshotAnchor }.first - } -} - -// Setup notification names to be observed -extension Notification.Name { - static let cameraDidChangeTrackingState = Notification.Name("cameraDidChangeTrackingState") - static let sessionDidUpdate = Notification.Name("sessionDidUpdate") - static let virtualObjectDidRenderAtAnchor = Notification.Name("virtualObjectDidRenderAtAnchor") -} - -@available(iOS 12.0, *) -extension ARFrame.WorldMappingStatus: CustomStringConvertible { - public var description: String { - switch self { - case .notAvailable: - return "Not Available" - case .limited: - return "Limited" - case .extending: - return "Extending" - case .mapped: - return "Mapped" - } - } -} - -extension ARCamera.TrackingState: CustomStringConvertible { - public var description: String { - switch self { - case .normal: - return "Normal" - case .notAvailable: - return "Not Available" - case .limited(.initializing): - return "Initializing" - case .limited(.excessiveMotion): - return "Excessive Motion" - case .limited(.insufficientFeatures): - return "Insufficient Features" - case .limited(.relocalizing): - return "Relocalizing" - } - } -} - -extension ARCamera.TrackingState { - var localizedFeedback: String { - switch self { - case .normal: - // No planes detected; provide instructions for this app's AR interactions. - return "Move around to map the environment." - - case .notAvailable: - return "Tracking unavailable." - - case .limited(.excessiveMotion): - return "Move the device more slowly." - - case .limited(.insufficientFeatures): - return "Point the device at an area with visible surface detail, or improve lighting conditions." - - case .limited(.relocalizing): - return "Resuming session — move to where you were when the session was interrupted." - - case .limited(.initializing): - return "Initializing AR session." - } - } -} diff --git a/iOS App/ARPen/Extensions And Helpers/ARMenusSupportFile.swift b/iOS App/ARPen/Extensions/ARMenusSupportFile.swift similarity index 100% rename from iOS App/ARPen/Extensions And Helpers/ARMenusSupportFile.swift rename to iOS App/ARPen/Extensions/ARMenusSupportFile.swift diff --git a/iOS App/ARPen/Extensions And Helpers/CylinderLine.swift b/iOS App/ARPen/Extensions/CylinderLine.swift similarity index 100% rename from iOS App/ARPen/Extensions And Helpers/CylinderLine.swift rename to iOS App/ARPen/Extensions/CylinderLine.swift diff --git a/iOS App/ARPen/Extensions And Helpers/DegreesToRadians.swift b/iOS App/ARPen/Extensions/DegreesToRadians.swift similarity index 100% rename from iOS App/ARPen/Extensions And Helpers/DegreesToRadians.swift rename to iOS App/ARPen/Extensions/DegreesToRadians.swift diff --git a/iOS App/ARPen/Extensions And Helpers/SCNVector3Extensions.swift b/iOS App/ARPen/Extensions/SCNVector3Extensions.swift similarity index 98% rename from iOS App/ARPen/Extensions And Helpers/SCNVector3Extensions.swift rename to iOS App/ARPen/Extensions/SCNVector3Extensions.swift index 2f95b7e..2485b27 100644 --- a/iOS App/ARPen/Extensions And Helpers/SCNVector3Extensions.swift +++ b/iOS App/ARPen/Extensions/SCNVector3Extensions.swift @@ -136,10 +136,6 @@ func * (vector: SCNVector3, scalar: Float) -> SCNVector3 { return SCNVector3Make(vector.x * scalar, vector.y * scalar, vector.z * scalar) } -func * (scalar: Float, vector: SCNVector3) -> SCNVector3 { - return vector * scalar -} - /** * Multiplies the x and y fields of a SCNVector3 with the same scalar value. */ diff --git a/iOS App/ARPen/Info.plist b/iOS App/ARPen/Info.plist index 10dbc1f..fa3649b 100644 --- a/iOS App/ARPen/Info.plist +++ b/iOS App/ARPen/Info.plist @@ -17,13 +17,13 @@ CFBundlePackageType APPL CFBundleShortVersionString - $(MARKETING_VERSION) + 1.1 CFBundleVersion 1 LSRequiresIPhoneOS NSBluetoothAlwaysUsageDescription - To receive information about the buttons pressed on the ARPen, this app uses Bluetooth. + ARPen uses Bluetooth to connect to the pen and stays connected to receive button presses. NSBluetoothPeripheralUsageDescription To receive information about the buttons pressed on the ARPen, this app uses Bluetooth. NSCameraUsageDescription diff --git a/iOS App/ARPen/MarkerBox.swift b/iOS App/ARPen/MarkerBox.swift index c0b11b6..9b3f8d8 100644 --- a/iOS App/ARPen/MarkerBox.swift +++ b/iOS App/ARPen/MarkerBox.swift @@ -5,6 +5,7 @@ // Created by Felix Wehnert on 16.01.18. // Copyright © 2018 RWTH Aachen. All rights reserved. // + import SceneKit /** @@ -16,10 +17,6 @@ class MarkerBox: SCNNode { var penTipPositionHistory: [SCNVector3] = [] var penLength: Double = 12 - static private var secureCoding = true - override public class var supportsSecureCoding: Bool { return secureCoding } - let positionFilter = PositionFilter(alphaValue: 0.5, gammaValue: 0.5, slerpFactor: 0.5) - /** * Describes in which landscape orientation the device is currently hold * If the device is hold in portrait orientation, the state keeps in the last landscape state @@ -39,13 +36,10 @@ class MarkerBox: SCNNode { markerArray = [SCNNode(), SCNNode(), SCNNode(), SCNNode(), SCNNode(), SCNNode(), SCNNode(), SCNNode()] penLength = length super.init() - self.name = "MarkerBox" //Observe device orientation. If orientation changes rotated() is called - NotificationCenter.default.addObserver(self, selector: #selector(rotated), name: UIDevice.orientationDidChangeNotification, object: nil) - //set orientationState to the current device orientation - rotated() + NotificationCenter.default.addObserver(self, selector: #selector(rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil) //Make pen tip calculation calculatePenTip(length: length) @@ -68,42 +62,117 @@ class MarkerBox: SCNNode { } func calculatePenTip(length: Double){ - let penLength: Double = length //measured from center of the cube to the pen tip. In meters - let cubeSideLength: Double = 0.04 //in meters - let markerOffset: Double = 0.005 //x & y offset of marker from center of the cube's side. For "close" markers. In meters - // translation values from the detected marker position to the pen tip - var xTranslationClose, yTranslationClose, zTranslationClose, //translation values for the sides of the cube closer to the stem of the pen -> "close" markers - xTranslationAway, yTranslationAway, zTranslationAway, //translation values for the sides of the cube away from the stem of the pen -> "away" markers - xCHIARPen, yCHIARPen, //CHI = CHI 2019, Glasgow - xLMARPen, yLMARPen: Double //LM = Laser Messe, Munich + let a: Double = length + var xs, ys, zs, xl, yl, zl, + xCHIARPen, yCHIARPen, + xLMARPen, yLMARPen: Double + + let angle = (35.3).degreesToRadians - //angle between the stem of the pen and the floor when the cube is standing on the floor (=angle between the adjacent side and hypothenuse of the triangle of diagonal of one side (adjacent side) and length of cube side (opposite side)) - let angle = (35.26).degreesToRadians + xs = ((cos(angle) * a) + 0.005)/sqrt(2) + ys = xs + zs = sin(angle) * a + zs -= 0.02 + xs *= -1 + ys *= -1 + zs *= -1 - //calculation of translation values for the "close" markers. The calculations assume looking along the z Axis in positive direction (directly onto the marker) - //for z, first move from position of the marker into the center of the cube (positive Z -> inside the cube) - zTranslationClose = 0.5 * cubeSideLength - //second, apply translation to the z position of the pen tip (from the marker's coordinate system) - zTranslationClose -= (sin(angle) * penLength) + xl = (cos(angle) * a)/sqrt(2) + yl = xl + zl = sin(angle) * a + zl += 0.02 + xl *= -1 + yl *= -1 + + // Calculate the translation vector for full-sized business card ARPen used for CHI 2019, Scotland, 2019. + let markerOffsetFromBottomForCHIARPen: Double = 0.01975 // distance from the bottom of the card to the center of the marker + let widthOfCHIARPen = 0.085 + let heightOfCHIARPen = 0.055 + + xCHIARPen = 0.75 * widthOfCHIARPen // assuming marker center is at three-fourths of the card width + yCHIARPen = heightOfCHIARPen - markerOffsetFromBottomForCHIARPen // height of the card is 5.5 cm + + // Calculate the translation vector for half-sized business card ARPen used for Laser Messe, Munich, 2019. + let markerOffsetFromBottomForLaserMesseARPen: Double = 0.01375; // distance from the bottom of the card to the center of the marker + let heightOfLaserMesseARPen = 0.0275; - //for x, first correct the marker offset to move to the center of the cube's side - xTranslationClose = -markerOffset - //second, apply translation to the x position of the pen tip (from the marker's coordinate system) - xTranslationClose -= ((cos(angle) * penLength)/sqrt(2)) - //since the translations for y is the same as for x, copy the calculation - yTranslationClose = xTranslationClose + xLMARPen = 0.0713; // marker's center distance from the left edge of the card + yLMARPen = heightOfLaserMesseARPen/2 - markerOffsetFromBottomForLaserMesseARPen; + + + var i = 0 + for marker in markerArray { + marker.name = "Marker #\(i+1)" + marker.childNodes.first(where: {($0.name?.hasPrefix("PenTipPoint") == true)})?.removeFromParentNode() + + let markerFace = MarkerFace(rawValue: i+1) ?? .notExpected + guard markerFace != .notExpected else { + fatalError("markerArray shouldn't be longer than 6 elements!") + } + + let point = SCNNode() + point.name = "PenTipPoint from #\(i+1)" + + switch (markerFace) { + case (.back): + point.position = SCNVector3(xs, ys, zs) + case (.top): + point.position = SCNVector3(xs, ys, zs) + case (.right): + point.position = SCNVector3(xs, ys, zs) + case (.bottom): + point.position = SCNVector3(-xl, yl, zl) + case (.left): + point.position = SCNVector3(xl, yl, zl) + case (.front): + point.position = SCNVector3(-xl, yl, zl) + case (.CHIARPen): + point.position = SCNVector3(-xCHIARPen, -yCHIARPen, 0) + case (.laserMesseARPen): + point.position = SCNVector3(-xLMARPen, -yLMARPen, 0) + default: + break + } + + //Invert the coordinates in landscape homebutton left + if orientationState == .HomeButtonLeft { + point.position.x *= -1 + point.position.y *= -1 + } + + marker.addChildNode(point) + if !self.childNodes.contains(marker){ + self.addChildNode(marker) + } + + i += 1 + } + calculateDirectionPoint(length: length) + } + + // used to calculate a node for pen direction tracking + func calculateDirectionPoint(length: Double){ + let a: Double = length * 2 + var xs, ys, zs, xl, yl, zl, + xCHIARPen, yCHIARPen, + xLMARPen, yLMARPen: Double + let angle = (35.3).degreesToRadians - //calculation of translation values for the "away" markers. The calculations assume looking along the z Axis in positive direction (directly onto the marker) - //for z, first move from position of the marker into the center of the cube (positive Z -> inside the cube) - zTranslationAway = 0.5 * cubeSideLength - //second, apply translation to the z position of the pen tip (from the marker's coordinate system) - zTranslationAway += (sin(angle) * penLength) + xs = ((cos(angle) * a) + 0.005)/sqrt(2) + ys = xs + zs = sin(angle) * a + zs -= 0.02 + xs *= -1 + ys *= -1 + zs *= -1 - //for x, apply translation to the x position of the pen tip (from the marker's coordinate system) - xTranslationAway = (cos(angle) * penLength)/sqrt(2) - //since the translations for y is the same as for x, copy the calculation - yTranslationAway = -xTranslationAway + xl = (cos(angle) * a)/sqrt(2) + yl = xl + zl = sin(angle) * a + zl += 0.02 + xl *= -1 + yl *= -1 // Calculate the translation vector for full-sized business card ARPen used for CHI 2019, Scotland, 2019. let markerOffsetFromBottomForCHIARPen: Double = 0.01975 // distance from the bottom of the card to the center of the marker @@ -124,7 +193,9 @@ class MarkerBox: SCNNode { var i = 0 for marker in markerArray { marker.name = "Marker #\(i+1)" - marker.childNodes.first?.removeFromParentNode() + + marker.childNodes.first(where: {($0.name?.hasPrefix("DirectionPoint") == true)})?.removeFromParentNode() + let markerFace = MarkerFace(rawValue: i+1) ?? .notExpected guard markerFace != .notExpected else { @@ -132,65 +203,25 @@ class MarkerBox: SCNNode { } let point = SCNNode() - point.name = "Point from #\(i+1)" + point.name = "DirectionPoint from #\(i+1)" - //for rotation: for simplicity, take the top rotation as the basis. rotate other markers to fit the top orientation and then apply the same rotation to the pen tip - let quaternionFromTopMarkerToPenTip = simd_quatf(angle: Float(-54.74.degreesToRadians), axis: float3(x: 0.707, y: -0.707, z: 0)) switch (markerFace) { case (.back): - point.position = SCNVector3(xTranslationClose, yTranslationClose, zTranslationClose) - point.eulerAngles = SCNVector3(x: 0, y: -Float.pi/2, z: -Float.pi/2) - //in case of HomeButtonLeft the orientation has to be rotated another 180 degrees after Rotation to top marker - if orientationState == .HomeButtonLeft { - point.eulerAngles.z += Float.pi - } - point.simdLocalRotate(by: quaternionFromTopMarkerToPenTip) + point.position = SCNVector3(xs, ys, zs) case (.top): - point.position = SCNVector3(xTranslationClose, yTranslationClose, zTranslationClose) - if orientationState == .HomeButtonLeft { - point.eulerAngles.z += Float.pi - } - point.simdLocalRotate(by: quaternionFromTopMarkerToPenTip) + point.position = SCNVector3(xs, ys, zs) case (.right): - point.position = SCNVector3(xTranslationClose, yTranslationClose, zTranslationClose) - point.eulerAngles = SCNVector3(x: Float.pi/2, y: 0, z: Float.pi/2) - if orientationState == .HomeButtonLeft { - point.eulerAngles.z += Float.pi - } - point.simdLocalRotate(by: quaternionFromTopMarkerToPenTip) + point.position = SCNVector3(xs, ys, zs) case (.bottom): - point.position = SCNVector3(xTranslationAway, yTranslationAway, zTranslationAway) - point.eulerAngles.y = Float.pi - if orientationState == .HomeButtonLeft { - point.eulerAngles.z += Float.pi - } - point.simdLocalRotate(by: quaternionFromTopMarkerToPenTip) + point.position = SCNVector3(-xl, yl, zl) case (.left): - point.position = SCNVector3(-xTranslationAway, yTranslationAway, zTranslationAway) //The x translation needs to be inverted as the marker is rotated compared to the others - point.eulerAngles.x = -Float.pi/2 - if orientationState == .HomeButtonLeft { - point.eulerAngles.z += Float.pi - } - point.simdLocalRotate(by: quaternionFromTopMarkerToPenTip) + point.position = SCNVector3(xl, yl, zl) case (.front): - point.position = SCNVector3(xTranslationAway, yTranslationAway, zTranslationAway) - point.eulerAngles = SCNVector3(x: 0, y: Float.pi/2, z: Float.pi/2) - if orientationState == .HomeButtonLeft { - point.eulerAngles.z += Float.pi - } - point.simdLocalRotate(by: quaternionFromTopMarkerToPenTip) + point.position = SCNVector3(-xl, yl, zl) case (.CHIARPen): - point.position = SCNVector3(-xCHIARPen, -yCHIARPen, 0) - point.eulerAngles = SCNVector3(x: 0, y: -Float.pi/2, z: Float(-135.degreesToRadians)) - if orientationState == .HomeButtonLeft { - point.eulerAngles.z += Float.pi - } + point.position = SCNVector3(-2 * xCHIARPen, -2 * yCHIARPen, 0) case (.laserMesseARPen): - point.position = SCNVector3(-xLMARPen, -yLMARPen, 0) - point.eulerAngles = SCNVector3(x: 0, y: -Float.pi/2, z: Float(-135.degreesToRadians)) - if orientationState == .HomeButtonLeft { - point.eulerAngles.z += Float.pi - } + point.position = SCNVector3(-2 * xLMARPen, -2 * yLMARPen, 0) default: break } @@ -205,6 +236,8 @@ class MarkerBox: SCNNode { if !self.childNodes.contains(marker){ self.addChildNode(marker) } + + //marker.childNodes.first!.addChildNode(point) i += 1 } @@ -231,10 +264,8 @@ class MarkerBox: SCNNode { Determine the position of the pin point by ONLY considering the specified IDs - parameter ids: A list of marker IDs that are used to determine the position */ - func positionWith(ids: [MarkerFace]) -> SCNNode { - //hold the computed pen tip properties for each marker -> can be averaged to return pen tip node - var penTipPosition = SCNVector3Zero - var penTipOrientation = simd_quatf.init(ix: 0, iy: 0, iz: 0, r: 1) + func posititonWith(ids: [MarkerFace]) -> SCNVector3 { + var vector = SCNVector3Zero var mutableIds : [MarkerFace] = ids if mutableIds.count == 3 { @@ -258,41 +289,147 @@ class MarkerBox: SCNNode { } } - //average orientation between seen markers (averaging of rotation adapted from: https://answers.unity.com/questions/815266/find-and-average-rotations-together.html) - var counter : Float = 0 for id in mutableIds { - let candidateNode = SCNNode() - let transform = self.markerArray[id.rawValue-1].childNodes.first!.convertTransform(SCNMatrix4Identity, to: nil) + //let point = self.markerArray[id.rawValue-1].childNodes.first!.convertPosition(SCNVector3Zero, to: nil) - candidateNode.transform = transform - penTipPosition += candidateNode.position - - counter += 1 - penTipOrientation = simd_slerp(penTipOrientation, candidateNode.simdOrientation, 1.0/counter) + let point = self.markerArray[id.rawValue-1].childNodes.first(where: {($0.name?.hasPrefix("PenTipPoint") == true)})?.convertPosition(SCNVector3Zero, to: nil) + + vector += point ?? SCNVector3Zero } + vector /= Float(mutableIds.count) - penTipPosition /= Float(mutableIds.count) - - //apply smoothing to pen position & orientation - penTipPosition = self.positionFilter.filteredPositionAfter(newPosition: penTipPosition) - penTipOrientation = self.positionFilter.filteredOrientationAfter(newOrientation: penTipOrientation) + //Average with past n tip positions + let n = 1 + for pastPenTip in penTipPositionHistory { + vector += pastPenTip + } + vector /= Float(penTipPositionHistory.count + 1) + penTipPositionHistory.append(vector) - let returnNode = SCNNode() - returnNode.position = penTipPosition - returnNode.simdOrientation = penTipOrientation - return returnNode + //Remove latest item if too much items are in penTipPositionHistory + if penTipPositionHistory.count > n { + penTipPositionHistory.remove(at: 0) + } + return vector } + + /** + Determine the position of the pin point by ONLY considering the specified IDs + - parameter ids: A list of marker IDs that are used to determine the position + */ + func posititonDirectionWith(ids: [MarkerFace]) -> SCNVector3 { + var vector = SCNVector3Zero + var mutableIds : [MarkerFace] = ids + + if mutableIds.count == 3 { + let allowedDeviation: Float = 1.2 //Don't forget that some markers are not perfectly in the middle of the cube's face! + + //Calculate distances + let distance12 = markerArray[0].position.distance(vector: markerArray[1].position) + let distance13 = markerArray[0].position.distance(vector: markerArray[2].position) + let distance23 = markerArray[1].position.distance(vector: markerArray[2].position) + + //If distance of one marker to another one deviates too much from the other inter-marker distances, this point is removed from calculation + if distance12 > allowedDeviation * distance23 && distance13 > allowedDeviation * distance23 { + //Point 1 offsetted + mutableIds.remove(at: 0) + } else if distance12 > allowedDeviation * distance13 && distance23 > allowedDeviation * distance13 { + //Point 2 offsetted + mutableIds.remove(at: 1) + } else if distance13 > 1.3 * distance12 && distance23 > 1.3 * distance12 { + //Point 3 offsetted + mutableIds.remove(at: 2) + } + } + + for id in mutableIds { + +// let point = self.markerArray[id.rawValue-1].childNodes.first!.childNodes.first!.convertPosition(SCNVector3Zero, to: nil) +// //let point = self.markerArray[id.rawValue-1].childNodes.first!.convertPosition(childNodes.first!.position, to: nil) +// +// vector += point + + let point = self.markerArray[id.rawValue-1].childNodes.first(where: {($0.name?.hasPrefix("DirectionPoint") == true)})?.convertPosition(SCNVector3Zero, to: nil) + + vector += point ?? SCNVector3Zero + + } + vector /= Float(mutableIds.count) + +// //Average with past n tip positions +// let n = 1 +// for pastPenTip in penTipPositionHistory { +// vector += pastPenTip +// } +// vector /= Float(penTipPositionHistory.count + 1) +// penTipPositionHistory.append(vector) +// +// //Remove latest item if too much items are in penTipPositionHistory +// if penTipPositionHistory.count > n { +// penTipPositionHistory.remove(at: 0) +// } + return vector + } + + + func posititonMarkerOrigin(ids: [MarkerFace]) -> SCNVector3 { + var vector = SCNVector3Zero + var mutableIds : [MarkerFace] = ids + + if mutableIds.count == 3 { + let allowedDeviation: Float = 1.2 //Don't forget that some markers are not perfectly in the middle of the cube's face! + + //Calculate distances + let distance12 = markerArray[0].position.distance(vector: markerArray[1].position) + let distance13 = markerArray[0].position.distance(vector: markerArray[2].position) + let distance23 = markerArray[1].position.distance(vector: markerArray[2].position) + + //If distance of one marker to another one deviates too much from the other inter-marker distances, this point is removed from calculation + if distance12 > allowedDeviation * distance23 && distance13 > allowedDeviation * distance23 { + //Point 1 offsetted + mutableIds.remove(at: 0) + } else if distance12 > allowedDeviation * distance13 && distance23 > allowedDeviation * distance13 { + //Point 2 offsetted + mutableIds.remove(at: 1) + } else if distance13 > 1.3 * distance12 && distance23 > 1.3 * distance12 { + //Point 3 offsetted + mutableIds.remove(at: 2) + } + } + + for id in mutableIds { + let point = self.markerArray[id.rawValue-1].convertPosition(SCNVector3Zero, to: nil) + vector += point + } + vector /= Float(mutableIds.count) + // + // //Average with past n tip positions + // let n = 1 + // for pastPenTip in penTipPositionHistory { + // vector += pastPenTip + // } + // vector /= Float(penTipPositionHistory.count + 1) + // penTipPositionHistory.append(vector) + // + // //Remove latest item if too much items are in penTipPositionHistory + // if penTipPositionHistory.count > n { + // penTipPositionHistory.remove(at: 0) + // } + return vector + } + + + required init?(coder aDecoder: NSCoder) { - self.markerArray = [SCNNode(), SCNNode(), SCNNode(), SCNNode(), SCNNode(), SCNNode(), SCNNode(), SCNNode()] - super.init(coder: aDecoder) + fatalError("init(coder:) has not been implemented") } private enum DeviceOrientationState { case HomeButtonLeft case HomeButtonRight } - + } /** diff --git a/iOS App/ARPen/Modeling/GeometryNodes/ARPBoolNode.swift b/iOS App/ARPen/Modeling/GeometryNodes/ARPBoolNode.swift deleted file mode 100755 index 8609bbf..0000000 --- a/iOS App/ARPen/Modeling/GeometryNodes/ARPBoolNode.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// ARPBoolNode.swift -// ARPen -// -// Created by Jan Benscheid on 15.02.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation - -enum BooleanOperation { - case join, cut, intersect -} - -enum BooleanError: Error { - case operationUnknown -} - -/** - Node for Boolean operations - */ -class ARPBoolNode: ARPGeomNode { - - var a: ARPGeomNode - var b: ARPGeomNode - - let operation: BooleanOperation - - init(a: ARPGeomNode, b: ARPGeomNode, operation op: BooleanOperation) throws { - self.a = a - self.b = b - self.operation = op - - super.init(pivotChild: a) - self.geometryColor = a.geometryColor - - self.content.addChildNode(a) - self.content.addChildNode(b) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func build() throws -> OCCTReference { - - let ref: OCCTReference? - - // The name assignment was needed for the user study. - switch self.operation { - case .cut: - ref = try? OCCTAPI.shared.boolean(from: a.occtReference!, cut: b.occtReference!) - self.name = "(\(a.name ?? "a")-\(b.name ?? "b"))" - case .join: - ref = try? OCCTAPI.shared.boolean(join: a.occtReference!, with: b.occtReference!) - if (a.name ?? "").count >= (b.name ?? "").count { - self.name = "(\(a.name ?? "a")+\(b.name ?? "b"))" - } else { - self.name = "(\(b.name ?? "b")+\(a.name ?? "a"))" - } - case .intersect: - ref = try? OCCTAPI.shared.boolean(intersect: a.occtReference!, with: b.occtReference!) - if (a.name ?? "").count >= (b.name ?? "").count { - self.name = "(\(a.name ?? "a")x\(b.name ?? "b"))" - } else { - self.name = "(\(b.name ?? "b")x\(a.name ?? "a"))" - } - } - - if let r = ref { - OCCTAPI.shared.setPivotOf(handle: r, pivot: pivotChild.worldTransform) - } - - return ref ?? "" - } -} diff --git a/iOS App/ARPen/Modeling/GeometryNodes/ARPBox.swift b/iOS App/ARPen/Modeling/GeometryNodes/ARPBox.swift deleted file mode 100755 index 357eab3..0000000 --- a/iOS App/ARPen/Modeling/GeometryNodes/ARPBox.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// ARPCube.swift -// ARPen -// -// Created by Jan Benscheid on 18.02.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation - -class ARPBox: ARPGeomNode { - - var width: Double = 1 - var height: Double = 1 - var length: Double = 1 - - override init() { - super.init() - } - - init(width: Double, height: Double, length: Double) { - self.width = width - self.height = height - self.length = length - - super.init() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func build() throws -> OCCTReference { - return try OCCTAPI.shared.createBox(width: width, height: height, length: length) - } -} diff --git a/iOS App/ARPen/Modeling/GeometryNodes/ARPCylinder.swift b/iOS App/ARPen/Modeling/GeometryNodes/ARPCylinder.swift deleted file mode 100755 index 501fefe..0000000 --- a/iOS App/ARPen/Modeling/GeometryNodes/ARPCylinder.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// ARPCube.swift -// ARPen -// -// Created by Jan Benscheid on 18.02.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation - -class ARPCylinder: ARPGeomNode { - - var radius: Double = 1 - var height: Double = 1 - - override init() { - super.init() - } - - init(radius: Double, height: Double) { - self.radius = radius - self.height = height - - super.init() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func build() throws -> OCCTReference { - return try OCCTAPI.shared.createCylinder(radius: self.radius, height: self.height) - } -} diff --git a/iOS App/ARPen/Modeling/GeometryNodes/ARPGeomNode.swift b/iOS App/ARPen/Modeling/GeometryNodes/ARPGeomNode.swift deleted file mode 100755 index afbc41f..0000000 --- a/iOS App/ARPen/Modeling/GeometryNodes/ARPGeomNode.swift +++ /dev/null @@ -1,198 +0,0 @@ -// -// ARPGeomNode.swift -// ARPen -// -// Created by Jan Benscheid on 15.02.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation - -/** - This is the base class for all Nodes which have an underlying representation in Open CASCADE (OCCT). - */ -class ARPGeomNode: ARPNode { - - /// Reference to the underlying shape in OCCT - var occtReference:OCCTReference? - - /// Contains the content of the node down the hierarchy, e.g. in case of a boolean operation between A and B, A and B will be placed here - var content: SCNNode = SCNNode() - /// Node for the main geometry. - var geometryNode: SCNNode = SCNNode() - /// Node for the "outlines" of the objects - var isoLinesNode: SCNNode = SCNNode() - - /// The child, which is supposed to be the pivot of the object - var pivotChild: SCNNode - - var geometryColor = UIColor.init(hue: CGFloat(Float.random(in: 0...1)), saturation: 0.3, brightness: 0.9, alpha: 1) - var lineColor = UIColor.black - - var highlightColor = UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1) - var selectedColor = UIColor.white - - /// For boolean operations via "Boolean Solid/Hole" - var isHole: Bool = false { - didSet { - self.geometryColor = geometryColor.withAlphaComponent(isHole ? 0.5 : 1) - self.geometryNode.geometry?.firstMaterial?.diffuse.contents = self.geometryColor - } - } - - /// This function is blocking and should be called asynchronous. - override init() { - self.pivotChild = SCNNode() - super.init() - appendVisualization() - self.content.addChildNode(pivotChild) - self.content.isHidden = true - rebuild() - } - - /// Initialize and define a child to be the pivot. This function is blocking and should be called asynchronous. - init(pivotChild:SCNNode) { - self.pivotChild = pivotChild - super.init() - appendVisualization() - self.content.addChildNode(self.pivotChild) - self.content.isHidden = true - rebuild() - } - - private func appendVisualization() { - self.addChildNode(content) - self.addChildNode(geometryNode) - self.addChildNode(isoLinesNode) - } - - /// Request a re-triangulation of the geometry from OCCT. This function is blocking and should be called asynchronous. - final func updateView() { - - let geom = OCCTAPI.shared.triangulate(handle: occtReference!) - let lines = OCCTAPI.shared.tubeframe(handle: occtReference!) - - // The node may have been transformed between the geometry's generation and the actual attachment in DispatchQueue.main.async - // transformDelta is used to capture this difference - - /* - let transformDelta = SCNNode() - self.addChildNode(transformDelta) - transformDelta.setWorldTransform(SCNMatrix4Identity) - */ - - DispatchQueue.main.async { - self.geometryNode.geometry = geom - self.geometryNode.geometry?.firstMaterial?.diffuse.contents = self.geometryColor - self.geometryNode.geometry?.firstMaterial?.emission.contents = self.highlightColor - self.geometryNode.geometry?.firstMaterial?.lightingModel = .blinn - self.geometryNode.geometry?.firstMaterial?.diffuse.intensity = 1; - self.updateHighlightedState() - self.isoLinesNode.geometry = lines - self.isoLinesNode.geometry?.firstMaterial?.diffuse.contents = self.lineColor - self.isoLinesNode.geometry?.firstMaterial?.emission.contents = self.selectedColor - self.isoLinesNode.geometry?.firstMaterial?.lightingModel = .constant - self.updateSelectedState() - //self.isoLinesNode.geometry?.firstMaterial?.readsFromDepthBuffer = false - //self.geometryNode.renderingOrder = -1 - - // This is necessary if you use world coordinates - //self.geometryNode.setWorldTransform(transformDelta.worldTransform) - //self.isoLinesNode.setWorldTransform(transformDelta.worldTransform) - //transformDelta.removeFromParentNode() - - //self.geometryNode.transform = SCNMatrix4Invert(self.content.transform) - //self.isoLinesNode.transform = SCNMatrix4Invert(self.content.transform) - } - } - - /// Call to apply changes in translation, rotation or scale to OCCT. - override func applyTransform() { - self.applyTransform_() - (parent?.parent as? ARPGeomNode)?.rebuild() - } - - private final func applyTransform_() { - // This was necessary for local coordinates - //OCCTAPI.shared.transform(handle: occtReference!, transformation: self.transform) - - // This is necessary for world coordinates - OCCTAPI.shared.transform(handle: occtReference!, transformation: self.worldTransform) - for c in content.childNodes { - if let geom = c as? ARPGeomNode { - geom.applyTransform_() - } - } - } - - /// This method is responsible for creating the geometry, s.t. it has its origin at (0,0,0). Therefore you have to ensure to either create it there, or to manually shift the origin using OCCTAPI.shared.pivot. What's more appropriate depends on the situation. - func build() throws -> OCCTReference { - fatalError("Must Override") - } - - /// Needs to be called when properties of an object change, which influence its appearance, e.g. when a node moved in a path. This function is blocking and should be called asynchronous. - final func rebuild() { - if let ref = occtReference { - OCCTAPI.shared.free(handle: ref) - } - if let ref = try? build() { - occtReference = ref - pivotToChild() - updateView() - (parent?.parent as? ARPGeomNode)?.rebuild() - } else { - print("FAILED TO REBUILD") - } - } - - /// Updates the pivot to be where the `pivotChild` is. - final func pivotToChild() { - /* - /// Changing the pivot in SceneKit has two oddities: - /// (1) The node shifts, s.t. the node's pivot stays in the same place relative to the scene - /// (2) The node's internal coordinate system does not change. Its position does however. Pivot != origin in SceneKit - - /// Because of (1), we *first* transform the object to the same world space transform as the child to be pivot... - self.setWorldTransform(child.worldTransform) - /// ... and then change its pivot. Otherwise the child objects would have already been moved relative to the scene. - self.pivot = child.transform - */ - self.setWorldTransform(pivotChild.worldTransform) - content.transform = SCNMatrix4Invert(pivotChild.transform) - } - - override func updateHighlightedState() { - if highlighted { - geometryNode.geometry?.firstMaterial?.emission.intensity = 1 - } else { - geometryNode.geometry?.firstMaterial?.emission.intensity = 0 - } - } - - override func updateSelectedState() { - if selected { - isoLinesNode.geometry?.firstMaterial?.emission.intensity = 1 - } else { - isoLinesNode.geometry?.firstMaterial?.emission.intensity = 0 - } - } - - override func updateVisitedState() { - self.content.isHidden = !visited - self.geometryNode.isHidden = visited - self.isoLinesNode.isHidden = visited - } - - /// Saves the object as an stl under the given file path. - func exportStl(filePath: URL) { - OCCTAPI.shared.exportStl(handle: occtReference!, filePath: filePath) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - OCCTAPI.shared.free(handle: occtReference!) - } -} diff --git a/iOS App/ARPen/Modeling/GeometryNodes/ARPLoft.swift b/iOS App/ARPen/Modeling/GeometryNodes/ARPLoft.swift deleted file mode 100755 index 0312615..0000000 --- a/iOS App/ARPen/Modeling/GeometryNodes/ARPLoft.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// ARPLoft.swift -// ARPen -// -// Created by Jan Benscheid on 16.04.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation - -/** - Node for creating a lofted solid. - */ -class ARPLoft: ARPGeomNode { - - var profiles: [ARPPath] - - init(profiles: [ARPPath]) throws { - - self.profiles = profiles - - super.init(pivotChild: profiles[0]) - - for profile in profiles { - self.content.addChildNode(profile) - } - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func addProfile(_ profile: ARPPath) { - profiles.append(profile) - content.addChildNode(profile) - profile.isHidden = true - rebuild() - } - - override func build() throws -> OCCTReference { - let ref = try? OCCTAPI.shared.loft(profiles: profiles.map({ $0.occtReference! })) - - if let r = ref { - OCCTAPI.shared.setPivotOf(handle: r, pivot: pivotChild.worldTransform) - } - - return ref ?? "" - } -} diff --git a/iOS App/ARPen/Modeling/GeometryNodes/ARPNode.swift b/iOS App/ARPen/Modeling/GeometryNodes/ARPNode.swift deleted file mode 100755 index 2a9f5f7..0000000 --- a/iOS App/ARPen/Modeling/GeometryNodes/ARPNode.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// ARPNode.swift -// ARPen -// -// Created by Jan Benscheid on 15.02.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation - -/** - This is the base class for all Nodes which have no underlying representation in Open CASCADE (OCCT), e.g. (currently) `ARPPathNode`s. -*/ -class ARPNode: SCNNode { - - var highlighted: Bool = false { - didSet { - updateHighlightedState() - } - } - - var selected: Bool = false { - didSet { - updateSelectedState() - } - } - - var visited: Bool = false { - didSet { - updateVisitedState() - } - } - - func isRootNode() -> Bool { - return (parent as? ARPNode) == nil - } - - func updateHighlightedState() {} - - func updateSelectedState() {} - - func updateVisitedState() {} - - func applyTransform() {} -} diff --git a/iOS App/ARPen/Modeling/GeometryNodes/ARPPath.swift b/iOS App/ARPen/Modeling/GeometryNodes/ARPPath.swift deleted file mode 100755 index 0ebfc00..0000000 --- a/iOS App/ARPen/Modeling/GeometryNodes/ARPPath.swift +++ /dev/null @@ -1,238 +0,0 @@ -// -// ARPPath.swift -// ARPen -// -// Created by Jan Benscheid on 25.03.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation - -enum CornerStyle: Int32 { - case sharp = 1, round = 2 -} - -/** - A node of the path - */ -class ARPPathNode: ARPNode { - /* - static let highlightAnimation = SCNAction.customAction(duration: 2*Double.pi, action: { (node, elapsedTime) in - let rgb = (sin(elapsedTime*2)+1) / 2 - let color = UIColor(red: rgb, green: rgb, blue: rgb, alpha: 1) - node.geometryNode.geometry?.firstMaterial?.emission.contents = color - })*/ - - static let fixAnimationDuration: Double = 0.3 - static let fixAnimation = SCNAction.customAction(duration: fixAnimationDuration, action: { (node, elapsedTime) in - let scale = 1 + sin((elapsedTime / CGFloat(ARPPathNode.fixAnimationDuration))*CGFloat.pi) * 1.5 - node.scale = SCNVector3(scale, scale, scale) - }) - - /// If points are this close to another, they will be considered the same point. - static let samePointTolerance: Float = 0.001 - - static let radius: CGFloat = 0.002 - - static let highlightScale: Float = 2 - static let highlightColor: UIColor = UIColor.white - - let sharpColor = UIColor.red - let roundColor = UIColor.blue - - let geometryNode: SCNNode = SCNNode() - - var cornerStyle = CornerStyle.sharp { - didSet { - updateCornerStyle() - } - } - - /// `active` is false if the node is not yet fixed and the pen is not detected. - var active = true { - didSet { - updateActiveState() - } - } - - /// `fixed` is false if the node is the one which is currently not yet placed. - var fixed = false { - didSet { - updateFixedState() - } - } - - convenience init(_ x: Float, _ y: Float, _ z: Float, cornerStyle: CornerStyle = CornerStyle.sharp) { - self.init(SCNVector3(x, y, z), cornerStyle: cornerStyle) - } - - convenience init(_ x: Float, _ y: Float, _ z: Float, cornerStyle: CornerStyle = CornerStyle.sharp, initFixed: Bool = false) { - self.init(SCNVector3(x, y, z), cornerStyle: cornerStyle) - self.fixed = initFixed - } - - init(_ position: SCNVector3, cornerStyle: CornerStyle = CornerStyle.sharp) { - super.init() - self.addChildNode(geometryNode) - self.geometryNode.geometry = SCNSphere(radius: ARPPathNode.radius) - // Don't shade path nodes. This was just a design decision. - self.geometryNode.geometry?.firstMaterial?.lightingModel = .constant - self.geometryNode.geometry?.firstMaterial?.emission.contents = ARPPathNode.highlightColor - self.geometryNode.geometry?.firstMaterial?.emission.intensity = 0 - self.cornerStyle = cornerStyle - updateCornerStyle() - updateFixedState() - self.position = position - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func applyTransform() { - (parent?.parent as? ARPGeomNode)?.rebuild() - } - - func updateCornerStyle() { - self.geometryNode.geometry?.firstMaterial?.diffuse.contents = self.cornerStyle == .sharp ? sharpColor : roundColor - } - - func updateFixedState() { - if self.fixed { - self.active = true - self.geometryNode.runAction(ARPPathNode.fixAnimation) - } - } - - func updateActiveState() { - if self.active { - self.isHidden = false - } else { - self.isHidden = true - } - } - - override func updateHighlightedState() { - if self.highlighted { - self.geometryNode.scale = SCNVector3(ARPPathNode.highlightScale, ARPPathNode.highlightScale, ARPPathNode.highlightScale) - } else { - self.geometryNode.scale = SCNVector3(1, 1, 1) - } - } - - override func updateSelectedState() { - if self.selected { - self.geometryNode.geometry?.firstMaterial?.emission.intensity = 1 - } else { - self.geometryNode.geometry?.firstMaterial?.emission.intensity = 0 - } - } -} - -/** - A path which can be used e.g. as a profile for all types of extrusions (if closed) or as as spine for sweeps. - */ -class ARPPath: ARPGeomNode { - - static let finalizeAnimationDuration: Double = 0.3 - /// Blinking when a path is finished - static let finalizeAnimation = SCNAction.customAction(duration: finalizeAnimationDuration, action: { (node, elapsedTime) in - (node as SCNNode).isHidden = Int((elapsedTime / CGFloat(ARPPath.finalizeAnimationDuration)) * 3).isMultiple(of: 2) - }) - - let color = UIColor.red - - var points: [ARPPathNode] = [ARPPathNode]() - var closed: Bool = false - - init(points: [ARPPathNode], closed: Bool) { - self.closed = closed - - for point in points { - self.points.append(point) - } - - super.init(pivotChild: points[0]) - - for point in self.points { - self.content.addChildNode(point) - } - self.content.isHidden = false - self.lineColor = self.closed ? UIColor.green : UIColor.red - } - - func appendPoint(_ point: ARPPathNode, at position: Int = -1) { - if position >= 0 { - self.points.insert(point, at: position) - } else { - self.points.append(point) - } - self.content.addChildNode(point) - } - - func removeLastPoint() { - let removed = self.points.removeLast() - removed.removeFromParentNode() - } - - /// Returns 0 of the points are at the same location, 1 if they are on the same line, 2 if they are on the same plane, 3 otherwise, given a certain tolerance. - func coincidentDimensions() -> Int { - return OCCTAPI.shared.conincidentDimensions(getPointsAsVectors()) - } - - /// Projects all points of the path onto a best fitting plane. - func flatten() { - for (old, new) in zip(points, OCCTAPI.shared.flattened(getPointsAsVectors())) { - old.worldPosition = new - } - self.rebuild() - } - - func removeNonFixedPoints() { - points.filter({ !$0.fixed }).forEach({ $0.removeFromParentNode() }) - points.removeAll(where: { !$0.fixed }) - } - - func getNonFixedPoint() -> ARPPathNode? { - return points.last(where: { !$0.fixed }) - } - - func getPointsAsVectors() -> [SCNVector3] { - return points.map { $0.worldPosition } - } - - func getCenter() -> SCNVector3 { - let sum = points.map { $0.worldPosition }.reduce(SCNVector3(0,0,0), { $0 + $1 }) - return sum / Float(points.count) - } - - /// Perform SVD on the points in the path and return the first principal component. - func getPC1() -> SCNVector3 { - return OCCTAPI.shared.pc1(getPointsAsVectors()) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func updateVisitedState() {} - - override func build() throws -> OCCTReference { - var calcClosed = closed - if let first = points.first, let last = points.last, - first.position.distance(vector: last.position) < ARPPathNode.samePointTolerance { - calcClosed = true - } - let positions = points.compactMap { (!calcClosed || $0.fixed) && $0.active ? $0.worldPosition : nil } - let corners = points.compactMap { (!calcClosed || $0.fixed) && $0.active ? $0.cornerStyle : nil } - - let ref = try? OCCTAPI.shared.createPath(points: positions, corners: corners, closed: calcClosed) - if let r = ref { - OCCTAPI.shared.setPivotOf(handle: r, pivot: pivotChild.worldTransform) - } - - self.lineColor = calcClosed ? UIColor.green : UIColor.red - - return ref ?? "" - } -} diff --git a/iOS App/ARPen/Modeling/GeometryNodes/ARPRevolution.swift b/iOS App/ARPen/Modeling/GeometryNodes/ARPRevolution.swift deleted file mode 100755 index 4ff0ef2..0000000 --- a/iOS App/ARPen/Modeling/GeometryNodes/ARPRevolution.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// ARPRevolution.swift -// ARPen -// -// Created by Jan Benscheid on 13.04.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation - -/** - Node for creating a revolved solid. - */ -class ARPRevolution: ARPGeomNode { - - var profile: ARPPath - var axis: ARPPath - - // **** For user study **** - var radiusTop: Float! - var radiusBottom: Float! - var angle: Float! - // ************************ - - init(profile: ARPPath, axis: ARPPath) throws { - - self.profile = profile - self.axis = axis - - super.init(pivotChild: axis) - - self.content.addChildNode(profile) - self.content.addChildNode(axis) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func build() throws -> OCCTReference { - - // Derive rotation axis from first and last point of `axis` path - var revAxis = Axis() - revAxis.direction = (axis.points.last!.worldPosition - axis.points.first!.worldPosition).normalized() - revAxis.position = axis.points.first!.worldPosition - - var points = profile.points.map({ ARPPathNode($0.worldPosition, cornerStyle: $0.cornerStyle) }) - points.first!.cornerStyle = .sharp - points.last!.cornerStyle = .sharp - // Create additional nodes which are the first- and last node of the profile, projected onto the axis, in order to create a closed volume. - let top = ARPPathNode(revAxis.projectOnto(point: points.last!.worldPosition)) - let bottom = ARPPathNode(revAxis.projectOnto(point: points.first!.worldPosition)) - - // **** For user study **** - self.radiusTop = points.last!.worldPosition.distance(vector: top.worldPosition) - self.radiusBottom = points.first!.worldPosition.distance(vector: bottom.worldPosition) - self.angle = acos(revAxis.direction.y) * 180 / Float.pi - // ************************ - - points.append(top) - points.insert(bottom, at: 0) - - for p in points { - p.fixed = true - } - - let closedProfile = ARPPath(points: points, closed: true) - closedProfile.flatten() - - // Adjust revolution axis to newly flattened points - revAxis.direction = (closedProfile.points.last!.worldPosition - closedProfile.points.first!.worldPosition).normalized() - revAxis.position = closedProfile.points.first!.worldPosition - revAxis.direction /// It's necessary to shift this point because somehow revolving around a point which exists inside the path yields a construction error - - let ref = try? OCCTAPI.shared.revolve(profile: closedProfile.occtReference!, aroundAxis: revAxis) - - if let r = ref { - OCCTAPI.shared.setPivotOf(handle: r, pivot: pivotChild.worldTransform) - } - - return ref ?? "" - } -} diff --git a/iOS App/ARPen/Modeling/GeometryNodes/ARPSphere.swift b/iOS App/ARPen/Modeling/GeometryNodes/ARPSphere.swift deleted file mode 100755 index 832b4fe..0000000 --- a/iOS App/ARPen/Modeling/GeometryNodes/ARPSphere.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// ARPSphere.swift -// ARPen -// -// Created by Jan Benscheid on 15.02.19. -// Copyright © 2018 RWTH Aachen. All rights reserved. -// - -import Foundation - -class ARPSphere: ARPGeomNode { - - var radius: Double = 0.5 - - override init() { - super.init() - } - - init(radius: Double) { - self.radius = radius - super.init() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func build() throws -> OCCTReference { - return try OCCTAPI.shared.createSphere(radius: radius) - } -} diff --git a/iOS App/ARPen/Modeling/GeometryNodes/ARPSweep.swift b/iOS App/ARPen/Modeling/GeometryNodes/ARPSweep.swift deleted file mode 100755 index ef331dd..0000000 --- a/iOS App/ARPen/Modeling/GeometryNodes/ARPSweep.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// ARPSweep.swift -// ARPen -// -// Created by Jan Benscheid on 26.03.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// -import Foundation - -/** - Node for creating a swept solid. - */ -class ARPSweep: ARPGeomNode { - - var profile: ARPPath - var path: ARPPath - - init(profile: ARPPath, path: ARPPath) throws { - - self.profile = profile - self.path = path - - super.init(pivotChild: profile) - - self.content.addChildNode(profile) - self.content.addChildNode(path) - - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func build() throws -> OCCTReference { - let ref = try? OCCTAPI.shared.sweep(profile: profile.occtReference!, path: path.occtReference!) - - if let r = ref { - OCCTAPI.shared.setPivotOf(handle: r, pivot: pivotChild.worldTransform) - } - - return ref ?? "" - } -} diff --git a/iOS App/ARPen/Modeling/OCCTAPI/OCCTAPI.swift b/iOS App/ARPen/Modeling/OCCTAPI/OCCTAPI.swift deleted file mode 100755 index 335bba7..0000000 --- a/iOS App/ARPen/Modeling/OCCTAPI/OCCTAPI.swift +++ /dev/null @@ -1,190 +0,0 @@ -// -// OCCTAPI.swift -// ARPen -// -// Created by Jan Benscheid on 08.02.19. -// Copyright © 2019 Jan Benscheid. All rights reserved. -// - -import Foundation -import SceneKit - -typealias OCCTReference = String - -enum OCCTError: Error { - case couldNotCreateGeometry -} - -/** - This class is the Swift API to the low-level geometry manipulation code. Its main purpose is to convert between C types and Swift types. - Unless a new functionality is needed, I suggest to use the functions provided in this class for all geometry manipulations. - */ -class OCCTAPI { - - static let shared = OCCTAPI() - - let occt:OCCT = OCCT() - - func createSphere(radius:Double) throws -> OCCTReference { - if let cString = occt.createSphere(radius) { - let ref = OCCTReference(cString: cString) - return ref - } else { - throw OCCTError.couldNotCreateGeometry - } - } - - func createBox(width:Double, height:Double, length:Double) throws -> OCCTReference { - if let cString = occt.createBox(width, height: height, length: length) { - let ref = OCCTReference(cString: cString) - return ref - } else { - throw OCCTError.couldNotCreateGeometry - } - } - - func createCylinder(radius:Double, height:Double) throws -> OCCTReference { - if let cString = occt.createCylinder(radius, height: height) { - let ref = OCCTReference(cString: cString) - return ref - } else { - throw OCCTError.couldNotCreateGeometry - } - } - - func createPath(points:[SCNVector3], corners:[CornerStyle], closed:Bool) throws -> OCCTReference { - if let cString = occt.createPath(points, length:Int32(points.count), corners: corners.map({ $0.rawValue }), closed:closed) { - let ref = OCCTReference(cString: cString) - return ref - } else { - throw OCCTError.couldNotCreateGeometry - } - } - - func boolean(from a: OCCTReference, cut b: OCCTReference) throws -> OCCTReference { - if let difference = occt.booleanCut(a, subtract: b) { - let ref = OCCTReference(cString: difference) - return ref - } else { - throw OCCTError.couldNotCreateGeometry - } - } - - func boolean(join a: OCCTReference, with b: OCCTReference) throws -> OCCTReference { - if let sum = occt.booleanJoin(a, with: b) { - let ref = OCCTReference(cString: sum) - return ref - } else { - throw OCCTError.couldNotCreateGeometry - } - } - - func boolean(intersect a: OCCTReference, with b: OCCTReference) throws -> OCCTReference { - if let sum = occt.booleanIntersect(a, with: b) { - let ref = OCCTReference(cString: sum) - return ref - } else { - throw OCCTError.couldNotCreateGeometry - } - } - - func sweep(profile: OCCTReference, path: OCCTReference) throws -> OCCTReference { - if let sum = occt.sweep(profile, along: path) { - let ref = OCCTReference(cString: sum) - return ref - } else { - throw OCCTError.couldNotCreateGeometry - } - } - - func revolve(profile: OCCTReference, aroundAxis: Axis) throws -> OCCTReference { - if let sum = occt.revolve(profile, aroundAxis: aroundAxis.position, withDirection: aroundAxis.direction) { - let ref = OCCTReference(cString: sum) - return ref - } else { - throw OCCTError.couldNotCreateGeometry - } - } - - func loft(profiles: [OCCTReference]) throws -> OCCTReference { - if let sum = occt.loft(profiles as [Any], length: Int32(profiles.count)) { - let ref = OCCTReference(cString: sum) - return ref - } else { - throw OCCTError.couldNotCreateGeometry - } - } - - func transform(handle: OCCTReference, transformation: SCNMatrix4) { - occt.setTransformOf(handle, transformation: transformation) - } - - func setPivotOf(handle: OCCTReference, pivot: SCNMatrix4) { - occt.setPivotOf(handle, pivot: pivot) - } - - /// Calculates a new pivot point for shape based on the center of its bounding box, moves it there, and returns the coordinates. - func center(handle: OCCTReference) -> SCNVector3 { - return occt.center(handle); - } - - /// Returns the input points, projected onto a distance-minimizing common plane. - func flattened(_ of: [SCNVector3]) -> [SCNVector3] { - var array = [SCNVector3]() - let res = occt.flattened(of, ofLength: Int32(of.count)) - for i in 0.. SCNVector3 { - return occt.pc1(of: of, ofLength: Int32(of.count)) - } - - /// Calculates a least-squares fitting circle for the points and returns its center. - func circleCenter(_ of: [SCNVector3]) -> SCNVector3 { - return occt.circleCenter(of: of, ofLength: Int32(of.count)) - } - - /// Returns 0 of the points are at the same location, 1 if they are on the same line, 2 if they are on the same plane, 3 otherwise, given a certain tolerance. - func conincidentDimensions(_ of: [SCNVector3]) -> Int { - return Int(occt.coincidentDimensions(of: of, ofLength: Int32(of.count))) - } - - /// Returns a triangulated version of the shape as an `SCNGeometry` object. - func triangulate(handle: OCCTReference) -> SCNGeometry { - return occt.sceneKitMesh(of: handle) - } - - /// Returns the lines of the shape as an `SCNGeometry` object. - func wireframe(handle: OCCTReference) -> SCNGeometry { - return occt.sceneKitLines(of: handle) - } - - /// Returns the lines of the shape as an `SCNGeometry` object in the shape of cylindrical segments. - func tubeframe(handle: OCCTReference) -> SCNGeometry { - return occt.sceneKitTubes(of: handle) - } - - /// Exports a shape to stl under the given `filePath`. - func exportStl(handle: OCCTReference, filePath: URL) { - var fileName = filePath.absoluteString - - // C function 'fopen' used by OCCT does not work if path starts with "file:///private" - if fileName.starts(with: "file:///") { - fileName.removeFirst(8) - } - if fileName.starts(with: "private") { - fileName.removeFirst(7) - } - - occt.stl(of: handle, toFile: fileName.cString(using: String.Encoding.utf8)) - } - - /// Effectively deletes a shape's representation. - func free(handle: OCCTReference) { - occt.freeShape(handle) - } -} diff --git a/iOS App/ARPen/Modeling/Util.swift b/iOS App/ARPen/Modeling/Util.swift deleted file mode 100755 index c82a3f8..0000000 --- a/iOS App/ARPen/Modeling/Util.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// Util.swift -// ARPen -// -// Created by Jan Benscheid on 15.02.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation -import SceneKit - -class Util { - static func getUUID() -> String { - return NSUUID().uuidString - } -} - -extension float4x4 { - init(_ matrix: SCNMatrix4) { - self.init([ - float4(matrix.m11, matrix.m12, matrix.m13, matrix.m14), - float4(matrix.m21, matrix.m22, matrix.m23, matrix.m24), - float4(matrix.m31, matrix.m32, matrix.m33, matrix.m34), - float4(matrix.m41, matrix.m42, matrix.m43, matrix.m44) - ]) - } -} - -extension float4 { - init(_ vector: SCNVector4) { - self.init(vector.x, vector.y, vector.z, vector.w) - } - - init(_ vector: SCNVector3) { - self.init(vector.x, vector.y, vector.z, 1) - } -} - -extension SCNVector4 { - init(_ vector: float4) { - self.init(x: vector.x, y: vector.y, z: vector.z, w: vector.w) - } - - init(_ vector: SCNVector3) { - self.init(x: vector.x, y: vector.y, z: vector.z, w: 1) - } -} - -extension SCNVector3 { - init(_ vector: float4) { - self.init(x: vector.x / vector.w, y: vector.y / vector.w, z: vector.z / vector.w) - } -} - -func * (left: SCNMatrix4, right: SCNVector3) -> SCNVector3 { - let matrix = float4x4(left) - let vector = float4(right) - let result = matrix * vector - - return SCNVector3(result) -} - -struct Axis { - var position: SCNVector3 = SCNVector3(0, 0, 0) - var direction: SCNVector3 = SCNVector3(0, 1, 0) - - func projectOnto(point: SCNVector3) -> SCNVector3 { - return self.position + self.direction*(point - self.position).dot(vector: self.direction) - } -} diff --git a/iOS App/ARPen/MultipeerSession.swift b/iOS App/ARPen/MultipeerSession.swift deleted file mode 100644 index 2f559d7..0000000 --- a/iOS App/ARPen/MultipeerSession.swift +++ /dev/null @@ -1,103 +0,0 @@ -/* - A simple abstraction of the MultipeerConnectivity API as used in this app. - - Copyright © 2018 Apple Inc. - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -import MultipeerConnectivity - -/// - Tag: MultipeerSession -class MultipeerSession: NSObject { - static let serviceType = "ar-multi-sample" - - private let myPeerID = MCPeerID(displayName: UIDevice.current.name) - private var session: MCSession! - private var serviceAdvertiser: MCNearbyServiceAdvertiser! - private var serviceBrowser: MCNearbyServiceBrowser! - - private let receivedDataHandler: (Data, MCPeerID) -> Void - - /// - Tag: MultipeerSetup - init(receivedDataHandler: @escaping (Data, MCPeerID) -> Void ) { - self.receivedDataHandler = receivedDataHandler - - super.init() - - session = MCSession(peer: myPeerID, securityIdentity: nil, encryptionPreference: .required) - session.delegate = self - - serviceAdvertiser = MCNearbyServiceAdvertiser(peer: myPeerID, discoveryInfo: nil, serviceType: MultipeerSession.serviceType) - serviceAdvertiser.delegate = self - serviceAdvertiser.startAdvertisingPeer() - - serviceBrowser = MCNearbyServiceBrowser(peer: myPeerID, serviceType: MultipeerSession.serviceType) - serviceBrowser.delegate = self - serviceBrowser.startBrowsingForPeers() - } - - func sendToAllPeers(_ data: Data) { - do { - try session.send(data, toPeers: session.connectedPeers, with: .reliable) - } catch { - print("error sending data to peers: \(error.localizedDescription)") - } - } - - var connectedPeers: [MCPeerID] { - return session.connectedPeers - } -} - -extension MultipeerSession: MCSessionDelegate { - - func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) { - // not used - } - - func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { - receivedDataHandler(data, peerID) - } - - func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) { - fatalError("This service does not send/receive streams.") - } - - func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) { - fatalError("This service does not send/receive resources.") - } - - func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) { - fatalError("This service does not send/receive resources.") - } - -} - -extension MultipeerSession: MCNearbyServiceBrowserDelegate { - - /// - Tag: FoundPeer - public func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String: String]?) { - // Invite the new peer to the session. - browser.invitePeer(peerID, to: session, withContext: nil, timeout: 10) - } - - public func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) { - // This app doesn't do anything with non-invited peers, so there's nothing to do here. - } - -} - -extension MultipeerSession: MCNearbyServiceAdvertiserDelegate { - - /// - Tag: AcceptInvite - func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) { - // Call handler to accept invitation and join the session. - invitationHandler(true, self.session) - } - -} diff --git a/iOS App/ARPen/Open CASCADE/Builders.h b/iOS App/ARPen/Open CASCADE/Builders.h deleted file mode 100755 index 7f4c878..0000000 --- a/iOS App/ARPen/Open CASCADE/Builders.h +++ /dev/null @@ -1,46 +0,0 @@ -// -// Builders.h -// ARPen -// -// Created by Jan Benscheid on 27.09.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -#ifndef Builders_h -#define Builders_h - -#include - -@interface Builders : NSObject - -+ (const char *) createSphere:(double) radius; -+ (const char *) createBox:(double) width - height:(double) height - length:(double) length; -+ (const char *) createCylinder:(double) radius - height:(double) height; -+ (const char *) createPath:(const SCNVector3 []) points - length:(int) length - corners:(const int []) corners - closed:(bool) closed; - - -+ (const char *) sweep:(const char *) profile - along:(const char *) path; -+ (const char *) revolve:(const char *) profile - aroundAxis:(SCNVector3) axisPosition - withDirection:(SCNVector3) axisDirection; -+ (const char *) loft:(NSArray *) profiles - length:(int) length; - - -+ (const char *) booleanCut:(const char *) a - subtract:(const char *) b; -+ (const char *) booleanJoin:(const char *) a - with:(const char *) b; -+ (const char *) booleanIntersect:(const char *) a - with:(const char *) b; - -@end - -#endif /* Builders_h */ diff --git a/iOS App/ARPen/Open CASCADE/Builders.mm b/iOS App/ARPen/Open CASCADE/Builders.mm deleted file mode 100755 index 2cfc863..0000000 --- a/iOS App/ARPen/Open CASCADE/Builders.mm +++ /dev/null @@ -1,283 +0,0 @@ -// -// Builders.m -// ARPen -// -// Created by Jan Benscheid on 27.09.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// -#import - -#include "Builders.h" - -#include "Registry.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -@implementation Builders : NSObject - -+ (const char *) createBox:(double) width - height:(double) height - length:(double) length -{ - gp_Pnt corner = gp_Pnt(-width/2, -height/2, -length/2); - TopoDS_Shape aBox = BRepPrimAPI_MakeBox(corner, width, height, length); - TCollection_AsciiString key = [Registry storeInRegistry:aBox]; - - return [Registry toHeapCString:key]; -} - -+ (const char *) createCylinder:(double) radius - height:(double) height -{ - TopoDS_Shape aCylinder = BRepPrimAPI_MakeCylinder(radius, height); - - gp_Trsf rotate = gp_Trsf(); - rotate.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)), M_PI/2); - - gp_Trsf shift = gp_Trsf(); - shift.SetTranslation(gp_Vec(0, 0, -height/2)); - - BRepBuilderAPI_Transform shiftTransform = BRepBuilderAPI_Transform(aCylinder, shift, Standard_True); - TopoDS_Shape aShiftedCylinder = shiftTransform.Shape(); - // Y and Z are swapped in OCCT, therefore we rotate the cylinder. - BRepBuilderAPI_Transform rotateTransform = BRepBuilderAPI_Transform(aShiftedCylinder, rotate, Standard_True); - TopoDS_Shape aRotatedCylinder = rotateTransform.Shape(); - TCollection_AsciiString key = [Registry storeInRegistry:aRotatedCylinder]; - - return [Registry toHeapCString:key]; -} - -+ (const char *) createSphere:(double) radius -{ - TopoDS_Shape aSphere = BRepPrimAPI_MakeSphere(radius); - TCollection_AsciiString key = [Registry storeInRegistry:aSphere]; - - return [Registry toHeapCString:key]; -} - -+ (const char *) createPath:(const SCNVector3 []) points - length:(int) length - corners:(const int []) corners - closed:(bool) closed -{ - BRepBuilderAPI_MakeWire makeWire; - - TColgp_SequenceOfPnt curvePoints; - - int startAt = 0; - bool onlyRoundCorners = true; - - if (closed) { - // A little trick to make curvature continuity at the start/endpoint easier: - // Find out if the path consists purely of round corners. In that case OCCT can handle this for us. - // Otherwise, choose a sharp corner to start with, so that there is no round corner at the seam. - for (int i = 0; i < length; i++) { - if (corners[i] == 1) { - onlyRoundCorners = false; - startAt = i; - break; - } - } - } - - // If the path is closed and there is a sharp corner at the seam, we need to make one additional step to add the closing edge. - // Remember that we always start/end at a sharp corner if there is one. - int overshoot = (closed && !onlyRoundCorners) ? 1 : 0; - - bool curveMode = false; - for (int i = 1; i < length + overshoot; i++) { - - int ci = (startAt + i) % length; - int pi = (startAt + i-1) % length; - gp_Pnt currPoint(points[ci].x, points[ci].y, points[ci].z); - gp_Pnt prevPoint(points[pi].x, points[pi].y, points[pi].z); - int currCorner = corners[ci]; - int prevCorner = corners[pi]; - - if (currCorner == 1 && prevCorner == 1) { - if (!prevPoint.IsEqual(currPoint, 0.0001)) { - TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(prevPoint, currPoint); - makeWire.Add(edge); - } - } - - // A curve has started - if (!curveMode && (prevCorner == 2 || currCorner == 2)) { - curvePoints = TColgp_SequenceOfPnt(); - curvePoints.Append(prevPoint); - curveMode = true; - } - - // A curve continues - if (curveMode) { - curvePoints.Append(currPoint); - } - - // A curve has ended - if (curveMode && (currCorner != 2 || i == length+overshoot-1)) { - curveMode = false; - - int segmentLength = curvePoints.Length(); - Handle(TColgp_HArray1OfPnt) segmentPoints = new TColgp_HArray1OfPnt(1, segmentLength); - TColgp_SequenceOfPnt::Iterator iter = TColgp_SequenceOfPnt::Iterator(curvePoints); - int j = 1; - for (; iter.More(); iter.Next()) { - segmentPoints->SetValue(j++, iter.Value()); - } - - try { - OCC_CATCH_SIGNALS - - GeomAPI_Interpolate interpolate = GeomAPI_Interpolate(segmentPoints, closed && onlyRoundCorners, 0.001); - interpolate.Perform(); - Handle(Geom_BSplineCurve) curve = interpolate.Curve(); - BRepBuilderAPI_MakeEdge makeEdge = BRepBuilderAPI_MakeEdge(curve); - TopoDS_Edge edge = makeEdge.Edge(); - makeWire.Add(edge); - } catch (...) {} - } - } - - TopoDS_Shape wire; - - try { - OCC_CATCH_SIGNALS - wire = makeWire.Wire(); - } catch (...) { - if (length == 1) { - gp_Pnt point(points[0].x, points[0].y, points[0].z); - wire = BRepBuilderAPI_MakeVertex(point); - } else { - wire = TopoDS_Wire(); - } - } - - TCollection_AsciiString key = [Registry storeInRegistry:wire]; - - return [Registry toHeapCString:key]; -} - -+ (const char *) sweep:(const char *) profile - along:(const char *) path; -{ - TCollection_AsciiString keyProfile = TCollection_AsciiString(profile); - TCollection_AsciiString keyPath = TCollection_AsciiString(path); - - TopoDS_Shape shapeProfile = [Registry retrieveFromRegistryTransformed: keyProfile]; - TopoDS_Shape shapePath = [Registry retrieveFromRegistryTransformed: keyPath]; - - TopoDS_Face profileFace = BRepBuilderAPI_MakeFace(TopoDS::Wire(shapeProfile)); - - TopoDS_Shape solid = BRepOffsetAPI_MakePipe(TopoDS::Wire(shapePath), profileFace); - - return [Registry storeInRegistryWithCString:solid]; -} - -+ (const char *) revolve:(const char *) profile - aroundAxis:(SCNVector3) axisPosition - withDirection:(SCNVector3) axisDirection -{ - TCollection_AsciiString keyProfile = TCollection_AsciiString(profile); - - TopoDS_Shape shapeProfile = [Registry retrieveFromRegistryTransformed: keyProfile]; - TopoDS_Face profileFace = BRepBuilderAPI_MakeFace(TopoDS::Wire(shapeProfile)); - - gp_Ax1 axis = gp_Ax1(gp_Pnt(axisPosition.x, axisPosition.y, axisPosition.z), - gp_Dir(axisDirection.x, axisDirection.y, axisDirection.z)); - - - BRepPrimAPI_MakeRevol makeRevol = BRepPrimAPI_MakeRevol(profileFace, axis); - makeRevol.Build(); - TopoDS_Shape revolution = makeRevol.Shape(); - - return [Registry storeInRegistryWithCString:revolution]; -} - -+ (const char *) loft:(NSArray *) profiles - length:(int) length; -{ - BRepOffsetAPI_ThruSections thruSections = BRepOffsetAPI_ThruSections(Standard_True); - for (int i = 0; i < length; i++) { - NSString *profile = profiles[i]; - TCollection_AsciiString keyProfile = TCollection_AsciiString(profile.UTF8String); - TopoDS_Shape shapeProfile = [Registry retrieveFromRegistryTransformed: keyProfile]; - if (shapeProfile.ShapeType() == TopAbs_WIRE) { - thruSections.AddWire(TopoDS::Wire(shapeProfile)); - } else if (shapeProfile.ShapeType() == TopAbs_VERTEX) { - thruSections.AddVertex(TopoDS::Vertex(shapeProfile)); - } - } - - TopoDS_Shape loft = thruSections.Shape(); - - return [Registry storeInRegistryWithCString:loft]; -} - - - -+ (const char *) booleanCut:(const char *) a - subtract:(const char *) b; -{ - TCollection_AsciiString keyA = TCollection_AsciiString(a); - TCollection_AsciiString keyB = TCollection_AsciiString(b); - - TopoDS_Shape shapeA = [Registry retrieveFromRegistryTransformed: keyA]; - TopoDS_Shape shapeB = [Registry retrieveFromRegistryTransformed: keyB]; - - TopoDS_Shape difference = BRepAlgoAPI_Cut(shapeA, shapeB); - - return [Registry storeInRegistryWithCString:difference]; -} - - -+ (const char *) booleanJoin:(const char *) a - with:(const char *) b -{ - TCollection_AsciiString keyA = TCollection_AsciiString(a); - TCollection_AsciiString keyB = TCollection_AsciiString(b); - - TopoDS_Shape shapeA = [Registry retrieveFromRegistryTransformed: keyA]; - TopoDS_Shape shapeB = [Registry retrieveFromRegistryTransformed: keyB]; - - TopoDS_Shape sum = BRepAlgoAPI_Fuse(shapeA, shapeB); - - return [Registry storeInRegistryWithCString:sum]; -} - -+ (const char *) booleanIntersect:(const char *) a - with:(const char *) b -{ - TCollection_AsciiString keyA = TCollection_AsciiString(a); - TCollection_AsciiString keyB = TCollection_AsciiString(b); - - TopoDS_Shape shapeA = [Registry retrieveFromRegistryTransformed: keyA]; - TopoDS_Shape shapeB = [Registry retrieveFromRegistryTransformed: keyB]; - - TopoDS_Shape sum = BRepAlgoAPI_Common(shapeA, shapeB); - - return [Registry storeInRegistryWithCString:sum]; -} - -@end diff --git a/iOS App/ARPen/Open CASCADE/Helpers.h b/iOS App/ARPen/Open CASCADE/Helpers.h deleted file mode 100755 index 09cd4ee..0000000 --- a/iOS App/ARPen/Open CASCADE/Helpers.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// Helpers.h -// ARPen -// -// Created by Jan Benscheid on 27.09.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -#ifndef Helpers_h -#define Helpers_h - -#include - -#include -#include - -@interface Helpers : NSObject - -/// For transformations which include scaling. Warning: As scaling was not possible for the user, this function has not been tested since early in the development and might contain errors. -+ (void) setGTransformOf:(const char *) label - affine:(SCNMatrix4) affine - translation:(SCNVector3) translation; -+ (void) setTransformOf:(const char *) label - transformation:(SCNMatrix4) mat; -+ (void) setPivotOf:(const char *) label - pivot:(SCNMatrix4) mat; - -/// Calculates a new pivot point for shape based on the center of its bounding box, moves it there, and returns the coordinates. -+ (SCNVector3) center:(const char *) label; - -/// Returns the input points, projected onto a distance-minimizing common plane. -+ (const SCNVector3 *) flattened:(const SCNVector3 []) points - ofLength:(int) length; - -/// Calculates a least-squares fitting circle for the points and returns its center. -+ (SCNVector3) circleCenterOf:(const SCNVector3 []) points - ofLength:(int) length; -/// Returns the normal vector of the plane fitted through the points (or in other words, the first principal component). -+ (SCNVector3) pc1Of:(const SCNVector3 []) points - ofLength:(int) length; -/// Returns 0 of the points are at the same location, 1 if they are on the same line, 2 if they are on the same plane, 3 otherwise, given a certain tolerance. -+ (int) coincidentDimensionsOf:(const SCNVector3 [])points - ofLength:(int)length; - -/// Calculates a distance-minimizing common plane for the input points. -+ (gp_Pln) getFittingPlane:(const TColgp_Array1OfPnt&) ocPoints; -/// Convert points from SCNVector3 to gp_Pnt. -+ (TColgp_Array1OfPnt) convertPoints:(const SCNVector3 []) points - ofLength:(int) length; - - -@end - -#endif /* Helpers_h */ diff --git a/iOS App/ARPen/Open CASCADE/Helpers.mm b/iOS App/ARPen/Open CASCADE/Helpers.mm deleted file mode 100755 index d5b4b0d..0000000 --- a/iOS App/ARPen/Open CASCADE/Helpers.mm +++ /dev/null @@ -1,222 +0,0 @@ -// -// Helpers.mm -// ARPen -// -// Created by Jan Benscheid on 27.09.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// -#import - -#include "Helpers.h" - -#include "Registry.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -@implementation Helpers : NSObject - -/// Describes the tolerance for considering a set of points co-planar/co-linear/the same. -static const double flatteningTolerance = 0.01; - -/// For transformations which include scaling. Warning: As scaling was not possible for the user, this function has not been tested since early in the development and might contain errors. -+ (void) setGTransformOf:(const char *) label - affine:(SCNMatrix4) affine - translation:(SCNVector3) translation { - - TCollection_AsciiString key(label); - TopoDS_Shape shape = [Registry retrieveFromRegistry:key]; - gp_Mat affineMat(affine.m11, affine.m13, affine.m12, affine.m21, affine.m23, affine.m22, affine.m31, affine.m33, affine.m32); - gp_XYZ transVec(translation.x, translation.z, translation.y); - gp_GTrsf trans(affineMat, transVec); - - BRepBuilderAPI_GTransform builder(shape, trans, Standard_True); - TopoDS_Shape newShape = builder.ModifiedShape(shape); - - [Registry deleteFromRegistry:key]; - [Registry storeInRegistry:newShape withKey:key]; -} - -+ (void) setTransformOf:(const char *) label - transformation:(SCNMatrix4) mat { - TCollection_AsciiString key(label); - gp_Trsf trans; - trans.SetValues(mat.m11, mat.m21, mat.m31, mat.m41, mat.m12, mat.m22, mat.m32, mat.m42, mat.m13, mat.m23, mat.m33, mat.m43); - [Registry storeInTransformRegistry:trans withKey:key]; -} - -+ (void) setPivotOf:(const char *) label - pivot:(SCNMatrix4) mat { - - TCollection_AsciiString key = TCollection_AsciiString(label); - TopoDS_Shape shape = [Registry retrieveFromRegistry: key]; - - gp_Trsf trans; - trans.SetValues(mat.m11, mat.m21, mat.m31, mat.m41, mat.m12, mat.m22, mat.m32, mat.m42, mat.m13, mat.m23, mat.m33, mat.m43); - gp_Trsf transInv = trans.Inverted(); - BRepBuilderAPI_Transform trsf(shape, transInv); - - TopoDS_Shape newShape = trsf.Shape(); - [Registry storeInRegistry:newShape withKey:key]; - [Registry storeInTransformRegistry:trans withKey:key]; -} - -/// Calculates a new pivot point for shape based on the center of its bounding box, moves it there, and returns the coordinates. -+ (SCNVector3) center:(const char *) label { - - TCollection_AsciiString key = TCollection_AsciiString(label); - TopoDS_Shape shape = [Registry retrieveFromRegistry: key]; - - Bnd_Box B; - BRepBndLib::Add(shape, B, Standard_False); - Standard_Real Xmin, Ymin, Zmin, Xmax, Ymax, Zmax; - B.Get(Xmin, Ymin, Zmin, Xmax, Ymax, Zmax); - - SCNVector3 newCenter = SCNVector3Make((float) ((Xmin+Xmax)/2), (float) ((Ymin+Ymax)/2), (float) ((Zmin+Zmax)/2)); - - gp_Trsf move; - move.SetTranslation(gp_Vec(-newCenter.x, -newCenter.y, -newCenter.z)); - BRepBuilderAPI_Transform trsf(shape, move); - - TopoDS_Shape newShape = trsf.Shape(); - - [Registry storeInRegistry:newShape withKey:key]; - - return newCenter; -} - -/// Returns the input points, projected onto a distance-minimizing common plane. -+ (const SCNVector3 *) flattened:(const SCNVector3 []) points - ofLength:(int) length -{ - TColgp_Array1OfPnt ocPoints = [self convertPoints:points ofLength:length]; - - gp_Pln pln = [self getFittingPlane:ocPoints]; - Handle(Geom_Plane) plane = new Geom_Plane(pln); - - for (int i = 1; i <= length; i++) { - GeomAPI_ProjectPointOnSurf proj = GeomAPI_ProjectPointOnSurf(ocPoints.Value(i), plane); - ocPoints.SetValue(i, proj.Point(1)); - } - - SCNVector3 *res = new SCNVector3[length]; - for (int i = 1; i <= length; i++) { - gp_Pnt pt = ocPoints.Value(i); - res[i-1] = {(float)pt.X(), (float)pt.Y(), (float)pt.Z()}; - } - - return res; -} - -/// Calculates a least-squares fitting circle for the points and returns its center. -+ (SCNVector3) circleCenterOf:(const SCNVector3 []) points - ofLength:(int) length -{ - TColgp_Array1OfPnt ocPoints = [self convertPoints:points ofLength:length]; - - gp_Pln pln = [self getFittingPlane:ocPoints]; - Handle(Geom_Plane) plane = new Geom_Plane(pln); - - math_Matrix M = math_Matrix(1, length, 1, 3); - math_Vector b = math_Vector(1, length); - - for (int i = 1; i <= length; i++) { - GeomAPI_ProjectPointOnSurf proj = GeomAPI_ProjectPointOnSurf(ocPoints.Value(i), plane); - Standard_Real u, v; - proj.Parameters(1, u, v); - M(i, 1) = u; - M(i, 2) = v; - M(i, 3) = 1; - b(i) = u*u + v*v; - } - - math_GaussLeastSquare gls = math_GaussLeastSquare(M); - math_Vector x = math_Vector(1,3); - x(1) = pln.Location().X(); - x(2) = pln.Location().Y(); - x(3) = pln.Location().Z(); - if (gls.IsDone()) { - gls.Solve(b, x); - } - - Standard_Real ru = x(1) * 0.5; - Standard_Real rv = x(2) * 0.5; - //Standard_Real rz = Sqrt(x(3)+rx*rx+ry*ry); - - gp_Pnt r = plane->Value(ru, rv); - - SCNVector3 res = {(float)r.X(), (float)r.Y(), (float)r.Z()}; - - return res; -} - -/// Returns the normal vector of the plane fitted through the points (or in other words, the first principal component). -+ (SCNVector3) pc1Of:(const SCNVector3 []) points - ofLength:(int) length -{ - TColgp_Array1OfPnt ocPoints = [self convertPoints:points ofLength:length]; - gp_Pln pln = [self getFittingPlane:ocPoints]; - gp_Ax1 axis = pln.Axis(); - gp_Dir dir = axis.Direction(); - - return {(float)dir.X(), (float)dir.Y(), (float)dir.Z()}; -} - -/// Calculates a distance-minimizing common plane for the input points. -+ (gp_Pln) getFittingPlane:(const TColgp_Array1OfPnt&) ocPoints { - GProp_PGProps Pmat(ocPoints); - gp_Pnt g = Pmat.CentreOfMass(); - Standard_Real Xg,Yg,Zg; - g.Coord(Xg,Yg,Zg); - GProp_PrincipalProps Pp = Pmat.PrincipalProperties(); - gp_Vec V1 = Pp.FirstAxisOfInertia(); - gp_Pln pln = gp_Pln(g, V1); - - return pln; -} - -/// Convert points from SCNVector3 to gp_Pnt. -+ (TColgp_Array1OfPnt) convertPoints:(const SCNVector3 []) points - ofLength:(int) length -{ - TColgp_Array1OfPnt ocPoints = TColgp_Array1OfPnt(1, length); - - for (int i = 1; i <= length; i++) { - ocPoints.SetValue(i, gp_Pnt(points[i-1].x, points[i-1].y, points[i-1].z)); - } - - return ocPoints; -} - -/// Returns 0 of the points are at the same location, 1 if they are on the same line, 2 if they are on the same plane, 3 otherwise, given a certain tolerance. -+ (int) coincidentDimensionsOf:(const SCNVector3 [])points - ofLength:(int)length -{ - TColgp_Array1OfPnt ocPoints = TColgp_Array1OfPnt(1, length); - - for (int i = 1; i <= length; i++) { - ocPoints.SetValue(i, gp_Pnt(points[i-1].x, points[i-1].y, points[i-1].z)); - } - - GProp_PEquation eq = GProp_PEquation(ocPoints, flatteningTolerance); - if (eq.IsPlanar()) { - return 2; - } else if (eq.IsLinear()) { - return 1; - } else if (eq.IsPoint()) { - return 0; - } else { - return 3; - } -} - -@end diff --git a/iOS App/ARPen/Open CASCADE/Meshing.h b/iOS App/ARPen/Open CASCADE/Meshing.h deleted file mode 100755 index e29fc1f..0000000 --- a/iOS App/ARPen/Open CASCADE/Meshing.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// Meshing.h -// ARPen -// -// Created by Jan Benscheid on 27.09.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -#ifndef Meshing_h -#define Meshing_h - -#include - -@interface Meshing : NSObject - -/// Returns a TopoDS_Shape (referenced by `label`), converted into an `SCNGeometry` object. -+ (SCNGeometry *) sceneKitMeshOf:(const char *) label; -/// Returns all lines of a TopoDS_Shape (referenced by `label`), converted into an `SCNGeometry` object of primitive type "Lines". -+ (SCNGeometry *) sceneKitLinesOf:(const char *) label; -/// Returns all lines of a TopoDS_Shape (referenced by `label`), converted into a `SCNGeometry` object consisting of a series of cylinders. -+ (SCNGeometry *) sceneKitTubesOf:(const char *) label; -/// Triangulates a TopoDS_Shape (referenced by `label`) and saves it as an stl at `filename`. -+ (void) stlOf:(const char *) label - toFile:(const char *) filename; - -@end - -#endif /* Meshing_h */ diff --git a/iOS App/ARPen/Open CASCADE/Meshing.mm b/iOS App/ARPen/Open CASCADE/Meshing.mm deleted file mode 100755 index 25008f0..0000000 --- a/iOS App/ARPen/Open CASCADE/Meshing.mm +++ /dev/null @@ -1,382 +0,0 @@ -// -// Meshing.m -// ARPen -// -// Created by Jan Benscheid on 27.09.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -#import - -#include "Meshing.h" - -#include "Registry.h" - -#include -#include -#include -#include -#include -#include -#include -#include - - -@implementation Meshing : NSObject - -/// OpenCascade uses millimeters internally, while SceneKit uses meters. Objects are scaled by this factor for stl export. -static const double scalingFactor = 1000; - -/// [Linear deflection](https://www.opencascade.com/doc/occt-7.1.0/overview/html/occt_user_guides__modeling_algos.html#occt_modalg_11) for mesh conversion in preview. -static const double meshDeflection = 0.0005; -/// [Linear deflection](https://www.opencascade.com/doc/occt-7.1.0/overview/html/occt_user_guides__modeling_algos.html#occt_modalg_11) for mesh conversion in export. -static const double meshDeflectionExport = 0.0002; -/// [Linear deflection](https://www.opencascade.com/doc/occt-7.1.0/overview/html/occt_user_guides__modeling_algos.html#occt_modalg_11) for line conversion in preview. -static const double lineDeflection = 0.0003; - -/// Radius of cylinder tube for conversion of lines. -static const float tubeRadius = 0.0005; -/// Number of sides per tube segment. -static const int tubeSides = 3; - -/// Returns a TopoDS_Shape (referenced by `label`), converted into an `SCNGeometry` object. -+ (SCNGeometry *) sceneKitMeshOf:(const char *)label { - TCollection_AsciiString key = TCollection_AsciiString(label); - TopoDS_Shape shape = [Registry retrieveFromRegistry:key]; - return [self triangulate:shape withDeflection:meshDeflection]; -} - -/// Returns all lines of a TopoDS_Shape (referenced by `label`), converted into an `SCNGeometry` object of primitive type "Lines". -+ (SCNGeometry *) sceneKitLinesOf:(const char *)label { - TCollection_AsciiString key = TCollection_AsciiString(label); - TopoDS_Shape shape = [Registry retrieveFromRegistry:key]; - return [self getEdges:shape withDeflection:lineDeflection]; -} - -/// Returns all lines of a TopoDS_Shape (referenced by `label`), converted into a `SCNGeometry` object consisting of a series of cylinders. -+ (SCNGeometry *) sceneKitTubesOf:(const char *)label { - TCollection_AsciiString key = TCollection_AsciiString(label); - TopoDS_Shape shape = [Registry retrieveFromRegistry:key]; - return [self getTube:shape withDeflection:lineDeflection]; -} - -/// Returns all lines of a TopoDS_Shape, converted into a `SCNGeometry` object consisting of a series of cylinders. For a detailed explaination, see documentation of function `triangulate`. -+ (SCNGeometry *) getTube:(TopoDS_Shape &)shape - withDeflection:(const Standard_Real)deflection -{ - int noOfNodes = 0; - int noOfSegments = 0; - - // Determine necessary amount of tube segements - for (TopExp_Explorer exEdge(shape, TopAbs_EDGE); exEdge.More(); exEdge.Next()) - { - BRepAdaptor_Curve curveAdaptor; - curveAdaptor.Initialize(TopoDS::Edge(exEdge.Current())); - - GCPnts_QuasiUniformDeflection uniformAbscissa; - uniformAbscissa.Initialize(curveAdaptor, deflection); - - if (uniformAbscissa.IsDone()) - { - Standard_Integer nbr = uniformAbscissa.NbPoints(); - if (uniformAbscissa.NbPoints() >= 2) { - noOfNodes += nbr; - noOfSegments += nbr - 1; - } - } - } - - int noOfVertices = noOfSegments*((tubeSides+1)*2); - int noOfTriangles = noOfSegments * tubeSides * 2; - SCNVector3 vertices[noOfVertices]; - SCNVector3 normals[noOfVertices]; - int indices[noOfTriangles * 3]; - - int vertexIndex = 0; - int triIndex = 0; - - for (TopExp_Explorer exEdge(shape, TopAbs_EDGE); exEdge.More(); exEdge.Next()) - { - BRepAdaptor_Curve curveAdaptor; - curveAdaptor.Initialize(TopoDS::Edge(exEdge.Current())); - - GCPnts_QuasiUniformDeflection uniformAbscissa; - uniformAbscissa.Initialize(curveAdaptor, deflection); - - if (uniformAbscissa.IsDone()) - { - Standard_Integer nbr = uniformAbscissa.NbPoints(); - gp_Pnt prev; - for ( Standard_Integer i = 1 ; i <= nbr ; i++ ) - { - gp_Pnt pt = curveAdaptor.Value(uniformAbscissa.Parameter(i)); - - if (i >= 2) { - // Create cylinder - gp_Vec vec; - if (pt.IsEqual(prev, 0.00001)) { - // In rare occasions (we found it when subtracting spheres from each other), both points may be equal, which results in a crash. Use a default direction then. - vec = gp_Vec(0, 1, 0); - } else { - vec = gp_Vec(prev, pt).Normalized(); - } - gp_Vec notParallel = gp_Vec(1, 0, 0); - if (abs(notParallel.Dot(vec)) >= 0.99) { - notParallel = gp_Vec(0, 1, 0); - } - gp_Vec perpendicular = vec.Crossed(notParallel).Normalized(); - gp_Ax1 rotationAxis = gp_Ax1(pt, gp_Dir(vec)); - - for (int j = 0; j <= tubeSides; j++) { - float rotation = (M_PI*2) * (((float)j) / tubeSides); - gp_Vec dir = perpendicular.Rotated(rotationAxis, rotation); - gp_Vec offset = dir.Scaled(tubeRadius); - gp_Pnt v1 = prev.Translated(offset); - gp_Pnt v2 = pt.Translated(offset); - vertices[vertexIndex] = {(float)v1.X(), (float)v1.Y(), (float)v1.Z()}; - vertices[vertexIndex+1]= {(float)v2.X(), (float)v2.Y(), (float)v2.Z()}; - normals[vertexIndex] = {(float)dir.X(), (float)dir.Y(), (float)dir.Z()}; - normals[vertexIndex+1] = {(float)dir.X(), (float)dir.Y(), (float)dir.Z()}; - if (j >= 1) { - indices[(triIndex*3)+0] = vertexIndex; - indices[(triIndex*3)+1] = vertexIndex+1; - indices[(triIndex*3)+2] = vertexIndex-1; - triIndex ++; - indices[(triIndex*3)+0] = vertexIndex-1; - indices[(triIndex*3)+1] = vertexIndex-2; - indices[(triIndex*3)+2] = vertexIndex; - triIndex ++; - } - vertexIndex += 2; - } - } - prev = pt; - } - } - } - - SCNGeometry *geometry = [self convertToSCNMesh:vertices withNormals:normals withIndices:indices vertexCount:noOfVertices primitiveCount:noOfTriangles]; - return geometry; -} - -/// Returns all lines of a TopoDS_Shape, converted into an `SCNGeometry` object of primitive type "Lines". For a detailed explaination, see documentation of function `triangulate`. -+ (SCNGeometry *) getEdges:(TopoDS_Shape &)shape - withDeflection:(const Standard_Real)deflection -{ - int noOfNodes = 0; - int noOfSegments = 0; - - // Determine necessary amount of tube segements - for (TopExp_Explorer exEdge(shape, TopAbs_EDGE); exEdge.More(); exEdge.Next()) - { - BRepAdaptor_Curve curveAdaptor; - curveAdaptor.Initialize(TopoDS::Edge(exEdge.Current())); - - GCPnts_QuasiUniformDeflection uniformAbscissa; - uniformAbscissa.Initialize(curveAdaptor, deflection); - - if(uniformAbscissa.IsDone()) - { - Standard_Integer nbr = uniformAbscissa.NbPoints(); - noOfNodes += nbr; - noOfSegments += nbr - 1; - } - } - - SCNVector3 vertices[noOfNodes]; - int indices[noOfSegments * 2]; - - int vertexIndex = 0; - int segmentIndex = 0; - for (TopExp_Explorer exEdge(shape, TopAbs_EDGE); exEdge.More(); exEdge.Next()) - { - BRepAdaptor_Curve curveAdaptor; - curveAdaptor.Initialize(TopoDS::Edge(exEdge.Current())); - - GCPnts_QuasiUniformDeflection uniformAbscissa; - uniformAbscissa.Initialize(curveAdaptor, deflection); - - if(uniformAbscissa.IsDone()) - { - Standard_Integer nbr = uniformAbscissa.NbPoints(); - for ( Standard_Integer i = 1 ; i <= nbr ; i++ ) - { - gp_Pnt pt = curveAdaptor.Value(uniformAbscissa.Parameter(i)); - vertices[vertexIndex] = {(float) pt.X(), (float) pt.Y(), (float) pt.Z()}; - - if (i >= 2) { - indices[segmentIndex++] = vertexIndex-1; - indices[segmentIndex++] = vertexIndex; - } - - vertexIndex ++; - } - } - } - - SCNGeometry *geometry = [self convertToSCNLines:vertices withIndices:indices vertexCount:noOfNodes primitiveCount:noOfSegments]; - return geometry; -} - -/// Convertes a TopoDS_Shape into a SceneKit compatible mesh. Inspired by https://github.com/openscenegraph/OpenSceneGraph/blob/master/src/osgPlugins/OpenCASCADE/ReaderWriterOpenCASCADE.cpp and StlAPI_Writer.cxx of OCCT Source. -+ (SCNGeometry *) triangulate:(TopoDS_Shape &)shape - withDeflection:(const Standard_Real)deflection -{ - // Update the incremental mesh - BRepMesh_IncrementalMesh mesh(shape, deflection); - - // First count the required nodes and triangles - int noOfNodes = 0; - int noOfTriangles = 0; - - // Iterate through the faces. BRepMesh_IncrementalMesh does not create a triangulation for the entire object, but rather associates one with each face. - for (TopExp_Explorer ex(shape, TopAbs_FACE); ex.More(); ex.Next()) - { - TopLoc_Location aLoc; - // This method does not calculate a triangulation. It simply reads out the one calculated when calling BRepMesh_IncrementalMesh. Therefore this loop is fast. - Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face (ex.Current()), aLoc); - if (!aTriangulation.IsNull()) - { - noOfNodes += aTriangulation->NbNodes(); - noOfTriangles += aTriangulation->NbTriangles(); - } - } - - SCNVector3 vertices[noOfNodes]; - SCNVector3 normals[noOfNodes]; - int indices[noOfTriangles * 3]; - - int vertexIndex = 0; - int triangleIndex = 0; - // Now loop over the faces again, populating the arrays - for (TopExp_Explorer ex(shape, TopAbs_FACE); ex.More(); ex.Next()) - { - TopoDS_Face face = TopoDS::Face(ex.Current()); - - TopLoc_Location location; - // Triangulate current face - Handle (Poly_Triangulation) triangulation = BRep_Tool::Triangulation(face, location); - Poly::ComputeNormals(triangulation); - gp_Trsf transformation = location.Transformation(); - if (!triangulation.IsNull()) - { - // Populate vertex and normal array - int noOfNodes = triangulation->NbNodes(); - const TColgp_Array1OfPnt& nodes = triangulation->Nodes(); - for (Standard_Integer i = nodes.Lower(); i <= nodes.Upper(); ++i) - { - gp_Pnt pt = nodes(i); - pt.Transform(transformation); - - gp_Dir normal = triangulation->Normal(i); - normal.Transform(transformation); - if (face.Orientation() == TopAbs_REVERSED) - { - normal = normal.Reversed(); - } - - // nodes.Lower() will be 1, because in OCCT Arrays start at 1 - // In OCCT Z is up, while in SceneKit Y is up, so Z and Y have to be swapped - vertices[vertexIndex + i - 1] = {(float) pt.X(), (float) pt.Y(), (float) pt.Z()}; - normals[vertexIndex + i - 1] = {(float) normal.X(), (float) normal.Y(), (float) normal.Z()}; - } - - // Populate index array - const Poly_Array1OfTriangle& triangles = triangulation->Triangles(); - - Standard_Integer v1, v2, v3; - for (Standard_Integer i = triangles.Lower(); i <= triangles.Upper(); ++i) - { - if (face.Orientation() != TopAbs_REVERSED) - { - triangles(i).Get(v1, v2, v3); - } else - { - triangles(i).Get(v1, v3, v2); - } - - indices[triangleIndex++] = vertexIndex + v1 - 1; - indices[triangleIndex++] = vertexIndex + v2 - 1; - indices[triangleIndex++] = vertexIndex + v3 - 1; - } - - vertexIndex += noOfNodes; - } - } - - SCNGeometry *geometry = [self convertToSCNMesh:vertices withNormals:normals withIndices:indices vertexCount:noOfNodes primitiveCount:noOfTriangles]; - - return geometry; -} - -/// Bundles line data into a SceneKit compatible geometry object. Inspired by https://github.com/matthewreagan/TerrainMesh3D/blob/master/TerrainMesh3D/TerrainMesh.m -+ (SCNGeometry *) convertToSCNLines:(nonnull const SCNVector3 *)vertices - withIndices:(nonnull const int *)indices - vertexCount:(int)noOfVertices - primitiveCount:(int)noOfPrimitives -{ - SCNGeometrySource *vertexSource = - vertexSource = [SCNGeometrySource geometrySourceWithVertices:vertices count:noOfVertices]; - - NSData *indexData = [NSData dataWithBytes:indices length:sizeof(int) * noOfPrimitives * 2]; - SCNGeometryElement *element = - [SCNGeometryElement geometryElementWithData:indexData - primitiveType:SCNGeometryPrimitiveTypeLine - primitiveCount:noOfPrimitives - bytesPerIndex:sizeof(int)]; - - SCNGeometry *geometry; - - geometry = [SCNGeometry geometryWithSources:@[vertexSource] - elements:@[element]]; - - return geometry; -} - -/// Bundles mesh data into a SceneKit compatible geometry object. Inspired by https://github.com/matthewreagan/TerrainMesh3D/blob/master/TerrainMesh3D/TerrainMesh.m -+ (SCNGeometry *) convertToSCNMesh:(nonnull const SCNVector3 *)vertices - withNormals:(nonnull const SCNVector3 *)normals - withIndices:(nonnull const int *)indices - vertexCount:(int)noOfVertices - primitiveCount:(int)noOfPrimitives -{ - SCNGeometrySource *vertexSource = - vertexSource = [SCNGeometrySource geometrySourceWithVertices:vertices count:noOfVertices]; - - SCNGeometrySource *normalSource = - normalSource = [SCNGeometrySource geometrySourceWithNormals:normals count:noOfVertices]; - - NSData *indexData = [NSData dataWithBytes:indices length:sizeof(int) * noOfPrimitives * 3]; - SCNGeometryElement *element = - [SCNGeometryElement geometryElementWithData:indexData - primitiveType:SCNGeometryPrimitiveTypeTriangles - primitiveCount:noOfPrimitives - bytesPerIndex:sizeof(int)]; - - SCNGeometry *geometry; - - geometry = [SCNGeometry geometryWithSources:@[vertexSource, normalSource] - elements:@[element]]; - - return geometry; -} - -/// Triangulates a TopoDS_Shape (referenced by `label`) and saves it as an stl at `filename`. -+ (void) stlOf:(const char *) label - toFile:(const char *) filename -{ - TCollection_AsciiString key = TCollection_AsciiString(label); - TopoDS_Shape shape = [Registry retrieveFromRegistry:key]; - - // Scale shape, as OpenCascade uses millimeters internally - gp_Trsf trans; - trans.SetScaleFactor(scalingFactor); - BRepBuilderAPI_Transform trsf(shape, trans); - TopoDS_Shape newShape = trsf.Shape(); - - BRepMesh_IncrementalMesh mesh(newShape, meshDeflectionExport*scalingFactor); - StlAPI_Writer writer = StlAPI_Writer(); - writer.Write(newShape, filename); -} - -@end diff --git a/iOS App/ARPen/Open CASCADE/OCCT.h b/iOS App/ARPen/Open CASCADE/OCCT.h deleted file mode 100755 index 6308bbe..0000000 --- a/iOS App/ARPen/Open CASCADE/OCCT.h +++ /dev/null @@ -1,95 +0,0 @@ -// -// OCCT.h -// ARPen -// -// Created by Jan Benscheid on 25.01.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -#ifndef OCCT_h -#define OCCT_h - -#include -#include - -/** - In general: A TopoDS_Shape (OCCTs internal geometry representation) is referenced by a C string (const char *) - */ -@interface OCCT : NSObject - -// Methods from Registry -/// Effectively deletes a shape -- (void) freeShape:(const char *) label; - - -// Methods from Helpers -/// For transformations which include scaling. Warning: As scaling was not possible for the user, this function has not been tested since early in the development and might contain errors. -- (void) setGTransformOf:(const char *) label - affine:(SCNMatrix4) affine - translation:(SCNVector3) translation; -- (void) setTransformOf:(const char *) label - transformation:(SCNMatrix4) mat; -- (void) setPivotOf:(const char *) label - pivot:(SCNMatrix4) mat; - -/// Calculates a new pivot point for shape based on the center of its bounding box, moves it there, and returns the coordinates. --(SCNVector3) center:(const char *) label; - -/// Returns the input points, projected onto a distance-minimizing common plane. -- (const SCNVector3 *) flattened:(const SCNVector3 []) points - ofLength:(int) length; - -/// Calculates a least-squares fitting circle for the points and returns its center. -- (SCNVector3) circleCenterOf:(const SCNVector3 []) points - ofLength:(int) length; -/// Returns the normal vector of the plane fitted through the points (or in other words, the first principal component). -- (SCNVector3) pc1Of:(const SCNVector3 []) points - ofLength:(int) length; -/// Returns 0 of the points are at the same location, 1 if they are on the same line, 2 if they are on the same plane, 3 otherwise, given a certain tolerance. -- (int) coincidentDimensionsOf:(const SCNVector3 [])points - ofLength:(int)length; - - -// Methods from Builders -// I hope the method headers are self-documenting :P -- (const char *) createSphere:(double) radius; -- (const char *) createBox:(double) width - height:(double) height - length:(double) length; -- (const char *) createCylinder:(double) radius - height:(double) height; -- (const char *) createPath:(const SCNVector3 []) points - length:(int) length - corners:(const int []) corners - closed:(bool) closed; - -- (const char *) sweep:(const char *) profile - along:(const char *) path; -- (const char *) revolve:(const char *) profile - aroundAxis:(SCNVector3) axisPosition - withDirection:(SCNVector3) axisDirection; -- (const char *) loft:(NSArray *) profiles - length:(int) length; - -- (const char *) booleanCut:(const char *) a - subtract:(const char *) b; -- (const char *) booleanJoin:(const char *) a - with:(const char *) b; -- (const char *) booleanIntersect:(const char *) a - with:(const char *) b; - - -// Methods from Meshing -/// Returns a TopoDS_Shape (referenced by `label`), converted into an `SCNGeometry` object. -- (SCNGeometry *) sceneKitMeshOf:(const char *) label; -/// Returns all lines of a TopoDS_Shape (referenced by `label`), converted into an `SCNGeometry` object of primitive type "Lines". -- (SCNGeometry *) sceneKitLinesOf:(const char *) label; -/// Returns all lines of a TopoDS_Shape (referenced by `label`), converted into a `SCNGeometry` object consisting of a series of cylinders. -- (SCNGeometry *) sceneKitTubesOf:(const char *) label; -/// Triangulates a TopoDS_Shape (referenced by `label`) and saves it as an stl at `filename`. -- (void) stlOf:(const char *) label - toFile:(const char *) filename; - -@end - -#endif /* OCCT_h */ diff --git a/iOS App/ARPen/Open CASCADE/OCCT.mm b/iOS App/ARPen/Open CASCADE/OCCT.mm deleted file mode 100755 index e839d7f..0000000 --- a/iOS App/ARPen/Open CASCADE/OCCT.mm +++ /dev/null @@ -1,144 +0,0 @@ -// -// OCCT.m -// ARPen -// -// Created by Jan Benscheid on 25.01.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -#import - -#include "OCCT.h" -#include "Registry.h" -#include "Helpers.h" -#include "Builders.h" -#include "Meshing.h" - -/// This class just forwards function calls. I did not find a more elegant way on how to achieve Swift-compatibility on the spot, but probably there is. -@implementation OCCT : NSObject - -// Registry -- (void) freeShape:(const char *) label { - [Registry freeShape:label]; -} - - -// Helpers -- (SCNVector3) center:(const char *) label { - return [Helpers center:label]; -} - -- (const SCNVector3 *) flattened:(const SCNVector3 []) points - ofLength:(int) length { - return [Helpers flattened:points ofLength:length]; -} - -- (SCNVector3) pc1Of:(const SCNVector3 []) points - ofLength:(int) length { - return [Helpers pc1Of:points ofLength:length]; -} - -- (SCNVector3) circleCenterOf:(const SCNVector3 []) points - ofLength:(int) length { - return [Helpers circleCenterOf:points ofLength:length]; -} - -- (int) coincidentDimensionsOf:(const SCNVector3 []) points - ofLength:(int) length { - return [Helpers coincidentDimensionsOf:points ofLength:length]; -} - -- (void) setGTransformOf:(const char *) label - affine:(SCNMatrix4) affine - translation:(SCNVector3) translation { - return [Helpers setGTransformOf:label affine:affine translation:translation]; -} - -- (void) setTransformOf:(const char *) label - transformation:(SCNMatrix4) mat { - return [Helpers setTransformOf:label transformation:mat]; -} - -- (void) setPivotOf:(const char *) label - pivot:(SCNMatrix4) mat { - return [Helpers setPivotOf:label pivot:mat]; -} - - - -// Builders -- (const char *) createSphere:(double) radius { - return [Builders createSphere:radius]; -} - -- (const char *) createBox:(double) width - height:(double) height - length:(double) length { - return [Builders createBox:width height:height length:length]; -} - -- (const char *) createCylinder:(double) radius - height:(double) height { - return [Builders createCylinder:radius height:height]; -} - -- (const char *) createPath:(const SCNVector3 []) points - length:(int) length - corners:(const int []) corners - closed:(bool) closed { - return [Builders createPath:points length:length corners:corners closed:closed]; -} - - -- (const char *) sweep:(const char *) profile - along:(const char *) path { - return [Builders sweep:profile along:path]; -} - -- (const char *) revolve:(const char *) profile - aroundAxis:(SCNVector3) axisPosition - withDirection:(SCNVector3) axisDirection { - return [Builders revolve:profile aroundAxis:axisPosition withDirection:axisDirection]; -} - -- (const char *) loft:(NSArray *) profiles - length:(int) length { - return [Builders loft:profiles length:length]; -} - - -- (const char *) booleanCut:(const char *) a - subtract:(const char *) b { - return [Builders booleanCut:a subtract:b]; -} - -- (const char *) booleanJoin:(const char *) a - with:(const char *) b { - return [Builders booleanJoin:a with:b]; -} - -- (const char *) booleanIntersect:(const char *) a - with:(const char *) b { - return [Builders booleanIntersect:a with:b]; -} - - -// Meshing -- (SCNGeometry *) sceneKitMeshOf:(const char *) label { - return [Meshing sceneKitMeshOf:label]; -} - -- (SCNGeometry *) sceneKitLinesOf:(const char *) label { - return [Meshing sceneKitLinesOf:label]; -} - -- (SCNGeometry *) sceneKitTubesOf:(const char *) label { - return [Meshing sceneKitTubesOf:label]; -} - -- (void) stlOf:(const char *) label - toFile:(const char *) filename { - return [Meshing stlOf:label toFile:filename]; -} - -@end diff --git a/iOS App/ARPen/Open CASCADE/Registry.h b/iOS App/ARPen/Open CASCADE/Registry.h deleted file mode 100755 index a35e02c..0000000 --- a/iOS App/ARPen/Open CASCADE/Registry.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// Registry.h -// ARPen -// -// Created by Jan Benscheid on 27.09.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -#ifndef Registry_h -#define Registry_h - -#include -#include -#include - -@interface Registry : NSObject - -/// Generates a random null-terminated alphanumeric string of lenghth 32 to be used as a key for objects -+ (TCollection_AsciiString) randomString; -/// Converts a OCCT string to a C string and stores in the heap. -+ (const char *) toHeapCString:(TCollection_AsciiString) input; - -/// Stores a TopoDS_Shape in the registry and returns the generated key as an OCCT string. -+ (TCollection_AsciiString) storeInRegistry:(TopoDS_Shape &) shape; -/// Stores a TopoDS_Shape in the registry under a predefined key as an OCCT string. -+ (void) storeInRegistry:(TopoDS_Shape &) shape - withKey:(TCollection_AsciiString) key; -/// Stores a TopoDS_Shape in the registry and returns the generated key as a C string. -+ (const char *) storeInRegistryWithCString:(TopoDS_Shape &) shape; -/// Stores a transform in the registry under a predefined key as an OCCT string. -+ (void) storeInTransformRegistry:(gp_Trsf &) transform - withKey:(TCollection_AsciiString) key; - - -/// Effectively deletes a shape -+ (void) freeShape:(const char *) label; -/// Deletes the TopoDS_Shape with the given key from the registry. -+ (void) deleteFromRegistry:(TCollection_AsciiString) key; - -/// Returns a TopoDS_Shape from the registry using an OCCT string as key. -+ (TopoDS_Shape) retrieveFromRegistry:(TCollection_AsciiString) key; -/// Returns a TopoDS_Shape from the registry using a C string as key. -+ (TopoDS_Shape) retrieveFromRegistryWithCString:(const char *) label; -/// Returns a TopoDS_Shape from the registry (with its transformations applied) using an OCCT string as key. -+ (TopoDS_Shape) retrieveFromRegistryTransformed:(TCollection_AsciiString) key; - -@end - -#endif /* Registry_h */ diff --git a/iOS App/ARPen/Open CASCADE/Registry.mm b/iOS App/ARPen/Open CASCADE/Registry.mm deleted file mode 100755 index 58d8800..0000000 --- a/iOS App/ARPen/Open CASCADE/Registry.mm +++ /dev/null @@ -1,121 +0,0 @@ -// -// Registry.m -// ARPen -// -// Created by Jan Benscheid on 27.09.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -#import - -#include "Registry.h" - -#include -#include -#include -#include -#include -#include - -@implementation Registry : NSObject - -// OCCT is weird when it comes to applying transformations. Sometimes they are idempotent, sometimes not. I therefore treat transformations separately from the objects and apply them when necessary. -// It might have been more "elegant" to use pointers instead of strings for identifierts, but this solution was easier for me in combination with Swift. -static NCollection_DataMap shapeRegistry = NCollection_DataMap(); -static NCollection_DataMap transformRegistry = NCollection_DataMap(); - -/// Generates a random null-terminated alphanumeric string of lenghth 32 to be used as a key for objects -+ (TCollection_AsciiString) randomString { - static int length = 32; - char s[length + 1]; - - static const char alphanum[] = - "0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz"; - - for (int i = 0; i < length; ++i) { - s[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; - } - - s[length] = 0; - TCollection_AsciiString res = TCollection_AsciiString(s); - return res; -} - -/// Converts a OCCT string to a C string and stores in the heap. -+ (const char *) toHeapCString:(TCollection_AsciiString) input { - const char *conv = input.ToCString(); - char *res = new char[strlen(conv) + 1]; - std::copy(conv, conv + strlen(conv) + 1, res); - return res; -} - -/// Stores a TopoDS_Shape in the registry and returns the generated key as an OCCT string. -+ (TCollection_AsciiString) storeInRegistry:(TopoDS_Shape &) shape { - TCollection_AsciiString key = [self randomString]; - shapeRegistry.Bind(key, shape); - return key; -} - -/// Stores a TopoDS_Shape in the registry under a predefined key as an OCCT string. -+ (void) storeInRegistry:(TopoDS_Shape &) shape - withKey:(TCollection_AsciiString) key { - shapeRegistry.Bind(key, shape); -} - -/// Stores a TopoDS_Shape in the registry and returns the generated key as a C string. -+ (const char *) storeInRegistryWithCString:(TopoDS_Shape &) shape { - TCollection_AsciiString key = [self randomString]; - shapeRegistry.Bind(key, shape); - return [self toHeapCString:key]; -} - -/// Stores a transform in the registry under a predefined key as an OCCT string. -+ (void) storeInTransformRegistry:(gp_Trsf &) transform - withKey:(TCollection_AsciiString) key { - transformRegistry.Bind(key, transform); -} - -/// Deletes the TopoDS_Shape with the given key from the registry. -+ (void) deleteFromRegistry:(TCollection_AsciiString) key { - shapeRegistry.UnBind(key); - // TODO: Maybe key needs to be free'd manually? - transformRegistry.UnBind(key); -} - -/// Returns a TopoDS_Shape from the registry using an OCCT string as key. -+ (TopoDS_Shape) retrieveFromRegistry:(TCollection_AsciiString) key { - return shapeRegistry.Find(key); -} - -/// Returns a TopoDS_Shape from the registry using a C string as key. -+ (TopoDS_Shape) retrieveFromRegistryWithCString:(const char *) label { - TCollection_AsciiString key = TCollection_AsciiString(label); - return [self retrieveFromRegistry:key]; -} - -/// Returns a TopoDS_Shape from the registry (with its transformations applied) using an OCCT string as key. -+ (TopoDS_Shape) retrieveFromRegistryTransformed:(TCollection_AsciiString) key { - TopoDS_Shape shape = shapeRegistry.Find(key); - gp_Trsf trans; - try { - OCC_CATCH_SIGNALS - trans = transformRegistry.Find(key); - } catch (...) { - trans = gp_Trsf(); - } - // May be dangerous! If undesired behaviour occurs, try changing to Standard_True - BRepBuilderAPI_Transform builder(shape, trans, Standard_False); - return builder.Shape(); -} - -/// Effectively deletes a shape -+ (void) freeShape:(const char *) label { - TopoDS_Shape shape = [self retrieveFromRegistryWithCString:label]; - shape.Nullify(); - TCollection_AsciiString key = TCollection_AsciiString(label); - [self deleteFromRegistry:key]; -} - -@end diff --git a/iOS App/ARPen/OpenCV/MarkerBox.h b/iOS App/ARPen/OpenCV/MarkerBox.h new file mode 100644 index 0000000..15a288c --- /dev/null +++ b/iOS App/ARPen/OpenCV/MarkerBox.h @@ -0,0 +1,17 @@ +// +// MarkerBox.h +// OpenCV +// +// Created by Felix Wehnert on 23.10.17. +// Copyright © 2017 Felix Wehnert. All rights reserved. +// + +#import + +@interface MarkerBox : SCNNode +- (instancetype)init; +- (SCNVector3)positionWithIds:(int*)ids count:(int)count; +- (SCNVector3)rotationWithIds:(int*)ids count:(int)count; +- (void)setPosition:(SCNVector3)position rotation:(SCNVector3)rotation forId:(int)markerId; +- (void)setTransform:(SCNMatrix4)transform forId:(int)markerID; +@end diff --git a/iOS App/ARPen/OpenCV/MarkerBox.m b/iOS App/ARPen/OpenCV/MarkerBox.m new file mode 100644 index 0000000..d347d5a --- /dev/null +++ b/iOS App/ARPen/OpenCV/MarkerBox.m @@ -0,0 +1,129 @@ +// +// MarkerBox.m +// OpenCV +// +// Created by Felix Wehnert on 23.10.17. +// Copyright © 2017 Felix Wehnert. All rights reserved. +// + +#import "MarkerBox.h" + +@interface MarkerBox() + +@property NSArray* markerArray; + +@end + +@implementation MarkerBox +@synthesize markerArray; + +#define RADIANS_TO_DEGREES(radians) ((radians) * (180.0 / M_PI)) +#define DEGREES_TO_RADIANS(angle) ((angle) / 180.0 * M_PI) + +/** + The initializer will calculate the posistion of the pencil point and will initialize all needed properties. + */ +- (instancetype)init +{ + self = [super init]; + if (self) { + self.name = @"MarkerBox"; + markerArray = [NSArray arrayWithObjects:[SCNNode node], [SCNNode node], [SCNNode node], [SCNNode node], [SCNNode node], [SCNNode node], nil]; + float a = 0.15; // The length of the pencil. + float xs, ys, zs; + + ys = xs = ((cos(DEGREES_TO_RADIANS(35.3))*a)+0.005)/sqrt(2); + zs = sin(DEGREES_TO_RADIANS(35.3))*a; + zs -= 0.02; + xs *= -1; + ys *= -1; + zs *= -1; + + float xl, yl, zl; + + yl = xl = (cos(DEGREES_TO_RADIANS(35.3))*a)/sqrt(2); + zl = sin(DEGREES_TO_RADIANS(35.3))*a; + zl += 0.02; + xl *= -1; + yl *= -1; + + // Place every children at the correct relative position. + [markerArray enumerateObjectsUsingBlock:^(SCNNode * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + obj.name = [NSString stringWithFormat:@"Marker #%lu", (idx+1)]; + SCNNode* point = [SCNNode node]; + point.name = [NSString stringWithFormat:@"Point from #%lu", (idx+1)]; + + switch(idx) { + case 0: // Marker 1 small + point.position = SCNVector3Make(xs, ys, zs); + break; + case 1: // Marker 2 small + point.position = SCNVector3Make(xs, ys, zs); + break; + case 2: // Marker 3 small + point.position = SCNVector3Make(xs, ys, zs); + break; + case 3: // Marker 4 big + point.position = SCNVector3Make(-xl, yl, zl); + break; + case 4: // Marker 5 big + point.position = SCNVector3Make(xl, yl, zl); + break; + case 5: // Marker 6 big + point.position = SCNVector3Make(-xl, yl, zl); + break; + } + + [obj addChildNode:point]; + [self addChildNode:obj]; + }]; + } + return self; +} + +/** + Sets the position for a specific marker + */ +- (void)setPosition:(SCNVector3)position rotation:(SCNVector3)rotation forId:(int)markerId { + [[self.markerArray objectAtIndex:markerId-1] setPosition:position]; + [[self.markerArray objectAtIndex:markerId-1] setEulerAngles:rotation]; +} + +/** + Sets the transform for a specific marker + */ +-(void)setTransform:(SCNMatrix4)transform forId:(int)markerID { + [[self.markerArray objectAtIndex:markerID-1] setTransform:transform]; +} + +/** + Calculates the position of the pencil point based on the given marker ids. + */ +- (SCNVector3)positionWithIds:(int*)ids count:(int)count { + __block SCNVector3 vector = SCNVector3Zero; + + for(int i = 0; i < count; i++) { + int markerId = ids[i]; + SCNVector3 point = [[[self.markerArray objectAtIndex:markerId-1].childNodes objectAtIndex:0] convertPosition:SCNVector3Zero toNode:nil]; + vector.x += point.x; + vector.y += point.y; + vector.z += point.z; + } + + vector.x /= count; + vector.y /= count; + vector.z /= count; + return vector; +} + +/** + Calculate the rotation of the marker box based on the given marker ids. + */ +- (SCNVector3)rotationWithIds:(int*)ids count:(int)count { + SCNMatrix4 matrix = [[[self.markerArray objectAtIndex:ids[0]-1].childNodes objectAtIndex:0] convertTransform:SCNMatrix4Identity toNode:nil]; + SCNNode* node = [SCNNode node]; + node.transform = matrix; + return node.eulerAngles; +} + +@end diff --git a/iOS App/ARPen/PenScene.swift b/iOS App/ARPen/PenScene.swift index 3078ea0..cd39826 100644 --- a/iOS App/ARPen/PenScene.swift +++ b/iOS App/ARPen/PenScene.swift @@ -31,8 +31,10 @@ class PenScene: SCNScene { */ var markerFound = true - static private var secureCoding = true - override public class var supportsSecureCoding: Bool { return secureCoding } + + var directionNode = SCNNode() + var projectionNode = SCNNode() + var arAnchorImage = SCNNode() /** Calling this method will convert the whole scene with every nodes in it to an stl file @@ -41,14 +43,10 @@ class PenScene: SCNScene { */ func share() -> URL { let filePath = URL(fileURLWithPath: NSTemporaryDirectory() + "/scene.stl") - if let node = drawingNode.childNodes.first as? ARPGeomNode { - node.exportStl(filePath: filePath) - } else { - let drawingItems = MDLObject(scnNode: self.drawingNode) - let asset = MDLAsset() - asset.add(drawingItems) - try! asset.export(to: filePath) - } + let drawingItems = MDLObject(scnNode: self.drawingNode) + let asset = MDLAsset() + asset.add(drawingItems) + try! asset.export(to: filePath) return filePath } @@ -62,7 +60,7 @@ class PenScene: SCNScene { } // the following property is needed since initWithCoder is overwritten in this class. Since no decoding happens in the function and the decoding is passed on to the superclass, this class supports secure coding as well. -// override public class var supportsSecureCoding: Bool { return true } + override public class var supportsSecureCoding: Bool { return true } /** This initializer will be called after `init(named:)` is called. */ @@ -77,41 +75,15 @@ class PenScene: SCNScene { self.pencilPoint.name = "PencilPoint" self.pencilPoint.geometry?.materials.first?.diffuse.contents = UIColor.init(red: 0.73, green: 0.12157, blue: 0.8, alpha: 1) - /*//simple coordinate system on the pencil point - let coordSystemNode = SCNNode() - let cylinderRadius = CGFloat(0.0005) - let cylinderHeight = CGFloat(0.01) - - let xAxisGeometry = SCNCylinder(radius: cylinderRadius, height: cylinderHeight) - xAxisGeometry.materials.first?.diffuse.contents = UIColor.red - let xAxisNode = SCNNode(geometry: xAxisGeometry) - xAxisNode.eulerAngles.z = -Float.pi/2 - xAxisNode.position.x = Float(cylinderHeight/2) - coordSystemNode.addChildNode(xAxisNode) - - let yAxisGeometry = SCNCylinder(radius: cylinderRadius, height: cylinderHeight) - yAxisGeometry.materials.first?.diffuse.contents = UIColor.green - let yAxisNode = SCNNode(geometry: yAxisGeometry) - yAxisNode.position.y = Float(cylinderHeight/2) - coordSystemNode.addChildNode(yAxisNode) - - let zAxisGeometry = SCNCylinder(radius: cylinderRadius, height: cylinderHeight) - zAxisGeometry.materials.first?.diffuse.contents = UIColor.blue - let zAxisNode = SCNNode(geometry: zAxisGeometry) - zAxisNode.eulerAngles.x = Float.pi/2 - zAxisNode.position.z = Float(cylinderHeight/2) - coordSystemNode.addChildNode(zAxisNode) - - self.pencilPoint.addChildNode(coordSystemNode)*/ + self.projectionNode.geometry = SCNSphere(radius: 0.002) + self.projectionNode.name = "projectionNode" + + self.projectionNode.geometry?.materials.first?.diffuse.contents = UIColor.green self.rootNode.addChildNode(self.pencilPoint) self.rootNode.addChildNode(self.drawingNode) - } - - func reinitializePencilPoint() { - self.pencilPoint = SCNNode() - - setupPencilPoint() + self.rootNode.addChildNode(self.directionNode) + self.rootNode.addChildNode(self.projectionNode) } } diff --git a/iOS App/ARPen/PluginManager.swift b/iOS App/ARPen/PluginManager.swift index f85c558..ab48a42 100644 --- a/iOS App/ARPen/PluginManager.swift +++ b/iOS App/ARPen/PluginManager.swift @@ -29,13 +29,16 @@ class PluginManager: ARManagerDelegate, PenManagerDelegate { var delegate: PluginManagerDelegate? var experimentalPluginsStartAtIndex: Int + //var studyPlugin : ClosestPointPainting + /** inits every plugin */ init(scene: PenScene) { self.paintPlugin = PaintPlugin() - self.plugins = [paintPlugin, CubeByDraggingPlugin(), SphereByDraggingPlugin(), CylinderByDraggingPlugin(), PyramidByDraggingPlugin(), CubeByExtractionPlugin(), ARMenusPlugin(), TranslationDemoPlugin(), CombinationPlugin(), ModelingPlugin(), SweepPluginProfileAndPath(), SweepPluginTwoProfiles(), LoftPlugin(), RevolvePluginProfileAndAxis(), RevolvePluginProfileAndCircle(), RevolvePluginTwoProfiles(), CombinePluginFunction(), CombinePluginSolidHole(), MinVisPlugin(), DepthRayPlugin(), BubblePlugin(), ShadowPlugin(), HeatmapPlugin()] - self.pluginInstructionsCanBeHidden = Array(repeating: true, count: self.plugins.count) +// self.plugins = [paintPlugin, CubeByDraggingPlugin(), SphereByDraggingPlugin(), CylinderByDraggingPlugin(), PyramidByDraggingPlugin(), CubeByExtractionPlugin(), ARMenusPlugin(), TranslationDemoPlugin(), CombinationPlugin(), RaycastPainting(), ClosestPointPainting(), StudyObjectGeneration()] + self.plugins = [ClosestPointPainting(), RaycastPainting(), StudyFreehandPainting()] + self.pluginInstructionsCanBeHidden = Array(repeating: false, count: self.plugins.count) self.experimentalPluginsStartAtIndex = 7 @@ -44,9 +47,6 @@ class PluginManager: ARManagerDelegate, PenManagerDelegate { self.activePlugin = plugins.first self.arManager.delegate = self self.arPenManager.delegate = self - - //listen to softwarePenButton notifications - NotificationCenter.default.addObserver(self, selector: #selector(self.softwareButtonEvent(_:)), name: .softwarePenButtonEvent, object: nil) } /** @@ -56,13 +56,6 @@ class PluginManager: ARManagerDelegate, PenManagerDelegate { self.buttons[button] = pressed } - //Callback from Notification Center. Format of userInfo: ["buttonPressed"] is the button the event is for, ["buttonState"] is the boolean whether the button is pressed or not - @objc func softwareButtonEvent(_ notification: Notification){ - if let buttonPressed = notification.userInfo?["buttonPressed"] as? Button, let buttonState = notification.userInfo?["buttonState"] as? Bool { - self.buttons[buttonPressed] = buttonState - } - } - /** Callback from PenManager */ @@ -97,6 +90,20 @@ class PluginManager: ARManagerDelegate, PenManagerDelegate { func undoPreviousStep() { // Todo: Add undo functionality for all plugins. - self.paintPlugin.undoPreviousAction() + + if (self.activePlugin!.pluginIdentifier == ((ClosestPointPainting()) as Plugin).pluginIdentifier){ + let studyPlugin = ClosestPointPainting() + studyPlugin.undoPreviousAction() + } + else + if (self.activePlugin!.pluginIdentifier == ((RaycastPainting()) as Plugin).pluginIdentifier){ + let studyPlugin = RaycastPainting() + studyPlugin.undoPreviousAction() + } + else{ + self.paintPlugin.undoPreviousAction() + } + + } } diff --git a/iOS App/ARPen/Plugins/.DS_Store b/iOS App/ARPen/Plugins/.DS_Store new file mode 100644 index 0000000..67eeb58 Binary files /dev/null and b/iOS App/ARPen/Plugins/.DS_Store differ diff --git a/iOS App/ARPen/Plugins/ARMenusPlugin.swift b/iOS App/ARPen/Plugins/ARMenusPlugin.swift index 8f79d72..79bbcae 100644 --- a/iOS App/ARPen/Plugins/ARMenusPlugin.swift +++ b/iOS App/ARPen/Plugins/ARMenusPlugin.swift @@ -10,9 +10,15 @@ import ARKit class ARMenusPlugin: Plugin, MenuDelegate { + var pluginImage : UIImage? = UIImage.init(named: "ARMenusPlugin") + var pluginInstructionsImage: UIImage? = UIImage.init(named: "MenusInstructions") + var pluginIdentifier: String = "Menus" + var needsBluetoothARPen: Bool = false + var pluginDisabledImage: UIImage? = UIImage.init(named: "ARMenusPluginDisabled") + var currentView: ARSCNView? //You need to set this to nil when switching to another plugin! - override var currentScene: PenScene? { + var currentScene: PenScene? { didSet { if currentScene != nil { targetNode = SCNNode(geometry: SCNBox(width: 0.02, height: 0.02, length: 0.02, chamferRadius: 0.0)) @@ -39,33 +45,25 @@ class ARMenusPlugin: Plugin, MenuDelegate { } } - override init() { - super.init() - - self.pluginImage = UIImage.init(named: "ARMenusPlugin") - self.pluginInstructionsImage = UIImage.init(named: "MenusInstructions") - self.pluginIdentifier = "Menus" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "ARMenusPluginDisabled") - } - - override func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { - super.activatePlugin(withScene: scene, andView: view) + func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { + self.currentView = view + self.currentScene = scene self.currentScene!.pencilPoint.geometry?.firstMaterial?.readsFromDepthBuffer = false self.currentScene!.pencilPoint.renderingOrder = 120 } - override func deactivatePlugin(){ + func deactivatePlugin(){ self.currentScene?.pencilPoint.geometry?.firstMaterial?.readsFromDepthBuffer = true self.currentScene?.pencilPoint.renderingOrder = 0 openMenuNode?.closeMenu(asAbort: true) targetNode?.removeFromParentNode() - super.deactivatePlugin() + self.currentView = nil + self.currentScene = nil } var isPressed = false - override func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { + func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { guard scene.markerFound else { return } diff --git a/iOS App/ARPen/Plugins/BooleanOperationPlugins/CombinePluginFunction.swift b/iOS App/ARPen/Plugins/BooleanOperationPlugins/CombinePluginFunction.swift deleted file mode 100755 index 9444687..0000000 --- a/iOS App/ARPen/Plugins/BooleanOperationPlugins/CombinePluginFunction.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// CombinePluginFunction.swift -// ARPen -// -// Created by Jan Benscheid on 30.09.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation -import ARKit - -class CombinePluginFunction: ModelingPlugin { - - private var buttonEvents: ButtonEvents - private var arranger: Arranger - - override init() { - arranger = Arranger() - buttonEvents = ButtonEvents() - - super.init() - - self.pluginImage = UIImage.init(named: "Bool(Function)") - self.pluginInstructionsImage = UIImage.init(named: "PaintPluginInstructions") - self.pluginIdentifier = "Combine(Function)" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "ARMenusPluginDisabled") - - buttonEvents.didPressButton = self.didPressButton - } - - override func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { - super.activatePlugin(withScene: scene, andView: view) - self.arranger.activate(withScene: scene, andView: view) - - self.button1Label.text = "Select/Move" - self.button2Label.text = "Merge" - self.button3Label.text = "Cut" - } - - override func deactivatePlugin() { - arranger.deactivate() - - super.deactivatePlugin() - } - - override func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { - buttonEvents.update(buttons: buttons) - arranger.update(scene: scene, buttons: buttons) - } - - func didPressButton(_ button: Button) { - - switch button { - case .Button1: - break - case .Button2, .Button3: - if arranger.selectedTargets.count == 2 { - guard let b = arranger.selectedTargets.removeFirst() as? ARPGeomNode, - let a = arranger.selectedTargets.removeFirst() as? ARPGeomNode else { - return - } - - DispatchQueue.global(qos: .userInitiated).async { - if let diff = try? ARPBoolNode(a: a, b: b, operation: button == .Button2 ? .join : .cut) { - DispatchQueue.main.async { - self.currentScene?.drawingNode.addChildNode(diff) - - } - } - } - } - } - } -} diff --git a/iOS App/ARPen/Plugins/BooleanOperationPlugins/CombinePluginSolidHole.swift b/iOS App/ARPen/Plugins/BooleanOperationPlugins/CombinePluginSolidHole.swift deleted file mode 100755 index 4bd2a5d..0000000 --- a/iOS App/ARPen/Plugins/BooleanOperationPlugins/CombinePluginSolidHole.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// CombinePluginSolidHole.swift -// ARPen -// -// Created by Jan Benscheid on 19.04.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// -import ARKit - -class CombinePluginSolidHole: ModelingPlugin { - - private var buttonEvents: ButtonEvents - private var arranger: Arranger - - override init() { - arranger = Arranger() - buttonEvents = ButtonEvents() - - super.init() - - self.pluginImage = UIImage.init(named: "Bool(Hole)") - self.pluginInstructionsImage = UIImage.init(named: "PaintPluginInstructions") - self.pluginIdentifier = "Combine(Hole)" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "ARMenusPluginDisabled") - - buttonEvents.didPressButton = self.didPressButton - } - - override func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { - super.activatePlugin(withScene: scene, andView: view) - // Forward activation to arranger - self.arranger.activate(withScene: scene, andView: view) - - self.button1Label.text = "Select/Move" - self.button2Label.text = "Solid ↔ Hole" - self.button3Label.text = "Combine" - } - - override func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { - buttonEvents.update(buttons: buttons) - arranger.update(scene: scene, buttons: buttons) - } - - func didPressButton(_ button: Button) { - - switch button { - case .Button1: - break - case .Button2: - for case let target as ARPGeomNode in arranger.selectedTargets { - target.isHole = !target.isHole - } - if case let target as ARPGeomNode = arranger.hoverTarget, !arranger.selectedTargets.contains(target) { - target.isHole = !target.isHole - } - case .Button3: - if arranger.selectedTargets.count == 2 { - guard let a = arranger.selectedTargets.removeFirst() as? ARPGeomNode, - let b = arranger.selectedTargets.removeFirst() as? ARPGeomNode else { - return - } - - var target = a - var tool = b - var createHole = false - var operation: BooleanOperation! - if a.isHole == b.isHole { - operation = .join - if a.isHole { - /// Hole + hole = join, but new object is a hole - createHole = true - } - } else { - operation = .cut - target = b.isHole ? a : b - tool = b.isHole ? b : a - } - - DispatchQueue.global(qos: .userInitiated).async { - if let res = try? ARPBoolNode(a: target, b: tool, operation: operation) { - - DispatchQueue.main.async { - self.currentScene?.drawingNode.addChildNode(res) - res.isHole = createHole - } - } - } - } - } - } -} diff --git a/iOS App/ARPen/Plugins/BooleanOperationPlugins/CombinePluginTutorial.swift b/iOS App/ARPen/Plugins/BooleanOperationPlugins/CombinePluginTutorial.swift deleted file mode 100755 index 7ad5d61..0000000 --- a/iOS App/ARPen/Plugins/BooleanOperationPlugins/CombinePluginTutorial.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// CombinePluginTutorial.swift -// ARPen -// -// Created by Jan Benscheid on 30.09.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation -import ARKit - -/** -This class should demonstrate the exemplary usage of the geometry manipulation code. -*/ -class CombinePluginTutorial: ModelingPlugin { - - /// A layer for easier access to the buttons - private var buttonEvents: ButtonEvents - /// A "sub-plugin" for selecting and moving objects - private var arranger: Arranger - - override init() { - // Initialize arranger - arranger = Arranger() - // Initialize button events helper and listen to its press event. - buttonEvents = ButtonEvents() - - super.init() - - self.pluginImage = UIImage.init(named: "PaintPlugin") - self.pluginInstructionsImage = UIImage.init(named: "PaintPluginInstructions") - self.pluginIdentifier = "Combine (Function)" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "ARMenusPluginDisabled") - - buttonEvents.didPressButton = self.didPressButton - } - - /// Called whenever the user switches to the plugin, or returns from the settings with the plugin selected. - override func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { - super.activatePlugin(withScene: scene, andView: view) - // Forward activation to arranger - self.arranger.activate(withScene: scene, andView: view) - - self.button1Label.text = "Select/Move" - self.button2Label.text = "Merge" - self.button3Label.text = "Cut" - } - - override func deactivatePlugin() { - // Forward deactivation to arranger - arranger.deactivate() - - super.deactivatePlugin() - } - - override func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { - // Forward update event - buttonEvents.update(buttons: buttons) - arranger.update(scene: scene, buttons: buttons) - } - - func didPressButton(_ button: Button) { - - switch button { - case .Button1: - break - case .Button2, .Button3: - if arranger.selectedTargets.count == 2 { - // Pop the first two selected targets from the stack to use them for creating a Boolean combination - guard let b = arranger.selectedTargets.removeFirst() as? ARPGeomNode, - let a = arranger.selectedTargets.removeFirst() as? ARPGeomNode else { - return - } - - // Geometry creation may take time and should be done asynchronous. - DispatchQueue.global(qos: .userInitiated).async { - // Choose either join/add or cut/subtract depending on the button pressed. - if let diff = try? ARPBoolNode(a: a, b: b, operation: button == .Button2 ? .join : .cut) { - // Attach the resulting object to the scene synchronous. - DispatchQueue.main.async { - self.currentScene?.drawingNode.addChildNode(diff) - // You don't need to (and must not) delete the `a` and `b`. When creating the Boolean operation, they became children of the `ARPBoolNode` object in order to allow for hierarchical editing. - - } - } - } - } - } - } -} diff --git a/iOS App/ARPen/Plugins/ClosestPointPainting.swift b/iOS App/ARPen/Plugins/ClosestPointPainting.swift new file mode 100644 index 0000000..c935e1f --- /dev/null +++ b/iOS App/ARPen/Plugins/ClosestPointPainting.swift @@ -0,0 +1,1268 @@ +// +// ClosestPointPainting.swift +// ARPen +// +// Created by Martin on 08.07.19. +// Copyright © 2019 RWTH Aachen. All rights reserved. +// + +import Foundation +import ARKit + +class ClosestPointPainting: Plugin, UserStudyRecordPluginProtocol { + + var recordManager: UserStudyRecordManager! + + + var pluginImage : UIImage? = UIImage.init(named: "ClosestPointPainting") + var pluginInstructionsImage: UIImage? = UIImage.init(named: "ClosestPointPaintingPluginInstructions") + var pluginIdentifier: String = "Closest Point" + var needsBluetoothARPen: Bool = false + var pluginDisabledImage: UIImage? = UIImage.init(named: "ARMenusPluginDisabled") + var currentScene : PenScene? + var currentView: ARSCNView? + + var penColor: UIColor = UIColor.init(red: 0.73, green: 0.12157, blue: 0.8, alpha: 1) + + var objectCounter: Int = 0 + var objectCurrentlyBuilding: Bool = false + + + let objectOpacity = 0.7 + let penTipOpacity = 0.5 + + let cubeSizeSmall = 0.08 + let cubeSizeLarge = 0.12 + + // trial data + var userID = 5 + var trialNumber = 0 + var trialID = -1 + var trialList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + var trialValid = true + + /** + The starting point is the point of the pencil where the button was first pressed. + If this var is nil, there was no initial point + */ + private var startingPoint: SCNVector3? + + /** + The previous point is the point of the pencil one frame before. + If this var is nil, there was no last point + */ + private var previousPoint: SCNVector3? + //collection of currently & last drawn line elements to offer undo + var previousDrawnLineNodes: [[SCNNode]]? + private var currentLine : [SCNNode]? + private var removedOneLine = false + + private var closestPoint: SCNVector3? + + private var averageError = Float(-1) + + private var dataExported = true + private var retryTrial = false + + func undoPreviousAction() { + retryTrial = true + //print("undoButton") + } + + func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { + guard scene.markerFound else { + //Don't reset the previous point to avoid disconnected lines if the marker detection failed for some frames + //self.previousPoint = nil + return + } + + + let pressed2 = buttons[Button.Button2]! + + if (pressed2){ + //print("retryTrial") + while (self.previousDrawnLineNodes!.count > 0) { + //print("removing line") + let lastLine = self.previousDrawnLineNodes?.last + + self.previousDrawnLineNodes?.removeLast() + + // Remove the previous line + for currentNode in lastLine! { + currentNode.removeFromParentNode() + } + } + let targetMeasurementDict = [ + "TrialNumber" : String(describing: trialNumber), + "TrialID": String(describing: trialID), + "TrialValid": String(describing: false) + ] + self.recordManager.addNewRecord(withIdentifier: self.pluginIdentifier, andData: targetMeasurementDict) + trialNumber = trialNumber + 1 + retryTrial = false + } + + + let pressed = buttons[Button.Button1]! + + + + //let objectList = scene.rootNode.childNodes + //let objectList = scene.drawingNode.childNodes + var objectList: [SCNNode] + + if let arImageNode = scene.rootNode.childNode(withName: "ARImage", recursively: false){ + objectList = arImageNode.childNodes + } else { + objectList = scene.drawingNode.childNodes + } + + + var closestDistance = Float(-1) + var closestPosition: SCNVector3? + closestPosition = nil + var numberOfPositions: Int = 0 + var currentError = Float(-1) + + + if (objectList.contains(where: {($0.name?.hasPrefix("currentStudyObject") ?? false)})){ + + for object in objectList{ + if (object.name?.hasPrefix("currentStudyObjectNode") ?? false){ + for obj in object.childNodes{ + var distance = Float(-1) + var surfacePosition = SCNVector3Zero + if (obj.name?.hasPrefix("currentStudyObjectBoxNode") ?? false){ + // use positioning with box, ignoring chamfer + + // convert pen position to box space + var localPosition = scene.pencilPoint.convertPosition(SCNVector3Zero, to: obj) + + // clamp coordinates to box size + + if (localPosition.x > Float((obj.geometry as! SCNBox).width)/2){ + localPosition.x = Float((obj.geometry as! SCNBox).width)/2 + } + if (localPosition.x < Float(-(obj.geometry as! SCNBox).width)/2){ + localPosition.x = Float(-(obj.geometry as! SCNBox).width)/2 + } + + if (localPosition.y > Float((obj.geometry as! SCNBox).height)/2){ + localPosition.y = Float((obj.geometry as! SCNBox).height)/2 + } + if (localPosition.y < Float(-(obj.geometry as! SCNBox).height)/2){ + localPosition.y = Float(-(obj.geometry as! SCNBox).height)/2 + } + + if (localPosition.z > Float((obj.geometry as! SCNBox).length)/2){ + localPosition.z = Float((obj.geometry as! SCNBox).length)/2 + } + if (localPosition.z < Float(-(obj.geometry as! SCNBox).length)/2){ + localPosition.z = Float(-(obj.geometry as! SCNBox).length)/2 + } + // if inside box, go to closest edge position + + if (localPosition.x > Float(-(obj.geometry as! SCNBox).width)/2 && + localPosition.x < Float( (obj.geometry as! SCNBox).width)/2 && + localPosition.y > Float(-(obj.geometry as! SCNBox).height)/2 && + localPosition.y < Float( (obj.geometry as! SCNBox).height)/2 && + localPosition.z > Float(-(obj.geometry as! SCNBox).length)/2 && + localPosition.z < Float( (obj.geometry as! SCNBox).length)/2 ){ + + + // find closest edge position + var deltaX = Float(0) + var deltaY = Float(0) + var deltaZ = Float(0) + + if (localPosition.x >= 0){ + deltaX = Float((obj.geometry as! SCNBox).width)/2 - localPosition.x + } + if (localPosition.x < 0){ + deltaX = -Float((obj.geometry as! SCNBox).width)/2 - localPosition.x + } + if (localPosition.y >= 0){ + deltaY = Float((obj.geometry as! SCNBox).height)/2 - localPosition.y + } + if (localPosition.y < 0){ + deltaY = -Float((obj.geometry as! SCNBox).height)/2 - localPosition.y + } + if (localPosition.z >= 0){ + deltaZ = Float((obj.geometry as! SCNBox).length)/2 - localPosition.z + } + if (localPosition.z < 0){ + deltaZ = -Float((obj.geometry as! SCNBox).length)/2 - localPosition.z + } + + if (deltaX < 0){ + deltaX = -deltaX + } + if (deltaY < 0){ + deltaY = -deltaY + } + if (deltaZ < 0){ + deltaZ = -deltaZ + } + + if (deltaX <= deltaY && deltaX <= deltaZ){ + // X position is closest to edge + if (localPosition.x >= 0){ + localPosition.x = Float((obj.geometry as! SCNBox).width)/2 + } + if (localPosition.x < 0){ + localPosition.x = -Float((obj.geometry as! SCNBox).width)/2 + } + } + if (deltaY < deltaX && deltaY <= deltaZ){ + // Y position is closest to edge + if (localPosition.y >= 0){ + localPosition.y = Float((obj.geometry as! SCNBox).height)/2 + } + if (localPosition.y < 0){ + localPosition.y = -Float((obj.geometry as! SCNBox).height)/2 + } + } + if (deltaZ < deltaX && deltaZ < deltaY){ + // Z position is closest to edge + if (localPosition.z >= 0){ + localPosition.z = Float((obj.geometry as! SCNBox).length)/2 + } + if (localPosition.z < 0){ + localPosition.z = -Float((obj.geometry as! SCNBox).length)/2 + } + } + } + // convert pos back to global space + surfacePosition = obj.convertPosition(localPosition, to: nil) + distance = surfacePosition.distance(vector: scene.pencilPoint.position) + } + if (obj.name?.hasPrefix("currentStudyObjectSphereNode") ?? false){ + // use positioning with sphere + + // distance = distance(sphere, point) - sphere.radius + + // surfacePosition = sphere_pos + (pen_pos-sphere_pos) * (radius/distance distance(sphere, point)) + + distance = scene.pencilPoint.position.distance(vector: obj.position) - Float((obj.geometry as! SCNSphere).radius) + + // this is to prevent a division by zero, which could occur if pen is positioned exactly in sphere's center + if (scene.pencilPoint.position.distance(vector: obj.position) == 0){ + surfacePosition = obj.position + (scene.pencilPoint.position - obj.position) * (Float((obj.geometry as! SCNSphere).radius)/(scene.pencilPoint.position.distance(vector: obj.position) + 0.0001)) + } + else { + surfacePosition = obj.position + (scene.pencilPoint.position - obj.position) * (Float((obj.geometry as! SCNSphere).radius)/scene.pencilPoint.position.distance(vector: obj.position)) + } + + } + if (obj.name?.hasPrefix("GuideCylinder") ?? false){ + // use positioning with cylinder + + // convert pen position to cylinder's local space + + var localPosition = scene.pencilPoint.convertPosition(SCNVector3Zero, to: obj) + // if height over or under cylinder: + // clamp height and skip inside volume checking + if (localPosition.y > Float((obj.geometry as! SCNCylinder).height)){ + localPosition.y = Float((obj.geometry as! SCNCylinder).height) + } + if (localPosition.y < -Float((obj.geometry as! SCNCylinder).height)){ + localPosition.y = -Float((obj.geometry as! SCNCylinder).height) + } + + var flatPenPosition = SCNVector3(localPosition.x, 0, localPosition.z) + + // this is to prevent a division by zero, which could occur if pen is positioned exactly on cylinder axis + if (flatPenPosition.x == 0 && flatPenPosition.z == 0){ + flatPenPosition.x = 0.01 + } + surfacePosition = flatPenPosition * (Float((obj.geometry as! SCNCylinder).radius)/flatPenPosition.distance(vector: SCNVector3Zero)) + + surfacePosition.y = localPosition.y + + surfacePosition = obj.convertPosition(surfacePosition, to: nil) + + distance = scene.pencilPoint.position.distance(vector: surfacePosition) + + } + if (distance > 0 && distance < closestDistance || closestDistance < 0){ + closestDistance = distance + closestPosition = surfacePosition + } + } + } + } + } + + // if no valid position on object was found, default to pencil point + if closestPosition == nil { + closestPosition = scene.pencilPoint.position + } + + + if pressed, let previousPoint = self.previousPoint { + if currentLine == nil { + currentLine = [SCNNode]() + } + let cylinderNode = SCNNode() + + scene.directionNode.position = closestPosition! + + cylinderNode.buildLineInTwoPointsWithRotation(from: + cylinderNode.convertPosition(previousPoint, to: scene.arAnchorImage), to: scene.directionNode.convertPosition(SCNVector3Zero, to: scene.arAnchorImage), radius: 0.001, color: penColor) + + cylinderNode.name = "cylinderLine" + + scene.arAnchorImage.addChildNode(cylinderNode) + //scene.drawingNode.addChildNode(cylinderNode) + + //add last drawn line element to currently drawn line collection + currentLine?.append(cylinderNode) + + + // For study: measure distance to closest guide line position: + + if (objectList.contains(where: {($0.name?.hasPrefix("currentStudyObjectNode") ?? false)})){ + for obj in objectList{ + var distance = Float(-1) + var surfacePosition = SCNVector3Zero + //if (obj.name?.hasPrefix("GuideBox") ?? false){ + for child in obj.childNodes{ + if (child.name?.hasPrefix("currentStudyObjectGuideNode") ?? false){ + // use positioning with box, ignoring chamfer + // convert pen position to box space + var localPosition = scene.projectionNode.convertPosition(SCNVector3Zero, to: obj) + + // clamp coordinates to box size + + if (localPosition.x > Float((child.geometry as! SCNBox).width)/2){ + localPosition.x = Float((child.geometry as! SCNBox).width)/2 + } + if (localPosition.x < Float(-(child.geometry as! SCNBox).width)/2){ + localPosition.x = Float(-(child.geometry as! SCNBox).width)/2 + } + + if (localPosition.y > Float((child.geometry as! SCNBox).height)/2){ + localPosition.y = Float((child.geometry as! SCNBox).height)/2 + } + if (localPosition.y < Float(-(child.geometry as! SCNBox).height)/2){ + localPosition.y = Float(-(child.geometry as! SCNBox).height)/2 + } + + if (localPosition.z > Float((child.geometry as! SCNBox).length)/2){ + localPosition.z = Float((child.geometry as! SCNBox).length)/2 + } + if (localPosition.z < Float(-(child.geometry as! SCNBox).length)/2){ + localPosition.z = Float(-(child.geometry as! SCNBox).length)/2 + } + // if inside box, go to closest edge position + + if (localPosition.x > Float(-(child.geometry as! SCNBox).width)/2 && + localPosition.x < Float( (child.geometry as! SCNBox).width)/2 && + localPosition.y > Float(-(child.geometry as! SCNBox).height)/2 && + localPosition.y < Float( (child.geometry as! SCNBox).height)/2 && + localPosition.z > Float(-(child.geometry as! SCNBox).length)/2 && + localPosition.z < Float( (child.geometry as! SCNBox).length)/2 ){ + + + // find closest edge position + var deltaX = Float(0) + var deltaY = Float(0) + var deltaZ = Float(0) + + if (localPosition.x >= 0){ + deltaX = Float((child.geometry as! SCNBox).width)/2 - localPosition.x + } + if (localPosition.x < 0){ + deltaX = -Float((child.geometry as! SCNBox).width)/2 - localPosition.x + } + if (localPosition.y >= 0){ + deltaY = Float((child.geometry as! SCNBox).height)/2 - localPosition.y + } + if (localPosition.y < 0){ + deltaY = -Float((child.geometry as! SCNBox).height)/2 - localPosition.y + } + if (localPosition.z >= 0){ + deltaZ = Float((child.geometry as! SCNBox).length)/2 - localPosition.z + } + if (localPosition.z < 0){ + deltaZ = -Float((child.geometry as! SCNBox).length)/2 - localPosition.z + } + + if (deltaX < 0){ + deltaX = -deltaX + } + if (deltaY < 0){ + deltaY = -deltaY + } + if (deltaZ < 0){ + deltaZ = -deltaZ + } + + if (deltaX <= deltaY && deltaX <= deltaZ){ + // X position is closest to edge + if (localPosition.x >= 0){ + localPosition.x = Float((child.geometry as! SCNBox).width)/2 + } + if (localPosition.x < 0){ + localPosition.x = -Float((child.geometry as! SCNBox).width)/2 + } + } + if (deltaY < deltaX && deltaY <= deltaZ){ + // Y position is closest to edge + if (localPosition.y >= 0){ + localPosition.y = Float((child.geometry as! SCNBox).height)/2 + } + if (localPosition.y < 0){ + localPosition.y = -Float((child.geometry as! SCNBox).height)/2 + } + } + if (deltaZ < deltaX && deltaZ < deltaY){ + // Z position is closest to edge + if (localPosition.z >= 0){ + localPosition.z = Float((child.geometry as! SCNBox).length)/2 + } + if (localPosition.z < 0){ + localPosition.z = -Float((child.geometry as! SCNBox).length)/2 + } + } + } + // convert pos back to global space + surfacePosition = child.convertPosition(localPosition, to: nil) + distance = surfacePosition.distance(vector: scene.projectionNode.position) + } + if (distance > 0 && distance < closestDistance || closestDistance < 0){ + //print("found new closest position") + numberOfPositions += 1 + closestDistance = distance + + currentError = closestDistance + averageError = (distance + averageError * Float(numberOfPositions-1)) / Float(numberOfPositions) + //print("average error: ", averageError) + } + } + } + } + + let timestamp = Date().timeIntervalSince1970 + let userID = 0 + + + var currentStudyObject: SCNNode? + + if (objectList.contains(where: {($0.name?.hasPrefix("currentStudyObjectNode") ?? false)})){ + + for obj in objectList{ + + if (obj.name?.hasPrefix("currentStudyObjectNode") ?? false){ + currentStudyObject = obj + } + } + } + + let studyObjectSize = Float(99) + let studyObjectCornerType = "CornerTypeUnknown" + + let targetMeasurementDict = [ + "TrialNumber" : String(describing: trialNumber), + "TrialID" : String(describing: trialID), + "PenTipPositionX" : String(describing: scene.pencilPoint.position.x), + "PenTipPositionY" : String(describing: scene.pencilPoint.position.y), + "PenTipPositionZ" : String(describing: scene.pencilPoint.position.z), + "ProjectionPositionX" : String(describing: scene.projectionNode.position.x), + "ProjectionPositionY" : String(describing: scene.projectionNode.position.y), + "ProjectionPositionZ" : String(describing: scene.projectionNode.position.z), + "RelativeProjectionX" : String(describing: scene.projectionNode.convertPosition(SCNVector3Zero, to: scene.arAnchorImage).x), + "RelativeProjectionY" : String(describing: scene.projectionNode.convertPosition(SCNVector3Zero, to: scene.arAnchorImage).y), + "RelativeProjectionZ" : String(describing: scene.projectionNode.convertPosition(SCNVector3Zero, to: scene.arAnchorImage).z), + "CurrentError" : String(describing: closestDistance), + "AverageError" : String(describing: averageError), + "StudyObjectPositionX" : String(describing: currentStudyObject?.position.x ?? -1), + "StudyObjectPositionY" : String(describing: currentStudyObject?.position.y ?? -1), + "StudyObjectPositionZ" : String(describing: currentStudyObject?.position.z ?? -1) + + ] + + + + + dataExported = false + self.recordManager.addNewRecord(withIdentifier: self.pluginIdentifier, andData: targetMeasurementDict) + + + } else if !pressed { + if let currentLine = self.currentLine { + self.previousDrawnLineNodes?.append(currentLine) + self.currentLine = nil + } + } + + + + + + + + self.previousPoint = closestPosition + scene.projectionNode.position = closestPosition! + + // MARK: Button3, Object Creation + + + //Check state of the first button -> used to create the cube + let pressed3 = buttons[Button.Button3]! + + //if the button is pressed -> either set the starting point of the cube (first action) or scale the cube to fit from the starting point to the current point + if pressed3 { + + if !dataExported { + //self.recordManager.saveToFile() + self.recordManager.urlToCSV() + dataExported = true + //print("exporting Data") + } + + var objectNode = SCNNode() + if let startingPoint = self.startingPoint { + // spawnPoint is the position at which StudyObjects are created + + //let spawnPoint = SCNVector3Zero + var spawnPoint = scene.pencilPoint.position + if let arImageNode = scene.rootNode.childNode(withName: "ARImage", recursively: false){ + spawnPoint = arImageNode.position + } + //see if there is an active box node that is currently being drawn. Otherwise create it + + // if no object exists: create new object + if !objectCurrentlyBuilding{ + // object exists already: delete it and create new object + + // Case: study object was created as child of drawing node + if let studyObjectNode = scene.drawingNode.childNode(withName: "currentStudyObjectNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() +// while (self.previousDrawnLineNodes!.count > 0){ +// undoPreviousAction() +// } + } + + + if let studyObjectNode = scene.drawingNode.childNode(withName: "currentStudyObjectBoxNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() +// while (self.previousDrawnLineNodes!.count > 0){ +// undoPreviousAction() +// } + } + + if let studyObjectNode = scene.drawingNode.childNode(withName: "currentStudyObjectSphereNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() +// while (self.previousDrawnLineNodes!.count > 0){ +// undoPreviousAction() +// } + } + + if let studyObjectNode = scene.drawingNode.childNode(withName: "currentStudyObjectGuideNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() +// while (self.previousDrawnLineNodes!.count > 0){ +// undoPreviousAction() +// } + } + + // Case: study object was created as child of AR Image node + if let arImageNode = scene.rootNode.childNode(withName: "ARImage", recursively: false){ + if let studyObjectNode = arImageNode.childNode(withName: "currentStudyObjectNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() + + while (self.previousDrawnLineNodes!.count > 0){ + let lastLine = self.previousDrawnLineNodes?.last + self.previousDrawnLineNodes?.removeLast() + + // Remove the previous line + for currentNode in lastLine! { + currentNode.removeFromParentNode() + } + } + } + + + if let studyObjectNode = arImageNode.childNode(withName: "currentStudyObjectBoxNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() +// while (self.previousDrawnLineNodes!.count > 0){ +// undoPreviousAction() +// } + } + + if let studyObjectNode = arImageNode.childNode(withName: "currentStudyObjectSphereNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() +// while (self.previousDrawnLineNodes!.count > 0){ +// undoPreviousAction() +// } + } + + if let studyObjectNode = arImageNode.childNode(withName: "currentStudyObjectGuideNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() +// while (self.previousDrawnLineNodes!.count > 0){ +// undoPreviousAction() +// } + } + } + + if (objectCounter == 0 || objectCounter >= trialList.count){ + trialList.shuffle() + objectCounter = 0 + //print("shuffle") + } + + trialID = trialList[objectCounter] + + // whether previous object existed or not, create new one + switch trialList[objectCounter] { + case 0: + // flat corner small + objectNode = generateObject(size: 1, shape: 1, corner: 3, startingPoint: spawnPoint, scene: scene) + break + case 1: + // flat corner large + objectNode = generateObject(size: 2, shape: 1, corner: 3, startingPoint: spawnPoint, scene: scene) + break + case 2: + // exterior corner small + objectNode = generateObject(size: 1, shape: 1, corner: 1, startingPoint: spawnPoint, scene: scene) + break + case 3: + // exterior corner large + objectNode = generateObject(size: 2, shape: 1, corner: 1, startingPoint: spawnPoint, scene: scene) + break + case 4: + // interior corner small + objectNode = generateObject(size: 1, shape: 1, corner: 2, startingPoint: spawnPoint, scene: scene) + break + case 5: + // interior corner large + objectNode = generateObject(size: 2, shape: 1, corner: 2, startingPoint: spawnPoint, scene: scene) + break + case 6: + // Right Side Flat small + objectNode = generateObject(size: 1, shape: 1, corner: 4, startingPoint: spawnPoint, scene: scene) + break + case 7: + // Right Side Flat large + objectNode = generateObject(size: 2, shape: 1, corner: 4, startingPoint: spawnPoint, scene: scene) + break + case 8: + // Front Side Flat small + objectNode = generateObject(size: 1, shape: 1, corner: 5, startingPoint: spawnPoint, scene: scene) + break + case 9: + // Front Side Flat large + objectNode = generateObject(size: 2, shape: 1, corner: 5, startingPoint: spawnPoint, scene: scene) + break + + default: + break + } + + if let arImageNode = scene.rootNode.childNode(withName: "ARImage", recursively: false){ + arImageNode.addChildNode(objectNode) + } else { + scene.drawingNode.addChildNode(objectNode) + } + + objectCurrentlyBuilding = true + objectCounter += 1 + } + + + } + else { + //if the button is pressed but no startingPoint exists -> first frame with the button pressed. Set current pencil position as the start point + self.startingPoint = scene.pencilPoint.position + } + } + else { + //if the button is not pressed, check if a startingPoint is set -> released button. Reset the startingPoint to nil and set the name of the drawn box to "finished" + if objectCurrentlyBuilding{ + objectCurrentlyBuilding = false + } + if self.startingPoint != nil { + self.startingPoint = nil + if let boxNode = scene.drawingNode.childNode(withName: "currentStudyObjectNode", recursively: false), let boxNodeGeometry = boxNode.geometry as? SCNBox { + boxNode.name = "currentStudyObjectNode" + + //store a new record with the size of the finished box + let boxDimensionsDict = ["Width" : String(describing: boxNodeGeometry.width), "Height" : String(describing: boxNodeGeometry.height), "Length" : String(describing: boxNodeGeometry.length)] + self.recordManager.addNewRecord(withIdentifier: "BoxFinished", andData: boxDimensionsDict) + } + } + + + + } + + + + + + } + + + func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { + self.currentScene = scene + self.currentView = view + previousDrawnLineNodes = [[SCNNode]]() + + scene.pencilPoint.opacity = CGFloat(penTipOpacity) + scene.projectionNode.opacity = 1 + + } + + func deactivatePlugin() { + guard let scene = self.currentScene else { + + self.currentScene = nil + self.currentView = nil + + return + + } + + + if let arImageNode = scene.rootNode.childNode(withName: "ARImage", recursively: false){ + if let studyObjectNode = arImageNode.childNode(withName: "currentStudyObjectNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() + + while (self.previousDrawnLineNodes!.count > 0){ + let lastLine = self.previousDrawnLineNodes?.last + self.previousDrawnLineNodes?.removeLast() + + // Remove the previous line + for currentNode in lastLine! { + currentNode.removeFromParentNode() + } + } + } + } + + self.currentScene = nil + self.currentView = nil + } + + // MARK: Object Creation + + func generateObject(size: Int, shape: Int, corner: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + var objectNode = SCNNode() + + switch corner{ + // interior + case 1: + objectNode = createCubeInterior(size: size, startingPoint: startingPoint, scene: scene) + break + // exterior + case 2: + objectNode = createCubeExterior(size: size, startingPoint: startingPoint, scene: scene) + break + // flat + case 3: + objectNode = createCubeFlat(size: size, startingPoint: startingPoint, scene: scene) + break + // flat + right side + case 4: + objectNode = createCubeFlatRightSide(size: size, startingPoint: startingPoint, scene: scene) + break + // flat + front side + case 5: + objectNode = createCubeFlatFrontSide(size: size, startingPoint: startingPoint,scene: scene) + break + default: break + } + + trialNumber = trialNumber+1 + + + return objectNode + + } + + func createCubeExterior(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + objectNode.addChildNode(boxNode) + + var spawnPoint = startingPoint + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeSmall/2, 0) + break + case 2: cubeSize = Float(cubeSizeLarge) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeLarge/2, 0) + break + default: cubeSize = 0.2 + break + } + + objectNode.position = spawnPoint + + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + //set the dimensions of the box + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + + + + // create guiding line around object + createTargetLine(parentNode: objectNode, cubeSize: cubeSize, height: cubeSize/2) + + + + boxNode.opacity = CGFloat(objectOpacity) + + return objectNode + } + + + func createTargetLine(parentNode: SCNNode, cubeSize: Float, height: Float){ + // create four guide boxes, so that they form a ring, not a solid plane + let guideNode1 = SCNNode() + let guideNode2 = SCNNode() + let guideNode3 = SCNNode() + let guideNode4 = SCNNode() + guideNode1.name = "currentStudyObjectGuideNode" + guideNode2.name = "currentStudyObjectGuideNode" + guideNode3.name = "currentStudyObjectGuideNode" + guideNode4.name = "currentStudyObjectGuideNode" + parentNode.addChildNode(guideNode1) + parentNode.addChildNode(guideNode2) + parentNode.addChildNode(guideNode3) + parentNode.addChildNode(guideNode4) + + guideNode1.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry1 = guideNode1.geometry as! SCNBox + guideNode2.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry2 = guideNode2.geometry as! SCNBox + guideNode3.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry3 = guideNode3.geometry as! SCNBox + guideNode4.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry4 = guideNode4.geometry as! SCNBox + + let guideSize = cubeSize * 1.001 + + guideNode1.position = SCNVector3(cubeSize/2, height, 0) + guideNode2.position = SCNVector3(-cubeSize/2, height, 0) + guideNode3.position = SCNVector3(0, height, cubeSize/2) + guideNode4.position = SCNVector3(0, height, -cubeSize/2) + + //set the dimensions of the boxes + guideNodeGeometry1.width = CGFloat(0.0005) + guideNodeGeometry1.height = CGFloat(0.0005) + guideNodeGeometry1.length = CGFloat(guideSize) + guideNodeGeometry2.width = CGFloat(0.0005) + guideNodeGeometry2.height = CGFloat(0.0005) + guideNodeGeometry2.length = CGFloat(guideSize) + guideNodeGeometry3.width = CGFloat(guideSize) + guideNodeGeometry3.height = CGFloat(0.0005) + guideNodeGeometry3.length = CGFloat(0.0005) + guideNodeGeometry4.width = CGFloat(guideSize) + guideNodeGeometry4.height = CGFloat(0.0005) + guideNodeGeometry4.length = CGFloat(0.0005) + + guideNodeGeometry1.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry2.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry3.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry4.materials.first?.diffuse.contents = UIColor.red + + // Add starting point and draw direction signifiers + let startPointNode = SCNNode() + startPointNode.name = "currentStudyObjectStartingPointNode" + parentNode.addChildNode(startPointNode) + startPointNode.geometry = SCNSphere.init(radius: 0.002) + let startPointGeometry = startPointNode.geometry as! SCNSphere + startPointGeometry.materials.first?.diffuse.contents = UIColor.red + startPointNode.position = SCNVector3(-cubeSize/2, height, cubeSize/2) + + + let directionArrowNode = SCNNode() + directionArrowNode.name = "currentStudyObjectDirectionArrowNode" + parentNode.addChildNode(directionArrowNode) + directionArrowNode.geometry = SCNCone.init(topRadius: 0, bottomRadius: 0.0035, height: 0.008) + let directionArrowGeometry = directionArrowNode.geometry as! SCNCone + directionArrowGeometry.materials.first?.diffuse.contents = UIColor.red + directionArrowNode.position = SCNVector3(0, height, cubeSize/2) + directionArrowNode.eulerAngles = SCNVector3(-cubeSize/4, 0, -Float.pi/2) + } + + + func createCubeInterior(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + scene.drawingNode.addChildNode(objectNode) + objectNode.addChildNode(boxNode) + + var spawnPoint = startingPoint + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeSmall/2, 0) + break + case 2: cubeSize = Float(cubeSizeLarge) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeLarge/2, 0) + break + default: cubeSize = 0.2 + break + } + + objectNode.position = spawnPoint + + // create box itself + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + // create "base" + let baseNode = SCNNode() + baseNode.name = "currentStudyObjectBoxNode" + objectNode.addChildNode(baseNode) + + baseNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let baseNodeGeometry = baseNode.geometry as! SCNBox + + baseNodeGeometry.width = CGFloat(cubeSize * 1.4) + baseNodeGeometry.height = CGFloat(0.001) + baseNodeGeometry.length = CGFloat(cubeSize * 1.4) + + + // create guiding line around object + createTargetLine(parentNode: objectNode, cubeSize: cubeSize, height: 0) + + + boxNode.opacity = CGFloat(objectOpacity) + baseNode.opacity = CGFloat(objectOpacity) + baseNode.position = SCNVector3(0, -0.001, 0) + + return objectNode + } + + func createCubeFlat(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + scene.drawingNode.addChildNode(objectNode) + objectNode.addChildNode(boxNode) + + var spawnPoint = startingPoint + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeSmall/2, 0) + break + case 2: cubeSize = Float(cubeSizeLarge) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeLarge/2, 0) + break + default: cubeSize = 0.2 + break + } + + objectNode.position = spawnPoint + + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + //set the dimensions of the box + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + + // create guiding line around object + createTargetLine(parentNode: objectNode, cubeSize: cubeSize, height: 0) + + + boxNode.opacity = CGFloat(objectOpacity) + + return objectNode + + } + + func createCubeFlatRightSide(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + scene.drawingNode.addChildNode(objectNode) + objectNode.addChildNode(boxNode) + + var spawnPoint = startingPoint + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeSmall/2, 0) + break + case 2: cubeSize = Float(cubeSizeLarge) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeLarge/2, 0) + break + default: cubeSize = 0.2 + break + } + + objectNode.position = spawnPoint + + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + //set the dimensions of the box + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + + + boxNode.opacity = CGFloat(objectOpacity) + + + // create guiding line around object + // create four guide boxes, so that they form a ring, not a solid plane + let guideNode1 = SCNNode() + let guideNode2 = SCNNode() + let guideNode3 = SCNNode() + let guideNode4 = SCNNode() + guideNode1.name = "currentStudyObjectGuideNode" + guideNode2.name = "currentStudyObjectGuideNode" + guideNode3.name = "currentStudyObjectGuideNode" + guideNode4.name = "currentStudyObjectGuideNode" + objectNode.addChildNode(guideNode1) + objectNode.addChildNode(guideNode2) + objectNode.addChildNode(guideNode3) + objectNode.addChildNode(guideNode4) + + guideNode1.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry1 = guideNode1.geometry as! SCNBox + guideNode2.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry2 = guideNode2.geometry as! SCNBox + guideNode3.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry3 = guideNode3.geometry as! SCNBox + guideNode4.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry4 = guideNode4.geometry as! SCNBox + + let guideSize = cubeSize * 1.001 + + // 1 = Top, 2 = Back, 3 = Bottom, 4 = Front + guideNode1.position = SCNVector3(cubeSize/2, cubeSize/2, 0) + guideNode2.position = SCNVector3(cubeSize/2, 0, cubeSize/2) + guideNode3.position = SCNVector3(cubeSize/2, -cubeSize/2, 0) + guideNode4.position = SCNVector3(cubeSize/2, 0, -cubeSize/2) + + //set the dimensions of the boxes + guideNodeGeometry1.width = CGFloat(0.0005) + guideNodeGeometry1.height = CGFloat(0.0005) + guideNodeGeometry1.length = CGFloat(guideSize) + guideNodeGeometry2.width = CGFloat(0.0005) + guideNodeGeometry2.height = CGFloat(guideSize) + guideNodeGeometry2.length = CGFloat(0.0005) + guideNodeGeometry3.width = CGFloat(0.0005) + guideNodeGeometry3.height = CGFloat(0.0005) + guideNodeGeometry3.length = CGFloat(guideSize) + guideNodeGeometry4.width = CGFloat(0.0005) + guideNodeGeometry4.height = CGFloat(guideSize) + guideNodeGeometry4.length = CGFloat(0.0005) + + guideNodeGeometry1.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry2.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry3.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry4.materials.first?.diffuse.contents = UIColor.red + + // Add starting point and draw direction signifiers + let startPointNode = SCNNode() + startPointNode.name = "currentStudyObjectStartingPointNode" + objectNode.addChildNode(startPointNode) + startPointNode.geometry = SCNSphere.init(radius: 0.002) + let startPointGeometry = startPointNode.geometry as! SCNSphere + startPointGeometry.materials.first?.diffuse.contents = UIColor.red + startPointNode.position = SCNVector3(cubeSize/2, -cubeSize/2, cubeSize/2) + + + let directionArrowNode = SCNNode() + directionArrowNode.name = "currentStudyObjectDirectionArrowNode" + objectNode.addChildNode(directionArrowNode) + directionArrowNode.geometry = SCNCone.init(topRadius: 0, bottomRadius: 0.0035, height: 0.008) + let directionArrowGeometry = directionArrowNode.geometry as! SCNCone + directionArrowGeometry.materials.first?.diffuse.contents = UIColor.red + directionArrowNode.position = SCNVector3(cubeSize/2, 0, cubeSize/2) + directionArrowNode.eulerAngles = SCNVector3(0, 0, 0) + + + + + return objectNode + } + + func createCubeFlatFrontSide(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + scene.drawingNode.addChildNode(objectNode) + objectNode.addChildNode(boxNode) + + var spawnPoint = startingPoint + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeSmall/2, 0) + break + case 2: cubeSize = Float(cubeSizeLarge) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeLarge/2, 0) + break + default: cubeSize = 0.2 + break + } + + objectNode.position = spawnPoint + + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + //set the dimensions of the box + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + + + boxNode.opacity = CGFloat(objectOpacity) + + + // create guiding line around object + // create four guide boxes, so that they form a ring, not a solid plane + let guideNode1 = SCNNode() + let guideNode2 = SCNNode() + let guideNode3 = SCNNode() + let guideNode4 = SCNNode() + guideNode1.name = "currentStudyObjectGuideNode" + guideNode2.name = "currentStudyObjectGuideNode" + guideNode3.name = "currentStudyObjectGuideNode" + guideNode4.name = "currentStudyObjectGuideNode" + objectNode.addChildNode(guideNode1) + objectNode.addChildNode(guideNode2) + objectNode.addChildNode(guideNode3) + objectNode.addChildNode(guideNode4) + + guideNode1.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry1 = guideNode1.geometry as! SCNBox + guideNode2.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry2 = guideNode2.geometry as! SCNBox + guideNode3.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry3 = guideNode3.geometry as! SCNBox + guideNode4.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry4 = guideNode4.geometry as! SCNBox + + let guideSize = cubeSize * 1.001 + + // 1 = Top, 2 = Right, 3 = Bottom, 4 = Left + guideNode1.position = SCNVector3(0, cubeSize/2, cubeSize/2) + guideNode2.position = SCNVector3(cubeSize/2, 0, cubeSize/2) + guideNode3.position = SCNVector3(0, -cubeSize/2, cubeSize/2) + guideNode4.position = SCNVector3(-cubeSize/2, 0, cubeSize/2) + + //set the dimensions of the boxes + guideNodeGeometry1.width = CGFloat(guideSize) + guideNodeGeometry1.height = CGFloat(0.0005) + guideNodeGeometry1.length = CGFloat(0.0005) + guideNodeGeometry2.width = CGFloat(0.0005) + guideNodeGeometry2.height = CGFloat(guideSize) + guideNodeGeometry2.length = CGFloat(0.0005) + guideNodeGeometry3.width = CGFloat(guideSize) + guideNodeGeometry3.height = CGFloat(0.0005) + guideNodeGeometry3.length = CGFloat(0.0005) + guideNodeGeometry4.width = CGFloat(0.0005) + guideNodeGeometry4.height = CGFloat(guideSize) + guideNodeGeometry4.length = CGFloat(0.0005) + + guideNodeGeometry1.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry2.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry3.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry4.materials.first?.diffuse.contents = UIColor.red + + // Add starting point and draw direction signifiers + let startPointNode = SCNNode() + startPointNode.name = "currentStudyObjectStartingPointNode" + objectNode.addChildNode(startPointNode) + startPointNode.geometry = SCNSphere.init(radius: 0.002) + let startPointGeometry = startPointNode.geometry as! SCNSphere + startPointGeometry.materials.first?.diffuse.contents = UIColor.red + startPointNode.position = SCNVector3(-cubeSize/2, cubeSize/2, cubeSize/2) + + + let directionArrowNode = SCNNode() + directionArrowNode.name = "currentStudyObjectDirectionArrowNode" + objectNode.addChildNode(directionArrowNode) + directionArrowNode.geometry = SCNCone.init(topRadius: 0, bottomRadius: 0.0035, height: 0.008) + let directionArrowGeometry = directionArrowNode.geometry as! SCNCone + directionArrowGeometry.materials.first?.diffuse.contents = UIColor.red + directionArrowNode.position = SCNVector3(0, cubeSize/2, cubeSize/2) + directionArrowNode.eulerAngles = SCNVector3(0, 0, -Float.pi/2) + + + + + return objectNode + } + + + } + diff --git a/iOS App/ARPen/Plugins/CombinationPlugin.swift b/iOS App/ARPen/Plugins/CombinationPlugin.swift index 7b098c1..4084fc9 100644 --- a/iOS App/ARPen/Plugins/CombinationPlugin.swift +++ b/iOS App/ARPen/Plugins/CombinationPlugin.swift @@ -11,6 +11,14 @@ import ARKit class CombinationPlugin: Plugin { + var pluginImage : UIImage? = UIImage.init(named: "Move2DemoPlugin") + var pluginInstructionsImage: UIImage? = UIImage.init(named: "Move2PluginInstruction") + var pluginIdentifier: String = "Move 2" + var needsBluetoothARPen: Bool = true + var pluginDisabledImage: UIImage? = UIImage.init(named: "TranslationDemoPluginDisabled") + var currentScene : PenScene? + var currentView: ARSCNView? + var sceneConstructionResults : (superNode: SCNNode, studyNodes: [ARPenStudyNode])? var boxes : [ARPenBoxNode]? var activeTargetBox : ARPenBoxNode? { @@ -60,17 +68,7 @@ class CombinationPlugin: Plugin { } private var dropTargets = [ARPenDropTargetNode(withFloorPosition: SCNVector3Make(0.1238, 0, 0.08695)), ARPenDropTargetNode(withFloorPosition: SCNVector3Make(-0.1238, 0, 0.08695)), ARPenDropTargetNode(withFloorPosition: SCNVector3Make(0.1238, 0, -0.08695)), ARPenDropTargetNode(withFloorPosition: SCNVector3Make(-0.1238, 0, -0.08695))] - override init() { - super.init() - - self.pluginImage = UIImage.init(named: "Move2DemoPlugin") - self.pluginInstructionsImage = UIImage.init(named: "Move2PluginInstruction") - self.pluginIdentifier = "Move 2" - self.needsBluetoothARPen = true - self.pluginDisabledImage = UIImage.init(named: "TranslationDemoPluginDisabled") - } - - override func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { + func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { guard scene.markerFound else { //self.previousPoint = nil @@ -203,9 +201,10 @@ class CombinationPlugin: Plugin { } - override func activatePlugin(withScene scene: PenScene, andView view: ARSCNView){ + func activatePlugin(withScene scene: PenScene, andView view: ARSCNView){ - super.activatePlugin(withScene: scene, andView: view) + self.currentScene = scene + self.currentView = view self.fillSceneWithCubes(withScene: scene, andView : view) @@ -244,20 +243,21 @@ class CombinationPlugin: Plugin { - override func deactivatePlugin() { + func deactivatePlugin() { self.activeTargetBox = nil //_ = self.currentScene?.drawingNode.childNodes.map({$0.removeFromParentNode()}) if let constructionResults = self.sceneConstructionResults { constructionResults.superNode.removeFromParentNode() self.sceneConstructionResults = nil } + self.currentScene = nil self.currentView?.superview?.layer.borderWidth = 0.0 if let gestureRecognizer = self.gestureRecognizer { self.currentView?.removeGestureRecognizer(gestureRecognizer) } - super.deactivatePlugin() + self.currentView = nil } } diff --git a/iOS App/ARPen/Plugins/CubeByDraggingPlugin.swift b/iOS App/ARPen/Plugins/CubeByDraggingPlugin.swift index eb1856a..d45f86b 100644 --- a/iOS App/ARPen/Plugins/CubeByDraggingPlugin.swift +++ b/iOS App/ARPen/Plugins/CubeByDraggingPlugin.swift @@ -14,24 +14,21 @@ class CubeByDraggingPlugin: Plugin, UserStudyRecordPluginProtocol { //reference to userStudyRecordManager to add new records var recordManager: UserStudyRecordManager! + var pluginImage : UIImage? = UIImage.init(named: "CubeByDraggingPlugin") + var pluginInstructionsImage: UIImage? = UIImage.init(named: "CubePluginInstructions") + var pluginIdentifier: String = "Cube" + var needsBluetoothARPen: Bool = false + var pluginDisabledImage: UIImage? = UIImage.init(named: "ARMenusPluginDisabled") + var currentScene : PenScene? + var currentView: ARSCNView? + /** The starting point is the point of the pencil where the button was first pressed. If this var is nil, there was no initial point */ private var startingPoint: SCNVector3? - - override init() { - super.init() - - self.pluginImage = UIImage.init(named: "CubeByDraggingPlugin") - self.pluginInstructionsImage = UIImage.init(named: "CubePluginInstructions") - self.pluginIdentifier = "Cube" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "ARMenusPluginDisabled") - } - - override func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { + func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { guard scene.markerFound else { //Don't reset the previous point to avoid restarting cube if the marker detection failed for some frames //self.startingPoint = nil @@ -93,4 +90,15 @@ class CubeByDraggingPlugin: Plugin, UserStudyRecordPluginProtocol { } + + func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { + self.currentScene = scene + self.currentView = view + } + + func deactivatePlugin() { + self.currentScene = nil + self.currentView = nil + } + } diff --git a/iOS App/ARPen/Plugins/CubeByExtractionPlugin.swift b/iOS App/ARPen/Plugins/CubeByExtractionPlugin.swift index dc6132b..0ba76f4 100644 --- a/iOS App/ARPen/Plugins/CubeByExtractionPlugin.swift +++ b/iOS App/ARPen/Plugins/CubeByExtractionPlugin.swift @@ -12,25 +12,21 @@ import ARKit class CubeByExtractionPlugin: Plugin,UserStudyRecordPluginProtocol { var recordManager: UserStudyRecordManager! + + var pluginImage : UIImage? = UIImage.init(named: "CubeByExtractionPlugin") + var pluginInstructionsImage: UIImage? = UIImage.init(named: "ExtrudePluginInstructions") + var pluginIdentifier: String = "Extrude" + var needsBluetoothARPen: Bool = true + var pluginDisabledImage: UIImage? = UIImage.init(named: "CubeByExtractionPluginDisabled") + var currentScene : PenScene? + var currentView: ARSCNView? /** The starting point is the point of the pencil where the button was first pressed. If this var is nil, there was no initial point */ private var startingPoint: SCNVector3? - override init() { - super.init() - - self.pluginImage = UIImage.init(named: "CubeByExtractionPlugin") - self.pluginInstructionsImage = UIImage.init(named: "ExtrudePluginInstructions") - self.pluginIdentifier = "Extrude" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "CubeByExtractionPluginDisabled") - - nibNameOfCustomUIView = "SecondButton" - } - - override func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { + func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { guard scene.markerFound else { //Don't reset the previous point to avoid restarting cube if the marker detection failed for some frames //self.startingPoint = nil @@ -109,13 +105,14 @@ class CubeByExtractionPlugin: Plugin,UserStudyRecordPluginProtocol { } } - @IBAction func secondSoftwareButtonPressed(_ sender: Any) { - let buttonEventDict:[String: Any] = ["buttonPressed": Button.Button2, "buttonState" : true] - NotificationCenter.default.post(name: .softwarePenButtonEvent, object: nil, userInfo: buttonEventDict) + func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { + self.currentScene = scene + self.currentView = view } - @IBAction func secondSoftwareButtonReleased(_ sender: Any) { - let buttonEventDict:[String: Any] = ["buttonPressed": Button.Button2, "buttonState" : false] - NotificationCenter.default.post(name: .softwarePenButtonEvent, object: nil, userInfo: buttonEventDict) + func deactivatePlugin() { + self.currentScene = nil + self.currentView = nil } + } diff --git a/iOS App/ARPen/Plugins/CylinderByDraggingPlugin.swift b/iOS App/ARPen/Plugins/CylinderByDraggingPlugin.swift index 587d526..1079f45 100644 --- a/iOS App/ARPen/Plugins/CylinderByDraggingPlugin.swift +++ b/iOS App/ARPen/Plugins/CylinderByDraggingPlugin.swift @@ -13,23 +13,21 @@ class CylinderByDraggingPlugin: Plugin, UserStudyRecordPluginProtocol { //reference to userStudyRecordManager to add new records var recordManager: UserStudyRecordManager! + var pluginImage : UIImage? = UIImage.init(named: "CylinderPlugin") + var pluginInstructionsImage: UIImage? = UIImage.init(named: "CylinderPluginInstructions") + var pluginIdentifier: String = "Cylinder" + var needsBluetoothARPen: Bool = false + var pluginDisabledImage: UIImage? = UIImage.init(named: "ARMenusPluginDisabled") + var currentScene : PenScene? + var currentView: ARSCNView? + /** The starting point is the point of the pencil where the button was first pressed. If this var is nil, there was no initial point */ private var startingPoint: SCNVector3? - override init() { - super.init() - - self.pluginImage = UIImage.init(named: "CylinderPlugin") - self.pluginInstructionsImage = UIImage.init(named: "CylinderPluginInstructions") - self.pluginIdentifier = "Cylinder" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "ARMenusPluginDisabled") - } - - override func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { + func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { guard scene.markerFound else { //Don't reset the previous point to avoid restarting cube if the marker detection failed for some frames //self.startingPoint = nil @@ -90,5 +88,16 @@ class CylinderByDraggingPlugin: Plugin, UserStudyRecordPluginProtocol { } + + func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { + self.currentScene = scene + self.currentView = view + } + + func deactivatePlugin() { + self.currentScene = nil + self.currentView = nil + } + } diff --git a/iOS App/ARPen/Plugins/DepthVisualizationPlugins/BubblePlugin.swift b/iOS App/ARPen/Plugins/DepthVisualizationPlugins/BubblePlugin.swift deleted file mode 100644 index 21efd0c..0000000 --- a/iOS App/ARPen/Plugins/DepthVisualizationPlugins/BubblePlugin.swift +++ /dev/null @@ -1,133 +0,0 @@ - -import Foundation -import ARKit -import SpriteKit - -class BubblePlugin: MinVisPlugin { - - private var sonarPoint: SCNVector3? - - private let sonarBubbleNum : Int = 5 - private var sonarBubbleIndex : Int = 0 - private var buttonIsPressable = true - - override init(){ - super.init() - self.pluginIdentifier = "Bubble" - } - - override func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { - - super.didUpdateFrame(scene: scene, buttons: buttons) - - let pressed = buttons[Button.Button2]! - if pressed { - - if self.buttonIsPressable { - - self.buttonIsPressable = false - - guard let sonarBubble = self.studySceneConstruction?.superNode.childNode(withName: "sonarBubble\(self.sonarBubbleIndex)", recursively: false) else { - var sonarBubble = SCNNode() - sonarBubble = SCNNode.init(geometry: SCNSphere.init(radius: 0.0)) - sonarBubble.name = "sonarBubble\(self.sonarBubbleIndex)" - sonarBubble.geometry?.firstMaterial?.diffuse.contents = UIColor.blue - sonarBubble.worldPosition = scene.pencilPoint.worldPosition - self.studySceneConstruction?.superNode.addChildNode(sonarBubble) - return - } - - sonarBubble.worldPosition = scene.pencilPoint.worldPosition - (sonarBubble.geometry as? SCNSphere)?.radius = 0.0 - sonarBubble.geometry?.firstMaterial?.transparency = 1.0 - self.sonarPoint = scene.pencilPoint.worldPosition - self.studySceneConstruction?.superNode.childNodes.forEach({ - ($0 as? ARPenStudyNode)?.setShaderArgument(name: "waveDist\(self.sonarBubbleIndex)", value: 0.0 as Float) - }) - - SCNTransaction.begin() - SCNTransaction.animationDuration = 1.0 - self.studySceneConstruction?.superNode.childNodes.filter({($0 as SCNNode).worldPosition.distance(vector: self.sonarPoint!) < 0.15}).forEach({ - ($0 as? ARPenStudyNode)?.setShaderArgument(name: "waveDist\(self.sonarBubbleIndex)", value: 0.1 as Float) - }) - (sonarBubble.geometry as? SCNSphere)?.radius = 0.1 - sonarBubble.geometry?.firstMaterial?.transparency = 0.0 - SCNTransaction.commit() - - self.studySceneConstruction?.superNode.childNodes.filter({($0 as SCNNode).worldPosition.distance(vector: self.sonarPoint!) < 0.15}).forEach({ - ($0 as? ARPenStudyNode)?.setShaderArgument(name: "sonX\(self.sonarBubbleIndex)", value: self.sonarPoint?.x ?? 0.0) - ($0 as? ARPenStudyNode)?.setShaderArgument(name: "sonY\(self.sonarBubbleIndex)", value: self.sonarPoint?.y ?? 0.0) - ($0 as? ARPenStudyNode)?.setShaderArgument(name: "sonZ\(self.sonarBubbleIndex)", value: self.sonarPoint?.z ?? 0.0) - }) - - self.sonarBubbleIndex = (self.sonarBubbleIndex + 1) % self.sonarBubbleNum - } - } else { - self.buttonIsPressable = true - } - } - - override func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { - super.activatePlugin(withScene: scene, andView: view) - - for i : Int in 0 ..< self.sonarBubbleNum { - var sonarBubble = SCNNode() - sonarBubble = SCNNode.init(geometry: SCNSphere.init(radius: 0.0)) - sonarBubble.name = "sonarBubble\(i)" - sonarBubble.geometry?.firstMaterial?.diffuse.contents = UIColor.blue - sonarBubble.worldPosition = scene.pencilPoint.worldPosition - self.studySceneConstruction?.superNode.addChildNode(sonarBubble) - } - - var shaderCode : String = "#pragma arguments\n" - - for i : Int in 0 ..< self.sonarBubbleNum { - shaderCode += """ - float sonX\(i); - float sonY\(i); - float sonZ\(i); - float waveDist\(i);\n - """ - } - - shaderCode += """ - #pragma body - float4 finalColor = _surface.diffuse; - float4 sonarColor = float4(0.0,0.0,1.0,1.0);\n - """ - - for i : Int in 0 ..< self.sonarBubbleNum { - shaderCode += """ - float distToSon\(i) = distance(float3(sonX\(i),sonY\(i),sonZ\(i)),in.origPosition); - float x\(i) = abs(distToSon\(i) - waveDist\(i)); - float mixMasterFactor\(i) = 1.0 - fmax(ceil(-100.0 * x\(i) * (x\(i) - 0.001)),0.0); - float mixFactor\(i) = fmax(fmax(waveDist\(i) - 0.095, 0.0) * 200, mixMasterFactor\(i)); - finalColor = finalColor * (mixFactor\(i)) + sonarColor * (1.0-mixFactor\(i));\n - """ - } - - shaderCode += "_surface.diffuse = finalColor;" - - self.studySceneConstruction?.studyNodes.forEach({ - $0.setShaderModifier(shaderModifiers: [SCNShaderModifierEntryPoint.geometry: """ - - - #pragma varyings - float3 origPosition; - - #pragma body - out.origPosition = (scn_node.modelTransform * _geometry.position).xyz; - """, - SCNShaderModifierEntryPoint.surface: shaderCode - ]) - }) - } - - override func deactivatePlugin() { - self.studySceneConstruction?.superNode.childNodes.forEach({$0.geometry?.shaderModifiers = nil}) - - super.deactivatePlugin() - } - -} - diff --git a/iOS App/ARPen/Plugins/DepthVisualizationPlugins/DepthRayPlugin.swift b/iOS App/ARPen/Plugins/DepthVisualizationPlugins/DepthRayPlugin.swift deleted file mode 100644 index d37b97f..0000000 --- a/iOS App/ARPen/Plugins/DepthVisualizationPlugins/DepthRayPlugin.swift +++ /dev/null @@ -1,96 +0,0 @@ - -import Foundation -import ARKit -import SpriteKit - -class DepthRayPlugin: MinVisPlugin { - - var distanceLabel: SKLabelNode? - - private var sonarPoint: SCNVector3? - - private let testOffset : [(Float, Float)] = [(0, 2), (0, -2), (2, 0), (-2, 0), (0, 4), (0, -4), (4, 0), (-4, 0), (0, 6), (0, -6), (6, 0), (-6, 0), (0, 8), (0, -8), (8, 0), (-8, 0)] - - override init(){ - super.init() - self.pluginIdentifier = "DepthRay" - } - - override func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { - - super.didUpdateFrame(scene: scene, buttons: buttons) - - if self.helpActive { - if let arSceneView = self.currentView { - let projectedPencilPosition = arSceneView.projectPoint(scene.pencilPoint.position) - - for index : Int in 0.. 0 { - scene.drawingNode.childNode(withName: "indicatorPlane", recursively: false)?.geometry?.firstMaterial?.transparency = 1.0 - } else { - scene.drawingNode.childNode(withName: "indicatorPlane", recursively: false)?.geometry?.firstMaterial?.transparency = 0.0 - continue - } - - let distanceInCM = ((hitResults.filter({$0.node.name != "PencilPoint" && $0.node.name != "indicatorPlane" && !($0.node.name?.starts(with: "BoundingBox") ?? false)}).first?.worldCoordinates.distance(vector: scene.pencilPoint.position)) ?? 0) * 100 - self.distanceLabel?.text = String(format: "%.1f", distanceInCM) - - scene.drawingNode.childNode(withName: "indicatorPlane", recursively: false)?.position = scene.pencilPoint.position + SCNVector3(x: 0, y: 0.005, z: 0) - - break - } - } - } else { - scene.drawingNode.childNode(withName: "indicatorPlane", recursively: false)?.geometry?.firstMaterial?.transparency = 0.0 - } - } - - override func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { - super.activatePlugin(withScene: scene, andView: view) - - let renderToTextureScene = SKScene(size: CGSize(width: 2000, height: 250)) - renderToTextureScene.backgroundColor = UIColor.clear - - let indicatorBackground = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 2000, height: 250), cornerRadius: 10) - indicatorBackground.fillColor = #colorLiteral(red: 0.9529411793, green: 0.6862745285, blue: 0.1333333403, alpha: 1) - indicatorBackground.strokeColor = #colorLiteral(red: 0.3098039329, green: 0.2039215714, blue: 0.03921568766, alpha: 1) - indicatorBackground.lineWidth = 10 - indicatorBackground.alpha = 0.4 - - self.distanceLabel = SKLabelNode(text: "0") - self.distanceLabel?.fontSize = 300 - self.distanceLabel?.fontName = "San Fransisco" - self.distanceLabel?.position = CGPoint(x:1000,y:20) - - renderToTextureScene.addChild(indicatorBackground) - renderToTextureScene.addChild(self.distanceLabel!) - - let indicatorPlane = SCNPlane(width: 0.02, height: 0.005) - - let renderToTextureMaterial = SCNMaterial() - renderToTextureMaterial.isDoubleSided = false - renderToTextureMaterial.diffuse.contents = renderToTextureScene - renderToTextureMaterial.diffuse.contentsTransform = SCNMatrix4Translate(SCNMatrix4MakeScale(1, -1, 1), 0, 1, 0) - indicatorPlane.materials = [renderToTextureMaterial] - - let indicatorNode = SCNNode(geometry: indicatorPlane) - indicatorNode.name = "indicatorPlane" - indicatorNode.constraints = [SCNBillboardConstraint()] - indicatorNode.renderingOrder = 19998 - indicatorNode.geometry?.firstMaterial?.readsFromDepthBuffer = false - self.currentScene?.drawingNode.addChildNode(indicatorNode) - } - - override func deactivatePlugin() { - currentScene?.drawingNode.childNode(withName: "indicatorPlane", recursively: false)?.removeFromParentNode() - - super.deactivatePlugin() - } - -} - - - diff --git a/iOS App/ARPen/Plugins/DepthVisualizationPlugins/HeatmapPlugin.swift b/iOS App/ARPen/Plugins/DepthVisualizationPlugins/HeatmapPlugin.swift deleted file mode 100644 index 1d8f7fc..0000000 --- a/iOS App/ARPen/Plugins/DepthVisualizationPlugins/HeatmapPlugin.swift +++ /dev/null @@ -1,82 +0,0 @@ - -import Foundation -import ARKit -import SpriteKit - -class HeatmapPlugin: MinVisPlugin { - - private var sonarPoint: SCNVector3? - - override init(){ - super.init() - self.pluginIdentifier = "Heatmap" - } - - override func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { - - super.didUpdateFrame(scene: scene, buttons: buttons) - - self.sonarPoint = scene.pencilPoint.worldPosition - - self.studySceneConstruction?.superNode.childNodes.forEach({ - ($0 as? ARPenStudyNode)?.setShaderArgument(name: "sonX", value: self.sonarPoint?.x ?? 0.0) - ($0 as? ARPenStudyNode)?.setShaderArgument(name: "sonY", value: self.sonarPoint?.y ?? 0.0) - ($0 as? ARPenStudyNode)?.setShaderArgument(name: "sonZ", value: self.sonarPoint?.z ?? 0.0) - ($0 as? ARPenStudyNode)?.setShaderArgument(name: "active", value: self.helpActive ? 1.0 : 0.0) - }) - } - - override func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { - super.activatePlugin(withScene: scene, andView: view) - - self.setShaderModifiers() - } - - override func calculateNextTarget() { - super.calculateNextTarget() - - self.setShaderModifiers() - } - - func setShaderModifiers() { - self.studySceneConstruction?.superNode.childNodes.filter({$0.name != "testBlob"}).forEach({ - ($0 as? ARPenStudyNode)?.setShaderModifier(shaderModifiers: [SCNShaderModifierEntryPoint.geometry: """ - - - #pragma varyings - float3 origPosition; - - #pragma body - out.origPosition = (scn_node.modelTransform * _geometry.position).xyz; - """, - SCNShaderModifierEntryPoint.surface: """ - #pragma arguments - float sonX; - float sonY; - float sonZ; - float active; - - #pragma body - float distToSon = distance(float3(sonX,sonY,sonZ),in.origPosition); - - if (active > 0.5){ - if (distToSon > 0.01) { - _surface.diffuse = float4(distToSon*5.0,1.0-distToSon*5.0,0.0,1.0); - } else { - _surface.diffuse = float4(0.0,0.0,1.0,1.0); - } - _surface.emission = float4(0.0,0.0,0.0,0.0); - } - """]) - }) - } - - override func deactivatePlugin() { - self.studySceneConstruction?.superNode.childNodes.forEach({$0.geometry?.shaderModifiers = nil}) - - super.deactivatePlugin() - } - -} - - diff --git a/iOS App/ARPen/Plugins/DepthVisualizationPlugins/MinVisPlugin.swift b/iOS App/ARPen/Plugins/DepthVisualizationPlugins/MinVisPlugin.swift deleted file mode 100644 index 0abf229..0000000 --- a/iOS App/ARPen/Plugins/DepthVisualizationPlugins/MinVisPlugin.swift +++ /dev/null @@ -1,383 +0,0 @@ - -import Foundation -import ARKit - -class MinVisPlugin: Plugin, UserStudyRecordPluginProtocol { - - var recordManager: UserStudyRecordManager! - - static var nodeType : ARPenStudyNode.Type = ARPenWireBoxNode.self - - var studySceneConstruction : (superNode: SCNNode, studyNodes: [ARPenStudyNode])? - - var targetPosition : SCNVector3? - - private var previousPoint: SCNVector3? - private var currentLine : [SCNNode]? - - private var nextSceneAligned = true //set to true for demo purpose. Only used in study settings. -// //only needed for study settings -// private var backAlignmentTarget : SCNNode? -// private var frontAlignmentTarget : SCNNode? - - private var trialNum : Int = -1 - private var trialRedo : Int = 0 - private var trialRedoOngoing : Bool = false - - var helpActive : Bool = false - var button2WasPressed : Bool = false - - var testRun : Bool = true - - let LINE_NAME = "drawnLine" - - override init() { - super.init() - - self.pluginImage = UIImage.init(named: "SonarPlugin") - self.pluginInstructionsImage = UIImage.init(named: "None") - self.pluginIdentifier = "MinVis" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "SonarPluginDisabled") - - nibNameOfCustomUIView = "SecondButton" - } - - override func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { - - guard self.recordManager != nil else {return} - - if (self.recordManager.currentActiveUserID != nil) { - if (!testRun && self.nextSceneAligned) { - self.recordManager.addNewRecord(withIdentifier: String(describing: type(of: self)), andData: [ - "timestamp" : "\(Date().millisecondsSince1970)", - "penVisible" : scene.markerFound ? "true" : "false", - "trial" : "\(self.trialNum)", - "trialRedo" : self.trialRedoOngoing ? "\(self.trialRedo)" : "0", - "boxtype" : MinVisPlugin.nodeType == ARPenWireBoxNode.self ? "wire" : "full", - "targetX" : "\(targetPosition!.x)", - "targetY" : "\(targetPosition!.y)", - "targetZ" : "\(targetPosition!.z)", - "penX" : "\(scene.pencilPoint.worldPosition.x)", - "penY" : "\(scene.pencilPoint.worldPosition.y)", - "penZ" : "\(scene.pencilPoint.worldPosition.z)", - "helpButtonActive" : buttons[Button.Button2]! ? "true" : "false", - "lineButtonActive" : buttons[Button.Button1]! ? "true" : "false", - "helpActive" : helpActive ? "true" : "false" - ]) - } - } else { - // for review testing disable the need to log information - //return - } - - //only needed in study setting (to align scene&pen) -// if let arSceneView = self.currentView { -// let projectedAlignmentPosition = arSceneView.projectPoint(frontAlignmentTarget?.worldPosition ?? SCNVector3(0,0,0)) -// let projectedCGPoint = CGPoint(x: CGFloat(projectedAlignmentPosition.x), y: CGFloat(projectedAlignmentPosition.y)) -// let hitResults = arSceneView.hitTest(projectedCGPoint, options: [SCNHitTestOption.searchMode : SCNHitTestSearchMode.all.rawValue]) -// -// if hitResults.filter({$0.node.name == "backAlignmentTarget"}).count > 0 && scene.markerFound { -// self.nextSceneAligned = true -// backAlignmentTarget?.removeFromParentNode() -// frontAlignmentTarget?.removeFromParentNode() -// } -// } - - let pressed = buttons[Button.Button1]! - - if self.nextSceneAligned || self.currentLine != nil { - if pressed, let previousPoint = self.previousPoint { - if self.currentLine == nil { - self.currentLine = [SCNNode]() - } - let cylinderNode = SCNNode() - cylinderNode.buildLineInTwoPointsWithRotation(from: previousPoint, to: scene.pencilPoint.position, radius: 0.001, color: UIColor.orange) - cylinderNode.name = LINE_NAME - scene.drawingNode.addChildNode(cylinderNode) - self.currentLine?.append(cylinderNode) - //self.nextSceneAligned = false - } else if !pressed { - if self.currentLine != nil { - self.currentLine = nil - - scene.drawingNode.childNodes.filter({$0.name == LINE_NAME}).forEach({ - $0.removeFromParentNode() - }) - - self.calculateNextTarget() - self.trialRedoOngoing = false - } - } - } - - - let pressed2 = buttons[Button.Button2]! - if pressed2 { - if !self.button2WasPressed { - self.button2WasPressed = true - self.helpActive = !self.helpActive - } - } else { - self.button2WasPressed = false - } - - - self.previousPoint = scene.pencilPoint.position - } - - override func activatePlugin(withScene scene: PenScene, andView view: ARSCNView){ - super.activatePlugin(withScene: scene, andView: view) - - if (MinVisPlugin.nodeType == ARPenWireBoxNode.self) { - MinVisPlugin.nodeType = ARPenBoxNode.self - } else { - MinVisPlugin.nodeType = ARPenWireBoxNode.self - } - - self.constructStudyScene() - self.calculateNextTarget() - self.stopStudy() - } - - override func deactivatePlugin() { - self.stopStudy() - - if let studySceneConstruction = self.studySceneConstruction { - studySceneConstruction.superNode.removeFromParentNode() - self.studySceneConstruction = nil - } - - self.currentScene?.drawingNode.childNodes.filter({$0.name == LINE_NAME}).forEach({ - $0.removeFromParentNode() - }) - - self.currentView?.superview?.layer.borderWidth = 0.0 - super.deactivatePlugin() - } - - func constructStudyScene() { - //print("CONSTRUCT SCENE") - let sceneConstructor = ARPenGridSceneConstructor.init() - self.studySceneConstruction = sceneConstructor.preparedARPenNodes(withScene: self.currentScene!, andView: self.currentView!, andStudyNodeType: MinVisPlugin.nodeType) - //remove image alignment for review - //self.studySceneConstruction!.superNode.position = SCNVector3(0,0,0) - self.currentScene?.drawingNode.addChildNode(self.studySceneConstruction!.superNode) - } - - func calculateNextTarget() { - - self.helpActive = false - - self.trialNum += 1 - - if (self.trialNum > 15) { - self.stopStudy() - } - - if (self.trialNum < 0) { - self.trialNum = 0 - } - - let nodeCap = 8 - var minX = Float.infinity - var maxX = -Float.infinity - var minY = Float.infinity - var maxY = -Float.infinity - var minZ = Float.infinity - var maxZ = -Float.infinity - - self.studySceneConstruction?.superNode.childNodes.forEach({ - if ($0.position.x < minX) { - minX = $0.position.x - } else if ($0.position.x > maxX) { - maxX = $0.position.x - } - - if ($0.position.y < minY) { - minY = $0.position.y - } else if ($0.position.y > maxY) { - maxY = $0.position.y - } - - if ($0.position.z < minZ) { - minZ = $0.position.z - } else if ($0.position.z > maxZ) { - maxZ = $0.position.z - } - }) - - self.studySceneConstruction?.studyNodes.forEach({ - $0.isActiveTarget = false - $0.highlighted = true - }) - - self.studySceneConstruction?.superNode.childNodes.filter({$0.name == "testBlob"}).forEach({ - $0.removeFromParentNode() - }) - - var studyNodes = self.studySceneConstruction?.studyNodes.shuffled() ?? []; - - while !(studyNodes.prefix(nodeCap).contains(self.studySceneConstruction!.studyNodes[trialNum])) { - studyNodes = self.studySceneConstruction?.studyNodes.shuffled() ?? []; - } - - for studyNode in studyNodes { - studyNode.removeFromParentNode() - } - - for studyNode in studyNodes.prefix(nodeCap) { - self.studySceneConstruction?.superNode.addChildNode(studyNode) - } - - if let randomStudyNode = self.studySceneConstruction?.studyNodes[trialNum] { - - var possibleFacesToTest = ["front"] - - if (randomStudyNode.position.y < minY + (maxY - minY) / 3.0) { - possibleFacesToTest.append("top") - } - - if (randomStudyNode.position.y > minY + (maxY - minY) / 3.0 * 2.0) { - possibleFacesToTest.append("bottom") - } - - if (randomStudyNode.position.x < minX + (maxX - minX) / 3.0) { - possibleFacesToTest.append("right") - } - - if (randomStudyNode.position.x > minX + (maxX - minX) / 3.0 * 2.0) { - possibleFacesToTest.append("left") - } - - if let chosenFace = possibleFacesToTest.randomElement() { - var firstPlaneVertex = SCNVector3(0.0, 0.0, 0.0) - var secondPlaneVertex = SCNVector3(0.0, 0.0, 0.0) - - if chosenFace == "front" { - firstPlaneVertex = SCNVector3(randomStudyNode.position.x + randomStudyNode.dimension / 2.0, randomStudyNode.position.y + randomStudyNode.dimension / 2.0, randomStudyNode.position.z + randomStudyNode.dimension / 2.0) - secondPlaneVertex = SCNVector3(randomStudyNode.position.x - randomStudyNode.dimension / 2.0, randomStudyNode.position.y - randomStudyNode.dimension / 2.0, randomStudyNode.position.z + randomStudyNode.dimension / 2.0) - } - - if chosenFace == "right" { - firstPlaneVertex = SCNVector3(randomStudyNode.position.x + randomStudyNode.dimension / 2.0, randomStudyNode.position.y + randomStudyNode.dimension / 2.0, randomStudyNode.position.z + randomStudyNode.dimension / 2.0) - secondPlaneVertex = SCNVector3(randomStudyNode.position.x + randomStudyNode.dimension / 2.0, randomStudyNode.position.y - randomStudyNode.dimension / 2.0, randomStudyNode.position.z - randomStudyNode.dimension / 2.0) - } - - if chosenFace == "left" { - firstPlaneVertex = SCNVector3(randomStudyNode.position.x - randomStudyNode.dimension / 2.0, randomStudyNode.position.y + randomStudyNode.dimension / 2.0, randomStudyNode.position.z + randomStudyNode.dimension / 2.0) - secondPlaneVertex = SCNVector3(randomStudyNode.position.x - randomStudyNode.dimension / 2.0, randomStudyNode.position.y - randomStudyNode.dimension / 2.0, randomStudyNode.position.z - randomStudyNode.dimension / 2.0) - } - - if chosenFace == "top" { - firstPlaneVertex = SCNVector3(randomStudyNode.position.x + randomStudyNode.dimension / 2.0, randomStudyNode.position.y + randomStudyNode.dimension / 2.0, randomStudyNode.position.z + randomStudyNode.dimension / 2.0) - secondPlaneVertex = SCNVector3(randomStudyNode.position.x - randomStudyNode.dimension / 2.0, randomStudyNode.position.y + randomStudyNode.dimension / 2.0, randomStudyNode.position.z - randomStudyNode.dimension / 2.0) - } - - if chosenFace == "bottom" { - firstPlaneVertex = SCNVector3(randomStudyNode.position.x + randomStudyNode.dimension / 2.0, randomStudyNode.position.y - randomStudyNode.dimension / 2.0, randomStudyNode.position.z + randomStudyNode.dimension / 2.0) - secondPlaneVertex = SCNVector3(randomStudyNode.position.x - randomStudyNode.dimension / 2.0, randomStudyNode.position.y - randomStudyNode.dimension / 2.0, randomStudyNode.position.z - randomStudyNode.dimension / 2.0) - } - - let directionVector = secondPlaneVertex - firstPlaneVertex - - var xFraction = Float.random(in: 0 ... 1) - var yFraction = Float.random(in: 0 ... 1) - var zFraction = Float.random(in: 0 ... 1) - - // if we have a wireframe box only mark the edges - if randomStudyNode is ARPenWireBoxNode { - randomizerLoop : while (true) { - let randomVectorElement = Int.random(in: 0 ... 2) - let randomClampValue = Int.random(in: 0 ... 1) - - if (randomVectorElement == 0 && firstPlaneVertex.x != secondPlaneVertex.x) { - xFraction = Float(randomClampValue) - break randomizerLoop - } - - if (randomVectorElement == 1 && firstPlaneVertex.y != secondPlaneVertex.y) { - yFraction = Float(randomClampValue) - break randomizerLoop - } - - if (randomVectorElement == 2 && firstPlaneVertex.z != secondPlaneVertex.z) { - zFraction = Float(randomClampValue) - break randomizerLoop - } - } - } - - randomStudyNode.isActiveTarget = true - randomStudyNode.highlighted = true - - var targetBlob = SCNNode() - targetBlob = SCNNode.init(geometry: SCNSphere.init(radius: 0.002)) - targetBlob.name = "testBlob" - targetBlob.geometry?.firstMaterial?.diffuse.contents = UIColor.init(red: 0.7, green: 0.0, blue: 0.7, alpha: 1.0) - targetBlob.geometry?.firstMaterial?.emission.contents = UIColor.blue - targetBlob.position = SCNVector3(firstPlaneVertex.x + (directionVector.x) * xFraction, firstPlaneVertex.y + (directionVector.y) * yFraction, firstPlaneVertex.z + (directionVector.z) * zFraction) - self.studySceneConstruction?.superNode.addChildNode(targetBlob) - self.targetPosition = targetBlob.worldPosition - -// backAlignmentTarget?.removeFromParentNode() -// frontAlignmentTarget?.removeFromParentNode() - -// var frontAlignmentTarget = SCNNode() -// frontAlignmentTarget = SCNNode.init(geometry: SCNSphere.init(radius: 0.001)) -// frontAlignmentTarget.name = "frontAlignmentTarget" -// frontAlignmentTarget.geometry?.firstMaterial?.diffuse.contents = UIColor.yellow -// frontAlignmentTarget.geometry?.firstMaterial?.emission.contents = UIColor.yellow -// frontAlignmentTarget.position = SCNVector3((minX + maxX) / 2.0, (minY + maxY) / 2.0, maxZ) -// frontAlignmentTarget.renderingOrder = 20000 -// frontAlignmentTarget.geometry?.firstMaterial?.readsFromDepthBuffer = false -// self.studySceneConstruction?.superNode.addChildNode(frontAlignmentTarget) -// self.frontAlignmentTarget = frontAlignmentTarget - -// var backAlignmentTarget = SCNNode() -// backAlignmentTarget = SCNNode.init(geometry: SCNSphere.init(radius: 0.005)) -// backAlignmentTarget.name = "backAlignmentTarget" -// backAlignmentTarget.geometry?.firstMaterial?.diffuse.contents = UIColor.red -// backAlignmentTarget.geometry?.firstMaterial?.emission.contents = UIColor.red -// backAlignmentTarget.position = SCNVector3((minX + maxX) / 2.0, (minY + maxY) / 2.0, minZ) -// backAlignmentTarget.renderingOrder = 19999 -// backAlignmentTarget.geometry?.firstMaterial?.readsFromDepthBuffer = false -// self.studySceneConstruction?.superNode.addChildNode(backAlignmentTarget) -// self.backAlignmentTarget = backAlignmentTarget - } - } - } - - func startStudy() { - if (self.testRun) { - self.testRun = false - self.trialNum = -1 - self.calculateNextTarget() - } else { - if self.nextSceneAligned { - self.trialNum = max(self.trialNum - 1, -1) - } else { - self.trialNum = max(self.trialNum - 2, -1) - } - self.trialRedo = self.trialRedo + 1 - self.trialRedoOngoing = true - self.calculateNextTarget() - } - } - - func stopStudy() { - self.testRun = true - self.trialNum = -1 - } - - @IBAction func secondSoftwareButtonPressed(_ sender: Any) { - let buttonEventDict:[String: Any] = ["buttonPressed": Button.Button2, "buttonState" : true] - NotificationCenter.default.post(name: .softwarePenButtonEvent, object: nil, userInfo: buttonEventDict) - } - - @IBAction func secondSoftwareButtonReleased(_ sender: Any) { - let buttonEventDict:[String: Any] = ["buttonPressed": Button.Button2, "buttonState" : false] - NotificationCenter.default.post(name: .softwarePenButtonEvent, object: nil, userInfo: buttonEventDict) - } - -} diff --git a/iOS App/ARPen/Plugins/DepthVisualizationPlugins/ShadowPlugin.swift b/iOS App/ARPen/Plugins/DepthVisualizationPlugins/ShadowPlugin.swift deleted file mode 100644 index d1bd9a0..0000000 --- a/iOS App/ARPen/Plugins/DepthVisualizationPlugins/ShadowPlugin.swift +++ /dev/null @@ -1,97 +0,0 @@ - -import Foundation -import ARKit -import SpriteKit - -class ShadowPlugin: MinVisPlugin { - - var gridPlane : SCNNode? - - override init(){ - super.init() - self.pluginIdentifier = "Shadow" - } - - override func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { - - super.didUpdateFrame(scene: scene, buttons: buttons) - - self.gridPlane?.worldPosition.y = (scene.rootNode.childNode(withName: "iDevice Camera", recursively: false)?.worldPosition.y ?? 0.0) - 0.1 - - if self.helpActive { - if self.gridPlane?.parent == nil{ - self.studySceneConstruction?.superNode.addChildNode(self.gridPlane!) - } - } else { - self.gridPlane?.removeFromParentNode() - } - } - - override func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { - super.activatePlugin(withScene: scene, andView: view) - - let lightNode = SCNNode() - lightNode.name = "topLight" - lightNode.eulerAngles = SCNVector3Make(Float.pi * 1.5, 0, 0); - - lightNode.light = SCNLight() - lightNode.light?.type = .directional - lightNode.light?.color = UIColor.white - - // scene distances are rather small - lightNode.light?.zNear = 0.01 - lightNode.light?.zFar = 0.5 - - // casting shadows in 90° angle produces artifacts, ramping up shadow samples "fixes" it - lightNode.light?.castsShadow = true - lightNode.light?.shadowRadius = 1.0 - lightNode.light?.shadowSampleCount = 16 - - self.currentScene?.drawingNode.addChildNode(lightNode) - - - var minX = Float.infinity - var maxX = -Float.infinity - var minY = Float.infinity - var maxY = -Float.infinity - var minZ = Float.infinity - var maxZ = -Float.infinity - - self.studySceneConstruction?.superNode.childNodes.forEach({ - if ($0.position.x < minX) { - minX = $0.position.x - } else if ($0.position.x > maxX) { - maxX = $0.position.x - } - - if ($0.position.y < minY) { - minY = $0.position.y - } else if ($0.position.y > maxY) { - maxY = $0.position.y - } - - if ($0.position.z < minZ) { - minZ = $0.position.z - } else if ($0.position.z > maxZ) { - maxZ = $0.position.z - } - }) - - var gridPlane = SCNNode() - gridPlane = SCNNode.init(geometry: SCNPlane(width: 1, height: 1)) - gridPlane.name = "gridPlane" - gridPlane.geometry?.firstMaterial?.diffuse.contents = UIColor.white - gridPlane.geometry?.firstMaterial?.transparency = 0.75 - gridPlane.position = SCNVector3((minX + maxX) / 2.0, (minY + maxY) / 2.0, (minZ + maxZ) / 2.0) - gridPlane.rotation = SCNVector4(1, 0, 0, -0.5 * Double.pi) - self.studySceneConstruction?.superNode.addChildNode(gridPlane) - self.gridPlane = gridPlane - } - - override func deactivatePlugin() { - self.currentScene?.drawingNode.childNode(withName: "topLight", recursively: false)?.removeFromParentNode() - - super.deactivatePlugin() - } - -} diff --git a/iOS App/ARPen/Plugins/LoftPlugin.swift b/iOS App/ARPen/Plugins/LoftPlugin.swift deleted file mode 100755 index ac64fea..0000000 --- a/iOS App/ARPen/Plugins/LoftPlugin.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// LoftPlugin.swift -// ARPen -// -// Created by Jan Benscheid on 16.04.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation -import ARKit - -class LoftPlugin: ModelingPlugin { - - private var freePaths: [ARPPath] = [ARPPath]() - private var loft: ARPLoft? - private var busy: Bool = false - - override init() { - - super.init() - - curveDesigner.didCompletePath = self.didCompletePath - - self.pluginImage = UIImage.init(named: "Loft") - self.pluginInstructionsImage = UIImage.init(named: "PaintPluginInstructions") - self.pluginIdentifier = "Loft" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "ARMenusPluginDisabled") - } - - override func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { - super.activatePlugin(withScene: scene, andView: view) - - self.freePaths.removeAll() - self.loft = nil - } - - func didCompletePath(_ path: ARPPath) { - - if !(path.closed || path.points.count == 1) { - return - } - - path.flatten() - freePaths.append(path) - - DispatchQueue.global(qos: .userInitiated).async { - if let l = self.loft { - l.addProfile(path) - self.freePaths.removeAll(where: { $0 === path }) - } else { - if self.freePaths.count >= 2 { - let paths = [self.freePaths.removeFirst(), self.freePaths.removeFirst()] - if let l = try? ARPLoft(profiles: paths) { - self.loft = l - DispatchQueue.main.async { - self.currentScene?.drawingNode.addChildNode(l) - } - } - } - } - } - } -} diff --git a/iOS App/ARPen/Plugins/ModelingPlugin.swift b/iOS App/ARPen/Plugins/ModelingPlugin.swift deleted file mode 100755 index b08bb94..0000000 --- a/iOS App/ARPen/Plugins/ModelingPlugin.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// CurvePlugin.swift -// ARPen -// -// Created by Jan Benscheid on 30.06.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation -import ARKit - -class ModelingPlugin: Plugin { - - - @IBOutlet weak var button1Label: UILabel! - @IBOutlet weak var button2Label: UILabel! - @IBOutlet weak var button3Label: UILabel! - - /// The curve designer "sub-plugin", responsible for the interactive path creation - var curveDesigner: CurveDesigner - - override init() { - // Initialize curve designer - curveDesigner = CurveDesigner() - - super.init() - - //Specify Plugin Information - self.pluginImage = UIImage.init(named: "PaintPlugin") - self.pluginInstructionsImage = UIImage.init(named: "PaintPluginInstructions") - self.pluginIdentifier = "Paint Curves" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "ARMenusPluginDisabled") - - // This UI contains buttons to represent the other two buttons on the pen and an undo button - // Important: when using this xib-file, implement the IBActions shown below and the IBOutlets above - nibNameOfCustomUIView = "AllButtonsAndUndo" - - } - - override func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { - super.activatePlugin(withScene: scene, andView: view) - - self.curveDesigner.reset() - - self.button1Label.text = "Finish" - self.button2Label.text = "Sharp Corner" - self.button3Label.text = "Round Corner" - } - - override func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { - curveDesigner.update(scene: scene, buttons: buttons) - } - - @IBAction func softwarePenButtonPressed(_ sender: UIButton) { - var buttonEventDict = [String: Any]() - switch sender.tag { - case 2: - buttonEventDict = ["buttonPressed": Button.Button2, "buttonState" : true] - case 3: - buttonEventDict = ["buttonPressed": Button.Button3, "buttonState" : true] - default: - print("other button pressed") - } - NotificationCenter.default.post(name: .softwarePenButtonEvent, object: nil, userInfo: buttonEventDict) - } - - @IBAction func softwarePenButtonReleased(_ sender: UIButton) { - var buttonEventDict = [String: Any]() - switch sender.tag { - case 2: - buttonEventDict = ["buttonPressed": Button.Button2, "buttonState" : false] - case 3: - buttonEventDict = ["buttonPressed": Button.Button3, "buttonState" : false] - default: - print("other button pressed") - } - NotificationCenter.default.post(name: .softwarePenButtonEvent, object: nil, userInfo: buttonEventDict) - } - @IBAction func undoButtonPressed(_ sender: Any) { - curveDesigner.undo() - } -} diff --git a/iOS App/ARPen/Plugins/ObjectCreationPlugin.swift b/iOS App/ARPen/Plugins/ObjectCreationPlugin.swift index 325e697..2726935 100644 --- a/iOS App/ARPen/Plugins/ObjectCreationPlugin.swift +++ b/iOS App/ARPen/Plugins/ObjectCreationPlugin.swift @@ -14,20 +14,18 @@ import ARKit */ class ObjectCreationPlugin: Plugin { + var pluginImage: UIImage? = UIImage.init(named: "ObjectCreationPlugin") + var pluginInstructionsImage: UIImage? = UIImage.init(named: "DefaultInstructions") + var pluginIdentifier: String = "Object Creation" + var needsBluetoothARPen: Bool = false + var pluginDisabledImage: UIImage? = UIImage.init(named: "ARMenusPluginDisabled") + var currentScene : PenScene? + var currentView: ARSCNView? + private var pointArray: [SCNVector3] = [] private var alreadyAdded = false - override init() { - super.init() - - self.pluginImage = UIImage.init(named: "ObjectCreationPlugin") - self.pluginInstructionsImage = UIImage.init(named: "DefaultInstructions") - self.pluginIdentifier = "Object Creation" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "ARMenusPluginDisabled") - } - - override func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { + func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { if !buttons[.Button2]!{ alreadyAdded = false return @@ -66,4 +64,15 @@ class ObjectCreationPlugin: Plugin { } } + + func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { + self.currentScene = scene + self.currentView = view + } + + func deactivatePlugin() { + self.currentScene = nil + self.currentView = nil + } + } diff --git a/iOS App/ARPen/Plugins/PaintPlugin.swift b/iOS App/ARPen/Plugins/PaintPlugin.swift index f815bc9..812045a 100644 --- a/iOS App/ARPen/Plugins/PaintPlugin.swift +++ b/iOS App/ARPen/Plugins/PaintPlugin.swift @@ -11,6 +11,14 @@ import ARKit class PaintPlugin: Plugin { + var pluginImage : UIImage? = UIImage.init(named: "PaintPlugin") + var pluginInstructionsImage: UIImage? = UIImage.init(named: "PaintPluginInstructions") + var pluginIdentifier: String = "Draw" + var needsBluetoothARPen: Bool = false + var pluginDisabledImage: UIImage? = UIImage.init(named: "ARMenusPluginDisabled") + var currentScene : PenScene? + var currentView: ARSCNView? + var penColor: UIColor = UIColor.init(red: 0.73, green: 0.12157, blue: 0.8, alpha: 1) /** The previous point is the point of the pencil one frame before. @@ -22,19 +30,8 @@ class PaintPlugin: Plugin { private var currentLine : [SCNNode]? private var removedOneLine = false - - override init() { - super.init() - - self.pluginImage = UIImage.init(named: "PaintPlugin") - self.pluginInstructionsImage = UIImage.init(named: "PaintPluginInstructions") - self.pluginIdentifier = "Draw" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "ARMenusPluginDisabled") - } - func undoPreviousAction() { - if (self.previousDrawnLineNodes?.count ?? 0 > 0) { + if (self.previousDrawnLineNodes!.count > 0) { let lastLine = self.previousDrawnLineNodes?.last self.previousDrawnLineNodes?.removeLast() @@ -46,7 +43,7 @@ class PaintPlugin: Plugin { } } - override func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { + func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { guard scene.markerFound else { //Don't reset the previous point to avoid disconnected lines if the marker detection failed for some frames //self.previousPoint = nil @@ -87,9 +84,16 @@ class PaintPlugin: Plugin { } - override func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { - super.activatePlugin(withScene: scene, andView: view) + + func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { + self.currentScene = scene + self.currentView = view previousDrawnLineNodes = [[SCNNode]]() } + func deactivatePlugin() { + self.currentScene = nil + self.currentView = nil + } + } diff --git a/iOS App/ARPen/Plugins/Plugin.swift b/iOS App/ARPen/Plugins/Plugin.swift index 570d448..7bcd260 100644 --- a/iOS App/ARPen/Plugins/Plugin.swift +++ b/iOS App/ARPen/Plugins/Plugin.swift @@ -9,55 +9,28 @@ import ARKit /** - The Plugin structure. If you want to write a new plugin you must inherit from this class. + The Plugin protocol. If you want to write a new plugin you must use this protocol. */ -class Plugin: NSObject { +protocol Plugin { - var pluginImage : UIImage? - var pluginIdentifier : String = "ARPen Plugin" + var pluginImage : UIImage? { get } + var pluginIdentifier : String { get } - //to use a custom UI for your plugin: - // 1) create a new xib file in the folder "PluginUIs". The name should be that of the plugin - // 2) set the main view in the xib file as a "PassthroughView" - // 3) set the background color of the view to clear color - // 4) to use outlets and actions, set the new plugin class as the file owner in the xib - // 5) in the init method of your plugin, set "nibNameOfCustomUIView" to the file name of your xib - // (an example for these steps is shown for the CubeByExtractionPlugin) + var needsBluetoothARPen: Bool { get } - //view for custom UI elements. Will be added as a subview to the main view when the plugin is activated. - //the view has to be a PassthroughView (see helper class) to only react to touches on its UI elements and not block the underlying AR view - var customPluginUI : PassthroughView? - //this holds the name of the xib file with the custom UI. If set (e.g. in the init method of the new plugin) this loads the new UI and assigns it to the customPluginUI property - var nibNameOfCustomUIView : String? = nil {didSet{ - if let nibNameOfCustomUI = nibNameOfCustomUIView, let customView = UINib(nibName: nibNameOfCustomUI, bundle: .main).instantiate(withOwner: self, options: nil).first as? PassthroughView { - customPluginUI = customView - } - }} - - var needsBluetoothARPen: Bool = false - - var currentScene : PenScene? - var currentView : ARSCNView? - - var pluginInstructionsImage: UIImage? - var pluginDisabledImage: UIImage? + var currentScene : PenScene? {get set} + var currentView : ARSCNView? {get set} + var pluginInstructionsImage: UIImage? { get } + var pluginDisabledImage: UIImage? { get } /** This method must be implemented by all protocols. Params: - scene: The current PenScene instance. There you can find a lot state information about the pen. - buttons: An array of all buttons and there state. If buttons[.Button1] is true, then the buttons is pressed at the moment. */ - func didUpdateFrame(scene: PenScene, buttons: [Button: Bool]){ - - } + func didUpdateFrame(scene: PenScene, buttons: [Button: Bool]) - func activatePlugin(withScene scene: PenScene, andView view: ARSCNView){ - self.currentScene = scene - self.currentView = view - } - func deactivatePlugin(){ - self.currentScene = nil - self.currentView = nil - } + func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) + func deactivatePlugin() } diff --git a/iOS App/ARPen/Plugins/PluginUIs/AllButtonsAndUndo.xib b/iOS App/ARPen/Plugins/PluginUIs/AllButtonsAndUndo.xib deleted file mode 100644 index 751f5a0..0000000 --- a/iOS App/ARPen/Plugins/PluginUIs/AllButtonsAndUndo.xib +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iOS App/ARPen/Plugins/PluginUIs/SecondButton.xib b/iOS App/ARPen/Plugins/PluginUIs/SecondButton.xib deleted file mode 100644 index 9c4a8ae..0000000 --- a/iOS App/ARPen/Plugins/PluginUIs/SecondButton.xib +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iOS App/ARPen/Plugins/PyramidByDraggingPlugin.swift b/iOS App/ARPen/Plugins/PyramidByDraggingPlugin.swift index 9e18cac..c03ea1b 100644 --- a/iOS App/ARPen/Plugins/PyramidByDraggingPlugin.swift +++ b/iOS App/ARPen/Plugins/PyramidByDraggingPlugin.swift @@ -13,23 +13,21 @@ class PyramidByDraggingPlugin: Plugin, UserStudyRecordPluginProtocol { //reference to userStudyRecordManager to add new records var recordManager: UserStudyRecordManager! + var pluginImage : UIImage? = UIImage.init(named: "PyramidPlugin") + var pluginInstructionsImage: UIImage? = UIImage.init(named: "PyramidPluginInstructions") + var pluginIdentifier: String = "Pyramid" + var needsBluetoothARPen: Bool = false + var pluginDisabledImage: UIImage? = UIImage.init(named: "ARMenusPluginDisabled") + var currentScene : PenScene? + var currentView: ARSCNView? + /** The starting point is the point of the pencil where the button was first pressed. If this var is nil, there was no initial point */ private var startingPoint: SCNVector3? - override init() { - super.init() - - self.pluginImage = UIImage.init(named: "PyramidPlugin") - self.pluginInstructionsImage = UIImage.init(named: "PyramidPluginInstructions") - self.pluginIdentifier = "Pyramid" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "ARMenusPluginDisabled") - } - - override func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { + func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { guard scene.markerFound else { //Don't reset the previous point to avoid restarting cube if the marker detection failed for some frames //self.startingPoint = nil @@ -93,5 +91,16 @@ class PyramidByDraggingPlugin: Plugin, UserStudyRecordPluginProtocol { } + + func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { + self.currentScene = scene + self.currentView = view + } + + func deactivatePlugin() { + self.currentScene = nil + self.currentView = nil + } + } diff --git a/iOS App/ARPen/Plugins/RaycastPainting.swift b/iOS App/ARPen/Plugins/RaycastPainting.swift new file mode 100644 index 0000000..1df4612 --- /dev/null +++ b/iOS App/ARPen/Plugins/RaycastPainting.swift @@ -0,0 +1,1077 @@ +// +// RaycastPainting.swift +// ARPen +// +// Created by Martin on 08.07.19. +// Copyright © 2019 RWTH Aachen. All rights reserved. +// + + +import Foundation +import ARKit + +class RaycastPainting: Plugin, UserStudyRecordPluginProtocol { + + var recordManager: UserStudyRecordManager! + var pluginImage : UIImage? = UIImage.init(named: "RaycastPainting") + var pluginInstructionsImage: UIImage? = UIImage.init(named: "RaycastPaintingPluginInstructions") + var pluginIdentifier: String = "Raycasting" + var needsBluetoothARPen: Bool = false + var pluginDisabledImage: UIImage? = UIImage.init(named: "ARMenusPluginDisabled") + var currentScene : PenScene? + var currentView: ARSCNView? + + var penColor: UIColor = UIColor.init(red: 0.73, green: 0.12157, blue: 0.8, alpha: 1) + + var objectCounter: Int = 0 + var objectCurrentlyBuilding: Bool = false + + let objectOpacity = 0.7 + let penTipOpacity = 0.5 + + let cubeSizeSmall = 0.08 + let cubeSizeLarge = 0.12 + + // trial data + var userID = 5 + var trialNumber = 0 + var trialID = -1 + + var trialList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + + var trialValid = true + + + private var dataExported = true + + /** + The starting point is the point of the pencil where the button was first pressed. + If this var is nil, there was no initial point + */ + private var startingPoint: SCNVector3? + + + /** + The previous point is the point of the pencil one frame before. + If this var is nil, there was no last point + */ + private var previousPoint: SCNVector3? + //collection of currently & last drawn line elements to offer undo + private var previousDrawnLineNodes: [[SCNNode]]? + private var currentLine : [SCNNode]? + private var removedOneLine = false + + private var closestPoint: SCNVector3? + + enum HitTestTypes: Int { + case notHittable = 0b0001 + case hittable = 0b0010 + } + + func undoPreviousAction() { + while (self.previousDrawnLineNodes!.count > 0) { + let lastLine = self.previousDrawnLineNodes?.last + + self.previousDrawnLineNodes?.removeLast() + + // Remove the previous line + for currentNode in lastLine! { + currentNode.removeFromParentNode() + } + + // remove current study object + guard let scene = currentScene else{ + return + } + + + if let arImageNode = scene.rootNode.childNode(withName: "ARImage", recursively: false){ + if let studyObjectNode = arImageNode.childNode(withName: "currentStudyObjectNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() + while (self.previousDrawnLineNodes!.count > 0){ + let lastLine = self.previousDrawnLineNodes?.last + + self.previousDrawnLineNodes?.removeLast() + + // Remove the previous line + for currentNode in lastLine! { + currentNode.removeFromParentNode() + } + } + } + } + } + + if (objectCounter > 0){ + objectCounter = objectCounter - 1 + } + + let targetMeasurementDict = [ + "TrialNumber" : String(describing: trialNumber), + "TrialID": String(describing: trialID), + "TrialValid": String(describing: false) + ] + self.recordManager.addNewRecord(withIdentifier: self.pluginIdentifier, andData: targetMeasurementDict) + } + + func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { + guard scene.markerFound else { + //Don't reset the previous point to avoid disconnected lines if the marker detection failed for some frames + //self.previousPoint = nil + return + } + + + + let pressed2 = buttons[Button.Button2]! + + if (pressed2){ + //print("retryTrial") + while (self.previousDrawnLineNodes!.count > 0) { + //print("removing line") + let lastLine = self.previousDrawnLineNodes?.last + + self.previousDrawnLineNodes?.removeLast() + + // Remove the previous line + for currentNode in lastLine! { + currentNode.removeFromParentNode() + } + } + let targetMeasurementDict = [ + "TrialNumber" : String(describing: trialNumber), + "TrialID": String(describing: trialID), + "TrialValid": String(describing: false) + ] + self.recordManager.addNewRecord(withIdentifier: self.pluginIdentifier, andData: targetMeasurementDict) + trialNumber = trialNumber + 1 + //retryTrial = false + } + + + // MARK: Button1, Drawing + + let pressed1 = buttons[Button.Button1]! + + + // Draw Ray from pen tip forward, to detect any valid object to draw on + + var hitPosition: SCNVector3? + hitPosition = nil + + let direction = scene.directionNode.position - scene.pencilPoint.position + // length of the ray. length = 1 is distance from marker node to pen tip + let length: Float = 3 + + let pointA = scene.pencilPoint.position + let pointB = scene.pencilPoint.position + SCNVector3(direction.x * length, direction.y * length, direction.z * length) + + scene.pencilPoint.categoryBitMask = HitTestTypes.notHittable.rawValue + + var objectList: [SCNNode] + if let arImageNode = scene.rootNode.childNode(withName: "ARImage", recursively: false){ + objectList = arImageNode.childNodes + }else{ + objectList = scene.drawingNode.childNodes + } + for obj in objectList { + if (obj.name?.hasPrefix("currentStudyObjectNode") ?? false){ + obj.categoryBitMask = HitTestTypes.hittable.rawValue + + for child in obj.childNodes{ + child.categoryBitMask = HitTestTypes.hittable.rawValue + } + } + + if (obj.name?.hasPrefix("currentStudyObjectBoxNode") ?? false){ + obj.categoryBitMask = HitTestTypes.hittable.rawValue + } + if (obj.name?.hasPrefix("currentStudyObjectSphereNode") ?? false){ + obj.categoryBitMask = HitTestTypes.hittable.rawValue + } + } + + + let rayHits = scene.rootNode.hitTestWithSegment(from: pointA, to: pointB, options: [SCNPhysicsWorld.TestOption.backfaceCulling.rawValue:false, SCNHitTestOption.categoryBitMask.rawValue : HitTestTypes.hittable.rawValue]) + + let rayHit = rayHits.first(where: {(($0.node.name?.hasPrefix("currentStudyObject") == true))}) + + let hitNode = SCNNode() + + self.previousPoint = self.previousPoint ?? pointA + + hitNode.position = rayHit?.worldCoordinates ?? self.previousPoint! + + hitPosition = hitNode.position + + + // For study: measure distance to closest ring position: + var closestDistance = Float(-1) + var closestPosition: SCNVector3? + closestPosition = nil + var numberOfPositions: Int = 0 + var averageError: Float = 0 + + if (hitPosition!.x == self.previousPoint!.x && hitPosition!.y == self.previousPoint!.y && hitPosition!.z == self.previousPoint!.z){ + //print("no hit") + + } + + if (hitPosition!.x != self.previousPoint!.x || hitPosition!.y != self.previousPoint!.y || hitPosition!.z != self.previousPoint!.z){ + //print("hit") + if pressed1, let previousPoint = self.previousPoint { + if currentLine == nil { + currentLine = [SCNNode]() + } + let cylinderNode = SCNNode() + + scene.directionNode.position = hitPosition ?? scene.directionNode.position + + cylinderNode.buildLineInTwoPointsWithRotation(from: + cylinderNode.convertPosition(previousPoint, to: scene.arAnchorImage), to: scene.directionNode.convertPosition(SCNVector3Zero, to: scene.arAnchorImage), radius: 0.001, color: penColor) + + cylinderNode.name = "cylinderLine" + + scene.arAnchorImage.addChildNode(cylinderNode) + //scene.drawingNode.addChildNode(cylinderNode) + + //add last drawn line element to currently drawn line collection + currentLine?.append(cylinderNode) + + + + var currentStudyObject: SCNNode? + + if (objectList.contains(where: {($0.name?.hasPrefix("currentStudyObjectNode") ?? false)})){ + + for obj in objectList{ + + if (obj.name?.hasPrefix("currentStudyObjectNode") ?? false){ + currentStudyObject = obj + } + } + } + + let studyObjectSize = Float(99) + let studyObjectCornerType = "CornerTypeUnknown" + + let targetMeasurementDict = [ + + "TrialNumber" : String(describing: trialNumber), + "TrialID" : String(describing: trialID), + "PenTipPositionX" : String(describing: scene.pencilPoint.position.x), + "PenTipPositionY" : String(describing: scene.pencilPoint.position.y), + "PenTipPositionZ" : String(describing: scene.pencilPoint.position.z), + "ProjectionPositionX" : String(describing: scene.projectionNode.position.x), + "ProjectionPositionY" : String(describing: scene.projectionNode.position.y), + "ProjectionPositionZ" : String(describing: scene.projectionNode.position.z), + "RelativeProjectionX" : String(describing: scene.projectionNode.convertPosition(SCNVector3Zero, to: scene.arAnchorImage).x), + "RelativeProjectionY" : String(describing: scene.projectionNode.convertPosition(SCNVector3Zero, to: scene.arAnchorImage).y), + "RelativeProjectionZ" : String(describing: scene.projectionNode.convertPosition(SCNVector3Zero, to: scene.arAnchorImage).z), + "StudyObjectPositionX" : String(describing: currentStudyObject?.position.x ?? -1), + "StudyObjectPositionY" : String(describing: currentStudyObject?.position.y ?? -1), + "StudyObjectPositionZ" : String(describing: currentStudyObject?.position.z ?? -1) + ] + + dataExported = false + self.recordManager.addNewRecord(withIdentifier: self.pluginIdentifier, andData: targetMeasurementDict) + } + + + + + } else if !pressed1 { + if let currentLine = self.currentLine { + self.previousDrawnLineNodes?.append(currentLine) + self.currentLine = nil + } + } + + + + self.previousPoint = hitPosition + scene.projectionNode.position = hitPosition! + + // MARK: Button3, Object Creation + + + //Check state of the first button -> used to create the cube + let pressed3 = buttons[Button.Button3]! + + //if the button is pressed -> either set the starting point of the cube (first action) or scale the cube to fit from the starting point to the current point + if pressed3 { + + if !dataExported { + //self.recordManager.saveToFile() + self.recordManager.urlToCSV() + dataExported = true + } + + var objectNode = SCNNode() + if let startingPoint = self.startingPoint { + // spawnPoint is the position at which StudyObjects are created + + //let spawnPoint = SCNVector3Zero + var spawnPoint = scene.pencilPoint.position + if let arImageNode = scene.rootNode.childNode(withName: "ARImage", recursively: false){ + spawnPoint = arImageNode.position + } + //see if there is an active box node that is currently being drawn. Otherwise create it + + // if no object exists: create new object + if !objectCurrentlyBuilding{ + // object exists already: delete it and create new object + // Case: study object was created as child of drawing node + if let studyObjectNode = scene.drawingNode.childNode(withName: "currentStudyObjectNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() + // while (self.previousDrawnLineNodes!.count > 0){ + // undoPreviousAction() + // } + } + + + if let studyObjectNode = scene.drawingNode.childNode(withName: "currentStudyObjectBoxNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() + // while (self.previousDrawnLineNodes!.count > 0){ + // undoPreviousAction() + // } + } + + if let studyObjectNode = scene.drawingNode.childNode(withName: "currentStudyObjectSphereNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() + // while (self.previousDrawnLineNodes!.count > 0){ + // undoPreviousAction() + // } + } + + if let studyObjectNode = scene.drawingNode.childNode(withName: "currentStudyObjectGuideNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() + // while (self.previousDrawnLineNodes!.count > 0){ + // undoPreviousAction() + // } + } + + // Case: study object was created as child of AR Image node + if let arImageNode = scene.rootNode.childNode(withName: "ARImage", recursively: false){ + if let studyObjectNode = arImageNode.childNode(withName: "currentStudyObjectNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() + + while (self.previousDrawnLineNodes!.count > 0){ + let lastLine = self.previousDrawnLineNodes?.last + self.previousDrawnLineNodes?.removeLast() + + // Remove the previous line + for currentNode in lastLine! { + currentNode.removeFromParentNode() + } + } + while (self.previousDrawnLineNodes!.count > 0) { + let lastLine = self.previousDrawnLineNodes?.last + + self.previousDrawnLineNodes?.removeLast() + + // Remove the previous line + for currentNode in lastLine! { + currentNode.removeFromParentNode() + } + } + for line in arImageNode.childNodes{ + if (line.name == "cylinderLine"){ + line.opacity = 0 + } + } + } + + + if let studyObjectNode = arImageNode.childNode(withName: "currentStudyObjectBoxNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() + // while (self.previousDrawnLineNodes!.count > 0){ + // undoPreviousAction() + // } + } + + if let studyObjectNode = arImageNode.childNode(withName: "currentStudyObjectSphereNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() + // while (self.previousDrawnLineNodes!.count > 0){ + // undoPreviousAction() + // } + } + + if let studyObjectNode = arImageNode.childNode(withName: "currentStudyObjectGuideNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() + // while (self.previousDrawnLineNodes!.count > 0){ + // undoPreviousAction() + // } + } + } + + if (objectCounter == 0 || objectCounter >= trialList.count){ + trialList.shuffle() + objectCounter = 0 + //print("shuffle") + } + + trialID = trialList[objectCounter] + + // whether previous object existed or not, create new one + switch trialList[objectCounter] { + case 0: + // flat corner small + objectNode = generateObject(size: 1, shape: 1, corner: 3, startingPoint: spawnPoint, scene: scene) + break + case 1: + // flat corner large + objectNode = generateObject(size: 2, shape: 1, corner: 3, startingPoint: spawnPoint, scene: scene) + break + case 2: + // exterior corner small + objectNode = generateObject(size: 1, shape: 1, corner: 1, startingPoint: spawnPoint, scene: scene) + break + case 3: + // exterior corner large + objectNode = generateObject(size: 2, shape: 1, corner: 1, startingPoint: spawnPoint, scene: scene) + break + case 4: + // interior corner small + objectNode = generateObject(size: 1, shape: 1, corner: 2, startingPoint: spawnPoint, scene: scene) + break + case 5: + // interior corner large + objectNode = generateObject(size: 2, shape: 1, corner: 2, startingPoint: spawnPoint, scene: scene) + break + case 6: + // Right Side Flat small + objectNode = generateObject(size: 1, shape: 1, corner: 4, startingPoint: spawnPoint, scene: scene) + break + case 7: + // Right Side Flat large + objectNode = generateObject(size: 2, shape: 1, corner: 4, startingPoint: spawnPoint, scene: scene) + break + case 8: + // Front Side Flat small + objectNode = generateObject(size: 1, shape: 1, corner: 5, startingPoint: spawnPoint, scene: scene) + break + case 9: + // Front Side Flat large + objectNode = generateObject(size: 2, shape: 1, corner: 5, startingPoint: spawnPoint, scene: scene) + break + + default: + break + } + + if let arImageNode = scene.rootNode.childNode(withName: "ARImage", recursively: false){ + arImageNode.addChildNode(objectNode) + } else { + scene.drawingNode.addChildNode(objectNode) + } + + objectCurrentlyBuilding = true + objectCounter += 1 + } + + + } + else { + //if the button is pressed but no startingPoint exists -> first frame with the button pressed. Set current pencil position as the start point + self.startingPoint = scene.pencilPoint.position + } + } + + + else { + //if the button is not pressed, check if a startingPoint is set -> released button. Reset the startingPoint to nil and set the name of the drawn box to "finished" + if objectCurrentlyBuilding{ + objectCurrentlyBuilding = false + } + if self.startingPoint != nil { + self.startingPoint = nil + if let boxNode = scene.drawingNode.childNode(withName: "currentStudyObjectNode", recursively: false), let boxNodeGeometry = boxNode.geometry as? SCNBox { + boxNode.name = "currentStudyObjectNode" + + //store a new record with the size of the finished box + let boxDimensionsDict = ["Width" : String(describing: boxNodeGeometry.width), "Height" : String(describing: boxNodeGeometry.height), "Length" : String(describing: boxNodeGeometry.length)] + self.recordManager.addNewRecord(withIdentifier: "BoxFinished", andData: boxDimensionsDict) + } + } + + } + + + + + // MARK: Study Data + + } + + + func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { + self.currentScene = scene + self.currentView = view + previousDrawnLineNodes = [[SCNNode]]() + + scene.pencilPoint.opacity = CGFloat(penTipOpacity) + scene.projectionNode.opacity = 1 + } + + func deactivatePlugin() { + guard let scene = self.currentScene else { + + self.currentScene = nil + self.currentView = nil + + return + + } + + if let arImageNode = scene.rootNode.childNode(withName: "ARImage", recursively: false){ + if let studyObjectNode = arImageNode.childNode(withName: "currentStudyObjectNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() + + while (self.previousDrawnLineNodes!.count > 0){ + let lastLine = self.previousDrawnLineNodes?.last + + self.previousDrawnLineNodes?.removeLast() + + // Remove the previous line + for currentNode in lastLine! { + currentNode.removeFromParentNode() + } + } + for line in arImageNode.childNodes{ + if (line.name == "cylinderLine"){ + line.opacity = 0 + } + } + } + } + + self.currentScene = nil + self.currentView = nil + + } + + // MARK: Object Creation + + func generateObject(size: Int, shape: Int, corner: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + var objectNode = SCNNode() + + switch corner{ + // interior + case 1: + objectNode = createCubeInterior(size: size, startingPoint: startingPoint, scene: scene) + break + // exterior + case 2: + objectNode = createCubeExterior(size: size, startingPoint: startingPoint, scene: scene) + break + // flat + case 3: + objectNode = createCubeFlat(size: size, startingPoint: startingPoint, scene: scene) + break + // flat + right side + case 4: + objectNode = createCubeFlatRightSide(size: size, startingPoint: startingPoint, scene: scene) + break + // flat + front side + case 5: + objectNode = createCubeFlatFrontSide(size: size, startingPoint: startingPoint,scene: scene) + break + default: break + } + + trialNumber = trialNumber+1 + + + return objectNode + + } + + func createCubeExterior(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + objectNode.addChildNode(boxNode) + + var spawnPoint = startingPoint + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeSmall/2, 0) + break + case 2: cubeSize = Float(cubeSizeLarge) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeLarge/2, 0) + break + default: cubeSize = 0.2 + break + } + + objectNode.position = spawnPoint + + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + //set the dimensions of the box + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + + + + // create guiding line around object + createTargetLine(parentNode: objectNode, cubeSize: cubeSize, height: cubeSize/2) + + + + boxNode.opacity = CGFloat(objectOpacity) + + return objectNode + } + + + func createTargetLine(parentNode: SCNNode, cubeSize: Float, height: Float){ + // create four guide boxes, so that they form a ring, not a solid plane + let guideNode1 = SCNNode() + let guideNode2 = SCNNode() + let guideNode3 = SCNNode() + let guideNode4 = SCNNode() + guideNode1.name = "currentStudyObjectGuideNode" + guideNode2.name = "currentStudyObjectGuideNode" + guideNode3.name = "currentStudyObjectGuideNode" + guideNode4.name = "currentStudyObjectGuideNode" + parentNode.addChildNode(guideNode1) + parentNode.addChildNode(guideNode2) + parentNode.addChildNode(guideNode3) + parentNode.addChildNode(guideNode4) + + guideNode1.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry1 = guideNode1.geometry as! SCNBox + guideNode2.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry2 = guideNode2.geometry as! SCNBox + guideNode3.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry3 = guideNode3.geometry as! SCNBox + guideNode4.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry4 = guideNode4.geometry as! SCNBox + + let guideSize = cubeSize * 1.001 + + guideNode1.position = SCNVector3(cubeSize/2, height, 0) + guideNode2.position = SCNVector3(-cubeSize/2, height, 0) + guideNode3.position = SCNVector3(0, height, cubeSize/2) + guideNode4.position = SCNVector3(0, height, -cubeSize/2) + + //set the dimensions of the boxes + guideNodeGeometry1.width = CGFloat(0.0005) + guideNodeGeometry1.height = CGFloat(0.0005) + guideNodeGeometry1.length = CGFloat(guideSize) + guideNodeGeometry2.width = CGFloat(0.0005) + guideNodeGeometry2.height = CGFloat(0.0005) + guideNodeGeometry2.length = CGFloat(guideSize) + guideNodeGeometry3.width = CGFloat(guideSize) + guideNodeGeometry3.height = CGFloat(0.0005) + guideNodeGeometry3.length = CGFloat(0.0005) + guideNodeGeometry4.width = CGFloat(guideSize) + guideNodeGeometry4.height = CGFloat(0.0005) + guideNodeGeometry4.length = CGFloat(0.0005) + + guideNodeGeometry1.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry2.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry3.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry4.materials.first?.diffuse.contents = UIColor.red + + // Add starting point and draw direction signifiers + let startPointNode = SCNNode() + startPointNode.name = "currentStudyObjectStartingPointNode" + parentNode.addChildNode(startPointNode) + startPointNode.geometry = SCNSphere.init(radius: 0.002) + let startPointGeometry = startPointNode.geometry as! SCNSphere + startPointGeometry.materials.first?.diffuse.contents = UIColor.red + startPointNode.position = SCNVector3(-cubeSize/2, height, cubeSize/2) + + + let directionArrowNode = SCNNode() + directionArrowNode.name = "currentStudyObjectDirectionArrowNode" + parentNode.addChildNode(directionArrowNode) + directionArrowNode.geometry = SCNCone.init(topRadius: 0, bottomRadius: 0.0035, height: 0.008) + let directionArrowGeometry = directionArrowNode.geometry as! SCNCone + directionArrowGeometry.materials.first?.diffuse.contents = UIColor.red + directionArrowNode.position = SCNVector3(0, height, cubeSize/2) + directionArrowNode.eulerAngles = SCNVector3(-cubeSize/4, 0, -Float.pi/2) + } + + + func createCubeInterior(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + scene.drawingNode.addChildNode(objectNode) + objectNode.addChildNode(boxNode) + + var spawnPoint = startingPoint + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeSmall/2, 0) + break + case 2: cubeSize = Float(cubeSizeLarge) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeLarge/2, 0) + break + default: cubeSize = 0.2 + break + } + + objectNode.position = spawnPoint + + // create box itself + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + // create "base" + let baseNode = SCNNode() + baseNode.name = "currentStudyObjectBoxNode" + objectNode.addChildNode(baseNode) + + baseNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let baseNodeGeometry = baseNode.geometry as! SCNBox + + baseNodeGeometry.width = CGFloat(cubeSize * 1.4) + baseNodeGeometry.height = CGFloat(0.001) + baseNodeGeometry.length = CGFloat(cubeSize * 1.4) + + + // create guiding line around object + createTargetLine(parentNode: objectNode, cubeSize: cubeSize, height: 0) + + + boxNode.opacity = CGFloat(objectOpacity) + baseNode.opacity = CGFloat(objectOpacity) + baseNode.position = SCNVector3(0, -0.001, 0) + + return objectNode + } + + func createCubeFlat(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + scene.drawingNode.addChildNode(objectNode) + objectNode.addChildNode(boxNode) + + var spawnPoint = startingPoint + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeSmall/2, 0) + break + case 2: cubeSize = Float(cubeSizeLarge) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeLarge/2, 0) + break + default: cubeSize = 0.2 + break + } + + objectNode.position = spawnPoint + + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + //set the dimensions of the box + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + + // create guiding line around object + createTargetLine(parentNode: objectNode, cubeSize: cubeSize, height: 0) + + + boxNode.opacity = CGFloat(objectOpacity) + + return objectNode + + } + + func createCubeFlatRightSide(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + scene.drawingNode.addChildNode(objectNode) + objectNode.addChildNode(boxNode) + + var spawnPoint = startingPoint + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeSmall/2, 0) + break + case 2: cubeSize = Float(cubeSizeLarge) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeLarge/2, 0) + break + default: cubeSize = 0.2 + break + } + + objectNode.position = spawnPoint + + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + //set the dimensions of the box + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + + + boxNode.opacity = CGFloat(objectOpacity) + + + // create guiding line around object + // create four guide boxes, so that they form a ring, not a solid plane + let guideNode1 = SCNNode() + let guideNode2 = SCNNode() + let guideNode3 = SCNNode() + let guideNode4 = SCNNode() + guideNode1.name = "currentStudyObjectGuideNode" + guideNode2.name = "currentStudyObjectGuideNode" + guideNode3.name = "currentStudyObjectGuideNode" + guideNode4.name = "currentStudyObjectGuideNode" + objectNode.addChildNode(guideNode1) + objectNode.addChildNode(guideNode2) + objectNode.addChildNode(guideNode3) + objectNode.addChildNode(guideNode4) + + guideNode1.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry1 = guideNode1.geometry as! SCNBox + guideNode2.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry2 = guideNode2.geometry as! SCNBox + guideNode3.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry3 = guideNode3.geometry as! SCNBox + guideNode4.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry4 = guideNode4.geometry as! SCNBox + + let guideSize = cubeSize * 1.001 + + // 1 = Top, 2 = Back, 3 = Bottom, 4 = Front + guideNode1.position = SCNVector3(cubeSize/2, cubeSize/2, 0) + guideNode2.position = SCNVector3(cubeSize/2, 0, cubeSize/2) + guideNode3.position = SCNVector3(cubeSize/2, -cubeSize/2, 0) + guideNode4.position = SCNVector3(cubeSize/2, 0, -cubeSize/2) + + //set the dimensions of the boxes + guideNodeGeometry1.width = CGFloat(0.0005) + guideNodeGeometry1.height = CGFloat(0.0005) + guideNodeGeometry1.length = CGFloat(guideSize) + guideNodeGeometry2.width = CGFloat(0.0005) + guideNodeGeometry2.height = CGFloat(guideSize) + guideNodeGeometry2.length = CGFloat(0.0005) + guideNodeGeometry3.width = CGFloat(0.0005) + guideNodeGeometry3.height = CGFloat(0.0005) + guideNodeGeometry3.length = CGFloat(guideSize) + guideNodeGeometry4.width = CGFloat(0.0005) + guideNodeGeometry4.height = CGFloat(guideSize) + guideNodeGeometry4.length = CGFloat(0.0005) + + guideNodeGeometry1.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry2.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry3.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry4.materials.first?.diffuse.contents = UIColor.red + + // Add starting point and draw direction signifiers + let startPointNode = SCNNode() + startPointNode.name = "currentStudyObjectStartingPointNode" + objectNode.addChildNode(startPointNode) + startPointNode.geometry = SCNSphere.init(radius: 0.002) + let startPointGeometry = startPointNode.geometry as! SCNSphere + startPointGeometry.materials.first?.diffuse.contents = UIColor.red + startPointNode.position = SCNVector3(cubeSize/2, -cubeSize/2, cubeSize/2) + + + let directionArrowNode = SCNNode() + directionArrowNode.name = "currentStudyObjectDirectionArrowNode" + objectNode.addChildNode(directionArrowNode) + directionArrowNode.geometry = SCNCone.init(topRadius: 0, bottomRadius: 0.0035, height: 0.008) + let directionArrowGeometry = directionArrowNode.geometry as! SCNCone + directionArrowGeometry.materials.first?.diffuse.contents = UIColor.red + directionArrowNode.position = SCNVector3(cubeSize/2, 0, cubeSize/2) + directionArrowNode.eulerAngles = SCNVector3(0, 0, 0) + + + + + return objectNode + } + + func createCubeFlatFrontSide(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + scene.drawingNode.addChildNode(objectNode) + objectNode.addChildNode(boxNode) + + var spawnPoint = startingPoint + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeSmall/2, 0) + break + case 2: cubeSize = Float(cubeSizeLarge) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeLarge/2, 0) + break + default: cubeSize = 0.2 + break + } + + objectNode.position = spawnPoint + + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + //set the dimensions of the box + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + + + boxNode.opacity = CGFloat(objectOpacity) + + + // create guiding line around object + // create four guide boxes, so that they form a ring, not a solid plane + let guideNode1 = SCNNode() + let guideNode2 = SCNNode() + let guideNode3 = SCNNode() + let guideNode4 = SCNNode() + guideNode1.name = "currentStudyObjectGuideNode" + guideNode2.name = "currentStudyObjectGuideNode" + guideNode3.name = "currentStudyObjectGuideNode" + guideNode4.name = "currentStudyObjectGuideNode" + objectNode.addChildNode(guideNode1) + objectNode.addChildNode(guideNode2) + objectNode.addChildNode(guideNode3) + objectNode.addChildNode(guideNode4) + + guideNode1.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry1 = guideNode1.geometry as! SCNBox + guideNode2.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry2 = guideNode2.geometry as! SCNBox + guideNode3.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry3 = guideNode3.geometry as! SCNBox + guideNode4.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry4 = guideNode4.geometry as! SCNBox + + let guideSize = cubeSize * 1.001 + + // 1 = Top, 2 = Right, 3 = Bottom, 4 = Left + guideNode1.position = SCNVector3(0, cubeSize/2, cubeSize/2) + guideNode2.position = SCNVector3(cubeSize/2, 0, cubeSize/2) + guideNode3.position = SCNVector3(0, -cubeSize/2, cubeSize/2) + guideNode4.position = SCNVector3(-cubeSize/2, 0, cubeSize/2) + + //set the dimensions of the boxes + guideNodeGeometry1.width = CGFloat(guideSize) + guideNodeGeometry1.height = CGFloat(0.0005) + guideNodeGeometry1.length = CGFloat(0.0005) + guideNodeGeometry2.width = CGFloat(0.0005) + guideNodeGeometry2.height = CGFloat(guideSize) + guideNodeGeometry2.length = CGFloat(0.0005) + guideNodeGeometry3.width = CGFloat(guideSize) + guideNodeGeometry3.height = CGFloat(0.0005) + guideNodeGeometry3.length = CGFloat(0.0005) + guideNodeGeometry4.width = CGFloat(0.0005) + guideNodeGeometry4.height = CGFloat(guideSize) + guideNodeGeometry4.length = CGFloat(0.0005) + + guideNodeGeometry1.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry2.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry3.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry4.materials.first?.diffuse.contents = UIColor.red + + // Add starting point and draw direction signifiers + let startPointNode = SCNNode() + startPointNode.name = "currentStudyObjectStartingPointNode" + objectNode.addChildNode(startPointNode) + startPointNode.geometry = SCNSphere.init(radius: 0.002) + let startPointGeometry = startPointNode.geometry as! SCNSphere + startPointGeometry.materials.first?.diffuse.contents = UIColor.red + startPointNode.position = SCNVector3(-cubeSize/2, cubeSize/2, cubeSize/2) + + + let directionArrowNode = SCNNode() + directionArrowNode.name = "currentStudyObjectDirectionArrowNode" + objectNode.addChildNode(directionArrowNode) + directionArrowNode.geometry = SCNCone.init(topRadius: 0, bottomRadius: 0.0035, height: 0.008) + let directionArrowGeometry = directionArrowNode.geometry as! SCNCone + directionArrowGeometry.materials.first?.diffuse.contents = UIColor.red + directionArrowNode.position = SCNVector3(0, cubeSize/2, cubeSize/2) + directionArrowNode.eulerAngles = SCNVector3(0, 0, -Float.pi/2) + + + + + return objectNode + } + + + } + diff --git a/iOS App/ARPen/Plugins/RevolvePlugins/RevolvePluginProfileAndAxis.swift b/iOS App/ARPen/Plugins/RevolvePlugins/RevolvePluginProfileAndAxis.swift deleted file mode 100755 index 06bd774..0000000 --- a/iOS App/ARPen/Plugins/RevolvePlugins/RevolvePluginProfileAndAxis.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// RevolvePluginProfileAndAxis.swift -// ARPen -// -// Created by Jan Benscheid on 15.04.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation -import ARKit - -class RevolvePluginProfileAndAxis: ModelingPlugin { - - private var freePaths: [ARPPath] = [ARPPath]() - private var busy: Bool = false - - - override init() { - super.init() - - curveDesigner.didCompletePath = self.didCompletePath - - self.pluginImage = UIImage.init(named: "Revolve(Profile+Axis)") - self.pluginInstructionsImage = UIImage.init(named: "PaintPluginInstructions") - self.pluginIdentifier = "Revolve (Profile + Axis)" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "ARMenusPluginDisabled") - } - - func didCompletePath(_ path: ARPPath) { - freePaths.append(path) - if let profile = freePaths.first(where: { !$0.closed && $0.points.count > 2 }), - let axisPath = freePaths.first(where: { !$0.closed && $0.points.count == 2 }) { - - DispatchQueue.global(qos: .userInitiated).async { - profile.flatten() - - if let revolution = try? ARPRevolution(profile: profile, axis: axisPath) { - - DispatchQueue.main.async { - self.currentScene?.drawingNode.addChildNode(revolution) - self.freePaths.removeAll(where: { $0 === profile || $0 === axisPath }) - } - } - } - } - } -} diff --git a/iOS App/ARPen/Plugins/RevolvePlugins/RevolvePluginProfileAndCircle.swift b/iOS App/ARPen/Plugins/RevolvePlugins/RevolvePluginProfileAndCircle.swift deleted file mode 100755 index deff988..0000000 --- a/iOS App/ARPen/Plugins/RevolvePlugins/RevolvePluginProfileAndCircle.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// RevolvePluginProfileAndCircle.swift -// ARPen -// -// Created by Jan Benscheid on 15.04.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation -import ARKit - -class RevolvePluginProfileAndCircle: ModelingPlugin { - - private var freePaths: [ARPPath] = [ARPPath]() - private var busy: Bool = false - - override init() { - - super.init() - - // Listen to the `didCompletePath` event. - curveDesigner.didCompletePath = self.didCompletePath - - self.pluginImage = UIImage.init(named: "Revolve(Profile+Circle)") - self.pluginInstructionsImage = UIImage.init(named: "PaintPluginInstructions") - self.pluginIdentifier = "Revolve (Profile + Circle" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "ARMenusPluginDisabled") - } - - func didCompletePath(_ path: ARPPath) { - freePaths.append(path) - if let profile = freePaths.first(where: { !$0.closed && $0.points.count >= 2 }), - let circle = freePaths.first(where: { $0.closed }) { - - DispatchQueue.global(qos: .userInitiated).async { - profile.flatten() - circle.flatten() - - let axisDir = circle.getPC1() - let axisPos = OCCTAPI.shared.circleCenter(circle.getPointsAsVectors()) - let axisPath = ARPPath(points: [ - ARPPathNode(axisPos), - ARPPathNode(axisPos + axisDir) - ], closed: false); - - if let revolution = try? ARPRevolution(profile: profile, axis: axisPath) { - - DispatchQueue.main.async { - self.currentScene?.drawingNode.addChildNode(revolution) - circle.removeFromParentNode() - self.freePaths.removeAll(where: { $0 === profile || $0 == circle }) - } - } - } - } - } -} diff --git a/iOS App/ARPen/Plugins/RevolvePlugins/RevolvePluginTwoProfiles.swift b/iOS App/ARPen/Plugins/RevolvePlugins/RevolvePluginTwoProfiles.swift deleted file mode 100755 index 66a3584..0000000 --- a/iOS App/ARPen/Plugins/RevolvePlugins/RevolvePluginTwoProfiles.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// RevolvePluginTwoProfiles.swift -// ARPen -// -// Created by Jan Benscheid on 15.04.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation -import ARKit - -class RevolvePluginTwoProfiles: ModelingPlugin { - - private var freePaths: [ARPPath] = [ARPPath]() - private var busy: Bool = false - - override init() { - - super.init() - - // Listen to the `didCompletePath` event. - curveDesigner.didCompletePath = self.didCompletePath - - self.pluginImage = UIImage.init(named: "Revolve(2Profiles)") - self.pluginInstructionsImage = UIImage.init(named: "PaintPluginInstructions") - self.pluginIdentifier = "Revolve (Two Profiles)" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "ARMenusPluginDisabled") - } - - func didCompletePath(_ path: ARPPath) { - freePaths.append(path) - if let profile1 = freePaths.first(where: { !$0.closed && $0.points.count >= 2 }), - let profile2 = freePaths.last(where: { !$0.closed && $0.points.count >= 2 }), - profile1 !== profile2 { - - DispatchQueue.global(qos: .userInitiated).async { - profile1.flatten() - profile2.flatten() - - let profile1Start = profile1.points.first!.worldPosition - let profile1End = profile1.points.last!.worldPosition - let profile2Start = profile2.points.first!.worldPosition - let profile2End = profile2.points.last!.worldPosition - - // The following lines determine which start- and endpoints of the profiles belong to each other, in case one profile was drawn e.g. top to bottom, and the other bottom to top. - let centerStart, centerEnd: SCNVector3! - let distanceParallel = profile1Start.distance(vector: profile2Start) + profile1End.distance(vector: profile2End) - let distanceCross = profile1Start.distance(vector: profile2End) + profile1End.distance(vector: profile2Start) - - if distanceParallel < distanceCross { - centerStart = (profile1Start + profile2Start) / 2 - centerEnd = (profile1End + profile2End) / 2 - } else { - centerStart = (profile1Start + profile2End) / 2 - centerEnd = (profile1End + profile2Start) / 2 - } - - let axisPath = ARPPath(points: [ - ARPPathNode(centerStart), - ARPPathNode(centerEnd) - ], closed: false); - - - if let revolution = try? ARPRevolution(profile: profile1, axis: axisPath) { - - DispatchQueue.main.async { - self.currentScene?.drawingNode.addChildNode(revolution) - profile2.removeFromParentNode() - self.freePaths.removeAll(where: { $0 === profile1 || $0 == profile2 }) - } - } - } - } - } -} diff --git a/iOS App/ARPen/Plugins/SphereByDraggingPlugin.swift b/iOS App/ARPen/Plugins/SphereByDraggingPlugin.swift index ba145bc..fca41f6 100644 --- a/iOS App/ARPen/Plugins/SphereByDraggingPlugin.swift +++ b/iOS App/ARPen/Plugins/SphereByDraggingPlugin.swift @@ -13,23 +13,21 @@ class SphereByDraggingPlugin: Plugin, UserStudyRecordPluginProtocol { //reference to userStudyRecordManager to add new records var recordManager: UserStudyRecordManager! + var pluginImage : UIImage? = UIImage.init(named: "SpherePlugin") + var pluginInstructionsImage: UIImage? = UIImage.init(named: "SpherePluginInstructions") + var pluginIdentifier: String = "Sphere" + var needsBluetoothARPen: Bool = false + var pluginDisabledImage: UIImage? = UIImage.init(named: "ARMenusPluginDisabled") + var currentScene : PenScene? + var currentView: ARSCNView? + /** The starting point is the point of the pencil where the button was first pressed. If this var is nil, there was no initial point */ private var startingPoint: SCNVector3? - override init() { - super.init() - - self.pluginImage = UIImage.init(named: "SpherePlugin") - self.pluginInstructionsImage = UIImage.init(named: "SpherePluginInstructions") - self.pluginIdentifier = "Sphere" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "ARMenusPluginDisabled") - } - - override func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { + func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { guard scene.markerFound else { //Don't reset the previous point to avoid restarting cube if the marker detection failed for some frames //self.startingPoint = nil @@ -88,5 +86,16 @@ class SphereByDraggingPlugin: Plugin, UserStudyRecordPluginProtocol { } + + func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { + self.currentScene = scene + self.currentView = view + } + + func deactivatePlugin() { + self.currentScene = nil + self.currentView = nil + } + } diff --git a/iOS App/ARPen/Plugins/StudyFreehandPainting.swift b/iOS App/ARPen/Plugins/StudyFreehandPainting.swift new file mode 100644 index 0000000..197eb77 --- /dev/null +++ b/iOS App/ARPen/Plugins/StudyFreehandPainting.swift @@ -0,0 +1,1091 @@ +// +// StudyFreehandPainting.swift +// ARPen +// +// Created by Martin on 19.02.20. +// Copyright © 2020 RWTH Aachen. All rights reserved. +// + +import Foundation +import ARKit + +class StudyFreehandPainting: Plugin, UserStudyRecordPluginProtocol { + + var recordManager: UserStudyRecordManager! + + + var pluginImage : UIImage? = UIImage.init(named: "FreehandPainting") + var pluginInstructionsImage: UIImage? = UIImage.init(named: "FreehandPaintingPluginInstructions") + var pluginIdentifier: String = "Freehand" + var needsBluetoothARPen: Bool = false + var pluginDisabledImage: UIImage? = UIImage.init(named: "ARMenusPluginDisabled") + var currentScene : PenScene? + var currentView: ARSCNView? + + var penColor: UIColor = UIColor.init(red: 0.73, green: 0.12157, blue: 0.8, alpha: 1) + + var objectCounter: Int = 0 + var objectCurrentlyBuilding: Bool = false + + + let objectOpacity = 0.7 + + let cubeSizeSmall = 0.08 + let cubeSizeLarge = 0.12 + + // trial data + var userID = 5 + var trialNumber = 0 + var trialID = -1 + var trialList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + var trialValid = true + + /** + The starting point is the point of the pencil where the button was first pressed. + If this var is nil, there was no initial point + */ + private var startingPoint: SCNVector3? + + /** + The previous point is the point of the pencil one frame before. + If this var is nil, there was no last point + */ + private var previousPoint: SCNVector3? + //collection of currently & last drawn line elements to offer undo + var previousDrawnLineNodes: [[SCNNode]]? + private var currentLine : [SCNNode]? + private var removedOneLine = false + + private var closestPoint: SCNVector3? + + private var averageError = Float(-1) + + private var dataExported = true + private var retryTrial = false + + // modified undoPreviousAction for user study + func undoPreviousAction() { + retryTrial = true + //print("undoButton") + } + + func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { + guard scene.markerFound else { + //Don't reset the previous point to avoid disconnected lines if the marker detection failed for some frames + //self.previousPoint = nil + return + } + + + let pressed2 = buttons[Button.Button2]! + + if (pressed2){ + //print("retryTrial") + while (self.previousDrawnLineNodes!.count > 0) { + //print("removing line") + let lastLine = self.previousDrawnLineNodes?.last + + self.previousDrawnLineNodes?.removeLast() + + // Remove the previous line + for currentNode in lastLine! { + currentNode.removeFromParentNode() + } + } + let targetMeasurementDict = [ + "TrialNumber" : String(describing: trialNumber), + "TrialID": String(describing: trialID), + "TrialValid": String(describing: false) + ] + self.recordManager.addNewRecord(withIdentifier: self.pluginIdentifier, andData: targetMeasurementDict) + trialNumber = trialNumber + 1 + retryTrial = false + } + + + let pressed = buttons[Button.Button1]! + + var objectList: [SCNNode] + + if let arImageNode = scene.rootNode.childNode(withName: "ARImage", recursively: false){ + objectList = arImageNode.childNodes + } else { + objectList = scene.drawingNode.childNodes + } + + + var closestDistance = Float(-1) + var closestPosition: SCNVector3? + closestPosition = nil + var numberOfPositions: Int = 0 + var currentError = Float(-1) + + + + if pressed, let previousPoint = self.previousPoint { + + + if currentLine == nil { + currentLine = [SCNNode]() + } + let cylinderNode = SCNNode() + + cylinderNode.buildLineInTwoPointsWithRotation(from: + cylinderNode.convertPosition(previousPoint, to: scene.arAnchorImage), to: scene.pencilPoint.convertPosition(SCNVector3Zero, to: scene.arAnchorImage), radius: 0.001, color: penColor) + //cylinderNode.buildLineInTwoPointsWithRotation(from: previousPoint, to: scene.pencilPoint.position, radius: 0.001, color: penColor) + + cylinderNode.name = "cylinderLine" + scene.arAnchorImage.addChildNode(cylinderNode) + //scene.drawingNode.addChildNode(cylinderNode) + + //add last drawn line element to currently drawn line collection + currentLine?.append(cylinderNode) + // For study: measure distance to closest guide line position: + + if (objectList.contains(where: {($0.name?.hasPrefix("currentStudyObjectNode") ?? false)})){ + for obj in objectList{ + var distance = Float(-1) + var surfacePosition = SCNVector3Zero + + for child in obj.childNodes{ + if (child.name?.hasPrefix("currentStudyObjectGuideNode") ?? false){ + // use positioning with box, ignoring chamfer + // convert pen position to box space + var localPosition = scene.projectionNode.convertPosition(SCNVector3Zero, to: obj) + + // clamp coordinates to box size + + if (localPosition.x > Float((child.geometry as! SCNBox).width)/2){ + localPosition.x = Float((child.geometry as! SCNBox).width)/2 + } + if (localPosition.x < Float(-(child.geometry as! SCNBox).width)/2){ + localPosition.x = Float(-(child.geometry as! SCNBox).width)/2 + } + + if (localPosition.y > Float((child.geometry as! SCNBox).height)/2){ + localPosition.y = Float((child.geometry as! SCNBox).height)/2 + } + if (localPosition.y < Float(-(child.geometry as! SCNBox).height)/2){ + localPosition.y = Float(-(child.geometry as! SCNBox).height)/2 + } + + if (localPosition.z > Float((child.geometry as! SCNBox).length)/2){ + localPosition.z = Float((child.geometry as! SCNBox).length)/2 + } + if (localPosition.z < Float(-(child.geometry as! SCNBox).length)/2){ + localPosition.z = Float(-(child.geometry as! SCNBox).length)/2 + } + // if inside box, go to closest edge position + + if (localPosition.x > Float(-(child.geometry as! SCNBox).width)/2 && + localPosition.x < Float( (child.geometry as! SCNBox).width)/2 && + localPosition.y > Float(-(child.geometry as! SCNBox).height)/2 && + localPosition.y < Float( (child.geometry as! SCNBox).height)/2 && + localPosition.z > Float(-(child.geometry as! SCNBox).length)/2 && + localPosition.z < Float( (child.geometry as! SCNBox).length)/2 ){ + + + // find closest edge position + var deltaX = Float(0) + var deltaY = Float(0) + var deltaZ = Float(0) + + if (localPosition.x >= 0){ + deltaX = Float((child.geometry as! SCNBox).width)/2 - localPosition.x + } + if (localPosition.x < 0){ + deltaX = -Float((child.geometry as! SCNBox).width)/2 - localPosition.x + } + if (localPosition.y >= 0){ + deltaY = Float((child.geometry as! SCNBox).height)/2 - localPosition.y + } + if (localPosition.y < 0){ + deltaY = -Float((child.geometry as! SCNBox).height)/2 - localPosition.y + } + if (localPosition.z >= 0){ + deltaZ = Float((child.geometry as! SCNBox).length)/2 - localPosition.z + } + if (localPosition.z < 0){ + deltaZ = -Float((child.geometry as! SCNBox).length)/2 - localPosition.z + } + + if (deltaX < 0){ + deltaX = -deltaX + } + if (deltaY < 0){ + deltaY = -deltaY + } + if (deltaZ < 0){ + deltaZ = -deltaZ + } + + if (deltaX <= deltaY && deltaX <= deltaZ){ + // X position is closest to edge + if (localPosition.x >= 0){ + localPosition.x = Float((child.geometry as! SCNBox).width)/2 + } + if (localPosition.x < 0){ + localPosition.x = -Float((child.geometry as! SCNBox).width)/2 + } + } + if (deltaY < deltaX && deltaY <= deltaZ){ + // Y position is closest to edge + if (localPosition.y >= 0){ + localPosition.y = Float((child.geometry as! SCNBox).height)/2 + } + if (localPosition.y < 0){ + localPosition.y = -Float((child.geometry as! SCNBox).height)/2 + } + } + if (deltaZ < deltaX && deltaZ < deltaY){ + // Z position is closest to edge + if (localPosition.z >= 0){ + localPosition.z = Float((child.geometry as! SCNBox).length)/2 + } + if (localPosition.z < 0){ + localPosition.z = -Float((child.geometry as! SCNBox).length)/2 + } + } + } + // convert pos back to global space + surfacePosition = child.convertPosition(localPosition, to: nil) + distance = surfacePosition.distance(vector: scene.projectionNode.position) + + } + if (distance > 0 && distance < closestDistance || closestDistance < 0){ + //print("found new closest position") + numberOfPositions += 1 + closestDistance = distance + + currentError = closestDistance + averageError = (distance + averageError * Float(numberOfPositions-1)) / Float(numberOfPositions) + //print("average error: ", averageError) + } + } + } + } + + let timestamp = Date().timeIntervalSince1970 + let userID = 0 + + + var currentStudyObject: SCNNode? + + if (objectList.contains(where: {($0.name?.hasPrefix("currentStudyObjectNode") ?? false)})){ + + for obj in objectList{ + + if (obj.name?.hasPrefix("currentStudyObjectNode") ?? false){ + currentStudyObject = obj + } + } + } + + let studyObjectSize = Float(99) + let studyObjectCornerType = "CornerTypeUnknown" + + + + let targetMeasurementDict = [ + + "TrialNumber" : String(describing: trialNumber), + "TrialID" : String(describing: trialID), + "PenTipPositionX" : String(describing: scene.pencilPoint.position.x), + "PenTipPositionY" : String(describing: scene.pencilPoint.position.y), + "PenTipPositionZ" : String(describing: scene.pencilPoint.position.z), + "ProjectionPositionX" : String(describing: scene.projectionNode.position.x), + "ProjectionPositionY" : String(describing: scene.projectionNode.position.y), + "ProjectionPositionZ" : String(describing: scene.projectionNode.position.z), + "RelativeProjectionX" : String(describing: scene.projectionNode.convertPosition(SCNVector3Zero, to: scene.arAnchorImage).x), + "RelativeProjectionY" : String(describing: scene.projectionNode.convertPosition(SCNVector3Zero, to: scene.arAnchorImage).y), + "RelativeProjectionZ" : String(describing: scene.projectionNode.convertPosition(SCNVector3Zero, to: scene.arAnchorImage).z), + "StudyObjectPositionX" : String(describing: currentStudyObject?.position.x ?? -1), + "StudyObjectPositionY" : String(describing: currentStudyObject?.position.y ?? -1), + "StudyObjectPositionZ" : String(describing: currentStudyObject?.position.z ?? -1) + ] + + + dataExported = false + self.recordManager.addNewRecord(withIdentifier: self.pluginIdentifier, andData: targetMeasurementDict) + + + } else if !pressed { + if let currentLine = self.currentLine { + self.previousDrawnLineNodes?.append(currentLine) + self.currentLine = nil + } + } + + + + + + + + self.previousPoint = scene.pencilPoint.position + scene.projectionNode.position = closestPosition ?? scene.projectionNode.position + + // MARK: Button3, Object Creation + + + //Check state of the first button -> used to create the cube + let pressed3 = buttons[Button.Button3]! + + //if the button is pressed -> either set the starting point of the cube (first action) or scale the cube to fit from the starting point to the current point + if pressed3 { + + + if !dataExported { + //self.recordManager.saveToFile() + self.recordManager.urlToCSV() + dataExported = true + } + + + var objectNode = SCNNode() + if let startingPoint = self.startingPoint { + // spawnPoint is the position at which StudyObjects are created + + //let spawnPoint = SCNVector3Zero + var spawnPoint = scene.pencilPoint.position + if let arImageNode = scene.rootNode.childNode(withName: "ARImage", recursively: false){ + spawnPoint = arImageNode.position + } + //see if there is an active box node that is currently being drawn. Otherwise create it + + // if no object exists: create new object + if !objectCurrentlyBuilding{ + // object exists already: delete it and create new object + + // Case: study object was created as child of drawing node + if let studyObjectNode = scene.drawingNode.childNode(withName: "currentStudyObjectNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() +// while (self.previousDrawnLineNodes!.count > 0){ +// undoPreviousAction() +// } + } + + + if let studyObjectNode = scene.drawingNode.childNode(withName: "currentStudyObjectBoxNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() +// while (self.previousDrawnLineNodes!.count > 0){ +// undoPreviousAction() +// } + } + + if let studyObjectNode = scene.drawingNode.childNode(withName: "currentStudyObjectSphereNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() +// while (self.previousDrawnLineNodes!.count > 0){ +// undoPreviousAction() +// } + } + + if let studyObjectNode = scene.drawingNode.childNode(withName: "currentStudyObjectGuideNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() +// while (self.previousDrawnLineNodes!.count > 0){ +// undoPreviousAction() +// } + } + + if let arImageNode = scene.rootNode.childNode(withName: "ARImage", recursively: false){ + if let studyObjectNode = arImageNode.childNode(withName: "currentStudyObjectNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() + + while (self.previousDrawnLineNodes!.count > 0){ + let lastLine = self.previousDrawnLineNodes?.last + self.previousDrawnLineNodes?.removeLast() + + // Remove the previous line + for currentNode in lastLine! { + currentNode.removeFromParentNode() + } + } + } + + + if let studyObjectNode = arImageNode.childNode(withName: "currentStudyObjectBoxNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() +// while (self.previousDrawnLineNodes!.count > 0){ +// undoPreviousAction() +// } + } + + if let studyObjectNode = arImageNode.childNode(withName: "currentStudyObjectSphereNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() +// while (self.previousDrawnLineNodes!.count > 0){ +// undoPreviousAction() +// } + } + + if let studyObjectNode = arImageNode.childNode(withName: "currentStudyObjectGuideNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() +// while (self.previousDrawnLineNodes!.count > 0){ +// undoPreviousAction() +// } + } + } + + if (objectCounter == 0 || objectCounter >= trialList.count){ + trialList.shuffle() + objectCounter = 0 + //print("shuffle") + } + + trialID = trialList[objectCounter] + + // whether previous object existed or not, create new one + switch trialList[objectCounter] { + case 0: + // flat corner small + objectNode = generateObject(size: 1, shape: 1, corner: 3, startingPoint: spawnPoint, scene: scene) + break + case 1: + // flat corner large + objectNode = generateObject(size: 2, shape: 1, corner: 3, startingPoint: spawnPoint, scene: scene) + break + case 2: + // exterior corner small + objectNode = generateObject(size: 1, shape: 1, corner: 1, startingPoint: spawnPoint, scene: scene) + break + case 3: + // exterior corner large + objectNode = generateObject(size: 2, shape: 1, corner: 1, startingPoint: spawnPoint, scene: scene) + break + case 4: + // interior corner small + objectNode = generateObject(size: 1, shape: 1, corner: 2, startingPoint: spawnPoint, scene: scene) + break + case 5: + // interior corner large + objectNode = generateObject(size: 2, shape: 1, corner: 2, startingPoint: spawnPoint, scene: scene) + break + case 6: + // Right Side Flat small + objectNode = generateObject(size: 1, shape: 1, corner: 4, startingPoint: spawnPoint, scene: scene) + break + case 7: + // Right Side Flat large + objectNode = generateObject(size: 2, shape: 1, corner: 4, startingPoint: spawnPoint, scene: scene) + break + case 8: + // Front Side Flat small + objectNode = generateObject(size: 1, shape: 1, corner: 5, startingPoint: spawnPoint, scene: scene) + break + case 9: + // Front Side Flat large + objectNode = generateObject(size: 2, shape: 1, corner: 5, startingPoint: spawnPoint, scene: scene) + break + + default: + break + } + + if let arImageNode = scene.rootNode.childNode(withName: "ARImage", recursively: false){ + arImageNode.addChildNode(objectNode) + } else { + scene.drawingNode.addChildNode(objectNode) + } + // + // let objectNode = generateObject(size: 1, shape: 1, direction: 1, corner: 2, startingPoint: scene.pencilPoint.position, scene: scene) + // objectNode.name = "currentStudyObjectNode" + objectCurrentlyBuilding = true + objectCounter += 1 + } + + + } + else { + //if the button is pressed but no startingPoint exists -> first frame with the button pressed. Set current pencil position as the start point + self.startingPoint = scene.pencilPoint.position + } + } + else { + //if the button is not pressed, check if a startingPoint is set -> released button. Reset the startingPoint to nil and set the name of the drawn box to "finished" + if objectCurrentlyBuilding{ + objectCurrentlyBuilding = false + } + if self.startingPoint != nil { + self.startingPoint = nil + if let boxNode = scene.drawingNode.childNode(withName: "currentStudyObjectNode", recursively: false), let boxNodeGeometry = boxNode.geometry as? SCNBox { + boxNode.name = "currentStudyObjectNode" + + //store a new record with the size of the finished box + let boxDimensionsDict = ["Width" : String(describing: boxNodeGeometry.width), "Height" : String(describing: boxNodeGeometry.height), "Length" : String(describing: boxNodeGeometry.length)] + self.recordManager.addNewRecord(withIdentifier: "BoxFinished", andData: boxDimensionsDict) + } + } + + + + } + + + + + + } + + + func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { + self.currentScene = scene + self.currentView = view + previousDrawnLineNodes = [[SCNNode]]() + + + scene.pencilPoint.opacity = 1 + scene.projectionNode.opacity = 0 + } + + func deactivatePlugin() { + guard let scene = self.currentScene else { + + self.currentScene = nil + self.currentView = nil + + return + + } + + if let arImageNode = scene.rootNode.childNode(withName: "ARImage", recursively: false){ + if let studyObjectNode = arImageNode.childNode(withName: "currentStudyObjectNode", recursively: false){ + for obj in studyObjectNode.childNodes{ + obj.removeFromParentNode() + } + studyObjectNode.removeFromParentNode() + + while (self.previousDrawnLineNodes!.count > 0){ + let lastLine = self.previousDrawnLineNodes?.last + + self.previousDrawnLineNodes?.removeLast() + + // Remove the previous line + for currentNode in lastLine! { + currentNode.removeFromParentNode() + } + } + } + } + + self.currentScene = nil + self.currentView = nil + + } + // MARK: Object Creation + + +func generateObject(size: Int, shape: Int, corner: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + var objectNode = SCNNode() + + switch corner{ + // interior + case 1: + objectNode = createCubeInterior(size: size, startingPoint: startingPoint, scene: scene) + break + // exterior + case 2: + objectNode = createCubeExterior(size: size, startingPoint: startingPoint, scene: scene) + break + // flat + case 3: + objectNode = createCubeFlat(size: size, startingPoint: startingPoint, scene: scene) + break + // flat + right side + case 4: + objectNode = createCubeFlatRightSide(size: size, startingPoint: startingPoint, scene: scene) + break + // flat + front side + case 5: + objectNode = createCubeFlatFrontSide(size: size, startingPoint: startingPoint,scene: scene) + break + default: break + } + + trialNumber = trialNumber+1 + + + return objectNode + +} + +func createCubeExterior(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + objectNode.addChildNode(boxNode) + + var spawnPoint = startingPoint + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeSmall/2, 0) + break + case 2: cubeSize = Float(cubeSizeLarge) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeLarge/2, 0) + break + default: cubeSize = 0.2 + break + } + + objectNode.position = spawnPoint + + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + //set the dimensions of the box + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + + + + // create guiding line around object + createTargetLine(parentNode: objectNode, cubeSize: cubeSize, height: cubeSize/2) + + + + boxNode.opacity = CGFloat(objectOpacity) + + return objectNode +} + + +func createTargetLine(parentNode: SCNNode, cubeSize: Float, height: Float){ + // create four guide boxes, so that they form a ring, not a solid plane + let guideNode1 = SCNNode() + let guideNode2 = SCNNode() + let guideNode3 = SCNNode() + let guideNode4 = SCNNode() + guideNode1.name = "currentStudyObjectGuideNode" + guideNode2.name = "currentStudyObjectGuideNode" + guideNode3.name = "currentStudyObjectGuideNode" + guideNode4.name = "currentStudyObjectGuideNode" + parentNode.addChildNode(guideNode1) + parentNode.addChildNode(guideNode2) + parentNode.addChildNode(guideNode3) + parentNode.addChildNode(guideNode4) + + guideNode1.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry1 = guideNode1.geometry as! SCNBox + guideNode2.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry2 = guideNode2.geometry as! SCNBox + guideNode3.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry3 = guideNode3.geometry as! SCNBox + guideNode4.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry4 = guideNode4.geometry as! SCNBox + + let guideSize = cubeSize * 1.001 + + guideNode1.position = SCNVector3(cubeSize/2, height, 0) + guideNode2.position = SCNVector3(-cubeSize/2, height, 0) + guideNode3.position = SCNVector3(0, height, cubeSize/2) + guideNode4.position = SCNVector3(0, height, -cubeSize/2) + + //set the dimensions of the boxes + guideNodeGeometry1.width = CGFloat(0.0005) + guideNodeGeometry1.height = CGFloat(0.0005) + guideNodeGeometry1.length = CGFloat(guideSize) + guideNodeGeometry2.width = CGFloat(0.0005) + guideNodeGeometry2.height = CGFloat(0.0005) + guideNodeGeometry2.length = CGFloat(guideSize) + guideNodeGeometry3.width = CGFloat(guideSize) + guideNodeGeometry3.height = CGFloat(0.0005) + guideNodeGeometry3.length = CGFloat(0.0005) + guideNodeGeometry4.width = CGFloat(guideSize) + guideNodeGeometry4.height = CGFloat(0.0005) + guideNodeGeometry4.length = CGFloat(0.0005) + + guideNodeGeometry1.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry2.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry3.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry4.materials.first?.diffuse.contents = UIColor.red + + // Add starting point and draw direction signifiers + let startPointNode = SCNNode() + startPointNode.name = "currentStudyObjectStartingPointNode" + parentNode.addChildNode(startPointNode) + startPointNode.geometry = SCNSphere.init(radius: 0.002) + let startPointGeometry = startPointNode.geometry as! SCNSphere + startPointGeometry.materials.first?.diffuse.contents = UIColor.red + startPointNode.position = SCNVector3(-cubeSize/2, height, cubeSize/2) + + + let directionArrowNode = SCNNode() + directionArrowNode.name = "currentStudyObjectDirectionArrowNode" + parentNode.addChildNode(directionArrowNode) + directionArrowNode.geometry = SCNCone.init(topRadius: 0, bottomRadius: 0.0035, height: 0.008) + let directionArrowGeometry = directionArrowNode.geometry as! SCNCone + directionArrowGeometry.materials.first?.diffuse.contents = UIColor.red + directionArrowNode.position = SCNVector3(0, height, cubeSize/2) + directionArrowNode.eulerAngles = SCNVector3(-cubeSize/4, 0, -Float.pi/2) +} + + +func createCubeInterior(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + scene.drawingNode.addChildNode(objectNode) + objectNode.addChildNode(boxNode) + + var spawnPoint = startingPoint + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeSmall/2, 0) + break + case 2: cubeSize = Float(cubeSizeLarge) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeLarge/2, 0) + break + default: cubeSize = 0.2 + break + } + + objectNode.position = spawnPoint + + // create box itself + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + // create "base" + let baseNode = SCNNode() + baseNode.name = "currentStudyObjectBoxNode" + objectNode.addChildNode(baseNode) + + baseNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let baseNodeGeometry = baseNode.geometry as! SCNBox + + baseNodeGeometry.width = CGFloat(cubeSize * 1.4) + baseNodeGeometry.height = CGFloat(0.001) + baseNodeGeometry.length = CGFloat(cubeSize * 1.4) + + + // create guiding line around object + createTargetLine(parentNode: objectNode, cubeSize: cubeSize, height: 0) + + + boxNode.opacity = CGFloat(objectOpacity) + baseNode.opacity = CGFloat(objectOpacity) + baseNode.position = SCNVector3(0, -0.001, 0) + + return objectNode +} + +func createCubeFlat(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + scene.drawingNode.addChildNode(objectNode) + objectNode.addChildNode(boxNode) + + var spawnPoint = startingPoint + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeSmall/2, 0) + break + case 2: cubeSize = Float(cubeSizeLarge) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeLarge/2, 0) + break + default: cubeSize = 0.2 + break + } + + objectNode.position = spawnPoint + + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + //set the dimensions of the box + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + + // create guiding line around object + createTargetLine(parentNode: objectNode, cubeSize: cubeSize, height: 0) + + + boxNode.opacity = CGFloat(objectOpacity) + + return objectNode + +} + +func createCubeFlatRightSide(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + scene.drawingNode.addChildNode(objectNode) + objectNode.addChildNode(boxNode) + + var spawnPoint = startingPoint + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeSmall/2, 0) + break + case 2: cubeSize = Float(cubeSizeLarge) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeLarge/2, 0) + break + default: cubeSize = 0.2 + break + } + + objectNode.position = spawnPoint + + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + //set the dimensions of the box + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + + + boxNode.opacity = CGFloat(objectOpacity) + + + // create guiding line around object + // create four guide boxes, so that they form a ring, not a solid plane + let guideNode1 = SCNNode() + let guideNode2 = SCNNode() + let guideNode3 = SCNNode() + let guideNode4 = SCNNode() + guideNode1.name = "currentStudyObjectGuideNode" + guideNode2.name = "currentStudyObjectGuideNode" + guideNode3.name = "currentStudyObjectGuideNode" + guideNode4.name = "currentStudyObjectGuideNode" + objectNode.addChildNode(guideNode1) + objectNode.addChildNode(guideNode2) + objectNode.addChildNode(guideNode3) + objectNode.addChildNode(guideNode4) + + guideNode1.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry1 = guideNode1.geometry as! SCNBox + guideNode2.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry2 = guideNode2.geometry as! SCNBox + guideNode3.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry3 = guideNode3.geometry as! SCNBox + guideNode4.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry4 = guideNode4.geometry as! SCNBox + + let guideSize = cubeSize * 1.001 + + // 1 = Top, 2 = Back, 3 = Bottom, 4 = Front + guideNode1.position = SCNVector3(cubeSize/2, cubeSize/2, 0) + guideNode2.position = SCNVector3(cubeSize/2, 0, cubeSize/2) + guideNode3.position = SCNVector3(cubeSize/2, -cubeSize/2, 0) + guideNode4.position = SCNVector3(cubeSize/2, 0, -cubeSize/2) + + //set the dimensions of the boxes + guideNodeGeometry1.width = CGFloat(0.0005) + guideNodeGeometry1.height = CGFloat(0.0005) + guideNodeGeometry1.length = CGFloat(guideSize) + guideNodeGeometry2.width = CGFloat(0.0005) + guideNodeGeometry2.height = CGFloat(guideSize) + guideNodeGeometry2.length = CGFloat(0.0005) + guideNodeGeometry3.width = CGFloat(0.0005) + guideNodeGeometry3.height = CGFloat(0.0005) + guideNodeGeometry3.length = CGFloat(guideSize) + guideNodeGeometry4.width = CGFloat(0.0005) + guideNodeGeometry4.height = CGFloat(guideSize) + guideNodeGeometry4.length = CGFloat(0.0005) + + guideNodeGeometry1.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry2.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry3.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry4.materials.first?.diffuse.contents = UIColor.red + + // Add starting point and draw direction signifiers + let startPointNode = SCNNode() + startPointNode.name = "currentStudyObjectStartingPointNode" + objectNode.addChildNode(startPointNode) + startPointNode.geometry = SCNSphere.init(radius: 0.002) + let startPointGeometry = startPointNode.geometry as! SCNSphere + startPointGeometry.materials.first?.diffuse.contents = UIColor.red + startPointNode.position = SCNVector3(cubeSize/2, -cubeSize/2, cubeSize/2) + + + let directionArrowNode = SCNNode() + directionArrowNode.name = "currentStudyObjectDirectionArrowNode" + objectNode.addChildNode(directionArrowNode) + directionArrowNode.geometry = SCNCone.init(topRadius: 0, bottomRadius: 0.0035, height: 0.008) + let directionArrowGeometry = directionArrowNode.geometry as! SCNCone + directionArrowGeometry.materials.first?.diffuse.contents = UIColor.red + directionArrowNode.position = SCNVector3(cubeSize/2, 0, cubeSize/2) + directionArrowNode.eulerAngles = SCNVector3(0, 0, 0) + + + + + return objectNode +} + +func createCubeFlatFrontSide(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + scene.drawingNode.addChildNode(objectNode) + objectNode.addChildNode(boxNode) + + var spawnPoint = startingPoint + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeSmall/2, 0) + break + case 2: cubeSize = Float(cubeSizeLarge) + spawnPoint = spawnPoint + SCNVector3(0, cubeSizeLarge/2, 0) + break + default: cubeSize = 0.2 + break + } + + objectNode.position = spawnPoint + + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + //set the dimensions of the box + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + + + boxNode.opacity = CGFloat(objectOpacity) + + + // create guiding line around object + // create four guide boxes, so that they form a ring, not a solid plane + let guideNode1 = SCNNode() + let guideNode2 = SCNNode() + let guideNode3 = SCNNode() + let guideNode4 = SCNNode() + guideNode1.name = "currentStudyObjectGuideNode" + guideNode2.name = "currentStudyObjectGuideNode" + guideNode3.name = "currentStudyObjectGuideNode" + guideNode4.name = "currentStudyObjectGuideNode" + objectNode.addChildNode(guideNode1) + objectNode.addChildNode(guideNode2) + objectNode.addChildNode(guideNode3) + objectNode.addChildNode(guideNode4) + + guideNode1.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry1 = guideNode1.geometry as! SCNBox + guideNode2.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry2 = guideNode2.geometry as! SCNBox + guideNode3.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry3 = guideNode3.geometry as! SCNBox + guideNode4.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry4 = guideNode4.geometry as! SCNBox + + let guideSize = cubeSize * 1.001 + + // 1 = Top, 2 = Right, 3 = Bottom, 4 = Left + guideNode1.position = SCNVector3(0, cubeSize/2, cubeSize/2) + guideNode2.position = SCNVector3(cubeSize/2, 0, cubeSize/2) + guideNode3.position = SCNVector3(0, -cubeSize/2, cubeSize/2) + guideNode4.position = SCNVector3(-cubeSize/2, 0, cubeSize/2) + + //set the dimensions of the boxes + guideNodeGeometry1.width = CGFloat(guideSize) + guideNodeGeometry1.height = CGFloat(0.0005) + guideNodeGeometry1.length = CGFloat(0.0005) + guideNodeGeometry2.width = CGFloat(0.0005) + guideNodeGeometry2.height = CGFloat(guideSize) + guideNodeGeometry2.length = CGFloat(0.0005) + guideNodeGeometry3.width = CGFloat(guideSize) + guideNodeGeometry3.height = CGFloat(0.0005) + guideNodeGeometry3.length = CGFloat(0.0005) + guideNodeGeometry4.width = CGFloat(0.0005) + guideNodeGeometry4.height = CGFloat(guideSize) + guideNodeGeometry4.length = CGFloat(0.0005) + + guideNodeGeometry1.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry2.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry3.materials.first?.diffuse.contents = UIColor.red + guideNodeGeometry4.materials.first?.diffuse.contents = UIColor.red + + // Add starting point and draw direction signifiers + let startPointNode = SCNNode() + startPointNode.name = "currentStudyObjectStartingPointNode" + objectNode.addChildNode(startPointNode) + startPointNode.geometry = SCNSphere.init(radius: 0.002) + let startPointGeometry = startPointNode.geometry as! SCNSphere + startPointGeometry.materials.first?.diffuse.contents = UIColor.red + startPointNode.position = SCNVector3(-cubeSize/2, cubeSize/2, cubeSize/2) + + + let directionArrowNode = SCNNode() + directionArrowNode.name = "currentStudyObjectDirectionArrowNode" + objectNode.addChildNode(directionArrowNode) + directionArrowNode.geometry = SCNCone.init(topRadius: 0, bottomRadius: 0.0035, height: 0.008) + let directionArrowGeometry = directionArrowNode.geometry as! SCNCone + directionArrowGeometry.materials.first?.diffuse.contents = UIColor.red + directionArrowNode.position = SCNVector3(0, cubeSize/2, cubeSize/2) + directionArrowNode.eulerAngles = SCNVector3(0, 0, -Float.pi/2) + + + + + return objectNode +} + + +} + diff --git a/iOS App/ARPen/Plugins/StudyObjectGeneration.swift b/iOS App/ARPen/Plugins/StudyObjectGeneration.swift new file mode 100644 index 0000000..c074189 --- /dev/null +++ b/iOS App/ARPen/Plugins/StudyObjectGeneration.swift @@ -0,0 +1,583 @@ +// +// StudyObjectGeneration.swift +// ARPen +// +// Created by Martin on 09.09.19. +// Copyright © 2019 RWTH Aachen. All rights reserved. +// + +import Foundation +import ARKit + +//include the UserStudyRecordPluginProtocol to demo recording of user study data +class StudyObjectGeneration: Plugin, UserStudyRecordPluginProtocol { + //reference to userStudyRecordManager to add new records + var recordManager: UserStudyRecordManager! + + var pluginImage : UIImage? = UIImage.init(named: "CubeByDraggingPlugin") + var pluginInstructionsImage: UIImage? = UIImage.init(named: "CubePluginInstructions") + var pluginIdentifier: String = "StudyObjectCreator" + var needsBluetoothARPen: Bool = false + var pluginDisabledImage: UIImage? = UIImage.init(named: "ARMenusPluginDisabled") + var currentScene : PenScene? + var currentView: ARSCNView? + + var objectCounter: Int = 0 + var objectCurrentlyBuilding: Bool = false + + let objectOpacity = 0.4 + + let cubeSizeSmall = 0.05 + let cubeSizeLarge = 0.2 + + // trial data + var userID = 5 + var trialNumber = 0 + + /** + The starting point is the point of the pencil where the button was first pressed. + If this var is nil, there was no initial point + */ + private var startingPoint: SCNVector3? + + func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { + guard scene.markerFound else { + //Don't reset the previous point to avoid restarting cube if the marker detection failed for some frames + //self.startingPoint = nil + return + } + + + + //Check state of the first button -> used to create the cube + let pressed = buttons[Button.Button1]! + + //if the button is pressed -> either set the starting point of the cube (first action) or scale the cube to fit from the starting point to the current point + if pressed { + if let startingPoint = self.startingPoint { + //see if there is an active box node that is currently being drawn. Otherwise create it + + + var spawnPoint = scene.pencilPoint.position + + // if no object exists: create new object + if !objectCurrentlyBuilding{ + // object exists already: delete it and create new object + + if let objectNode = scene.drawingNode.childNode(withName: "currentStudyObjectNode", recursively: false){ + for obj in objectNode.childNodes{ + obj.removeFromParentNode() + } + objectNode.removeFromParentNode() + } + + + if let objectNode = scene.drawingNode.childNode(withName: "currentStudyObjectBoxNode", recursively: false){ + for obj in objectNode.childNodes{ + obj.removeFromParentNode() + } + objectNode.removeFromParentNode() + } + + if let objectNode = scene.drawingNode.childNode(withName: "currentStudyObjectSphereNode", recursively: false){ + for obj in objectNode.childNodes{ + obj.removeFromParentNode() + } + objectNode.removeFromParentNode() + } + + if let objectNode = scene.drawingNode.childNode(withName: "currentStudyObjectGuideNode", recursively: false){ + for obj in objectNode.childNodes{ + obj.removeFromParentNode() + } + objectNode.removeFromParentNode() + } + + // whether previous object existed or not, create new one + switch objectCounter { + case 0: + // flat corner small + let objectNode = generateObject(size: 1, shape: 1, corner: 3, startingPoint: spawnPoint, scene: scene) + break + case 1: + // flat corner large + let objectNode = generateObject(size: 2, shape: 1, corner: 3, startingPoint: spawnPoint, scene: scene) + break + case 2: + // exterior corner small + let objectNode = generateObject(size: 1, shape: 1, corner: 1, startingPoint: spawnPoint, scene: scene) + break + case 3: + // exterior corner large + let objectNode = generateObject(size: 2, shape: 1, corner: 1, startingPoint: spawnPoint, scene: scene) + break + case 4: + // interior corner small + let objectNode = generateObject(size: 1, shape: 1, corner: 2, startingPoint: spawnPoint, scene: scene) + break + default: + // interior corner large + let objectNode = generateObject(size: 2, shape: 1, corner: 2, startingPoint: spawnPoint, scene: scene) + break + } +// +// let objectNode = generateObject(size: 1, shape: 1, direction: 1, corner: 2, startingPoint: scene.pencilPoint.position, scene: scene) +// objectNode.name = "currentStudyObjectNode" + objectCurrentlyBuilding = true + objectCounter += 1 + } + + + } + else { + //if the button is pressed but no startingPoint exists -> first frame with the button pressed. Set current pencil position as the start point + //self.startingPoint = scene.pencilPoint.position + self.startingPoint = SCNVector3Zero + } + } + else { + //if the button is not pressed, check if a startingPoint is set -> released button. Reset the startingPoint to nil and set the name of the drawn box to "finished" + if objectCurrentlyBuilding{ + objectCurrentlyBuilding = false + } + if self.startingPoint != nil { + self.startingPoint = nil + if let boxNode = scene.drawingNode.childNode(withName: "currentStudyObjectNode", recursively: false), let boxNodeGeometry = boxNode.geometry as? SCNBox { + boxNode.name = "currentStudyObjectNode" + + //store a new record with the size of the finished box + let boxDimensionsDict = ["Width" : String(describing: boxNodeGeometry.width), "Height" : String(describing: boxNodeGeometry.height), "Length" : String(describing: boxNodeGeometry.length)] + self.recordManager.addNewRecord(withIdentifier: "BoxFinished", andData: boxDimensionsDict) + } + } + + } + + } + + func getTrialNumber()->Int{ + return trialNumber + } + + func getUserID()->Int{ + return userID + } + + func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { + self.currentScene = scene + self.currentView = view + } + + func deactivatePlugin() { + self.currentScene = nil + self.currentView = nil + } + + + + +func generateObject(size: Int, shape: Int, corner: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + var objectNode = SCNNode() + + switch corner{ + // interior + case 1: + objectNode = createCubeInterior(size: size, startingPoint: startingPoint, scene: scene) + break + // exterior + case 2: + objectNode = createCubeExterior(size: size, startingPoint: startingPoint, scene: scene) + break + // flat + case 3: + objectNode = createCubeFlat(size: size, startingPoint: startingPoint, scene: scene) + break + default: break + } + + trialNumber = trialNumber+1 + return objectNode + +} + +func createCubeExterior(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + scene.drawingNode.addChildNode(objectNode) + objectNode.addChildNode(boxNode) + + + objectNode.position = startingPoint + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + break + case 2: cubeSize = Float(cubeSizeLarge) + break + default: cubeSize = 0.2 + break + } + + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + //set the dimensions of the box + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + + + + // create guiding line around object + let guideNode = SCNNode() + guideNode.name = "currentStudyObjectGuideNode" + objectNode.addChildNode(guideNode) + + guideNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry = guideNode.geometry as! SCNBox + + + cubeSize = cubeSize * 1.001 + + guideNode.position = SCNVector3(0, cubeSize/2, 0) + + + + //set the dimensions of the box + guideNodeGeometry.width = CGFloat(cubeSize) + guideNodeGeometry.height = CGFloat(0.0005) + guideNodeGeometry.length = CGFloat(cubeSize) + +// switch direction{ +// // horizontal +// case 1: +// +// //set the dimensions of the box +// guideNodeGeometry.width = CGFloat(cubeSize) +// guideNodeGeometry.height = CGFloat(0.0005) +// guideNodeGeometry.length = CGFloat(cubeSize) +// +// break +// // vertical +// case 2: +// +// guideNodeGeometry.width = CGFloat(0.0005) +// guideNodeGeometry.height = CGFloat(cubeSize) +// guideNodeGeometry.length = CGFloat(cubeSize) +// +// break +// // diagonal +// case 3: +// +// guideNodeGeometry.width = CGFloat(cubeSize) +// guideNodeGeometry.height = CGFloat(cubeSize) +// guideNodeGeometry.length = CGFloat(cubeSize) +// +// break +// default: +// +// +// guideNodeGeometry.width = CGFloat(cubeSize) +// guideNodeGeometry.height = CGFloat(cubeSize) +// guideNodeGeometry.length = CGFloat(cubeSize) +// +// break +// } + + guideNodeGeometry.materials.first?.diffuse.contents = UIColor.red + + + boxNode.opacity = CGFloat(objectOpacity) + + return objectNode +} + + +func createCubeInterior(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + scene.drawingNode.addChildNode(objectNode) + objectNode.addChildNode(boxNode) + + objectNode.position = startingPoint + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + break + case 2: cubeSize = Float(cubeSizeLarge) + break + default: cubeSize = 0.2 + break + } + // create box itself + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + // create "base" + let baseNode = SCNNode() + baseNode.name = "currentStudyObjectBoxNode" + objectNode.addChildNode(baseNode) + + baseNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let baseNodeGeometry = baseNode.geometry as! SCNBox + + baseNodeGeometry.width = CGFloat(cubeSize * 3) + baseNodeGeometry.height = CGFloat(0.001) + baseNodeGeometry.length = CGFloat(cubeSize * 3) + +// baseNode.position = SCNVector3(0, -0.5 * cubeSize, 0) + baseNode.position = SCNVector3Zero + + // create guiding line around object + let guideNode = SCNNode() + guideNode.name = "currentStudyObjectGuideNode" + objectNode.addChildNode(guideNode) + + cubeSize = cubeSize * 1.001 + + guideNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry = guideNode.geometry as! SCNBox + + guideNodeGeometry.width = CGFloat(cubeSize) + guideNodeGeometry.height = CGFloat(0.0005) + guideNodeGeometry.length = CGFloat(cubeSize) + guideNodeGeometry.materials.first?.diffuse.contents = UIColor.red + + + // guideNode.position = SCNVector3(0, -0.5 * cubeSize, 0) + guideNode.position = SCNVector3Zero + + boxNode.opacity = CGFloat(objectOpacity) + //baseNode.opacity = CGFloat(objectOpacity) + baseNode.position = SCNVector3(0, -0.001, 0) + + return objectNode +} + +func createCubeFlat(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + scene.drawingNode.addChildNode(objectNode) + objectNode.addChildNode(boxNode) + + objectNode.position = startingPoint + + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + break + case 2: cubeSize = Float(cubeSizeLarge) + break + default: cubeSize = 0.2 + break + } + + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + //set the dimensions of the box + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + // create guiding line around object + let guideNode = SCNNode() + guideNode.name = "currentStudyObjectGuideNode" + objectNode.addChildNode(guideNode) + + cubeSize = cubeSize * 1.001 + + guideNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry = guideNode.geometry as! SCNBox + + + guideNode.position = SCNVector3Zero + + + //set the dimensions of the box + guideNodeGeometry.width = CGFloat(cubeSize) + guideNodeGeometry.height = CGFloat(0.0005) + guideNodeGeometry.length = CGFloat(cubeSize) + +// switch direction{ +// // horizontal +// case 1: +// +// //set the dimensions of the box +// guideNodeGeometry.width = CGFloat(cubeSize) +// guideNodeGeometry.height = CGFloat(0.0005) +// guideNodeGeometry.length = CGFloat(0.0005) +// +// break +// // vertical +// case 2: +// +// guideNodeGeometry.width = CGFloat(0.0005) +// guideNodeGeometry.height = CGFloat(0.0005) +// guideNodeGeometry.length = CGFloat(cubeSize) +// +// break +// // diagonal +// case 3: +// +// guideNodeGeometry.width = CGFloat(cubeSize) +// guideNodeGeometry.height = CGFloat(0.0005) +// guideNodeGeometry.length = CGFloat(cubeSize) +// +// break +// default: +// +// +// guideNodeGeometry.width = CGFloat(cubeSize) +// guideNodeGeometry.height = CGFloat(0.0005) +// guideNodeGeometry.length = CGFloat(cubeSize) +// +// break +// } + guideNodeGeometry.materials.first?.diffuse.contents = UIColor.red + + + boxNode.opacity = CGFloat(objectOpacity) + + return objectNode} + + +//func createSphereInterior(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ +// let objectNode = SCNNode() +// objectNode.name = "currentStudyObjectNode" +// let sphereNode = SCNNode() +// sphereNode.name = "currentStudyObjectSphereNode" +// scene.drawingNode.addChildNode(objectNode) +// objectNode.addChildNode(sphereNode) +// +// objectNode.position = scene.pencilPoint.position +// +// var sphereSize: Float +// switch size{ +// case 1: sphereSize = 0.1 +// break +// case 2: sphereSize = 0.5 +// break +// default: sphereSize = 0.2 +// break +// } +// +// sphereSize = sphereSize / 2 +// // create sphere itself +// sphereNode.geometry = SCNSphere.init(radius: CGFloat(sphereSize)) +// let sphereNodeGeometry = sphereNode.geometry as! SCNSphere +// +// sphereNodeGeometry.radius = CGFloat(sphereSize) +// +// sphereNode.position = SCNVector3Zero +// +// // create "base" +// let baseNode = SCNNode() +// baseNode.name = "currentStudyObjectBoxNode" +// objectNode.addChildNode(baseNode) +// +// baseNode.geometry = SCNBox.init(width: CGFloat(sphereSize), height: CGFloat(sphereSize), length: CGFloat(sphereSize), chamferRadius: 0.0) +// let baseNodeGeometry = baseNode.geometry as! SCNBox +// +// baseNodeGeometry.width = CGFloat(sphereSize * 3) +// baseNodeGeometry.height = CGFloat(0.001) +// baseNodeGeometry.length = CGFloat(sphereSize * 3) +// +// baseNode.position = SCNVector3Zero +// +// // create guiding line around object +// let guideNode = SCNNode() +// guideNode.name = "currentStudyObjectGuideNode" +// objectNode.addChildNode(guideNode) +// +// sphereSize = sphereSize * 1.001 +// +// guideNode.geometry = SCNCylinder.init(radius: CGFloat(sphereSize), height: 0.0005) +// let guideNodeGeometry = guideNode.geometry as! SCNCylinder +// +// guideNodeGeometry.radius = CGFloat(sphereSize) +// guideNodeGeometry.height = CGFloat(0.0005) +// guideNodeGeometry.materials.first?.diffuse.contents = UIColor.red +// +// +// guideNode.position = SCNVector3(0, 0, 0) +// +// sphereNode.opacity = CGFloat(objectOpacity) +// baseNode.opacity = CGFloat(objectOpacity) +// +// return objectNode +//} + + +//func createSphereExterior(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ +// let objectNode = SCNNode() +// objectNode.name = "currentStudyObjectNode" +// let sphereNode = SCNNode() +// sphereNode.name = "currentStudyObjectSphereNode" +// scene.drawingNode.addChildNode(objectNode) +// objectNode.addChildNode(sphereNode) +// +// objectNode.position = scene.pencilPoint.position +// +// var sphereSize: Float +// switch size{ +// case 1: sphereSize = 0.1 +// break +// case 2: sphereSize = 0.5 +// break +// default: sphereSize = 0.2 +// break +// } +// sphereSize = sphereSize / 2 +// // create sphere itself +// sphereNode.geometry = SCNSphere.init(radius: CGFloat(sphereSize)) +// let sphereNodeGeometry = sphereNode.geometry as! SCNSphere +// +// sphereNodeGeometry.radius = CGFloat(sphereSize) +// +// sphereNode.position = SCNVector3Zero +// +// // create guiding line around object +// let guideNode = SCNNode() +// guideNode.name = "currentStudyObjectGuideNode" +// objectNode.addChildNode(guideNode) +// +// sphereSize = sphereSize * 1.001 +// +// guideNode.geometry = SCNCylinder.init(radius: CGFloat(sphereSize), height: 0.0005) +// let guideNodeGeometry = guideNode.geometry as! SCNCylinder +// +// guideNodeGeometry.radius = CGFloat(sphereSize) +// guideNodeGeometry.height = CGFloat(0.0005) +// guideNodeGeometry.materials.first?.diffuse.contents = UIColor.red +// +// guideNode.position = SCNVector3(0, 0, 0) +// +// sphereNode.opacity = CGFloat(objectOpacity) +// +// return objectNode +//} + +} diff --git a/iOS App/ARPen/Plugins/StudyObjectPlugin.swift b/iOS App/ARPen/Plugins/StudyObjectPlugin.swift new file mode 100644 index 0000000..755e702 --- /dev/null +++ b/iOS App/ARPen/Plugins/StudyObjectPlugin.swift @@ -0,0 +1,396 @@ +// +// StudyObjectPlugin.swift +// ARPen +// +// Created by Martin on 07.12.19. +// Copyright © 2019 RWTH Aachen. All rights reserved. +// + + +import Foundation +import ARKit + +//include the UserStudyRecordPluginProtocol to demo recording of user study data +class StudyObjectPlugin: Plugin, UserStudyRecordPluginProtocol { + //reference to userStudyRecordManager to add new records + var recordManager: UserStudyRecordManager! + + var pluginImage : UIImage? = UIImage.init(named: "CubeByDraggingPlugin") + var pluginInstructionsImage: UIImage? = UIImage.init(named: "CubePluginInstructions") + var pluginIdentifier: String = "StudyObjectCreator" + var needsBluetoothARPen: Bool = false + var pluginDisabledImage: UIImage? = UIImage.init(named: "ARMenusPluginDisabled") + var currentScene : PenScene? + var currentView: ARSCNView? + + var objectCounter: Int = 0 + var objectCurrentlyBuilding: Bool = false + + let objectOpacity = 0.4 + + let cubeSizeSmall = 0.05 + let cubeSizeLarge = 0.2 + + // trial data + var userID = 5 + var trialNumber = 0 + + /** + The starting point is the point of the pencil where the button was first pressed. + If this var is nil, there was no initial point + */ + private var startingPoint: SCNVector3? + + func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { + guard scene.markerFound else { + //Don't reset the previous point to avoid restarting cube if the marker detection failed for some frames + //self.startingPoint = nil + return + } + + // MARK: Button3, Object Creation + + //Check state of the first button -> used to create the cube + let pressed3 = buttons[Button.Button3]! + + //if the button is pressed -> either set the starting point of the cube (first action) or scale the cube to fit from the starting point to the current point + if pressed3 { + if let startingPoint = self.startingPoint { + //see if there is an active box node that is currently being drawn. Otherwise create it + + // if no object exists: create new object + if !objectCurrentlyBuilding{ + // object exists already: delete it and create new object + + if let objectNode = scene.drawingNode.childNode(withName: "currentStudyObjectNode", recursively: false){ + for obj in objectNode.childNodes{ + obj.removeFromParentNode() + } + objectNode.removeFromParentNode() + } + + + if let objectNode = scene.drawingNode.childNode(withName: "currentStudyObjectBoxNode", recursively: false){ + for obj in objectNode.childNodes{ + obj.removeFromParentNode() + } + objectNode.removeFromParentNode() + } + + if let objectNode = scene.drawingNode.childNode(withName: "currentStudyObjectSphereNode", recursively: false){ + for obj in objectNode.childNodes{ + obj.removeFromParentNode() + } + objectNode.removeFromParentNode() + } + + if let objectNode = scene.drawingNode.childNode(withName: "currentStudyObjectGuideNode", recursively: false){ + for obj in objectNode.childNodes{ + obj.removeFromParentNode() + } + objectNode.removeFromParentNode() + } + + // whether previous object existed or not, create new one + switch objectCounter { + case 0: + // flat corner small + let objectNode = generateObject(size: 1, shape: 1, corner: 3, startingPoint: scene.pencilPoint.position, scene: scene) + break + case 1: + // flat corner large + let objectNode = generateObject(size: 2, shape: 1, corner: 3, startingPoint: scene.pencilPoint.position, scene: scene) + break + case 2: + // exterior corner small + let objectNode = generateObject(size: 1, shape: 1, corner: 1, startingPoint: scene.pencilPoint.position, scene: scene) + break + case 3: + // exterior corner large + let objectNode = generateObject(size: 2, shape: 1, corner: 1, startingPoint: scene.pencilPoint.position, scene: scene) + break + case 4: + // interior corner small + let objectNode = generateObject(size: 1, shape: 1, corner: 2, startingPoint: scene.pencilPoint.position, scene: scene) + break + default: + // interior corner large + let objectNode = generateObject(size: 2, shape: 1, corner: 2, startingPoint: scene.pencilPoint.position, scene: scene) + break + } +// +// let objectNode = generateObject(size: 1, shape: 1, direction: 1, corner: 2, startingPoint: scene.pencilPoint.position, scene: scene) +// objectNode.name = "currentStudyObjectNode" + objectCurrentlyBuilding = true + objectCounter += 1 + } + + + } + else { + //if the button is pressed but no startingPoint exists -> first frame with the button pressed. Set current pencil position as the start point + self.startingPoint = scene.pencilPoint.position + } + } + else { + //if the button is not pressed, check if a startingPoint is set -> released button. Reset the startingPoint to nil and set the name of the drawn box to "finished" + if objectCurrentlyBuilding{ + objectCurrentlyBuilding = false + } + if self.startingPoint != nil { + self.startingPoint = nil + if let boxNode = scene.drawingNode.childNode(withName: "currentStudyObjectNode", recursively: false), let boxNodeGeometry = boxNode.geometry as? SCNBox { + boxNode.name = "currentStudyObjectNode" + + //store a new record with the size of the finished box + let boxDimensionsDict = ["Width" : String(describing: boxNodeGeometry.width), "Height" : String(describing: boxNodeGeometry.height), "Length" : String(describing: boxNodeGeometry.length)] + self.recordManager.addNewRecord(withIdentifier: "BoxFinished", andData: boxDimensionsDict) + } + } + + } + + // MARK: Button1, Drawing + + + } + + func getTrialNumber()->Int{ + return trialNumber + } + + func getUserID()->Int{ + return userID + } + + func activatePlugin(withScene scene: PenScene, andView view: ARSCNView) { + self.currentScene = scene + self.currentView = view + } + + func deactivatePlugin() { + self.currentScene = nil + self.currentView = nil + } + + + + +func generateObject(size: Int, shape: Int, corner: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + var objectNode = SCNNode() + + switch corner{ + // interior + case 1: + objectNode = createCubeInterior(size: size, startingPoint: startingPoint, scene: scene) + break + // exterior + case 2: + objectNode = createCubeExterior(size: size, startingPoint: startingPoint, scene: scene) + break + // flat + case 3: + objectNode = createCubeFlat(size: size, startingPoint: startingPoint, scene: scene) + break + default: break + } + + trialNumber = trialNumber+1 + return objectNode + +} + +func createCubeExterior(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + scene.drawingNode.addChildNode(objectNode) + objectNode.addChildNode(boxNode) + + + objectNode.position = scene.pencilPoint.position + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + break + case 2: cubeSize = Float(cubeSizeLarge) + break + default: cubeSize = 0.2 + break + } + + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + //set the dimensions of the box + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + + + + // create guiding line around object + let guideNode = SCNNode() + guideNode.name = "currentStudyObjectGuideNode" + objectNode.addChildNode(guideNode) + + guideNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry = guideNode.geometry as! SCNBox + + + cubeSize = cubeSize * 1.001 + + guideNode.position = SCNVector3(0, cubeSize/2, 0) + + + + //set the dimensions of the box + guideNodeGeometry.width = CGFloat(cubeSize) + guideNodeGeometry.height = CGFloat(0.0005) + guideNodeGeometry.length = CGFloat(cubeSize) + + + guideNodeGeometry.materials.first?.diffuse.contents = UIColor.red + + + boxNode.opacity = CGFloat(objectOpacity) + + return objectNode +} + + +func createCubeInterior(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + scene.drawingNode.addChildNode(objectNode) + objectNode.addChildNode(boxNode) + + objectNode.position = scene.pencilPoint.position + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + break + case 2: cubeSize = Float(cubeSizeLarge) + break + default: cubeSize = 0.2 + break + } + // create box itself + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + // create "base" + let baseNode = SCNNode() + baseNode.name = "currentStudyObjectBoxNode" + objectNode.addChildNode(baseNode) + + baseNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let baseNodeGeometry = baseNode.geometry as! SCNBox + + baseNodeGeometry.width = CGFloat(cubeSize * 3) + baseNodeGeometry.height = CGFloat(0.001) + baseNodeGeometry.length = CGFloat(cubeSize * 3) + +// baseNode.position = SCNVector3(0, -0.5 * cubeSize, 0) + baseNode.position = SCNVector3Zero + + // create guiding line around object + let guideNode = SCNNode() + guideNode.name = "currentStudyObjectGuideNode" + objectNode.addChildNode(guideNode) + + cubeSize = cubeSize * 1.001 + + guideNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry = guideNode.geometry as! SCNBox + + guideNodeGeometry.width = CGFloat(cubeSize) + guideNodeGeometry.height = CGFloat(0.0005) + guideNodeGeometry.length = CGFloat(cubeSize) + guideNodeGeometry.materials.first?.diffuse.contents = UIColor.red + + + // guideNode.position = SCNVector3(0, -0.5 * cubeSize, 0) + guideNode.position = SCNVector3Zero + + boxNode.opacity = CGFloat(objectOpacity) + //baseNode.opacity = CGFloat(objectOpacity) + baseNode.position = SCNVector3(0, -0.001, 0) + + return objectNode +} + +func createCubeFlat(size: Int, startingPoint: SCNVector3, scene: PenScene)->SCNNode{ + + let objectNode = SCNNode() + objectNode.name = "currentStudyObjectNode" + let boxNode = SCNNode() + boxNode.name = "currentStudyObjectBoxNode" + scene.drawingNode.addChildNode(objectNode) + objectNode.addChildNode(boxNode) + + objectNode.position = scene.pencilPoint.position + + + var cubeSize: Float + switch size{ + case 1: cubeSize = Float(cubeSizeSmall) + break + case 2: cubeSize = Float(cubeSizeLarge) + break + default: cubeSize = 0.2 + break + } + + boxNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let boxNodeGeometry = boxNode.geometry as! SCNBox + + //set the dimensions of the box + boxNodeGeometry.width = CGFloat(cubeSize) + boxNodeGeometry.height = CGFloat(cubeSize) + boxNodeGeometry.length = CGFloat(cubeSize) + + boxNode.position = SCNVector3Zero + + // create guiding line around object + let guideNode = SCNNode() + guideNode.name = "currentStudyObjectGuideNode" + objectNode.addChildNode(guideNode) + + cubeSize = cubeSize * 1.001 + + guideNode.geometry = SCNBox.init(width: CGFloat(cubeSize), height: CGFloat(cubeSize), length: CGFloat(cubeSize), chamferRadius: 0.0) + let guideNodeGeometry = guideNode.geometry as! SCNBox + + + guideNode.position = SCNVector3Zero + + + //set the dimensions of the box + guideNodeGeometry.width = CGFloat(cubeSize) + guideNodeGeometry.height = CGFloat(0.0005) + guideNodeGeometry.length = CGFloat(cubeSize) + + guideNodeGeometry.materials.first?.diffuse.contents = UIColor.red + + + boxNode.opacity = CGFloat(objectOpacity) + + return objectNode} + +} diff --git a/iOS App/ARPen/Plugins/SweepPlugins/SweepPluginProfileAndPath.swift b/iOS App/ARPen/Plugins/SweepPlugins/SweepPluginProfileAndPath.swift deleted file mode 100755 index 6f14401..0000000 --- a/iOS App/ARPen/Plugins/SweepPlugins/SweepPluginProfileAndPath.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// SweepPluginProfileAndPath.swift -// ARPen -// -// Created by Jan Benscheid on 04.04.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation -import ARKit - -class SweepPluginProfileAndPath: ModelingPlugin { - - private var freePaths: [ARPPath] = [ARPPath]() - private var busy: Bool = false - - override init() { - - super.init() - - curveDesigner.didCompletePath = self.didCompletePath - - self.pluginImage = UIImage.init(named: "Sweep(Path)") - self.pluginInstructionsImage = UIImage.init(named: "PaintPluginInstructions") - self.pluginIdentifier = "Sweep (Path)" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "ARMenusPluginDisabled") - } - - func didCompletePath(_ path: ARPPath) { - freePaths.append(path) - if let profile = freePaths.first(where: { $0.closed }), - let spine = freePaths.first(where: { !$0.closed && $0.points.count > 1 }) { - DispatchQueue.global(qos: .userInitiated).async { - profile.flatten() - - if let sweep = try? ARPSweep(profile: profile, path: spine) { - - DispatchQueue.main.async { - self.currentScene?.drawingNode.addChildNode(sweep) - self.freePaths.removeAll(where: { $0 === profile || $0 === spine }) - - } - } - } - } - } -} diff --git a/iOS App/ARPen/Plugins/SweepPlugins/SweepPluginTutorial.swift b/iOS App/ARPen/Plugins/SweepPlugins/SweepPluginTutorial.swift deleted file mode 100755 index 6b39bfa..0000000 --- a/iOS App/ARPen/Plugins/SweepPlugins/SweepPluginTutorial.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// SweepPluginTutorial.swift -// ARPen -// -// Created by Jan Benscheid on 29.9.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation -import ARKit - -/** - This class should demonstrate the exemplary usage of the geometry manipulation code. - */ -class SweepPluginTutorial: ModelingPlugin { - - /// Paths, which are not yet used to create a sweep - private var freePaths: [ARPPath] = [ARPPath]() - - override init() { - - super.init() - - // Listen to the `didCompletePath` event. - curveDesigner.didCompletePath = self.didCompletePath - - self.pluginImage = UIImage.init(named: "PaintPlugin") - self.pluginInstructionsImage = UIImage.init(named: "PaintPluginInstructions") - self.pluginIdentifier = "Sweep Plugin Tutorial" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "ARMenusPluginDisabled") - } - - func didCompletePath(_ path: ARPPath) { - // Add newly completed path to set of free paths. - freePaths.append(path) - - // Look in the free paths for one that is closed and one which is open and has more than one point. - // Use them to create a sweep. - if let profile = freePaths.first(where: { $0.closed }), - let spine = freePaths.first(where: { !$0.closed && $0.points.count > 1 }) { - - // Geometry creation may take time and should be done asynchronous. - DispatchQueue.global(qos: .userInitiated).async { - - // Only planar paths can be used as profile for sweeping. - profile.flatten() - - // Try to create a sweep - if let sweep = try? ARPSweep(profile: profile, path: spine) { - // Attach the swept object to the scene synchronous. - DispatchQueue.main.async { - self.currentScene?.drawingNode.addChildNode(sweep) - // Remove the links to the used paths from the set of free paths. - // You don't need to (and must not) delete the paths themselves. When creating the sweep, they became children of the `ARPSweep` object in order to allow for hierarchical editing. - self.freePaths.removeAll(where: { $0 === profile || $0 === spine }) - } - } - } - } - } - -} diff --git a/iOS App/ARPen/Plugins/SweepPlugins/SweepPluginTwoProfiles.swift b/iOS App/ARPen/Plugins/SweepPlugins/SweepPluginTwoProfiles.swift deleted file mode 100755 index f292ee5..0000000 --- a/iOS App/ARPen/Plugins/SweepPlugins/SweepPluginTwoProfiles.swift +++ /dev/null @@ -1,115 +0,0 @@ -// -// SweepPluginTwoProfiles.swift -// ARPen -// -// Created by Jan Benscheid on 04.04.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation -import ARKit - -class SweepPluginTwoProfiles: ModelingPlugin { - - private var freePaths: [ARPPath] = [ARPPath]() - private var busy: Bool = false - - - override init() { - super.init() - - curveDesigner.didCompletePath = self.didCompletePath - - self.pluginImage = UIImage.init(named: "Sweep(2Profiles)") - self.pluginInstructionsImage = UIImage.init(named: "PaintPluginInstructions") - self.pluginIdentifier = "Sweep (2 Profiles)" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "ARMenusPluginDisabled") - } - - func didCompletePath(_ path: ARPPath) { - freePaths.append(path) - if let profile1 = freePaths.first(where: { $0.closed }), - let profile2 = freePaths.last(where: { $0.closed }), - profile1 !== profile2 { - - profile1.flatten() - profile2.flatten() - - let center1 = profile1.getCenter() - let center2 = profile2.getCenter() - - //let midpoint = (center1 + center2) / 2 - let pc1 = profile1.getPC1() - let pc2 = profile2.getPC1() - - var points = [ARPPathNode(center1, cornerStyle: .sharp)] - - var normal1: SCNVector3! - var normal2: SCNVector3! - var midpoint1: SCNVector3! - var midpoint2: SCNVector3! - - let pathScale = center1.distance(vector: center2) / 4 - - // Find the slinky direction with the least amount of bending - var minBending = Float.greatestFiniteMagnitude - for d1 in [-1.0, 1.0] { - for d2 in [-1.0, 1.0] { - - var n1 = pc1 * Float(d1) - var n2 = pc2 * Float(d2) - - // Edge case 1: If the resulting normals are very similar, orient them upwards (slinky-behaviour). - if n1.dot(vector: n2) > 0.8 && n1.y < 0 { - n1 *= -1 - n2 *= -1 - } - - var mid1 = center1 + n1*pathScale - mid1 += (center2 - center1) * 0.1 - - var mid2 = center2 + n2*pathScale - mid2 += (center1 - center2) * 0.1 - - let m1toc1 = (center1 - mid1).normalized() - let m1tom2 = (mid2 - mid1).normalized() - let m2toc2 = (center2 - mid2).normalized() - let bending = m1toc1.dot(vector: m1tom2) + (m1tom2 * -1).dot(vector: m2toc2) - - if (bending < minBending) { - minBending = bending - midpoint1 = mid1 - midpoint2 = mid2 - normal1 = n1 - normal2 = n2 - } - } - } - - // Edge case 2: If both normals are almost aligned with the center line between the profiles, don't add additional points s.t. the spine is just a straight line. - if !((center2 - center1).normalized().dot(vector: normal1) > 0.8 && - (center1 - center2).normalized().dot(vector: normal2) > 0.8) { - points.append(ARPPathNode(midpoint1, cornerStyle: .round)) - points.append(ARPPathNode(midpoint2, cornerStyle: .round)) - } - - points.append(ARPPathNode(center2, cornerStyle: .sharp)) - - let spine = ARPPath(points: points, closed: false) - self.currentScene?.drawingNode.addChildNode(spine) - - DispatchQueue.global(qos: .userInitiated).async { - - if let sweep = try? ARPSweep(profile: profile1, path: spine) { - - DispatchQueue.main.async { - self.currentScene?.drawingNode.addChildNode(sweep) - profile2.removeFromParentNode() - self.freePaths.removeAll(where: { $0 === profile1 || $0 === profile2 }) - } - } - } - } - } -} diff --git a/iOS App/ARPen/Plugins/TranslationDemoPlugin.swift b/iOS App/ARPen/Plugins/TranslationDemoPlugin.swift index b509f96..c53b3bd 100644 --- a/iOS App/ARPen/Plugins/TranslationDemoPlugin.swift +++ b/iOS App/ARPen/Plugins/TranslationDemoPlugin.swift @@ -11,7 +11,15 @@ import ARKit class TranslationDemoPlugin: Plugin { - static var nodeType : ARPenStudyNode.Type = ARPenBoxNode.self + var pluginImage : UIImage? = UIImage.init(named: "Move1DemoPlugin") + var pluginInstructionsImage: UIImage? = UIImage.init(named: "Move1PluginInstruction") + var pluginIdentifier: String = "Move 1" + var needsBluetoothARPen: Bool = false + var pluginDisabledImage: UIImage? = UIImage.init(named: "TranslationDemoPluginDisabled") + var currentScene : PenScene? + var currentView: ARSCNView? + + static var nodeType : ARPenStudyNode.Type = ARPenWireBoxNode.self var sceneConstructionResults : (superNode: SCNNode, studyNodes: [ARPenStudyNode])? var boxes : [ARPenStudyNode]? @@ -63,17 +71,7 @@ class TranslationDemoPlugin: Plugin { } private var dropTargets = [ARPenDropTargetNode(withFloorPosition: SCNVector3Make(0.1238, 0, 0.08695)), ARPenDropTargetNode(withFloorPosition: SCNVector3Make(-0.1238, 0, 0.08695)), ARPenDropTargetNode(withFloorPosition: SCNVector3Make(0.1238, 0, -0.08695)), ARPenDropTargetNode(withFloorPosition: SCNVector3Make(-0.1238, 0, -0.08695))] - override init() { - super.init() - - self.pluginImage = UIImage.init(named: "Move1DemoPlugin") - self.pluginInstructionsImage = UIImage.init(named: "Move1PluginInstruction") - self.pluginIdentifier = "Move 1" - self.needsBluetoothARPen = false - self.pluginDisabledImage = UIImage.init(named: "TranslationDemoPluginDisabled") - } - - override func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { + func didUpdateFrame(scene: PenScene, buttons: [Button : Bool]) { guard let boxes = self.boxes else {return} @@ -215,14 +213,16 @@ class TranslationDemoPlugin: Plugin { } - override func activatePlugin(withScene scene: PenScene, andView view: ARSCNView){ - super.activatePlugin(withScene: scene, andView: view) + func activatePlugin(withScene scene: PenScene, andView view: ARSCNView){ + + if (TranslationDemoPlugin.nodeType == ARPenWireBoxNode.self) { + TranslationDemoPlugin.nodeType = ARPenBoxNode.self + } else { + TranslationDemoPlugin.nodeType = ARPenWireBoxNode.self + } -// if (TranslationDemoPlugin.nodeType == ARPenWireBoxNode.self) { -// TranslationDemoPlugin.nodeType = ARPenBoxNode.self -// } else { -// TranslationDemoPlugin.nodeType = ARPenWireBoxNode.self -// } + self.currentScene = scene + self.currentView = view self.fillSceneWithCubes(withScene: scene, andView : view) @@ -255,14 +255,15 @@ class TranslationDemoPlugin: Plugin { self.activeTargetBox = nil } - override func deactivatePlugin() { + func deactivatePlugin() { self.activeTargetBox = nil //_ = self.currentScene?.drawingNode.childNodes.map({$0.removeFromParentNode()}) if let constructionResults = self.sceneConstructionResults { constructionResults.superNode.removeFromParentNode() self.sceneConstructionResults = nil } + self.currentScene = nil self.currentView?.superview?.layer.borderWidth = 0.0 - super.deactivatePlugin() + self.currentView = nil } } diff --git a/iOS App/ARPen/Plugins/Util/Arranger.swift b/iOS App/ARPen/Plugins/Util/Arranger.swift deleted file mode 100755 index 226adeb..0000000 --- a/iOS App/ARPen/Plugins/Util/Arranger.swift +++ /dev/null @@ -1,210 +0,0 @@ -// -// Arranger.swift -// ARPen -// -// Created by Jan Benscheid on 08.06.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation -import ARKit - -/** -This class handles the selecting and arranging and "visiting" of objects, as this functionality is shared across multiple plugins. An examplary usage can be seen in `CombinePluginTutorial.swift`. -To "visit" means to march down the hierarchy of a node, e.g. to rearrange the object which form a Boolean operation. -*/ -class Arranger { - - var currentScene: PenScene? - var currentView: ARSCNView? - - /// The time (in seconds) after which holding the main button on an object results in dragging it. - static let timeTillDrag: Double = 1 - /// The minimum distance to move the pen starting at an object while holding the main button which results in dragging it. - static let maxDistanceTillDrag: Float = 0.015 - /// Move the object with its center to the pen tip when dragging starts. - static let snapWhenDragging: Bool = true - - var hoverTarget: ARPNode? { - didSet { - if let old = oldValue { - old.highlighted = false - } - if let target = hoverTarget { - target.highlighted = true - } - } - } - var selectedTargets: [ARPNode] = [] - var visitTarget: ARPGeomNode? - var dragging: Bool = false - private var buttonEvents: ButtonEvents - private var justSelectedSomething = false - - private var lastClickPosition: SCNVector3? - private var lastClickTime: Date? - private var lastPenPosition: SCNVector3? - - var didSelectSomething: ((ARPNode) -> Void)? - - init() { - buttonEvents = ButtonEvents() - buttonEvents.didPressButton = self.didPressButton - buttonEvents.didReleaseButton = self.didReleaseButton - buttonEvents.didDoubleClick = self.didDoubleClick - } - - func activate(withScene scene: PenScene, andView view: ARSCNView) { - self.currentView = view - self.currentScene = scene - self.visitTarget = nil - self.dragging = false - self.justSelectedSomething = false - self.lastClickPosition = nil - self.lastClickTime = nil - self.lastPenPosition = nil - } - - func deactivate() { - for target in selectedTargets { - unselectTarget(target) - } - } - - func update(scene: PenScene, buttons: [Button : Bool]) { - buttonEvents.update(buttons: buttons) - - if let hit = hitTest(pointerPosition: scene.pencilPoint.position) { - hoverTarget = hit - } else { - hoverTarget = nil - } - - // Start dragging when either the button has been held for long enough or pen has moved a certain distance. - if (buttons[.Button1] ?? false) && - ((Date() - (lastClickTime ?? Date())) > Arranger.timeTillDrag - || (lastPenPosition?.distance(vector: scene.pencilPoint.position) ?? 0) > Arranger.maxDistanceTillDrag) { - dragging = true - - if Arranger.snapWhenDragging { - let center = selectedTargets.reduce(SCNVector3(0,0,0), { $0 + $1.position }) / Float(selectedTargets.count) - let shift = scene.pencilPoint.position - center - for target in selectedTargets { - target.position += shift - } - } - } - - if dragging, let lastPos = lastPenPosition { - for target in selectedTargets { - target.position += scene.pencilPoint.position - lastPos - } - lastPenPosition = scene.pencilPoint.position - } - } - - func didPressButton(_ button: Button) { - - switch button { - case .Button1: - lastClickPosition = currentScene?.pencilPoint.position - lastPenPosition = currentScene?.pencilPoint.position - lastClickTime = Date() - - if let target = hoverTarget { - if !selectedTargets.contains(target) { - selectTarget(target) - } - } else { - for target in selectedTargets { - unselectTarget(target) - } - } - default: - break - } - } - - func didReleaseButton(_ button: Button) { - switch button { - case .Button1: - if dragging { - for target in selectedTargets { - DispatchQueue.global(qos: .userInitiated).async { - // Do this in the background, as it may cause a time-intensive rebuild in the parent object - target.applyTransform() - } - } - } else { - if let target = hoverTarget, !justSelectedSomething { - if selectedTargets.contains(target) { - unselectTarget(target) - } - } - } - justSelectedSomething = false - lastPenPosition = nil - dragging = false - default: - break - } - } - - func didDoubleClick(_ button: Button) { - if button == .Button1, - let scene = currentScene { - if let hit = hitTest(pointerPosition: scene.pencilPoint.position) as? ARPGeomNode { - if hit.parent?.parent === visitTarget || visitTarget == nil { - visitTarget(hit) - } else { - leaveTarget() - } - } else { - leaveTarget() - } - } - } - - - func visitTarget(_ target: ARPGeomNode) { - unselectTarget(target) - target.visited = true - visitTarget = target - } - - func leaveTarget() { - if let target = visitTarget { - target.visited = false - if let parent = target.parent?.parent as? ARPGeomNode { - parent.visited = true - visitTarget = parent - } else { - visitTarget = nil - } - } - } - - - func selectTarget(_ target: ARPNode) { - target.selected = true - selectedTargets.append(target) - justSelectedSomething = true - didSelectSomething?(target) - } - - func unselectTarget(_ target: ARPNode) { - target.selected = false - selectedTargets.removeAll(where: { $0 === target }) - } - - func hitTest(pointerPosition: SCNVector3) -> ARPNode? { - guard let sceneView = self.currentView else { return nil } - let projectedPencilPosition = sceneView.projectPoint(pointerPosition) - let projectedCGPoint = CGPoint(x: CGFloat(projectedPencilPosition.x), y: CGFloat(projectedPencilPosition.y)) - - // Cast a ray from that position and find the first ARPenNode - let hitResults = sceneView.hitTest(projectedCGPoint, options: [SCNHitTestOption.searchMode : SCNHitTestSearchMode.all.rawValue]) - - return hitResults.filter( { $0.node != currentScene?.pencilPoint } ).first?.node.parent as? ARPNode - } -} diff --git a/iOS App/ARPen/Plugins/Util/ButtonEvents.swift b/iOS App/ARPen/Plugins/Util/ButtonEvents.swift deleted file mode 100755 index 9958acb..0000000 --- a/iOS App/ARPen/Plugins/Util/ButtonEvents.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// ButtonEvents.swift -// ARPen -// -// Created by Jan Benscheid on 15.04.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation - -/** -This class adds some convenient functionality to the pen's hardware buttons. -*/ -class ButtonEvents { - - static let doubleClickMaxDuration = 0.5 - - var didPressButton: ((Button) -> Void)? - var didReleaseButton: ((Button) -> Void)? - var didDoubleClick: ((Button) -> Void)? - - private var pressedThisFrame: [Button : Bool] = [:] - private var releasedThisFrame: [Button : Bool] = [:] - private var doubleClickedThisFrame: [Button : Bool] = [:] - - var buttons: [Button : Bool] = [:] - private var previousButtons: [Button : Bool] = [:] - private var previousClick: [Button : Date] = [:] - - func update(buttons: [Button : Bool]) { - self.buttons = buttons - - for (button, _) in buttons { - pressedThisFrame[button] = false - releasedThisFrame[button] = false - doubleClickedThisFrame[button] = false - - if buttonPressed(button) { - pressedThisFrame[button] = true - didPressButton?(button) - } else if buttonReleased(button) { - releasedThisFrame[button] = true - didReleaseButton?(button) - if let prev = previousClick[button], (Date() - prev) <= ButtonEvents.doubleClickMaxDuration { - doubleClickedThisFrame[button] = true - didDoubleClick?(button) - } - previousClick[button] = Date() - } - } - - previousButtons = buttons - } - - private func buttonPressed(_ button: Button) -> Bool { - if let n = buttons[button], let p = previousButtons[button] { - return n && !p - } else { - return false - } - } - - private func buttonReleased(_ button: Button) -> Bool { - if let n = buttons[button], let p = previousButtons[button] { - return !n && p - } else { - return false - } - } - - func justPressed(_ button: Button) -> Bool { - return pressedThisFrame[button] ?? false - } - - func justReleased(_ button: Button) -> Bool { - return releasedThisFrame[button] ?? false - } - - func justDoubleClicked(_ button: Button) -> Bool { - return releasedThisFrame[button] ?? false - } -} diff --git a/iOS App/ARPen/Plugins/Util/CurveDesigner.swift b/iOS App/ARPen/Plugins/Util/CurveDesigner.swift deleted file mode 100755 index 6db8887..0000000 --- a/iOS App/ARPen/Plugins/Util/CurveDesigner.swift +++ /dev/null @@ -1,195 +0,0 @@ -// -// CurveDesigner.swift -// ARPen -// -// Created by Jan Benscheid on 15.04.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation - -/** - This class handles the interactive creation of ARPPaths, as this functionality is shared across multiple plugins. An examplary usage can be seen in `SweepPluginTutorial.swift`. - */ -class CurveDesigner { - - /// Snapping distance for closing a path - static let snappingDistance: Float = 0.01 - /// When pressing and holding the button, this is the regular interval in which new nodes are created - static let minNextPointDistance: Float = 0.02 - - /// Function called when a new path is started - var didStartPath: ((ARPPath) -> Void)? - /// Function called when a new path is finished - var didCompletePath: ((ARPPath) -> Void)? - - /// The currently edited path - var activePath: ARPPath? = nil - - /// true, if the user just switched between round and sharp corners - private var blocked: Bool = false - /// true, if the path is currently being calculated in parallel in the backend, to reduce redundant calculations - private var busy: Bool = false - private var scene: PenScene! - - private var buttonEvents: ButtonEvents - - /// Guard to avoid inserting multiple nodes in one frame - private var addedThisFrame: Bool = false - - init() { - buttonEvents = ButtonEvents() - buttonEvents.didPressButton = self.didPressButton - buttonEvents.didReleaseButton = self.didReleaseButton - self.reset() - } - - func reset() { - self.blocked = false - self.busy = false - self.activePath = nil - self.addedThisFrame = false - } - - func update(scene: PenScene, buttons: [Button : Bool]) { - self.scene = scene - addedThisFrame = false - buttonEvents.update(buttons: buttons) - - if let path = activePath { - if path.points.first!.position.distance(vector: scene.pencilPoint.position) < CurveDesigner.snappingDistance && path.points.count > 2 { - path.getNonFixedPoint()?.position = path.points.first!.worldPosition - if buttonEvents.buttons[.Button2]! || buttonEvents.buttons[.Button3]! { - addNode(noNewPath: true) - } - } else { - path.getNonFixedPoint()?.position = scene.pencilPoint.position - } - - if let nonFixed = path.getNonFixedPoint() { - nonFixed.active = scene.markerFound - } - - tryRebuildPreview() - } - - if (buttonEvents.buttons[.Button2]! || buttonEvents.buttons[.Button3]!) && readyForNextPoint() { - addNode(noNewPath: true) - } - } - - func undo() { - if let path = self.activePath { - path.removeLastPoint() - if let last = path.points.last { - last.fixed = false - } else { - self.activePath = nil - } - } - } - - func injectUIButtons(_ buttons: [Button : UIButton]) { - buttons[.Button1]?.setTitle("Finish", for: .normal) - buttons[.Button2]?.setTitle("Sharp corner", for: .normal) - buttons[.Button3]?.setTitle("Round corner", for: .normal) - } - - private func didPressButton(_ button: Button) { - switch button { - case .Button1: - finishActivePath() - case .Button2, .Button3: - addNode() - } - } - - private func didReleaseButton(_ button: Button) { - blocked = false - /* - switch button { - case .Button1: - break - case .Button2, .Button3: - if let path = activePath, path.points.count > 2 && path.closed { - finishActivePath() - } - }*/ - } - - /// Add a node to either the currently active path, or create a new path if none is active (unless `noNewPath`is set). - private func addNode(noNewPath: Bool = false) { - if addedThisFrame { - return - } - addedThisFrame = true - let cornerStyle = buttonEvents.buttons[.Button2]! ? CornerStyle.sharp : CornerStyle.round - - if activePath == nil && !noNewPath { - let path = ARPPath(points: [ARPPathNode(scene.pencilPoint.position, cornerStyle: cornerStyle)], closed: false) - activePath = path - scene.drawingNode.addChildNode(path) - didStartPath?(path) - } - - if let path = activePath { - if pathEndsTouch(path) { - finishActivePath() - } else { - let activePoint = path.getNonFixedPoint() - if cornerStyle == activePoint?.cornerStyle { - activePoint?.fixed = true - path.appendPoint(ARPPathNode(scene.pencilPoint.position, cornerStyle: cornerStyle)) - } else { - activePoint?.cornerStyle = cornerStyle - blocked = true - } - } - } - } - - private func pathEndsTouch(_ path: ARPPath) -> Bool { - if let nonFixedPoint = path.getNonFixedPoint(), path.points.count > 2 { - if path.points.first!.worldPosition.distance(vector: nonFixedPoint.worldPosition) < CurveDesigner.snappingDistance { - return true - } - } - return false - } - - private func finishActivePath() { - if let path = activePath { - if pathEndsTouch(path) { - path.closed = true -// path.flatten() - } - path.removeNonFixedPoints() - path.rebuild() - path.runAction(ARPPath.finalizeAnimation) - activePath = nil - - didCompletePath?(path) - } - } - - func readyForNextPoint() -> Bool { - if let path = activePath, - let lastFixed = path.points.last(where: { $0.fixed }), - let lastFree = path.points.last(where: { !$0.fixed }) { - if lastFree.worldPosition.distance(vector: lastFixed.worldPosition) < CurveDesigner.minNextPointDistance { - return false - } - } - return !blocked - } - - func tryRebuildPreview() { - if !busy, let path = activePath { - busy = true - DispatchQueue.global(qos: .userInitiated).async { - path.rebuild() - self.busy = false - } - } - } -} diff --git a/iOS App/ARPen/Plugins/Util/TaskScenes.swift b/iOS App/ARPen/Plugins/Util/TaskScenes.swift deleted file mode 100755 index 4eebb8c..0000000 --- a/iOS App/ARPen/Plugins/Util/TaskScenes.swift +++ /dev/null @@ -1,251 +0,0 @@ -// -// CombinationDemoScenes.swift -// ARPen -// -// Created by Jan Benscheid on 18.06.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation - -class TaskScenes { - - static let shiftXRange: Float = 0 - static let shiftZRange: Float = 0 - - static func populateSceneBasedOnTask(scene: SCNNode, task: String, centeredAt position: SCNVector3) { - - var objects = [ARPNode]() - - let shiftX = Float.random(in: -shiftXRange/2...shiftXRange/2) - let shiftZ = Float.random(in: -shiftZRange/2...shiftZRange/2) - let shiftedPosition = position + SCNVector3(shiftX, 0, shiftZ) - - switch task { - case "Cube": - objects = TaskScenes.cubeScene(centeredAt: shiftedPosition) - case "Phone stand": - objects = TaskScenes.phoneStandScene(centeredAt: shiftedPosition) - case "Handle": - objects = TaskScenes.handleScene(centeredAt: shiftedPosition) - case "Flower pot": - objects = TaskScenes.flowerPotScene(centeredAt: shiftedPosition) - case "Door stopper": - objects = TaskScenes.doorStopperScene(centeredAt: shiftedPosition) - case "Candle holder": - objects = TaskScenes.candleHolderScene(centeredAt: shiftedPosition) - case "Spoon": - objects = TaskScenes.spoonScene(centeredAt: shiftedPosition) - case "Pen holder": - objects = TaskScenes.penHolderScene(centeredAt: shiftedPosition) - case "Combine demo": - objects = TaskScenes.combinationDemoScene(centeredAt: shiftedPosition) - default: - break - } - - for obj in objects { - scene.addChildNode(obj) - } - } - - static func isTaskDone(scene scn: SCNNode?, task tsk: String?) -> Bool { - - guard let scene = scn, let task = tsk else { - return false - } - - switch task { - case "Candle holder": - for case let node as ARPBoolNode in scene.childNodes { - if node.name == "(cube-cylinder)" { - return true - } - } - return false - case "Spoon": - for case let node as ARPBoolNode in scene.childNodes { - if node.name == "((bigSphere-smallSphere)+cylinder)" || - node.name == "((bigSphere+cylinder)-smallSphere)" { - return true - } - } - return false - case "Pen holder": - for case let node as ARPBoolNode in scene.childNodes { - if node.name == "(((smallCylinder-smallCylinderInner)+bigCylinder)-bigCylinderInner)" { - return true - } - } - return false - default: - return false - } - } - - static func candleHolderScene(centeredAt positon: SCNVector3) -> [ARPNode] { - let cube = ARPBox(width: 0.05, height: 0.05, length: 0.05) - cube.position = positon + SCNVector3(-0.1, 0.025, 0) - cube.applyTransform() - cube.name = "cube" - - let cylinder = ARPCylinder(radius: 0.02, height: 0.025) - cylinder.position = positon + SCNVector3(0.1, 0.0125, 0) - cylinder.applyTransform() - cylinder.name = "cylinder" - - return [cube, cylinder] - } - - static func spoonScene(centeredAt positon: SCNVector3) -> [ARPNode] { - let bigSphere = ARPSphere(radius: 0.02) - bigSphere.position = positon + SCNVector3(-0.1, 0.02, 0) - bigSphere.applyTransform() - bigSphere.name = "bigSphere" - - let smallSphere = ARPSphere(radius: 0.0175) - smallSphere.position = positon + SCNVector3(0, 0.0175, 0) - smallSphere.applyTransform() - smallSphere.name = "smallSphere" - - let cylinder = ARPCylinder(radius: 0.005, height: 0.07) - cylinder.position = positon + SCNVector3(0.1, 0.005, 0) - cylinder.rotation = SCNVector4(0, 0, 1, Float.pi/2) - cylinder.applyTransform() - cylinder.name = "cylinder" - - return [bigSphere, smallSphere, cylinder] - } - - static func penHolderScene(centeredAt positon: SCNVector3) -> [ARPNode] { - let bigCylinder = ARPCylinder(radius: 0.025, height: 0.08) - bigCylinder.position = positon + SCNVector3(-0.2, 0.04, 0) - bigCylinder.applyTransform() - bigCylinder.name = "bigCylinder" - - let bigCylinderInner = ARPCylinder(radius: 0.02, height: 0.08) - bigCylinderInner.position = positon + SCNVector3(-0.1, 0.04, 0) - bigCylinderInner.applyTransform() - bigCylinderInner.name = "bigCylinderInner" - - let smallCylinder = ARPCylinder(radius: 0.02, height: 0.06) - smallCylinder.position = positon + SCNVector3(0.1, 0.03, 0) - smallCylinder.applyTransform() - smallCylinder.name = "smallCylinder" - - let smallCylinderInner = ARPCylinder(radius: 0.015, height: 0.06) - smallCylinderInner.position = positon + SCNVector3(0.2, 0.03, 0) - smallCylinderInner.applyTransform() - smallCylinderInner.name = "smallCylinderInner" - - return [bigCylinder, bigCylinderInner, smallCylinder, smallCylinderInner] - } - - static func combinationDemoScene(centeredAt positon: SCNVector3) -> [ARPNode] { - let sphere = ARPSphere(radius: 0.02) - sphere.position = positon + SCNVector3(-0.1, 0.02, 0) - sphere.applyTransform() - sphere.name = "sphere" - - let box = ARPBox(width: 0.09, height: 0.03, length: 0.02) - box.position = positon + SCNVector3(0, 0.0175, 0) - box.applyTransform() - box.name = "box" - - let cylinder = ARPCylinder(radius: 0.005, height: 0.06) - cylinder.position = positon + SCNVector3(0.1, 0.005, 0) - cylinder.rotation = SCNVector4(0, 0, 1, Float.pi/2) - cylinder.applyTransform() - cylinder.name = "cylinder" - - return [sphere, box, cylinder] - } - - static let cubeSize: Float = 0.04 - static let cubeScale: Float = 1.5 - static func cubeScene(centeredAt position: SCNVector3) -> [ARPPath] { - let d = cubeScale * cubeSize - - let profile = ARPPath(points: [ - ARPPathNode(position.x - d/2, position.y, position.z - d/2, cornerStyle: .sharp, initFixed: true), - ARPPathNode(position.x - d/2, position.y, position.z + d/2, cornerStyle: .sharp, initFixed: true), - ARPPathNode(position.x + d/2, position.y, position.z + d/2, cornerStyle: .sharp, initFixed: true), - ARPPathNode(position.x + d/2, position.y, position.z - d/2, cornerStyle: .sharp, initFixed: true) - ], closed: true) - - return [profile] - } - - static let phoneStandHeight: Float = 0.07 - static let phoneStandScale: Float = 1 - static func phoneStandScene(centeredAt position: SCNVector3) -> [ARPPath] { - let s: Float = TaskScenes.phoneStandScale - - let profile = ARPPath(points: [ - ARPPathNode(position.x + 0.03*s, position.y, position.z + 0.018*s, cornerStyle: .sharp, initFixed: true), - ARPPathNode(position.x + 0.0125*s, position.y, position.z - 0.018*s, cornerStyle: .sharp, initFixed: true), - ARPPathNode(position.x + 0.0102*s, position.y, position.z - 0.008*s, cornerStyle: .round, initFixed: true), - ARPPathNode(position.x, position.y, position.z, cornerStyle: .sharp, initFixed: true), - ARPPathNode(position.x - 0.0394*s, position.y, position.z - 0.081*s, cornerStyle: .sharp, initFixed: true), - ARPPathNode(position.x - 0.088*s, position.y, position.z + 0.018*s, cornerStyle: .sharp, initFixed: true) - ], closed: true) - - return [profile] - } - - static func calcExtrusionDeviation(profile: ARPPath, spine: ARPPath, targetHeight: Float) -> Float { - let target = spine.points.first!.worldPosition + profile.getPC1() * targetHeight - let actual = spine.points.last!.worldPosition - let deviation = target.distance(vector: actual) / targetHeight - return deviation - } - - /// WARNING: In current implementation, handle must not be rotated, otherwise deviation measurements will be incorrect! - static let handleWidth: Float = 0.1 - static let handleScale: Float = 1 - static func handleScene(centeredAt position: SCNVector3) -> [ARPPath] { - let s: Float = TaskScenes.handleScale - - let profile = ARPPath(points: [ - ARPPathNode(position.x - 0.035*s, position.y, position.z - 0.015*s, cornerStyle: .sharp, initFixed: true), - ARPPathNode(position.x - 0.035*s, position.y, position.z + 0.015*s, cornerStyle: .sharp, initFixed: true), - ARPPathNode(position.x - 0.065*s, position.y, position.z + 0.015*s, cornerStyle: .sharp, initFixed: true), - ARPPathNode(position.x - 0.065*s, position.y, position.z - 0.015*s, cornerStyle: .sharp, initFixed: true) - ], closed: true) - - return [profile] - } - - static let flowerPotRadiusTop: Float = 0.027 - static let flowerPotRadiusBottom: Float = 0.018 - static let flowerPotScale: Float = 1 - static func flowerPotScene(centeredAt position: SCNVector3) -> [ARPPath] { - let s: Float = TaskScenes.flowerPotScale - - let profile = ARPPath(points: [ - ARPPathNode(position.x - 0.018*s, position.y, position.z, cornerStyle: .sharp, initFixed: true), - ARPPathNode(position.x - 0.023*s, position.y + 0.005*s, position.z, cornerStyle: .round, initFixed: true), - ARPPathNode(position.x - 0.018*s, position.y + 0.01*s, position.z, cornerStyle: .sharp, initFixed: true), - ARPPathNode(position.x - 0.032*s, position.y + 0.035*s, position.z, cornerStyle: .round, initFixed: true), - ARPPathNode(position.x - 0.018*s, position.y + 0.07*s, position.z, cornerStyle: .round, initFixed: true), - ARPPathNode(position.x - 0.027*s, position.y + 0.081*s, position.z, cornerStyle: .sharp, initFixed: true) - ], closed: false) - - return [profile] - } - - static let doorStopperRadiusTop: Float = 0.03 - static let doorStopperRadiusBottom: Float = 0.03 - static let doorStopperScale: Float = 1 - static func doorStopperScene(centeredAt position: SCNVector3) -> [ARPPath] { - let s: Float = TaskScenes.doorStopperScale - - let profile = ARPPath(points: [ - ARPPathNode(position.x - 0.03*s, position.y, position.z, cornerStyle: .sharp, initFixed: true), - ARPPathNode(position.x - 0.045*s, position.y + 0.015*s, position.z, cornerStyle: .round, initFixed: false), - ARPPathNode(position.x - 0.03*s, position.y + 0.03*s, position.z, cornerStyle: .sharp, initFixed: true) - ], closed: false) - - return [profile] - } -} diff --git a/iOS App/ARPen/Plugins/Util/TaskTimeLogger.swift b/iOS App/ARPen/Plugins/Util/TaskTimeLogger.swift deleted file mode 100755 index 9196388..0000000 --- a/iOS App/ARPen/Plugins/Util/TaskTimeLogger.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// TaskTimeLogger.swift -// ARPen -// -// Created by Jan Benscheid on 14.06.19. -// Copyright © 2019 RWTH Aachen. All rights reserved. -// - -import Foundation - -class TaskTimeLogger { - - var defaultDict = [String: String]() - private var startTime: Date? - private var accumulatedTime: TimeInterval = 0 - private var running: Bool = false - - func startUnlessRunning() { - if self.startTime == nil { - self.reset() - self.resume() - } - } - - func pause() { - if running, let startTime = self.startTime { - self.accumulatedTime += Date().timeIntervalSince(startTime) - self.running = false - } - } - - func resume() { - self.startTime = Date() - self.running = true - } - - func reset() { - self.accumulatedTime = 0 - self.startTime = nil - self.running = false - } - - func finish() -> [String:String] { - if self.startTime != nil { - self.pause() - - var targetMeasurementDict = self.defaultDict - targetMeasurementDict["TaskTime"] = String(describing: self.accumulatedTime) - - self.reset() - return targetMeasurementDict - } else { - return defaultDict - } - - } -} diff --git a/iOS App/ARPen/SettingsViewControllers/SettingsTableViewController.swift b/iOS App/ARPen/SettingsViewControllers/SettingsTableViewController.swift index e343836..be9bbda 100644 --- a/iOS App/ARPen/SettingsViewControllers/SettingsTableViewController.swift +++ b/iOS App/ARPen/SettingsViewControllers/SettingsTableViewController.swift @@ -71,10 +71,10 @@ class SettingsTableViewController: UITableViewController, UITextFieldDelegate { func setClearSceneButtonLabel() { if self.scene.drawingNode.childNodes.count > 0 { - self.clearSceneButton.setTitle("Clear Scene", for: UIControl.State.normal) + self.clearSceneButton.setTitle("Clear Scene", for: UIControlState.normal) self.clearSceneButton.isEnabled = true } else { - self.clearSceneButton.setTitle("No objects in the scene", for: UIControl.State.normal) + self.clearSceneButton.setTitle("No objects in the scene", for: UIControlState.normal) self.clearSceneButton.isEnabled = false } } @@ -195,10 +195,5 @@ class SettingsTableViewController: UITableViewController, UITextFieldDelegate { destinationVC.userStudyRecordManager = self.userStudyRecordManager } } - @IBAction func visitOpenCascadeWebsiteButtonPressed(_ sender: Any) { - if let url = URL(string: "https://www.opencascade.com/") { - UIApplication.shared.open(url, options: [:]) - } - } } diff --git a/iOS App/ARPen/SettingsViewControllers/UserStudyIDListTableViewController.swift b/iOS App/ARPen/SettingsViewControllers/UserStudyIDListTableViewController.swift index 861c089..dff448a 100644 --- a/iOS App/ARPen/SettingsViewControllers/UserStudyIDListTableViewController.swift +++ b/iOS App/ARPen/SettingsViewControllers/UserStudyIDListTableViewController.swift @@ -53,14 +53,14 @@ class UserStudyIDListTableViewController: UITableViewController { return cell } - override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle { + override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle { //During editing, the delete button should be displayed for each row return .delete } // Override to support editing the table view. - override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { + override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { // Delete the row from the data source let removedKey = self.userStudyKeys.remove(at: indexPath.row) diff --git a/iOS App/ARPen/SettingsViewControllers/UserStudyRecordsListTableViewController.swift b/iOS App/ARPen/SettingsViewControllers/UserStudyRecordsListTableViewController.swift index 92c6397..b5a534c 100644 --- a/iOS App/ARPen/SettingsViewControllers/UserStudyRecordsListTableViewController.swift +++ b/iOS App/ARPen/SettingsViewControllers/UserStudyRecordsListTableViewController.swift @@ -79,13 +79,13 @@ class UserStudyRecordsListTableViewController: UITableViewController, UINavigati } - override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle { + override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle { //During editing, the delete button should be displayed for each row return .delete } // Override to support editing the table view. - override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { + override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { // Delete the row from the data source & update the userStudyRecordsManager self.userStudyRecordsManager.deleteRecord(atPosition: indexPath.row, forID: self.userID) diff --git a/iOS App/ARPen/SnapshotAnchor.swift b/iOS App/ARPen/SnapshotAnchor.swift deleted file mode 100644 index 0403737..0000000 --- a/iOS App/ARPen/SnapshotAnchor.swift +++ /dev/null @@ -1,66 +0,0 @@ -/* - Copyright © 2018 Apple Inc. - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Abstract: - A custom anchor for saving a snapshot image in an ARWorldMap. - */ - -import ARKit - -/// - Tag: SnapshotAnchor -class SnapshotAnchor: ARAnchor { - - let imageData: Data - - convenience init?(capturing view: ARSCNView) { - guard let frame = view.session.currentFrame - else { return nil } - - let image = CIImage(cvPixelBuffer: frame.capturedImage) - let orientation = CGImagePropertyOrientation(cameraOrientation: (UIDevice.current.orientation == UIDeviceOrientation.landscapeLeft) ? UIDeviceOrientation.landscapeLeft : UIDeviceOrientation.landscapeRight) - - let context = CIContext(options: [CIContextOption.useSoftwareRenderer: false]) - guard let data = context.jpegRepresentation(of: image.oriented(orientation), - colorSpace: CGColorSpaceCreateDeviceRGB(), - options: [kCGImageDestinationLossyCompressionQuality as CIImageRepresentationOption: 0.7]) - else { return nil } - - self.init(imageData: data, transform: frame.camera.transform) - } - - init(imageData: Data, transform: float4x4) { - self.imageData = imageData - super.init(name: "snapshot", transform: transform) - } - - required init(anchor: ARAnchor) { - self.imageData = (anchor as! SnapshotAnchor).imageData - super.init(anchor: anchor) - } - - override class var supportsSecureCoding: Bool { - return true - } - - required init?(coder aDecoder: NSCoder) { - if let snapshot = aDecoder.decodeObject(forKey: "snapshot") as? Data { - self.imageData = snapshot - } else { - return nil - } - - super.init(coder: aDecoder) - } - - override func encode(with aCoder: NSCoder) { - super.encode(with: aCoder) - aCoder.encode(imageData, forKey: "snapshot") - } - -} diff --git a/iOS App/ARPen/UserStudyData/UserStudyRecordManager.swift b/iOS App/ARPen/UserStudyData/UserStudyRecordManager.swift index a500490..b410731 100644 --- a/iOS App/ARPen/UserStudyData/UserStudyRecordManager.swift +++ b/iOS App/ARPen/UserStudyData/UserStudyRecordManager.swift @@ -28,6 +28,11 @@ class UserStudyRecordManager : NSObject{ super.init() //set previous user study data, if a matching plist file exists in the home directory of the device userStudyData = loadFromFile() ?? [:] + + self.currentActiveUserID = -1 + if let currentActiveUserID = currentActiveUserID, self.userStudyData[currentActiveUserID] == nil { + self.userStudyData[currentActiveUserID] = [] + } } //accessing userStudyData diff --git a/iOS App/ARPen/UserStudySetup/ARPenBoxNode.swift b/iOS App/ARPen/UserStudySetup/ARPenBoxNode.swift index 365adfa..62168b3 100644 --- a/iOS App/ARPen/UserStudySetup/ARPenBoxNode.swift +++ b/iOS App/ARPen/UserStudySetup/ARPenBoxNode.swift @@ -36,8 +36,6 @@ class ARPenBoxNode : ARPenStudyNode { self.setCorners() } - // the following property is needed since initWithCoder is overwritten in this class. Since no decoding happens in the function and the decoding is passed on to the superclass, this class supports secure coding as well. - override public class var supportsSecureCoding: Bool { return true } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) diff --git a/iOS App/ARPen/UserStudySetup/ARPenDropTargetNode.swift b/iOS App/ARPen/UserStudySetup/ARPenDropTargetNode.swift index de95af1..9005459 100644 --- a/iOS App/ARPen/UserStudySetup/ARPenDropTargetNode.swift +++ b/iOS App/ARPen/UserStudySetup/ARPenDropTargetNode.swift @@ -81,8 +81,6 @@ class ARPenDropTargetNode : SCNNode { self.setCorners() } - // the following property is needed since initWithCoder is overwritten in this class. Since no decoding happens in the function and the decoding is passed on to the superclass, this class supports secure coding as well. - override public class var supportsSecureCoding: Bool { return true } required init?(coder aDecoder: NSCoder) { let thePosition = SCNVector3Make(0, 0, 0) self.originalPosition = thePosition diff --git a/iOS App/ARPen/UserStudySetup/ARPenGridSceneConstructor.swift b/iOS App/ARPen/UserStudySetup/ARPenGridSceneConstructor.swift index c1b17c3..22ea7a0 100644 --- a/iOS App/ARPen/UserStudySetup/ARPenGridSceneConstructor.swift +++ b/iOS App/ARPen/UserStudySetup/ARPenGridSceneConstructor.swift @@ -45,7 +45,7 @@ struct ARPenGridSceneConstructor : ARPenSceneConstructor { for _ in 0...cubesPerDimension { - let dimensionOfBox = 0.03 //+ Double(Int.random(in: 0 ... 1)) * 0.01 + let dimensionOfBox = 0.03 let range = (-0.025, 0.025) let randomDoubleForX = drand48() diff --git a/iOS App/ARPen/UserStudySetup/ARPenStudyNode.swift b/iOS App/ARPen/UserStudySetup/ARPenStudyNode.swift index 78264fe..ccb5de3 100644 --- a/iOS App/ARPen/UserStudySetup/ARPenStudyNode.swift +++ b/iOS App/ARPen/UserStudySetup/ARPenStudyNode.swift @@ -18,7 +18,7 @@ class ARPenStudyNode : SCNNode { var highlighted : Bool = false { didSet { if highlighted { - self.geometry?.firstMaterial?.emission.intensity = 0.5 + self.geometry?.firstMaterial?.emission.intensity = 1.0 } else { self.geometry?.firstMaterial?.emission.intensity = 0.0 } @@ -28,8 +28,8 @@ class ARPenStudyNode : SCNNode { var isActiveTarget = false { didSet { if self.isActiveTarget { - self.geometry?.firstMaterial?.diffuse.contents = UIColor.orange - self.geometry?.firstMaterial?.emission.contents = UIColor.orange + self.geometry?.firstMaterial?.diffuse.contents = UIColor.green + self.geometry?.firstMaterial?.emission.contents = UIColor.yellow } else { self.geometry?.firstMaterial?.diffuse.contents = UIColor.white self.geometry?.firstMaterial?.emission.contents = UIColor.white @@ -47,8 +47,6 @@ class ARPenStudyNode : SCNNode { self.position = thePosition } - // the following property is needed since initWithCoder is overwritten in this class. Since no decoding happens in the function and the decoding is passed on to the superclass, this class supports secure coding as well. - override public class var supportsSecureCoding: Bool { return true } required init?(coder aDecoder: NSCoder) { self.dimension = 0.0 let thePosition = SCNVector3Make(0, 0, 0) @@ -81,20 +79,4 @@ class ARPenStudyNode : SCNNode { func isPointInside(point : SCNVector3) -> Bool { return false } - - func setShaderModifier(shaderModifiers : [SCNShaderModifierEntryPoint : String]) { - self.geometry?.shaderModifiers = shaderModifiers - - self.childNodes.forEach({ - $0.geometry?.shaderModifiers = shaderModifiers - }) - } - - func setShaderArgument(name : String, value : Float) { - self.geometry?.firstMaterial?.setValue(value, forKey: name) - - self.childNodes.forEach({ - $0.geometry?.firstMaterial?.setValue(value, forKey: name) - }) - } } diff --git a/iOS App/ARPen/UserStudySetup/ARPenWireBoxNode.swift b/iOS App/ARPen/UserStudySetup/ARPenWireBoxNode.swift index 138587c..c279d91 100644 --- a/iOS App/ARPen/UserStudySetup/ARPenWireBoxNode.swift +++ b/iOS App/ARPen/UserStudySetup/ARPenWireBoxNode.swift @@ -14,7 +14,7 @@ class ARPenWireBoxNode : ARPenStudyNode { didSet { if highlighted { self.childNodes.forEach({ - $0.geometry?.firstMaterial?.emission.intensity = 0.5 + $0.geometry?.firstMaterial?.emission.intensity = 1.0 }) } else { self.childNodes.forEach({ @@ -28,8 +28,8 @@ class ARPenWireBoxNode : ARPenStudyNode { didSet { if self.isActiveTarget { self.childNodes.forEach({ - $0.geometry?.firstMaterial?.diffuse.contents = UIColor.orange - $0.geometry?.firstMaterial?.emission.contents = UIColor.orange + $0.geometry?.firstMaterial?.diffuse.contents = UIColor.green + $0.geometry?.firstMaterial?.emission.contents = UIColor.yellow }) } else { self.childNodes.forEach({ @@ -57,15 +57,13 @@ class ARPenWireBoxNode : ARPenStudyNode { // create invisible bounding geometry to enable ray casting let boundingBoxGeometry = SCNBox.init(width: CGFloat(self.dimension), height: CGFloat(self.dimension), length: CGFloat(self.dimension), chamferRadius: 0.0) self.geometry = boundingBoxGeometry - self.name = "BoundingBox \(thePosition.x), \(thePosition.y), \(thePosition.z)" + self.name = "\(thePosition.x), \(thePosition.y), \(thePosition.z)" self.geometry?.firstMaterial?.diffuse.contents = UIColor.red self.geometry?.firstMaterial?.transparency = 0.0 self.buildWireFrame() } - // the following property is needed since initWithCoder is overwritten in this class. Since no decoding happens in the function and the decoding is passed on to the superclass, this class supports secure coding as well. - override public class var supportsSecureCoding: Bool { return true } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) @@ -98,7 +96,6 @@ class ARPenWireBoxNode : ARPenStudyNode { let wire = SCNNode() wire.buildLineInTwoPointsWithRotation(from: info.0 - self.position, to: info.1 - self.position, radius: 0.001, color: UIColor.white) wire.geometry?.firstMaterial?.emission.contents = UIColor.white - wire.geometry?.firstMaterial?.emission.intensity = 0.0 self.addChildNode(wire) } } @@ -159,16 +156,4 @@ class ARPenWireBoxNode : ARPenStudyNode { self.corners.rbh = SCNVector3Make(thePosition.x + halfDimension, thePosition.y + halfDimension, thePosition.z - halfDimension) self.corners.rfh = SCNVector3Make(thePosition.x + halfDimension, thePosition.y + halfDimension, thePosition.z + halfDimension) } - - override func setShaderModifier(shaderModifiers : [SCNShaderModifierEntryPoint : String]) { - self.childNodes.forEach({ - $0.geometry?.shaderModifiers = shaderModifiers - }) - } - - override func setShaderArgument(name : String, value : Float) { - self.childNodes.forEach({ - $0.geometry?.firstMaterial?.setValue(value, forKey: name) - }) - } } diff --git a/iOS App/ARPen/ViewController.swift b/iOS App/ARPen/ViewController.swift index 80c52b2..47c56b2 100644 --- a/iOS App/ARPen/ViewController.swift +++ b/iOS App/ARPen/ViewController.swift @@ -9,7 +9,6 @@ import UIKit import SceneKit import ARKit -import MultipeerConnectivity /** The "Main" ViewController. This ViewController holds the instance of the PluginManager. @@ -30,28 +29,6 @@ class ViewController: UIViewController, ARSCNViewDelegate, PluginManagerDelegate @IBOutlet weak var pluginInstructionsLookupButton: UIButton! @IBOutlet weak var settingsButton: UIButton! @IBOutlet weak var undoButton: UIButton! - @IBOutlet weak var viewForCustomPluginView: UIView! - - // Persistence: Saving and loading current model - @IBOutlet weak var saveModelButton: UIButton! - @IBOutlet weak var loadModelButton: UIButton! - @IBOutlet weak var shareModelButton: UIButton! - - @IBOutlet weak var snapshotThumbnail: UIImageView! // Screenshot thumbnail to help the user find feature points in the World - @IBOutlet weak var statusLabel: UILabel! - - // This ARAnchor acts as the point of reference for all models when storing/loading - var persistenceSavePointAnchor: ARAnchor? - var persistenceSavePointAnchorName: String = "persistenceSavePointAnchor" - - // This ARAnchor acts as the point of reference for all models when sharing - var sharePointAnchor: ARAnchor? - var sharePointAnchorName: String = "sharePointAnchor" - - var saveIsSuccessful: Bool = false - - var storedNode: SCNReferenceNode? = nil // A reference node used to pre-load the models and render later - var sharedNode: SCNNode? = nil let menuButtonHeight = 70 let menuButtonPadding = 5 @@ -63,27 +40,28 @@ class ViewController: UIViewController, ARSCNViewDelegate, PluginManagerDelegate */ var pluginManager: PluginManager! - let userStudyRecordManager = UserStudyRecordManager() // Manager for storing data from user studies + //Manager for user study data + let userStudyRecordManager = UserStudyRecordManager() - var multipeerSession: MultipeerSession! - //A standard viewDidLoad + /** + A quite standard viewDidLoad + */ override func viewDidLoad() { super.viewDidLoad() - - // Make the corners of UI buttons rounded - self.makeRoundedCorners(button: self.pluginInstructionsLookupButton) - self.makeRoundedCorners(button: self.settingsButton) - self.makeRoundedCorners(button: self.undoButton) - self.makeRoundedCorners(button: self.saveModelButton) - self.makeRoundedCorners(button: self.loadModelButton) - self.makeRoundedCorners(button: self.shareModelButton) + + self.pluginInstructionsLookupButton.layer.masksToBounds = true + self.pluginInstructionsLookupButton.layer.cornerRadius = self.pluginInstructionsLookupButton.frame.width/2 + + self.settingsButton.layer.masksToBounds = true + self.settingsButton.layer.cornerRadius = self.settingsButton.frame.width/2 + + self.undoButton.layer.masksToBounds = true + self.undoButton.layer.cornerRadius = self.undoButton.frame.width/2 self.undoButton.isHidden = false self.undoButton.isEnabled = true - self.shareModelButton.isHidden = true - // Create a new scene let scene = PenScene(named: "art.scnassets/ship.scn")! scene.markerBox = MarkerBox() @@ -91,8 +69,9 @@ class ViewController: UIViewController, ARSCNViewDelegate, PluginManagerDelegate self.pluginManager = PluginManager(scene: scene) self.pluginManager.delegate = self - self.arSceneView.session.delegate = self.pluginManager.arManager self.arSceneView.delegate = self + self.arSceneView.session.delegate = self.pluginManager.arManager + self.arSceneView.autoenablesDefaultLighting = true self.arSceneView.pointOfView?.name = "iDevice Camera" @@ -100,50 +79,97 @@ class ViewController: UIViewController, ARSCNViewDelegate, PluginManagerDelegate // Set the scene to the view arSceneView.scene = scene - // Setup tap gesture recognizer for plugin instructions + // Setup tap gesture recognizer for imageForPluginInstructions let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.imageForPluginInstructionsTapped(_:))) self.imageForPluginInstructions.isUserInteractionEnabled = true self.imageForPluginInstructions.addGestureRecognizer(tapGestureRecognizer) - // Hide plugin instructions + // Hide the imageForPluginInstructions self.imageForPluginInstructions.isHidden = true - //self.displayPluginInstructions(forPluginID: currentActivePluginID) + self.displayPluginInstructions(forPluginID: currentActivePluginID) - // Set the user study record manager reference in the app delegate (for saving state when leaving the app) + // set user study record manager reference in the app delegate (for saving state when leaving the app) if let appDelegate = UIApplication.shared.delegate as? AppDelegate { appDelegate.userStudyRecordManager = self.userStudyRecordManager } else { print("Record manager was not set up in App Delegate") } - // Read in any already saved map to see if we can load one - if mapDataFromFile != nil { - self.loadModelButton.isHidden = false - } - // Observe camera's tracking state and session information - NotificationCenter.default.addObserver(self, selector: #selector(handleStateChange(_:)), name: Notification.Name.cameraDidChangeTrackingState, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(handleStateChange(_:)), name: Notification.Name.sessionDidUpdate, object: nil) - // Remove snapshot thumbnail when model has been loaded - NotificationCenter.default.addObserver(self, selector: #selector(removeSnapshotThumbnail(_:)), name: Notification.Name.virtualObjectDidRenderAtAnchor, object: nil) - // Enable host-guest sharing to share ARWorldMap - multipeerSession = MultipeerSession(receivedDataHandler: receivedData) + guard let referenceImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources", bundle: nil) else { + fatalError("Missing expected asset catalog resources.") + } + + let configuration = ARWorldTrackingConfiguration() + configuration.detectionImages = referenceImages + arSceneView.session.run(configuration) + } + // MARK: - ARSCNViewDelegate (Image detection results) + /// - Tag: ARImageAnchor-Visualizing + func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { + guard let imageAnchor = anchor as? ARImageAnchor else { return } + let referenceImage = imageAnchor.referenceImage + + self.arSceneView.session.setWorldOrigin(relativeTransform: anchor.transform) + + + // Create a plane to visualize the initial position of the detected image. + let plane = SCNPlane(width: referenceImage.physicalSize.width, + height: referenceImage.physicalSize.height) + let planeNode = SCNNode(geometry: plane) + + planeNode.opacity = 0.5 + + + /* + `SCNPlane` is vertically oriented in its local coordinate space, but + `ARImageAnchor` assumes the image is horizontal in its local space, so + rotate the plane to match. + */ + planeNode.eulerAngles.x = -.pi / 2 + + /* + Image anchors are not tracked after initial detection, so create an + animation that limits the duration for which the plane visualization appears. + */ + //planeNode.runAction(self.imageHighlightAction) + + // Add the plane visualization to the scene. + node.addChildNode(planeNode) + node.name = "ARImage" + //print("placing ARImage at ", node.position) + + (arSceneView.scene as! PenScene).arAnchorImage = node + + + } + + var imageHighlightAction: SCNAction { + return .sequence([ + .wait(duration: 0.25), + .fadeOpacity(to: 0.85, duration: 0.25), + .fadeOpacity(to: 0.15, duration: 0.25), + .fadeOpacity(to: 0.85, duration: 0.25), +// .fadeOut(duration: 20), +// .removeFromParentNode() + ]) + } /** viewWillAppear. Init the ARSession */ override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - - // Create a session configuration - let configuration = ARWorldTrackingConfiguration() - - // Run the view's session - arSceneView.session.run(configuration) - +// +// // Create a session configuration +// let configuration = ARWorldTrackingConfiguration() +// +// // Run the view's session +// arSceneView.session.run(configuration) +// // Hide navigation bar self.navigationController?.setNavigationBarHidden(true, animated: true) } @@ -152,34 +178,14 @@ class ViewController: UIViewController, ARSCNViewDelegate, PluginManagerDelegate super.viewWillDisappear(animated) // Pause the view's session - arSceneView.session.pause() + // arSceneView.session.pause() // Show navigation bar self.navigationController?.setNavigationBarHidden(false, animated: true) } - // Prepare the SettingsViewController by passing the scene - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - guard let segueIdentifier = segue.identifier else { return } - - if segueIdentifier == "ShowSettingsSegue" { - let destinationVC = segue.destination as! UINavigationController - guard let destinationSettingsController = destinationVC.viewControllers.first as? SettingsTableViewController else { - return - - } - destinationSettingsController.scene = self.arSceneView.scene as? PenScene - //pass reference to the record manager (to show active user ID and export data) - destinationSettingsController.userStudyRecordManager = self.userStudyRecordManager - destinationSettingsController.bluetoothARPenConnected = self.bluetoothARPenConnected - } - - } - - // MARK: - Plugins - func setupPluginMenu(){ - // Define target height and width for the scrollview to hold all buttons + //define target height and width for the scrollview to hold all buttons let targetWidth = Int(self.pluginMenuScrollView.frame.width) let experimentalPluginLabelHeight: Int = 40 @@ -283,8 +289,6 @@ class ViewController: UIViewController, ARSCNViewDelegate, PluginManagerDelegate newActivePluginButton.layer.borderWidth = 1 if let currentActivePlugin = self.pluginManager.activePlugin { - //remove custom view elements from view - currentActivePlugin.customPluginUI?.removeFromSuperview() currentActivePlugin.deactivatePlugin() } //activate plugin in plugin manager and update currently active plugin property @@ -297,10 +301,6 @@ class ViewController: UIViewController, ARSCNViewDelegate, PluginManagerDelegate if let currentScene = self.pluginManager.arManager.scene { if !(newActivePlugin.needsBluetoothARPen && !self.bluetoothARPenConnected) { newActivePlugin.activatePlugin(withScene: currentScene, andView: self.arSceneView) - if let customPluginUI = newActivePlugin.customPluginUI { - customPluginUI.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: viewForCustomPluginView.frame.size) - viewForCustomPluginView.addSubview(customPluginUI) - } } } currentActivePluginID = pluginID @@ -340,9 +340,31 @@ class ViewController: UIViewController, ARSCNViewDelegate, PluginManagerDelegate self.displayPluginInstructions(forPluginID: self.currentActivePluginID) } - // MARK: - ARManager delegate + /** + Prepare the SettingsViewController by passing the scene + */ + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + guard let segueIdentifier = segue.identifier else { return } + + if segueIdentifier == "ShowSettingsSegue" { + let destinationVC = segue.destination as! UINavigationController + guard let destinationSettingsController = destinationVC.viewControllers.first as? SettingsTableViewController else { + return + + } + destinationSettingsController.scene = self.arSceneView.scene as! PenScene + //pass reference to the record manager (to show active user ID and export data) + destinationSettingsController.userStudyRecordManager = self.userStudyRecordManager + destinationSettingsController.bluetoothARPenConnected = self.bluetoothARPenConnected + } + + } + - // Callback from the ARManager + // Mark: - ARManager Delegate + /** + Callback from the ARManager + */ func arKitInitialiazed() { guard let arKitActivity = self.arKitActivity else { return @@ -399,403 +421,27 @@ class ViewController: UIViewController, ARSCNViewDelegate, PluginManagerDelegate //Software Pen Button Actions @IBAction func softwarePenButtonPressed(_ sender: Any) { - //next line is the direct way possible here, but we'll show the way how the button states can be send from everywhere in the map - //self.pluginManager.button(.Button1, pressed: true) - //sent notification of button press to the pluginManager - let buttonEventDict:[String: Any] = ["buttonPressed": Button.Button1, "buttonState" : true] - NotificationCenter.default.post(name: .softwarePenButtonEvent, object: nil, userInfo: buttonEventDict) + self.pluginManager.button(.Button1, pressed: true) } - @IBAction func softwarePenButtonReleased(_ sender: Any) { - //next line is the direct way possible here, but we'll show the way how the button states can be send from everywhere in the map - //self.pluginManager.button(.Button1, pressed: false) - //sent notification of button release to the pluginManager - let buttonEventDict:[String: Any] = ["buttonPressed": Button.Button1, "buttonState" : false] - NotificationCenter.default.post(name: .softwarePenButtonEvent, object: nil, userInfo: buttonEventDict) - } - - @IBAction func undoButtonPressed(_ sender: Any) { - self.pluginManager.undoPreviousStep() + self.pluginManager.button(.Button1, pressed: false) } - - // MARK: - ARSCNViewDelegate - - // Invoked when new anchors are added to the scene - func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { - guard let anchorName = anchor.name else { - return - } - - if (anchorName == persistenceSavePointAnchorName) { - // Save the reference to the virtual object anchor when the anchor is added from relocalizing - if persistenceSavePointAnchor == nil { - persistenceSavePointAnchor = anchor - } - - DispatchQueue.main.async { - self.storedNode = SCNReferenceNode(url: self.sceneSaveURL) // Fetch models saved earlier - self.storedNode!.load() - - let scene = self.arSceneView.scene as! PenScene - for child in self.storedNode!.childNodes { - scene.drawingNode.addChildNode(child) - } - } - } else if (anchorName == sharePointAnchorName) { - // Perform rendering operations asynchronously - DispatchQueue.main.async { - guard let sharedNode = self.sharedNode else { - return - } - - let scene = self.arSceneView.scene as! PenScene - scene.drawingNode.addChildNode(sharedNode) - print("Adding storedNode to sharePointAnchor") - } - } - else { - print("An unknown ARAnchor has been added!") - return - } + //Software Pen Button 3 Actions + @IBAction func softwarePenButton2Pressed(_ sender: Any) { + self.pluginManager.button(.Button2, pressed: true) } - - // MARK: - Persistence: Save and load ARWorldMap - - // Receives notification on when session or camera tracking state changes and updates label - @objc func handleStateChange(_ notification: Notification) { - guard let userInfo = notification.userInfo else { - print("notification.userInfo is empty") - return - } - switch notification.name { - case .sessionDidUpdate: - updateStatusLabel(for: userInfo["frame"] as! ARFrame, trackingState: userInfo["trackingState"] as! ARCamera.TrackingState) - - // Enable Save button only when the mapping status is good and - // drawingNode has at least one object - let frame = userInfo["frame"] as! ARFrame - switch frame.worldMappingStatus { - case .extending, .mapped: - let scene = self.arSceneView.scene as! PenScene - saveModelButton.isEnabled = scene.drawingNode.childNodes.count > 0 - default: - saveModelButton.isEnabled = false - } - break - case .cameraDidChangeTrackingState: - updateStatusLabel(for: userInfo["currentFrame"] as! ARFrame, trackingState: userInfo["trackingState"] as! ARCamera.TrackingState) - break - default: - print("Received unknown notification: \(notification.name)") - } + @IBAction func softwarePenButton2Released(_ sender: Any) { + self.pluginManager.button(.Button2, pressed: false) } - - // Setup ARAnchor that serves as the point of reference for all drawings - func setupPersistenceAnchor() { - // Remove existing anchor if it exists - if let existingPersistenceAnchor = persistenceSavePointAnchor { - self.arSceneView.session.remove(anchor: existingPersistenceAnchor) - } - - // Add ARAnchor for save point - persistenceSavePointAnchor = ARAnchor(name: persistenceSavePointAnchorName, transform: matrix_identity_float4x4) + //Software Pen Button 3 Actions + @IBAction func softwarePenButton3Pressed(_ sender: Any) { + self.pluginManager.button(.Button3, pressed: true) } - - // Create URL for storing WorldMap in a lazy manner - lazy var mapSaveURL: URL = { - do { - return try FileManager.default - .url(for: .documentDirectory, - in: .userDomainMask, - appropriateFor: nil, - create: true) - .appendingPathComponent("map.arexperience") - } catch { - fatalError("Can't get file save URL: \(error.localizedDescription)") - } - }() - - // Create URL for storing all models in the current AR scence in a lazy manner - lazy var sceneSaveURL: URL = { - do { - return try FileManager.default - .url(for: .documentDirectory, - in: .userDomainMask, - appropriateFor: nil, - create: true) - .appendingPathComponent("scene.scn") - } catch { - fatalError("Can't get scene save URL: \(error.localizedDescription)") - - } - }() - - // Save the world map and models - @IBAction func saveCurrentScene(_ sender: Any) { - self.setupPersistenceAnchor() - - self.arSceneView.session.getCurrentWorldMap { worldMap, error in - guard let map = worldMap - else { - self.showAlert(title: "Can't get current world map", message: error!.localizedDescription) - return - } - - // Add a snapshot image indicating where the map was captured. - guard let snapshotAnchor = SnapshotAnchor(capturing: self.arSceneView) - else { fatalError("Can't take snapshot") } - map.anchors.append(snapshotAnchor) - map.anchors.append(self.persistenceSavePointAnchor!) - - do { - let data = try NSKeyedArchiver.archivedData(withRootObject: map, requiringSecureCoding: true) - try data.write(to: self.mapSaveURL, options: [.atomic]) - - DispatchQueue.main.async { - self.loadModelButton.isHidden = false - self.loadModelButton.isEnabled = true - - // Save the current PenScene to sceneSaveURL - let scene = self.arSceneView.scene as! PenScene - let savedNode = SCNReferenceNode(url: self.sceneSaveURL) - var nodesCreatedWithOpenCascade: [SCNNode] = [] - - if savedNode!.isLoaded == false { - print("No prior save found, saving current PenScene.") - scene.pencilPoint.removeFromParentNode() // Remove pencilPoint before saving - - // Remove all geometries created via Open Cascade - scene.drawingNode.childNodes(passingTest: { (node, stop) -> Bool in - let geometryType = type(of: node) - - // If the geometry created by Open Cascade, remove before sharing (but store them locally for retrieval). - if ((geometryType == ARPSphere.self) || (geometryType == ARPGeomNode.self) || (geometryType == ARPRevolution.self) || - (geometryType == ARPBox.self) || (geometryType == ARPNode.self) || (geometryType == ARPSweep.self) || - (geometryType == ARPCylinder.self) || (geometryType == ARPLoft.self) || (geometryType == ARPPath.self) || - (geometryType == ARPBoolNode.self) || (geometryType == ARPPathNode.self)) { - - nodesCreatedWithOpenCascade.append(node) - node.removeFromParentNode() - return false - } else { - return true - } - }) - - if scene.write(to: self.sceneSaveURL, options: nil, delegate: nil, progressHandler: nil) { - // Handle save if needed - scene.reinitializePencilPoint() - nodesCreatedWithOpenCascade.forEach({ scene.drawingNode.addChildNode($0) }) - - self.saveIsSuccessful = true - - // Reset the value after two seconds so that the label disappears - _ = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { timer in - self.saveIsSuccessful = false - } // Todo: See if there is a more elegant way to do this - } else { - scene.reinitializePencilPoint() - nodesCreatedWithOpenCascade.forEach({ scene.drawingNode.addChildNode($0) }) - - return - } - } - } - self.statusLabel.text = "Write successful!" - } catch { - fatalError("Can't save map: \(error.localizedDescription)") - } - } - } - - - // Called opportunistically to verify that map data can be loaded from filesystem - var mapDataFromFile: Data? { - return try? Data(contentsOf: mapSaveURL) - } - - // Called opportunistically to verify that scene data can be loaded from filesystem - var sceneDataFromFile: Data? { - return try? Data(contentsOf: sceneSaveURL) + @IBAction func softwarePenButton3Released(_ sender: Any) { + self.pluginManager.button(.Button3, pressed: false) } - // Load the world map and models - @IBAction func loadScene(_ sender: Any) { - /// - Tag: ReadWorldMap - let worldMap: ARWorldMap = { - guard let data = mapDataFromFile - else { fatalError("Map data should already be verified to exist before Load button is enabled.") } - do { - guard let worldMap = try NSKeyedUnarchiver.unarchivedObject(ofClass: ARWorldMap.self, from: data) - else { fatalError("No ARWorldMap in archive.") } - return worldMap - } catch { - fatalError("Can't unarchive ARWorldMap from file data: \(error)") - } - }() - - // Display the snapshot image stored in the world map to aid user in relocalizing. - if let snapshotData = worldMap.snapshotAnchor?.imageData, - let snapshot = UIImage(data: snapshotData) { - snapshotThumbnail.isHidden = false - snapshotThumbnail.image = snapshot - - } else { - print("No snapshot image in world map") - } - - // Remove the snapshot anchor from the world map since we do not need it in the scene. - worldMap.anchors.removeAll(where: { $0 is SnapshotAnchor }) - - let configuration = ARWorldTrackingConfiguration() - configuration.initialWorldMap = worldMap - self.arSceneView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors]) - - isRelocalizingMap = true - persistenceSavePointAnchor = nil - - self.setupPersistenceAnchor() - self.arSceneView.session.add(anchor: persistenceSavePointAnchor!) // Add anchor to the current scene - } - - var isRelocalizingMap = false - - // Provide feedback and instructions to the user about saving and loading the map and models respectively - // TODO: This needs to be updated for sharing - func updateStatusLabel(for frame: ARFrame, trackingState: ARCamera.TrackingState) { - var message: String = "" - self.snapshotThumbnail.isHidden = true - - switch (trackingState) { - case (.limited(.relocalizing)) where isRelocalizingMap: - message = "Move your device to the location shown in the image." - self.snapshotThumbnail.isHidden = false - case .normal, .notAvailable: - if !multipeerSession.connectedPeers.isEmpty && mapProvider == nil { - let peerNames = multipeerSession.connectedPeers.map({ $0.displayName }).joined(separator: ", ") - message = "Connected with \(peerNames)." - - let scene = self.arSceneView.scene as! PenScene - if (scene.drawingNode.childNodes.count > 0) { - self.shareModelButton.isHidden = false - } - } - else if (self.saveIsSuccessful) { - message = "Save successful" - } - else { - message = "" - } - case .limited(.initializing) where mapProvider != nil, - .limited(.relocalizing) where mapProvider != nil: - message = "Received map from \(mapProvider!.displayName)." - default: - message = "" - } - - statusLabel.text = message - } - - // Remove snapshot thumbnail - @objc func removeSnapshotThumbnail(_ notification: Notification) { - self.snapshotThumbnail.isHidden = true - } - - - // MARK: - Share ARWorldMap with other users - - func setupAndShareAnchor() { - // Place an anchor for a virtual character. The model appears in renderer(_:didAdd:for:). - let transform = self.arSceneView.scene.rootNode.simdTransform - let anchor = ARAnchor(name: sharePointAnchorName, transform: transform) - self.arSceneView.session.add(anchor: anchor) - - // Send the anchor info to peers, so they can place the same content. - guard let data = try? NSKeyedArchiver.archivedData(withRootObject: anchor, requiringSecureCoding: true) - else { fatalError("can't encode anchor") } - self.multipeerSession.sendToAllPeers(data) - } - - - @IBAction func shareModelButtonPressed(_ sender: Any) { - self.arSceneView.session.getCurrentWorldMap { worldMap, error in - guard let map = worldMap - else { - print("Error: \(error!.localizedDescription)") - return - } - - DispatchQueue.main.async { - let scene = self.arSceneView.scene as! PenScene - scene.pencilPoint.removeFromParentNode() // Remove pencilPoint before sharing - var nodesCreatedWithOpenCascade: [SCNNode] = [] - - // Remove all geometries created via Open Cascade - scene.drawingNode.childNodes(passingTest: { (node, stop) -> Bool in - let geometryType = type(of: node) - print("geometryType:\(geometryType)") - - if ((geometryType == ARPSphere.self) || (geometryType == ARPGeomNode.self) || (geometryType == ARPRevolution.self) || - (geometryType == ARPBox.self) || (geometryType == ARPNode.self) || (geometryType == ARPSweep.self) || - (geometryType == ARPCylinder.self) || (geometryType == ARPLoft.self) || (geometryType == ARPPath.self) || - (geometryType == ARPBoolNode.self) || (geometryType == ARPPathNode.self)) { - print("Detected geometry created via Open Cascade.\n") - - nodesCreatedWithOpenCascade.append(node) - node.removeFromParentNode() - return false - } else { - print("Detected geometry *not* created via Open Cascade.\n") - return true - } - }) - - // Share content first so that the content is not duplicated for this device - guard let sceneData = try? NSKeyedArchiver.archivedData(withRootObject: scene.drawingNode, requiringSecureCoding: true) - else { fatalError("can't encode scene data") } - self.multipeerSession.sendToAllPeers(sceneData) - scene.reinitializePencilPoint() - nodesCreatedWithOpenCascade.forEach({ scene.drawingNode.addChildNode($0) }) - - self.setupAndShareAnchor() - - // Send the WorldMap to all peers - guard let data = try? NSKeyedArchiver.archivedData(withRootObject: map, requiringSecureCoding: true) - else { fatalError("can't encode map") } - self.multipeerSession.sendToAllPeers(data) - } - } - } - - var mapProvider: MCPeerID? - - /// - Tag: ReceiveData - func receivedData(_ data: Data, from peer: MCPeerID) { - - if let unarchivedData = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data){ - - if unarchivedData is ARWorldMap, let worldMap = unarchivedData as? ARWorldMap { - // Run the session with the received world map. - let configuration = ARWorldTrackingConfiguration() - configuration.planeDetection = .horizontal - configuration.initialWorldMap = worldMap - self.arSceneView.session.run(configuration, options: [.resetTracking]) - - // Remember who provided the map for showing UI feedback. - mapProvider = peer - } else if unarchivedData is ARAnchor, let anchor = unarchivedData as? ARAnchor { - self.arSceneView.session.add(anchor: anchor) - print("added the anchor (\(anchor.name ?? "(can't parse)")) received from peer: \(peer)") - } else if unarchivedData is SCNNode, let sceneData = unarchivedData as? SCNNode { -// scene.write(to: self.sceneStoreURL, options: nil, delegate: nil, progressHandler: nil) - self.sharedNode = sceneData - print("saved scene data into sharedNode") - } - else { - print("Unknown Data Recieved From = \(peer)") - } - } else { - print("can't decode data received from \(peer)") - } + @IBAction func undoButtonPressed(_ sender: Any) { + self.pluginManager.undoPreviousStep() } } diff --git a/iOS App/Open CASCADE/occt.framework.z01 b/iOS App/Open CASCADE/occt.framework.z01 deleted file mode 100755 index 4a2fa8d..0000000 Binary files a/iOS App/Open CASCADE/occt.framework.z01 and /dev/null differ diff --git a/iOS App/Open CASCADE/occt.framework.z02 b/iOS App/Open CASCADE/occt.framework.z02 deleted file mode 100755 index 15d3b4e..0000000 Binary files a/iOS App/Open CASCADE/occt.framework.z02 and /dev/null differ diff --git a/iOS App/Open CASCADE/occt.framework.z03 b/iOS App/Open CASCADE/occt.framework.z03 deleted file mode 100755 index 947350a..0000000 Binary files a/iOS App/Open CASCADE/occt.framework.z03 and /dev/null differ diff --git a/iOS App/Open CASCADE/occt.framework.zip b/iOS App/Open CASCADE/occt.framework.zip deleted file mode 100755 index aef71c0..0000000 Binary files a/iOS App/Open CASCADE/occt.framework.zip and /dev/null differ