Вычислять новые градиентные позиции после того, как многоугольник заполняет изменения размеров
Что я хочу сделать?
Обновления по этому вопросу: 7/10/2012 - "gradientTransform not not" Вдохновленный Duopixel
7/11/2012 - "SVG-код из примера" 7/16/2012 - "@dignifiedquire возьмите эту проблему"
Я пытаюсь создать инструмент, позволяющий пользователю динамически изменять размер полигонов. Большинство полигонов заполнены градиентами fill="url(#top_surface_1_gradient)"
. Как я это делаю, это простой JavaScript script, который:
- ищет mousemove и события click над определенным полигоном
- измеряет количество движения
- изменяет половину координат многоугольника (для того, чтобы иметь эффект растяжения), используя этот алгоритм для определения новых координат:
x = x_movement
, y = x_movement * Math.tan( 31 * (Math.PI/180) )
- многоугольники, заполненные одним цветом, ОК
- многоугольники, заполненные градиентом, не позволяют мне продемонстрировать:
Визуально
![step_1]()
Итак, это первый шаг, пользователь не растягивает.
![step_2]()
Здесь возникает проблема. Поскольку я не знаю, как изменить координаты x1, y1
и x2, y2
для градиента, он просто остается висящим в нем старой позицией, в то время как многоугольник растянут. Результатом является форма, которая не выдерживает иллюзию глубины.
![step_3]()
Конечный результат, который я ищу. И не забывайте, что градиент может иметь совершенно произвольный угол от начала. В результате этого результата, который я ищу, изменились координаты градиента x1, y1
и x2, y2
. Какой алгоритм должен использоваться для расчета этих позиций? Я ищу решение, которое полностью слепило к углу градиента.
Ниже приведен SVG со всеми соответствующими координатами, которые были использованы для генерации этих примеров:
Использование SVG-кода
Шаг 1:
<!-- Step 1 -->
<linearGradient id="top_surface_1_gradient" gradientUnits="userSpaceOnUse" x1="165.3425" y1="39.7002" x2="-49.991" y2="43.0337">
<stop offset="0" style="stop-color:#FFFFFF"/>
<stop offset="0.6687" style="stop-color:#CCCCCC"/>
<stop offset="1" style="stop-color:#FFFFFF"/>
</linearGradient>
<polygon id="top_surface_1" fill="url(#top_surface_1_gradient)" points="137.145,41.204 68.572,0 0,41.204 68.572,82.396"/>
Шаг 2
<!-- Step 2 -->
<linearGradient id="top_surface_2_gradient" gradientUnits="userSpaceOnUse" x1="250.0491" y1="233.8115" x2="23.7637" y2="237.3146">
<stop offset="0" style="stop-color:#FFFFFF"/>
<stop offset="0.6687" style="stop-color:#CCCCCC"/>
<stop offset="1" style="stop-color:#FFFFFF"/>
</linearGradient>
<polygon id="top_surface_2" fill="url(#top_surface_2_gradient)" points="205.788,215.557 137.215,174.354 0.078,256.629 68.649,297.823"/>
Шаг 3
<!-- Step 3 -->
<linearGradient id="top_surface_3_gradient" gradientUnits="userSpaceOnUse" x1="248.4543" y1="454.5225" x2="-75.535" y2="459.5381">
<stop offset="0" style="stop-color:#FFFFFF"/>
<stop offset="0.6687" style="stop-color:#CCCCCC"/>
<stop offset="1" style="stop-color:#FFFFFF"/>
</linearGradient>
<polygon id="top_surface_3" fill="url(#top_surface_3_gradient)" points="205.788,415.557 137.215,374.354 0.078,456.629 68.649,497.823"/>
Я потратил бесчисленное количество часов на разработку решений для этой проблемы, и я просто не мог обойти это. Любая помощь будет принята с благодарностью.
Обновление: gradientTransform не совсем
Использование атрибута gradientTransform и отсутствие x1, y1; x2, y2 для градиента, мы достигаем результатов, которые заполняют многоугольник так, как это необходимо (это решение можно найти здесь: http://jsfiddle.net/hqXx2/). Единственное место, где разрывается решение, - это когда полигон заполнен градиентом, который начинается за пределами многоугольника и/или заканчивается где-то снаружи/внутри. Позвольте мне проиллюстрировать:
Это то, что достигается с помощью решения, которое предлагает Duopixel.
![step_4]()
Это пример использования, которого невозможно достичь, используя упомянутое выше решение. Я изменил окраску, чтобы заметно усилить угол и градиентные остановки.
![step_5]()
Код SVG из примера
Здесь код для большей, правильно расширенной группы полигонов:
<g>
<linearGradient id="surface_center_inside_bottom_1_" gradientUnits="userSpaceOnUse" x1="167.7629" y1="634.5986" x2="-72.9039" y2="599.2647">
<stop offset="0" style="stop-color:#FFFFFF"/>
<stop offset="0.8528" style="stop-color:#CCCCCC"/>
<stop offset="0.9954" style="stop-color:#CCCCCC"/>
</linearGradient>
<polygon id="surface_center_inside_bottom_9_" fill="url(#surface_center_inside_bottom_1_)" points="137.145,620.04 68.572,578.837 0,620.04 68.572,661.233"/>
<linearGradient id="surface_right_inside_side_1_" gradientUnits="userSpaceOnUse" x1="178.8889" y1="600.1787" x2="33.103" y2="517.9229">
<stop offset="0" style="stop-color:#FFFFFF"/>
<stop offset="0.9816" style="stop-color:#A3A5A8"/>
</linearGradient>
<polygon id="surface_right_inside_side_3_" fill="url(#surface_right_inside_side_1_)" points="136.526,620.374 68.359,578.501 68.572,493.837 137.358,535.37"/>
<linearGradient id="surface_right_inside_side_2_" gradientUnits="userSpaceOnUse" x1="126.2664" y1="563.249" x2="-28.4" y2="621.916">
<stop offset="0" style="stop-color:#FF0000"/>
<stop offset="0.6698" style="stop-color:#00FFFF"/>
<stop offset="1" style="stop-color:#FF0000"/>
</linearGradient>
<polygon id="surface_right_inside_side_5_" fill="url(#surface_right_inside_side_2_)" points="68.573,661.239 0,620.036 0,535.036 68.573,576.231"/>
<linearGradient id="surface_center_outside_top_1_" gradientUnits="userSpaceOnUse" x1="167.3728" y1="533.5059" x2="-47.9608" y2="536.8394">
<stop offset="0.0016" style="stop-color:#FF0000"/>
<stop offset="0.6735" style="stop-color:#00FFFF"/>
<stop offset="1" style="stop-color:#FF0000"/>
</linearGradient>
<polygon id="surface_center_outside_top_3_" fill="url(#surface_center_outside_top_1_)" points="137.145,535.041 68.572,493.837 0,535.041 68.572,576.233"/>
</g>
И вот код SVG для меньшего, который мне нужно развернуть:
<g>
<linearGradient id="surface_right_inside_side_4_" gradientUnits="userSpaceOnUse" x1="273.4377" y1="319.251" x2="78.0696" y2="209.0197">
<stop offset="0" style="stop-color:#FFFFFF"/>
<stop offset="0.9816" style="stop-color:#A3A5A8"/>
</linearGradient>
<polygon id="surface_right_inside_side_9_" fill="url(#surface_right_inside_side_4_)" points="205.112,366.797 136.945,324.924 137.157,156.261 205.731,197.464"/>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="247.2952" y1="408.1992" x2="-103.1108" y2="356.7538">
<stop offset="0" style="stop-color:#FFFFFF"/>
<stop offset="0.8528" style="stop-color:#CCCCCC"/>
<stop offset="0.9954" style="stop-color:#CCCCCC"/>
</linearGradient>
<polygon fill="url(#SVGID_1_)" points="205.731,366.465 137.157,325.262 0.021,407.536 68.592,448.729"/>
<linearGradient id="surface_right_inside_side_7_" gradientUnits="userSpaceOnUse" x1="160.3313" y1="296.623" x2="-52.0119" y2="377.1676">
<stop offset="0" style="stop-color:#FF0000"/>
<stop offset="0.6698" style="stop-color:#00FFFF"/>
<stop offset="1" style="stop-color:#FF0000"/>
</linearGradient>
<polygon id="surface_right_inside_side_6_" fill="url(#surface_right_inside_side_7_)" points="68.532,448.767 0,407.497 0.021,238.536 68.592,279.729"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="248.4749" y1="215.7417" x2="-75.5139" y2="220.7572">
<stop offset="0.0016" style="stop-color:#FF0000"/>
<stop offset="0.6735" style="stop-color:#00FFFF"/>
<stop offset="1" style="stop-color:#FF0000"/>
</linearGradient>
<polygon fill="url(#SVGID_2_)" points="205.731,197.464 137.157,156.261 68.592,197.333 0.021,238.536 68.592,279.729"/>
</g>
@dignifiedquire возьмите эту проблему
Я реализовал предложенный алгоритм @dignifiedquire на тестовом сайте: Здесь тестовая ссылка. Я сделал абсолютное относительное преобразование самостоятельно, и он просто показывает тот же результат, который я обычно добавлял бы таким же значениям полигона x и y в градиент x и y. Это основная проблема - как преобразовать эти значения в такое значение, которое преобразует градиент, как в моих примерах выше?
Требуется дополнительная помощь.
Ответы
Ответ 1
Вы можете применить преобразования к градиентам, это означает, что вы можете делать такие вещи, как gradientTransform="rotate(45)
. Это решает проблему вращения.
Вы должны использовать относительные единицы и установить для пользователя пространство objectBoundingBox
, чтобы значения x
и y
соответствовали размерам вашего многоугольника. Ваш svg будет выглядеть следующим образом.
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<linearGradient id="top_surface_2_gradient" gradientUnits="objectBoundingBox" x1="0%" y1="0%" x2="100%" y2="100%" gradientTransform="rotate(0 0.5 0.5)">
<stop offset="0" style="stop-color:#000"/>
<stop offset="1" style="stop-color:#fff"/>
</linearGradient>
<polygon id="top_surface_2" fill="url(#top_surface_2_gradient)" points="205.788,215.557 137.215,174.354 0.078,256.629 68.649,297.823"/>
</svg>
Вы можете проверить, как это работает на полигонах разного размера: http://jsfiddle.net/hqXx2/
Ответ 2
Обновление 3 Альтернативная идея
Альтернативным решением могло бы быть вычисление процентных значений на основе двух конечных точек градиента.
На этом рисунке вы видите исходный многоугольник abcd
, его ограничивающий прямоугольник a'b'c'd'
и градиент g1g2
. Теперь цель состоит в том, чтобы сначала вычислить две точки g1
и g2
в абсолютных величинах, а затем вычислить относительные значения этих двух.
![bounding box]()
Я вывел алгоритм, который выполняет большую часть того, что я описал, но ему не удается вычислить пересечение градиента и ограничивающего прямоугольника. У меня есть идея о том, как решить эту проблему, но сейчас не время ее реализации, поэтому я перечисляю шаги для этого.
Основная идея состоит в том, чтобы проверить, пересекает ли градиент с одной из линий через углы ограничивающей рамки (a'b'
, b'c'
, c'd'
, d'a'
), а затем проверяет, находится ли пересечение на краю обсуждаемый. В настоящее время есть два особых случая, требующих обработки. 1. Градиент вертикальный, это означает, что его наклон бесконечен 2. Сторона, о которой идет речь, является вертикальной, снова это означает, что ее наклон бесконечен
Все остальные случаи легко разрешимы с помощью базовой математики (двухточечная форма линии, пересечение двух линий).
Мой алгоритм
_ = require('underscore')
function intersectGradientWithPolygon(polygon, gradient){
var sides = [
["a", "b"],
["b", "c"],
["c", "d"],
["d", "a"]
];
var boundingBox = calculateBoundingBox(polygon);
// intersect each side of the bounding box with the gradient
var intersections = _.map(sides, function(side){
var intersection = intersect(boundingBox[side[0]], boundingBox[side[1]], gradient.a, gradient.b);
if(intersection){
// calculate the percentages
console.log(JSON.stringify(intersection));
return calcPercentage(intersection, boundingBox);
}
});
return intersections;
}
function Point(x,y){
this.x = x;
this.y = y;
}
function calcPercentage(intersection, boundingBox){
var lengthX = (boundingBox.max.x - boundingBox.min.x),
lengthY = (boundingBox.max.y - boundingBox.min.y),
x = (intersection.x / lengthX) * 100,
y = (intersection.y / lengthY) * 100;
}
function calculateBoundingBox(polygon){
var xValues = _.pluck(polygon, 'x'),
yValues = _.pluck(polygon, 'y'),
maxX = _.max(xValues),
maxY = _.max(yValues),
minX = _.min(xValues),
minY = _.min(yValues);
return {
"a": new Point(minX, maxY),
"b": new Point(maxX, maxY),
"c": new Point(maxX, minY),
"d": new Point(minX, minY),
"max": new Point(maxX, maxY),
"min": new Point(minX, minY)
};
}
// tests if the two lines a1, b1 and a2, b2 intersect and
// returns the point of intersection if the do so
function intersect(a1, b1, a2, b2){
var s = new Point( );
// TODO
// This is the part that is missing
// one needs to implement what I described above at this point
//
if (isInIntervall(s.x, a1.x, b1.x) && isInIntervall(s.y, a2.y, b2.y)){
return s;
}
else {
return false;
}
}
// test if a point is in the intervall [a,b]
function isInIntervall(point, a, b){
return (point >= a) && (point <=b);
}
Обновление 2
Вопрос: Также как меняется координаты градиента, если многоугольник перемещается в пространстве в целом и не растягивается.?
Ответ: Вы вычисляете сумму, которую вы переместите одну точку вашего полигона по x и y и переместите точки градиента на ту же сумму.
Теперь я изменил алгоритм, основанный на масштабировании с одной стороны многоугольника на абсолютное количество единиц.
Я также создал изображение, чтобы объяснить, что делает алгоритм
- оригинальный многоугольник
- масштабированный многоугольник с помощью масштабного коэффициента, определяемого входами
- переместите многоугольник обратно в исходное место
![explain]()
Обновлено 15.7.2012
Я вывел алгоритм, основанный на идее, которую я предложил с помощью матриц преобразования для преобразования. У меня не было времени проверить его, но код работает под node.js и должен запускаться в браузере, если вы включаете underscore.js и sylvester (операции с матрицами) в вашем документе.
Настройка
/* underscore for some helper methods
* http:*http:*underscorejs.org
*/
_ = require("underscore");
/* matrix operations
* http:*sylvester.jcoglan.com
*/
require("sylvester");
Входы
var gradient = {
"a":{
"x": 165.3425,
"y": 39.7002
},
"b":{
"x": -49.991,
"y": 43.0337
}
};
var polygon = {
"a": {
"x": 137.145,
"y": 41.204
},
"b": {
"x": 68.572,
"y": 0
},
"c": {
"x": 0,
"y": 41.204
},
"d": {
"x": 68.572,
"y": 82.396
}
};
// the scales are now absolute values in the same units as the coordinates
var scaleAbsX = 100;
var scaleAbsY = 100 * Math.tan( 62/2 * (Math.PI/180) );
// this describes the side that is scaled
var side = ["a", "b"];
Алгоритм
scalePolyWithGradient = function(polygon, gradient, scaleAbsX, scaleAbsY, side){
// 1. Scale by factor: derive factor from input scaling and then translate into scaling matrix
// 2. Apply scale to the gradient
// 3. Translate both back
// create a scaling matrix based of the input scales
// get the two points of the scaled side
var point1 = polygon[side[0]],
point2 = polygon[side[1]];
// scale these points
var scaledPoint1 = { "x": point1.x + scaleAbsX,
"y": point1.y + scaleAbsY },
scaledPoint2 = { "x": point2.x + scaleAbsX,
"y": point2.y + scaleAbsY };
// calculate the relative scales
var scaleRelX = scaledPoint1.x / point1.x,
scaleRelY = scaledPoint1.y / point1.y;
// create the scale matrix
var scaleMatrix = $M([ [scaleRelX, 0],
[0, scaleRelY] ]);
// scale both the polygon and the gradient
// we iterate so that the translation is done on every point
var scale = function(point){
// convert the point into a matrix
point = $M([[point.x],
[point.y]]);
// scale
var newPoint = scaleMatrix.multiply(point);
return { "x": newPoint.elements[0][0],
"y": newPoint.elements[1][0]};
};
var newPolygon = {},
newGradient = {};
_.each(polygon, function(point, key){
newPolygon[key] = scale(point);
});
_.each(gradient, function(point, key){
newGradient[key] = scale(point);
});
// calculate the translation to move them to the original position
// and move them back
// we know that the points to move to their original position are the
// ones not in the scale side
var sides = _.keys(polygon), // all possible sides
movePoints = _.difference(sides, side), // the points not used in the scale
point = movePoints[0]; // the first of these points
// we use these points to create the translation
var oldPoint = polygon[point],
newPoint = newPolygon[point];
var translateMatrix = $M([ [newPoint.x - oldPoint.x],
[newPoint.y - oldPoint.y] ]);
var translate = function(point){
// convert the point into a matrix
point = $M([[point.x],
[point.y]]);
// translate
var newPoint = point.add(translateMatrix);
return { "x": newPoint.elements[0][0],
"y": newPoint.elements[1][0]};
};
// apply the translation
_.each(newPolygon, function(point, key){
newPolygon[key] = translate(point);
});
_.each(newGradient, function(point, key){
newGradient[key] = translate(point);
});
// return the new coordinates
return [newPolygon, newGradient];
};
// apply the algorithm
var newPolygon, newGradient = null;
var result = scalePolyWithGradient(polygon, gradient, scaleAbsX, scaleAbsY, side);
newPolygon = result[0];
newGradient = result[1];
Результат
newPolygon = { "a": {
"x": 178.2885,
"y":82.405
},
"b": {
"x": 96.00089999999999,
"y": 20.598999999999997
},
"c": {
"x": 13.714500000000001,
"y": 82.405
},
"d": {
"x": 96.00089999999999,
"y":144.19299999999998
}
}
newGradient = { "a": {
"x": 212.12550000000005,
"y":80.14930000000001
},
"b": {
"x": -46.274699999999996,
"y": 85.14955
}
}
Старый ответ
Изображение здесь, потому что я не могу загружать изображения в stackoverflow (репутация низка)
Я отвлек сторону многоугольника, чтобы мы могли сосредоточиться на этом. Левое изображение перед масштабированием. Теперь я нарисовал "весь" градиент, чтобы показать, что нужно масштабировать. Чтобы вычислить нужные координаты, просто масштабируется квадрат градиента в той же пропорции, что и сторона многоугольника.
Я знаю, что это изображение без вращения, но этот метод можно расширить, чтобы включить его.
Я могу выработать алгоритм для этого, но у меня не было времени для этого. Поэтому, если это то, что вы хотите, дайте мне знать, и я доберусь до него завтра.