Browse Source

added tasks 5 & 6

master
fdai7303 2 years ago
parent
commit
e4f4bf6760
  1. 4
      .vscode/settings.json
  2. BIN
      dist/manyspheres-finished.png
  3. 39
      dist/manyspheres.html
  4. BIN
      dist/phong-finished.png
  5. 43
      dist/phong.html
  6. BIN
      dist/raytracing-finished.png
  7. 39
      dist/raytracing.html
  8. 19
      src/04/bresenhamsimple.ts
  9. 12
      src/04/ddasimple.ts
  10. 32
      src/05/camera.ts
  11. 39
      src/05/intersection.ts
  12. 28
      src/05/manyspheres.ts
  13. 38
      src/05/ray.ts
  14. 28
      src/05/raytracing.ts
  15. 51
      src/05/setup-manyspheres.ts
  16. 38
      src/05/setup-raytracing.ts
  17. 49
      src/05/sphere.ts
  18. 256
      src/05/vector.ts
  19. 35
      src/06/phong.ts
  20. 32
      src/06/raytracing.ts
  21. 62
      src/06/setup-phong.ts
  22. 50
      test/ray-spec.ts
  23. 78
      test/sphere-spec.ts
  24. 275
      test/vector-spec.ts

4
.vscode/settings.json

@ -0,0 +1,4 @@
{
"mochaExplorer.require": "ts-node/register",
"mochaExplorer.files": "test/*.ts",
}

BIN
dist/manyspheres-finished.png

After

Width: 500  |  Height: 500  |  Size: 2.5 KiB

39
dist/manyspheres.html

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Graphische Datenverarbeitung - Raytracing - Many Spheres</title>
<script type="text/javascript" src="manyspheres.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">
Implement a Raytracer by sending a ray into the scene for every pixel. Color the pixel with the color of the nearest sphere if the ray hits.
</figcaption>
</figure>
<figure class="col-sm-6 figure">
<img class="figure-img mx-auto d-block" src="manyspheres-finished.png">
<figcaption class="figure-caption text-center">Reference image</figcaption>
</figure>
</div>
</div>
</body>
</html>

BIN
dist/phong-finished.png

After

Width: 500  |  Height: 500  |  Size: 24 KiB

43
dist/phong.html

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Graphische Datenverarbeitung - Raytracing - Phong</title>
<script type="text/javascript" src="phong.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">Phong</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">
Implement a Raytracer that uses the Phong Lighting model.
<div class="form-group text-left">
<label for="shininess">Shininess</label>
<input class="form-control-range" id="shininess" type="range" min="1" max="50">
</div>
</figcaption>
</figure>
<figure class="col-sm-6 figure">
<img class="figure-img mx-auto d-block" src="phong-finished.png">
<figcaption class="figure-caption text-center">Reference image</figcaption>
</figure>
</div>
</div>
</body>
</html>

BIN
dist/raytracing-finished.png

After

Width: 500  |  Height: 500  |  Size: 2.9 KiB

39
dist/raytracing.html

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Graphische Datenverarbeitung - Raytracing - Basic Raytracing</title>
<script type="text/javascript" src="raytracing.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">
Implement a Raytracer by sending a ray into the scene for every pixel. Color the pixel black if the ray hits the given sphere.
</figcaption>
</figure>
<figure class="col-sm-6 figure">
<img class="figure-img mx-auto d-block" src="raytracing-finished.png">
<figcaption class="figure-caption text-center">Reference image</figcaption>
</figure>
</div>
</div>
</body>
</html>

19
src/04/bresenhamsimple.ts

