Javascript で動く軽量物理エンジン OimoPhysics と3Dレンダラ Three.js で書いた短いサンプルコード


以前から「OimoPhysics」を使ってゲームを作ってみたいと思っていた。Three.js は仕事でも使ってるくらいなのだが、[物理エンジン]に関しては、まったくのど素人。

[物理エンジン]:https://ja.wikipedia.org/wiki/%E7%89%A9%E7%90%86%E6%BC%94%E7%AE%97%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%B3

OimoPhysics は、Javascriptで動く軽量な[物理エンジン]。
Three.jsは、みなさんご存知の3D描画ライブラリ。

で、ググるものの、サンプルコードが見つからない。最大あって、ここ👇くらい。
http://el-ement.com/blog/2018/01/13/oimophysics-released/

Oimo.js のコードはいくらか見つかるのだが、Oimo.js は、OimoPhysics の分家らしいので、ここは本家 OimoPhysics にこだわってみたい。



OimoPhysics
https://github.com/saharan/OimoPhysics

Oimo.js
https://lo-th.github.io/Oimo.js/#kinematic3

Three.js
https://threejs.org/

で、見つけたのがこのサンプル👇。いやぁ、すごい力作です。いろいろな[物理エンジン]とレンダラの組み合わせがすごいです。
https://qiita.com/cx20/items/3ebed669fb9c9e797935

~~http://jsdo.it/cx20/Goua~~
> jsdo.itは 2019/10/31 を持ちまして終了いたしました。

これ以上ググっても、堂々巡りな感じがしてきたので、こちらのコードを足がかりに、自分が理解できるレベルまで写経してみようと思った。（先人のコードに感謝）

その結果がこちら👇。やく166行のhtml。

