Texture reloading just turns objects white

Started by Cowbox, October 15, 2012, 04:23:37 PM

Previous topic - Next topic

Cowbox

Same voxel engine as the last thread I made.

All I'm trying to do is implement saving and loading.

Saving works, a little slower than I had expected, but at least outputs files in .png with ARGB.

Loading the textures for all the layers of voxels only works the first time when I run this method:
private void loadWorld(String name)
{
System.out.println("Loading "+name);
// Load each layer image
for(int i=0;i<layerNumber;i++)
{
Texture t=new Texture("user/"+name+"/layer"+i+".png",true);
Texture t2=new Texture("user/"+name+"/layer"+i+".png",true);
t.setGLFiltering(false);
tm.removeTexture("layer"+i);
tm.removeTexture("layerg"+i);
tm.addTexture("layer"+i,t);
tm.addTexture("layerg"+i,t2);
ids[i]=tm.getTextureID("layer"+i);
idgs[i]=tm.getTextureID("layerg"+i);
layers[i]=new TextureInfo(ids[i]);
layers[i].add(tm.getTextureID("floorTexN"),TextureInfo.MODE_MODULATE);
layergs[i]=new TextureInfo(idgs[i]);
pics[i]=Toolkit.getDefaultToolkit().getImage("user/"+name+"/layer"+i+".png");
}
System.out.println("Loaded "+name);
}

