我正在使用Snap.SVG中的一些函数,主要是path2curve
及其周围的函数来构建一个SVG变形插件。
我已设置演示版here on Codepen以更好地说明问题。基本上变形形状简单到复杂,反之亦然,在Javascript功能方面正常工作,但是,视觉效果不是很令人满意。
第一个形状变形看起来很糟糕,第二个看起来好一点,因为我稍微改变/旋转它的点,但最后一个例子是完美的。
所以我需要一个更好的path2curve
或一个函数来在另一个函数构建curves数组之前准备路径字符串。 Snap.SVG有一个名为getClosest
的函数,我认为它可能很有用,但没有记录。
没有关于此主题的任何文档,所以我将非常感谢RaphaelJS / SnapSVG / d3.js / three / js开发人员的任何建议/输入。
答案 0 :(得分:3)
我在下面提供了一个使用Snap.svg的可运行代码片段,我相信它可以为您的问题提供一个解决方案。关于试图找到将起始形状变形为结束形状的最佳方法,该算法基本上将起始形状的点一次旋转一个位置,对(旋转)起点上的相应点之间的距离的平方求和形状和(未更改的)结束形状,并找到所有这些总和的最小值。即它基本上是最小二乘法。最小值标识旋转,作为第一个猜测,将提供"最短"变形轨迹。然而,尽管进行了这些坐标重新分配,但所有的轮换都是如此。根据需要,应在视觉上产生相同的起始形状。
当然,这是一个"盲人"数学方法,但它可能有助于在进行手动视觉分析之前为您提供一个起点。作为奖励,即使您不喜欢算法所选择的轮换,它也会提供路径' d'所有其他轮换的属性字符串,因此已经为您完成了一些工作。
您可以修改代码段以提供所需的任何起始和结束形状。限制如下:
顺便说一句,回应你的一些评论,虽然我发现Snap.svg很有趣,但我发现它的文档有点缺乏。
更新:下面的代码段可在Firefox(Mac或Windows)和Safari中使用。但是,Chrome似乎无法从其外部网站访问Snap.svg库(< script ... github ...>)。 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(" point coordinates: " + rotInfo.ptsStr); // show point coordinates
show(" path 'd' string: " + rotInfo.dStr); // show 'd' string needed to draw it
show(" 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(" ");
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);
}
&#13;
pre {
margin: 0;
padding: 0;
}
&#13;
<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>
&#13;