@Asynchronous
@AdapterSourceName("glasses_container")
public interface IContainer<E> extends IClearable {

	public E addObject(E drawable);

	@Alias("getObjectById")
	@ScriptCallable(returnTypes = ReturnType.OBJECT, description = "Get object by id")
	public E getById(@Arg(name = "id", description = "Id of drawed object") Index id);

	@ScriptCallable(returnTypes = ReturnType.TABLE, description = "Get the ids of all the objects")
	public Set<Index> getAllIds(@Env(Constants.ARG_ARCHITECTURE) IArchitecture access);

	@ScriptCallable(returnTypes = ReturnType.TABLE, description = "Get all objects")
	public Map<Index, E> getAllObjects(@Env(Constants.ARG_ARCHITECTURE) IArchitecture access);
} @Asynchronous
@AdapterSourceName("glasses_drawable")
public abstract class Drawable extends StructuredObjectBase {

	public static enum VerticalAlignment {
		TOP,
		MIDDLE,
		BOTTOM;

		public static final VerticalAlignment[] VALUES = values();
	}

	public static enum HorizontalAlignment {
		LEFT,
		MIDDLE,
		RIGHT;

		public static final HorizontalAlignment[] VALUES = values();
	}

	private static class Alignment implements IStructureElement {
		private static final int MASK = 0x3;

		private int id;

		public VerticalAlignment screenVerticalAnchor = VerticalAlignment.TOP;
		public HorizontalAlignment screenHorizontalAnchor = HorizontalAlignment.LEFT;

		public VerticalAlignment objectVerticalAnchor = VerticalAlignment.TOP;
		public HorizontalAlignment objectHorizontalAnchor = HorizontalAlignment.LEFT;

		@Override
		public void readFromStream(DataInput input) throws IOException {
			byte value = input.readByte();
			screenVerticalAnchor = VerticalAlignment.VALUES[(value >> 0) & MASK];
			screenHorizontalAnchor = HorizontalAlignment.VALUES[(value >> 2) & MASK];

			objectVerticalAnchor = VerticalAlignment.VALUES[(value >> 4) & MASK];
			objectHorizontalAnchor = HorizontalAlignment.VALUES[(value >> 6) & MASK];
		}

		@Override
		public void writeToStream(DataOutput output) throws IOException {
			byte value = (byte)((screenVerticalAnchor.ordinal() << 0) |
					(screenHorizontalAnchor.ordinal() << 2) |
					(objectVerticalAnchor.ordinal() << 4) |
					(objectHorizontalAnchor.ordinal() << 6));
			output.writeByte(value);
		}

		public float getScreenAnchorX(float screenWidth) {
			switch (screenHorizontalAnchor) {
				case MIDDLE:
					return screenWidth / 2.0f;
				case RIGHT:
					return screenWidth;
				case LEFT:
				default:
					return 0;

			}
		}

		public float getObjectAnchorX(Box2d box) {
			switch (objectHorizontalAnchor) {
				case MIDDLE:
					return -box.width / 2.0f;
				case RIGHT:
					return -box.width;
				case LEFT:
				default:
					return 0;

			}
		}

		public float getScreenAnchorY(float screenHeight) {
			switch (screenVerticalAnchor) {
				case BOTTOM:
					return screenHeight;
				case MIDDLE:
					return screenHeight / 2.0f;
				default:
				case TOP:
					return 0;
			}
		}

		public float getObjectAnchorY(Box2d box) {
			switch (objectVerticalAnchor) {
				case BOTTOM:
					return -box.height;
				case MIDDLE:
					return -box.height / 2.0f;
				default:
				case TOP:
					return 0;
			}
		}
	}

	private Box2d boundingBox = Box2d.NULL;

	private float rotationSin;

	private float rotationCos;

	private final Alignment alignment = new Alignment();

	@Property
	@StructureField
	public short z;

	@Property
	@StructureField
	public boolean visible = true;

	@Property
	@StructureField
	public boolean clickable = true;

	@Property
	@StructureField
	public float rotation = 0;