(ids and idgs, are just for storing the layer IDs for faster editing elsewhere (so it's not searching for strings))

The second time I run it, all the objects those textures apply to are just plain white. D:

What is going wrong then? What should I be doing?

NOTE:
I tried using replaceTexture but all that did was replace it then reload it from the first instance it loaded the second I tried editing... So I thought I'd go down a more manual route and it's just getting me to an obvious error I can recreate than the glitchy replacement problems replaceTexture was giving me. :P

Haaaaaaaalp :'( (plz)

EgonOlsen

Why do you want to reload the textures if they already exist in the manager? Anyway, if you do this, you should use replaceTexture. Sadly, i don't get what the problem with using that method should have been, so i can't answer what went wrong. Maybe you can explain the problem with replaceTexture in more details.
Doing it your way, creates new internal ids for the textures. The current objects will still use the old ones, can't find their textures anymore and turn white instead. If you do a setTexture() on each object again, it will work again. replaceTexture() keeps the ids, which is why it's the better approach. The best approach is not to reload the texture...like i said in the first sentence, i'm not sure why you are doing that!?

Cowbox

#2
Ok, I've changed it to replaceTexture and eliminated some of the repeated lines that I clearly don't need.

This is the loadWorld method that gets called once at the start and any other time the user presses F9 (to reload a previous version of the world after editing.)

/**
* Load the entire world
* @param name The name of the world to load
*/
private void loadWorld(String name)
{
System.out.println("Loading "+name);
// Load each layer image
for(int i=0;i<layerNumber;i++)
{
Texture t=new Texture("user/"+name+"/layer"+i+".png",true);
Texture t2=new Texture("user/"+name+"/layer"+i+".png",true);
t.setGLFiltering(false);
tm.replaceTexture(ids[i],t);
tm.replaceTexture(idgs[i],t2);
pics[i]=Toolkit.getDefaultToolkit().getImage("user/"+name+"/layer"+i+".png");
}
System.out.println("Loaded "+name);
}


This is one of the places where voxels are edited (when clicking the mouse, it adds a voxel to the layer), it uses replaceTexture successfully, and edits pics[] (which is an array of java.awt.Images for saving/loading/creating textures from)
for(int testing=layerNumber-2;testing>-1;testing--)
{
BufferedImage dest=new BufferedImage(mapSize,mapSize,BufferedImage.TYPE_INT_ARGB);
Graphics g=dest.getGraphics();
g.drawImage(pics[testing],0,0,null);
int digX=mapSize-((int)digger.getTranslation().x+1),
digY=mapSize-((int)digger.getTranslation().z+1);
// If the pixel is transparent
if((dest.getRGB(digX,digY)&0xFF000000)<0)
{
testing++;
dest=new BufferedImage(mapSize,mapSize,BufferedImage.TYPE_INT_ARGB);
g=dest.getGraphics();
g.drawImage(pics[testing],0,0,null);
dest.setRGB(digX,digY,0xFF808080);
g.dispose();
pics[testing]=Toolkit.getDefaultToolkit().createImage(dest.getSource());
tmp=new Texture(pics[testing],true);
tmp.setGLFiltering(false);
tm.replaceTexture(ids[testing],tmp);
tm.replaceTexture(idgs[testing],new Texture(pics[testing],true));
break;
}
g.dispose();
}


For some reason, if I load at startup, edit the layers, save the map (it works fine up to here, the images really do save correctly) then load the map again, if I edit the map after this 2nd load, it will use the images from the first load and ignore what I just saved... - The textures literally revert back infront of me to the previous versions and what I drew between 1st load and 1st save disapears.

What is going on there :(

EDIT:
Here are some pictures showing exactly what happens at each step:

This is an area of the map after the first load

This is an area of the map after drawing some voxels on it (editing the images and replaceTextureing)

This is after saving, obviously, nothing changed, it's written the files successfully though

This is after drawing some new voxels (just to prove the load worked)

This is after loading (note, it loads correctly)

This is after drawing fresh voxels on the newly loaded map (note all the stuff on the right of the tree is gone :( )

EgonOlsen

I would think that the problem is somewhere in your code, because i've no idea how the engine should be able to dig out texture data from three iterations before. There's no cache or anything like that for this data.
Looking at your code snippets, i have to admit that i've no clue what this is supposed to do...create some BI, draw another image into that, create a new image from it and replace the one in the array with it!? Then create two new texture from it...i don't get the purpose, so i can't comment on that. From the usage of replaceTexture and such, i don't see a problem there though.

You might want to set the Logger to debug...maybe that gives some additional hints.

Another hint: If possible, use an ITextureEffect implementation instead of creating textures and BufferedImages all the time. It'll be way faster.

Cowbox

#4
Ok, I'll give you a quick rundown of how it all works just to set some grounding for what I'm doing/attempting:

The voxel world is generated out of 20 .png images with ARGB pixels, the images are loaded in into 2 textures.
The first is a texture with GLFiltering on, so the texels are blurred. The second is the exact same image load but without GLFiltering.
(I've been storing the texture IDs for these textures in an int[] array instead of having to use the string references each time I want to change things.)
The textures are put onto plains with transparency turned on. This obviously means that the see through parts on the textures enable the user to see other layers through a higher up one.
The plains are separated in the Y dimension so that the textures appear to create voxels and occupy 3D space purely by the fact there are 20 1024x1024 layers.
Where the engine so far implies I'm using 1 plain per image, equating to 20 layers and 20 plains, this isn't quite accurate.
This leads to a very flat and blocky feel. To make the voxels seem more volumetric, I have another number of intermediate layers, these still use the texture of the overall layer, but are placed inbetween those main layers. (This number is not determined yet; on my laptop, I use from about 2 to 5 intermediate layers, and on my desktop I use between 8 and 20)
All of this combined means that the number of plains being rendered with an ARGB texture can range between 40 and 400 depending on what I'm doing and where I am. (This means the framerate, even on a good computer can be as low as 60 or 70.)
The other thing I'm doing is with the 2nd texture that is loaded, with the GLFiltering on texels. This is simply used as the bottom of the stack of duplicate layers to add shadowing, so that needs to be regenerated alongside the normal unfiltered texture used in the majority of the layers.
(A side point is that the top most layer also has normal mapping on it but that seems to be working fine, so ignore it for now, if I took it out, the engine functionality would not change and nothing more/less would break.)

Here's a quick picture showing all of this stuff:


This is just a quick comparison of what it would look like without normal mapping or the shadow layer:


Right, that explains how the engine works.

What I'm doing now, is simply allowing the user to save the world after editing with F5 and load the world with F9.

All I am able to do when I'm in the actual program is draw voxels in empty slots (where the alpha value is 0) with the left mouse button and remove them with the right mouse button. This is all working brilliantly (if a little slow, but we'll get to the ITextureEffect in a bit).
This is where all the creating buffered images and drawing stuff comes from.

In order to edit the texture data (the voxels) I need to be able to manually set pixels (or even better, texels) to a value. To do this, I'm having to keep a record of the image loaded for the textures and use it when drawing pixels on mouseclick.
I've got to get a graphics context for an editable image, draw the record image for the texture being edited onto it, draw what I'm drawing onto that (usually a single pixel, a line or subtracting some pixels) then reload that into a new texture. (As I said, this all works fine.)

This all worked perfectly when I did this in Slick2D as well, so I know the procedure, it's just messing up with things like replaceTexture for some reason.

The saving is working. If I save my map and alt tab to where the 20 .pngs are saved, I can see the pixels have been edited.

For some reason when I load the images a 2nd time from file though, it seems to latch onto the old version as opposed to the new loaded version.

If you can see a much more concise and more efficient way to edit texture data that I can save to and load from files, I welcome it. - The method I'm using right now, while I understand what it's doing, it is really slow, and is having problems clearly. :(
(Could ITextureEffect achieve all this?)

Cowbox

#5
Wow... that's just ridiculous.

I wasn't even trying to solve it, all I was doing was making it more efficient and I replaced this line in the loadWorld method:
pics[i]=Toolkit.getDefaultToolkit().getImage("user/"+name+"/layer"+i+".png");
with this:
pics[i]=ImageIO.read(new File("user/"+name+"/layer"+i+".png"));
And suddenly it all works 100% fine.

What the hell is wrong with that line that should make it behave like that? xD

I was dead sure everything was good and working, but sure enough, on the 2nd call of that line, it wouldn't work. Somehow, this new ImageIO method is doing what I really wanted, whilst doing the same thing on 1st call. D:

EDIT:
I am still intrigued as to whether there is a way to make the editing of voxels faster with some other methods. :S

Whilst the engine is powerful, it would be open to much more possibility with faster texture interaction. :D

EgonOlsen

Most likely some caching is going on that caused the problem. The docs for that method say:

QuoteThe underlying toolkit attempts to resolve multiple requests with the same filename to the same returned Image.

...it might not be aware of the fact that the image may have changed between the calls and returns an old image.

About the speedup: Have a look at the ITextureEffect. It gives you direct access to the pixel data of a texture and might help to speed things up.

Cowbox

:O

I don't understand how something with 3 methods (none of which seem to correspond to texel or pixel interaction) can do all that?

xD Please enlighten me, I really am interested in speeding this all up. :D

EgonOlsen

In the apply()-method, you have a dest array. You have to write your new texture's pixels into that array. So:


  • set your implemention to the target texture with setEffect()
  • call applyEffect() on the texture each time it should be updated
  • ...that will call the apply()-method automatically. In that method, fill the dest[]-array with the new pixels in ARGB.
  • Keep in mind that the dest[]-array is a bit larger than you might expect. That's a tribut to the software renderer. Just leave the extra pixels alone.

Cowbox

I don't really understand how to use an interface like this. (I only really know how things like KeyListener work with the main class, I don't really know how to use an interface on something.)

I'm also not seeing any examples of usage of this kinda thing. :(

(Also debating whether it'd allow me to save the images back out, but that should surely be a matter of creating java.awt.Images from int[]s yeah?)

EgonOlsen

Somehow like this...it's very basic pseudo code and might not be suitable for your case, but it should get you started:


import java.awt.Color;
import java.awt.Image;

import com.threed.jpct.ITextureEffect;
import com.threed.jpct.Texture;


public class Example
{

  public static void main(String[] args)
  {
    Image image = loadImage();
    Texture tex = new Texture(image, true);
    PixelArray pixels = new PixelArray(image);
    ITextureEffect effect = new TextureChanger(pixels);
    tex.setEffect(effect);

    // ...

    pixels.setPixel(100, 100, Color.BLACK);

    // ...

    tex.applyEffect();
  }


  private static Image loadImage()
  {
    // TODO Load the image here
    return null;
  }


  private static class PixelArray
  {
    private int[] pixels;
    private int width;
    private int height;


    public PixelArray(Image source)
    {
      // TODO extract your texture's pixels from the source image and store it inside pixels here.
      width = source.getWidth(null);
      height = source.getHeight(null);
    }


    public void setPixel(int x, int y, Color col)
    {
      int pos = x + y * (width);
      if (pos > pixels.length || pos < 0)
      {
        return;
      }
      int c = col.getAlpha() << 24 | col.getRed() << 16 | col.getGreen() << 8 | col.getBlue();
      pixels[pos] = c;
    }


    public int[] getPixels()
    {
      return pixels;
    }
  }

  private static class TextureChanger
    implements ITextureEffect
  {

    private PixelArray source = null;


    public TextureChanger(PixelArray pixels)
    {
      this.source = pixels;
    }


    @Override
    public void apply(int[] dest, int[] source)
    {
      System.arraycopy(this.source, 0, dest, 0, this.source.getPixels().length);
    }


    @Override
    public boolean containsAlpha()
    {
      return false;
    }


    @Override
    public void init(Texture tex)
    {
    // Nothing to do here
    }
  }
}


Cowbox

Ok, I tried utilizing that code but it seems to be just as slow as the method I was already using. :(

I also can't get it to work.

Doing something like this:
PixelArray pixels=new PixelArray(pics[17]);
pixels.setPixel(512,512,Color.BLACK);
pixels.setPixel(513,511,Color.WHITE);
texs[17].applyEffect();

Just to draw some arbitrary voxels in the middle of a layer I could clearly see, didn't seem to change anything. :(

I don't know if I coded everything correctly because I had to fix this line:
System.arraycopy(source,0,this.dest,0,this.source.getPixels().length);
As it was giving me an arraycopy error.
I changed it to this:
System.arraycopy(source,0,dest,0,this.source.getPixels().length);
So the program runs but doesn't actually update anything. D:



I can't tell if the method really is as slow as mine or if it's because I had to create an array of ITextureEffects (1 for each layer), 2 an arrays of textures (2 for each layer) and each time a draw is called, it had to recreate the pixel object and convert the layer into a BufferedImage. (Which it was originally doing in mine anyhow. xD)

EgonOlsen

The idea was to drop these constant creations of BufferedImages/pixel arrays and work on the pixels directly unless you need an Image to save it. Updating the texture via an ITextureEffect isn't free, but it should be much faster than this create/copy/extract/convert stuff with the BufferedImages.

This...

System.arraycopy(source,0,this.dest,0,this.source.getPixels().length);

...is bogus and it's not what i wrote in that code snippet above. It should read


System.arraycopy(this.source, 0, dest, 0, this.source.getPixels().length);


Because source without this. refers to the local method parameter and that's not what you want. If that call gives an error, your arrays are somehow fishy. dest has the size of the texture in pixels+something. this.source should have the size of the texture. If this.source is larger than dest, you are creating it wrong or the effect has been assigned to the wrong texture. How do you get the pixel array for the PixelArray-instance from your image?

Cowbox

#13
I copied that wrong, the line given with this. infront of source comes back with this error when attempting to apply:
Exception in thread "main" java.lang.ArrayStoreException
at java.lang.System.arraycopy(Native Method)
at voxeng5.Voxeng5$TextureChanger.apply(Voxeng5.java:828)
at com.threed.jpct.Texture.applyEffect(Unknown Source)
at voxeng5.Voxeng5.input(Voxeng5.java:458)
at voxeng5.Voxeng5.gameLoop(Voxeng5.java:363)
at voxeng5.Voxeng5.main(Voxeng5.java:60)
Java Result: 1


If I change it to just source, it works but doesn't actually draw anything on the texture. :(

I am converting the image into the int array with this code snippet:
public int[] getImagePixels(BufferedImage image)
{
    PixelGrabber grabber;
    int[] pixels = new int[image.getWidth() * image.getHeight()];
    try
    {
        grabber = new PixelGrabber(image, 0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
        grabber.grabPixels(0);

    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
    return pixels;
}


And converting the Image to parse into that as a BufferedImage using this method that was already present in my program:
private BufferedImage imageToBufferedImage(Image image,int width,int height)
{
BufferedImage dest=new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
Graphics g=dest.getGraphics();
g.drawImage(image,0,0,null);
g.dispose();
return dest;
}


I currently have the code sat with all this implemented, the speed is good when drawing but it doesn't draw anything. :( I'm missing like on thing out. xD

The major fallback of this is that there isn't really a way to save this back out after editing a texture. :( (Oh wait, or is that where getPixels() would come in handy? - And I'd just recreate the image from an int[]?)

Cowbox

#14
Aha!

I've got it!

But for some reason it turns everything blue xD!


Damn, I'm so close xD!

The performance is really nice with this, it's just turning everything blue for no reason.

(Also, saving works, so that's a miracle.)



EDIT:
There also seems to be a problem with the setPixel method.

If I set a voxel to a colour value of 0,0,0,0 it just shows up as black and ignores the alpha.

I changed it so it accepted ints instead of Colors and can't seem to get transparency at all.

If I set a pixel to 0 it keeps a transparent pixel the same but sets a non transparent one to black.

If I set a pixel to 0xFF000000 (the transparency mask I'm using) it sets the pixels to be non transparent (and blue of course).

I don't know whether these are issues to do with this blueing of the map that would disappear when it's fixed, but it's worth taking a note of. :D

I'm seriously stumped on why it turns blue still... :( I'm desperately looking for a solution to that one.