Apple Engine

Apple, iPhone, iOS, その周辺のことについて

iOS で SceneKit を試す(Swift 3) その70 - PhysicsBody にある3つのビットマスク

PhysicsBody には SCNNode のビットマスクとは別に、
以下のビットマスクが存在している。

  • Category mask
  • Collision mask
  • Contact mask

 

Physics Inspector (Command + Option + 6) でも、
同様のパラメーターがある

f:id:x67x6fx74x6f:20170816151853p:plain

 

Category mask

PhysicsBody の Category mask となり、AND演算の結果が 0 以外であれば同じカテゴリーに所属することとなる。

 

Collision mask

衝突判定となる Collision mask。

CategoryBitmask で同じカテゴリーに所属していて、互いの CollisionBitmask が AND演算の結果が 0 以外であれば、衝突判定が行われる。

そのため、CategoryBitmask と CollisionBitmask の要件が合わないと、Dynamic の PhysicsBody は障害物から突き抜けてしまう。

 

Category mask と Collision mask の使用例

f:id:x67x6fx74x6f:20170816151736g:plain

 

X 値 -1.5 (奥) 0 (中央) 1.5 (手前)
Category mask 2 1 1
Collision mask -1 1 -1

 

ピンクの板

3つとも Static のデフォルト値

  • Category mask 2
  • Collision mask -3

 

床(SCNFloor)
  • Category mask 1
  • Collision mask -3

 

今までの Bitmask と同様マイナスはすべて許容する。

奥の球は Category mask、中央の球は Collision mask の演算で弾かれるため板を通過する。

さらに、奥の球は Category mask が床との演算でも弾かれるため、床からも通過してしまう。

 

Contact mask

他の PhysicsBody が接触した際に使用する Contact mask でデフォルト値は 0。

値が 0 以外のノード同士がぶつかり合うと、 SCNPhysicsContactDelegate に SCNPhysicsContact オブジェクトがメッセージとして送られる。
(仕様上、片方が 0 の場合スルーされるはずだが、SCNPhysicsContact が送られる場合があり謎)

Category mask や Collision mask のように衝突処理自体に影響を与えない。

 

コード

コードでの設定方法。Dynamic での初期値。

let ballNode = SCNNode(geometry: SCNSphere())
ballNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
ballNode.physicsBody?.categoryBitMask = 1
ballNode.physicsBody?.collisionBitMask = -1
ballNode.physicsBody?.contactTestBitMask = 0

scene.rootNode.addChildNode(ballNode)

 

今回はここまで。

iOS で SceneKit を試す(Swift 3) その69 - PhysicsBody の振る舞い 3

コードでしか変更できない物理シミュレーション設定についてのご紹介。

 

momentOfInertia、usesDefaultMomentOfInertia

PhysicsBody の慣性モーメントを変更する。

SceneKit では形状と質量に合わせて自動設定されるが usesDefaultMomentOfInertia を false にして、 momentOfInertia を設定すると独自の慣性モーメントを付加できる。

例えば、X 軸回転方向に制限をかける

let ball = SCNSphere(radius: 1)
let ballNode = SCNNode(geometry: ball)
ballNode.position.y = 10
ballNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)

ballNode.physicsBody?.usesDefaultMomentOfInertia = false
ballNode.physicsBody?.momentOfInertia = SCNVector3(0, 1, 1)

scene.rootNode.addChildNode(ballNode)

 

力、トルク、衝動(力積、インパルス)

3つのメソッドとそれらをキャンセルするメソッドがある。

ユーザー操作で PhysicsBody を動かす場合はこちらを使うことになる。

  • applyForce(SCNVector3, asImpulse: Bool)
  • applyForce(SCNVector3, at: SCNVector3, asImpulse: Bool)
  • applyTorque(SCNVector4, asImpulse: Bool)

 

説明

applyForce の2つは移動の力となり、applyTorque は回転であるトルクの力が現在与えられている力に加わる。

1つ目の applyForce はジオメトリの重心から力を与え、
2つ目の applyForce の at はノードのローカル座標の場所から力を与える。

asImpulse を true にすると運動量の瞬間的な変化が適用され、false にすると最初のアニメーションループ(動作1フレーム目)にのみ適応されている模様。

 

