/* Copyright (C) 2005-2007 Feeling Software Inc. Portions of the code are: Copyright (C) 2005-2007 Sony Computer Entertainment America MIT License: http://www.opensource.org/licenses/mit-license.php */ #include "StdAfx.h" #include "FCDocument/FCDocument.h" #include "FCDocument/FCDLightTools.h" #include "FCDocument/FCDLight.h" #include "FCDocument/FCDAnimated.h" #include "FCDocument/FCDAnimation.h" #include "FCDocument/FCDAnimationCurve.h" #include "FCDocument/FCDAnimationCurveTools.h" #include "FCDocument/FCDAnimationKey.h" #include "FCDocument/FCDAnimationMultiCurve.h" #include "FCDocument/FCDAnimationChannel.h" #include "FCDocument/FCDLibrary.h" namespace FCDLightTools { void LoadPenumbra(FCDLight* light, float penumbraValue, FCDAnimated* penumbraAnimated, bool createAnimationChannel) { bool fallOffIsOuter = false; // Take care of the default non-animated values float fallOffAngle = light->GetFallOffAngle(); if (penumbraValue < 0) { penumbraValue = -penumbraValue; fallOffAngle -= 2*penumbraValue; light->SetFallOffAngle(fallOffAngle); fallOffIsOuter = true; } float outerAngle = fallOffAngle + 2*penumbraValue; light->SetOuterAngle(outerAngle); // Take care of the animated values FCDParameterAnimatableFloat& outerAngleValue = light->GetOuterAngle(); FCDAnimated* outerAngleAnimated = outerAngleValue.GetAnimated(); if (penumbraAnimated->HasCurve() && penumbraAnimated != outerAngleAnimated) { penumbraAnimated->Clone(outerAngleAnimated); } FCDParameterAnimatableFloat& fallOffAngleValue = light->GetFallOffAngle(); FCDAnimated* fallOffAngleAnimated = fallOffAngleValue.GetAnimated(); FCDAnimationCurve* outerAngleCurve = outerAngleAnimated->GetCurve(0); FCDAnimationCurve* fallOffAngleCurve = fallOffAngleAnimated->GetCurve(0); // no animations on the penumbra nor the fallOffAngle, so no animation needed if (outerAngleCurve == NULL && fallOffAngleCurve == NULL) return; // there's animation on the fallOffAngle only, so just need to copy the animation to the outterAngle as well if (outerAngleCurve == NULL) { fallOffAngleAnimated->Clone(outerAngleAnimated); FUAssert(outerAngleAnimated != NULL, return); // should not be null because we know fallOffAngle is animated outerAngleAnimated->SetTargetObject(light); // if we penumbra was negative, fallOffAngle needs to be lowered, else outerAngle needs to be raised float offset = 0.0f; FCDAnimated* animated = NULL; if (fallOffIsOuter) { offset = -2 * penumbraValue; animated = fallOffAngleAnimated; } else { offset = 2 * penumbraValue; animated = outerAngleAnimated; } // now the outerAngle has the penumbra on it too, so have to update the keys // add twice the penumbra to the fallOffAngle to get the OuterAngle FCDConversionOffsetFunctor offsetFunctor(offset); FCDAnimationCurveTrackList& curves = animated->GetCurves()[0]; size_t curvesCount = curves.size(); for (size_t curvesCounter = 0; curvesCounter < curvesCount; curvesCounter++) { curves.at(curvesCounter)->ConvertValues(&offsetFunctor, &offsetFunctor); } return; } outerAngleAnimated->SetTargetObject(light); // at this point, outerAngleAnimated != NULL // FIXME: [aleung] This is not necessarily true, but close enough for now size_t outerAngleKeysCount = outerAngleCurve->GetKeyCount(); FCDAnimationKey** outerAngleKeys = outerAngleCurve->GetKeys(); bool hasNegPenumbra = fallOffIsOuter; if (!hasNegPenumbra) { for (size_t i = 0; i < outerAngleKeysCount; i++) { if (outerAngleKeys[i]->output < 0) { hasNegPenumbra = true; break; } } } // there's animation on the penumbra only and there's no negative values, so we just need to animate the outerAngle if ((fallOffAngleCurve == NULL) && !hasNegPenumbra) { FCDAnimationCurveListList& curvesList = outerAngleAnimated->GetCurves(); // fall off is not animated, easy case FCDConversionScaleFunctor scaleFunctor(2); FCDConversionOffsetFunctor offsetFunctor(fallOffAngleValue); FCDAnimationCurveTrackList& curves = curvesList[0]; size_t curvesCount = curves.size(); for (size_t curvesCounter = 0; curvesCounter < curvesCount; curvesCounter++) { curves.at(curvesCounter)->ConvertValues(&scaleFunctor, &scaleFunctor); curves.at(curvesCounter)->ConvertValues(&offsetFunctor, &offsetFunctor); } return; } // at this point, we need curves for both fallOffAngle and outerAngle! if (fallOffAngleCurve == NULL) { if (createAnimationChannel) { outerAngleValue.GetAnimated()->Clone(fallOffAngleAnimated); } FUAssert(fallOffAngleAnimated != NULL, return); // should not be null because outerAngle is animated fallOffAngleAnimated->SetTargetObject(light); fallOffAngleCurve = fallOffAngleAnimated->GetCurve(0); if (fallOffAngleCurve == NULL) { FUAssert(createAnimationChannel, ;); // should only happen if we created new animated fallOffAngleAnimated->RemoveCurve(0); FCDAnimation* animation = light->GetDocument()->GetAnimationLibrary()->AddEntity(); animation->SetDaeId("compensate_falloff_for_penumbra"); FCDAnimationChannel* channel = animation->AddChannel(); fallOffAngleCurve = channel->AddCurve(); outerAngleCurve->Clone(fallOffAngleCurve); fallOffAngleAnimated->AddCurve(0, fallOffAngleCurve); } // remove the keys FCDAnimationCurveTrackList& curves = fallOffAngleAnimated->GetCurves()[0]; size_t curvesCount = curves.size(); for (size_t i = 0; i < curvesCount; i++) { curves.at(i)->SetKeyCount(0, FUDaeInterpolation::BEZIER); // add keys at the beginning and end with constant pre/post infinity // the output value should be outerAngle if fallOffIsOuter, otherwise, it should be fallOffAngle curves.at(i)->SetPreInfinity(FUDaeInfinity::CONSTANT); curves.at(i)->SetPostInfinity(FUDaeInfinity::CONSTANT); FCDAnimationKey* firstKey = curves.at(i)->AddKey(FUDaeInterpolation::LINEAR); firstKey->input = outerAngleKeys[0]->input; firstKey->output = (fallOffIsOuter)? light->GetOuterAngle() : light->GetFallOffAngle(); FCDAnimationKey* lastKey = curves.at(i)->AddKey(FUDaeInterpolation::LINEAR); lastKey->input = outerAngleKeys[(outerAngleKeysCount > 1)? outerAngleKeysCount - 1 : 0]->input; lastKey->output = (fallOffIsOuter)? light->GetOuterAngle() : light->GetFallOffAngle(); } } // now we have both penumbra and fallOffAngle animated, have to create missing keys by merging the curves FloatList multiCurveValues(2, 0.0f); FCDAnimationCurveConstList multiCurveCurves(2); multiCurveValues[0] = fallOffAngleValue; multiCurveValues[1] = outerAngleValue; multiCurveCurves[0] = fallOffAngleCurve; multiCurveCurves[1] = outerAngleCurve; FCDAnimationMultiCurve* multiCurve = FCDAnimationCurveTools::MergeCurves(multiCurveCurves, multiCurveValues); size_t dimension = multiCurve->GetDimension(); FUAssert(dimension == 2, return); // go through the keys to see if there's any switches in signs. If so, we need to insert intermediate keys to the // outerAngleCurve (penumbra) at the penumbra = 0 point to get desired result { size_t multiKeyCount = multiCurve->GetKeyCount(); FUAssert(multiKeyCount > 0, return); // should not have no keys FCDAnimationMKey** multiKeys = multiCurve->GetKeys(); size_t additionalKeys = 0; bool isNegative = multiKeys[0]->output[1] < 0; bool wasZero = IsEquivalent(multiKeys[0]->output[1], 0.0f); bool hasFlip = false; for (size_t i = 1; i < multiKeyCount; i++) // start from 1 since we won't add key before the first key { FCDAnimationMKey* key = multiKeys[i]; if (IsEquivalent(key->output[1], 0.0f)) { i++; if (i < multiKeyCount) { isNegative = multiKeys[i]->output[1] < 0; wasZero = IsEquivalent(multiKeys[i]->output[1], 0.0f); } continue; } else { if (wasZero) { isNegative = key->output[1] < 0; wasZero = false; continue; } wasZero = false; } if ((isNegative && (key->output[1] > 0)) || (!isNegative && (key->output[1] < 0))) { hasFlip = true; isNegative = !isNegative; // flip in sign, find out where to add the additional key using binary search float higherTime = key->input; float lowerTime = multiKeys[i-1]->input; // will not fail since the first sign is taken float output[2]; float currentTime = (higherTime + lowerTime) / 2.0f; multiCurve->Evaluate(currentTime, output); while (!IsEquivalent(output[1], 0.0f)) // while penumbra output != 0 { if ((isNegative && (output[1] < 0)) || (!isNegative && (output[1] > 0))) { higherTime = currentTime; } else { lowerTime = currentTime; } currentTime = (higherTime + lowerTime) / 2.0f; multiCurve->Evaluate(currentTime, output); } size_t index = i + additionalKeys; FCDAnimationKey* newKey = outerAngleCurve->AddKey((FUDaeInterpolation::Interpolation)multiKeys[i-1]->interpolation, currentTime, index); additionalKeys++; newKey->input = currentTime; newKey->output = 0.0f; if (key->interpolation == FUDaeInterpolation::BEZIER) { FCDAnimationKeyBezier* newBkey = (FCDAnimationKeyBezier*)newKey; float previousInput = multiKeys[i-1]->input; float nextInput = multiKeys[i]->input; newBkey->inTangent.x = (previousInput + 2.0f * newBkey->input) / 3.0f; newBkey->outTangent.x = (nextInput + 2.0f * newBkey->input) / 3.0f; // calculate estimate inTangents float prevDistance = newBkey->input - newBkey->inTangent.x; float prevDelta = prevDistance / 1000; // small enough to get an estimate multiCurve->Evaluate(currentTime - prevDelta, output); float slope = output[1] / prevDelta; newBkey->inTangent.y = prevDistance * slope; // calculate estimate outTangents float nextDistance = newBkey->outTangent.x - newBkey->input; float nextDelta = nextDistance / 1000; // small enough to get an estimate multiCurve->Evaluate(currentTime + nextDelta, output); slope = output[1] / nextDelta; newBkey->outTangent.y = nextDistance * slope; } } } if (hasFlip) { // recompute the multiCurve with the intermediate values multiCurve = FCDAnimationCurveTools::MergeCurves(multiCurveCurves, multiCurveValues); dimension = multiCurve->GetDimension(); FUAssert(dimension == 2, return); } } // erase the current keys fallOffAngleCurve->SetKeyCount(0, FUDaeInterpolation::BEZIER); outerAngleCurve->SetKeyCount(0, FUDaeInterpolation::BEZIER); // add the new keys and set their input and output values FCDAnimationMKey** keys = multiCurve->GetKeys(); size_t keyCount = multiCurve->GetKeyCount(); for (size_t i = 0; i < keyCount; i++) { FCDAnimationMKey* key = keys[i]; FCDAnimationKey* fallOffAngleKey = fallOffAngleCurve->AddKey((FUDaeInterpolation::Interpolation)key->interpolation); FCDAnimationKey* outerAngleKey = outerAngleCurve->AddKey((FUDaeInterpolation::Interpolation)key->interpolation); fallOffAngleKey->input = key->input; outerAngleKey->input = key->input; float offset = key->output[0]; fallOffAngleKey->output = offset; outerAngleKey->output = offset + 2*key->output[1]; if (key->interpolation == FUDaeInterpolation::BEZIER) { FCDAnimationMKeyBezier* bkey = (FCDAnimationMKeyBezier*)key; FCDAnimationKeyBezier* fallOffAngleBkey = (FCDAnimationKeyBezier*) fallOffAngleKey; FCDAnimationKeyBezier* outerAngleBkey = (FCDAnimationKeyBezier*) outerAngleKey; fallOffAngleBkey->inTangent = bkey->inTangent[0]; fallOffAngleBkey->outTangent = bkey->outTangent[0]; outerAngleBkey->inTangent = bkey->inTangent[1]; outerAngleBkey->outTangent = bkey->outTangent[1]; outerAngleBkey->inTangent.v = 2*outerAngleBkey->inTangent.v; // deal with the offset in next pass outerAngleBkey->outTangent.v = 2*outerAngleBkey->outTangent.v; // deal with the offset in next pass float inTangentsOffset = (fallOffAngleBkey->inTangent.y - fallOffAngleBkey->output) / (fallOffAngleBkey->inTangent.x - fallOffAngleBkey->input); float outTangentsOffset = (fallOffAngleBkey->outTangent.y - fallOffAngleBkey->output) / (fallOffAngleBkey->outTangent.x - fallOffAngleBkey->input); outerAngleBkey->outTangent.y += offset + outTangentsOffset * (outerAngleBkey->outTangent.x - outerAngleBkey->input); outerAngleBkey->inTangent.y += offset + inTangentsOffset * (outerAngleBkey->inTangent.x - outerAngleBkey->input); } } // flip the keys (and their tangents) if necessary FCDAnimationKey** finalFallOffAngleKeys = fallOffAngleCurve->GetKeys(); FCDAnimationKey** finalOuterAngleKeys = outerAngleCurve->GetKeys(); bool previousFlipKeys = false; FCDAnimationKeyBezier* previousFallOffAngleBkey = NULL; FCDAnimationKeyBezier* previousOuterAngleBkey = NULL; for (size_t i = 0; i < keyCount; i++) { FCDAnimationKey* fallOffAngleKey = finalFallOffAngleKeys[i]; FCDAnimationKey* outerAngleKey = finalOuterAngleKeys[i]; // FIXME: [aleung] this is not necessarily enough, but close enough for now bool flipKeys = false; if (outerAngleKey->output < fallOffAngleKey->output) { float temp = fallOffAngleKey->output; fallOffAngleKey->output = outerAngleKey->output; outerAngleKey->output = temp; flipKeys = true; } if (fallOffAngleKey->interpolation == FUDaeInterpolation::BEZIER) // both same interpolation { FCDAnimationKeyBezier* fallOffAngleBkey = (FCDAnimationKeyBezier*) fallOffAngleKey; FCDAnimationKeyBezier* outerAngleBkey = (FCDAnimationKeyBezier*) outerAngleKey; if (flipKeys) { // flip outTangents FMVector2 temp = outerAngleBkey->outTangent; outerAngleBkey->outTangent = fallOffAngleBkey->outTangent; fallOffAngleBkey->outTangent = temp; temp = outerAngleBkey->inTangent; outerAngleBkey->inTangent = fallOffAngleBkey->inTangent; fallOffAngleBkey->inTangent = temp; if ((!previousFlipKeys) && (i != 0)) { // have to flip their outTangents if (previousFallOffAngleBkey != NULL) { temp = previousOuterAngleBkey->outTangent; previousOuterAngleBkey->outTangent = previousFallOffAngleBkey->outTangent; previousFallOffAngleBkey->outTangent = temp; } } } else if (previousFlipKeys) // we need to flip our inTangents even though we did not flip { // flip inTangents FMVector2 temp = outerAngleBkey->inTangent; outerAngleBkey->inTangent = fallOffAngleBkey->inTangent; fallOffAngleBkey->inTangent = temp; } previousFallOffAngleBkey = fallOffAngleBkey; previousOuterAngleBkey = outerAngleBkey; } else { previousFallOffAngleBkey = NULL; previousOuterAngleBkey = NULL; } previousFlipKeys = flipKeys; } } }