@ -21,4 +21,23 @@ export function bresenhamSimple(data: Uint8ClampedArray, pointA: [number, number
// TODO: 1. Calculate dx and dy and set the start position x and y
// TODO: 2. Calculate the initial epsilon of the bresenham algorithm
// TODO: 3. Go from pointA[0] to pointB[0], and update epsilon in each step as given in the bresenham algorithm. Increase y when necessary.
var dX = pointB[0] - pointA[0];
var dY = pointB[1] - pointA[1];
var x = pointA[0];
var y = pointA[1];
var err = 2 * dY - dX;
while(x < pointB[0]) {
if(err <= 0) {
err += 2 * dY;
} else {
y = y + 1;
err += 2*dY - 2*dX;
}
setPixel(data, x, y, width, height);
x++;
}
}

12
src/04/ddasimple.ts

@ -25,4 +25,16 @@ export function ddaSimple(
// TODO: Calculcate the slope m for a line from pointA to pointB.
// TODO: In this example, the main direction of the line is the x-direction.
// TODO: Go from the x-coordinate of pointA (pointA[0]) to the x-coordinate of pointB (pointB[0]) and calculate the y-coordinate of the pixels in between.
pointA[0] = Math.round(pointA[0]);
pointA[1] = Math.round(pointA[1]);
pointB[0] = Math.round(pointB[0]);
pointB[1] = Math.round(pointB[1]);
var m = (pointB[1] + pointA[1]) / (pointB[0] + pointA[0]);
setPixel(data, pointA[0], pointA[1], width, height);
setPixel(data, pointB[0], pointB[1], width, height);
for(let i = 1; i < (pointB[0] - pointA[0]); i++) {
setPixel(data, pointA[0] + i, pointA[1] + Math.round( m * i), width, height);
}
}

32
src/05/camera.ts

@ -0,0 +1,32 @@
import Vector from '../05/vector';
/**
* A class representing a camera
*/
export default class Camera {
public width: number;
public height: number;
public alpha: number;
public origin: Vector;
/**
* Creates a new camera with an image canvas, a field of view, and a position in world space.
* For now, the camera is always viewing along the negative z-axis.
* @param width The width of the canvas
* @param height The height of the canvas
* @param alpha The field of view in X dimension of the camera
* @param origin The origin of the camera in world coordinates
*/
constructor(
width: number,
height: number,
alpha: number,
origin: Vector = new Vector(0, 0, 0, 1)
) {
this.width = width;
this.height = height;
this.alpha = alpha;
this.origin = origin;
}
}

39
src/05/intersection.ts

@ -0,0 +1,39 @@
import Vector from './vector';
/**
* Class representing a ray-sphere intersection in 3D space
*/
export default class Intersection {
public t: number;
public point: Vector;
public normal: Vector;
/**
* Create an Intersection
* @param t The distance on the ray
* @param point The intersection point
* @param normal The normal of the surface at the point of intersection
*/
constructor(t: number = Infinity,
point: Vector = null,
normal: Vector = null) {
this.t = t;
this.point = point;
this.normal = normal;
}
/**
* Determines whether this intersection
* is closer than the other
* @param other The other Intersection
* @return The result
*/
closerThan(other: Intersection): boolean {
if (this.t < other.t)
return true;
else
return false;
}
}

28
src/05/manyspheres.ts

@ -0,0 +1,28 @@
import Camera from './camera';
import Sphere from './sphere';
import Intersection from './intersection';
import Vector from './vector';
import Ray from './ray';
/**
* Compute the color of the pixel (x, y) by raytracing
* using a given camera and multiple spheres.
*
* @param data The linearised pixel array
* @param camera The camera used for raytracing
* @param spheres The spheres to raytrace
* @param x The x coordinate of the pixel to convert
* @param y The y coordinate of the pixel to convert
* @param width The width of the canvas
* @param height The height of the canvas
*/
export function raytrace(data: Uint8ClampedArray,
camera: Camera,
spheres: Array<Sphere>,
x: number, y: number,
width: number, height: number) {
// TODO: Generate ray and perform intersection with every sphere.
// TODO: On intersection set pixel color to color of the sphere
// TODO: containing the closest intersection point.
}

38
src/05/ray.ts

@ -0,0 +1,38 @@
import Vector from './vector';
import Camera from './camera';
/**
* Class representing a ray
*/
export default class Ray {
public origin: Vector = null;
public direction: Vector = null;
/**
* Creates a new ray with origin and direction
* @param origin The origin of the Ray
* @param direction The direction of the Ray
*/
constructor(origin: Vector, direction: Vector) {
this.origin = origin;
this.direction = direction;
}
/**
* Creates a ray from the camera through the image plane.
* The image plane is positioned in direction of the negative z-axis.
* @param x The pixel's x-position in the canvas
* @param y The pixel's y-position in the canvas
* @param camera The Camera
* @return The resulting Ray
*/
static makeRay(x: number, y: number, camera: Camera): Ray {
// TODO: Generate a ray from the camera origin through pixel (x, y)
// TODO: on the image plane. In addition to the coordinates (x, y), you will need the
// TODO: width and height of the camera (i.e. the width and height of the camera's
// TODO: image plane), and the angle alpha specifying the camera's field of view.
return null;
}
}

28
src/05/raytracing.ts

@ -0,0 +1,28 @@
import Camera from './camera';
import Sphere from './sphere';
import Ray from './ray';
/**
* Compute the color of the pixel (x, y) by raytracing
* using a given camera and a sphere.
*
* @param data The linearised pixel array
* @param camera The camera used for raytracing
* @param sphere The sphere to raytrace
* @param x The x coordinate of the pixel to convert
* @param y The y coordinate of the pixel to convert
* @param width The width of the canvas
* @param height The height of the canvas
*/
export function raytrace(data: Uint8ClampedArray,
camera: Camera,
sphere: Sphere,
x: number, y: number,
width: number, height: number) {
// TODO: Create a ray from the camera's position through the pixel
// TODO: (x, y) in the camera's image plane, and perform intersection
// TODO: with the given sphere. Set color of pixel (x, y) in the data
// TODO: array to black, if the ray hits the sphere.
}

51
src/05/setup-manyspheres.ts

@ -0,0 +1,51 @@
import 'bootstrap';
import 'bootstrap/scss/bootstrap.scss';
import Sphere from './sphere';
import Vector from './vector';
import Camera from './camera';
import { raytrace } from './manyspheres';
window.addEventListener('load', evt => {
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 spheres: Sphere[] = [
new Sphere(
new Vector(0, 0, -10, 1),
2.0,
new Vector(1, 0, 0, 1)
),
new Sphere(
new Vector(2, 0, -12, 1),
1.5,
new Vector(0, 1, 0, 1)
),
new Sphere(
new Vector(-2, 0, -8, 1),
1.0,
new Vector(0, 0, 1, 1)
)
];
const camera = new Camera(canvas.width, canvas.height, Math.PI / 3);
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
raytrace(data, camera, spheres, x, y, canvas.width, canvas.height);
// 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);
}
}
});

