Encontrar si un punto se encuentra dentro de un rectángulo o no


82

Quiero saber si un punto se encuentra dentro de un rectángulo o no. El rectángulo se puede orientar de cualquier forma y no es necesario alinear el eje.

Un método que se me ocurrió fue rotar el rectángulo y las coordenadas del punto para alinear el eje del rectángulo y luego simplemente probar las coordenadas del punto, ya sea que se encuentren dentro de las del rectángulo o no.

El método anterior requiere rotación y, por tanto, operaciones de coma flotante. ¿Existe alguna otra forma eficaz de hacer esto?


Puede hacer una verificación rápida para ver si el punto cayó en el cuadro delimitador ortogonal del rectángulo girado, y falla rápidamente si no. (Sí, eso es solo la mitad de una respuesta (hmmm, hay tres cajas ortogonales que se pueden formar con los puntos de las esquinas ... y es tarde (la geometría conceptual es la primera en desaparecer))).
msw

Pero tengo que rotar primero, ¿verdad?
avd

1
Hasta que nos diga cómo se define su rectángulo, no habrá mucho valor práctico en las respuestas. Cuando trabaja con coordenadas enteras, el método utilizado para representar la forma tiene una importancia crítica al seleccionar el algoritmo.
2010

Respuestas:


80

¿Cómo se representa el rectángulo? ¿Tres puntos? ¿Cuatro puntos? ¿Punto, lados y ángulo? ¿Dos puntos y un lado? ¿Algo más? Sin saberlo, cualquier intento de responder a su pregunta tendrá un valor puramente académico.

En cualquier caso, para cualquier polígono convexo (incluido el rectángulo), la prueba es muy simple: verifique cada borde del polígono, asumiendo que cada borde está orientado en dirección contraria a las agujas del reloj, y pruebe si el punto está a la izquierda del borde (a la izquierda -medio plano de la mano). Si todos los bordes pasan la prueba, el punto está adentro. Si al menos uno falla, el punto está afuera.

Para probar si el punto se (xp, yp)encuentra en el lado izquierdo del borde (x1, y1) - (x2, y2), solo necesita calcular

D = (x2 - x1) * (yp - y1) - (xp - x1) * (y2 - y1)

Si D > 0, el punto está en el lado izquierdo. Si D < 0, el punto está en el lado derecho. Si D = 0, el punto está en la línea.


La versión anterior de esta respuesta describía una versión aparentemente diferente de la prueba del lado izquierdo (ver más abajo). Pero se puede demostrar fácilmente que calcula el mismo valor.

... Para probar si el punto se (xp, yp)encuentra en el lado izquierdo del borde (x1, y1) - (x2, y2), necesita construir la ecuación de la línea que contiene el borde. La ecuación es la siguiente

A * x + B * y + C = 0

dónde

A = -(y2 - y1)
B = x2 - x1
C = -(A * x1 + B * y1)

Ahora todo lo que necesita hacer es calcular

D = A * xp + B * yp + C

Si D > 0, el punto está en el lado izquierdo. Si D < 0, el punto está en el lado derecho. Si D = 0, el punto está en la línea.

Sin embargo, esta prueba, nuevamente, funciona para cualquier polígono convexo, lo que significa que podría ser demasiado genérico para un rectángulo. Un rectángulo podría permitir una prueba más simple ... Por ejemplo, en un rectángulo (o en cualquier otro paralelogramo) los valores de Ay Btienen la misma magnitud pero diferentes signos para los bordes opuestos (es decir, paralelos), que se pueden aprovechar para simplificar la prueba .


Eso es cierto solo para el conjunto de coordenadas matemático. En la pantalla de la PC y para el GPS, las direcciones del eje son diferentes y no puede estar seguro de tener el conjunto correcto de inecuaciones. O puede estar seguro de que no lo ha hecho. Mi respuesta es mejor :-).
Gangnus

AndreyT @Gangnus, precisión rápida, solo necesitas comprobar que el signo de la ecuación es el mismo para todos los puntos de la forma convexa en relación a P, esto te permite no preocuparte por los sistemas de coordenadas o en qué dirección está tu forma convexa definido;))
Jason Rogers

