Подтвердить что ты не робот

Вычислять новые градиентные позиции после того, как многоугольник заполняет изменения размеров

Что я хочу сделать?

Обновления по этому вопросу: 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. Это основная проблема - как преобразовать эти значения в такое значение, которое преобразует градиент, как в моих примерах выше?

Требуется дополнительная помощь.

4b9b3361

Ответ 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 (репутация низка)

Я отвлек сторону многоугольника, чтобы мы могли сосредоточиться на этом. Левое изображение перед масштабированием. Теперь я нарисовал "весь" градиент, чтобы показать, что нужно масштабировать. Чтобы вычислить нужные координаты, просто масштабируется квадрат градиента в той же пропорции, что и сторона многоугольника.

Я знаю, что это изображение без вращения, но этот метод можно расширить, чтобы включить его.

Я могу выработать алгоритм для этого, но у меня не было времени для этого. Поэтому, если это то, что вы хотите, дайте мне знать, и я доберусь до него завтра.