LoginSignup
12
14

More than 5 years have passed since last update.

pythonista3で物理シミュレーション

Last updated at Posted at 2017-05-05

IMG_0400.PNG

概要

pythonista3にはObjective-CのクラスをPythonから呼び出すobjc_utilという強力なツールが存在します。
今回は、この機能を使って、scenkitをラッパーして物理シミュレーションを体感してみます。
立方体と球体を出現させて、自由落下させるだけの簡単なデモです。

ソースは以下のとおり。

physics_body.py
# 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方向の速度を与えるだけです。
以下少し量を増やした物理シミュレーションを示します。青いボールとオレンジのボールが横から飛んできます。
いろいろ設定を変えて楽しめます。
IMG_0411.PNG

# 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()  
12
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
14