2
Hay un par de extensiones para esto que le permiten acelerar las cosas. 1. Dado que los dos lados opuestos del rectángulo son paralelos, sus coeficientes A, B pueden ser los mismos. 2. Dado que los otros dos lados son perpendiculares a estos, sus coeficientes A'y B'pueden estar dados por A'=By B'=-A. 3. No tiene sentido calcular A xp + B ypambos bordes, así que combínelos en una sola prueba. Entonces tu prueba de estar en un rectángulo se convierte en (v_min < A xp + B yp < v_max) && ( w_min < B xp - A yp < w_max ).
Michael Anderson

@MichaelAnderson ¿Y qué son v_min, etc.?
Anónimo

v_mines el valor mínimo de A x + B ypara todos los valores en el interior del rectángulo (que es el valor mínimo cuando se evalúa en las esquinas). v_maxes el máximo correspondiente. Los w_?valores son los mismos, pero para Bx - A y.
Michael Anderson

41

Suponiendo que el rectángulo está representado por tres puntos A, B, C, con AB y BC perpendiculares, solo necesita verificar las proyecciones del punto de consulta M en AB y BC:

0 <= dot(AB,AM) <= dot(AB,AB) &&
0 <= dot(BC,BM) <= dot(BC,BC)

ABes vector AB, con coordenadas (Bx-Ax, Por-Ay), y dot(U,V)es el producto escalar de vectores U y V: Ux*Vx+Uy*Vy.

Actualizar . Tomemos un ejemplo para ilustrar esto: A (5,0) B (0,2) C (1,5) y D (6,3). De las coordenadas del punto, obtenemos AB = (- 5,2), BC = (1,3), punto (AB, AB) = 29, punto (BC, BC) = 10.

Para el punto de consulta M (4,2), tenemos AM = (- 1,2), BM = (4,0), punto (AB, AM) = 9, punto (BC, BM) = 4. M está dentro del rectángulo.

Para el punto de consulta P (6,1), tenemos AP = (1,1), BP = (6, -1), punto (AB, AP) = - 3, punto (BC, BP) = 3. P no está dentro del rectángulo, porque su proyección en el lado AB no está dentro del segmento AB.


1
0,2 - 10,2 - 10,10 - 2,10 no es un rectángulo.
Eric Bainville

2
Trace los puntos y considere verificar la precisión de su primer comentario.
Eric Bainville

3
¡Esta es la mejor respuesta!
Tahlil

1
Me gusta que esta respuesta sea concisa, mantenga el número de operaciones más o menos tan bajo como las otras buenas respuestas aquí, pero también tiene la ventaja de ser muy intuitivo y visualizable.
Anónimo

22

He tomado prestada la respuesta de Eric Bainville:

0 <= dot(AB,AM) <= dot(AB,AB) && 0 <= dot(BC,BM) <= dot(BC,BC)

Que en javascript se ve así:

function pointInRectangle(m, r) {
    var AB = vector(r.A, r.B);
    var AM = vector(r.A, m);
    var BC = vector(r.B, r.C);
    var BM = vector(r.B, m);
    var dotABAM = dot(AB, AM);
    var dotABAB = dot(AB, AB);
    var dotBCBM = dot(BC, BM);
    var dotBCBC = dot(BC, BC);
    return 0 <= dotABAM && dotABAM <= dotABAB && 0 <= dotBCBM && dotBCBM <= dotBCBC;
}

function vector(p1, p2) {
    return {
            x: (p2.x - p1.x),
            y: (p2.y - p1.y)
    };
}

function dot(u, v) {
    return u.x * v.x + u.y * v.y; 
}

p.ej:

var r = {
    A: {x: 50, y: 0},
    B: {x: 0, y: 20},
    C: {x: 10, y: 50},
    D: {x: 60, y: 30}
};

var m = {x: 40, y: 20};

entonces:

pointInRectangle(m, r); // returns true.

Aquí hay un codepen para dibujar la salida como una prueba visual :) http://codepen.io/mattburns/pen/jrrprN

ingrese la descripción de la imagen aquí


Hola @matt burns. Usé tu método y lo puse en un proyecto de prueba mío: jsfiddle.net/caymanbruce/06wjp2sk/6 Pero no pude hacerlo funcionar. No tengo idea de por qué todavía está probando el punto dentro del rectángulo original sin rotación. Utilizo un mouseoverevento en mi proyecto, por lo que siempre que el mouse esté sobre el punto que se supone que está dentro del rectángulo, mostrará un punto circular negro alrededor del mouse, y fuera del rectángulo no debería mostrar nada. Necesito ayuda para que funcione, pero estoy muy confundido.
newguy

