将 2d 虚拟图像分配给使用 bezierpath 绘制的 SCNShape

问题描述 投票:0回答:1

我有drawBtn在平面上绘制球体,然后我有lineNode,它根据用户相机不断更新线位置,以便他们知道要在哪里添加新球体来创建形状。

    class ViewController: UIViewController , ARSCNViewDelegate, ARCoachingOverlayViewDelegate, ARSessionDelegate{

    @IBOutlet weak var imageview: UIImageView!
    @IBOutlet weak var sceneView: ARSCNView!
    
    var nodes: [SCNNode] = []
    var linenodes : [SCNNode] = []
    var startingNode : SCNNode!
    var lineNode : LineNode!
    
    
    let coachingOverlay = ARCoachingOverlayView()
    var isEditingEnabled = true
    
    var session: ARSession {
        return sceneView.session
    }
    
    // MARK: - View Didload
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //Setup an AR SceneView Session
        sceneView.delegate = self
        sceneView.session.delegate = self
        let Config = ARWorldTrackingConfiguration()
        Config.planeDetection = [.horizontal, .vertical]
        sceneView.session.run(Config,  options: [.resetTracking, .removeExistingAnchors])

        
        let tapgesture = UITapGestureRecognizer(target: self , action: #selector(handleTap))
        imageview.addGestureRecognizer(tapgesture)
        imageview.isUserInteractionEnabled = true

    }

Draw btn 处理光线投射结果并将节点添加到节点数组中, // 标记:- 绘制球体以创建形状

    @IBAction func DrawBtn(_ sender: UIButton) {
        let screenPoint = CGPoint(x: sceneView.bounds.midX, y: sceneView.bounds.midY)
        guard let raycastQuery = sceneView.raycastQuery(from: screenPoint, allowing: .estimatedPlane, alignment: .any) else {return}
        let raycastResults = sceneView.session.raycast(raycastQuery)
        
        guard let firstResult = raycastResults.first  else {  return }
      
        
        let position = SCNVector3.positionFrom(matrix: firstResult.worldTransform)
        
        let sphere = SCNSphere(color: .systemBlue, radius: 0.01)
        let node = SCNNode(geometry: sphere)
        
        node.position = position
        
        let lastNode = nodes.last

        sceneView.scene.rootNode.addChildNode(node)
        
        // Add the Sphere to the list.
        nodes.append(node)
        
        // Setting our starting point for drawing a line in real time
        self.startingNode = nodes.last

        if lastNode != nil {
            // If there are 2 nodes or more
            
            if nodes.count >= 2 {
                // Create a node line between the nodes
                let LineBetweenNodes = LineNode(from: (lastNode?.position)!,   to: node.position, lineColor: UIColor.systemBlue)
            
                // Add the Node to the scene.
                sceneView.scene.rootNode.addChildNode(LineBetweenNodes)
               
                
            }
            
        }

        
    }

然后我们就有了用户可以分配给绘制路径的 imageView ,

    // MARK: - handle Tap on ImageView

    @objc func handleTap( _ sender : UITapGestureRecognizer){
        
        if startingNode === nodes.last {
            // Create a line segment from the last node to the first node
            if let firstNode = nodes.first {
                let LineBetweenNodes = LineNode(from: startingNode.position, to: firstNode.position, lineColor: UIColor.systemBlue)
                sceneView.scene.rootNode.addChildNode(LineBetweenNodes)
             
                //Add Image to the Closed Path
                AddImage()
            }
        }

    }

这就是我们遇到问题的地方。路径绘制正确,它正确显示了 3d 值,但没有以所需的形状显示图像。

    // MARK: - Add Image function

      func AddImage(){
          isEditingEnabled = false
          let path = UIBezierPath()
              
              // Iterate through the sphere nodes array
              for (index, nodes) in nodes.enumerated() {
                  let position = nodes.position
                  
                  if index == 0 {
                      // Move to the starting position
                      path.move(to: CGPoint(x: CGFloat(position.x), y: CGFloat(position.y)))
                  } else {
                      // Add a line segment to the next sphere node
                      path.addLine(to: CGPoint(x: CGFloat(position.x), y: CGFloat(position.y)))
                      
                  }
              }
              path.close()
          
      let shape = SCNShape(path: path, extrusionDepth: 0.01)
      shape.firstMaterial?.diffuse.contents = UIImage(named: "grass1")
      let shapeNode = SCNNode(geometry: shape)
          shapeNode.position.z = -0.2
          sceneView.scene.rootNode.addChildNode(shapeNode)
      }
    
    
    
    
    

    func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
        DispatchQueue.main.async { [self] in
            // get current hit position
            // and check if start-node is available
            if isEditingEnabled {
                
                guard let currentPosition = self.doHitTestOnExistingPlanes(),
                      let start = self.startingNode else {
                    return
                    
                }
                self.lineNode?.removeFromParentNode()
                self.lineNode = LineNode(from: start.position, to: currentPosition, lineColor: UIColor.systemBlue)
                self.sceneView.scene.rootNode.addChildNode(self.lineNode!)
            }
            else {
                self.lineNode?.removeFromParentNode()

            }
            
            
        }
    }
 
    func doHitTestOnExistingPlanes() -> SCNVector3? {
        
        let screenPoint = CGPoint(x: sceneView.bounds.midX, y: sceneView.bounds.midY)
        guard let raycastQuery = sceneView.raycastQuery(from: screenPoint, allowing: .estimatedPlane, alignment: .any) else {return nil }
        
        let raycastResults = sceneView.session.raycast(raycastQuery)
        
        guard let firstResult = raycastResults.first  else {  return nil }
        // get vector from transform
        let hitPos = SCNVector3.positionFrom(matrix: firstResult.worldTransform)
        return hitPos
        
        
        
    }
    
}

