Обновление SCNPhysicsWorld Force Simulation - PullRequest
3 голосов
/ 26 мая 2020

Я пытаюсь получить предсказание / согласование на стороне клиента, работающее для модели клиент / сервер. Я использовал Scenekit для рендеринга трехмерного мира и обработки физических расчетов.

Чтобы это работало, мне нужна возможность заставить симуляцию физики «вычислять» заданное количество кадров (применять силы, проверять коллизии, обновлять положения / ориентацию узлов), не отображая эти обновления. Таким образом, когда я определяю, что у клиента было неверное предсказание относительно того, что сервер отправляет обратно, я могу переориентировать все узлы в симуляции физики в соответствии с тем, что отправил сервер (включая установку физических свойств, таких как скорость / angular скорость ), а затем перемотайте вперед по «событиям», которые сервер еще не обработал, чтобы вернуться к текущему моменту времени клиента.

Я понимаю, что SceneKit не использует детерминированный c физический движок, но Я надеюсь, что смогу получить что-то, что выглядит «достаточно хорошо» (моя система ошибочных прогнозов имеет порог для определения того, не совпадает ли данный прогноз с серверным).

Процесс выглядит примерно так:

  1. Клиент запускает симуляцию физики локально на основе локальных входов / текущего локального состояния симуляции
  2. Клиент генерирует собственные пакеты хоста каждый раз, когда запускается симуляция физики, кэшируя их на текущую дату кадра.
  3. Клиент получает пакет хоста от сервера s определение того, как должна выглядеть физическая симуляция. Этот пакет основан на пакете, который клиент отправлял серверу в прошлом. Пакет хоста содержит идентификатор (дату), который клиент связал со своим пакетом, который он ранее отправил на сервер.
  4. Клиент просматривает свою предыдущую кэшированную версию того, как он видел мир физики в прошлом для этого Дата. Если он слишком сильно отличается от того, что отправил хост, мы определяем, что у нас было неверное предсказание и его необходимо исправить.
  5. Клиент переориентирует все узлы в сцене, чтобы они соответствовали состоянию пакета хоста ( установка положения, вращения, скорости, angular скорость, и т.д. c)
  6. Затем клиент перебирает все пакеты, которые он отправил на сервер ПОСЛЕ даты хост-пакета (клиентские пакеты, которые сервер имеет еще не обработанные как часть этого хост-пакета), и повторно применяет их к соответствующим узлам.
  7. Часть, с которой я борюсь: каждый раз, когда клиент применяет набор исторических c пакетов к узлов в части 6, мне нужно заставить симуляцию физики «обработать» эти изменения (применить силы, генерируемые этими пакетами, проверить наличие коллизий, обновить позиции узлов), прежде чем перейти к следующему набору пакетов, которых у сервера еще нет обработано.

Я попытался поиграть с timeStep физического мира, но похоже, что это увеличивает свойство также уменьшает прилагаемые силы за физический «цикл» (мы получаем больше физических симуляций, но конечный результат - просто более «точное» моделирование за счет перемещения физических тел на меньшие расстояния перед проверкой столкновений).

Я пробовал играть со скоростью физического мира, но а) увеличение этого значения снижает точность моделирования физики, б) по какой-то причине сцены, созданные в редакторе сцен, имеют скорость по умолчанию 6 вместо 1, поэтому определение подходящее значение здесь немного сбивает с толку, и c) похоже, что оно не повлияет до следующей попытки запуска симуляции.

Я попытался поиграть с sceneTime вида сцены, увеличивая его на 1 каждый раз, когда я обрабатываю набор исторических входных данных, которые, как я думал, работают, но при более тщательном исследовании кажется, что это ничего не дает.

Я попытался приостановить сцену, применить свои изменения и затем воспроизвести сцену, но приостановка сцены также приостанавливает симуляцию физики.

Есть ли способ, чтобы SCNPhysicsWorld обновлял физическую симуляцию вручную / многократно в al oop, без запуска каких-либо вызовов рендеринга?

1 Ответ

0 голосов
/ 17 июня 2020

После множества проб и ошибок мне не удалось получить ничего похожего на принудительное обновление симуляции для работы со SceneKit. При обращении в Apple я также был проинформирован, что эта функция в настоящее время не поддерживается.

