FileDocCategorySizeDatePackage
AbstractAnimate.javaAPI DocphoneME MR2 API (J2ME)46342Wed May 02 18:00:36 BST 2007com.sun.perseus.model

AbstractAnimate

public abstract class AbstractAnimate extends Animation
AbstractAnimate is used as a base class for various animation classes (see Animate and AnimateMotion).
version
$Id: AbstractAnimate.java,v 1.5 2006/06/29 10:47:28 ln156897 Exp $

Fields Summary
public static final int
CALC_MODE_LINEAR
calcMode value for linear interpolation.
public static final int
CALC_MODE_DISCRETE
calcMode value for discrete interpolation
public static final int
CALC_MODE_PACED
calcMode value for paced interpolation.
public static final int
CALC_MODE_SPLINE
calcMode for spline interpolation.
public static final float
MIN_FLATNESS_SQUARE
Minimum required flatness for keySpline approximations by polylines.
String
to
End value specification
String
from
Starting value specification.
String
by
Intermediate value specification.
String
values
Complete values specification list.
int
calcMode
The interpolation mode, one of the CALC_MODE_XYZ values.
int
actualCalcMode
The actual calcMode. For types which do not support interpolation, the calcMode is forced to be discrete.
float[]
keyTimes
Key times, to control the animation pace. May be null or a list of values between 0 and 1.
float[]
keySplines
Key splines, used to control the speed of animation on a particular time interval (interval pacing). May be null or an array of arrays of 4 floating point values.
float[]
refSplines
refSplines is an array of points which define a linear approximation of the keySplines. refSplines is computed in the validate() method and used in the curve() method. There is one float array per keySpline.
boolean
additive
Controls whether the animation is additive or not. True maps to the SVG 'sum' value and false maps to the SVG 'replace' value.
boolean
accumulate
Controls whether the animation has a cumulative behavior or not. This covers the behavior over multiple iterations of the simple duration.
RefValues
refValues
The RefValues corresponding to this <set> element. A <set> element has a single segment with the same begin and end value.
RefValues
toRefValues
Used, temporarily, to hold the refValues for the to attribute.
RefValues
fromRefValues
Used, temporarily, to hold the refValues for the from attribute.
RefValues
byRefValues
Used, temporarily, to hold the refValues for the by attribute.
RefValues
valuesRefValues
Used, temporarily, to hold the refValues for the values attribute.
float[]
refTimes
refTimes holds key times for the animation. refTimes is computed in the validate method. refTimes holds the key times for the begining of each segment. Therefore, there is as many refTimes as there are segments, in all cases.
float[]
sisp
A working buffer for mapping segment index and progress
boolean
isToAnimation
Simple flag to check if we are dealing with a to-animation or not. This flag is needed to control the addition and cumulative behavior on to-animations, which is different than that on other animations (e.g., a values or a from-to animation).
Constructors Summary
public AbstractAnimate(DocumentNode ownerDocument, String localName)
Builds a new Animate element that belongs to the given document. This Animate will belong to the DocumentNode's time container.

param
ownerDocument the document this node belongs to.
param
localName the animation element's local name.
throws
IllegalArgumentException if the input ownerDocument is null


                                                     
       
                              
        super(ownerDocument, localName);
    
Methods Summary
floatcalcMode(float p, int si)
Intepolates the input value depending on the calcMode attribute and, in the case of spline interpolation, depending on the keySplines value.

param
p the value to interpolate. Should be in the [0, 1] range.
param
si the time segment from which the computation is done. This is needed to identify the correct keySplines to use in case of spline animations.

        switch (actualCalcMode) {
        case CALC_MODE_DISCRETE:
            return 0;
        case CALC_MODE_LINEAR:
            // Linear does not modify the current penetration
            // because we are on an x = y line.
            case CALC_MODE_PACED:
            // Paced does not modify the current penetration
            // because we have computed the time intervals 
            // so as to have constant velocity. There is no
            // further interpolation in this method.
            return p;
        case CALC_MODE_SPLINE:
        default:
            return curve(p, refSplines[si]);
        }
    