在节点之间添加线的额外类

    class LineNode: SCNNode{

   
    init(from vectorA: SCNVector3, to vectorB: SCNVector3, lineColor color: UIColor) {
        super.init()
        let height = self.distance(from: vectorA, to: vectorB)
        self.position = vectorA
        let nodeVector2 = SCNNode()
        nodeVector2.position = vectorB
        let nodeZAlign = SCNNode()
        nodeZAlign.eulerAngles.x = Float.pi/2
        let box = SCNBox(width: 0.003, height: height, length: 0.001, chamferRadius: 0)
        let material = SCNMaterial()
        material.diffuse.contents = color
        box.materials = [material]
        let nodeLine = SCNNode(geometry: box)
        nodeLine.position.y = Float(-height/2) + 0.001
        nodeZAlign.addChildNode(nodeLine)
        self.addChildNode(nodeZAlign)
        self.constraints = [SCNLookAtConstraint(target: nodeVector2)]
        
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        
    }
    
    func distance(from vectorA: SCNVector3, to vectorB: SCNVector3)-> CGFloat
    {
        return CGFloat(sqrt((vectorA.x - vectorB.x) * (vectorA.x - vectorB.x) +   (vectorA.y - vectorB.y) * (vectorA.y - vectorB.y) + (vectorA.z - vectorB.z) * (vectorA.z - vectorB.z)))
        
    }
}

我的代码中使用的扩展。

    extension SCNSphere {
        convenience init(color: UIColor, radius: CGFloat) {
            self.init(radius: radius)
            let material = SCNMaterial()
            material.diffuse.contents = color
            materials = [material]
            
        }
    
}

extension SCNVector3 {
    
    static func positionFrom(matrix: matrix_float4x4) -> SCNVector3 {
        let column = matrix.columns.3
        return SCNVector3(column.x, column.y, column.z)
    }
    
}

