0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

wslでelixir その187

0
Posted at

概要

wsl(wsl2じゃない)で、elixirやってみた。
Livebookで、box2dwebkino、書いてみた。

box2dwebとは

Box2DWebとは、C++で開発された有名な2D物理演算エンジン「Box2D」を、JavaScriptに移植したオープンソースのライブラリです。
Webブラウザ上で、重力、物体の質量、摩擦、跳ね返り(弾性)といった古典力学的な物理シミュレーションをリアルタイムに計算・描画することができます。
かつて大ヒットしたゲーム『Angry Birds』などにもBox2Dの技術が使われており、Webゲームやインタラクティブなコンテンツの開発で広く利用されてきました。

写真

image.png

setup

Mix.install([
  {:kino, "~> 0.19"}
])

サンプルコード

defmodule Box2DWebKino do
  use Kino.JS

  def new() do
    Kino.JS.new(__MODULE__, nil)
  end

  asset "main.js" do
    """
    export async function init(ctx, data) {
      const STAGE_WIDTH = 450;
      const STAGE_HEIGHT = 450;
      const SCALE = 30;
      const FPS = 1000 / 60;

      ctx.root.innerHTML = `
        <div style="padding: 10px; background: #222; border-radius: 8px; width: ${STAGE_WIDTH}px; margin: 0 auto; user-select: none;">
          <canvas id="canvas" width="${STAGE_WIDTH}" height="${STAGE_HEIGHT}" style="background: #111; display: block; cursor: crosshair;"></canvas>
          <div style="color: #aaa; font-family: monospace; font-size: 12px; margin-top: 5px; text-align: center;">
            Box2DWeb + Kino.JS (Drag & Drop Physics)
          </div>
        </div>
      `;

      const canvas = ctx.root.querySelector("#canvas");
      const canvasCtx = canvas.getContext("2d");

      await new Promise((resolve, reject) => {
        const script = document.createElement("script");
        script.src = "https://cdn.jsdelivr.net/npm/box2dweb@2.1.0-b/box2d.min.js";
        script.onload = resolve;
        script.onerror = reject;
        ctx.root.appendChild(script);
      });

      const b2Vec2 = Box2D.Common.Math.b2Vec2;
      const b2BodyDef = Box2D.Dynamics.b2BodyDef;
      const b2Body = Box2D.Dynamics.b2Body;
      const b2FixtureDef = Box2D.Dynamics.b2FixtureDef;
      const b2World = Box2D.Dynamics.b2World;
      const b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape;
      const b2CircleShape = Box2D.Collision.Shapes.b2CircleShape;
      const b2DebugDraw = Box2D.Dynamics.b2DebugDraw;
      const b2MouseJointDef = Box2D.Dynamics.Joints.b2MouseJointDef;
      const b2AABB = Box2D.Collision.b2AABB;

      const world = new b2World(new b2Vec2(0, 10), true);
      const fixDef = new b2FixtureDef();
      fixDef.density = 1.0;
      fixDef.friction = 0.5;
      fixDef.restitution = 0.2;
      const bodyDef = new b2BodyDef();
      bodyDef.type = b2Body.b2_staticBody;
      bodyDef.position.Set(STAGE_WIDTH / 2 / SCALE, (STAGE_HEIGHT - 10) / SCALE);
      fixDef.shape = new b2PolygonShape();
      fixDef.shape.SetAsBox((STAGE_WIDTH / 2) / SCALE, 10 / SCALE);
      world.CreateBody(bodyDef).CreateFixture(fixDef);
      bodyDef.position.Set(STAGE_WIDTH / 2 / SCALE, 10 / SCALE);
      world.CreateBody(bodyDef).CreateFixture(fixDef);
      bodyDef.position.Set(10 / SCALE, STAGE_HEIGHT / 2 / SCALE);
      fixDef.shape.SetAsBox(10 / SCALE, (STAGE_HEIGHT / 2) / SCALE);
      world.CreateBody(bodyDef).CreateFixture(fixDef);
      bodyDef.position.Set((STAGE_WIDTH - 10) / SCALE, STAGE_HEIGHT / 2 / SCALE);
      world.CreateBody(bodyDef).CreateFixture(fixDef);
      bodyDef.type = b2Body.b2_dynamicBody;
      for (let i = 0; i < 20; ++i) 
      {
        bodyDef.position.Set((Math.random() * (STAGE_WIDTH - 60) + 30) / SCALE, (Math.random() * (STAGE_HEIGHT - 60) + 30) / SCALE);
        if (Math.random() > 0.5) 
        {
          fixDef.shape = new b2CircleShape(Math.random() * 15 / SCALE + 10 / SCALE);
        } 
        else 
        {
          fixDef.shape = new b2PolygonShape();
          fixDef.shape.SetAsBox((Math.random() * 15 + 10) / SCALE, (Math.random() * 15 + 10) / SCALE);
        }
        world.CreateBody(bodyDef).CreateFixture(fixDef);
      }
      const debugDraw = new b2DebugDraw();
      debugDraw.SetSprite(canvasCtx);
      debugDraw.SetDrawScale(SCALE);
      debugDraw.SetFillAlpha(0.3);
      debugDraw.SetLineThickness(1.0);
      debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
      world.SetDebugDraw(debugDraw);
      let mouseJoint = null;
      let mousePVec = new b2Vec2();
      let isMouseDown = false;
      function getCanvasMousePos(e) {
        const rect = canvas.getBoundingClientRect();
        const clientX = e.touches ? e.touches[0].clientX : e.clientX;
        const clientY = e.touches ? e.touches[0].clientY : e.clientY;
        return new b2Vec2((clientX - rect.left) / SCALE, (clientY - rect.top) / SCALE);
      }
      function getBodyAtMouse(pX, pY) {
        mousePVec.Set(pX, pY);
        const aabb = new b2AABB();
        aabb.lowerBound.Set(pX - 0.001, pY - 0.001);
        aabb.upperBound.Set(pX + 0.001, pY + 0.001);
        let selectedBody = null;
        world.QueryAABB(function(fixture) {
          if (fixture.GetBody().GetType() !== b2Body.b2_staticBody) 
          {
            if (fixture.GetShape().TestPoint(fixture.GetBody().GetTransform(), mousePVec)) 
            {
              selectedBody = fixture.GetBody();
              return false;
            }
          }
          return true;
        }, aabb);
        return selectedBody;
      }
      function handleMouseDown(e) {
        isMouseDown = true;
        const p = getCanvasMousePos(e);
        const body = getBodyAtMouse(p.x, p.y);        
        if (body) 
        {
          const md = new b2MouseJointDef();
          md.bodyA = world.GetGroundBody();
          md.bodyB = body;
          md.target.Set(p.x, p.y);
          md.collideConnected = true;
          md.maxForce = 300.0 * body.GetMass();
          mouseJoint = world.CreateJoint(md);
          body.SetAwake(true);
        }
      }
      function handleMouseMove(e) {
        if (!isMouseDown) return;
        const p = getCanvasMousePos(e);
        if (mouseJoint) 
        {
          mouseJoint.SetTarget(new b2Vec2(p.x, p.y));
        }
      }
      function handleMouseUp() {
        isMouseDown = false;
        if (mouseJoint) 
        {
          world.DestroyJoint(mouseJoint);
          mouseJoint = null;
        }
      }
      canvas.addEventListener("mousedown", handleMouseDown);
      canvas.addEventListener("mousemove", handleMouseMove);
      window.addEventListener("mouseup", handleMouseUp);
      canvas.addEventListener("touchstart", handleMouseDown, { passive: true });
      canvas.addEventListener("touchmove", handleMouseMove, { passive: true });
      canvas.addEventListener("touchend", handleMouseUp);
      function update() {
        world.Step(1 / 60, 10, 10);
        world.DrawDebugData();
        world.ClearForces();
      }
      const loopInterval = setInterval(update, FPS);
      ctx.handleDestroy(() => {
        clearInterval(loopInterval);
        canvas.removeEventListener("mousedown", handleMouseDown);
        canvas.removeEventListener("mousemove", handleMouseMove);
        window.removeEventListener("mouseup", handleMouseUp);
        canvas.removeEventListener("touchstart", handleMouseDown);
        canvas.removeEventListener("touchmove", handleMouseMove);
        canvas.removeEventListener("touchend", handleMouseUp);
      });
    }
    """
  end
end


Box2DWebKino.new()

以上。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?