Swift ARKit-如何使用SceneKit选择特定节点
我是arkit框架的新手,我有一个小问题。我有这个项目,我可以使用object.scn,把它放在一个平面上,移动,旋转和缩放。我的问题是:如果我放置一个物体,我可以处理它,但当我放置第二个物体时,就不可能移动第一个物体。所以现在我只能在最后一个节点上做一些事情,但我想和所有节点一起工作 代码视图控制器Swift ARKit-如何使用SceneKit选择特定节点,swift,scenekit,arkit,Swift,Scenekit,Arkit,我是arkit框架的新手,我有一个小问题。我有这个项目,我可以使用object.scn,把它放在一个平面上,移动,旋转和缩放。我的问题是:如果我放置一个物体,我可以处理它,但当我放置第二个物体时,就不可能移动第一个物体。所以现在我只能在最后一个节点上做一些事情,但我想和所有节点一起工作 代码视图控制器 import UIKit import SceneKit import ARKit class ViewController: UIViewController, ARSCNViewDeleg
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
//=================== VARIABLES ========================//
//is used to take memory about the selection that user do
var selectedNode: SCNNode?
//collection of node for deleting function
var placedNodes = [SCNNode]()
var planeNodes = [SCNNode]()
//Point where is the last node
var lastObjectPlacedPoint: CGPoint?
@IBOutlet var sceneView: ARSCNView!
let configuration = ARWorldTrackingConfiguration()
//selections for the segmented control
enum ObjectPlacementMode {
case noselection, plane
}
//this variable of ObjectPlacementMode type, represents the 3 selections in the segmented control. Default is .freeform
var objectMode: ObjectPlacementMode = .noselection{
didSet{
reloadConfiguration(removeAnchor: false)
}
}
//variable to hide the plane
var showPlanOverlay = false{
didSet{
for node in planeNodes{
node.isHidden = !showPlanOverlay
}
}
}
//================== VIEW LIFECYCLE ======================//
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
sceneView.autoenablesDefaultLighting = true
registerGestureRecognizers()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
reloadConfiguration()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
// ================== PREPARE FOR SEGUE ===================//
//prepare for segue to pass the selected option to the optionController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showOptions" {
let optionsViewController = segue.destination as! OptionsContainerViewController
optionsViewController.delegate = self
}
}
// ================== IBACTION FUNCTION ===================//
//this update the variable whe you click
@IBAction func changeObjectMode(_ sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
objectMode = .noselection
case 1:
objectMode = .plane
default:
break
}
}
// ================== TOUCH FUNCTION =====================//
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//recall parent implementation
super.touchesBegan(touches, with: event)
//to verify that the user has chosen an object from the popover i check if the selectedNode has a value and if there is at least one finger touch
guard let node = selectedNode,
let touch = touches.first else {return}
switch objectMode {
//if the user select freeform, i pass the node to the method
case .noselection:
// let touchPoint = touch.location(in: sceneView)
break
case .plane:
let touchPoint = touch.location(in: sceneView)
addNode(node, toPlaneUsingPoint: touchPoint)
}
}
//when the user lifts the finger from the screen
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
lastObjectPlacedPoint = nil
}
// ===================== GESTURE RECOGNIZER WITH FUNCTION (MOVE, PINCH AND ROTATE) ======================= //
private func registerGestureRecognizers(){
let moveGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(move))
moveGestureRecognizer.minimumPressDuration = 0.1
self.sceneView.addGestureRecognizer(moveGestureRecognizer)
let pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(scale))
self.sceneView.addGestureRecognizer(pinchGestureRecognizer)
let rotationGestureRecognizer = UIRotationGestureRecognizer(target: self, action: #selector(rotate))
self.sceneView.addGestureRecognizer(rotationGestureRecognizer)
}
@objc func move(recognizer: UILongPressGestureRecognizer){
guard let scene = recognizer.view as? ARSCNView else {return}
let touch = recognizer.location(in: scene)
let results = sceneView.hitTest(touch, types: [.existingPlaneUsingExtent])
if let match = results.first {
let t = match.worldTransform
placedNodes.last?.position = SCNVector3(x: t.columns.3.x, y: t.columns.3.y, z: t.columns.3.z)
}
}
@objc func scale(sender: UIPinchGestureRecognizer) {
let sceneView = sender.view as! ARSCNView
let pinchLocation = sender.location(in: sceneView)
let hitTest = sceneView.hitTest(pinchLocation)
if !hitTest.isEmpty {
let results = hitTest.first!
let node = results.node
let pinchAction = SCNAction.scale(by: sender.scale, duration: 0)
print(sender.scale)
// node.runAction(pinchAction)
placedNodes.last?.runAction(pinchAction)
sender.scale = 1.0
}
}
@objc func rotate(sender: UIRotationGestureRecognizer) {
let sceneView = sender.view as! ARSCNView
let holdLocation = sender.location(in: sceneView)
let hitTest = sceneView.hitTest(holdLocation)
if !hitTest.isEmpty {
let result = hitTest.first!
if sender.state == .began {
let rotation = SCNAction.rotateBy(x: 0, y: CGFloat(360.degreesToRadians), z: 0, duration: 1)
// let forever = SCNAction.repeatForever(rotation)
// result.node.runAction(rotation)
placedNodes.last?.runAction(rotation)
} else if sender.state == .ended {
result.node.removeAllActions()
}
}
}
// ==================== DETECT PLANE, CREATE FLOOR AND ADD NODE TO PLAN DETECTED ========================= //
//with this method you can recognize image or plane, in order to add a node on it
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
if let planeAnchor = anchor as? ARPlaneAnchor{
nodeAdded(node, for: planeAnchor)
}
}
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor,
let planeNode = node.childNodes.first,
let plane = planeNode.geometry as? SCNPlane else{ return }
//update the dimension of the plane
planeNode.position = SCNVector3(planeAnchor.center.x, 0, planeAnchor.center.z)
plane.width = CGFloat(planeAnchor.extent.x)
plane.height = CGFloat(planeAnchor.extent.z)
}
func createFloor(planeAnchor: ARPlaneAnchor) -> SCNNode {
let node = SCNNode()
//here i'm creating a plane with size that match the dimension of the planeAnchor
let geometry = SCNPlane(width: CGFloat(planeAnchor.extent.x), height: CGFloat(planeAnchor.extent.z))
node.geometry = geometry
//Rotation of the plane 90 degree
node.eulerAngles.x = -Float.pi / 2
node.opacity = 0.25
return node
}
// ======================== ADD NODE FUNCTION ========================= //
//when you tap, will be created a clone node that will be added to the scene and in the list
func addNodeToSceneRoot(_ node:SCNNode){
let cloneNode = node.clone()
sceneView.scene.rootNode.addChildNode(cloneNode)
placedNodes.append(cloneNode)
}
//when you detect a plane, create a node. From now every other node will be children of this, not to the root node. Control that the object must be on a plane
func addNode(_ node:SCNNode, toPlaneUsingPoint point: CGPoint){
let results = sceneView.hitTest(point, types: [.existingPlaneUsingExtent])
if let match = results.first {
let t = match.worldTransform
node.position = SCNVector3(x: t.columns.3.x, y: t.columns.3.y, z: t.columns.3.z)
addNodeToSceneRoot(node)
lastObjectPlacedPoint = point
}
}
func nodeAdded(_ node: SCNNode, for anchor: ARPlaneAnchor){
let floor = createFloor(planeAnchor: anchor)
floor.isHidden = !showPlanOverlay
//add node (plane) to the scene, and add it to the array
node.addChildNode(floor)
planeNodes.append(floor)
}
// ======================== RELOAD CONFIGURATION ============================ //
//this method reload the session based on the value of the objectMode, with plane detection always active
func reloadConfiguration(removeAnchor: Bool = true){
configuration.planeDetection = [.horizontal, .vertical]
let options: ARSession.RunOptions
if removeAnchor {
options = [.removeExistingAnchors]
for node in planeNodes{
node.removeFromParentNode()
}
planeNodes.removeAll()
for node in placedNodes{
node.removeFromParentNode()
}
placedNodes.removeAll()
}else{
options = []
}
sceneView.session.run(configuration, options: options)
}
}
// =========================== EXTENSION ============================= //
//this list of method will be called whe you select an option in the Options menu
extension ViewController: OptionsViewControllerDelegate {
//this one is called whe the user has selected the shape, color, size
func objectSelected(node: SCNNode) {
dismiss(animated: true, completion: nil)
selectedNode = node
}
//this is called when the user tap on Enable/Disable Plane Visualization
func togglePlaneVisualization() {
dismiss(animated: true, completion: nil)
showPlanOverlay = !showPlanOverlay
}
//this is called when the user tap on Undo Last Object, and locate the last node, remove it from the scene and remove it from the collection
func undoLastObject() {
if let lastNode = placedNodes.last{
lastNode.removeFromParentNode()
placedNodes.removeLast()
}
}
//this is called when the user tap on Reset Scene
func resetScene() {
dismiss(animated: true, completion: nil)
reloadConfiguration()
}
}
extension Int {
var degreesToRadians: Double { return Double(self) * .pi/180}
}
导入UIKit
导入SceneKit
进口阿基特
类ViewController:UIViewController、ARSCNViewDelegate{
//=========================变量========================//
//用于获取有关用户所做选择的内存
变量selectedNode:SCNNode?
//用于删除函数的节点集合
变量placedNodes=[SCNNode]()
变量planeNodes=[SCNNode]()
//最后一个节点在哪里
var lastObjectPlacedPoint:CGPoint?
@IBVAR场景视图:ARSCNView!
let configuration=ARWorldTrackingConfiguration()
//分段控件的选择
枚举ObjectPlacementMode{
病例选择,平面
}
//此ObjectPlacementMode类型的变量表示分段控件中的3个选择。默认值为.freeform
var objectMode:ObjectPlacementMode=.noselection{
迪塞特{
重新加载配置(removeAnchor:false)
}
}
//变量来隐藏平面
var showPlanOverlay=false{
迪塞特{
对于平面节点中的节点{
node.ishiden=!showPlanOverlay
}
}
}
//=========================查看生命周期======================//
重写func viewDidLoad(){
super.viewDidLoad()
sceneView.delegate=self
sceneView.autoenablesDefaultLighting=true
registerGestureRecognizers()
}
覆盖函数视图将出现(uo动画:Bool){
超级。视图将显示(动画)
重新加载配置()
}
覆盖函数视图将消失(u动画:Bool){
超级。视图将消失(动画)
sceneView.session.pause()
}
//====================================为赛段做准备===================//
//准备将所选选项传递给optionController
覆盖功能准备(对于segue:UIStoryboardSegue,发送方:有吗?){
如果segue.identifier==“showOptions”{
让optionViewController=segue.destination为!optionContainerServiceController
选项ViewController.delegate=self
}
}
//============================iAction函数===================//
//这将在单击时更新变量
@iAction func changeObjectMode(\发送方:UISegmentedControl){
切换发送器。选择段索引{
案例0:
objectMode=.noselection
案例1:
objectMode=.plane
违约:
打破
}
}
//=========================触摸功能=====================//
覆盖func TouchesBegind(Touchs:Set,带有事件:UIEvent?){
//召回父实现
super.touchesbeated(touches,with:event)
//为了验证用户是否从popover中选择了对象,我检查selectedNode是否有值,以及是否至少有一次手指触摸
guard let node=selectedNode,
让touch=touch.first-else{return}
切换对象模式{
//如果用户选择freeform,我将节点传递给该方法
案例.选择:
//让接触点=接触位置(在:场景视图中)
打破
案件.飞机:
让接触点=接触位置(在:场景视图中)
addNode(节点,toPlaneUsingPoint:接触点)
}
}
//当用户从屏幕上抬起手指时
覆盖函数touchesend(touchs:Set,带有事件:UIEvent?){
super.touchesend(触摸,带有:事件)
lastObjectPlacedPoint=nil
}
//========================================================================================================================//
专用func注册表检测识别器(){
让moveGestureRecognizer=ui长按GestureRecognizer(目标:自我,操作:#选择器(移动))
MoveGestureRecognitor.minimumPressDuration=0.1
self.sceneView.addgestureignizer(移动gestureignizer)
让pinchGestureRecognizer=UIPinchGestureRecognizer(目标:自我,动作:#选择器(比例))
self.sceneView.addgestureignizer(pinchgestureignizer)
让rotationGestureRecognizer=UIRotationGestureRecognizer(目标:自我,动作:#选择器(旋转))
self.sceneView.addgestureignizer(旋转gestureignizer)
}
@objc func move(识别器:UILongPressGestureRecognitor){
guard let scene=recognizer.view as?ARSCNView else{return}
let touch=识别器。位置(在:场景中)
让结果=sceneView.hitTest(触摸,类型:[.existingPlaneUsingExtent])
如果让match=results.first{
设t=match.worldTransform
placedNodes.last?.position=SCInvector3(x:t.columns.3.x,y:t.columns.3.y,z:t.columns.3.z)
}
}
@objc func量表(发送方:UIPinchGestureRecognitor){
让sceneView=sender.view为!ARSCNView
让pinchLocation=sender.location(在:sceneView中)
让hitTest=sceneView.hitTest(pinchLocation)
如果!hitTest.isEmpty{
让结果=hitTest.first!
让node=results.node
让pinchAction=SCNAction.scale(by:sender.scale,持续时间:0)
打印(发送器刻度)
//node.runAction(pinchAction)
placedNodes.last?运行操作(pinchAction)
sender.scale=1.0
}
}
@objc func rotate(发送方:UIRotationGestureRecognitor){
让sceneView=sender.view为!ARSCNView
让holdLocation=sender.location(在:sceneView中)
让hitTest=sceneView.hitTest(holdLocation)
如果!hitTest.isEmpty{
让结果=hitTest