mouseoverdebería ser mousemove, solo error tipográfico.
newguy


En principio, su método es correcto, pero su rectángulo no es un rectángulo en su ejemplo. He hecho una versión mejorada de la suya aquí donde me apego a la fórmula original y al esquema de nomenclatura, y donde la entrada es de hecho un verdadero rectángulo.
JohannesB

15
# Pseudo code
# Corners in ax,ay,bx,by,dx,dy
# Point in x, y

bax = bx - ax
bay = by - ay
dax = dx - ax
day = dy - ay

if ((x - ax) * bax + (y - ay) * bay < 0.0) return false
if ((x - bx) * bax + (y - by) * bay > 0.0) return false
if ((x - ax) * dax + (y - ay) * day < 0.0) return false
if ((x - dx) * dax + (y - dy) * day > 0.0) return false

return true

Lea esto como: "si conectamos el punto a tres vértices del rectángulo, entonces los ángulos entre esos segmentos y lados deben ser agudos"
P Shved

3
El problema con enfoques como este es que funcionan en teoría, pero en la práctica uno podría tener problemas. El OP no dijo cómo se representa el rectángulo. Esta respuesta supone que está representada por tres puntos - a, by d. Si bien tres puntos es una forma válida de representar un rectángulo arbitrario en teoría, en la práctica es imposible hacerlo con precisión en coordenadas enteras en el caso general. En el caso general, uno terminará con un paralelogramo, que está muy cerca de un rectángulo pero aún no es un rectángulo.
2010

3
Es decir, los ángulos en esa forma no serán exactamente de 90 grados. Hay que tener mucho cuidado al realizar pruebas basadas en ángulos en tal situación. Nuevamente, depende de cómo el OP define "adentro" para un "rectángulo" representado de manera imprecisa. Y, nuevamente, sobre cómo se representa el rectángulo.
2010

+1 a sus dos comentarios. Solo @avd puede decirnos si esto es lo suficientemente bueno.
Jonas Elfström

Funciona perfectamente para mí ... Usando trigonometría y geometría con bastante frecuencia, es bueno no tener que encontrar una fórmula para resolver un problema común. Gracias.
sq2

12

Me doy cuenta de que este es un hilo antiguo, pero para cualquiera que esté interesado en ver esto desde una perspectiva puramente matemática, hay un hilo excelente en el intercambio de pila de matemáticas, aquí:

/math/190111/how-to-check-if-a-point-is-inside-a-rectangle

Editar: Inspirado por este hilo, he creado un método simple de vectores para determinar rápidamente dónde se encuentra su punto.

Suponga que tiene un rectángulo con puntos en p1 = (x1, y1), p2 = (x2, y2), p3 = (x3, y3) y p4 = (x4, y4), en sentido horario. Si un punto p = (x, y) se encuentra dentro del rectángulo, entonces el producto escalar (p - p1). (P2 - p1) estará entre 0 y | p2 - p1 | ^ 2, y (p - p1). (p4 - p1) estará entre 0 y | p4 - p1 | ^ 2. Esto equivale a tomar la proyección del vector p - p1 a lo largo y ancho del rectángulo, con p1 como origen.

Esto puede tener más sentido si muestro un código equivalente:

p21 = (x2 - x1, y2 - y1)
p41 = (x4 - x1, y4 - y1)

p21magnitude_squared = p21[0]^2 + p21[1]^2
p41magnitude_squared = p41[0]^2 + p41[1]^2

for x, y in list_of_points_to_test:

    p = (x - x1, y - y1)

    if 0 <= p[0] * p21[0] + p[1] * p21[1] <= p21magnitude_squared:
        if 0 <= p[0] * p41[0] + p[1] * p41[1]) <= p41magnitude_squared:
            return "Inside"
        else:
            return "Outside"
    else:
        return "Outside"

Y eso es. También funcionará para paralelogramos.


¿Puede resumir la discusión hasta ahora? De lo contrario, probablemente debería haber sido un comentario, no una respuesta. Con un poco más de reputación, podrá publicar comentarios .
Nathan Tuggy

