Skip to content

Commit

Permalink
Fire texture generator
Browse files Browse the repository at this point in the history
This was a real doozy! Trying to figure out how I should scale the fire was a challenge, but I managed to figure it out in the end.
  • Loading branch information
NeRdTheNed committed Sep 15, 2021
1 parent e3c3859 commit 90944f8
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The program can also generate some non-deterministic textures, if a valid value

- Water textures from Minecraft Classic 0.0.19a
- Lava textures from Minecraft Classic 0.0.19a and 0.0.22a
- Fire textures

## How to generate the textures.

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/mcTextureGen/MCTextureGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import mcTextureGen.generators.Classic19aLavaGenerator;
import mcTextureGen.generators.Classic19aWaterGenerator;
import mcTextureGen.generators.Classic22aLavaGenerator;
import mcTextureGen.generators.FireGenerator;
import mcTextureGen.generators.GearRotationFramesGenerator;
import mcTextureGen.generators.MC4k1Generator;
import mcTextureGen.generators.MC4k2Generator;
Expand All @@ -20,7 +21,7 @@ public final class MCTextureGenerator {
// private static boolean hasDebugInfo = true;

public static AbstractTextureGenerator[] getTextureGenerators() {
return new AbstractTextureGenerator[] { new MC4k1Generator(), new MC4k2Generator(), new GearRotationFramesGenerator(), new NetherPortalGenerator(), new Classic19aWaterGenerator(), new Classic19aLavaGenerator(), new Classic22aLavaGenerator() };
return new AbstractTextureGenerator[] { new MC4k1Generator(), new MC4k2Generator(), new GearRotationFramesGenerator(), new NetherPortalGenerator(), new Classic19aWaterGenerator(), new Classic19aLavaGenerator(), new Classic22aLavaGenerator(), new FireGenerator() };
}

public static void main(final String[] args) {
Expand Down
120 changes: 120 additions & 0 deletions src/main/java/mcTextureGen/generators/FireGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package mcTextureGen.generators;

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.util.Random;

import mcTextureGen.data.TextureGroup;

public final class FireGenerator extends AbstractTextureGenerator {

private static final int fireMulti = 1;

// Set up fire texture size
private static final int fireTextureWidth = 16 * fireMulti;
private static final int fireTextureHeight = 20 * fireMulti;

// Pixels to sample from.
private static final int sampleXLeft = 1;
private static final int sampleXRight = 1;
private static final int sampleYDown = 0;
private static final int sampleYUp = 1;

// This was originally a counter that was incremented inside the code that sampled neighboring pixels,
// but the counter would always end up at the same value (24, purely deterministic at build time).
private static final int selfSamples = 1;
private static final int sampleCounterStart = 18 * fireMulti;
private static final int sampleCounterIterations = (sampleXLeft + selfSamples + sampleXRight) * (sampleYDown + selfSamples + sampleYUp);
private static final int sampleCounterEnd = sampleCounterStart + sampleCounterIterations;

// wtFloat is a very strange constant. I felt the name was appropriate, given how hard it was to pin down.
// These sorts of constants keep me awake at night.

// Earlier versions of Minecraft.
//private static final float wtFloat = 1.06F;
// Later versions.
//private static final float wtFloat = 1.0600001F;

// This is the current method I've settled on to determine the scaled value of wtFloat.
// If fireMulti is 1, it results in 1.06F.
private static final float wtFloat = 1.0F + (selfSamples * 0.01F) + (((sampleCounterIterations - selfSamples) * 0.01F) / fireMulti);

// TODO It's rumored that some versions of pocket edition have this as 25.2?
// If that's true, removing " + (selfSamples * 0.01F)" from wtFloat might simulate this.
private static final float divPixelIntensity = sampleCounterEnd * wtFloat;

@Override
public String getGeneratorName() {
return "Fire";
}

@Override
public TextureGroup[] getTextureGroups() {
return new TextureGroup[] { fireTextures() };
}

private TextureGroup fireTextures() {
float[] fireImagePrevious = new float[fireTextureWidth * fireTextureHeight];
float[] fireImageCurrent = new float[fireTextureWidth * fireTextureHeight];
final Random rand = getRandom();
final BufferedImage[] fireImages = new BufferedImage[nonDeterministicFrames];

for (int currentFrame = 0; currentFrame < nonDeterministicFrames; currentFrame++) {
for (int fireX = 0; fireX < fireTextureWidth; ++fireX) {
// Loop over every row except the bottom row
for (int fireY = 0; fireY < (fireTextureHeight - 1); ++fireY) {
// Start by sampling the pixel above
float pixelIntensity = fireImagePrevious[fireX + ((fireY + 1) * fireTextureWidth)] * sampleCounterStart;

// Sample from one pixel left to one pixel right, on this row and the above row
for (int localFireX = fireX - sampleXLeft; localFireX <= (fireX + sampleXRight); ++localFireX) {
for (int localFireY = fireY - sampleYDown; localFireY <= (fireY + sampleYUp); ++localFireY) {
// Check to make sure the pixel we're sampling is in bounds
if ((localFireX >= 0) && (localFireY >= 0) && (localFireX < fireTextureWidth) && (localFireY < fireTextureHeight)) {
pixelIntensity += fireImagePrevious[localFireX + (localFireY * fireTextureWidth)];
}
}
}

// Set pixel value, compensating for the additional sampling
fireImageCurrent[fireX + (fireY * fireTextureWidth)] = pixelIntensity / divPixelIntensity;
}

// Randomize bottom row of pixels
fireImageCurrent[fireX + ((fireTextureHeight - 1) * fireTextureWidth)] = (float)((rand.nextDouble() * rand.nextDouble() * rand.nextDouble() * (3 + fireMulti)) + (rand.nextDouble() * 0.1F) + 0.2F);
}

final float[] fireImageCurrentTemp = fireImageCurrent;
fireImageCurrent = fireImagePrevious;
fireImagePrevious = fireImageCurrentTemp;
final BufferedImage currentFireImage = new BufferedImage(fireTextureWidth, fireTextureHeight, BufferedImage.TYPE_4BYTE_ABGR);
final byte[] imageByteData = ((DataBufferByte) currentFireImage.getRaster().getDataBuffer()).getData();

for (int currentPixel = 0; currentPixel < (fireTextureWidth * fireTextureHeight); ++currentPixel) {
// Boost intensity
float pixelIntensity = fireImagePrevious[currentPixel] * 1.8F;

// Clamp pixel intensity
if (pixelIntensity > 1.0F) {
pixelIntensity = 1.0F;
}

if (pixelIntensity < 0.0F) {
pixelIntensity = 0.0F;
}

final int imageOffset = currentPixel * 4;
// If the pixel isn't intense enough, make it transparent
imageByteData[imageOffset + 0] = (byte) (pixelIntensity < 0.5F ? 0 : 255);
imageByteData[imageOffset + 1] = (byte) (pixelIntensity * pixelIntensity * pixelIntensity * pixelIntensity * pixelIntensity * pixelIntensity * pixelIntensity * pixelIntensity * pixelIntensity * pixelIntensity * 255.0F);
imageByteData[imageOffset + 2] = (byte) (pixelIntensity * pixelIntensity * 255.0F);
imageByteData[imageOffset + 3] = (byte) ((pixelIntensity * 155.0F) + 100.0F);
}

fireImages[currentFrame] = currentFireImage;
}

return new TextureGroup("Fire_Textures", fireImages);
}

}

0 comments on commit 90944f8

Please sign in to comment.