/* JWildfire - an image and animation processor written in Java Copyright (C) 1995-2017 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.render; import org.jwildfire.base.Tools; import org.jwildfire.base.mathlib.GfxMathLib; import org.jwildfire.base.mathlib.MathLib; import org.jwildfire.base.mathlib.VecMathLib; import org.jwildfire.base.mathlib.VecMathLib.RGBColorD; import org.jwildfire.base.mathlib.VecMathLib.UVPairD; import org.jwildfire.base.mathlib.VecMathLib.VectorD; import org.jwildfire.create.tina.base.Flame; import org.jwildfire.create.tina.base.raster.AbstractRaster; import org.jwildfire.create.tina.base.raster.RasterPoint; import org.jwildfire.create.tina.base.solidrender.DistantLight; import org.jwildfire.create.tina.base.solidrender.MaterialSettings; import org.jwildfire.create.tina.random.AbstractRandomGenerator; import org.jwildfire.create.tina.random.MarsagliaRandomGenerator; import org.jwildfire.create.tina.swing.ChannelMixerCurves; import org.jwildfire.create.tina.variation.RessourceManager; import org.jwildfire.image.Pixel; import org.jwildfire.image.SimpleImage; @SuppressWarnings("serial") public class LogDensityFilter extends FilterHolder { private final ColorFunc colorFunc; private AbstractRaster raster; private int rasterWidth, rasterHeight, rasterSize; private int destImageWidth, destImageHeight; private double precalcLogArray[]; private final AbstractRandomGenerator dofRandGen; private boolean solidRendering; private SimpleImage bgImage; private LogScaleCalculator logScaleCalculator; public LogDensityFilter(Flame pFlame, AbstractRandomGenerator pRandGen) { super(pFlame); colorFunc = pFlame.getChannelMixerMode().getColorFunc(pFlame, pRandGen); solidRendering = flame.getSolidRenderSettings().isSolidRenderingEnabled(); if (solidRendering && flame.getCamDOF() > MathLib.EPSILON) { dofRandGen = new MarsagliaRandomGenerator(); dofRandGen.randomize(pFlame.hashCode()); } else { dofRandGen = null; } if (flame.getBGImageFilename().length() > 0) { try { bgImage = (SimpleImage) RessourceManager.getImage(flame.getBGImageFilename()); if (bgImage.getImageWidth() < 2 || bgImage.getImageHeight() < 2) { bgImage = null; } } catch (Exception ex) { ex.printStackTrace(); } } } public void setRaster(AbstractRaster pRaster, int pRasterWidth, int pRasterHeight, int pImageWidth, int pImageHeight) { raster = pRaster; rasterWidth = pRasterWidth; rasterHeight = pRasterHeight; rasterSize = rasterWidth * rasterHeight; logScaleCalculator = new LogScaleCalculator(flame, pImageWidth, pImageHeight, oversample); precalcLogArray = logScaleCalculator.precalcLogArray(); destImageWidth = pImageWidth; destImageHeight = pImageHeight; } public void transformPointSimple(LogDensityPoint pFilteredPnt, int pX, int pY) { double balanceRed = logScaleCalculator.getBalanceRed(); double balanceGreen = logScaleCalculator.getBalanceGreen(); double balanceBlue = logScaleCalculator.getBalanceBlue(); pFilteredPnt.clear(); int solidSampleCount = 0; for (int px = 0; px < oversample; px++) { for (int py = 0; py < oversample; py++) { getSample(pFilteredPnt, pX * oversample + px, pY * oversample + py); if (solidRendering) { if (pFilteredPnt.rp.hasSolidColors && addSolidColors(pFilteredPnt, pFilteredPnt.rp, 1.0)) { solidSampleCount++; pFilteredPnt.dofDist += pFilteredPnt.rp.dofDist; pFilteredPnt.intensity = 1.0; } } else { double logScale; long pCount = pFilteredPnt.rp.count; if (pCount < precalcLogArray.length) { logScale = precalcLogArray[(int) pCount]; } else { logScale = logScaleCalculator.calcLogScale(pCount); } if (pCount > 0) { if (colorFunc == ColorFunc.NULL) { pFilteredPnt.red += logScale * pFilteredPnt.rp.red * balanceRed; pFilteredPnt.green += logScale * pFilteredPnt.rp.green * balanceGreen; pFilteredPnt.blue += logScale * pFilteredPnt.rp.blue * balanceBlue; } else { final double scale = ChannelMixerCurves.FILTER_SCALE; double rawR = pFilteredPnt.rp.red * scale / pCount; double rawG = pFilteredPnt.rp.green * scale / pCount; double rawB = pFilteredPnt.rp.blue * scale / pCount; pFilteredPnt.red += logScale * colorFunc.mapRGBToR(rawR, rawG, rawB) * pCount * balanceRed / scale; pFilteredPnt.green += logScale * colorFunc.mapRGBToG(rawR, rawG, rawB) * pCount * balanceGreen / scale; pFilteredPnt.blue += logScale * colorFunc.mapRGBToB(rawR, rawG, rawB) * pCount * balanceBlue / scale; } pFilteredPnt.intensity += logScale * pCount * flame.getWhiteLevel(); } } } } if (solidRendering && (solidSampleCount > 0)) { double dCount = 1.0 / (double) solidSampleCount; pFilteredPnt.dofDist *= dCount; pFilteredPnt.solidRed *= dCount; pFilteredPnt.solidGreen *= dCount; pFilteredPnt.solidBlue *= dCount; } calculateBGColor(pFilteredPnt, pX, pY); } private void getZSample(ZBufferSample pDest, int pX, int pY) { raster.readZBufferSafe(pX, pY, pDest); } private void getSample(LogDensityPoint pFilteredPnt, int pX, int pY) { raster.readRasterPointSafe(pX, pY, pFilteredPnt.rp); } public double calcDensity(long pSampleCount, long pRasterSize) { return (double) pSampleCount / (double) pRasterSize * oversample; } public double calcDensity(long pSampleCount) { if (rasterSize == 0) { throw new IllegalStateException(); } return (double) pSampleCount / (double) rasterSize * oversample; } public void transformPoint(LogDensityPoint pFilteredPnt, int pX, int pY) { double balanceRed = logScaleCalculator.getBalanceRed(); double balanceGreen = logScaleCalculator.getBalanceGreen(); double balanceBlue = logScaleCalculator.getBalanceBlue(); pFilteredPnt.clear(); if (noiseFilterSize > 1) { if (solidRendering) { for (int i = 0; i < noiseFilterSize; i++) { for (int j = 0; j < noiseFilterSize; j++) { getSample(pFilteredPnt, pX * oversample + j - noiseFilterSizeHalve, pY * oversample + i - noiseFilterSizeHalve); if (pFilteredPnt.rp.hasSolidColors) { double f = filter[i][j] / (double) (oversample * oversample); if (addSolidColors(pFilteredPnt, pFilteredPnt.rp, f)) { pFilteredPnt.dofDist += f * pFilteredPnt.rp.dofDist; pFilteredPnt.intensity += f; } } } } } else { if (colorFunc == ColorFunc.NULL) { for (int i = 0; i < noiseFilterSize; i++) { for (int j = 0; j < noiseFilterSize; j++) { getSample(pFilteredPnt, pX * oversample + j - noiseFilterSizeHalve, pY * oversample + i - noiseFilterSizeHalve); long count = pFilteredPnt.rp.count; int pIdx = (int) count; if (pIdx > 0) { double logScale; if (pIdx < precalcLogArray.length) { logScale = precalcLogArray[pIdx]; } else { logScale = logScaleCalculator.calcLogScale(count); } pFilteredPnt.red += filter[i][j] * logScale * pFilteredPnt.rp.red * balanceRed; pFilteredPnt.green += filter[i][j] * logScale * pFilteredPnt.rp.green * balanceGreen; pFilteredPnt.blue += filter[i][j] * logScale * pFilteredPnt.rp.blue * balanceBlue; pFilteredPnt.intensity += filter[i][j] * logScale * count * flame.getWhiteLevel(); } } } } else { for (int i = 0; i < noiseFilterSize; i++) { for (int j = 0; j < noiseFilterSize; j++) { getSample(pFilteredPnt, pX * oversample + j - noiseFilterSizeHalve, pY * oversample + i - noiseFilterSizeHalve); long count = pFilteredPnt.rp.count; int pIdx = (int) count; if (pIdx > 0) { double logScale; if (pIdx < precalcLogArray.length) { logScale = precalcLogArray[pIdx]; } else { logScale = logScaleCalculator.calcLogScale(count); } final double scale = ChannelMixerCurves.FILTER_SCALE; double rawR = pFilteredPnt.rp.red * scale / (double) count; double rawG = pFilteredPnt.rp.green * scale / (double) count; double rawB = pFilteredPnt.rp.blue * scale / (double) count; double transR = colorFunc.mapRGBToR(rawR, rawG, rawB) * count / scale; double transG = colorFunc.mapRGBToG(rawR, rawG, rawB) * count / scale; double transB = colorFunc.mapRGBToB(rawR, rawG, rawB) * count / scale; pFilteredPnt.red += filter[i][j] * logScale * transR * balanceRed; pFilteredPnt.green += filter[i][j] * logScale * transG * balanceGreen; pFilteredPnt.blue += filter[i][j] * logScale * transB * balanceBlue; pFilteredPnt.intensity += filter[i][j] * logScale * count * flame.getWhiteLevel(); } } } } } } else { int solidSampleCount = 0; for (int px = 0; px < oversample; px++) { for (int py = 0; py < oversample; py++) { getSample(pFilteredPnt, pX * oversample + px - noiseFilterSizeHalve, pY * oversample + py - noiseFilterSizeHalve); if (solidRendering) { if (pFilteredPnt.rp.hasSolidColors && addSolidColors(pFilteredPnt, pFilteredPnt.rp, 1.0)) { pFilteredPnt.dofDist += pFilteredPnt.rp.dofDist; solidSampleCount++; } } else { double logScale; long pCount = pFilteredPnt.rp.count; if (pCount > 0) { if (pCount < precalcLogArray.length) { logScale = precalcLogArray[(int) pCount]; } else { logScale = logScaleCalculator.calcLogScale(pCount); } if (colorFunc == ColorFunc.NULL) { pFilteredPnt.red += logScale * pFilteredPnt.rp.red * balanceRed; pFilteredPnt.green += logScale * pFilteredPnt.rp.green * balanceGreen; pFilteredPnt.blue += logScale * pFilteredPnt.rp.blue * balanceBlue; } else { final double scale = ChannelMixerCurves.FILTER_SCALE; double rawR = pFilteredPnt.rp.red * scale / pCount; double rawG = pFilteredPnt.rp.green * scale / pCount; double rawB = pFilteredPnt.rp.blue * scale / pCount; pFilteredPnt.red += logScale * colorFunc.mapRGBToR(rawR, rawG, rawB) * pCount * balanceRed / scale; pFilteredPnt.green += logScale * colorFunc.mapRGBToG(rawR, rawG, rawB) * pCount * balanceGreen / scale; pFilteredPnt.blue += logScale * colorFunc.mapRGBToB(rawR, rawG, rawB) * pCount * balanceBlue / scale; } pFilteredPnt.intensity += logScale * pFilteredPnt.rp.count * flame.getWhiteLevel(); } } } } if (solidRendering) { if (solidSampleCount > 0) { double dCount = 1.0 / (double) solidSampleCount; pFilteredPnt.dofDist *= dCount; pFilteredPnt.solidRed *= dCount; pFilteredPnt.solidGreen *= dCount; pFilteredPnt.solidBlue *= dCount; pFilteredPnt.intensity = (double) solidSampleCount / (double) (oversample * oversample); } } } pFilteredPnt.clip(); calculateBGColor(pFilteredPnt, pX, pY); } public void transformZPoint(ZBufferSample pAccumSample, ZBufferSample pSample, int pX, int pY) { pAccumSample.clear(); int solidSampleCount = 0; for (int px = 0; px < oversample; px++) { for (int py = 0; py < oversample; py++) { getZSample(pSample, pX * oversample + px, pY * oversample + py); if (pSample.hasZ) { pAccumSample.z += pSample.z; pAccumSample.hasZ = true; solidSampleCount++; } } } if (solidSampleCount > 0) { pAccumSample.z /= (double) solidSampleCount; } } private boolean addSolidColors(LogDensityPoint dest, RasterPoint rp, double colorScale) { if (solidRendering && rp.hasNormals) { LightViewCalculator lightViewCalculator = raster.getLightViewCalculator(); double avgVisibility; if (rp.hasShadows) { avgVisibility = 0.0; int shadowCount = 0; for (int i = 0; i < rp.visibility.length; i++) { avgVisibility += rp.visibility[i]; shadowCount++; } if (shadowCount > 0) { avgVisibility /= (double) shadowCount; } else { avgVisibility = 1.0; } } else { avgVisibility = 1.0; } RGBColorD rawColor; MaterialSettings material = flame.getSolidRenderSettings().getInterpolatedMaterial(rp.material); if (material == null) { RGBColorD bgColor = new RGBColorD(dest.bgRed, dest.bgGreen, dest.bgBlue, 1.0 / VecMathLib.COLORSCL); double visibility = 0.0; for (int i = 0; i < flame.getSolidRenderSettings().getLights().size(); i++) { DistantLight light = flame.getSolidRenderSettings().getLights().get(i); visibility += light.isCastShadows() && rp.hasShadows ? rp.visibility[i] : avgVisibility; } visibility = GfxMathLib.clamp(visibility); rawColor = new RGBColorD(bgColor, visibility); } else { double aoInt = Tools.limitValue(flame.getSolidRenderSettings().getAoIntensity(), 0.0, 4.0); boolean withSSAO = flame.getSolidRenderSettings().isAoEnabled(); double ambientIntensity = Math.max(0.0, withSSAO ? (material.getAmbient() - rp.ao * aoInt) : material.getAmbient()); double aoDiffuseInfluence = flame.getSolidRenderSettings().getAoAffectDiffuse(); double diffuseIntensity = Math.max(0.0, withSSAO ? (material.getDiffuse() - rp.ao * aoInt * aoDiffuseInfluence) : material.getDiffuse()); double specularIntensity = material.getPhong(); SimpleImage reflectionMap = null; if (material.getReflMapIntensity() > MathLib.EPSILON && material.getReflMapFilename() != null && material.getReflMapFilename().length() > 0) { try { reflectionMap = (SimpleImage) RessourceManager.getImage(material.getReflMapFilename()); } catch (Exception e) { material.setReflMapFilename(null); e.printStackTrace(); } } RGBColorD objColor = new RGBColorD(rp.solidRed, rp.solidGreen, rp.solidBlue, 1.0 / VecMathLib.COLORSCL); rawColor = new RGBColorD(objColor, ambientIntensity * avgVisibility); VectorD normal = new VectorD(rp.nx, rp.ny, rp.nz); VectorD viewDir = new VectorD(0.0, 0.0, 1.0); for (int i = 0; i < flame.getSolidRenderSettings().getLights().size(); i++) { DistantLight light = flame.getSolidRenderSettings().getLights().get(i); VectorD lightDir = lightViewCalculator.getLightDir()[i]; double visibility = light.isCastShadows() && rp.hasShadows ? rp.visibility[i] : avgVisibility; double cosa = VectorD.dot(lightDir, normal); if (cosa > MathLib.EPSILON) { double diffResponse = material.getLightDiffFunc().evaluate(cosa); rawColor.addFrom( light.getRed() + objColor.r * ambientIntensity / 3.0, light.getGreen() + objColor.g * ambientIntensity / 3.0, light.getBlue() + objColor.b * ambientIntensity / 3.0, visibility * diffResponse * diffuseIntensity * light.getIntensity()); } if (specularIntensity > MathLib.EPSILON) { VectorD r = VectorD.reflect(lightDir, normal); double vr = VectorD.dot(viewDir, r); if (vr < MathLib.EPSILON) { double specularResponse = MathLib.pow(material.getLightDiffFunc().evaluate(-vr), material.getPhongSize()); rawColor.addFrom(material.getPhongRed(), material.getPhongGreen(), material.getPhongBlue(), visibility * specularResponse * specularIntensity * light.getIntensity()); } } // http://www.reindelsoftware.com/Documents/Mapping/Mapping.html if (reflectionMap != null) { double reflectionMapIntensity = Math.max(0.0, withSSAO ? (material.getReflMapIntensity() - rp.ao * aoInt * aoDiffuseInfluence) : material.getReflMapIntensity()); VectorD r = VectorD.reflect(viewDir, normal); UVPairD uv; switch (material.getReflectionMapping()) { case SPHERICAL: uv = UVPairD.sphericalOpenGlMapping(r); break; case BLINN_NEWELL: default: uv = UVPairD.sphericalBlinnNewellLatitudeMapping(r); break; } RGBColorD reflMapColor = uv.getColorFromMap(reflectionMap); rawColor.addFrom(reflMapColor.r, reflMapColor.g, reflMapColor.b, visibility * reflectionMapIntensity); } } } dest.solidRed += rawColor.r * colorScale * VecMathLib.COLORSCL; dest.solidGreen += rawColor.g * colorScale * VecMathLib.COLORSCL; dest.solidBlue += rawColor.b * colorScale * VecMathLib.COLORSCL; dest.hasSolidColors = true; return true; } return false; } private void calculateBGColor(LogDensityPoint dest, int pX, int pY) { if (bgImage != null) { Pixel toolPixel = dest.toolPixel; if (destImageWidth == bgImage.getImageWidth() && destImageHeight == bgImage.getImageHeight()) { toolPixel.setARGBValue(bgImage.getARGBValue(pX, pY)); dest.bgRed = toolPixel.r; dest.bgGreen = toolPixel.g; dest.bgBlue = toolPixel.b; } else { double xCoord = (double) pX * (double) (bgImage.getImageWidth() - 1) / (double) (destImageWidth - 1); double yCoord = (double) pY * (double) (bgImage.getImageHeight() - 1) / (double) (destImageHeight - 1); toolPixel.setARGBValue(bgImage.getARGBValueIgnoreBounds((int) xCoord, (int) yCoord)); int luR = toolPixel.r; int luG = toolPixel.g; int luB = toolPixel.b; toolPixel.setARGBValue(bgImage.getARGBValueIgnoreBounds(((int) xCoord) + 1, (int) yCoord)); int ruR = toolPixel.r; int ruG = toolPixel.g; int ruB = toolPixel.b; toolPixel.setARGBValue(bgImage.getARGBValueIgnoreBounds((int) xCoord, ((int) yCoord) + 1)); int lbR = toolPixel.r; int lbG = toolPixel.g; int lbB = toolPixel.b; toolPixel.setARGBValue(bgImage.getARGBValueIgnoreBounds(((int) xCoord) + 1, ((int) yCoord) + 1)); int rbR = toolPixel.r; int rbG = toolPixel.g; int rbB = toolPixel.b; double x = MathLib.frac(xCoord); double y = MathLib.frac(yCoord); dest.bgRed = Tools.roundColor(GfxMathLib.blerp(luR, ruR, lbR, rbR, x, y)); dest.bgGreen = Tools.roundColor(GfxMathLib.blerp(luG, ruG, lbG, rbG, x, y)); dest.bgBlue = Tools.roundColor(GfxMathLib.blerp(luB, ruB, lbB, rbB, x, y)); } } else { switch (flame.getBgColorType()) { case GRADIENT_2X2: double x = (double) pX / (double) (destImageWidth - 1); double y = (double) pY / (double) (destImageHeight - 1); dest.bgRed = Tools.roundColor(GfxMathLib.blerp(flame.getBgColorULRed(), flame.getBgColorURRed(), flame.getBgColorLLRed(), flame.getBgColorLRRed(), x, y)); dest.bgGreen = Tools.roundColor(GfxMathLib.blerp(flame.getBgColorULGreen(), flame.getBgColorURGreen(), flame.getBgColorLLGreen(), flame.getBgColorLRGreen(), x, y)); dest.bgBlue = Tools.roundColor(GfxMathLib.blerp(flame.getBgColorULBlue(), flame.getBgColorURBlue(), flame.getBgColorLLBlue(), flame.getBgColorLRBlue(), x, y)); break; default: dest.bgRed = flame.getBgColorRed(); dest.bgGreen = flame.getBgColorGreen(); dest.bgBlue = flame.getBgColorBlue(); break; } } } }