PhyKitCocoapod 1.0.1

PhyKitCocoapod 1.0.1

Adam Eisfeld 维护。



  • AdamEisfeld

Bullet 物理库的开源 iOS / macOS 封装,并额外支持 SceneKit。

需求

PhyKit 被打包为一个通用的 XCFramework,针对 iOS 和 macOS 构建,通过 Swift 包管理器或 Cocoapods 分发。

通过 Swift 包管理器安装

  1. 在 Xcode 中,选择 File > Swift Packages > Add Package Dependency
  2. 输入 "https://github.com/AdamEisfeld/PhyKit" 作为远程 URL,然后点击下一步
  3. 输入所需的版本号,然后点击下一步
  4. 点击完成

然后,将其导入到您的 Swift 源中并开始使用该框架

import PhyKit

通过 Cocoapods 安装

在 podfile 中参考 PhyKit ("PhyKit" 已经在 CocoaPods 上使用,所以 pod 名称是 PhyKitCocoapod)

pod 'PhyKitCocoapod'

运行 pod install 或 pod update。然后,将其导入到您的 Swift 源中并开始使用该框架

import PhyKit

示例用法

创建一个物理世界来运行您的仿真

A PHYWorld 负责在一系列附加的 PHYRigidBody 实例上运行 Bullet 物理仿真

let physicsWorld = PHYWorld()

创建一个动态刚体并将其添加到物理世界

动态刚体会受到力和碰撞的影响。

// Create a collision shape to describe the rigid body
let dynamicCollisionShape = PHYCollisionShapeBox(size: 1.0)

// Create a rigid body from this physics shape, with a type of "dynamic" so forces / collisions affect it
let dynamicBody = PHYRigidBody(type: .dynamic, shape: dynamicCollisionShape)

// Customize the rigid body's physical properties
dynamicBody.restitution = 0.8

// Add the rigid body to the physics world
physicsWorld.add(dynamicBody)

配置刚体的变换

有几种修改刚体位置/旋转的方法。下面展示了一些。

// Position the rigid body 10 units above the "ground"
dynamicBody.position = .vector(0, 10, 0)

// You can adjust a rigid body's rotation either via quaternions...
dynamicBody.orientation = .quaternion(0,0,0,1)

// ...or via euler angles (in either radians or degrees)
dynamicBody.orientation = .euler(0, 0, 45, .degrees)

创建一个静态刚体并将其添加到物理世界

静态刚体不受力和碰撞的影响,但其他动态刚体可以碰撞它们。PhyKit 还支持运动学刚体,此处未展示。

// Create a collision shape to describe the rigid body
let staticCollisionShape = PHYCollisionShapeBox(width: 100, height: 0.01, length: 100)

// Create a rigid body from this physics shape, with a type of "static" so forces / collisions don't affect it. Other dynamic bodies can still collide with this body.
let staticBody = PHYRigidBody(type: .static, shape: staticCollisionShape)

// Add the rigid body to the physics world
physicsWorld.add(staticBody)

步进物理仿真

要运行仿真,您必须通过一些时间增量“步进”物理仿真。这种时间通常等于自上次步进仿真以来流逝的时间。

您可以使用 CADisplayLink(PHYDisplayLink 在 iOS 上使用 CADisplayLink,在 macOS 上使用 CVDisplayLink),或者 SceneKit 的 SCNView 的渲染循环来执行此调用。

class SomeViewController: UIViewController {

    var sceneTime: TimeInterval? = nil

    ... setup an SCNView, and wire this view controller up to the view's delegate property

}
extension SomeViewController: SCNSceneRendererDelegate {

    func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {

        // Determine how much time has elapsed since we last stepped the simulation
        sceneTime = sceneTime ?? time
        let physicsTime = time - sceneTime!

        // Step the simulation
        physicsWorld.simulationTime = physicsTime

    }

}

添加触发区域

有时检测一个刚体在物理模拟中进入特定区域是有用的。PHYTriggers 提供了这项功能

let triggerShape = PHYCollisionShapeSphere(radius: 1.0)
let trigger = PHYTrigger(shape: triggerShape)
physicsWorld.add(trigger)

