1

I am using a couple of functions from Snap.SVG, mainly path2curve and the functions around it to build a SVG morph plugin.

I've setup a demo here on Codepen to better illustrate the issue. Basically morphing shapes simple to complex and the other way around is working properly as of Javascript functionality, however, the visual isn't very pleasing.

The first shape morph looks awful, the second looks a little better because I changed/rotated it's points a bit, but the last example is perfect.

So I need either a better path2curve or a function to prepare the path string before the other function builds the curves array. Snap.SVG has a function called getClosest that I think may be useful but it's not documented.

There isn't any documentation available on this topic so I would appreciate any suggestion/input from RaphaelJS / SnapSVG / d3.js / three/js developers.

4

1 回答 1

3

我在下面提供了一个可运行的代码片段,它使用了 Snap.svg,我相信它可以为您的问题提供一种解决方案。关于试图找到将起始形状变形为结束形状的最佳方法,该算法本质上是一次将起始形状的点旋转一个位置,将(旋转的)起始点上对应点之间距离的平方求和形状和(未改变的)结束形状​​,并找到所有这些总和的最小值。即它基本上是最小二乘法。最小值标识作为第一个猜测将提供“最短”变形轨迹的旋转。然而,尽管进行了这些坐标重新分配,但所有“旋转”都应根据需要产生视觉上相同的起始形状。

当然,这是一种“盲目”的数学方法,但它可能有助于在进行手动可视化分析之前为您提供一个起点。作为奖励,即使您不喜欢算法选择的旋转,它也会为所有其他旋转提供路径“d”属性字符串,因此已经为您完成了一些工作。

您可以修改片段以提供所需的任何开始和结束形状。限制如下:

  • 每个形状都应具有相同数量的点(尽管点类型,例如“lineto”、“cubic bezier curve”、“horizo​​ntal lineto”等可以完全不同)
  • 每个形状都应该是封闭的,即以“Z”结尾
  • 所需的变形应该只涉及翻译。如果需要缩放或旋转,则应在仅基于平移计算变形后应用这些。

顺便说一句,在回应您的一些评论时,虽然我发现 Snap.svg 很有趣,但我也发现它的文档有些缺乏。

更新下面的代码片段适用于 Firefox(Mac 或 Windows)和 Safari。但是,Chrome 似乎无法从其外部网站 (<script...github...>) 访问 Snap.svg 库。Opera 和 Internet Explorer 也有问题。因此,请在工作浏览器中尝试该代码段,或尝试将代码段代码以及 Snap 库代码复制到您自己的计算机上。(这是从代码片段中访问第三方库的问题吗?为什么浏览器存在差异?有见地的评论将不胜感激。)

var
  s         = Snap(),
  colors    = ["red", "blue", "green", "orange"], // colour list can be any length
  staPath   = s.path("M25,35 l-15,-25 C35,20 25,0 40,0 L80,40Z"),  // create the "start" shape
  endPath   = s.path("M10,110 h30 l30,20 C30,120 35,135 25,135Z"), // create the "end"   shape
  staSegs   = getSegs(staPath), // convert the paths to absolute values, using only cubic bezier
  endSegs   = getSegs(endPath), //   segments, & extract the pt coordinates & segment strings
  numSegs   = staSegs.length,   // note: the # of pts is one less than the # of path segments
  numPts    = numSegs - 1,      //   b/c the path's initial 'moveto' pt is also the 'close' pt
  linePaths = [],
  minSumLensSqrd = Infinity,
  rotNumOfMin,
  rotNum = 0;

document.querySelector('button').addEventListener('click', function() {
  if (rotNum < numPts) {
    linePaths.forEach(function(linePath) {linePath.remove();}); // erase any previous coloured lines
    var sumLensSqrd = 0;
    for (var ptNum = 0; ptNum < numPts; ptNum += 1) { // draw new lines, point-to-point
      var linePt1 = staSegs[(rotNum + ptNum) % numPts]; // the new line begins on the 'start' shape
      var linePt2 = endSegs[          ptNum  % numPts]; // and finished on the 'end' shape
      var linePathStr = "M" + linePt1.x + "," + linePt1.y + "L" + linePt2.x + "," + linePt2.y;
      var linePath = s.path(linePathStr).attr({stroke: colors[ptNum % colors.length]}); // draw it
      var lineLen = Snap.path.getTotalLength(linePath); // calculate its length
      sumLensSqrd += lineLen * lineLen; // square the length, and add it to the accumulating total
      linePaths[ptNum] = linePath; // remember the path to facilitate erasing it later
    }
    if (sumLensSqrd < minSumLensSqrd) { // keep track of which rotation has the lowest value
      minSumLensSqrd = sumLensSqrd;     //   of the sum of lengths squared (the 'lsq sum')
      rotNumOfMin = rotNum;             //   as well as the corresponding rotation number
    }
    show("ROTATION OF POINTS #" + rotNum + ":"); // display info about this rotation
    var rotInfo = getRotInfo(rotNum);
    show("&nbsp;&nbsp;point coordinates: " + rotInfo.ptsStr); // show point coordinates
    show("&nbsp;&nbsp;path 'd' string: " + rotInfo.dStr); // show 'd' string needed to draw it
    show("&nbsp;&nbsp;sum of (coloured line lengths squared) = " + sumLensSqrd); // the 'lsq sum'
    rotNum += 1; // analyze the next rotation of points
  } else { // once all the rotations have been analyzed individually...
    linePaths.forEach(function(linePath) {linePath.remove();}); // erase any coloured lines
    show("&nbsp;");
    show("BEST ROTATION, i.e. rotation with lowest sum of (lengths squared): #" + rotNumOfMin);
      // show which rotation to use
    show("Use the shape based on this rotation of points for morphing");
    $("button").off("click");
  }
});

function getSegs(path) {
  var absCubDStr = Snap.path.toCubic(Snap.path.toAbsolute(path.attr("d")));
  return Snap.parsePathString(absCubDStr).map(function(seg, segNum) {
    return {x: seg[segNum ? 5 : 1], y: seg[segNum ? 6 : 2], seg: seg.toString()};
  });
}

function getRotInfo(rotNum) {
  var ptsStr = "";
  for (var segNum = 0; segNum < numSegs; segNum += 1) {
    var oldSegNum = rotNum + segNum;
    if (segNum === 0) {
      var dStr = "M" + staSegs[oldSegNum].x + "," + staSegs[oldSegNum].y;
    } else {
      if (oldSegNum >= numSegs) oldSegNum -= numPts;
      dStr += staSegs[oldSegNum].seg;
    }
    if (segNum !== (numSegs - 1)) {
      ptsStr += "(" + staSegs[oldSegNum].x + "," + staSegs[oldSegNum].y + "), ";
    }
  }
  ptsStr = ptsStr.slice(0, ptsStr.length - 2);
  return {ptsStr: ptsStr, dStr: dStr};
}

function show(msg) {
  var m = document.createElement('pre');
  m.innerHTML = msg;
  document.body.appendChild(m);
}
pre {
  margin: 0;
  padding: 0;
}
<script src="//cdn.jsdelivr.net/snap.svg/0.4.1/snap.svg-min.js"></script>
<p>Best viewed on full page</p>
<p>Coloured lines show morph trajectories for the points for that particular rotation of points. The algorithm seeks to optimize those trajectories, essentially trying to find the "shortest" cumulative routes.</p>
<p>The order of points can be seen by following the colour of the lines: red, blue, green, orange (at least when this was originally written), repeating if there are more than 4 points.</p>
<p><button>Click to show rotation of points on top shape</button></p>

于 2016-02-19T05:33:46.060 回答