我是arkit框架的新手,我有一个小问题。我有一个可以在其中使用对象.scn的项目,将其放置在平面上,移动,旋转和缩放。我的问题是:如果放置一个对象,我可以处理它,但是当我放置第二个对象时,无法移动第一个对象。所以现在我只能在最后一个节点上执行操作,但是我想与所有这些一起工作。
代码ViewController
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}
}
谢谢大家可以帮助我。
最佳答案
您的手势功能正在修改placedNodes.last
而不是selectedNode
。
关于swift - ARKit-如何使用SceneKit选择特定的点头,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/59239015/