Package com.ardor3d.spline

Source Code of com.ardor3d.spline.ArcLengthTable$ArcLengthEntry

/**
* Copyright (c) 2008-2012 Ardor Labs, Inc.
*
* This file is part of Ardor3D.
*
* Ardor3D is free software: you can redistribute it and/or modify it
* under the terms of its license which may be found in the accompanying
* LICENSE file or at <http://www.ardor3d.com/LICENSE>.
*/

/**
*
*/

package com.ardor3d.spline;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.ardor3d.math.Vector3;
import com.ardor3d.scenegraph.controller.ComplexSpatialController;
import com.ardor3d.scenegraph.controller.interpolation.InterpolationController;

/**
* ArcLengthTable class contains methods for generating and storing arc lengths of a curve. Arc Lengths are used to get
* constant speed interpolation over a curve.
* <p>
* This class does not automatically generate the look up tables, you must manually call {@link #generate(int, boolean)}
* to generate the table.
* </p>
*/
public class ArcLengthTable {

    /** Classes logger */
    private static final Logger LOGGER = Logger.getLogger(ArcLengthTable.class.getName());

    /** Table containing arc lengths for look up */
    private Map<Integer, List<ArcLengthEntry>> _lookupTable;

    /** The curve who's values to cache */
    private final Curve _curve;

    /**
     * Creates a new instance of <code>ArcLengthTable</code>.
     *
     * @param curve
     *            The curve to create the table for, can not be <code>null</code>.
     */
    public ArcLengthTable(final Curve curve) {
        super();

        if (null == curve) {
            throw new IllegalArgumentException("curve was null!");
        }

        _curve = curve;
    }

    /**
     * @param index
     *            The index of the control point you want the length for.
     * @return The total approximate length of the segment starting at the given index.
     */
    public double getLength(final int index) {
        if (null == _lookupTable) {
            throw new IllegalStateException(
                    "You must generate the look up table before calling this method! see generate()");
        }

        final List<ArcLengthEntry> entries = _lookupTable.get(index);

        if (null == entries) {
            throw new IllegalArgumentException("entries was null, the index parameter was invalid. index=" + index);
        }

        final ArcLengthEntry arcLength = entries.get(entries.size() - 1);

        return arcLength.getLength();
    }

    /**
     * @param index
     *            The index of the first control point you are interpolating from.
     * @param distance
     *            The distance you want the spatial to travel.
     * @return The delta you should use to travel the specified distance from the control point given, will not be
     *         negative but may be greater than 1.0 if the distance is greater than the length of the segment.
     */
    public double getDelta(final int index, final double distance) {
        if (null == _lookupTable) {
            throw new IllegalStateException(
                    "You must generate the look up table before calling this method! see generate()");
        }

        ArcLengthEntry previous = null;
        ArcLengthEntry next = null;

        final List<ArcLengthEntry> entries = _lookupTable.get(index);

        if (null == entries) {
            throw new IllegalArgumentException("entries was null, the index parameter was invalid. index=" + index);
        }

        for (final ArcLengthEntry entry : entries) {
            if (entry.getLength() <= distance) {
                previous = entry;
            }
            if (entry.getLength() >= distance) {
                next = entry;
                break;
            }
        }

        if (null == previous) {
            throw new IllegalArgumentException(
                    "previous was null, either the index or distance parameters were invalid. index=" + index
                            + ", distance=" + distance);
        }

        final double delta;

        /*
         * If next is null then the length we need to travel is longer than the length of the segment, in this case we
         * work out the delta required to travel from the start of the segment minus what we've already travelled. We
         * then add that delta to the delta required to traverse the next segment and return that value, it's up to the
         * controller to handle this value correctly (update indices etc)
         *
         * We need to be careful about wrapping around the end of the curve.
         */
        if (null == next) {
            final int newIndex = (index + 1 >= _lookupTable.size()) ? 1 : index + 1;

            delta = getDelta(newIndex, distance - previous.getLength()) + previous.getDelta();

        } else {
            if (previous.equals(next)) {
                delta = previous.getDelta();

            } else {
                final double d0 = previous.getDelta();
                final double d1 = next.getDelta();
                final double l0 = previous.getLength();
                final double l1 = next.getLength();

                delta = (d0 + ((distance - l0) / (l1 - l0)) * (d1 - d0));
            }
        }

        return delta;
    }