	@Property(type = ArgType.OBJECT,
			getterDesc = "Get userdata",
			setterDesc = "Set userdata (no restrictions, not sent to clients)",
			nullable = true)
	public Object userdata;

	protected Drawable() {}

	public void onUpdate() {
		final double rotationRad = rotation / 180 * Math.PI;
		rotationCos = (float)Math.cos(rotationRad);
		rotationSin = (float)Math.sin(rotationRad);
	}

	public Point2d transformToLocal(float worldX, float worldY, float screenWidth, float screenHeight) {
		final float worldLeft = alignment.getScreenAnchorX(screenWidth) + boundingBox.left;
		final float worldTop = alignment.getScreenAnchorY(screenHeight) + boundingBox.top;
		worldX -= worldLeft;
		worldY -= worldTop;

		float localX = worldX * +rotationCos + worldY * +rotationSin;
		float localY = worldX * -rotationSin + worldY * +rotationCos;

		localX -= alignment.getObjectAnchorX(boundingBox);
		localY -= alignment.getObjectAnchorY(boundingBox);

		return new Point2d(localX, localY);
	}

	@SideOnly(Side.CLIENT)
	public void draw(ScaledResolution resolution, RenderState renderState, float partialTicks) {
		final float screenX = alignment.getScreenAnchorX(resolution.getScaledWidth()) + boundingBox.left;
		final float screenY = alignment.getScreenAnchorY(resolution.getScaledHeight()) + boundingBox.top;

		final float anchorX = alignment.getObjectAnchorX(boundingBox);
		final float anchorY = alignment.getObjectAnchorY(boundingBox);

		GL11.glPushMatrix();
		if (rotation != 0) {
			GL11.glTranslatef(screenX, screenY, z);
			GL11.glRotated(rotation, 0, 0, 1);
			GL11.glTranslatef(anchorX, anchorY, 0);
		} else {
			GL11.glTranslatef(screenX + anchorX, screenY + anchorY, z);
		}

		drawContents(renderState, partialTicks);
		GL11.glPopMatrix();
	}

	protected void setBoundingBox(Box2d boundingBox) {
		Preconditions.checkNotNull(boundingBox);
		this.boundingBox = boundingBox;
	}

	@SideOnly(Side.CLIENT)
	protected abstract void drawContents(RenderState renderState, float partialTicks);

	protected abstract DrawableType getTypeEnum();

	protected abstract boolean isVisible();

	public final boolean shouldRender() {
		return visible && isVisible();
	}

	public boolean isClickable() {
		return clickable;
	}

	@Override
	public int getType() {
		return getTypeEnum().ordinal();
	}

	@ScriptCallable(returnTypes = ReturnType.STRING, name = "getType", description = "Get object type")
	public String getTypeName() {
		return getTypeEnum().name().toLowerCase();
	}

	public VerticalAlignment getScreenVerticalAnchor() {
		return alignment.screenVerticalAnchor;
	}

	@ScriptCallable
	public void setScreenAnchor(@Arg(name = "horizontal") HorizontalAlignment horizontal, @Arg(name = "vertical") VerticalAlignment vertical) {
		alignment.screenVerticalAnchor = vertical;
		alignment.screenHorizontalAnchor = horizontal;
		markModified(alignment.id);
	}

	@ScriptCallable
	public void setObjectAnchor(@Arg(name = "horizontal") HorizontalAlignment horizontal, @Arg(name = "vertical") VerticalAlignment vertical) {
		alignment.objectVerticalAnchor = vertical;
		alignment.objectHorizontalAnchor = horizontal;
		markModified(alignment.id);
	}

	@ScriptCallable
	public void setAlignment(@Arg(name = "horizontal") HorizontalAlignment horizontal, @Arg(name = "vertical") VerticalAlignment vertical) {
		alignment.objectVerticalAnchor = alignment.screenVerticalAnchor = vertical;
		alignment.objectHorizontalAnchor = alignment.screenHorizontalAnchor = horizontal;
		markModified(alignment.id);
	}

