Clock.java 7.66 KB
package com.examplemod;

import static com.mumfrey.liteloader.gl.GL.*;
import static net.minecraft.client.renderer.vertex.DefaultVertexFormats.*;

import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.VertexBuffer;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.util.ReadableColor;

import java.util.Calendar;

/**
 * Simple implementation of an analogue clock to demonstrate how to LiteLoader all the things 
 *
 * @author Adam Mummery-Smith
 */
public class Clock
{
	/**
	 * This is the clock face resource, you need to create a resource location for any assets that you
	 * wish to use. It is best to make these static to avoid new instances being created for every instance
	 * of the referencing object, this also means they will only be garbage collected when the class is
	 * garbage collected or when no instances of the class are left.
	 * 
	 * The first parameter for the resource location is the "domain" and this should normally be your mod's
	 * name. The domain MUST be lower case! The second is the resource "path" and represents the path to the
	 * resource within the domain. It is convention that the path always start with the resource type, such
	 * as "textures" in this case.
	 * 
	 * Resources are always stored in a path of the form "assets/{domain}/{path}" which makes the appropriate
	 * path to the CLOCKFACE resource: "/assets/example/textures/clock/face.png"
	 */
	private static final ResourceLocation CLOCKFACE = new ResourceLocation("example", "textures/clock/face.png");
	
	/**
	 * Angles for the hands
	 */
	private float smallHandAngle, largeHandAngle, secondHandAngle;
	
	/**
	 * Sizes for each of the hands
	 */
	private float smallHandSize, largeHandSize, secondHandSize;
	
	/**
	 * Width of the hands 
	 */
	private float handWidth = 1.0F;
	
	/**
	 * Colours for each of the hands
	 */
	private ReadableColor smallHandColour, largeHandColour, secondHandColour;
	
	/**
	 * Size and position for the clock
	 */
	private int xPos, yPos, size;
	
	/**
	 * Whether the clock is currently visible
	 */
	private boolean visible = true;

	/**
	 * @param xPos X position for the clock
	 * @param yPos Y position for the clock
	 */
	public Clock(int xPos, int yPos)
	{
		this.setPosition(xPos, yPos);
		this.setSize(64);
		
		this.largeHandColour  = ReadableColor.WHITE;   
		this.smallHandColour  = ReadableColor.GREY;
		this.secondHandColour = ReadableColor.ORANGE;
	}

	/**
	 * @param xPos
	 * @param yPos
	 */
	public void setPosition(int xPos, int yPos)
	{
		this.xPos = Math.max(0,  xPos);
		this.yPos = Math.max(0,  yPos);
	}

	/**
	 * Set the size of the clock
	 * 
	 * @param size new size (min is 32)
	 */
	public void setSize(int size)
	{
		this.size  = Math.max(32, size);
		
		this.smallHandSize  = this.size * 0.25F;
		this.largeHandSize  = this.size * 0.38F;
		this.secondHandSize = this.size * 0.35F;
		
		this.handWidth = this.size / 64.0F;
	}
	
	/**
	 * Get the current size
	 */
	public int getSize()
	{
		return this.size;
	}
	
	/**
	 * Get whether the clock is currently visible
	 */
	public boolean isVisible()
	{
		return this.visible;
	}
	
	/**
	 * Set whether the clock should be visible
	 * 
	 * @param visible new visibility setting
	 */
	public void setVisible(boolean visible)
	{
		this.visible = visible;
	}
	
	/**
	 * Render the clock at its current position, unless hidden
	 * 
	 * @param minecraft Minecraft game instance
	 */
	public void render(Minecraft minecraft)
	{
		if (this.isVisible())
		{
			// First, update the hand angles
			this.calculateAngles();
			
			// Then render the actual clock
			this.renderClock(minecraft);
		}
	}

	/**
	 * Gets the current time and calculates the angles for the clock hands
	 */
	private void calculateAngles()
	{
		Calendar calendar = Calendar.getInstance();
		
		int hour   = calendar.get(Calendar.HOUR);
		int minute = calendar.get(Calendar.MINUTE);
		int second = calendar.get(Calendar.SECOND);
		
		this.smallHandAngle  = 360.0F * (0.0833F * hour + 0.00138F * minute);
		this.largeHandAngle  = 360.0F * (0.0166F * minute);
		this.secondHandAngle = 360.0F * (0.0166F * second);
	}

