歯車について勉強する5 の履歴(No.4)

更新


工作/歯車について勉強する

歯車について勉強するシリーズ

概容

ウォームホイールの形状は単純なインボリュート曲線やトロコイド曲線では表現できず、 相手のウォームの形状を使って部材を切削するような計算を行わないと求められない。

その計算をやってみようという話。

なんとか計算できるようになりました

ウォームホイールの中心面内ではウォーム形状はラックと変わらないためウォームホイールの歯形は通常のインボリュート歯車と変わらないものが得られます。(下のシミュレーションで「厚さ変位」をゼロとした場合)

中心面から厚さ方向にずれた部分では、ウォームの形状は通常のラック形状とは異なるものになるため、それによって切削されるウォームホイールの歯形もインボリュート歯車とは異なるものになります。

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: 75 (from 2010/06/03), today: 3, yesterday: 3