public Layer bumpSpecular(Channel bumpmap, float lx, float ly, float lz, float shadow, float light_r, float light_g, float light_b, int specular) { if(!(bumpmap.getWidth() == width && bumpmap.getHeight() == height)) throw new Exception("bumpmap size does not match layer size"); float lnorm = (float)Math.Sqrt(lx*lx + ly*ly + lz*lz); float nz = 4*(1f/Math.Min(width, height)); float nzlz = nz*lz; float nz2 = nz*nz; int power = 2<<specular; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { float nx = bumpmap.getPixelWrap(x + 1, y) - bumpmap.getPixelWrap(x - 1, y); float ny = bumpmap.getPixelWrap(x, y + 1) - bumpmap.getPixelWrap(x, y - 1); float brightness = nx*lx + ny*ly; float costheta = (brightness + nzlz)/((float)Math.Sqrt(nx*nx + ny*ny + nz2)*lnorm); float highlight; if (costheta > 0) { highlight = (float)Math.Pow(costheta, power); } else { highlight = 0; } putPixelClip(x, y, (r.getPixel(x, y) + highlight*light_r)*(bumpmap.getPixel(x, y)*shadow + 1 - shadow), (g.getPixel(x, y) + highlight*light_g)*(bumpmap.getPixel(x, y)*shadow + 1 - shadow), (b.getPixel(x, y) + highlight*light_b)*(bumpmap.getPixel(x, y)*shadow + 1 - shadow)); } } return this; }
public Layer bump(Channel bumpmap, float lx, float ly, float shadow, float light_r, float light_g, float light_b, float ambient_r, float ambient_g, float ambient_b) { if(!(bumpmap.getWidth() == width && bumpmap.getHeight() == height)) throw new Exception("bumpmap size does not match layer size"); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { float nx = bumpmap.getPixelWrap(x + 1, y) - bumpmap.getPixelWrap(x - 1, y); float ny = bumpmap.getPixelWrap(x, y + 1) - bumpmap.getPixelWrap(x, y - 1); float brightness = nx*lx + ny*ly; if (brightness >= 0) { putPixelClip(x, y, (r.getPixel(x, y) + brightness*light_r)*(bumpmap.getPixel(x, y)*shadow + 1 - shadow), (g.getPixel(x, y) + brightness*light_g)*(bumpmap.getPixel(x, y)*shadow + 1 - shadow), (b.getPixel(x, y) + brightness*light_b)*(bumpmap.getPixel(x, y)*shadow + 1 - shadow)); } else { putPixelClip(x, y, (r.getPixel(x, y) + brightness*(1 - ambient_r))*(bumpmap.getPixel(x, y)*shadow + 1 - shadow), (g.getPixel(x, y) + brightness*(1 - ambient_g))*(bumpmap.getPixel(x, y)*shadow + 1 - shadow), (b.getPixel(x, y) + brightness*(1 - ambient_b))*(bumpmap.getPixel(x, y)*shadow + 1 - shadow)); } } } return this; }
public Layer bumpFast(Channel bumpmap, float lx, float light, float ambient) { if(!(bumpmap.getWidth() == width && bumpmap.getHeight() == height)) throw new Exception("bumpmap size does not match layer size"); ambient = 1f - ambient; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { float brightness = lx*(bumpmap.getPixelWrap(x + 1, y) - bumpmap.getPixelWrap(x - 1, y)); if (brightness >= 0) { brightness = brightness*light; putPixel(x, y, r.getPixel(x, y) + brightness, g.getPixel(x, y) + brightness, b.getPixel(x, y) + brightness); } else { brightness = brightness*ambient; putPixel(x, y, r.getPixel(x, y) + brightness, g.getPixel(x, y) + brightness, b.getPixel(x, y) + brightness); } } } return this; }
public Channel placeDarkest(Channel sprite, int x_offset, int y_offset) { for (int y = y_offset; y < y_offset + sprite.getHeight(); y++) { for (int x = x_offset; x < x_offset + sprite.getWidth(); x++) { putPixelWrap(x, y, Math.Min(getPixelWrap(x, y), sprite.getPixelWrap(x - x_offset, y - y_offset))); } } return this; }
public Channel scaleDouble() { if(!(width == height)) throw new Exception("square images only"); // calculate filter Channel filter = new Channel(width<<1, height<<1); for (int y = 0; y < height; y++) { int y_shift = y<<1; for (int x = 0; x < width; x++) { int x_shift = x<<1; float value = 0.25f*getPixel(x, y); filter.putPixel(x_shift, y_shift, value); filter.putPixel(x_shift + 1, y_shift, value); filter.putPixel(x_shift, y_shift + 1, value); filter.putPixel(x_shift + 1, y_shift + 1, value); } } // draw image Channel channel = new Channel(width<<1, height<<1); for (int y = 1; y < (height<<1) - 1; y++) { for (int x = 1; x < (width<<1) - 1; x++) { channel.putPixel(x, y, filter.getPixel(x - 1, y) + filter.getPixel(x + 1, y) + filter.getPixel(x, y - 1) + filter.getPixel(x, y + 1)); } } // fix edges int max = (width<<1) - 1; for (int i = 0; i < max; i++) { channel.putPixel(0, i, filter.getPixelWrap(-1, i) + filter.getPixelWrap(1, i) + filter.getPixelWrap(0, i - 1) + filter.getPixelWrap(0, i + 1)); channel.putPixel(i, 0, filter.getPixelWrap(i, -1) + filter.getPixelWrap(i, 1) + filter.getPixelWrap(i - 1, 0) + filter.getPixelWrap(i + 1, 0)); channel.putPixel(max, i, filter.getPixelWrap(max - 1, i) + filter.getPixelWrap(max + 1, i) + filter.getPixelWrap(max, i - 1) + filter.getPixelWrap(max, i + 1)); channel.putPixel(i, max, filter.getPixelWrap(i, max - 1) + filter.getPixelWrap(i, max + 1) + filter.getPixelWrap(i - 1, max) + filter.getPixelWrap(i + 1, max)); } pixels = channel.getPixels(); width = width<<1; height = height<<1; return this; }
public Channel place(Channel sprite, Channel alpha, int x_offset, int y_offset) { float alpha_val; for (int y = y_offset; y < y_offset + sprite.getHeight(); y++) { for (int x = x_offset; x < x_offset + sprite.getWidth(); x++) { alpha_val = alpha.getPixel(x - x_offset, y - y_offset); putPixelWrap(x, y, alpha_val*sprite.getPixelWrap(x - x_offset, y - y_offset) + (1 - alpha_val)*getPixelWrap(x, y)); } } return this; }
public Channel bump(Channel bumpmap, float lx, float ly, float shadow, float light, float ambient) { if(!(bumpmap.getWidth() == width && bumpmap.getHeight() == height)) throw new Exception("bumpmap does not match channel size"); Channel channel = new Channel(width, height); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { float nx = bumpmap.getPixelWrap(x + 1, y) - bumpmap.getPixelWrap(x - 1, y); float ny = bumpmap.getPixelWrap(x, y + 1) - bumpmap.getPixelWrap(x, y - 1); float brightness = nx*lx + ny*ly; if (brightness >= 0) { channel.putPixelClip(x, y, (getPixel(x, y) + brightness*light)*(bumpmap.getPixel(x, y)*shadow + 1 - shadow)); } else { channel.putPixelClip(x, y, (getPixel(x, y) + brightness*(1 - ambient))*(bumpmap.getPixel(x, y)*shadow + 1 - shadow)); } } } pixels = channel.getPixels(); return this; }
public Channel smoothWrap(int radius) { radius = Math.Max(1, radius); Channel filter = new Channel(width, height); float factor = 1f/((2*radius + 1)*(2*radius + 1)); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { filter.putPixel(x, y, factor*getPixel(x, y)); } } for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { float sum = 0f; for (int i = -radius; i < radius + 1; i++) { for (int j = -radius; j < radius + 1; j++) { sum += filter.getPixelWrap(x + j, y + i); } } putPixel(x, y, sum); } } return this; }
public Channel smoothFast() { Channel filter = new Channel(width, height); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { filter.putPixel(x, y, 0.25f*getPixel(x, y)); } } for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { putPixel(x, y, filter.getPixelWrap(x - 1, y) + filter.getPixelWrap(x + 1, y) + filter.getPixelWrap(x, y - 1) + filter.getPixelWrap(x, y + 1)); } } return this; }
public static Channel erode5(Channel channel, Channel rain, float erosion_water, float erosion_flow, float evaporation, float water_threshold, float solulibility, int ipr, int iterations) { Channel w = new Channel(channel.width, channel.height); // water map Channel dw = new Channel(channel.width, channel.height); // delta water map Channel s = new Channel(channel.width, channel.height); // sediment map Channel ds = new Channel(channel.width, channel.height); // delta sediment map Console.Write("Hydraulic erosion 5: "); for (int i = 0; i < iterations; i++) { Console.Write("."); // save frames /* if (channel.width > 128 && i%10 == 0) { if (i < 10) { channel.toLayer().saveAsPNG("erosion00" + i); } else if (i < 100) { channel.toLayer().saveAsPNG("erosion0" + i); } else { channel.toLayer().saveAsPNG("erosion" + i); } } */ // water is added according to rain map if (i%ipr == 0) { w.channelAdd(rain); } // the presence of water dissolves material channel.channelSubtract(w.copy().multiply(erosion_water)); s.channelAdd(w.copy().multiply(erosion_water)); // water and sediment are transported float h, h1, h2, h3, h4, d1, d2, d3, d4, total_height, total_height_diff, total_height_diff_inv, avr_height, water_amount; int cells; for (int y = 0; y < channel.height; y++) { for (int x = 0; x < channel.width; x++) { // water transport // calculate total heights and height differences h = channel.getPixel(x, y) + w.getPixel(x, y) + s.getPixel(x, y); h1 = channel.getPixelWrap(x , y + 1) + w.getPixelWrap(x , y + 1) + s.getPixelWrap(x , y + 1); h2 = channel.getPixelWrap(x - 1, y ) + w.getPixelWrap(x - 1, y ) + s.getPixelWrap(x - 1, y ); h3 = channel.getPixelWrap(x + 1, y ) + w.getPixelWrap(x + 1, y ) + s.getPixelWrap(x + 1, y ); h4 = channel.getPixelWrap(x , y - 1) + w.getPixelWrap(x , y - 1) + s.getPixelWrap(x , y - 1); d1 = h - h1; d2 = h - h2; d3 = h - h3; d4 = h - h4; // calculate amount of water to transport total_height = 0f; total_height_diff = 0f; cells = 1; if (d1 > 0) { total_height_diff+= d1; total_height+= h1; cells++; } if (d2 > 0) { total_height_diff+= d2; total_height+= h2; cells++; } if (d3 > 0) { total_height_diff+= d3; total_height+= h3; cells++; } if (d4 > 0) { total_height_diff+= d4; total_height+= h4; cells++; } if (cells == 1) { continue; } avr_height = total_height/cells; water_amount = Math.Min(w.getPixel(x, y), h - avr_height); dw.putPixel(x, y, dw.getPixel(x, y) - water_amount); total_height_diff_inv = water_amount/total_height_diff; // transport water if (d1 > 0) { dw.putPixelWrap(x, y + 1, dw.getPixelWrap(x, y + 1) + d1*total_height_diff_inv); } if (d2 > 0) { dw.putPixelWrap(x - 1, y, dw.getPixelWrap(x - 1, y) + d2*total_height_diff_inv); } if (d3 > 0) { dw.putPixelWrap(x + 1, y, dw.getPixelWrap(x + 1, y) + d3*total_height_diff_inv); } if (d4 > 0) { dw.putPixelWrap(x, y - 1, dw.getPixelWrap(x, y - 1) + d4*total_height_diff_inv); } // sediment transport /* h = s.getPixel(x, y); h1 = s.getPixelWrap(x , y + 1); h2 = s.getPixelWrap(x - 1, y ); h3 = s.getPixelWrap(x + 1, y ); h4 = s.getPixelWrap(x , y - 1); d1 = h - h1; d2 = h - h2; d3 = h - h3; d4 = h - h4; // calculate amount of sediment to transport total_height = 0f; total_height_diff = 0f; cells = 1; if (d1 > 0) { total_height_diff+= d1; total_height+= h1; cells++; } if (d2 > 0) { total_height_diff+= d2; total_height+= h2; cells++; } if (d3 > 0) { total_height_diff+= d3; total_height+= h3; cells++; } if (d4 > 0) { total_height_diff+= d4; total_height+= h4; cells++; } if (cells == 1) { continue; } avr_height = total_height/cells; sediment_amount = Math.Min(s.getPixel(x, y), h - avr_height); ds.putPixel(x, y, ds.getPixel(x, y) - sediment_amount); total_height_diff_inv = sediment_amount/total_height_diff; // transport sediment if (d1 > 0) { ds.putPixelWrap(x, y + 1, ds.getPixelWrap(x, y + 1) + d1*total_height_diff_inv); } if (d2 > 0) { ds.putPixelWrap(x - 1, y, ds.getPixelWrap(x - 1, y) + d2*total_height_diff_inv); } if (d3 > 0) { ds.putPixelWrap(x + 1, y, ds.getPixelWrap(x + 1, y) + d3*total_height_diff_inv); } if (d4 > 0) { ds.putPixelWrap(x, y - 1, ds.getPixelWrap(x, y - 1) + d4*total_height_diff_inv); } */ } } // more sediment is dissolved according to amount of water flow /* channel.channelSubtract(dw.copy().fill(0f, Float.MIN_VALUE, 0f).multiply(erosion_flow)); s.channelAdd(dw.copy().fill(0f, Float.MIN_VALUE, 0f).multiply(erosion_flow)); */ // apply water and sediment delta maps w.channelAdd(dw); //w.fill(0f, Float.MIN_VALUE, water_threshold); // remove water below threshold amount s.channelAdd(ds); dw.fill(0f); ds.fill(0f); // water evaporates w.multiply(evaporation); // sediment is deposited for (int y = 0; y < channel.height; y++) { for (int x = 0; x < channel.width; x++) { float deposition = s.getPixel(x, y) - w.getPixel(x, y)*solulibility; if (deposition > 0) { s.putPixel(x, y, s.getPixel(x, y) - deposition); channel.putPixel(x, y, channel.getPixel(x, y) + deposition); } } } } Console.WriteLine("DONE"); return channel; }