##Three.jsでエドワード・マイブリッジの馬をアニメーションする
Wikipediaに載っているエドワード・マイブリッジの連続捨身?をThree.jsでアニメーションしてみた。
##使用したもの
- d3.js
- Three.js
##Three.jsでエドワード・マイブリッジの馬をアニメーションする
Wikipediaに載っているエドワード・マイブリッジの連続捨身?をThree.jsでアニメーションしてみた。
##使用したもの
<!DOCTYPE html> | |
<html vocab="http://schema.org" lang="ja"> | |
<head> | |
<title>YouTube Uploader</title> | |
<meta charset="utf-8" /> | |
<meta name="description" content="指定した音声ファイルと静止画をffmpegで動画化しYoutubeにアップロードします。" /> | |
<meta name="keywords" content="Youtube,d3.js,Q.js,jquery" /> | |
<meta name="author" content="sfpgmr" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" /> | |
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/d3/3.5.2/d3.js"></script> | |
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.js"></script> | |
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/q.js/1.1.2/q.min.js" ></script> | |
<!--<script type="text/javascript" src="./graphics.js"></script> --> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.2/normalize.min.css" /> | |
<style> | |
body { | |
margin: 0; | |
padding: 0; | |
overflow: hidden; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="content"></div> | |
<script type="text/javascript" src="index.js"></script> | |
<script> | |
</script> | |
</body> | |
</html> |
/// <reference path="http://cdnjs.cloudflare.com/ajax/libs/d3/3.5.2/d3.js" /> | |
/// <reference path="http://cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.js" /> | |
/// <reference path="./q.intellisense.js" /> | |
/// <reference path="./graphics.js" /> | |
"use strict" | |
var WIDTH = window.innerWidth, HEIGHT = window.innerHeight; | |
var renderer = new THREE.WebGLRenderer({ antialias: false, sortObjects: true }); | |
renderer.setSize(WIDTH, HEIGHT); | |
renderer.setClearColor(0x000000, 1); | |
renderer.domElement.id = 'console'; | |
renderer.domElement.className = 'console'; | |
renderer.domElement.style.zIndex = 0; | |
d3.select('#content').node().appendChild(renderer.domElement); | |
renderer.clear(); | |
// シーンの作成 | |
var scene = new THREE.Scene(); | |
// カメラの作成 | |
var camera = new THREE.PerspectiveCamera(90.0, WIDTH / HEIGHT); | |
camera.position.x = 0.0; | |
camera.position.y = 0.0; | |
camera.position.z = (WIDTH / 2.0) * HEIGHT / WIDTH; | |
camera.lookAt(new THREE.Vector3(0.0, 0.0, 0.0)); | |
// テクスチャー定義 | |
var textureFiles = { | |
totalTextureCount: 0, | |
promises: [], | |
textures: {} | |
}; | |
function createSpriteGeometry(width,height) { | |
var geometry = new THREE.Geometry(); | |
var sizeHalfX = width / 2; | |
var sizeHalfY = height / 2; | |
// geometry. | |
geometry.vertices.push(new THREE.Vector3(-sizeHalfX, sizeHalfY, 0)); | |
geometry.vertices.push(new THREE.Vector3(sizeHalfX, sizeHalfY, 0)); | |
geometry.vertices.push(new THREE.Vector3(sizeHalfX, -sizeHalfY, 0)); | |
geometry.vertices.push(new THREE.Vector3(-sizeHalfX, -sizeHalfY, 0)); | |
geometry.faces.push(new THREE.Face3(0, 2, 1)); | |
geometry.faces.push(new THREE.Face3(0, 3, 2)); | |
return geometry; | |
} | |
/// テクスチャー上の指定スプライトのUV座標を求める | |
function createSpriteUV(geometry, texture, cellWidth, cellHeight, cellNo) { | |
var width = texture.image.width; | |
var height = texture.image.height; | |
var uCellCount = (width / cellWidth) | 0; | |
var vCellCount = (height / cellHeight) | 0; | |
var vPos = vCellCount - ((cellNo / uCellCount) | 0); | |
var uPos = cellNo % uCellCount; | |
var uUnit = cellWidth / width; | |
var vUnit = cellHeight / height; | |
geometry.faceVertexUvs[0].push([ | |
new THREE.Vector2((uPos) * cellWidth / width, (vPos) * cellHeight / height), | |
new THREE.Vector2((uPos + 1) * cellWidth / width, (vPos - 1) * cellHeight / height), | |
new THREE.Vector2((uPos + 1) * cellWidth / width, (vPos) * cellHeight / height) | |
]); | |
geometry.faceVertexUvs[0].push([ | |
new THREE.Vector2((uPos) * cellWidth / width, (vPos) * cellHeight / height), | |
new THREE.Vector2((uPos) * cellWidth / width, (vPos - 1) * cellHeight / height), | |
new THREE.Vector2((uPos + 1) * cellWidth / width, (vPos - 1) * cellHeight / height) | |
]); | |
} | |
function updateSpriteUV(geometry, texture, cellWidth, cellHeight, cellNo) { | |
var width = texture.image.width; | |
var height = texture.image.height; | |
var uCellCount = (width / cellWidth) | 0; | |
var vCellCount = (height / cellHeight) | 0; | |
var vPos = vCellCount - ((cellNo / uCellCount) | 0); | |
var uPos = cellNo % uCellCount; | |
var uUnit = cellWidth / width; | |
var vUnit = cellHeight / height; | |
var uvs = geometry.faceVertexUvs[0][0]; | |
uvs[0].x = (uPos) * uUnit; | |
uvs[0].y = (vPos) * vUnit; | |
uvs[1].x = (uPos + 1) * uUnit; | |
uvs[1].y = (vPos - 1) * vUnit; | |
uvs[2].x = (uPos + 1) * uUnit; | |
uvs[2].y = (vPos) * vUnit; | |
uvs = geometry.faceVertexUvs[0][1]; | |
uvs[0].x = (uPos) * uUnit; | |
uvs[0].y = (vPos) * vUnit; | |
uvs[1].x = (uPos) * uUnit; | |
uvs[1].y = (vPos - 1) * vUnit; | |
uvs[2].x = (uPos + 1) * uUnit; | |
uvs[2].y = (vPos - 1) * vUnit; | |
geometry.uvsNeedUpdate = true; | |
} | |
function createSpriteMaterial(texture) { | |
// メッシュの作成・表示 /// | |
var material = new THREE.MeshBasicMaterial({ map: texture /*,depthTest:true*/, transparent: true }); | |
material.shading = THREE.FlatShading; | |
material.side = THREE.FrontSide; | |
material.alphaTest = 0.5; | |
material.needsUpdate = true; | |
// material. | |
return material; | |
} | |
function loadTexture(info) { | |
var defer = Q.defer(); | |
THREE.ImageUtils.loadTexture(info.path, {}, | |
function (texture) { | |
info.texture = texture; | |
if (info.path.match(/\.png/i)) { | |
texture.premultiplyAlpha = true; | |
} | |
texture.magFilter = THREE.NearestFilter; | |
texture.minFilter = THREE.LinearMipMapLinearFilter; | |
defer.resolve(info); | |
},// Error | |
function (e) { | |
defer.reject(e); | |
}); | |
return defer.promise; | |
} | |
var textureInfos = [ | |
{ name:'horse' ,path: './The_Horse_in_Motion.jpg'}, | |
{ name:'horse01',path:'./The_Horse_in_Motion01.jpg'} | |
]; | |
window.addEventListener('load',function(){ | |
Q.all( | |
textureInfos.map(function (info){ | |
return loadTexture(info); | |
})) | |
.then( | |
function (array){ | |
var textures = {}; | |
array.forEach(function(info){ | |
textures[info.name] = info.texture; | |
}); | |
// 馬の絵 | |
var horseMaterial = new THREE.MeshBasicMaterial({ map: textures.horse }); | |
horseMaterial.shading = THREE.FlatShading; | |
//material.antialias = false; | |
horseMaterial.transparent = true; | |
horseMaterial.alphaTest = 0.5; | |
horseMaterial.depthTest = true; | |
var horseMesh = new THREE.Mesh( | |
new THREE.PlaneGeometry(textures.horse.image.width, textures.horse.image.height), | |
horseMaterial | |
); | |
horseMesh.scale.x = 1.0; | |
horseMesh.scale.y = 1.0; | |
horseMesh.position.y = 0.0; | |
// 馬スプライト | |
var spriteWidth = textures.horse01.image.width / 4; | |
var spriteHeight = textures.horse01.image.height / 3; | |
var horseSpriteMaterial = createSpriteMaterial(textures.horse01); | |
var horseGeometry = createSpriteGeometry(spriteWidth,spriteHeight); | |
createSpriteUV(horseGeometry,textures.horse01,spriteWidth,spriteHeight,0); | |
var horseSpriteMesh = new THREE.Mesh(horseGeometry,horseSpriteMaterial); | |
scene.add(horseSpriteMesh); | |
var index = 0.0; | |
var speed = (1.0 / 60.0)/(60.0 / (143.0 * 11.0)) ; | |
function render(){ | |
requestAnimationFrame(render); | |
index += speed; | |
if(index > 10) index = 10.0 - index; | |
updateSpriteUV(horseGeometry,textures.horse01,spriteWidth,spriteHeight,parseInt(index)); | |
renderer.render(scene,camera); | |
} | |
render(); | |
} | |
).catch( | |
function (e){ | |
alert(e.stack); | |
} | |
); | |
}); | |