Public
Edited
Jan 29, 2024
4 stars
Insert cell
Insert cell
{
// Create the context
const context = DOM.context2d(ewidth, height);
context.canvas.value = context;

// Just set a color based upon our (i,j) value. Note, in Canvas space, the origin
// is *upper left*, not *lower left*. I don't correct for this.
// Fill in a pattern based upon pixel position in image coordinates
for (let i = 0; i < ewidth; ++i) {
for (let j = 0; j < height; ++j) {
// filStyle uses CSS color names; so you need a *string* rgb function.
context.fillStyle = `rgb(${Math.floor((i / ewidth) * 255)}, ${Math.floor(
(j / height) * 255
)}, 0)`;
context.fillRect(i, j, 1, 1);
}
}

return context.canvas;
}
Insert cell
Insert cell
Insert cell
{
// Create the context
const context = DOM.context2d(ewidth, height);
context.canvas.value = context;

// Flip the context vertically
context.transform(1, 0, 0, -1, 0, height);

// Fill in a pattern based upon pixel position. Due to the flip, image and spatial
// coordinates align
for (let i = 0; i < ewidth; ++i) {
for (let j = 0; j < height; ++j) {
// Just set a color based upon our (i,j) value. Note, in Canvas space, the origin
// filStyle uses CSS color names; so you need a *string* rgb function.
context.fillStyle = `rgb(${Math.floor((i / ewidth) * 255)}, ${Math.floor(
(j / height) * 255
)}, 0)`;
context.fillRect(i, j, 1, 1);
}
}

return context.canvas;
}
Insert cell
Insert cell
Insert cell
glm = import("gl-matrix")
Insert cell
Insert cell
eye = glm.vec3.fromValues(0, 0, 1000)
Insert cell
Insert cell
view_dir = glm.vec3.fromValues(0, 0, -1)
Insert cell
Insert cell
viewport_offset = (0.5 * height) / Math.tan(glm.glMatrix.toRadian(60 / 2))
Insert cell
Insert cell
Insert cell
Insert cell
up = glm.vec3.fromValues(0, 1, 0)
Insert cell
Insert cell
ihat = glm.vec3.cross(
glm.vec3.create(),
up,
glm.vec3.negate(glm.vec3.create(), view_dir)
)
Insert cell
jhat = up
Insert cell
Insert cell
p_00 = glm.vec3.scaleAndAdd(
glm.vec3.create(),
glm.vec3.scaleAndAdd(
glm.vec3.create(),
viewport_center,
ihat,
-0.5 * ewidth + 0.5
),
jhat,
-0.5 * height + 0.5
)
Insert cell
Insert cell
{
// Create the context
const context = DOM.context2d(ewidth, height);
context.canvas.value = context;

// Flip the context vertically
context.transform(1, 0, 0, -1, 0, height);

// Fill in a pattern based upon the ray's direction. Redder means more to the right,
// green more to to top, blue means farther away
let ray_origin = glm.vec3.scaleAndAdd(
glm.vec3.create(),
glm.vec3.scaleAndAdd(glm.vec3.create(), p_00, ihat, 600),
jhat,
0
);

// Where is the ray going? We use perpective, so its ray_origin - eye
let ray_dir = glm.vec3.normalize(
glm.vec3.create(),
glm.vec3.subtract(glm.vec3.create(), ray_origin, eye)
);

for (let i = 0; i < ewidth; ++i) {
for (let j = 0; j < height; ++j) {
// What is the ray origin?
let ray_origin = glm.vec3.scaleAndAdd(
glm.vec3.create(),
glm.vec3.scaleAndAdd(glm.vec3.create(), p_00, ihat, i),
jhat,
j
);

// Where is the ray going? We use perpective, so its ray_origin - eye
let ray_dir = glm.vec3.normalize(
glm.vec3.create(),
glm.vec3.subtract(glm.vec3.create(), ray_origin, eye)
);

// filStyle uses CSS color names; so you need a *string* rgb function.
context.fillStyle = `rgb(${Math.floor(
0.5 * (ray_dir[0] + 1) * 255
)}, ${Math.floor(0.5 * (ray_dir[1] + 1) * 255)}, ${Math.floor(
0.5 * (ray_dir[2] + 1) * 255
)})`;
context.fillRect(i, j, 1, 1);
}
}

return context.canvas;
}
Insert cell
Insert cell
Insert cell
s1_origin = glm.vec3.fromValues(0, 0, 0)
Insert cell
s1_radius = 0.25 * ewidth
Insert cell
Insert cell
function intersect_sphere(origin, dir, center, radius) {
// Check discriminant
const a = 1.0;
const origin_sub_center = glm.vec3.subtract(
glm.vec3.create(),
origin,
center
);
const b = glm.vec3.dot(dir, origin_sub_center);
const c =
glm.vec3.dot(origin_sub_center, origin_sub_center) - radius * radius;
if (b * b - a * c < 0) return Number.NEGATIVE_INFINITY;

// Find roots
let t0 = (-b - Math.sqrt(b * b - a * c)) / a;
let t1 = (-b + Math.sqrt(b * b - a * c)) / a;
if (t0 < 0) return t1;
else return t0;
}
Insert cell
Insert cell
{
// Create the context
const context = DOM.context2d(ewidth, height);
context.canvas.value = context;

// Flip the context vertically
context.transform(1, 0, 0, -1, 0, height);

const origin = glm.vec3.fromValues(0, 0, 0);

// Color based upon if the sphere was hit-
for (let i = 0; i < ewidth; ++i) {
for (let j = 0; j < height; ++j) {
// What is the ray origin?
let ray_origin = glm.vec3.scaleAndAdd(
glm.vec3.create(),
glm.vec3.scaleAndAdd(glm.vec3.create(), p_00, ihat, i),
jhat,
j
);

// Where is the ray going? We use perpective, so its ray_origin - eye
let ray_dir = glm.vec3.normalize(
glm.vec3.create(),
glm.vec3.subtract(glm.vec3.create(), ray_origin, eye)
);

// Did we hit the sphere?
if (intersect_sphere(ray_origin, ray_dir, s1_origin, s1_radius) > 0)
context.fillStyle = "white";
else context.fillStyle = "black";

context.fillRect(i, j, 1, 1);
}
}

return context.canvas;
}
Insert cell
Insert cell
s2_origin = glm.vec3.fromValues(-100, +100, 175)
Insert cell
s2_radius = 0.15 * ewidth
Insert cell
Insert cell
{
// Create the context
const context = DOM.context2d(ewidth, height);
context.canvas.value = context;

// Flip the context vertically
context.transform(1, 0, 0, -1, 0, height);

const s1_origin = glm.vec3.fromValues(0, 0, 0);
const s1_radius = 0.25 * ewidth;
const s2_origin = glm.vec3.fromValues(-100, +100, 175);
const s2_radius = 0.15 * ewidth;

// Color based upon which is closest and if there are multiple intersections
for (let i = 0; i < ewidth; ++i) {
for (let j = 0; j < height; ++j) {
// What is the ray origin?
let ray_origin = glm.vec3.scaleAndAdd(
glm.vec3.create(),
glm.vec3.scaleAndAdd(glm.vec3.create(), p_00, ihat, i),
jhat,
j
);

// Where is the ray going? We use perpective, so its ray_origin - eye
let ray_dir = glm.vec3.normalize(
glm.vec3.create(),
glm.vec3.subtract(glm.vec3.create(), ray_origin, eye)
);

// Did we hit the first sphere?
let s1_dist = intersect_sphere(ray_origin, ray_dir, s1_origin, s1_radius);

// Did we hit the second sphere?
let s2_dist = intersect_sphere(ray_origin, ray_dir, s2_origin, s2_radius);

// Color appropriately
if (s1_dist >= 0 && s2_dist < 0) context.fillStyle = "red";
else if (s1_dist < 0 && s2_dist >= 0) context.fillStyle = "green";
else if (s1_dist > 0 && s2_dist >= 0 && s2_dist < s1_dist)
context.fillStyle = "blue";
else if (s1_dist >= 0 && s2_dist >= 0 && s2_dist >= s1_dist)
context.fillStyle = "yellow";
else context.fillStyle = "black";

context.fillRect(i, j, 1, 1);
}
}

return context.canvas;
}
Insert cell
Insert cell
light_dir = glm.vec3.normalize(glm.vec3.create(), glm.vec3.fromValues(-1, 1, 3))
Insert cell
Insert cell
function shade_phong(
normal,
to_light,
to_view,
diffuse_color,
specular_color,
shininess
) {
// Ambient color
const color = glm.vec3.scale(glm.vec3.create(), diffuse_color, 0.1);

const ndotl = glm.vec3.dot(normal, to_light);
if (ndotl > 0) {
glm.vec3.scaleAndAdd(color, color, diffuse_color, ndotl);

const reflect = glm.vec3.scaleAndAdd(
glm.vec3.create(),
glm.vec3.negate(glm.vec3.create(), light_dir),
normal,
2 * ndotl
);
const rdotv = glm.vec3.dot(reflect, to_view);
if (rdotv > 0)
glm.vec3.scaleAndAdd(
color,
color,
specular_color,
Math.pow(rdotv, shininess)
);
}

return color;
}
Insert cell
Insert cell
s1_diffuse = glm.vec3.fromValues(0.5, 0.5, 0.5)
Insert cell
s1_specular = glm.vec3.fromValues(0.4, 0.4, 0.4)
Insert cell
s1_shininess = 64
Insert cell
s2_diffuse = glm.vec3.fromValues(0.7, 0, 0)
Insert cell
s2_specular = glm.vec3.fromValues(0.2, 0.2, 0.2)
Insert cell
s2_shininess = 2
Insert cell
Insert cell
objects = [
{
origin: s1_origin,
radius: s1_radius,
diffuse: s1_diffuse,
specular: s1_specular,
shininess: s1_shininess,
intersect: function (r, d) {
return intersect_sphere(r, d, this.origin, this.radius);
}
},
{
origin: s2_origin,
radius: s2_radius,
diffuse: s2_diffuse,
specular: s2_specular,
shininess: s2_shininess,
intersect: function (r, d) {
return intersect_sphere(r, d, this.origin, this.radius);
}
}
]
Insert cell
{
// Create the context
const context = DOM.context2d(ewidth, height);
context.canvas.value = context;

// Flip the context vertically
context.transform(1, 0, 0, -1, 0, height);

// Phong shade the spheres locally
for (let i = 0; i < ewidth; ++i) {
for (let j = 0; j < height; ++j) {
// What is the ray origin?
let ray_origin = glm.vec3.scaleAndAdd(
glm.vec3.create(),
glm.vec3.scaleAndAdd(glm.vec3.create(), p_00, ihat, i),
jhat,
j
);

// Where is the ray going? We use perpective, so its ray_origin - eye
let ray_dir = glm.vec3.normalize(
glm.vec3.create(),
glm.vec3.subtract(glm.vec3.create(), ray_origin, eye)
);

// Test each object individually. Note that the intersection function
// has been made part of the object for generality: You can define your
// own object that has an intersect method
let hit = undefined;
let hit_dist = Number.POSITIVE_INFINITY;
for (let i = 0; i < objects.length; ++i) {
let dist = objects[i].intersect(ray_origin, ray_dir);
if (dist >= 0 && (hit === undefined || dist < hit_dist)) {
hit = i;
hit_dist = dist;
}
}

// Color appropriately
if (hit === undefined) context.fillStyle = "black";
else {
let point = glm.vec3.scaleAndAdd(
glm.vec3.create(),
ray_origin,
ray_dir,
hit_dist
);
let object = objects[hit];
let color = shade_phong(
glm.vec3.normalize(
glm.vec3.create(),
glm.vec3.subtract(glm.vec3.create(), point, object.origin)
),
light_dir,
glm.vec3.negate(glm.vec3.create(), view_dir),
object.diffuse,
object.specular,
object.shininess
);
context.fillStyle = `rgb(${255 * color[0]}, ${255 * color[1]}, ${
255 * color[2]
})`;
}

context.fillRect(i, j, 1, 1);
}
}

return context.canvas;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more