38
src/05/setup-raytracing.ts

@ -0,0 +1,38 @@
import 'bootstrap';
import 'bootstrap/scss/bootstrap.scss';
import Sphere from './sphere';
import Vector from './vector';
import Camera from './camera';
import { raytrace } from './raytracing';
window.addEventListener('load', evt => {
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 sphere = new Sphere(
new Vector(0, 0, -10, 1), // position
4.0, // radius
new Vector(0, 0, 0, 1) // color
);
const camera = new Camera(canvas.width, canvas.height, Math.PI / 3);
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
raytrace(data, camera, sphere, x, y, canvas.width, canvas.height);
// 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);
}
}
});

49
src/05/sphere.ts

@ -0,0 +1,49 @@
import Vector from './vector';
import Intersection from './intersection';
import Ray from './ray';
/**
* A class representing a sphere
*/
export default class Sphere {
public center: Vector;
public radius: number;
public color: Vector;
/**
* Creates a new Sphere with center and radius
* @param center The center of the Sphere
* @param radius The radius of the Sphere
* @param color The color of the Sphere
*/
constructor(
center: Vector,
radius: number,
color: Vector
) {
this.center = center;
this.radius = radius;
this.color = color;
}
/**
* Calculates the intersection of the sphere with the given ray
* @param ray The ray to intersect with
* @return The intersection if there is one, null if there is none
*/
intersect(ray: Ray): Intersection | null {
// TODO: Calculate the quadratic equation for ray-sphere
// TODO: intersection. You will need the origin of your ray as x0,
// TODO: the ray direction, and the radius of the sphere.
// TODO: Don't forget to translate your ray's starting position with
// TODO: respect to the center of the sphere.
// TODO: Calculate the discriminant c, and distinguish between the 3
// TODO: possible outcomes: no hit, one hit, or two hits.
// TODO: Return an Intersection or null if there was no hit. In case
// TODO: of two hits, return the one closer to the start point of
// TODO: the ray.
return null;
}
}

256
src/05/vector.ts

