Published
Edited
Feb 17, 2021
Insert cell
Insert cell
Insert cell
viewof rayANDsphere = {
let ray = {
direction: [1, 1],
origin: [0, 0],
tmin: 0,
tmax: Number.POSITIVE_INFINITY
};
let sphere = {
center: [3, 3],
radius: 2
};

let rayANDsphere = { ray, sphere };
const intersectSphere = () => {
ray.tmin = 0;
ray.tmax = Number.POSITIVE_INFINITY;
return ray_sphere(ray, sphere);
};
const svgnode = DOM.svg(width, height);
const svg = d3.select(svgnode);
// .attr("transform", `translate(${[margin.left, margin.top]})`);
let direction = ray.direction;
let origin = ray.origin;
let sphereIntersected = ray_sphere(ray, sphere);
svgnode.value = rayANDsphere;
const g = svg;

const circle = g
.append("circle")
.datum({
cx: xScale(sphere.center[0]),
cy: yScale(sphere.center[1]),
r: xScale(sphere.center[0] + sphere.radius) - xScale(sphere.center[0])
})
.attr("cursor", "move")
.attr("cx", d => d.cx)
.attr("cy", d => d.cy)
.attr("r", d => d.r)
.style("fill-opacity", 0.1)
.style("stroke", "black")
.call(d3.drag().on("drag", draggedSphere));

const mainVector = g
.append("polyline")
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("marker-end", "url(#arrow)");

const rayForward = g
.append("polyline")
.attr("stroke", "magenta")
.attr("stroke-width", 1);
const rayBackward = g
.append("polyline")
.attr("stroke", "red")
.attr("opacity", 0.25)
.attr("stroke-width", 1)
.attr("stroke-dasharray", "4");

const rayRange = g
.append("polyline")
.attr("stroke", "black")
.attr("stroke-width", 2);

const rayRangePoints = g
.append("g")
.attr("stroke", "black")
.attr("fill", "none")
.attr("stroke-width", 1);
const rayRangeBegin = rayRangePoints.append("circle").attr("r", 3);
const rayRangeEnd = rayRangePoints.append("circle").attr("r", 3);

const label = g
.append("g")
.style("font-style", "italic")
.style("font-size", 11);
const labelTmin = label.append("text");
const labelTmax = label.append("text");

const point = g
.append("g")
.attr("cursor", "move")
//.attr("pointer-events", "all")
.attr("stroke", "transparent")
.attr("stroke-width", 5)
.append("circle")
.datum(scale(direction))
.attr("r", 2.5)
.call(d3.drag().on("drag", draggedRayHead));

update(scale(direction));

function draggedSphere(event, d) {
d.cx = Math.max(0, Math.min(width - d.r, event.x)); //event.x;
d.cy = Math.max(0, Math.min(height - d.r, event.y)); //event.y;
circle
.raise()
.attr("cx", d.cx)
.attr("cy", d.cy);

sphere.center[0] = xScale.invert(d.cx);
sphere.center[1] = yScale.invert(d.cy);
svgnode.dispatchEvent(new CustomEvent("input"));
update(d, true);
}
function draggedRayHead(event, d) {
d[0] = event.x;
d[1] = event.y;
direction[0] = xScale.invert(d[0]);
direction[1] = yScale.invert(d[1]);

d3.select(this).raise();
svgnode.dispatchEvent(new CustomEvent("input"));
update(d);
}

function pointOnRay(t) {
return [origin[0] + t * direction[0], origin[1] + t * direction[1]];
}
function pForward() {
return pointOnRay(1000);
}
function pBackward() {
return pointOnRay(-1000);
}

function update(d, sphereFlag) {
const sphereIntersected = intersectSphere();

let p_tMin = scale(pointOnRay(ray.tmin)),
p_tMax = scale(pointOnRay(ray.tmax));

if (!sphereFlag) point.attr("cx", d[0]).attr("cy", d[1]);

rayForward.attr("points", [scale(origin), scale(pForward())]);
rayBackward.attr("points", [scale(pBackward()), scale(origin)]);

if (sphereIntersected) {
label
.style("text-anchor", direction[0] <= 0 ? "start" : "end")
.style("alignment-baseline", direction[1] <= 0 ? "auto" : "hanging")
.style("opacity", 1);
labelTmin
.attr("transform", `translate(${p_tMin})`)
.attr("dx", direction[0] >= 0 ? windowExtent.x[0] : windowExtent.x[1])
.attr("dy", direction[1] >= 0 ? windowExtent.y[0] : windowExtent.y[1])
.text(`t = ${d3.format(".1f")(ray.tmin)}`);
labelTmax
.attr("transform", `translate(${p_tMax})`)
.attr("dx", direction[0] >= 0 ? windowExtent.x[0] : windowExtent.x[1])
.attr("dy", direction[1] >= 0 ? windowExtent.y[0] : windowExtent.y[1])
.text(`t = ${d3.format(".1f")(ray.tmax)}`);

rayRange.style("opacity", 1);
rayRangePoints.style("opacity", 1);
rayRange.attr("points", [p_tMin, p_tMax]);
rayRangeBegin
.datum(p_tMin)
.attr("cx", d => d[0])
.attr("cy", d => d[1]);
rayRangeEnd
.datum(p_tMax)
.attr("cx", d => d[0])
.attr("cy", d => d[1]);
} else {
label.style("opacity", 0);
rayRange.style("opacity", 0);
rayRangePoints.style("opacity", 0);
}
if (!sphereFlag) mainVector.attr("points", [scale(origin), d]);
}
return svgnode;
}
Insert cell
ray_sphere = (r, s) => {
const swap = (x, y) => [y, x];
// t^2 * d^2 + 2 * t * d * (o-c) + (o-c)^2 - r^2 = 0
// A = d^2, B = 2*d*(o-c), C = (o-c)^2 - r^2
// t = (-B +/- sqrt(B^2 - 4*A*C))/ 2A
const A = r.direction[0] * r.direction[0] + r.direction[1] * r.direction[1];
const OminusC = [r.origin[0] - s.center[0], r.origin[1] - s.center[1]];
const B = 2 * (r.direction[0] * OminusC[0] + r.direction[1] * OminusC[1]);
const C =
OminusC[0] * OminusC[0] + OminusC[1] * OminusC[1] - s.radius * s.radius;
let discriminand = B * B - 4 * A * C;
if (discriminand < 0) return false;
const D = Math.sqrt(discriminand);
let tmin = (-B - D) / (2 * A);
let tmax = (-B + D) / (2 * A);

tmin = d3.max([r.tmin, tmin]);
tmax = d3.min([r.tmax, tmax]);

if (tmin > tmax) return false;
else {
r.tmin = tmin;
r.tmax = tmax;
return true;
}
}
Insert cell
Insert cell
md`### Visualization related definitions`
Insert cell
scale = p => [xScale(p[0]), yScale(p[1])]
Insert cell
windowAspect = width/height
Insert cell
yScale = d3
.scaleLinear()
.domain(windowExtent.y)
.range([height, 0])
Insert cell
windowExtent = ({ x: [-10 * windowAspect, 10 * windowAspect], y: [-10, 10] })
Insert cell
xScale = d3
.scaleLinear()
.domain(windowExtent.x)
.range([0, width])
Insert cell
Insert cell
md`### External Libraries and Tools`
Insert cell
arrowhead
Insert cell
import { arrowhead} from "@harrystevens/linear-algebra"
Insert cell
import {rangeSlider, themes} from '@mootari/range-slider'
Insert cell
d3 = require("d3")
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