Анимация Кривошипно-шатунного механизма

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
  <svg version="1.1"
    baseProfile="full"
    xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink"
    xmlns:ev="http://www.w3.org/2001/xml-events"
    width="1100"
    height="600">

  <title>  Slider-Crank </title>

  <defs>
    <rect
                id="Slidebar"
      stroke-width="1"
            stroke="black"
              fill="silver"
      fill-opacity="1"
                 x="0"
                 y="-12"
             width="300"
            height="24"
    />
    <g id="Crosshead" stroke-width="1" stroke="black" fill-opacity="1">
      <rect
           fill="gold"
              x="-50"
              y="-25"
          width="100"
         height="50"
      />
      <circle cx="0" cy="0" r="15" fill="white"/>
    </g>
    <g id="Crank" stroke-width="1" stroke="black" fill-opacity="1">
      <path fill="silver"
             d="M -99.959 40.000
                A  40  40 0 0 1 -99.959, -40.000
                A 450 450 0 0 0  -9.950, -49.000
                A  50  50 0 1 1  -9.950,  49.000
                A 450 450 0 0 0 -99.959,  40.000
                z"/>
      <circle cx="-100" cy="0" r="25" fill="white"/>
      <circle cx="0"    cy="0" r="30" fill="lightgrey"/>
    </g>
    <g id="ConRod" stroke-width="1" stroke="black" fill-opacity="0.7">
      <path fill="silver"
             d="M  12.387  21.715
                A 30 30 0 0 1  27.551  17.776
                L 453.475  22.035
                A 30 30 0 0 1 473.243  29.733
                A 40 40 0 0 1 473.243 -29.733
                A 30 30 0 0 1 453.475 -22.035
                L  27.551 -17.776
                A 30 30 0 0 1 12.387  -21.715
                A 25 25 0 0 1 12.387   21.715
                z"/>
      <circle cx="0"   cy="0" r="25" fill="silver"/>
      <circle cx="0"   cy="0" r="15" fill="white"/>
      <circle cx="500" cy="0" r="40" fill="silver"/>
      <circle cx="500" cy="0" r="25" fill="white"/>
    </g>

  </defs>

  <use xlink:href="#Slidebar"  x="150" y="263"/>
  <use xlink:href="#Slidebar"  x="150" y="337"/>
  <use xlink:href="#Crosshead" x="200" y="300">
    <animate
      attributeName="x"
            dur="3s"
            values="  200.000;
                      207.202;
                      227.545;
                      257.557;
                      292.430;
                      327.159;
                      357.557;
                      380.753;
                      395.140;
                      400.000;
                      395.140;
                      380.753;
                      357.557;
                      327.159;
                      292.430;
                      257.557;
                      227.545;
                      207.202;
                      200.000"
        repeatCount="indefinite"/>
  </use>

  <use xlink:href="#Crank" x="800" y="300">
    <animateTransform
      attributeName="transform" type="rotate" from="0,800,300"
         by="360" dur="3s" repeatCount="indefinite"/>
  </use>
  <use xlink:href="#ConRod" x="200" y="300">
    <animate
      attributeName="x"
            dur="3s"
            values="  200.000;
                      207.202;
                      227.545;
                      257.557;
                      292.430;
                      327.159;
                      357.557;
                      380.753;
                      395.140;
                      400.000;
                      395.140;
                      380.753;
                      357.557;
                      327.159;
                      292.430;
                      257.557;
                      227.545;
                      207.202;
                      200.000"

        repeatCount="indefinite"/>
    <animateTransform
      attributeName="transform" type="rotate" from="0,200,300"
         repeatCount="indefinite" dur="3s"
         values="  0.000, 200.000, 300;
                  -3.922, 207.202, 300;
                  -7.386, 227.545, 300;
                  -9.974, 257.557, 300;
                 -11.359, 292.430, 300;
                 -11.359, 327.159, 300;
                  -9.974, 357.557, 300;
                  -7.386, 380.753, 300;
                  -3.922, 395.140, 300;
                   0.000, 400.000, 300;
                   3.922, 395.140, 300;
                   7.386, 380.753, 300;
                   9.974, 357.557, 300;
                  11.359, 327.159, 300;
                  11.359, 292.430, 300;
                   9.974, 257.557, 300;
                   7.386, 227.545, 300;
                   3.922, 207.202, 300;
                   0.000, 200.000, 300"/>
  </use>