将 PhyKit 连接到 SceneKit

我们需要一种方式来可视化物理模拟。您可以使用您选择的渲染器(例如自定义 OpenGL 或 Metal 渲染器),或者使用 SceneKit 来处理繁重的工作。

PHYScene 类提供了一个机制,用于将 PHYRigidBody 连接到 SCNNode,使得节点的位置/方向更新与相关的 PHYRigidBody 在模拟中移动时同步。

创建一个物理场景

let physicsScene = PHYScene(isMotionStateEnabled: true)

添加/连接 PHYRigidBodies 和 SCNNodes

let node: SCNNode = ...
let rigidBody: PHYRigidBody = ...
let physicsWorld: PHYWorld = ...
let scene: SCNScene = ...

scene.rootNode.addChildNode(node)
physicsWorld.add(rigidBody)
physicsScene.attach(rigidBody, to: node)

运动状态或手动更新

为了使 SCNNodes 与它们附加的 PHYRigidBody 实例对齐,PHYScene 有两种选择,这取决于传递给它初始化器的 isMotionStateEnabled 值

  • isMotionStateEnabled = true: 物理场景将自动监听 Bullet 物理引擎上任何刚体的变换更改,并将那些应用到相关的 scenekit 节点。
  • isMotionStateEnabled = false: 您必须手动强制物理场景更新其 scenekit 节点变换,通过在物理场景上调用 iterativelyOrientAllNodesToAttachedRigidBodies()。

操作

与SceneKit中的SCNActions类似,您可以使用PHYActions构建在时间上更改刚体属性,例如动画。

您可以通过创建自定义的PHYAction,或者使用提供的一些辅助构造函数之一

// Move the rigid body to a new position in 1 second
let moveActionTo = PHYAction.move(rigidBody, to: someNewPosition, duration: 1.0)
moveActionTo.run()

// Move the rigid body forwards by 1 unit / second, constantly
let moveActionBy = PHYAction.move(rigidBody, by: .vector(0, 0, 1), duration: 1.0)
moveActionBy.repeatCount = -1 // Repeat forever
moveActionBy.run()

// Rotate the rigid body around it's y axis 180 degrees / second, 3 times
let orientAction = PHYAction.orient(rigidBody, by: .euler(0, 180, 0, .degrees), duration: 1.0)
orientAction.repeatCount = 3
orientAction.run()

请注意,只有运动学刚体支持PHYActions。将PHYActions添加到静力/动力刚体可能会导致未定义的行为。

光线投射

通过PHYWorld进行光线投射,以确定哪些刚体与光线相交

let start: PHYVector3 = .vector(0, 10, 0)
let end: PHYVector3 = .vector(0, -10, 0)
let results = physicsWorld.rayCast(from: start, to: end)
for result in results {
   let intersectedRigidBody = result.rigidBody
   let intersectionWorldPosition = result.worldPosition
   let intersectionWorldNormal = result.worldNormal
}

代理

监视模拟更改

通过遵守PHYWorldSimulationDelegate协议,成为PHYWorld的模拟代理。这个代理将在模拟向前推进之前和之后收到通知。

观察碰撞

通过遵守PHYWorldCollisionDelegate协议,成为PTYWorld的碰撞代理。这个代理将在任意两个刚体之间的碰撞开始、继续和结束时收到通知。

观察触发区域

通过遵守PHYWorldTriggerDelegate协议,成为PHYWorld的触发代理。这个代理将在刚体进入、停留在和退出触发区域时收到通知。

观察物理场景更新

通过遵循 PHYSceneUpdateDelegate 协议,使 PHYScene 成为代理。这个代理将在节点定向到它们的刚体之前和之后被通知,还在物理场景的 isMotionStateEnabled 设置为 true 时,给定刚体的内部变换发生变化时被通知。

碰撞形状

