Javascript/이론

[ JS ] rotate3d 값 얻기

OnnJE 2023. 2. 8. 13:55
반응형

  JS를 통해 요소의 rotate3d 프로퍼티를 얻기 위해서 만든 클래스입니다.

 

1. Constructor

  constructor에서는 transform property를 얻고자 하는 요소를 parameter로 두었습니다. 따라서 클래스 성성시 요소를 받아 요소의 matrix3d 데이터를 저장합니다.

 

class Transform {
  constructor(ele) {
    this.ele = ele;
    this.matrix3d = window
      .getComputedStyle(ele)
      .getPropertyValue("transform") // column priority
      .split("(")[1]
      .split(")")[0]
      .split(",")
      .map((ele) => Number(ele));
  }
}

 

2. Method

1) show()

  요소의 matrix3d 데이터를 행렬꼴로 콘솔창에 출력합니다.

 

show() {
    let temp = this.matrix3d.slice().map((ele) => Number(ele.toFixed(2)));

    if (temp.length === 6) {
      console.log(
        `
        ${temp[0]} / ${temp[2]} / ${temp[4]}
        ${temp[1]} / ${temp[3]} / ${temp[5]}
        0 / 0 / 1
        `
      );
    } else if (temp.length === 16) {
      console.log(
        `
        ${temp[0]} / ${temp[4]} / ${temp[8]} / ${temp[12]}
        ${temp[1]} / ${temp[5]} / ${temp[9]} / ${temp[13]}
        ${temp[2]} / ${temp[6]} / ${temp[10]} / ${temp[14]}
        ${temp[3]} / ${temp[7]} / ${temp[11]} / ${temp[15]}
        `
      );
    }

    return;
  }

 

 

2) showAllFactors()

  메서드에서는 각 변환 인자를 콘솔창에 출력합니다.

 

showAllFactors() {
    let t = this.getTranslateFactors();
    let s = this.getScaleFactors();
    let r = this.getRotateFactors();

    console.log("\n--------------translation--------------\n");
    Object.keys(t).forEach((ele) => console.log(`${ele} : ${t[ele]}`));
    console.log("\n---------------scaling-----------------\n");
    Object.keys(s).forEach((ele) => console.log(`${ele} : ${s[ele]}`));
    console.log("\n---------------rotation----------------\n");
    Object.keys(r).forEach((ele) => console.log(`${ele} : ${r[ele]}`));
  }

 

 

3) getTranslateFactors

  tx, ty, tz의 키값을 가지는 객체로 translation에 사용된 인자를 반환합니다. 각 key에 대응하는 value의 자료형은 숫자입니다.

 

getTranslateFactors() {
    if (this.matrix3d.length === 6)
      return {
        tx: this.matrix3d[4].toFixed(2),
        ty: this.matrix3d[5].toFixed(2),
      };
    if (this.matrix3d.length === 16)
      return {
        tx: this.matrix3d[12].toFixed(2),
        ty: this.matrix3d[13].toFixed(2),
        tz: this.matrix3d[14].toFixed(2),
      };
  }

 

 

4) getScaleFactors

  sx, sy, sz의 키값을 가지는 객체로 scalling에 사용된 인자를 반환합니다.  각 key에 대응하는 value의 자료형은 숫자입니다.

 

getScaleFactors() {
    if (this.matrix3d.length === 6)
      return {
        sx: Math.pow(
          this.matrix3d[0] ** 2 + this.matrix3d[2] ** 2,
          0.5
        ).toFixed(2),
        sy: Math.pow(
          this.matrix3d[1] ** 2 + this.matrix3d[3] ** 2,
          0.5
        ).toFixed(2),
      };
    if (this.matrix3d.length === 16)
      return {
        sx: Math.pow(
          this.matrix3d[0] ** 2 + this.matrix3d[4] ** 2 + this.matrix3d[8] ** 2,
          0.5
        ).toFixed(2),
        sy: Math.pow(
          this.matrix3d[1] ** 2 + this.matrix3d[5] ** 2 + this.matrix3d[9] ** 2,
          0.5
        ).toFixed(2),
        sz: Math.pow(
          this.matrix3d[2] ** 2 +
            this.matrix3d[6] ** 2 +
            this.matrix3d[10] ** 2,
          0.5
        ).toFixed(2),
      };
  }

 

 

