Skip to content

Commit

Permalink
Display module, closes #10!
Browse files Browse the repository at this point in the history
  • Loading branch information
fnuecke committed Dec 17, 2015
1 parent cbd567f commit 7309f4d
Show file tree
Hide file tree
Showing 8 changed files with 336 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/main/java/li/cil/tis3d/common/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public final class Constants {
public static final String NAME_BLOCK_CONTROLLER = "controller";
public static final String NAME_ITEM_BOOK_CODE = "bookCode";
public static final String NAME_ITEM_BOOK_MANUAL = "bookManual";
public static final String NAME_ITEM_MODULE_DISPLAY = "moduleDisplay";
public static final String NAME_ITEM_MODULE_AUDIO = "moduleAudio";
public static final String NAME_ITEM_MODULE_EXECUTION = "moduleExecution";
public static final String NAME_ITEM_MODULE_INFRARED = "moduleInfrared";
Expand Down
11 changes: 8 additions & 3 deletions src/main/java/li/cil/tis3d/common/ProxyCommon.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import li.cil.tis3d.common.item.ItemModule;
import li.cil.tis3d.common.network.Network;
import li.cil.tis3d.common.provider.ModuleProviderAudio;
import li.cil.tis3d.common.provider.ModuleProviderDisplay;
import li.cil.tis3d.common.provider.ModuleProviderExecution;
import li.cil.tis3d.common.provider.ModuleProviderInfrared;
import li.cil.tis3d.common.provider.ModuleProviderRandom;
Expand Down Expand Up @@ -63,6 +64,7 @@ public void onPreInit(final FMLPreInitializationEvent event) {
registerBlock(Constants.NAME_BLOCK_CONTROLLER, BlockController::new, TileEntityController.class);

registerModule(Constants.NAME_ITEM_MODULE_AUDIO);
registerModule(Constants.NAME_ITEM_MODULE_DISPLAY);
registerModule(Constants.NAME_ITEM_MODULE_EXECUTION);
registerModule(Constants.NAME_ITEM_MODULE_INFRARED);
registerModule(Constants.NAME_ITEM_MODULE_RANDOM);
Expand All @@ -79,6 +81,7 @@ public void onInit(final FMLInitializationEvent event) {
OreDictionary.registerOre("book", GameRegistry.findItem(API.MOD_ID, Constants.NAME_ITEM_BOOK_MANUAL));

registerModuleOre(Constants.NAME_ITEM_MODULE_AUDIO);
registerModuleOre(Constants.NAME_ITEM_MODULE_DISPLAY);
registerModuleOre(Constants.NAME_ITEM_MODULE_EXECUTION);
registerModuleOre(Constants.NAME_ITEM_MODULE_INFRARED);
registerModuleOre(Constants.NAME_ITEM_MODULE_RANDOM);
Expand All @@ -90,6 +93,7 @@ public void onInit(final FMLInitializationEvent event) {
addBlockRecipe(Constants.NAME_BLOCK_CONTROLLER, "gemDiamond", 1);

addModuleRecipe(Constants.NAME_ITEM_MODULE_AUDIO, Item.getItemFromBlock(Blocks.noteblock));
addModuleRecipe(Constants.NAME_ITEM_MODULE_DISPLAY, "blockQuartz");
addModuleRecipe(Constants.NAME_ITEM_MODULE_EXECUTION, "ingotGold");
addModuleRecipe(Constants.NAME_ITEM_MODULE_INFRARED, Items.spider_eye);
addModuleRecipe(Constants.NAME_ITEM_MODULE_RANDOM, Items.ender_pearl);
Expand All @@ -107,6 +111,7 @@ public void onInit(final FMLInitializationEvent event) {

// Register providers for built-in modules.
ModuleAPI.addProvider(new ModuleProviderAudio());
ModuleAPI.addProvider(new ModuleProviderDisplay());
ModuleAPI.addProvider(new ModuleProviderExecution());
ModuleAPI.addProvider(new ModuleProviderInfrared());
ModuleAPI.addProvider(new ModuleProviderStack());
Expand Down Expand Up @@ -145,15 +150,15 @@ protected Item registerModule(final String name) {
return registerItem(name, ItemModule::new);
}

private void registerModuleOre(final String name) {
private static void registerModuleOre(final String name) {
if (Settings.disabledModules.contains(name)) {
return;
}

OreDictionary.registerOre(API.MOD_ID + ":module", GameRegistry.findItem(API.MOD_ID, name));
}

private void addBlockRecipe(final String name, final Object specialIngredient, final int count) {
private static void addBlockRecipe(final String name, final Object specialIngredient, final int count) {
GameRegistry.addRecipe(new ShapedOreRecipe(new ItemStack(GameRegistry.findBlock(API.MOD_ID, name), count),
"IRI",
"RSR",
Expand All @@ -163,7 +168,7 @@ private void addBlockRecipe(final String name, final Object specialIngredient, f
'S', specialIngredient));
}

private void addModuleRecipe(final String name, final Object specialIngredient) {
private static void addModuleRecipe(final String name, final Object specialIngredient) {
if (Settings.disabledModules.contains(name)) {
return;
}
Expand Down
278 changes: 278 additions & 0 deletions src/main/java/li/cil/tis3d/common/module/ModuleDisplay.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
package li.cil.tis3d.common.module;

import li.cil.tis3d.api.machine.Casing;
import li.cil.tis3d.api.machine.Face;
import li.cil.tis3d.api.machine.Pipe;
import li.cil.tis3d.api.machine.Port;
import li.cil.tis3d.api.prefab.module.AbstractModuleRotatable;
import li.cil.tis3d.api.util.RenderUtil;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.texture.TextureUtil;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;

import java.util.Arrays;

public final class ModuleDisplay extends AbstractModuleRotatable {
// --------------------------------------------------------------------- //
// Persisted data

/**
* The uncompressed image as RGBA, for faster application of draw calls
* and uploading to the GPU. Also kept on the server to allow sending
* current state to newly connected/coming closer clients.
*/
private final int[] image = new int[RESOLUTION * RESOLUTION];

/**
* The current input state, i.e. what value we're currently reading.
*/
private State state = State.COLOR;

/**
* The currently being-built draw call. Stored as byte-array for more
* convenient saving and loading (and sending to clients).
*/
private final byte[] drawCall = new byte[State.values().length];

// --------------------------------------------------------------------- //
// Computed data

/**
* Current state of the display module, decides what happens with the next
* value read on any of the ports.
*/
private enum State {
COLOR, X, Y, W, H;

public static final State[] VALUES = State.values();

public State getNext() {
return VALUES[(ordinal() + 1) % VALUES.length];
}
}

/**
* Mapping of indices to colors as ARGB.
*/
private static final int[] COLORS = new int[]{
0xFFFFFFFF, // 0: White
0xFFFFCC33, // 1: Orange
0xFFCC66CC, // 2: Magenta
0xFF6699FF, // 3: Light Blue
0xFFFFFF33, // 4: Yellow
0xFF33CC33, // 5: Lime
0xFFFF6699, // 6: Pink
0xFF333333, // 7: Gray
0xFFCCCCCC, // 8: Silver
0xFF336699, // 9: Cyan
0xFF9933CC, // 10: Purple
0xFF333399, // 11: Blue
0xFF663300, // 12: Brown
0xFF336600, // 13: Green
0xFFFF3333, // 14: Red
0xFF000000 // 15: Black
};

// Resolution of the screen in pixels, width = height.
private static final int RESOLUTION = 32;

// Don't allow displaying stuff on the edge of the casing. I mean we could,
// technically, but that'd usually look pretty weird. Also it's more
// intuitive that the usable area start in the inner, black part.
private static final int MARGIN = 2;

// NBT tag names.
private static final String TAG_IMAGE = "image";
private static final String TAG_STATE = "state";
private static final String TAG_CLEAR = "clear";
private static final String TAG_DRAW_CALL = "drawCall";

/**
* The ID of the uploaded texture on the GPU (client only).
*/
private int glTextureId;

// --------------------------------------------------------------------- //

public ModuleDisplay(final Casing casing, final Face face) {
super(casing, face);
}

// --------------------------------------------------------------------- //
// Module

@Override
public void step() {
for (final Port port : Port.VALUES) {
stepInput(port);
}
}

@Override
public void onDisabled() {
Arrays.fill(image, 0);
state = State.COLOR;

sendClear();
}

@Override
public void onDisposed() {
if (getCasing().getCasingWorld().isRemote) {
deleteTexture();
}
}

@Override
public void onData(final NBTTagCompound nbt) {
if (nbt.getBoolean(TAG_CLEAR)) {
Arrays.fill(image, 0);
} else {
applyDrawCall(nbt.getByteArray(TAG_DRAW_CALL));
}
TextureUtil.uploadTexture(getGlTextureId(), image, RESOLUTION, RESOLUTION);
}

@SideOnly(Side.CLIENT)
@Override
public void render(final boolean enabled, final float partialTicks) {
if (!enabled) {
return;
}

rotateForRendering();

OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 0f);

GlStateManager.bindTexture(getGlTextureId());

RenderUtil.drawQuad();
}

@Override
public void readFromNBT(final NBTTagCompound nbt) {
super.readFromNBT(nbt);

final int[] imageNbt = nbt.getIntArray(TAG_IMAGE);
System.arraycopy(imageNbt, 0, image, 0, Math.min(imageNbt.length, image.length));

state = State.valueOf(nbt.getString(TAG_STATE));

final byte[] drawCallNbt = nbt.getByteArray(TAG_DRAW_CALL);
System.arraycopy(drawCallNbt, 0, drawCall, 0, Math.min(drawCallNbt.length, drawCall.length));
}

@Override
public void writeToNBT(final NBTTagCompound nbt) {
super.writeToNBT(nbt);

nbt.setIntArray(TAG_IMAGE, image);
nbt.setString(TAG_STATE, state.name());
nbt.setByteArray(TAG_DRAW_CALL, drawCall);
}

// --------------------------------------------------------------------- //

/**
* Update the input of the module, adding any read value to our draw call.
*/
private void stepInput(final Port port) {
// Continuously read from all ports, set output to last received value.
final Pipe receivingPipe = getCasing().getReceivingPipe(getFace(), port);
if (!receivingPipe.isReading()) {
receivingPipe.beginRead();
}
if (receivingPipe.canTransfer()) {
process(receivingPipe.read());

// Start reading again right away to read as fast as possible.
receivingPipe.beginRead();
}
}

/**
* Process a value read from any port.
*
* @param value the value that was read.
*/
private void process(final int value) {
drawCall[state.ordinal()] = (byte) (value & 0xFF);
state = state.getNext();
if (state == State.COLOR) {
// Draw call completed, apply and send to client.
applyDrawCall(drawCall);
sendDrawCall();
}
}

/**
* Apply a draw call encoded in the specified byte array to our data array.
*
* @param drawCall the draw call to apply.
*/
private void applyDrawCall(final byte[] drawCall) {
final byte color = drawCall[State.COLOR.ordinal()];
final byte xin = drawCall[State.X.ordinal()];
final byte yin = drawCall[State.Y.ordinal()];
final byte w = drawCall[State.W.ordinal()];
final byte h = drawCall[State.H.ordinal()];

final int x0 = MARGIN + Math.max(0, xin);
final int x1 = MARGIN + Math.min(RESOLUTION - 2 * MARGIN, xin + w);
final int y0 = MARGIN + Math.max(0, yin);
final int y1 = MARGIN + Math.min(RESOLUTION - 2 * MARGIN, yin + h);

for (int y = y0; y < y1; y++) {
final int offset = y * RESOLUTION;
for (int x = x0; x < x1; x++) {
final int index = offset + x;
image[index] = COLORS[color % COLORS.length];
}
}
}

/**
* Getter for the ID of the texture on the GPU we're using, creates one if necessary.
*
* @return the texture ID we're currently using.
*/
private int getGlTextureId() {
if (glTextureId == 0) {
glTextureId = GlStateManager.generateTexture();
TextureUtil.allocateTexture(glTextureId, RESOLUTION, RESOLUTION);
TextureUtil.uploadTexture(glTextureId, image, RESOLUTION, RESOLUTION);
}
return glTextureId;
}

/**
* Deletes our texture from the GPU, if we have one.
*/
private void deleteTexture() {
if (glTextureId != 0) {
TextureUtil.deleteTexture(glTextureId);
glTextureId = 0;
}
}

/**
* Indicate to our client representation to clear the image data.
*/
private void sendClear() {
final NBTTagCompound nbt = new NBTTagCompound();
nbt.setBoolean(TAG_CLEAR, true);
getCasing().sendData(getFace(), nbt);
}

/**
* Send a draw call to our client representation.
*/
private void sendDrawCall() {
final NBTTagCompound nbt = new NBTTagCompound();
nbt.setByteArray(TAG_DRAW_CALL, drawCall);
getCasing().sendData(getFace(), nbt);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package li.cil.tis3d.common.provider;

import li.cil.tis3d.api.API;
import li.cil.tis3d.api.machine.Casing;
import li.cil.tis3d.api.machine.Face;
import li.cil.tis3d.api.module.Module;
import li.cil.tis3d.api.module.ModuleProvider;
import li.cil.tis3d.common.Constants;
import li.cil.tis3d.common.module.ModuleDisplay;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraftforge.fml.common.registry.GameRegistry;

/**
* The provider for the display module.
*/
public final class ModuleProviderDisplay implements ModuleProvider {
private final Item item = GameRegistry.findItem(API.MOD_ID, Constants.NAME_ITEM_MODULE_DISPLAY);

@Override
public boolean worksWith(final ItemStack stack, final Casing casing, final Face face) {
return stack.getItem() == item;
}

@Override
public Module createModule(final ItemStack stack, final Casing casing, final Face face) {
return new ModuleDisplay(casing, face);
}
}
14 changes: 14 additions & 0 deletions src/main/resources/assets/tis3d/blockstates/moduleDisplay.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"forge_marker": 1,
"defaults": {
"textures": {},
"model": "tis3d:module.obj"
},
"variants": {
"normal": [{}],
"inventory": [{
"transform": "forge:default-block",
"textures" : { "#casing:Module" : "tis3d:items/moduleDisplay" }
}]
}
}
Loading

0 comments on commit 7309f4d

Please sign in to comment.