	@Override
	public void createElements(IElementAddCallback<IStructureElement> callback) {
		super.createElements(callback);
		alignment.id = callback.addElement(alignment);
	}

	public Box2d getBoundingBox() {
		return boundingBox;
	}
} @ScriptObject
@AdapterSourceName("glasses_gradient")
public class GradientBox extends Drawable {
	@Property
	@StructureField
	public float x;

	@Property
	@StructureField
	public float y;

	@Property
	@StructureField
	public float width;

	@Property
	@StructureField
	public float height;

	@Property
	@StructureField
	public int color1;

	@Property
	@StructureField
	public float opacity1;

	@Property
	@StructureField
	public int color2;

	@Property
	@StructureField
	public float opacity2;

	@Property
	@StructureField
	public int gradient;

	GradientBox() {}

	public GradientBox(float x, float y, float width, float height, int color1, float opacity1, int color2, float opacity2, int gradient) {
		this.x = x;
		this.y = y;
		this.width = width;
		this.height = height;

		this.color1 = color1;
		this.opacity1 = opacity1;
		// compat hack
		if (gradient == 0) {
			this.color2 = color1;
			this.opacity2 = opacity1;
		} else {
			this.color2 = color2;
			this.opacity2 = opacity2;
		}
		this.gradient = gradient;
	}

	@Override
	@SideOnly(Side.CLIENT)
	protected void drawContents(RenderState renderState, float partialTicks) {
		renderState.setupSolidRender();

		{
			renderState.setColor(color1, opacity1);
			GL11.glBegin(GL11.GL_QUADS);
			if (gradient == 1) {
				GL11.glVertex2f(0, height);
				GL11.glVertex2f(width, height);
			} else {
				GL11.glVertex2f(width, height);
				GL11.glVertex2f(width, 0);
			}
		}

		{
			renderState.setColor(color2, opacity2);
			if (gradient == 1) {
				GL11.glVertex2f(width, 0);
				GL11.glVertex2f(0, 0);
			} else {
				GL11.glVertex2f(0, 0);
				GL11.glVertex2f(0, height);
			}
		}

		GL11.glEnd();
	}

	@Override
	public DrawableType getTypeEnum() {
		return DrawableType.GRADIENT;
	}

	@Override
	public boolean isVisible() {
		return opacity1 > 0 || opacity2 > 0;
	}

	@Override
	public void onUpdate() {
		super.onUpdate();
		setBoundingBox(Box2d.fromOriginAndSize(x, y, width, height));
	}
} @ScriptObject
@AdapterSourceName("glasses_gradient_line")
public class GradientLine extends Line<ColorPoint2d> {

	public GradientLine() {
		this(ColorPoint2d.NULL, ColorPoint2d.NULL);
	}

	public GradientLine(ColorPoint2d p1, ColorPoint2d p2) {
		super(p1, p2);
	}

	@Override
	protected IPointListBuilder<ColorPoint2d> createBuilder() {
		return new ColorPointListBuilder();
	}

	@Override
	protected DrawableType getTypeEnum() {
		return DrawableType.GRADIENT_LINE;
	}

} @ScriptObject
@AdapterSourceName("glasses_gradient_line_strip")
public class GradientLineStrip extends LineStrip<ColorPoint2d> {

	public GradientLineStrip() {}

	public GradientLineStrip(ColorPoint2d... points) {
		super(points);
	}

	@Override
	protected IPointListBuilder<ColorPoint2d> createBuilder() {
		return new ColorPointListBuilder();
	}

	@Override
	protected DrawableType getTypeEnum() {
		return DrawableType.GRADIENT_LINE_STRIP;
	}

} @ScriptObject
@AdapterSourceName("glasses_gradient_polygon")
public class GradientPolygon extends Polygon<ColorPoint2d> {

	public GradientPolygon() {}

	public GradientPolygon(ColorPoint2d... points) {
		super(points);
	}

	@Override
	protected IPointListBuilder<ColorPoint2d> createBuilder() {
		return new ColorPointListBuilder();
	}

