/*
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 static org.jwildfire.base.mathlib.MathLib.EPSILON;
import static org.jwildfire.base.mathlib.MathLib.M_2PI;
import static org.jwildfire.base.mathlib.MathLib.M_PI;
import static org.jwildfire.base.mathlib.MathLib.cos;
import static org.jwildfire.base.mathlib.MathLib.exp;
import static org.jwildfire.base.mathlib.MathLib.fabs;
import static org.jwildfire.base.mathlib.MathLib.log;
import static org.jwildfire.base.mathlib.MathLib.sin;
import static org.jwildfire.base.mathlib.MathLib.sqrt;
import java.io.Serializable;
import java.util.List;
import org.jwildfire.base.Tools;
import org.jwildfire.base.mathlib.GfxMathLib;
import org.jwildfire.base.mathlib.MathLib;
import org.jwildfire.create.GradientCreator;
import org.jwildfire.create.tina.base.Constants;
import org.jwildfire.create.tina.base.DrawMode;
import org.jwildfire.create.tina.base.Flame;
import org.jwildfire.create.tina.base.Layer;
import org.jwildfire.create.tina.base.XForm;
import org.jwildfire.create.tina.base.XYZPoint;
import org.jwildfire.create.tina.base.XYZProjectedPoint;
import org.jwildfire.create.tina.base.solidrender.ShadowType;
import org.jwildfire.create.tina.palette.RGBPalette;
import org.jwildfire.create.tina.palette.RenderColor;
import org.jwildfire.create.tina.random.AbstractRandomGenerator;
import org.jwildfire.create.tina.render.GammaCorrectionFilter.HSLRGBConverter;
import org.jwildfire.create.tina.variation.FlameTransformationContext;
import org.jwildfire.create.tina.variation.RessourceManager;
import org.jwildfire.image.Pixel;
import org.jwildfire.image.SimpleImage;
public class DefaultRenderIterationState extends RenderIterationState {
private static final long serialVersionUID = 2L;
protected XYZPoint affineT;
protected XYZPoint varT;
protected XYZPoint p;
protected XYZPoint q;
protected XForm xf;
protected final XYZProjectedPoint prj;
protected PointProjector projector;
protected final ColorProvider colorProvider;
protected final boolean solidRendering;
public DefaultRenderIterationState(AbstractRenderThread pRenderThread, FlameRenderer pRenderer, RenderPacket pPacket, Layer pLayer, FlameTransformationContext pCtx, AbstractRandomGenerator pRandGen, boolean pUsePlotBuffer) {
super(pRenderThread, pRenderer, pPacket, pLayer, pCtx, pRandGen);
plotBuffer = initPlotBuffer(pUsePlotBuffer ? (RenderMode.PRODUCTION.equals(pRenderer.getRenderInfo().getRenderMode()) ? Tools.PLOT_BUFFER_SIZE : Tools.PLOT_BUFFER_SIZE / 2) : 1);
solidRendering = flame.getSolidRenderSettings().isSolidRenderingEnabled();
projector = new DefaultPointProjector();
if (pLayer.getGradientMapFilename() != null && pLayer.getGradientMapFilename().length() > 0) {
colorProvider = new GradientMapColorProvider(pLayer.getGradientMapFilename());
}
else {
colorProvider = pLayer.isSmoothGradient() ? new SmoothColorProvider() : new DefaultColorProvider();
}
boolean withLightmaps = ShadowType.areShadowsEnabled(flame.getSolidRenderSettings().getShadowType());
prj = new XYZProjectedPoint(withLightmaps ? flame.getSolidRenderSettings().getLights().size() : 0);
Flame flame = pPacket.getFlame();
switch (flame.getPostSymmetryType()) {
case POINT: {
if (flame.getPostSymmetryOrder() > 1) {
int order = flame.getPostSymmetryOrder() <= 64 ? flame.getPostSymmetryOrder() : 64;
projector = new PointSymmetryProjector(projector, order, flame.getPostSymmetryCentreX(), flame.getPostSymmetryCentreY());
}
break;
}
case X_AXIS:
projector = new XAxisSymmetryProjector(projector, flame.getPostSymmetryDistance(), flame.getPostSymmetryCentreX(), flame.getPostSymmetryCentreY(), flame.getPostSymmetryRotation());
break;
case Y_AXIS:
projector = new YAxisSymmetryProjector(projector, flame.getPostSymmetryDistance(), flame.getPostSymmetryCentreX(), flame.getPostSymmetryCentreY(), flame.getPostSymmetryRotation());
break;
default: // nothing to do
break;
}
}
public void init() {
if (raster != null)
raster.notifyInit(view.getLightViewCalculator());
}
public void preFuseIter() {
affineT = new XYZPoint(); // affine part of the transformation
varT = new XYZPoint(); // complete transformation
p = new XYZPoint();
q = new XYZPoint();
p.x = 2.0 * randGen.random() - 1.0;
p.y = 2.0 * randGen.random() - 1.0;
p.z = 0.0;
p.color = randGen.random();
p.material = randGen.random();
p.modGamma = 0.0;
p.modContrast = 0.0;
p.modSaturation = 0.0;
p.modHue = 0.0;
xf = layer.getXForms().get(0);
transformPoint();
for (int i = 0; i <= Constants.INITIAL_ITERATIONS; i++) {
xf = selectNextXForm(xf);
if (xf == null) {
xf = layer.getXForms().get(0);
return;
}
transformPoint();
}
}
protected XForm selectNextXForm(XForm pFrom) {
return pFrom.getNextAppliedXFormTable()[randGen.random(Constants.NEXT_APPLIED_XFORM_TABLE_SIZE)];
}
public void validateState() {
if (Double.isInfinite(p.x) || Double.isInfinite(p.y) || Double.isInfinite(p.z) || Double.isNaN(p.x) || Double.isNaN(p.y) || Double.isNaN(p.z) || xf == null) {
preFuseIter();
}
}
public void iterateNext() {
xf = selectNextXForm(xf);
transformPoint();
if (xf.getDrawMode() == DrawMode.HIDDEN)
return;
else if ((xf.getDrawMode() == DrawMode.OPAQUE) && (randGen.random() > xf.getOpacity()))
return;
List<XForm> finalXForms = layer.getFinalXForms();
if (finalXForms.size() > 0) {
applyFinalTransforms(finalXForms);
}
else {
applyEmptyFinalTransform();
}
projector.projectPoint(q);
}
public void iterateNext(List<RenderSlice> pSlices) {
int nextXForm = randGen.random(Constants.NEXT_APPLIED_XFORM_TABLE_SIZE);
xf = xf.getNextAppliedXFormTable()[nextXForm];
if (xf == null) {
return;
}
transformPoint();
if (xf.getDrawMode() == DrawMode.HIDDEN)
return;
else if ((xf.getDrawMode() == DrawMode.OPAQUE) && (randGen.random() > xf.getOpacity()))
return;
List<XForm> finalXForms = layer.getFinalXForms();
if (finalXForms.size() > 0) {
applyFinalTransforms(finalXForms);
}
else {
applyEmptyFinalTransform();
}
if (setupSliceRaster(q, pSlices)) {
projector.projectPoint(q);
}
}
private boolean setupSliceRaster(XYZPoint pPoint, List<RenderSlice> pSlices) {
int sliceIdx = getSliceIndex(pPoint, pSlices, 0);
if (sliceIdx >= 0) {
raster = pSlices.get(sliceIdx).getRaster();
return true;
}
else {
raster = null;
return false;
}
}
private int getSliceIndex(XYZPoint pPoint, List<RenderSlice> pSlices, int pStartIndex) {
double value = pPoint.z;
for (int i = pStartIndex; i < pSlices.size(); i++) {
RenderSlice slice = pSlices.get(i);
if (value >= slice.getZmin() && value < slice.getZmax()) {
return i;
}
}
return -1;
}
protected void applyEmptyFinalTransform() {
q.assign(p);
}
protected void applyFinalTransforms(List<XForm> finalXForms) {
finalXForms.get(0).transformPoint(ctx, affineT, varT, p, q);
for (int i = 1; i < finalXForms.size(); i++) {
finalXForms.get(i).transformPoint(ctx, affineT, varT, q, q);
}
}
protected void transformPoint() {
xf.transformPoint(ctx, affineT, varT, p, p);
}
protected double plotRed, plotGreen, plotBlue, plotContribution;
protected int plotBufferIdx = 0;
protected PlotSample[] plotBuffer;
protected int shadowMapPlotBufferIdx[] = initShadowMapPlotBufferIdx();
protected PlotSample[][] shadowMapPlotBuffer = initShadowMapPlotBuffer();
interface ColorProvider extends Serializable {
RenderColor getColor(XYZPoint pTPoint, XYZPoint pFPoint);
}
private class DefaultColorProvider implements ColorProvider {
private static final long serialVersionUID = 1L;
@Override
public RenderColor getColor(XYZPoint pTPoint, XYZPoint pFPoint) {
int colorIdx = (int) (pTPoint.color * paletteIdxScl + 0.5);
if (colorIdx < 0)
colorIdx = 0;
else if (colorIdx >= RGBPalette.PALETTE_SIZE)
colorIdx = RGBPalette.PALETTE_SIZE - 1;
return colorMap[colorIdx];
}
}
private class SmoothColorProvider implements ColorProvider {
private static final long serialVersionUID = 1L;
private final RenderColor rc = new RenderColor();
@Override
public RenderColor getColor(XYZPoint pTPoint, XYZPoint pFPoint) {
double colorIdx = pTPoint.color * paletteIdxScl;
int lIdx = (int) colorIdx;
double lR, lG, lB;
if (lIdx >= 0 && lIdx < RGBPalette.PALETTE_SIZE) {
lR = colorMap[lIdx].red;
lG = colorMap[lIdx].green;
lB = colorMap[lIdx].blue;
}
else {
lR = lG = lB = 0.0;
}
double rR, rG, rB;
int rIdx = lIdx + 1;
if (rIdx >= 0 && rIdx < RGBPalette.PALETTE_SIZE) {
rR = colorMap[rIdx].red;
rG = colorMap[rIdx].green;
rB = colorMap[rIdx].blue;
}
else {
rR = rG = rB = 0.0;
}
double t = MathLib.frac(colorIdx);
rc.red = GfxMathLib.lerp(lR, rR, t);
rc.green = GfxMathLib.lerp(lG, rG, t);
rc.blue = GfxMathLib.lerp(lB, rB, t);
return rc;
}
}
private class GradientMapColorProvider implements ColorProvider {
private static final long serialVersionUID = 1L;
private final SimpleImage map;
private SimpleImage createDfltImage() {
GradientCreator creator = new GradientCreator();
return creator.createImage(256, 256);
}
private final Pixel toolPixel = new Pixel();
private final RenderColor rc = new RenderColor();
public GradientMapColorProvider(String pGradientMap) {
SimpleImage image;
try {
image = (SimpleImage) RessourceManager.getImage(pGradientMap);
}
catch (Exception e) {
e.printStackTrace();
image = createDfltImage();
}
map = image;
}
@Override
public RenderColor getColor(XYZPoint pTPoint, XYZPoint pFPoint) {
double localColorAdd = layer.getGradientMapLocalColorAdd();
double localColorMultiply = layer.getGradientMapLocalColorScale();
double x = (pFPoint.x * (1.0 - localColorMultiply) + pFPoint.x * localColorMultiply * pFPoint.color + localColorAdd * pFPoint.color) * layer.getGradientMapHorizScale() + layer.getGradientMapHorizOffset();
double y = (pFPoint.y * (1.0 - localColorMultiply) + pFPoint.y * localColorMultiply * pFPoint.color + localColorAdd * pFPoint.color) * layer.getGradientMapVertScale() + layer.getGradientMapVertOffset();
double width = map.getImageWidth() - 2;
double height = map.getImageHeight() - 2;
double fx = MathLib.fabs(x);
double imageX = MathLib.fmod(fx * width, width);
if (((int) fx) % 2 != 0) {
imageX = width - imageX;
}
double fy = MathLib.fabs(y);
double imageY = MathLib.fmod(fy * height, height);
if (((int) fy) % 2 != 0) {
imageY = height - imageY;
}
toolPixel.setARGBValue(map.getARGBValueIgnoreBounds((int) imageX, (int) imageY));
int luR = toolPixel.r;
int luG = toolPixel.g;
int luB = toolPixel.b;
toolPixel.setARGBValue(map.getARGBValueIgnoreBounds(((int) imageX) + 1, (int) imageY));
int ruR = toolPixel.r;
int ruG = toolPixel.g;
int ruB = toolPixel.b;
toolPixel.setARGBValue(map.getARGBValueIgnoreBounds((int) imageX, ((int) imageY) + 1));
int lbR = toolPixel.r;
int lbG = toolPixel.g;
int lbB = toolPixel.b;
toolPixel.setARGBValue(map.getARGBValueIgnoreBounds(((int) imageX) + 1, ((int) imageY) + 1));
int rbR = toolPixel.r;
int rbG = toolPixel.g;
int rbB = toolPixel.b;
double localX = MathLib.frac(imageX);
double localY = MathLib.frac(imageY);
rc.red = GfxMathLib.blerp(luR, ruR, lbR, rbR, localX, localY);
rc.green = GfxMathLib.blerp(luG, ruG, lbG, rbG, localX, localY);
rc.blue = GfxMathLib.blerp(luB, ruB, lbB, rbB, localX, localY);
return rc;
}
}
protected void plotPoint(int screenX, int screenY, double rawX, double rawY, double intensity, XYZPoint origin) {
if (p.rgbColor) {
plotRed = p.redColor;
plotGreen = p.greenColor;
plotBlue = p.blueColor;
}
else if (q.rgbColor) {
plotRed = q.redColor;
plotGreen = q.greenColor;
plotBlue = q.blueColor;
}
else {
RenderColor color = colorProvider.getColor(p, q);
plotRed = color.red;
plotGreen = color.green;
plotBlue = color.blue;
}
if (!solidRendering)
transformPlotColor(p);
double finalRed = plotRed * intensity;
double finalGreen = plotGreen * intensity;
double finalBlue = plotBlue * intensity;
plotBuffer[plotBufferIdx++].set(screenX, screenY, finalRed, finalGreen, finalBlue, rawX, rawY, prj.z * view.bws, p.material, prj.dofDist, origin.x, origin.y, origin.z, p.receiveOnlyShadows);
if (plotBufferIdx >= plotBuffer.length) {
applySamplesToRaster();
}
if (observers != null && observers.size() > 0) {
for (IterationObserver observer : observers) {
observer.notifyIterationFinished(renderThread, screenX, screenY, prj, q.x, q.y, q.z, finalRed, finalGreen, finalBlue);
}
}
}
private PlotSample[][] initShadowMapPlotBuffer() {
if (flame.isWithShadows()) {
PlotSample[][] res = new PlotSample[flame.getSolidRenderSettings().getLights().size()][];
for (int i = 0; i < flame.getSolidRenderSettings().getLights().size(); i++) {
if (flame.getSolidRenderSettings().getLights().get(i).isCastShadows()) {
res[i] = new PlotSample[Tools.PLOT_BUFFER_SIZE];
for (int j = 0; j < res[i].length; j++) {
res[i][j] = new PlotSample();
}
}
else {
res[i] = null;
}
}
return res;
}
else {
return new PlotSample[0][];
}
}
private int[] initShadowMapPlotBufferIdx() {
if (flame.isWithShadows()) {
return new int[flame.getSolidRenderSettings().getLights().size()];
}
else {
return new int[0];
}
}
protected void applySamplesToRaster() {
raster.addSamples(plotBuffer, plotBufferIdx);
plotBufferIdx = 0;
}
private PlotSample[] initPlotBuffer(int pSize) {
PlotSample[] res = new PlotSample[pSize];
for (int i = 0; i < res.length; i++) {
res[i] = new PlotSample();
}
return res;
}
protected void transformPlotColor(XYZPoint p) {
if (fabs(p.modGamma) > EPSILON) {
double gamma = 4.2 / (4.2 - p.modGamma);
double alpha = plotRed * 0.299 + plotGreen * 0.588 + plotBlue * 0.113;
if (alpha > EPSILON) {
double modAlpha = Math.pow(alpha, gamma);
plotRed *= modAlpha / alpha;
plotGreen *= modAlpha / alpha;
plotBlue *= modAlpha / alpha;
}
}
if (fabs(p.modContrast) > EPSILON) {
double gamma = 1.2 / (1.2 - p.modContrast * 0.5);
plotRed = (plotRed - 127.5) * gamma + 127.5;
plotGreen = (plotGreen - 127.5) * gamma + 127.5;
plotBlue = (plotBlue - 127.5) * gamma + 127.5;
}
if (fabs(p.modSaturation) > EPSILON) {
double avg = plotRed * 0.299 + plotGreen * 0.588 + plotBlue * 0.113;
plotRed += (plotRed - avg) * p.modSaturation;
plotGreen += (plotGreen - avg) * p.modSaturation;
plotBlue += (plotBlue - avg) * p.modSaturation;
}
if (fabs(p.modHue) > EPSILON) {
hslrgbConverter.fromRgb(plotRed / MathLib.C_255, plotGreen / MathLib.C_255, plotBlue / MathLib.C_255);
hslrgbConverter.fromHsl(hslrgbConverter.getHue() + p.modHue, hslrgbConverter.getSaturation(), hslrgbConverter.getLuminosity());
plotRed = Tools.roundColor(hslrgbConverter.getRed() * MathLib.C_255);
plotGreen = Tools.roundColor(hslrgbConverter.getGreen() * MathLib.C_255);
plotBlue = Tools.roundColor(hslrgbConverter.getBlue() * MathLib.C_255);
}
}
HSLRGBConverter hslrgbConverter = new HSLRGBConverter();
public interface PointProjector {
void projectPoint(XYZPoint q);
}
public abstract class AxisSymmetryProjector implements PointProjector {
protected final PointProjector parent;
protected final double distance;
protected final double centreX;
protected final double centreY;
protected final double rotation;
protected final XYZPoint a;
protected final XYZPoint b;
protected final double sina, cosa;
protected final double halve_dist;
protected final boolean doRotate;
public AxisSymmetryProjector(PointProjector pParent, double pDistance, double pCentreX, double pCentreY, double pRotation) {
parent = pParent;
distance = pDistance;
centreX = pCentreX;
centreY = pCentreY;
rotation = pRotation;
a = new XYZPoint();
b = new XYZPoint();
double a = rotation * M_2PI / 180.0 / 2.0;
doRotate = fabs(a) > EPSILON;
sina = sin(a);
cosa = cos(a);
halve_dist = distance / 2.0;
}
}
public class XAxisSymmetryProjector extends AxisSymmetryProjector implements PointProjector {
public XAxisSymmetryProjector(PointProjector pParent, double pDistance, double pCentreX, double pCentreY, double pRotation) {
super(pParent, pDistance, pCentreX, pCentreY, pRotation);
}
@Override
public void projectPoint(XYZPoint q) {
if (q.doHide)
return;
a.assign(q);
b.assign(q);
double dx, dy;
dx = q.x - centreX;
a.x = centreX + dx + halve_dist;
b.x = centreX - dx - halve_dist;
if (doRotate) {
dx = a.x - centreX;
dy = a.y - centreY;
a.x = centreX + dx * cosa + dy * sina;
a.y = centreY + dy * cosa - dx * sina;
dx = b.x - centreX;
dy = b.y - centreY;
b.x = centreX + dx * cosa - dy * sina;
b.y = centreY + dy * cosa + dx * sina;
}
parent.projectPoint(a);
parent.projectPoint(b);
}
}
public class YAxisSymmetryProjector extends AxisSymmetryProjector implements PointProjector {
public YAxisSymmetryProjector(PointProjector pParent, double pDistance, double pCentreX, double pCentreY, double pRotation) {
super(pParent, pDistance, pCentreX, pCentreY, pRotation);
}
@Override
public void projectPoint(XYZPoint q) {
if (q.doHide)
return;
a.assign(q);
b.assign(q);
double dx, dy;
dy = q.y - centreY;
a.y = centreY + dy + halve_dist;
b.y = centreY - dy - halve_dist;
if (doRotate) {
dx = a.x - centreX;
dy = a.y - centreY;
a.x = centreX + dx * cosa + dy * sina;
a.y = centreY + dy * cosa - dx * sina;
dx = b.x - centreX;
dy = b.y - centreY;
b.x = centreX + dx * cosa - dy * sina;
b.y = centreY + dy * cosa + dx * sina;
}
parent.projectPoint(a);
parent.projectPoint(b);
}
}
public static class PointSymmetryProjector implements PointProjector {
private final PointProjector parent;
private final double centreX, centreY;
private final int order;
private final double sina[], cosa[];
private final XYZPoint ps;
public PointSymmetryProjector(PointProjector pParent, int pOrder, double pCentreX, double pCentreY) {
parent = pParent;
order = pOrder >= 1 ? pOrder : 1;
centreX = pCentreX;
centreY = pCentreY;
ps = new XYZPoint();
sina = new double[order];
cosa = new double[order];
double da = M_2PI / (double) order;
double angle = 0.0;
for (int i = 0; i < order; i++) {
sina[i] = sin(angle);
cosa[i] = cos(angle);
angle += da;
}
}
@Override
public void projectPoint(XYZPoint q) {
if (q.doHide)
return;
double dx = q.x - centreX;
double dy = q.y - centreY;
parent.projectPoint(q);
for (int i = 0; i < order; i++) {
ps.assign(q);
ps.x = centreX + dx * cosa[i] + dy * sina[i];
ps.y = centreY + dy * cosa[i] - dx * sina[i];
parent.projectPoint(ps);
}
}
}
public class DefaultPointProjector implements PointProjector {
XYZPoint untransformed = new XYZPoint();
@Override
public void projectPoint(XYZPoint q) {
if (q.doHide)
return;
// TODO overkill, need only xyz
untransformed.assign(q);
boolean insideView = view.project(q, prj);
if (prj.hasLight != null && !p.receiveOnlyShadows) {
for (int i = 0; i < prj.hasLight.length; i++) {
if (prj.hasLight[i]) {
plotShadowMapPoint(i, prj.lightX[i], prj.lightY[i], prj.lightZ[i]);
}
}
}
if (!insideView || q.isNaN())
return;
double rawX, rawY;
int screenX, screenY;
if ((flame.getAntialiasAmount() > EPSILON) && (flame.getAntialiasRadius() > EPSILON) && (randGen.random() > 1.0 - flame.getAntialiasAmount())) {
double dr = exp(flame.getAntialiasRadius() * sqrt(-log(randGen.random()))) - 1.0;
double da = randGen.random() * 2.0 * M_PI;
rawX = view.bws * prj.x + dr * cos(da);
screenX = (int) (rawX + 0.5);
if (screenX < 0 || screenX >= renderer.rasterWidth)
return;
rawY = view.bhs * prj.y + dr * sin(da);
screenY = (int) (rawY + 0.5);
if (screenY < 0 || screenY >= renderer.rasterHeight)
return;
}
else {
rawX = view.bws * prj.x;
screenX = (int) (rawX + 0.5);
if (screenX < 0 || screenX >= renderer.rasterWidth)
return;
rawY = view.bhs * prj.y;
screenY = (int) (rawY + 0.5);
if (screenY < 0 || screenY >= renderer.rasterHeight)
return;
}
double intensity = prj.intensity * layer.getWeight();
plotPoint(screenX, screenY, rawX, rawY, intensity, untransformed);
}
private void plotShadowMapPoint(int i, double x, double y, double z) {
shadowMapPlotBuffer[i][shadowMapPlotBufferIdx[i]++].set(x, y, z);
if (shadowMapPlotBufferIdx[i] >= shadowMapPlotBuffer[i].length) {
applyShadowMapSamplesToRaster(i);
}
}
private void applyShadowMapSamplesToRaster(int idx) {
raster.addShadowMapSamples(idx, shadowMapPlotBuffer[idx], shadowMapPlotBufferIdx[idx]);
shadowMapPlotBufferIdx[idx] = 0;
}
}
public void cleanup() {
if (plotBufferIdx > 0) {
applySamplesToRaster();
}
}
}