//-----------------------------------------------------------------------------
// Copyright (c) 2013 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#include "2d/assets/ParticleAssetField.h"

#ifndef _CORE_MATH_H_
#include "2d/core/CoreMath.h"
#endif

#ifndef _MMATH_H_
#include "math/mMath.h"
#endif

#ifndef _SIMBASE_H_
#include "sim/simBase.h"
#endif

#ifndef _STRINGUNIT_H_
#include "string/stringUnit.h"
#endif

//-----------------------------------------------------------------------------

static StringTableEntry particleAssetFieldRepeatTimeName   = StringTable->insert( "RepeatTime" );
static StringTableEntry particleAssetFieldMaxTimeName      = StringTable->insert( "MaxTime" );
static StringTableEntry particleAssetFieldMinValueName     = StringTable->insert( "MinValue" );
static StringTableEntry particleAssetFieldMaxValueName     = StringTable->insert( "MaxValue" );
static StringTableEntry particleAssetFieldDefaultValueName = StringTable->insert( "DefaultValue" );
static StringTableEntry particleAssetFieldValueScaleName   = StringTable->insert( "ValueScale" );
static StringTableEntry particleAssetFieldDataKeysName     = StringTable->insert( "Keys" );

static StringTableEntry particleAssetFieldDataKeyName      = StringTable->insert( "Key" );
static StringTableEntry particleAssetFieldDataKeyTimeName  = StringTable->insert( "Time" );
static StringTableEntry particleAssetFieldDataKeyValueName = StringTable->insert( "Value" );

ParticleAssetField::DataKey ParticleAssetField::BadDataKey( -1.0f, 0.0f );

//-----------------------------------------------------------------------------

ParticleAssetField::ParticleAssetField() :
                        mFieldName( StringTable->EmptyString ),
                        mRepeatTime( 1.0f ),
                        mMaxTime( 1.0f ),
                        mMinValue( 0.0f ),
                        mMaxValue( 0.0f ),
                        mDefaultValue( 1.0f ),
                        mValueScale( 1.0f ),
                        mValueBoundsDirty( true )
{
    // Set Vector Associations.
    VECTOR_SET_ASSOCIATION( mDataKeys );
}

//-----------------------------------------------------------------------------

ParticleAssetField::~ParticleAssetField()
{

}

//-----------------------------------------------------------------------------

void ParticleAssetField::copyTo( ParticleAssetField& field )
{
    field.mFieldName = mFieldName;
    field.mRepeatTime = mRepeatTime;
    field.mMaxTime = mMaxTime;
    field.mMinValue = mMinValue;
    field.mMaxValue = mMaxValue;
    field.mDefaultValue = mDefaultValue;
    field.mValueScale = mValueScale;

    // Copy data keys.    
    field.clearDataKeys();
    for ( S32 i = 0; i < mDataKeys.size(); i++ )
    {
        DataKey key = mDataKeys[i];
        field.addDataKey(key.mTime, key.mValue);
    }
}

//-----------------------------------------------------------------------------

void ParticleAssetField::initialize( const F32 maxTime, const F32 minValue, const F32 maxValue, const F32 defaultValue )
{
    // Set the value bounds.
    setValueBounds( maxTime, minValue, maxValue, defaultValue );

    // Reset the value bounds dirty flag.
    mValueBoundsDirty = false;
}

//-----------------------------------------------------------------------------

