/* 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.solidrender; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import org.jwildfire.base.Tools; import org.jwildfire.base.mathlib.GfxMathLib; import org.jwildfire.base.mathlib.MathLib; import org.jwildfire.create.tina.base.Flame; import org.jwildfire.create.tina.base.raster.NormalsCalculator; import org.jwildfire.create.tina.base.raster.RasterPoint; import org.jwildfire.create.tina.render.LightViewCalculator; import org.jwildfire.create.tina.render.PlotSample; import org.jwildfire.create.tina.render.filter.FilterKernel; import org.jwildfire.create.tina.render.filter.GaussianFilterKernel; @SuppressWarnings("serial") public class ShadowCalculator implements Serializable { private final int rasterWidth, rasterHeight; private final float originXBuf[][]; private final float originYBuf[][]; private final float originZBuf[][]; private double[] lightX, lightY, shadowIntensity; private double shadowDistBias; private final int lightCount; private static final boolean withAcceleration = true; private float[][][] accLightProjectionZBuf; private int shadowMapSize; private final double shadowMapXScale[], shadowMapYScale[], shadowMapXCentre[], shadowMapYCentre[]; private static final int PRE_SHADOWMAP_SIZE = 40960; private final float pre_shadowXBuf[][], pre_shadowYBuf[][], pre_shadowZBuf[][]; private final int pre_shadowIndex[]; private final int shadowSmoothRadius; private boolean shadowSoften; private final double shadowSmoothKernel[][]; private final float shadowZBuf[][][]; private LightViewCalculator lightViewCalculator; public ShadowCalculator(int rasterWidth, int rasterHeight, float[][] originXBuf, float[][] originYBuf, float[][] originZBuf, double imgSize, Flame flame) { this.rasterWidth = rasterWidth; this.rasterHeight = rasterHeight; this.originXBuf = originXBuf; this.originYBuf = originYBuf; this.originZBuf = originZBuf; lightCount = flame.getSolidRenderSettings().getLights().size(); shadowZBuf = new float[lightCount][][]; pre_shadowIndex = new int[lightCount]; pre_shadowXBuf = new float[lightCount][]; pre_shadowYBuf = new float[lightCount][]; pre_shadowZBuf = new float[lightCount][]; shadowMapXCentre = new double[lightCount]; shadowMapYCentre = new double[lightCount]; shadowMapXScale = new double[lightCount]; shadowMapYScale = new double[lightCount]; lightX = new double[lightCount]; lightY = new double[lightCount]; shadowIntensity = new double[lightCount]; shadowMapSize = flame.getSolidRenderSettings().getShadowmapSize(); if (shadowMapSize < 64) { shadowMapSize = 64; } shadowDistBias = flame.getSolidRenderSettings().getShadowmapBias(); for (int i = 0; i < lightCount; i++) { DistantLight light = flame.getSolidRenderSettings().getLights().get(i); if (light.isCastShadows()) { shadowZBuf[i] = new float[shadowMapSize][shadowMapSize]; pre_shadowXBuf[i] = new float[PRE_SHADOWMAP_SIZE]; pre_shadowYBuf[i] = new float[PRE_SHADOWMAP_SIZE]; pre_shadowZBuf[i] = new float[PRE_SHADOWMAP_SIZE]; for (int k = 0; k < shadowZBuf[i].length; k++) { for (int l = 0; l < shadowZBuf[i][0].length; l++) { shadowZBuf[i][k][l] = NormalsCalculator.ZBUF_ZMIN; } } lightX[i] = light.getAltitude(); lightY[i] = light.getAzimuth(); shadowIntensity[i] = 1.0 - GfxMathLib.clamp(light.getShadowIntensity(), 0.0, 1.0); } else { shadowZBuf[i] = null; pre_shadowXBuf[i] = pre_shadowYBuf[i] = pre_shadowZBuf[i] = null; } } shadowSoften = ShadowType.SMOOTH.equals(flame.getSolidRenderSettings().getShadowType()); double rawSmoothRadius = flame.getSolidRenderSettings().getShadowSmoothRadius(); if (rawSmoothRadius < MathLib.EPSILON) { rawSmoothRadius = 0.0; } shadowSmoothRadius = clipSmoothRadius(Tools.FTOI(rawSmoothRadius * 6.0 * imgSize / 1000.0)); if (shadowSmoothRadius < 1.0) { shadowSoften = false; shadowSmoothKernel = null; } else { shadowSmoothKernel = getShadowSmoothKernel(shadowSmoothRadius); } } private int clipSmoothRadius(int radius) { return radius < 128 ? radius : 128; } public void accelerateShadows() { if (withAcceleration) { accLightProjectionZBuf = new float[lightCount][][]; for (int i = 0; i < lightCount; i++) { if (shadowZBuf[i] != null) { accLightProjectionZBuf[i] = new float[rasterWidth][rasterHeight]; for (int pX = 0; pX < rasterWidth; pX++) { for (int pY = 0; pY < rasterHeight; pY++) { accLightProjectionZBuf[i][pX][pY] = NormalsCalculator.ZBUF_ZMIN; float originX = originXBuf[pX][pY]; if (originX != NormalsCalculator.ZBUF_ZMIN) { float originY = originYBuf[pX][pY]; if (originY != NormalsCalculator.ZBUF_ZMIN) { float originZ = originZBuf[pX][pY]; if (originZ != NormalsCalculator.ZBUF_ZMIN) { int x = projectPointToLightMapX(i, lightViewCalculator.applyLightProjectionX(i, originX, originY, originZ)); int y = projectPointToLightMapY(i, lightViewCalculator.applyLightProjectionY(i, originX, originY, originZ)); if (x >= 0 && x < shadowZBuf[i].length && y >= 0 && y < shadowZBuf[i][0].length) { double lightZ = lightViewCalculator.applyLightProjectionZ(i, originX, originY, originZ); accLightProjectionZBuf[i][pX][pY] = (float) GfxMathLib.step(shadowZBuf[i][x][y] - shadowDistBias, lightZ); } } } } } } } } } } private static Map<Integer, double[][]> shadowSmoothKernelMap = new HashMap<>(); private double[][] getShadowSmoothKernel(int radius) { double res[][] = shadowSmoothKernelMap.get(Integer.valueOf(radius)); if (res == null) { FilterKernel shadowSmoothKernel = new GaussianFilterKernel(); double shadowKernelScale = 1.0; double shadowScaledInvRadius = 1.0 / shadowSmoothRadius * shadowKernelScale * shadowSmoothKernel.getSpatialSupport(); int size = 2 * radius + 1; res = new double[size][size]; for (int k = -radius; k <= radius; k++) { int x = radius + k; double k_square = MathLib.sqr(k * shadowScaledInvRadius); for (int l = -radius; l <= radius; l++) { int y = radius + l; double r = MathLib.sqrt(k_square + MathLib.sqr(l * shadowScaledInvRadius)); res[x][y] = shadowSmoothKernel.getFilterCoeff(r); } } synchronized (shadowSmoothKernelMap) { shadowSmoothKernelMap.put(Integer.valueOf(radius), res); } } return res; } public void calcShadows(int pX, int pY, RasterPoint pDestRasterPoint) { pDestRasterPoint.hasShadows = true; for (int i = 0; i < shadowZBuf.length; i++) { pDestRasterPoint.visibility[i] = 1.0; if (shadowZBuf[i] != null) { if (shadowSoften) { calcSmoothShadowIntensity(pX, pY, pDestRasterPoint, i, shadowSmoothRadius, shadowSmoothKernel); } else { calcFastShadowIntensity(pX, pY, pDestRasterPoint, i); } pDestRasterPoint.visibility[i] = GfxMathLib.clamp(pDestRasterPoint.visibility[i] + shadowIntensity[i]); } } } private void calcFastShadowIntensity(int pX, int pY, RasterPoint pDestRasterPoint, int i) { if (accLightProjectionZBuf != null && accLightProjectionZBuf[i] != null) { if (accLightProjectionZBuf[i][pX][pY] > NormalsCalculator.ZBUF_ZMIN) { pDestRasterPoint.visibility[i] = accLightProjectionZBuf[i][pX][pY]; } } else { float originX = originXBuf[pX][pY]; if (originX != NormalsCalculator.ZBUF_ZMIN) { float originY = originYBuf[pX][pY]; if (originY != NormalsCalculator.ZBUF_ZMIN) { float originZ = originZBuf[pX][pY]; if (originZ != NormalsCalculator.ZBUF_ZMIN) { int x = projectPointToLightMapX(i, lightViewCalculator.applyLightProjectionX(i, originX, originY, originZ)); int y = projectPointToLightMapY(i, lightViewCalculator.applyLightProjectionY(i, originX, originY, originZ)); if (x >= 0 && x < shadowZBuf[i].length && y >= 0 && y < shadowZBuf[i][0].length) { double lightZ = lightViewCalculator.applyLightProjectionZ(i, originX, originY, originZ); if (lightZ < shadowZBuf[i][x][y] - shadowDistBias) { pDestRasterPoint.visibility[i] = 0.0; } // pDestRasterPoint.visibility[i] = GfxMathLib.step(shadowZBuf[i][x][y] - bias, lightZ); } } } } } } private void calcSmoothShadowIntensity(int pX, int pY, RasterPoint pDestRasterPoint, int i, int shadowSmoothRadius, double[][] shadowSmoothKernel) { pDestRasterPoint.visibility[i] = 1.0; double totalIntensity = 0.0; if (accLightProjectionZBuf != null && accLightProjectionZBuf[i] != null) { int dl = shadowSmoothRadius / 8 + 1; for (int k = -shadowSmoothRadius; k <= shadowSmoothRadius; k += dl) { int dstX = pX + k; if (dstX >= 0 && dstX < originXBuf.length) { for (int l = -shadowSmoothRadius; l <= shadowSmoothRadius; l += dl) { int dstY = pY + l; if (dstY >= 0 && dstY < originXBuf[0].length) { if (accLightProjectionZBuf[i][dstX][dstY] > NormalsCalculator.ZBUF_ZMIN) { double intensity = shadowSmoothKernel[k + shadowSmoothRadius][l + shadowSmoothRadius]; totalIntensity += intensity; pDestRasterPoint.visibility[i] += accLightProjectionZBuf[i][dstX][dstY] * intensity; } } } } } } else { for (int k = -shadowSmoothRadius; k <= shadowSmoothRadius; k++) { int dstX = pX + k; if (dstX >= 0 && dstX < originXBuf.length) { for (int l = -shadowSmoothRadius; l <= shadowSmoothRadius; l++) { int dstY = pY + l; if (dstY >= 0 && dstY < originXBuf[0].length) { float originX = originXBuf[dstX][dstY]; if (originX != NormalsCalculator.ZBUF_ZMIN) { float originY = originYBuf[dstX][dstY]; if (originY != NormalsCalculator.ZBUF_ZMIN) { float originZ = originZBuf[dstX][dstY]; if (originZ != NormalsCalculator.ZBUF_ZMIN) { int x = projectPointToLightMapX(i, lightViewCalculator.applyLightProjectionX(i, originX, originY, originZ)); int y = projectPointToLightMapY(i, lightViewCalculator.applyLightProjectionY(i, originX, originY, originZ)); if (x >= 0 && x < shadowZBuf[i].length && y >= 0 && y < shadowZBuf[i][0].length) { double intensity = shadowSmoothKernel[k + shadowSmoothRadius][l + shadowSmoothRadius]; totalIntensity += intensity; double lightZ = lightViewCalculator.applyLightProjectionZ(i, originX, originY, originZ); pDestRasterPoint.visibility[i] += GfxMathLib.step(shadowZBuf[i][x][y] - shadowDistBias, lightZ) * intensity; } } } } } } } } } if (totalIntensity > MathLib.EPSILON) { pDestRasterPoint.visibility[i] /= totalIntensity; } } private int projectPointToLightMapX(int shadowMapIdx, double x) { return (int) (shadowMapXScale[shadowMapIdx] * x + shadowMapXCentre[shadowMapIdx] + 0.5); } private int projectPointToLightMapY(int shadowMapIdx, double y) { return (int) (shadowMapYScale[shadowMapIdx] * y + shadowMapYCentre[shadowMapIdx] + 0.5); } public void addShadowMapSamples(int pShadowMapIdx, PlotSample[] pPlotBuffer, int pCount) { float[][] shadowMap = shadowZBuf[pShadowMapIdx]; for (int i = 0; i < pCount; i++) { PlotSample sample = pPlotBuffer[i]; if (pre_shadowIndex[pShadowMapIdx] < 0) { int x = projectPointToLightMapX(pShadowMapIdx, sample.x); if (x >= 0 && x < shadowMap.length) { int y = projectPointToLightMapY(pShadowMapIdx, sample.y); if (y >= 0 && y < shadowMap[0].length) { if (sample.z > shadowMap[x][y]) { shadowMap[x][y] = (float) sample.z; } } } } else { if (pre_shadowIndex[pShadowMapIdx] < pre_shadowXBuf[pShadowMapIdx].length) { int idx = pre_shadowIndex[pShadowMapIdx]; pre_shadowXBuf[pShadowMapIdx][idx] = (float) sample.x; pre_shadowYBuf[pShadowMapIdx][idx] = (float) sample.y; pre_shadowZBuf[pShadowMapIdx][idx] = (float) sample.z; pre_shadowIndex[pShadowMapIdx]++; } else { float xmin = pre_shadowXBuf[pShadowMapIdx][0]; float xmax = xmin; float ymin = pre_shadowYBuf[pShadowMapIdx][0]; float ymax = ymin; for (int k = 0; k < pre_shadowXBuf[pShadowMapIdx].length; k++) { if (pre_shadowXBuf[pShadowMapIdx][k] < xmin) { xmin = pre_shadowXBuf[pShadowMapIdx][k]; } else if (pre_shadowXBuf[pShadowMapIdx][k] > xmax) { xmax = pre_shadowXBuf[pShadowMapIdx][k]; } if (pre_shadowYBuf[pShadowMapIdx][k] < ymin) { ymin = pre_shadowYBuf[pShadowMapIdx][k]; } else if (pre_shadowYBuf[pShadowMapIdx][k] > ymax) { ymax = pre_shadowYBuf[pShadowMapIdx][k]; } } { double safety = 0.03; double dx = xmax - xmin; double dy = ymax - ymin; xmin -= safety * dx; xmax += safety * dx; ymin -= safety * dy; ymax += safety * dy; } double eps = 0.001; double dx = xmax - xmin; double dy = ymax - ymin; if (dx < eps) { dx = eps; } if (dy < eps) { dy = eps; } shadowMapXScale[pShadowMapIdx] = (double) shadowMapSize / dx; shadowMapYScale[pShadowMapIdx] = (double) shadowMapSize / dy; shadowMapXCentre[pShadowMapIdx] = -xmin * shadowMapXScale[pShadowMapIdx]; shadowMapYCentre[pShadowMapIdx] = -ymin * shadowMapYScale[pShadowMapIdx]; pre_shadowIndex[pShadowMapIdx] = -1; for (int k = 0; k < pre_shadowXBuf[pShadowMapIdx].length; k++) { int x = projectPointToLightMapX(pShadowMapIdx, pre_shadowXBuf[pShadowMapIdx][k]); if (x >= 0 && x < shadowMap.length) { int y = projectPointToLightMapY(pShadowMapIdx, pre_shadowYBuf[pShadowMapIdx][k]); if (y >= 0 && y < shadowMap[0].length) { float z = pre_shadowZBuf[pShadowMapIdx][k]; if (z > shadowMap[x][y]) { shadowMap[x][y] = z; } } } } } } } } public void setLightViewCalculator(LightViewCalculator lightViewCalculator) { this.lightViewCalculator = lightViewCalculator; } public void finalizeRaster() { // for (int i = 0; i < shadowZBuf.length; i++) { // if (shadowZBuf[i] != null) { // RasterTools.saveFloatBuffer(shadowZBuf[i], "D:\\TMP\\wf_shadowmap_" + i + ".png"); // } // if (accLightProjectionZBuf[i] != null) { // RasterTools.saveFloatBuffer(accLightProjectionZBuf[i], "D:\\TMP\\wf_acc_shadowmap_" + i + ".png"); // } // } } public LightViewCalculator getLightViewCalculator() { return lightViewCalculator; } }