	@Override
	protected IPolygonBuilder<ColorPoint2d> createPolygonBuilder() {
		return new GradientPolygonBuilder();
	}

	@Override
	protected DrawableType getTypeEnum() {
		return DrawableType.GRADIENT_POLYGON;
	}

	@Override
	protected void drawContents(RenderState renderState, float partialTicks) {
		if (canRender()) {
			super.drawContents(renderState, partialTicks);
			renderPolygon(renderState);
		}
	}

} @ScriptObject
@AdapterSourceName("glasses_gradient_quad")
public class GradientQuad extends Quad<ColorPoint2d> {

	public GradientQuad() {
		this(ColorPoint2d.NULL, ColorPoint2d.NULL, ColorPoint2d.NULL, ColorPoint2d.NULL);
	}

	public GradientQuad(ColorPoint2d p1, ColorPoint2d p2, ColorPoint2d p3, ColorPoint2d p4) {
		super(p1, p2, p3, p4);
	}

	@Override
	protected IPointListBuilder<ColorPoint2d> createBuilder() {
		return new ColorPointListBuilder();
	}

	@Override
	protected DrawableType getTypeEnum() {
		return DrawableType.GRADIENT_QUAD;
	}

} @ScriptObject
@AdapterSourceName("glasses_gradient_triangle")
public class GradientTriangle extends Triangle<ColorPoint2d> {

	public GradientTriangle() {
		this(ColorPoint2d.NULL, ColorPoint2d.NULL, ColorPoint2d.NULL);
	}

	public GradientTriangle(ColorPoint2d p1, ColorPoint2d p2, ColorPoint2d p3) {
		super(p1, p2, p3);
	}

	@Override
	protected IPointListBuilder<ColorPoint2d> createBuilder() {
		return new ColorPointListBuilder();
	}

	@Override
	protected DrawableType getTypeEnum() {
		return DrawableType.GRADIENT_TRIANGLE;
	}

} @ScriptObject
@AdapterSourceName("glasses_icon")
public class ItemIcon extends Drawable {
	private static final int BASE_HEIGHT = 16;

	private static final int BASE_WIDTH = 16;

	private ItemStack dummyStack;

	@Property
	@StructureField
	public float x;

	@Property
	@StructureField
	public float y;

	@SideOnly(Side.CLIENT)
	private RenderItem renderItem;

	private ItemStack drawStack;

	@Property
	@StructureField
	public float scale = 1;

	@Property
	@StructureField
	public String itemId;

	@Property
	@StructureField
	public int meta;

	@Property
	@StructureField
	public float damageBar;

	@Property
	@StructureField
	public String label;

	ItemIcon() {}

	public ItemIcon(float x, float y, String itemId, int meta) {
		this.x = x;
		this.y = y;
		this.itemId = itemId;
		this.meta = meta;
	}

	@SideOnly(Side.CLIENT)
	private RenderItem getRenderItem() {
		if (renderItem == null) renderItem = new RenderItem();
		return renderItem;
	}

	@Override
	@SideOnly(Side.CLIENT)
	protected void drawContents(RenderState renderState, float partialTicks) {
		renderState.enableTexture();
		renderState.enableDepthTest();
		renderState.enableCullFace();
		renderState.disableLight();
		renderState.setColor(0xFFFFFF, 1.0f);

		GL11.glScalef(scale, scale, scale);
		final RenderItem renderItem = getRenderItem();
		final Minecraft minecraft = Minecraft.getMinecraft();
		final TextureManager textureManager = minecraft.getTextureManager();

		renderItem.renderItemAndEffectIntoGUI(minecraft.fontRenderer, textureManager, drawStack, 0, 0);

		if (damageBar > 0 || !Strings.isNullOrEmpty(label)) {
			renderItem.renderItemOverlayIntoGUI(minecraft.fontRenderer, textureManager, dummyStack, 0, 0, label);
		}

		RenderUtils.disableLightmap();

		renderState.readState();
	}

	@Override
	public DrawableType getTypeEnum() {
		return DrawableType.ITEM;
	}