protected voidcomputeRefTimes()
Computes refTimes from the calcMode and keyTimes attributes. Validates that the keyTimes attribute is compatible with the animate set up. This may be overridden by subclasses (e.g., animateMotion), when there are special rules for checking keyTimes compatiblity.

        // c.1 Validate that keyTimes is compatible with the animation set-up
        //
        // if (calcMode == paced)
        //    refTimes such that change velocity is constant
        //    refTimes = getPacedTiming(refValues) // see follow on slides
        //
        // else if (keyTimes defined) 
        //    refTimes = keyTimes
        //    if (calcMode == discreet)
        //        refTimes[n] = 1 // Accounts for the added nth value 
        //                        // in refValues
        //
        // else refTimes = f(refValues)
        //     the simple duration is divided into n-1 intervals, 
        //     where n = refValues.length
        //     refTimes = defaultTiming(refValues)
        if (actualCalcMode == CALC_MODE_PACED) {
            // keyTimes are ignored
            // See: http://www.w3.org/TR/smil20/smil-timing.html#animation-adef-keyTimes
            refTimes = getPacedTiming(refValues);
        } else if (keyTimes != null) {
            // Check keyTimes is compatible with the animation specification.
            // 
            // a) In all cases, the first keyTime must be zero.
            // 
            // b) For non-discrete animations,
            //    b.1) the last keyTime must be one.
            //    b.2) there should be one more keyTimes than there are 
            //         segments.
            // 
            // c) For discrete animations,
            //    c.1) there should be as many keyTimes as there are segments.
            //
            if (/* a) */ keyTimes.length < 1 || keyTimes[0] != 0
                ||
                /* b) */ (actualCalcMode != CALC_MODE_DISCRETE 
                          && 
                          (/* b.1) */ keyTimes[keyTimes.length - 1] != 1
                          ||
                           /* b.2) */ keyTimes.length != 
                                            refValues.getSegments() + 1))
                ||
                /* c) */ (actualCalcMode == CALC_MODE_DISCRETE
                          &&
                          /* c.1) */ keyTimes.length != 
                                            refValues.getSegments())) {
                throw animationError(idRef, 
                        traitNamespace,
                        traitName,
                        targetElement.getNamespaceURI(),
                        targetElement.getLocalName(),
                        getId(),
                        getNamespaceURI(),
                        getLocalName(),
                        Messages.formatMessage(
                            Messages.ERROR_INVALID_ANIMATION_KEY_TIMES, 
                            new Object[] {
                                getTrait(SVGConstants.SVG_KEY_TIMES_ATTRIBUTE)
                            }));
            }

            // If the calcMode is _not_ discrete, we trim the last '1' 
            // value.
            if (actualCalcMode != CALC_MODE_DISCRETE) {
                refTimes = new float[keyTimes.length - 1];
                System.arraycopy(keyTimes, 0, refTimes, 0, refTimes.length);
            } else {
                refTimes = keyTimes;
            }


            // Validate that there are as many refTimes as there are refValues.
            // We do the check using the first component, as we know there is at
            // least one component no matter what value type we are dealing 
            // with.
            if (refTimes.length != refValues.getSegments()) {
                throw animationError(idRef, 
                        traitNamespace,
                        traitName,
                        targetElement.getNamespaceURI(),
                        targetElement.getLocalName(),
                        getId(),
                        getNamespaceURI(),
                        getLocalName(),
                        Messages.formatMessage(
                            Messages.ERROR_INVALID_ANIMATION_KEY_TIMES, 
                            new Object[] {
                                getTrait(SVGConstants.SVG_KEY_TIMES_ATTRIBUTE)
                            }));
            }
        } else {
            // Simple case, give the same time length to each segment.
            refTimes = getDefaultTiming(refValues);
        }

    
static floatcurve(float p, float[][] polyline)
Computes the curve value for the requested value on the specified curves, defined by a polyline approximation. The method assumes that the input polyline points are between 0 and 1 and in increasing order along the x axis. The method considers the p value to be on the polyline's x-axis and finds the two points between which it lies and returns a linear approximation for that segment. For degenerate cases (e.g., if the polyline x-axis values are not in the [0, 1] interval or if p is not in the [0, 1] interval either), the method returns 0 if the input penetration p is less than the first polyline point. The method returns 1 if the input penetration is more than the last polyline x-axis coordinate.

