Как использовать SCNAvoidOccluderConstraint (любой пример)
У кого-нибудь есть пример использования SCNAvoidOccluderConstraint
?
Единственное описание, которое я нашел:
@abstract Ограничения SCNAvoidOccluderConstraint помещают получатель в положение, которое не позволяет узлам с указанной категорией перекрывать цель.
@discussion Целевой узел и его дочерние элементы игнорируются как потенциальные окклюдеры.
ОБНОВЛЕНИЕ: Xcode 9 был официально выпущен и до сих пор нет ни одной строчки в документации.
2 ответа
Уже поздно, но вот рабочий пример (на Python, но его можно легко воспроизвести в Swift или ObjC). Мяч с SCNAvoidOccluderConstraint на нем возвращается по своей траектории всякий раз, когда блок в центре препятствует обзору другого шара.
"""
avoid occluder demo
"""
from objc_util import *
import sceneKit as scn
import ui
import math
def dot(v1, v2):
return sum(x*y for x,y in zip(list(v1),list(v2)))
def det2(v1, v2):
return v1[0]*v2[1] - v1[1]*v2[0]
class Demo:
@classmethod
def run(cls):
cls().main()
@on_main_thread
def main(self):
main_view = ui.View()
w, h = ui.get_screen_size()
main_view.frame = (0,0,w,h)
main_view.name = 'avoid occluder demo'
scene_view = scn.View(main_view.frame, superView=main_view)
scene_view.autoresizingMask = scn.ViewAutoresizing.FlexibleHeight | scn.ViewAutoresizing.FlexibleWidth
scene_view.allowsCameraControl = True
scene_view.delegate = self
scene_view.backgroundColor = 'white'
scene_view.rendersContinuously = True
scene_view.scene = scn.Scene()
root_node = scene_view.scene.rootNode
floor_geometry = scn.Floor()
floor_node = scn.Node.nodeWithGeometry(floor_geometry)
root_node.addChildNode(floor_node)
ball_radius = 0.2
ball_geometry = scn.Sphere(radius=ball_radius)
ball_geometry.firstMaterial.diffuse.contents = (.48, .48, .48)
ball_geometry.firstMaterial.specular.contents = (.88, .88, .88)
self.ball_node_1 = scn.Node.nodeWithGeometry(ball_geometry)
self.ball_node_2 = scn.Node.nodeWithGeometry(ball_geometry)
root_node.addChildNode(self.ball_node_1)
root_node.addChildNode(self.ball_node_2)
occluder_geometry = scn.Box(0.3, 2., 15., 0.2)
occluder_geometry.firstMaterial.diffuse.contents = (.91, .91, .91)
occluder_node = scn.Node.nodeWithGeometry(occluder_geometry)
occluder_node.position = (0., 0.8, 0.)
root_node.addChildNode(occluder_node)
self.orbit_r = 10
self.omega_speed_1 = math.pi/1500
self.omega_speed_2 = 1.5*self.omega_speed_1
self.ball_node_1.position = (self.orbit_r, 0.5, 0.)
self.ball_node_2.position = (0., 0.5, self.orbit_r)
constraint = scn.AvoidOccluderConstraint.avoidOccluderConstraintWithTarget(self.ball_node_1)
self.ball_node_2.constraints = [constraint]
camera_node = scn.Node()
camera_node.camera = scn.Camera()
camera_node.position = (0.5*self.orbit_r , 0.5*self.orbit_r, 1.5*self.orbit_r)
camera_node.lookAt(root_node.position)
root_node.addChildNode(camera_node)
light_node = scn.Node()
light_node.position = (self.orbit_r, self.orbit_r, self.orbit_r)
light = scn.Light()
light.type = scn.LightTypeDirectional
light.castsShadow = True
light.shadowSampleCount = 32
light.color = (.99, 1.0, .86)
light_node.light = light
light_node.lookAt(root_node.position)
root_node.addChildNode(light_node)
main_view.present(hide_title_bar=False)
def update(self, view, atTime):
pos_1 = self.ball_node_1.presentationNode.position
pos_2 = self.ball_node_2.presentationNode.position
self.omega_1 = -math.atan2(det2((pos_1.x, pos_1.z), (1., 0.)), dot((pos_1.x, pos_1.z), (1., 0.)))
self.omega_2 = -math.atan2(det2((pos_2.x, pos_2.z), (1., 0.)), dot((pos_2.x, pos_2.z), (1., 0.)))
self.omega_1 += self.omega_speed_1
self.omega_2 += self.omega_speed_2
self.ball_node_1.position = (self.orbit_r*math.cos(self.omega_1), 0.5, self.orbit_r*math.sin(self.omega_1))
self.ball_node_2.position = (self.orbit_r*math.cos(self.omega_2), 0.5, self.orbit_r*math.sin(self.omega_2))
Demo.run()
Недавно я наткнулся на этот старичок, надеясь получить больше информации о том, можно ли использовать это ограничение для решения конкретной проблемы. К сожалению, документация Apple по-прежнему ужасна, поэтому я оставлю здесь еще несколько заметок для потомков.
SCNAvoidOccluderConstraint использует SCNNode в качестве цели. Если объект перекрывает линию обзора между целевым узлом и узлом, к которому вы добавили это ограничение, узел с ограничением переместится к ближайшей точке между его исходным положением и целью, чтобы восстановить линию обзора с помощью целевой узел.
В большинстве случаев эта точка будет находиться непосредственно по другую сторону мешающего объекта, поэтому ваш ограниченный объект не будет полностью перемещаться на другую сторону мешающего объекта; его центральная точка будет совмещена с видимым краем окклюдера по отношению к цели.
Кроме того, вы можете использовать categoryBitMask SCNNode и occluderCategoryBitMask ограничения, чтобы исключить определенные узлы из числа окклюдеров .
Вот свободная адаптация оригинального ответа @pulbrich в Swift, которая иллюстрирует использование. Вы можете вставить это в файл GameViewController.swift проекта XCode SceneKit Game по умолчанию :
import SceneKit
import QuartzCore
class GameViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene()
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
cameraNode.look(at: SCNVector3(0,0,0))
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 0)
scene.rootNode.addChildNode(lightNode)
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = NSColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
let scnView = self.view as! SCNView
scnView.scene = scene
scnView.allowsCameraControl = true
scnView.showsStatistics = true
scnView.backgroundColor = NSColor.black
//--------------------------
let ball_radius = 0.2
let ball_geometry = SCNSphere(radius: ball_radius)
let ball_node_1 = SCNNode.init(geometry: ball_geometry)
ball_node_1.name = "b1"
let ball_node_2 = SCNNode.init(geometry:ball_geometry)
ball_node_2.name = "b2"
scene.rootNode.addChildNode(ball_node_1)
scene.rootNode.addChildNode(ball_node_2)
ball_node_1.worldPosition = SCNVector3(5, 5, 0)
ball_node_2.worldPosition = SCNVector3(5, -5, 0)
let occluder_geometry = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0)
let occluder_node = SCNNode.init(geometry: occluder_geometry)
scene.rootNode.addChildNode(occluder_node)
let constraint = SCNAvoidOccluderConstraint(target: ball_node_1)
ball_node_2.constraints = [constraint]
let a1 = CABasicAnimation(keyPath: "position")
a1.toValue = SCNVector3(-5,5,0)
a1.duration = 5
a1.autoreverses = true
a1.repeatCount = .infinity
ball_node_1.addAnimation(a1, forKey: "move1")
//IMPORTANT NOTE: this constraint will hose CABasicAnimation,
//but the presense of the animation will snap it back to the toValue
//vector when the target is no longer occluded
let a2 = CABasicAnimation(keyPath: "position")
a2.toValue = SCNVector3(5,-5,0)
//a2.toValue = SCNVector3(-5,-5,0)
a2.duration = 5
a2.autoreverses = true
a2.repeatCount = .infinity
ball_node_2.addAnimation(a2, forKey: "move2")
}
}