Bullet 物理库的开源 iOS / macOS 封装,并额外支持 SceneKit。
需求
PhyKit 被打包为一个通用的 XCFramework,针对 iOS 和 macOS 构建,通过 Swift 包管理器或 Cocoapods 分发。
通过 Swift 包管理器安装
- 在 Xcode 中,选择 File > Swift Packages > Add Package Dependency
- 输入 "https://github.com/AdamEisfeld/PhyKit" 作为远程 URL,然后点击下一步
- 输入所需的版本号,然后点击下一步
- 点击完成
然后,将其导入到您的 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
- 确保已安装 cmake
- 通过 https://cmake.com.cn/download/ 安装 cmake
- 通过 PATH="/Applications/CMake.app/Contents/bin":"$PATH" 安装 cmake 命令行工具
- 安装所需的 Bullet 库
- 从 https://github.com/bulletphysics/bullet3 安装 Bullet
- 在 Bullet 文件夹中运行 xcode.command 生成 Xcode 项目
修改 PhyKit 针对哪些 Bullet 源文件
- 在 Bullet 文件夹中找到包含所需源文件的文件夹。例如,"src/LinearMath"
- 将该文件夹复制到回购中,位于 PhyKit-shared/bullet
- 将此文件夹拖动到 PhyKit Xcode 项目的 "bullet" 组下
- 确保选择 "创建组",不要选择任何目标,然后点击完成
- 在添加的目录中搜索任何 .cpp 文件,并将它们手动添加到 PhyKit-ios 和 PhyKit-macos 目标中
- 通过目标/构建阶段/编译源文件将 "-w" 编译器标志添加到所有 cpp 文件中
更新 PhyKit
完成任何所需更改后,按照以下步骤发布新的 PhyKit 版本
- 在终端中,导航到 PhyKit 的根目录,并运行辅助脚本
./buildPhy.sh
。此脚本将
- 为所有支持的平台创建 PhyKit 的 .xcarchives
- 将这些 xcarchives 打包成一个 xcframework
- 仍在终端中,运行辅助脚本 deployPhy,传递要应用给该发布版本的版本号(例如
./deployPhy.sh 1.2.3
)。PhyKit 使用语义化版本控制(请参阅 https://semver.org)。此脚本将
- 更新 podspec 中声明的版本
- 使用版本号标记当前 git 分支
- 运行 "pod lib lint" 以确保 Cocoapods 功能不受影响。
- 为提交准备所有更改
- 提交所有更改(将出现提交消息对话框)
- 将分支和版本标签推进远程仓库
- 将 Pod 推送到 CocoaPods 以供公共使用
添加 Objc 文件
- 在 "objc" 文件夹下添加任何所需的 Objc 文件
- 确保实现文件支持 objc++,通过将 ".m" 扩展名替换为 ".mm"
- 将任何 C++ 依赖项添加到单独的 "MyClassName+Internal.h" 文件中,该文件扩展了您的类
- 将导入添加到 PhyKit.h 文件中。不要在此处导入 "/暴露给 Swift" 的 "MyClassName+Internal.h" 文件
添加平台特定文件
- 将任何iOS特定文件添加到PhyKit-ios目录 / 或将其链接到PhyKit-ios目标
- 将任何macOS特定文件添加到PhyKit-macos目录 / 或将其链接到PhyKit-macos目标
开发
打开包含的“Scratchpad” Xcode项目,以对PHYKit的源代码进行操作,这些源代码连接到一个基本场景设置的样板跨平台应用程序。
作者
Adam Eisfeld, [email protected]
许可
PhyKit在zlib许可下提供。更多信息请参阅LICENSE文件。