単位

  • Force の単位はニュートン
  • Torque の単位はニュートンメートル

 

asImpulse が true の場合は

  • Force の単位はニュートン秒
  • Torque の単位はニュートンメートル秒

 

コード

node.physicsBody?.applyForce(SCNVector3(1,0,0), asImpulse: true)
node.physicsBody?.applyForce(SCNVector3(1,0,0), at: SCNVector3(0,0,0), asImpulse: true)
node.physicsBody?.applyTorque(SCNVector4(0,0,-1,1), asImpulse: true)

// Force や Torque のキャンセル
node.physicsBody?.clearAllForces()

 

重力や空気抵抗、他の PhysicsBody の摩擦や衝突などによる力の減衰、PhysicsField の影響があるため、実際に動かしてみないと動作の把握が難しい感じはある。

 

今回はここまで。

iOS で SceneKit を試す(Swift 3) その68 - PhysicsBody の振る舞い 2

もう1つの項目 Velocity について。
Static は力が加えられることはないのでこの項目はない。

Physics Inspector (Command + Option + 6) の Velocity には以下のパラメーターがある

  

Velocity

f:id:x67x6fx74x6f:20170816101417p:plain

 

Linear velocity

物理アニメーション時に現在の移動に加え指定した方向へ力を与える。
デフォルト値は x: 0.0 y: 0.0 z: 0.0

x を 1 にすると、プラス方向に メートル/秒 で移動の力が働く。

値を読み取る際は SCNSceneRendererDelegate プロトコルのアニメーションループの状況に異なるので注意。

 

Angular velocity

物理アニメーション時に現在の回転に加え指定した方向へ力を与える。
デフォルト値は x: 0.0 y: 0.0 z: 0.0

x を 1 にすると、プラス方向に メートル/秒 で回転の力が働く。

Linear velocity 同様。
値を読み取る際は SCNSceneRendererDelegate プロトコルのアニメーションループの状況に異なるので注意。

 

Linear factor

物理アニメーションの移動方向に制限を与える。
デフォルト値は x: 1.0 y: 1.0 z: 1.0

x: 1 y: 1 z: 0 に設定すると Z 軸方向の移動が行われなくなり、2次元平面を移動しているようになる。

 

Angular factor

物理アニメーションの回転方向に制限を与える。
デフォルト値は x: 1.0 y: 1.0 z: 1.0

x: 0 y: 1 z: 0 に設定すると Y 軸方向のみ回転する。

 

コード

let ball = SCNSphere(radius: 1)
let ballNode = SCNNode(geometry: ball)
ballNode.position.y = 10
ballNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)

ballNode.physicsBody?.velocity = SCNVector3(0,0,0)
ballNode.physicsBody?.angularVelocity = SCNVector4(0, 0, 0, 1)
ballNode.physicsBody?.velocityFactor =  SCNVector3(1, 1, 1)
ballNode.physicsBody?.angularVelocityFactor = SCNVector3(1, 1, 1)

scene.rootNode.addChildNode(ballNode)

 

今回はここまで。

iOS で SceneKit を試す(Swift 3) その67 - PhysicsBody の振る舞い 1

今回は Scene Editor の Physics Inspector (Command + Option + 6) 部分の Settings のところを見てゆく。

このパラメーターは物理シミュレーション時にジオメトリがどのような特性を持っているか設定する。

次回に説明する Velocity を含め、設定するパラメーターは少ないが、
他の PhysicsBody や PhysicsField の影響があるため、細かな数値設定をする必要がある。

 

Settings

以下のパラメーターがあり、Static は物理シミュレーションの影響を受けないため質量(Mass)の項目はない。

f:id:x67x6fx74x6f:20170815184356p:plain

  • Mass (Dynamic, Kinematic のみ)
  • Friction
  • Restitution
  • Rolling friction
  • Damping
  • Angular damping
  • Charge
  • Gravity: Affected by gravity
  • Resting: Allows resting

 

Mass

質量を表す。

デフォルト値は 1 で、1kg という設定。
0 にすると静止する。

例えば、他の Dynamic の PhysicsBody が同じ速度でぶつかった場合、質量が高いものの方が跳ね返す力が強くなる。

 

