Как сделать анимацию кулачкового механизма?

У меня есть гифка работы кулачкового механизма:

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

Эта иллюстрация показывает, что при равномерном вращении вала с насаженным на него кулачком, толкатель получает неравномерные возвратно-поступательные движения толкателя, который скользит по поверхности кулачка.

Я воспроизвел основные контуры этого механизма.

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

Вот мой код статичного механизма:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="xMinYMin meet"  id="svg4" width="390" height="700" viewBox="0 0 390 700">
  
           <!-- Верхняя скоба -->
 <g stroke="#717171" stroke-width="2" fill="none" > 
  <path d="M149 124h87v31h-87z"   />
  <path d="M175 124v31"    />
  <path d="M210 124v31"    />
  <path d="M161 134c3 0 6 3 6 6s-3 6-6 6c-2 0-5-4-5-6 0-3 3-6 5-6z"/>
   <path d="M221 134c3 0 6 3 6 6s-3 6-6 6c-2 0-5-4-5-6 0-3 3-6 5-6z"  />
  </g>
   
             <!-- Нижняя скоба -->
 <g stroke="#717171" stroke-width="2" fill="none" > 
 
  <path id="circ910" d="M221 278c3 0 6 3 6 6s-3 6-6 6c-2 0-5-4-5-6 0-3 3-6 5-6z"/>
  <path id="circ912" d="M161 278c3 0 6 3 6 6s-3 6-6 6c-2 0-5-4-5-6 0-3 3-6 5-6z"/> 
    <path d="M149 267h87v31h-87z"  />
    <path d="M175 267v31" id="path916" />
    <path  d="M210 267v31" id="path918"  /> 
   </g>
                 <!-- Кулачок -->
        <g  ig="gr1"  stroke-width="2"   >
   <path id="cam" fill="#8080B1" stroke="black" fill-opacity="0.5" d="M83 493c1-7 19-23 32-30 10-6 34-10 34-10 6-5 35-42 59-48 21-5 60-15 65-10 11 15 21 57 10 93-4 12-10 27-18 39-5 8-14 14-20 22l-5 9s-4 12-4 19c0 20-9 36-20 49-10 12-32 26-39 24-6-1-21-18-28-30-13-21-22-38-24-69 0-4-19-14-26-23-7-10-17-27-16-35z" />  
   
   <circle id="path866" cx="192" cy="514.6" r="36" fill="#5F5F5F" stroke="black"  stroke-width="2"  />
    <circle id="spindle" cx="192" cy="514.6" r="20" fill="#4A4A4A" stroke="black"  stroke-width="2" />
                   <!-- Шпонка -->
      <path id="key" fill="#151515" stroke="black" d="M189 491h6v10h-6v-10h6"  />  
        
      </g>  
                      <!-- Толкатель -->
  <path id="kernel" d="M184 60v340l8 14 7-14V59z"  fill="none" stroke="red" stroke-width="2" >
      
  </path>
  <circle id="path868" cx="192" cy="514.4" r="144.7" opacity=".5" fill="none" fill-opacity="1" stroke="#1515B1" stroke-width="2" />
  
  
</svg>

Попробовал сделать анимацию кулачкового механизма:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" preserveAspectRatio="xMinYMin meet"  id="svg4" width="390" height="700" viewBox="0 0 390 700">
 
  
 
           <!-- Верхняя скоба -->
 <g stroke="#717171" stroke-width="2" fill="none" > 
  <path d="M149 124h87v31h-87z"   />
  <path d="M175 124v31"    />
  <path d="M210 124v31"    />
  <path d="M161 134c3 0 6 3 6 6s-3 6-6 6c-2 0-5-4-5-6 0-3 3-6 5-6z"/>
   <path d="M221 134c3 0 6 3 6 6s-3 6-6 6c-2 0-5-4-5-6 0-3 3-6 5-6z"  />
  </g>
   
             <!-- Нижняя скоба -->
 <g stroke="#717171" stroke-width="2" fill="none" > 
 
  <path id="circ910" d="M221 278c3 0 6 3 6 6s-3 6-6 6c-2 0-5-4-5-6 0-3 3-6 5-6z"/>
  <path id="circ912" d="M161 278c3 0 6 3 6 6s-3 6-6 6c-2 0-5-4-5-6 0-3 3-6 5-6z"/> 
    <path d="M149 267h87v31h-87z"  />
    <path d="M175 267v31" id="path916" />
    <path  d="M210 267v31" id="path918"  /> 
   </g>
  
          <g  ig="gr1"  stroke-width="2"   >  
                    <!-- Кулачок -->
   <path id="cam" fill="#8080B1" stroke="black" fill-opacity="0.5" d="M83 493c1-7 19-23 32-30 10-6 34-10 34-10 6-5 35-42 59-48 21-5 60-15 65-10 11 15 21 57 10 93-4 12-10 27-18 39-5 8-14 14-20 22l-5 9s-4 12-4 19c0 20-9 36-20 49-10 12-32 26-39 24-6-1-21-18-28-30-13-21-22-38-24-69 0-4-19-14-26-23-7-10-17-27-16-35z" />  
                         <!-- Вал -->
   <circle id="path866" cx="192" cy="514.6" r="36" fill="#5F5F5F" stroke="black"  stroke-width="2"  />
    <circle id="spindle" cx="192" cy="514.6" r="20" fill="#4A4A4A" stroke="black"  stroke-width="2" />
    
      <path id="key" fill="#151515" stroke="black" d="M189 491h6v10h-6v-10h6"  />  
                             <!-- Анимация вращения вала с кулачком -->
       <animateTransform attributeName="transform" type="rotate" begin="0s" dur="2s" values="0 192 514.6;360 192 514.6" repeatCount="indefinite" />  
      
     </g>  
                            <!-- Толкатель -->
  <path id="kernel" d="M184 60v340l8 14 7-14V59z"  fill="none" stroke="red" stroke-width="2" >
                          <!-- Анимация перемещения толкателя -->
      <animateTransform attributeName="transform" type="translate" begin="0s" dur="2s" calcMode="linear" 
         values="0 0;0 30;0 0"  repeatCount="indefinite" />  
  </path>
  <circle id="path868" cx="192" cy="514.4" r="144.7" fill="none" fill-opacity="1" stroke="#1515B1" stroke-width="2" />
  
  