@ -0,0 +1,256 @@
/**
* Class representing a vector in 4D space
*/
export default class Vector {
/**
* The variable to hold the vector data
*/
data: [number, number, number, number];
/**
* Create a vector
* @param x The x component
* @param y The y component
* @param z The z component
* @param w The w component
*/
constructor(x: number, y: number, z: number, w: number) {
// TODO: Set the data member components to the given values
}
/**
* Returns the x component of the vector
* @return The x component of the vector
*/
get x(): number {
// TODO: Return actual value
return null;
}
/**
* Sets the x component of the vector to val
* @param val - The new value
*/
set x(val: number) {
// TODO: Set actual value
}
/**
* Returns the first component of the vector
* @return The first component of the vector
*/
get r(): number {
// TODO: Return actual value
return null;
}
/**
* Sets the first component of the vector to val
* @param val The new value
*/
set r(val: number) {
// TODO: Set actual value
}
/**
* Returns the y component of the vector
* @return The y component of the vector
*/
get y(): number {
// TODO: Return actual value
return null;
}
/**
* Sets the y component of the vector to val
* @param val The new value
*/
set y(val: number) {
// TODO: Set actual value
}
/**
* Returns the second component of the vector
* @return The second component of the vector
*/
get g(): number {
// TODO: Return actual value
return null;
}
/**
* Sets the second component of the vector to val
* @param val The new value
*/
set g(val: number) {
// TODO: Set actual value
}
/**
* Returns the z component of the vector
* @return The z component of the vector
*/
get z(): number {
// TODO: Return actual value
return null;
}
/**
* Sets the z component of the vector to val
* @param val The new value
*/
set z(val: number) {
// TODO: Set actual value
}
/**
* Returns the third component of the vector
* @return The third component of the vector
*/
get b(): number {
// TODO: Return actual value
return null;
}
/**
* Sets the third component of the vector to val
* @param val The new value
*/
set b(val: number) {
// TODO: Set actual value
}
/**
* Returns the w component of the vector
* @return The w component of the vector
*/
get w(): number {
// TODO: Return actual value
return null;
}
/**
* Sets the w component of the vector to val
* @param val The new value
*/
set w(val: number) {
// TODO: Set actual value
}
/**
* Returns the fourth component of the vector
* @return The fourth component of the vector
*/
get a(): number {
// TODO: Return actual value
return null;
}
/**
* Sets the fourth component of the vector to val
* @param val The new value
*/
set a(val: number) {
// TODO: Set actual value
}
/**
* Creates a new vector with the vector added
* @param other The vector to add
* @return The new vector;
*/
add(other: Vector): Vector {
// TODO: Return new vector with result
return null;
}
/**
* Creates a new vector with the vector subtracted
* @param other The vector to subtract
* @return The new vector
*/
sub(other: Vector): Vector {
// TODO: Return new vector with result
return null;
}
/**
* Creates a new vector with the scalar multiplied
* @param other The scalar to multiply
* @return The new vector
*/
mul(other: number): Vector {
// TODO: Return new vector with result
return null;
}
/**
* Creates a new vector with the scalar divided
* @param other The scalar to divide
* @return The new vector
*/
div(other: number): Vector {
// TODO: Return new vector with result
return null;
}
/**
* Dot product
* @param other The vector to calculate the dot product with
* @return The result of the dot product
*/
dot(other: Vector): number {
// TODO: Compute and return dot product
return 0;
}
/**
* Cross product
* Calculates the cross product using the first three components.
* @param other The vector to calculate the cross product with
* @return The result of the cross product as new Vector
*/
cross(other: Vector): Vector {
// TODO: Return new vector with result
// TODO: The fourth component should be set to 0
return null;
}
/**
* Normalizes this vector in place
* @returns this vector for easier function chaining
*/
normalize(): Vector {
// TODO: Normalize this vector and return it
return this;
}
/**
* Compares the vector to another vector.
* @param other The vector to compare to.
* @return True if the vectors carry equal numbers.
*/
equals(other: Vector): boolean {
// TODO: Perform comparison and return result
// TODO: Respect inaccuracies: coordinates within 0.000001 of each other
// TODO: should be considered equal
return false;
}
/**
* Calculates the length of the vector
* @return The length of the vector
*/
get length(): number {
// TODO: Calculate and return length
return 0;
}
/**
* Returns an array representation of the vector
* @return An array representation.
*/
valueOf(): [number, number, number, number] {
return this.data;
}
}

35
src/06/phong.ts

@ -0,0 +1,35 @@
import Vector from '../05/vector';
import Intersection from '../05/intersection';
/**
* Calculate the color of an object at the intersection point according to the Phong Lighting model.
* @param color The color of the intersected object
* @param intersection The intersection information
* @param lightPositions The light positions
* @param shininess The shininess parameter of the Phong model
* @param cameraPosition The position of the camera
* @return The resulting color
*/
export function phong(
color: Vector,
intersection: Intersection,
lightPositions: Array<Vector>,
shininess: number,
cameraPosition: Vector
): Vector {
const lightColor = new Vector(0.8, 0.8, 0.8, 0);
const kA = 1.0;
const kD = 0.5;
const kS = 0.5;
// TODO: Compute light intensity according to phong reflection model.
// TODO: Compute diffuse lighting using light color, diffuse
// TODO: reflectivity, light positions and an intersection point.
// TODO: Compute specular reflection using light color, specular
// TODO: reflectivity, shininess, light positions, an intersection
// TODO: point, and eye (camera) position.
// TODO: Return complete phong emission using object color, ambient,
// TODO: diffuse and specular terms.
return color;
}

32
src/06/raytracing.ts

@ -0,0 +1,32 @@
import Camera from '../05/camera';
import Sphere from '../05/sphere';
import Intersection from '../05/intersection';
import Vector from '../05/vector';
import Ray from '../05/ray';
import { phong } from './phong';
/**
* Compute the color of the pixel (x, y) by raytracing
* using a given camera and multiple spheres.
*
* @param data The linearised pixel array
* @param camera The camera used for raytracing
* @param spheres The spheres to raytrace
* @param x The x coordinate of the pixel to convert
* @param y The y coordinate of the pixel to convert
* @param width The width of the canvas
* @param height The height of the canvas
*/
export function raytracePhong(data: Uint8ClampedArray,
camera: Camera,
spheres: Array<Sphere>,
lightPositions: Array<Vector>,
shininess: number,
x: number, y: number,
width: number, height: number) {
// TODO: Create ray from camera through image plane at position (x, y).
// TODO: Compute closest intersection with spheres in the scene.
// TODO: Compute emission at point of intersection using phong model.
// TODO: Set pixel color accordingly.
}

62
src/06/setup-phong.ts

@ -0,0 +1,62 @@
import 'bootstrap';
import 'bootstrap/scss/bootstrap.scss';
import Vector from '../05/vector';
import Sphere from '../05/sphere';
import Ray from '../05/ray';
import Camera from '../05/camera';
import Intersection from '../05/intersection';
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);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const spheres: Sphere[] = [
new Sphere(new Vector(.5, -.2, -2, 1), 0.4, new Vector(.3, 0, 0, 1)),
new Sphere(new Vector(-.5, -.2, -1.7, 1), 0.2, new Vector(0, 0, .3, 1))
];
const lightPositions = [
new Vector(1, 1, -1, 1)
];
let shininess = 10;
const camera = new Camera(
canvas.width, canvas.height, Math.PI / 3, new Vector(0, 0, 0, 1)
);
function animate() {
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
raytracePhong(data, camera, spheres, lightPositions, shininess, x, y, canvas.width, canvas.height);
// 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);
}
}
}
const shininessElement =
document.getElementById("shininess") as HTMLInputElement;
shininessElement.onchange = function () {
shininess = Number(shininessElement.value);
window.requestAnimationFrame(animate);
}
shininess = Number(shininessElement.value);
window.requestAnimationFrame(animate);
});

50
test/ray-spec.ts