void ParticleAssetField::setValueBounds( F32 maxTime, F32 minValue, F32 maxValue, F32 defaultValue )
{
    // Check Max Time.
    if ( maxTime <= 0.0f )
    {
        // Warn.
        Con::warnf("ParticleAssetField::setValueBounds() - Max-time '%f' is invalid", maxTime );

        // Set Default Max Time.
        maxTime = 1.0f;
    }

    // Set Max Time.
    mMaxTime = maxTime;

    // Check Value Range Normalisation.
    if ( minValue > maxValue )
    {
        // Warn.
        Con::warnf("ParticleAssetField::setValueBounds() - Value Range is not normalised! (minValue:%f / maxValue:%f)", minValue, maxValue );

        // Normalise Y-Axis.
        F32 temp = minValue;
        minValue = maxValue;
        maxValue = temp;
    }
    // Check Value Range Scale.
    else if ( minValue == maxValue )
    {
        // Warn.
        Con::warnf("ParticleAssetField::setValueBounds() - Value Range has no scale! (minValue:%f / maxValue:%f)", minValue, maxValue );

        // Insert some Y-Axis Scale.
        maxValue = minValue + 0.001f;
    }

    // Set Bounds.
    mMinValue = minValue;
    mMaxValue = maxValue;

    // Check Default Value.
    if ( defaultValue < minValue || defaultValue > maxValue )
    {
        // Warn.
        Con::warnf("ParticleAssetField::setValueBounds() - Default Value is out of range! (minValue:%f / maxValue:%f / defaultValue:%f)", minValue, maxValue, defaultValue );

        // Clamp at lower value.
        defaultValue = minValue;
    }

    // Set Default Value.
    mDefaultValue = defaultValue;

    // Reset the data keys if none are present.
    if ( mDataKeys.size() == 0 )
        resetDataKeys();

    // Flag the value bounds as dirty.
    mValueBoundsDirty = true;
}

//-----------------------------------------------------------------------------

void ParticleAssetField::setFieldName( const char* pFieldName )
{
    // Sanity!
    AssertFatal( mFieldName == StringTable->EmptyString, "ParticleAssetField::setFieldName() - Cannot set particle asset field name once it has been set." );

    mFieldName = StringTable->insert( pFieldName );

    // Sanity!
    AssertFatal( mFieldName != StringTable->EmptyString, "ParticleAssetField::setFieldName() - Field name cannot be empty." );
}

//-----------------------------------------------------------------------------

bool ParticleAssetField::setRepeatTime( const F32 repeatTime )
{
    // Check repeat time.
    if ( repeatTime < 0.0f )
    {
        // Warn.
        Con::warnf("ParticleAssetField::setRepeatTime() - Repeat time''%f'' is invalid.", repeatTime );

        // Return Error.
        return false;
    }

    // Set repeat time.
    mRepeatTime = repeatTime;

    // Return Okay.
    return true;
}

//-----------------------------------------------------------------------------

bool ParticleAssetField::setValueScale( const F32 valueScale )
{
    // Check Value Scale.
    if ( valueScale < 0.0f )
    {
        // Warn.
        Con::warnf("ParticleAssetField::setValueScale() - Invalid Value Scale! (%f)", valueScale );

        // Return Error.
        return false;
    }

    // Set Value Scale/
    mValueScale = valueScale;

    // Return Okay.
    return true;
}

//-----------------------------------------------------------------------------

void ParticleAssetField::resetDataKeys(void)
{
    // Clear Data Keys.
    mDataKeys.clear();

    // Add default value Data-Key.
    addDataKey( 0.0f, mDefaultValue );
}

//-----------------------------------------------------------------------------

S32 ParticleAssetField::setSingleDataKey( const F32 value )
{
    // Clear Data Keys.
    mDataKeys.clear();

    // Add a single key with the specified value.
    return addDataKey( 0.0f, value );
}

//-----------------------------------------------------------------------------

S32 ParticleAssetField::addDataKey( const F32 time, const F32 value )
{
    // Check Max Time.
    if ( time > mMaxTime )
    {
        // Warn.
        Con::warnf("ParticleAssetField::addDataKey() - Time is out of bounds! (time:%f)", time );

        // Return Error.
        return -1;
    }

    // If data key exists already then set it and return the key index.
    U32 index = 0;
    for ( index = 0; index < getDataKeyCount(); index++ )
    {
        // Found Time?
        if ( mDataKeys[index].mTime == time )
        {
            // Yes, so set time.
            mDataKeys[index].mValue = value;

            // Return Index.
            return index;
        }
        // Past Time?
        else if ( mDataKeys[index].mTime > time )
            // Finish search.
            break;
    }

    // Insert Data-Key.
    mDataKeys.insert( index );

    // Set Data-Key.
    mDataKeys[index].mTime = time;
    mDataKeys[index].mValue = value;

    // Return Index.
    return index;
}

//-----------------------------------------------------------------------------

bool ParticleAssetField::removeDataKey( const U32 index )
{
    // Cannot Remove First Node!
    if ( index == 0 )
    {
        // Warn.
        Con::warnf("rParticleAssetField::emoveDataKey() - Cannot remove first Data-Key!");
        return false;
    }

    // Check Index.
    if ( index >= getDataKeyCount() )
    {
        // Warn.
        Con::warnf("rParticleAssetField::emoveDataKey() - Index out of range! (%d of %d)", index, getDataKeyCount()-1);
        return false;
    }

    // Remove Index.
    mDataKeys.erase(index);

    // Return Okay.
    return true;
}

//-----------------------------------------------------------------------------

void ParticleAssetField::clearDataKeys( void )
{
    // Reset Data Keys.
    resetDataKeys();
}

//-----------------------------------------------------------------------------

bool ParticleAssetField::setDataKeyValue( const U32 index, const F32 value )
{
    // Check Index.
    if ( index >= getDataKeyCount() )
    {
        // Warn.
        Con::warnf("ParticleAssetField::setDataKeyValue() - Index out of range! (%d of %d)", index, getDataKeyCount()-1);
        return false;
    }

    // Set Data Key Value.
    mDataKeys[index].mValue = value;

    // Return Okay.
    return true;
}

//-----------------------------------------------------------------------------

F32 ParticleAssetField::getDataKeyValue( const U32 index ) const
{
    // Check Index.
    if ( index >= getDataKeyCount() )
    {
        // Warn.
        Con::warnf("ParticleAssetField::getDataKeyValue() - Index out of range! (%d of %d)", index, getDataKeyCount()-1);
        return 0.0f;
    }

    // Return Data Key Value.
    return mDataKeys[index].mValue;
}

//-----------------------------------------------------------------------------

F32 ParticleAssetField::getDataKeyTime( const U32 index ) const
{
    // Check Index.
    if ( index >= getDataKeyCount() )
    {
        // Warn.
        Con::warnf("ParticleAssetField::getDataKeyTime() - Index out of range! (%d of %d)", index, getDataKeyCount()-1);
        return 0.0f;
    }

    // Return Data Key Time.
    return mDataKeys[index].mTime;
}

//-----------------------------------------------------------------------------

const ParticleAssetField::DataKey& ParticleAssetField::getDataKey( const U32 index ) const
{
    // Check Index.
    if ( index >= getDataKeyCount() )
    {
        // Warn.
        Con::warnf("ParticleAssetField::getDataKey() - Index out of range! (%d of %d)", index, getDataKeyCount()-1);
        return BadDataKey;
    }

    // Return Data-Key.
    return mDataKeys[index];
}

//-----------------------------------------------------------------------------

F32 ParticleAssetField::getFieldValue( F32 time ) const
{
    // Return First Entry if it's the only one or we're using zero time.
    if ( mIsZero(time) || getDataKeyCount() < 2)
        return mDataKeys[0].mValue * mValueScale;

    // Clamp Key-Time.
    time = getMin(getMax( 0.0f, time ), mMaxTime);

    // Repeat Time.
    time = mFmod( time * mRepeatTime, mMaxTime + FLT_EPSILON );

    // Fetch Max Key Index.
    const U32 maxKeyIndex = getDataKeyCount()-1;

    // Return Last Value if we're on/past the last time.
    if ( time >= mDataKeys[maxKeyIndex].mTime )
        return mDataKeys[maxKeyIndex].mValue * mValueScale;

    // Find Data-Key Indexes.
    U32 index1;
    U32 index2;
    for ( index1 = 0; index1 < getDataKeyCount(); index1++ )
        if ( mDataKeys[index1].mTime >= time )
            break;

    // If we're exactly on a Data-Key then return that key.
    if ( mIsEqual( mDataKeys[index1].mTime, time) )
        return mDataKeys[index1].mValue * mValueScale;

    // Set Adjacent Indexes.
    index2 = index1--;

    // Fetch Index Times.
    const F32 time1 = mDataKeys[index1].mTime;
    const F32 time2 = mDataKeys[index2].mTime;
    // Calculate Time Differential.
    const F32 dTime = (time-time1)/(time2-time1);

    // Return lerped Value.
    return ((mDataKeys[index1].mValue * (1.0f-dTime)) + (mDataKeys[index2].mValue * dTime)) * mValueScale;
}

