public abstract class

TimeCycleSplineSet

extends java.lang.Object

 java.lang.Object

↳androidx.constraintlayout.core.motion.utils.TimeCycleSplineSet

Subclasses:

TimeCycleSplineSet.CustomSet, TimeCycleSplineSet.CustomVarSet, ViewTimeCycle, ViewTimeCycle.PathRotate, ViewTimeCycle.CustomSet

Gradle dependencies

compile group: 'androidx.constraintlayout', name: 'constraintlayout-core', version: '1.1.0-beta01'

  • groupId: androidx.constraintlayout
  • artifactId: constraintlayout-core
  • version: 1.1.0-beta01

Artifact androidx.constraintlayout:constraintlayout-core:1.1.0-beta01 it located at Google repository (https://maven.google.com/)

Overview

This engine allows manipulation of attributes by wave shapes oscillating in time

Summary

Fields
protected static final intCURVE_OFFSET

protected static final intCURVE_PERIOD

protected static final intCURVE_VALUE

protected float[]mCache

protected booleanmContinue

protected intmCount

protected CurveFitmCurveFit

protected floatmLastCycle

protected longmLastTime

protected int[]mTimePoints

protected java.lang.StringmType

protected float[][]mValues

protected intmWaveShape

protected static floatsVal2PI

Constructors
publicTimeCycleSplineSet()

Methods
protected floatcalcWave(float period)

public CurveFitgetCurveFit()

public voidsetPoint(int position, float value, float period, int shape, float offset)

protected voidsetStartTime(long currentTime)

public voidsetType(java.lang.String type)

public voidsetup(int curveType)

public java.lang.StringtoString()

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait

Fields

protected CurveFit mCurveFit

protected int mWaveShape

protected int[] mTimePoints

protected float[][] mValues

protected int mCount

protected java.lang.String mType

protected float[] mCache

protected static final int CURVE_VALUE

protected static final int CURVE_PERIOD

protected static final int CURVE_OFFSET

protected static float sVal2PI

protected boolean mContinue

protected long mLastTime

protected float mLastCycle

Constructors

public TimeCycleSplineSet()

Methods

public java.lang.String toString()

public void setType(java.lang.String type)

protected float calcWave(float period)

Parameters:

period: cycles per second

public CurveFit getCurveFit()

protected void setStartTime(long currentTime)

public void setPoint(int position, float value, float period, int shape, float offset)

public void setup(int curveType)

Source

/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.constraintlayout.core.motion.utils;

import androidx.constraintlayout.core.motion.CustomAttribute;
import androidx.constraintlayout.core.motion.CustomVariable;
import androidx.constraintlayout.core.motion.MotionWidget;

import java.text.DecimalFormat;

/**
 * This engine allows manipulation of attributes by wave shapes oscillating in time
 *
 *
 */
public abstract class TimeCycleSplineSet {
    private static final String TAG = "SplineSet";
    protected CurveFit mCurveFit;
    protected int mWaveShape = 0;
    protected int[] mTimePoints = new int[10];
    protected float[][] mValues = new float[10][3];
    protected int mCount;
    protected String mType;
    protected float[] mCache = new float[3];
    protected static final int CURVE_VALUE = 0;
    protected static final int CURVE_PERIOD = 1;
    protected static final int CURVE_OFFSET = 2;
    protected static float sVal2PI = (float) (2 * Math.PI);
    protected boolean mContinue = false;
    protected long mLastTime;
    protected float mLastCycle = Float.NaN;

    @Override
    public String toString() {
        String str = mType;
        DecimalFormat df = new DecimalFormat("##.##");
        for (int i = 0; i < mCount; i++) {
            str += "[" + mTimePoints[i] + " , " + df.format(mValues[i]) + "] ";
        }
        return str;
    }

    public void setType(String type) {
        mType = type;
    }

    /**
     * @param period cycles per second
     */
    protected float calcWave(float period) {
        float p = period;
        switch (mWaveShape) {
            default:
            case Oscillator.SIN_WAVE:
                return (float) Math.sin(p * sVal2PI);
            case Oscillator.SQUARE_WAVE:
                return (float) Math.signum(p * sVal2PI);
            case Oscillator.TRIANGLE_WAVE:
                return 1 - Math.abs(p);
            case Oscillator.SAW_WAVE:
                return ((p * 2 + 1) % 2) - 1;
            case Oscillator.REVERSE_SAW_WAVE:
                return (1 - ((p * 2 + 1) % 2));
            case Oscillator.COS_WAVE:
                return (float) Math.cos(p * sVal2PI);
            case Oscillator.BOUNCE:
                float x = 1 - Math.abs((p * 4) % 4 - 2);
                return 1 - x * x;
        }
    }

    public CurveFit getCurveFit() {
        return mCurveFit;
    }

    protected void setStartTime(long currentTime) {
        mLastTime = currentTime;
    }

    // @TODO: add description
    public void setPoint(int position, float value, float period, int shape, float offset) {
        mTimePoints[mCount] = position;
        mValues[mCount][CURVE_VALUE] = value;
        mValues[mCount][CURVE_PERIOD] = period;
        mValues[mCount][CURVE_OFFSET] = offset;
        mWaveShape = Math.max(mWaveShape, shape); // the highest value shape is chosen
        mCount++;
    }

    public static class CustomSet extends TimeCycleSplineSet {
        String mAttributeName;
        KeyFrameArray.CustomArray mConstraintAttributeList;
        KeyFrameArray.FloatArray mWaveProperties = new KeyFrameArray.FloatArray();
        float[] mTempValues;
        float[] mCustomCache;

        public CustomSet(String attribute, KeyFrameArray.CustomArray attrList) {
            mAttributeName = attribute.split(",")[1];
            mConstraintAttributeList = attrList;
        }

        // @TODO: add description
        @Override
        public void setup(int curveType) {
            int size = mConstraintAttributeList.size();
            int dimensionality = mConstraintAttributeList.valueAt(0).numberOfInterpolatedValues();
            double[] time = new double[size];
            mTempValues = new float[dimensionality + 2];
            mCustomCache = new float[dimensionality];
            double[][] values = new double[size][dimensionality + 2];
            for (int i = 0; i < size; i++) {
                int key = mConstraintAttributeList.keyAt(i);
                CustomAttribute ca = mConstraintAttributeList.valueAt(i);
                float[] waveProp = mWaveProperties.valueAt(i);
                time[i] = key * 1E-2;
                ca.getValuesToInterpolate(mTempValues);
                for (int k = 0; k < mTempValues.length; k++) {
                    values[i][k] = mTempValues[k];
                }
                values[i][dimensionality] = waveProp[0];
                values[i][dimensionality + 1] = waveProp[1];
            }
            mCurveFit = CurveFit.get(curveType, time, values);
        }

        // @TODO: add description
        @Override
        public void setPoint(int position, float value, float period, int shape, float offset) {
            throw new RuntimeException("don't call for custom attribute "
                    + "call setPoint(pos, ConstraintAttribute,...)");
        }

        // @TODO: add description
        public void setPoint(int position,
                CustomAttribute value,
                float period,
                int shape,
                float offset) {
            mConstraintAttributeList.append(position, value);
            mWaveProperties.append(position, new float[]{period, offset});
            mWaveShape = Math.max(mWaveShape, shape); // the highest value shape is chosen
        }

        // @TODO: add description
        public boolean setProperty(MotionWidget view, float t, long time, KeyCache cache) {
            mCurveFit.getPos(t, mTempValues);
            float period = mTempValues[mTempValues.length - 2];
            float offset = mTempValues[mTempValues.length - 1];
            long delta_time = time - mLastTime;

            if (Float.isNaN(mLastCycle)) { // it has not been set
                mLastCycle = cache.getFloatValue(view, mAttributeName, 0); // check the cache
                if (Float.isNaN(mLastCycle)) {  // not in cache so set to 0 (start)
                    mLastCycle = 0;
                }
            }

            mLastCycle = (float) ((mLastCycle + delta_time * 1E-9 * period) % 1.0);
            mLastTime = time;
            float wave = calcWave(mLastCycle);
            mContinue = false;
            for (int i = 0; i < mCustomCache.length; i++) {
                mContinue |= mTempValues[i] != 0.0;
                mCustomCache[i] = mTempValues[i] * wave + offset;
            }
            view.setInterpolatedValue(mConstraintAttributeList.valueAt(0), mCustomCache);
            if (period != 0.0f) {
                mContinue = true;
            }
            return mContinue;
        }
    }

    // @TODO: add description
    public void setup(int curveType) {
        if (mCount == 0) {
            System.err.println("Error no points added to " + mType);
            return;
        }
        Sort.doubleQuickSort(mTimePoints, mValues, 0, mCount - 1);
        int unique = 0;
        for (int i = 1; i < mTimePoints.length; i++) {
            if (mTimePoints[i] != mTimePoints[i - 1]) {
                unique++;
            }
        }
        if (unique == 0) {
            unique = 1;
        }
        double[] time = new double[unique];
        double[][] values = new double[unique][3];
        int k = 0;

        for (int i = 0; i < mCount; i++) {
            if (i > 0 && mTimePoints[i] == mTimePoints[i - 1]) {
                continue;
            }
            time[k] = mTimePoints[i] * 1E-2;
            values[k][0] = mValues[i][0];
            values[k][1] = mValues[i][1];
            values[k][2] = mValues[i][2];
            k++;
        }
        mCurveFit = CurveFit.get(curveType, time, values);
    }

    protected static class Sort {
        static void doubleQuickSort(int[] key, float[][] value, int low, int hi) {
            int[] stack = new int[key.length + 10];
            int count = 0;
            stack[count++] = hi;
            stack[count++] = low;
            while (count > 0) {
                low = stack[--count];
                hi = stack[--count];
                if (low < hi) {
                    int p = partition(key, value, low, hi);
                    stack[count++] = p - 1;
                    stack[count++] = low;
                    stack[count++] = hi;
                    stack[count++] = p + 1;
                }
            }
        }

        private static int partition(int[] array, float[][] value, int low, int hi) {
            int pivot = array[hi];
            int i = low;
            for (int j = low; j < hi; j++) {
                if (array[j] <= pivot) {
                    swap(array, value, i, j);
                    i++;
                }
            }
            swap(array, value, i, hi);
            return i;
        }

        private static void swap(int[] array, float[][] value, int a, int b) {
            int tmp = array[a];
            array[a] = array[b];
            array[b] = tmp;
            float[] tmpv = value[a];
            value[a] = value[b];
            value[b] = tmpv;
        }
    }


    public static class CustomVarSet extends TimeCycleSplineSet {
        String mAttributeName;
        KeyFrameArray.CustomVar mConstraintAttributeList;
        KeyFrameArray.FloatArray mWaveProperties = new KeyFrameArray.FloatArray();
        float[] mTempValues;
        float[] mCustomCache;

        public CustomVarSet(String attribute, KeyFrameArray.CustomVar attrList) {
            mAttributeName = attribute.split(",")[1];
            mConstraintAttributeList = attrList;
        }

        // @TODO: add description
        @Override
        public void setup(int curveType) {
            int size = mConstraintAttributeList.size();
            int dimensionality = mConstraintAttributeList.valueAt(0).numberOfInterpolatedValues();
            double[] time = new double[size];
            mTempValues = new float[dimensionality + 2];
            mCustomCache = new float[dimensionality];
            double[][] values = new double[size][dimensionality + 2];
            for (int i = 0; i < size; i++) {
                int key = mConstraintAttributeList.keyAt(i);
                CustomVariable ca = mConstraintAttributeList.valueAt(i);
                float[] waveProp = mWaveProperties.valueAt(i);
                time[i] = key * 1E-2;
                ca.getValuesToInterpolate(mTempValues);
                for (int k = 0; k < mTempValues.length; k++) {
                    values[i][k] = mTempValues[k];
                }
                values[i][dimensionality] = waveProp[0];
                values[i][dimensionality + 1] = waveProp[1];
            }
            mCurveFit = CurveFit.get(curveType, time, values);
        }

        // @TODO: add description
        @Override
        public void setPoint(int position, float value, float period, int shape, float offset) {
            throw new RuntimeException("don't call for custom attribute "
                    + "call setPoint(pos, ConstraintAttribute,...)");
        }

        // @TODO: add description
        public void setPoint(int position,
                CustomVariable value,
                float period,
                int shape,
                float offset) {
            mConstraintAttributeList.append(position, value);
            mWaveProperties.append(position, new float[]{period, offset});
            mWaveShape = Math.max(mWaveShape, shape); // the highest value shape is chosen
        }

        // @TODO: add description
        public boolean setProperty(MotionWidget view, float t, long time, KeyCache cache) {
            mCurveFit.getPos(t, mTempValues);
            float period = mTempValues[mTempValues.length - 2];
            float offset = mTempValues[mTempValues.length - 1];
            long delta_time = time - mLastTime;

            if (Float.isNaN(mLastCycle)) { // it has not been set
                mLastCycle = cache.getFloatValue(view, mAttributeName, 0); // check the cache
                if (Float.isNaN(mLastCycle)) {  // not in cache so set to 0 (start)
                    mLastCycle = 0;
                }
            }

            mLastCycle = (float) ((mLastCycle + delta_time * 1E-9 * period) % 1.0);
            mLastTime = time;
            float wave = calcWave(mLastCycle);
            mContinue = false;
            for (int i = 0; i < mCustomCache.length; i++) {
                mContinue |= mTempValues[i] != 0.0;
                mCustomCache[i] = mTempValues[i] * wave + offset;
            }
            mConstraintAttributeList.valueAt(0).setInterpolatedValue(view, mCustomCache);
            if (period != 0.0f) {
                mContinue = true;
            }
            return mContinue;
        }
    }
}