7
bool pointInRectangle(Point A, Point B, Point C, Point D, Point m ) {
    Point AB = vect2d(A, B);  float C1 = -1 * (AB.y*A.x + AB.x*A.y); float  D1 = (AB.y*m.x + AB.x*m.y) + C1;
    Point AD = vect2d(A, D);  float C2 = -1 * (AD.y*A.x + AD.x*A.y); float D2 = (AD.y*m.x + AD.x*m.y) + C2;
    Point BC = vect2d(B, C);  float C3 = -1 * (BC.y*B.x + BC.x*B.y); float D3 = (BC.y*m.x + BC.x*m.y) + C3;
    Point CD = vect2d(C, D);  float C4 = -1 * (CD.y*C.x + CD.x*C.y); float D4 = (CD.y*m.x + CD.x*m.y) + C4;
    return     0 >= D1 && 0 >= D4 && 0 <= D2 && 0 >= D3;}





Point vect2d(Point p1, Point p2) {
    Point temp;
    temp.x = (p2.x - p1.x);
    temp.y = -1 * (p2.y - p1.y);
    return temp;}

Puntos dentro del polígono

Acabo de implementar la respuesta de AnT usando c ++. Usé este código para verificar si la coordinación del píxel (X, Y) se encuentra dentro de la forma o no.


Sería realmente útil alguna explicación de lo que estás haciendo aquí.
Brad

Sólo quería decir gracias. Convertí lo que tenías que trabajar para un Unity Shader y lo reduje por 3 puntos en lugar de 4. ¡Funcionó bien! Salud.
Dustin Jensen

Funcionó para mí, aquí hay una implementación en C # que hice para Unity DOTS: gist.github.com/rgoupil/04b59be8ddb56c992f25e1489c61b310
JamesDev

6

Si no puede resolver el problema del rectángulo, intente dividir el problema en problemas más fáciles. Divide el rectángulo en 2 triángulos y comprueba si el punto está dentro de alguno de ellos como explican aquí

Esencialmente, recorre los bordes en cada dos pares de líneas desde un punto. Luego, use el producto cruzado para verificar si el punto está entre las dos líneas usando el producto cruzado. Si se verifica para los 3 puntos, entonces el punto está dentro del triángulo. Lo bueno de este método es que no crea ningún error de punto flotante que ocurre si verifica los ángulos.


Es un algoritmo correcto, pero terriblemente ineficaz.
Gangnus

4

Si un punto está dentro de un rectángulo. En un avión. Para coordenadas de matemático o geodesia (GPS)

  • Sea el rectángulo formado por los vértices A, B, C, D. El punto es P. Las coordenadas son rectangulares: x, y.
  • Prolonguemos los lados del rectángulo. Así que tenemos 4 líneas rectas l AB , l BC , l CD , l DA o, para abreviar, l 1 , l 2 , l 3 , l 4 .
  • Haz una ecuación para cada l i . El tipo de ecuación:

    f yo (P) = 0.

P es un punto. Para los puntos pertenecientes a l i , la ecuación es verdadera.

  • Necesitamos las funciones en el lado izquierdo de las ecuaciones. Son f 1 , f 2 , f 3 , f 4 .
  • Observe que para cada punto de un lado de l i la función f i es mayor que 0, para los puntos del otro lado f i es menor que 0.
  • Entonces, si estamos verificando que P esté en un rectángulo, solo necesitamos que p esté en los lados correctos de las cuatro líneas. Entonces, tenemos que verificar cuatro funciones para sus signos.
  • Pero, ¿qué lado de la línea es el correcto, al que pertenece el rectángulo? Es el lado, donde se encuentran los vértices del rectángulo que no pertenecen a la línea. Para verificar, podemos elegir cualquiera de los dos vértices que no pertenecen.
  • Entonces, tenemos que verificar esto:

    f AB (P) f AB (C)> = 0

    f BC (P) f BC (D)> = 0

    f CD (P) f CD (A)> = 0

    f DA (P) f DA (B)> = 0

