Advertisement
Guest User

grants code

a guest
Sep 17th, 2019
144
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.92 KB | None | 0 0
  1. // utility function to make sure we don't have too small numbers
  2. function epsilon( value: number ): number {
  3. return Math.abs( value ) < 0.000001 ? 0 : value;
  4. }
  5. // convert degrees to radians
  6. var degreeToRadiansFactor = Math.PI / 180;
  7. function degToRad( degrees: number): number {
  8. return degrees * degreeToRadiansFactor;
  9. }
  10.  
  11. // convert radians to degress
  12. var radianToDegreesFactor = 180 / Math.PI;
  13. function radToDeg( radians: number): number {
  14. return radians * radianToDegreesFactor;
  15. }
  16.  
  17. // minimal vector class
  18. export class Vector {
  19. constructor(public x: number,
  20. public y: number,
  21. public z: number) {
  22. }
  23. static times(k: number, v: Vector) { return new Vector(k * v.x, k * v.y, k * v.z); }
  24. static minus(v1: Vector, v2: Vector) { return new Vector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); }
  25. static plus(v1: Vector, v2: Vector) { return new Vector(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z); }
  26. static dot(v1: Vector, v2: Vector) { return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; }
  27. static mag(v: Vector) { return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); }
  28. static norm(v: Vector) {
  29. var mag = Vector.mag(v);
  30. var div = (mag === 0) ? Infinity : 1.0 / mag;
  31. return Vector.times(div, v);
  32. }
  33. static cross(v1: Vector, v2: Vector) {
  34. return new Vector(v1.y * v2.z - v1.z * v2.y,
  35. v1.z * v2.x - v1.x * v2.z,
  36. v1.x * v2.y - v1.y * v2.x);
  37. }
  38. toString(): string {
  39. return "[" + this.x + ", " + this.y + ", " + this.z + "]";
  40. }
  41. }
  42.  
  43. ///////////////////////////////////////////
  44. // Skelton of a matrix class, that you should fill in
  45. //
  46. // Note: these methods create and return new matrix objects. A realistic implementation would NOT do that,
  47. // as it causes a lot of allocations (and, in a language like Javascript, allocating and throwing away objects
  48. // eventualy triggers garbage collection, which can cause smooth animation to stutter). But, we do it here as
  49. // it is easier to implement correctly.
  50. export class Matrix {
  51. // the matrix elements
  52. elements: number[];
  53.  
  54. // construct a new matrix (including copying one and creating an identity matrix)
  55. constructor ( n11: number, n12: number, n13: number, n14: number,
  56. n21: number, n22: number, n23: number, n24: number,
  57. n31: number, n32: number, n33: number, n34: number,
  58. n41: number, n42: number, n43: number, n44: number ) {
  59. this.elements = new Array<number>( 16 );
  60. var te = this.elements;
  61. te[ 0 ] = n11; te[ 4 ] = n12; te[ 8 ] = n13; te[ 12 ] = n14;
  62. te[ 1 ] = n21; te[ 5 ] = n22; te[ 9 ] = n23; te[ 13 ] = n24;
  63. te[ 2 ] = n31; te[ 6 ] = n32; te[ 10 ] = n33; te[ 14 ] = n34;
  64. te[ 3 ] = n41; te[ 7 ] = n42; te[ 11 ] = n43; te[ 15 ] = n44;
  65. return this;
  66. }
  67.  
  68. // transpose the matrix, returning a new matrix with the result
  69. static transpose(m: Matrix): Matrix {
  70. var te = m.elements;
  71. return new Matrix(te[0], te[1], te[2], te[3],
  72. te[4], te[5], te[6], te[7],
  73. te[8], te[9], te[10], te[11],
  74. te[12], te[13], te[14], te[15]);
  75. }
  76.  
  77. // copy the matrix to a new matrix
  78. static copy (m: Matrix): Matrix {
  79. var te = m.elements;
  80. return new Matrix(te[0], te[4], te[8], te[12],
  81. te[1], te[5], te[9], te[13],
  82. te[2], te[6], te[10], te[14],
  83. te[3], te[7], te[11], te[15]);
  84. }
  85.  
  86. // return a new matrix containing the identify matrix
  87. static identity(): Matrix {
  88. return new Matrix(1, 0, 0, 0,
  89. 0, 1, 0, 0,
  90. 0, 0, 1, 0,
  91. 0, 0, 0, 1);
  92. }
  93.  
  94. // create a new rotation matrix from the input vector.
  95. // eu.x, eu.y, eu.z contain the rotations in degrees around the three axes.
  96. // Apply the rotations in the order x, y, z.
  97. static makeRotationFromEuler (eu: Vector): Matrix {
  98. var xAng = degToRad(eu.x);
  99. var yAng = degToRad(eu.y);
  100. var zAng = degToRad(eu.z);
  101. var xMat = new Matrix(1, 0, 0, 0,
  102. 0, Math.cos(xAng), -Math.sin(xAng), 0,
  103. 0, Math.sin(xAng), Math.cos(xAng), 0,
  104. 0, 0, 0, 1);
  105. var yMat = new Matrix(Math.cos(yAng), 0, Math.sin(yAng), 0,
  106. 0, 1, 0, 0,
  107. -Math.sin(yAng), 0, Math.cos(yAng), 0,
  108. 0, 0, 0, 1);
  109. var zMat = new Matrix(Math.cos(zAng), -Math.sin(zAng), 0, 0,
  110. Math.sin(zAng), Math.cos(zAng), 0, 0,
  111. 0, 0, 1, 0,
  112. 0, 0, 0, 1);
  113. return (xMat.multiply(yMat)).multiply(zMat);
  114. }
  115.  
  116. // create a new translation matrix from the input vector
  117. // t.x, t.y, t.z contain the translation values in each direction
  118. static makeTranslation(t: Vector): Matrix {
  119. return new Matrix(1, 0, 0, t.x,
  120. 0, 1, 0, t.y,
  121. 0, 0, 1, t.z,
  122. 0, 0, 0, 1);
  123. }
  124.  
  125. // create inverse of translation matrix from the input vector
  126. // t.x, t.y, t.z contain the translation values in each direction
  127. static makeTranslationInverse(t: Vector): Matrix {
  128. return new Matrix(1, 0, 0, -t.x,
  129. 0, 1, 0, -t.y,
  130. 0, 0, 1, -t.z,
  131. 0, 0, 0, 1);
  132. }
  133.  
  134. // create a new scale matrix from the input vector
  135. // s.x, s.y, s.z contain the scale values in each direction
  136. static makeScale(s: Vector): Matrix {
  137. return new Matrix(s.x, 0, 0, 0,
  138. 0, s.y, 0, 0,
  139. 0, 0, s.z, 0,
  140. 0, 0, 0, 1);
  141.  
  142. }
  143.  
  144. // create a inverse of scale matrix from the input vector
  145. // s.x, s.y, s.z contain the scale values in each direction
  146. static makeScaleInverse(s: Vector): Matrix {
  147. return new Matrix(1/s.x, 0, 0, 0,
  148. 0, 1/s.y, 0, 0,
  149. 0, 0, 1/s.z, 0,
  150. 0, 0, 0, 1);
  151.  
  152. }
  153.  
  154.  
  155. // gets the value of a cell in the product of matrix multiplication
  156. // given the input matricies and the row and column of the cell
  157. colRowMult(a: number[], b: number[], row: number, col: number): number {
  158. var ans = 0;
  159. for (var i = 0; i < 4; i++) {
  160. ans += a[row + i * 4] * b[col * 4 + i];
  161. }
  162. return ans;
  163. }
  164. // compose transformations with multiplication. Multiply this * b,
  165. // returning the result in a new matrix
  166. multiply (b: Matrix ): Matrix {
  167. var te = this.elements;
  168. var be = b.elements;
  169. var m = this.colRowMult;
  170. return new Matrix(m(te, be, 0, 0), m(te, be, 0, 1), m(te, be, 0, 2), m(te, be, 0, 3),
  171. m(te, be, 1, 0), m(te, be, 1, 1), m(te, be, 1, 2), m(te, be, 1, 3),
  172. m(te, be, 2, 0), m(te, be, 2, 1), m(te, be, 2, 2), m(te, be, 2, 3),
  173. m(te, be, 3, 0), m(te, be, 3, 1), m(te, be, 3, 2), m(te, be, 3, 3));
  174. }
  175.  
  176. // get the translation/positional componenet out of the matrix
  177. getPosition(): Vector {
  178. var te = this.elements;
  179. return new Vector(te[12], te[13], te[14])
  180. }
  181.  
  182. // get the x, y and z vectors out of the rotation part of the matrix
  183. getXVector(): Vector {
  184. var te = this.elements;
  185. return new Vector(te[0], te[1], te[2]);
  186. }
  187. getYVector(): Vector {
  188. var te = this.elements;
  189. return new Vector(te[4], te[5], te[6]);
  190. }
  191. getZVector(): Vector {
  192. var te = this.elements;
  193. return new Vector(te[7], te[8], te[9]);
  194. }
  195.  
  196. // utility if you want to print it out
  197. toString(): string {
  198. var te = this.elements;
  199. return "[" +
  200. te[ 0 ] + ", " + te[ 4 ] + ", " + te[ 8 ] + ", " + te[ 12 ] + ",\n" +
  201. te[ 1 ] + ", " + te[ 5 ] + ", " + te[ 9 ] + ", " + te[ 13 ] + ",\n" +
  202. te[ 2 ] + ", " + te[ 6 ] + ", " + te[ 10 ]+ ", " + te[ 14 ] + ",\n" +
  203. te[ 3 ] + ", " + te[ 7 ] + ", " + te[ 11 ]+ ", " + te[ 15 ] + "]";
  204. }
  205. }
  206.  
  207. //////////////////////////////////////////
  208. // The nodes in the graph and the scene are inspired by the raytracer, but are different.
  209. // All the nodes in the tree are Things
  210. export class Thing {
  211. // the children of the node, and the parent
  212. children: Thing[];
  213. parent: Thing | null;
  214.  
  215. // store position and scale as vectors, but orientation as a matrix, since there are many
  216. // ways to create an orientation matrix
  217. position: Vector;
  218. rotation: Matrix;
  219. scale: Vector;
  220.  
  221. // the transform should be computed as position * rotation * scale, and NOT be set by the
  222. // programmer who is using this library
  223. transform: Matrix;
  224.  
  225. // inverse should be computed
  226. inverseTransform: Matrix;
  227.  
  228. // each node will store it's worldTransform as well as it's local transform
  229. worldTransform: Matrix;
  230.  
  231. cameraTransform: Matrix;
  232.  
  233. constructor() {
  234. this.position = new Vector(0,0,0);
  235. this.rotation = Matrix.identity();
  236. this.scale = new Vector(1,1,1);
  237.  
  238. this.parent = null;
  239. this.children = new Array();
  240. this.transform = Matrix.identity();
  241. this.inverseTransform = Matrix.identity();
  242. this.worldTransform = Matrix.identity();
  243. this.cameraTransform = Matrix.identity();
  244. }
  245.  
  246. // add and remote children to this Thing. Note that we are careful to have the parent set, and to ensure only
  247. // one parent has this element as a child
  248. add(c: Thing) {
  249. this.children.push(c);
  250. if (c.parent) {
  251. c.parent.remove(c);
  252. }
  253. c.parent = this;
  254. }
  255. remove(c: Thing) {
  256. var index = this.children.indexOf( c );
  257.  
  258. if ( index !== - 1 ) {
  259. c.parent = null;
  260. this.children.splice( index, 1 );
  261. }
  262. }
  263.  
  264. // compute transform from position * rotation * scale and inverseTransform from their inverses
  265. computeTransforms() {
  266. var posMat = Matrix.makeTranslation(this.position);
  267. var scaleMat = Matrix.makeScale(this.scale);
  268. this.transform = (posMat.multiply(this.rotation)).multiply(scaleMat);
  269.  
  270. var invPos = Matrix.makeTranslationInverse(this.position);
  271. var invScale = Matrix.makeScaleInverse(this.scale);
  272. var invRot = Matrix.transpose(this.rotation);
  273. this.inverseTransform = (invScale.multiply(invRot)).multiply(invPos);
  274. if (this.parent) {
  275. this.worldTransform = this.parent.worldTransform.multiply(this.transform);
  276. }
  277. }
  278.  
  279. // traverse the graph, executing the provided callback on this node and it's children
  280. // execute the callback before traversing the children
  281. traverse ( callback: (obj: Thing ) => void ) {
  282. callback(this);
  283. for (var i = 0; i < this.children.length; i++) {
  284. var child = this.children[i];
  285. child.traverse(callback);
  286. }
  287. }
  288. traverseHTMLBois ( callback: (obj: HTMLDivThing ) => void ) {
  289. if (this instanceof HTMLDivThing) {
  290. callback(this);
  291. }
  292. for (var i = 0; i < this.children.length; i++) {
  293. var child = this.children[i];
  294. child.traverseHTMLBois(callback);
  295. }
  296. }
  297.  
  298. findCamera(): Camera | null{
  299. if (this instanceof Camera) {
  300. return this;
  301. }
  302. if (this.children.length == 0) {
  303. return null;
  304. }
  305. var res = null;
  306. for (var i = 0; i < this.children.length; i++) {
  307. var child = this.children[i];
  308. res = child.findCamera();
  309. if (res != null) {
  310. return res;
  311. }
  312. }
  313. return res;
  314. }
  315. findWorldInverseTransform(cam: Thing): Matrix{
  316. if (this == cam) {
  317. return this.inverseTransform;
  318. }
  319. var ans = Matrix.identity();
  320. for (var i = 0; i < this.children.length; i++) {
  321. var child = this.children[i];
  322. ans = child.findWorldInverseTransform(cam);
  323. if (ans != Matrix.identity()) {
  324. return ans.multiply(this.inverseTransform);
  325. }
  326. }
  327. return Matrix.identity();
  328. }
  329. }
  330.  
  331. // The Thing that puts something on the screen is the HTMLDivThing.
  332. // The HTMLDivThing is simply a holder for the div being manipulated by the library.
  333. // By having it be a class, we can recognize when a node is one of these and handle appropriately
  334. export class HTMLDivThing extends Thing {
  335. constructor(public div: HTMLDivElement) {
  336. super();
  337. this.div.style.position = 'absolute';
  338. }
  339. }
  340.  
  341. // The Camera Thing. There must be one and only one in the Scene.
  342. export class Camera extends Thing {
  343. // hint: you will need to figure out and keep track of the inverse transform from
  344. // the camera up to the root of the scene.
  345. worldInverseTransform: Matrix;
  346.  
  347. constructor(public fovy: number) {
  348. super();
  349. this.worldInverseTransform = Matrix.identity();
  350. }
  351.  
  352. // get the focal length (distance from the viewplane) for a window of a specified
  353. // height and the camera's fovy
  354. getFocalLength (height: number): number {
  355. return ((height/2) / Math.tan(degToRad(this.fovy/2)))
  356. }
  357. }
  358.  
  359. // A scene!
  360. export class Scene {
  361. world: Thing;
  362. camera: Camera | null;
  363.  
  364. // internal
  365. private domElement: HTMLDivElement;
  366. private width: number;
  367. private height: number;
  368. private windowTransform: string;
  369.  
  370. constructor(public container: HTMLDivElement) {
  371. this.world = new Thing();
  372. this.camera = null;
  373.  
  374. this.domElement = document.createElement( 'div' );
  375.  
  376. // uncomment this to clip the contents of the domElement to the boundaries of the
  377. // domElement; otherwise, div's can go outside of it's boundaries (useful for
  378. // debugging!)
  379.  
  380. //this.domElement.style.overflow = 'hidden';
  381.  
  382. // set the transform-style to "preserve-3d" so the 3D values inherit
  383. this.domElement.style.transformStyle = "preserve-3d";
  384.  
  385. // add our new DOM element to the provided container
  386. this.container.appendChild(this.domElement);
  387.  
  388. // get the size of the provided container, and set our DOM element to it's size
  389. var rect = container.getBoundingClientRect();
  390. this.width = rect.width;
  391. this.height = rect.height;
  392. this.domElement.style.width = this.width + 'px';
  393. this.domElement.style.height = this.height + 'px';
  394.  
  395. // CSS uses a weird +y is DOWN coordinate frame, so we're going to
  396. // scale by -1 in Y in each of the elements, and then undo that scale here.
  397. // By doing this, all of our transformations can by in the more common
  398. // +1 is UP coordinate frame.
  399. // We'll also translate to the center of the viewport (CSS coords are now in the
  400. // lower left)
  401. this.windowTransform = "matrix3d(1,0,0,0, 0,-1,0,0, 0,0,1,0, 0,0,0,1)" +
  402. " translate3d(" + this.width/2 + 'px, ' + this.height/2 + 'px, 0px)';
  403. }
  404.  
  405. // convenience function provided so you don't have to fight with this peculiarity of CSS.
  406. // we invert Y here, as described above. We also translate the DIV so it's center is
  407. // at the origin instead of it's lower left corner.
  408. getObjectCSSMatrix( m: Matrix ): string {
  409. var elements = m.elements;
  410.  
  411. return 'translate3d(-50%, -50%, 0) matrix3d(' +
  412. epsilon( elements[ 0 ] ) + ',' +
  413. epsilon( elements[ 1 ] ) + ',' +
  414. epsilon( elements[ 2 ] ) + ',' +
  415. epsilon( elements[ 3 ] ) + ',' +
  416. epsilon( - elements[ 4 ] ) + ',' +
  417. epsilon( - elements[ 5 ] ) + ',' +
  418. epsilon( - elements[ 6 ] ) + ',' +
  419. epsilon( - elements[ 7 ] ) + ',' +
  420. epsilon( elements[ 8 ] ) + ',' +
  421. epsilon( elements[ 9 ] ) + ',' +
  422. epsilon( elements[ 10 ] ) + ',' +
  423. epsilon( elements[ 11 ] ) + ',' +
  424. epsilon( elements[ 12 ] ) + ',' +
  425. epsilon( elements[ 13 ] ) + ',' +
  426. epsilon( elements[ 14 ] ) + ',' +
  427. epsilon( elements[ 15 ] ) +
  428. ')';
  429. };
  430.  
  431. // the render function.
  432. //
  433. // In here, you should:
  434. // - update all the Things' internal matrices
  435. // - update all the Things' worldTransforms
  436. // - find the Camera and save it, and figure out it's inverse transformation to the root
  437. // - set the perspective on this.container and add a translation to move the camera to it's origin,
  438. // both based on the focalLength, as follows:
  439. // var focalLength = this.camera.getFocalLength(this.height).toString();
  440. // this.container.style.perspective = focalLength + "px";
  441. // this.domElement.style.transform = "translate3d(0px,0px," + focalLength + "px)" + this.windowTransform;
  442. // - for each object, figure out the entire transformation to that object
  443. // (including the inverse camera transformation).
  444. // - add the DIV's in the HTMLDivThings directly to this.domElement (do not use a
  445. // heirarchy) and set the transformation as follows:
  446. // const transformStr = this.getObjectCSSMatrix(m);
  447. // obj.div.style.transform = transformStr;
  448. //
  449. // hint: you will need to traverse the graph more than once to do all of this.
  450.  
  451. render() {
  452. // here is an example of declaring a function inline and calling the traverse method
  453. // to walk through the graph.
  454. var updateMatricies = (obj: Thing) => {
  455. obj.computeTransforms();
  456. }
  457. this.world.traverse(updateMatricies);
  458. this.camera = this.world.findCamera();
  459.  
  460. if (this.camera) {
  461. var worldInverseTransform = this.world.findWorldInverseTransform(this.camera);
  462. this.camera.worldInverseTransform = worldInverseTransform;
  463. var focalLength = this.camera.getFocalLength(this.height).toString();
  464. this.container.style.perspective = focalLength + "px";
  465. this.domElement.style.transform = "translate3d(0px,0px," + focalLength + "px)" + this.windowTransform;
  466. var cameraTransform = (obj: Thing) => {
  467. obj.cameraTransform = worldInverseTransform.multiply(obj.worldTransform);
  468. }
  469. this.world.traverse(cameraTransform);
  470. var domStuff = (obj: HTMLDivThing) => {
  471. const transformStr = this.getObjectCSSMatrix(obj.cameraTransform);
  472. obj.div.style.transform = transformStr;
  473. this.domElement.appendChild(obj.div);
  474. }
  475. this.world.traverseHTMLBois(domStuff);
  476. }
  477.  
  478. }
  479. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement