fdai7303
2 years ago
24 changed files with 1247 additions and 0 deletions
-
4.vscode/settings.json
-
BINdist/manyspheres-finished.png
-
39dist/manyspheres.html
-
BINdist/phong-finished.png
-
43dist/phong.html
-
BINdist/raytracing-finished.png
-
39dist/raytracing.html
-
19src/04/bresenhamsimple.ts
-
12src/04/ddasimple.ts
-
32src/05/camera.ts
-
39src/05/intersection.ts
-
28src/05/manyspheres.ts
-
38src/05/ray.ts
-
28src/05/raytracing.ts
-
51src/05/setup-manyspheres.ts
-
38src/05/setup-raytracing.ts
-
49src/05/sphere.ts
-
256src/05/vector.ts
-
35src/06/phong.ts
-
32src/06/raytracing.ts
-
62src/06/setup-phong.ts
-
50test/ray-spec.ts
-
78test/sphere-spec.ts
-
275test/vector-spec.ts
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"mochaExplorer.require": "ts-node/register", |
||||
|
"mochaExplorer.files": "test/*.ts", |
||||
|
} |
After Width: 500 | Height: 500 | Size: 2.5 KiB |
@ -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> |
After Width: 500 | Height: 500 | Size: 24 KiB |
@ -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> |
After Width: 500 | Height: 500 | Size: 2.9 KiB |
@ -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> |
@ -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; |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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.
|
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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.
|
||||
|
} |
@ -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); |
||||
|
} |
||||
|
} |
||||
|
}); |
@ -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); |
||||
|
} |
||||
|
} |
||||
|
}); |
@ -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; |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
@ -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.
|
||||
|
} |
@ -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); |
||||
|
}); |
@ -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); |
||||
|
}); |
||||
|
}); |
@ -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
|
@ -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); |
||||
|
}); |
||||
|
}); |
Write
Preview
Loading…
Cancel
Save
Reference in new issue