Skip to content

Instantly share code, notes, and snippets.

@mmzsource
Last active May 30, 2022 08:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mmzsource/86065dea8201c490340ccc3043e2760f to your computer and use it in GitHub Desktop.
Save mmzsource/86065dea8201c490340ccc3043e2760f to your computer and use it in GitHub Desktop.
mdlr('mmzsource:raytrace', m => {
// SHARED
function isEqualNumber(n1, n2, epsilon=Number.EPSILON) {
return Math.abs(n1-n2) < epsilon;
}
function dot(r,c){
console.assert(r.length === c.length);
let sum = 0;
for (let i = 0; i < r.length; i++) {
sum += r[i] * c[i];
}
return sum;
}
// COLOR STUFF
function color(r,g,b) {return [r,g,b];}
function getRed(color){return color[0];}
function getGreen(color){return color[1];}
function getBlue(color){return color[2];}
function isEqualColor(c1, c2, epsilon=Number.EPSILON) {
return isEqualNumber(c1[0], c2[0], epsilon) &&
isEqualNumber(c1[1], c2[1], epsilon) &&
isEqualNumber(c1[2], c2[2], epsilon);
}
function cadd(c1, c2) {
return color(c1[0]+c2[0], c1[1]+c2[1], c1[2]+c2[2]);
}
function csub(c1, c2) {
return color(c1[0]-c2[0], c1[1]-c2[1], c1[2]-c2[2]);
}
function cmul([r,g,b], s) {
return color(r*s, g*s, b*s);
}
function cprod(c1, c2) {
return color(c1[0]*c2[0], c1[1]*c2[1], c1[2]*c2[2]);
}
// TUPLE STUFF
function tuple(x, y, z, w) {
return [[Number(x)], [Number(y)], [Number(z)], [Number(w)]];
}
function getX([[x],[y],[z],[w]]){return x;}
function getY([[x],[y],[z],[w]]){return y;}
function getZ([[x],[y],[z],[w]]){return z;}
function getW([[x],[y],[z],[w]]){return w;}
function point(x,y,z) {
return tuple(x,y,z,1.0);
}
function vector(x,y,z){
return tuple(x,y,z,0.0);
}
function isTuple([[x],[y],[z],[w]]) {
return typeof x === 'number' &&
typeof y === 'number' &&
typeof z === 'number' &&
typeof w === 'number' &&
(isEqualNumber(w, 0.0) ||
isEqualNumber(w, 1.0));
}
function isPoint(tuple) {
return isTuple(tuple) && isEqualNumber(tuple[3][0], 1.0);
}
function isVector(tuple) {
return isTuple(tuple) && isEqualNumber(tuple[3][0], 0.0);
}
function isEqualTuple(t1, t2) {
return isEqualNumber(t1[0][0], t2[0][0]) &&
isEqualNumber(t1[1][0], t2[1][0]) &&
isEqualNumber(t1[2][0], t2[2][0]) &&
isEqualNumber(t1[3][0], t2[3][0]);
}
function add(t1, t2) {
return tuple((t1[0][0] + t2[0][0]), (t1[1][0] + t2[1][0]), (t1[2][0] + t2[2][0]), (t1[3][0] + t2[3][0]));
}
function sub(t1, t2) {
return tuple((t1[0][0] - t2[0][0]), (t1[1][0] - t2[1][0]), (t1[2][0] - t2[2][0]), (t1[3][0] - t2[3][0]));
}
function neg([[x],[y],[z],[w]]) {
return tuple(-x, -y, -z, -w);
}
function tmul([[x],[y],[z],[w]], s) {
return tuple(x*s, y*s, z*s, w*s);
}
function div(t, s) {
return tmul(t, 1/s);
}
function mag([[x],[y],[z],[w]]) {
return Math.sqrt(x*x + y*y + z*z + w*w);
}
function norm(v) {
const m = mag(v);
const [[x],[y],[z],[w]] = v;
return tuple(x/m, y/m, z/m, w/m);
}
function cross(v1, v2) {
return vector(v1[1][0]*v2[2][0] - v1[2][0]*v2[1][0], v1[2][0]*v2[0][0] - v1[0][0]*v2[2][0], v1[0][0]*v2[1][0] - v1[1][0]*v2[0][0]);
}
// MATRIX STUFF
function sameLength(a, b) {
return a.length === a.length;
}
function sameValues(m1, m2, epsilon){
let equalValues = true;
outer:
for (let r = 0; r < m1.length; r++) {
for (let c = 0; c < m1[r].length; c++) {
if (!isEqualNumber(m1[r][c], m2[r][c], epsilon)) {
equalValues = false;
break outer;
}
}
}
return equalValues;
}
function isEqual2DMatrix(m1,m2,epsilon=Number.EPSILON) {
return sameLength(m1,m2) && sameLength(m1[0],m2[0]) && sameValues(m1,m2,epsilon);
}
function cols(m) {
const rl = m.length;
const cl = m[0].length;
const cs = []; //cols
for(let c = 0; c < cl; c++) {
let col = new Array();
for (let r = 0; r < rl; r++) {
col.push(m[r][c]);
}
cs.push(col);
}
return cs;
}
// Creates matrix with r rows and c cols
function matrix(r,c){
let m = [];
for (let i=0; i < r; i++) {
let cols = Array.from({length: c});
m.push(cols);
}
return m;
}
function identityMatrixFor(m) {
let cols = m[0].length;
let im = matrix(cols, cols);
for (let r = 0; r < cols; r++) {
for (let c = 0; c < cols; c++) {
r === c ? im[r][c] = 1 : im[r][c] = 0;
}
}
return im;
}
function mmul(m1,m2) {
const rs = m1.length;
const cs = cols(m2);
let m = matrix(rs, cs.length);
for (let r = 0; r < m.length; r++) {
for (let c = 0; c < m[0].length; c++) {
m[r][c] = dot(m1[r], cs[c]);
}
}
return m;
}
function transpose(m) {
const t = matrix(m[0].length, m.length);
for (let r = 0; r < t.length; r++) {
for (let c = 0; c < t[0].length; c++) {
t[r][c] = m[c][r];
}
}
return t;
}
function determinant(m) {
let det = 0;
if (m.length == 2) {
det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
}
else {
for (let col=0 ; col < m.length; col++){
det = det + m[0][col] * cofactor(m, 0, col);
}
}
return det;
}
function submatrix(m,r,c) {
let sm = matrix(m.length -1, m.length -1);
for(let row = 0; row < m.length; row++) {
for(let col = 0; col < m[0].length; col++) {
if (row !== r && col !== c) {
let smr = row > r ? row - 1 : row;
let smc = col > c ? col - 1 : col;
sm[smr][smc] = m[row][col];
}
}
}
return sm;
}
function minor(m,r,c) {
const sm = submatrix(m,r,c);
return determinant(sm);
}
function cofactor(m,r,c){
const min = minor(m,r,c);
return (r+c) % 2 !== 0 ? -1 * min : min;
}
function isInvertible(m) {
return determinant(m) !== 0;
}
function inverse(m) {
if (isInvertible(m) === false) {throw `this matrix is not invertible: ${m}`};
const im = matrix(m.length, m.length); // square matrix
const det = determinant(m);
for (let row = 0; row < m.length; row++) {
for (let col = 0; col < m.length; col++) {
let c = cofactor(m,row,col);
im[col][row] = c / det;
}
}
return im;
}
function translation(x,y,z) {
let t = identityMatrixFor(matrix(4,4));
t[0][3] = x;
t[1][3] = y;
t[2][3] = z;
return t;
}
function scaling(x,y,z) {
let s = identityMatrixFor(matrix(4,4));
s[0][0] = x;
s[1][1] = y;
s[2][2] = z;
return s;
}
function rotationX(rad) {
let r = identityMatrixFor(matrix(4,4));
r[1][1] = Math.cos(rad);
r[1][2] = -Math.sin(rad);
r[2][1] = Math.sin(rad);
r[2][2] = Math.cos(rad);
return r;
}
function rotationY(rad) {
let r = identityMatrixFor(matrix(4,4));
r[0][0] = Math.cos(rad);
r[0][2] = Math.sin(rad);
r[2][0] = -Math.sin(rad);
r[2][2] = Math.cos(rad);
return r;
}
function rotationZ(rad) {
let r = identityMatrixFor(matrix(4,4));
r[0][0] = Math.cos(rad);
r[0][1] = -Math.sin(rad);
r[1][0] = Math.sin(rad);
r[1][1] = Math.cos(rad);
return r;
}
function shearing(xy,xz,yx,yz,zx,zy) {
let r = identityMatrixFor(matrix(4,4));
r[0][1] = xy;
r[0][2] = xz;
r[1][0] = yx;
r[1][2] = yz;
r[2][0] = zx;
r[2][1] = zy;
return r;
}
// RAYCAST STUFF
function ray(origin, direction){
return {origin, direction};
}
function sphere() {
return {
origin: point(0,0,0),
radius: 1,
transform: identityMatrixFor(matrix(4,4)),
material: material()
};
}
function position(ray, t) {
return add(ray.origin, tmul(ray.direction, t));
}
function discriminant(ray, sphere) {
const sphereToRay = sub(ray.origin, point(0,0,0));
const a = dot(ray.direction, ray.direction);
const b = 2 * dot(ray.direction, sphereToRay);
const c = dot(sphereToRay, sphereToRay) - 1;
return {a: a, b: b, c: c, discriminant: b * b - 4 * a * c};
}
function intersects(sphere, ray) {
const r = transform(ray, inverse(sphere.transform));
const d = discriminant(r, sphere);
if (d.discriminant < 0) {
return [];
}
else {
const i1 = intersection((-d.b - Math.sqrt(d.discriminant)) / (2 * d.a), sphere);
const i2 = intersection((-d.b + Math.sqrt(d.discriminant)) / (2 * d.a), sphere);
return intersections(i1,i2);
}
}
function intersection(t, o) {
return {t: t, object: o};
}
function intersections(...args) {
return args;
}
function hit(intersections) {
let smallestPositive = {t: Number.MAX_SAFE_INTEGER};
for (let i = 0; i < intersections.length ; i++) {
const intersection = intersections[i];
if (intersection.t > 0 && intersection.t < smallestPositive.t) {
smallestPositive = intersection;
}
}
if (smallestPositive.t !== Number.MAX_SAFE_INTEGER) {
return smallestPositive;
}
// return undefined if no smallestPositive is found
}
function transform(r, m) {
const o = mmul(m, r.origin);
const d = mmul(m, r.direction);
return ray(o, d);
}
function setTransform(s, t) {
s.transform = t;
return s;
}
function normalAt(sphere, worldPoint){
const objectPoint = mmul(inverse(sphere.transform), worldPoint);
const objectNormal = sub(objectPoint, point(0,0,0));
const worldNormal = mmul(transpose(inverse(sphere.transform)), objectNormal);
worldNormal[3][0] = 0; // hack
return norm(worldNormal);
}
function reflect(inVec, normVec){
return sub(inVec, tmul(tmul(normVec, 2), dot(inVec, normVec)));
}
function pointLight(position, intensity){
return {position, intensity};
}
function material(){
return {
color: color(1,1,1),
ambient: 0.1,
diffuse: 0.9,
specular: 0.9,
shininess: 200
};
}
function lighting(material, light, point, eyeV, normalV) {
const black = color(0,0,0);
const effectiveColor = cprod(material.color, light.intensity);
const lightV = norm(sub(light.position, point));
const ambient = cmul(effectiveColor, material.ambient);
const lightDotNormal = dot(lightV, normalV);
let diffuse = null;
let specular = null;
if (lightDotNormal < 0) {
diffuse = black;
specular = black;
} else {
diffuse = cmul(effectiveColor, (material.diffuse * lightDotNormal));
const reflectV = reflect(neg(lightV), normalV);
const reflectDotEye = dot(reflectV, eyeV);
if (reflectDotEye <= 0) {
specular = black;
} else {
const factor = Math.pow(reflectDotEye, material.shininess);
specular = cmul(light.intensity, (material.specular * factor));
}
}
return cadd(ambient, cadd(diffuse, specular));
}
return { color, getRed, getGreen, getBlue, isEqualColor,
cadd, csub, cmul, cprod,
tuple, point, vector,
isTuple, isPoint, isVector, isEqualNumber, isEqualTuple,
add, sub, neg, tmul, div, mag, norm, dot, cross,
getX, getY, getZ, getW,
isEqual2DMatrix, mmul, identityMatrixFor, transpose,
determinant, submatrix, minor, cofactor, isInvertible,
inverse, translation, scaling, rotationX, rotationY, rotationZ,
shearing,
ray, sphere, position, intersects, intersection, intersections, hit,
transform, setTransform, normalAt, reflect, pointLight, material,
lighting};
});
mdlr('[test]mmzsource:raytrace', m => {
const test = m.test;
const expect = test.expect;
const { color, getRed, getGreen, getBlue, isEqualColor,
cadd, csub, cmul, cprod,
tuple, point, vector,
isTuple, isPoint, isVector, isEqualNumber, isEqualTuple,
add, sub, neg, tmul, div, mag, norm, dot, cross,
getX, getY, getZ, getW,
isEqual2DMatrix, mmul, identityMatrixFor, transpose,
determinant, submatrix, minor, cofactor, isInvertible,
inverse, translation, scaling, rotationX, rotationY, rotationZ,
shearing,
ray, sphere, position, intersects, intersection, intersections, hit,
transform, setTransform, normalAt, reflect, pointLight, material,
lighting } = m.require('mmzsource:raytrace');
// COLOR TESTS
test.it('should create a color', done => {
const c = color(-0.5,0.4,1.7);
expect(getRed(c)).to.eql(-0.5);
expect(getGreen(c)).to.eql(0.4);
expect(getBlue(c)).to.eql(1.7);
done();
});
test.it('should add colors', done => {
const c1 = color(0.9, 0.6, 0.75);
const c2 = color(0.7, 0.1, 0.25);
expect(isEqualColor(cadd(c1,c2), color(1.6, 0.7, 1.0))).to.be.true();
done();
});
test.it('should subtract colors', done => {
const c1 = color(0.9, 0.6, 0.75);
const c2 = color(0.7, 0.1, 0.25);
expect(isEqualColor(csub(c1,c2), color(0.2, 0.5, 0.5))).to.be.true();
done();
});
test.it('should multiply a color by a scalar', done => {
expect(isEqualColor(cmul(color(0.2, 0.3, 0.4), 2), color(0.4, 0.6, 0.8))).to.be.true();
done();
});
test.it('should calculate the hadamard product of 2 colors', done => {
const c1 = color(0.2, 0.3, 0.4);
const c2 = color(0.5, 0.6, 0.7);
expect(isEqualColor(cprod(c1,c2), color(0.10, 0.18, 0.28))).to.be.true();
done();
})
// TUPLE TESTS
test.it('should create a point when tuple.w = 1.0', done => {
const t = tuple(4.3, -4.2, 3.1, 1.0);
expect(isPoint(t)).to.be.true();
expect(isVector(t)).to.be.false();
done();
});
test.it('should create a vector when tuple.w = 0.0', done => {
const t = tuple(4.3, -4.2, 3.1, 0.0);
expect(isVector(t)).to.be.true();
expect(isPoint(t)).to.be.false();
done();
});
test.it('should create a tuple with w = 1.0', done => {
const p = point(4, -4, 3);
expect(isTuple(p) && isEqualNumber(getW(p), 1.0)).to.be.true();
done();
});
test.it('should create a tuple with w = 0.0', done => {
const v = vector(4, -4, 3);
expect(isTuple(v) && isEqualNumber(getW(v), 0.0)).to.be.true();
done();
});
test.it('should add 2 tuples', done => {
const t1 = tuple(3, -2, 5, 1.0);
const t2 = tuple(-2, 3, 1, 0);
expect(isEqualTuple(add(t1, t2), tuple(1, 1, 6, 1))).to.be.true();
done();
});
test.it('should add a point and a vector', done => {
const p = point(1, 2, 3);
const v = vector(5, 10, 20);
const expected = tuple(6, 12, 23, 1.0);
expect(isEqualTuple(add(p, v), expected)).to.be.true();
expect(isEqualTuple(add(v, p), expected)).to.be.true();
expect(isPoint(add(p, v))).to.be.true();
done();
});
test.it('should add 2 vectors', done => {
const v1 = vector(-1, -2, -3);
const v2 = vector(-10, -20, -30);
const expected = tuple(-11, -22, -33, 0.0);
expect(isEqualTuple(add(v1, v2), expected)).to.be.true();
expect(isEqualTuple(add(v2, v1), expected)).to.be.true();
expect(isVector(add(v1, v2))).to.be.true();
done();
});
// No real meaning in the domain
test.it('should add 2 points', done => {
const p1 = point(0, 0, 0);
const p2 = point(-100, 42, 1024);
const expected = tuple(-100, 42, 1024, 2.0);
expect(isEqualTuple(add(p1, p2), expected)).to.be.true();
expect(isEqualTuple(add(p2, p1), expected)).to.be.true();
expect(isPoint(add(p1, p2))).to.be.false();
expect(isVector(add(p1, p2))).to.be.false();
done();
});
test.it('should subtract 2 tuples', done => {
const t1 = tuple(3, -2, 5, 1.0);
const t2 = tuple(-20, 3, 1, 0);
expect(isEqualTuple(sub(t1, t2), tuple(23, -5, 4, 1.0))).to.be.true();
done();
});
test.it('should subtract a point and a vector', done => {
const p = point(1, 2, 3);
const v = vector(5, 10, 20);
expect(isEqualTuple(sub(p, v), tuple(-4, -8, -17, 1.0))).to.be.true();
expect(isEqualTuple(sub(v, p), tuple(4, 8, 17, -1.0))).to.be.true();
expect(isPoint(sub(p, v))).to.be.true();
done();
});
test.it('should subtract 2 vectors', done => {
const v1 = vector(-1, -2, -3);
const v2 = vector(10, 20, 30);
expect(isEqualTuple(sub(v1, v2), tuple(-11, -22, -33, 0.0))).to.be.true();
expect(isEqualTuple(sub(v2, v1), tuple(11, 22, 33, 0.0))).to.be.true();
expect(isVector(sub(v1, v2))).to.be.true();
done();
});
test.it('should subtract 2 points', done => {
const p1 = point(0, 0, 0);
const p2 = point(100, 42, 1024);
expect(isEqualTuple(sub(p1, p2), tuple(-100, -42, -1024, 0.0))).to.be.true();
expect(isEqualTuple(sub(p2, p1), tuple(100, 42, 1024, 0.0))).to.be.true();
expect(isVector(sub(p1, p2)));
done();
});
test.it('should negate a tuple', done => {
expect(isEqualTuple(neg(tuple(1, 2, 3, 4)), tuple(-1, -2, -3, -4))).to.be.true();
done();
});
test.it('should multiply a tuple by a scalar', done => {
const t = tuple(22, 33, 44, 55);
expect(isEqualTuple(tmul(t, 4), tuple(88, 132, 176, 220))).to.be.true();
expect(isEqualTuple(tmul(t, 0.2), tuple(4.4, 6.6000000000000005, 8.8, 11))).to.be.true();
done();
});
test.it('should divide a tuple by a scalar', done => {
const t = tuple(22, 33, 44, 55);
expect(isEqualTuple(div(t, 0.25), tuple(88, 132, 176, 220))).to.be.true();
expect(isEqualTuple(div(t, 5), tuple(4.4, 6.6000000000000005, 8.8, 11))).to.be.true();
done();
});
test.it('should calculate the magnitude of a vector', done => {
expect(mag(vector(1, 0, 0))).to.eql(1);
expect(mag(vector(0, 1, 0))).to.eql(1);
expect(mag(vector(0, 0, 1))).to.eql(1);
expect(mag(vector(-1, -2, -3))).to.eql(Math.sqrt(14));
done();
});
test.it('should normalize a vector', done => {
expect(isEqualTuple(norm(vector(4, 0, 0)), vector(1, 0, 0))).to.be.true();
const n = norm(vector(1, 2, 3));
expect(isEqualTuple(n, vector(0.2672612419124244, 0.5345224838248488, 0.8017837257372732, 0))).to.be.true();
expect(isEqualNumber(mag(n), 1)).to.be.true();
expect(mag(n)).to.eql(1);
done();
});
test.it('should calculate the dot product of 2 tuples', done => {
const v1 = vector(1, 2, 3);
const v2 = vector(2, 3, 4);
expect(dot(v1, v2)).to.eql(20);
done();
});
test.it('should calculate the cross product of 2 vectors', done => {
const v1 = vector(1, 2, 3);
const v2 = vector(2, 3, 4);
expect(isEqualTuple(cross(v1, v2), vector(-1, 2, -1))).to.be.true();
expect(isEqualTuple(cross(v2, v1), vector(1, -2, 1))).to.be.true();
const x = vector(1, 0, 0);
const y = vector(0, 1, 0);
const z = vector(0, 0, 1);
expect(isEqualTuple(cross(x, y), z)).to.be.true();
expect(isEqualTuple(cross(z, x), y)).to.be.true();
expect(isEqualTuple(cross(y, z), x)).to.be.true();
done();
});
// MATRIX TESTS
test.it('should create zero based matrices, lookup via r,c', done => {
const m = [[1,2,3,4],[5.5,6.5,7.5,8.5],[9,10,11,12],[13.5,14.5,15.5,16.5]];
expect(m[0][0]).to.eql(1);
expect(m[0][3]).to.eql(4);
expect(m[1][0]).to.eql(5.5);
expect(m[1][2]).to.eql(7.5);
expect(m[2][2]).to.eql(11);
expect(m[3][0]).to.eql(13.5);
expect(m[3][2]).to.eql(15.5);
done();
});
test.it('should check for equality given some EPSILON', done => {
const m1 = [[1,2,3],[4,5,6],[7,8,9]];
const m2 = [[1,2,3],[4,5,6],[7,8,9]];
const m3 = [[0,0,0],[1,1,1],[2,2,2]];
const m4 = [[0,0][1,1]];
expect(isEqual2DMatrix(m1,m1)).to.be.true();
expect(isEqual2DMatrix(m1,m2)).to.be.true();
expect(isEqual2DMatrix(m1,m3)).to.be.false();
expect(isEqual2DMatrix(m3,m4)).to.be.false();
done();
});
test.it('should multiply matrices', done => {
const m1 = [[0,1],[2,3]];
const m2 = [[5,6],[7,8]];
expect(isEqual2DMatrix(mmul(m1,m2),[[7,8],[31,36]])).to.be.true();
const m3 = [[1,2,3],[4,5,6]];
const m4 = [[7,8],[9,10],[11,12]];
expect(isEqual2DMatrix(mmul(m3,m4), [[58,64],[139,154]])).to.be.true();
const m5 = [[1,2,3]];
const m6 = [[4],[5],[6]];
expect(isEqual2DMatrix(mmul(m5,m6), [[32]])).to.be.true();
const m7 = [[1,2,3,4],[2,4,4,2],[8,6,4,1],[0,0,0,1]];
const m8 = [[1],[2],[3],[1]]; // tuple in matrix format
expect(isEqual2DMatrix(mmul(m7,m8), [[18],[24],[33],[1]])).to.be.true();
done();
});
test.it('should create identity matrices', done => {
const m1 = [[1,2],[3,4]];
const im1 = identityMatrixFor(m1);
expect(isEqual2DMatrix(im1, [[1,0],[0,1]])).to.be.true();
expect(isEqual2DMatrix(mmul(m1,im1), m1)).to.be.true();
expect(isEqual2DMatrix(mmul(im1,m1), m1)).to.be.true();
const m2 = [[1,2,3],[4,5,6],[7,8,9]];
const im2 = identityMatrixFor(m2);
expect(isEqual2DMatrix(im2, [[1,0,0],[0,1,0],[0,0,1]])).to.be.true();
expect(isEqual2DMatrix(mmul(m2,im2), m2)).to.be.true();
expect(isEqual2DMatrix(mmul(im2,m2), m2)).to.be.true();
done();
});
test.it('should transpose matrices', done => {
const m1 = [[0,9,3,0],[9,8,0,8],[1,8,5,3],[0,0,5,8]];
const t1 = [[0,9,1,0],[9,8,8,0],[3,0,5,5],[0,8,3,8]];
expect(isEqual2DMatrix(transpose(m1),t1)).to.be.true();
expect(isEqual2DMatrix(transpose(transpose(m1)), m1)).to.be.true();
const m2 = [[1,2],[3,4],[5,6]];
const t2 = [[1,3,5],[2,4,6]];
expect(isEqual2DMatrix(transpose(m2), t2)).to.be.true();
expect(isEqual2DMatrix(transpose(transpose(m2)), m2)).to.be.true();
const im = identityMatrixFor([[11,22],[33,44]]);
expect(isEqual2DMatrix(transpose(im), im)).to.be.true();
done();
});
test.it('should calculate determinant of 2x2 matrix', done => {
const m = [[1,5],[-3,2]];
expect(determinant(m)).to.eql(17);
done();
});
test.it('should calculate submatrix of 3x3 matrix', done => {
const m = [[1,5,0],[-3,2,7],[0,6,-3]];
const sm = submatrix(m,0,2);
const expected = [[-3,2],[0,6]];
expect(isEqual2DMatrix(sm, expected)).to.be.true();
done();
});
test.it('should calculate submatrix of 4x4 matrix', done => {
const m = [[-6,1,1,6],[-8,5,8,6],[-1,0,8,2],[-7,1,-1,1]];
const sm = submatrix(m,2,1);
const expected = [[-6,1,6],[-8,8,6],[-7,-1,1]];
expect(isEqual2DMatrix(sm, expected)).to.be.true();
done();
});
test.it('should calculate minor in a 3x3 matrix', done => {
const m = [[3,5,0],[2,-1,-7],[6,-1,5]];
expect(minor(m,1,0)).to.eql(25);
done();
});
test.it('should calculate cofactor of 3x3 matrix', done => {
const m = [[3,5,0],[2,-1,-7],[6,-1,5]];
expect(minor(m,0,0)).to.eql(-12);
expect(cofactor(m,0,0)).to.eql(-12);
expect(minor(m,1,0)).to.eql(25);
expect(cofactor(m,1,0)).to.eql(-25);
done();
});
test.it('should calculate determinant of a 3x3 matrix', done => {
const m = [[1,2,6],[-5,8,-4],[2,6,4]];
expect(cofactor(m,0,0)).to.eql(56);
expect(cofactor(m,0,1)).to.eql(12);
expect(cofactor(m,0,2)).to.eql(-46);
expect(determinant(m)).to.eql(-196);
done();
});
test.it('should calculate determinant of a 4x4 matrix', done => {
const m = [[-2,-8,3,5],[-3,1,7,3],[1,2,-9,6],[-6,7,7,-9]];
expect(cofactor(m,0,0)).to.eql(690);
expect(cofactor(m,0,1)).to.eql(447);
expect(cofactor(m,0,2)).to.eql(210);
expect(cofactor(m,0,3)).to.eql(51);
expect(determinant(m)).to.eql(-4071);
done();
});
test.it('should determine invertibility of a matrix', done => {
const m1 = [[6,4,4,4],[5,5,7,6],[4,-9,3,-7],[9,1,7,-6]];
expect(determinant(m1)).to.eql(-2120);
expect(isInvertible(m1)).to.be.true();
const m2 = [[-4,2,-2,-3],[9,6,2,6],[0,-5,1,-5],[0,0,0,0]];
expect(determinant(m2)).to.eql(0);
expect(isInvertible(m2)).to.be.false();
done();
});
test.it('should calculate inverse of a matrix part 1', done => {
const m = [[-5,2,6,-8],[1,-5,1,8],[7,7,-6,-7],[1,-3,7,4]];
const i = inverse(m);
const expected = [[ 0.21805, 0.45113, 0.24060, -0.04511],
[-0.80827, -1.45677, -0.44361, 0.52068],
[-0.07895, -0.22368, -0.05263, 0.19737],
[-0.52256, -0.81391, -0.30075, 0.30639]]
expect(determinant(m)).to.eql(532);
expect(cofactor(m,2,3)).to.eql(-160);
expect(i[3][2]).to.eql(-160/532);
expect(cofactor(m,3,2)).to.eql(105);
expect(i[2][3]).to.eql(105/532);
expect(isEqual2DMatrix(i, expected, 0.0001)).to.be.true();
done();
});
test.it('should calculate inverse of a matrix part 2', done => {
const m = [[8,-5,9,2],[7,5,6,1],[-6,0,9,6],[-3,0,-9,-4]];
const i = inverse(m);
const expected = [[-0.15385, -0.15385, -0.28205, -0.53846],
[-0.07692, 0.12308, 0.02564, 0.03077],
[ 0.35897, 0.35897, 0.43590, 0.92308],
[-0.69231, -0.69231, -0.76923, -1.92308]]
expect(isEqual2DMatrix(i, expected, 0.0001)).to.be.true();
done();
});
test.it('should calculate inverse of a matrix part 3', done => {
const m = [[9,3,0,9],[-5,-2,-6,-3],[-4,9,6,4],[-7,6,6,2]];
const i = inverse(m);
const expected = [[-0.04074, -0.07778, 0.14444, -0.22222],
[-0.07778, 0.03333, 0.36667, -0.33333],
[-0.02901, -0.14630, -0.10926, 0.12963],
[ 0.17778, 0.06667, -0.26667, 0.33333]]
expect(isEqual2DMatrix(i, expected, 0.0001)).to.be.true();
done();
});
test.it('should return original matrix when multiplying product by its inverse', done => {
const a = [[3,-9,7,3],[3,-8,2,-9],[-4,4,4,1],[-6,5,-1,1]];
const b = [[8,2,2,2],[3,-1,7,0],[7,0,5,4],[6,-2,0,5]];
const c = mmul(a,b);
expect(isEqual2DMatrix(a, mmul(c, inverse(b)), 0.0001)).to.be.true();
done();
});
test.it('should create translation matrices', done => {
const x=3, y=4, z=5;
const expected = [[1,0,0,x],[0,1,0,y],[0,0,1,z],[0,0,0,1]];
expect(isEqual2DMatrix(translation(x,y,z), expected)).to.be.true();
done();
});
test.it('should multiply point with a translation matrix', done => {
const t = translation(5,-3,2);
const p = point(-3,4,5);
const expected = point(2,1,7);
expect(isEqual2DMatrix(mmul(t,p), expected)).to.be.true();
done();
});
test.it('should multiply points with inverse of a translation matrix', done => {
const t = translation(5,-3,2);
const inv = inverse(t);
const p = point(-3,4,5);
const expected = point(-8,7,3);
expect(isEqual2DMatrix(mmul(inv,p), expected)).to.be.true();
done();
});
test.it('should leave vectors as is', done => {
const t = translation(5,-3,2);
const v = vector(-3,4,5);
expect(isEqual2DMatrix(mmul(t,v), v)).to.be.true();
done();
});
test.it('should create scaling matrices', done => {
const x=3, y=4, z=5;
const expected = [[x,0,0,0],[0,y,0,0],[0,0,z,0],[0,0,0,1]];
expect(isEqual2DMatrix(scaling(x,y,z), expected)).to.be.true();
done();
});
test.it('should apply scaling to a point', done => {
const s = scaling(2,3,4);
const p = point(-4,6,8);
const expected = point(-8,18,32);
expect(isEqual2DMatrix(mmul(s,p), expected)).to.be.true();
done();
});
test.it('should apply scaling to a vector', done => {
const s = scaling(2,3,4);
const p = point(-4,6,8);
const expected = point(-8,18,32);
expect(isEqual2DMatrix(mmul(s,p), expected)).to.be.true();
done();
});
test.it('should multiply with the inverse of a scaling matrix', done => {
const s = scaling(2,3,4);
const inv = inverse(s);
const v = vector(-4,6,8);
const expected = vector(-2,2,2);
expect(isEqual2DMatrix(mmul(inv, v), expected)).to.be.true();
done();
});
test.it('should reflect by scaling by a negative value', done => {
const s = scaling(-1,1,1);
const p = point(2,3,4);
const expected = point(-2,3,4);
expect(isEqual2DMatrix(mmul(s,p), expected)).to.be.true();
done();
});
test.it('should create x-axis rotation matrix', done => {
const r = Math.PI/4;
const a = Math.cos(r), b = -Math.sin(r), c = Math.sin(r), d = Math.cos(r);
const halfQuarter = rotationX(r);
const expected = [[1,0,0,0],[0,a,b,0],[0,c,d,0],[0,0,0,1]];
expect(isEqual2DMatrix(halfQuarter, expected)).to.be.true();
done();
});
test.it('should handle x rotations', done => {
const p = point(0,1,0);
const halfQuarter = rotationX(Math.PI/4);
const fullQuarter = rotationX(Math.PI/2);
const hqExpected = point(0,Math.sqrt(2)/2,Math.sqrt(2)/2);
const fqExpected = point(0,0,1);
expect(isEqual2DMatrix(mmul(halfQuarter, p), hqExpected)).to.be.true();
expect(isEqual2DMatrix(mmul(fullQuarter, p), fqExpected)).to.be.true();
done();
});
test.it('should handle inverse x rotations', done => {
const p = point(0,1,0);
const halfQuarter = rotationX(Math.PI/4);
const inv = inverse(halfQuarter);
const expected = point(0,Math.sqrt(2)/2,-Math.sqrt(2)/2);
expect(isEqual2DMatrix(mmul(inv, p), expected)).to.be.true();
done();
});
test.it('should create y-axis rotation matrix', done => {
const r = Math.PI/4;
const a = Math.cos(r), b = Math.sin(r), c = -Math.sin(r), d = Math.cos(r);
const halfQuarter = rotationY(r);
const expected = [[a,0,b,0],[0,1,0,0],[c,0,d,0],[0,0,0,1]];
expect(isEqual2DMatrix(halfQuarter, expected)).to.be.true();
done();
});
test.it('should handle y rotations', done => {
const p = point(0,0,1);
const halfQuarter = rotationY(Math.PI/4);
const fullQuarter = rotationY(Math.PI/2);
const hqExpected = point(Math.sqrt(2)/2,0,Math.sqrt(2)/2);
const fqExpected = point(1,0,0);
expect(isEqual2DMatrix(mmul(halfQuarter, p), hqExpected)).to.be.true();
expect(isEqual2DMatrix(mmul(fullQuarter, p), fqExpected)).to.be.true();
done();
});
test.it('should create z-axis rotation matrix', done => {
const r = Math.PI/4;
const a = Math.cos(r), b = -Math.sin(r), c = Math.sin(r), d = Math.cos(r);
const halfQuarter = rotationZ(r);
const expected = [[a,b,0,0],[c,d,0,0],[0,0,1,0],[0,0,0,1]];
expect(isEqual2DMatrix(halfQuarter, expected)).to.be.true();
done();
});
test.it('should handle z rotations', done => {
const p = point(0,1,0);
const halfQuarter = rotationZ(Math.PI/4);
const fullQuarter = rotationZ(Math.PI/2);
const hqExpected = point(-Math.sqrt(2)/2,Math.sqrt(2)/2,0);
const fqExpected = point(-1,0,0);
expect(isEqual2DMatrix(mmul(halfQuarter, p), hqExpected)).to.be.true();
expect(isEqual2DMatrix(mmul(fullQuarter, p), fqExpected)).to.be.true();
done();
});
test.it('should create shearing matrix', done => {
const xy = 2, xz = 3, yx = 4, yz = 5, zx = 6, zy = 7;
const expected = [[1,xy,xz,0],[yx,1,yz,0],[zx,zy,1,0],[0,0,0,1]];
expect(isEqual2DMatrix(shearing(xy,xz,yx,yz,zx,zy), expected)).to.be.true();
done();
});
test.it('should apply shearing transformation of x in proportion to y', done => {
const s = shearing(1,0,0,0,0,0);
const p = point(2,3,4);
expect(isEqual2DMatrix(mmul(s,p), point(5,3,4))).to.be.true();
done();
});
test.it('should apply shearing transformation of x in proportion to z', done => {
const s = shearing(0,1,0,0,0,0);
const p = point(2,3,4);
expect(isEqual2DMatrix(mmul(s,p), point(6,3,4))).to.be.true();
done();
});
test.it('should apply shearing transformation of y in proportion to x', done => {
const s = shearing(0,0,1,0,0,0);
const p = point(2,3,4);
expect(isEqual2DMatrix(mmul(s,p), point(2,5,4))).to.be.true();
done();
});
test.it('should apply shearing transformation of y in proportion to z', done => {
const s = shearing(0,0,0,1,0,0);
const p = point(2,3,4)
expect(isEqual2DMatrix(mmul(s,p), point(2,7,4))).to.be.true();
done();
});
test.it('should apply shearing transformation of z in proportion to x', done => {
const s = shearing(0,0,0,0,1,0);
const p = point(2,3,4);
expect(isEqual2DMatrix(mmul(s,p), point(2,3,6))).to.be.true();
done();
});
test.it('should apply shearing transformation of z in proportion to y', done => {
const s = shearing(0,0,0,0,0,1);
const p = point(2,3,4);
expect(isEqual2DMatrix(mmul(s,p), point(2,3,7))).to.be.true();
done();
});
test.it('should chain transformation in reverse order', done => {
const p = point(1,0,1);
const a = rotationX(Math.PI/2);
const b = scaling(5,5,5);
const c = translation(10,5,7);
const expected = point(15,0,7);
const p2 = mmul(a,p); // rotate
expect(isEqual2DMatrix(p2, point(1,-1,0), 0.0001)).to.be.true();
const p3 = mmul(b,p2); // then scale
expect(isEqual2DMatrix(p3, point(5,-5,0), 0.0001)).to.be.true();
const p4 = mmul(c,p3); // then apply translation
expect(isEqual2DMatrix(p4, expected, 0.0001)).to.be.true();
// now chain them:
const ct = mmul(c, mmul(b,a));
const p5 = mmul(ct, p);
expect(isEqual2DMatrix(p5, expected, 0.0001)).to.be.true();
done();
});
// RAY TESTS
test.it('should create rays', done => {
const r = ray(point(1,2,3), vector(4,5,6));
expect(isEqual2DMatrix(r.origin, point(1,2,3))).to.be.true();
expect(isEqual2DMatrix(r.direction, vector(4,5,6))).to.be.true();
done();
});
test.it('should compute a point from a distance', done => {
const r = ray(point(2,3,4), vector(1,0,0));
expect(isEqual2DMatrix(position(r, 0), point(2,3,4))).to.be.true();
expect(isEqual2DMatrix(position(r,1), point(3,3,4))).to.be.true();
expect(isEqual2DMatrix(position(r,-1), point(1,3,4))).to.be.true();
expect(isEqual2DMatrix(position(r,2.5), point(4.5,3,4))).to.be.true();
done();
});
test.it('should compute intersection points', done => {
const r = ray(point(0,0,-5), vector(0,0,1));
const s = sphere();
const xs = intersects(s,r);
expect(xs.length).to.eql(2);
expect(xs[0].t).to.eql(4);
expect(xs[1].t).to.eql(6);
done();
});
test.it('should compute intersection points at tangent', done => {
const r = ray(point(0,1,-5), vector(0,0,1));
const s = sphere();
const xs = intersects(s,r);
expect(xs.length).to.eql(2);
expect(xs[0].t).to.eql(5);
expect(xs[1].t).to.eql(5);
done();
});
test.it('should compute no intersection points when ray misses sphere', done => {
const r = ray(point(0,2,-5), vector(0,0,1));
const s = sphere();
const xs = intersects(s,r);
expect(xs.length).to.eql(0);
done();
});
test.it('should compute intersection points when ray originates inside sphere', done => {
const r = ray(point(0,0,0), vector(0,0,1));
const s = sphere();
const xs = intersects(s,r);
expect(xs.length).to.eql(2);
expect(xs[0].t).to.eql(-1);
expect(xs[1].t).to.eql(1);
done();
});
test.it('should compute intersection points when sphere is behind ray', done => {
const r = ray(point(0,0,5), vector(0,0,1));
const s = sphere();
const xs = intersects(s,r);
expect(xs.length).to.eql(2);
expect(xs[0].t).to.eql(-6);
expect(xs[1].t).to.eql(-4);
done();
});
test.it('should have datastructure to encapsulate intersection', done => {
const s = sphere();
const i = intersection(3.5, s);
expect(i.t).to.eql(3.5);
expect(i.object).to.eql(s);
done();
});
test.it('should aggregate intersections', done => {
const s = sphere();
const i1 = intersection(1, s);
const i2 = intersection(2, s);
const xs = intersections(i1, i2);
expect(xs.length).to.eql(2);
expect(xs[0].t).to.eql(1);
expect(xs[1].t).to.eql(2);
done();
});
test.it('should add the objects to the intersections', done => {
const r = ray(point(0,0,-5), vector(0,0,1));
const s = sphere();
const xs = intersects(s,r);
expect(xs.length).to.eql(2);
expect(xs[0].object).to.eql(s);
expect(xs[1].object).to.eql(s);
done();
});
test.it('should detect hit in positive intersections', done => {
const s = sphere();
const i1 = intersection(1, s);
const i2 = intersection(2, s);
const xs = intersections(i2, i1);
const h = hit(xs);
expect(h).to.eql(i1);
expect(h.t).to.eql(i1.t);
done();
});
test.it('should filter negative intersections when determining hit', done => {
const s = sphere();
const i1 = intersection(-1, s);
const i2 = intersection(1, s);
const xs = intersections(i2, i1);
const h = hit(xs);
expect(h).to.eql(i2);
expect(h.t).to.eql(i2.t);
done();
});
test.it('should return no hit when all intersections have a negative t value', done => {
const s = sphere();
const i1 = intersection(-2, s);
const i2 = intersection(-1, s);
const xs = intersections(i2, i1);
const h = hit(xs);
expect(h).to.eql(undefined);
done();
});
test.it('should return the lowest nonnegative intersection', done => {
const s = sphere();
const i1 = intersection(5, s);
const i2 = intersection(7, s);
const i3 = intersection(-3, s);
const i4 = intersection(2,s);
const xs = intersections(i1, i2, i3, i4);
const h = hit(xs);
expect(h).to.eql(i4);
expect(h.t).to.eql(i4.t);
done();
});
test.it('should translate a ray', done => {
const r1 = ray(point(1,2,3), vector(0,1,0));
const m = translation(3,4,5);
const r2 = transform(r1,m);
expect(isEqualTuple(r2.origin, point(4,6,8))).to.be.true();
expect(isEqualTuple(r2.direction, vector(0,1,0))).to.be.true();
done();
});
test.it('should scale a ray', done => {
const r1 = ray(point(1,2,3), vector(0,1,0));
const m = scaling(2,3,4);
const r2 = transform(r1,m);
expect(isEqualTuple(r2.origin, point(2,6,12))).to.be.true();
expect(isEqualTuple(r2.direction, vector(0,3,0))).to.be.true();
done();
});
test.it('should provide a sphere with a default transformation', done => {
const s = sphere();
expect(s.transform).to.eql([[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]);
done();
});
test.it('should change a spheres transformation', done => {
let s1 = sphere();
const t = translation(2,3,4);
s1 = setTransform(s1, t);
expect(s1.transform).to.eql(t);
done();
});
test.it('should intersect a scaled sphere with a ray', done => {
const r = ray(point(0,0,-5), vector(0,0,1));
let s = sphere();
s = setTransform(s, scaling(2,2,2));
const xs = intersects(s,r);
expect(xs.length).to.eql(2);
expect(xs[0].t).to.eql(3);
expect(xs[1].t).to.eql(7);
done();
});
test.it('should intersect a translated sphere with a ray', done => {
const r = ray(point(0,0,-5), vector(0,0,1));
let s = sphere();
s = setTransform(s, translation(5,0,0));
const xs = intersects(s,r);
expect(xs.length).to.eql(0);
done();
});
test.it('should return the normal on a sphere at a point on the x-axis', done => {
const s = sphere();
const n = normalAt(s, point(1,0,0));
expect(n).to.eql(vector(1,0,0));
done();
});
test.it('should return the normal on a sphere at a point on the y-axis', done => {
const s = sphere();
const n = normalAt(s, point(0,1,0));
expect(n).to.eql(vector(0,1,0));
done();
});
test.it('should return the normal on a sphere at a point on the z-axis', done => {
const s = sphere();
const n = normalAt(s, point(0,0,1));
expect(n).to.eql(vector(0,0,1));
done();
});
test.it('should return a normalized vector', done => {
const s = sphere();
const x = Math.sqrt(3) / 3;
const n = normalAt(s, point(x,x,x));
expect(n).to.eql(norm(n));
done();
});
test.it('should compute the normal on a translated sphere', done => {
let s = sphere();
s = setTransform(s, translation(0,1,0));
const n = normalAt(s, point(0, 1.70711, -0.70711));
expect(isEqual2DMatrix(n, vector(0, 0.70711, -0.70711), 0.0001)).to.be.true();
done();
});
test.it('should compute the normal on a transformed sphere', done => {
let s = sphere();
let m = mmul(scaling(1, 0.5, 1), rotationX(Math.PI/5));
s = setTransform(s, m);
const x = Math.sqrt(2) / 2;
const n = normalAt(s, point(0, x, -x));
expect(isEqual2DMatrix(n, vector(0, 0.97014, -0.24254), 0.0001)).to.be.true();
done();
});
test.it('should reflect a vector approaching at 45 degrees', done => {
const v = vector(1,-1,0);
const n = vector(0,1,0);
const r = reflect(v,n);
expect(r).to.eql(vector(1,1,0));
done();
});
test.it('should reflect a vector off a slanted surface', done => {
const v = vector(0,-1,0);
const x = Math.sqrt(2) / 2;
const n = vector(x,x,0);
const r = reflect(v,n);
expect(isEqual2DMatrix(r, vector(1,0,0), 0.0001)).to.be.true();
done();
});
test.it('should create light with position and intensity', done => {
const intensity = color(1,1,1);
const position = point(0,0,0);
const light = pointLight(position, intensity);
expect(light.position).to.eql(position);
expect(light.intensity).to.eql(intensity);
done();
});
test.it('should create a default material', done => {
const m = material();
expect(m.color).to.eql(color(1,1,1));
expect(m.ambient).to.eql(0.1);
expect(m.diffuse).to.eql(0.9);
expect(m.specular).to.eql(0.9);
expect(m.shininess).to.eql(200);
done();
});
test.it('should add default material to a sphere', done => {
const s = sphere();
expect(s.material).to.eql(material());
done();
});
test.it('should assign a material to a sphere', done => {
const s = sphere();
const m = material();
m.ambient = 1;
s.material = m;
expect(s.material.ambient).to.eql(1);
done();
});
test.it('should return lighting at full strength', done => {
const m = material();
const position = point(0,0,0);
const eyeV = vector(0,0,-1);
const normalV = vector(0,0,-1);
const light = pointLight(point(0,0,-10), color(1,1,1));
const result = lighting(m, light, position, eyeV, normalV);
expect(isEqualColor(result, color(1.9, 1.9, 1.9), 0.0001)).to.be.true();
done();
});
test.it('should return low specular value; eye offset = 45 degrees', done => {
const x = Math.sqrt(2) / 2;
const m = material();
const position = point(0,0,0);
const eyeV = vector(0, x, x);
const normalV = vector(0,0,-1);
const light = pointLight(point(0,0,-10), color(1,1,1));
const result = lighting(m, light, position, eyeV, normalV);
expect(isEqualColor(result, color(1.0, 1.0, 1.0), 0.0001)).to.be.true();
done();
});
test.it('should return lower specular & diffuse values; light offset = 45 degrees', done => {
const m = material();
const position = point(0,0,0);
const eyeV = vector(0, 0, -1);
const normalV = vector(0,0,-1);
const light = pointLight(point(0,10,-10), color(1,1,1));
const result = lighting(m, light, position, eyeV, normalV);
expect(isEqualColor(result, color(0.7364, 0.7364, 0.7364), 0.0001)).to.be.true();
done();
});
test.it('should return ambient component when light is behind surface', done => {
const m = material();
const position = point(0,0,0);
const eyeV = vector(0,0,-1);
const normalV = vector(0,0,-1);
const light = pointLight(point(0,0,10), color(1,1,1));
const result = lighting(m, light, position, eyeV, normalV);
expect(isEqualColor(result, color(0.1,0.1,0.1), 0.0001)).to.be.true();
done();
});
})
mdlr('mmzsource:rt-h5', m => {
const style = `width:100vw; height:100vh;`;
const doc = document.body;
doc.innerHTML = `<canvas width=500 height=500 style="${style}"></canvas>`;
doc.style.margin = "0";
doc.style.overflow = "hidden";
const canvas = doc.querySelector('canvas');
const { width, height } = canvas;
const ctx = canvas.getContext('2d');
const image = ctx.getImageData(0, 0, width, height);
const { color, getRed, getGreen, getBlue } = m.require('mmzsource:canvas');
const shapeColor = color(0,0,1);
const rt = m.require('mmzsource:raytrace');
const rayOrigin = rt.point(0,0,-5);
const wallZ = 10;
const wallSize = 7;
const pixelSize = wallSize / width;
const half = wallSize * 0.5;
const shape = rt.sphere();
function animate() {
for (let y = 0; y < height; y++) {
let worldY = half - pixelSize * y;
for (let x = 0; x < width; x++) {
const worldX = -half + pixelSize * x;
const position = rt.point(worldX, worldY, wallZ);
const r = rt.ray(rayOrigin, rt.norm(rt.sub(position, rayOrigin)));
const xs = rt.intersects(shape, r);
if (rt.hit(xs)) {
const pixel = (x*4)+(y*4*width);
image.data[pixel + 0] = getRed(shapeColor) * 255;
image.data[pixel + 1] = getGreen(shapeColor) * 255;
image.data[pixel + 2] = getBlue(shapeColor) * 255;
image.data[pixel + 3] = 255;
}
}
}
ctx.putImageData(image, 0, 0);
}
requestAnimationFrame(animate);
// CREATE PPM FILE CONTENTS
let ppm = `P3\n${width} ${height}\n255`;
for (let x = 0; x < width; x++) {
ppm += `\n`;
for (let y = 0; y < height; y++) {
let pixel = (x*4) + (y*4*width);
ppm += `${image.data[pixel + 0]} ${image.data[pixel + 1]} ${image.data[pixel + 2]} `
}
}
ppm += `\n`;
console.log(ppm);
})
mdlr('mmzsource:rt-h5');
mdlr('mmzsource:rt-h6', m => {
const style = `width:100vw; height:100vh;`;
const doc = document.body;
doc.innerHTML = `<canvas width=200 height=200 style="${style}"></canvas>`;
doc.style.margin = "0";
doc.style.overflow = "hidden";
const canvas = doc.querySelector('canvas');
const { width, height } = canvas;
const ctx = canvas.getContext('2d');
const image = ctx.getImageData(0, 0, width, height);
// module import problem ... moved stuff to raytrace (temporarily?)
// const { color, getRed, getGreen, getBlue } = m.require('mmzsource:canvas');
const rt = m.require('mmzsource:raytrace');
const rayOrigin = rt.point(0,0,-5);
const wallZ = 10;
const wallSize = 7;
const pixelSize = wallSize / width; // assuming width === height
const half = wallSize * 0.5 // assuming width === height
let material = rt.material();
let shape = rt.sphere();
material.color = rt.color(1,0.2,1);
shape.material = material;
const lightPosition = rt.point(-10,10,-10);
const lightColor = rt.color(1,1,1);
const light = rt.pointLight(lightPosition, lightColor);
function animate() {
for (let y = 0; y < height; y++) {
let worldY = half - pixelSize * y;
for (let x = 0; x < width; x++) {
const worldX = -half + pixelSize * x;
const position = rt.point(worldX, worldY, wallZ);
const r = rt.ray(rayOrigin, rt.norm(rt.sub(position, rayOrigin)));
const xs = rt.intersects(shape, r);
const h = rt.hit(xs);
if (h) {
const point = rt.position(r,h.t);
const normal = rt.normalAt(h.object, point);
const eye = rt.neg(r.direction);
const color = rt.lighting(h.object.material, light, point, eye, normal);
const pixel = (x*4)+(y*4*width);
image.data[pixel + 0] = rt.getRed(color) * 255;
image.data[pixel + 1] = rt.getGreen(color) * 255;
image.data[pixel + 2] = rt.getBlue(color) * 255;
image.data[pixel + 3] = 255;
}
}
}
ctx.putImageData(image, 0, 0);
// CREATE PPM FILE CONTENTS
let ppm = `P3\n${width} ${height}\n255`;
for (let x = 0; x < width; x++) {
ppm += `\n`;
for (let y = 0; y < height; y++) {
let pixel = (x*4) + (y*4*width);
ppm += `${image.data[pixel + 0]} ${image.data[pixel + 1]} ${image.data[pixel + 2]} `
}
}
ppm += `\n`;
console.log(ppm);
}
requestAnimationFrame(animate);
})
mdlr('mmzsource:rt-h6');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment