Webで3Dを簡単に扱えるようになる「three.js」を使い、ボール軌道の描画に挑戦してみます。
なお私は、ボールの運動方程式をまったく知りません。
運動方程式を調べよう
ボールの運動方程式は、ネットで簡単に見つけられます。
とりあえず、検索1番目に出てきた「いろいろな運動 」というPDFを開いてみました。
・・・よし、見なかったことにしよう。
イージングアニメーションを活用
今持っているWeb知識のみでどうにかします。
ボールの動きは、jQueryアニメーションのイージング「Easing function」 を利用できるかもしれません。
バウンドは「easeOutBounce」、減速は「easeOutCubic」を使い、適当な<span>にアニメーションを割り当ててみます。
<span id="ballX"><span id="ballY" class="ball"></span><span id="ballZ" class="ball"></span></span>
var $ballX = $("#ballX"),
$ballY = $("#ballY"),
$ballZ = $("#ballZ");
$ballX.animate({
'left': '500px'
},{
'duration': 3000,
'easing': 'easeOutCubic'
});
$ballY.animate({
'top': '-200px'
},{
'duration': 3000,
'easing': 'easeOutCubic'
});
$ballZ.animate({
'top': '100px'
},{
'duration': 2000,
'easing': 'easeOutBounce'
});
コートを上から見て、ボールの横位置がx、縦位置がy、高さがzとなる想定です。
あとは60ミリ秒おきに各spanの座標を配列に変換するスクリプトを実行すると・・・
[{"x":-368.33,"y":206.92,"z":107.49},{"x":-336.53,"y":194.17,"z":105.6},{"x":-306.63,"y":182.19,"z":102.08},{"x":-284.88,"y":173.47,"z":98.32},{"x":-256.94,"y":162.27,"z":91.8},{"x":-236.98,"y":154.27,"z":85.82},{"x":-211,"y":143.86,"z":76.17},{"x":-187.42,"y":134.39,"z":65.33},{"x":-170.08,"y":127.45,"z":55.89},{"x":-147.94,"y":118.56,"z":41.77},{"x":-132.19,"y":112.25,"z":30.13},{"x":-112.11,"y":104.2,"z":13.03},{"x":-93.44,"y":96.72,"z":13.82},{"x":-80.02,"y":91.33,"z":19.33},{"x":-62.98,"y":84.5,"z":25.25},{"x":-50.95,"y":79.67,"z":28.57},{"x":-35.72,"y":73.56,"z":31.5},{"x":-21.69,"y":67.92,"z":32.75},{"x":-11.69,"y":63.92,"z":32.6},{"x":0.91,"y":58.86,"z":30.91},{"x":9.72,"y":55.33,"z":28.55},{"x":20.77,"y":50.89,"z":23.89},{"x":30.83,"y":46.86,"z":17.66},{"x":37.92,"y":44,"z":11.83},{"x":46.73,"y":40.45,"z":10.07},{"x":52.83,"y":38.02,"z":12.33},{"x":60.36,"y":34.98,"z":13.91},{"x":67.11,"y":32.28,"z":13.78},{"x":71.88,"y":30.36,"z":12.57},{"x":77.52,"y":28.09,"z":9.53},{"x":81.39,"y":26.55,"z":8.49},{"x":86.08,"y":24.66,"z":9.36},{"x":90.17,"y":23.02,"z":8.58},{"x":92.94,"y":21.89,"z":7.8},{"x":96.22,"y":20.58,"z":7.8},{"x":98.38,"y":19.7,"z":7.8},{"x":100.89,"y":18.69,"z":7.8},{"x":103,"y":17.84,"z":7.8},{"x":104.34,"y":17.3,"z":7.8},{"x":105.88,"y":16.69,"z":7.8},{"x":106.52,"y":16.42,"z":7.8},{"x":107.81,"y":15.89,"z":7.8},{"x":108.59,"y":15.58,"z":7.8},{"x":109.03,"y":15.41,"z":7.8},{"x":109.47,"y":15.22,"z":7.8},{"x":109.69,"y":15.14,"z":7.8},{"x":109.86,"y":15.06,"z":7.8},{"x":109.95,"y":15.02,"z":7.8},{"x":109.98,"y":15.02,"z":7.8},{"x":110,"y":15,"z":7.8},{"x":110,"y":15,"z":7.8}]
三次元の座標配列が書き出されました。
three.jsでボール軌道を描画
座標データさえできれば、ボールの軌道を描画していくのは簡単です。
60ミリ秒おきに、半透明のボールを描画していきます。
window.addEventListener('DOMContentLoaded', init);
function init() {
"use strict";
var canvasID = "#court",
width = 1280,
height = 720,
ballSize = 8.6, //ボール直径
ballDents = 0.8, //ボールのへこみ
balls = {};
var orbit = [{"x":-368.33,"y":206.92,"z":107.49},{"x":-336.53,"y":194.17,"z":105.6},{"x":-306.63,"y":182.19,"z":102.08},{"x":-284.88,"y":173.47,"z":98.32},{"x":-256.94,"y":162.27,"z":91.8},{"x":-236.98,"y":154.27,"z":85.82},{"x":-211,"y":143.86,"z":76.17},{"x":-187.42,"y":134.39,"z":65.33},{"x":-170.08,"y":127.45,"z":55.89},{"x":-147.94,"y":118.56,"z":41.77},{"x":-132.19,"y":112.25,"z":30.13},{"x":-112.11,"y":104.2,"z":13.03},{"x":-93.44,"y":96.72,"z":13.82},{"x":-80.02,"y":91.33,"z":19.33},{"x":-62.98,"y":84.5,"z":25.25},{"x":-50.95,"y":79.67,"z":28.57},{"x":-35.72,"y":73.56,"z":31.5},{"x":-21.69,"y":67.92,"z":32.75},{"x":-11.69,"y":63.92,"z":32.6},{"x":0.91,"y":58.86,"z":30.91},{"x":9.72,"y":55.33,"z":28.55},{"x":20.77,"y":50.89,"z":23.89},{"x":30.83,"y":46.86,"z":17.66},{"x":37.92,"y":44,"z":11.83},{"x":46.73,"y":40.45,"z":10.07},{"x":52.83,"y":38.02,"z":12.33},{"x":60.36,"y":34.98,"z":13.91},{"x":67.11,"y":32.28,"z":13.78},{"x":71.88,"y":30.36,"z":12.57},{"x":77.52,"y":28.09,"z":9.53},{"x":81.39,"y":26.55,"z":8.49},{"x":86.08,"y":24.66,"z":9.36},{"x":90.17,"y":23.02,"z":8.58},{"x":92.94,"y":21.89,"z":7.8},{"x":96.22,"y":20.58,"z":7.8},{"x":98.38,"y":19.7,"z":7.8},{"x":100.89,"y":18.69,"z":7.8},{"x":103,"y":17.84,"z":7.8},{"x":104.34,"y":17.3,"z":7.8},{"x":105.88,"y":16.69,"z":7.8},{"x":106.52,"y":16.42,"z":7.8},{"x":107.81,"y":15.89,"z":7.8},{"x":108.59,"y":15.58,"z":7.8},{"x":109.03,"y":15.41,"z":7.8},{"x":109.47,"y":15.22,"z":7.8},{"x":109.69,"y":15.14,"z":7.8},{"x":109.86,"y":15.06,"z":7.8},{"x":109.95,"y":15.02,"z":7.8},{"x":109.98,"y":15.02,"z":7.8},{"x":110,"y":15,"z":7.8},{"x":110,"y":15,"z":7.8}];
function createBall(x,y,z,color,id){
var colorData;
id = id || "";
id = color + id;
if(color === "red"){
colorData = 0xb71c1c;
}
else if(color === "blue"){
colorData = 0x0D47A1;
}
else{
colorData = 0xFFFFFF;
}
var geometry = new THREE.SphereGeometry(ballSize, 24, 24);
var material = new THREE.MeshLambertMaterial({color: colorData});
balls[id] = new THREE.Mesh(geometry, material);
balls[id].position.set(x, y, z);
scene.add(balls[id]);
}
function createOrbitBall(x,y,z,color){
var colorData;
if(color === "red"){
colorData = 0xb71c1c;
}
else if(color === "blue"){
colorData = 0x0D47A1;
}
else{
colorData = 0xFFFFFF;
}
var geometry = new THREE.SphereGeometry(ballSize, 12, 12);
var material = new THREE.MeshLambertMaterial({
color: colorData, wireframe: true,
transparent: true,
opacity: 0.1}
);
var orbitBall = new THREE.Mesh(geometry, material);
orbitBall.position.set(x, y, z);
scene.add(orbitBall);
}
function createCourt(){
var loader = new THREE.TextureLoader();
loader.load("common/img/court.png", function(texture){
readiness(texture);
});
function readiness(texture){
var geometry = new THREE.PlaneGeometry(1258, 608);
var material = new THREE.MeshPhongMaterial({color: 0xFFFFFF,map: texture});
var ground = new THREE.Mesh(geometry, material);
scene.add(ground);
camera.lookAt(ground.position);
}
}
// レンダラー作成
var renderer = new THREE.WebGLRenderer({
canvas: document.querySelector(canvasID),
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(width, height);
// シーン作成
var scene = new THREE.Scene();
// カメラ作成
var camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000);
camera.position.set(0, -700, 500);
// ボール作成
var z = ballSize - ballDents;
createBall(-440, 215, z, "jack");
createBall(-390, 215, z, "red", 1);
createBall(-415, 215, z, "red", 2);
createBall(-390, 20, z, "red", 3);
createBall(-415, 20, z, "red", 4);
createBall(-390, -175, z, "red", 5);
createBall(-415, -175, z, "red", 6);
createBall(-390, 115, z, "blue", 1);
createBall(-415, 115, z, "blue", 2);
createBall(-390, -80, z, "blue", 3);
createBall(-415, -80, z, "blue", 4);
createBall(-390, -275, z, "blue", 5);
createBall(-415, -275, z, "blue", 6);
createCourt();
// 平行光源
var light = new THREE.DirectionalLight(0xFFFFFF);
light.intensity = 2;
light.position.set(-2, -1, 10);
scene.add(light);
// 環境光源
var ambient = new THREE.AmbientLight(0x222222);
ambient.intensity = 4;
scene.add(ambient);
// アニメーション実行
var i = 0;
var animOrbit = function(){
if(i < orbit.length){
balls.jack.position.set(orbit[i].x, orbit[i].y, orbit[i].z);
createOrbitBall(orbit[i].x, orbit[i].y, orbit[i].z,"jack");
renderer.render(scene, camera);
i++;
}
else{
clearInterval(setOrbit);
}
};
var setOrbit = window.setInterval(animOrbit, 60);
}
実行してみると・・・
アニメーションしながら、自然なボールの軌道を描画することができました。
さらにカメラをマウスで操作できるように改修し、いろいろなアングルで見られるようにしました。
最終的に、現実のコートで複数カメラで撮影した動画を、OpenCVで三次元座標に変換し、ボールの軌道を可視化します。
なので、ボールの軌道を自作する必要はなかったのですが・・・試しです。