PhysicsKit 2.0.0

PhysicsKit 2.0.0

Adam Eisfeld 维护。



  • AdamEisfeld

这是一个开源的 iOS/macOS 封装库,用于 Bullet 物理库,并增加了对 SceneKit 的支持。

要求

PhysicsKit 被包装为一个通用的 XCFramework,为 iOS 和 macOS 构建,通过 Swift Package Manager 或 Cocoapods 发布。

通过 Cocoapods 安装

在 podfile 中参考 PhysicsKit

pod 'PhysicsKit'

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

import PhysicsKit

通过 Swift Package Manager 安装

  1. 在 Xcode 中,选择 File > Swift Packages > Add Package Dependency
  2. 输入 "https://github.com/AdamEisfeld/PhysicsKit" 作为远程 URL,然后点击 Next
  3. 输入所需的版本号或分支,然后点击 Next
  4. 点击 Finish

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

import PhysicsKit

示例用法

创建一个物理世界来运行你的模拟

PKPhysicsWorld处理在一系列附加的PKRigidBody实例上运行Bullet物理模拟。

let physicsWorld = PKPhysicsWorld()

创建一个动态刚体以添加到物理世界

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

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

// Create a rigid body from this physics shape, with a type of "dynamic" so forces / collisions affect it
let dynamicBody = PKRigidBody(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)

创建一个静态刚体以添加到物理世界

静态刚体不受力/碰撞的影响,但其他动态刚体可以与之碰撞。PhysicsKit还支持运动学刚体,此处未展示。

// Create a collision shape to describe the rigid body
let staticCollisionShape = PKCollisionShapeBox(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 = PKRigidBody(type: .static, shape: staticCollisionShape)

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

步骤物理模拟

要运行模拟,必须通过某些时间增量向前推进物理模拟。通常,这个时间将等于上一次步骤模拟以来经过的时间。

您可以使用CADisplayLink或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

    }

}

添加触发区域

有时检测给定的刚体何时进入物理模拟中的某个区域是有用的。PKTriggers提供了这个功能

let triggerShape = PKCollisionShapeSphere(radius: 1.0)
let trigger = PKTrigger(shape: triggerShape)
physicsWorld.add(trigger)

将PhysicsKit连接到SceneKit

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

PhysicsScene类提供了一个将PKRigidBody连接到SCNNode的机制,使得节点位置/方向会在关联的PKRigidBody在模拟中移动时更新。

创建PhysicsScene

let physicsScene = PKPhysicsScene(isMotionStateEnabled: true)

添加/附加PKRigidBodies和SCNNodes

let node: SCNNode = ...
let rigidBody: PKRigidBody = ...
let physicsWorld: PKPhysicsWorld = ...
let scene: SCNScene = ...

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

运动状态或手动更新

为了使SCNNodes与附加的PKRigidBody实例对齐,PKPhysicsScene有两个选项,根据传递给其初始化器的isMotionStateEnabled值确定

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

动作

从PhysicsKit 1.0.2版本开始,添加了PKActions。类似于SceneKit中的SCNActions,您可以构建PKActions来随时间修改刚体的属性,用于动画等。

您可以创建自定义的PKAction,或使用提供的辅助构造函数,例如

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

// Move the rigid body forwards by 1 unit / second, constantly
let moveActionBy = PKAction.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 = PKAction.orient(rigidBody, by: .euler(0, 180, 0, .degrees), duration: 1.0)
orientAction.repeatCount = 3
orientAction.run()

请注意,只有运动刚体支持PKActions。将PKActions添加到静态/动态刚体可能会产生未定义的行为。

光线投射

从PhysicsKit 1.0.3版本开始,添加了光线投射功能。通过PKPhysicsWorld执行光线投射,以确定哪些刚体与光线相交

let start: PKVector3 = .vector(0, 10, 0)
let end: PKVector3 = .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
}

代理

观察模拟变化

通过符合PKPhysicsWorldSimulationDelegate协议,成为PKPhysicsWorld的模拟代理。此代理将在模拟步进之前和之后被通知。

