|
| 1 | +/* |
| 2 | +* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved. |
| 3 | +* |
| 4 | +* Permission is hereby granted, free of charge, to any person obtaining a |
| 5 | +* copy of this software and associated documentation files (the "Software"), |
| 6 | +* to deal in the Software without restriction, including without limitation |
| 7 | +* the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| 8 | +* and/or sell copies of the Software, and to permit persons to whom the |
| 9 | +* Software is furnished to do so, subject to the following conditions: |
| 10 | +* |
| 11 | +* The above copyright notice and this permission notice shall be included in |
| 12 | +* all copies or substantial portions of the Software. |
| 13 | +* |
| 14 | +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 15 | +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 16 | +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| 17 | +* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 18 | +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| 19 | +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
| 20 | +* DEALINGS IN THE SOFTWARE. |
| 21 | +*/ |
| 22 | + |
| 23 | +#pragma once |
| 24 | + |
| 25 | +#include "../rtx_graph_component_macros.h" |
| 26 | +#include "animation_utils.h" |
| 27 | + |
| 28 | +namespace dxvk { |
| 29 | +namespace components { |
| 30 | + |
| 31 | +#define LIST_INPUTS(X) \ |
| 32 | + X(RtComponentPropertyType::Float, 0.0f, value, "Value", "The input value to interpolate.") \ |
| 33 | + X(RtComponentPropertyType::Float, 0.0f, inputMin, "Input Min", "If `Value` equals `Input Min`, the output will be `Output Min`.") \ |
| 34 | + X(RtComponentPropertyType::Float, 1.0f, inputMax, "Input Max", "If `Value` equals `Input Max`, the output will be `Output Max`.") \ |
| 35 | + X(RtComponentPropertyType::Bool, false, clampInput, "Clamp Input", "If true, `value` will be clamped to the input range.", property.optional = true) \ |
| 36 | + X(RtComponentPropertyType::Uint32, static_cast<uint32_t>(InterpolationType::Linear), easingType, "Easing Type", \ |
| 37 | + "The type of easing to apply.", \ |
| 38 | + property.enumValues = kInterpolationTypeEnumValues) \ |
| 39 | + X(RtComponentPropertyType::Bool, false, shouldReverse, "Should Reverse", "If true, the easing is applied backwards. If `Value` is coming from a loopFloat component that is using `pingpong`, hook this up to `isReversing` from that component.", property.optional = true) \ |
| 40 | + X(RtComponentPropertyType::NumberOrVector, 0.0f, outputMin, "Output Min", "What a `Value` of `Input Min` maps to.") \ |
| 41 | + X(RtComponentPropertyType::NumberOrVector, 1.0f, outputMax, "Output Max", "What a `Value` of `Input Max` maps to.") |
| 42 | + |
| 43 | +#define LIST_STATES(X) |
| 44 | + |
| 45 | +#define LIST_OUTPUTS(X) \ |
| 46 | + X(RtComponentPropertyType::NumberOrVector, 0.0f, interpolatedValue, "Interpolated Value", "The final interpolated value after applying input normalization, easing, and output mapping.") |
| 47 | + |
| 48 | +// Manually declaring the class to allow for custom applySceneOverrides method |
| 49 | +template <RtComponentPropertyType rangePropertyType> |
| 50 | +class Interpolate : public RtRegisteredComponentBatch<Interpolate<rangePropertyType>> { |
| 51 | +private: |
| 52 | + static constexpr RtComponentPropertyType valuePropertyType = RtComponentPropertyType::Float; |
| 53 | + static constexpr RtComponentPropertyType inputMinPropertyType = RtComponentPropertyType::Float; |
| 54 | + static constexpr RtComponentPropertyType inputMaxPropertyType = RtComponentPropertyType::Float; |
| 55 | + static constexpr RtComponentPropertyType clampInputPropertyType = RtComponentPropertyType::Bool; |
| 56 | + static constexpr RtComponentPropertyType easingTypePropertyType = RtComponentPropertyType::Uint32; |
| 57 | + static constexpr RtComponentPropertyType shouldReversePropertyType = RtComponentPropertyType::Bool; |
| 58 | + static constexpr RtComponentPropertyType outputMinPropertyType = rangePropertyType; |
| 59 | + static constexpr RtComponentPropertyType outputMaxPropertyType = rangePropertyType; |
| 60 | + static constexpr RtComponentPropertyType interpolatedValuePropertyType = rangePropertyType; |
| 61 | + REMIX_COMPONENT_BODY( |
| 62 | + /* the Component class name */ Interpolate, |
| 63 | + /* the UI name */ "Interpolate", |
| 64 | + /* the UI categories */ "Transform", |
| 65 | + /* the doc string */ "Interpolates a value from an input range to an output range with optional easing. " \ |
| 66 | + "\nCombines normalization (reverse LERP), easing, and mapping (LERP) into a single component. " \ |
| 67 | + "\n\nNote input values outside of input range are valid, and that easing can lead to the output value being " \ |
| 68 | + "outside of the output range even when input is inside the input range." \ |
| 69 | + "\n Inverted ranges (max < min) are supported, but the results are undefined and may change without warning.", |
| 70 | + /* the version number */ 1, |
| 71 | + LIST_INPUTS, LIST_STATES, LIST_OUTPUTS |
| 72 | + ) |
| 73 | + void Interpolate::updateRange(const Rc<DxvkContext>& context, const size_t start, const size_t end) { |
| 74 | + for (size_t i = start; i < end; i++) { |
| 75 | + // Step 1: Normalize input value to 0-1 range (reverse LERP / float_to_strength) |
| 76 | + float normalizedValue = m_value[i]; |
| 77 | + if (m_inputMax[i] == m_inputMin[i]) { |
| 78 | + ONCE(Logger::err(str::format("InterpolateFloat: Input Min and Input Max are the same. Setting normalized value to 0.0f. Input Min: ", m_inputMin[i], " Input Max: ", m_inputMax[i]))); |
| 79 | + normalizedValue = 0.0f; // Avoid division by zero |
| 80 | + } else { |
| 81 | + if (m_clampInput[i]) { |
| 82 | + normalizedValue = clamp(normalizedValue, m_inputMin[i], m_inputMax[i]); |
| 83 | + } |
| 84 | + normalizedValue = (normalizedValue - m_inputMin[i]) / (m_inputMax[i] - m_inputMin[i]); |
| 85 | + } |
| 86 | + |
| 87 | + // cache in a const value to avoid double branch |
| 88 | + const bool shouldReverse = m_shouldReverse[i]; |
| 89 | + |
| 90 | + // Step 2: Apply easing (ease component logic) |
| 91 | + // If should reverse, flip the input value |
| 92 | + if (shouldReverse) { |
| 93 | + normalizedValue = 1.0f - normalizedValue; |
| 94 | + } |
| 95 | + |
| 96 | + // Apply the easing |
| 97 | + float easedValue = applyInterpolation(static_cast<InterpolationType>(m_easingType[i]), normalizedValue); |
| 98 | + |
| 99 | + // If should reverse, flip the output back |
| 100 | + if (shouldReverse) { |
| 101 | + easedValue = 1.0f - easedValue; |
| 102 | + } |
| 103 | + |
| 104 | + // Step 3: Map eased value to output range (LERP / strength_to_float) |
| 105 | + m_interpolatedValue[i] = lerp(m_outputMin[i], m_outputMax[i], easedValue); |
| 106 | + } |
| 107 | + } |
| 108 | +}; |
| 109 | + |
| 110 | +#undef LIST_INPUTS |
| 111 | +#undef LIST_STATES |
| 112 | +#undef LIST_OUTPUTS |
| 113 | + |
| 114 | + |
| 115 | +namespace { |
| 116 | + // Instantiate all of the typed variants we want to support. |
| 117 | + template class Interpolate<RtComponentPropertyType::Float>; |
| 118 | + template class Interpolate<RtComponentPropertyType::Float2>; |
| 119 | + template class Interpolate<RtComponentPropertyType::Float3>; |
| 120 | + template class Interpolate<RtComponentPropertyType::Color4>; |
| 121 | +} // namespace |
| 122 | + |
| 123 | +} // namespace components |
| 124 | +} // namespace dxvk |
| 125 | + |
0 commit comments