param
p the value for which the curve polynomial should be computed.
param
polyline the polyline curve approximation. Should not be null. each element in the polyline array should not be null. Each element in the array should be at least of length 2.

        int i = 0;
        for (; i < polyline.length; i++) {
            if (p < polyline[i][0]) {
                break;
            }
        }

        if (i == polyline.length || i == 0) {
            // Degenerate cases.
            //
            // This should never happen:
            //
            // Should not be polyline.length because the last entry in polyline
            // should be '1' and the maximum input value for p should be '1'.
            //
            // Should not get 0 because the first entry should be zero and the
            // input parameter should be greater or equal to zero.
            // 
            return i / (float) polyline.length; // returns 0 for 0 and 1 for 1
        }

        float[] from = polyline[i - 1];
        float[] to   = polyline[i];

        // Compute the progress between from and to:
        //
        // p = (1 - t) * from[0] + t.to[0]
        // p = t * (t[0] - from[0]) + from[0]
        // t = (p - from[0]) / (to[0] - from[0])
        //
        // So, the interpolated progress is:
        //
        // ip = (1 - t) * from[1] + t.to[1]
        // ip = (1 - (p - from[0]) / (to[0] - from[0])) * from[1] 
        //      + ((p - from[0]) / (to[0] - from[0])) * to[1]
        // ip = ((to[0] - from[0] - p + from[0]) / (to[0] - from[0])) * from[1]
        //      + (to[1] * p - to[1] * from[0]) / (to[0] - from[0])
        // ip = ((to[0] - p) / (to[0] - from[0]) * from[1] 
        //      + (to[1] * p - to[1] * from[0]) / (to[0] - from[0])
        // ip = (to[0] * from[1] - p * from[1]) / (to[0] - from[0]) 
        //      + (to[1] * p - to[1] * from[0]) / (to[0] - from[0])
        // ip = (to[0] * from[1] - p * from[1] + to[1] * p - to[1] * from[0]) 
        //      / (to[0] - from[0])
        // ip = (to[0] * from[1] - to[1] * from[0] + p * (to[1] - from[1])) 
        //      / (to[0] - from[0])
        return (to[0] * from[1] - to[1] * from[0] + p * (to[1] - from[1])) 
                / (to[0] - from[0]);
    
java.lang.Object[]f(long t)
This is the Animate element's animation function. f(t) { a. Compute the 'simple duration penetration' p p = t / dur where dur is the simple duration. b. Compute the 'current time segment' i refTimes[i] <= p < refTimes[i+1] c. Compute the 'segment penetration' sp sp = (p - refTimes[i]) / (refTimes[i+1] - refTimes[i]) Note: 0 <= sp <= 1 d. Compute the 'interpolated interval penetration' isp isp = calcMode(sp) Note: 0 <= isp <= 1 e. Compute the animated value: v = refValues.compute(isp) v has the same number of components as refValues. }

param
t the animation's simple time.

        // a. Compute the simple duration penetration p

        // See: http://www.w3.org/TR/smil20/smil-timing.html#animation-animationNS-InterpolationAndIndefSimpleDur
        float p = 0;
        if (timedElementSupport.simpleDur != null 
            && 
            timedElementSupport.simpleDur.isResolved()) {
            p = t / (float) timedElementSupport.simpleDur.value;
        }

        // Iterate for each component.
        int nc = refValues.getComponents();
        float sp = 0;
        int si = 0;
        float endTime = 1;
        float beginTime = 0;
        int i = 0;

        // b. Compute the 'current time segment' index si[ci] 
            
        // We iterate from 1 because the first value in refTimes
        // is always 0. 
        for (i = 1; i < refTimes.length; i++) {
            if (p < refTimes[i]) {
                endTime = refTimes[i];
                break;
            }
            beginTime = refTimes[i];
        }

        si = i - 1;

        // c. Compute the segment penetration
        if (endTime == beginTime) {
            sp = 1;
        } else {
            sp = (p - beginTime) / (endTime - beginTime);
        }
        
        // d. Compute the 'interpolated segment penetration'
        sp = calcMode(sp, si);

        // At this point, we have computed:
        // a. the array of time segment indices corresponding to 't'
        // b. the array of penetration into the time segments. 
        //
        // The following call lets the animate implementation map 
        // the time segment indices and the time segment penetration
        // into refValues indices and penetration, in case these are
        // different. Typically, these are the same, but they may be
        // different, for example in the case of animateMotion with
        // keyPoints.
        sisp[0] = si;
        sisp[1] = sp;
        mapToSegmentProgress(si, sp, sisp);
        si = (int) sisp[0];
        sp = sisp[1];

        // If we are dealing with a to-animation, we need to get the
        // value for the single segment's start value.
        if (isToAnimation) {
            Object[] baseValue = baseVal.getBaseValue();
            refValues.getSegment(0).setStart(baseValue);
            return refValues.compute(si, sp);
        }

        // The reminder of this method is for non-additive animations
        
        // Compute the simple function value
        Object[] v = refValues.compute(si, sp);

        // Account for cumulative behavior.
        if (!isToAnimation && accumulate && timedElementSupport.curIter > 0) {
            // Last simple time value, lv
            Object[] lv = 
                refValues.getSegment(refValues.getSegments() - 1).getEnd();
            Object[] r = traitAnim.multiply(lv, timedElementSupport.curIter);
            v = traitAnim.sum(r, v);
        }

        // Now, account for additive behavior.
        if (!additive) {
            return v;
        } 

        return traitAnim.sum(baseVal.getBaseValue(), v);
    
