diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..b093415 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,44 @@ +module.exports = function(grunt) { + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + + indoormap: { + // potential config here + }, + autoprefixer: { + dist: { + files: { + 'css/app.css': 'css/app.css' + } + }, + }, + sass: { + dist: { + options: { + outputStyle: 'compressed' + }, + files: { + 'css/app.css': 'scss/app.scss' + } + } + }, + watch: { + sass: { + files: ['scss/*.scss'], + tasks: ['sass', 'autoprefixer'], + }, + livereload: { + files: ['*.js', 'css/app.css'], + options: { + livereload: true + } + } + } + }); + + // load all grunt tasks + require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); + + grunt.registerTask('build', ['sass']); + grunt.registerTask('default', ['build','watch']); +}; diff --git a/README.md b/README.md index 2daa421..3943628 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,5 @@ - Wireframe of room per floor tagged with name of room - Highlight room by drawing color semitransparent glowing cube on tob of room floor - Controller by query param and browser controls + +# Ref: http://threejs.org/examples/#webgl_lines_colors diff --git a/css/app.css b/css/app.css new file mode 100644 index 0000000..33d21f8 --- /dev/null +++ b/css/app.css @@ -0,0 +1 @@ +body,html{width:100%;height:100%;padding:0;margin:0;overflow:hidden;}canvas{width:100%;height:100%;}.debug #svg{position:absolute;top:0;bottom:0;left:0;right:0;width:100%;height:100%;} \ No newline at end of file diff --git a/data/neuf04.svg b/data/neuf04.svg index 59e61d6..f2fbd4c 100644 --- a/data/neuf04.svg +++ b/data/neuf04.svg @@ -15,10 +15,10 @@ width="1000" height="1000" xml:space="preserve" - sodipodi:docname="neuf04_simplified_bare.svg">image/svg+xml \ No newline at end of file + inkscape:label="#rect11470" /> diff --git a/data/rooms.json b/data/rooms.json index 0a8b7e2..d106b5a 100644 --- a/data/rooms.json +++ b/data/rooms.json @@ -1 +1,506 @@ -{"rooms":[{"number":"447","pos":{"x":"53.564445","y":"-158.08394"},"size":{"height":"77.731247","width":"77.904381"}},{"number":"450","pos":{"x":"318.01108","y":"-185.0863"},"size":{"height":"69.93187","width":"36.000183"}},{"number":"448","pos":{"x":"354.71475","y":"-185.08415"},"size":{"height":"100.39677","width":"112.169"}},{"number":"423","pos":{"x":"466.89041","y":"-185.09079"},"size":{"height":"101.79505","width":"146.80646"}},{"number":"427","pos":{"x":"665.62469","y":"-230.11064"},"size":{"height":"45.028267","width":"76.190018"}},{"number":"437","pos":{"x":"756.34747","y":"-184.39943"},"size":{"height":"291.53769","width":"35.318886"}},{"number":["428","429"],"pos":{"x":"791.67664","y":"-184.38683"},"size":{"height":"146.09106","width":"117.69923"}},{"number":"430","pos":{"x":"793.05017","y":"-36.899376"},"size":{"height":"68.555847","width":"117.72215"}},{"number":"431","pos":{"x":"793.05017","y":"30.963974"},"size":{"height":"76.865646","width":"118.41465"}},{"number":"440","pos":{"x":"792.3576","y":"107.82961"},"size":{"height":"146.11398","width":"118.41465"}},{"number":"436","pos":{"x":"756.38678","y":"107.17565"},"size":{"height":"294.92099","width":"35.932209"}},{"number":"435","pos":{"x":"792.3576","y":"321.80695"},"size":{"height":"81.020546","width":"117.02967"}},{"number":"444","pos":{"x":"685.02264","y":"474.84579"},"size":{"height":"71.325775","width":"72.710747"}},{"number":"402","pos":{"x":"827.67426","y":"498.39026"},"size":{"height":"44.318932","width":"72.710747"}},{"number":"420","pos":{"x":"758.47394","y":"581.2832"},"size":{"height":"115.85214","width":"69.047668"}},{"number":"411","pos":{"x":"22.717258","y":"580.50562"},"size":{"height":"82.557472","width":"53.380753"}},{"number":"403","pos":{"x":"152.27817","y":"580.30676"},"size":{"height":"38.193451","width":"43.090046"}},{"number":"409","pos":{"x":"195.37247","y":"580.31085"},"size":{"height":"78.540001","width":"49.447254"}},{"number":"412","pos":{"x":"23.009211","y":"546.86469"},"size":{"height":"33.930614","width":"589.30219"}},{"number":"407","pos":{"x":"319.73325","y":"580.29822"},"size":{"height":"117.53546","width":"75.99839"}},{"number":"406","pos":{"x":"395.63904","y":"580.30676"},"size":{"height":"118.00798","width":"70.021324"}},{"number":"405","pos":{"x":"465.17068","y":"580.79633"},"size":{"height":"117.02865","width":"146.89789"}},{"number":"404","pos":{"x":"612.0686","y":"581.7757"},"size":{"height":"115.55968","width":"76.386887"}},{"number":"421","pos":{"x":"688.44983","y":"581.48309"},"size":{"height":"115.65492","width":"70.522224"}},{"number":"419","pos":{"x":"827.03021","y":"543.58331"},"size":{"height":"153.60759","width":"80.791679"}},{"number":"422","pos":{"x":"22.019121","y":"108.75488"},"size":{"height":"32.623585","width":"34.29538"}},{"number":"417","pos":{"x":"22.518375","y":"141.57167"},"size":{"height":"205.65706","width":"105.27682"}},{"number":"414","pos":{"x":"23.008051","y":"401.58087"},"size":{"height":"43.579708","width":"71.490303"}},{"number":"413","pos":{"x":"23.008051","y":"444.6709"},"size":{"height":"102.8285","width":"71.490265"}}]} \ No newline at end of file +{ + "rooms": [ + { + "number": "447", + "position": { + "x": 32.507306, + "y": 106.53646 + }, + "dimensions": { + "height": "77.731247", + "width": "77.904381" + } + }, + { + "number": "424", + "position": { + "x": 8.183843, + "y": 81.95326000000003 + }, + "dimensions": { + "height": "102.3144", + "width": "24.410036" + } + }, + { + "number": "424", + "position": { + "x": 32.420785, + "y": 81.60706000000002 + }, + "dimensions": { + "height": "24.756271", + "width": "77.904366" + } + }, + { + "number": "450", + "position": { + "x": 296.953941, + "y": 79.53410000000002 + }, + "dimensions": { + "height": "69.93187", + "width": "36.000183" + } + }, + { + "number": "448", + "position": { + "x": 333.657611, + "y": 79.53625000000002 + }, + "dimensions": { + "height": "100.39677", + "width": "112.169" + } + }, + { + "number": "423", + "position": { + "x": 445.83327099999997, + "y": 79.52961000000002 + }, + "dimensions": { + "height": "101.79505", + "width": "146.80646" + } + }, + { + "number": "426", + "position": { + "x": 592.562431, + "y": 80.28223000000003 + }, + "dimensions": { + "height": "66.478394", + "width": "142.67001" + } + }, + { + "number": "426", + "position": { + "x": 596.241571, + "y": 48.68484000000001 + }, + "dimensions": { + "height": "31.161745", + "width": "48.699829" + } + }, + { + "number": "427", + "position": { + "x": 644.567551, + "y": 34.50976000000003 + }, + "dimensions": { + "height": "45.028267", + "width": "76.190018" + } + }, + { + "number": "437", + "position": { + "x": 735.290331, + "y": 80.22097000000002 + }, + "dimensions": { + "height": "291.53769", + "width": "35.318886" + } + }, + { + "number": [ + "428", + "429" + ], + "position": { + "x": 770.619501, + "y": 80.23357000000001 + }, + "dimensions": { + "height": "146.09106", + "width": "117.69923" + } + }, + { + "number": "430", + "position": { + "x": 771.993031, + "y": 227.72102400000003 + }, + "dimensions": { + "height": "68.555847", + "width": "117.72215" + } + }, + { + "number": "431", + "position": { + "x": 771.993031, + "y": 295.584374 + }, + "dimensions": { + "height": "76.865646", + "width": "118.41465" + } + }, + { + "number": "440", + "position": { + "x": 771.300461, + "y": 372.45001 + }, + "dimensions": { + "height": "146.11398", + "width": "118.41465" + } + }, + { + "number": "436", + "position": { + "x": 735.329641, + "y": 371.79605000000004 + }, + "dimensions": { + "height": "294.92099", + "width": "35.932209" + } + }, + { + "number": "435", + "position": { + "x": 771.300461, + "y": 586.4273499999999 + }, + "dimensions": { + "height": "81.020546", + "width": "117.02967" + } + }, + { + "number": "444", + "position": { + "x": 663.965501, + "y": 739.4661900000001 + }, + "dimensions": { + "height": "71.325775", + "width": "72.710747" + } + }, + { + "number": "432", + "position": { + "x": 947.883721, + "y": 661.21558 + }, + "dimensions": { + "height": "101.79505", + "width": "47.781349" + } + }, + { + "number": "432", + "position": { + "x": 889.022691, + "y": 737.3887400000001 + }, + "dimensions": { + "height": "75.480667", + "width": "60.246052" + } + }, + { + "number": "402", + "position": { + "x": 806.617121, + "y": 763.01066 + }, + "dimensions": { + "height": "44.318932", + "width": "72.710747" + } + }, + { + "number": "420", + "position": { + "x": 737.416801, + "y": 845.9036 + }, + "dimensions": { + "height": "115.85214", + "width": "69.047668" + } + }, + { + "number": "438", + "position": { + "x": 591.254811, + "y": 810.7920300000001 + }, + "dimensions": { + "height": "35.316593", + "width": "214.66977" + } + }, + { + "number": "438", + "position": { + "x": 737.368761, + "y": 666.75544 + }, + "dimensions": { + "height": "96.255173", + "width": "94.177734" + } + }, + { + "number": "438", + "position": { + "x": 831.546501, + "y": 742.23609 + }, + "dimensions": { + "height": "21.466982", + "width": "58.168594" + } + }, + { + "number": "438", + "position": { + "x": 737.022451, + "y": 763.35682 + }, + "dimensions": { + "height": "47.088917", + "width": "69.248306" + } + }, + { + "number": "410", + "position": { + "x": 55.278837, + "y": 845.12431 + }, + "dimensions": { + "height": "83.735199", + "width": "75.554283" + } + }, + { + "number": "410", + "position": { + "x": 1.659669000000001, + "y": 928.3727200000001 + }, + "dimensions": { + "height": "34.078312", + "width": "150.62112" + } + }, + { + "number": "411", + "position": { + "x": 1.6601190000000017, + "y": 845.12602 + }, + "dimensions": { + "height": "82.557472", + "width": "53.380753" + } + }, + { + "number": "403", + "position": { + "x": 131.22103099999998, + "y": 844.9271600000001 + }, + "dimensions": { + "height": "38.193451", + "width": "43.090046" + } + }, + { + "number": "409", + "position": { + "x": 174.315331, + "y": 844.93125 + }, + "dimensions": { + "height": "78.540001", + "width": "49.447254" + } + }, + { + "number": "408", + "position": { + "x": 152.27645099999998, + "y": 923.76225 + }, + "dimensions": { + "height": "38.193455", + "width": "146.40823" + } + }, + { + "number": "408", + "position": { + "x": 223.27709099999998, + "y": 844.9271 + }, + "dimensions": { + "height": "78.345589", + "width": "75.407585" + } + }, + { + "number": "412", + "position": { + "x": 1.9520720000000011, + "y": 811.48509 + }, + "dimensions": { + "height": "33.930614", + "width": "589.30219" + } + }, + { + "number": "407", + "position": { + "x": 298.676111, + "y": 844.91862 + }, + "dimensions": { + "height": "117.53546", + "width": "75.99839" + } + }, + { + "number": "406", + "position": { + "x": 374.581901, + "y": 844.9271600000001 + }, + "dimensions": { + "height": "118.00798", + "width": "70.021324" + } + }, + { + "number": "405", + "position": { + "x": 444.113541, + "y": 845.41673 + }, + "dimensions": { + "height": "117.02865", + "width": "146.89789" + } + }, + { + "number": "404", + "position": { + "x": 591.0114609999999, + "y": 846.3961 + }, + "dimensions": { + "height": "115.55968", + "width": "76.386887" + } + }, + { + "number": "421", + "position": { + "x": 667.392691, + "y": 846.10349 + }, + "dimensions": { + "height": "115.65492", + "width": "70.522224" + } + }, + { + "number": "419", + "position": { + "x": 805.973071, + "y": 808.20371 + }, + "dimensions": { + "height": "153.60759", + "width": "80.791679" + } + }, + { + "number": "422", + "position": { + "x": 0.961981999999999, + "y": 373.37528000000003 + }, + "dimensions": { + "height": "32.623585", + "width": "34.29538" + } + }, + { + "number": "417", + "position": { + "x": 1.4612359999999995, + "y": 406.19207000000006 + }, + "dimensions": { + "height": "205.65706", + "width": "105.27682" + } + }, + { + "number": "415", + "position": { + "x": 1.4612520000000018, + "y": 610.86979 + }, + "dimensions": { + "height": "54.841881", + "width": "43.579708" + } + }, + { + "number": "415", + "position": { + "x": 44.551297999999996, + "y": 633.39411 + }, + "dimensions": { + "height": "31.338219", + "width": "63.1661" + } + }, + { + "number": "414", + "position": { + "x": 1.9509119999999989, + "y": 666.20127 + }, + "dimensions": { + "height": "43.579708", + "width": "71.490303" + } + }, + { + "number": "413", + "position": { + "x": 1.9509119999999989, + "y": 709.2913000000001 + }, + "dimensions": { + "height": "102.8285", + "width": "71.490265" + } + } + ], + "dimensions": { + "width": "1000", + "heigh": "1000" + } +} \ No newline at end of file diff --git a/index.html b/index.html index 26f3990..16fc957 100644 --- a/index.html +++ b/index.html @@ -4,10 +4,14 @@ Indoor map of Chateau Neuf - - + + - + + + + +
diff --git a/index.js b/index.js index e69de29..c167e5f 100644 --- a/index.js +++ b/index.js @@ -0,0 +1,69 @@ +var cubes = []; +var scene = new THREE.Scene(); + +//var sceneWidth = 1000; +//var sceneHeight = 1000; +var sceneWidth = window.innerWidth; +var sceneHeight = window.innerHeight; + +var camera = new THREE.PerspectiveCamera( 75, sceneHeight / sceneWidth, 0.1, 2500 ); + +var renderer = new THREE.WebGLRenderer({ alpha: true}); +renderer.setSize(sceneWidth, sceneHeight); +document.body.appendChild( renderer.domElement ); + +// material +var green_color = 0x00ff00; +var red_color = 0xff0000; +var material = new THREE.MeshBasicMaterial( { color: green_color, wireframe: true } ); +var red_material = new THREE.MeshBasicMaterial( { color: red_color, wireframe: true } ); + +function render() { + var i = 0; + requestAnimationFrame(render); + + for(i=0; i -1 || room.number.indexOf("429") > -1) ) { + // KAK + cube = new THREE.Mesh( geometry, red_material ); + } else { + cube = new THREE.Mesh( geometry, material ); + } + + cube.position.x = room.position.x; + cube.position.y = room.position.y * -1; // FIXME why flipped? + cube.name = room.number; + + cubes.push(cube); + scene.add( cube ); + } + + camera.position.z = 1000; + camera.position.x = 500; + camera.position.y = -500; + + //camera.lookAt(scene.position); + //camera.rotation.z = -270 * (Math.PI / 180); + + render(); +} + +// load rooms +$.getJSON("/data/rooms.json", onRoomData); + diff --git a/package.json b/package.json index 2ae83c0..ff29a17 100644 --- a/package.json +++ b/package.json @@ -12,5 +12,13 @@ "three": "~0.66.2", "libxml-to-js": "~0.3.11", "brfs": "~1.1.1" + }, + "devDependencies": { + "node-sass": "latest", + "grunt": "latest", + "grunt-autoprefixer": "latest", + "grunt-contrib-watch": "latest", + "grunt-sass": "latest", + "matchdep": "latest" } } diff --git a/parse.js b/parse.js index cc7fd40..f90630b 100644 --- a/parse.js +++ b/parse.js @@ -2,7 +2,8 @@ var util = require("util"), fs = require("fs"), - parser = require('libxml-to-js'); + parser = require('libxml-to-js'), + utils = require("./utils"); var xml = fs.readFileSync(__dirname + "/data/neuf04.svg", "utf-8"); parser(xml, function (err, result) { @@ -13,27 +14,42 @@ parser(xml, function (err, result) { // rooms var g = result.g.rect; + var transform = result.g['@'].transform; + console.log(transform); + //translate(-21.057139,264.6204) + var translateX = -21.057139; + var translateY = 264.6204; var rooms = []; Object.keys(g).forEach(function(key) { var r = g[key]['@']; var number = r.id; + // more than one room number per shape? if(r.id.indexOf(',') > -1) { number = r.id.split(","); } - rooms.push({ + // more than one shape per room number? + if(r.id.indexOf('_') > -1) { + number = r.id.split("_")[0]; + } + // TODO adjust position to top left instead of center + // ie. find vector from center to top left corner + var room = { number: number, - pos: { - x: r.x, - y: r.y + position: { + x: parseFloat(r.x) + translateX, + y: parseFloat(r.y) + translateY }, - size: { + dimensions: { height: r.height, width: r.width, } - }); + }; + //utils.applySVGTransform(room, transform); + console.log(room); + rooms.push(room); }); + var data = {rooms: rooms, dimensions: { width: width, heigh: height}}; -fs.writeFileSync(__dirname + "/data/rooms.json", JSON.stringify({rooms: rooms}), "utf-8"); - + fs.writeFileSync(__dirname + "/data/rooms.json", JSON.stringify(data, null, 4), "utf-8"); }); diff --git a/scss/app.scss b/scss/app.scss new file mode 100644 index 0000000..4b395a7 --- /dev/null +++ b/scss/app.scss @@ -0,0 +1,22 @@ +body, +html +{ + width: 100%; + height: 100%; + padding: 0; + margin: 0; + overflow: hidden; +} +canvas { + width: 100%; + height: 100%; +} +.debug #svg{ + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: 100%; + height: 100%; +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..db51878 --- /dev/null +++ b/server.js @@ -0,0 +1,9 @@ +var express = require('express'), + http = require('http'); + +var app = express() + .use(express.logger('dev')) + .use("/", express.static(__dirname)); + +console.log("http://localhost:3000"); +http.createServer(app).listen(3000); diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..799f85b --- /dev/null +++ b/utils.js @@ -0,0 +1,289 @@ +/* From https://github.com/asutherland/d3-threeD/blob/master/lib/d3-threeD.js + * ...Which might be useful in the future + * Ref: http://www.smartjava.org/content/render-geographic-information-3d-threejs-and-d3js + */ +const DEGS_TO_RADS = Math.PI / 180, + UNIT_SIZE = 100; + +const DIGIT_0 = 48, DIGIT_9 = 57, COMMA = 44, SPACE = 32, PERIOD = 46, + MINUS = 45; +function transformSVGPath(pathStr) { + var path = 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'; + break; + case 'm': + x += eatNum(); + y += eatNum(); + path.moveTo(x, y); + activeCmd = 'l'; + 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); + } + if (firstX === null) { + firstX = x; + firstY = y; + } + // just reissue the command + if (canRepeat && nextIsNum()) + continue; + activeCmd = pathStr[idx++]; + } + + return path; +} + +function applySVGTransform(obj, tstr) { + var idx = tstr.indexOf('('), len = tstr.length, + cmd = tstr.substring(0, idx++); + function eatNum() { + var sidx, c, isFloat = false, s; + // eat delims + while (idx < len) { + c = tstr.charCodeAt(idx); + if (c !== COMMA && c !== SPACE) + break; + idx++; + } + if (c === MINUS) + sidx = idx++; + else + sidx = idx; + // eat number + while (idx < len) { + c = tstr.charCodeAt(idx); + if (DIGIT_0 <= c && c <= DIGIT_9) { + idx++; + continue; + } + else if (c === PERIOD) { + idx++; + isFloat = true; + continue; + } + + s = tstr.substring(sidx, idx); + return isFloat ? parseFloat(s) : parseInt(s); + } + + s = tstr.substring(sidx); + return isFloat ? parseFloat(s) : parseInt(s); + } + switch (cmd) { + case 'translate': + obj.position.x = Math.floor(eatNum()); + obj.position.y = Math.floor(eatNum()); + //obj.position.x = Math.floor(eatNum() * UNIT_SIZE); + //obj.position.y = Math.floor(eatNum() * UNIT_SIZE); + //console.log("translated:", obj.position.x, obj.position.y); + break; + case 'scale': + obj.scale.x = Math.floor(eatNum() * UNIT_SIZE); + obj.scale.y = Math.floor(eatNum() * UNIT_SIZE); + break; + default: + console.warn("don't understand transform", tstr); + break; + } +} + +exports.transformSVGPath = transformSVGPath; +exports.applySVGTransform = applySVGTransform;