    /**
     * Actually generates the arc length table, this needs to be called before this class can actually perform any
     * useful functions.
     *
     * @param step
     *            The larger the step value used the more accurate the resulting table will be and thus the smoother the
     *            motion will be, must be greater than zero.
     * @param reverse
     *            <code>true</code> to generate the table while stepping from the end of the curve to the beginning,
     *            <code>false</code> to generate the table from the beginning of the curve. You only need to generate a
     *            reverse table if you are using the {@link ComplexSpatialController.RepeatType#CYCLE cycle} repeat
     *            type.
     */
    public void generate(final int step, final boolean reverse) {
        if (step <= 0) {
            throw new IllegalArgumentException("step must be > 0! step=" + step);
        }

        _lookupTable = new HashMap<Integer, List<ArcLengthEntry>>();

        final Vector3 target = Vector3.fetchTempInstance();
        final Vector3 previous = Vector3.fetchTempInstance();

        final int loopStart = reverse ? (_curve.getControlPointCount() - 2) : 1;
        final double tStep = InterpolationController.DELTA_MAX / step;

        for (int i = loopStart; continueLoop(i, reverse); i = updateCounter(i, reverse)) {
            final int startIndex = i;
            double t = 0f;
            double length = 0;

            previous.set(_curve.getControlPoints().get(i));

            final ArrayList<ArcLengthEntry> entries = new ArrayList<ArcLengthEntry>();
            entries.add(new ArcLengthEntry(0f, 0));

            final int endIndex = reverse ? startIndex - 1 : startIndex + 1;

            while (true) {
                t += tStep;

                /*
                 * If we are over delta max force to 1. We need to do this to avoid precision issues causing errors
                 * later on (e.g. if last entry for an index had a delta of 0.996 and later during an update we passed
                 * 0.998 for that index we'd fail to find a valid entry and error out)
                 */
                if (t > InterpolationController.DELTA_MAX) {
                    t = InterpolationController.DELTA_MAX;
                }

                _curve.interpolate(startIndex, endIndex, t, target);

                length += previous.distance(target);

                previous.set(target);

                entries.add(new ArcLengthEntry(t, length));

                if (t == InterpolationController.DELTA_MAX) {
                    break;
                }
            }

            _lookupTable.put(i, entries);
        }

        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.fine("look up table = " + _lookupTable);
        }

        Vector3.releaseTempInstance(target);
        Vector3.releaseTempInstance(previous);
    }

    private boolean continueLoop(final int i, final boolean reverse) {
        return (reverse ? i > 0 : i < _curve.getControlPointCount() - 2);
    }

    private int updateCounter(final int i, final boolean reverse) {
        return (reverse ? i - 1 : i + 1);
    }

    /**
     * A private inner class used to store the required arc length variables for the table
     */
    private static class ArcLengthEntry implements Serializable {
        /** Serial UID */
        private static final long serialVersionUID = 1L;

        private final double _delta;
        private final double _length;

        public ArcLengthEntry(final double delta, final double length) {
            super();

            _delta = delta;
            _length = length;
        }

        public double getDelta() {
            return _delta;
        }

        public double getLength() {
            return _length;
        }

        @Override
        public String toString() {
            return "ArcLengthEntry[length=" + _length + ", delta=" + _delta + ']';
        }
    }

}
TOP

Related Classes of com.ardor3d.spline.ArcLengthTable$ArcLengthEntry

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.