float[]getDefaultTiming(RefValues refValues)
Computes refTimes so that each animation segment lasts the same length of time.

        int ns = refValues.getSegments();
        float[] refTimes = new float[ns];
        float startTime = 0;
        float segLength = 1 / (float) ns;
        for (int i = 0; i < ns; i++) {
            refTimes[i] = startTime;
            startTime += segLength;
        }

        return refTimes;
    
float[]getPacedTiming(RefValues refValues)
Computes refTimes so that there is a paced speed on each values segment.

        // a) Compute the refValues length, D
        //    D = sum(from 0 to n; seg(i).length()); 
        //
        // b) Compute the overall paced velocity, V
        //    V = D / dur; 
        //
        // c) For each segment i, compute its refTime:
        //    refTime[j][i] = refTime[j][i-1] + (seg(i).length(j) / V(j)) / dur;
        
        float D = refValues.getLength();

        int ns = refValues.getSegments();
        refTimes = new float[ns];
        float prevRefTime = 0;

        if (D > 0) {
            // For each segment index si
            for (int si = 1; si < ns; si++) {
                refTimes[si] = prevRefTime + refValues.getLength(si -1) / D;
                prevRefTime = refTimes[si];
            }
        } else {
            // Segments are all of zero length, they should be evenly spaced
            // in time.
            float sl = 1f / ns;
            for (int si = 1; si < ns; si++) {
                refTimes[si] = si * sl;
            }
        }

        return refTimes;
    
public java.lang.StringgetTraitImpl(java.lang.String name)

        if (SVGConstants.SVG_TO_ATTRIBUTE == name) {
            return to;
        } else if (SVGConstants.SVG_FROM_ATTRIBUTE == name) {
            return from;
        } else if (SVGConstants.SVG_BY_ATTRIBUTE == name) {
            return by;
        } else if (SVGConstants.SVG_VALUES_ATTRIBUTE == name) {
            return values;
        } else if (SVGConstants.SVG_CALC_MODE_ATTRIBUTE == name) {
            switch (calcMode) {
            case CALC_MODE_DISCRETE:
                return SVGConstants.SVG_DISCRETE_VALUE;
            case CALC_MODE_LINEAR:
                return SVGConstants.SVG_LINEAR_VALUE;
            case CALC_MODE_PACED:
                return SVGConstants.SVG_PACED_VALUE;
            case CALC_MODE_SPLINE:
            default:
                return SVGConstants.SVG_SPLINE_VALUE;
            }
        } else if (SVGConstants.SVG_KEY_TIMES_ATTRIBUTE == name) {
            return toStringTrait(keyTimes);
        } else if (SVGConstants.SVG_KEY_SPLINES_ATTRIBUTE == name) {
            return toStringTrait(keySplines);
        } else if (SVGConstants.SVG_ADDITIVE_ATTRIBUTE == name) {
            if (additive) {
                return SVGConstants.SVG_SUM_VALUE;
            }
            return SVGConstants.SVG_REPLACE_VALUE;
        } else if (SVGConstants.SVG_ACCUMULATE_ATTRIBUTE == name) {
            if (accumulate) {
                return SVGConstants.SVG_SUM_VALUE;
            }
            return SVGConstants.SVG_NONE_VALUE;
        } else {
            return super.getTraitImpl(name);
        }
    
static booleanisFlat(float[][] curve, float flatness)

param
curve the spline curve to test for flatness. The curve is considered flat if the distance from the two intermediate control points from the line between the first and the last control points is less than the desired flatness maximum. The input array must have at least four elements and each one must be at least 2 elements long. If not, an ArrayOutOfBoundsException is thrown. The curve array should not be null.
param
flatness. The maximum distance allowed for the intermediate curve control points.

        // Compute the square distance of P2 and P3 to the (P1, P4) line.
        float dx = curve[3][0] - curve[0][0];
        float dy = curve[3][1] - curve[0][1];
        float div = dx * dx + dy * dy;
        
        if (div == 0) {
            return true;
        }
        
        float den = (dx * (curve[1][1] - curve[0][1])
                     -
                     dy * (curve[1][0] - curve[0][0]));

        float dP2Sq =  (den * den) / div;

        den = (dx * (curve[2][1] - curve[0][1])
               -
               dy * (curve[2][0] - curve[0][0]));

        float dP3Sq =  (den * den) / div;

        return (dP2Sq <= flatness && dP3Sq <= flatness);
        
    
