我有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)
我认为你需要花一些时间学习和理解 3D 几何......
您为贝塞尔路径生成的点存在于 3D 空间中 - 因此您生成的节点/对象不会是简单的“填充路径”。
定位/大小/方向等也将取决于设备的方向当AR视图初始化时...也就是说,如果您在设备平放在桌子上时启动应用程序,然后然后拿起设备并指向它,您的“世界坐标”将与您垂直握住设备和/或指向不同方向时启动应用程序有很大不同。
无论如何...
如果我对您的
AddImage()
功能进行一些更改,包括:
position.z
代替 position.y
此编辑功能:
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()
:
创建/添加节点后旋转设备:
使用更多“绘制”点: