pythonista3にはObjective-CのクラスをPythonから呼び出すobjc_utilという強力なツールが存在します。
今回は、この機能を使って、scenkitをラッパーして物理シミュレーションを体感してみます。
立方体と球体を出現させて、自由落下させるだけの簡単なデモです。
ソースは以下のとおり。
# coding: utf-8
from objc_util import *
import ui
import math
import motion
import random
from scene import *
load_framework('SceneKit')
SCNView, SCNScene, SCNBox, SCNSphere,SCNFloor, SCNNode, SCNLight, SCNMaterial, SCNCamera, SCNAction, SCNTransaction,SCNLookAtConstraint,SCNPhysicsShape,SCNPhysicsBody,UIFont= map(ObjCClass, ['SCNView', 'SCNScene', 'SCNBox', 'SCNSphere','SCNFloor', 'SCNNode', 'SCNLight', 'SCNMaterial','SCNCamera', 'SCNAction','SCNTransaction','SCNLookAtConstraint','SCNPhysicsShape','SCNPhysicsBody','UIFont'])
class SCNVector3 (Structure):
_fields_ = [('x', c_float), ('y', c_float), ('z', c_float)]
W=1
L=1
H=1
LOCALTIME=0
def ObjCMethodPrint(obj):
print(obj.__class__.__name__)
if hasattr(obj, '__call__'):
print(inspect.getargspec(obj))
else:
print(dir(obj))
@on_main_thread
class MyScene (Scene):
def setup(self):
pass
def draw(self):
global LOCALTIME
LOCALTIME=LOCALTIME+1
if (LOCALTIME % 60)==0 and LOCALTIME<=60000:
sphere= SCNSphere.sphereWithRadius_(1*random.random())
sphere.material().setColor_(UIColor.greenColor().CGColor())
sphere_node = SCNNode.nodeWithGeometry_(sphere)
sphere_node.setPosition_((random.random(), 20*random.random(), random.random()))
physicsShape = SCNPhysicsShape.shapeWithGeometry_options_(sphere,None)
physicsBody = SCNPhysicsBody.dynamicBody()
physicsBody.reflectivity =.9
sphere_node.physicsBody = physicsBody
self.root_node.addChildNode_(sphere_node)
def make_view(self,mc):
main_view_objc = mc
scene_view = SCNView.alloc().initWithFrame_options_(((0, 0),(100, 100)), None).autorelease()
scene_view.setAutoresizingMask_(18)
scene_view.setAllowsCameraControl_(True)
scene = SCNScene.scene()
self.root_node = scene.rootNode()
#ボックスを生成
box= SCNBox.boxWithWidth_height_length_chamferRadius_(W, L,H,0)
box.material().setColor_(UIColor.redColor().CGColor())
box_node = SCNNode.nodeWithGeometry_(box)
box_node.setPosition_((0, 8, 0))
physicsShape = SCNPhysicsShape.shapeWithGeometry_options_(box,None)
physicsBody = SCNPhysicsBody.dynamicBody()
physicsBody.reflectivity =.8
box_node.physicsBody = physicsBody
self.root_node.addChildNode_(box_node)
#球体を生成
sphere= SCNSphere.sphereWithRadius_(0.5)
sphere.material().setColor_(UIColor.greenColor().CGColor())
sphere_node = SCNNode.nodeWithGeometry_(sphere)
sphere_node.setPosition_((0, 10, 0))
physicsShape = SCNPhysicsShape.shapeWithGeometry_options_(sphere,None)
physicsBody = SCNPhysicsBody.dynamicBody()
physicsBody.reflectivity =.9
sphere_node.physicsBody = physicsBody
self.root_node.addChildNode_(sphere_node)
#床を作成
floor = SCNFloor.floor()
floor.reflectivity = 0.25
floor_node= SCNNode.nodeWithGeometry_(floor)
floor_node.physicsBody=SCNPhysicsBody.staticBody()
self.root_node.addChildNode_(floor_node)
#光を生成
light_node = SCNNode.node()
light_node.setPosition_((0, 70, 100))
light = SCNLight.light()
light.setType_('omni')
light.setCastsShadow_(True)
light.setColor_(UIColor.whiteColor().CGColor())
light_node.setLight_(light)
self.root_node.addChildNode_(light_node)
#カメラをセット
camera = SCNCamera.camera()
camera_node = SCNNode.node()
camera_node.setCamera(camera)
camera_node.setPosition((0, 1,20))
self.root_node.addChildNode_(camera_node)
scene_view.setScene_(scene)
main_view_objc.addSubview_(scene_view)
if __name__ == "__main__":
#ObjCMethodPrint(SCNPhysicsBody)
#set view
main_view = ui.View()
main_view_objc = ObjCInstance(main_view)
main_view.name = 'SceneKit Demo'
#run MyScene
my_scene = MyScene()
scene_view = SceneView()
scene_view.scene = my_scene
main_view_objc.addSubview_(scene_view)
#make scenekit
my_scene.make_view(main_view_objc)
#present view
main_view.present()
Scenekitのメソッドを良く理解せずに適当に使っているので悪しからず。
#物理属性の設定
物理属性としてdynamic,static,kinematicがあり、床にはstaticを、球体とボックスにはdynamicを設定しています。
反発係数のプロパティは、reflectivityで与えます。
#レンダリングループ
レンダリングループさせるためにsceneオブジェクトのdraw()を使っています。
1秒に25回描画してくれるようです。
上記ソースのようにMySceneクラスを作って、以下のようにインスタンスを作って呼び出せば、レンダリングループしてくれます。
#run MyScene
my_scene = MyScene()
scene_view = SceneView()
scene_view.scene = my_scene
main_view_objc.addSubview_(scene_view)
#scenekit viewの作成
SCNViewを使って、viewを作っておきます。
setAllowsCameraControlをTrueにしておくとviewをタップして動かせます。
#make scenekit
my_scene.make_view(main_view_objc)
def make_view(self,mc):
main_view_objc = mc
scene_view = SCNView.alloc().initWithFrame_options_(((0, 0),(400, 400)), None).autorelease()
scene_view.setAutoresizingMask_(18)
scene_view.setAllowsCameraControl_(True)
#ボックス、球体、床、ライト、カメラの配置
root_nodeを作って、addChildNode_メソッドを使ってボックス、球体、床、ライト、カメラをぶら下げていきます。
scene = SCNScene.scene()
self.root_node = scene.rootNode()
#ボックス、球体の生成
ボックスはboxWithWidth_height_length_chamferRadius_プロパティで幅、長さ、高さ、角の丸みを指定します。
球体は、sphereWithRadius_プロパティで球体の半径を指定します。
material().setColor_で色を指定します。
物理属性は、physicsBodyプロパティで指定します。
最後にaddChildNodeメソッドでroot_nodeにぶら下げておきます。
#ボックスを生成
box= SCNBox.boxWithWidth_height_length_chamferRadius_(W, L,H,0)
box.material().setColor_(UIColor.redColor().CGColor())
box_node = SCNNode.nodeWithGeometry_(box)
box_node.setPosition_((0, 8, 0))
physicsShape = SCNPhysicsShape.shapeWithGeometry_options_(box,None)
physicsBody = SCNPhysicsBody.dynamicBody()
physicsBody.reflectivity =.8
box_node.physicsBody = physicsBody
self.root_node.addChildNode_(box_node)
#球体を生成
sphere= SCNSphere.sphereWithRadius_(0.5)
sphere.material().setColor_(UIColor.greenColor().CGColor())
sphere_node = SCNNode.nodeWithGeometry_(sphere)
sphere_node.setPosition_((0, 10, 0))
physicsShape = SCNPhysicsShape.shapeWithGeometry_options_(sphere,None)
physicsBody = SCNPhysicsBody.dynamicBody()
physicsBody.reflectivity =.9
sphere_node.physicsBody = physicsBody
self.root_node.addChildNode_(sphere_node)
#床の作成
床は、SCNFloor.floor()で作成します。
動かないようにstatic属性を与えておきます。
#床を作成
floor = SCNFloor.floor()
floor.reflectivity = 0.25
floor_node= SCNNode.nodeWithGeometry_(floor)
floor_node.physicsBody=SCNPhysicsBody.staticBody()
self.root_node.addChildNode_(floor_node)
#ライトの生成
ライトはSCNLight.light()で生成します。
ライトのタイプはspot,omni,directional,ambientが存在選べます。
#光を生成
light_node = SCNNode.node()
light_node.setPosition_((0, 70, 100))
light = SCNLight.light()
light.setType_('omni')
light.setCastsShadow_(True)
light.setColor_(UIColor.whiteColor().CGColor())
light_node.setLight_(light)
self.root_node.addChildNode_(light_node)
#カメラの配置
カメラは、SCNCamera.camera()で生成します。
セットした物体が写るようにカメラを配置します。
写る位置を探すのは結構めんどくさいです。
#カメラをセット
camera = SCNCamera.camera()
camera_node = SCNNode.node()
camera_node.setCamera(camera)
camera_node.setPosition((0, 1,20))
self.root_node.addChildNode_(camera_node)
#物体の追加生成
60描画毎に球体を追加生成しています。
LOCALTIME<=60000で打ち止めにしています。
同じ位置から落下させると上に乗ってしまうので、出現位置をランダムに変えています。
def draw(self):
global LOCALTIME
LOCALTIME=LOCALTIME+1
if (LOCALTIME % 60)==0 and LOCALTIME<=60000:
sphere= SCNSphere.sphereWithRadius_(1*random.random())
sphere.material().setColor_(UIColor.greenColor().CGColor())
sphere_node = SCNNode.nodeWithGeometry_(sphere)
sphere_node.setPosition_((random.random(), 20*random.random(), random.random()))
physicsShape = SCNPhysicsShape.shapeWithGeometry_options_(sphere,None)
physicsBody = SCNPhysicsBody.dynamicBody()
physicsBody.reflectivity =.9
sphere_node.physicsBody = physicsBody
self.root_node.addChildNode_(sphere_node)
#速度の追加
初期速度を追加は、physicsBody.velocity=((0,0,-100))のようにx,y,z方向の速度を与えるだけです。
以下少し量を増やした物理シミュレーションを示します。青いボールとオレンジのボールが横から飛んできます。
いろいろ設定を変えて楽しめます。
# coding: utf-8
from objc_util import *
import ui
import math
import motion
import random
from scene import *
load_framework('SceneKit')
SCNView, SCNScene, SCNBox, SCNSphere,SCNFloor, SCNNode, SCNLight, SCNMaterial, SCNCamera, SCNAction, SCNTransaction,SCNLookAtConstraint,SCNPhysicsShape,SCNPhysicsBody,UIFont= map(ObjCClass, ['SCNView', 'SCNScene', 'SCNBox', 'SCNSphere','SCNFloor', 'SCNNode', 'SCNLight', 'SCNMaterial','SCNCamera', 'SCNAction','SCNTransaction','SCNLookAtConstraint','SCNPhysicsShape','SCNPhysicsBody','UIFont'])
class SCNVector3 (Structure):
_fields_ = [('x', c_float), ('y', c_float), ('z', c_float)]
W=1
L=1
H=1
LOCALTIME=0
def ObjCMethodPrint(obj):
print(obj.__class__.__name__)
if hasattr(obj, '__call__'):
print(inspect.getargspec(obj))
else:
print(dir(obj))
@on_main_thread
class MyScene (Scene):
def setup(self):
pass
def draw(self):
global LOCALTIME
LOCALTIME=LOCALTIME+1
if (LOCALTIME % 10)==0 and LOCALTIME<=6000:
if random.random()<=0.5 :
makeobject= SCNSphere.sphereWithRadius_(1*random.random())
else:
makeobject= SCNBox.boxWithWidth_height_length_chamferRadius_(2*W*random.random(), 2*L*random.random(),2*H*random.random(),0)
makeobject.material().setColor_(UIColor.greenColor().CGColor())
object_node = SCNNode.nodeWithGeometry_(makeobject)
object_node.setPosition_((random.random(), 20*random.random(), random.random()))
physicsShape = SCNPhysicsShape.shapeWithGeometry_options_(makeobject,None)
physicsBody = SCNPhysicsBody.dynamicBody()
physicsBody.reflectivity =.9
physicsBody.velocity=((30*(0.5-random.random()),30*(0.5-random.random()),30*(0.5-random.random())))
object_node.physicsBody = physicsBody
object_node.runAction_(SCNAction.rotateToX_y_z_duration_(math.pi*random.random(),math.pi*random.random(),math.pi*random.random(),0))
self.root_node.addChildNode_(object_node)
def make_view(self,mc):
main_view_objc = mc
scene_view = SCNView.alloc().initWithFrame_options_(((0, 0),(100, 100)), None).autorelease()
scene_view.setAutoresizingMask_(18)
scene_view.setAllowsCameraControl_(True)
scene = SCNScene.scene()
self.root_node = scene.rootNode()
#ボックスを生成
for i in range(99):
cube=SCNBox.boxWithWidth_height_length_chamferRadius_(W, L,H,0)
cube.material().setColor_(UIColor.yellowColor().CGColor())
cube_node = SCNNode.nodeWithGeometry_(cube)
cube_node.setPosition_((W*(i%10), L*(int(i/10)), -H*20))
physicsShape = SCNPhysicsShape.shapeWithGeometry_options_(cube,None)
physicsBody = SCNPhysicsBody.dynamicBody()
physicsBody.reflectivity =.8
cube_node.physicsBody = physicsBody
self.root_node.addChildNode_(cube_node)
for i in range(100):
box= SCNBox.boxWithWidth_height_length_chamferRadius_(W, L,H,0)
box.material().setColor_(UIColor.redColor().CGColor())
box_node = SCNNode.nodeWithGeometry_(box)
box_node.setPosition_((8*random.random(), 100*random.random(), -8*random.random()))
box_node.runAction_(SCNAction.rotateToX_y_z_duration_(math.pi*random.random(),math.pi*random.random(),math.pi*random.random(),0))
physicsShape = SCNPhysicsShape.shapeWithGeometry_options_(box,None)
physicsBody = SCNPhysicsBody.dynamicBody()
physicsBody.reflectivity =.8
box_node.physicsBody = physicsBody
self.root_node.addChildNode_(box_node)
#球体を生成
sphere= SCNSphere.sphereWithRadius_(1.5)
#ObjCMethodPrint(sphere)
sphere.material().setColor_(UIColor.blueColor().CGColor())
sphere_node = SCNNode.nodeWithGeometry_(sphere)
sphere_node.setPosition_((L*5, H*12, 150))
physicsShape = SCNPhysicsShape.shapeWithGeometry_options_(sphere,None)
physicsBody = SCNPhysicsBody.dynamicBody()
physicsBody.reflectivity =.9
physicsBody.velocity=((0,0,-150))
#ObjCMethodPrint(physicsBody)
sphere_node.physicsBody = physicsBody
self.root_node.addChildNode_(sphere_node)
#球体を生成 2個め
sphere= SCNSphere.sphereWithRadius_(2)
#ObjCMethodPrint(sphere)
sphere.material().setColor_(UIColor.orangeColor().CGColor())
sphere_node = SCNNode.nodeWithGeometry_(sphere)
sphere_node.setPosition_((L*5, H*40, 200))
physicsShape = SCNPhysicsShape.shapeWithGeometry_options_(sphere,None)
physicsBody = SCNPhysicsBody.dynamicBody()
physicsBody.reflectivity =.9
physicsBody.velocity=((0,0,-100))
#ObjCMethodPrint(physicsBody)
sphere_node.physicsBody = physicsBody
self.root_node.addChildNode_(sphere_node)
#床を作成
floor = SCNFloor.floor()
floor.reflectivity = 0.25
floor_node= SCNNode.nodeWithGeometry_(floor)
floor_node.physicsBody=SCNPhysicsBody.staticBody()
self.root_node.addChildNode_(floor_node)
#光を生成
light_node = SCNNode.node()
light_node.setPosition_((0, 70, 100))
light = SCNLight.light()
light.setType_('omni')
light.setCastsShadow_(True)
light.setColor_(UIColor.whiteColor().CGColor())
light_node.setLight_(light)
self.root_node.addChildNode_(light_node)
#カメラをセット
camera = SCNCamera.camera()
camera_node = SCNNode.node()
camera_node.setCamera(camera)
camera_node.setPosition((-20, 1,20))
camera_node.setRotation_((0, 1, 0, -math.pi*1/3))
self.root_node.addChildNode_(camera_node)
scene_view.setScene_(scene)
main_view_objc.addSubview_(scene_view)
if __name__ == "__main__":
#ObjCMethodPrint(SCNPhysicsBody)
#set view
main_view = ui.View()
main_view_objc = ObjCInstance(main_view)
main_view.name = 'SceneKit Demo'
#run MyScene
my_scene = MyScene()
scene_view = SceneView()
scene_view.scene = my_scene
main_view_objc.addSubview_(scene_view)
#make scenekit
my_scene.make_view(main_view_objc)
#present view
main_view.present()