Published
Edited
Aug 2, 2021
5 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
//
// Returns a subdivided curve using Hobbs' algorithm. The pertubartion
// parameters are noise functions that return a value between 0 and 1
// * noiseSplit controls the edge subdivision point
// * noiseAngle controls the displacement angle with respect to the edge normal
// * noiseAmplitude controls the displacement vector size. 1 yields a displacement with the same size
// as the edge
//
function hobbsPerturb(
curve,
noiseSplit,
noiseAngle,
noiseAmplitude,
moveVertices = false
) {
let result = new Curve();
let n = curve.length;
let q = curve[curve.length - 1];
for (let i = 0; i < n; i++) {
let p = curve[i];
let q = curve[(i + n - 1) % n];
let e = p.sub(q);
let mid = q.add(e.scale(noiseSplit()));
let displaced = mid.add(
e
.rotate(-Math.PI / 2 + Math.PI * (noiseAngle() - 0.5))
.scale(noiseAmplitude())
);
result.push(q, displaced);
if (moveVertices) {
let r = curve[(i + n - 2) % n];
let qdisplaced = q
.add(p.sub(r))
.rotate(-Math.PI / 2 + Math.PI * (noiseAngle() - 0.5))
.scale(noiseAmplitude() / 2);
result.push(qdisplaced, displaced);
} else {
result.push(q, displaced);
}
}
return result;
}
Insert cell
Insert cell
viewof splitDraws = Inputs.range([1, 10], {
label: "Edge split noise n",
step: 1,
value: 2
})
Insert cell
viewof angleDraws = Inputs.range([1, 10], {
label: "Angle noise n", step:1,
value: 2
})
Insert cell
viewof amplitudeDraws = Inputs.range([1, 10], {
label: "Amplitude noise n", step:1,
value: 1
})
Insert cell
viewof amplitudeScale = Inputs.range([0.01, 2], {
label: "Amplitude scale",
value: 1,
step: 0.01
})
Insert cell
viewof layerPerturb = Inputs.range([0, 10], {
label: "Additional perturbation steps per layer",
step: 1,
value: 3
})
Insert cell
viewof layers = Inputs.range([1, 100], { label: "layers", value: 20, step: 1 })
Insert cell
viewof corePerturb = Inputs.range([1, 10], {
label: "Initial perturbation steps",
step: 1,
value: 3
})
Insert cell
viewof useResample = Inputs.checkbox(["initial", "layers"], {
label: "Perturbations followed by arc length resampling?"
})
Insert cell
viewof alpha = Inputs.range([0.01, 1], {
label: "Transparency",
value: 0.02,
step: 0.01
})
Insert cell
viewof color = ColorInput({ value: "#ff0000", label: "color" })
Insert cell
Insert cell
noiseSplit = d3.randomBates(splitDraws)
Insert cell
noiseAngle = d3.randomBates(angleDraws)
Insert cell
noiseAmplitude = {
const noise = d3.randomBates(amplitudeDraws);
return () => 2 * amplitudeScale * Math.abs(noise() - 0.5);
}
Insert cell
Insert cell
import { Curve, Vec } from "@esperanc/2d-geometry-utils"
Insert cell
import { color as ColorInput } from "@esperanc/color-input"
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more