//-----------------------------------------------------------------------------

F32 ParticleAssetField::calculateFieldBV( const ParticleAssetField& base, const ParticleAssetField& variation, const F32 effectAge, const bool modulate, const F32 modulo )
{
    // Fetch Graph Components.
    const F32 baseValue   = base.getFieldValue( effectAge );
    const F32 varValue    = variation.getFieldValue( effectAge ) * 0.5f;

    // Modulate?
    if ( modulate )
        // Return Modulo Calculation.
        return mFmod( baseValue + CoreMath::mGetRandomF(-varValue, varValue), modulo );
    else
        // Return Clamped Calculation.
        return mClampF( baseValue + CoreMath::mGetRandomF(-varValue, varValue), base.getMinValue(), base.getMaxValue() );
}

//-----------------------------------------------------------------------------

F32 ParticleAssetField::calculateFieldBVE( const ParticleAssetField& base, const ParticleAssetField& variation, const ParticleAssetField& effect, const F32 effectAge, const bool modulate, const F32 modulo )
{
    // Fetch Graph Components.
    const F32 baseValue   = base.getFieldValue( effectAge );
    const F32 varValue    = variation.getFieldValue( effectAge ) * 0.5f;
    const F32 effectValue = effect.getFieldValue( effectAge );

    // Modulate?
    if ( modulate )
        // Return Modulo Calculation.
        return mFmod( (baseValue + CoreMath::mGetRandomF(-varValue, varValue)) * effectValue, modulo );
    else
        // Return Clamped Calculation.
        return mClampF( (baseValue + CoreMath::mGetRandomF(-varValue, varValue)) * effectValue, base.getMinValue(), base.getMaxValue() );
}

//-----------------------------------------------------------------------------

F32 ParticleAssetField::calculateFieldBVLE( const ParticleAssetField& base, const ParticleAssetField& variation, const ParticleAssetField& overlife, const ParticleAssetField& effect, const F32 effectAge, const F32 particleAge, const bool modulate, const F32 modulo )
{
    // Fetch Graph Components.
    const F32 baseValue   = base.getFieldValue( effectAge );
    const F32 varValue    = variation.getFieldValue( effectAge ) * 0.5f;
    const F32 effectValue = effect.getFieldValue( effectAge );
    const F32 lifeValue   = overlife.getFieldValue( particleAge );

    // Modulate?
    if ( modulate )
        // Return Modulo Calculation.
        return mFmod( (baseValue + CoreMath::mGetRandomF(-varValue, varValue)) * effectValue * lifeValue, modulo );
    else
        // Return Clamped Calculation.
        return mClampF( (baseValue + CoreMath::mGetRandomF(-varValue, varValue)) * effectValue * lifeValue, base.getMinValue(), base.getMaxValue() );
}

//------------------------------------------------------------------------------

void ParticleAssetField::onTamlCustomWrite( TamlCustomNode* pCustomNode )
{
    // Debug Profiling.
    PROFILE_SCOPE(ParticleAssetField_OnTamlCustomWrite);

    // Add a child (ignore it if there ends up being no children).
    TamlCustomNode* pAssetField = pCustomNode->addNode( getFieldName(), true );

    // Sanity!
    AssertFatal( pAssetField != NULL, "ParticleAssetField::onTamlCustomWrite() - Could not create field." );

    if ( mValueBoundsDirty && (mNotEqual( getMinValue(), 0.0f ) || mNotEqual( getMaxValue(), 0.0f )) )
    {
        pAssetField->addField( particleAssetFieldMinValueName, getMinValue() );
        pAssetField->addField( particleAssetFieldMaxValueName, getMaxValue() );
    }
    
    if ( mValueBoundsDirty && mNotEqual( getMaxTime(), 1.0f ) )
        pAssetField->addField( particleAssetFieldMaxTimeName, getMaxTime() );

    if ( mValueBoundsDirty && mNotEqual( getDefaultValue(), 1.0f ) )
        pAssetField->addField( particleAssetFieldDefaultValueName, getDefaultValue() );

    if ( mNotEqual( getValueScale(), 1.0f ) )
        pAssetField->addField( particleAssetFieldValueScaleName, getValueScale() );

    if ( mNotEqual( getRepeatTime(), 1.0f ) )
        pAssetField->addField( particleAssetFieldRepeatTimeName, getRepeatTime() );

    // Fetch key count.
    const U32 keyCount = getDataKeyCount();

    // Finish if no data keys.
    if ( keyCount == 0 )
        return;

    // Finish if there's only one key and it's the default one.
    if ( keyCount == 1 && mIsEqual(mDataKeys[0].mTime, 0.0f) && mIsEqual(mDataKeys[0].mValue, mDefaultValue) )
        return;

    // Iterate the keys.
    for( U32 index = 0; index < keyCount; ++index )
    {
        // Fetch the data key.
        const DataKey& dataKey = mDataKeys[index];

        // Add a key node.
        TamlCustomNode* pKeyNode = pAssetField->addNode( particleAssetFieldDataKeyName );

        // Add key fields.
        pKeyNode->addField( particleAssetFieldDataKeyTimeName, dataKey.mTime );
        pKeyNode->addField( particleAssetFieldDataKeyValueName, dataKey.mValue );
    }
}

//-----------------------------------------------------------------------------

void ParticleAssetField::onTamlCustomRead( const TamlCustomNode* pCustomNode )
{
    // Debug Profiling.
    PROFILE_SCOPE(ParticleAssetField_OnTamlCustomRead);

    // Fetch existing values.
    F32 repeatTime = getRepeatTime();
    F32 maxTime = getMaxTime();
    F32 minValue = getMinValue();
    F32 maxValue = getMaxValue();
    F32 defaultValue = getDefaultValue();
    F32 valueScale = getValueScale();

    // Set-up a temporary set of keys.
    Vector<DataKey> keys;

    // Clear the existing keys.
    mDataKeys.clear();

    // Fetch fields.
    const TamlCustomFieldVector& fields = pCustomNode->getFields();

    // Iterate fields.
    for ( TamlCustomFieldVector::const_iterator fieldItr = fields.begin(); fieldItr != fields.end(); ++fieldItr )
    {
        // Fetch field.
        TamlCustomField* pField = *fieldItr;

        // Fetch property field name.
        StringTableEntry fieldName = pField->getFieldName();

        if ( fieldName == particleAssetFieldRepeatTimeName )
        {
            pField->getFieldValue( repeatTime );
        }
        else if ( fieldName == particleAssetFieldMaxTimeName )
        {
            pField->getFieldValue( maxTime );
            mValueBoundsDirty = true;
        }
        else if ( fieldName == particleAssetFieldMinValueName )
        {
            pField->getFieldValue( minValue );
            mValueBoundsDirty = true;
        }
        else if ( fieldName == particleAssetFieldMaxValueName )
        {
            pField->getFieldValue( maxValue );
            mValueBoundsDirty = true;
        }
        else if ( fieldName == particleAssetFieldDefaultValueName )
        {
            pField->getFieldValue( defaultValue );
            mValueBoundsDirty = true;
        }
        else if ( fieldName == particleAssetFieldValueScaleName )
        {
            pField->getFieldValue( valueScale );
        }
        else if ( fieldName == particleAssetFieldDataKeysName )
        {
            const char* pDataKeys = pField->getFieldValue();
            const S32 elementCount = StringUnit::getUnitCount( pDataKeys, " ,\t" );

            // Are there a valid number of elements?
            if ( elementCount < 2 || (elementCount % 2 ) != 0 )
            {
                // No, so warn.
                Con::warnf( "ParticleAssetField::onTamlCustomRead() - An invalid set of data keys was found." );
            }
            else
            {
                // Iterate the elements.
                for( S32 elementIndex = 0; elementIndex <= (elementCount-2); elementIndex += 2 )
                {
                    DataKey key;
                    key.mTime = dAtof( StringUnit::getUnit( pDataKeys, elementIndex, " ,\t" ) );
                    key.mValue = dAtof( StringUnit::getUnit( pDataKeys, elementIndex+1, " ,\t" ) );
                    keys.push_back( key );
                }
            }            
        }
    }

    // Fetch any children.
    const TamlCustomNodeVector& children = pCustomNode->getChildren();

    // Iterate node children.
    for( TamlCustomNodeVector::const_iterator childItr = children.begin(); childItr != children.end(); ++childItr )
    {
        // Fetch node.
        TamlCustomNode* pKeyNode = *childItr;

        // Ignore anything that isn't a key.
        if ( pKeyNode->getNodeName() != particleAssetFieldDataKeyName )
            continue;

        // Fetch the fields.
        const TamlCustomField* pTimeField = pKeyNode->findField( particleAssetFieldDataKeyTimeName );
        const TamlCustomField* pValueField = pKeyNode->findField( particleAssetFieldDataKeyValueName );

        // Did we find the fields?
        if ( pTimeField == NULL || pValueField == NULL )
        {
            // No, so warn.
            Con::warnf("ParticleAssetField::onTamlCustomRead() - Found a key but it did not have a time and value field." );

            continue;
        }

        // Read key.
        DataKey key;
        pTimeField->getFieldValue( key.mTime );
        pValueField->getFieldValue( key.mValue );
        keys.push_back( key );
    }

    // Set the value bounds.
    setValueBounds( maxTime, minValue, maxValue, defaultValue );

    // Set the value scale.
    setValueScale( valueScale );

    // Set the repeat time.
    setRepeatTime( repeatTime );

    // Set the data keys.
    mDataKeys = keys;
}

//-----------------------------------------------------------------------------