	/**
	 * Renders the clock
	 * 
	 * @param minecraft Minecraft game instance
	 */
	private void renderClock(Minecraft minecraft)
	{
		// Render the face
		this.renderClockFace(minecraft);
		
		// Render each of the hands
		this.renderClockHand(this.largeHandAngle,  this.largeHandSize,  this.handWidth * 1.2F, this.largeHandColour);
		this.renderClockHand(this.smallHandAngle,  this.smallHandSize,  this.handWidth * 2.0F, this.smallHandColour);
		this.renderClockHand(this.secondHandAngle, this.secondHandSize, this.handWidth * 1.2F, this.secondHandColour);
	}

	/**
	 * Renders the clock face texture using the texture resource
	 * 
	 * @param minecraft Minecraft game instance
	 */
	private void renderClockFace(Minecraft minecraft)
	{
		// Bind the texture resource
		minecraft.getTextureManager().bindTexture(Clock.CLOCKFACE);
		
		// Draw a rectangle using the currently bound texture
		glDrawTexturedRect(this.xPos, this.yPos, this.size, this.size, 1, 1, 511, 511);
	}
	
	/**
	 * Render one of the hands 
	 */
	private void renderClockHand(float angle, float length, float width, ReadableColor colour)
	{
		// Push the current transform onto the stack
		glPushMatrix();
		
		// Transform to the mid point of the clock
		glTranslatef(this.xPos + (this.size / 2), this.yPos + (this.size / 2), 0);
		
		// and rotate by the hand angle
		glRotatef(angle, 0.0F, 0.0F, 1.0F);
		
		// then draw the hand (straight up of course)
		glDrawRect(width * -0.5F, length * 0.2F, width * 0.5F, -length, colour);
		
		// and finally restore the current transform
		glPopMatrix();
	}
	
    /**
     * Draw a rectangle using the currently bound texture
     */
    private static void glDrawTexturedRect(int x, int y, int width, int height, int u, int v, int u2, int v2)
    {
		// Set the appropriate OpenGL modes
		glDisableLighting();
        glDisableBlend();
		glAlphaFunc(GL_GREATER, 0.01F);
		glEnableTexture2D();
		glColor4f(1.0F, 1.0F, 1.0F, 1.0F);

		float texMapScale = 0.001953125F; // 512px
        
        // We use the tessellator rather than drawing individual quads because it uses vertex arrays to
        // draw the quads more efficiently.
		Tessellator tessellator = Tessellator.getInstance();
		VertexBuffer worldRenderer = tessellator.getBuffer();
        worldRenderer.begin(GL_QUADS, POSITION_TEX);
        worldRenderer.pos(x + 0,     y + height, 0).tex(u  * texMapScale, v2 * texMapScale).endVertex();
        worldRenderer.pos(x + width, y + height, 0).tex(u2 * texMapScale, v2 * texMapScale).endVertex();
        worldRenderer.pos(x + width, y + 0,      0).tex(u2 * texMapScale, v  * texMapScale).endVertex();
        worldRenderer.pos(x + 0,     y + 0,      0).tex(u  * texMapScale, v  * texMapScale).endVertex();
        tessellator.draw();
    }

	/**
	 * Draw an opaque rectangle
	 */
	private static void glDrawRect(float x1, float y1, float x2, float y2, ReadableColor colour)
    {
		// Set GL modes
        glDisableBlend();
        glDisableTexture2D();
        glDisableCulling();
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glColor4f(colour.getRed(), colour.getGreen(), colour.getBlue(), 1.0F);
        
        // Draw the quad
		Tessellator tessellator = Tessellator.getInstance();
		VertexBuffer worldRenderer = tessellator.getBuffer();
        worldRenderer.begin(GL_QUADS, POSITION);
        worldRenderer.pos(x1, y2, 0).endVertex();
        worldRenderer.pos(x2, y2, 0).endVertex();
        worldRenderer.pos(x2, y1, 0).endVertex();
        worldRenderer.pos(x1, y1, 0).endVertex();
        tessellator.draw();
        
        // Restore GL modes
        glEnableCulling();
        glEnableTexture2D();
    }
}