Friction

設置面の荒さによって抵抗を設定する摩擦の値。 デフォルト値は 0.5。

対象の PhysicsBody の Friction も 0 の場合は摩擦抵抗がなくなり滑り続け、互いが 1.0 の場合は全く滑らなくなる。

 

Restitution

他の PhysicsBody と衝突する際に運動エネルギーをそれだけ失うか、またはどれだけ運動エネルギーを得るかを設定する。

デフォルト値は 0.5。

1以上にすると衝突の際に力が発生するため、物体が弾む。
そのため、SCNFloor を Static な PhysicsBody で設定し、Dynamic の SCNSphere を上から落とした際、 デフォルトではそのまま床に張り付くが、Restitution を 1 以上にすると SCNSphere が弾む。

 

Rolling friction

回転運動に対する摩擦による抵抗力。

デフォルト値は 0。

設置面の床と回転して落ちる球の Rolling friction の値を大きくすると、 設置時に回転せずに止まるようになる。

 

Damping

流体摩擦、または空気の摩擦抵抗の影響をシミュレートする。

デフォルト値は 0.1。

0.5 にすると空間での抵抗を受けゆっくり落ち、1.0 にすると静止する。

 

Angular damping

Damping は移動に対してであったが、こちらは回転に関してのパラメーター。

デフォルト値は 0.1。

0.5 にすると空間での抵抗を受けゆっくり回転し、1.0 にすると全く回転はしない。

 

Charge

PhysicsBody が持つ電荷を設定する。
SCNPhysicsField の Electric Field、または Magnetic Field などの磁力に関係する外圧を使用する際に影響を受け、負の値はマイナス、正の値はプラスとなる。

デフォルト値は 0.0 で電場や磁場の影響を受けない。

 

Gravity

Affected by gravity のチェックを入れると PhysicsWorld で設定されている重力の影響を受ける。
そのため Dynamic の PhysicsBody でチェックを外すと重力の影響を受けないため静止する。

デフォルト値はオン(true)。

 

Resting

isResting というプロパティが存在しており、 力の影響を受けていない場合、SceneKit の物理シミュレーションによって自動的に true に設定され停止状態になる。

Allows resting のチェックをオンにすることで自動的に isResting を false に戻してシミュレーションさせるか否かを決める。

デフォルト値はオン(true)。

 

コード

let ball = SCNSphere(radius: 1)
let ballNode = SCNNode(geometry: ball)
ballNode.position.y = 10
ballNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)

ballNode.physicsBody?.mass = 1.0
ballNode.physicsBody?.friction = 0.5
ballNode.physicsBody?.restitution = 0.5
ballNode.physicsBody?.rollingFriction = 0.0
ballNode.physicsBody?.damping = 0.1
ballNode.physicsBody?.angularDamping = 0.1
ballNode.physicsBody?.charge = 0.0
ballNode.physicsBody?.isAffectedByGravity = true
ballNode.physicsBody?.allowsResting = true

scene.rootNode.addChildNode(ballNode)

 

今回はここまで。

iOS で SceneKit を試す(Swift 3) その66 - PhysicsBody の当たり判定 PhysicsShape について

まず、最初にあたり判定から。

Scene Editor のデフォルト、もしくはコードで SCNPhysicsBody 設定の際 nil を渡すと、 そのジオメトリの形状が当たり判定となる。

また、当たり判定のジオメトリが荒いほど物理シミュレーションの演算が軽くなる。

 

Scene Editor での表示

Scene Editor では当たり判定が緑色のワイアーフレームで表示される。

デフォルトの場合、その形状になるため、
球を配置すると、球の当たり判定となる。

f:id:x67x6fx74x6f:20170815165903p:plain

 

Scene Editor での設定

SceneEditor で ノードを選択し、Physics Inspector (Command + Option + 6) を開くと下の方に Physics Shape の設定がある。

こちらを編集することで当たり判定を変更することができる。

f:id:x67x6fx74x6f:20170815165935p:plain

 

Shape

Default shape はジオメトリの形状を使用し、以下はビルトインのジオメトリ、最後の Custom Node は自前で設定したジオメトリを適応する。

