SeriousNinja
SeriousNinja

Reputation: 21

Mesh Warp SVG - I think I've got really close to having it working

I'm trying to make a similar distort function as Illustrators "Free Distort". I have managed to find two scripts that works separately but not together. example warp

1st script: Warps using control points but only the edges. 2nd script: Warps anywhere on the SVG by smudging with the mouse.

I think there is a solution somewhere in these two codes but I haven't been able to find it.

Here is a combination of the two (notice you can smudge and drag the control points but not smudge(warp) by dragging the control points)

To understand what I am talking about please first try to drag the dots and also click on the svg element directly to smudge it.

https://jsfiddle.net/476f0av9/

let brushSize = 300;

const container = document.getElementById('smudge')
const svg = document.getElementById('svg')
const cursor = document.getElementById('cursor')
const warp = new Warp(svg)

const svgPos = container.getBoundingClientRect()
const originX = svgPos.left
const originY = svgPos.top

let pointCount = 0
let smudging = false
let mouseX = 0
let mouseY = 0
let lastMouseX = 0
let lastMouseY = 0
let mouseDeltaX = 0
let mouseDeltaY = 0

////////////////////////////////////////////////////////////////////////

          
          const controlPath = document.getElementById('control-path');
 const width = svg.width.baseVal.value;
          const height = svg.height.baseVal.value;
          
          // Need to interpolate first, so angles remain sharp
       //   warp.interpolate(40);
          
          // Define the number of columns and rows in the grid
          const numColumns = 5;
          const numRows = 5;
          
          // Calculate the grid cell size
          const cellWidth = width / (numColumns - 1);
          const cellHeight = height / (numRows - 1);
          
          // Create the initial control points grid
          const controlPoints = [];
          
          for (let row = 0; row < numRows; row++) {
          
              for (let col = 0; col < numColumns; col++) {
          
                  const x = col * cellWidth;
                  const y = row * cellHeight;
                  
                  controlPoints.push([x, y]);
              }
          }
          
          // Funny things happen when control points are positioned perfectly on other points... buff it out
          const controlBuffer = 0.1;
          
          for (let i = 0; i < controlPoints.length; i++) {
          
              const point = controlPoints[i];
          
              if (point[0] === 0) point[0] -= controlBuffer;
              if (point[1] === 0) point[1] -= controlBuffer;
              if (point[0] === width) point[0] += controlBuffer;
              if (point[1] === height) point[1] += controlBuffer;
          }
          
drawControlShape();
          
 // Function to draw the control shape
   function drawControlShape(element = controlPath, V = controlPoints) {
          
              const path = [];
          
              for (let row = 0; row < numRows; row++) {
          
  const rowPoints = V.slice(row * numColumns, (row + 1) * numColumns);

                  path.push(`M${rowPoints[0][0]} ${rowPoints[0][1]}`);
          
                  for (let col = 1; col < numColumns; col++) {
          
                      path.push(`L${rowPoints[col][0]} ${rowPoints[col][1]}`);
                  }
              }
          
              for (let col = 0; col < numColumns; col++) {
          
                  const colPoints = [];
          
                  for (let row = 0; row < numRows; row++) {
          
                    colPoints.push(V[row * numColumns + col]);
                  }
          
                  path.push(`M${colPoints[0][0]} ${colPoints[0][1]}`);
          
                  for (let row = 1; row < numRows; row++) {
          
                    path.push(`L${colPoints[row][0]} ${colPoints[row][1]}`);
                  }
              }
          
              element.setAttribute('d', path.join(''));
          }
          
       // Add code to draw dots at each control point
          const svgControl = document.getElementById('svg-control');
          
          for (let i = 0; i < controlPoints.length; i++) {
          
            const [x, y] = controlPoints[i];
            const dot = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
          
            dot.setAttribute('cx', x);
            dot.setAttribute('cy', y);
            dot.setAttribute('r', 5);
            dot.setAttribute('fill', 'red');
          
            svgControl.appendChild(dot);
          }
          
          
  var dots = document.querySelectorAll('#svg-control circle');
          
          // Function to initiate dragging
          function startDrag(event) {

            // Store the current dot and its initial position
            const activeDot = event.target;

            let initialX = event.clientX;
            let initialY = event.clientY;
            let offsetX = parseInt(activeDot.getAttribute('cx'));
            let offsetY = parseInt(activeDot.getAttribute('cy'));

            // Get the index of the active dot in the dots NodeList
            const index = Array.from(dots).indexOf(activeDot);

            // Add event listeners for mouse move and mouse up events
            document.addEventListener('mousemove', drag);
            document.addEventListener('mouseup', endDrag);

            // Function to handle dragging
            function drag(event) {

              // Calculate the distance moved by the mouse
              const deltaX = event.clientX - initialX;
              const deltaY = event.clientY - initialY;

              // Update the position of the dot based on the mouse movement
              const newX = offsetX + deltaX;
              const newY = offsetY + deltaY;
              activeDot.setAttribute('cx', newX);
              activeDot.setAttribute('cy', newY);

              // Calculate the row and column indices of the active dot
              const rowIndex = Math.floor(index / numColumns);
              const colIndex = index % numColumns;

              // Update the positions of the affected control points in the grid
              controlPoints[rowIndex * numColumns + colIndex] = [newX, newY];

              // Redraw the control shape
              drawControlShape();

              // Recompute the weights
             // smudge it !
             // warp.transform((v0, V) => reposition(v0, V));
            }

            // Function to end dragging
            function endDrag() {
              // Remove the event listeners for mouse move and mouse up events
              document.removeEventListener('mousemove', drag);
              document.removeEventListener('mouseup', endDrag);
            }
          }
          
          // Add event listeners for mouse interactions on each dot
          dots.forEach(dot => {
            
            dot.addEventListener('mousedown', startDrag);
          });
          