@ -0,0 +1,50 @@
import Ray from "../src/05/ray";
import Camera from "../src/05/camera";
import { assert, expect } from 'chai';
describe('Ray', () => {
it('can be initialized with two numbers and a camera', () => {
const r: Ray = Ray.makeRay(0, 0, new Camera(128, 128, 45));
expect(r).to.be.an('object');
});
it('the origin of the ray is initialized correctly', () => {
const r: Ray = Ray.makeRay(0, 0, new Camera(128, 128, 45));
expect(r).to.be.an('object');
expect(r.origin.x).to.equal(0);
expect(r.origin.y).to.equal(0);
expect(r.origin.z).to.equal(0);
expect(r.origin.w).to.equal(1);
});
it('the direction is normalized', () => {
const r: Ray = Ray.makeRay(64, 64, new Camera(129, 129, 45));
expect(r).to.be.an('object');
expect(r.direction.length).to.equal(1);
});
it('the direction is initialized correctly', () => {
const r: Ray = Ray.makeRay(64, 64, new Camera(129, 129, 45));
expect(r).to.be.an('object');
expect(r.direction.x).to.be.closeTo(0, 0.01);
expect(r.direction.y).to.be.closeTo(0, 0.01);
expect(r.direction.z).to.be.closeTo(-1, 0.01);
expect(r.direction.w).to.be.closeTo(0, 0.01);
const r2: Ray = Ray.makeRay(0, 0, new Camera(129, 129, 45));
expect(r2).to.be.an('object');
expect(r2.direction.x).to.be.closeTo(-0.435, 0.01);
expect(r2.direction.y).to.be.closeTo(0.435, 0.01);
expect(r2.direction.z).to.be.closeTo(-0.787, 0.01);
expect(r2.direction.w).to.be.closeTo(0, 0.01);
const r3: Ray = Ray.makeRay(10, 7, new Camera(64, 64, 90));
expect(r3).to.be.an('object');
expect(r3.direction.x).to.be.closeTo(-0.564, 0.01);
expect(r3.direction.y).to.be.closeTo(0.642, 0.01);
expect(r3.direction.z).to.be.closeTo(-0.518, 0.01);
expect(r3.direction.w).to.equal(0);
});
});

78
test/sphere-spec.ts

@ -0,0 +1,78 @@
import Sphere from "../src/05/sphere";
import Intersection from "../src/05/intersection";
import { assert, expect } from 'chai';
import Vector from "../src/05/vector";
import Ray from "../src/05/ray";
describe('Sphere', () => {
it('can be initialized with center, radius and color', () => {
const s: Sphere = new Sphere(new Vector(0, 0, 0, 1), 1, new Vector(0, 0, 0, 0));
expect(s).to.be.an('object');
});
it('a sphere at origin and radius 1 can be intersected correctly with the x axis', () => {
const s: Sphere = new Sphere(new Vector(0, 0, 0, 1), 1, new Vector(0, 0, 0, 0));
const i: Intersection = s.intersect(new Ray(new Vector(-10, 0, 0, 1), new Vector(1, 0, 0, 0)));
expect(s).to.be.an('object');
expect(i).to.be.an('object');
expect(i.point.x).to.equal(-1);
expect(i.point.y).to.equal(0);
expect(i.point.z).to.equal(0);
});
it('a sphere at origin and radius 1 can be intersected correctly with the y axis', () => {
const s: Sphere = new Sphere(new Vector(0, 0, 0, 1), 1, new Vector(0, 0, 0, 0));
const i: Intersection = s.intersect(new Ray(new Vector(0, -10, 0, 1), new Vector(0, 1, 0, 0)));
expect(s).to.be.an('object');
expect(i).to.be.an('object');
expect(i.point.x).to.equal(0);
expect(i.point.y).to.equal(-1);
expect(i.point.z).to.equal(0);
});
it('a sphere at origin and radius 1 can be intersected correctly with the z axis', () => {
const s: Sphere = new Sphere(new Vector(0, 0, 0, 1), 1, new Vector(0, 0, 0, 0));
const i: Intersection = s.intersect(new Ray(new Vector(0, 0, -10, 1), new Vector(0, 0, 1, 0)));
expect(s).to.be.an('object');
expect(i).to.be.an('object');
expect(i.point.x).to.equal(0);
expect(i.point.y).to.equal(0);
expect(i.point.z).to.equal(-1);
});
it('intersection is correct when radius != 1', () => {
const s: Sphere = new Sphere(new Vector(0, 0, 0, 1), 2.5, new Vector(0, 0, 0, 0));
const i: Intersection = s.intersect(new Ray(new Vector(-10, 0, 0, 1), new Vector(1, 0, 0, 0)));
expect(s).to.be.an('object');
expect(i).to.be.an('object');
expect(i.point.x).to.equal(-2.5);
expect(i.point.y).to.equal(0);
expect(i.point.z).to.equal(0);
expect(i.t).to.equal(7.5);
});
it('intersection is correct when center != (0, 0, 0, 1)', () => {
const s: Sphere = new Sphere(new Vector(1, 0, 0, 1), 2.5, new Vector(0, 0, 0, 0));
const i: Intersection = s.intersect(new Ray(new Vector(-10, 0, 0, 1), new Vector(1, 0, 0, 0)));
expect(s).to.be.an('object');
expect(i).to.be.an('object');
expect(i.point.x).to.equal(-1.5);
expect(i.point.y).to.equal(0);
expect(i.point.z).to.equal(0);
expect(i.t).to.equal(8.5);
});
});
// TODO: Test normalization of normal