Я решил использовать sh отдельный физический движок для достижения моих целей вместо SceneKit, что позволит мне ориентировать мои узлы SCN на вычисления внешнего физического движка.

С этой целью я создал PhysicsKit, оболочку с открытым исходным кодом для популярного физического движка Bullet, который можно найти здесь: https://github.com/AdamEisfeld/PhysicsKit

Фреймворк доступен через Cocoapods, и я буду работать над добавлением поддержки Carthage / SPM в будущем. README в репозитории и документация по классам должны помочь в наладке и запуске, но в интересах включения некоторого реального кода в этот пост, вот краткий обзор, демонстрирующий, как мы можем использовать фреймворк для запуска этапа моделирования физики. , показывающий, как прыгающий мяч сталкивается с земной поверхностью:

import UIKit
import SceneKit
import PhysicsKit

class ViewController: UIViewController {

    let sceneView = SCNView()
    let scene = SCNScene(named: "scenes.scnassets/world.scn")!
    let physicsWorld = PKPhysicsWorld()
    let physicsScene = PKPhysicsScene(isMotionStateEnabled: true)
    var sceneTime: TimeInterval? = nil
    let cameraNode = SCNNode()

    override func viewDidLoad() {

        super.viewDidLoad()

        setupSceneView()

        setupGroundPlane()
        setupBouncyBall()

    }

    private func setupSceneView() {
        // Embed the scene view into our view
        sceneView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(sceneView)
        NSLayoutConstraint.activate([
            sceneView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            sceneView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            sceneView.topAnchor.constraint(equalTo: view.topAnchor),
            sceneView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])
        // Play the sceneview so we get constant calls to the delegate's update(atTime...) function
        sceneView.isPlaying = true
        // Set the delegate so we can update our physics world with the scene view
        sceneView.delegate = self
        // Set the scene
        sceneView.scene = scene

        // Configure the camera
        cameraNode.camera = SCNCamera()
        scene.rootNode.addChildNode(cameraNode)
        sceneView.pointOfView = cameraNode
        cameraNode.position = SCNVector3(0, 0, 20)

    }

    private func setupGroundPlane() {

        // Create a visual node representing a ground plane, and add it to our scene
        let groundGeometry = SCNFloor()
        let groundNode = SCNNode(geometry: groundGeometry)
        scene.rootNode.addChildNode(groundNode)

        // Create a physics body for the ground and add it to our physics world
        let groundShape = PKCollisionShapeStaticPlane()
        let groundBody = PKRigidBody(type: .static, shape: groundShape)
        physicsWorld.add(groundBody)

        // Shift the ground down 10 units
        groundBody.position = .vector(0, -10, 0)

        // Make the ground a little bouncy
        groundBody.restitution = 0.6

        // Wire the transform of the ground's rigid body to it's node
        physicsScene.attach(groundBody, to: groundNode)
    }

    private func setupBouncyBall() {

        // Create a visual node representing a bouncy ball, and add it to our scene
        let ballGeometry = SCNSphere(radius: 1.0)
        let ballNode = SCNNode(geometry: ballGeometry)
        scene.rootNode.addChildNode(ballNode)

        // Create a physics body for the bouncy ball and add it to our physics world
        let ballShape = PKCollisionShapeSphere(radius: 1.0)
        let ballBody = PKRigidBody(type: .dynamic(mass: 1.0), shape: ballShape)
        physicsWorld.add(ballBody)

        // Make the ball a little bouncy
        ballBody.restitution = 0.6

        // Wire the transform of the ball's rigid body to it's node
        physicsScene.attach(ballBody, to: ballNode)

    }

}

extension ViewController: SCNSceneRendererDelegate {

    func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
        sceneTime = sceneTime ?? time
        let physicsTime = time - sceneTime!

        // Here we get to the root of the SO question: We can now manually increment the physicsWorld's
        // simulation time, irrespective of the scene's render loop. We could iteratively increment
        // this simulationTime value as many times as we want in a single render cycle of the scene view.
        physicsWorld.simulationTime = physicsTime
        physicsScene.iterativelyOrientAllNodesToAttachedRigidBodies()
    }

}

Я не буду отмечать это как принятый ответ, поскольку он не отвечает на исходный вопрос, но я хотел включить его здесь на будущее ссылка в том случае, если это кому-то поможет. Возможно, в будущем SceneKit будет обновлен для поддержки шагов физики вручную.

  • Adam
...