d3_threeD.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  3. * You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. var transformSVGPathExposed;
  5. function d3threeD(exports) {
  6. const DEGS_TO_RADS = Math.PI / 180,
  7. UNIT_SIZE = 1;
  8. const DIGIT_0 = 48, DIGIT_9 = 57, COMMA = 44, SPACE = 32, PERIOD = 46,
  9. MINUS = 45;
  10. function transformSVGPath(pathStr) {
  11. var paths = [];
  12. var path = new THREE.Shape();
  13. var idx = 1, len = pathStr.length, activeCmd,
  14. x = 0, y = 0, nx = 0, ny = 0, firstX = null, firstY = null,
  15. x1 = 0, x2 = 0, y1 = 0, y2 = 0,
  16. rx = 0, ry = 0, xar = 0, laf = 0, sf = 0, cx, cy;
  17. function eatNum() {
  18. var sidx, c, isFloat = false, s;
  19. // eat delims
  20. while (idx < len) {
  21. c = pathStr.charCodeAt(idx);
  22. if (c !== COMMA && c !== SPACE)
  23. break;
  24. idx++;
  25. }
  26. if (c === MINUS)
  27. sidx = idx++;
  28. else
  29. sidx = idx;
  30. // eat number
  31. while (idx < len) {
  32. c = pathStr.charCodeAt(idx);
  33. if (DIGIT_0 <= c && c <= DIGIT_9) {
  34. idx++;
  35. continue;
  36. }
  37. else if (c === PERIOD) {
  38. idx++;
  39. isFloat = true;
  40. continue;
  41. }
  42. s = pathStr.substring(sidx, idx);
  43. return isFloat ? parseFloat(s) : parseInt(s);
  44. }
  45. s = pathStr.substring(sidx);
  46. return isFloat ? parseFloat(s) : parseInt(s);
  47. }
  48. function nextIsNum() {
  49. var c;
  50. // do permanently eat any delims...
  51. while (idx < len) {
  52. c = pathStr.charCodeAt(idx);
  53. if (c !== COMMA && c !== SPACE)
  54. break;
  55. idx++;
  56. }
  57. c = pathStr.charCodeAt(idx);
  58. return (c === MINUS || (DIGIT_0 <= c && c <= DIGIT_9));
  59. }
  60. var canRepeat;
  61. var enteredSub = false;
  62. var zSeen = false;
  63. activeCmd = pathStr[0];
  64. while (idx <= len) {
  65. canRepeat = true;
  66. switch (activeCmd) {
  67. // moveto commands, become lineto's if repeated
  68. case 'M':
  69. enteredSub = false;
  70. x = eatNum();
  71. y = eatNum();
  72. path.moveTo(x, y);
  73. activeCmd = 'L';
  74. break;
  75. case 'm':
  76. x += eatNum();
  77. y += eatNum();
  78. path.moveTo(x, y);
  79. activeCmd = 'l';
  80. break;
  81. case 'Z':
  82. case 'z':
  83. // z is a special case. This ends a segment and starts
  84. // a new path. Since the three.js path is continuous
  85. // we should start a new path here. This also draws a
  86. // line from the current location to the start location.
  87. canRepeat = false;
  88. if (x !== firstX || y !== firstY)
  89. path.lineTo(firstX, firstY);
  90. paths.push(path);
  91. // reset the elements
  92. firstX = null;
  93. firstY = null;
  94. // avoid x,y being set incorrectly
  95. enteredSub = true;
  96. path = new THREE.Shape();
  97. zSeen = true;
  98. break;
  99. // - lines!
  100. case 'L':
  101. case 'H':
  102. case 'V':
  103. nx = (activeCmd === 'V') ? x : eatNum();
  104. ny = (activeCmd === 'H') ? y : eatNum();
  105. path.lineTo(nx, ny);
  106. x = nx;
  107. y = ny;
  108. break;
  109. case 'l':
  110. case 'h':
  111. case 'v':
  112. nx = (activeCmd === 'v') ? x : (x + eatNum());
  113. ny = (activeCmd === 'h') ? y : (y + eatNum());
  114. path.lineTo(nx, ny);
  115. x = nx;
  116. y = ny;
  117. break;
  118. // - cubic bezier
  119. case 'C':
  120. x1 = eatNum(); y1 = eatNum();
  121. case 'S':
  122. if (activeCmd === 'S') {
  123. x1 = 2 * x - x2; y1 = 2 * y - y2;
  124. }
  125. x2 = eatNum();
  126. y2 = eatNum();
  127. nx = eatNum();
  128. ny = eatNum();
  129. path.bezierCurveTo(x1, y1, x2, y2, nx, ny);
  130. x = nx; y = ny;
  131. break;
  132. case 'c':
  133. x1 = x + eatNum();
  134. y1 = y + eatNum();
  135. case 's':
  136. if (activeCmd === 's') {
  137. x1 = 2 * x - x2;
  138. y1 = 2 * y - y2;
  139. }
  140. x2 = x + eatNum();
  141. y2 = y + eatNum();
  142. nx = x + eatNum();
  143. ny = y + eatNum();
  144. path.bezierCurveTo(x1, y1, x2, y2, nx, ny);
  145. x = nx; y = ny;
  146. break;
  147. // - quadratic bezier
  148. case 'Q':
  149. x1 = eatNum(); y1 = eatNum();
  150. case 'T':
  151. if (activeCmd === 'T') {
  152. x1 = 2 * x - x1;
  153. y1 = 2 * y - y1;
  154. }
  155. nx = eatNum();
  156. ny = eatNum();
  157. path.quadraticCurveTo(x1, y1, nx, ny);
  158. x = nx;
  159. y = ny;
  160. break;
  161. case 'q':
  162. x1 = x + eatNum();
  163. y1 = y + eatNum();
  164. case 't':
  165. if (activeCmd === 't') {
  166. x1 = 2 * x - x1;
  167. y1 = 2 * y - y1;
  168. }
  169. nx = x + eatNum();
  170. ny = y + eatNum();
  171. path.quadraticCurveTo(x1, y1, nx, ny);
  172. x = nx; y = ny;
  173. break;
  174. // - elliptical arc
  175. case 'A':
  176. rx = eatNum();
  177. ry = eatNum();
  178. xar = eatNum() * DEGS_TO_RADS;
  179. laf = eatNum();
  180. sf = eatNum();
  181. nx = eatNum();
  182. ny = eatNum();
  183. if (rx !== ry) {
  184. console.warn("Forcing elliptical arc to be a circular one :(",
  185. rx, ry);
  186. }
  187. // SVG implementation notes does all the math for us! woo!
  188. // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
  189. // step1, using x1 as x1'
  190. x1 = Math.cos(xar) * (x - nx) / 2 + Math.sin(xar) * (y - ny) / 2;
  191. y1 = -Math.sin(xar) * (x - nx) / 2 + Math.cos(xar) * (y - ny) / 2;
  192. // step 2, using x2 as cx'
  193. var norm = Math.sqrt(
  194. (rx*rx * ry*ry - rx*rx * y1*y1 - ry*ry * x1*x1) /
  195. (rx*rx * y1*y1 + ry*ry * x1*x1));
  196. if (laf === sf)
  197. norm = -norm;
  198. x2 = norm * rx * y1 / ry;
  199. y2 = norm * -ry * x1 / rx;
  200. // step 3
  201. cx = Math.cos(xar) * x2 - Math.sin(xar) * y2 + (x + nx) / 2;
  202. cy = Math.sin(xar) * x2 + Math.cos(xar) * y2 + (y + ny) / 2;
  203. var u = new THREE.Vector2(1, 0),
  204. v = new THREE.Vector2((x1 - x2) / rx,
  205. (y1 - y2) / ry);
  206. var startAng = Math.acos(u.dot(v) / u.length() / v.length());
  207. if (u.x * v.y - u.y * v.x < 0)
  208. startAng = -startAng;
  209. // we can reuse 'v' from start angle as our 'u' for delta angle
  210. u.x = (-x1 - x2) / rx;
  211. u.y = (-y1 - y2) / ry;
  212. var deltaAng = Math.acos(v.dot(u) / v.length() / u.length());
  213. // This normalization ends up making our curves fail to triangulate...
  214. if (v.x * u.y - v.y * u.x < 0)
  215. deltaAng = -deltaAng;
  216. if (!sf && deltaAng > 0)
  217. deltaAng -= Math.PI * 2;
  218. if (sf && deltaAng < 0)
  219. deltaAng += Math.PI * 2;
  220. path.absarc(cx, cy, rx, startAng, startAng + deltaAng, sf);
  221. x = nx;
  222. y = ny;
  223. break;
  224. case ' ':
  225. // if it's an empty space, just skip it, and see if we can find a real command
  226. break;
  227. default:
  228. throw new Error("weird path command: " + activeCmd);
  229. }
  230. if (firstX === null && !enteredSub) {
  231. firstX = x;
  232. firstY = y;
  233. }
  234. // just reissue the command
  235. if (canRepeat && nextIsNum())
  236. continue;
  237. activeCmd = pathStr[idx++];
  238. }
  239. if (zSeen) {
  240. return paths;
  241. } else {
  242. paths.push(path);
  243. return paths;
  244. }
  245. }
  246. transformSVGPathExposed = transformSVGPath;
  247. function applySVGTransform(obj, tstr) {
  248. var idx = tstr.indexOf('('), len = tstr.length,
  249. cmd = tstr.substring(0, idx++);
  250. function eatNum() {
  251. var sidx, c, isFloat = false, s;
  252. // eat delims
  253. while (idx < len) {
  254. c = tstr.charCodeAt(idx);
  255. if (c !== COMMA && c !== SPACE)
  256. break;
  257. idx++;
  258. }
  259. if (c === MINUS)
  260. sidx = idx++;
  261. else
  262. sidx = idx;
  263. // eat number
  264. while (idx < len) {
  265. c = tstr.charCodeAt(idx);
  266. if (DIGIT_0 <= c && c <= DIGIT_9) {
  267. idx++;
  268. continue;
  269. }
  270. else if (c === PERIOD) {
  271. idx++;
  272. isFloat = true;
  273. continue;
  274. }
  275. s = tstr.substring(sidx, idx);
  276. return isFloat ? parseFloat(s) : parseInt(s);
  277. }
  278. s = tstr.substring(sidx);
  279. return isFloat ? parseFloat(s) : parseInt(s);
  280. }
  281. switch (cmd) {
  282. case 'translate':
  283. obj.position.x = Math.floor(eatNum() * UNIT_SIZE);
  284. obj.position.y = Math.floor(eatNum() * UNIT_SIZE);
  285. break;
  286. case 'scale':
  287. obj.scale.x = Math.floor(eatNum() * UNIT_SIZE);
  288. obj.scale.y = Math.floor(eatNum() * UNIT_SIZE);
  289. break;
  290. default:
  291. console.warn("don't understand transform", tstr);
  292. break;
  293. }
  294. }
  295. applySVGTransformExposed = applySVGTransform;
  296. function wrap_setAttribute(name, value) {
  297. }
  298. function wrap_setAttributeNS(namespace, name, value) {
  299. }
  300. var extrudeDefaults = {
  301. amount: 20,
  302. bevelEnabled: true,
  303. material: 0,
  304. extrudeMaterial: 0,
  305. };
  306. function commonSetAttribute(name, value) {
  307. switch (name) {
  308. case 'x':
  309. this.position.x = Math.floor(value * UNIT_SIZE);
  310. break;
  311. case 'y':
  312. this.position.y = Math.floor(value * UNIT_SIZE);
  313. break;
  314. case 'class':
  315. this.clazz = value;
  316. break;
  317. case 'stroke':
  318. case 'fill':
  319. if (typeof(value) !== 'string')
  320. value = value.toString();
  321. this.material.color.setHex(parseInt(value.substring(1), 16));
  322. break;
  323. case 'transform':
  324. applySVGTransform(this, value);
  325. break;
  326. case 'd':
  327. var shape = transformSVGPath(value),
  328. geom = shape.extrude(extrudeDefaults);
  329. this.geometry = geom;
  330. this.geometry.boundingSphere = {radius: 3 * UNIT_SIZE};
  331. this.scale.set(UNIT_SIZE, UNIT_SIZE, UNIT_SIZE);
  332. break;
  333. default:
  334. throw new Error("no setter for: " + name);
  335. }
  336. }
  337. function commonSetAttributeNS(namespace, name, value) {
  338. this.setAttribute(name, value);
  339. }
  340. function Group(parentThing) {
  341. THREE.Object3D.call(this);
  342. this.d3class = '';
  343. parentThing.add(this);
  344. };
  345. Group.prototype = new THREE.Object3D();
  346. Group.prototype.constructor = Group;
  347. Group.prototype.d3tag = 'g';
  348. Group.prototype.setAttribute = commonSetAttribute;
  349. Group.prototype.setAttributeNS = commonSetAttributeNS;
  350. function fabGroup() {
  351. return new Group(this);
  352. }
  353. function Mesh(parentThing, tag, geometry, material) {
  354. THREE.Mesh.call(this, geometry, material);
  355. this.d3tag = tag;
  356. this.d3class = '';
  357. parentThing.add(this);
  358. }
  359. Mesh.prototype = new THREE.Mesh();
  360. Mesh.prototype.constructor = Mesh;
  361. Mesh.prototype.setAttribute = commonSetAttribute;
  362. Mesh.prototype.setAttributeNS = commonSetAttributeNS;
  363. const SPHERE_SEGS = 16, SPHERE_RINGS = 16,
  364. DEFAULT_COLOR = 0xcc0000;
  365. var sharedSphereGeom = null,
  366. sharedCubeGeom = null;
  367. function fabSphere() {
  368. if (!sharedSphereGeom)
  369. sharedSphereGeom = new THREE.SphereGeometry(
  370. UNIT_SIZE / 2, SPHERE_SEGS, SPHERE_RINGS);
  371. var material = new THREE.MeshLambertMaterial({
  372. color: DEFAULT_COLOR,
  373. });
  374. return new Mesh(this, 'sphere', sharedSphereGeom, material);
  375. }
  376. function fabCube() {
  377. if (!sharedCubeGeom)
  378. sharedCubeGeom = new THREE.CubeGeometry(UNIT_SIZE, UNIT_SIZE, UNIT_SIZE);
  379. var material = new THREE.MeshLambertMaterial({
  380. color: DEFAULT_COLOR,
  381. });
  382. return new Mesh(this, 'cube', sharedCubeGeom, material);
  383. }
  384. function fabPath() {
  385. // start with a cube that we will replace with the path once it gets created
  386. if (!sharedCubeGeom)
  387. sharedCubeGeom = new THREE.CubeGeometry(UNIT_SIZE, UNIT_SIZE, UNIT_SIZE);
  388. var material = new THREE.MeshLambertMaterial({
  389. color: DEFAULT_COLOR,
  390. });
  391. return new Mesh(this, 'path', sharedCubeGeom, material);
  392. }
  393. function Scene() {
  394. THREE.Scene.call(this);
  395. this.renderer = null;
  396. this.camera = null;
  397. this.controls = null;
  398. this._d3_width = null;
  399. this._d3_height = null;
  400. }
  401. Scene.prototype = new THREE.Scene();
  402. Scene.prototype.constructor = Scene;
  403. Scene.prototype._setBounds = function() {
  404. this.renderer.setSize(this._d3_width, this._d3_height);
  405. var aspect = this.camera.aspect;
  406. this.camera.position.set(
  407. this._d3_width * UNIT_SIZE / 2,
  408. this._d3_height * UNIT_SIZE / 2,
  409. Math.max(this._d3_width * UNIT_SIZE / Math.sqrt(2),
  410. this._d3_height * UNIT_SIZE / Math.sqrt(2)));
  411. this.controls.target.set(this.camera.position.x, this.camera.position.y, 0);
  412. console.log("camera:", this.camera.position.x, this.camera.position.y,
  413. this.camera.position.z);
  414. //this.camera.position.z = 1000;
  415. };
  416. Scene.prototype.setAttribute = function(name, value) {
  417. switch (name) {
  418. case 'width':
  419. this._d3_width = value;
  420. if (this._d3_height)
  421. this._setBounds();
  422. break;
  423. case 'height':
  424. this._d3_height = value;
  425. if (this._d3_width)
  426. this._setBounds();
  427. break;
  428. }
  429. };
  430. function fabVis() {
  431. var camera, scene, controls, renderer;
  432. // - scene
  433. scene = new Scene();
  434. threeJsScene = scene;
  435. // - camera
  436. camera = scene.camera = new THREE.PerspectiveCamera(
  437. 75,
  438. window.innerWidth / window.innerHeight,
  439. 1, 100000);
  440. /*
  441. camera = scene.camera = new THREE.OrthographicCamera(
  442. window.innerWidth / -2, window.innerWidth / 2,
  443. window.innerHeight / 2, window.innerHeight / -2,
  444. 1, 50000);
  445. */
  446. scene.add(camera);
  447. // - controls
  448. // from misc_camera_trackball.html example
  449. controls = scene.controls = new THREE.TrackballControls(camera);
  450. controls.rotateSpeed = 1.0;
  451. controls.zoomSpeed = 1.2;
  452. controls.panSpeed = 0.8;
  453. controls.noZoom = false;
  454. controls.noPan = false;
  455. controls.staticMoving = true;
  456. controls.dynamicDampingFactor = 0.3;
  457. controls.keys = [65, 83, 68];
  458. controls.addEventListener('change', render);
  459. // - light
  460. /*
  461. var pointLight = new THREE.PointLight(0xFFFFFF);
  462. pointLight.position.set(10, 50, 130);
  463. scene.add(pointLight);
  464. */
  465. var spotlight = new THREE.SpotLight(0xffffff);
  466. spotlight.position.set(-50000, 50000, 100000);
  467. scene.add(spotlight);
  468. var backlight = new THREE.SpotLight(0x888888);
  469. backlight.position.set(50000, -50000, -100000);
  470. scene.add(backlight);
  471. /*
  472. var ambientLight = new THREE.AmbientLight(0x888888);
  473. scene.add(ambientLight);
  474. */
  475. function helperPlanes(maxBound) {
  476. var geom = new THREE.PlaneGeometry(maxBound, maxBound, 4, 4);
  477. for (var i = 0; i < 4; i++) {
  478. var color, cx, cy;
  479. switch (i) {
  480. case 0:
  481. color = 0xff0000;
  482. cx = maxBound / 2;
  483. cy = maxBound / 2;
  484. break;
  485. case 1:
  486. color = 0x00ff00;
  487. cx = maxBound / 2;
  488. cy = -maxBound / 2;
  489. break;
  490. case 2:
  491. color = 0x0000ff;
  492. cx = -maxBound / 2;
  493. cy = -maxBound / 2;
  494. break;
  495. case 3:
  496. color = 0xffff00;
  497. cx = -maxBound / 2;
  498. cy = maxBound / 2;
  499. break;
  500. }
  501. var material = new THREE.MeshLambertMaterial({ color: color });
  502. var mesh = new THREE.Mesh(geom, material);
  503. mesh.position.set(cx, cy, -1);
  504. scene.add(mesh);
  505. }
  506. }
  507. //helperPlanes(UNIT_SIZE * 225);
  508. // - renderer
  509. renderer = scene.renderer = new THREE.WebGLRenderer({
  510. // too slow...
  511. //antialias: true,
  512. });
  513. this.appendChild( renderer.domElement );
  514. // - stats
  515. var stats = new Stats();
  516. stats.domElement.style.position = 'absolute';
  517. stats.domElement.style.top = '0px';
  518. stats.domElement.style.zIndex = 100;
  519. this.appendChild( stats.domElement );
  520. function animate() {
  521. requestAnimationFrame(animate, renderer.domElement);
  522. controls.update();
  523. }
  524. function render() {
  525. renderer.render(scene, camera);
  526. stats.update();
  527. }
  528. animate();
  529. return scene;
  530. };
  531. }
  532. var $d3g = {};
  533. d3threeD($d3g);