	@Override
	public boolean isVisible() {
		return drawStack != null;
	}

	@Override
	public void onUpdate() {
		super.onUpdate();

		this.drawStack = findDrawStack(itemId, meta);

		int damage = (int)(damageBar * Items.diamond_hoe.getMaxDamage());
		this.dummyStack = new ItemStack(Items.diamond_hoe, 1, damage);

		final int width = Math.round(BASE_WIDTH * scale);
		final int height = Math.round(BASE_HEIGHT * scale);
		setBoundingBox(Box2d.fromOriginAndSize(x, y, width, height));
	}

	private static ItemStack findDrawStack(String itemId, int meta) {
		if (Strings.isNullOrEmpty(itemId)) return null;
		String[] itemSplit = itemId.split(":");
		if (itemSplit.length != 2) return null;

		Item item = GameRegistry.findItem(itemSplit[0], itemSplit[1]);
		if (item == null) return null;

		return new ItemStack(item, 1, meta);
	}

} @ScriptObject
@AdapterSourceName("glasses_liquid")
public class LiquidIcon extends Drawable {
	@Property
	@StructureField
	public float x;

	@Property
	@StructureField
	public float y;

	@Property
	@StructureField
	public float width;

	@Property
	@StructureField
	public float height;

	@Property
	@StructureField
	public String fluid;

	@Property
	@StructureField
	public float alpha = 1;

	private IIcon fluidIcon;

	private int iconWidth;

	private int iconHeight;

	LiquidIcon() {}

	public LiquidIcon(float x, float y, float width, float height, String fluid) {
		this.x = x;
		this.y = y;
		this.width = width;
		this.height = height;
		this.fluid = fluid;
	}

	@Override
	@SideOnly(Side.CLIENT)
	protected void drawContents(RenderState renderState, float partialTicks) {
		if (fluidIcon == null || iconWidth <= 0 || iconHeight <= 0) return;

		renderState.setupTexturedRender();
		renderState.setColor(0xFFFFFF, alpha);

		TextureManager render = FMLClientHandler.instance().getClient().renderEngine;
		render.bindTexture(TextureMap.locationBlocksTexture);
		float xIterations = width / iconWidth;
		float yIterations = height / iconHeight;

		for (float xIteration = 0; xIteration < xIterations; xIteration += 1) {
			for (float yIteration = 0; yIteration < yIterations; yIteration += 1) {
				// Draw whole or partial
				final float xDrawSize = Math.min(xIterations - xIteration, 1);
				final float yDrawSize = Math.min(yIterations - yIteration, 1);

				GlassesRenderingUtils.drawTexturedQuad(
						xIteration * iconWidth,
						yIteration * iconHeight,
						fluidIcon,
						xDrawSize * iconWidth,
						yDrawSize * iconHeight,
						xDrawSize,
						yDrawSize);
			}
		}
	}

	@Override
	public DrawableType getTypeEnum() {
		return DrawableType.LIQUID;
	}

	@Override
	public boolean isVisible() {
		return alpha > 0 && fluidIcon != null && iconWidth > 0 && iconHeight > 0;
	}

	@Override
	public void onUpdate() {
		super.onUpdate();

		fluidIcon = findFluidIcon(fluid);
		if (fluidIcon != null) {
			iconWidth = fluidIcon.getIconWidth();
			iconHeight = fluidIcon.getIconHeight();
		}

		setBoundingBox(Box2d.fromOriginAndSize(x, y, width, height));
	}

	private static IIcon findFluidIcon(String fluid) {
		Fluid drawLiquid = FluidRegistry.getFluid(fluid);
		return drawLiquid != null? drawLiquid.getFlowingIcon() : null;
	}

} @ScriptObject
@AdapterSourceName("glasses_point")
public class Point extends Drawable {

	@Property
	@StructureField
	public int color;

	@Property
	@StructureField
	public float opacity;

	@Property
	@StructureField
	public Point2d coord = Point2d.NULL;

	@Property
	@StructureField
	public float size = 1;