void ParticleAssetField::WriteCustomTamlSchema( const AbstractClassRep* pClassRep, TiXmlElement* pParentElement )
{
    // Sanity!
    AssertFatal( pClassRep != NULL,  "ParticleAssetField::WriteCustomTamlSchema() - ClassRep cannot be NULL." );
    AssertFatal( pParentElement != NULL,  "ParticleAssetField::WriteCustomTamlSchema() - Parent Element cannot be NULL." );

    // Create Field element.
    TiXmlElement* pFieldElement = new TiXmlElement( "xs:element" );
    pFieldElement->SetAttribute( "name", getFieldName() );
    pFieldElement->SetAttribute( "minOccurs", 0 );
    pFieldElement->SetAttribute( "maxOccurs", 1 );
    pParentElement->LinkEndChild( pFieldElement );

    // Create complex type Element.
    TiXmlElement* pFieldComplexTypeElement = new TiXmlElement( "xs:complexType" );
    pFieldElement->LinkEndChild( pFieldComplexTypeElement );

    // Create choice element.
    TiXmlElement* pFieldChoiceElement = new TiXmlElement( "xs:choice" );
    pFieldChoiceElement->SetAttribute( "minOccurs", 0 );
    pFieldChoiceElement->SetAttribute( "maxOccurs", 1 );
    pFieldComplexTypeElement->LinkEndChild( pFieldChoiceElement );

    // Create key element.
    TiXmlElement* pKeyElement = new TiXmlElement( "xs:element" );
    pKeyElement->SetAttribute( "name", particleAssetFieldDataKeyName );
    pKeyElement->SetAttribute( "minOccurs", 0 );
    pKeyElement->SetAttribute( "maxOccurs", "unbounded" );
    pFieldChoiceElement->LinkEndChild( pKeyElement );

    // Create complex type Element.
    TiXmlElement* pKeyComplexTypeElement = new TiXmlElement( "xs:complexType" );
    pKeyElement->LinkEndChild( pKeyComplexTypeElement );

    // Create "Time" attribute.
    TiXmlElement* pKeyTimeAttribute = new TiXmlElement( "xs:attribute" );
    pKeyTimeAttribute->SetAttribute( "name", particleAssetFieldDataKeyTimeName );
    pKeyComplexTypeElement->LinkEndChild( pKeyTimeAttribute );
    TiXmlElement* pKeyTimeSimpleType = new TiXmlElement( "xs:simpleType" );
    pKeyTimeAttribute->LinkEndChild( pKeyTimeSimpleType );
    TiXmlElement* pKeyTimeRestriction = new TiXmlElement( "xs:restriction" );
    pKeyTimeRestriction->SetAttribute( "base", "xs:float" );
    pKeyTimeSimpleType->LinkEndChild( pKeyTimeRestriction );
    TiXmlElement* pKeyTimeMinRestriction = new TiXmlElement( "xs:minInclusive" );
    pKeyTimeMinRestriction->SetAttribute( "value", "0" );
    pKeyTimeRestriction->LinkEndChild( pKeyTimeMinRestriction );

    // Create "Value" attribute.
    TiXmlElement* pKeyValueAttribute = new TiXmlElement( "xs:attribute" );
    pKeyValueAttribute->SetAttribute( "name", particleAssetFieldDataKeyValueName );
    pKeyValueAttribute->SetAttribute( "type", "xs:float" );
    pKeyComplexTypeElement->LinkEndChild( pKeyValueAttribute );

    // Create "Min Value" attribute.
    TiXmlElement* pFieldMinValue = new TiXmlElement( "xs:attribute" );
    pFieldMinValue->SetAttribute( "name", particleAssetFieldMinValueName );
    pFieldMinValue->SetAttribute( "type", "xs:float" );
    pFieldComplexTypeElement->LinkEndChild( pFieldMinValue );

    // Create "Max Value" attribute.
    TiXmlElement* pFieldMaxValue = new TiXmlElement( "xs:attribute" );
    pFieldMaxValue->SetAttribute( "name", particleAssetFieldMaxValueName );
    pFieldMaxValue->SetAttribute( "type", "xs:float" );
    pFieldComplexTypeElement->LinkEndChild( pFieldMaxValue );

    // Create "Max Time" attribute.
    TiXmlElement* pFieldMaxTime = new TiXmlElement( "xs:attribute" );
    pFieldMaxTime->SetAttribute( "name", particleAssetFieldMaxTimeName );
    pFieldMaxTime->SetAttribute( "type", "xs:float" );
    pFieldComplexTypeElement->LinkEndChild( pFieldMaxTime );

    // Create "Default Value" attribute.
    TiXmlElement* pFieldDefaultValue = new TiXmlElement( "xs:attribute" );
    pFieldDefaultValue->SetAttribute( "name", particleAssetFieldDefaultValueName );
    pFieldDefaultValue->SetAttribute( "type", "xs:float" );
    pFieldComplexTypeElement->LinkEndChild( pFieldDefaultValue );

    // Create "Value Scale" attribute.
    TiXmlElement* pFieldValueScale = new TiXmlElement( "xs:attribute" );
    pFieldValueScale->SetAttribute( "name", particleAssetFieldValueScaleName );
    pFieldValueScale->SetAttribute( "type", "xs:float" );
    pFieldComplexTypeElement->LinkEndChild( pFieldValueScale );

    // Create "Repeat Time" attribute.
    TiXmlElement* pFieldRepeatTime = new TiXmlElement( "xs:attribute" );
    pFieldRepeatTime->SetAttribute( "name", particleAssetFieldRepeatTimeName );
    pFieldRepeatTime->SetAttribute( "type", "xs:float" );
    pFieldComplexTypeElement->LinkEndChild( pFieldRepeatTime );
}