f:id:x67x6fx74x6f:20170815170316p:plain

  • Default shape
  • Sphere
  • Cylinder
  • Panel
  • Pyramid
  • Cone
  • Tube
  • Capsule
  • Torus
  • Custom Node

 

Type

  • Convex
  • Bounding Box

Convex はデフォルトで Shape で設定したおおよその形状を利用し、Bounding Box はジオメトリを囲んだ直方体を変邸に利用する。

 

Scale

当たり判定である Shape の大きさ。デフォルトは 1 でフィットする。

 

Collision Margin

ドキュメントに説明がないが、文字通り当たり判定である Shape にマージンを加える。

Scale 1、Collision Margin 2 の時と、Scale 2、Collision Margin 1 の場合はほぼ同等の振る舞い。

 

コード

コード上にのみあるものは keepAsCompound の設定と、Type になかった SCNPhysicsShape.ShapeType.concavePolyhedron

keepAsCompound はデフォルトで true。
当たり判定となる SCNNode の形状を合成する模様。

ShapeType の concavePolyhedron は Convex よりもちゃんとした形状で当たり判定をつくる模様。  

let ball = SCNSphere(radius: 1)
let ballNode = SCNNode(geometry: ball)
ballNode.position.y = 10

let ballShape = SCNPhysicsShape(geometry: ball, options: [
        SCNPhysicsShape.Option.scale : 1,
        SCNPhysicsShape.Option.type : SCNPhysicsShape.ShapeType.convexHull,
        SCNPhysicsShape.Option.collisionMargin : 0,
        SCNPhysicsShape.Option.keepAsCompound : true
    ])

ballNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: ballShape)

scene.rootNode.addChildNode(ballNode)

 

今回はここまで。

iOS で SceneKit を試す(Swift 3) その65 - 物理シミュレーションについて

以前、軽く紹介していたが、何回かに分けては詳しく見ていこうと思う。

SceneKit での物理シミュレーションの種類

大きく分けると以下のもの

  • PhysicsWorld
  • PhysicsBody
  • PhysicsField

PhysicsWorld は物理シミュレーションは全体についての設定。
PhysicsBody は物理シミュレーションをさせる対象物。
PhysicsField は風などの外部からの力を指す。

 

その他

物理シミュレーションにはこれ以外にも PhysicsBody の当たり判定を決める PhysicsShape。

PhysicsWorld 内で物が当たった時に作用する PhysicsContact とその Delegate。

車の動きをシミュレーションする PhysicsVehicle。

ヒンジやボールジョイントをシミュレーションする Joint がある。 (ドキュメント内では Bone も Joint と呼んでいる箇所があるのでややこしい)

 

物理シミュレーションの種類

SCNNode であれば全て物理シミュレーションが可能ではあるが、 物理シミュレーションを行う際に状況に合わせた種類を選択する必要がある。

種類は以下の3つ

  • Static
  • Dynamic
  • Kinematic

 

Static

設置した部分に固定され他の PhysicsBody の力や衝突の影響を受けない。
例えば、物理シミュレーションの際に固定されて欲しいと思われる床や部屋などで使用する。

Dynamic

すべての力や衝突の影響を受ける。
物理シミュレーションを有効にするもので、固定させておきたいもの以外はすべてこれを使うことになる。

Kinematic

Static 同様に画面に固定されるが、動いた時に物理シミュレーションが動作する。

例えば、Static で床をつくった場合、物理シミュレーションの判定は最初に設置した状態で当たり判定を固定する。
そのため、Static の場合、床を X 軸 45 度に傾けてもその場から動くことはない。
Kinematic の場合は床を固定させながら、動かしても物理シミュレーションが有効になる。

f:id:x67x6fx74x6f:20170815112308g:plain

テクスチャ画像が間違っているけど Kinetic は Kinematic。

 

負荷的には Dynamic > Kinematic > Static となり、
Static にできるものはできるだけ Static で設定した方が良い。

 

SceneKit での設定値

Scene Editor を開きノードを選択。

Physics Inspector (Command + Option + 6) を開き Physics Body の Type を選択すると

以下のパラメーター設定ができる。

 

Static

f:id:x67x6fx74x6f:20170815105023p:plain

 

Dynamic、Kinematic

