1. 程式人生 > >threejs svg path 座標轉換

threejs svg path 座標轉換

import {Shape, Vector2} from 'three';
export default class Wall {
  constructor (result) {
    this.DEGS_TO_RADS = Math.PI / 180;
    this.DIGIT_0 = 48;
    this.DIGIT_9 = 57;
    this.COMMA = 44;
    this.SPACE = 32;
    this.PERIOD = 46;
    this.MINUS = 45;
    this.idx = 1;
    this.len = result[0].getAttribute('d').length;
    this.pathStr = result[0].getAttribute('d');
    this.transformSVGPath().then(res => {
      console.log(res)
    })
  }
  async transformSVGPath () {
    let paths = [];
    let path = new Shape();
    let x = 0;
    let y = 0;
    let nx = 0;
    let ny = 0;
    let firstX = null;
    let firstY = null;
    let x1 = 0;
    let x2 = 0;
    let y1 = 0;
    let y2 = 0;
    let rx = 0;
    let ry = 0;
    let xar = 0;
    let laf = 0;
    let sf = 0;
    let cx;
    let cy;
    let canRepeat;
    let enteredSub = false;
    let zSeen = false;
    let activeCmd = this.pathStr[0];
    while (this.idx <= this.len) {
      canRepeat = true;
      switch (activeCmd) {
        case 'M':
          enteredSub = false;
          x = this.eatNum();
          y = this.eatNum();
          path.moveTo(x, y);
          activeCmd = 'L';
          break;
        case 'm':
          x += this.eatNum();
          y += this.eatNum();
          path.moveTo(x, y);
          activeCmd = 'l';
          break;
        case 'Z':
        case 'z':
          canRepeat = false;
          if (x !== firstX || y !== firstY) path.lineTo(firstX, firstY);
          paths.push(path);
          firstX = null;
          firstY = null;
          enteredSub = true;
          path = new Shape();
          zSeen = true;
          break;
        case 'L':
        case 'H':
        case 'V':
          nx = (activeCmd === 'V') ? x : this.eatNum();
          ny = (activeCmd === 'H') ? y : this.eatNum();
          path.lineTo(nx, ny);
          x = nx;
          y = ny;
          break;
        case 'l':
        case 'h':
        case 'v':
          nx = (activeCmd === 'v') ? x : (x + this.eatNum());
          ny = (activeCmd === 'h') ? y : (y + this.eatNum());
          path.lineTo(nx, ny);
          x = nx;
          y = ny;
          break;
        case 'C':
          x1 = this.eatNum();
          y1 = this.eatNum();
        case 'S':
          if (activeCmd === 'S') {
            x1 = 2 * x - x2; y1 = 2 * y - y2;
          }
          x2 = this.eatNum();
          y2 = this.eatNum();
          nx = this.eatNum();
          ny = this.eatNum();
          path.bezierCurveTo(x1, y1, x2, y2, nx, ny);
          x = nx; y = ny;
          break;
        case 'c':
          x1 = x + this.eatNum();
          y1 = y + this.eatNum();
        case 's':
          if (activeCmd === 's') {
            x1 = 2 * x - x2;
            y1 = 2 * y - y2;
          }
          x2 = x + this.eatNum();
          y2 = y + this.eatNum();
          nx = x + this.eatNum();
          ny = y + this.eatNum();
          path.bezierCurveTo(x1, y1, x2, y2, nx, ny);
          x = nx; y = ny;
          break;
        case 'Q':
          x1 = this.eatNum();
          y1 = this.eatNum();
        case 'T':
          if (activeCmd === 'T') {
            x1 = 2 * x - x1;
            y1 = 2 * y - y1;
          }
          nx = this.eatNum();
          ny = this.eatNum();
          path.quadraticCurveTo(x1, y1, nx, ny);
          x = nx;
          y = ny;
          break;
        case 'q':
          x1 = x + this.eatNum();
          y1 = y + this.eatNum();
        case 't':
          if (activeCmd === 't') {
            x1 = 2 * x - x1;
            y1 = 2 * y - y1;
          }
          nx = x + this.eatNum();
          ny = y + this.eatNum();
          path.quadraticCurveTo(x1, y1, nx, ny);
          x = nx; y = ny;
          break;
        case 'A':
          rx = this.eatNum();
          ry = this.eatNum();
          xar = this.eatNum() * this.DEGS_TO_RADS;
          laf = this.eatNum();
          sf = this.eatNum();
          nx = this.eatNum();
          ny = this.eatNum();
          if (rx !== ry) {
            console.warn('Forcing elliptical arc to be a circular one :(',
              rx, ry);
          }
          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;
          let 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;
          cx = Math.cos(xar) * x2 - Math.sin(xar) * y2 + (x + nx) / 2;
          cy = Math.sin(xar) * x2 + Math.cos(xar) * y2 + (y + ny) / 2;
          let u = new Vector2(1, 0);
          let v = new Vector2((x1 - x2) / rx, (y1 - y2) / ry);
          let startAng = Math.acos(u.dot(v) / u.length() / v.length());
          if (u.x * v.y - u.y * v.x < 0) startAng = -startAng;
          u.x = (-x1 - x2) / rx;
          u.y = (-y1 - y2) / ry;
          let deltaAng = Math.acos(v.dot(u) / v.length() / u.length());
          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;
        case ' ':
          break;
        default:
          throw new Error('weird path command: ' + activeCmd);
      }
      if (firstX === null && !enteredSub) {
        firstX = x;
        firstY = y;
      }
      if (canRepeat && this.nextIsNum()) continue;
      activeCmd = this.pathStr[this.idx++];
    }
    if (zSeen) {
      return paths;
    } else {
      paths.push(path);
      return paths;
    }
  }
  eatNum () {
    let sidx;
    let c;
    let isFloat = false;
    let s;
    while (this.idx < this.len) {
      c = this.pathStr.charCodeAt(this.idx);
      if (c !== this.COMMA && c !== this.SPACE) break;
      this.idx++;
    }
    if (c === this.MINUS) {
      sidx = this.idx++;
    } else {
      sidx = this.idx;
    }
    while (this.idx < this.len) {
      c = this.pathStr.charCodeAt(this.idx);
      if (this.DIGIT_0 <= c && c <= this.DIGIT_9) {
        this.idx++;
        continue;
      } else if (c === this.PERIOD) {
        this.idx++;
        isFloat = true;
        continue;
      }
      s = this.pathStr.substring(sidx, this.idx);
      return isFloat ? parseFloat(s) : parseInt(s);
    }
    s = this.pathStr.substring(sidx);
    return isFloat ? parseFloat(s) : parseInt(s);
  }
  nextIsNum () {
    let c;
    while (this.idx < this.len) {
      c = this.pathStr.charCodeAt(this.idx);
      if (c !== this.COMMA && c !== this.SPACE) break;
      this.idx++;
    }
    c = this.pathStr.charCodeAt(this.idx);
    return (c === this.MINUS || (this.DIGIT_0 <= c && c <= this.DIGIT_9));
  }
}