(5) getRotateFactors

  u, deg를 키값으로 갖는 객체로 rotate 인자를 반환합니다. 앞서 정의된 getScaleFactors()를 이용해 요소의 matrix3d 데이터에서 scaling factor를 제거하며, u와 deg를 구하는 원리는 아래 링크에서 확인 가능합니다.

 

 

[Computer Graphics] 3D 변환 행렬과 행렬 분해

1. 3D 변환 행렬의 구성 3D 공간에서의 변환은 보통 4x4 매트릭스를 통해 이루어집니다.(homogenous coordinate라고 표현하며 하나의 행렬을 통해 여러 종류의 연산을 나타낼 수 있다는 장점을 가집니다.)

ojhallae.tistory.com

 

  각 key에 대응하는 value의 자료형은 사용의 편의성을 위해 문자열로 반환합니다.

 

getRotateFactors() {
    let scaleFactors = Object.values(this.getScaleFactors());
    let dimension = scaleFactors.length;
    let tempMatrix = this.matrix3d.slice();
    let deg;

    if (dimension === 2) {
      [0, 1].forEach((ele) => {
        for (let i = 0; i < 2; i++) {
          tempMatrix[ele + 2 * i] /= scaleFactors[ele];
        }
      });

      deg = (Math.acos(tempMatrix[0]) * 180) / Math.PI;

      return { u: undefined, deg: deg.toFixed(2) + "deg" };
    } else if (dimension === 3) {
      [0, 1, 2].forEach((ele) => {
        for (let i = 0; i < 3; i++) {
          tempMatrix[ele + 4 * i] /= scaleFactors[ele];
        }
      });

      let u = [
        tempMatrix[6] - tempMatrix[9],
        tempMatrix[8] - tempMatrix[2],
        tempMatrix[1] - tempMatrix[4],
      ];

      if (u[0].toFixed(2) != 1) {
        deg =
          (Math.acos((tempMatrix[0] - u[0] ** 2) / (1 - u[0] ** 2)) * 180) /
          Math.PI;
      } else if (u[1].toFixed(2) != 1) {
        deg =
          (Math.acos((tempMatrix[5] - u[1] ** 2) / (1 - u[1] ** 2)) * 180) /
          Math.PI;
      } else if (u[2].toFixed(2) != 1) {
        deg =
          (Math.acos((tempMatrix[10] - u[2] ** 2) / (1 - u[2] ** 2)) * 180) /
          Math.PI;
      }

      return {
        u: u.map((ele) => ele.toFixed(2)).join(","),
        deg: deg.toFixed(2) + "deg",
      };
    }
  }

 

 

6) setSameTransformTo

  클래스 생성시 인자로 전달받은 요소의 trasform property에 적용된 translation, scaling, rotation을 메서드의 인자로 받은 요소에 적용합니다. 

 

setSameTransformTo(ele) {
    let t = this.getTranslateFactors();
    let s = this.getScaleFactors();
    let r = this.getRotateFactors();
    console.log(t, s, r);

    ele.style.transform = `translate3d(${t["tx"]}px, ${t["ty"]}px, ${
      t["tz"]
    }px) scale3d(${s["sx"] + "," + s["sy"] + "," + s["sz"]}) rotate3d(${
      r["u"]
    }, ${r["deg"]})`;
  }

 

 

3. Full code

class Transform {
  constructor(ele) {
    this.ele = ele;
    this.matrix3d = window
      .getComputedStyle(ele)
      .getPropertyValue("transform") // column priority
      .split("(")[1]
      .split(")")[0]
      .split(",")
      .map((ele) => Number(ele));
  }

  show() {
    let temp = this.matrix3d.slice().map((ele) => Number(ele.toFixed(2)));

    if (temp.length === 6) {
      console.log(
        `
        ${temp[0]} / ${temp[2]} / ${temp[4]}
        ${temp[1]} / ${temp[3]} / ${temp[5]}
        0 / 0 / 1
        `
      );
    } else if (temp.length === 16) {
      console.log(
        `
        ${temp[0]} / ${temp[4]} / ${temp[8]} / ${temp[12]}
        ${temp[1]} / ${temp[5]} / ${temp[9]} / ${temp[13]}
        ${temp[2]} / ${temp[6]} / ${temp[10]} / ${temp[14]}
        ${temp[3]} / ${temp[7]} / ${temp[11]} / ${temp[15]}
        `
      );
    }

    return;
  }