碰撞形状是物理模拟的基本构建模块。PhyKit 提供了许多构造物理形状的选项,以获得正确的行为,包括

  • PHYCollisionShapeBox
  • PHYCollisionShapeSphere
  • PHYCollisionShapeCapsule
  • PHYCollisionShapeGeometry (允许你从一个现有的 SceneKit SCNGeometry 实例构造物理形状,这些实例可以从外部 Collada .dae 文件、SceneKit .scn 文件加载,或者从一系列 SCNGeometry 实例构建)
  • PHYCollisionShapeStaticPlane (一个无限平面,对于地面/墙壁很有用)
  • PHYCollisionShapeFromData (允许你从先前序列化的碰撞形状数据构造物理形状)
  • PHYCollisionShapeCompound (允许你从一个其他 PHYCollisionShapes 数组中构造物理形状)

PHYCollisionShapeFromData 很有用,可以加快模拟设置。生成碰撞网格可能需要一些时间,尤其是在从非标准原始形状(例如 PHYCollisionShapeGeometry)加载时。所有 PHYCollisionShapes 都有 serialize() 函数,可以让你获取它们的 Data 表示,这可以保存到磁盘/与你的应用程序包捆绑在一起。然后你可以使用 PHYCollisionShapeFromData 加载这些形状。

let originalCollisionShape = PHYCollisionShapeGeometry(...)
let data = originalCollisionShape.serialize() // Save this data to disk somewhere, and stop using PHYCollisionShapeGeometry in favor of:
let recreatedCollisionShape = PHYCollisionShapeFromData(data)

更新 Bullet

PhyKit 目标是 Bullet v2.89

本地安装 Bullet

  1. 确保已安装 cmake
  1. 安装所需的 Bullet 库

修改 PhyKit 针对哪些 Bullet 源文件

  1. 在 Bullet 文件夹中找到包含所需源文件的文件夹。例如,"src/LinearMath"
  2. 将该文件夹复制到回购中,位于 PhyKit-shared/bullet
  3. 将此文件夹拖动到 PhyKit Xcode 项目的 "bullet" 组下
  4. 确保选择 "创建组",不要选择任何目标,然后点击完成
  5. 在添加的目录中搜索任何 .cpp 文件,并将它们手动添加到 PhyKit-ios 和 PhyKit-macos 目标中
  6. 通过目标/构建阶段/编译源文件将 "-w" 编译器标志添加到所有 cpp 文件中

更新 PhyKit

完成任何所需更改后,按照以下步骤发布新的 PhyKit 版本

  1. 在终端中,导航到 PhyKit 的根目录,并运行辅助脚本 ./buildPhy.sh。此脚本将
  • 为所有支持的平台创建 PhyKit 的 .xcarchives
  • 将这些 xcarchives 打包成一个 xcframework
  1. 仍在终端中,运行辅助脚本 deployPhy,传递要应用给该发布版本的版本号(例如 ./deployPhy.sh 1.2.3)。PhyKit 使用语义化版本控制(请参阅 https://semver.org)。此脚本将
  • 更新 podspec 中声明的版本
  • 使用版本号标记当前 git 分支
  • 运行 "pod lib lint" 以确保 Cocoapods 功能不受影响。
  • 为提交准备所有更改
  • 提交所有更改(将出现提交消息对话框)
  • 将分支和版本标签推进远程仓库
  • 将 Pod 推送到 CocoaPods 以供公共使用

添加 Objc 文件

  1. 在 "objc" 文件夹下添加任何所需的 Objc 文件
  2. 确保实现文件支持 objc++,通过将 ".m" 扩展名替换为 ".mm"
  3. 将任何 C++ 依赖项添加到单独的 "MyClassName+Internal.h" 文件中,该文件扩展了您的类
  4. 将导入添加到 PhyKit.h 文件中。不要在此处导入 "/暴露给 Swift" 的 "MyClassName+Internal.h" 文件

添加平台特定文件

  1. 将任何iOS特定文件添加到PhyKit-ios目录 / 或将其链接到PhyKit-ios目标
  2. 将任何macOS特定文件添加到PhyKit-macos目录 / 或将其链接到PhyKit-macos目标

开发

打开包含的“Scratchpad” Xcode项目,以对PHYKit的源代码进行操作,这些源代码连接到一个基本场景设置的样板跨平台应用程序。

作者

Adam Eisfeld, [email protected]

许可

PhyKit在zlib许可下提供。更多信息请参阅LICENSE文件。