//////////////////////////////////////////////////////////////
window.addEventListener('keydown', function(e)
{
    switch(e.key)
    {
        case 'ArrowUp': { brushSize += 10 } break
        case 'ArrowDown': { brushSize -= 10 }
    }
    
    brushSize = Math.max(10, Math.min(brushSize, 250))
})

window.addEventListener('mousedown', e => smudging = true)
window.addEventListener('mouseup', e => smudging = false)
window.addEventListener('mousemove', function(e)
{
    mouseX = e.clientX;
    mouseY = e.clientY;
    mouseDeltaX = mouseX - lastMouseX
    mouseDeltaY = mouseY - lastMouseY
    lastMouseX = mouseX
    lastMouseY = mouseY
})

function smudge([x, y])
{
    const pointX = x + originX
    const pointY = y + originY
    const deltaX = mouseX - pointX
    const deltaY = mouseY - pointY
    const delta = Math.sqrt(deltaX**2 + deltaY**2)
  

    if(delta <= brushSize)
    {
        x += mouseDeltaX * ((brushSize - delta) / brushSize)
        y += mouseDeltaY * ((brushSize - delta) / brushSize)
    }
    
    return [x, y]
}

function update()
{
  
    cursor.style.transform = `translate(${mouseX}px, ${mouseY}px)`
    cursor.style.fontSize = `${brushSize}px`
  
  
    if(smudging)
    {
        if(pointCount < 4000)
        {
            // warp.preInterpolate(smudge, 8)
            warp.interpolate(8)
        }
            
        pointCount = 0
        warp.transform(function(points)
        {
            pointCount++
            return smudge(points)
        })
        
        mouseDeltaX = (lastMouseX === mouseX ? 0 : mouseDeltaX)
        mouseDeltaY = (lastMouseY === mouseY ? 0 : mouseDeltaY)
    
    }
    
    requestAnimationFrame(update)
}

update()
#svg8,
#svg-control {
position: absolute;
top:0px;
left:0px;
    overflow: visible;
}

 #svg-control {
      position: absolute;
       z-index: 9000000;
      top: 0;
      left: 0;
    }

#control-path {
    fill: none;
    stroke: red;
    stroke-width: 1px;
}

body
{
  padding: 10px;
}

    #smudge {
      position: relative;
    }

    #cursor {
      position: absolute;
    }
 <div id="cursor"></div>
 
 <svg id="svg-control" width="120" height="120">
    <path id="control-path" d="M50 50L100 50L100 100L50 100Z" stroke="black" fill="transparent"/>
  </svg>

<div id="smudge">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 105"  height="500px" width="500px" id="svg" >
    
  <g fill="#97C024" stroke="#97C024" stroke-linejoin="round" stroke-linecap="round">
    <path d="M14,40v24M81,40v24M38,68v24M57,68v24M28,42v31h39v-31z" stroke-width="12"/>
    <path d="M32,5l5,10M64,5l-6,10 " stroke-width="2"/>
  </g>
  <path d="M22,35h51v10h-51zM22,33c0-31,51-31,51,0" fill="#97C024"/>
  <g fill="#FFF">
    <circle cx="36" cy="22" r="2"/>
    <circle cx="59" cy="22" r="2"/>
  </g>
</svg>
</div>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/warp.js"></script>

Any help would be really appreciated.

Thank you.

Upvotes: 1

Views: 107

Answers (0)

Related Questions