Las no ecuaciones no son estrictas, porque si un punto está en el borde, también pertenece al rectángulo. Si no necesita puntos en el borde, puede cambiar las inecuaciones por estrictas. Pero mientras trabaja en operaciones de punto flotante, la elección es irrelevante.

  • Para un punto, que está en el rectángulo, las cuatro inecuaciones son verdaderas. Tenga en cuenta que también funciona para cada polígono convexo, solo diferirá el número de líneas / ecuaciones.
  • Lo único que queda es obtener una ecuación para una línea que pasa por dos puntos. Es una ecuación lineal muy conocida. Escribámoslo para una recta AB y un punto P:

    f AB (P) ≡ (x A -x B ) (y P -y B ) - (y A -y B ) (x P -x B )

La verificación podría simplificarse - vayamos a lo largo del rectángulo en el sentido de las agujas del reloj - A, B, C, D, A. Entonces todos los lados correctos estarán a la derecha de las líneas. Entonces, no necesitamos comparar con el lado donde está otro vértice. Y necesitamos comprobar un conjunto de inecuaciones más cortas:

f AB (P)> = 0

f BC (P)> = 0

f CD (P)> = 0

f DA (P)> = 0

Pero esto es correcto para el conjunto de coordenadas normal, matemático (de la escuela de matemáticas), donde X está a la derecha e Y a la parte superior. Y para las coordenadas de geodesia , como se usan en GPS, donde X está arriba e Y está a la derecha, tenemos que girar las inecuaciones:

f AB (P) <= 0

f BC (P) <= 0

f CD (P) <= 0

f DA (P) <= 0

Si no está seguro con las direcciones de los ejes, tenga cuidado con esta verificación simplificada: busque un punto con la ubicación conocida, si ha elegido las inecuaciones correctas.


"donde X está en la parte superior e Y está a la derecha, tenemos que girar las inecuaciones:" Eso depende de cómo se determina "en el sentido de las agujas del reloj". Si considera que las coordenadas son matemáticas, el sentido de las agujas del reloj se convertirá automáticamente en el sentido contrario a las agujas del reloj, y aún puede usar el primer conjunto de desigualdades. En otras palabras, puede ir bien sin este lío si solo considera que las coordenadas son matemáticas en todos los aspectos. Invertir o intercambiar las coordenadas no tendrá ningún efecto sobre este predicado.
Palo

@Palo Mathematics no tiene orientación por sí misma. Si. Pero el algoritmo tiene varios puntos y sería mucho mejor tener resultados comprensibles y sensibles (en la vida real) en cualquiera de sus puntos, para poder probar. Sin eso hasta el final de su segunda oración, difícilmente puede verificar si está resolviendo las inecuaciones correctamente, usando su imaginación espacial.
Gangnus

0

La forma más fácil en la que pensé fue simplemente proyectar el punto sobre el eje del rectángulo. Dejame explicar:

Si puede obtener el vector desde el centro del rectángulo hasta el borde superior o inferior y el borde izquierdo o derecho. Y también tiene un vector desde el centro del rectángulo hasta su punto, puede proyectar ese punto en sus vectores de ancho y alto.

P = vector de puntos, H = vector de altura, W = vector de ancho

Obtenga el vector unitario W ', H' dividiendo los vectores por su magnitud

