2012-07-09 18 views
23

¿Qué quiero hacer?Calcular nuevas posiciones de degradado después de que el polígono rellena cambia las dimensiones

actualizaciones a esta pregunta: 7/10/2012 - "gradientTransform no del todo" Inspirado por Duopixel 7/11/2012 - "Código SVG del ejemplo" 7/16/2012 - "@dignifiedquire asumir esta problema "

Estoy tratando de crear una herramienta que permita al usuario cambiar el tamaño de polígonos de forma dinámica. La mayoría de los polígonos están rellenos con gradientes fill="url(#top_surface_1_gradient)". A mi modo de hacerlo es un simple script de JavaScript que:

  1. busca a mousemove & eventos de clic sobre un determinado polígono
  2. mide la cantidad de movimiento
  3. cambios de la mitad de las coordenadas del polígono (en para tener el efecto de estiramiento) el uso de este algoritmo para definir nuevas coordenadas: x = x_movement, y = x_movement * Math.tan(31 * (Math.PI/180))
  4. polígonos que están llenos de un solo color están bien
  5. polígonos que están llenos con un gradiente son n OT, déjame demostrar:

visualmente

step_1

Así que este es el primer paso, sin estiramiento que se ha hecho por parte del usuario.

step_2

Aquí es donde ocurre el problema. Como no sé cómo debo cambiar las coordenadas x1, y1 y x2, y2 para el degradado, simplemente permanece colgado en su posición anterior mientras el polígono se ha estirado. El resultado es una forma que no puede mantener la ilusión de profundidad.

step_3

El resultado final estoy buscando. Y sin olvidar que el gradiente podría tener un ángulo completamente aleatorio desde el principio. En este resultado final, que estoy buscando, se han cambiado las coordenadas x1, y1 y x2, y2 del degradado. ¿Qué algoritmo se debe usar para calcular estas posiciones? Estoy buscando una solución que sea completamente ciega al ángulo del gradiente.

a continuación es el SVG con todas las coordenadas apropiadas que se utilizó para generar estos ejemplos:

Uso de código SVG

Paso 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"/> 

Paso 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"/> 

Paso 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"/> 

He pasado innumerables horas desarrollando soluciones para este problema y simplemente no podía entenderlo. Cualquier ayuda sería muy apreciada.

Actualización: gradientTransform no del todo

Usando el atributo gradientTransform y sin x1, y1; Las coordenadas x2, y2 para el gradiente, logramos resultados que llenan el polígono de una manera casi igual a la necesaria (esta solución se puede encontrar aquí: http://jsfiddle.net/hqXx2/). El único lugar donde se rompe la solución es cuando el polígono se llena con un degradado que comienza fuera del polígono y/o termina en algún lugar fuera/dentro. Déjenme ilustrar:

Esto es lo que se logra con la solución que Duopixel sugiere. step_4

Este es el caso de uso que es imposible de lograr con la solución mencionada anteriormente. Cambié el color para amplificar visiblemente el ángulo y el gradiente. step_5

Código SVG del ejemplo

Aquí está el código para el grupo más grande, correctamente ampliada de polígonos:

<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> 

Y aquí está el código SVG para el más pequeño, lo que necesito para expandir:

<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 asumir este problema

he implementado @dignifiedquire sugirió algo en un sitio de prueba: Here's the test link. Hice la conversión absoluta a relativa por mi cuenta y solo muestra el mismo resultado que normalmente tendría al agregar los mismos valores de cambio xey del polígono al gradiente xey. Ese es el problema principal: ¿cómo traducir esos valores en tal valor, que transforman el gradiente como en mis ejemplos anteriores?

Se necesita más ayuda.

+0

Parece un problema espinoso. Sin embargo, tengo curiosidad por saber cómo entra PHP en la mezcla, ya que no veo ningún código PHP en absoluto, y solo algunas breves menciones de javascript. ¿Podrías ampliar esto? – GordonM

+0

Seguramente es complicado. Bueno, estoy codificando una solución dinámica (JS) y una de servidor (PHP). Como conozco y uso PHP para ese trabajo, lo elegí para esta pregunta. El mismo algoritmo se puede calcular utilizando JS y PHP. Así que imagina que almacenaré una cierta configuración en un DB, digamos 'new_width' = 'x + 100'. Necesitaré PHP para volver a dibujar el polígono utilizando el mismo algo una vez que el usuario vuelva a cargar la página o envíe un enlace a alguien. Espero que aclare un poco las cosas. –

+0

Un poco fuera de tema, pero si usa dos idiomas y uno de ellos admite hablar con un servidor y el otro admite ejecutarlo en un servidor, entonces tal vez pueda reducir la cantidad de trabajo que necesita realizar implementando solo el algoritmo en el lado del servidor y obtener resultados desde el lado del cliente con AJAX? – GordonM

Respuesta

4

Puede aplicar transformaciones a degradados, esto significa que puede hacer cosas como gradientTransform="rotate(45). Esto resuelve tu problema de rotación.

Debe usar unidades relativas y establecer el espacio de usuario en objectBoundingBox para que los valores x y y correspondan a las dimensiones de su polígono.Tu svg se vería así.

<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>​ 

Puede probar cómo funciona en diferentes polígonos de tamaño aquí: http://jsfiddle.net/hqXx2/

+0

Eso es cierto, pero solo si no hay necesidad de un ángulo. En mi caso, hay una necesidad para eso. Su solución crea un gradiente de 90 grados, que comienza con 0 puntos del polígono y termina con el punto final del polígono.Necesito desacoplar completamente el degradado del polígono en la fase de diseño, mientras que al mismo tiempo tengo un algoritmo para cambiar dinámicamente los degradados una vez desplegados dentro de la interfaz. Gracias por la idea :) –