	Point() {}

	public Point(Point2d coord, int color, float opacity) {
		this.coord = coord;
		this.color = color;
		this.opacity = opacity;
	}

	private void updateBoundingBox() {
		// no point in rotating point
		setBoundingBox(Box2d.fromOriginAndSize(coord.x, coord.y, 0, 0));
	}

	@Override
	protected void drawContents(RenderState renderState, float partialTicks) {
		renderState.setColor(color, opacity);

		renderState.setupSolidRender();
		renderState.setPointSize(size);

		GL11.glBegin(GL11.GL_POINTS);
		GL11.glVertex2i(0, 0);
		GL11.glEnd();
	}

	@Override
	protected DrawableType getTypeEnum() {
		return DrawableType.POINT;
	}

	@Override
	protected boolean isVisible() {
		return true;
	}

	@Override
	public void onUpdate() {
		if (size <= 0) size = 1;
		updateBoundingBox();
	}

} @ScriptObject
@AdapterSourceName("glasses_box")
public class SolidBox extends Drawable {
	@Property
	@StructureField
	public float x;

	@Property
	@StructureField
	public float y;

	@Property
	@StructureField
	public float width;

	@Property
	@StructureField
	public float height;

	@Property
	@StructureField
	public int color;

	@Property
	@StructureField
	public float opacity;

	SolidBox() {}

	public SolidBox(float x, float y, float width, float height, int color, float opacity) {
		this.x = x;
		this.y = y;
		this.width = width;
		this.height = height;

		this.color = color;
		this.opacity = opacity;
	}

	@Override
	@SideOnly(Side.CLIENT)
	protected void drawContents(RenderState renderState, float partialTicks) {
		renderState.setupSolidRender();
		renderState.setColor(color, opacity);

		GL11.glBegin(GL11.GL_QUADS);
		GL11.glVertex2f(0, 0);
		GL11.glVertex2f(0, height);
		GL11.glVertex2f(width, height);
		GL11.glVertex2f(width, 0);
		GL11.glEnd();
	}

	@Override
	public DrawableType getTypeEnum() {
		return DrawableType.BOX;
	}

	@Override
	public boolean isVisible() {
		return opacity > 0;
	}

	@Override
	public void onUpdate() {
		super.onUpdate();

		setBoundingBox(Box2d.fromOriginAndSize(x, y, width, height));
	}
} @ScriptObject
@AdapterSourceName("glasses_line")
public class SolidLine extends Line<Point2d> {

	@Property
	@StructureField
	public int color = 0xFFFFFF;

	@Property
	@StructureField
	public float opacity = 1.0f;

	public SolidLine() {
		this(Point2d.NULL, Point2d.NULL, 0x0, 0.0f);
	}

	public SolidLine(Point2d p1, Point2d p2, int color, float opacity) {
		super(p1, p2);

		this.color = color;
		this.opacity = opacity;
	}

	@Override
	protected void drawContents(RenderState renderState, float partialTicks) {
		renderState.setColor(color, opacity);
		super.drawContents(renderState, partialTicks);
	}

	@Override
	protected IPointListBuilder<Point2d> createBuilder() {
		return new PointListBuilder();
	}

	@Override
	protected DrawableType getTypeEnum() {
		return DrawableType.LINE;
	}

} @ScriptObject
@AdapterSourceName("glasses_line_strip")
public class SolidLineStrip extends LineStrip<Point2d> {

	@Property
	@StructureField
	public int color = 0xFFFFFF;

	@Property
	@StructureField
	public float opacity = 1.0f;

	public SolidLineStrip() {}

	public SolidLineStrip(int color, float opacity, Point2d... points) {
		super(points);

		this.color = color;
		this.opacity = opacity;
	}

	@Override
	protected void drawContents(RenderState renderState, float partialTicks) {
		renderState.setColor(color, opacity);
		super.drawContents(renderState, partialTicks);
	}

	@Override
	protected IPointListBuilder<Point2d> createBuilder() {
		return new PointListBuilder();
	}