protected voidmapToSegmentProgress(int si, float sp, float[] sisp)
The following call lets the animate implementation map the time segment indices and the time segment penetration into refValues indices and penetration, in case these are different. Typically, these are the same, but they may be different, for example in the case of animateMotion with keyPoints.

    
final voidselectRefValues()
Computes the 'right' source for reference values, depending on the precedence rules for the different values sources.

throws
DOMException if there is no way to compute a set of reference values, for example if none of the values sources is specified.

        refValues = null;
        selectRefValuesExtra();
        if (refValues != null) {
            return;
        }

        // Pseudo-code for accounting for precedence rules.
        //
        // If (values defined)
        //     // values anim
        //     refValues = values; 
        // else if (from defined)
        //     if (to defined)
        //         // from-to anim
        //         refValues = (from; to)
        //     else if (by defined)
        //         // from-by anim
        //        refValues = (from; from+by) 
        //     else
        //     // no effect, there is nothing like a from anim
        // else if (by defined)
        //     // by anim
        //     refValues = (0; by) and force additive anim
        // else if (to defined)
        //     // to anim
        //     refValues = (baseVal; to)
        // else
        //    // no effect
        //
        isToAnimation = false;
        if (values != null) {
            refValues = valuesRefValues;
        } else if (from != null) {
            refValues = fromRefValues;
            
            if (to != null) {
                // from-to animatin
                refValues.getSegment(0).collapse(toRefValues.getSegment(0), 
                        this);
            } else if (by != null) {
                // from-by animation
                if (refValues.getSegment(0).isAdditive()) {
                    try {
                        refValues.getSegment(0).addToEnd(
                                byRefValues.getSegment(0).getStart());
                    } catch (IllegalArgumentException iae) {
                        // Incompatible by value
                        throw new DOMException(DOMException.NOT_SUPPORTED_ERR, 
                                Messages.formatMessage(
                                    Messages.ERROR_INCOMPATIBLE_FROM_BY,
                                    new Object[] {
                                        traitName,
                                        traitNamespace,
                                        getLocalName(),
                                        getNamespaceURI(),
                                        from,
                                        to
                                    }));
                    }
                } else {
                    throw new DOMException(
                            DOMException.NOT_SUPPORTED_ERR,
                            Messages.formatMessage(
                                Messages.ERROR_ATTRIBUTE_NOT_ADDITIVE_FROM_BY,
                                new Object[] {
                                    traitName,
                                    traitNamespace,
                                    getLocalName(),
                                    getNamespaceURI() 
                                }));
                }
            } else {
                throw animationError(idRef, 
                        traitNamespace,
                        traitName,
                        targetElement.getNamespaceURI(),
                        targetElement.getLocalName(),
                        getId(),
                        getNamespaceURI(),
                        getLocalName(),
                        Messages.formatMessage(
                            Messages.ERROR_INVALID_ANIMATION_FROM_ANIM, 
                            null));
            }
        } else if (by != null) {
            // by animation
            if (!byRefValues.getSegment(0).isAdditive()) {
                throw new DOMException(
                        DOMException.NOT_SUPPORTED_ERR,
                        Messages.formatMessage(
                            Messages.ERROR_ATTRIBUTE_NOT_ADDITIVE_BY,
                            new Object[] {
                                traitName,
                                traitNamespace,
                                getLocalName(),
                                getNamespaceURI()
                            }));
            }
            refValues = byRefValues;
            refValues.getSegment(0).setZeroStart();
            additive = true;
        } else if (to != null) {
            // to animation
            // We cannot compute the base value yet so we can only set the 
            // refValues to the toRefValues and delay computing the segment's 
            // begin until we actually have a baseValue. This will have to be 
            // updated on each computing cycle because the base value may change
            // over time.
            isToAnimation = true;
            refValues = toRefValues;
        } else {
            // SMIL Animation
            // specification:
            // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues
            // "similarly, if none of the from, to, by or values attributes are
            // specified, the animation will have no effect."
            hasNoEffect = true;
        }        
    
voidselectRefValuesExtra()
Allows extensions to select a different source for refValues, in case the extension has addition values sources that have higher precedence than the default. For example, animateMotion has the path attribute and the mpath children which have higher precedence.