f:id:x67x6fx74x6f:20170815105033p:plain

 

今回はここまで。

iOS で SceneKit を試す(Swift 3) その64 - SCNSkinner と Bone について

人型のキャラクタなどに稼動できる骨を入れて、骨を動かすと該当箇所のジオメトリが変化する 所謂、Skinning と Bone を使用してアニメーションを SCNSkinner が行う。

f:id:x67x6fx74x6f:20170814142434p:plain

公式ドキュメントの画像を参照

 

正直、コード上で書くのは手間がかかり、Scene Editor で編集ができないため、 他のゲームエンジンと同様に 3DCG のオーサリングツールで Bone を入れ、 アニメーションを設定したオブジェクトファイルを作成した方が速い。

 

使い方

コードで設定する場合は、 キャラクタなどのスキンにボーンと各種設定を SCNNode から適応させる。

init(baseGeometry: SCNGeometry?, 
            bones: [SCNNode], 
boneInverseBindTransforms: [NSValue]?, 
     boneWeights: SCNGeometrySource, 
     boneIndices: SCNGeometrySource)
パラメーター名 説明
baseGeometry キャラクタなど変形させたいジオメトリ。スキン
bones 階層化されたボーン情報。SCNNode がボーンとなるためその配列を渡す
boneInverseBindTransforms ボーンの初期位置。SCNMatrix4 が格納された NSValue の配列
boneWeights ボーンが boneIndices で設定したスキンの頂点に対してどれだけの影響があるかを設定したもの。所謂、ウエイト。ここで設定する Geometry Source の頂点用のベクトルが4つの要素以上になると GPU から CPU に移りパフォーマンスが悪くなるので注意
boneIndices ボーンが動いた際、スキン上のどの頂点を動かすか設定する

 

Scene Editor での見えかた

Scene Graph View でのスキン。
Attributes に Skinner が設定されている。

f:id:x67x6fx74x6f:20170814142525p:plain

 

Scene Graph View でのボーン。
Xcode のメニューバー Editor > Display > Show Joints で簡易表示される。

f:id:x67x6fx74x6f:20170814142546p:plain

 

わかりづらいので、同じく Show Bounding Box でボーンの範囲を表示している。

f:id:x67x6fx74x6f:20170814142605p:plain

 

Xcode 9 からは 3DCG のオーサリングツール同様に正八面体を伸ばした形でボーンが表示される。

 

アニメーション

f:id:x67x6fx74x6f:20170814142626g:plain

 

設定しているボーンをスキンに渡す

設定されているボーンの集まりであるスケルトンは、以下の方法で他のジオメトリに渡すことができる。

SCNNodeA.skinner.skeleton = SCNNodeB.skinner.skeleton

 

他のノードのボーンアニメーションを使用する

ボーンの構造が同じであれば、他の scn やオブジェクトファイルから呼び出してアニメーションをすることができる。

let url = Bundle.main.url(forResource: "art.scnassets/boss_attack", withExtension: "dae")
let sceneSource = SCNSceneSource(url: url!, options: [
    SCNSceneSource.LoadingOption.animationImportPolicy : SCNSceneSource.AnimationImportPolicy.doNotPlay
    ])

let attackAnimation:CAAnimation = sceneSource!.entryWithIdentifier("attackID", withClass: CAAnimation.self)!
attackAnimation.fadeInDuration = 0.3
attackAnimation.fadeOutDuration = 0.3

baseNode.addAnimation(attackAnimation, forKey: "attackID")

 

IK (Inverse Kinematics) を使用してみる

コードでのみで設定可能である点と、3DCG のオーサリングツールでキャラクタアニメーションを作る関係上、あまり使用されることはないと思われるが機能的には存在している。

shoulderNode を肩のボーンとし、handNode を手のボーン。 そこまでを SCNIKConstraint の inverseKinematicsConstraint(chainRootNode:) で設定し

SCNIKConstraint の targetPosition で移動させる。

let ik:SCNIKConstraint = SCNIKConstraint.inverseKinematicsConstraint(chainRootNode: shoulderNode)
handNode.constraints = [ik]

ik.targetPosition = SCNVector3(2, 0, 2)

 

今回はここまで