I've created a function to generate a 2-dimension array of Perlin Noise data, starting with a base noise array of random values between 0 and 1.
The base white noise array looks like this:
From there, I use the smoothNoise() function to interpolate values to create good looking image (which I don't have because it doesn't work)
The smooth noise function is called once every octave. The images of what data the smooth noise function returns is as follows (octaves lowest to highest)
After all the smoothing is done the image outputted turns black. I would upload an image but it is just black so there is no need.
My Code is:
import java.util.Random;
public class Noise2D {
int width;
int height;
public Noise2D(int width, int height){
this.width = width;
this.height = height;
}
public double[][] generateWhiteNoise(int width,int height){
Random r = new Random(0);
double[][] whiteNoise = new double[width][height];
for(int i = 0; i<whiteNoise.length; i++){
for(int j = 0; j<whiteNoise[0].length;j++){
double rNum = r.nextDouble()%1;
whiteNoise[i][j] = (double)rNum;
}
}
ImageWriter.writeImage(whiteNoise, "WhiteNoise");
return whiteNoise;
}
public double interpolate(double x0, double x1, double alpha){
return x0 * (1 - alpha) + alpha * x1;
}
public double[][] generateSmoothNoise(double[][] baseNoise, int octave){
int width = baseNoise.length;
int height = baseNoise[0].length;
double[][] smoothNoise = new double[width][height];
int period = 1<< octave; //2^i
double frequency = 1.0/period;
for(int x = 0; x<width; x++){
int x0 = (x/period)*period; //7/3 = 2 *3 = 6
int x1 = (x0+period)%width;
double hBlend = (x-x0)*frequency;
for(int y = 0; y<height; y++){
int y0 = (y/period)*period;
int y1 = (y0 + period)%height;
double vBlend = (y - y0)*frequency;
double top = interpolate(baseNoise[x0][y0],baseNoise[x1][y1],hBlend);
double bottom = interpolate(baseNoise[x0][y1],baseNoise[x1][y0],hBlend);
smoothNoise[x][y] = interpolate(top,bottom,vBlend);
}
}
ImageWriter.writeImage(smoothNoise,"Smooth"+Integer.toString(octave));
return smoothNoise;
}
public double[][] generatePerlinNoise(double baseNoise[][], int octaves){
int width = baseNoise.length;
int height = baseNoise[0].length;
double persistence = 0.5;
double[][][] smoothNoise = new double[octaves][][];
for(int i = 0; i<octaves; i++){
smoothNoise[i] = generateSmoothNoise(baseNoise,i);
}
double[][] perlinNoise = new double[width][height];
double amplitude = 1;
double totalAmplitude = 0;
for(int octave = octaves-1; octave>=0; octave--){
amplitude*=persistence;
totalAmplitude+=amplitude;
for(int x = 0; x<width;x++){
for(int y = 0; y<height; y++){
perlinNoise[x][y] = smoothNoise[octave][x][y] * amplitude;
}
}
}
ImageWriter.writeImage(perlinNoise,"files");
for(int i = 0; i<width; i++){
for(int j = 0; j<height; j++){
perlinNoise[i][j] /= totalAmplitude;
}
}
return perlinNoise;
}
}
The Class is defined and methods are called by:
Perlin p = new Perlin(256,256);
writeImage(p.smoothNoise(p.makeNoise(256,256), 1, 16), "Perlin");
1 is frequency, 16 is octave count
Where the data is written in the function writeImage(2d array, "name") using the following code (I don't think there is a problem with this part but I'll post it anyway):
public static void writeImage(double[][] data,String name){
BufferedImage img = new BufferedImage(data.length,data[0].length,BufferedImage.TYPE_INT_RGB);
for(int y = 0; y<data.length; y++){
for(int x = 0; x<data[y].length; x++){
if(data[y][x]>1){
data[y][x] = 1;
}
if(data[y][x]<0){
data[y][x] = 0;
}
Color c = new Color((float)data[y][x],(float)data[y][x],(float)data[y][x]);
img.setRGB(x,y,c.getRGB());
}
}
try{
File file = new File(name+".png");
file.createNewFile();
ImageIO.write(img,"png",file);
}catch(IOException e){
e.printStackTrace();
}
}
public static void writeExistingImage(BufferedImage img){
try{
File file = new File("noise2.png");
file.createNewFile();
ImageIO.write(img,"png",file);
}catch(IOException e){
e.printStackTrace();
}
}
To conclude, I believe the problem lies within the smoothNoise method, although I could quite possibly be wrong because my knowledge of Perlin Noise is not extensive. If the problem does not lie here, I assume it lies in the generatePerlinNoise() method. Any help would be greatly appreciated. I will be so happy with any suggestion, I have been trying to solve this problem for an extremely long time.
To clarify:
My problem is that the generatePerlinNoise method returns a set of data(2d) array that makes a black image (rather than a cool noise image)
I also think that the smooth noise images are not supposed to look like they are split up into squares
Related
I need to implement image blur
to do this, I have to use a double array in the form of a matrix, and implement the following:
each coefficient of the convolution matrix must be multiplied by the color value of the corresponding neighbor of the current (changeable) pixel
I need to handle going beyond 0 to 255
I tried to create and fill in the matrix with the 1/9 numbers that are in the task, but then I don't understand what and by what should I multiply?
has anyone solved this?
public static void main(String[] args) throws IOException {
BufferedImage image = ImageIO.read(new File("image.jpg"));
WritableRaster raster = image.getRaster();
int width = raster.getWidth();
int height = raster.getHeight();
final int colorsCountInRgb = 3;
final int colorMaximum = 255;
int[] pixel = new int[colorsCountInRgb];
double[][] matrix = new double[3][3];
double matrixMultiplier = 1 / 9d;
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++) {
matrix[i][j] = matrixMultiplier;
}
}
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
raster.getPixel(x, y, pixel);
raster.setPixel(x, y, pixel);
}
}
ImageIO.write(image, "png", new File("out.png"));
}
I've been working on a project lately that creates procedurally generated terrain which uses 3d simplex noise as well as the Marching cubes algorithm. I am currently running it on my cpu which takes around 10-20 seconds to render a 200x200x200 terrain which isn't optimal if I want to make the world infinite. Is there any way to improve the speed which the terrain renders or is this the maximum speed I can achieve. (I've already tried using compute shaders but limitations with GLSL didn't allow me to pursue that idea.)
Terrain Generation Code
package Terrain;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import Entities.Entity;
import Maths.Vector3f;
import Models.RawModel;
import Render.Loader;
public class Terrain {
private int width = 200, height = 2, interval = 8;
private float x,y,z = 2f;
private int x1,y1,z1;
private Loader loader;
private List<Entity> cubes = new ArrayList<Entity>();
private RawModel model;
private double perlin2DScale = 0.01, perlin3DScale = 0.01;//, maskScale = 0.00;
private double perlin3Dratio = 0.8;// ratio of 3d noise to 2d;
private double amp = 1; //height of mountains
List<Vector3f> verticeArray = new ArrayList<Vector3f>();
float[][][] terrainMap = new float[width+1][height+1][width+1];
float[] SimplexMap3D = new float[(width+1)*(height+1)*(width+1)];
float[] SimplexMap2D = new float[(width+1)*(width+1)];
private float surfaceLevel = 1f;
int seed;
//SimplexComputeBuffer compute = new SimplexComputeBuffer();
public Terrain(float x, float z, Loader loader){
this.loader = loader;
this.x1 = (int) (x*width);
this.z1 = (int) (z*width);
this.x = x*width*interval;
this.z = z*width*interval;
Random rand = new Random();
seed = rand.nextInt(100000);
loadMarchingTerrain();
}
public void changeAmp(double i){
x+=i;
System.out.println("amp ="+amp);
verticeArray.clear();
loadMarchingTerrain();
}
public void changeSurface(double i){
surfaceLevel+=i;
System.out.println("surface ="+surfaceLevel);
verticeArray.clear();
loadMarchingTerrain();
}
public void loadMarchingTerrain(){
for(int x = x1; x<x1+width+1; x++){
for(int y = y1; y<y1+height+1; y++){
for(int z = z1; z<z1+width+1; z++){
double noise3d = this.sumOctaves(4,x,y,z,0.5,perlin3DScale,0,1); // creates 3d terrain like caves and overhangs
double noise2d = this.sumOctaves(4,x,z,0.5,perlin2DScale,0,1); // creates 2d terrain like mountains and hills (gives only height)
//double mask = this.sumOctaves(1,x,z,0.5,maskScale,0,1); // creates a 2d mask to vary heights of regions
float curHeight = (float)height*(float)(noise2d*(1-perlin3Dratio)+noise3d*perlin3Dratio); // mixing them together with correct ratio of 3d and 2d data
terrainMap[x-x1][y-y1][z-z1] = (float)-y+curHeight;
}
}
}
for(int x = 0; x<width; x++){
for(int y = 0; y<height; y++){
for(int z = 0; z<width; z++){
marchCube(new Vector3f(x,y,z));
}
}
}
float[] vertices = new float[verticeArray.size()*3];
int[] indice = new int[verticeArray.size()];
int vertexCount = 0;
for(Vector3f v : verticeArray){
vertices[vertexCount++] =v.x*interval;
vertices[vertexCount++] =v.y*interval;
vertices[vertexCount++] =v.z*interval;
}
for(int i = 0; i<indice.length; i++){
indice[i] = i;
}
model = loader.loadToVao(vertices, null, indice);
}
public int configIndex(float[] cube){
int configIndex = 0;
for(int i = 0; i<8; i++){
if(cube[i] > surfaceLevel){
configIndex |= 1 << i;
}
}
return configIndex;
}
public float sampleTerrain(Vector3f point){
return terrainMap[(int) point.x][(int) point.y][(int) point.z];
}
public Vector3f smoothPoint(Vector3f vert1, Vector3f vert2, int indice, float[] cube){
float sampleVert1 = cube[MarchingCubeTable.edges[indice][0]];
float sampleVert2 = cube[MarchingCubeTable.edges[indice][1]];
float difference = sampleVert1-sampleVert2;
if(difference == 0){
difference = surfaceLevel;
}else{
difference = (surfaceLevel-sampleVert1)/difference;
}
Vector3f a2 = vert1.subtract(vert2).scale(difference);
Vector3f vertPos = vert1.add(a2);
return vertPos;
}
public void marchCube(Vector3f position){
//create cube
float[] cube = new float[8];
for(int i = 0; i<8; i++){
Vector3f corner = position.add(MarchingCubeTable.cornerTable[i]);
cube[i] = terrainMap[(int) corner.x][(int) corner.y][(int) corner.z];
}
//search through index
int currentConfigIndex = configIndex(cube);
if(currentConfigIndex == 0 || currentConfigIndex == 255){
return;
}
//search through points
int edgeIndex = 0;
for(int j = 0; j<5; j++){
for(int i = 0; i<3; i++){
int indice = MarchingCubeTable.getIndex(currentConfigIndex)[edgeIndex];
if(indice == -1){
return;
}
Vector3f vert2 = position.add(MarchingCubeTable.cornerTable[MarchingCubeTable.edges[indice][0]]);
Vector3f vert1 = position.add(MarchingCubeTable.cornerTable[MarchingCubeTable.edges[indice][1]]);
Vector3f vertPos = this.smoothPoint(vert1, vert2, indice, cube);
verticeArray.add(vertPos);
edgeIndex++;
}
}
}
/*
* Simplex Noise functions
*/
public double sumOctaves(int iterations, double x, double y, double z, double persistance, double scale, double low, double high){
double maxamp = 0;
double amp = this.amp;
double frequency = scale;
double noise = 0;
for(int i = 0; i<iterations; i++){
noise += SimplexNoise.noise((x)*frequency, (y)*frequency, (z)*frequency)*amp;
maxamp += amp;
amp *= persistance;
frequency *= 2;
}
noise /= maxamp;
noise = noise * (high - low) / 2 + (high + low) / 2;
return noise;
}
public double sumOctaves(int iterations, int x, int y, double persistance, double scale, double low, double high){
double maxamp = 0;
double amp = this.amp;
double frequency = scale;
double noise = 0;
for(int i = 0; i<iterations; i++){
noise += SimplexNoise.noise((x)*frequency, (y)*frequency)*amp;
maxamp += amp;
amp *= persistance;
frequency *= 2;
}
noise /= maxamp;
noise = noise * (high - low) / 2 + (high + low) / 2;
return noise;
}
public List<Entity> getCubes(){
return cubes;
}
public float getX() {
return x;
}
public float getZ() {
return z;
}
public float getY() {
return y;
}
public RawModel getRawModel(){
return model;
}
}
The study project is about warping text along bezier curves(recursive polynomial form) on processing using the geomerative library to get shape from a .ttf type font file.(It needs a ttf file in the data directory to function.) currently, the sketch seems to throws errors when
fill(any color); is used at the part where the code draws the character shapes and the bezier curve's length gets shorter than a certain length. If fill(); is not used, the sketch seems to function okay without any errors.
The goal is to use the fill(); function to fill the characters without errors.
I've tried;
1) getting rid of the beginContour(); and endContour(); because I thought it hasn't been written properly.(I thought it was wrong because the contours should only be drawn when the shape is the inner side of a letter but currently, it draws contours when it's not the first or last shape) But the sketch throws errors even when the contour function was not used (fill(); was used).
2) thought it had something to do with the length of the curve, so tried to add a if statement to the part where it draws the letters. So far, I've tried using the width of the RGroup generated from the initial font size and string in void setup(){}, and the length of the bezier curve. The condition examples within the if statement was as follows;
-draw letters when the RGroup shape's width is smaller than the length of the curve
-draw letters when the "indent"(a variable to calculate the position on the curve) value is smaller than the length of the curve. (this case made the sketch to draw letters only when the letters were placed within the curve, but the error still occurred)
-draw letters when the "indent"(a variable to calculate the position on the curve) value is smaller than the width of the RGroup.
I've failed to see where exactly the problem is occurring, so I'm sharing the entire code within the sketch, but I marked the spot where I presume the error is happening with "//*******".
This study was based on the following link.
The geomerative library documentation can be seen from the following link.
//build a bezier curve using the recursive polynomial form
//number of control points(num+1 points)
int num = 4;
//arraylist to store the picked values
ArrayList<Point> pt;
float nfac;
import geomerative.*;
RFont fnt;
RGroup rg;
RPoint [][]rp;
String str = "(O_o)/ Oooh";
FloatList X;
FloatList Y;
FloatList SUM;
void setup(){
size(1000,1000,P2D);
RG.init(this);
pt = new ArrayList<Point>();
//pick a number of points with random positions and store x,y values in them
for(int i=0; i<=num; i++){
float x = random(0,width);
float y = random(0,height);
pt.add(new Point(x,y));
}
fnt = new RFont("Zapfino.ttf",100);
//RCommand.setSegmentAngle(random(0,HALF_PI));
//RCommand.setSegmentator(RCommand.ADAPTATIVE);
RCommand.setSegmentLength(3);
RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
rg = fnt.toGroup(str);
rp = rg.getPointsInPaths();
X = new FloatList();
Y = new FloatList();
SUM = new FloatList();
}
void draw(){
background(255);
noFill();
strokeCap(ROUND);
strokeWeight(2);
//draw points
beginShape();
for(int i=0; i<=num; i++){
vertex(pt.get(i).x,pt.get(i).y);
}
endShape();
for(int i=0; i<=num; i++){
ellipse(pt.get(i).x,pt.get(i).y,10,10);
}
//draw curve
nfac = 1;
for(int i=0; i<num; i++){
nfac *= (i+1);
}
strokeWeight(2);
beginShape();
int Bzindex = 0;
for(float t=0; t<=1; t+=0.01){
float x = 0;
float y = 0;
Bzindex++;
for(int i=0; i<=num; i++){
float coef = 1;
float kfac = 1;
float k_nfac = 1;
for(int k=i; k>0; k--){
kfac *= k;
}
for(int k=(num-i); k>0; k--){
k_nfac *= k;
}
coef = nfac/(kfac*k_nfac);
x += coef*(pow((1-t),num-i)*pow(t,i)*pt.get(i).x);
y += coef*(pow((1-t),num-i)*pow(t,i)*pt.get(i).y);
}
vertex(x,y);
X.set(Bzindex,x);
Y.set(Bzindex,y);
}
endShape();
//get arclength by pulling points from a floatlist
int numberOfDivisions = X.size()-2;
int maxPoint = numberOfDivisions+1;
float sum = 0;
float prevPointX = X.get(0);
float prevPointY = Y.get(0);
for(int i=1; i<=maxPoint; i++){
float pointX = X.get(i);
float pointY = Y.get(i);
sum += dist(pointX,pointY,prevPointX,prevPointY);
SUM.set(i-1,sum);
prevPointX = pointX;
prevPointY = pointY;
}
//
//*******************************************************
noFill();
//creates error only when using fill();
fill(0,255,0);
stroke(0);
//noStroke();
strokeWeight(4);
float indent = SUM.get(0);
beginShape();
for(int i=0; i<rp.length; i++){
if(i>0){
beginContour();
}
for(int j=0; j<rp[i].length; j++){
float t = 0;
indent = rp[i][j].x+SUM.get(0);
//
float targetArcLength = indent;
int index = IndexOfLargestValueSmallerThan(targetArcLength);
if(SUM.get(index)==targetArcLength){
t = index/(SUM.size()-1);
}else{
float lengthBefore = SUM.get(index);
float lengthAfter = SUM.get(index+1);
float segmentLength = lengthAfter - lengthBefore;
float segmentFraction = (targetArcLength - lengthBefore)/segmentLength;
t = (index+segmentFraction)/(SUM.size()-1);
}
float x = 0;
float y = 0;
float vx = 0;
float vy = 0;
for(int l=0; l<=num; l++){
float coef = 1;
float kfac = 1;
float k_nfac = 1;
for(int k=l; k>0; k--){
kfac *= k;
}
for(int k=(num-l); k>0; k--){
k_nfac *= k;
}
coef = nfac/(kfac*k_nfac);
x += coef*(pow((1-t),num-l)*pow(t,l)*pt.get(l).x);
y += coef*(pow((1-t),num-l)*pow(t,l)*pt.get(l).y);
float v = (pow(1-t,num-l)*l*pow(t,l-1))+((num-l)*pow(1-t,num-l-1)*(-1)*pow(t,l));
vx += coef*pt.get(l).x*(v);
vy += coef*pt.get(l).y*(v);
}
PVector P = new PVector(x,rp[i][j].y+y);
PVector ldir = new PVector(P.x-x,P.y-y);
PVector dir = new PVector(vy,-vx);
//
ldir.rotate(dir.heading()+PI/2);
vertex(x+ldir.x,y+ldir.y);
}
if(i<rp.length&&i>0){
endContour();
}
}
endShape();
//**************************************************************
}
int IndexOfLargestValueSmallerThan(float _targetArcLength){
int index = 0;
for(int i=0; i<SUM.size()-1; i++){
if(SUM.get(i)<=_targetArcLength){
index = i;
}
}
return index;
}
void mouseDragged(){
int which = -1;
if((mouseX<width)&&(mouseX>0)&&(mouseY<height)&&(mouseY>0)){
for(int i=0; i<=num; i++){
if(dist(mouseX,mouseY,pt.get(i).x,pt.get(i).y)<50){
which = i;
pt.get(which).update(mouseX,mouseY);
}
}
}
}
class Point{
float x,y;
Point(float _x, float _y){
x = _x;
y = _y;
}
void update(float _newx, float _newy){
x = _newx;
y = _newy;
}
}
Sometimes the error happens when the sketch is loaded. Most of the time, it loads okay but throws an error when you drag the point around a bit. The error code sometimes refers to the point where the control points of the curve are updated by mouse position, but because error somtimes occurs when the sketch is loaded as well, I didn't think it was a problem connected to the updated positions.
The error code is as follows;
a.lang.AssertionError
at processing.opengl.PSurfaceJOGL$2.run(PSurfaceJOGL.java:412)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.AssertionError
at jogamp.opengl.glu.tessellator.Sweep.AddRightEdges(Sweep.java:358)
at jogamp.opengl.glu.tessellator.Sweep.SweepEvent(Sweep.java:1105)
at jogamp.opengl.glu.tessellator.Sweep.__gl_computeInterior(Sweep.java:1339)
at jogamp.opengl.glu.tessellator.GLUtessellatorImpl.gluTessEndPolygon(GLUtessellatorImpl.java:526)
at com.jogamp.opengl.glu.GLU.gluTessEndPolygon(GLU.java:896)
at processing.opengl.PJOGL$Tessellator.endPolygon(PJOGL.java:641)
at processing.opengl.PGraphicsOpenGL$Tessellator.tessellatePolygon(PGraphicsOpenGL.java:12621)
at processing.opengl.PGraphicsOpenGL.tessellate(PGraphicsOpenGL.java:2255)
at processing.opengl.PGraphicsOpenGL.endShape(PGraphicsOpenGL.java:1965)
at processing.core.PGraphics.endShape(PGraphics.java:1707)
at processing.core.PApplet.endShape(PApplet.java:11641)
at bezier_polynomial_recursive_text_03.draw(bezier_polynomial_recursive_text_03.java:218)
at processing.core.PApplet.handleDraw(PApplet.java:2475)
at processing.opengl.PSurfaceJOGL$DrawListener.display(PSurfaceJOGL.java:866)
at jogamp.opengl.GLDrawableHelper.displayImpl(GLDrawableHelper.java:692)
at jogamp.opengl.GLDrawableHelper.display(GLDrawableHelper.java:674)
RuntimeException: java.lang.AssertionError
at jogamp.opengl.GLAutoDrawableBase$2.run(GLAutoDrawableBase.java:443)
at jogamp.opengl.GLDrawableHelper.invokeGLImpl(GLDrawableHelper.java:1293)
at jogamp.opengl.GLDrawableHelper.invokeGL(GLDrawableHelper.java:1147)
at com.jogamp.newt.opengl.GLWindow.display(GLWindow.java:759)
at com.jogamp.opengl.util.AWTAnimatorImpl.display(AWTAnimatorImpl.java:81)
at com.jogamp.opengl.util.AnimatorBase.display(AnimatorBase.java:452)
at com.jogamp.opengl.util.FPSAnimator$MainTask.run(FPSAnimator.java:178)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
I don't think this would be a direct answer to my question, but it did stop the errors from occurring while using both fill() and the P2D renderer. The main problem, as pointed out by laancelot above, indeed seems to have been connected with stack overflow. So I approached the problem in two ways written below; Conclusion: The direct reason was a poorly expressed math formula.
1) switching the RPoints inside a class.
-I don't think this was the direct reason the errors were occurring, because at the stage where only this part of rewriting the code was done, the errors were still there. But maybe it was part of the problem. I'm not sure.
2) rewriting the part where the code expresses the formula to evaluate bezier curves at a specific point.
-Previously, the formula was made by using the explicit definition of a bezier curve with degree n. And, as a result, the formula had to be calculated(more like made) for every point in the RPoint points. As is mentioned on the wikipedia page about bezier curves, this way of computing is not recommended.
-on the revised code, the formula used to warp text was expressed in the polynomial form. Thus, it was able to pre-calculate the coefficients of the polynomial before the RPoint points were iterated. This seems to have solved the problem.
I'm still not really confident about what actually caused the problem and why it has been solved, and which part of the code I should show to explain this to others, so I'll share the entire code that has been rewritten. You need processing, the geomerative library, and a ttf type font file in a data folder to test the code. I have marked the place where the revised version of the formula is implicated. (It's still really messy....)
//n number of points
int num = 4;
//arraylist to store the picked values
ArrayList<cntrlPoint> pt;
//import the geomerative library
import geomerative.*;
//string
String str = "(O_o)/ Oooh";
FloatList X;
FloatList Y;
FloatList SUM;
RClass rc;
void setup() {
size(1000, 1000, P2D);
pt = new ArrayList<cntrlPoint>();
//pick a number of points with random positions
for (int i=0; i<=num; i++) {
float x = random(0, width);
float y = random(0, height);
pt.add(new cntrlPoint(x, y));
}
RG.init(this);
rc = new RClass();
X = new FloatList();
Y = new FloatList();
SUM = new FloatList();
}
void draw() {
background(255);
noFill();
strokeWeight(2);
drwCntrlPoints();
drwCurve();
gtArcLength();
fill(0,255,0);
rc.crtPoly(pt);
rc.drwText();
}
void drwCntrlPoints() {
//draw points
beginShape();
for (int i=0; i<=num; i++) {
vertex(pt.get(i).x, pt.get(i).y);
}
endShape();
for (int i=0; i<=num; i++) {
ellipse(pt.get(i).x, pt.get(i).y, 10, 10);
}
}
void drwCurve() {
//draw curve
float curveDetail = 0.01;
float nfac = 1;
for (int i=0; i<num; i++) {
nfac *= (i+1);
}
int arcIndex = 0;
strokeWeight(2);
beginShape();
for (float t=0; t<=1; t+=curveDetail) {
float x = 0;
float y = 0;
arcIndex++;
for (int i=0; i<=num; i++) {
float coef = 1;
float kfac = 1;
float k_nfac = 1;
for (int k=i; k>0; k--) {
kfac *= k;
}
for (int k=(num-i); k>0; k--) {
k_nfac *= k;
}
coef = nfac/(kfac*k_nfac);
x += coef*(pow((1-t), num-i)*pow(t, i)*pt.get(i).x);
y += coef*(pow((1-t), num-i)*pow(t, i)*pt.get(i).y);
}
vertex(x, y);
X.set(arcIndex, x);
Y.set(arcIndex, y);
}
endShape();
}
void gtArcLength() {
//get arclength by pulling points from a floatlist
int numberOfDivisions = X.size()-2;
int maxPoint = numberOfDivisions+1;
float sum = 0;
float prevPointX = X.get(0);
float prevPointY = Y.get(0);
for (int i=1; i<=maxPoint; i++) {
float pointX = X.get(i);
float pointY = Y.get(i);
sum += dist(pointX, pointY, prevPointX, prevPointY);
SUM.set(i-1, sum);
prevPointX = pointX;
prevPointY = pointY;
}
}
//*******factorial
int fact(int fa){
if(fa==1){
return 1;
}
if(fa==0){
return 1;
}
else{
return fa*fact(fa-1);
}
}
//********************
int IndexOfLargestValueSmallerThan(float _targetArcLength) {
int index = 0;
for (int i=0; i<SUM.size()-1; i++) {
if (SUM.get(i)<=_targetArcLength) {
index = i;
}
}
return index;
}
void mouseDragged() {
int which = -1;
if ((mouseX<width)&&(mouseX>0)&&(mouseY<height)&&(mouseY>0)) {
for (int i=0; i<=num; i++) {
if (dist(mouseX, mouseY, pt.get(i).x, pt.get(i).y)<80) {
which = i;
}
}
if (which>-1) {
pt.get(which).update(mouseX, mouseY);
}
}
}
class RClass {
//get ttf file
//create rfont
RFont fnt;
//turn rfont to rgroup to get points
RGroup rg;
//going to get point in path, so that the characters in the string can be seperated
RPoint [][]rp;
//floatlist to store coefficients
FloatList Cx;
FloatList Cy;
RClass() {
fnt = new RFont("Zapfino.ttf", 100);
rg = fnt.toGroup(str);
rp = rg.getPointsInPaths();
//RCommand.setSegmentAngle(random(0,HALF_PI));
//RCommand.setSegmentator(RCommand.ADAPTATIVE);
RCommand.setSegmentLength(3);
RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
Cx = new FloatList();
Cy = new FloatList();
}
//**********************************here
void crtPoly(ArrayList<cntrlPoint> _pt){
float ptsize = _pt.size();
for(int j=0; j<ptsize; j++){
float coefx = 0;
float coefy = 0;
float pi = 1;
float sigx = 0;
float sigy = 0;
for(int m=0; m<=j-1; m++){
pi *= (ptsize-1-m);
}
for(int i=0; i<=j; i++){
sigx += (pow(-1,i+j)*pt.get(i).x)/(fact(i)*fact(j-i));
sigy += (pow(-1,i+j)*pt.get(i).y)/(fact(i)*fact(j-i));
}
coefx = pi*sigx;
coefy = pi*sigy;
Cx.set(j,coefx);
Cy.set(j,coefy);
}
}
//**************************************
void drwText() {
float indent = SUM.get(0);
beginShape();
for (int i=0; i<rp.length; i++) {
if(i>0){
beginContour();
}
for (int j=0; j<rp[i].length; j++) {
float t = 0;
indent = rp[i][j].x+SUM.get(0);
float targetArcLength = indent;
int index = IndexOfLargestValueSmallerThan(targetArcLength);
if (SUM.get(index)==targetArcLength) {
t = index/(SUM.size()-1);
} else {
float lengthBefore = SUM.get(index);
float lengthAfter = SUM.get(index+1);
float segmentLength = lengthAfter - lengthBefore;
float segmentFraction = (targetArcLength - lengthBefore)/segmentLength;
t = (index+segmentFraction)/(SUM.size()-1);
}
//***************************here
float x = 0;
float y = 0;
float vx = 0;
float vy = 0;
for(int l=0; l<=num; l++){
x += Cx.get(l)*pow(t,l);
y += Cy.get(l)*pow(t,l);
}
for(int l=1; l<=num; l++){
vx += l*Cx.get(l)*pow(t,l-1);
vy += l*Cy.get(l)*pow(t,l-1);
}
//**************************************
PVector P = new PVector(x, rp[i][j].y+y);
PVector ldir = new PVector(P.x-x, P.y-y);
PVector dir = new PVector(vy, -vx);
//
ldir.rotate(dir.heading()+PI/2);
vertex(x+ldir.x, y+ldir.y);
}
if(i>0&&i<rp.length){
endContour();
}
}
endShape();
}
}
class cntrlPoint{
float x,y;
cntrlPoint(float _x, float _y){
x = _x;
y = _y;
}
void update(float _newx, float _newy){
x = _newx;
y = _newy;
}
}
I am new to Perlin noise and I have hit a roadblock. The perlin noise function I have translated from C++ seems to work correctly for one octave, however I have found that the lower octaves of noise aren't added to the original Perlin Noise. Here is my code:
public class Perlin {
float[][] generateWhiteNoise(int width, int height) {
Random random = new Random(0);
float[][] noise = new float[width][height];
for (int i = 0; i < noise.length; i++) {
for (int j = 0; j < noise[i].length; j++){
noise[i][j] = (float)random.nextDouble();
}
}
return noise;
}
float[][] generateSmoothNoise(float[][] baseNoise, int octave){
int width = baseNoise.length;
int height = baseNoise[0].length;
float[][] smoothNoise = baseNoise;
int samplePeriod = (int) Math.pow(2,octave); // calculates 2 ^ k
float sampleFrequency = 1.0f / samplePeriod;
for (int i = 0; i < width; i++) {
//calculate the horizontal sampling indices
int sample_i0 = (i / samplePeriod) * samplePeriod;
int sample_i1 = (sample_i0 + samplePeriod) % width; //wrap around
float horizontal_blend = (i - sample_i0) * sampleFrequency;
for (int j = 0; j < height; j++){
//calculate the vertical sampling indices
int sample_j0 = (j / samplePeriod) * samplePeriod;
int sample_j1 = (sample_j0 + samplePeriod) % height; //wrap around
float vertical_blend = (j - sample_j0) * sampleFrequency;
//blend the top two corners
float top = interpolate(baseNoise[sample_i0][sample_j0],
baseNoise[sample_i1][sample_j0], horizontal_blend);
//blend the bottom two corners
float bottom = interpolate(baseNoise[sample_i0][sample_j1],
baseNoise[sample_i1][sample_j1], horizontal_blend);
//final blend
smoothNoise[i][j] = interpolate(top, bottom, vertical_blend);
}
}
return smoothNoise;
}
float interpolate(float x0, float x1, float alpha){
return (float) ((float)(x0) * (float)(1 - alpha) + (float)(alpha * x1));
}
float[][] generatePerlinNoise(float[][] baseNoise, int octaveCount) {
int width = baseNoise.length;
int height = baseNoise[0].length;
float[][][] smoothNoise = new float[octaveCount][][]; //an array of 2D arrays containing
float persistance = .5f;
//generate smooth noise
for (int i = 0; i<octaveCount; i++) {
System.out.println("Generating Smooth Noise: " + i);
smoothNoise[i] = generateSmoothNoise(baseNoise, i);
}
float[][] perlinNoise = new float[width][height];
float amplitude = 1.0f;
float totalAmplitude = 0.0f;
//blend noise together
for (int octave = octaveCount - 1; octave >= 0; octave--) {
amplitude *= persistance;
totalAmplitude += amplitude;
System.out.println("Adding smooth noise for octave: " + octave + " at amplitude: " + amplitude);
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
perlinNoise[i][j] += smoothNoise[octave][i][j] * amplitude;
}
}
}
//normalization
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
perlinNoise[i][j] /= totalAmplitude;
}
}
return perlinNoise;
}
public float[][] printVals(float[][] baseNoise){
baseNoise = generatePerlinNoise(generateWhiteNoise(800,800),6);
for(int i = 0; i<baseNoise.length; i++){
String row = "";
for(int j = 0; j<baseNoise[i].length;j++){
row+= (int)(baseNoise[i][j]*255) + " ";
}
System.out.println(row);
}
return baseNoise;
}
}
Here is the code in that I use to get values:
baseNoise = generatePerlinNoise(generateWhiteNoise(800,800),6);
Here are the outputs at octaves 1,2,3, and 4
Any help would be appreciated!
Edit:
Through trial and error, I have found that the most likely area for there to be problems is in the generatePerlinNoise() function. If I change the octaves, I get the desired noise level, which is what I want. That also means that the generateWhiteNoise() and generateSmoothNoise() works. So, somewhere within the generatePerlinNoise() blending there is a problem, but it seems like it should work.
I have found an answer. I had thought that the generateSmoothNoise() command was going to give me multi-octave perlin noise. However, I realized that I needed to build another command similar to what happens with the blending of the smooth noise to blend my different octaves of perlin noise.
public float[][] generateMultiOctavePerlinNoise(int octaves, double persistence, double dropoff, int width, int height){
float[][][]noise = new float[octaves][width][height];
for(int i = octaves - 1; i > 0;i--){
noise[i] = generatePerlinNoise(generateWhiteNoise(width,height),octaves - i);
}
float[][] multiOctave = new float[width][height];
for(int a= 0; a<noise.length; a++){
persistence*= dropoff;
for(int i = 0; i<multiOctave.length; i++){
for(int j = 0; j<multiOctave[i].length; j++){
multiOctave[i][j] += noise[a][i][j]*persistence;
}
}
}
return multiOctave;
}
I have been working with perlin noise recently and when implementing it into a tile engine I am using, I have noticed that the perlin noise function produced "blocks" as seen in the picture below. Each pixel is another different location in a 500 by 500 array that is returned from the perlin noise function.
in this example the persistence is 0.5 with an octave count of 5
When playing with it further, the more octaves I have, the larger the block chunks.
Here is the Code that I am using to call the perlin noise function:
PerlinNoise p = new PerlinNoise();
//returns a float[][] array of 500 by 500
p.GeneratePerlinNoise(p.genWhiteNoise(500, 500), 5, (float) 0.1);
PerlinNoise class
import java.util.Random;
public class PerlinNoise {
Random r;
public PerlinNoise() {
r = new Random();
}
public void setSeed(long seed) {
r.setSeed(seed);
}
public void printOutArray(float[][] arr) {
for(int i = 0; i < arr.length; i++) {
for(int n = 0; n < arr[0].length; n++) {
System.out.print(arr[i][n] + ", ");
}
System.out.print("\n");
}
}
public void printOutTerrain(float[][] arr) {
for(int i = 0; i < arr.length; i++) {
for(int n = 0; n < arr[0].length; n++) {
float a = arr[i][n];
if(a < 0.4) {
System.out.print("W");
} else {
System.out.print("L");
}
}
System.out.print("\n");
}
}
//-------------------------------------------------------------//
float[][] genWhiteNoise(int width, int height) {
float[][] noise = new float[height][width];
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
noise[y][x] = r.nextFloat();
}
}
return noise;
}
float[][] genSmoothNoise(float[][] baseNoise, int octave) {
int height = baseNoise.length;
int width = baseNoise[0].length;
float[][] smoothNoise = new float[height][width];
int samplePeriod = 1 << octave; //calculates 2^k
float sampleFrequency = (float) (1.0/samplePeriod);
for(int i = 0; i < height; i++) {
int sample_i0 = (i / samplePeriod) * samplePeriod;
int sample_i1 = (sample_i0 + samplePeriod) % height; //wrap around
float vertical_blend = (i - sample_i0) * sampleFrequency;
for(int n = 0; n < width; n++) {
int sample_n0 = (n / samplePeriod) * samplePeriod;
int sample_n1 = (sample_n0 + samplePeriod) % width; //wrap around
float horizontal_blend = (n - sample_n0) * sampleFrequency;
//blend the top two corners
float top = Interpolate(baseNoise[sample_i0][sample_n0],
baseNoise[sample_i1][sample_n0], horizontal_blend);
//blend the bottom two corners
float bottom = Interpolate(baseNoise[sample_i0][sample_n1],
baseNoise[sample_i1][sample_n1], horizontal_blend);
//final blend
smoothNoise[i][n] = Interpolate(top, bottom, vertical_blend);
}
}
return smoothNoise;
}
float[][] GeneratePerlinNoise(float[][] baseNoise, int octaveCount, float persistance)
{
int height = baseNoise.length;
int width = baseNoise[0].length;
float[][][] smoothNoise = new float[octaveCount][][]; //an array of 2D arrays containing
//generate smooth noise
for (int i = 0; i < octaveCount; i++)
{
smoothNoise[i] = genSmoothNoise(baseNoise, i);
}
float[][] perlinNoise = new float[height][width];
float amplitude = 1.0f;
float totalAmplitude = 0.0f;
//blend noise together
for (int octave = octaveCount - 1; octave >= 0; octave--)
{
amplitude *= persistance;
totalAmplitude += amplitude;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
perlinNoise[i][j] += smoothNoise[octave][i][j] * amplitude;
}
}
}
//normalisation
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
perlinNoise[i][j] /= totalAmplitude;
}
}
return perlinNoise;
}
//linear average between two points
float Interpolate(float x0, float x1, float alpha)
{
return Cosine_Interpolate(x0, x1, alpha);
}
//Linear Interpolation
float Linear_Interpolate(float x0, float x1, float alpha)
{
return x0 * (1 - alpha) + alpha * x1;
}
//Cosine interpolation (much smoother)
float Cosine_Interpolate(float x0, float x1, float alpha)
{
float ft = (float) (alpha * 3.141592653589);
float f = (float) ((1 - Math.cos(ft)) * 0.5);
return x0*(1-f) + x1*f;
}
}
So to reiterate my question: Why is my perlin noise function behaving the way it does, as in only generating space in chunks?
So to fix this, all I had to do is swap the vertical_blend and horizontal_blend variables in the genSmoothNoise() method. It's amazing what you notice after a break