观察碰撞

通过符合PKPhysicsWorldCollisionDelegate协议,成为PKPhysicsWorld的碰撞代理。此代理将在任意两个刚体开始、继续和结束时被通知碰撞。

观察触发区域

通过符合PKPhysicsWorldTriggerDelegate协议,将自身设置为PKPhysicsWorld的触发代理。此代理将在刚体进入、保持和离开触发区域时被通知。

观察物理场景更新

通过符合PKPhysicsSceneUpdateDelegate协议,将自身设置为PKPhysicsScene的代理。该代理将在节点定位到其刚体之前和之后被通知,并且当物理场景的isMotionStateEnabled设置为true时,如果给定刚体的内部转换发生变化,也会被通知。

碰撞形状

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

  • PKCollisionShapeBox
  • PKCollisionShapeSphere
  • PKCollisionShapeCapsule
  • PKCollisionShapeGeometry(允许您从现有的SceneKit SCNGeometry实例构建物理形状,这些实例可以从外部Collada .dae文件、SceneKit .scn文件或从一系列SCNGeometry实例加载)
  • PKCollisionShapeStaticPlane(无限平面,对地面/墙壁很有用)
  • PKCollisionShapeFromData(允许您从先前序列化的碰撞形状数据构建物理形状)
  • PKCollisionShapeCompound(允许您从其他PKCollisionShapes数组构建物理形状)

PKCollisionShapeFromData对于加快模拟设置很有用。生成碰撞网格可能需要一些时间,尤其是在从非标准原形状(例如 PKCollisionShapeGeometry)加载时。所有PKCollisionShapes都具有一个serialize()函数,允许您获得其数据的表示形式,这可以保存到磁盘/与应用程序包一起打包。然后您可以使用PKCollisionShapeFromData加载这些形状。

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

更新Bullet

PhysicsKit当前针对Bullet v2.89

本地安装Bullet

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

修改PhysicsKit目标所使用的Bullet源文件

  1. 找到包含您所需源代码的bullet文件夹。例如,"src/LinearMath"
  2. 将此文件夹复制到repo中,在PhysicsKit-shared/bullet下
  3. 将此文件夹拖到PhysicsKit Xcode项目的"bullet"组下
  4. 确保选择"创建组",不要选择任何目标,并点击完成
  5. 在添加的目录中搜索任何.cpp文件,并手动将它们添加到PhysicsKit-ios和PhysicsKit-macos目标中
  6. 通过Target / Build Phases / Compile Sources将"-w"编译器标志添加到所有cpp文件中

更新PhysicsKit

在做出任何所需的更改后,按照以下步骤发布新的PhysicsKit版本

  1. 在终端中,导航到PhysicsKit的根目录,并运行helper脚本buildPhysicsKit.sh。此脚本将
  • 为所有支持的平台创建PhysicsKit的.xcarchives
  • 将这些.xcarchives打包成一个 xcframework
  1. 仍在终端中,运行helper脚本deployPhysicsKit.sh,并传递您想要应用给当前版本的版本号。PhysicsKit使用语义版本化(请参阅https://semver.org)。此脚本将
  • 更新podspec中声明的版本
  • 使用版本号标记当前git分支
  • 运行"pod lib lint"以确保Cocoapods功能没有损坏
  • 暂存所有更改以进行提交
  • 提交所有更改(将出现一个提交信息对话框)
  • 将分支和版本标签推到远程repo上
  • 将pod推送到CocoaPods以便公开消费

添加 Objective-C 文件

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

添加平台特定文件

  1. 将任何 iOS 专用文件添加到 PhysicsKit-ios 目录/将其链接到 PhysicsKit-ios 目标
  2. 将任何 macOS 专用文件添加到 PhysicsKit-macos 目录/将其链接到 PhysicsKit-macos 目标

作者

Adam Eisfeld,[email protected]

许可证

PhysicsKit 可在 zlib 许可证下使用。有关更多信息,请参阅 LICENSE 文件。