</svg>

Я пытаюсь воспроизвести эту анимацию движения кривошипного механизма с помощью svg и javascript.

Ползунку нужна анимация перемещения, а кривошипу - анимация вращения, но как я могу получить анимацию шатуна? В svg есть значения, заданные для animate и animateTransform, но есть ли уравнение, которое позволяет нам воспроизвести это?

Примечание переводчика:

То есть нужно универсальное решение, которое позволит при разных входных данных: размеры кривошипа, шатуна и длины хода ползунка реализовать анимацию всего кривошипно-шатунного механизма.
Есть ли у кого-нибудь идеи?

Свободный перевод вопроса crank motion with svg от участника @sonia maklouf.


Ответы (2 шт):

Автор решения: barsik34566

введите сюда описание изображения

Координаты B можно через тригонометрию Параметрическое уравнение окружности

x= cos(ang_rad)*radius

y= sin(ang_rad)*radius

Координаты C можно погуглить «пересечение окружности и прямой” пересечение окружности радиуса BC и прямой CA. Пересечений будет 2 выбрать где меньший X.

→ Ссылка
Автор решения: Leonid

Если без детальной прорисовки, то можно так. Меняем угол вращения колена кривошипа (синяя линия), тем самым получаем точку соединения c шатуном (зеленая). Исходя из длины шатуна находим точку соединения с ползуном (зеленый круг), который движется по постоянному пути (желтая линия).

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

const w = canvas.width = 600;
const h = canvas.height = 180;

{
    const center = [w*2/3,h/2];
    const joint_radius = 10;
    const crank_len = 80;
    const rotation_speed = Math.PI/180;
    const conrod_len = 200;
    
    let angle = 0;
    ctx.lineWidth = 5;
    
    draw();

    function draw(){
        ctx.clearRect(0,0,w,h);
        
        ctx.strokeStyle = 'yellow';
        ctx.stroke(new Path2D(`M ${center[0]+crank_len-conrod_len} ${center[1]} h ${-crank_len*2}`));
        
        ctx.fillStyle = 'red';
        ctx.beginPath();
        ctx.arc(...center,joint_radius,0,2*Math.PI);
        ctx.fill();
        
        let crank_x = center[0] + Math.cos(angle)*crank_len;
        let crank_y = center[1] + Math.sin(angle)*crank_len;
        
        ctx.fillStyle = 'blue';
        ctx.beginPath();
        ctx.arc(crank_x,crank_y,joint_radius,0,2*Math.PI);
        ctx.fill();
        
        ctx.strokeStyle = 'blue';
        ctx.stroke(new Path2D(`M ${center[0]} ${center[1]} L ${crank_x} ${crank_y}`));
        
        let crosshead_x = crank_x - Math.sqrt(conrod_len**2 - (crank_y-center[1])**2);
        let crosshead_y = center[1];
        
        ctx.fillStyle = 'green';
        ctx.beginPath();
        ctx.arc(crosshead_x,crosshead_y,joint_radius,0,2*Math.PI);
        ctx.fill();
        
        ctx.strokeStyle = 'green';
        ctx.stroke(new Path2D(`M ${crank_x} ${crank_y} L ${crosshead_x} ${crosshead_y}`));
                  
        angle = angle%(2*Math.PI) + rotation_speed;
        
        requestAnimationFrame(draw);        
    }
}
<canvas></canvas>

UPD Можно таким же образом манипулировать атрибутами в SVG. Некоторые настройки можно менять в разумных пределах (помечены комментариями).