proj_P, H = P - (P.H ') H' proj_P, W = P - (P.W ') W'

A menos que me equivoque, lo cual no creo que esté ... (corríjame si me equivoco) pero si la magnitud de la proyección de su punto en el vector de altura es menor que la magnitud del vector de altura (que es la mitad de la altura del rectángulo) y la magnitud de la proyección de su punto en el vector de ancho es, entonces tiene un punto dentro de su rectángulo.

Si tiene un sistema de coordenadas universal, es posible que deba calcular los vectores de altura / ancho / punto mediante la resta de vectores. ¡Las proyecciones vectoriales son increíbles! recuérdalo.


0

En la continuación matts responde. Necesitamos usar la solución /math/190111/how-to-check-if-a-point-is-inside-a-rectangle/190373#190373 para que funcione

A continuación no funciona
0 <= punto (AB, AM) <= punto (AB, AB) && 0 <= punto (BC, BM) <= punto (BC, BC)

A continuación funciona
0 <= punto (AB, AM) <= punto (AB, AB) && 0 <= punto (AM, AC) <= punto (AC, AC)

verifica pegando a continuación en la consola javascript // solución Javascript para la misma

            var screenWidth = 320;
            var screenHeight = 568;
            var appHeaderWidth = 320;
            var AppHeaderHeight = 65;
            var regionWidth = 200;
            var regionHeight = 200;

            this.topLeftBoundary = {
                A: {x: 0, y: AppHeaderHeight},
                B: {x: regionWidth, y: AppHeaderHeight},
                C: {x: 0, y: regionHeight + AppHeaderHeight},
                D: {x: regionWidth, y: regionHeight + AppHeaderHeight}
            }

            this.topRightBoundary = {
                A: {x: screenWidth, y: AppHeaderHeight},
                B: {x: screenWidth - regionWidth, y: AppHeaderHeight},
                C: {x: screenWidth, y: regionHeight + AppHeaderHeight},
                D: {x: screenWidth - regionWidth, y: regionHeight + AppHeaderHeight}
            }

            this.bottomRightBoundary = {
                A: {x: screenWidth, y: screenHeight},
                B: {x: screenWidth - regionWidth, y: screenHeight},
                C: {x: screenWidth, y: screenHeight - regionHeight},
                D: {x: screenWidth - regionWidth, y: screenHeight - regionHeight}
            }

            this.bottomLeftBoundary = {
                A: {x: 0, y: screenHeight},
                B: {x: regionWidth, y: screenHeight},
                C: {x: 0, y: screenHeight - regionHeight},
                D: {x: regionWidth, y: screenHeight - regionHeight}
            }
            console.log(this.topLeftBoundary);
            console.log(this.topRightBoundary);
            console.log(this.bottomRightBoundary);
            console.log(this.bottomLeftBoundary);

            checkIfTapFallsInBoundary = function (region, point) {
                console.log("region " + JSON.stringify(region));
                console.log("point" + JSON.stringify(point));

                var r = region;
                var m = point;

                function vector(p1, p2) {
                    return {
                        x: (p2.x - p1.x),
                        y: (p2.y - p1.y)
                    };
                }

                function dot(u, v) {
                    console.log("DOT " + (u.x * v.x + u.y * v.y));
                    return u.x * v.x + u.y * v.y;
                }

                function pointInRectangle(m, r) {
                    var AB = vector(r.A, r.B);
                    var AM = vector(r.A, m);
                    var AC = vector(r.A, r.C);
                    var BC = vector(r.B, r.C);
                    var BM = vector(r.B, m);

                    console.log("AB " + JSON.stringify(AB));
                    console.log("AM " + JSON.stringify(AM));
                    console.log("AM " + JSON.stringify(AC));
                    console.log("BC " + JSON.stringify(BC));
                    console.log("BM " + JSON.stringify(BM));

                    var dotABAM = dot(AB, AM);
                    var dotABAB = dot(AB, AB);
                    var dotBCBM = dot(BC, BM);
                    var dotBCBC = dot(BC, BC);
                    var dotAMAC = dot(AM, AC);
                    var dotACAC = dot(AC, AC);

                    console.log("ABAM " + JSON.stringify(dotABAM));
                    console.log("ABAB " + JSON.stringify(dotABAB));
                    console.log("BCBM " + JSON.stringify(dotBCBM));
                    console.log("BCBC " + JSON.stringify(dotBCBC));
                    console.log("AMAC " + JSON.stringify(dotAMAC));
                    console.log("ACAC" + JSON.stringify(dotACAC));

                    var check = ((0 <= dotABAM && dotABAM <= dotABAB) && (0 <= dotBCBM && dotBCBM <= dotBCBC));
                    console.log(" first check" + check);
                    var check = ((0 <= dotABAM && dotABAM <= dotABAB) && (0 <= dotAMAC && dotAMAC <= dotACAC));
                    console.log("second check" + check);
                    return check;
                }

                return pointInRectangle(m, r);
            }

        //var point = {x: 136, y: 342};

            checkIfTapFallsInBoundary(topLeftBoundary, {x: 136, y: 342});
            checkIfTapFallsInBoundary(topRightBoundary, {x: 136, y: 274});
            checkIfTapFallsInBoundary(bottomRightBoundary, {x: 141, y: 475});
            checkIfTapFallsInBoundary(bottomRightBoundary, {x: 131, y: 272});
            checkIfTapFallsInBoundary(bottomLeftBoundary, {x: 131, y: 272});
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.