+0

Aunque mi ejemplo y los gráficos relacionados muestran el gradiente para la superficie superior (I.E es bastante horizontal), hay otros polígonos que tienen gradientes con 45, 60 y otros ángulos gangosos. Aún un voto positivo. –

+0

En ese caso, te estás metiendo en algunas matemáticas bastante complicadas, deberías escalar tus elementos con una matriz de transformación y luego aplicar esa transformación a tu camino de polígono y tu gradiente. Agregué porcentajes a mi respuesta, quizás pueda encontrar una fórmula para convertir de grados a porcentajes. – Duopixel

7

Actualizar 3 idea alternativa

Una solución alternativa podría ser para calcular los valores de porcentaje sobre la base de los dos puntos finales del gradiente
En esta imagen puede ver el polígono original abcd, su cuadro delimitador a'b'c'd' y el degradado g1g2. El objetivo ahora es calcular primero los dos puntos g1 y g2 en valores absolutos y luego calcular los valores relativos de estos dos.

bounding box

me ha derivado un algoritmo que hace la mayor parte de lo que he descrito pero falla por debajo de cálculo de la intersección entre el gradiente y la caja de contorno. Tengo una idea sobre cómo resolver este problema, pero ahora no es el momento de implementarlo, así que enumero los pasos para eso.
La idea básica es comprobar si el degradado se cruza con una de las líneas de las esquinas del cuadro delimitador (a'b', b'c', c'd', d'a') y luego comprobar si la intersección está en el borde en cuestión. Ahora hay dos casos especiales que necesitan manejo. 1. el gradiente es vertical, esto significa que su pendiente es infinito 2. el lado en cuestión es vertical, otra vez esto significa que su pendiente es infinito Todos los demás casos son fáciles de resolver con matemática básica (forma de dos puntos de una línea, intersección de dos líneas).

Mi algoritmo

_ = 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); 
} 

Actualización 2

Pregunta: también cómo debe el gradiente coordina el cambio si el polígono se mueve en el espacio como un todo y no se estiró. ?

Respuesta: Calcula la cantidad que mueve un punto de su polígono en xey y mueve los puntos del degradado exactamente la misma cantidad.

He cambiado el algoritmo para que esté basado en escalar en un lado del polígono por cantidad absoluta de unidades. También he creado una imagen para explicar lo que hace el algoritmo

  1. polígono original,
  2. polígono escalado por un factor de escala determinado por las entradas
  3. mover el polígono de nuevo a su lugar original

explain

Actualización 15.7.2012 He derivado un algoritmo basado en la idea que propuse usar matrices de transformación para la transformación. No tuve tiempo de probarlo pero el código se está ejecutando bajo node.js y debería ejecutarse en el navegador si incluye underscore.js y sylvester (operaciones de matriz) en su documento.

El programa de instalación

/* underscore for some helper methods 
* http:*http:*underscorejs.org 
*/ 
_ = require("underscore"); 

/* matrix operations 
* http:*sylvester.jcoglan.com 
*/ 
require("sylvester"); 

Las entradas

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"]; 

el algoritmo de

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]; 

El resultado

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 
       } 
       } 

viejo respuesta

The image is here porque no puede subir imágenes a stackoverflow (reputación es a menor)

me extrajo el lado del polígono para que podamos enfocar en ese. La imagen de la izquierda está antes de escalar. Ahora he dibujado el degradado "completo" para mostrar lo que debe escalarse. Para descubrir las coordenadas necesarias, uno simplemente escala el cuadrado del degradado en la misma proporción que el lado del polígono.

Sé que esta imagen no tiene rotación, pero este método se puede ampliar para incorporarlo también.

Puedo obtener un algoritmo para esto pero no he tenido el tiempo para hacerlo. Entonces, si esto es lo que quieres, házmelo saber y lo abordaré mañana.

+0

Así que hay un par de preguntas con respecto a la respuesta propuesta: 1. ¿El polígono realmente se estira solo en el eje vertical 2. Qué sucedería si el mismo modelo se estirara en el eje horizontal 3. Prueba de concepto para una rotación gradiente. Si esos 3 echan un vistazo, ¡felicítense! Esperando ansiosamente su opinión sobre ellos y, por supuesto, el algo. Voto por el esfuerzo. –

+0

La primera impresión se ve muy bien (no he podido implementarla aún), haré el control completo y le responderé hoy. Increíble trabajo de todos modos! –

+0

Right @dignifiedquire Estoy tratando de ajustar el degradado de acuerdo con la cantidad de movimiento del mouse. ¿Podría señalar cómo se relaciona eso con la escala X y la escala Y? Porque los entiendo como valores relativos, pero ¿cómo obtengo valores absolutos, como scaleX + = 10. –

Cuestiones relacionadas