{
    const svg = document.querySelector('svg');
    const crank = svg.querySelector('#crank');
    const crosshead = svg.querySelector('#crosshead');
    const conrod = svg.querySelector('#conrod');
    const bar_rect = svg.querySelector('#slidebar');
    
    const bar1 = svg.querySelector('#bar1');
    const bar2 = svg.querySelector('#bar2');
    
    // Эти параметры можно изменять (в разумных пределах)
    const center = [800,150];
    const crank_len = 100;
    const conrod_len = 500;
    const crosshead_len = 100;
    const rotation_speed = 1;
    // Дальше ничего менять не надо))
    
    
    bar_rect.setAttribute('width', crank_len*2 + crosshead_len);
    bar1.setAttribute('y', center[1] - 37.5);
    bar1.setAttribute('x', center[0] - crank_len - conrod_len - crosshead_len/2);
    bar2.setAttribute('y', center[1] + 37.5);
    bar2.setAttribute('x', center[0] - crank_len - conrod_len - crosshead_len/2);
    
    crosshead.querySelector('rect').setAttribute('x', -crosshead_len/2);
    crosshead.querySelector('rect').setAttribute('width', crosshead_len);
    
    let angle = 0;
    
    draw();

    function draw(){     
        crank.setAttribute('transform', `translate(${center[0]},${center[1]}) rotate(${angle+180}, 0, 0) scale(${crank_len/100})`);
        
        let crank_x = center[0] + Math.cos(angle*(Math.PI/180))*crank_len;
        let crank_y = center[1] + Math.sin(angle*(Math.PI/180))*crank_len;
        
        let crosshead_x = crank_x - Math.sqrt(conrod_len**2 - (crank_y-center[1])**2);
        let crosshead_y = center[1];
        
        crosshead.setAttribute('transform', `translate(${crosshead_x},${crosshead_y})`);
        
        let conrod_angle = Math.acos((crank_x-crosshead_x)/conrod_len)*(180/Math.PI);
        
        conrod_angle = (crank_y > center[1])? conrod_angle : -conrod_angle;
        
        conrod.setAttribute('transform', `translate(${crosshead_x},${crosshead_y}) rotate(${conrod_angle},0,0) scale(${conrod_len/500})`);

                
        angle = angle%(360) + rotation_speed;
        
        requestAnimationFrame(draw);        
    }

}
<svg viewBox="0 0 1100 600">
    <defs>
        <rect
            id="slidebar"
            stroke-width="1"
            stroke="black"
            fill="silver"
            fill-opacity="1"
            x="0"
            y="-12"
            width="300"
            height="24"
        />

    </defs>

    <use id="bar1" xlink:href="#slidebar"/>
    <use id="bar2" xlink:href="#slidebar"/>

    <g id="crosshead" stroke-width="1" stroke="black" fill-opacity="1">
        <rect
            fill="gold"
            x="-50"
            y="-25"
            width="100"
            height="50"
        />
        <circle cx="0" cy="0" r="15" fill="white"/>
    </g>

    <g id="crank" stroke-width="1" stroke="black" fill-opacity="1">
        <path fill="silver"
            d="M -99.959 40.000
                A  40  40 0 0 1 -99.959, -40.000
                A 450 450 0 0 0  -9.950, -49.000
                A  50  50 0 1 1  -9.950,  49.000
                A 450 450 0 0 0 -99.959,  40.000
                z"/>
        <circle cx="-100" cy="0" r="25" fill="white"/>
        <circle cx="0"    cy="0" r="30" fill="lightgrey"/>
    </g>
    <g id="conrod" stroke-width="1" stroke="black" fill-opacity="0.7">
        <path fill="silver"
                d="M  12.387  21.715
                    A 30 30 0 0 1  27.551  17.776
                    L 453.475  22.035
                    A 30 30 0 0 1 473.243  29.733
                    A 40 40 0 0 1 473.243 -29.733
                    A 30 30 0 0 1 453.475 -22.035
                    L  27.551 -17.776
                    A 30 30 0 0 1 12.387  -21.715
                    A 25 25 0 0 1 12.387   21.715
                    z"/>
        <circle cx="0"   cy="0" r="25" fill="silver"/>
        <circle cx="0"   cy="0" r="15" fill="white"/>
        <circle cx="500" cy="0" r="40" fill="silver"/>
        <circle cx="500" cy="0" r="25" fill="white"/>
    </g>
</svg>

→ Ссылка