1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-19 06:52:02 +02:00

full regeneration

This commit is contained in:
Pomax
2020-09-19 18:34:03 -07:00
parent ad872f83c5
commit 4c0e71cd4a
234 changed files with 1468 additions and 1376 deletions

View File

@@ -15,6 +15,7 @@ draw() {
translate(this.width/2, 0);
line(0,0,0,this.height);
// draw the realigned curve:
this.drawRTCurve(
this.rotatePoints(
this.translatePoints(
@@ -25,7 +26,7 @@ draw() {
}
translatePoints(points) {
// translate to (0,0)
// translate so that the curve starts at (0,0)
let m = points[0];
return points.map(v => {
return {
@@ -36,7 +37,7 @@ translatePoints(points) {
}
rotatePoints(points) {
// rotate so that last point is (...,0)
// rotate the curve so that the point is (...,0)
let degree = curve.points.length - 1;
let dx = points[degree].x;
let dy = points[degree].y;
@@ -50,13 +51,18 @@ rotatePoints(points) {
}
drawRTCurve(points) {
let degree = curve.points.length - 1;
let ncurve = new Bezier(this, points);
// draw axes
translate(60, this.height/2);
setStroke(`grey`);
line(0,-this.height,0,this.height);
line(-60,0,this.width,0);
line(0, -this.height, 0, this.height);
line(-60, 0, this.width, 0);
// draw transformed curve
let degree = curve.points.length - 1;
let ncurve = new Bezier(this, points);
ncurve.drawCurve();
// and label the last point in the transformed curve
setFill(`black`);
text(`(0,0)`, 5,15);
text(`(${points[degree].x|0},0)`, points[degree].x, -5, CENTER);

View File

@@ -40,7 +40,8 @@ If we drop all the zero-terms, this gives us:
We can see that our original curve definition has been simplified considerably. The following graphics illustrate the result of aligning our example curves to the x-axis, with the cubic case using the coordinates that were just used in the example formulae:
<div class="figure">
<graphics-element title="Aligning a quadratic curve" width="550" src="./aligning.js" data-type="quadratic"></graphics-element>
&nbsp;
<graphics-element title="Aligning a cubic curve" width="550" src="./aligning.js" data-type="cubic"></graphics-element>
</div>

View File

@@ -1,57 +1,4 @@
const Tvalues24 = [
-0.0640568928626056260850430826247450385909,
0.0640568928626056260850430826247450385909,
-0.1911188674736163091586398207570696318404,
0.1911188674736163091586398207570696318404,
-0.3150426796961633743867932913198102407864,
0.3150426796961633743867932913198102407864,
-0.4337935076260451384870842319133497124524,
0.4337935076260451384870842319133497124524,
-0.5454214713888395356583756172183723700107,
0.5454214713888395356583756172183723700107,
-0.6480936519369755692524957869107476266696,
0.6480936519369755692524957869107476266696,
-0.7401241915785543642438281030999784255232,
0.7401241915785543642438281030999784255232,
-0.8200019859739029219539498726697452080761,
0.8200019859739029219539498726697452080761,
-0.8864155270044010342131543419821967550873,
0.8864155270044010342131543419821967550873,
-0.9382745520027327585236490017087214496548,
0.9382745520027327585236490017087214496548,
-0.9747285559713094981983919930081690617411,
0.9747285559713094981983919930081690617411,
-0.9951872199970213601799974097007368118745,
0.9951872199970213601799974097007368118745,
];
const Cvalues24 = [
0.1279381953467521569740561652246953718517,
0.1279381953467521569740561652246953718517,
0.1258374563468282961213753825111836887264,
0.1258374563468282961213753825111836887264,
0.121670472927803391204463153476262425607,
0.121670472927803391204463153476262425607,
0.1155056680537256013533444839067835598622,
0.1155056680537256013533444839067835598622,
0.1074442701159656347825773424466062227946,
0.1074442701159656347825773424466062227946,
0.0976186521041138882698806644642471544279,
0.0976186521041138882698806644642471544279,
0.086190161531953275917185202983742667185,
0.086190161531953275917185202983742667185,
0.0733464814110803057340336152531165181193,
0.0733464814110803057340336152531165181193,
0.0592985849154367807463677585001085845412,
0.0592985849154367807463677585001085845412,
0.0442774388174198061686027482113382288593,
0.0442774388174198061686027482113382288593,
0.0285313886289336631813078159518782864491,
0.0285313886289336631813078159518782864491,
0.0123412297999871995468056670700372915759,
0.0123412297999871995468056670700372915759,
];
import { C, T } from "./ct-values.js";
setup() {
this.curve = Bezier.defaultCubic(this);
@@ -71,14 +18,11 @@ draw() {
}
computeLength(curve) {
const z = 0.5,
len = Tvalues24.length;
const z = 0.5, len = T.length;
let sum = 0;
for (let i = 0, t; i < len; i++) {
t = z * Tvalues24[i] + z;
sum += Cvalues24[i] * this.arcfn(t, curve.derivative(t));
t = z * T[i] + z;
sum += C[i] * this.arcfn(t, curve.derivative(t));
}
return z * sum;
}

View File

@@ -0,0 +1,55 @@
const T = [
-0.0640568928626056260850430826247450385909,
0.0640568928626056260850430826247450385909,
-0.1911188674736163091586398207570696318404,
0.1911188674736163091586398207570696318404,
-0.3150426796961633743867932913198102407864,
0.3150426796961633743867932913198102407864,
-0.4337935076260451384870842319133497124524,
0.4337935076260451384870842319133497124524,
-0.5454214713888395356583756172183723700107,
0.5454214713888395356583756172183723700107,
-0.6480936519369755692524957869107476266696,
0.6480936519369755692524957869107476266696,
-0.7401241915785543642438281030999784255232,
0.7401241915785543642438281030999784255232,
-0.8200019859739029219539498726697452080761,
0.8200019859739029219539498726697452080761,
-0.8864155270044010342131543419821967550873,
0.8864155270044010342131543419821967550873,
-0.9382745520027327585236490017087214496548,
0.9382745520027327585236490017087214496548,
-0.9747285559713094981983919930081690617411,
0.9747285559713094981983919930081690617411,
-0.9951872199970213601799974097007368118745,
0.9951872199970213601799974097007368118745,
];
const C = [
0.1279381953467521569740561652246953718517,
0.1279381953467521569740561652246953718517,
0.1258374563468282961213753825111836887264,
0.1258374563468282961213753825111836887264,
0.121670472927803391204463153476262425607,
0.121670472927803391204463153476262425607,
0.1155056680537256013533444839067835598622,
0.1155056680537256013533444839067835598622,
0.1074442701159656347825773424466062227946,
0.1074442701159656347825773424466062227946,
0.0976186521041138882698806644642471544279,
0.0976186521041138882698806644642471544279,
0.086190161531953275917185202983742667185,
0.086190161531953275917185202983742667185,
0.0733464814110803057340336152531165181193,
0.0733464814110803057340336152531165181193,
0.0592985849154367807463677585001085845412,
0.0592985849154367807463677585001085845412,
0.0442774388174198061686027482113382288593,
0.0442774388174198061686027482113382288593,
0.0285313886289336631813078159518782864491,
0.0285313886289336631813078159518782864491,
0.0123412297999871995468056670700372915759,
0.0123412297999871995468056670700372915759,
];
export { C, T }

View File

@@ -16,6 +16,9 @@ draw() {
setStroke("red");
curve.drawSkeleton(`lightblue`);
// instead of running an arclength summation, we
// just... sum the lengths of our line segments.
LUT.forEach((p1,i) => {
if (i===0) return;
let p0 = LUT[i-1];

View File

@@ -18,6 +18,9 @@ draw() {
curve.drawCurve();
curve.drawPoints();
// Start with complete nonsense min/max values, where
// min is huge and max is tiny, so we can bring them
// down and up, respectively.
let minx = Number.MAX_SAFE_INTEGER,
miny = minx,
@@ -28,6 +31,7 @@ draw() {
noFill();
setStroke(`red`);
// For each extremum, see if that changes the bbox values.
[0, ...extrema.x, ...extrema.y, 1].forEach(t => {
let p = curve.get(t);
if (p.x < minx) minx = p.x;
@@ -37,6 +41,7 @@ draw() {
if (t > 0 && t< 1) circle(p.x, p.y, 3);
});
// And we're done.
setStroke(`#0F0`);
rect(minx, miny, maxx - minx, maxy - miny);
}

View File

@@ -1,6 +1,8 @@
let curve;
setup() {
setPanelCount(3);
let type = this.parameters.type ?? `quadratic`;
if (type === `quadratic`) {
curve = Bezier.defaultQuadratic(this);
@@ -14,11 +16,21 @@ setup() {
draw() {
clear();
const dim = this.height;
let pcount = curve.points.length;
curve.drawSkeleton();
curve.drawCurve();
curve.drawPoints();
translate(dim, 0);
nextPanel();
this.drawComponentX(dim, pcount);
resetTransform();
nextPanel();
nextPanel();
this.drawComponentY(dim, pcount);
}
drawComponentX(dim, pcount) {
setStroke(`black`);
line(0,0,0,dim);
@@ -26,14 +38,15 @@ draw() {
translate(40,20);
drawAxes(`t`, 0, 1, `X`, 0, dim, dim, dim);
let pcount = curve.points.length;
// remap our curve so that y becomes our x
// coordinates, and x becomes the t intervals.
new Bezier(this, curve.points.map((p,i) => ({
x: (i/(pcount-1)) * dim,
y: p.x
}))).drawCurve();
}
resetTransform();
translate(2*dim, 0);
drawComponentY(dim, pcount) {
setStroke(`black`);
line(0,0,0,dim);
@@ -41,6 +54,8 @@ draw() {
translate(40,20);
drawAxes(`t`, 0,1, `Y`, 0, dim, dim, dim);
// remap our curve, leaving y as is, but
// making x be the t intervals.
new Bezier(this, curve.points.map((p,i) => ({
x: (i/(pcount-1)) * dim,
y: p.y

View File

@@ -10,4 +10,6 @@ If you move points in a curve sideways, you should only see the middle graph cha
<graphics-element title="Quadratic Bézier curve components" width="825" src="./components.js" data-type="quadratic"></graphics-element>
&nbsp;
<graphics-element title="Cubic Bézier curve components" width="825" src="./components.js" data-type="cubic"></graphics-element>

View File

@@ -19,6 +19,8 @@ draw() {
curve.drawPoints();
});
// For the "both directions" version, we also want
// to show the circle that fits our curve.
if (this.parameters.omni) {
let t = this.position;
let curve = q;
@@ -30,14 +32,16 @@ draw() {
drawCurvature(curve) {
let s, t, p, n, k, ox, oy;
for(s=0; s<256; s++) {
setStroke(`rgba(255,127,${s},0.6)`);
// compute the curvature at `t`:
t = s/255;
p = curve.get(t);
n = curve.normal(t);
k = this.computeCurvature(curve, t) * 10000;
// and then draw it.
ox = k * n.x;
oy = k * n.y;
setStroke(`rgba(255,127,${s},0.6)`);
line(p.x, p.y, p.x + ox, p.y + oy);
// And if requested, also draw it along the anti-normal.
@@ -55,6 +59,7 @@ computeCurvature(curve, t) {
qdsum = d.x * d.x + d.y * d.y,
dnm = qdsum ** 3/2;
// shortcut
if (num === 0 || dnm === 0) return 0;
return num / dnm;
@@ -70,7 +75,6 @@ drawIncidentCircle(curve, t) {
setFill(`rgba(200,200,255,0.4)`);
setStroke(`red`);
line(p.x, p.y, rx, ry);
circle(p.x, p.y, 3);
circle(rx, ry, 3);

View File

@@ -1,6 +1,7 @@
let curve;
setup() {
setPanelCount(3);
const type = this.parameters.type ?? `quadratic`;
if (type === `quadratic`) {
curve = Bezier.defaultQuadratic(this);
@@ -19,7 +20,19 @@ draw() {
curve.drawCurve();
curve.drawPoints();
translate(dim, 0);
nextPanel();
this.drawComponentX(dim, degree);
resetTransform();
nextPanel();
nextPanel();
this.drawComponentY(dim, degree);
}
drawComponentX(dim, degree) {
setStroke(`black`);
line(0,0,0,dim);
@@ -27,13 +40,16 @@ draw() {
translate(40,20);
drawAxes(`t`, 0, 1, `X`, 0, dim, dim, dim);
this.plotDimension(dim, new Bezier(this, curve.points.map((p,i) => ({
const B = new Bezier(this, curve.points.map((p,i) => ({
x: (i/degree) * dim,
y: p.x
}))));
})));
resetTransform();
translate(2*dim, 0);
// this is where things differ from the previous section
this.plotDimension(dim, B);
}
drawComponentY(dim, degree) {
setStroke(`black`);
line(0,0,0,dim);
@@ -41,10 +57,13 @@ draw() {
translate(40,20);
drawAxes(`t`, 0,1, `Y`, 0, dim, dim, dim);
this.plotDimension(dim, new Bezier(this, curve.points.map((p,i) => ({
x: (i/degree) * dim,
y: p.y
}))))
const B = new Bezier(this, curve.points.map((p,i) => ({
x: (i/degree) * dim,
y: p.y
})));
// this is where things differ from the previous section
this.plotDimension(dim, B)
}
plotDimension(dim, dimension) {
@@ -144,18 +163,27 @@ plotCubicDimension(t1, y1, t2, y2, dim, dimension, reverse) {
}
getRoots(v1, v2, v3) {
if (v3 === undefined) {
return [-v1 / (v2 - v1)];
}
// is this actually a line?
if (v3 === undefined) return [-v1 / (v2 - v1)];
const a = v1 - 2*v2 + v3,
b = 2 * (v2 - v1),
c = v1,
d = b*b - 4*a*c;
// quadratic root finding is not super complex.
const a = v1 - 2*v2 + v3;
// no root:
if (a === 0) return [];
const b = 2 * (v2 - v1),
c = v1,
d = b*b - 4*a*c;
// no root:
if (d < 0) return [];
// one root:
const f = -b / (2*a);
if (d === 0) return [f]
// two roots:
const l = sqrt(d) / (2*a);
return [f-l, f+l];
}

View File

@@ -12,6 +12,7 @@ draw() {
curve.drawCurve();
curve.drawPoints();
// align our curve and let's do some root finding
const p = curve.align().points,
a = p[2].x * p[1].y,
@@ -25,8 +26,11 @@ draw() {
roots = [];
if (this.almost(x, 0) ) {
if (!this.almost(y, 0) ) {
// because of floating point maths, we can't check whether
// x or y "are" zero, because they could be some value that is
// just off from zero due to floating point compound errors.
if (approx(x, 0)) {
if (!approx(y, 0)) {
roots.push(-z / y);
}
}
@@ -36,15 +40,14 @@ draw() {
sq = sqrt(det),
d2 = 2 * x;
if (!this.almost(d2, 0) ) {
if (!approx(d2, 0) ) {
roots.push(-(y+sq) / d2);
roots.push((sq-y) / d2);
}
}
setStroke(`red`);
setFill(`red`);
// Aaaan let's draw them
setColor(`red`);
roots.forEach(t => {
if (0 <= t && t <= 1) {
let p = curve.get(t);
@@ -53,7 +56,3 @@ draw() {
}
});
}
almost(v1, v2, epsilon=0.00001) {
return abs(v1 - v2) < epsilon;
}

View File

@@ -3,36 +3,37 @@
If you want to move objects along a curve, or "away from" a curve, the two vectors you're most interested in are the tangent vector and normal vector for curve points. These are actually really easy to find. For moving and orienting along a curve, we use the tangent, which indicates the direction of travel at specific points, and is literally just the first derivative of our curve:
\[
\left \{ \begin{matrix}
\begin{matrix}
tangent_x(t) = B'_x(t) \\
\\
tangent_y(t) = B'_y(t)
\end{matrix} \right.
\end{matrix}
\]
This gives us the directional vector we want. We can normalize it to give us uniform directional vectors (having a length of 1.0) at each point, and then do whatever it is we want to do based on those directions:
\[
d = || tangent(t) || = \sqrt{B'_x(t)^2 + B'_y(t)^2}
\]
\[
\left \{ \begin{matrix}
\hat{x}(t) = || tangent_x(t) ||
=\frac{tangent_x(t)}{ || tangent(t) || }
\begin{matrix}
d = \left \| tangent(t) \right \| = \sqrt{B'_x(t)^2 + B'_y(t)^2} \\
\\
\hat{x}(t) = \left \| tangent_x(t) \right \|
=\frac{tangent_x(t)}{ \left \| tangent(t) \right \| }
= \frac{B'_x(t)}{d} \\
\hat{y}(t) = || tangent_y(t) ||
= \frac{tangent_y(t)}{ || tangent(t) || }
\\
\hat{y}(t) = \left \| tangent_y(t) \right \|
= \frac{tangent_y(t)}{ \left \| tangent(t) \right \| }
= \frac{B'_y(t)}{d}
\end{matrix} \right.
\end{matrix}
\]
The tangent is very useful for moving along a line, but what if we want to move away from the curve instead, perpendicular to the curve at some point <i>t</i>? In that case we want the *normal* vector. This vector runs at a right angle to the direction of the curve, and is typically of length 1.0, so all we have to do is rotate the normalized directional vector and we're done:
\[
\left \{ \begin{array}{l}
\begin{array}{l}
normal_x(t) = \hat{x}(t) \cdot \cos{\frac{\pi}{2}} - \hat{y}(t) \cdot \sin{\frac{\pi}{2}} = - \hat{y}(t) \\
\\
normal_y(t) = \underset{quarter\ circle\ rotation} {\underbrace{ \hat{x}(t) \cdot \sin{\frac{\pi}{2}} + \hat{y}(t) \cdot \cos{\frac{\pi}{2}} }} = \hat{x}(t)
\end{array} \right.
\end{array}
\]
<div class="note">

View File

@@ -6,6 +6,7 @@ setup() {
curve = Bezier.defaultQuadratic(this);
} else {
curve = Bezier.defaultCubic(this);
// to show this off for Cubic curves we need to change some of the points
curve.points[0].x = 30;
curve.points[0].y = 230;
curve.points[1].x = 75;
@@ -25,24 +26,30 @@ draw() {
let t = i/10.0;
let p = curve.get(t);
let d = this.type === `quadratic` ? this.getQuadraticDerivative(t, pts) : this.getCubicDerivative(t, pts);
let m = sqrt(d.x*d.x + d.y*d.y);
d = { x: d.x/m, y: d.y/m };
let n = this.getNormal(t, d);
setStroke(`blue`);
line(p.x, p.y, p.x + d.x*f, p.y + d.y*f);
setStroke(`red`);
line(p.x, p.y, p.x + n.x*f, p.y + n.y*f);
setStroke(`black`);
circle(p.x, p.y, 3);
this.drawVectors(f, t, p, d);
}
curve.drawPoints();
}
drawVectors(f, t, p, d) {
let m = sqrt(d.x*d.x + d.y*d.y);
d = { x: d.x/m, y: d.y/m };
let n = this.getNormal(t, d);
// draw the tangent vector
setStroke(`blue`);
line(p.x, p.y, p.x + d.x*f, p.y + d.y*f);
// draw the normal vector
setStroke(`red`);
line(p.x, p.y, p.x + n.x*f, p.y + n.y*f);
// and the point these are for
setStroke(`black`);
circle(p.x, p.y, 3);
}
getQuadraticDerivative(t, points) {
let mt = (1 - t), d = [
{

View File

@@ -4,6 +4,7 @@ import { project, projectXY, projectXZ, projectYZ } from "./projection.js";
let d, cube;
setup() {
// step 1: let's define a cube to show our curve "in"
d = this.width/2 + 25;
cube = [
{x:0, y:0, z:0},
@@ -15,16 +16,25 @@ setup() {
{x:d, y:d, z:d},
{x:0, y:d, z:d}
].map(p => project(p));
// step 2: let's also define our 3D curve
const points = this.points = [
{x:120, y: 0, z: 0},
{x:120, y:220, z: 0},
{x: 30, y: 0, z: 30},
{x: 0, y: 0, z:200}
];
// step 3: to draw this curve to the screen, we need to project the
// coordinates from 3D to 2D, for which we use what is called
// a "cabinet projection".
this.curve = new Bezier(this, points.map(p => project(p)));
// We also construct handy projections on just the X/Y, X/Z, and Y/Z planes.
this.cxy = new Bezier(this, points.map(p => projectXY(p)));
this.cxz = new Bezier(this, points.map(p => projectXZ(p)));
this.cyz = new Bezier(this, points.map(p => projectYZ(p)));
setSlider(`.slide-control`, `position`, 0);
}
@@ -33,21 +43,32 @@ draw() {
translate(this.width/2 - 60, this.height/2 + 75);
const curve = this.curve;
// Draw all our planar curve projections first
this.drawCurveProjections();
// And the "back" side of our cube
this.drawCubeBack();
// Then, we draw the real curve
curve.drawCurve(`grey`);
setStroke(`grey`)
line(curve.points[0].x, curve.points[0].y, curve.points[1].x, curve.points[1].y);
line(curve.points[2].x, curve.points[2].y, curve.points[3].x, curve.points[3].y);
curve.points.forEach(p => circle(p.x, p.y, 2));
// And the current point on that curve
this.drawPoint(this.position);
// and then we can add the "front" of the cube.
this.drawCubeFront();
}
drawCurveProjections() {
this.cxy.drawCurve(`#EEF`);
this.cxz.drawCurve(`#EEF`);
this.cyz.drawCurve(`#EEF`);
}
drawCubeBack() {
const c = cube;
@@ -64,12 +85,6 @@ drawCubeBack() {
line(c[0].x, c[0].y, c[4].x, c[4].y);
}
drawCurveProjections() {
this.cxy.drawCurve(`#EEF`);
this.cxz.drawCurve(`#EEF`);
this.cyz.drawCurve(`#EEF`);
}
drawPoint(t) {
const {o, r, n, dt} = this.getFrenetVectors(t, this.points);
@@ -78,8 +93,13 @@ drawPoint(t) {
const p = project(o);
circle(p.x, p.y, 3);
// Draw our axis of rotation,
this.drawVector(p, vec.normalize(r), 40, `blue`, `r`);
// our normal,
this.drawVector(p, vec.normalize(n), 40, `red`, `n`);
// and our derivative.
this.drawVector(p, vec.normalize(dt), 40, `green`, `t`);
setFill(`black`)
@@ -88,8 +108,6 @@ drawPoint(t) {
drawCubeFront() {
const c = cube;
// rest of the cube
setStroke("lightgrey");
line(c[1].x, c[1].y, c[2].x, c[2].y);
line(c[2].x, c[2].y, c[3].x, c[3].y);
@@ -103,13 +121,20 @@ drawCubeFront() {
}
getFrenetVectors(t, originalPoints) {
// The frenet vectors are based on the (unprojected) curve,
// and its derivative curve.
const curve = new Bezier(this, originalPoints);
const d1curve = new Bezier(this, curve.dpoints[0]);
const o = curve.get(t);
const dt = d1curve.get(t);
const ddt = d1curve.derivative(t);
const o = curve.get(t);
const b = vec.plus(dt, ddt);
const r = vec.cross(b, dt);
// project the derivative into the future
const f = vec.plus(dt, ddt);
// and then find the axis of rotation wrt the plane
// spanned by the currented and projected derivative
const r = vec.cross(f, dt);
// after which the normal is found by rotating the
// tangent in that plane.
const n = vec.normalize(vec.cross(r, dt));
return { o, dt, r, n };
}

View File

@@ -1,23 +1,25 @@
/**
* A cabinet projection utility
* A cabinet projection utility library
*/
// Universal projector function
// Universal projector function:
function project(point3d, offset = { x: 0, y: 0 }, phi = -Math.PI / 6) {
// what they rarely tell you: if you want Z to "go up",
// X to "come out of the screen", and Y to be the "left/right",
// we need to switch some coordinates around:
const x = point3d.y,
y = -point3d.z,
z = -point3d.x;
const a = point3d.y,
b = -point3d.z,
c = -point3d.x / 2;
return {
x: offset.x + x + (z / 2) * Math.cos(phi),
y: offset.y + y + (z / 2) * Math.sin(phi),
x: offset.x + a + c * Math.cos(phi),
y: offset.y + b + c * Math.sin(phi),
};
}
// and some rebuilt planar projectors
// and some planar projectors:
function projectXY(p, offset, phi) {
return project({ x: p.x, y: p.y, z: 0 }, offset, phi);
}
@@ -30,6 +32,4 @@ function projectYZ(p, offset, phi) {
return project({ x: 0, y: p.y, z: p.z }, offset, phi);
}
export { project, projectXY, projectXZ, projectYZ }
export { project, projectXY, projectXZ, projectYZ };

View File

@@ -4,6 +4,7 @@ import { project, projectXY, projectXZ, projectYZ } from "./projection.js";
let d, cube;
setup() {
// We have the same setup as for the previous graphic
d = this.width/2 + 25;
cube = [
{x:0, y:0, z:0},
@@ -71,6 +72,7 @@ drawCurveProjections() {
}
drawPoint(t) {
// The only thing different compared to the previous graphic is this call:
const {o, r, n, dt } = this.getRMF(t, this.points);
setStroke(`red`);
@@ -102,75 +104,6 @@ drawCubeFront() {
line(c[7].x, c[7].y, c[4].x, c[4].y);
}
getRMF(t, originalPoints) {
const curve = new Bezier(this, originalPoints);
const d1curve = new Bezier(this, curve.dpoints[0]);
if (!this.rmf_LUT) {
this.rmf_LUT = this.generateRMF(originalPoints, curve, d1curve);
}
// find the frame for "t".
const last = this.rmf_LUT.length - 1;
const f = t * last;
const i = Math.floor(f);
// intenger index, or last index: we're done.
if (f === i) return this.rmf_LUT[i];
// no integer index: interpolate based on the adjacent frames.
const j = i + 1, ti = i/last, tj = j/last, ratio = (t - ti) / (tj - ti);
return this.lerpFrames(ratio, this.rmf_LUT[i], this.rmf_LUT[j]);
}
generateRMF(originalPoints, curve, d1curve) {
const frames = []
frames.push(this.getFrenetVectors(0, originalPoints));
for(let i=0, steps=24; i<steps; i++) {
const x0 = frames[i],
// get the next frame
t = (i+1)/steps,
x1 = {
o: curve.get(t),
dt: d1curve.get(t)
},
// then mirror the rotational axis and tangent
v1 = vec.minus(x1.o, x0.o),
c1 = vec.dot(v1, v1),
riL = vec.minus(x0.r, vec.scale(v1, 2/c1 * vec.dot(v1, x0.r))),
dtiL = vec.minus(x0.dt, vec.scale(v1, 2/c1 * vec.dot(v1, x0.dt))),
// and use those to compute a more stable rotational axis
v2 = vec.minus(x1.dt, dtiL),
c2 = vec.dot(v2, v2);
x1.r = vec.minus(riL, vec.scale(v2, 2/c2 * vec.dot(v2, riL)));
// and with that stable axis, a new normal.
x1.n = vec.cross(x1.r, x1.dt);
frames.push(x1);
}
return frames;
}
getFrenetVectors(t, originalPoints) {
const curve = new Bezier(this, originalPoints);
const d1curve = new Bezier(this, curve.dpoints[0]);
const dt = d1curve.get(t);
const ddt = d1curve.derivative(t);
const o = curve.get(t);
const b = vec.normalize(vec.plus(dt, ddt));
const r = vec.normalize(vec.cross(b, dt));
const n = vec.normalize(vec.cross(r, dt));
return { o, dt, r, n };
}
lerpFrames(t, f1, f2) {
var frame = {};
[`o`, `dt`, `r`, `n`].forEach(type => frame[type] = vec.lerp(t, f1[type], f2[type]));
return frame;
}
drawVector(from, vec, length, color, label) {
setStroke(color);
setFill(`black`);
@@ -189,3 +122,78 @@ drawVector(from, vec, length, color, label) {
});
text(label, from.x + txt.x, from.y + txt.y);
}
// This is where things are... rather different
getRMF(t, originalPoints) {
// If we don't have a rotation-minimizing lookup table, build it.
if (!this.rmf_LUT) {
const curve = new Bezier(this, originalPoints);
const d1curve = new Bezier(this, curve.dpoints[0]);
this.rmf_LUT = this.generateRMF(originalPoints, curve, d1curve);
}
// find the frame for "t":
const last = this.rmf_LUT.length - 1;
const f = t * last;
const i = Math.floor(f);
// If we're looking at an integer index, we're done.
if (f === i) return this.rmf_LUT[i];
// If we're not, we need to interpolate the adjacent frames
const j = i + 1, ti = i/last, tj = j/last, ratio = (t - ti) / (tj - ti);
return this.lerpFrames(ratio, this.rmf_LUT[i], this.rmf_LUT[j]);
}
generateRMF(originalPoints, curve, d1curve) {
// Start with the frenet frame just before t=0 and shift it to t=0
const first = this.getFrenetVectors(-0.001, originalPoints);
first.o = curve.get(0);
// Then we construct each next rotation-minimizing fame by reflecting
// the previous frame and correcting the resulting vectors.
const frames = [first];
for(let i=0, steps=24; i<steps; i++) {
const x0 = frames[i],
// get the next frame
t = (i+1)/steps,
x1 = {
o: curve.get(t),
dt: d1curve.get(t)
},
// then mirror the rotational axis and tangent
v1 = vec.minus(x1.o, x0.o),
c1 = vec.dot(v1, v1),
riL = vec.minus(x0.r, vec.scale(v1, 2/c1 * vec.dot(v1, x0.r))),
dtiL = vec.minus(x0.dt, vec.scale(v1, 2/c1 * vec.dot(v1, x0.dt))),
// then use those to compute a more stable rotational axis
v2 = vec.minus(x1.dt, dtiL),
c2 = vec.dot(v2, v2);
// Fix the axis of rotation vector...
x1.r = vec.minus(riL, vec.scale(v2, 2/c2 * vec.dot(v2, riL)));
// ... and then compute the normal as usual
x1.n = vec.cross(x1.r, x1.dt);
frames.push(x1);
}
return frames;
}
getFrenetVectors(t, originalPoints) {
const curve = new Bezier(this, originalPoints),
d1curve = new Bezier(this, curve.dpoints[0]),
dt = d1curve.get(t),
ddt = d1curve.derivative(t),
o = curve.get(t),
b = vec.normalize(vec.plus(dt, ddt)),
r = vec.normalize(vec.cross(b, dt)),
n = vec.normalize(vec.cross(r, dt));
return { o, dt, r, n };
}
lerpFrames(t, f1, f2) {
var frame = {};
[`o`, `dt`, `r`, `n`].forEach(type => frame[type] = vec.lerp(t, f1[type], f2[type]));
return frame;
}

View File

@@ -1,71 +1,70 @@
function normalize(v) {
let z = v.z || 0;
var d = Math.sqrt(v.x*v.x + v.y*v.y + z*z);
let r = { x:v.x/d, y:v.y/d };
if (v.z !== undefined) r.z = z/d;
return r;
let z = v.z || 0;
var d = Math.sqrt(v.x * v.x + v.y * v.y + z * z);
let r = { x: v.x / d, y: v.y / d };
if (v.z !== undefined) r.z = z / d;
return r;
}
function dot(v1, v2) {
let z1 = v1.z || 0;
let z2 = v2.z || 0;
return v1.x * v2.x + v1.y * v2.y + z1 * z2;
let z1 = v1.z || 0;
let z2 = v2.z || 0;
return v1.x * v2.x + v1.y * v2.y + z1 * z2;
}
function scale(v, s) {
let r = {
x: s * v.x,
y: s * v.y
}
if (v.z !== undefined) {
r.z = s * v.z
}
return r;
let r = {
x: s * v.x,
y: s * v.y,
};
if (v.z !== undefined) {
r.z = s * v.z;
}
return r;
}
function plus(v1, v2) {
let r = {
x: v1.x + v2.x,
y: v1.y + v2.y
};
if (v1.z !== undefined || v2.z !== undefined) {
r.z = (v1.z||0) + (v2.z||0);
};
return r;
let r = {
x: v1.x + v2.x,
y: v1.y + v2.y,
};
if (v1.z !== undefined || v2.z !== undefined) {
r.z = (v1.z || 0) + (v2.z || 0);
}
return r;
}
function minus(v1, v2) {
let r = {
x: v1.x - v2.x,
y: v1.y - v2.y
};
if (v1.z !== undefined || v2.z !== undefined) {
r.z = (v1.z||0) - (v2.z||0);
};
return r;
let r = {
x: v1.x - v2.x,
y: v1.y - v2.y,
};
if (v1.z !== undefined || v2.z !== undefined) {
r.z = (v1.z || 0) - (v2.z || 0);
}
return r;
}
function cross(v1, v2) {
if (v1.z === undefined || v2.z === undefined) {
throw new Error(`Cross product is not defined for 2D vectors.`);
}
return {
x: v1.y * v2.z - v1.z * v2.y,
y: v1.z * v2.x - v1.x * v2.z,
z: v1.x * v2.y - v1.y * v2.x
};
if (v1.z === undefined || v2.z === undefined) {
throw new Error(`Cross product is not defined for 2D vectors.`);
}
return {
x: v1.y * v2.z - v1.z * v2.y,
y: v1.z * v2.x - v1.x * v2.z,
z: v1.x * v2.y - v1.y * v2.x,
};
}
function lerp(t, v1, v2) {
let r = {
x: (1-t)*v1.x + t*v2.x,
y: (1-t)*v1.y + t*v2.y
};
if (v1.z !== undefined || v2.z !== undefined) {
r.z = (1-t)*(v1.z||0) + t*(v2.z||0);
};
return r;
let r = {
x: (1 - t) * v1.x + t * v2.x,
y: (1 - t) * v1.y + t * v2.y,
};
if (v1.z !== undefined || v2.z !== undefined) {
r.z = (1 - t) * (v1.z || 0) + t * (v2.z || 0);
}
return r;
}
export default { normalize, dot, scale, plus, minus, cross, lerp }
export default { normalize, dot, scale, plus, minus, cross, lerp };

View File

@@ -1,17 +1,19 @@
let points = [];
setup() {
const w = this.width,
h = this.height;
for (let i=0; i<10; i++) {
points.push({
x: w/2 + random(20) + cos(PI*2 * i/10) * (w/2 - 40),
y: h/2 + random(20) + sin(PI*2 * i/10) * (h/2 - 40)
});
}
setMovable(points);
this.bindButtons();
const w = this.width,
h = this.height;
// let's create a wiggle-circle by randomizing points on a circle
for (let i=0; i<10; i++) {
points.push({
x: w/2 + random(20) + cos(PI*2 * i/10) * (w/2 - 40),
y: h/2 + random(20) + sin(PI*2 * i/10) * (h/2 - 40)
});
}
setMovable(points);
this.bindButtons();
}
bindButtons() {
let rbutton = find(`.raise`);
@@ -27,103 +29,105 @@ draw() {
}
drawCurve() {
// we can't "just draw" this curve, since it'll be an arbitrary order,
// And the canvas only does 2nd and 3rd - we use de Casteljau's algorithm:
start();
noFill();
setStroke(`black`);
for(let t=0; t<=1; t+=0.01) {
let q = JSON.parse(JSON.stringify(points));
while(q.length > 1) {
for (let i=0; i<q.length-1; i++) {
q[i] = {
x: q[i].x + (q[i+1].x - q[i].x) * t,
y: q[i].y + (q[i+1].y - q[i].y) * t
};
}
q.splice(q.length-1, 1);
// we can't "just draw" this curve, since it'll be an arbitrary order,
// And the canvas only does 2nd and 3rd - we use de Casteljau's algorithm:
start();
noFill();
setStroke(`black`);
for(let t=0; t<=1; t+=0.01) {
let q = JSON.parse(JSON.stringify(points));
while(q.length > 1) {
for (let i=0; i<q.length-1; i++) {
q[i] = {
x: q[i].x + (q[i+1].x - q[i].x) * t,
y: q[i].y + (q[i+1].y - q[i].y) * t
};
}
vertex(q[0].x, q[0].y);
q.splice(q.length-1, 1);
}
end();
vertex(q[0].x, q[0].y);
}
end();
start();
setStroke(`lightgrey`);
points.forEach(p => vertex(p.x, p.y));
end();
start();
setStroke(`lightgrey`);
points.forEach(p => vertex(p.x, p.y));
end();
setStroke(`black`);
points.forEach(p => circle(p.x, p.y, 3));
setStroke(`black`);
points.forEach(p => circle(p.x, p.y, 3));
}
raise() {
const p = points,
const p = points,
np = [p[0]],
k = p.length;
for (let i = 1, pi, pim; i < k; i++) {
pi = p[i];
pim = p[i - 1];
np[i] = {
x: ((k - i) / k) * pi.x + (i / k) * pim.x,
y: ((k - i) / k) * pi.y + (i / k) * pim.y,
};
}
np[k] = p[k - 1];
points = np;
resetMovable(points);
redraw();
// raising the order of a curve is lossless:
for (let i = 1, pi, pim; i < k; i++) {
pi = p[i];
pim = p[i - 1];
np[i] = {
x: ((k - i) / k) * pi.x + (i / k) * pim.x,
y: ((k - i) / k) * pi.y + (i / k) * pim.y,
};
}
np[k] = p[k - 1];
points = np;
resetMovable(points);
redraw();
}
lower() {
// Based on https://www.sirver.net/blog/2011/08/23/degree-reduction-of-bezier-curves/
// Based on https://www.sirver.net/blog/2011/08/23/degree-reduction-of-bezier-curves/
// TODO: FIXME: this is the same code as in the old codebase,
// and it does something odd to the either the
// first or last point... it starts to travel
// A LOT more than it looks like it should... O_o
// TODO: FIXME: this is the same code as in the old codebase,
// and it does something odd to the either the
// first or last point... it starts to travel
// A LOT more than it looks like it should... O_o
const p = points,
k = p.length,
data = [],
n = k-1;
const p = points,
k = p.length,
data = [],
n = k-1;
if (k <= 3) return;
if (k <= 3) return;
// build M, which will be (k) rows by (k-1) columns
for(let i=0; i<k; i++) {
data[i] = (new Array(k - 1)).fill(0);
if(i===0) { data[i][0] = 1; }
else if(i===n) { data[i][i-1] = 1; }
else {
data[i][i-1] = i / k;
data[i][i] = 1 - data[i][i-1];
}
// build M, which will be (k) rows by (k-1) columns
for(let i=0; i<k; i++) {
data[i] = (new Array(k - 1)).fill(0);
if(i===0) { data[i][0] = 1; }
else if(i===n) { data[i][i-1] = 1; }
else {
data[i][i-1] = i / k;
data[i][i] = 1 - data[i][i-1];
}
}
// Apply our matrix operations:
const M = new Matrix(data);
const Mt = M.transpose(M);
const Mc = Mt.multiply(M);
const Mi = Mc.invert();
// Apply our matrix operations:
const M = new Matrix(data);
const Mt = M.transpose(M);
const Mc = Mt.multiply(M);
const Mi = Mc.invert();
if (!Mi) {
return console.error('MtM has no inverse?');
}
if (!Mi) {
return console.error('MtM has no inverse?');
}
// And then we map our k-order list of coordinates
// to an n-order list of coordinates, instead:
const V = Mi.multiply(Mt);
const x = new Matrix(points.map(p => [p.x]));
const nx = V.multiply(x);
const y = new Matrix(points.map(p => [p.y]));
const ny = V.multiply(y);
// And then we map our k-order list of coordinates
// to an n-order list of coordinates, instead:
const V = Mi.multiply(Mt);
const x = new Matrix(points.map(p => [p.x]));
const nx = V.multiply(x);
const y = new Matrix(points.map(p => [p.y]));
const ny = V.multiply(y);
points = nx.data.map((x,i) => ({
x: x[0],
y: ny.data[i][0]
}));
points = nx.data.map((x,i) => ({
x: x[0],
y: ny.data[i][0]
}));
resetMovable(points);
redraw();
resetMovable(points);
redraw();
}

View File

@@ -12,11 +12,13 @@ draw() {
curve.drawCurve();
curve.drawPoints();
// Similar to aligning, we transform the curve first
let translated = this.translatePoints(curve.points);
let rotated = this.rotatePoints(translated);
let rtcurve = new Bezier(this, rotated);
let extrema = rtcurve.extrema();
// and the we run the regular bounding box code
let minx = Number.MAX_SAFE_INTEGER,
miny = minx,
maxx = Number.MIN_SAFE_INTEGER,
@@ -37,6 +39,8 @@ draw() {
noFill();
setStroke(`#0F0`);
// But, crucially, we now need to reverse-transform the bbox corners:
let tx = curve.points[0].x;
let ty = curve.points[0].y;
let a = rotated[0].a;

View File

@@ -1,6 +1,7 @@
let curve;
setup() {
setPanelCount(2);
curve = new Bezier(this, 20, 250, 30, 20, 200, 250, 220, 20);
setMovable(curve.points);
setSlider(`.slide-control`, `position`, 0.5);
@@ -15,29 +16,27 @@ draw() {
let w = this.height;
let h = this.height;
let bbox = curve.bbox();
let x = bbox.x.min + (bbox.x.max - bbox.x.min) * this.position;
// construct an `x` value based on our slider position
let x = bbox.x.min + (bbox.x.max - bbox.x.min) * this.position;
if (bbox.x.min < x && x < bbox.x.max) {
setStroke("red");
line(x,0,x,h);
text(`x=${x | 0}`, x + 5, h - 30);
}
translate(w, 0);
// In the next panel, let's draw x = t(x)
nextPanel()
setStroke("black");
line(0,0,0,h);
// draw x = t(x)
line(0, h-20, w, h-20);
text('0', 10, h-10);
text('⅓', 10 + (w-10)/3, h-10);
text('⅔', 10 + 2*(w-10)/3, h-10);
text('1', w-10, h-10);
let p, s = { x: 0, y: h - curve.get(0).x };
for (let step = 0.02, t = step; t < 1 + step; t += step) {
p = {x: t * w, y: h - curve.get(t).x };
line(s.x, s.y, p.x, p.y);
@@ -48,6 +47,7 @@ draw() {
text("↑\nx", 10, h/2);
text("t →", w/2, h-10);
// and its "intercept" line
if (bbox.x.min < x && x < bbox.x.max) {
setStroke("red");
line(0, h-x, w, h-x);

View File

@@ -28,6 +28,7 @@ draw() {
// find our answer:
let y = round(curve.get(t).y);
// and draw everything
setStroke("red");
line(x, y, x, h);
line(x, y, 0, y);

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 35 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 39 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 53 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 127 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 62 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 39 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 36 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 39 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 76 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 29 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 32 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 29 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 32 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 32 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 67 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 29 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 42 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 30 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 53 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 29 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 39 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 32 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 59 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Some files were not shown because too many files have changed in this diff Show More