275
test/vector-spec.ts

@ -0,0 +1,275 @@
import Vector from "../src/05/vector";
import { expect } from 'chai';
describe('Vector', () => {
it('can be initialized with 4 numbers', () => {
const v = new Vector(1, 2, 3, 4);
expect(v).to.be.an('object');
expect(v.data[0]).to.be.a("number");
expect(v.data[0]).to.equal(1);
expect(v.data[1]).to.be.a("number");
expect(v.data[1]).to.equal(2);
expect(v.data[2]).to.be.a("number");
expect(v.data[2]).to.equal(3);
expect(v.data[3]).to.be.a("number");
expect(v.data[3]).to.equal(4);
});
it('x component can be set and retrieved', () => {
const v = new Vector(0, 0, 0, 0);
expect(v).has.property('x');
v.x = 42;
expect(v.x).to.equal(42);
});
it('y component can be set and retrieved', () => {
const v = new Vector(0, 0, 0, 0);
expect(v).has.property('y');
v.y = 42;
expect(v.y).to.equal(42);
});
it('z component can be set and retrieved', () => {
const v = new Vector(0, 0, 0, 0);
expect(v).has.property('z');
v.z = 42;
expect(v.z).to.equal(42);
});
it('w component can be set and retrieved', () => {
const v = new Vector(0, 0, 0, 0);
expect(v).has.property('w');
v.w = 42;
expect(v.w).to.equal(42);
});
it('r component can be set and retrieved', () => {
const v = new Vector(0, 0, 0, 0);
expect(v).has.property('r');
v.r = 42;
expect(v.r).to.equal(42);
});
it('g component can be set and retrieved', () => {
const v = new Vector(0, 0, 0, 0);
expect(v).has.property('g');
v.g = 42;
expect(v.g).to.equal(42);
});
it('b component can be set and retrieved', () => {
const v = new Vector(0, 0, 0, 0);
expect(v).has.property('b');
v.b = 42;
expect(v.b).to.equal(42);
});
it('a component can be set and retrieved', () => {
const v = new Vector(0, 0, 0, 0);
expect(v).has.property('a');
v.a = 42;
expect(v.a).to.equal(42);
});
it('method add exists', () => {
const v = new Vector(0, 0, 0, 0);
expect(v).to.respondTo('add');
});
it('method sub exists', () => {
const v = new Vector(0, 0, 0, 0);
expect(v).to.respondTo('sub');
});
it('method mul exists', () => {
const v = new Vector(0, 0, 0, 0);
expect(v).to.respondTo('mul');
});
it('method div exists', () => {
const v = new Vector(0, 0, 0, 0);
expect(v).to.respondTo('div');
});
it('method normalize exists', () => {
const v = new Vector(0, 0, 0, 0);
expect(v).to.respondTo('normalize');
});
it('length returns the correct result', () => {
const v = new Vector(0, 4, 3, 0);
expect(v).has.property('length');
expect(v.length).to.equal(5);
});
it('addition adds the other vector', () => {
const a = new Vector(1, 2, 3, 1);
const b = new Vector(1, -2, 0, 0);
expect(a).to.respondTo('add');
const c: Vector = a.add(b);
expect(c).to.not.be.null;
expect(c).to.be.an('object');
expect(c.x).to.equal(2);
expect(c.y).to.equal(0);
expect(c.z).to.equal(3);
expect(c.w).to.equal(1);
});
it('addition does not change the value of "this"', () => {
const a = new Vector(1, 2, 3, 1);
const b = new Vector(1, -2, 0, 0);
expect(a).to.respondTo('add');
const c: Vector = a.add(b);
expect(a).to.not.be.null;
expect(a).to.be.an('object');
expect(a.x).to.equal(1);
expect(a.y).to.equal(2);
expect(a.z).to.equal(3);
expect(a.w).to.equal(1);
});
it('subtraction subtracts the other vector', () => {
const a = new Vector(1, 3, 3, 0);
const b = new Vector(2, -2, 0, 0);
expect(a).to.respondTo('sub');
const c: Vector = a.sub(b);
expect(c).to.not.be.null;
expect(c).to.be.an('object');
expect(c.x).to.equal(-1);
expect(c.y).to.equal(5);
expect(c.z).to.equal(3);
expect(c.w).to.equal(0);
});
it('subtraction does not change the value of "this"', () => {
const a = new Vector(1, 2, 3, 1);
const b = new Vector(1, -2, 0, 0);
expect(a).to.respondTo('sub');
const c: Vector = a.sub(b);
expect(a).to.not.be.null;
expect(a).to.be.an('object');
expect(a.x).to.equal(1);
expect(a.y).to.equal(2);
expect(a.z).to.equal(3);
expect(a.w).to.equal(1);
});
it('multiplication with a scalar multiplies correctly', () => {
const a = new Vector(1, 3, 3, 0);
expect(a).to.respondTo('mul');
const c: Vector = a.mul(4);
expect(c.x).to.equal(4);
expect(c.y).to.equal(12);
expect(c.z).to.equal(12);
expect(c.w).to.equal(0);
});
it('multiplication does not change the value of "this"', () => {
const a = new Vector(1, 3, 3, 0);
expect(a).to.respondTo('mul');
const c: Vector = a.mul(4);
expect(a.x).to.equal(1);
expect(a.y).to.equal(3);
expect(a.z).to.equal(3);
expect(a.w).to.equal(0);
});
it('division by a scalar divides correctly', () => {
const a = new Vector(3, 12, 6, 0);
expect(a).to.respondTo('div');
const c: Vector = a.div(3);
expect(c.x).to.equal(1);
expect(c.y).to.equal(4);
expect(c.z).to.equal(2);
expect(c.w).to.equal(0);
});
it('division does not change the value of "this"', () => {
const a = new Vector(1, 3, 3, 0);
expect(a).to.respondTo('div');
const c: Vector = a.div(4);
expect(a.x).to.equal(1);
expect(a.y).to.equal(3);
expect(a.z).to.equal(3);
expect(a.w).to.equal(0);
});
it('cross product returns the correct result', () => {
const a = new Vector(1, 3, 3, 0);
const b = new Vector(2, -2, 0, 0);
expect(a).to.respondTo('cross');
const c = a.cross(b);
expect(c.x).to.equal(6);
expect(c.y).to.equal(6);
expect(c.z).to.equal(-8);
expect(c.w).to.equal(0);
});
it('cross product does not change the value of "this"', () => {
const a = new Vector(1, 3, 3, 0);
const b = new Vector(2, -2, 0, 0);
expect(a).to.respondTo('cross');
const c = a.cross(b);
expect(a.x).to.equal(1);
expect(a.y).to.equal(3);
expect(a.z).to.equal(3);
expect(a.w).to.equal(0);
});
it('dot product is correct', () => {
const a = new Vector(1, 3, 3, 0);
const b = new Vector(2, 2, 1, 0);
const c = new Vector(2, -2, 0, 0);
expect(a).to.respondTo('dot');
const d = a.dot(b);
const e = b.dot(c);
expect(d).to.equal(11);
expect(e).to.equal(0);
});
it('equality of different vectors returns false', () => {
const a = new Vector(1, 3, 3, 1);
const b = new Vector(2, -2, 0, 1);
expect(a).to.respondTo('equals');
expect(a.equals(b)).to.equal(false);
});
it('equality of equal vectors returns true', () => {
const a = new Vector(1, 3, 3, 1);
expect(a).to.respondTo('equals');
expect(a.equals(a)).to.equal(true);
});
it('vectors very close to each other are equal', () => {
const a = new Vector(1, 3, 3, 1);
const b = new Vector(1.0000000000001, 3.000000000001, 2.9999999999999, 1);
expect(a).to.respondTo('equals');
expect(a.equals(b)).to.equal(true);
});
});
Loading…
Cancel
Save