一种新方法: 如果我们为每个球体节点添加一个锚点,并将它们连接在一起,它就会给我们一个形状,但不会显示图像:

    if anchorsList.count >= 3  {
        
        // Create a custom geometry connecting the anchor positions
        var vertices = anchorsList.map { anchor in
            SCNVector3(anchor.transform.columns.3.x, anchor.transform.columns.3.y, anchor.transform.columns.3.z)
        }
        // Close the shape by repeating the first vertex at the end
        vertices.append(vertices[0])
        let source = SCNGeometrySource(vertices: vertices)
        var indices: [UInt32] = []

        // Create indices for shape fan
        for i in 1..<(vertices.count - 1) {
            indices.append(0)
            indices.append(UInt32(i))
            indices.append(UInt32(i + 1))
        }
        let element = SCNGeometryElement(indices: indices, primitiveType: .triangles)
        let geometry = SCNGeometry(sources: [source], elements: [element])
   
      
        
        // Add material to the geometry
        let material = SCNMaterial()
        let image = UIImage(named: "grass1")
        
        material.diffuse.contents = UIColor.red
            material.isDoubleSided = true
            geometry.materials = [material]
       

material.diffuse.contentsTransform = SCNMatrix4MakeScale(2, 2, 1)

            let node = SCNNode(geometry: geometry)
            sceneView.allowsCameraControl = true
              
//            node.eulerAngles = SCNVector3(0,0,0)
            sceneView.scene.rootNode.addChildNode(node)
uiimage arkit nsbezierpath
1个回答
0
投票

我认为你需要花一些时间学习和理解 3D 几何......

您为贝塞尔路径生成的点存在于 3D 空间中 - 因此您生成的节点/对象不会是简单的“填充路径”。

定位/大小/方向等也将取决于设备的方向当AR视图初始化时...也就是说,如果您在设备平放在桌子上时启动应用程序,然后然后拿起设备并指向它,您的“世界坐标”将与您垂直握住设备和/或指向不同方向时启动应用程序有很大不同。

无论如何...

如果我对您的

AddImage()
功能进行一些更改,包括:

  • 使用
    position.z
    代替
    position.y
  • 创建一些材质,以便我们可以看到几何形状
  • 增加挤压深度,以便我们能够理解我们所看到的几何形状
  • 使用第一个节点作为新对象的 z 位置,以帮助将其放置在大致所需的区域中

此编辑功能:

func AddImage(){
    isEditingEnabled = false
    let path = UIBezierPath()
    
    // Iterate through the sphere nodes array
    for (index, nodes) in nodes.enumerated() {
        let position = nodes.position
        
        // use position.z instead of position.y
        
        //if index == 0 {
        //  // Move to the starting position
        //  path.move(to: CGPoint(x: CGFloat(position.x), y: CGFloat(position.y)))
        //} else {
        //  // Add a line segment to the next sphere node
        //  path.addLine(to: CGPoint(x: CGFloat(position.x), y: CGFloat(position.y)))
        //}
        
        if index == 0 {
            // Move to the starting position
            path.move(to: CGPoint(x: CGFloat(position.x), y: CGFloat(position.z)))
        } else {
            // Add a line segment to the next sphere node
            path.addLine(to: CGPoint(x: CGFloat(position.x), y: CGFloat(position.z)))
        }
        
    }
    path.close()
    
    guard let gImg = UIImage(named: "grass1") else {
        fatalError()
    }
    
    // couple different materials so we can see the geometry of the shape

    let grassMat = SCNMaterial()
    grassMat.diffuse.contents = gImg
    
    let cyanMaterial = SCNMaterial()
    cyanMaterial.diffuse.contents = UIColor.cyan
    
    let orangeMaterial = SCNMaterial()
    orangeMaterial.diffuse.contents = UIColor.orange
    
    let shape = SCNShape(path: path, extrusionDepth: 0.05)

    shape.materials = [grassMat, orangeMaterial, cyanMaterial]
    
    let shapeNode = SCNNode(geometry: shape)

    if let fNode = nodes.first {
        shapeNode.position.z = fNode.position.z
    }
    
    sceneView.scene.rootNode.addChildNode(shapeNode)
    
}

给了我这些结果...

点击绘图按钮 3 次后:

拨打电话后

AddImage()
:

创建/添加节点后旋转设备:

使用更多“绘制”点:

© www.soinside.com 2019 - 2024. All rights reserved.