/*
JWildfire - an image and animation processor written in Java
Copyright (C) 1995-2016 Andreas Maschke
This is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with this software;
if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jwildfire.create.tina.base.raster;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.jwildfire.base.Prefs;
import org.jwildfire.base.ThreadTools;
import org.jwildfire.base.mathlib.MathLib;
import org.jwildfire.create.tina.render.image.AbstractImageRenderThread;
@SuppressWarnings("serial")
public class NormalsCalculator implements Serializable {
private final int rasterWidth, rasterHeight;
private final float nxBuf[][];
private final float nyBuf[][];
private final float nzBuf[][];
private final float originXBuf[][];
private final float originYBuf[][];
private final float originZBuf[][];
public final static float ZBUF_ZMIN = -Float.MAX_VALUE;
private class Corner implements Serializable {
public final int dx, dy;
public Corner(int dx, int dy) {
super();
this.dx = dx;
this.dy = dy;
}
public boolean isInside(int xOff, int yOff) {
int x = xOff + dx;
if (x < 0 || x >= rasterWidth)
return false;
int y = yOff + dy;
if (y < 0 || y >= rasterHeight)
return false;
return true;
}
}
public static class CornerPair implements Serializable {
public final Corner a, b;
public CornerPair(Corner a, Corner b) {
super();
this.a = a;
this.b = b;
}
public boolean isInside(int xOff, int yOff) {
return a.isInside(xOff, yOff) && b.isInside(xOff, yOff);
}
}
private final CornerPair[] NNEIGHBOURS_COARSE = new CornerPair[] {
new CornerPair(new Corner(-1, 0), new Corner(-1, -1)),
new CornerPair(new Corner(-1, -1), new Corner(0, -1)),
new CornerPair(new Corner(0, -1), new Corner(1, -1)),
new CornerPair(new Corner(1, -1), new Corner(1, 0)),
new CornerPair(new Corner(1, 0), new Corner(1, 1)),
new CornerPair(new Corner(1, 1), new Corner(0, 1)),
new CornerPair(new Corner(0, 1), new Corner(-1, 1)),
new CornerPair(new Corner(-1, 1), new Corner(-1, 0))
};
public boolean hasNormalAtLocation(int x, int y) {
return nxBuf[x][y] != ZBUF_ZMIN;
}
public void refreshNormalAtLocation(int x, int y) {
double xb = originXBuf[x][y];
double yb = originYBuf[x][y];
double zb = originZBuf[x][y];
nxBuf[x][y] = nyBuf[x][y] = nzBuf[x][y] = ZBUF_ZMIN;
if (zb != ZBUF_ZMIN) {
double nx = 0.0, ny = 0.0, nz = 0.0;
double minzdist = MathLib.fabs(ZBUF_ZMIN * 10.0);
int samples = 0;
for (int k = 0; k < NNEIGHBOURS_COARSE.length; k++) {
if (NNEIGHBOURS_COARSE[k].isInside(x, y)) {
double azb = originZBuf[x + NNEIGHBOURS_COARSE[k].a.dx][y + NNEIGHBOURS_COARSE[k].a.dy];
if (azb != ZBUF_ZMIN) {
double ax = xb - originXBuf[x + NNEIGHBOURS_COARSE[k].a.dx][y + NNEIGHBOURS_COARSE[k].a.dy];
double ay = yb - originYBuf[x + NNEIGHBOURS_COARSE[k].a.dx][y + NNEIGHBOURS_COARSE[k].a.dy];
double az = zb - azb;
double bzb = originZBuf[x + NNEIGHBOURS_COARSE[k].b.dx][y + NNEIGHBOURS_COARSE[k].b.dy];
if (bzb != ZBUF_ZMIN) {
samples++;
double bx = xb - originXBuf[x + NNEIGHBOURS_COARSE[k].b.dx][y + NNEIGHBOURS_COARSE[k].b.dy];
double by = yb - originYBuf[x + NNEIGHBOURS_COARSE[k].b.dx][y + NNEIGHBOURS_COARSE[k].b.dy];
double bz = zb - bzb;
double zdist = MathLib.fabs(az) + MathLib.fabs(bz);
if (zdist < minzdist) {
minzdist = zdist;
nx = ay * bz - az * by;
ny = az * bx - ax * bz;
nz = ax * by - ay * bx;
}
}
}
}
if (samples > 5) {
break;
}
}
if (samples > 0) {
double r = MathLib.sqrt(nx * nx + ny * ny + nz * nz);
if (r > MathLib.EPSILON) {
nx /= r;
ny /= r;
nz /= r;
}
nxBuf[x][y] = (float) nx;
nyBuf[x][y] = (float) ny;
nzBuf[x][y] = (float) nz;
}
}
}
private class RefreshNormalsThread extends AbstractImageRenderThread {
private final int startRow, endRow;
public RefreshNormalsThread(int pStartRow, int pEndRow) {
startRow = pStartRow;
endRow = pEndRow;
}
@Override
public void run() {
setDone(false);
try {
for (int i = startRow; i < endRow; i++) {
for (int j = 0; j < rasterWidth; j++) {
refreshNormalAtLocation(j, i);
}
}
}
finally {
setDone(true);
}
}
}
public void refreshAllNormals() {
int threadCount = Prefs.getPrefs().getTinaRenderThreads();
if (threadCount < 1 || rasterHeight < 8 * threadCount) {
threadCount = 1;
}
int rowsPerThread = rasterHeight / threadCount;
List<RefreshNormalsThread> threads = new ArrayList<>();
for (int i = 0; i < threadCount; i++) {
int startRow = i * rowsPerThread;
int endRow = i < threadCount - 1 ? startRow + rowsPerThread : rasterHeight;
RefreshNormalsThread thread = new RefreshNormalsThread(startRow, endRow);
threads.add(thread);
if (threadCount > 1) {
new Thread(thread).start();
}
else {
thread.run();
}
}
ThreadTools.waitForThreads(threadCount, threads);
/*
for (int i = 0; i < rasterWidth; i++) {
for (int j = 0; j < rasterHeight; j++) {
refreshNormalsAtLocation(i, j);
}
}*/
}
public NormalsCalculator(int rasterWidth, int rasterHeight, float[][] nxBuf, float[][] nyBuf, float[][] nzBuf, float[][] originXBuf, float[][] originYBuf, float[][] originZBuf) {
super();
this.rasterWidth = rasterWidth;
this.rasterHeight = rasterHeight;
this.nxBuf = nxBuf;
this.nyBuf = nyBuf;
this.nzBuf = nzBuf;
this.originXBuf = originXBuf;
this.originYBuf = originYBuf;
this.originZBuf = originZBuf;
}
}