throws
DOMException if there is no way to compute a set of reference values, for example if none of the values sources is specified.

    
public voidsetTraitImpl(java.lang.String name, java.lang.String value)

        if (SVGConstants.SVG_TO_ATTRIBUTE == name) {
            checkWriteLoading(name);
            to = value;
        } else if (SVGConstants.SVG_FROM_ATTRIBUTE == name) {
            checkWriteLoading(name);
            from = value;
        } else if (SVGConstants.SVG_BY_ATTRIBUTE == name) {
            checkWriteLoading(name);
            by = value;
        } else if (SVGConstants.SVG_VALUES_ATTRIBUTE == name) {
            checkWriteLoading(name);
            values = value;
        } else if (SVGConstants.SVG_KEY_TIMES_ATTRIBUTE == name) {
            checkWriteLoading(name);

            // Generic float array parsing
            keyTimes = parseFloatArrayTrait(name, value.replace(';", ',"));

            // Now, check that the keyTimes values are from 0 to 1 and
            // in increasing order. Note that this only performs the 
            // validations which can be made prior to a call to validate.
            if (keyTimes[0] < 0 || keyTimes[0] > 1) {
                throw illegalTraitValue(name, value);
            }

            for (int i = 1; i < keyTimes.length; i++) {
                if (keyTimes[i] < 0 || keyTimes[i] > 1) {
                    throw illegalTraitValue(name, value);
                }

                if (keyTimes[i] < keyTimes[i - 1]) {
                    throw illegalTraitValue(name, value);
                }
            }
        } else if (SVGConstants.SVG_KEY_SPLINES_ATTRIBUTE == name) {
            checkWriteLoading(name);

            SimpleTokenizer st = new SimpleTokenizer(value, ";");
            int n = st.countTokens();
            keySplines = new float[n][];
            
            for (int i = 0; i < n; i++) {
                String splineDef = st.nextToken();
                keySplines[i] = parseFloatArrayTrait(name, splineDef);
                if (keySplines[i].length != 4 ||
                    keySplines[i][0] < 0 ||
                    keySplines[i][0] > 1 ||
                    keySplines[i][1] < 0 ||
                    keySplines[i][1] > 1 ||
                    keySplines[i][2] < 0 ||
                    keySplines[i][2] > 1 ||
                    keySplines[i][3] < 0 ||
                    keySplines[i][3] > 1) {
                    throw illegalTraitValue(name, value);
                }
            }
        } else if (SVGConstants.SVG_ADDITIVE_ATTRIBUTE == name) {
            checkWriteLoading(name);

            if (SVGConstants.SVG_REPLACE_VALUE.equals(value)) {
                additive = false;
            } else if (SVGConstants.SVG_SUM_VALUE.equals(value)) {
                additive = true;
            } else {
                throw illegalTraitValue(name, value);
            }
        } else if (SVGConstants.SVG_ACCUMULATE_ATTRIBUTE == name) {
            checkWriteLoading(name);

            if (SVGConstants.SVG_NONE_VALUE.equals(value)) {
                accumulate = false;
            } else if (SVGConstants.SVG_SUM_VALUE.equals(value)) {
                accumulate = true;
            } else {
                throw illegalTraitValue(name, value);
            }
        } else if (SVGConstants.SVG_CALC_MODE_ATTRIBUTE == name) {
            if (SVGConstants.SVG_DISCRETE_VALUE.equals(value)) {
                calcMode = CALC_MODE_DISCRETE;
            } else if (SVGConstants.SVG_LINEAR_VALUE.equals(value)) {
                calcMode = CALC_MODE_LINEAR;
            } else if (SVGConstants.SVG_PACED_VALUE.equals(value)) {
                calcMode = CALC_MODE_PACED;
            } else if (SVGConstants.SVG_SPLINE_VALUE.equals(value)) {
                calcMode = CALC_MODE_SPLINE;
            } else {
                throw illegalTraitValue(name, value);
            }
        } else {
            super.setTraitImpl(name, value);
        }
    
booleansupportsTrait(java.lang.String traitName)
Supported traits: to, attributeName

param
traitName the name of the trait which the element may support.
return
true if this element supports the given trait in one of the trait accessor methods.

        if (SVGConstants.SVG_TO_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_FROM_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_BY_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_VALUES_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_CALC_MODE_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_KEY_TIMES_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_KEY_SPLINES_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_ADDITIVE_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_ACCUMULATE_ATTRIBUTE == traitName) {
            return true;
        } else {
            return super.supportsTrait(traitName);
        }
    
static voidtoRefSpline(float[][] curve, java.util.Vector segments)
Converts the input spline curve (defined by its four control points) into a polyline approximation (i.e., an array of points). If the curve is flat enough (see the isFlat method), then the curve is approximated to a line between its two end control points and the last control point is added to the input segment vector. If the curve is not flat enough, then it is sub-divided and the subdivided curves are, recursively, tested for flatness. The flatness used for flatness test is controlled by the MIN_FLATNESS_SQUARE constant.

param
curve the spline curve to approximate. Should not be null. Should be an array of four arrays of two elements. If there are less than four arrays or if there are less than two elements in these arrays then an ArrayIndexOutOfBoundsException is thrown. If any element in the curve array is null, a NullPointerException is thrown.
param
segments the vector to which polyline points should be added. Should not be null.

        if (isFlat(curve, MIN_FLATNESS_SQUARE)) {
            segments.addElement(new float[] {curve[3][0], curve[3][1]});
            return;
        }

        float[][] lcurve = new float[4][2];
        float[][] rcurve = new float[4][2];

        // L1 = P1
        lcurve[0] = curve[0];

        // L2 = (P1 + P2) / 2
        lcurve[1][0] = (curve[0][0] + curve[1][0]) / 2;
        lcurve[1][1] = (curve[0][1] + curve[1][1]) / 2;

        // H = (P2 + P3) / 2
        float[] h = new float[2];
        h[0] = (curve[1][0] + curve[2][0]) / 2;
        h[1] = (curve[1][1] + curve[2][1]) / 2;

        // L3 = (L2 + H) / 2
        lcurve[2][0] = (lcurve[1][0] + h[0]) / 2;
        lcurve[2][1] = (lcurve[1][1] + h[1]) / 2;

        // R3 = (P3 + P4) / 2
        rcurve[2][0] = (curve[2][0] + curve[3][0]) / 2;
        rcurve[2][1] = (curve[2][1] + curve[3][1]) / 2;

        // R2 = (H + R3) / 2
        rcurve[1][0] = (h[0] + rcurve[2][0]) / 2;
        rcurve[1][1] = (h[1] + rcurve[2][1]) / 2;

        // L4 = R1 = (L3 + R2) / 2
        lcurve[3][0] = (lcurve[2][0] + rcurve[1][0]) / 2;
        lcurve[3][1] = (lcurve[2][1] + rcurve[1][1]) / 2;
        rcurve[0] = lcurve[3];

        // R4 = P4
        rcurve[3] = curve[3];

        toRefSpline(lcurve, segments);
        toRefSpline(rcurve, segments);
    
static float[][][]toRefSplines(float[][] splines)
Computes an array of points which do a linear approximation of the input splines.

param
splines the array of splines to approximate with a polyline. The splines array should be an made of four element floats. if any of the elements is null, a NullPointerException will be thrown. If any of the element has a length less than four, an ArrayIndexOutOfBoundsException will be thrown.
return
an array of point arrays. Each point array is a polyline approximation of the input spline.

        float[][][] rSplines = new float[splines.length][][];

        float[][] splineDef = new float[4][2];
        for (int i = 0; i < splines.length; i++) {
            Vector v = new Vector();
            v.addElement(new float[] {0, 0});
            splineDef[0][0] = 0;
            splineDef[0][1] = 0;
            splineDef[1][0] = splines[i][0];
            splineDef[1][1] = splines[i][1];
            splineDef[2][0] = splines[i][2];
            splineDef[2][1] = splines[i][3];
            splineDef[3][0] = 1;
            splineDef[3][1] = 1;
            toRefSpline(splineDef, v);
            float[][] polyline = new float[v.size()][];
            v.copyInto(polyline);
            rSplines[i] = polyline;
        }

        return rSplines;
    
voidvalidate()
Validating an Animate consists in: a) Setting its target element. If there was no idRef, then targetElement is still null and will be positioned to the parent node. b) Validating the from, to, by and values traits with the targetElement, using the target trait name, namespace and value. c) Validating the keyTimes and the keySplines trait values to check they are compatible with the values specification.