<p class="codepen" data-height="400" data-theme-id="0" data-default-tab="result" data-user="yamazaki3104" data-slug-hash="jjZyLX" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="OimoPhysics.js + Three.js">
  <span>See the Pen <a href="https://codepen.io/yamazaki3104/pen/jjZyLX/">
  OimoPhysics.js + Three.js</a> by yamazaki.3104 (<a href="https://codepen.io/yamazaki3104">@yamazaki3104</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>


```html
<html style='margin: 0;height: 100%;'>
<!-- Three.js r106 --><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/106/three.js"></script>
<!-- TrackballControls.js --><script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/controls/TrackballControls.js"></script>
<!-- OimoPhysics.js v1.2.0 --><script src="https://cdn.jsdelivr.net/gh/saharan/OimoPhysics@1.2.0/bin/js/OimoPhysics.js"></script>
<body style='margin: 0;height: 100%;'>
<script>

class THREEJS
{
    constructor()
    {
        const w = document.body.clientWidth
        const h = document.body.clientHeight

        this.renderer = new THREE.WebGLRenderer()
        this.renderer.setClearColor( 0x8888dd )
        this.renderer.setSize( w, h )

        this.camera = new THREE.PerspectiveCamera( 40, w / h, 0.1, 1000 )
        this.camera.position.x =  0
        this.camera.position.y = 20
        this.camera.position.z = 30
        this.trackball = new THREE.TrackballControls( this.camera )

        this.scene = new THREE.Scene()

        let directionalLight = new THREE.DirectionalLight( 0xffffff, 1 )
        directionalLight.position.set( 0.2, 0.5, 0.3 )
        this.scene.add( directionalLight )

        // this.scene.add( new THREE.AmbientLight( 0x101020 ) )

        document.body.appendChild( this.renderer.domElement )
    }

    add_mesh( _arg )
    {
        let mesh = new THREE.Mesh(
            new THREE.BoxGeometry( _arg.w, _arg.h, _arg.d ),
            new THREE.MeshLambertMaterial( { color: _arg.color } )
        )
        mesh.oimo_rigid_body = _arg.body
        this.scene.add( mesh )
    }

    render()
    {
        for ( let mesh of this.scene.children )
        {
            if ( ! mesh.oimo_rigid_body ) continue

            const pos = mesh.oimo_rigid_body.getPosition()
            mesh.position.x = pos.x
            mesh.position.y = pos.y
            mesh.position.z = pos.z

            const ori = mesh.oimo_rigid_body.getOrientation()
            mesh.quaternion.x = ori.x
            mesh.quaternion.y = ori.y
            mesh.quaternion.z = ori.z
            mesh.quaternion.w = ori.w
        }

        this.trackball.update()
        this.renderer.render( this.scene, this.camera )
    }
}

class OIMO_PHYSICS
{
    constructor( _threejs )
    {
        this.oimo_world = new OIMO.World()
        this.oimo_world.gravity = new OIMO.Vec3( 0, -9.80665, 0 )

        let ground_cnf      = new OIMO.RigidBodyConfig()
        ground_cnf.type     = OIMO.RigidBodyType.STATIC    // DYNAMIC, KINEMATIC, STATIC
        ground_cnf.position = new OIMO.Vec3( 0, -0.2, 0 )
        let rigid_body = new OIMO.RigidBody( ground_cnf )

        let ground_shape_cnf = new OIMO.ShapeConfig()
        ground_shape_cnf.geometry = new OIMO.BoxGeometry( new OIMO.Vec3( 25, 0.2, 25 ) )
        rigid_body.addShape( new OIMO.Shape( ground_shape_cnf ) )

        this.oimo_world.addRigidBody( rigid_body )

        this.threejs = _threejs
        this.threejs.add_mesh( { body: rigid_body, w: 25*2, h: 0.2*2, d: 25*2, color: 0x333333 } )
    }

    create_rigid_body( _arg )
    {
        let body_cnf = new OIMO.RigidBodyConfig()
        body_cnf.type= OIMO.RigidBodyType.DYNAMIC // DYNAMIC, KINEMATIC, STATIC
        body_cnf.position = new OIMO.Vec3( _arg.x, _arg.y, _arg.z )
        let rigid_body = new OIMO.RigidBody( body_cnf )

        let shape_cnf = new OIMO.ShapeConfig()
        shape_cnf.geometry = new OIMO.BoxGeometry( new OIMO.Vec3( _arg.w / 2, _arg.h / 2, _arg.d / 2 ) )
        rigid_body.addShape( new OIMO.Shape( shape_cnf ) )

        this.oimo_world.addRigidBody( rigid_body )

        this.threejs.add_mesh( { body: rigid_body, w: _arg.w, h: _arg.h, d: _arg.d, color: _arg.color } )

        return rigid_body
    }

    render( _sec )
    {
        this.oimo_world.step( _sec )
        this.threejs.render()
    }
}

// MAIN
let oimo_p = new OIMO_PHYSICS( new THREEJS() )

const dataSet = [
    0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,
    0,0,0,0,0,0,3,3,3,3,3,0,0,1,1,1,
    0,0,0,0,0,3,3,3,3,3,3,3,3,3,1,1,
    0,0,0,0,0,2,2,2,1,1,2,1,0,3,3,3,
    0,0,0,0,2,1,2,1,1,1,2,1,1,3,3,3,
    0,0,0,0,2,1,2,2,1,1,1,2,1,1,1,3,
    0,0,0,0,2,2,1,1,1,1,2,2,2,2,3,0,
    0,0,0,0,0,0,1,1,1,1,1,1,1,3,0,0,
    0,0,3,3,3,3,3,5,3,3,3,5,3,0,0,0,
    0,3,3,3,3,3,3,3,5,3,3,3,5,0,0,2,
    1,1,3,3,3,3,3,3,5,5,5,5,5,0,0,2,
    1,1,1,0,5,5,3,5,5,4,5,5,4,5,2,2,
    0,1,0,2,5,5,5,5,5,5,5,5,5,5,2,2,
    0,0,2,2,2,5,5,5,5,5,5,5,5,5,2,2,
    0,2,2,2,5,5,5,5,5,5,5,0,0,0,0,0,
    0,2,0,0,5,5,5,5,0,0,0,0,0,0,0,0
]

const box_size = 1.5
for ( let y=0 ; y<16; y++ ) {
    for ( let x=0 ; x<16; x++ ) {
        oimo_p.create_rigid_body( {
            x: (x-7) * box_size * 0.95, y: box_size * 0.5, z: (y-7) * box_size * 1.2,
            w: box_size*0.2, h: box_size*1, d: box_size*1,
            color: [ 0xDCAA6B, 0xffcccc, 0x800000, 0xff0000, 0xffff00, 0x0000ff ][ dataSet[ y * 16 + x ] ],
        })
    }
}

for ( let i=0 ; i<16; i++ ) {
    oimo_p.create_rigid_body({
        x: -7 * box_size, y: box_size * 3, z: (i-7) * box_size * 1.2,
        w: box_size, h: box_size, d: box_size,
        color: 0xeeeeee
    })
}

function animate()
{
    oimo_p.render( 1 / 30 )
    window.requestAnimationFrame( animate )
}
window.requestAnimationFrame( animate )

</script>
</body>
</html>
```
おもに、Three.js と OimoPhysics の役割がわかりやすくなるように class を分けた。

「Javascript で class なんて使わないよねー」って言っていたのに、今回は使ってみちゃいました。
この使い方であっているかは疑問。

さて、これを足がかりにして、次はジョイントを理解して、最終的には「じゃがいもゲーム」を作りたい！！

ってか、Joint のコードがまったく見当たらないのですが。どの辺りから攻めたらよいのでしょう（呆然）

このあたりかなぁ
https://github.com/saharan/OimoPhysics/blob/master/demos/src/demo/core/JointsDemo.hx