/*
* Copyright 2013 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.rendering.nui.internal;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.terasology.math.geom.Rect2i;
import org.terasology.math.geom.Vector2i;
import org.terasology.rendering.nui.Color;
import java.nio.FloatBuffer;
import java.util.Objects;
/**
*
*/
public final class Line {
private Line() {
}
/**
* Draws a 2D line segment in OpenGL.
*
* @param x1 The X-coordinate of the segment's start point.
* @param y1 The Y-coordinate of the segment's start point.
* @param x2 The X-coordinate of the segment's end point.
* @param y2 The Y-coordinate of the segment's end point.
* @param width Thickness of the line in pixels.
* @param color The line color.
* @param background The background color. Ignored if alpha blending is used.
* @param alpha The alpha channel. If set to 0, alpha blending is not used.
* @see <a href="http://artgrammer.blogspot.de/2011/05/drawing-nearly-perfect-2d-line-segments.html">
* Drawing nearly perfect 2D line segments in OpenGL
* </a>
*/
public static void draw(float x1, float y1, float x2, float y2, float width, Color color, Color background, float alpha) {
GL20.glUseProgram(0);
GL11.glDisable(GL11.GL_CULL_FACE);
GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GL11.glEnableClientState(GL11.GL_COLOR_ARRAY);
float t = 0;
float r = 0;
float f = width - (int) width;
float a;
boolean alphaBlend = alpha > 0;
float cRed = color.rf();
float cGreen = color.gf();
float cBlue = color.bf();
float bRed = background.rf();
float bGreen = background.gf();
float bBlue = background.bf();
if (alphaBlend) {
a = alpha;
} else {
a = 1.f;
}
if (width >= 0.0 && width < 1.0) {
t = 0.05f;
r = 0.48f + 0.32f * f;
if (!alphaBlend) {
cRed += 0.88 * (1 - f);
cGreen += 0.88 * (1 - f);
cBlue += 0.88 * (1 - f);
if (cRed > 1.0f) {
cRed = 1.0f;
}
if (cGreen > 1.0f) {
cGreen = 1.0f;
}
if (cBlue > 1.0f) {
cBlue = 1.0f;
}
} else {
a *= f;
}
} else if (width >= 1.0 && width < 2.0) {
t = 0.05f + f * 0.33f;
r = 0.768f + 0.312f * f;
} else if (width >= 2.0 && width < 3.0) {
t = 0.38f + f * 0.58f;
r = 1.08f;
} else if (width >= 3.0 && width < 4.0) {
t = 0.96f + f * 0.48f;
r = 1.08f;
} else if (width >= 4.0 && width < 5.0) {
t = 1.44f + f * 0.46f;
r = 1.08f;
} else if (width >= 5.0 && width < 6.0) {
t = 1.9f + f * 0.6f;
r = 1.08f;
} else if (width >= 6.0) {
float ff = width - 6.0f;
t = 2.5f + ff * 0.50f;
r = 1.08f;
}
//determine angle of the line to horizontal
float tx = 0; //core thinkness of a line
float ty = 0;
float rx = 0; //fading edge of a line
float ry = 0;
float cx = 0; //cap of a line
float cy = 0;
float epsilon = 0.01f;
float dx = x2 - x1;
float dy = y2 - y1;
if (Math.abs(dx) < epsilon) {
//vertical
tx = t;
ty = 0;
rx = r;
ry = 0;
if (width > 0.0 && width < 1.0) {
tx *= 8;
} else if (width == 1.0) {
tx *= 10;
}
} else if (Math.abs(dy) < epsilon) {
//horizontal
tx = 0;
ty = t;
rx = 0;
ry = r;
if (width > 0.0 && width < 1.0) {
ty *= 8;
} else if (width == 1.0) {
ty *= 10;
}
} else {
if (width < 3) { //approximate to make things even faster
float m = dy / dx;
//and calculate tx,ty,rx,ry
if (m > -0.4142 && m <= 0.4142) {
// -22.5< angle <= 22.5, approximate to 0 (degree)
tx = t * 0.1f;
ty = t;
rx = r * 0.6f;
ry = r;
} else if (m > 0.4142 && m <= 2.4142) {
// 22.5< angle <= 67.5, approximate to 45 (degree)
tx = t * -0.7071f;
ty = t * 0.7071f;
rx = r * -0.7071f;
ry = r * 0.7071f;
} else if (m > 2.4142 || m <= -2.4142) {
// 67.5 < angle <=112.5, approximate to 90 (degree)
tx = t;
ty = t * 0.1f;
rx = r;
ry = r * 0.6f;
} else if (m > -2.4142 && m < -0.4142) {
// 112.5 < angle < 157.5, approximate to 135 (degree)
tx = t * 0.7071f;
ty = t * 0.7071f;
rx = r * 0.7071f;
ry = r * 0.7071f;
}
} else { //calculate to exact
dx = y1 - y2;
dy = x2 - x1;
float len = (float) Math.sqrt(dx * dx + dy * dy);
dx /= len;
dy /= len;
cx = -0.6f * dy;
cy = 0.6f * dx;
tx = t * dx;
ty = t * dy;
rx = r * dx;
ry = r * dy;
}
}
//draw the line by triangle strip
float[] lineVertex =
{
x1 - tx - rx, y1 - ty - ry, //fading edge1
x2 - tx - rx, y2 - ty - ry,
x1 - tx, y1 - ty, //core
x2 - tx, y2 - ty,
x1 + tx, y1 + ty,
x2 + tx, y2 + ty,
x1 + tx + rx, y1 + ty + ry, //fading edge2
x2 + tx + rx, y2 + ty + ry
};
GL11.glVertexPointer(2, 0, wrap(lineVertex));
if (!alphaBlend) {
float[] lineColor =
{
bRed, bGreen, bBlue,
bRed, bGreen, bBlue,
cRed, cGreen, cBlue,
cRed, cGreen, cBlue,
cRed, cGreen, cBlue,
cRed, cGreen, cBlue,
bRed, bGreen, bBlue,
bRed, bGreen, bBlue
};
GL11.glColorPointer(3, 0, wrap(lineColor));
} else {
float[] lineColor =
{
cRed, cGreen, cBlue, 0,
cRed, cGreen, cBlue, 0,
cRed, cGreen, cBlue, a,
cRed, cGreen, cBlue, a,
cRed, cGreen, cBlue, a,
cRed, cGreen, cBlue, a,
cRed, cGreen, cBlue, 0,
cRed, cGreen, cBlue, 0
};
GL11.glColorPointer(4, 0, wrap(lineColor));
}
if ((Math.abs(dx) < epsilon || Math.abs(dy) < epsilon) && width <= 1.0) {
GL11.glDrawArrays(GL11.GL_TRIANGLE_STRIP, 0, 6);
} else {
GL11.glDrawArrays(GL11.GL_TRIANGLE_STRIP, 0, 8);
}
//cap (do not draw if too thin)
if (width >= 3) {
//draw cap
lineVertex = new float[]
{
x1 - rx + cx, y1 - ry + cy, //cap1
x1 + rx + cx, y1 + ry + cy,
x1 - tx - rx, y1 - ty - ry,
x1 + tx + rx, y1 + ty + ry,
x2 - rx - cx, y2 - ry - cy, //cap2
x2 + rx - cx, y2 + ry - cy,
x2 - tx - rx, y2 - ty - ry,
x2 + tx + rx, y2 + ty + ry
};
GL11.glVertexPointer(2, 0, wrap(lineVertex));
if (!alphaBlend) {
float[] lineColor =
{
bRed, bGreen, bBlue, //cap1
bRed, bGreen, bBlue,
cRed, cGreen, cBlue,
cRed, cGreen, cBlue,
bRed, bGreen, bBlue, //cap2
bRed, bGreen, bBlue,
cRed, cGreen, cBlue,
cRed, cGreen, cBlue
};
GL11.glColorPointer(3, 0, wrap(lineColor));
} else {
float[] lineColor =
{
cRed, cGreen, cBlue, 0, //cap1
cRed, cGreen, cBlue, 0,
cRed, cGreen, cBlue, a,
cRed, cGreen, cBlue, a,
cRed, cGreen, cBlue, 0, //cap2
cRed, cGreen, cBlue, 0,
cRed, cGreen, cBlue, a,
cRed, cGreen, cBlue, a
};
GL11.glColorPointer(4, 0, wrap(lineColor));
}
GL11.glDrawArrays(GL11.GL_TRIANGLE_STRIP, 0, 4);
GL11.glDrawArrays(GL11.GL_TRIANGLE_STRIP, 4, 4);
}
GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GL11.glDisableClientState(GL11.GL_COLOR_ARRAY);
GL11.glEnable(GL11.GL_CULL_FACE);
}
private static FloatBuffer wrap(float[] data) {
FloatBuffer buf = BufferUtils.createFloatBuffer(data.length);
buf.put(data);
buf.rewind();
return buf;
}
public static LineCoordinates getLineCoordinates(int startX, int startY, int endX, int endY, Rect2i baseRegion, Rect2i cropRegion) {
Rect2i region = Rect2i.createFromMinAndMax(Math.min(startX, endX), Math.min(startY, endY),
Math.max(startX, endX), Math.max(startY, endY));
Rect2i absoluteRegion = relativeToAbsolute(region, baseRegion);
Rect2i finalRegion = cropRegion.intersect(absoluteRegion);
if (!finalRegion.isEmpty()) {
int sx = startX > endX ? finalRegion.maxX() : finalRegion.minX();
int sy = startY > endY ? finalRegion.maxY() : finalRegion.minY();
int ex = startX > endX ? finalRegion.minX() : finalRegion.maxX();
int ey = startY > endY ? finalRegion.minY() : finalRegion.maxY();
return new LineCoordinates(new Vector2i(sx, sy), new Vector2i(ex, ey));
} else {
return null;
}
}
public static Rect2i relativeToAbsolute(Rect2i region, Rect2i baseRegion) {
return Rect2i.createFromMinAndSize(region.minX() + baseRegion.minX(), region.minY() + baseRegion.minY(), region.width(), region.height());
}
/**
* Helper class that wraps a line's start and end points.
*/
public static class LineCoordinates {
/**
* The start point.
*/
private Vector2i start;
/**
* The end point.
*/
private Vector2i end;
public LineCoordinates(Vector2i start, Vector2i end) {
this.start = start;
this.end = end;
}
public Vector2i getStart() {
return this.start;
}
public Vector2i getEnd() {
return this.end;
}
@Override
public int hashCode() {
return Objects.hash(this.start, this.end);
}
@Override
public boolean equals(Object o) {
if (o == null || !(o instanceof LineCoordinates)) {
return false;
}
if (o == this) {
return true;
}
LineCoordinates other = (LineCoordinates) o;
return this.start.equals(other.start) && this.end.equals(other.end);
}
@Override
public String toString() {
return String.format("[%s %s]", this.start, this.end);
}
}
}