throws
DOMException if there is a validation error, for example if the to value is incompatible with the target trait or if the target trait is not animatable.

        // =====================================================================
        // a) Set the target element.
        if (targetElement == null) {
            targetElement = (ElementNode) parent;
        }
        
        // =====================================================================
        // Check that the traitName attribute was specified.
        if (traitName == null) {
            throw illegalTraitValue(SVGConstants.SVG_ATTRIBUTE_NAME_ATTRIBUTE, 
                                    traitName);
        }

        // =====================================================================
        // b) Validate the to/from/by/values traits with the target element.  

        // Note that traitName should _never_ be null when validate() is 
        // invoked. It is either required (e.g., for Animate) or fixed (e.g., 
        // for AnimateMotion. If, for example in some unit test configutations,
        // this method is called without a specified traitName, the method 
        // generates a NullPointerException.
        traitAnim = targetElement.getSafeTraitAnimNS(traitNamespace, traitName);

        // If the traitAnim type does not support interpolation, force the 
        // calcMode to be discrete.
        if (traitAnim.supportsInterpolation()) {
            actualCalcMode = calcMode;
        } else {
            actualCalcMode = CALC_MODE_DISCRETE;
        }

        validateValues();

        // Now, apply precedence rules to compute the actual RefValues.
        selectRefValues();

        // If the animation has no effect, stop here.
        if (hasNoEffect) {
            return;
        }

        // =====================================================================
        // If we are dealing with discrete timing, we need to add a last time
        // segment so that we implement the desired behavior for discrete 
        // timing. With a single interval with start/end values, only the start
        // value will be shown. If we add a new interval with the end value, 
        // then the end value will be shown for the last time interval.
        if (actualCalcMode == CALC_MODE_DISCRETE) {
            refValues.makeDiscrete();
        }

        // Initialize the refValues
        refValues.initialize();

        // =====================================================================
        // c. Validate that keyTimes and keySplines trait values are compatible
        //    with the values specification.
        computeRefTimes();

        // c.1 Validate that keySplines is compatible with the animation set-up
        //
        // See:
        // http://www.w3.org/TR/2001/REC-smil20-20010807/smil20.html#animation-animationNS-InterpolationKeysplines
        //
        // The attribute is ignored unless the CALC_MODE is spline. For spline
        // interpolation, there must be one fewer sets of control points in the
        // keySplines attribute than there are keyTimes.
        //
        if (actualCalcMode == CALC_MODE_SPLINE) {
            if (keySplines == null 
                ||
                refTimes.length != keySplines.length) {
                throw animationError(idRef,
                        traitNamespace,
                        traitName,
                        targetElement.getNamespaceURI(),
                        targetElement.getLocalName(),
                        getId(),
                        getNamespaceURI(),
                        getLocalName(),
                        Messages.formatMessage(
                            Messages.ERROR_INVALID_ANIMATION_KEY_SPLINES, 
                            new Object[] {
                                getTrait(
                                    SVGConstants.SVG_KEY_SPLINES_ATTRIBUTE),
                                getTrait(SVGConstants.SVG_KEY_TIMES_ATTRIBUTE)
                            }));
            }

            // Turn the keySplines attribute into a set of (x, y) points
            // which can be interpolated between.
            refSplines = toRefSplines(keySplines);
        }
    
