├── .gitattributes ├── Images ├── Ease.jpg ├── Constant.jpg ├── Linear.jpg ├── ColorNode.gif ├── ColorRampNode_Islands.png ├── ColorRampNode_AppleAnimation.gif ├── ColorRampNode_Rainbow_Graph.png ├── ColorRampNode_Rainbow_Details.png ├── ColorRampNode_AppleAnimation_Graph.png ├── InterpolationsGraphs.svg └── InterpolationsGraphs.md ├── Resources └── Icon128.png ├── Content ├── M_ColorRamp_Islands.uasset ├── M_ColorRamp_Gradients.uasset ├── SM_Apple_MorphTargets.uasset ├── T_Grass_Toon_Seamless_01.uasset ├── T_Sand_Toon_Seamless_01.uasset └── M_ColorRamp_VertexAnimation_MorphTargets.uasset ├── Config └── DefaultColorRamp.ini ├── Source └── ColorRamp │ ├── Private │ ├── ColorRamp.cpp │ └── MaterialExpressionMoxColorRamp.cpp │ ├── Public │ ├── ColorRamp.h │ └── MaterialExpressionMoxColorRamp.h │ └── ColorRamp.Build.cs ├── ColorRamp.uplugin ├── .gitignore ├── .github └── workflows │ └── release.yml └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /Images/Ease.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoxAlehin/ColorRamp/HEAD/Images/Ease.jpg -------------------------------------------------------------------------------- /Images/Constant.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoxAlehin/ColorRamp/HEAD/Images/Constant.jpg -------------------------------------------------------------------------------- /Images/Linear.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoxAlehin/ColorRamp/HEAD/Images/Linear.jpg -------------------------------------------------------------------------------- /Images/ColorNode.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoxAlehin/ColorRamp/HEAD/Images/ColorNode.gif -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoxAlehin/ColorRamp/HEAD/Resources/Icon128.png -------------------------------------------------------------------------------- /Content/M_ColorRamp_Islands.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoxAlehin/ColorRamp/HEAD/Content/M_ColorRamp_Islands.uasset -------------------------------------------------------------------------------- /Images/ColorRampNode_Islands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoxAlehin/ColorRamp/HEAD/Images/ColorRampNode_Islands.png -------------------------------------------------------------------------------- /Content/M_ColorRamp_Gradients.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoxAlehin/ColorRamp/HEAD/Content/M_ColorRamp_Gradients.uasset -------------------------------------------------------------------------------- /Content/SM_Apple_MorphTargets.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoxAlehin/ColorRamp/HEAD/Content/SM_Apple_MorphTargets.uasset -------------------------------------------------------------------------------- /Content/T_Grass_Toon_Seamless_01.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoxAlehin/ColorRamp/HEAD/Content/T_Grass_Toon_Seamless_01.uasset -------------------------------------------------------------------------------- /Content/T_Sand_Toon_Seamless_01.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoxAlehin/ColorRamp/HEAD/Content/T_Sand_Toon_Seamless_01.uasset -------------------------------------------------------------------------------- /Images/ColorRampNode_AppleAnimation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoxAlehin/ColorRamp/HEAD/Images/ColorRampNode_AppleAnimation.gif -------------------------------------------------------------------------------- /Images/ColorRampNode_Rainbow_Graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoxAlehin/ColorRamp/HEAD/Images/ColorRampNode_Rainbow_Graph.png -------------------------------------------------------------------------------- /Images/ColorRampNode_Rainbow_Details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoxAlehin/ColorRamp/HEAD/Images/ColorRampNode_Rainbow_Details.png -------------------------------------------------------------------------------- /Images/ColorRampNode_AppleAnimation_Graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoxAlehin/ColorRamp/HEAD/Images/ColorRampNode_AppleAnimation_Graph.png -------------------------------------------------------------------------------- /Content/M_ColorRamp_VertexAnimation_MorphTargets.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MoxAlehin/ColorRamp/HEAD/Content/M_ColorRamp_VertexAnimation_MorphTargets.uasset -------------------------------------------------------------------------------- /Config/DefaultColorRamp.ini: -------------------------------------------------------------------------------- 1 | [CoreRedirects] 2 | +ClassRedirects=(OldName="/Script/ColorRamp.MaterialExpressionColorRamp",NewName="/Script/ColorRamp.MaterialExpressionMoxColorRamp") -------------------------------------------------------------------------------- /Source/ColorRamp/Private/ColorRamp.cpp: -------------------------------------------------------------------------------- 1 | #include "ColorRamp.h" 2 | 3 | #define LOCTEXT_NAMESPACE "FColorRampModule" 4 | 5 | void FColorRampModule::StartupModule() 6 | { 7 | 8 | } 9 | 10 | void FColorRampModule::ShutdownModule() 11 | { 12 | 13 | } 14 | 15 | #undef LOCTEXT_NAMESPACE 16 | 17 | IMPLEMENT_MODULE(FColorRampModule, ColorRamp) -------------------------------------------------------------------------------- /Source/ColorRamp/Public/ColorRamp.h: -------------------------------------------------------------------------------- 1 | // Copyright MoxAlehin. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Modules/ModuleManager.h" 7 | 8 | class FColorRampModule : public IModuleInterface 9 | { 10 | public: 11 | 12 | /** IModuleInterface implementation */ 13 | virtual void StartupModule() override; 14 | virtual void ShutdownModule() override; 15 | }; 16 | -------------------------------------------------------------------------------- /Source/ColorRamp/ColorRamp.Build.cs: -------------------------------------------------------------------------------- 1 | using UnrealBuildTool; 2 | 3 | public class ColorRamp : ModuleRules 4 | { 5 | public ColorRamp(ReadOnlyTargetRules Target) : base(Target) 6 | { 7 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 8 | 9 | PublicDependencyModuleNames.AddRange( 10 | new string[] 11 | { 12 | "Core", 13 | } 14 | ); 15 | 16 | PrivateDependencyModuleNames.AddRange( 17 | new string[] 18 | { 19 | "CoreUObject", 20 | "Engine", 21 | "Slate", 22 | "SlateCore", 23 | } 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ColorRamp.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "Color Ramp", 6 | "Description": "This plugin adds a new material node that allows for smooth blending of multiple color values. The node lets you define key points with specific colors and control their distribution to create various gradients.", 7 | "Category": "Blueprints", 8 | "CreatedBy": "Mox Alehin", 9 | "CreatedByURL": "https://github.com/MoxAlehin", 10 | "DocsURL": "https://github.com/MoxAlehin/ColorRamp", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": true, 14 | "IsBetaVersion": false, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "ColorRamp", 20 | "Type": "Runtime", 21 | "LoadingPhase": "Default" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | 22 | # Compiled Static libraries 23 | *.lai 24 | *.la 25 | *.a 26 | *.lib 27 | 28 | # Executables 29 | *.exe 30 | *.out 31 | *.app 32 | *.ipa 33 | 34 | # These project files can be generated by the engine 35 | *.xcodeproj 36 | *.xcworkspace 37 | *.sln 38 | *.suo 39 | *.opensdf 40 | *.sdf 41 | *.VC.db 42 | *.VC.opendb 43 | 44 | # Precompiled Assets 45 | SourceArt/**/*.png 46 | SourceArt/**/*.tga 47 | 48 | # Binary Files 49 | Binaries/* 50 | Plugins/*/Binaries/* 51 | 52 | # Builds 53 | Build/* 54 | 55 | # Whitelist PakBlacklist-.txt files 56 | !Build/*/ 57 | Build/*/** 58 | !Build/*/PakBlacklist*.txt 59 | 60 | # Don't ignore icon files in Build 61 | !Build/**/*.ico 62 | 63 | # Built data for maps 64 | *_BuiltData.uasset 65 | 66 | # Configuration files generated by the Editor 67 | Saved/* 68 | 69 | # Compiled source files for the engine to use 70 | Intermediate/* 71 | Plugins/*/Intermediate/* 72 | 73 | # Cache files for the editor to use 74 | DerivedDataCache/* 75 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Prepare Release for Plugin 2 | 3 | on: 4 | release: 5 | types: [published] # Trigger when a release is published 6 | 7 | jobs: 8 | prepare_release: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | # Checkout the repository code 13 | - name: Checkout repository 14 | uses: actions/checkout@v4 15 | 16 | # Set a variable with the repository name 17 | - name: Set name variable 18 | run: echo "NAME=$(basename ${{ github.repository }})" >> $GITHUB_ENV 19 | 20 | # Create a new directory with the repository name and move necessary files into it 21 | - name: Prepare release directory 22 | run: | 23 | mkdir $NAME 24 | cp *.uplugin $NAME/ 25 | cp -r Source $NAME/ 26 | cp -r Resources $NAME/ 27 | 28 | # Create a ZIP archive of the directory with the required files 29 | - name: Zip release 30 | run: zip -r $NAME-${{ github.event.release.tag_name }}.zip $NAME 31 | 32 | # Upload the ZIP archive as a release asset 33 | - name: Upload release archive 34 | uses: svenstaro/upload-release-action@v2 35 | with: 36 | repo_token: ${{ secrets.GITHUB_TOKEN }} 37 | file: ${{ env.NAME }}-${{ github.event.release.tag_name }}.zip 38 | asset_name: ${{ env.NAME }}-${{ github.event.release.tag_name }}.zip 39 | tag: ${{ github.event.release.tag_name }} -------------------------------------------------------------------------------- /Source/ColorRamp/Public/MaterialExpressionMoxColorRamp.h: -------------------------------------------------------------------------------- 1 | // Copyright MoxAlehin. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Materials/MaterialExpression.h" 7 | #include "MaterialExpressionMoxColorRamp.generated.h" 8 | 9 | UENUM() 10 | enum EInterpolationType : uint8 11 | { 12 | Constant, 13 | Linear, 14 | Ease, 15 | BSpline UMETA(DisplayName="B-Spline") 16 | }; 17 | 18 | UENUM() 19 | enum EPinType : uint8 20 | { 21 | HidePins, 22 | HidePinsDistributed UMETA(DisplayName="Hide Pins (Distributed)"), 23 | ShowColorPins, 24 | ShowColorPinsDistributed UMETA(DisplayName="Show Color Pins (Distributed)"), 25 | ShowPositionPins, 26 | ShowAllPinsAlternate UMETA(DisplayName="Show All Pins (Alternate)"), 27 | ShowAllPinsGroup UMETA(DisplayName="Show All Pins (Group)") 28 | }; 29 | 30 | USTRUCT() 31 | struct FColorRampPoint 32 | { 33 | GENERATED_USTRUCT_BODY() 34 | 35 | UPROPERTY() 36 | FExpressionInput Color; 37 | 38 | UPROPERTY() 39 | FExpressionInput Position; 40 | 41 | UPROPERTY(EditAnywhere, Category = "ColorRampPoint", meta = (ShowAsInputPin = "Advanced", DisplayName = "Default Color")) 42 | FLinearColor DefaultColor; 43 | 44 | UPROPERTY(EditAnywhere, Category = "ColorRampPoint", meta = (ShowAsInputPin = "Advanced", DisplayName = "Default Position", UIMin = "0.0", UIMax = "1.0")) 45 | float DefaultPosition; 46 | 47 | FColorRampPoint() 48 | : Color(), Position(), DefaultColor(FLinearColor::Black), DefaultPosition(0.0f) {} 49 | 50 | FColorRampPoint(const FExpressionInput& InColor, const FExpressionInput& InPosition, const FLinearColor& InDefaultColor, float InDefaultPosition) 51 | : Color(InColor), Position(InPosition), DefaultColor(InDefaultColor), DefaultPosition(InDefaultPosition) {} 52 | }; 53 | 54 | UCLASS(CollapseCategories, HideCategories = Object, MinimalAPI) 55 | class UMaterialExpressionMoxColorRamp : public UMaterialExpression 56 | { 57 | GENERATED_UCLASS_BODY() 58 | 59 | UPROPERTY(meta = (RequiredInput = "false", ToolTip = "Defaults to 'ConstAlpha' if not specified")) 60 | FExpressionInput Alpha; 61 | 62 | UPROPERTY(EditAnywhere, Category = "MaterialExpressionColorRamp", meta = (OverridingInputProperty = "Alpha", UIMin = "0.0", UIMax = "1.0")) 63 | float ConstAlpha; 64 | 65 | UPROPERTY(EditAnywhere, Category = "MaterialExpressionColorRamp", meta = (DisplayName = "Interpolation Type", ShowAsInputPin = "Advanced")) 66 | TEnumAsByte InterpolationType; 67 | 68 | UPROPERTY(EditAnywhere, Category = "MaterialExpressionColorRamp", meta = (DisplayName = "Pin Type", ShowAsInputPin = "Advanced")) 69 | TEnumAsByte PinType; 70 | 71 | UPROPERTY(EditAnywhere, Category = "MaterialExpressionColorRamp") 72 | TArray ColorPoints; 73 | 74 | void RebuildOutputs(); 75 | 76 | #if WITH_EDITOR 77 | virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; 78 | virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override; 79 | virtual void GetCaption(TArray& OutCaptions) const override; 80 | virtual TArrayView GetInputsView() override; 81 | virtual FExpressionInput* GetInput(int32 InputIndex) override; 82 | virtual FName GetInputName(int32 InputIndex) const override; 83 | virtual uint32 GetInputType(int32 InputIndex) override { return MCT_Float; } 84 | static int32 ApplyEaseInOutInterpolation(FMaterialCompiler* Compiler, int32 AlphaIndex, int32 PrevPositionIndex, int32 PositionIndex); 85 | int32 ApplyBSplineInterpolation(FMaterialCompiler* Compiler, int32 AlphaIndex, int32 CurrentIndex); 86 | #endif 87 | }; 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Color Ramp 2 | 3 | Color Ramp is a powerful Unreal Engine custom material expression that allows you to create smooth transitions between multiple color points based on defined positions, using various interpolation methods. Additionally, this node provides flexible pin configuration options, including distributed position management for automatic spacing of points. 4 | 5 | ![ColorNode](Images/ColorNode.gif) 6 | 7 | ## Color Interpolation 8 | 9 | The `Color Ramp` node enables you to define multiple color points and smoothly transition between them using interpolation. It supports several interpolation methods: 10 | 11 | - **Constant**: No blending; a sharp transition from one color to another. 12 | - **Linear**: Simple linear interpolation between colors. 13 | - **Ease**: Smooth interpolation with an ease-in/ease-out effect. 14 | 15 | ![InterpolationsGraphs](Images/InterpolationsGraphs.svg) 16 | 17 | You can specify the interpolation type for the entire color ramp by choosing from the available options in the `Interpolation Type` dropdown. 18 | 19 | ## Example Use Cases 20 | 21 | ### Multi-Color Transition 22 | 23 | To create a multi-color transition with custom position values, use `ShowAllPinsAlternate`. You can then control both the colors and their positadienions in the grt, allowing for more complex patterns. 24 | 25 | ![ColorRampNode_Rainbow_Graph](Images/ColorRampNode_Rainbow_Graph.png) 26 | 27 | ![ColorRampNode_Rainbow_Details](Images/ColorRampNode_Rainbow_Details.png) 28 | 29 | ### Vertex Animation Sampling 30 | 31 | ![ColorRampNode_AppleAnimation](Images/ColorRampNode_AppleAnimation.gif) 32 | 33 | ![ColorRampNode_AppleAnimation_Graph](Images/ColorRampNode_AppleAnimation_Graph.png) 34 | 35 | World Position Offsets in this example are baked into multiple UV channels in the model. 36 | 37 | ### Height based transitions 38 | 39 | ![ColorRampNode_Islands](Images/ColorRampNode_Islands.png) 40 | 41 | 42 | ## Pin Configuration 43 | 44 | The `Pin Type` enum allows you to configure which pins (color and position) are exposed and how they are distributed across the ramp: 45 | 46 | | Pin Type | Description | 47 | | --------------------------------- | ------------------------------------------------------------------------------------------- | 48 | | **Hide Pins** | All pins are hidden, and only default values for colors and positions are used. | 49 | | **Hide Pins (Distributed)** | Similar to `Hide Pins`, but positions are automatically distributed evenly from 0 to 1. | 50 | | **Show Color Pins** | Only color pins are exposed. | 51 | | **Show Color Pins (Distributed)** | Color pins are exposed, and positions are automatically distributed evenly from 0 to 1. | 52 | | **Show Position Pins** | Only position pins are exposed. | 53 | | **Show All Pins (Alternate)** | Both color and position pins are exposed alternately (e.g., `Color 1`, `Position 1`, etc.). | 54 | | **Show All Pins (Group)** | All color pins are exposed first, followed by all position pins. | 55 | 56 | This flexible pin configuration ensures that you have full control over how color and position data are exposed and manipulated. 57 | 58 | ### Points 59 | 60 | Each color point in the ramp has customizable default values for both color and position, ensuring that the ramp behaves predictably even when no inputs are connected. 61 | 62 | In modes like `Hide Pins (Distributed)` and `Show Color Pins (Distributed)`, the positions of the color points are automatically distributed evenly between 0 and 1. This feature is useful for creating smooth, evenly spaced gradients without the need for manual position adjustments. In other modes, such as `Show Position Pins` or `Show All Pins`, you can manually specify the position of each color point for finer control over the gradient's shape. 63 | 64 | For predictable behavior, the positions of the points need to follow one another, meaning the position of each subsequent point must be greater than the previous one. -------------------------------------------------------------------------------- /Source/ColorRamp/Private/MaterialExpressionMoxColorRamp.cpp: -------------------------------------------------------------------------------- 1 | // Copyright MoxAlehin. All Rights Reserved. 2 | 3 | #include "MaterialExpressionMoxColorRamp.h" 4 | #include "MaterialCompiler.h" 5 | 6 | #define LOCTEXT_NAMESPACE "MaterialExpression" 7 | 8 | UMaterialExpressionMoxColorRamp::UMaterialExpressionMoxColorRamp(const FObjectInitializer& ObjectInitializer) 9 | : Super(ObjectInitializer), 10 | ConstAlpha(1.0f), 11 | InterpolationType(EInterpolationType::Linear), 12 | PinType(EPinType::HidePinsDistributed) 13 | { 14 | ColorPoints.Add(FColorRampPoint(FExpressionInput(), FExpressionInput(), FLinearColor::Black, 0.0f)); 15 | ColorPoints.Add(FColorRampPoint(FExpressionInput(), FExpressionInput(), FLinearColor::White, 1.0f)); 16 | MenuCategories.Add(LOCTEXT( "Gradient", "Gradient" )); 17 | } 18 | 19 | void UMaterialExpressionMoxColorRamp::RebuildOutputs() 20 | { 21 | Outputs.Reset(1); 22 | bShowOutputNameOnPin = false; 23 | Outputs.Add(FExpressionOutput(TEXT(""))); 24 | } 25 | 26 | #if WITH_EDITOR 27 | 28 | int32 UMaterialExpressionMoxColorRamp::Compile(FMaterialCompiler* Compiler, int32 OutputIndex) 29 | { 30 | int32 AlphaIndex = Alpha.Expression ? Alpha.Compile(Compiler) : Compiler->Constant(ConstAlpha); 31 | 32 | if (ColorPoints.Num() == 0) 33 | { 34 | return Compiler->Errorf(TEXT("Color points are missing")); 35 | } 36 | 37 | if (PinType == EPinType::HidePinsDistributed || PinType == EPinType::ShowColorPinsDistributed) 38 | { 39 | for (int32 i = 0; i < ColorPoints.Num(); ++i) 40 | { 41 | ColorPoints[i].DefaultPosition = static_cast(i) / (ColorPoints.Num() - 1); 42 | } 43 | } 44 | 45 | int32 Result = -1; 46 | for (int32 i = 0; i < ColorPoints.Num(); ++i) 47 | { 48 | int32 ColorIndex = ColorPoints[i].Color.Expression ? ColorPoints[i].Color.Compile(Compiler) : Compiler->Constant3(ColorPoints[i].DefaultColor.R, ColorPoints[i].DefaultColor.G, ColorPoints[i].DefaultColor.B); 49 | int32 PositionIndex = ColorPoints[i].Position.Expression ? ColorPoints[i].Position.Compile(Compiler) : Compiler->Constant(ColorPoints[i].DefaultPosition); 50 | 51 | if (i == 0) 52 | { 53 | Result = ColorIndex; 54 | } 55 | else 56 | { 57 | int32 PrevPositionIndex = ColorPoints[i - 1].Position.Expression ? ColorPoints[i - 1].Position.Compile(Compiler) : Compiler->Constant(ColorPoints[i - 1].DefaultPosition); 58 | int32 LerpAlpha; 59 | 60 | switch (InterpolationType) 61 | { 62 | case EInterpolationType::Constant: 63 | LerpAlpha = Compiler->Step(PositionIndex, AlphaIndex); 64 | break; 65 | case EInterpolationType::Linear: 66 | LerpAlpha = Compiler->Div(Compiler->Sub(AlphaIndex, PrevPositionIndex), Compiler->Sub(PositionIndex, PrevPositionIndex)); 67 | LerpAlpha = Compiler->Clamp(LerpAlpha, Compiler->Constant(0.0f), Compiler->Constant(1.0f)); 68 | break; 69 | case EInterpolationType::Ease: 70 | LerpAlpha = ApplyEaseInOutInterpolation(Compiler, AlphaIndex, PrevPositionIndex, PositionIndex); 71 | break; 72 | case EInterpolationType::BSpline: 73 | LerpAlpha = ApplyBSplineInterpolation(Compiler, AlphaIndex, i); 74 | break; 75 | default: 76 | LerpAlpha = Compiler->Div(Compiler->Sub(AlphaIndex, PrevPositionIndex), Compiler->Sub(PositionIndex, PrevPositionIndex)); 77 | LerpAlpha = Compiler->Clamp(LerpAlpha, Compiler->Constant(0.0f), Compiler->Constant(1.0f)); 78 | break; 79 | } 80 | 81 | Result = Compiler->Lerp(Result, ColorIndex, LerpAlpha); 82 | } 83 | } 84 | 85 | return Result; 86 | } 87 | 88 | int32 UMaterialExpressionMoxColorRamp::ApplyEaseInOutInterpolation(FMaterialCompiler* Compiler, int32 AlphaIndex, int32 PrevPositionIndex, int32 PositionIndex) 89 | { 90 | int32 t = Compiler->Div(Compiler->Sub(AlphaIndex, PrevPositionIndex), Compiler->Sub(PositionIndex, PrevPositionIndex)); 91 | int32 easeInOut = Compiler->Sub(Compiler->Mul(t, t), Compiler->Mul(Compiler->Sub(t, Compiler->Constant(1.0f)), Compiler->Sub(t, Compiler->Constant(1.0f)))); 92 | return Compiler->Clamp(easeInOut, Compiler->Constant(0.0f), Compiler->Constant(1.0f)); 93 | } 94 | 95 | // Function to apply B-Spline interpolation 96 | // Ensure there are at least four points for cubic B-Spline 97 | // if (ColorPoints.Num() < 4) 98 | // { 99 | // return Compiler->Errorf(TEXT("B-Spline interpolation requires at least 4 color points")); 100 | // } 101 | 102 | int32 UMaterialExpressionMoxColorRamp::ApplyBSplineInterpolation(FMaterialCompiler* Compiler, int32 AlphaIndex, int32 CurrentIndex) 103 | { 104 | int32 t = Compiler->Constant(AlphaIndex); // Placeholder for AlphaIndex as parameter t 105 | int32 B = Compiler->Constant(0.0f); // Initialize B for accumulating the B-Spline result 106 | 107 | // Example using cubic B-spline basis; adjust depending on the required control points 108 | for (int32 i = -1; i <= 2; ++i) 109 | { 110 | int32 k = CurrentIndex + i; // Calculate control point index offset 111 | if (k < 0 || k >= ColorPoints.Num()) continue; // Skip out-of-bounds control points 112 | 113 | int32 PositionIndex = ColorPoints[k].Position.Expression ? ColorPoints[k].Position.Compile(Compiler) : Compiler->Constant(ColorPoints[k].DefaultPosition); 114 | int32 ColorIndex = ColorPoints[k].Color.Expression ? ColorPoints[k].Color.Compile(Compiler) : Compiler->Constant3(ColorPoints[k].DefaultColor.R, ColorPoints[k].DefaultColor.G, ColorPoints[k].DefaultColor.B); 115 | 116 | int32 BasisValue = Compiler->Constant(1.0f); 117 | B = Compiler->Add(B, Compiler->Mul(BasisValue, ColorIndex)); // Accumulate weighted color points 118 | } 119 | 120 | return Compiler->Clamp(B, Compiler->Constant(0.0f), Compiler->Constant(1.0f)); 121 | } 122 | 123 | void UMaterialExpressionMoxColorRamp::GetCaption(TArray& OutCaptions) const 124 | { 125 | OutCaptions.Add(TEXT("Color Ramp")); 126 | } 127 | 128 | TArrayView UMaterialExpressionMoxColorRamp::GetInputsView() 129 | { 130 | CachedInputs.Empty(); 131 | CachedInputs.Add(&Alpha); 132 | 133 | switch (PinType) 134 | { 135 | case EPinType::ShowColorPinsDistributed: 136 | case EPinType::ShowColorPins: 137 | for (FColorRampPoint& Point : ColorPoints) 138 | { 139 | CachedInputs.Add(&Point.Color); 140 | } 141 | break; 142 | 143 | case EPinType::ShowPositionPins: 144 | for (FColorRampPoint& Point : ColorPoints) 145 | { 146 | CachedInputs.Add(&Point.Position); 147 | } 148 | break; 149 | 150 | case EPinType::ShowAllPinsGroup: 151 | for (FColorRampPoint& Point : ColorPoints) 152 | { 153 | CachedInputs.Add(&Point.Color); 154 | } 155 | for (FColorRampPoint& Point : ColorPoints) 156 | { 157 | CachedInputs.Add(&Point.Position); 158 | } 159 | break; 160 | 161 | case EPinType::ShowAllPinsAlternate: 162 | for (FColorRampPoint& Point : ColorPoints) 163 | { 164 | CachedInputs.Add(&Point.Color); 165 | CachedInputs.Add(&Point.Position); 166 | } 167 | break; 168 | default: 169 | break; 170 | } 171 | 172 | return CachedInputs; 173 | } 174 | 175 | FExpressionInput* UMaterialExpressionMoxColorRamp::GetInput(int32 InputIndex) 176 | { 177 | if (InputIndex == 0) 178 | { 179 | return Α 180 | } 181 | 182 | int32 PointIndex; 183 | switch (PinType) 184 | { 185 | case EPinType::ShowColorPinsDistributed: 186 | case EPinType::ShowColorPins: 187 | PointIndex = InputIndex - 1; 188 | if (PointIndex >= 0 && PointIndex < ColorPoints.Num()) 189 | { 190 | return &ColorPoints[PointIndex].Color; 191 | } 192 | break; 193 | 194 | case EPinType::ShowPositionPins: 195 | PointIndex = InputIndex - 1; 196 | if (PointIndex >= 0 && PointIndex < ColorPoints.Num()) 197 | { 198 | return &ColorPoints[PointIndex].Position; 199 | } 200 | break; 201 | 202 | case EPinType::ShowAllPinsAlternate: 203 | PointIndex = (InputIndex - 1) / 2; 204 | if (PointIndex >= 0 && PointIndex < ColorPoints.Num()) 205 | { 206 | if ((InputIndex - 1) % 2 == 0) 207 | { 208 | return &ColorPoints[PointIndex].Color; 209 | } 210 | else 211 | { 212 | return &ColorPoints[PointIndex].Position; 213 | } 214 | } 215 | break; 216 | 217 | case EPinType::ShowAllPinsGroup: 218 | if (InputIndex - 1 < ColorPoints.Num()) 219 | { 220 | return &ColorPoints[InputIndex - 1].Color; 221 | } 222 | else if (InputIndex - 1 < ColorPoints.Num() * 2) 223 | { 224 | return &ColorPoints[InputIndex - 1 - ColorPoints.Num()].Position; 225 | } 226 | break; 227 | 228 | default: 229 | break; 230 | } 231 | 232 | return nullptr; 233 | } 234 | 235 | FName UMaterialExpressionMoxColorRamp::GetInputName(int32 InputIndex) const 236 | { 237 | if (InputIndex == 0) 238 | { 239 | return GET_MEMBER_NAME_STRING_CHECKED(UMaterialExpressionMoxColorRamp, Alpha); 240 | } 241 | 242 | int32 PointIndex; 243 | switch (PinType) 244 | { 245 | case EPinType::ShowColorPinsDistributed: 246 | case EPinType::ShowColorPins: 247 | PointIndex = InputIndex - 1; 248 | if (PointIndex >= 0 && PointIndex < ColorPoints.Num()) 249 | { 250 | return FName(*FString::Printf(TEXT("Color %d"), PointIndex)); 251 | } 252 | break; 253 | 254 | case EPinType::ShowPositionPins: 255 | PointIndex = InputIndex - 1; 256 | if (PointIndex >= 0 && PointIndex < ColorPoints.Num()) 257 | { 258 | return FName(*FString::Printf(TEXT("Position %d"), PointIndex)); 259 | } 260 | break; 261 | 262 | case EPinType::ShowAllPinsAlternate: 263 | PointIndex = (InputIndex - 1) / 2; 264 | if (PointIndex >= 0 && PointIndex < ColorPoints.Num()) 265 | { 266 | if ((InputIndex - 1) % 2 == 0) 267 | { 268 | return FName(*FString::Printf(TEXT("Color %d"), PointIndex)); 269 | } 270 | else 271 | { 272 | return FName(*FString::Printf(TEXT("Position %d"), PointIndex)); 273 | } 274 | } 275 | break; 276 | 277 | case EPinType::ShowAllPinsGroup: 278 | if (InputIndex - 1 < ColorPoints.Num()) 279 | { 280 | return FName(*FString::Printf(TEXT("Color %d"), InputIndex - 1)); 281 | } 282 | else if (InputIndex - 1 < ColorPoints.Num() * 2) 283 | { 284 | return FName(*FString::Printf(TEXT("Position %d"), InputIndex - 1 - ColorPoints.Num())); 285 | } 286 | break; 287 | 288 | default: 289 | break; 290 | } 291 | 292 | return NAME_None; 293 | } 294 | 295 | void UMaterialExpressionMoxColorRamp::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) 296 | { 297 | RebuildOutputs(); 298 | 299 | if (PropertyChangedEvent.GetMemberPropertyName() == GET_MEMBER_NAME_CHECKED(UMaterialExpressionMoxColorRamp, PinType)) 300 | { 301 | if (PinType == EPinType::HidePins || 302 | PinType == EPinType::ShowPositionPins || 303 | PinType == EPinType::HidePinsDistributed) 304 | { 305 | for (FColorRampPoint& Point : ColorPoints) 306 | { 307 | Point.Color.Expression = nullptr; 308 | } 309 | } 310 | if (PinType == EPinType::HidePins || 311 | PinType == EPinType::HidePinsDistributed || 312 | PinType == EPinType::ShowColorPinsDistributed || 313 | PinType == EPinType::ShowColorPins) 314 | { 315 | for (FColorRampPoint& Point : ColorPoints) 316 | { 317 | Point.Position.Expression = nullptr; 318 | } 319 | } 320 | } 321 | 322 | if (PropertyChangedEvent.GetMemberPropertyName() == GET_MEMBER_NAME_CHECKED(UMaterialExpressionMoxColorRamp, ColorPoints) || 323 | PropertyChangedEvent.GetMemberPropertyName() == GET_MEMBER_NAME_CHECKED(UMaterialExpressionMoxColorRamp, InterpolationType) || 324 | PropertyChangedEvent.GetMemberPropertyName() == GET_MEMBER_NAME_CHECKED(UMaterialExpressionMoxColorRamp, PinType)) 325 | { 326 | if (GraphNode) 327 | { 328 | GraphNode->ReconstructNode(); 329 | } 330 | } 331 | 332 | Super::PostEditChangeProperty(PropertyChangedEvent); 333 | } 334 | 335 | #endif 336 | 337 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Images/InterpolationsGraphs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | ConstantLinearEaseColor1Color2Color3Color401AlphaColor1Color2Color3Color4Color1Color2Color3Color4 -------------------------------------------------------------------------------- /Images/InterpolationsGraphs.md: -------------------------------------------------------------------------------- 1 | --- 2 | excalidraw-plugin: parsed 3 | --- 4 | 5 | 6 | %% 7 | # Excalidraw Data 8 | ## Text Elements 9 | Color1 ^JOQT5MeT 10 | 11 | Color2 ^MHh1cM1a 12 | 13 | Color3 ^6OaSlNdc 14 | 15 | Color4 ^dTdvNc1y 16 | 17 | 0 ^x5QKYbhl 18 | 19 | 1 ^TDJ9X7Bt 20 | 21 | Alpha ^km9hj5Z2 22 | 23 | Color1 ^gmbnCHTF 24 | 25 | Color2 ^Y7TFHV9e 26 | 27 | Color3 ^Lv66dA2H 28 | 29 | Color4 ^a7oFg0ea 30 | 31 | Color1 ^ctlgagnd 32 | 33 | Color2 ^t9fja5R9 34 | 35 | Color3 ^TnhXLOoi 36 | 37 | Color4 ^x71kkljs 38 | 39 | Constant ^edwfoL6e 40 | 41 | Linear ^61oOXByd 42 | 43 | Ease ^L8cc6346 44 | 45 | ## Embedded Files 46 | fe3ba13fd8c9238aa81f981e8eb618ef2aa37cb9: [[Linear.jpg]] 47 | 48 | c2e85b7fca13f301b725f135471f9c5065091a1d: [[Ease.jpg]] 49 | 50 | 2e7c27e64a635168917cb1f25d10342c7673190f: [[Constant.jpg]] 51 | 52 | 795bb870a9d2da1ae87cd3fc775909c75f95868f: [[ColorRampNode_AppleAnimation_Graph.png]] 53 | 54 | c1c6734fff58ed63a4d7cb9270729e931378f5f5: [[ColorRamp_Rainbow.png]] 55 | 56 | 4f81cf23976e60878ca4e0829d90475ad89cf5c8: [[ColorRampNode_Rainbow_Details.png]] 57 | 58 | b3cfe2e699013e8cc8becaac0feb81fede5e6d70: [[ColorRampNode_Rainbow_Graph.png]] 59 | 60 | ## Drawing 61 | ```json 62 | { 63 | "type": "excalidraw", 64 | "version": 2, 65 | "source": "https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/2.5.0", 66 | "elements": [ 67 | { 68 | "type": "image", 69 | "version": 727, 70 | "versionNonce": 1934013079, 71 | "index": "a9", 72 | "isDeleted": false, 73 | "id": "PmNI2Bno", 74 | "fillStyle": "hachure", 75 | "strokeWidth": 1, 76 | "strokeStyle": "solid", 77 | "roughness": 1, 78 | "opacity": 30, 79 | "angle": 0, 80 | "x": 324.9604692474214, 81 | "y": 346.20394819276294, 82 | "strokeColor": "transparent", 83 | "backgroundColor": "transparent", 84 | "width": 336.31612640626, 85 | "height": 336.31612640626, 86 | "seed": 51427, 87 | "groupIds": [ 88 | "3CT5UPoBEteyzMUm8XWAj" 89 | ], 90 | "frameId": null, 91 | "roundness": null, 92 | "boundElements": [], 93 | "updated": 1726848191952, 94 | "link": null, 95 | "locked": false, 96 | "status": "pending", 97 | "fileId": "fe3ba13fd8c9238aa81f981e8eb618ef2aa37cb9", 98 | "scale": [ 99 | 1, 100 | 1 101 | ] 102 | }, 103 | { 104 | "type": "image", 105 | "version": 774, 106 | "versionNonce": 1336190553, 107 | "index": "aA", 108 | "isDeleted": false, 109 | "id": "4On2alsr", 110 | "fillStyle": "hachure", 111 | "strokeWidth": 1, 112 | "strokeStyle": "solid", 113 | "roughness": 1, 114 | "opacity": 30, 115 | "angle": 0, 116 | "x": 783.2937111109969, 117 | "y": 346.52548755432366, 118 | "strokeColor": "transparent", 119 | "backgroundColor": "transparent", 120 | "width": 335.99458713642434, 121 | "height": 335.99458713642434, 122 | "seed": 91794, 123 | "groupIds": [ 124 | "3CT5UPoBEteyzMUm8XWAj" 125 | ], 126 | "frameId": null, 127 | "roundness": null, 128 | "boundElements": [], 129 | "updated": 1726848191952, 130 | "link": null, 131 | "locked": false, 132 | "status": "pending", 133 | "fileId": "c2e85b7fca13f301b725f135471f9c5065091a1d", 134 | "scale": [ 135 | 1, 136 | 1 137 | ] 138 | }, 139 | { 140 | "type": "image", 141 | "version": 1615, 142 | "versionNonce": 1730299831, 143 | "index": "aB", 144 | "isDeleted": false, 145 | "id": "eC84ECJk", 146 | "fillStyle": "hachure", 147 | "strokeWidth": 1, 148 | "strokeStyle": "solid", 149 | "roughness": 1, 150 | "opacity": 30, 151 | "angle": 0, 152 | "x": -127.67323175257866, 153 | "y": 346.2039485990231, 154 | "strokeColor": "transparent", 155 | "backgroundColor": "transparent", 156 | "width": 322.27109797762535, 157 | "height": 336.31612573715296, 158 | "seed": 96627, 159 | "groupIds": [ 160 | "3CT5UPoBEteyzMUm8XWAj" 161 | ], 162 | "frameId": null, 163 | "roundness": null, 164 | "boundElements": [], 165 | "updated": 1726848191952, 166 | "link": null, 167 | "locked": false, 168 | "status": "pending", 169 | "fileId": "2e7c27e64a635168917cb1f25d10342c7673190f", 170 | "scale": [ 171 | 1, 172 | 1 173 | ] 174 | }, 175 | { 176 | "type": "text", 177 | "version": 445, 178 | "versionNonce": 1221620537, 179 | "index": "aC", 180 | "isDeleted": false, 181 | "id": "edwfoL6e", 182 | "fillStyle": "solid", 183 | "strokeWidth": 2, 184 | "strokeStyle": "dotted", 185 | "roughness": 1, 186 | "opacity": 100, 187 | "angle": 0, 188 | "x": -26.212418247489545, 189 | "y": 293.74387802474916, 190 | "strokeColor": "#ffffff", 191 | "backgroundColor": "transparent", 192 | "width": 131.25, 193 | "height": 33.6, 194 | "seed": 572458841, 195 | "groupIds": [ 196 | "3CT5UPoBEteyzMUm8XWAj" 197 | ], 198 | "frameId": null, 199 | "roundness": null, 200 | "boundElements": [], 201 | "updated": 1726848191952, 202 | "link": null, 203 | "locked": false, 204 | "fontSize": 28, 205 | "fontFamily": 3, 206 | "text": "Constant", 207 | "rawText": "Constant", 208 | "textAlign": "left", 209 | "verticalAlign": "top", 210 | "containerId": null, 211 | "originalText": "Constant", 212 | "autoResize": true, 213 | "lineHeight": 1.2 214 | }, 215 | { 216 | "type": "text", 217 | "version": 484, 218 | "versionNonce": 1091451095, 219 | "index": "aD", 220 | "isDeleted": false, 221 | "id": "61oOXByd", 222 | "fillStyle": "solid", 223 | "strokeWidth": 2, 224 | "strokeStyle": "dotted", 225 | "roughness": 1, 226 | "opacity": 100, 227 | "angle": 0, 228 | "x": 443.1029073182179, 229 | "y": 293.74387807860126, 230 | "strokeColor": "#ffffff", 231 | "backgroundColor": "transparent", 232 | "width": 100.03125, 233 | "height": 33.6, 234 | "seed": 2080133527, 235 | "groupIds": [ 236 | "3CT5UPoBEteyzMUm8XWAj" 237 | ], 238 | "frameId": null, 239 | "roundness": null, 240 | "boundElements": [], 241 | "updated": 1726848191952, 242 | "link": null, 243 | "locked": false, 244 | "fontSize": 28, 245 | "fontFamily": 3, 246 | "text": "Linear", 247 | "rawText": "Linear", 248 | "textAlign": "left", 249 | "verticalAlign": "top", 250 | "containerId": null, 251 | "originalText": "Linear", 252 | "autoResize": true, 253 | "lineHeight": 1.2 254 | }, 255 | { 256 | "type": "text", 257 | "version": 546, 258 | "versionNonce": 1219470361, 259 | "index": "aE", 260 | "isDeleted": false, 261 | "id": "L8cc6346", 262 | "fillStyle": "solid", 263 | "strokeWidth": 2, 264 | "strokeStyle": "dotted", 265 | "roughness": 1, 266 | "opacity": 100, 267 | "angle": 0, 268 | "x": 918.4785045338865, 269 | "y": 293.7438777632186, 270 | "strokeColor": "#ffffff", 271 | "backgroundColor": "transparent", 272 | "width": 65.625, 273 | "height": 33.6, 274 | "seed": 872012919, 275 | "groupIds": [ 276 | "3CT5UPoBEteyzMUm8XWAj" 277 | ], 278 | "frameId": null, 279 | "roundness": null, 280 | "boundElements": [], 281 | "updated": 1726848191952, 282 | "link": null, 283 | "locked": false, 284 | "fontSize": 28, 285 | "fontFamily": 3, 286 | "text": "Ease", 287 | "rawText": "Ease", 288 | "textAlign": "left", 289 | "verticalAlign": "top", 290 | "containerId": null, 291 | "originalText": "Ease", 292 | "autoResize": true, 293 | "lineHeight": 1.2 294 | }, 295 | { 296 | "type": "line", 297 | "version": 432, 298 | "versionNonce": 1626958361, 299 | "index": "aF", 300 | "isDeleted": false, 301 | "id": "PSm765zu8HTcgkF8K2ze7", 302 | "fillStyle": "solid", 303 | "strokeWidth": 2, 304 | "strokeStyle": "solid", 305 | "roughness": 1, 306 | "opacity": 100, 307 | "angle": 0, 308 | "x": 326.3768360411986, 309 | "y": 682.4759543220557, 310 | "strokeColor": "#ffffff", 311 | "backgroundColor": "transparent", 312 | "width": 332.318851151669, 313 | "height": 330.3126907778267, 314 | "seed": 865121401, 315 | "groupIds": [], 316 | "frameId": null, 317 | "roundness": null, 318 | "boundElements": [], 319 | "updated": 1726846119682, 320 | "link": null, 321 | "locked": false, 322 | "startBinding": null, 323 | "endBinding": null, 324 | "lastCommittedPoint": null, 325 | "startArrowhead": null, 326 | "endArrowhead": null, 327 | "points": [ 328 | [ 329 | 0, 330 | 0 331 | ], 332 | [ 333 | 332.318851151669, 334 | -330.3126907778267 335 | ] 336 | ] 337 | }, 338 | { 339 | "type": "text", 340 | "version": 355, 341 | "versionNonce": 146777881, 342 | "index": "aN", 343 | "isDeleted": false, 344 | "id": "JOQT5MeT", 345 | "fillStyle": "solid", 346 | "strokeWidth": 2, 347 | "strokeStyle": "solid", 348 | "roughness": 1, 349 | "opacity": 100, 350 | "angle": 5.263841895658339, 351 | "x": -190.7158110012225, 352 | "y": 744.0049587126618, 353 | "strokeColor": "#e03131", 354 | "backgroundColor": "transparent", 355 | "width": 98.4375, 356 | "height": 33.6, 357 | "seed": 968699767, 358 | "groupIds": [], 359 | "frameId": null, 360 | "roundness": null, 361 | "boundElements": [], 362 | "updated": 1726846331845, 363 | "link": null, 364 | "locked": false, 365 | "fontSize": 28, 366 | "fontFamily": 3, 367 | "text": "Color1", 368 | "rawText": "Color1", 369 | "textAlign": "left", 370 | "verticalAlign": "top", 371 | "containerId": null, 372 | "originalText": "Color1", 373 | "autoResize": true, 374 | "lineHeight": 1.2 375 | }, 376 | { 377 | "type": "text", 378 | "version": 406, 379 | "versionNonce": 717306903, 380 | "index": "aO", 381 | "isDeleted": false, 382 | "id": "MHh1cM1a", 383 | "fillStyle": "solid", 384 | "strokeWidth": 2, 385 | "strokeStyle": "solid", 386 | "roughness": 1, 387 | "opacity": 100, 388 | "angle": 5.263841895658339, 389 | "x": -80.99810347319863, 390 | "y": 743.1783415995686, 391 | "strokeColor": "#2f9e44", 392 | "backgroundColor": "transparent", 393 | "width": 99.03125, 394 | "height": 33.6, 395 | "seed": 620682807, 396 | "groupIds": [], 397 | "frameId": null, 398 | "roundness": null, 399 | "boundElements": [], 400 | "updated": 1726846333509, 401 | "link": null, 402 | "locked": false, 403 | "fontSize": 28, 404 | "fontFamily": 3, 405 | "text": "Color2", 406 | "rawText": "Color2", 407 | "textAlign": "left", 408 | "verticalAlign": "top", 409 | "containerId": null, 410 | "originalText": "Color2", 411 | "autoResize": true, 412 | "lineHeight": 1.2 413 | }, 414 | { 415 | "type": "text", 416 | "version": 319, 417 | "versionNonce": 698315193, 418 | "index": "aW", 419 | "isDeleted": false, 420 | "id": "6OaSlNdc", 421 | "fillStyle": "solid", 422 | "strokeWidth": 2, 423 | "strokeStyle": "solid", 424 | "roughness": 1, 425 | "opacity": 100, 426 | "angle": 5.263841895658339, 427 | "x": 28.713491422431957, 428 | "y": 742.6282155877433, 429 | "strokeColor": "#1971c2", 430 | "backgroundColor": "transparent", 431 | "width": 98.4375, 432 | "height": 33.6, 433 | "seed": 1131672023, 434 | "groupIds": [], 435 | "frameId": null, 436 | "roundness": null, 437 | "boundElements": [], 438 | "updated": 1726846335000, 439 | "link": null, 440 | "locked": false, 441 | "fontSize": 28, 442 | "fontFamily": 3, 443 | "text": "Color3", 444 | "rawText": "Color3", 445 | "textAlign": "left", 446 | "verticalAlign": "top", 447 | "containerId": null, 448 | "originalText": "Color3", 449 | "autoResize": true, 450 | "lineHeight": 1.2 451 | }, 452 | { 453 | "type": "text", 454 | "version": 375, 455 | "versionNonce": 1668511703, 456 | "index": "ae", 457 | "isDeleted": false, 458 | "id": "dTdvNc1y", 459 | "fillStyle": "solid", 460 | "strokeWidth": 2, 461 | "strokeStyle": "solid", 462 | "roughness": 1, 463 | "opacity": 100, 464 | "angle": 5.263841895658339, 465 | "x": 136.19647772459197, 466 | "y": 747.93874390148, 467 | "strokeColor": "#9c36b5", 468 | "backgroundColor": "transparent", 469 | "width": 99.03125, 470 | "height": 33.6, 471 | "seed": 74413687, 472 | "groupIds": [], 473 | "frameId": null, 474 | "roundness": null, 475 | "boundElements": [], 476 | "updated": 1726846351333, 477 | "link": null, 478 | "locked": false, 479 | "fontSize": 28, 480 | "fontFamily": 3, 481 | "text": "Color4", 482 | "rawText": "Color4", 483 | "textAlign": "left", 484 | "verticalAlign": "top", 485 | "containerId": null, 486 | "originalText": "Color4", 487 | "autoResize": true, 488 | "lineHeight": 1.2 489 | }, 490 | { 491 | "type": "arrow", 492 | "version": 64, 493 | "versionNonce": 894342745, 494 | "index": "am", 495 | "isDeleted": false, 496 | "id": "fvwzdpnCSzGrupgh4S9HP", 497 | "fillStyle": "solid", 498 | "strokeWidth": 2, 499 | "strokeStyle": "solid", 500 | "roughness": 1, 501 | "opacity": 100, 502 | "angle": 0, 503 | "x": -204.80136046176034, 504 | "y": 696.6926199818331, 505 | "strokeColor": "#ffffff", 506 | "backgroundColor": "transparent", 507 | "width": 4.15585451849438, 508 | "height": 352.4164631683238, 509 | "seed": 1313028697, 510 | "groupIds": [], 511 | "frameId": null, 512 | "roundness": { 513 | "type": 2 514 | }, 515 | "boundElements": [], 516 | "updated": 1726845381895, 517 | "link": null, 518 | "locked": false, 519 | "startBinding": null, 520 | "endBinding": null, 521 | "lastCommittedPoint": null, 522 | "startArrowhead": null, 523 | "endArrowhead": "arrow", 524 | "points": [ 525 | [ 526 | 0, 527 | 0 528 | ], 529 | [ 530 | 4.15585451849438, 531 | -352.4164631683238 532 | ] 533 | ], 534 | "elbowed": false 535 | }, 536 | { 537 | "type": "text", 538 | "version": 12, 539 | "versionNonce": 1652020089, 540 | "index": "an", 541 | "isDeleted": false, 542 | "id": "x5QKYbhl", 543 | "fillStyle": "solid", 544 | "strokeWidth": 2, 545 | "strokeStyle": "solid", 546 | "roughness": 1, 547 | "opacity": 100, 548 | "angle": 0, 549 | "x": -248.8534183578007, 550 | "y": 685.0562273300487, 551 | "strokeColor": "#ffffff", 552 | "backgroundColor": "transparent", 553 | "width": 17, 554 | "height": 33.6, 555 | "seed": 818419191, 556 | "groupIds": [], 557 | "frameId": null, 558 | "roundness": null, 559 | "boundElements": [], 560 | "updated": 1726845395554, 561 | "link": null, 562 | "locked": false, 563 | "fontSize": 28, 564 | "fontFamily": 3, 565 | "text": "0", 566 | "rawText": "0", 567 | "textAlign": "left", 568 | "verticalAlign": "top", 569 | "containerId": null, 570 | "originalText": "0", 571 | "autoResize": true, 572 | "lineHeight": 1.2 573 | }, 574 | { 575 | "type": "text", 576 | "version": 62, 577 | "versionNonce": 1098315673, 578 | "index": "ao", 579 | "isDeleted": false, 580 | "id": "TDJ9X7Bt", 581 | "fillStyle": "solid", 582 | "strokeWidth": 2, 583 | "strokeStyle": "solid", 584 | "roughness": 1, 585 | "opacity": 100, 586 | "angle": 0, 587 | "x": -253.84044377999396, 588 | "y": 326.8215678358328, 589 | "strokeColor": "#ffffff", 590 | "backgroundColor": "transparent", 591 | "width": 17, 592 | "height": 33.6, 593 | "seed": 1342172505, 594 | "groupIds": [], 595 | "frameId": null, 596 | "roundness": null, 597 | "boundElements": [], 598 | "updated": 1726845394385, 599 | "link": null, 600 | "locked": false, 601 | "fontSize": 28, 602 | "fontFamily": 3, 603 | "text": "1", 604 | "rawText": "1", 605 | "textAlign": "left", 606 | "verticalAlign": "top", 607 | "containerId": null, 608 | "originalText": "1", 609 | "autoResize": true, 610 | "lineHeight": 1.2 611 | }, 612 | { 613 | "type": "text", 614 | "version": 362, 615 | "versionNonce": 227675257, 616 | "index": "ap", 617 | "isDeleted": false, 618 | "id": "km9hj5Z2", 619 | "fillStyle": "solid", 620 | "strokeWidth": 2, 621 | "strokeStyle": "solid", 622 | "roughness": 1, 623 | "opacity": 100, 624 | "angle": 4.714819869484632, 625 | "x": -277.6847848664801, 626 | "y": 515.4056745438625, 627 | "strokeColor": "#ffffff", 628 | "backgroundColor": "transparent", 629 | "width": 82.625, 630 | "height": 33.6, 631 | "seed": 829323801, 632 | "groupIds": [], 633 | "frameId": null, 634 | "roundness": null, 635 | "boundElements": [], 636 | "updated": 1726845410256, 637 | "link": null, 638 | "locked": false, 639 | "fontSize": 28, 640 | "fontFamily": 3, 641 | "text": "Alpha", 642 | "rawText": "Alpha", 643 | "textAlign": "left", 644 | "verticalAlign": "top", 645 | "containerId": null, 646 | "originalText": "Alpha", 647 | "autoResize": true, 648 | "lineHeight": 1.2 649 | }, 650 | { 651 | "type": "arrow", 652 | "version": 621, 653 | "versionNonce": 1047600407, 654 | "index": "ax", 655 | "isDeleted": false, 656 | "id": "_B9NKepeZ_agtDXKQtd9V", 657 | "fillStyle": "solid", 658 | "strokeWidth": 2, 659 | "strokeStyle": "solid", 660 | "roughness": 1, 661 | "opacity": 100, 662 | "angle": 0, 663 | "x": -203.0438304952106, 664 | "y": 695.9466767736997, 665 | "strokeColor": "#ffffff", 666 | "backgroundColor": "transparent", 667 | "width": 1434.7302962325962, 668 | "height": 0, 669 | "seed": 673948599, 670 | "groupIds": [], 671 | "frameId": null, 672 | "roundness": { 673 | "type": 2 674 | }, 675 | "boundElements": [], 676 | "updated": 1726846135493, 677 | "link": null, 678 | "locked": false, 679 | "startBinding": null, 680 | "endBinding": null, 681 | "lastCommittedPoint": null, 682 | "startArrowhead": null, 683 | "endArrowhead": "arrow", 684 | "points": [ 685 | [ 686 | 0, 687 | 0 688 | ], 689 | [ 690 | 1434.7302962325962, 691 | 0 692 | ] 693 | ], 694 | "elbowed": false 695 | }, 696 | { 697 | "type": "line", 698 | "version": 291, 699 | "versionNonce": 2085893305, 700 | "index": "az", 701 | "isDeleted": false, 702 | "id": "s8_Z7ab5T0acZ75abQYUA", 703 | "fillStyle": "solid", 704 | "strokeWidth": 2, 705 | "strokeStyle": "dotted", 706 | "roughness": 1, 707 | "opacity": 100, 708 | "angle": 0, 709 | "x": 87.19448741249346, 710 | "y": 588.7229824072482, 711 | "strokeColor": "#1971c2", 712 | "backgroundColor": "transparent", 713 | "width": 0, 714 | "height": 105.22806362448102, 715 | "seed": 555223609, 716 | "groupIds": [], 717 | "frameId": null, 718 | "roundness": null, 719 | "boundElements": [], 720 | "updated": 1726846684517, 721 | "link": null, 722 | "locked": false, 723 | "startBinding": null, 724 | "endBinding": null, 725 | "lastCommittedPoint": null, 726 | "startArrowhead": null, 727 | "endArrowhead": null, 728 | "points": [ 729 | [ 730 | 0, 731 | 0 732 | ], 733 | [ 734 | 0, 735 | 105.22806362448102 736 | ] 737 | ] 738 | }, 739 | { 740 | "type": "line", 741 | "version": 337, 742 | "versionNonce": 574270999, 743 | "index": "b00", 744 | "isDeleted": false, 745 | "id": "-7Zh0XKTqxffT6Vzmkh94", 746 | "fillStyle": "solid", 747 | "strokeWidth": 2, 748 | "strokeStyle": "dotted", 749 | "roughness": 1, 750 | "opacity": 100, 751 | "angle": 0, 752 | "x": 194.83630633675685, 753 | "y": 473.33462949522675, 754 | "strokeColor": "#9c36b5", 755 | "backgroundColor": "transparent", 756 | "width": 0, 757 | "height": 221.75577134751722, 758 | "seed": 1790379479, 759 | "groupIds": [], 760 | "frameId": null, 761 | "roundness": null, 762 | "boundElements": [], 763 | "updated": 1726846356086, 764 | "link": null, 765 | "locked": false, 766 | "startBinding": null, 767 | "endBinding": null, 768 | "lastCommittedPoint": null, 769 | "startArrowhead": null, 770 | "endArrowhead": null, 771 | "points": [ 772 | [ 773 | 0, 774 | 0 775 | ], 776 | [ 777 | 0, 778 | 221.75577134751722 779 | ] 780 | ] 781 | }, 782 | { 783 | "type": "line", 784 | "version": 129, 785 | "versionNonce": 1129709721, 786 | "index": "b01", 787 | "isDeleted": false, 788 | "id": "o34qOskFtTa6em2sNFGku", 789 | "fillStyle": "solid", 790 | "strokeWidth": 2, 791 | "strokeStyle": "dotted", 792 | "roughness": 1, 793 | "opacity": 100, 794 | "angle": 0, 795 | "x": 660.3337797114793, 796 | "y": 352.4479422017043, 797 | "strokeColor": "#9c36b5", 798 | "backgroundColor": "transparent", 799 | "width": 0, 800 | "height": 341.63468670386015, 801 | "seed": 452693465, 802 | "groupIds": [], 803 | "frameId": null, 804 | "roundness": null, 805 | "boundElements": [], 806 | "updated": 1726846359416, 807 | "link": null, 808 | "locked": false, 809 | "startBinding": null, 810 | "endBinding": null, 811 | "lastCommittedPoint": null, 812 | "startArrowhead": null, 813 | "endArrowhead": null, 814 | "points": [ 815 | [ 816 | 0, 817 | 0 818 | ], 819 | [ 820 | 0, 821 | 341.63468670386015 822 | ] 823 | ] 824 | }, 825 | { 826 | "type": "text", 827 | "version": 415, 828 | "versionNonce": 497287031, 829 | "index": "b03", 830 | "isDeleted": false, 831 | "id": "gmbnCHTF", 832 | "fillStyle": "solid", 833 | "strokeWidth": 2, 834 | "strokeStyle": "solid", 835 | "roughness": 1, 836 | "opacity": 100, 837 | "angle": 5.263841895658339, 838 | "x": 273.1608108687889, 839 | "y": 742.7115490789103, 840 | "strokeColor": "#e03131", 841 | "backgroundColor": "transparent", 842 | "width": 98.4375, 843 | "height": 33.6, 844 | "seed": 955817945, 845 | "groupIds": [], 846 | "frameId": null, 847 | "roundness": null, 848 | "boundElements": [], 849 | "updated": 1726846336769, 850 | "link": null, 851 | "locked": false, 852 | "fontSize": 28, 853 | "fontFamily": 3, 854 | "text": "Color1", 855 | "rawText": "Color1", 856 | "textAlign": "left", 857 | "verticalAlign": "top", 858 | "containerId": null, 859 | "originalText": "Color1", 860 | "autoResize": true, 861 | "lineHeight": 1.2 862 | }, 863 | { 864 | "type": "text", 865 | "version": 481, 866 | "versionNonce": 1707518361, 867 | "index": "b04", 868 | "isDeleted": false, 869 | "id": "Y7TFHV9e", 870 | "fillStyle": "solid", 871 | "strokeWidth": 2, 872 | "strokeStyle": "solid", 873 | "roughness": 1, 874 | "opacity": 100, 875 | "angle": 5.263841895658339, 876 | "x": 376.91033158705653, 877 | "y": 740.9667493797006, 878 | "strokeColor": "#2f9e44", 879 | "backgroundColor": "transparent", 880 | "width": 99.03125, 881 | "height": 33.6, 882 | "seed": 677985465, 883 | "groupIds": [], 884 | "frameId": null, 885 | "roundness": null, 886 | "boundElements": [], 887 | "updated": 1726847575736, 888 | "link": null, 889 | "locked": false, 890 | "fontSize": 28, 891 | "fontFamily": 3, 892 | "text": "Color2", 893 | "rawText": "Color2", 894 | "textAlign": "left", 895 | "verticalAlign": "top", 896 | "containerId": null, 897 | "originalText": "Color2", 898 | "autoResize": true, 899 | "lineHeight": 1.2 900 | }, 901 | { 902 | "type": "text", 903 | "version": 393, 904 | "versionNonce": 1048581047, 905 | "index": "b05", 906 | "isDeleted": false, 907 | "id": "Lv66dA2H", 908 | "fillStyle": "solid", 909 | "strokeWidth": 2, 910 | "strokeStyle": "solid", 911 | "roughness": 1, 912 | "opacity": 100, 913 | "angle": 5.263841895658339, 914 | "x": 487.97764375780457, 915 | "y": 741.7960529074554, 916 | "strokeColor": "#1971c2", 917 | "backgroundColor": "transparent", 918 | "width": 98.4375, 919 | "height": 33.6, 920 | "seed": 494091673, 921 | "groupIds": [], 922 | "frameId": null, 923 | "roundness": null, 924 | "boundElements": [], 925 | "updated": 1726847553241, 926 | "link": null, 927 | "locked": false, 928 | "fontSize": 28, 929 | "fontFamily": 3, 930 | "text": "Color3", 931 | "rawText": "Color3", 932 | "textAlign": "left", 933 | "verticalAlign": "top", 934 | "containerId": null, 935 | "originalText": "Color3", 936 | "autoResize": true, 937 | "lineHeight": 1.2 938 | }, 939 | { 940 | "type": "text", 941 | "version": 435, 942 | "versionNonce": 531228439, 943 | "index": "b06", 944 | "isDeleted": false, 945 | "id": "a7oFg0ea", 946 | "fillStyle": "solid", 947 | "strokeWidth": 2, 948 | "strokeStyle": "solid", 949 | "roughness": 1, 950 | "opacity": 100, 951 | "angle": 5.263841895658339, 952 | "x": 600.0730995946033, 953 | "y": 746.6453342677283, 954 | "strokeColor": "#9c36b5", 955 | "backgroundColor": "transparent", 956 | "width": 99.03125, 957 | "height": 33.6, 958 | "seed": 1732911737, 959 | "groupIds": [], 960 | "frameId": null, 961 | "roundness": null, 962 | "boundElements": [], 963 | "updated": 1726846368124, 964 | "link": null, 965 | "locked": false, 966 | "fontSize": 28, 967 | "fontFamily": 3, 968 | "text": "Color4", 969 | "rawText": "Color4", 970 | "textAlign": "left", 971 | "verticalAlign": "top", 972 | "containerId": null, 973 | "originalText": "Color4", 974 | "autoResize": true, 975 | "lineHeight": 1.2 976 | }, 977 | { 978 | "type": "line", 979 | "version": 297, 980 | "versionNonce": 1475852761, 981 | "index": "b0J", 982 | "isDeleted": false, 983 | "id": "YM7LqhKxBOErK3AtBMg-y", 984 | "fillStyle": "solid", 985 | "strokeWidth": 2, 986 | "strokeStyle": "dotted", 987 | "roughness": 1, 988 | "opacity": 100, 989 | "angle": 0, 990 | "x": 548.5561513168425, 991 | "y": 692.4335930867182, 992 | "strokeColor": "#1971c2", 993 | "backgroundColor": "transparent", 994 | "width": 0, 995 | "height": 230.5770144500379, 996 | "seed": 1600847639, 997 | "groupIds": [], 998 | "frameId": null, 999 | "roundness": null, 1000 | "boundElements": [], 1001 | "updated": 1726847578039, 1002 | "link": null, 1003 | "locked": false, 1004 | "startBinding": null, 1005 | "endBinding": null, 1006 | "lastCommittedPoint": null, 1007 | "startArrowhead": null, 1008 | "endArrowhead": null, 1009 | "points": [ 1010 | [ 1011 | 0, 1012 | 0 1013 | ], 1014 | [ 1015 | 0, 1016 | -230.5770144500379 1017 | ] 1018 | ] 1019 | }, 1020 | { 1021 | "type": "line", 1022 | "version": 185, 1023 | "versionNonce": 1971157913, 1024 | "index": "b0K", 1025 | "isDeleted": false, 1026 | "id": "p-XdMXanC__-j7qvjpD5X", 1027 | "fillStyle": "solid", 1028 | "strokeWidth": 2, 1029 | "strokeStyle": "dotted", 1030 | "roughness": 1, 1031 | "opacity": 100, 1032 | "angle": 0, 1033 | "x": 439.05728191937067, 1034 | "y": 692.3693295865284, 1035 | "strokeColor": "#2f9e44", 1036 | "backgroundColor": "transparent", 1037 | "width": 0, 1038 | "height": 121.13583786716754, 1039 | "seed": 244457431, 1040 | "groupIds": [], 1041 | "frameId": null, 1042 | "roundness": null, 1043 | "boundElements": [], 1044 | "updated": 1726847574191, 1045 | "link": null, 1046 | "locked": false, 1047 | "startBinding": null, 1048 | "endBinding": null, 1049 | "lastCommittedPoint": null, 1050 | "startArrowhead": null, 1051 | "endArrowhead": null, 1052 | "points": [ 1053 | [ 1054 | 0, 1055 | 0 1056 | ], 1057 | [ 1058 | 0, 1059 | -121.13583786716754 1060 | ] 1061 | ] 1062 | }, 1063 | { 1064 | "type": "text", 1065 | "version": 649, 1066 | "versionNonce": 379520503, 1067 | "index": "b0L", 1068 | "isDeleted": false, 1069 | "id": "ctlgagnd", 1070 | "fillStyle": "solid", 1071 | "strokeWidth": 2, 1072 | "strokeStyle": "solid", 1073 | "roughness": 1, 1074 | "opacity": 100, 1075 | "angle": 5.263841895658339, 1076 | "x": 722.9085396400847, 1077 | "y": 743.2190730203441, 1078 | "strokeColor": "#e03131", 1079 | "backgroundColor": "transparent", 1080 | "width": 98.4375, 1081 | "height": 33.6, 1082 | "seed": 1496856823, 1083 | "groupIds": [], 1084 | "frameId": null, 1085 | "roundness": null, 1086 | "boundElements": [], 1087 | "updated": 1726848191952, 1088 | "link": null, 1089 | "locked": false, 1090 | "fontSize": 28, 1091 | "fontFamily": 3, 1092 | "text": "Color1", 1093 | "rawText": "Color1", 1094 | "textAlign": "left", 1095 | "verticalAlign": "top", 1096 | "containerId": null, 1097 | "originalText": "Color1", 1098 | "autoResize": true, 1099 | "lineHeight": 1.2 1100 | }, 1101 | { 1102 | "type": "text", 1103 | "version": 706, 1104 | "versionNonce": 1798682873, 1105 | "index": "b0M", 1106 | "isDeleted": false, 1107 | "id": "t9fja5R9", 1108 | "fillStyle": "solid", 1109 | "strokeWidth": 2, 1110 | "strokeStyle": "solid", 1111 | "roughness": 1, 1112 | "opacity": 100, 1113 | "angle": 5.263841895658339, 1114 | "x": 832.6262471681084, 1115 | "y": 743.564927677062, 1116 | "strokeColor": "#2f9e44", 1117 | "backgroundColor": "transparent", 1118 | "width": 99.03125, 1119 | "height": 33.6, 1120 | "seed": 1976828439, 1121 | "groupIds": [], 1122 | "frameId": null, 1123 | "roundness": null, 1124 | "boundElements": [], 1125 | "updated": 1726848191952, 1126 | "link": null, 1127 | "locked": false, 1128 | "fontSize": 28, 1129 | "fontFamily": 3, 1130 | "text": "Color2", 1131 | "rawText": "Color2", 1132 | "textAlign": "left", 1133 | "verticalAlign": "top", 1134 | "containerId": null, 1135 | "originalText": "Color2", 1136 | "autoResize": true, 1137 | "lineHeight": 1.2 1138 | }, 1139 | { 1140 | "type": "text", 1141 | "version": 619, 1142 | "versionNonce": 1457073943, 1143 | "index": "b0N", 1144 | "isDeleted": false, 1145 | "id": "TnhXLOoi", 1146 | "fillStyle": "solid", 1147 | "strokeWidth": 2, 1148 | "strokeStyle": "solid", 1149 | "roughness": 1, 1150 | "opacity": 100, 1151 | "angle": 5.263841895658339, 1152 | "x": 942.3378420637391, 1153 | "y": 743.0148016652366, 1154 | "strokeColor": "#1971c2", 1155 | "backgroundColor": "transparent", 1156 | "width": 98.4375, 1157 | "height": 33.6, 1158 | "seed": 1515824951, 1159 | "groupIds": [], 1160 | "frameId": null, 1161 | "roundness": null, 1162 | "boundElements": [], 1163 | "updated": 1726848191952, 1164 | "link": null, 1165 | "locked": false, 1166 | "fontSize": 28, 1167 | "fontFamily": 3, 1168 | "text": "Color3", 1169 | "rawText": "Color3", 1170 | "textAlign": "left", 1171 | "verticalAlign": "top", 1172 | "containerId": null, 1173 | "originalText": "Color3", 1174 | "autoResize": true, 1175 | "lineHeight": 1.2 1176 | }, 1177 | { 1178 | "type": "text", 1179 | "version": 675, 1180 | "versionNonce": 31863257, 1181 | "index": "b0O", 1182 | "isDeleted": false, 1183 | "id": "x71kkljs", 1184 | "fillStyle": "solid", 1185 | "strokeWidth": 2, 1186 | "strokeStyle": "solid", 1187 | "roughness": 1, 1188 | "opacity": 100, 1189 | "angle": 5.263841895658339, 1190 | "x": 1049.8208283658992, 1191 | "y": 748.3253299789732, 1192 | "strokeColor": "#9c36b5", 1193 | "backgroundColor": "transparent", 1194 | "width": 99.03125, 1195 | "height": 33.6, 1196 | "seed": 623749207, 1197 | "groupIds": [], 1198 | "frameId": null, 1199 | "roundness": null, 1200 | "boundElements": [], 1201 | "updated": 1726848191952, 1202 | "link": null, 1203 | "locked": false, 1204 | "fontSize": 28, 1205 | "fontFamily": 3, 1206 | "text": "Color4", 1207 | "rawText": "Color4", 1208 | "textAlign": "left", 1209 | "verticalAlign": "top", 1210 | "containerId": null, 1211 | "originalText": "Color4", 1212 | "autoResize": true, 1213 | "lineHeight": 1.2 1214 | }, 1215 | { 1216 | "type": "line", 1217 | "version": 794, 1218 | "versionNonce": 472986679, 1219 | "index": "b0b", 1220 | "isDeleted": false, 1221 | "id": "-_aDlA2L2mooHX4fqorNO", 1222 | "fillStyle": "solid", 1223 | "strokeWidth": 2, 1224 | "strokeStyle": "dotted", 1225 | "roughness": 1, 1226 | "opacity": 100, 1227 | "angle": 0, 1228 | "x": 903.1500423058939, 1229 | "y": 690.8263004386707, 1230 | "strokeColor": "#2f9e44", 1231 | "backgroundColor": "transparent", 1232 | "width": 1.1368683772161603e-13, 1233 | "height": 125.22603197856427, 1234 | "seed": 1110771031, 1235 | "groupIds": [], 1236 | "frameId": null, 1237 | "roundness": null, 1238 | "boundElements": [], 1239 | "updated": 1726848191952, 1240 | "link": null, 1241 | "locked": false, 1242 | "startBinding": null, 1243 | "endBinding": null, 1244 | "lastCommittedPoint": null, 1245 | "startArrowhead": null, 1246 | "endArrowhead": null, 1247 | "points": [ 1248 | [ 1249 | 0, 1250 | 0 1251 | ], 1252 | [ 1253 | -1.1368683772161603e-13, 1254 | -125.22603197856427 1255 | ] 1256 | ] 1257 | }, 1258 | { 1259 | "type": "line", 1260 | "version": 711, 1261 | "versionNonce": 1641320121, 1262 | "index": "b0c", 1263 | "isDeleted": false, 1264 | "id": "Zh_wY1MVJP0tcBlwPOOH7", 1265 | "fillStyle": "solid", 1266 | "strokeWidth": 2, 1267 | "strokeStyle": "dotted", 1268 | "roughness": 1, 1269 | "opacity": 100, 1270 | "angle": 0, 1271 | "x": 1022.1937706344805, 1272 | "y": 696.4316392152556, 1273 | "strokeColor": "#1971c2", 1274 | "backgroundColor": "transparent", 1275 | "width": 0, 1276 | "height": 245.8320330626284, 1277 | "seed": 6381303, 1278 | "groupIds": [], 1279 | "frameId": null, 1280 | "roundness": null, 1281 | "boundElements": [], 1282 | "updated": 1726848191952, 1283 | "link": null, 1284 | "locked": false, 1285 | "startBinding": null, 1286 | "endBinding": null, 1287 | "lastCommittedPoint": null, 1288 | "startArrowhead": null, 1289 | "endArrowhead": null, 1290 | "points": [ 1291 | [ 1292 | 0, 1293 | 0 1294 | ], 1295 | [ 1296 | 0, 1297 | -245.8320330626284 1298 | ] 1299 | ] 1300 | }, 1301 | { 1302 | "type": "line", 1303 | "version": 287, 1304 | "versionNonce": 1049335127, 1305 | "index": "b0d", 1306 | "isDeleted": false, 1307 | "id": "2wlpyeZNut2QCx3qY0dxr", 1308 | "fillStyle": "solid", 1309 | "strokeWidth": 2, 1310 | "strokeStyle": "dotted", 1311 | "roughness": 1, 1312 | "opacity": 100, 1313 | "angle": 0, 1314 | "x": 1116.3636094501992, 1315 | "y": 695.715057953658, 1316 | "strokeColor": "#9c36b5", 1317 | "backgroundColor": "transparent", 1318 | "width": 0, 1319 | "height": 340.25118647175384, 1320 | "seed": 1099798007, 1321 | "groupIds": [], 1322 | "frameId": null, 1323 | "roundness": null, 1324 | "boundElements": [], 1325 | "updated": 1726848191952, 1326 | "link": null, 1327 | "locked": false, 1328 | "startBinding": null, 1329 | "endBinding": null, 1330 | "lastCommittedPoint": null, 1331 | "startArrowhead": null, 1332 | "endArrowhead": null, 1333 | "points": [ 1334 | [ 1335 | 0, 1336 | 0 1337 | ], 1338 | [ 1339 | 0, 1340 | -340.25118647175384 1341 | ] 1342 | ] 1343 | }, 1344 | { 1345 | "type": "line", 1346 | "version": 1691, 1347 | "versionNonce": 373888473, 1348 | "index": "b0f", 1349 | "isDeleted": false, 1350 | "id": "lggx05CfCBDvB3Mch_yg-", 1351 | "fillStyle": "solid", 1352 | "strokeWidth": 2, 1353 | "strokeStyle": "solid", 1354 | "roughness": 1, 1355 | "opacity": 100, 1356 | "angle": 0, 1357 | "x": -130.25413958188972, 1358 | "y": 682.5247531382743, 1359 | "strokeColor": "#ffffff", 1360 | "backgroundColor": "transparent", 1361 | "width": 334.1716277877263, 1362 | "height": 329.923937868933, 1363 | "seed": 2065625495, 1364 | "groupIds": [], 1365 | "frameId": null, 1366 | "roundness": null, 1367 | "boundElements": [], 1368 | "updated": 1726846011781, 1369 | "link": null, 1370 | "locked": false, 1371 | "startBinding": null, 1372 | "endBinding": null, 1373 | "lastCommittedPoint": null, 1374 | "startArrowhead": null, 1375 | "endArrowhead": null, 1376 | "points": [ 1377 | [ 1378 | 0, 1379 | 0 1380 | ], 1381 | [ 1382 | 109.45672976460173, 1383 | -1.5220525953325392 1384 | ], 1385 | [ 1386 | 109.07744988893313, 1387 | -95.4290119913573 1388 | ], 1389 | [ 1390 | 217.428556120559, 1391 | -93.90695939602477 1392 | ], 1393 | [ 1394 | 217.1778140083565, 1395 | -207.8897751211348 1396 | ], 1397 | [ 1398 | 325.02748356653524, 1399 | -208.55699082488422 1400 | ], 1401 | [ 1402 | 325.02748356653524, 1403 | -329.923937868933 1404 | ], 1405 | [ 1406 | 334.1716277877263, 1407 | -329.7500837385333 1408 | ] 1409 | ] 1410 | }, 1411 | { 1412 | "type": "line", 1413 | "version": 534, 1414 | "versionNonce": 417854233, 1415 | "index": "b0h", 1416 | "isDeleted": false, 1417 | "id": "JPkYX7cGtZZyoOfjepNQU", 1418 | "fillStyle": "solid", 1419 | "strokeWidth": 2, 1420 | "strokeStyle": "dashed", 1421 | "roughness": 1, 1422 | "opacity": 100, 1423 | "angle": 0, 1424 | "x": 260.6746600686558, 1425 | "y": 803.0335116959067, 1426 | "strokeColor": "#ffffff", 1427 | "backgroundColor": "transparent", 1428 | "width": 2.842170943040401e-14, 1429 | "height": 509.6302867645712, 1430 | "seed": 1468007255, 1431 | "groupIds": [], 1432 | "frameId": null, 1433 | "roundness": null, 1434 | "boundElements": [], 1435 | "updated": 1726848210312, 1436 | "link": null, 1437 | "locked": false, 1438 | "startBinding": null, 1439 | "endBinding": null, 1440 | "lastCommittedPoint": null, 1441 | "startArrowhead": null, 1442 | "endArrowhead": null, 1443 | "points": [ 1444 | [ 1445 | 0, 1446 | 0 1447 | ], 1448 | [ 1449 | 2.842170943040401e-14, 1450 | -509.6302867645712 1451 | ] 1452 | ] 1453 | }, 1454 | { 1455 | "type": "line", 1456 | "version": 633, 1457 | "versionNonce": 290729497, 1458 | "index": "b0i", 1459 | "isDeleted": false, 1460 | "id": "XB2q2TDaYGzyzZLMAYC9S", 1461 | "fillStyle": "solid", 1462 | "strokeWidth": 2, 1463 | "strokeStyle": "dashed", 1464 | "roughness": 1, 1465 | "opacity": 100, 1466 | "angle": 0, 1467 | "x": 723.0335879500475, 1468 | "y": 803.0335117230943, 1469 | "strokeColor": "#ffffff", 1470 | "backgroundColor": "transparent", 1471 | "width": 0, 1472 | "height": 502.5954561457054, 1473 | "seed": 969684953, 1474 | "groupIds": [], 1475 | "frameId": null, 1476 | "roundness": null, 1477 | "boundElements": [], 1478 | "updated": 1726848203943, 1479 | "link": null, 1480 | "locked": false, 1481 | "startBinding": null, 1482 | "endBinding": null, 1483 | "lastCommittedPoint": null, 1484 | "startArrowhead": null, 1485 | "endArrowhead": null, 1486 | "points": [ 1487 | [ 1488 | 0, 1489 | 0 1490 | ], 1491 | [ 1492 | 0, 1493 | -502.5954561457054 1494 | ] 1495 | ] 1496 | }, 1497 | { 1498 | "type": "line", 1499 | "version": 54, 1500 | "versionNonce": 880261881, 1501 | "index": "b0j", 1502 | "isDeleted": false, 1503 | "id": "AARXpMRVApzxmPlWK28re", 1504 | "fillStyle": "solid", 1505 | "strokeWidth": 2, 1506 | "strokeStyle": "dotted", 1507 | "roughness": 1, 1508 | "opacity": 100, 1509 | "angle": 0, 1510 | "x": -21.316894895549638, 1511 | "y": 681.3875622387928, 1512 | "strokeColor": "#2f9e44", 1513 | "backgroundColor": "transparent", 1514 | "width": 0, 1515 | "height": 13.582144641012064, 1516 | "seed": 1974160503, 1517 | "groupIds": [], 1518 | "frameId": null, 1519 | "roundness": null, 1520 | "boundElements": [], 1521 | "updated": 1726846695517, 1522 | "link": null, 1523 | "locked": false, 1524 | "startBinding": null, 1525 | "endBinding": null, 1526 | "lastCommittedPoint": null, 1527 | "startArrowhead": null, 1528 | "endArrowhead": null, 1529 | "points": [ 1530 | [ 1531 | 0, 1532 | 0 1533 | ], 1534 | [ 1535 | 0, 1536 | 13.582144641012064 1537 | ] 1538 | ] 1539 | }, 1540 | { 1541 | "type": "line", 1542 | "version": 2865, 1543 | "versionNonce": 624743031, 1544 | "index": "b0k", 1545 | "isDeleted": false, 1546 | "id": "JNmfOtJIzq7mm63mk_hQ1", 1547 | "fillStyle": "solid", 1548 | "strokeWidth": 2, 1549 | "strokeStyle": "solid", 1550 | "roughness": 0, 1551 | "opacity": 100, 1552 | "angle": 0, 1553 | "x": 785.7218368440547, 1554 | "y": 678.8839175740147, 1555 | "strokeColor": "#ffffff", 1556 | "backgroundColor": "transparent", 1557 | "width": 329.501298622569, 1558 | "height": 329.6627145184708, 1559 | "seed": 1778646167, 1560 | "groupIds": [], 1561 | "frameId": null, 1562 | "roundness": { 1563 | "type": 2 1564 | }, 1565 | "boundElements": [], 1566 | "updated": 1726848191952, 1567 | "link": null, 1568 | "locked": false, 1569 | "startBinding": null, 1570 | "endBinding": null, 1571 | "lastCommittedPoint": null, 1572 | "startArrowhead": null, 1573 | "endArrowhead": null, 1574 | "points": [ 1575 | [ 1576 | 0, 1577 | 0 1578 | ], 1579 | [ 1580 | 43.708543599618224, 1581 | -19.667803426562614 1582 | ], 1583 | [ 1584 | 68.00175267987083, 1585 | -89.47202013197023 1586 | ], 1587 | [ 1588 | 116.75857467601895, 1589 | -116.26477985537451 1590 | ], 1591 | [ 1592 | 162.0726907236335, 1593 | -141.69128373948888 1594 | ], 1595 | [ 1596 | 189.07279327137962, 1597 | -205.40109063858108 1598 | ], 1599 | [ 1600 | 234.9429603450676, 1601 | -226.60808940494957 1602 | ], 1603 | [ 1604 | 269.39987221002457, 1605 | -250.10151692891804 1606 | ], 1607 | [ 1608 | 292.3560045659269, 1609 | -307.50132345165576 1610 | ], 1611 | [ 1612 | 329.501298622569, 1613 | -329.6627145184708 1614 | ] 1615 | ] 1616 | }, 1617 | { 1618 | "type": "image", 1619 | "version": 193, 1620 | "versionNonce": 1544493388, 1621 | "index": "b0l", 1622 | "isDeleted": false, 1623 | "id": "v2Vb78iv", 1624 | "fillStyle": "hachure", 1625 | "strokeWidth": 1, 1626 | "strokeStyle": "solid", 1627 | "roughness": 1, 1628 | "opacity": 100, 1629 | "angle": 0, 1630 | "x": -1806.2393252965508, 1631 | "y": 1060.7962510832128, 1632 | "strokeColor": "transparent", 1633 | "backgroundColor": "transparent", 1634 | "width": 1117.4294136000353, 1635 | "height": 346.95143383351353, 1636 | "seed": 39140, 1637 | "groupIds": [], 1638 | "frameId": null, 1639 | "roundness": null, 1640 | "boundElements": [], 1641 | "updated": 1728745169163, 1642 | "link": null, 1643 | "locked": false, 1644 | "status": "pending", 1645 | "fileId": "795bb870a9d2da1ae87cd3fc775909c75f95868f", 1646 | "scale": [ 1647 | 1, 1648 | 1 1649 | ] 1650 | }, 1651 | { 1652 | "type": "image", 1653 | "version": 160, 1654 | "versionNonce": 252273268, 1655 | "index": "b0o", 1656 | "isDeleted": false, 1657 | "id": "EtNdEbnR", 1658 | "fillStyle": "hachure", 1659 | "strokeWidth": 1, 1660 | "strokeStyle": "solid", 1661 | "roughness": 1, 1662 | "opacity": 100, 1663 | "angle": 0, 1664 | "x": 1099.336452950346, 1665 | "y": 1136.1278944587234, 1666 | "strokeColor": "transparent", 1667 | "backgroundColor": "transparent", 1668 | "width": 433, 1669 | "height": 433, 1670 | "seed": 61971, 1671 | "groupIds": [], 1672 | "frameId": null, 1673 | "roundness": null, 1674 | "boundElements": [], 1675 | "updated": 1728745341106, 1676 | "link": null, 1677 | "locked": false, 1678 | "status": "pending", 1679 | "fileId": "c1c6734fff58ed63a4d7cb9270729e931378f5f5", 1680 | "scale": [ 1681 | 1, 1682 | 1 1683 | ] 1684 | }, 1685 | { 1686 | "type": "image", 1687 | "version": 327, 1688 | "versionNonce": 1985211124, 1689 | "index": "b0p", 1690 | "isDeleted": false, 1691 | "id": "g77BOChm", 1692 | "fillStyle": "hachure", 1693 | "strokeWidth": 1, 1694 | "strokeStyle": "solid", 1695 | "roughness": 1, 1696 | "opacity": 100, 1697 | "angle": 0, 1698 | "x": 1553.4562604136001, 1699 | "y": 1136.1278936587455, 1700 | "strokeColor": "transparent", 1701 | "backgroundColor": "transparent", 1702 | "width": 381.42006299652826, 1703 | "height": 433.00000034125446, 1704 | "seed": 26504, 1705 | "groupIds": [], 1706 | "frameId": null, 1707 | "roundness": null, 1708 | "boundElements": [], 1709 | "updated": 1728745351625, 1710 | "link": null, 1711 | "locked": false, 1712 | "status": "pending", 1713 | "fileId": "4f81cf23976e60878ca4e0829d90475ad89cf5c8", 1714 | "scale": [ 1715 | 1, 1716 | 1 1717 | ] 1718 | }, 1719 | { 1720 | "type": "image", 1721 | "version": 586, 1722 | "versionNonce": 1788662004, 1723 | "index": "b0q", 1724 | "isDeleted": false, 1725 | "id": "p4fTTn6G", 1726 | "fillStyle": "hachure", 1727 | "strokeWidth": 1, 1728 | "strokeStyle": "solid", 1729 | "roughness": 1, 1730 | "opacity": 100, 1731 | "angle": 0, 1732 | "x": 1099.3364529166338, 1733 | "y": 914.4035176930753, 1734 | "strokeColor": "transparent", 1735 | "backgroundColor": "transparent", 1736 | "width": 835.5398700833659, 1737 | "height": 198.0538951308719, 1738 | "seed": 98171, 1739 | "groupIds": [], 1740 | "frameId": null, 1741 | "roundness": null, 1742 | "boundElements": [], 1743 | "updated": 1728745352875, 1744 | "link": null, 1745 | "locked": false, 1746 | "status": "pending", 1747 | "fileId": "b3cfe2e699013e8cc8becaac0feb81fede5e6d70", 1748 | "scale": [ 1749 | 1, 1750 | 1 1751 | ] 1752 | }, 1753 | { 1754 | "id": "iYSHePMZ", 1755 | "type": "image", 1756 | "x": -377.67323243052897, 1757 | "y": 1072.3371627686204, 1758 | "width": 499.99999999999994, 1759 | "height": 323.86961093585694, 1760 | "angle": 0, 1761 | "strokeColor": "transparent", 1762 | "backgroundColor": "transparent", 1763 | "fillStyle": "hachure", 1764 | "strokeWidth": 1, 1765 | "strokeStyle": "solid", 1766 | "roughness": 1, 1767 | "opacity": 100, 1768 | "roundness": null, 1769 | "seed": 30068, 1770 | "version": 26, 1771 | "versionNonce": 660351820, 1772 | "updated": 1728745491547, 1773 | "isDeleted": true, 1774 | "groupIds": [], 1775 | "boundElements": [], 1776 | "link": null, 1777 | "locked": false, 1778 | "fileId": "cfebb366f90c51f2419e239adb9af7b235acc654", 1779 | "scale": [ 1780 | 1, 1781 | 1 1782 | ], 1783 | "index": "b0r" 1784 | } 1785 | ], 1786 | "appState": { 1787 | "theme": "light", 1788 | "viewBackgroundColor": "#1e1e1e", 1789 | "currentItemStrokeColor": "#ffffff", 1790 | "currentItemBackgroundColor": "transparent", 1791 | "currentItemFillStyle": "solid", 1792 | "currentItemStrokeWidth": 2, 1793 | "currentItemStrokeStyle": "solid", 1794 | "currentItemRoughness": 0, 1795 | "currentItemOpacity": 100, 1796 | "currentItemFontFamily": 3, 1797 | "currentItemFontSize": 28, 1798 | "currentItemTextAlign": "left", 1799 | "currentItemStartArrowhead": null, 1800 | "currentItemEndArrowhead": "arrow", 1801 | "currentItemArrowType": "round", 1802 | "scrollX": 1904.6913964801222, 1803 | "scrollY": 303.42265089920124, 1804 | "zoom": { 1805 | "value": 0.381283 1806 | }, 1807 | "currentItemRoundness": "round", 1808 | "gridSize": 20, 1809 | "gridStep": 5, 1810 | "gridModeEnabled": false, 1811 | "gridColor": { 1812 | "Bold": "rgba(68, 68, 68, 0.5)", 1813 | "Regular": "rgba(56, 56, 56, 0.5)" 1814 | }, 1815 | "currentStrokeOptions": null, 1816 | "frameRendering": { 1817 | "enabled": true, 1818 | "clip": true, 1819 | "name": true, 1820 | "outline": true 1821 | }, 1822 | "objectsSnapModeEnabled": true, 1823 | "activeTool": { 1824 | "type": "selection", 1825 | "customType": null, 1826 | "locked": false, 1827 | "lastActiveTool": null 1828 | } 1829 | }, 1830 | "files": {} 1831 | } 1832 | ``` 1833 | %% --------------------------------------------------------------------------------