	@Override
	protected DrawableType getTypeEnum() {
		return DrawableType.LINE_STRIP;
	}

} @ScriptObject
@AdapterSourceName("glasses_polygon")
public class SolidPolygon extends Polygon<Point2d> {

	@Property
	@StructureField
	public int color = 0xFFFFFF;

	@Property
	@StructureField
	public float opacity = 1.0f;

	public SolidPolygon() {}

	public SolidPolygon(int color, float opacity, Point2d... points) {
		super(points);

		this.color = color;
		this.opacity = opacity;
	}

	@Override
	protected IPointListBuilder<Point2d> createBuilder() {
		return new PointListBuilder();
	}

	@Override
	protected IPolygonBuilder<Point2d> createPolygonBuilder() {
		return new SolidPolygonBuilder();
	}

	@Override
	protected DrawableType getTypeEnum() {
		return DrawableType.POLYGON;
	}

	@Override
	protected void drawContents(RenderState renderState, float partialTicks) {
		if (canRender()) {
			super.drawContents(renderState, partialTicks);
			renderState.setColor(color, opacity);
			renderPolygon(renderState);
		}
	}

} @ScriptObject
@AdapterSourceName("glasses_quad")
public class SolidQuad extends Quad<Point2d> {

	@Property
	@StructureField
	public int color = 0xFFFFFF;

	@Property
	@StructureField
	public float opacity = 1.0f;

	public SolidQuad() {
		super(Point2d.NULL, Point2d.NULL, Point2d.NULL, Point2d.NULL);
	}

	public SolidQuad(Point2d p1, Point2d p2, Point2d p3, Point2d p4, int color, float opacity) {
		super(p1, p2, p3, p4);

		this.color = color;
		this.opacity = opacity;
	}

	@Override
	protected void drawContents(RenderState renderState, float partialTicks) {
		renderState.setColor(color, opacity);
		super.drawContents(renderState, partialTicks);
	}

	@Override
	protected IPointListBuilder<Point2d> createBuilder() {
		return new PointListBuilder();
	}

	@Override
	protected DrawableType getTypeEnum() {
		return DrawableType.QUAD;
	}

} @ScriptObject
@AdapterSourceName("glasses_triangle")
public class SolidTriangle extends Triangle<Point2d> {

	@Property
	@StructureField
	public int color = 0xFFFFFF;

	@Property
	@StructureField
	public float opacity = 1.0f;

	public SolidTriangle() {
		super(Point2d.NULL, Point2d.NULL, Point2d.NULL);
	}

	public SolidTriangle(Point2d p1, Point2d p2, Point2d p3, int color, float opacity) {
		super(p1, p2, p3);

		this.color = color;
		this.opacity = opacity;
	}

	@Override
	protected void drawContents(RenderState renderState, float partialTicks) {
		renderState.setColor(color, opacity);
		super.drawContents(renderState, partialTicks);
	}

	@Override
	protected IPointListBuilder<Point2d> createBuilder() {
		return new PointListBuilder();
	}

	@Override
	protected DrawableType getTypeEnum() {
		return DrawableType.TRIANGLE;
	}
} @ScriptObject
@AdapterSourceName("glasses_text")
public class Text extends Drawable {
	@Property
	@StructureField
	public float x;

	@Property
	@StructureField
	public float y;

	@Property
	@StructureField
	public String text;

	@Property
	@StructureField
	public int color;

	@Property
	@StructureField
	public float alpha = 1;

	@Property
	@StructureField
	public float scale = 1;

	Text() {}

	public Text(float x, float y, String text, int color) {
		this.x = x;
		this.y = y;
		this.text = text;
		this.color = color;
	}

	@Override
	@SideOnly(Side.CLIENT)
	protected void drawContents(RenderState renderState, float partialTicks) {
		renderState.setupTexturedRender();
		renderState.setColor(color, alpha);

		FontRenderer fontRenderer = FMLClientHandler.instance().getClient().fontRenderer;
		GL11.glScalef(scale, scale, scale);
		fontRenderer.drawString(text, 0, 0, ((int)(alpha * 255) << 24 | color));
	}