  showAllFactors() {
    let t = this.getTranslateFactors();
    let s = this.getScaleFactors();
    let r = this.getRotateFactors();

    console.log("\n--------------translation--------------\n");
    Object.keys(t).forEach((ele) => console.log(`${ele} : ${t[ele]}`));
    console.log("\n---------------scaling-----------------\n");
    Object.keys(s).forEach((ele) => console.log(`${ele} : ${s[ele]}`));
    console.log("\n---------------rotation----------------\n");
    Object.keys(r).forEach((ele) => console.log(`${ele} : ${r[ele]}`));
  }

  getTranslateFactors() {
    if (this.matrix3d.length === 6)
      return {
        tx: this.matrix3d[4].toFixed(2),
        ty: this.matrix3d[5].toFixed(2),
      };
    if (this.matrix3d.length === 16)
      return {
        tx: this.matrix3d[12].toFixed(2),
        ty: this.matrix3d[13].toFixed(2),
        tz: this.matrix3d[14].toFixed(2),
      };
  }

  getScaleFactors() {
    if (this.matrix3d.length === 6)
      return {
        sx: Math.pow(
          this.matrix3d[0] ** 2 + this.matrix3d[2] ** 2,
          0.5
        ).toFixed(2),
        sy: Math.pow(
          this.matrix3d[1] ** 2 + this.matrix3d[3] ** 2,
          0.5
        ).toFixed(2),
      };
    if (this.matrix3d.length === 16)
      return {
        sx: Math.pow(
          this.matrix3d[0] ** 2 + this.matrix3d[4] ** 2 + this.matrix3d[8] ** 2,
          0.5
        ).toFixed(2),
        sy: Math.pow(
          this.matrix3d[1] ** 2 + this.matrix3d[5] ** 2 + this.matrix3d[9] ** 2,
          0.5
        ).toFixed(2),
        sz: Math.pow(
          this.matrix3d[2] ** 2 +
            this.matrix3d[6] ** 2 +
            this.matrix3d[10] ** 2,
          0.5
        ).toFixed(2),
      };
  }

  getRotateFactors() {
    let scaleFactors = Object.values(this.getScaleFactors());
    let dimension = scaleFactors.length;
    let tempMatrix = this.matrix3d.slice();
    let deg;

    if (dimension === 2) {
      [0, 1].forEach((ele) => {
        for (let i = 0; i < 2; i++) {
          tempMatrix[ele + 2 * i] /= scaleFactors[ele];
        }
      });

      deg = (Math.acos(tempMatrix[0]) * 180) / Math.PI;

      return { u: undefined, deg: deg.toFixed(2) + "deg" };
    } else if (dimension === 3) {
      [0, 1, 2].forEach((ele) => {
        for (let i = 0; i < 3; i++) {
          tempMatrix[ele + 4 * i] /= scaleFactors[ele];
        }
      });

      let u = [
        tempMatrix[6] - tempMatrix[9],
        tempMatrix[8] - tempMatrix[2],
        tempMatrix[1] - tempMatrix[4],
      ];

      if (u[0].toFixed(2) != 1) {
        deg =
          (Math.acos((tempMatrix[0] - u[0] ** 2) / (1 - u[0] ** 2)) * 180) /
          Math.PI;
      } else if (u[1].toFixed(2) != 1) {
        deg =
          (Math.acos((tempMatrix[5] - u[1] ** 2) / (1 - u[1] ** 2)) * 180) /
          Math.PI;
      } else if (u[2].toFixed(2) != 1) {
        deg =
          (Math.acos((tempMatrix[10] - u[2] ** 2) / (1 - u[2] ** 2)) * 180) /
          Math.PI;
      }

      return {
        u: u.map((ele) => ele.toFixed(2)).join(","),
        deg: deg.toFixed(2) + "deg",
      };
    }
  }

  setSameTransformTo(ele) {
    let t = this.getTranslateFactors();
    let s = this.getScaleFactors();
    let r = this.getRotateFactors();
    console.log(t, s, r);

    ele.style.transform = `translate3d(${t["tx"]}px, ${t["ty"]}px, ${
      t["tz"]
    }px) scale3d(${s["sx"] + "," + s["sy"] + "," + s["sz"]}) rotate3d(${
      r["u"]
    }, ${r["deg"]})`;
  }
}
반응형