</svg>

Из работы программы видно, что хоть время начала анимаций и их продолжительность совпадает, но не получается добиться согласованной работы вращения кулачка и возвратно-поступательного движения толкателя.

Как согласовать анимацию вращения кулачка и неравномерное движение толкателя, которое зависит от профиля поверхности кулачка.


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

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

Отобразил только ключевые элементы. Пути переделал так, чтобы ось вращения диска с кулачками была на [0,0]. Для толкателя нулевой точкой сделал нижний кончик.

После отрисовки кулачка находим точку пересечения перебором координат y (при неизменной x равной центру вращения) пока эта точка не совпадет с контуром пути: ctx.isPointInStroke(cam,x,y).

render();

function render(){

    // Описание путей диска с кулачками (cam) и игольчатого толкателя (kernel)
    const cam_path = 'M-39.89 -11.27c0.39,-2.73 7.4,-8.97 12.47,-11.7 3.9,-2.33 13.25,-3.89 13.25,-3.89 2.34,-1.95 13.64,-16.37 23,-18.71 8.18,-1.95 23.38,-5.85 25.33,-3.9 4.29,5.85 8.19,22.22 3.9,36.25 -1.56,4.68 -3.9,10.52 -7.01,15.2 -1.95,3.12 -5.46,5.46 -7.8,8.58l-1.95 3.5c0,0 -1.56,4.68 -1.56,7.41 0,7.8 -3.51,14.03 -7.79,19.1 -3.9,4.68 -12.48,10.13 -15.21,9.35 -2.33,-0.39 -8.18,-7.01 -10.91,-11.69 -5.07,-8.18 -8.57,-14.81 -9.35,-26.89 0,-1.56 -7.41,-5.46 -10.14,-8.97 -2.73,-3.9 -6.62,-10.52 -6.23,-13.64l0 0z';
    const kernel_path = 'M-4 -70c2.67,0 5.33,0 8,0 0,21.54 0,43.08 0,64.62 -1.34,1.79 -2.67,3.58 -4,5.38 -1.33,-1.8 -2.67,-3.59 -4,-5.38 0,-21.54 0,-43.08 0,-64.62z';

    const canvas = document.querySelector('canvas');
    const ctx = canvas.getContext('2d');
    const w = canvas.width = 600;
    const h = canvas.height = 190;

    const cam = new Path2D(cam_path);       // Путь кулачка
    const kernel = new Path2D(kernel_path); // Путь толкателя
    
    let angle = 0;  // Угол вращения сцены (для кулачка)      
    
    animate();      // Функция будет вызывать сама себя на каждом кадре (помещать в очередь на отрисовку)
    
    function animate(){
        ctx.clearRect(0,0,w,h);                 // Очистка холста
    
        ctx.save();                            // Сохраняем настройки контекста
        ctx.translate(w/2, 129);               // Центр вращения
        ctx.rotate(angle * Math.PI/180);       // Вращаем полотно
        ctx.fillStyle = 'rgb(128, 128, 177)';  // Указываем стиль заливки
        ctx.fill(cam);                         // Заливаем путь кулачка
        ctx.stroke(cam);                       // Контур кулачка

        ctx.beginPath();                       // Внешняя окружность (вал?)
        ctx.arc(0,0,60,0,Math.PI*2,true);
        ctx.strokeStyle = 'rgb(100,0,180)';
        ctx.stroke();
      
        ctx.beginPath();                       // Шток (ось)
        ctx.arc(0,0,16,0,Math.PI*2,true);
        ctx.fillStyle = 'grey';
        ctx.strokeStyle = 'black';
        ctx.fill();
        ctx.stroke();
        ctx.beginPath();
        ctx.arc(0,0,10,0,Math.PI*2,true);
        ctx.stroke();

        ctx.fillStyle = 'black';                // Шпонка
        ctx.fillRect(7,-2,6,4);

        angle = angle%360 + 2;                  // Изменение угла за кадр

        let kernel_position = 0                 // Позиция кончика толкателя (инициализируем 0)
        while(!ctx.isPointInStroke(cam,w/2,kernel_position)){   // Пока точка не совпадет с контуром кулачка
            kernel_position++;                                  // Добавляем к позиции по Y
        }
        ctx.restore();                          // Возвращаем настройки контекста (translate, rotate, fillStyle, strokeStyle)
        
        ctx.save();
        ctx.translate(w/2, kernel_position);    // Позиция кончика толкателя
        ctx.fillStyle = 'red';
        ctx.fill(kernel);                       // Заливка толкателя
        ctx.stroke(kernel);                     // Контур толкателя
        ctx.restore();
        
        requestAnimationFrame(animate);         // Помещаем в очередь на отрисовку на следующем кадре
    }
}
    <canvas></canvas>

→ Ссылка