Sign in to follow this  

[java] Creating first-person view walls,ceiling,floor

This topic is 3860 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi. I hope you understand what I mean when I am calling it a first person view of walls (to your left and right), ceiling, and floor. Basically, tiles viewed from a certain perspective. I am wondering if anyone has created methods to construct these kind of perspective images before (Not using 3d, - pure java2d). I created methods for doing it, and it does so pretty accurately, however the performance is nothing to write home about. It's not methods you'd call on each cycle (rather on init stages). Basically, I am interested in finding out what your approach would be. I tried using different transforms at first (shearing etc), but I couldn't create the desired effect. In the end I ended up with splitting the src image up into X images (based on an interval along the x-axis of the src image), and scaling each individual image along the Y-axis, based on a given angle provided as a parameter. (I always split on the x-axis, and perform a rotate afterwards, to get floor/ceiling). This approach works, but it involves (based on quality setting) manipulating 256 images for a 256*256 tile. Each of the 256 images are only (1*256) in size, but still, the performance won't scale very well once you get to larger tiles. So, I am interested in input! Different approaches anyone?

Share this post


Link to post
Share on other sites
I split prior to rendering, yes. So basically it's a once-in-a-lifetime thing for each tile. But that's only my current scenario. There might be cases where you want to create this stuff on the fly as well.

I'll post some code later tonight (baking a cake right now hehe).

Share this post


Link to post
Share on other sites
Hi. I optimized it a bit since my last note, but I'll post the code here anyway. Hoping for suggestion on improving it ;)


/**
* @param src (The image that holds the texture. Should be a quadrant for best quality.
* @param pivout (The point along the x-axis where the perspective should begin. The pivout thus
* acts as a corner in the wall, the portion of the wall that is before the pivout
* appears as if you stare right into it).
* @param angle (The angle that the wall's perspective will be based on. 0..90)
* @param scale (Scaling factor along the x-axis for the portion of the wall after the pivout,
* ie the perspective portion of the wall.
* @return An image with a perspective wall. The image is guaranteed to be of the same size
* as the src. A high pivout and shallow angles does not matter, the image is cut
* at its fixed size.
*/

public BufferedImage createPerspectiveWall(BufferedImage src, int pivout, double angle, double scale)
{
// The relative height for a row on a given x coordinate.
int relativeHeight;
// Width of source image
int sw = src.getWidth();
// Height of source image
int sh = src.getHeight();
// Angle. Truncate angle to space 0..90
angle = Math.abs(angle)>90?90:Math.abs(angle);
// Heightfactor, - dependant on angle.
int heightFactor = (int)Math.round( ((angle/45) * ( sh ) ) );
// Create result image with source image dimensions.
BufferedImage resultImage = new BufferedImage(sw,sh,BufferedImage.TYPE_INT_ARGB);
// Get its graphics object.
Graphics2D g = resultImage.createGraphics();

// If there is a pivout, draw the portion along the x axis from 0..pivout
// without any scalation or "effects" whatsoever.
if(pivout > 0)
{
g.drawImage(src,
0,
0,
pivout,
resultImage.getHeight(),
0,
0,
pivout,
sh,
null);
}

// translate the graphics object, so that it's zeroed in after the pivout.
g.translate(pivout, 0);
// scale the remainder of the drawing.
g.scale(scale,1);
// Antialias in an attempt to soften corners.
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
// loop along the x axis until end of image (This is misleading, but remember we translated
// the graphics object!
for(int i = 0; i < (sw-pivout); i++)
{
// find the relative height on
relativeHeight = (int)Math.round((1 - (i/(double)sh))*heightFactor) + ( sh - heightFactor );
g.drawImage(src,
i,
(sh/2) - (relativeHeight/2),
i+1,
(sh/2) - (relativeHeight/2) + relativeHeight,
i + pivout,
0,
i+ 1 + pivout,
sh,
null);

}
// translate back,dispose, and return image
g.translate(-pivout,0);
g.dispose();
return resultImage;
}




Of course, this is only code for a left wall, but I can use the same code with a little transformation to create all kind of walls.

Share this post


Link to post
Share on other sites
When you create your buffered image, you should use GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().createCompatibleImage(sw, sh);. This will give you a better chance of having the image put in video card memory.

Don't use antialiasing. I think it is almost always done in software, so it is very slow. To test, just turn it off and see how fast it goes.

Method calls are very expensive and casting can be expensive as well. You should try not to do them in a loop. Replace:
(int)Math.round((1 - (i/(double)sh))*heightFactor) + ( sh - heightFactor );
with:

//outside loop
double dsh = sh;
//inside the loop only 1 cast instead of 2
relativeHeight = (int)((((1 - (i / dsh)) * heightFactor) + (sh - heightFactor)) + 0.5);



Reduce repeated operations by storing them in a temporary variable.

//saves 4 divides and 2 subtractions per loop
int tempHeight = (sh/2) - (relativeHeight/2)
for(int i = 0; i < (sw-pivout); i++) {
relativeHeight = (int)((((1 - (i / dsh)) * heightFactor) + (sh - heightFactor)) + 0.5);

int anotherTemp = i + pivout;
g.drawImage(src,
i,
tempHeight,
i+1,
tempHeight + relativeHeight,
anotherTemp,
0,
anotherTemp + 1,
sh,
null);
}
{/source]

Also try to profile your code. The problems may not be in the rendering that you show here.

Share this post


Link to post
Share on other sites
Hey. Thanks.

I usually try to declare outside loops to prevent inloop overhead. Must have forgotten it here. So nice with an extra pair of eyes! :-)

As to the gc.createCompat() vs. new Buffered() - I always, I mean always, used gc.createCompat() before. And people keep telling me, that since 1.5 - it doesn't really matter. I guess it does not hurt to be 110% sure though, so I'll change it.

The performance is no longer horrible. It's adequate I guess. I was, and am still, interested in what everyone thinks about the approach itself. There might be other approaches, totally different concepts, that might work just as well, and be more flexible/faster/better etc.

Thanks again for feedback, CJ.

Share this post


Link to post
Share on other sites
I am not sure if it would be faster, but you can look at writing to the image data directly. You use an array of bytes that will represent the data. The bytes will need to be in the same order as the BufferedImage (IE RGBA or ARGB).

The way you do that is with the WriteableRaster.

BufferedImage.getRaster().getDataElelements(...)
BufferedImage.getRaster().setDataElelements(...)

You can also ask at http://www.javagaming.org/. There are a lot of people over there that can answer these questions, but they don't visit here.

Share this post


Link to post
Share on other sites

This topic is 3860 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this