描画がおかしくなるバグもようやく直った。
ヒントは下記記事。
Converting SVG paths with holes to extruded shapes in three.js - Stack Overflow
ようするに穴あき部分のpathは馬ボディのshapeのholesにpathで追加しないとおかしくなるのであった。この部分はsvgの時点でどこが穴あきなのかわかる情報を付加しないとthree.jsではうまく変換することができない。なので私はpathのid属性に穴のpathは「holeXXXX」とつけて識別できるようにした。馬ボディのpathは「horsexx」としてある。
とりあえずChromeとFirefoxは動作することを確認した。Win10 Tech Preview 9926 のIE11では動作しなかった。
これでようやく次のステップに進める。しかしnw.jsとぜんぜん関係なくなってきてるな。。
動作サンプル
ソースコード・リソース
## SVGからthree.jsのshapeへの変換(2)
[エドワード・マイブリッジ](http://upload.wikimedia.org/wikipedia/commons/7/73/The_Horse_in_Motion.jpg)の「Horse in motion」をInkscapeでトレースし、各馬をセル化したものをthree.jsのshapeに変換し表示しています。
下記のURLから動くデモが見れます。Windows 10 Tech Preview 9926 のIE11では動作しませんでした。ひょっとするとIE11ではそもそも動作しないのかもしれません。原因は不明ですが。。
[http://bl.ocks.org/sfpgmr/855ad392435fcdd87584](http://bl.ocks.org/sfpgmr/855ad392435fcdd87584)
※前回のバグは下記記事が糸口となり解決しました。
[Converting SVG paths with holes to extruded shapes in three.js](http://stackoverflow.com/questions/16118274/converting-svg-paths-with-holes-to-extruded-shapes-in-three-js)
<!DOCTYPE html>
<html vocab="http://schema.org" lang="ja">
<head>
<title>SVGアニメーションのテスト</title>
<meta charset="utf-8" />
<meta name="description" content="SVGアニメーションのテスト" />
<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="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.2/d3.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.js"></script>
<script type="text/javascript" src="//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="//cdnjs.cloudflare.com/ajax/libs/normalize/3.0.2/normalize.min.css" />
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
}
#svg {
display:none;
}
</style>
</head>
<body>
<div id="content"></div>
<div id="svg"></div>
<div id="svgcell"></div>
<script type="text/javascript" src="index.js"></script>
<script>
</script>
</body>
</html>
//The MIT License (MIT)
//
//Copyright (c) 2015 Satoshi Fujiwara
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
/// <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="..\intellisense\q.intellisense.js" />
// stackoverflowより
// 絶対座標から相対座標への変換
// http://stackoverflow.com/questions/14179333/convert-svg-path-to-relative-commands
function convertToRelative(path) {
function set(type) {
var args = [].slice.call(arguments, 1)
, rcmd = 'createSVGPathSeg'+ type +'Rel'
, rseg = path[rcmd].apply(path, args);
segs.replaceItem(rseg, i);
}
var dx, dy, x0, y0, x1, y1, x2, y2, segs = path.pathSegList;
for (var x = 0, y = 0, i = 0, len = segs.numberOfItems; i < len; i++) {
var seg = segs.getItem(i)
, c = seg.pathSegTypeAsLetter;
if (/[MLHVCSQTAZz]/.test(c)) {
if ('x1' in seg) x1 = seg.x1 - x;
if ('x2' in seg) x2 = seg.x2 - x;
if ('y1' in seg) y1 = seg.y1 - y;
if ('y2' in seg) y2 = seg.y2 - y;
if ('x' in seg) dx = -x + (x = seg.x);
if ('y' in seg) dy = -y + (y = seg.y);
switch (c) {
case 'M': set('Moveto',dx,dy); break;
case 'L': set('Lineto',dx,dy); break;
case 'H': set('LinetoHorizontal',dx); break;
case 'V': set('LinetoVertical',dy); break;
case 'C': set('CurvetoCubic',dx,dy,x1,y1,x2,y2); break;
case 'S': set('CurvetoCubicSmooth',dx,dy,x2,y2); break;
case 'Q': set('CurvetoQuadratic',dx,dy,x1,y1); break;
case 'T': set('CurvetoQuadraticSmooth',dx,dy); break;
case 'A': set('Arc',dx,dy,seg.r1,seg.r2,seg.angle,
seg.largeArcFlag,seg.sweepFlag); break;
case 'Z': case 'z': x = x0; y = y0; break;
}
}
else {
if ('x' in seg) x += seg.x;
if ('y' in seg) y += seg.y;
}
// store the start of a subpath
if (c == 'M' || c == 'm') {
x0 = x;
y0 = y;
}
}
path.setAttribute('d', path.getAttribute('d').replace(/Z/g, 'z'));
}
// svg pathをthree.jsのshapeに変換する
// スペースの処理とy座標を反転するように修正
// From d3-threeD.js
// https://github.com/asutherland/d3-threeD
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
var DEGS_TO_RADS = Math.PI / 180, UNIT_SIZE = 100;
var DIGIT_0 = 48, DIGIT_9 = 57, COMMA = 44, SPACE = 32, PERIOD = 46, MINUS = 45;
function transformSVGPath(pathStr,obj) {
var path = obj ? new obj() : new THREE.Shape();
var idx = 1, len = pathStr.length, activeCmd,
x = 0, y = 0, nx = 0, ny = 0, firstX = null, firstY = null,
x1 = 0, x2 = 0, y1 = 0, y2 = 0,
rx = 0, ry = 0, xar = 0, laf = 0, sf = 0, cx, cy;
function eatNum() {
var sidx, c, isFloat = false, s;
// eat delims
while (idx < len) {
c = pathStr.charCodeAt(idx);
if (c !== COMMA && c !== SPACE)
break;
idx++;
}
if (c === MINUS)
sidx = idx++;
else
sidx = idx;
// eat number
while (idx < len) {
c = pathStr.charCodeAt(idx);
if (DIGIT_0 <= c && c <= DIGIT_9) {
idx++;
continue;
}
else if (c === PERIOD) {
idx++;
isFloat = true;
continue;
}
s = pathStr.substring(sidx, idx);
return isFloat ? parseFloat(s) : parseInt(s);
}
s = pathStr.substring(sidx);
return isFloat ? parseFloat(s) : parseInt(s);
}
function nextIsNum() {
var c;
// do permanently eat any delims...
while (idx < len) {
c = pathStr.charCodeAt(idx);
if (c !== COMMA && c !== SPACE)
break;
idx++;
}
c = pathStr.charCodeAt(idx);
return (c === MINUS || (DIGIT_0 <= c && c <= DIGIT_9));
}
var canRepeat;
activeCmd = pathStr[0];
while (idx <= len) {
canRepeat = true;
switch (activeCmd) {
// moveto commands, become lineto's if repeated
case 'M':
x = eatNum();
y = -eatNum();
path.moveTo(x, y);
activeCmd = 'L';
firstX = x;
firstY = y;
break;
case 'm':
x += eatNum();
y += -eatNum();
path.moveTo(x, y);
activeCmd = 'l';
firstX = x;
firstY = y;
break;
case 'Z':
case 'z':
canRepeat = false;
if (x !== firstX || y !== firstY)
path.lineTo(firstX, firstY);
break;
// - lines!
case 'L':
case 'H':
case 'V':
nx = (activeCmd === 'V') ? x : eatNum();
ny = (activeCmd === 'H') ? y : -eatNum();
path.lineTo(nx, ny);
x = nx;
y = ny;
break;
case 'l':
case 'h':
case 'v':
nx = (activeCmd === 'v') ? x : (x + eatNum());
ny = (activeCmd === 'h') ? y : (y + -eatNum());
path.lineTo(nx, ny);
x = nx;
y = ny;
break;
// - cubic bezier
case 'C':
x1 = eatNum(); y1 = -eatNum();
case 'S':
if (activeCmd === 'S') {
x1 = 2 * x - x2; y1 = 2 * y - y2;
}
x2 = eatNum();
y2 = -eatNum();
nx = eatNum();
ny = -eatNum();
path.bezierCurveTo(x1, y1, x2, y2, nx, ny);
x = nx; y = ny;
break;
case 'c':
x1 = x + eatNum();
y1 = y + -eatNum();
case 's':
if (activeCmd === 's') {
x1 = 2 * x - x2;
y1 = 2 * y - y2;
}
x2 = x + eatNum();
y2 = y + -eatNum();
nx = x + eatNum();
ny = y + -eatNum();
path.bezierCurveTo(x1, y1, x2, y2, nx, ny);
x = nx; y = ny;
break;
// - quadratic bezier
case 'Q':
x1 = eatNum(); y1 = -eatNum();
case 'T':
if (activeCmd === 'T') {
x1 = 2 * x - x1;
y1 = 2 * y - y1;
}
nx = eatNum();
ny = -eatNum();
path.quadraticCurveTo(x1, y1, nx, ny);
x = nx;
y = ny;
break;
case 'q':
x1 = x + eatNum();
y1 = y + -eatNum();
case 't':
if (activeCmd === 't') {
x1 = 2 * x - x1;
y1 = 2 * y - y1;
}
nx = x + eatNum();
ny = y + -eatNum();
path.quadraticCurveTo(x1, y1, nx, ny);
x = nx; y = ny;
break;
// - elliptical arc
case 'A':
rx = eatNum();
ry = eatNum();
xar = eatNum() * DEGS_TO_RADS;
laf = eatNum();
sf = eatNum();
nx = eatNum();
ny = -eatNum();
if (rx !== ry) {
console.warn("Forcing elliptical arc to be a circular one :(",
rx, ry);
}
// SVG implementation notes does all the math for us! woo!
// http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
// step1, using x1 as x1'
x1 = Math.cos(xar) * (x - nx) / 2 + Math.sin(xar) * (y - ny) / 2;
y1 = -Math.sin(xar) * (x - nx) / 2 + Math.cos(xar) * (y - ny) / 2;
// step 2, using x2 as cx'
var norm = Math.sqrt(
(rx*rx * ry*ry - rx*rx * y1*y1 - ry*ry * x1*x1) /
(rx*rx * y1*y1 + ry*ry * x1*x1));
if (laf === sf)
norm = -norm;
x2 = norm * rx * y1 / ry;
y2 = norm * -ry * x1 / rx;
// step 3
cx = Math.cos(xar) * x2 - Math.sin(xar) * y2 + (x + nx) / 2;
cy = Math.sin(xar) * x2 + Math.cos(xar) * y2 + (y + ny) / 2;
var u = new THREE.Vector2(1, 0),
v = new THREE.Vector2((x1 - x2) / rx,
(y1 - y2) / ry);
var startAng = Math.acos(u.dot(v) / u.length() / v.length());
if (u.x * v.y - u.y * v.x < 0)
startAng = -startAng;
// we can reuse 'v' from start angle as our 'u' for delta angle
u.x = (-x1 - x2) / rx;
u.y = (-y1 - y2) / ry;
var deltaAng = Math.acos(v.dot(u) / v.length() / u.length());
// This normalization ends up making our curves fail to triangulate...
if (v.x * u.y - v.y * u.x < 0)
deltaAng = -deltaAng;
if (!sf && deltaAng > 0)
deltaAng -= Math.PI * 2;
if (sf && deltaAng < 0)
deltaAng += Math.PI * 2;
path.absarc(cx, cy, rx, startAng, startAng + deltaAng, sf);
x = nx;
y = ny;
break;
default:
throw new Error("weird path command: " + activeCmd);
}
// just reissue the command
if (canRepeat && nextIsNum())
continue;
activeCmd = pathStr[idx++];
}
return path;
}
// from gist
// https://gist.github.com/gabrielflorit/3758456
function createShape( shape, color, x, y, z, rx, ry, rz, s ) {
// flat shape
var geometry = new THREE.ShapeGeometry( shape );
var material = new THREE.MeshBasicMaterial({
color: color,
side: THREE.DoubleSide,
overdraw: true
});
var mesh = new THREE.Mesh( geometry, material );
mesh.position.set( x, y, z );
mesh.rotation.set( rx, ry, rz );
mesh.scale.set( s, s, s );
return mesh;
}
// メイン
window.addEventListener('load',function(){
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));
window.addEventListener('resize',function()
{
WIDTH = window.innerWidth;
HEIGHT = window.innerHeight;
renderer.setSize(WIDTH,HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.position.z = (WIDTH / 2.0) * HEIGHT / WIDTH;
camera.updateProjectionMatrix();
});
var xml = Q.nfbind(d3.xml);
var gto;
// SVGファイルから馬のメッシュを作る
xml('./horse03.svg','image/svg+xml')
.then(function(svg){
try {
document.querySelector('#svg').appendChild(svg.firstChild);
d3.select('#svg').selectAll('g').each(function(){
var g = d3.select(this);
var boundingBox = g.select('rect').node();
var paths = g.selectAll('path');
var holes = [];
var shape = null;
var shapeId = null;
paths.each(function(){
// 馬セルの取り出しと座標補正
var path = d3.select(this);
convertToRelative(path.node());
var m = path.node().createSVGPathSegMovetoRel
(path.node().pathSegList[0].x - boundingBox.x.baseVal.value - boundingBox.width.baseVal.value / 2.0,
path.node().pathSegList[0].y - boundingBox.y.baseVal.value - boundingBox.height.baseVal.value / 2.0
);
path.node().pathSegList.replaceItem(m,0);
path.attr('d',path.attr('d'));
// svg pathからthree.js shape Meshへの変換
if(path.attr('id').match(/hole/)){
holes.push(transformSVGPath(path.attr('d'),THREE.Path));
} else {
shape = transformSVGPath(path.attr('d'));
shapeId = path.attr('id');
}
});
holes.forEach(function(d){
shape.holes.push(d);
});
var shapeMesh = createShape(shape,0xFFFF00,0,0,0,0,0,0,1.0);
shapeMesh.visible = false;
shapeMesh.name = shapeId;
scene.add(shapeMesh);
});
d3.select('#svg').remove();
} catch (e) {
console.log(e + '\n' + e.stack);
}
//レンダリング
(function render(index){
if(index > 10.0) index = 0.0;
var idx = parseInt(index,10);
scene.getObjectByName('horse' + ('00' + idx.toString(10)).slice(-2)).visible = true;
if(idx == 0){
scene.getObjectByName('horse10').visible = false;
} else {
scene.getObjectByName('horse' + ('00' + (idx - 1).toString(10)).slice(-2)).visible = false;
}
renderer.render(scene,camera);
index += 0.25;
requestAnimationFrame(render.bind(null,index));
})(0);
// console.log(d3.select('#svg').html());
});
});