歯車について勉強する5 の履歴(No.3)
更新歯車について勉強するシリーズ†
- 工作/歯車について勉強する 歯車の形状についての基礎
- 工作/歯車について勉強する2 仮想的なラックを使って実用的な歯車形状を切り出す話
- 工作/歯車について勉強する3 歯車に関するいろいろな計算機
- 工作/歯車について勉強する4 転位歯車の中心間距離と逆インボリュート関数
- 工作/歯車について勉強する5 ウォームホイールの歯形計算について
- 工作/歯車について勉強する/ゼネバ歯車 間欠歯車の一種です
- 工作/Fusion360歯車スクリプト 勉強した結果を使って作ってみました
- 工作/Fusion360歯車切削スクリプト Fusion 360 内で歯車の歯切りを行うスクリプトです
- 工作/Fusion360曲面生成スクリプト 歯切り結果の表面をきれいにするスクリプトです
- 工作/Fusion360ジョイント駆動スクリプト ジョイントを動かしてアニメーションさせるスクリプトです
概容†
ウォームホイールの形状は単純なインボリュート曲線やトロコイド曲線では表現できず、 相手のウォームの形状を使って部材を切削するような計算を行わないと求められない。
その計算をやってみようという話。
うまくいっているのかどうか?†
何かそれらしい歯形は出せるようになったのだけれど、 計算が正しいかどうかウォームと組み合わせて歯当たりを確認してみなきゃ。
ウォームホイールは中心面上の形状は通常のインボリュート歯車と変わらないのだけれど、 厚さ方向にシフトするとその歯形はインボリュート歯車からずれてくる。
下のシミュレーションにおける「厚さ変位」がそのパラメータ。 相手のウォームの半径や条数によっても形状が変わるので、そのあたりも指定できるようになっている。
LANG: p5js_live
// src/css-text.ts
function generateRandomId(length) {
let result = "";
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const charactersLength = characters.length;
let counter = 0;
while (counter < length) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
counter += 1;
}
return result;
}
var cssText = (id) => `
@media screen and (min-width: 640px) {
#${id} {
position: absolute;
left: 0;
top: 0;
opacity: 0.2;
}
#${id}:hover {
opacity: 0.7;
}
#${id} .control-slider {
padding-left: 10px;
}
}
@media screen and (max-width: 639px) {
#${id} .check-hide-tools {
display: none !important;
}
#${id} {
height: 30vh;
overflow-y: scroll;
line-height: 2;
}
#${id} > :last-child {
padding-bottom: 10vh;
}
#${id} .control-checkbox {
width:85px !important;
}
#${id} .control-slider input {
width: 200px !important;
}
}
#${id} {
display: block;
max-width: 500px
}
#${id} .control-slider {
display: block
}
#${id} .control-slider :first-child {
display: inline-block;
width: 80px
}
#${id} .control-slider :nth-child(2) {
display: inline-block;
width: 50px
}
#${id} .control-slider input {
display: inline-block;
width: 350px;
}
#${id} .control-checkbox {
display:inline-block;
width:110px;
overflow:hidden
}
`;
// src/initializeControls.ts
function initializeControls(parent, inputChanged = () => {
}) {
const id = generateRandomId(8);
const wrapper = elem("div", "", [elem("style", cssText(id))], { id });
parent.appendChild(wrapper);
const wrapControls = elem("div", "", [], {});
const checkHide = createCheckbox(wrapper, "ツールを非表示", () => {
wrapControls.style.display = checkHide.checked ? "none" : "block";
});
checkHide.parentElement.classList.add("check-hide-tools");
wrapper.appendChild(wrapControls);
const controls = {
m: createSlider(
wrapControls,
"モジュール",
{ min: 10, max: 800, value: 200, step: 1 },
inputChanged
),
alpha: createSlider(
wrapControls,
"圧力角",
{ min: 10, max: 32, value: 20, step: 1 },
inputChanged
),
z: createSlider(
wrapControls,
"歯数",
{ min: 4, max: 200, value: 12, step: 1 },
inputChanged
),
shift: createSlider(
wrapControls,
"転位",
{ min: -1, max: 1, value: 0, step: 0.01 },
inputChanged
),
backlash: createSlider(
wrapControls,
"バックラッシュ",
{ min: -5, max: 5, value: 0, step: 0.02 },
inputChanged
),
rw: createSlider(
wrapControls,
"ウォーム半径",
{ min: 7, max: 20, value: 7.5, step: 0.25 },
inputChanged
),
nw: createSlider(
wrapControls,
"条数",
{ min: 1, max: 6, value: 1, step: 1 },
inputChanged
),
t: createSlider(
wrapControls,
"厚さ変位",
{ min: -3, max: 3, value: -0.13, step: 0.01 },
inputChanged
),
n: createSlider(
wrapControls,
"選択",
{ min: -100, max: 100, value: 0, step: 1 },
inputChanged
),
ox: createSlider(
wrapControls,
"原点X",
{ min: -2, max: 2, value: -0, step: 0.01 },
inputChanged
),
oy: createSlider(
wrapControls,
"原点Y",
{ min: -4, max: 4, value: 0, step: 0.01 },
inputChanged
)
};
return {
get m() {
return Number(controls.m.value);
},
get z() {
return Number(controls.z.value);
},
get shift() {
return Number(controls.shift.value);
},
get alpha() {
return Number(controls.alpha.value) / 180 * Math.PI;
},
get fillet() {
return 0.4;
},
get mk() {
return 1;
},
get mf() {
return 1.25;
},
get backlash() {
return Number(controls.backlash.value) * Math.PI * this.m / 100;
},
get t() {
return Number(controls.t.value);
},
get nw() {
return Number(controls.nw.value);
},
get rw() {
return Number(controls.rw.value);
},
get n() {
return Number(controls.n.value);
},
get ox() {
return Number(controls.ox.value);
},
get oy() {
return Number(controls.oy.value);
}
};
}
function elem(tag, html = "", children = [], props = {}, events = {}) {
const result = document.createElement(tag);
if (html != "") result.innerHTML = html;
children.forEach((c) => result.appendChild(c));
Object.entries(props).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0).forEach(([k, v]) => {
if (k == "class") {
result.className = String(v);
} else if (typeof v == "object") {
Object.entries(v).forEach(([k2, v2]) => result[k][k2] = v2);
} else {
result[k] = v;
}
});
const notIsArray = (maybeArray) => {
return !Array.isArray(maybeArray);
};
Object.entries(events).forEach(([k, v]) => {
if (notIsArray(v)) v = [v];
v.forEach((f) => result.addEventListener(k, f));
});
return result;
}
function createSlider(parent, label, option, oninput) {
const value = elem("span", String(option.value));
const slider = elem(
"input",
"",
[],
{ type: "range", ...option },
{ input: () => slider.doInput() }
);
slider.doInput = () => {
value.innerHTML = slider.value;
oninput();
};
const wrapper = elem("div", "", [elem("span", label), value, slider], {
class: "control-slider"
});
parent.appendChild(wrapper);
return slider;
}
function createCheckbox(parent, label, oninput, checked = false) {
const check = elem(
"input",
"",
[],
{ type: "checkbox", checked },
{ input: (e) => check.doInput(e) }
);
check.doInput = oninput;
const wrapper = elem("label", "", [check, elem("span", label)], {
class: "control-checkbox"
});
parent.appendChild(wrapper);
return check;
}
// src/vector.ts
var Vector = class _Vector {
x;
y;
constructor(x, y) {
this.x = x;
this.y = y;
}
static polar(r, theta) {
return new _Vector(r * Math.cos(theta), r * Math.sin(theta));
}
angle() {
return Math.atan2(this.y, this.x);
}
equal(v) {
const epsilon = 1e-6;
return Math.abs(this.x - v.x) < epsilon && Math.abs(this.y - v.y) < epsilon;
}
add(v) {
return new _Vector(this.x + v.x, this.y + v.y);
}
sub(v) {
return new _Vector(this.x - v.x, this.y - v.y);
}
mul(scalar) {
return new _Vector(this.x * scalar, this.y * scalar);
}
rotate(angle, origin) {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
if (origin) return this.sub(origin).rotate(angle).add(origin);
return new _Vector(this.x * cos - this.y * sin, this.x * sin + this.y * cos);
}
addRotated(angle, v) {
return this.add(v.rotate(angle));
}
flipY() {
return new _Vector(this.x, -this.y);
}
flipX() {
return new _Vector(-this.x, this.y);
}
norm() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
normalize(length = 1) {
const len = this.norm() / length;
return new _Vector(this.x / len, this.y / len);
}
polar() {
return [this.norm(), this.angle()];
}
inner(v) {
return this.x * v.x + this.y * v.y;
}
outer(v) {
return this.x * v.y - this.y * v.x;
}
};
function vec(x, y) {
return new Vector(x, y);
}
function radiusFrom3Points(p1, p2, p3) {
const a = p1.sub(p2).norm();
const b = p2.sub(p3).norm();
const c = p3.sub(p1).norm();
const s = (a + b + c) / 2;
const area = Math.sqrt(s * (s - a) * (s - b) * (s - c));
return a * b * c / (4 * area);
}
// src/rack.ts
function rackCurves(params) {
const { m, z, alpha, shift, fillet, mf, mk, backlash } = params;
const rp = z * m / 2;
const { sin, tan, min, PI: PI2 } = Math;
const rc = 0.25;
const r1 = vec(rp + (shift + mk) * m, mk * m * tan(alpha) + backlash / 2);
const r2 = vec(rp + (shift - (mf - rc)) * m, -(mf - rc) * m * tan(alpha) + backlash / 2);
const r3 = vec(rp + (shift - mf) * m, -mf * m * tan(alpha) + backlash / 2);
const fillet1 = rc * m / (1 - sin(alpha));
let fr = min(fillet * m, fillet1);
let c = r3.add(vec(fr, -fr / tan((PI2 / 2 + alpha) / 2)));
if (c.y < -PI2 * m / 4) {
c = c.add(r3.sub(c).mul((-PI2 * m / 4 - c.y) / (r3.y - c.y)));
fr = c.x - r3.x;
}
return {
r1,
r2,
filletCenter: c,
filletRadius: fr
};
}
// src/segment.ts
var Curve = class _Curve {
points = [];
_translated;
_translation = [1, 0, 0, 0, 1, 0];
set translation(t) {
this._translated = void 0;
this._translation = [...t];
}
get translation() {
return this._translation;
}
param2point;
param2translated(param) {
const p2 = this.param2point(param);
return this.applyTranslation(p2);
}
rotate(angle, origin) {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
const [a, b, c, d, e, f] = this.translation;
const [ox, oy] = origin ? [origin.x, origin.y] : [0, 0];
this.translation = [
cos * a - sin * d,
cos * b - sin * e,
cos * (c - ox) - sin * (f - oy) + ox,
sin * a + cos * d,
sin * b + cos * e,
sin * (c - ox) + cos * (f - oy) + oy
];
}
translate(v) {
const [a, b, c, d, e, f] = this.translation;
this.translation = [a, b, c + v.x, d, e, f + v.y];
}
duplicate() {
const c = new _Curve();
c.points = [...this.points];
c.translation = [...this.translation];
c.param2point = this.param2point;
return c;
}
applyTranslation(p2) {
const [a, b, c, d, e, f] = this.translation;
return new Vector(a * p2.x + b * p2.y + c, d * p2.x + e * p2.y + f);
}
/// returns array of translated points
translated() {
if (!this._translated) {
this._translated = this.points.map(({ point }) => this.applyTranslation(point));
}
return this._translated;
}
draw(p2, o = new Vector(0, 0), mark = 0) {
p2.beginShape();
this.translated().forEach(({ x, y }) => {
const [px, py] = [o.x + x, o.y - y];
if (mark) p2.circle(px, py, mark);
p2.vertex(px, py);
});
p2.endShape();
}
crossPoints(c) {
const points = [];
const c1 = this.translated();
const c2 = c.translated();
for (let i = 0; i < c1.length - 1; i++) {
for (let j = 0; j < c2.length - 1; j++) {
const p2 = crossPoint(c1[i], c1[i + 1], c2[j], c2[j + 1]);
if (typeof p2 != "undefined") {
const [s, t] = p2;
points.push([
this.points[i].param * (1 - s) + this.points[i + 1].param * s,
c.points[j].param * (1 - t) + c.points[j + 1].param * t
]);
}
}
}
return points;
}
};
function crossPoint(p1, p2, q1, q2) {
const a = p2.sub(p1);
const b = q2.sub(q1);
const c = q1.sub(p1);
const d = a.outer(b);
if (d == 0) return;
const s = c.outer(a) / d;
const t = c.outer(b) / d;
if (s < 0 || s > 1 || t < 0 || t > 1) return;
return [s, t];
}
var Segment = class {
};
var Line = class _Line extends Segment {
constructor(start, end) {
super();
this.start = start;
this.end = end;
this._tangent = end.sub(start).normalize();
this._length = start.sub(end).norm();
}
_length;
_tangent;
get length() {
return this._length;
}
pointAt(t) {
return this.start.add(this._tangent.mul(t));
}
tangentAt() {
return this._tangent;
}
draw(p2, o) {
p2.line(o.x + this.start.x, o.y - this.start.y, o.x + this.end.x, o.y - this.end.y);
}
translate(v) {
return new _Line(this.start.add(v), this.end.add(v));
}
rotate(angle, origin) {
return new _Line(this.start.rotate(angle, origin), this.end.rotate(angle, origin));
}
};
var Arc = class _Arc extends Segment {
constructor(center, radius, start_angle, end_angle) {
super();
this.center = center;
this.radius = radius;
this.start_angle = start_angle;
this.end_angle = end_angle;
this._start = center.add(Vector.polar(radius, start_angle));
this._end = center.add(Vector.polar(radius, end_angle));
this._length = Math.abs(end_angle - start_angle) * radius;
}
_length;
_start;
_end;
get start() {
return this._start;
}
get end() {
return this._end;
}
get length() {
return this._length;
}
pointAt(t) {
return this.center.add(
Vector.polar(
this.radius,
this.start_angle + (this.end_angle - this.start_angle) * t / this.length
)
);
}
tangentAt(t) {
return Vector.polar(this.radius, this.start_angle + t / this.radius + Math.PI / 2);
}
draw(p2, o) {
p2.noFill();
p2.arc(
o.x + this.center.x,
o.y - this.center.y,
this.radius * 2,
this.radius * 2,
-this.end_angle,
-this.start_angle
);
}
translate(v) {
return new _Arc(this.center.add(v), this.radius, this.start_angle, this.end_angle);
}
rotate(angle, origin) {
return new _Arc(
this.center.rotate(angle, origin),
this.radius,
this.start_angle + angle,
this.end_angle + angle
);
}
};
var Segments = class _Segments extends Segment {
_segments;
_length;
constructor(...segments) {
super();
this._segments = segments;
this._length = segments.reduce((sum, seg) => sum + seg.length, 0);
}
get length() {
return this._length;
}
get start() {
return this._segments[0].start;
}
get end() {
return this._segments[this._segments.length - 1].end;
}
pointAt(t) {
for (let i = 0; i < this._segments.length; i++) {
if (t < this._segments[i].length) {
return this._segments[i].pointAt(t);
}
t -= this._segments[i].length;
}
return this.end;
}
tangentAt(t) {
for (let i = 0; i < this._segments.length; i++) {
if (t < this._segments[i].length) {
return this._segments[i].tangentAt(t);
}
t -= this._segments[i].length;
}
return this._segments[this._segments.length - 1].tangentAt(
this._segments[this._segments.length - 1].length
);
}
draw(p2, offset) {
this._segments.forEach((seg) => seg.draw(p2, offset));
}
translate(v) {
return new _Segments(...this._segments.map((seg) => seg.translate(v)));
}
rotate(angle, origin) {
return new _Segments(...this._segments.map((seg) => seg.rotate(angle, origin)));
}
to_curve(n, translation) {
const curve = new Curve();
if (translation) {
curve.param2point = (param) => translation(this.pointAt(param));
} else {
curve.param2point = (param) => this.pointAt(param);
}
translation ??= (v) => v;
const delta = this.length / n;
let len = 0;
this._segments.forEach((seg, i) => {
let nPoints = Math.ceil(seg.length / delta);
if (seg instanceof Arc) {
nPoints = Math.ceil(Math.max(nPoints, seg.length / seg.radius / (2 * Math.PI) * 36));
}
for (let j = i == 0 ? 0 : 1; j <= nPoints; j++) {
const t = seg.length * j / nPoints;
curve.points.push({ point: translation(seg.pointAt(t)), param: len + t });
}
len += seg.length;
});
return curve;
}
};
// src/spline.ts
var LengthMismatchException = class extends Error {
};
function interpolate(xs, ys) {
if (xs.length != ys.length) throw new LengthMismatchException();
if (xs.length < 5) {
return (x) => {
if (x <= xs[0]) return ys[0];
for (let i = 1; i < xs.length; i++) {
if (x <= xs[i]) {
return ys[i - 1] + (ys[i] - ys[i - 1]) * (x - xs[i - 1]) / (xs[i] - xs[i - 1]);
}
}
return ys[xs.length - 1];
};
}
xs = xs.slice(0);
const slopes = calcSlopes(xs, ys);
const coefs = calcCoefs(xs, ys, slopes);
return (x) => {
let i = findSegment(x, xs);
i = Math.max(0, Math.min(i, coefs.length - 1));
return polynomial(x - xs[i], coefs[i]);
};
}
function findSegment(x, xs) {
let l = 0;
let r = xs.length - 1;
while (l <= r) {
const m = l + r >>> 1;
const mx = xs[m];
if (mx < x) {
l = m + 1;
} else if (mx > x) {
r = m - 1;
} else if (mx == x) {
return m;
} else {
throw new Error("NaN found in xs.");
}
}
return l - 1;
}
function polynomial(x, coefs) {
return coefs.reduce((acc, coef) => acc * x + coef, 0);
}
function calcSlopes(xs, ys) {
const n = xs.length;
const dydx = new Array(n - 1);
for (let i = 0; i < dydx.length; i++) {
dydx[i] = (ys[i + 1] - ys[i]) / (xs[i + 1] - xs[i]);
}
const weights = new Array(n - 1);
for (let i = 1; i < weights.length; i++) {
weights[i] = Math.abs(dydx[i] - dydx[i - 1]);
}
const result = new Array(n);
result[0] = slopeFrom3Points(xs, ys, 0, 0, 1, 2);
result[1] = slopeFrom3Points(xs, ys, 1, 0, 1, 2);
for (let i = 2; i < n - 2; i++) {
const wp1 = weights[i + 1];
const wm1 = weights[i - 1];
if (Math.abs(wp1) < Number.EPSILON && Math.abs(wm1) < Number.EPSILON) {
const dx = xs[i + 1] - xs[i];
const dxm1 = xs[i] - xs[i - 1];
result[i] = (dx * dydx[i - 1] + dxm1 * dydx[i]) / (dx + dxm1);
} else {
result[i] = (wp1 * dydx[i - 1] + wm1 * dydx[i]) / (wp1 + wm1);
}
}
result[n - 2] = slopeFrom3Points(xs, ys, n - 2, n - 3, n - 2, n - 1);
result[n - 1] = slopeFrom3Points(xs, ys, n - 1, n - 3, n - 2, n - 1);
return result;
}
function slopeFrom3Points(xs, ys, i, i1, i2, i3) {
const dx = xs[i] - xs[i1];
const dx2 = xs[i2] - xs[i1];
const dx3 = xs[i3] - xs[i1];
const dydx2 = (ys[i2] - ys[i1]) / dx2;
const dydx3 = (ys[i3] - ys[i1]) / dx3;
return (dydx3 - dydx2) * (2 * dx - dx2) / (dx3 - dx2) + dydx2;
}
function calcCoefs(xs, ys, slopes) {
const n = xs.length;
const coefs = [];
for (let i = 0; i < n - 1; i++) {
const dx = xs[i + 1] - xs[i];
const dydx = (ys[i + 1] - ys[i]) / dx;
const dfi = slopes[i];
const dfi1 = slopes[i + 1];
coefs.push([
(-2 * dydx + dfi + dfi1) / (dx * dx),
// 3rd
(3 * dydx - 2 * dfi - dfi1) / dx,
// 2nd
dfi,
// 1st
ys[i]
// 0
]);
}
return coefs;
}
// src/function.ts
function minimize(l, r, f, epsilon = 1e-6) {
const phi = (1 + Math.sqrt(5)) / 2;
let m1 = r - (r - l) / phi;
let m2 = l + (r - l) / phi;
let f1 = f(m1);
let f2 = f(m2);
while (Math.abs(l - r) > epsilon) {
if (f1 < f2) {
r = m2;
m2 = m1;
f2 = f1;
m1 = r - (r - l) / phi;
f1 = f(m1);
} else {
l = m1;
m1 = m2;
f1 = f2;
m2 = l + (r - l) / phi;
f2 = f(m2);
}
}
return (l + r) / 2;
}
function root(l, r, f, epsilon = 1e-6) {
const fl = f(l);
const fr = f(r);
if (fl * fr > 0) {
return Math.abs(fl) < Math.abs(fr) ? l : r;
}
if (fl > fr) [l, r] = [r, l];
while (Math.abs(l - r) > epsilon) {
const m = (l + r) / 2;
if (f(m) < 0) {
l = m;
} else {
r = m;
}
}
return (l + r) / 2;
}
// src/main.ts
var { PI } = Math;
var sketch = (p2) => {
let params;
const canvas = {
width: 800,
height: 800,
ox: 0,
oy: 0
};
p2.setup = () => {
const { width, height } = canvas;
canvas.ox = width / 2;
canvas.oy = height * 2 / 3;
const c = p2.createCanvas(width, height);
c.style("max-width", "100%");
c.style("height", "auto");
const wrapper = c.elt.parentElement;
params = initializeControls(wrapper, inputChanged);
inputChanged();
};
const inputChanged = () => {
const { ox, oy, width, height } = canvas;
const rp = params.m * params.z / 2;
const rk = rp + params.m * params.mk;
const eps = params.m / 1e4;
const o = new Vector(ox + params.ox * params.m, oy - params.oy * params.m);
o.x -= rp;
const initializeCanvas = () => {
p2.fill(220);
p2.rect(0, 0, width, height);
p2.noFill();
p2.strokeWeight(1);
p2.stroke(64, 128, 64);
p2.drawingContext.setLineDash([10, 10]);
p2.line(o.x - width - params.ox + rp, o.y, o.x + width - params.ox + rp, o.y);
p2.line(o.x, o.y - height, o.x, o.y + height);
p2.circle(o.x, o.y, rk * 2);
p2.drawingContext.setLineDash([1]);
};
initializeCanvas();
const generateRackGeometry = (params2) => {
const {
r1: r1_,
filletCenter: center_,
filletRadius: radius
} = rackCurves({ ...params2, mk: -params2.shift + 1.1 });
const r1 = r1_.add(vec(0, PI * params2.m / 4));
const center = center_.add(vec(0, PI * params2.m / 4));
const r3 = center.add(Vector.polar(radius, params2.alpha + PI / 2));
const r4 = center.add(Vector.polar(radius, PI));
if (Math.abs(center.y) < eps) {
return new Segments(
new Line(r1, r3),
new Arc(center, radius, params2.alpha + PI / 2, 2 * PI - (params2.alpha + PI / 2)),
new Line(r3.flipY(), r1.flipY())
);
}
return new Segments(
// 歯先に中心が来ないときは2つの円弧と直線
new Line(r1, r3),
new Arc(center, radius, params2.alpha + PI / 2, PI),
new Line(r4, r4.flipY()),
new Arc(center.flipY(), radius, PI, 2 * PI - (params2.alpha + PI / 2)),
new Line(r3.flipY(), r1.flipY())
);
};
const rackGeometry = generateRackGeometry(params);
const calcWormCurve = (nPoints) => {
const wormAxisX = (params.z / 2 + params.shift + params.rw) * params.m;
const translate = (v) => {
const t = Math.asin(params.t * params.m / (v.x - wormAxisX));
return vec(wormAxisX + (v.x - wormAxisX) * Math.cos(t), v.y + params.nw * rp * t);
};
return rackGeometry.to_curve(nPoints, translate);
};
const wormCurve = calcWormCurve(100);
const generateTranslatedCurves = () => {
const calcTranslated = (t) => {
const c = wormCurve.duplicate();
c.translate(vec(0, t * rp));
c.rotate(-t);
const translated = c.translated();
const ts = c.points.map((cp) => cp.param);
const xs = translated.map((cp) => cp.x);
const ys = translated.map((cp) => cp.y);
const splineX = interpolate(ts, xs);
const splineY = interpolate(ts, ys);
const spline = (t2) => vec(splineX(t2), splineY(t2));
const length = c.points.at(-1).param;
const closest = minimize(0, length, (t2) => spline(t2).norm(), eps);
const closestP = spline(closest);
return { t, curve: c, spline, length, closest, closestP };
};
const curves2 = [];
const dt = PI / params.z / 2;
curves2.push(calcTranslated(0));
curves2.push(calcTranslated(dt));
while (curves2[0].closestP.norm() < curves2[1].closestP.norm())
curves2.unshift(calcTranslated(curves2[0].t - dt));
while (curves2[0].closestP.norm() < rk) curves2.unshift(calcTranslated(curves2[0].t - dt));
while (curves2.at(-1).closestP.norm() < curves2.at(-2).closestP.norm())
curves2.push(calcTranslated(curves2.at(-1).t + dt));
while (curves2.at(-1).closestP.norm() < rk)
curves2.push(calcTranslated(curves2.at(-1).t + dt));
for (let i2 = 1; i2 < curves2.length - 1; i2++) {
const p1 = curves2[i2 - 1].spline(curves2[i2 - 1].length / 2);
const p22 = curves2[i2].spline(curves2[i2].length / 2);
const p3 = curves2[i2 + 1].spline(curves2[i2 + 1].length / 2);
const v1 = p22.sub(p1);
const v2 = p3.sub(p22);
const r = radiusFrom3Points(p1, p22, p3);
if (v1.norm() > params.m / 1e3 && v1.norm() > r / 10) {
const c1 = calcTranslated((curves2[i2].t + curves2[i2 - 1].t) / 2);
curves2.splice(i2, 0, c1);
i2--;
} else if (v2.norm() > params.m / 1e3 && v2.norm() > r / 10) {
const c2 = calcTranslated((curves2[i2].t + curves2[i2 + 1].t) / 2);
curves2.splice(i2 + 1, 0, c2);
i2--;
}
}
return curves2;
};
const curves = generateTranslatedCurves();
const rf = curves.reduce(
(rf2, { closestP }) => closestP.norm() < rf2 ? closestP.norm() : rf2,
rk
);
const findIntersections = (initialDivision) => {
const intersect = (r) => {
const a1 = curves.reduce((a, { spline, closest, closestP }) => {
if (closestP.norm() > r) return a;
const t = root(0, closest, (t2) => spline(t2).norm() - r, eps);
return Math.max(a, spline(t).angle());
}, -Number.MAX_VALUE);
const a2 = curves.reduce((a, { spline, closest, closestP, length }) => {
if (closestP.norm() > r) return a;
const t = root(closest, length, (t2) => spline(t2).norm() - r, eps);
return Math.min(a, spline(t).angle());
}, Number.MAX_VALUE);
return { r, a1, a2 };
};
const intersections2 = [];
for (let i2 = 1; i2 <= initialDivision + 1; i2++) {
const r = rf + (rk - rf) * i2 / initialDivision;
intersections2.push(intersect(r));
}
intersections2.unshift(intersect(rf + (rk - rf) / (initialDivision * 2)));
intersections2.unshift(intersect(rf + (rk - rf) / (initialDivision * 8)));
intersections2.unshift(intersect(rf + (rk - rf) / (initialDivision * 128)));
for (let i2 = 1; i2 < intersections2.length - 1; i2++) {
const { r: r1, a1, a2: b1 } = intersections2[i2 - 1];
const { r: r2, a1: a2, a2: b2 } = intersections2[i2];
const { r: r3, a1: a3, a2: b3 } = intersections2[i2 + 1];
const p1 = Vector.polar(r1, a1);
const p22 = Vector.polar(r2, a2);
const p3 = Vector.polar(r3, a3);
const v1 = p22.sub(p1);
const v2 = p3.sub(p22);
const rr1 = radiusFrom3Points(p1, p22, p3);
const m = 40;
const mm = 5;
if (v1.norm() > params.m / m && v1.norm() > rr1 / mm) {
intersections2.splice(i2, 0, intersect((r1 + r2) / 2));
i2--;
continue;
}
if (v2.norm() > params.m / m && v2.norm() > rr1 / mm) {
intersections2.splice(i2 + 1, 0, intersect((r2 + r3) / 2));
i2--;
continue;
}
const q1 = Vector.polar(r1, b1);
const q2 = Vector.polar(r2, b2);
const q3 = Vector.polar(r3, b3);
const w1 = q2.sub(q1);
const w2 = q3.sub(q2);
const rr2 = radiusFrom3Points(q1, q2, q3);
if (w1.norm() > params.m / m && w1.norm() > rr2 / mm) {
intersections2.splice(i2, 0, intersect((r1 + r2) / 2));
i2--;
continue;
}
if (w2.norm() > params.m / m && w2.norm() > rr2 / mm) {
intersections2.splice(i2 + 1, 0, intersect((r2 + r3) / 2));
i2--;
continue;
}
}
return intersections2;
};
const intersections = findIntersections(16);
p2.stroke(160);
p2.strokeWeight(1);
curves.forEach(({ curve }) => {
curve.draw(p2, o);
});
p2.stroke(127, 0, 127);
p2.strokeWeight(1);
intersections.forEach(({ r, a1, a2 }) => {
const p1 = Vector.polar(r, a1);
const p22 = Vector.polar(r, a2);
p2.circle(p1.x + o.x, -p1.y + o.y, 5);
p2.circle(p22.x + o.x, -p22.y + o.y, 5);
});
p2.beginShape();
intersections.toReversed().forEach(({ r, a1 }) => {
p2.curveVertex(r * Math.cos(a1) + o.x, -r * Math.sin(a1) + o.y);
});
const q = vec(
rf * Math.cos((intersections[0].a1 + intersections[0].a2) / 2),
rf * Math.sin((intersections[0].a1 + intersections[0].a2) / 2)
);
p2.curveVertex(q.x + o.x, -q.y + o.y);
p2.circle(q.x + o.x, -q.y + o.y, 5);
intersections.forEach(({ r, a2 }) => {
p2.curveVertex(r * Math.cos(a2) + o.x, -r * Math.sin(a2) + o.y);
});
p2.endShape();
const findFixedPoints = ({ t, curve, spline }) => {
const tangents = [];
for (let i2 = 1; i2 < curve.points.length - 1; i2++) {
const dt1 = curve.points[i2 + 1].param - curve.points[i2].param;
const dt2 = -(curve.points[i2].param - curve.points[i2 - 1].param);
tangents.push(
spline(curve.points[i2].param + dt1).sub(spline(curve.points[i2].param + dt2)).normalize()
);
}
const rpSinT = rp * Math.sin(t);
const rpCosT = rp * Math.cos(t);
const angles = [];
const translated = curve.translated();
for (let i2 = 1; i2 < translated.length - 1; i2++) {
const { x, y } = translated[i2];
const dr = vec(-y - rpSinT, x - rpCosT).normalize();
angles.push(tangents[i2 - 1].outer(dr));
}
const fixedPoints = [];
for (let i2 = 0; i2 < angles.length - 1; i2++) {
if (angles[i2] * angles[i2 + 1] < 0) {
const a = angles[i2] / (angles[i2] - angles[i2 + 1]);
const pp = spline(curve.points[i2].param * (1 - a) + curve.points[i2 + 1].param * a);
fixedPoints.push(pp);
}
}
return fixedPoints;
};
const i = params.n + Math.round(curves.length / 2);
if (i > 0 && i < curves.length) {
p2.stroke(255, 255, 127);
p2.strokeWeight(2);
curves[i].curve.draw(p2, o);
const points = findFixedPoints(curves[i]);
points.forEach(({ x, y }) => p2.circle(x + o.x, -y + o.y, 5));
p2.stroke(0);
p2.strokeWeight(1);
}
return;
};
};
if (true) {
sketch(p);
} else {
new p5(sketch, window["p5wrapper"]);
}
計算の詳細†
後で追加する。
Counter: 448 (from 2010/06/03),
today: 4,
yesterday: 3