I want to make a Java program, in which moving sky is generated out of Simplex Noise, but I have performance issues (framerate is too low). I'm using https://github.com/KdotJPG/OpenSimplex2/blob/master/java/OpenSimplex2F.java noise class.
My function which generates the sky, generates it entirely again each frame:
private void generate() {
float y = offset;
for (int i = 0; i < frame.getHeight(); i++) {
float x = offset;
for (int j = 0; j < frame.getWidth(); j++) {
double a = noise.noise2(x, y) + 0.25f * noise.noise2(2 * x, 2 * y) + 0.125f * noise.noise2(4 * x, 4 * y);
a = a / (1 + 0.25 + 0.125);
a = (a + 1) / 2;
a *= 100;
Color color = Color.getHSBColor(220f / 360f, a / 100f, 1f);
background.setRGB(j, i, color.getRGB());
x += noiseResolution;
}
y += noiseResolution;
}
}
Where background is BufferedImage I'm drawing on and offset says how much noise is moved.
I have tried to save an array of pixels of background each frame and translate it by the number of pixels it should be moved, and then I generated only pixels that are new. Unfortunately, because it was rendered too fast then, the number of pixels it should be moved was e.g. 0.2, so I couldn't translate array indices by a fraction.
So I guess the only way is to somehow generate it in another way, but I totally have no idea how.
Thanks!
Not sure about Java, but in C++ with DirectX, OpenGL or any low level interface like that, this should be easily done on the GPU in either HLSL (DirectX), or GLSL (OpenGL). I implemented 5D Simplex noise and even zoomed in so it fills my large screen, and on my 9 year old computer with my old ho-hum graphics card it still does a couple hundred frames per second. Here's what it looks like.
https://www.youtube.com/watch?v=oRO1IGcWIwg
If you can run a fragment shader from Java, I would think that would be the way to go. I think there is some OpenGL interface in Java so you might want to look int that.
Related
I have a Java program written in Processing I made that draws a spiral in processing but I am not sure how some of the lines of code work. I wrote them based on a tutorial. I added comments in capital letters to the lines I do not understand. The comments in lowercase are lines that I do understand. If you understand how those lines work, please explain in very simple terms! Thank you so much.
void setup()
{
size(500,500);
frameRate(15);
}
void draw()
{
background(0); //fills background with black
noStroke(); //gets rid of stroke
int circlenumber = 999;// determines how many circles will be drawn
float radius = 5; //radius of each small circle
float area = (radius) * (radius) * PI; //area of each small circle
float total = 0; //total areas of circles already drawn
float offset = frameCount * 0.01; //HOW DOES IT WORK & WHAT DOES IT DO
for (int i = 1; i <= circlenumber; ++i) { // loops through all of the circles making up the pattern
float angle = i*19 + offset; //HOW DOES IT WORK & WHAT DOES IT DO
total += area; // adds up the areas of all the small circles that have already been drawn
float amplitude = sqrt( total / PI ); //amplitude of trigonometric spiral
float x = width/2 + cos(angle) * amplitude;//HOW DOES IT WORK & WHAT DOES IT DO
float hue = i;//determines circle color based on circle number
fill(hue, 44, 255);//fills circle with that color
ellipse(x, 1*i, radius*2, radius*2); //draws circle
}
}
Essentially what this is doing is doing a vertical cosine curve with a changing amplitude. Here is a link to a similar thing to what the program is doing. https://www.desmos.com/calculator/p9lwmvknkh
Here is an explanation of this different parts in order. I'm gonna reference some of the variables from the link I provided:
float offset = frameCount * 0.01
What this is doing is determining how quickly the cosine curve is animating. It is the "a" value from desmos. To have the program run, each ellipse must change its angle in the cosine function just a little bit each frame so that it moves. frameCount is a variable that stores the current amount of frames that the animation/sketch has run for, and it goes up every frame, similar to the a-value being animated.
for (int i = 1; i <= circlenumber; ++i) {
float angle = i*19 + offset;
This here is responsible for determining how far from the top the current ellipse should be, modified by a stretching factor. It's increasing each time so that each ellipse is slightly further along in the cosine curve. This is equivalent to the 5(y+a) from desmos. The y-value is the i as it is the dependent variable. That is the case because for each ellipse we need to determine how far it is from the top and then how far it is from the centre. The offset is the a-value because of the reasons discussed above.
float x = width/2 + cos(angle) * amplitude
This calculates how far the ellipse is from the centre of the screen (x-centre, y value is determined for each ellipse by which ellipse it is). The width/2 is simply moving all of the ellipses around the centre line. If you notice on Desmos, the center line is y-axis. Since in Processing, if something goes off screen (either below 0 or above width), we don't actually see it, the tutorial said to offset it so the whole thing shows. The cos(angle)*amplitude is essentially the whole function on Desmos. cos(angle) is the cosine part, while amplitude is the stuff before that. What this can be treated as is essentially just a scaled version of the dependent variable. On desmos, what I'm doing is sqrt(-y+4) while the tutorial essentially did sqrt(25*i). Every frame, the total (area) is reset to 0. Every time we draw a circle, we increase it by the pi * r^2 (area of circle). That is where the dependent variable (i) comes in. If you notice, they write float amplitude = sqrt( total / PI ); so the pi from the area is cancelled out.
One thing to keep in mind is that the circles aren't actually moving down, it's all an illusion. To demonstrate this, here is some modified code that will draw lines. If you track a circle along the line, you'll notice that it doesn't actually move down.
void setup()
{
size(500,500);
frameRate(15);
}
void draw()
{
background(0); //fills background with black
noStroke(); //gets rid of stroke
int circlenumber = 999;// determines how many circles will be drawn
float radius = 5; //radius of each small circle
float area = (radius) * (radius) * PI; //area of each small circle
float total = 0; //total areas of circles already drawn
float offset = frameCount * 0.01; //HOW DOES IT WORK & WHAT DOES IT DO
for (int i = 1; i <= circlenumber; ++i) { // loops through all of the circles making up the pattern
float angle = i*19 + offset; //HOW DOES IT WORK & WHAT DOES IT DO
total += area; // adds up the areas of all the small circles that have already been drawn
float amplitude = sqrt( total / PI ); //amplitude of trigonometric spiral
float x = width/2 + cos(angle) * amplitude;//HOW DOES IT WORK & WHAT DOES IT DO
float hue = i;//determines circle color based on circle number
fill(hue, 44, 255);//fills circle with that color
stroke(hue,44,255);
if(i%30 == 0)
line(0,i,width,i);
ellipse(x, i, radius*2, radius*2); //draws circle
}
}
Hopefully this helps clarify some of the issues with understanding.
I've made a lighting engine which allows for shadows. It works on a grid system where each pixel has a light value stored as an integer in an array. Here is a demonstration of what it looks like:
The shadow and the actual pixel coloring works fine. The only problem is the unlit pixels further out in the circle, which for some reason makes a very interesting pattern(you may need to zoom into the image to see it). Here is the code which draws the light.
public void implementLighting(){
lightLevels = new int[Game.WIDTH*Game.HEIGHT];
//Resets the light level map to replace it with the new lighting
for(LightSource lightSource : lights) {
//Iterates through all light sources in the world
double circumference = (Math.PI * lightSource.getRadius() * 2),
segmentToDegrees = 360 / circumference, distanceToLighting = lightSource.getLightLevel() / lightSource.getRadius();
//Degrades in brightness further out
for (double i = 0; i < circumference; i++) {
//Draws a ray to every outer pixel of the light source's reach
double radians = Math.toRadians(i*segmentToDegrees),
sine = Math.sin(radians),
cosine = Math.cos(radians),
x = lightSource.getVector().getScrX() + cosine,
y = lightSource.getVector().getScrY() + sine,
nextLit = 0;
for (double j = 0; j < lightSource.getRadius(); j++) {
int lighting = (int)(distanceToLighting * (lightSource.getRadius() - j));
double pixelHeight = super.getPixelHeight((int) x, (int)y);
if((int)j==(int)nextLit) addLighting((int)x, (int)y, lighting);
//If light is projected to have hit the pixel
if(pixelHeight > 0) {
double slope = (lightSource.getEmittingHeight() - pixelHeight) / (0 - j);
nextLit = (-lightSource.getRadius()) / slope;
/*If something is blocking it
* Using heightmap and emitting height, project where next lit pixel will be
*/
}
else nextLit++;
//Advances the light by one pixel if nothing is blocking it
x += cosine;
y += sine;
}
}
}
lights = new ArrayList<>();
}
The algorithm i'm using should account for every pixel within the radius of the light source not blocked by an object, so i'm not sure why some of the outer pixels are missing.
Thanks.
EDIT: What I found is, the unlit pixels within the radius of the light source are actually just dimmer than the other ones. This is a consequence of the addLighting method not simply changing the lighting of a pixel, but adding it to the value that's already there. This means that the "unlit" are the ones only being added to once.
To test this hypothesis, I made a program that draws a circle in the same way it is done to generate lighting. Here is the code that draws the circle:
BufferedImage image = new BufferedImage(WIDTH, HEIGHT,
BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
g.setColor(Color.white);
g.fillRect(0, 0, WIDTH, HEIGHT);
double radius = 100,
x = (WIDTH-radius)/2,
y = (HEIGHT-radius)/2,
circumference = Math.PI*2*radius,
segmentToRadians = (360*Math.PI)/(circumference*180);
for(double i = 0; i < circumference; i++){
double radians = segmentToRadians*i,
cosine = Math.cos(radians),
sine = Math.sin(radians),
xPos = x + cosine,
yPos = y + sine;
for (int j = 0; j < radius; j++) {
if(xPos >= 0 && xPos < WIDTH && yPos >= 0 && yPos < HEIGHT) {
int rgb = image.getRGB((int) Math.round(xPos), (int) Math.round(yPos));
if (rgb == Color.white.getRGB()) image.setRGB((int) Math.round(xPos), (int) Math.round(yPos), 0);
else image.setRGB((int) Math.round(xPos), (int) Math.round(yPos), Color.red.getRGB());
}
xPos += cosine;
yPos += sine;
}
}
Here is the result:
The white pixels are pixels not colored
The black pixels are pixels colored once
The red pixels are pixels colored 2 or more times
So its actually even worse than I originally proposed. It's a combination of unlit pixels, and pixels lit multiple times.
You should iterate over real image pixels, not polar grid points.
So correct pixel-walking code might look as
for(int x = 0; x < WIDTH; ++x) {
for(int y = 0; y < HEIGHT; ++y) {
double distance = Math.hypot(x - xCenter, y - yCenter);
if(distance <= radius) {
image.setRGB(x, y, YOUR_CODE_HERE);
}
}
}
Of course this snippet can be optimized choosing good filling polygon instead of rectangle.
This can be solved by anti-aliasing.
Because you push float-coordinate information and compress it , some lossy sampling occur.
double x,y ------(snap)---> lightLevels[int ?][int ?]
To totally solve that problem, you have to draw transparent pixel (i.e. those that less lit) around that line with a correct light intensity. It is quite hard to calculate though. (see https://en.wikipedia.org/wiki/Spatial_anti-aliasing)
Workaround
An easier (but dirty) approach is to draw another transparent thicker line over the line you draw, and tune the intensity as needed.
Or just make your line thicker i.e. using bigger blurry point but less lit to compensate.
It should make the glitch less obvious.
(see algorithm at how do I create a line of arbitrary thickness using Bresenham?)
An even better approach is to change your drawing approach.
Drawing each line manually is very expensive.
You may draw a circle using 2D sprite.
However, it is not applicable if you really want the ray-cast like in this image : http://www.iforce2d.net/image/explosions-raycast1.png
Split graphic - gameplay
For best performance and appearance, you may prefer GPU to render instead, but use more rough algorithm to do ray-cast for the gameplay.
Nonetheless, it is a very complex topic. (e.g. http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-16-shadow-mapping/ )
Reference
Here are more information:
http://what-when-how.com/opengl-programming-guide/antialiasing-blending-antialiasing-fog-and-polygon-offset-opengl-programming/ (opengl-antialias with image)
DirectX11 Non-Solid wireframe (a related question about directx11 with image)
An exercise in Shiffman's Nature of Code asks me to create Perlin Noise using Processing's noise() function. Here's my code I made to create Perlin Noise
float xSpace = 0; // Signifies "space" between noise() values on the x coordinate.
float ySpace = 0; // Signifies "space" between noise() values on the y coordinate.
void setup(){
size(500,500);
background(0);
loadPixels();
for(int x = 0; x < width; x++) {
for(int y = 0; y < height; y++) {
float bright = map(noise(xSpace,ySpace),0,1,0,255);
pixels[(y * width) + x] = color(bright);
//Each pixel in the pixels[] array is a color element.
ySpace = ySpace + 0.01;
}
xSpace = xSpace + 0.01 ;
}
updatePixels();
}
And when I run my code, it creates an image like this
I looked at the solution in the textbook. The textbook's solution and my solution are almost identical except the textbook reinitializes ySpace back to 0 with every iteration of the outer loop.
// Textbook's solution
for(int x = 0; x < width; x++) {
ySpace = 0;
for(int y = 0; y < height; y++) {
float bright = map(noise(xSpace,ySpace),0,1,0,255);
pixels[(y * width) + x] = color(bright);
ySpace = ySpace + 0.01;
}
xSpace = xSpace + 0.01 ;
}
However, when I run the textbook's code, the code creates a much smoother image like this
Why, when ySpace is reinitialized in the outer loop, does the image come out much smoother than the when its not? In other words, why is the textbook's code create a much smoother image than my code?
I noticed that the ySpace in my code will be significantly larger than the ySpace in the textbook's code once the for loop is complete. But I'm not sure if that's the reason why my code's image is not as smooth. From my understanding, noise(x,y) creates 2d Perlin Noise. When applied to a pixel, the pixel should be a similar color to the pixels around it, but it doesn't look like its happening in my code.
The noise() function essentially takes a 2D coordinate (or a 3D coordinate, or a single number, but in your case a 2D coordinate) and returns a random number based on that coordinate. Coordinates that are closer together will generate random numbers that are closer together.
With that in mind, think about what your code is doing and what the textbook is doing. The textbook is feeding in an x,y coordinate based on the position in the window, which makes sense since that's where the resulting random value is being drawn.
But your code keeps increasing the y coordinate no matter what. This might make sense if you only had a single column of pixels that just kept going down, but you're trying to loop over the pixels on the screen.
I had a quick question, and wondered if anyone had any ideas or libraries I could use for this. I am making a java game, and need to make 2d images concave. The problem is, 1: I don't know how to make an image concave. 2: I need the concave effect to be somewhat of a post process, think Oculus Rift. Everything is normal, but the camera of the player distorts the normal 2d images to look 3d. I am a Sophmore, so I don't know very complex math to accomplish this.
Thanks,
-Blue
If you're not using any 3D libraries or anything like that, just implement it as a simple 2D distortion. It doesn't have to be 100% mathematically correct as long as it looks OK. You can create a couple of arrays to store the distorted texture co-ordinates for your bitmap, which means you can pre-calculate the distortion once (which will be slow but only happens once) and then render multiple times using the pre-calculated values (which will be faster).
Here's a simple function using a power formula to generate a distortion field. There's nothing 3D about it, it just sucks in the center of the image to give a concave look:
int distortionU[][];
int distortionV[][];
public void computeDistortion(int width, int height)
{
// this will be really slow but you only have to call it once:
int halfWidth = width / 2;
int halfHeight = height / 2;
// work out the distance from the center in the corners:
double maxDistance = Math.sqrt((double)((halfWidth * halfWidth) + (halfHeight * halfHeight)));
// allocate arrays to store the distorted co-ordinates:
distortionU = new int[width][height];
distortionV = new int[width][height];
for(int y = 0; y < height; y++)
{
for(int x = 0; x < width; x++)
{
// work out the distortion at this pixel:
// find distance from the center:
int xDiff = x - halfWidth;
int yDiff = y - halfHeight;
double distance = Math.sqrt((double)((xDiff * xDiff) + (yDiff * yDiff)));
// distort the distance using a power function
double invDistance = 1.0 - (distance / maxDistance);
double distortedDistance = (1.0 - Math.pow(invDistance, 1.7)) * maxDistance;
distortedDistance *= 0.7; // zoom in a little bit to avoid gaps at the edges
// work out how much to multiply xDiff and yDiff by:
double distortionFactor = distortedDistance / distance;
xDiff = (int)((double)xDiff * distortionFactor);
yDiff = (int)((double)yDiff * distortionFactor);
// save the distorted co-ordinates
distortionU[x][y] = halfWidth + xDiff;
distortionV[x][y] = halfHeight + yDiff;
// clamp
if(distortionU[x][y] < 0)
distortionU[x][y] = 0;
if(distortionU[x][y] >= width)
distortionU[x][y] = width - 1;
if(distortionV[x][y] < 0)
distortionV[x][y] = 0;
if(distortionV[x][y] >= height)
distortionV[x][y] = height - 1;
}
}
}
Call it once passing the size of the bitmap that you want to distort. You can play around with the values or use a totally different formula to get the effect you want. Using an exponent less than one for the pow() function should give the image a convex look.
Then when you render your bitmap, or copy it to another bitmap, use the values in distortionU and distortionV to distort your bitmap, e.g.:
for(int y = 0; y < height; y++)
{
for(int x = 0; x < width; x++)
{
// int pixelColor = bitmap.getPixel(x, y); // gets undistorted value
int pixelColor = bitmap.getPixel(distortionU[x][y], distortionV[x][y]); // gets distorted value
canvas.drawPixel(x + offsetX, y + offsetY, pixelColor);
}
}
I don't know what your actual function for drawing a pixel to the canvas is called, the above is just pseudo-code.
This is my first attempt at creating a 2D game, so my code probably isn't as efficient as it could be. Anyway, I tried creating a method to create circles out of my tiles. The point of this method is to create circular dirt patches across my screen. Here is a bit of my code:
private void generateDirt(int x, int y) {
int dirt = 3;
int radius = random.nextInt(7) + 3;
for (int i = radius; i > 1; i--) {
for (int angle = 0; angle < 360; angle++) {
double theta = Math.toRadians(angle);
// Broken Line to solve jutting blocks
// if (theta % Math.PI == 0) theta = 0;
tiles[(int) (x + radius * (Math.sin(theta) * Math.cos(theta)))
+ (int) (y + radius
* (Math.sin(theta) * Math.sin(theta))) * width] = dirt;
}
radius--;
}
}
If I comment out the part where I decrease the radius, and draw just a single circle outline (comment out the outermost for loop(int i = radius...) then the circle is drawn perfectly, except for these two strange tiles jutting out in the side. Sometimes the jutting block is on the right side (I thought it was when it was equal to pi / 2) and on the bottom side as well. But the main problem is that when I attempt to fill the circle by decreasing the radius, the circle...well... becomes a square. It loses its round shape and develops very rigid corners.
I worked on this pretty late, I'm not even sure if my math is correct. TBH, I just kinda threw in the trig functions at random and finally got something that looked like a circle. If you can help me identify what is wrong, or tell me a better way to do this, please let me know! Thanks for the help!
*Also, the radius is actually the diameter (I counted), I need to change the name...
Well I found the answer to my own question. It turns out I don't need to convert my angles to radians. In fact, that just messes up the coordinates. Just using the "angle" instead of "theta" variable fixes the problem.