Как использовать 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")
    }
}
Другие вопросы по тегам