/* * 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); } } }