/*
* Copyright (c) 2009, 2010, 2011 Daniel Rendall
* This file is part of FractDim.
*
* FractDim is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FractDim 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with FractDim. If not, see <http://www.gnu.org/licenses/>
*/
package uk.co.danielrendall.fractdim.calculation;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import uk.co.danielrendall.fractdim.app.model.FractalDocument;
import uk.co.danielrendall.mathlib.geom2d.Line;
import uk.co.danielrendall.mathlib.geom2d.ParametricCurve;
import uk.co.danielrendall.mathlib.geom2d.Point;
import uk.co.danielrendall.fractdim.logging.Log;
import uk.co.danielrendall.fractdim.svgbridge.FDTranscoder;
import java.util.HashSet;
import java.util.Set;
/**
* @author Daniel Rendall
* @created 04-Jun-2009 20:50:55
*/
public class StatisticsCalculator extends AbstractNotifyingGraphics {
public static final double TWO_DEGREES = Math.PI / 90.0d;
private final Set<Set<Line>> curveLines = new HashSet<Set<Line>>();
private final double minCosine;
StatisticsCalculator(FractalDocument fractalDocument, double minAngle) {
super(fractalDocument);
minCosine = Math.cos(minAngle);
}
public Statistics process() {
Log.calc.info(String.format("Calculating stats for a shape wih %d curves with a minCosine of %s", numberOfCurves, minCosine));
TranscoderInput input = new TranscoderInput(fractalDocument.getSvgDoc());
FDTranscoder transcoder = new FDTranscoder(this);
try {
transcoder.transcode(input, new TranscoderOutput());
} catch (TranscoderException e) {
Log.app.warn("Couldn't transcode at - " + e.getMessage());
}
if (curveLines.size() != numberOfCurves) {
Log.calc.warn(String.format("Discrepancy - was expecting %d curves, but got %d", numberOfCurves, curveLines.size()));
}
return Statistics.create(curveLines);
}
public void doHandleCurve(ParametricCurve curve) {
Set<Line> accum = new HashSet<Line>();
// note - a sequence of methods leading to the real method to minimise duplicating objects
handleCurve(curve, 0.0, 1.0, accum);
curveLines.add(accum);
}
private void handleCurve(ParametricCurve curve, double rangeStart, double rangeEnd, Set<Line> accum) {
handleCurve(curve, rangeStart, curve.evaluate(rangeStart), rangeEnd, curve.evaluate(rangeEnd), accum);
}
private void handleCurve(ParametricCurve curve, double rangeStart, Point start, double rangeEnd, Point end, Set<Line> accum) {
handleCurve(curve, rangeStart, start, rangeEnd, end, new Line(start, end), accum);
}
/**
* We're passed the two end points of a curve, the line between them and the set for line accumulation.
* Calculate the value for the mid point of the curve and look to see how close to collinear the three
* points are (i.e. is dot products of the normalized start to mid and mid to end vectors very close to
* 1) If so, add the line to the accumulator, if not subdivide and continue.
* @param curve
* @param rangeStart
* @param start
* @param rangeEnd
* @param end
* @param startToEnd
* @param accum
*/
private void handleCurve(ParametricCurve curve, double rangeStart, Point start, double rangeEnd, Point end,
Line startToEnd, Set<Line> accum) {
double rangeMid = (rangeEnd + rangeStart) / 2.0d;
Point mid = curve.evaluate(rangeMid);
Line startToMid = new Line(start, mid);
Line midToEnd = new Line(mid, end);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
double dotProduct = startToMid.getVec().normalize().dotProduct(midToEnd.getVec().normalize());
if (dotProduct >= minCosine) {
if (dotProduct > 1.0) {
// this shouldn't happen, of course, but we'll allow for rounding errors
// Log.calc.warn(String.format("Dot product for %s and %s was %s", startToMid.getVec(), midToEnd.getVec(), dotProduct));
}
accum.add(startToEnd);
return;
}
handleCurve(curve, rangeStart, start, rangeMid, mid, startToMid, accum);
handleCurve(curve, rangeMid, mid, rangeEnd, end, midToEnd, accum);
}
}