fdai7303
1 year ago
16 changed files with 1287 additions and 0 deletions
-
BINdist/matrix-finished.png
-
63dist/matrix.html
-
BINdist/scenegraph-finished.png
-
41dist/scenegraph.html
-
194src/07/matrix.ts
-
156src/07/setup-matrix.ts
-
54src/08/matrixcache.ts
-
86src/08/nodes.ts
-
52src/08/raytracing.ts
-
106src/08/setup-scenegraph.ts
-
100src/08/transformation.ts
-
54src/08/traversal.ts
-
190test/matrix-spec.ts
-
52test/nodes-spec.ts
-
69test/transformation-spec.ts
-
70test/traversal-spec.ts
After Width: 500 | Height: 500 | Size: 18 KiB |
@ -0,0 +1,63 @@ |
|||
<!DOCTYPE html> |
|||
|
|||
<html lang="en"> |
|||
|
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<title>Graphische Datenverarbeitung - Math - Matrix</title> |
|||
<script type="text/javascript" src="matrix.bundle.js"></script> |
|||
<link rel="icon" type="image/png" href="ai-logo.png"> |
|||
</head> |
|||
|
|||
<body> |
|||
<nav class="navbar navbar-light bg-light"> |
|||
<div class="container"> |
|||
<a class="navbar-brand" href="/index.html"> |
|||
<img src="ai-logo.png" width="30" height="30" class="d-inline-block align-top" alt=""> |
|||
Graphische Datenverarbeitung |
|||
</a> |
|||
<div class="navbar-brand">Matrix</div> |
|||
</div> |
|||
</nav> |
|||
<br> |
|||
<div class="container"> |
|||
<div class="row"> |
|||
<figure class="figure col-sm-8"> |
|||
<canvas id="result" class="figure-img mx-auto d-block" width="500" height="500"></canvas> |
|||
<figcaption class="figure-caption text-center"> |
|||
Implement the Matrix class. You may use the sliders to transform the sphere's midpoint |
|||
</figcaption> |
|||
</figure> |
|||
<div class="col-sm-4"> |
|||
<div class="form-group text-left col-sm-12"> |
|||
<input class="form-check-input" type="checkbox" id="userotation"></input> |
|||
<label class="form-check-label" for="rotation">Rotation</label> |
|||
<input class="form-control-range" type="range" min="0" max="6.28318" step="0.01" id="rotation" |
|||
disabled></input> |
|||
</div> |
|||
<div class="form-group text-left col-sm-12"> |
|||
<input class="form-check-input" type="checkbox" id="usetranslation"></input> |
|||
<label class="form-check-label" for="translation">Translation X</label> |
|||
<input class="form-control-range" type="range" min="-1" max="1" step="0.02" id="translation" |
|||
disabled></input> |
|||
<p> |
|||
Translation is done before rotation. Therefore the translate direction might be different from |
|||
what you expect. |
|||
</p> |
|||
</div> |
|||
<div class="form-group text-left col-sm-12"> |
|||
<input class="form-check-input" type="checkbox" id="usescale"></input> |
|||
<label class="form-check-label" for="scale">Scale</label> |
|||
<input class="form-control-range" type="range" min="1" max="2" step="0.01" id="scale" |
|||
disabled></input> |
|||
<p> |
|||
We only scale the sphere midpoint positions. The slider is from 1 to 2. The camera is in the |
|||
origin. Scaling to 2 will move the spheres further away from the camera. |
|||
</p> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</body> |
|||
|
|||
</html> |
After Width: 500 | Height: 500 | Size: 22 KiB |
@ -0,0 +1,41 @@ |
|||
<!DOCTYPE html> |
|||
|
|||
<html lang="en"> |
|||
|
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<title>Graphische Datenverarbeitung - Raytracing - Scenegraph</title> |
|||
<script type="text/javascript" src="scenegraph.bundle.js"></script> |
|||
<link rel="icon" type="image/png" href="ai-logo.png"> |
|||
</head> |
|||
|
|||
<body> |
|||
<nav class="navbar navbar-light bg-light"> |
|||
<div class="container"> |
|||
<a class="navbar-brand" href="/index.html"> |
|||
<img src="ai-logo.png" width="30" height="30" class="d-inline-block align-top" alt=""> |
|||
Graphische Datenverarbeitung |
|||
</a> |
|||
<div class="navbar-brand">Raytracing</div> |
|||
</div> |
|||
</nav> |
|||
<br> |
|||
<div class="container"> |
|||
<div class="row"> |
|||
<figure class="figure col-sm-6"> |
|||
<canvas id="result" class="figure-img mx-auto d-block" width="500" height="500"></canvas> |
|||
<figcaption class="figure-caption text-center"> |
|||
<p>Implement a Raytracer using a Scenegraph.</p> |
|||
<button id="startAnimationBtn" class="btn btn-dark">Start</button> |
|||
<button id="stopAnimationBtn" class="btn btn-dark">Stop</button> |
|||
</figcaption> |
|||
</figure> |
|||
<figure class="col-sm-6 figure"> |
|||
<img class="figure-img mx-auto d-block" src="scenegraph-finished.png"> |
|||
<figcaption class="figure-caption text-center">Reference image</figcaption> |
|||
</figure> |
|||
</div> |
|||
</div> |
|||
</body> |
|||
|
|||
</html> |
@ -0,0 +1,194 @@ |
|||
import Vector from '../05/vector'; |
|||
|
|||
/** |
|||
* Class representing a 4x4 Matrix |
|||
*/ |
|||
export default class Matrix { |
|||
|
|||
/** |
|||
* Data representing the matrix values |
|||
*/ |
|||
data: Float32Array; |
|||
|
|||
/** |
|||
* Constructor of the matrix. Expects an array in row-major layout. Saves the data as column major internally. |
|||
* @param mat Matrix values row major |
|||
*/ |
|||
constructor(mat: Array<number>) { |
|||
this.data = new Float32Array(16); |
|||
for (let row = 0; row < 4; row++) { |
|||
for (let col = 0; col < 4; col++) { |
|||
this.data[row * 4 + col] = mat[col * 4 + row]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Returns the values of the matrix in row-major layout. |
|||
* @return The values of the matrix |
|||
*/ |
|||
getVals(): Array<number> { |
|||
|
|||
var mat = new Array<number>(16); |
|||
for (let row = 0; row < 4; row++) { |
|||
for (let col = 0; col < 4; col++) { |
|||
mat[row * 4 + col] = this.data[col * 4 + row]; |
|||
} |
|||
} |
|||
return mat; |
|||
} |
|||
|
|||
/** |
|||
* Returns the value of the matrix at position row, col |
|||
* @param row The value's row |
|||
* @param col The value's column |
|||
* @return The requested value |
|||
*/ |
|||
getVal(row: number, col: number): number { |
|||
return this.data[col * 4 + row]; |
|||
} |
|||
|
|||
/** |
|||
* Sets the value of the matrix at position row, col |
|||
* @param row The value's row |
|||
* @param val The value to set to |
|||
* @param col The value's column |
|||
*/ |
|||
setVal(row: number, col: number, val: number) { |
|||
this.data[col * 4 + row] = val; |
|||
} |
|||
|
|||
/** |
|||
* Returns a matrix that represents a translation |
|||
* @param translation The translation vector that shall be expressed by the matrix |
|||
* @return The resulting translation matrix |
|||
*/ |
|||
static translation(translation: Vector): Matrix { |
|||
|
|||
// TODO: Return a new Matrix that is translated according to the given Vector
|
|||
return Matrix.identity(); |
|||
} |
|||
|
|||
/** |
|||
* Returns a matrix that represents a rotation. The rotation axis is either the x, y or z axis (either x, y, z is 1). |
|||
* @param axis The axis to rotate around |
|||
* @param angle The angle to rotate |
|||
* @return The resulting rotation matrix |
|||
*/ |
|||
static rotation(axis: Vector, angle: number): Matrix { |
|||
|
|||
// TODO: Return a new rotation matrix, distinguish the different
|
|||
// TODO: axis of rotation. You can use the axis vector
|
|||
// TODO: (1, 0, 0, 0) to specify the x-axis
|
|||
// TODO: (0, 1, 0, 0) to specify the y-axis
|
|||
// TODO: (0, 0, 1, 0) to specify the z-axis
|
|||
return Matrix.identity(); |
|||
} |
|||
|
|||
/** |
|||
* Returns a matrix that represents a scaling |
|||
* @param scale The amount to scale in each direction |
|||
* @return The resulting scaling matrix |
|||
*/ |
|||
static scaling(scale: Vector): Matrix { |
|||
// TODO: Return a new scaling Matrix with the scaling components of the Vector scale
|
|||
return Matrix.identity(); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Returns the identity matrix |
|||
* @return A new identity matrix |
|||
*/ |
|||
static identity(): Matrix { |
|||
return new Matrix([ |
|||
1, 0, 0, 0, |
|||
0, 1, 0, 0, |
|||
0, 0, 1, 0, |
|||
0, 0, 0, 1 |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* Matrix multiplication |
|||
* @param other The matrix to multiply with |
|||
* @return The result of the multiplication this*other |
|||
*/ |
|||
mul(other: Matrix): Matrix { |
|||
// TODO: Return a new Matrix mat with mat = this * other
|
|||
return Matrix.identity(); |
|||
} |
|||
|
|||
/** |
|||
* Matrix-vector multiplication |
|||
* @param other The vector to multiply with |
|||
* @return The result of the multiplication this * other |
|||
*/ |
|||
mulVec(other: Vector): Vector { |
|||
// TODO: Return a new Vector vec with vec = this * other
|
|||
return new Vector(0, 0, 0, 0); |
|||
} |
|||
|
|||
/** |
|||
* Returns the transpose of this matrix |
|||
* @return A new matrix that is the transposed of this |
|||
*/ |
|||
transpose(): Matrix { |
|||
// TODO: Return a new matrix that is the transposed of this
|
|||
return Matrix.identity(); |
|||
} |
|||
|
|||
/** |
|||
* Constructs a lookat matrix |
|||
* @param eye The position of the viewer |
|||
* @param center The position to look at |
|||
* @param up The up direction |
|||
* @return The resulting lookat matrix |
|||
*/ |
|||
static lookat(eye: Vector, center: Vector, up: Vector): Matrix { |
|||
// TODO: Return a new lookat Matrix
|
|||
return Matrix.identity(); |
|||
} |
|||
|
|||
/** |
|||
* Constructs a new matrix that represents a projection normalisation transformation |
|||
* @param left Camera-space left value of lower near point |
|||
* @param right Camera-space right value of upper right far point |
|||
* @param bottom Camera-space bottom value of lower lower near point |
|||
* @param top Camera-space top value of upper right far point |
|||
* @param near Camera-space near value of lower lower near point |
|||
* @param far Camera-space far value of upper right far point |
|||
* @return The rotation matrix |
|||
*/ |
|||
static frustum(left: number, right: number, bottom: number, top: number, near: number, far: number): Matrix { |
|||
// TODO: Return a new frustum Matrix
|
|||
return Matrix.identity(); |
|||
} |
|||
|
|||
/** |
|||
* Constructs a new matrix that represents a projection normalisation transformation. |
|||
* @param fovy Field of view in y-direction |
|||
* @param aspect Aspect ratio between width and height |
|||
* @param near Camera-space distance to near plane |
|||
* @param far Camera-space distance to far plane |
|||
* @return The resulting matrix |
|||
*/ |
|||
static perspective(fovy: number, aspect: number, near: number, far: number): Matrix { |
|||
// TODO: Return a new perspective Matrix, possibly reuse
|
|||
// TODO: Matrix.frustum
|
|||
return Matrix.identity(); |
|||
} |
|||
|
|||
/** |
|||
* Debug print to console |
|||
*/ |
|||
print() { |
|||
for (let row = 0; row < 4; row++) { |
|||
console.log("> " + this.getVal(row, 0) + |
|||
"\t" + this.getVal(row, 1) + |
|||
"\t" + this.getVal(row, 2) + |
|||
"\t" + this.getVal(row, 3) |
|||
); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,156 @@ |
|||
import 'bootstrap'; |
|||
import 'bootstrap/scss/bootstrap.scss'; |
|||
import Ray from '../05/ray'; |
|||
import { phong } from '../06/phong'; |
|||
import Sphere from '../05/sphere'; |
|||
import Vector from '../05/vector'; |
|||
import Matrix from './matrix'; |
|||
|
|||
window.addEventListener('load', () => { |
|||
|
|||
const canvas = document.getElementById("result") as HTMLCanvasElement; |
|||
if (canvas === null) |
|||
return; |
|||
|
|||
const ctx = canvas.getContext("2d"); |
|||
var pixel = ctx.createImageData(1, 1); |
|||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); |
|||
const data = imageData.data; |
|||
|
|||
const lightPositions = [ |
|||
new Vector(1, 1, -1, 1) |
|||
]; |
|||
|
|||
const shininess = 10; |
|||
|
|||
const camera = { |
|||
origin: new Vector(0, 0, 0, 1), |
|||
width: canvas.width, |
|||
height: canvas.height, |
|||
alpha: Math.PI / 3 |
|||
} |
|||
|
|||
function setPixel(x: number, y: number, color: Vector) { |
|||
data[4 * (canvas.width * y + x) + 0] = Math.min(255, color.r * 255); |
|||
data[4 * (canvas.width * y + x) + 1] = Math.min(255, color.g * 255); |
|||
data[4 * (canvas.width * y + x) + 2] = Math.min(255, color.b * 255); |
|||
data[4 * (canvas.width * y + x) + 3] = 255; |
|||
} |
|||
|
|||
let rotation = Matrix.identity(); |
|||
let translation = Matrix.identity(); |
|||
let scale = Matrix.identity(); |
|||
|
|||
function animate() { |
|||
|
|||
let matrix = Matrix.identity(); |
|||
|
|||
if (useRotationElement.checked) { |
|||
matrix = matrix.mul(rotation); |
|||
} |
|||
|
|||
if (useTranslationElement.checked) { |
|||
matrix = matrix.mul(translation); |
|||
} |
|||
|
|||
if (useScaleElement.checked) { |
|||
matrix = matrix.mul(scale); |
|||
} |
|||
|
|||
const blank = new Vector(1, 1, 1, 0); |
|||
|
|||
const sphere = new Sphere(matrix.mulVec(new Vector(0.1, 0, -1.5, 1)), 0.4, new Vector(.3, 0, 0, 1)); |
|||
for (let y = 0; y < canvas.height; y++) { |
|||
for (let x = 0; x < canvas.width; x++) { |
|||
|
|||
const ray = Ray.makeRay(x, y, camera); |
|||
const intersection = sphere.intersect(ray); |
|||
if (intersection) { |
|||
const color = phong(sphere.color, intersection, lightPositions, shininess, camera.origin); |
|||
setPixel(x, y, color); |
|||
|
|||
// update pixel in HTML context2d
|
|||
for (let i = 0; i < 4; i ++) |
|||
pixel.data[i] = data[(x + y * canvas.width) * 4 + i]; |
|||
ctx.putImageData(pixel, x, y); |
|||
} else { |
|||
setPixel(x, y, blank); |
|||
|
|||
// update pixel in HTML context2d
|
|||
for (let i = 0; i < 4; i ++) |
|||
pixel.data[i] = data[(x + y * canvas.width) * 4 + i]; |
|||
ctx.putImageData(pixel, x, y); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
window.requestAnimationFrame(animate); |
|||
|
|||
const useRotationElement = |
|||
document.getElementById("userotation") as HTMLInputElement; |
|||
|
|||
useRotationElement.onchange = () => { |
|||
let range = document.getElementById("rotation") as HTMLInputElement; |
|||
if (useRotationElement.checked) { |
|||
range.value = "0"; |
|||
range.oninput = () => { |
|||
rotation = Matrix.rotation(new Vector(0, 0, 1, 0), |
|||
Number(range.value)); |
|||
window.requestAnimationFrame(animate); |
|||
} |
|||
range.disabled = false; |
|||
range.oninput(new Event("click")); |
|||
} else { |
|||
range.disabled = true; |
|||
rotation = Matrix.identity(); |
|||
} |
|||
window.requestAnimationFrame(animate); |
|||
} |
|||
|
|||
const useTranslationElement = document.getElementById("usetranslation") as HTMLInputElement; |
|||
useTranslationElement.onchange = () => { |
|||
let range = document.getElementById("translation") as HTMLInputElement; |
|||
if (useTranslationElement.checked) { |
|||
range.value = "0"; |
|||
range.oninput = () => { |
|||
translation = Matrix.translation(new Vector(Number(range.value), 0, 0, 0)); |
|||
window.requestAnimationFrame(animate); |
|||
} |
|||
range.disabled = false; |
|||
range.oninput(new Event("click")); |
|||
} else { |
|||
range.disabled = true; |
|||
translation = Matrix.identity(); |
|||
} |
|||
window.requestAnimationFrame(animate); |
|||
} |
|||
|
|||
const useScaleElement = document.getElementById("usescale") as HTMLInputElement; |
|||
useScaleElement.onchange = () => { |
|||
let range = document.getElementById("scale") as HTMLInputElement; |
|||
if (useScaleElement.checked) { |
|||
range.value = "1"; |
|||
range.oninput = () => { |
|||
scale = Matrix.scaling(new Vector( |
|||
Number(range.value), |
|||
Number(range.value), |
|||
Number(range.value), 0)); |
|||
window.requestAnimationFrame(animate); |
|||
} |
|||
range.disabled = false; |
|||
range.oninput(new Event("click")); |
|||
} else { |
|||
range.disabled = true; |
|||
scale = Matrix.identity(); |
|||
} |
|||
window.requestAnimationFrame(animate); |
|||
} |
|||
|
|||
const sliders = ["rotation", "translation", "scale"]; |
|||
for (let t of sliders) { |
|||
const elem = document.getElementById("use" + t) as HTMLInputElement; |
|||
if (elem.checked) { |
|||
elem.onchange(new Event("click")); |
|||
} |
|||
} |
|||
}); |
@ -0,0 +1,54 @@ |
|||
import Matrix from "../07/matrix" |
|||
import { MatrixTransformation } from "./transformation"; |
|||
import { Node } from "./nodes"; |
|||
|
|||
/** |
|||
* A map containing <Node, MatrixTransformation> pairs. Used to cache |
|||
* the MatrixTransformation of a Node that has already been traversed. |
|||
* The map needs to be updated when Transformations are changed in the scenegraph. |
|||
*/ |
|||
export class MatrixCache { |
|||
|
|||
static matrices = new Map<Node, MatrixTransformation>(); |
|||
|
|||
/** |
|||
* Clear the cache |
|||
*/ |
|||
static clear() { |
|||
|
|||
MatrixCache.matrices.clear(); |
|||
} |
|||
|
|||
/** |
|||
* Clear the cached transformation of a given node. |
|||
* @param node the node to clear |
|||
*/ |
|||
static delete(node: Node) { |
|||
|
|||
MatrixCache.matrices.delete(node); |
|||
} |
|||
|
|||
/** |
|||
* Add a transformation of a given node to the cache. |
|||
* @param node the node to cache |
|||
* @param transform the transformation of the node to cache |
|||
*/ |
|||
static add(node: Node, transform: MatrixTransformation) { |
|||
|
|||
MatrixCache.matrices.set(node, transform); |
|||
} |
|||
|
|||
/** |
|||
* Try to retrieve a cached transformation from the cache. |
|||
* @param node the node |
|||
* @returns the cached transformation, null if no transformation is cached for this node |
|||
*/ |
|||
static get(node: Node): MatrixTransformation { |
|||
|
|||
if (MatrixCache.matrices.has(node)) { |
|||
return MatrixCache.matrices.get(node); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
@ -0,0 +1,86 @@ |
|||
import Vector from '../05/vector'; |
|||
import Sphere from '../05/sphere'; |
|||
import Ray from '../05/ray'; |
|||
import Intersection from '../05/intersection'; |
|||
|
|||
import { MatrixTransformation, Transformation } from './transformation'; |
|||
|
|||
/** |
|||
* Class representing a Node in a Scenegraph |
|||
* A Node holds a transformation. |
|||
*/ |
|||
export class Node { |
|||
|
|||
public transform: Transformation = null; |
|||
|
|||
constructor(transform: Transformation) { |
|||
this.transform = transform; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Class representing a GroupNode in the Scenegraph. |
|||
* A GroupNode holds a transformation and is able |
|||
* to have child nodes attached to it. |
|||
* @extends Node |
|||
*/ |
|||
export class GroupNode extends Node { |
|||
|
|||
/** |
|||
* The children of the group node |
|||
*/ |
|||
children: Array<Node>; |
|||
|
|||
/** |
|||
* Constructor |
|||
* @param transform The node's transformation |
|||
*/ |
|||
constructor(transform: Transformation) { |
|||
|
|||
super(transform); |
|||
this.children = []; |
|||
} |
|||
|
|||
/** |
|||
* Adds a child node |
|||
* @param childNode The child node to add |
|||
*/ |
|||
add(childNode: Node) { |
|||
// TODO: Add the childNode to the list of children
|
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Class representing a Sphere in the Scenegraph |
|||
* @extends Node |
|||
*/ |
|||
export class SphereNode extends Node { |
|||
|
|||
public static unit_sphere: Sphere = new Sphere(new Vector(0.0, 0.0, 0.0, 1.0), 1.0, new Vector(0.0, 0.0, 0.0, 0.0)); |
|||
|
|||
public color: Vector = null; |
|||
|
|||
/** |
|||
* Creates a new Sphere. |
|||
* The sphere is defined around the origin |
|||
* with radius 1. |
|||
* @param color The color of the Sphere |
|||
*/ |
|||
constructor(transform: Transformation, color: Vector) { |
|||
|
|||
super(transform); |
|||
this.color = color; |
|||
} |
|||
|
|||
/** |
|||
* Calculate the intersection of the ray with unit_sphere |
|||
* @param ray The ray to intersect with |
|||
* @returns An Intersection or null if there is no intersection |
|||
*/ |
|||
public intersect(ray: Ray): Intersection { |
|||
|
|||
// TODO: Intersect this ray with the unit_sphere and return the Intersection
|
|||
// TODO: Reuse Sphere.intersect that you have already implemented
|
|||
return null; |
|||
} |
|||
} |
@ -0,0 +1,52 @@ |
|||
import Camera from '../05/camera'; |
|||
import Intersection from '../05/intersection'; |
|||
import Vector from '../05/vector'; |
|||
import Ray from '../05/ray'; |
|||
import { phong } from '../06/phong'; |
|||
import { Traversal } from './traversal'; |
|||
import { Node, SphereNode } from './nodes'; |
|||
|
|||
/** |
|||
* Compute the color of the pixel (x, y) by raytracing |
|||
* using a given camera and a given scenegraph. |
|||
* |
|||
* @param data The linearised pixel array |
|||
* @param camera The camera used for raytracing |
|||
* @param scenegraph The root node of the scene to raytrace |
|||
* @param lightPositions The light positions |
|||
* @param shininess The shininess parameter of the Phong model |
|||
* @param width The width of the canvas |
|||
* @param height The height of the canvas |
|||
*/ |
|||
export function raytracePhong(data: Uint8ClampedArray, |
|||
camera: Camera, |
|||
scenegraph: Node, |
|||
lightPositions: Array<Vector>, |
|||
shininess: number, |
|||
x: number, y: number, |
|||
width: number, height: number) { |
|||
|
|||
let index = (x + y * width) * 4; |
|||
|
|||
// Create ray from camera through image plane at position (x, y).
|
|||
const ray = Ray.makeRay(x, y, camera); |
|||
|
|||
let intersections = new Array<Intersection>(); |
|||
let intersectionObjects = new Array<SphereNode>(); |
|||
|
|||
// Compute all the intersections by traversing the scenegraph
|
|||
// using Traversal.traverse.
|
|||
|
|||
Traversal.traverse(scenegraph, ray, scenegraph.transform, intersections, intersectionObjects); |
|||
|
|||
// TODO: Find the closest intersection from the intersections array.
|
|||
// TODO: Compute emission at point of intersection using phong model.
|
|||
// TODO: Set pixel color accordingly.
|
|||
// If there are no intersections, set pixel color to white
|
|||
if (intersections.length == 0) { |
|||
data[index + 0] = 255; |
|||
data[index + 1] = 255; |
|||
data[index + 2] = 255; |
|||
data[index + 3] = 255; |
|||
} |
|||
} |
@ -0,0 +1,106 @@ |
|||
import 'bootstrap'; |
|||
import 'bootstrap/scss/bootstrap.scss'; |
|||
|
|||
import Vector from '../05/vector'; |
|||
import { GroupNode, SphereNode } from './nodes'; |
|||
import { Rotation, Scaling, Translation } from './transformation'; |
|||
import { raytracePhong } from './raytracing'; |
|||
|
|||
window.addEventListener('load', () => { |
|||
|
|||
const canvas = document.getElementById("result") as HTMLCanvasElement; |
|||
if (canvas === null) |
|||
return; |
|||
|
|||
const ctx = canvas.getContext("2d"); |
|||
var pixel = ctx.createImageData(1, 1); |
|||
let imageData: ImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); |
|||
|
|||
const lightPositions = [ |
|||
new Vector(0, 0, 0, 1) |
|||
]; |
|||
|
|||
const camera = { |
|||
origin: new Vector(0, 0, 0, 1), |
|||
width: canvas.width, |
|||
height: canvas.height, |
|||
alpha: Math.PI / 3 |
|||
} |
|||
|
|||
const root = new GroupNode(new Translation(new Vector(0, 0, -5, 0))); |
|||
let moonRotation = new Rotation(new Vector(0.0, 1.0, 0.0, 0.0), 0.0); |
|||
|
|||
// TODO: Create a SceneGraph looking like this:
|
|||
// TODO:
|
|||
// TODO: o root GroupNode with Translation (0, 0, -5)
|
|||
// TODO: / \
|
|||
// TODO: / \
|
|||
// TODO: SphereNode o o GroupNode with moonRotation
|
|||
// TODO: \
|
|||
// TODO: o GroupNode with Translation (2, 0, 0)
|
|||
// TODO: \
|
|||
// TODO: o GroupNode with Scaling (0.2, 0.2, 0.2)
|
|||
// TODO: \
|
|||
// TODO: o SphereNode
|
|||
// TODO:
|
|||
|
|||
let animationHandle: number; |
|||
|
|||
let lastTimestamp = 0; |
|||
let animationTime = 0; |
|||
let animationHasStarted = true; |
|||
|
|||
function animate(timestamp: number) { |
|||
|
|||
console.log("animate"); |
|||
let deltaT = timestamp - lastTimestamp; |
|||
|
|||
if (animationHasStarted) { |
|||
deltaT = 0; |
|||
animationHasStarted = false; |
|||
} |
|||
|
|||
animationTime += deltaT; |
|||
lastTimestamp = timestamp; |
|||
|
|||
let data = imageData.data; |
|||
data.fill(0); |
|||
|
|||
const width = imageData.width; |
|||
const height = imageData.height; |
|||
|
|||
for (let x = 0; x < width; x++) { |
|||
for (let y = 0; y < height; y++) { |
|||
|
|||
raytracePhong(data, camera, root, lightPositions, 20, x, y, width, height); |
|||
|
|||
for (let i = 0; i < 4; i ++) |
|||
pixel.data[i] = data[(x + y * canvas.width) * 4 + i]; |
|||
ctx.putImageData(pixel, x, y); |
|||
} |
|||
} |
|||
|
|||
moonRotation.angle = -animationTime / 5000; |
|||
} |
|||
|
|||
function startAnimation() { |
|||
if (animationHandle) { |
|||
window.cancelAnimationFrame(animationHandle); |
|||
} |
|||
|
|||
animationHasStarted = true; |
|||
|
|||
function animation(t: number) { |
|||
animate(t); |
|||
animationHandle = window.requestAnimationFrame(animation); |
|||
} |
|||
|
|||
animationHandle = window.requestAnimationFrame(animation); |
|||
} |
|||
animate(0); |
|||
|
|||
document.getElementById("startAnimationBtn").addEventListener( |
|||
"click", startAnimation); |
|||
document.getElementById("stopAnimationBtn").addEventListener( |
|||
"click", () => cancelAnimationFrame(animationHandle)); |
|||
}); |
@ -0,0 +1,100 @@ |
|||
import Matrix from "../07/matrix"; |
|||
import Vector from "../05/vector"; |
|||
|
|||
export interface Transformation { |
|||
getMatrix(): Matrix; |
|||
getInverseMatrix(): Matrix; |
|||
} |
|||
|
|||
// TODO: constructors do not compile without super call.
|
|||
|
|||
/** |
|||
* The MatrixTransformation class holds a transformation as well as its |
|||
* inverse using matrices. |
|||
*/ |
|||
export class MatrixTransformation implements Transformation { |
|||
matrix: Matrix; |
|||
inverse: Matrix; |
|||
|
|||
constructor(matrix: Matrix, inverse: Matrix) { |
|||
this.matrix = matrix; |
|||
this.inverse = inverse; |
|||
} |
|||
|
|||
getMatrix(): Matrix { |
|||
return this.matrix; |
|||
} |
|||
|
|||
getInverseMatrix(): Matrix { |
|||
return this.inverse; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Translation holds a matrix for the translation, |
|||
* and a matrix for the inverse translation. |
|||
*/ |
|||
export class Translation extends MatrixTransformation { |
|||
|
|||
constructor(translation: Vector) { |
|||
|
|||
// TODO: Create 2 matrices, one for the translation and
|
|||
// TODO: one for its inverse.
|
|||
// TODO: Call the constructor of the super class with the two matrices.
|
|||
// TODO: "super" has to be the first call in the constructor, so you have to put
|
|||
// TODO: everything into a single line
|
|||
super(null, null); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Rotation holds a matrix for the rotation, |
|||
* and a matrix for the inverse rotation. |
|||
*/ |
|||
export class Rotation extends MatrixTransformation { |
|||
|
|||
private _axis: Vector; |
|||
private _angle: number; |
|||
|
|||
constructor(axis: Vector, angle: number) { |
|||
|
|||
// TODO: Create 2 matrices, one for the rotation and
|
|||
// TODO: one for its inverse.
|
|||
// TODO: Call the constructor of the super class with the two matrices.
|
|||
// TODO: "super" has to be the first call in the constructor, so you have to put
|
|||
// TODO: everything into a single line.
|
|||
// TODO: Store the axis and angle for later recalculation when the angle is changed.
|
|||
super(null, null); |
|||
} |
|||
|
|||
set axis(axis: Vector) { |
|||
this._axis = axis; |
|||
this.recalculate(); |
|||
} |
|||
|
|||
set angle(angle: number) { |
|||
this._angle = angle; |
|||
this.recalculate(); |
|||
} |
|||
|
|||
private recalculate() { |
|||
// TODO: Calculate a new rotation matrix and inverse
|
|||
// TODO: from this._axis and this._angle
|
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Scaling holds a matrix for the scaling, |
|||
* and a matrix for the inverse scaling. |
|||
*/ |
|||
export class Scaling extends MatrixTransformation { |
|||
|
|||
constructor(scale: Vector) { |
|||
// TODO: Create 2 matrices, one for the scaling and
|
|||
// TODO: one for its inverse.
|
|||
// TODO: Call the constructor of the super class with the two matrices.
|
|||
// TODO: "super" has to be the first call in the constructor, so you have to put
|
|||
// TODO: everything into a single line.
|
|||
super(null, null); |
|||
} |
|||
} |
@ -0,0 +1,54 @@ |
|||
import Camera from "../05/camera"; |
|||
import Ray from "../05/ray"; |
|||
import Intersection from "../05/intersection"; |
|||
|
|||
import { Node, GroupNode, SphereNode } from "./nodes"; |
|||
import { Transformation, MatrixTransformation } from "./transformation"; |
|||
|
|||
export class Traversal { |
|||
|
|||
/** |
|||
* Traverse through the scenegraph while intersecting all the SphereNodes |
|||
* in the graph. |
|||
* If an intersection between ray and a SphereNode occurs, add the |
|||
* intersection to the Array of Intersections, and add the SphereNode to the |
|||
* Array of Nodes. |
|||
* |
|||
* @param node The node in the scenegraph to traverse |
|||
* @param ray The current ray with which to raytrace |
|||
* @param transformation The current world transformation during traversal |
|||
* @param intersections An Array of intersections that needs to be filled |
|||
* @param intersectionObjects An Array of intersected Nodes that needs to be filled |
|||
*/ |
|||
public static traverse(node: Node, ray: Ray, transformation: Transformation, |
|||
intersections: Array<Intersection>, intersectionObjects: Array<SphereNode>) { |
|||
|
|||
if (node instanceof GroupNode) { |
|||
|
|||
// TODO: Recurse through the list of child nodes:
|
|||
// TODO: Calculate a new world matrix =
|
|||
// TODO: current transformation Matrix * the child transformation matrix
|
|||
// TODO: And the inverse world matrix =
|
|||
// TODO: child inverse * current inverse transformation Matrix
|
|||
} |
|||
|
|||
if (node instanceof SphereNode) { |
|||
|
|||
// TODO: Calculate a new Ray for intersection testing.
|
|||
// TODO: If you passed the correct matrices during traversal of
|
|||
// TODO: the GroupNodes, "transformation" is currently
|
|||
// TODO: in world coordinates.
|
|||
// TODO: 1. Transform the ray's origin and direction vector to
|
|||
// TODO: the object coordinate system by multiplying with
|
|||
// TODO: the inverse transformation matrix.
|
|||
// TODO: 2. Perform the intersection by reusing sphere.intersect
|
|||
// TODO: 3. If there is an intersection, transform the resulting
|
|||
// TODO: intersection point and intersection normal back to
|
|||
// TODO: world coordinates, by multiplying with the current
|
|||
// TODO: transformation matrix. Re-calculate "t" from the
|
|||
// TODO: transformed intersection point in world coordinates.
|
|||
// TODO: 4. Push the intersection and intersection object to
|
|||
// TODO: "intersections" and "intersectionObjects", respectively
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,190 @@ |
|||
import Vector from "../src/05/vector"; |
|||
import Matrix from "../src/07/matrix"; |
|||
|
|||
import { assert, expect } from 'chai'; |
|||
import { SingleEntryPlugin } from "webpack"; |
|||
|
|||
describe('Matrix', () => { |
|||
|
|||
it('can be initialized with an array consisting of 16 numbers', () => { |
|||
const m = new Matrix([ |
|||
0, 0, 0, 0, |
|||
0, 0, 0, 0, |
|||
0, 0, 0, 0, |
|||
0, 0, 0, 1 |
|||
]); |
|||
expect(m).to.be.an('object'); |
|||
}); |
|||
|
|||
it('single values can be set and retrieved', () => { |
|||
const m = Matrix.identity(); |
|||
|
|||
expect(m).has.property('setVal'); |
|||
expect(m).has.property('getVal'); |
|||
m.setVal(1, 2, 342); |
|||
expect(m.getVal(1, 2)).to.equal(342); |
|||
}); |
|||
|
|||
it('all values can be retrieved', () => { |
|||
|
|||
const m = new Matrix([ |
|||
0, 1, 2, 3, |
|||
4, 5, 6, 7, |
|||
8, 9, 10, 11, |
|||
12, 13, 14, 15 |
|||
]); |
|||
|
|||
expect(m).has.property('getVals'); |
|||
var mat = m.getVals(); |
|||
assert.deepEqual(mat, [ |
|||
0, 1, 2, 3, |
|||
4, 5, 6, 7, |
|||
8, 9, 10, 11, |
|||
12, 13, 14, 15 |
|||
]); |
|||
}); |
|||
|
|||
it('translation works', () => { |
|||
|
|||
expect(Matrix).has.property('translation'); |
|||
|
|||
const t = Matrix.translation(new Vector(1, 2, 3, 0)); |
|||
|
|||
expect(t).to.not.be.null; |
|||
expect(t).to.be.an('object'); |
|||
|
|||
assert.deepEqual(t.getVals(), [ |
|||
1, 0, 0, 1, |
|||
0, 1, 0, 2, |
|||
0, 0, 1, 3, |
|||
0, 0, 0, 1 |
|||
]); |
|||
}); |
|||
|
|||
it('rotation around x-axis works', () => { |
|||
|
|||
expect(Matrix).has.property('rotation'); |
|||
|
|||
const x = Matrix.rotation(new Vector(1, 0, 0, 0), Math.PI / 4); |
|||
|
|||
expect(x).to.not.be.null; |
|||
expect(x).to.be.an('object'); |
|||
assert.deepEqual(x.getVals(), [ |
|||
1, 0, 0, 0, |
|||
0, 0.7071067690849304, -0.7071067690849304, 0, |
|||
0, 0.7071067690849304, 0.7071067690849304, 0, |
|||
0, 0, 0, 1 |
|||
]); |
|||
}); |
|||
|
|||
it('rotation around y-axis works', () => { |
|||
|
|||
expect(Matrix).has.property('rotation'); |
|||
|
|||
const y = Matrix.rotation(new Vector(0, 1, 0, 0), -Math.PI / 4); |
|||
expect(y).to.not.be.null; |
|||
expect(y).to.be.an('object'); |
|||
assert.deepEqual(y.getVals(), [ |
|||
0.7071067690849304, 0, -0.7071067690849304, 0, |
|||
0, 1, 0, 0, |
|||
0.7071067690849304, 0, 0.7071067690849304, 0, |
|||
0, 0, 0, 1 |
|||
]); |
|||
}); |
|||
|
|||
it('rotation around z-axis works', () => { |
|||
|
|||
expect(Matrix).has.property('rotation'); |
|||
|
|||
const z = Matrix.rotation(new Vector(0, 0, 1, 0), -Math.PI / 4); |
|||
|
|||
expect(z).to.not.be.null; |
|||
expect(z).to.be.an('object'); |
|||
assert.deepEqual(z.getVals(), [ |
|||
0.7071067690849304, 0.7071067690849304, 0, 0, |
|||
-0.7071067690849304, 0.7071067690849304, 0, 0, |
|||
0, 0, 1, 0, |
|||
0, 0, 0, 1 |
|||
]); |
|||
}); |
|||
|
|||
it('scaling matrix works', () => { |
|||
|
|||
expect(Matrix).has.property('scaling'); |
|||
|
|||
const m = Matrix.scaling(new Vector(2, 3, 4, 0)); |
|||
|
|||
expect(m).to.not.be.null; |
|||
expect(m).to.be.an('object'); |
|||
assert.deepEqual(m.getVals(), [ |
|||
2, 0, 0, 0, |
|||
0, 3, 0, 0, |
|||
0, 0, 4, 0, |
|||
0, 0, 0, 1 |
|||
]); |
|||
}); |
|||
|
|||
it('matrix vector multiplication works', () => { |
|||
|
|||
let m1: Matrix = new Matrix([ |
|||
-1, 0, 0, 1, |
|||
0, -1, 0, 2, |
|||
0, 0, 1, 3, |
|||
0, 0, 0, 1 ]); |
|||
|
|||
let a = new Vector(0, 1, 0, 1); |
|||
let b = new Vector(-1, 1, 0, 1); |
|||
|
|||
expect(m1).to.not.be.null; |
|||
expect(m1).to.be.an('object'); |
|||
|
|||
expect(m1).has.property('mulVec'); |
|||
|
|||
let at = m1.mulVec(a); |
|||
|
|||
assert.deepEqual(at.valueOf(), [ |
|||
1, 1, 3, 1 |
|||
]); |
|||
|
|||
let m2: Matrix = new Matrix([ |
|||
1, 0, 0, 0, |
|||
0, 0, -1, 0, |
|||
0, 1, 0, 0, |
|||
0, 0, 0, 1 ]); |
|||
|
|||
let bt = m2.mulVec(b); |
|||
|
|||
assert.deepEqual(bt.valueOf(), [ |
|||
-1, 0, 1, 1 |
|||
]); |
|||
}); |
|||
|
|||
it('matrix matrix multiplication works', () => { |
|||
|
|||
let m1: Matrix = new Matrix([ |
|||
-1, 0, 0, 1, |
|||
0, -1, 0, 2, |
|||
0, 0, 1, 3, |
|||
0, 0, 0, 1 ]); |
|||
|
|||
expect(m1).to.not.be.null; |
|||
expect(m1).to.be.an('object'); |
|||
|
|||
expect(m1).has.property('mul'); |
|||
|
|||
let m2: Matrix = new Matrix([ |
|||
1, 0, 0, 0, |
|||
0, 0, -1, 0, |
|||
0, 1, 0, 0, |
|||
0, 0, 0, 1 ]); |
|||
|
|||
let m = m1.mul(m2); |
|||
|
|||
assert.deepEqual(m.getVals(), [ |
|||
-1, 0, 0, 1, |
|||
0, 0, 1, 2, |
|||
0, 1, 0, 3, |
|||
0, 0, 0, 1 |
|||
]); |
|||
}); |
|||
}); |
@ -0,0 +1,52 @@ |
|||
import { assert, expect } from 'chai'; |
|||
import Intersection from '../src/05/intersection'; |
|||
import Ray from '../src/05/ray'; |
|||
|
|||
import Vector from "../src/05/vector"; |
|||
import Matrix from '../src/07/matrix'; |
|||
|
|||
import { GroupNode, SphereNode } from '../src/08/nodes'; |
|||
import { Translation, Rotation, Scaling, MatrixTransformation, Transformation } from "../src/08/transformation"; |
|||
|
|||
describe('GroupNode', () => { |
|||
|
|||
it('can be correctly initialized with a Transformation', () => { |
|||
|
|||
let t: Transformation = new MatrixTransformation(Matrix.identity(), Matrix.identity()); |
|||
let g: GroupNode = new GroupNode(t); |
|||
expect(g).to.be.an('object'); |
|||
}); |
|||
|
|||
it('can have children', () => { |
|||
|
|||
let t: Transformation = new MatrixTransformation(Matrix.identity(), Matrix.identity()); |
|||
let g: GroupNode = new GroupNode(t); |
|||
let c: GroupNode = new GroupNode(t); |
|||
|
|||
expect(g).to.be.an('object'); |
|||
expect(c).to.be.an('object'); |
|||
|
|||
g.add(c); |
|||
|
|||
expect(g.children).to.be.an('Array'); |
|||
expect(g.children.length).to.equal(1); |
|||
}); |
|||
}); |
|||
|
|||
describe('SphereNode', () => { |
|||
it('can be intersected in Origin', () => { |
|||
|
|||
let t: Transformation = new MatrixTransformation(Matrix.identity(), Matrix.identity()); |
|||
let s: SphereNode = new SphereNode(t, new Vector(0.0, 0.0, 0.0, 1.0)); |
|||
|
|||
expect(s).to.be.an('object'); |
|||
|
|||
let r: Ray = new Ray(new Vector(0.0, 0.0, 10, 1.0), new Vector(0.0, 0.0, -1.0, 0.0)); |
|||
let i: Intersection = s.intersect(r); |
|||
|
|||
assert.deepEqual(i.point.data, [ |
|||
0, 0, 1, 1, |
|||
]); |
|||
}); |
|||
}); |
|||
|
@ -0,0 +1,69 @@ |
|||
import { assert, expect } from 'chai'; |
|||
|
|||
import { Translation, Rotation, Scaling } from "../src/08/transformation"; |
|||
import Vector from "../src/05/vector"; |
|||
|
|||
describe('Translation', () => { |
|||
|
|||
it('can be correctly initialized with a Vector', () => { |
|||
|
|||
const t: Translation = new Translation(new Vector(1.0, 2.0, 3.0, 0.0)); |
|||
expect(t).to.be.an('object'); |
|||
assert.deepEqual(t.getMatrix().getVals(), [ |
|||
1, 0, 0, 1, |
|||
0, 1, 0, 2, |
|||
0, 0, 1, 3, |
|||
0, 0, 0, 1 |
|||
]); |
|||
assert.deepEqual(t.getInverseMatrix().getVals(), [ |
|||
1, 0, 0, -1, |
|||
0, 1, 0, -2, |
|||
0, 0, 1, -3, |
|||
0, 0, 0, 1 |
|||
]); |
|||
}); |
|||
}); |
|||
|
|||
describe('Rotation', () => { |
|||
|
|||
it('can be correctly initialized with an axis and an angle', () => { |
|||
|
|||
const r: Rotation = new Rotation(new Vector(0.0, 0.0, 1.0, 0.0), Math.PI / 4); |
|||
console.log(r.matrix); |
|||
expect(r).to.be.an('object'); |
|||
assert.deepEqual(r.getMatrix().getVals(), [ |
|||
0.7071067690849304, -0.7071067690849304, 0, 0, |
|||
0.7071067690849304, 0.7071067690849304, 0, 0, |
|||
0, 0, 1, 0, |
|||
0, 0, 0, 1 |
|||
]); |
|||
assert.deepEqual(r.getInverseMatrix().getVals(), [ |
|||
0.7071067690849304, 0.7071067690849304, 0, 0, |
|||
-0.7071067690849304, 0.7071067690849304, 0, 0, |
|||
0, 0, 1, 0, |
|||
0, 0, 0, 1 |
|||
]); |
|||
|
|||
}); |
|||
}); |
|||
|
|||
describe('Scaling', () => { |
|||
|
|||
it('can be correctly initialized with a Vector', () => { |
|||
|
|||
const s: Scaling = new Scaling(new Vector(1.0, 2.0, 4.0, 0.0)); |
|||
expect(s).to.be.an('object'); |
|||
assert.deepEqual(s.getMatrix().getVals(), [ |
|||
1, 0, 0, 0, |
|||
0, 2, 0, 0, |
|||
0, 0, 4, 0, |
|||
0, 0, 0, 1 |
|||
]); |
|||
assert.deepEqual(s.getInverseMatrix().getVals(), [ |
|||
1, 0, 0, 0, |
|||
0, 1/2, 0, 0, |
|||
0, 0, 1/4, 0, |
|||
0, 0, 0, 1 |
|||
]); |
|||
}); |
|||
}); |
@ -0,0 +1,70 @@ |
|||
import Ray from "../src/05/ray"; |
|||
import Camera from "../src/05/camera"; |
|||
|
|||
import { assert, expect } from 'chai'; |
|||
import { GroupNode, SphereNode } from "../src/08/nodes"; |
|||
import { Translation, Rotation } from "../src/08/transformation"; |
|||
import Vector from "../src/05/vector"; |
|||
import Sphere from "../src/05/sphere"; |
|||
import Intersection from "../src/05/intersection"; |
|||
import { Traversal } from "../src/08/traversal"; |
|||
|
|||
describe('Traversal', () => { |
|||
|
|||
it('Moved & rotated SphereNode can be intersected correctly', () => { |
|||
let t: GroupNode = new GroupNode(new Translation(new Vector(0.0, 0.0, -5.0, 0.0))); |
|||
let r: GroupNode = new GroupNode(new Rotation(new Vector(0.0, 1.0, 0.0, 0.0), Math.PI / 180 * 90)); |
|||
t.add(r); |
|||
|
|||
let s: SphereNode = new SphereNode(new Translation(new Vector(0.0, 0.0, 0.0, 0.0)), new Vector(0.0, 0.0, 0.0, 0.0)); |
|||
r.add(s); |
|||
|
|||
let ray: Ray = new Ray(new Vector(0.0, 0.0, 0.0, 1.0), new Vector(0.0, 0.0, -1.0, 0.0)); |
|||
|
|||
let intersection = new Array<Intersection>(); |
|||
let intersectionObjects = new Array<SphereNode>(); |
|||
Traversal.traverse(t, ray, t.transform, intersection, intersectionObjects); |
|||
|
|||
// rotation should not move sphere
|
|||
expect(intersection.length).to.equal(1); |
|||
expect(intersection[0].point.x).to.be.closeTo(0, 0.00001); |
|||
expect(intersection[0].point.y).to.be.closeTo(0, 0.00001); |
|||
expect(intersection[0].point.z).to.be.closeTo(-4, 0.00001); |
|||
expect(intersection[0].point.w).to.be.closeTo(1, 0.00001); |
|||
|
|||
r.children.pop(); |
|||
let s1: SphereNode = new SphereNode(new Translation(new Vector(1.0, 0.0, 0.0, 0.0)), new Vector(0.0, 0.0, 0.0, 0.0)); |
|||
r.add(s1); |
|||
intersection = new Array<Intersection>(); |
|||
intersectionObjects = new Array<SphereNode>(); |
|||
Traversal.traverse(t, ray, t.transform, intersection, intersectionObjects); |
|||
|
|||
// rotating 90° around y and translating in x direction should move in z direction
|
|||
expect(intersection.length).to.equal(1); |
|||
expect(intersection[0].point.x).to.be.closeTo(0, 0.00001); |
|||
expect(intersection[0].point.y).to.be.closeTo(0, 0.00001); |
|||
expect(intersection[0].point.z).to.be.closeTo(-5, 0.00001); |
|||
expect(intersection[0].point.w).to.be.closeTo(1, 0.00001); |
|||
|
|||
t.children.pop(); |
|||
r = new GroupNode(new Rotation(new Vector(0.0, 0.0, 1.0, 0.0), Math.PI / 180 * 90)); |
|||
t.add(r); |
|||
|
|||
let s2 = new SphereNode(new Translation(new Vector(0.999999999999, 0.0, 0.0, 0.0)), new Vector(0.0, 0.0, 0.0, 0.0)); |
|||
r.add(s2); |
|||
intersection = new Array<Intersection>(); |
|||
intersectionObjects = new Array<SphereNode>(); |
|||
Traversal.traverse(t, ray, t.transform, intersection, intersectionObjects); |
|||
|
|||
// rotating 90° around z and translating in x direction should move in y direction
|
|||
expect(intersection.length).to.equal(1); |
|||
expect(intersection[0].point.x).to.be.closeTo(0, 0.00001); |
|||
expect(intersection[0].point.y).to.be.closeTo(0, 0.00001); |
|||
expect(intersection[0].point.z).to.be.closeTo(-5, 0.00001); |
|||
expect(intersection[0].point.w).to.be.closeTo(1, 0.00001); |
|||
expect(intersection[0].normal.x).to.be.closeTo(0, 0.00001); |
|||
expect(intersection[0].normal.y).to.be.closeTo(-1, 0.00001); |
|||
expect(intersection[0].normal.z).to.be.closeTo(0, 0.00001); |
|||
expect(intersection[0].normal.w).to.be.closeTo(0, 0.00001); |
|||
}); |
|||
}); |
Write
Preview
Loading…
Cancel
Save
Reference in new issue