final voidvalidateValues()
Validates the different so-called values attributes, such as the to/from/by and values attributes. Derived classes may have more 'values' attributes, and may override this method to validate these additional attributes.

throws
DOMException if there is a validation error, for example if the to value is incompatible with the target trait or if the target trait is not animatable.

        // b.1) Validate the to traits value
        toRefValues = null;
        if (to != null) {
            toRefValues = traitAnim.toRefValues(this,
                                                new String[] {to},
                                                null,
                                                SVGConstants.SVG_TO_ATTRIBUTE);
        }

        // b.2) Validate the by traits value
        byRefValues = null;
        if (by != null) {
            byRefValues = traitAnim.toRefValues(this,
                                                new String[] {by},
                                                null,
                                                SVGConstants.SVG_BY_ATTRIBUTE);
        }

        // b.3) Validate the from traits value
        fromRefValues = null;
        if (from != null) {
            fromRefValues = traitAnim.toRefValues(this, 
                    new String[] {from},
                    null,
                    SVGConstants.SVG_FROM_ATTRIBUTE);
        }

        // b.4) Validate the values trait value
        valuesRefValues = null;
        if (values != null) {
            String[] v = parseStringArrayTrait(
                    SVGConstants.SVG_VALUES_ATTRIBUTE,
                    values,
                    ";");
            valuesRefValues = traitAnim.toRefValues(this, 
                    v,
                    null,
                    SVGConstants.SVG_VALUES_ATTRIBUTE);
        }

        validateValuesExtra();
    
voidvalidateValuesExtra()
Allows extension classes to validate addition values sources.

throws
DOMException if there is a validation error, for example if the to value is incompatible with the target trait or if the target trait is not animatable.