	@Override
	public DrawableType getTypeEnum() {
		return DrawableType.TEXT;
	}

	@Override
	public boolean isVisible() {
		return alpha > 0;
	}

	@Override
	public void onUpdate() {
		super.onUpdate();

		updateBoundingBox();
	}

	private void updateBoundingBox() {
		final int height = Math.round(8 * scale);

		final int width;
		if (Strings.isNullOrEmpty(text)) {
			width = 0;
		} else {
			FontRenderer fontRenderer = FMLClientHandler.instance().getClient().fontRenderer;
			width = Math.round(fontRenderer.getStringWidth(text) * scale);
		}

		setBoundingBox(Box2d.fromOriginAndSize(x, y, width, height));
	}
} @ScriptStruct
@SerializableClass
public class ColorPoint2d {

	public static final ColorPoint2d NULL = new ColorPoint2d(0, 0, 0, 0.0f);

	public ColorPoint2d() {}

	public ColorPoint2d(float x, float y, int rgb, float opacity) {
		this.x = x;
		this.y = y;
		this.rgb = rgb;
		this.opacity = opacity;
	}

	@Serialize
	@StructField(index = 0)
	public float x;

	@Serialize
	@StructField(index = 1)
	public float y;

	@Serialize
	@StructField(index = 2, optional = true)
	public int rgb = 0xFFFFFF;

	@Serialize
	@StructField(index = 3, optional = true)
	public float opacity = 1;

	@Override
	public String toString() {
		return String.format("(%f,%f %06X:%.2f", x, y, rgb, opacity);
	}

} @ScriptStruct
@SerializableClass
public class Point2d {

	public static final Point2d NULL = new Point2d(0, 0);

	public Point2d() {}

	public Point2d(float x, float y) {
		this.x = x;
		this.y = y;
	}

	@Serialize
	@StructField(index = 0)
	public float x;

	@Serialize
	@StructField(index = 1)
	public float y;

	@Override
	public String toString() {
		return "(" + x + "," + y + ")";
	}

}
import com.google.common.base.Objects;
import openperipheral.addons.glasses.IContainer;
import openperipheral.addons.glasses.drawable.Drawable;
import openperipheral.addons.glasses.drawable.GradientBox;
import openperipheral.addons.glasses.drawable.GradientLine;
import openperipheral.addons.glasses.drawable.GradientLineStrip;
import openperipheral.addons.glasses.drawable.GradientPolygon;
import openperipheral.addons.glasses.drawable.GradientQuad;
import openperipheral.addons.glasses.drawable.GradientTriangle;
import openperipheral.addons.glasses.drawable.ItemIcon;
import openperipheral.addons.glasses.drawable.LiquidIcon;
import openperipheral.addons.glasses.drawable.Point;
import openperipheral.addons.glasses.drawable.SolidBox;
import openperipheral.addons.glasses.drawable.SolidLine;
import openperipheral.addons.glasses.drawable.SolidLineStrip;
import openperipheral.addons.glasses.drawable.SolidPolygon;
import openperipheral.addons.glasses.drawable.SolidQuad;
import openperipheral.addons.glasses.drawable.SolidTriangle;
import openperipheral.addons.glasses.drawable.Text;
import openperipheral.addons.glasses.utils.ColorPoint2d;
import openperipheral.addons.glasses.utils.Point2d;
package openperipheral.addons.glasses.server;


public class DrawableFactory implements IDrawableFactory {

	private final IContainer<Drawable> container;

	private static Integer defaultColor(Integer color) {
		return Objects.firstNonNull(color, 0xFFFFFF);
	}

	private static Float defaultOpacity(Float opacity) {
		return Objects.firstNonNull(opacity, 1.0f);
	}

	public DrawableFactory(IContainer<Drawable> container) {
		this.container = container;
	}

	@Override
	public synchronized Drawable addText(float x, float y, String text, Integer color) {
		return container.addObject(new Text(x, y, text, defaultColor(color)));