ue-mcp 0.4.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +89 -0
- package/dist/bridge.d.ts +25 -0
- package/dist/bridge.js +124 -0
- package/dist/bridge.js.map +1 -0
- package/dist/deployer.d.ts +18 -0
- package/dist/deployer.js +175 -0
- package/dist/deployer.js.map +1 -0
- package/dist/editor-control.d.ts +15 -0
- package/dist/editor-control.js +181 -0
- package/dist/editor-control.js.map +1 -0
- package/dist/github-app.d.ts +4 -0
- package/dist/github-app.js +94 -0
- package/dist/github-app.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +125 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +2 -0
- package/dist/init.js +290 -0
- package/dist/init.js.map +1 -0
- package/dist/instructions.d.ts +1 -0
- package/dist/instructions.js +150 -0
- package/dist/instructions.js.map +1 -0
- package/dist/project.d.ts +32 -0
- package/dist/project.js +175 -0
- package/dist/project.js.map +1 -0
- package/dist/tools/animation.d.ts +2 -0
- package/dist/tools/animation.js +62 -0
- package/dist/tools/animation.js.map +1 -0
- package/dist/tools/asset.d.ts +2 -0
- package/dist/tools/asset.js +128 -0
- package/dist/tools/asset.js.map +1 -0
- package/dist/tools/audio.d.ts +2 -0
- package/dist/tools/audio.js +24 -0
- package/dist/tools/audio.js.map +1 -0
- package/dist/tools/blueprint.d.ts +2 -0
- package/dist/tools/blueprint.js +64 -0
- package/dist/tools/blueprint.js.map +1 -0
- package/dist/tools/demo.d.ts +2 -0
- package/dist/tools/demo.js +10 -0
- package/dist/tools/demo.js.map +1 -0
- package/dist/tools/editor.d.ts +2 -0
- package/dist/tools/editor.js +133 -0
- package/dist/tools/editor.js.map +1 -0
- package/dist/tools/feedback.d.ts +2 -0
- package/dist/tools/feedback.js +44 -0
- package/dist/tools/feedback.js.map +1 -0
- package/dist/tools/foliage.d.ts +2 -0
- package/dist/tools/foliage.js +29 -0
- package/dist/tools/foliage.js.map +1 -0
- package/dist/tools/gameplay.d.ts +2 -0
- package/dist/tools/gameplay.js +101 -0
- package/dist/tools/gameplay.js.map +1 -0
- package/dist/tools/gas.d.ts +2 -0
- package/dist/tools/gas.js +43 -0
- package/dist/tools/gas.js.map +1 -0
- package/dist/tools/landscape.d.ts +2 -0
- package/dist/tools/landscape.js +32 -0
- package/dist/tools/landscape.js.map +1 -0
- package/dist/tools/level.d.ts +2 -0
- package/dist/tools/level.js +66 -0
- package/dist/tools/level.js.map +1 -0
- package/dist/tools/material.d.ts +2 -0
- package/dist/tools/material.js +59 -0
- package/dist/tools/material.js.map +1 -0
- package/dist/tools/networking.d.ts +2 -0
- package/dist/tools/networking.js +42 -0
- package/dist/tools/networking.js.map +1 -0
- package/dist/tools/niagara.d.ts +2 -0
- package/dist/tools/niagara.js +42 -0
- package/dist/tools/niagara.js.map +1 -0
- package/dist/tools/pcg.d.ts +2 -0
- package/dist/tools/pcg.js +39 -0
- package/dist/tools/pcg.js.map +1 -0
- package/dist/tools/project.d.ts +2 -0
- package/dist/tools/project.js +282 -0
- package/dist/tools/project.js.map +1 -0
- package/dist/tools/reflection.d.ts +2 -0
- package/dist/tools/reflection.js +26 -0
- package/dist/tools/reflection.js.map +1 -0
- package/dist/tools/widget.d.ts +2 -0
- package/dist/tools/widget.js +34 -0
- package/dist/tools/widget.js.map +1 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.js +38 -0
- package/dist/types.js.map +1 -0
- package/package.json +76 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +746 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.h +81 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +49 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +37 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.cpp +75 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.h +50 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +1418 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +43 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +1773 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +48 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.cpp +289 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.h +18 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +1982 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +44 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +1217 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.h +71 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.cpp +465 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.h +49 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +1676 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +53 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +876 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +22 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +2222 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +54 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +783 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +24 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +898 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +24 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +1270 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +34 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +2190 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +53 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.cpp +554 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.h +29 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +601 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.h +25 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +1024 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.h +25 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +370 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.h +19 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.cpp +499 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.h +27 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +426 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +19 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +303 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.h +18 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +985 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +27 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.cpp +181 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.h +42 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +39 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/UE_MCP_BridgeModule.h +14 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
- package/plugin/ue_mcp_bridge/UE_MCP_Bridge.uplugin +55 -0
|
@@ -0,0 +1,898 @@
|
|
|
1
|
+
#include "LandscapeHandlers.h"
|
|
2
|
+
#include "HandlerRegistry.h"
|
|
3
|
+
#include "Dom/JsonObject.h"
|
|
4
|
+
#include "Dom/JsonValue.h"
|
|
5
|
+
#include "Editor.h"
|
|
6
|
+
#include "Engine/World.h"
|
|
7
|
+
#include "EngineUtils.h"
|
|
8
|
+
#include "Landscape.h"
|
|
9
|
+
#include "LandscapeProxy.h"
|
|
10
|
+
#include "LandscapeInfo.h"
|
|
11
|
+
#include "LandscapeComponent.h"
|
|
12
|
+
#include "LandscapeSplineActor.h"
|
|
13
|
+
#include "LandscapeSplinesComponent.h"
|
|
14
|
+
#include "LandscapeSplineControlPoint.h"
|
|
15
|
+
#include "LandscapeSplineSegment.h"
|
|
16
|
+
#include "Kismet/KismetSystemLibrary.h"
|
|
17
|
+
#include "Misc/FileHelper.h"
|
|
18
|
+
#include "Materials/MaterialInterface.h"
|
|
19
|
+
#include "EditorScriptingUtilities/Public/EditorAssetLibrary.h"
|
|
20
|
+
#include "Components/PrimitiveComponent.h"
|
|
21
|
+
#include "LandscapeLayerInfoObject.h"
|
|
22
|
+
#include "UObject/Package.h"
|
|
23
|
+
#include "AssetRegistry/AssetRegistryModule.h"
|
|
24
|
+
#include "AssetToolsModule.h"
|
|
25
|
+
#include "IAssetTools.h"
|
|
26
|
+
|
|
27
|
+
void FLandscapeHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
|
|
28
|
+
{
|
|
29
|
+
Registry.RegisterHandler(TEXT("get_landscape_info"), &GetLandscapeInfo);
|
|
30
|
+
Registry.RegisterHandler(TEXT("list_landscape_layers"), &ListLandscapeLayers);
|
|
31
|
+
Registry.RegisterHandler(TEXT("sample_landscape"), &SampleLandscape);
|
|
32
|
+
Registry.RegisterHandler(TEXT("list_landscape_splines"), &ListLandscapeSplines);
|
|
33
|
+
Registry.RegisterHandler(TEXT("get_landscape_component"), &GetLandscapeComponent);
|
|
34
|
+
Registry.RegisterHandler(TEXT("sculpt_landscape"), &SculptLandscape);
|
|
35
|
+
Registry.RegisterHandler(TEXT("paint_landscape_layer"), &PaintLandscapeLayer);
|
|
36
|
+
Registry.RegisterHandler(TEXT("import_heightmap"), &ImportHeightmap);
|
|
37
|
+
Registry.RegisterHandler(TEXT("set_landscape_material"), &SetLandscapeMaterial);
|
|
38
|
+
Registry.RegisterHandler(TEXT("get_landscape_bounds"), &GetLandscapeBounds);
|
|
39
|
+
Registry.RegisterHandler(TEXT("add_landscape_layer_info"), &AddLandscapeLayerInfo);
|
|
40
|
+
Registry.RegisterHandler(TEXT("import_landscape_heightmap"), &ImportHeightmap);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::GetLandscapeInfo(const TSharedPtr<FJsonObject>& Params)
|
|
44
|
+
{
|
|
45
|
+
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
46
|
+
|
|
47
|
+
UWorld* World = GEditor->GetEditorWorldContext().World();
|
|
48
|
+
if (!World)
|
|
49
|
+
{
|
|
50
|
+
Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
|
|
51
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
52
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Find landscape proxies in the world
|
|
56
|
+
TArray<TSharedPtr<FJsonValue>> LandscapeArray;
|
|
57
|
+
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
58
|
+
{
|
|
59
|
+
ALandscapeProxy* Landscape = *It;
|
|
60
|
+
if (!Landscape) continue;
|
|
61
|
+
|
|
62
|
+
TSharedPtr<FJsonObject> LandscapeObj = MakeShared<FJsonObject>();
|
|
63
|
+
LandscapeObj->SetStringField(TEXT("name"), Landscape->GetName());
|
|
64
|
+
LandscapeObj->SetStringField(TEXT("class"), Landscape->GetClass()->GetName());
|
|
65
|
+
|
|
66
|
+
// Get component count
|
|
67
|
+
TArray<ULandscapeComponent*> LandscapeComponents;
|
|
68
|
+
Landscape->GetComponents<ULandscapeComponent>(LandscapeComponents);
|
|
69
|
+
LandscapeObj->SetNumberField(TEXT("componentCount"), LandscapeComponents.Num());
|
|
70
|
+
|
|
71
|
+
// Get bounds
|
|
72
|
+
FBox Bounds = Landscape->GetComponentsBoundingBox();
|
|
73
|
+
if (Bounds.IsValid)
|
|
74
|
+
{
|
|
75
|
+
TSharedPtr<FJsonObject> BoundsObj = MakeShared<FJsonObject>();
|
|
76
|
+
BoundsObj->SetNumberField(TEXT("minX"), Bounds.Min.X);
|
|
77
|
+
BoundsObj->SetNumberField(TEXT("minY"), Bounds.Min.Y);
|
|
78
|
+
BoundsObj->SetNumberField(TEXT("minZ"), Bounds.Min.Z);
|
|
79
|
+
BoundsObj->SetNumberField(TEXT("maxX"), Bounds.Max.X);
|
|
80
|
+
BoundsObj->SetNumberField(TEXT("maxY"), Bounds.Max.Y);
|
|
81
|
+
BoundsObj->SetNumberField(TEXT("maxZ"), Bounds.Max.Z);
|
|
82
|
+
|
|
83
|
+
FVector Size = Bounds.GetSize();
|
|
84
|
+
BoundsObj->SetNumberField(TEXT("sizeX"), Size.X);
|
|
85
|
+
BoundsObj->SetNumberField(TEXT("sizeY"), Size.Y);
|
|
86
|
+
BoundsObj->SetNumberField(TEXT("sizeZ"), Size.Z);
|
|
87
|
+
LandscapeObj->SetObjectField(TEXT("bounds"), BoundsObj);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Get location
|
|
91
|
+
FVector Location = Landscape->GetActorLocation();
|
|
92
|
+
LandscapeObj->SetNumberField(TEXT("locationX"), Location.X);
|
|
93
|
+
LandscapeObj->SetNumberField(TEXT("locationY"), Location.Y);
|
|
94
|
+
LandscapeObj->SetNumberField(TEXT("locationZ"), Location.Z);
|
|
95
|
+
|
|
96
|
+
LandscapeArray.Add(MakeShared<FJsonValueObject>(LandscapeObj));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (LandscapeArray.Num() == 0)
|
|
100
|
+
{
|
|
101
|
+
Result->SetStringField(TEXT("landscape"), TEXT("none"));
|
|
102
|
+
}
|
|
103
|
+
else
|
|
104
|
+
{
|
|
105
|
+
Result->SetArrayField(TEXT("landscapes"), LandscapeArray);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
Result->SetNumberField(TEXT("count"), LandscapeArray.Num());
|
|
109
|
+
Result->SetBoolField(TEXT("success"), true);
|
|
110
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::ListLandscapeLayers(const TSharedPtr<FJsonObject>& Params)
|
|
114
|
+
{
|
|
115
|
+
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
116
|
+
|
|
117
|
+
UWorld* World = GEditor->GetEditorWorldContext().World();
|
|
118
|
+
if (!World)
|
|
119
|
+
{
|
|
120
|
+
Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
|
|
121
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
122
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
TArray<TSharedPtr<FJsonValue>> LayerArray;
|
|
126
|
+
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
127
|
+
{
|
|
128
|
+
ALandscapeProxy* Landscape = *It;
|
|
129
|
+
if (!Landscape) continue;
|
|
130
|
+
|
|
131
|
+
ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo();
|
|
132
|
+
if (LandscapeInfo)
|
|
133
|
+
{
|
|
134
|
+
for (const FLandscapeInfoLayerSettings& LayerSettings : LandscapeInfo->Layers)
|
|
135
|
+
{
|
|
136
|
+
if (LayerSettings.LayerInfoObj)
|
|
137
|
+
{
|
|
138
|
+
TSharedPtr<FJsonObject> LayerObj = MakeShared<FJsonObject>();
|
|
139
|
+
LayerObj->SetStringField(TEXT("name"), LayerSettings.GetLayerName().ToString());
|
|
140
|
+
LayerObj->SetStringField(TEXT("landscapeName"), Landscape->GetName());
|
|
141
|
+
LayerArray.Add(MakeShared<FJsonValueObject>(LayerObj));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
Result->SetArrayField(TEXT("layers"), LayerArray);
|
|
148
|
+
Result->SetNumberField(TEXT("count"), LayerArray.Num());
|
|
149
|
+
Result->SetBoolField(TEXT("success"), true);
|
|
150
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::SampleLandscape(const TSharedPtr<FJsonObject>& Params)
|
|
154
|
+
{
|
|
155
|
+
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
156
|
+
|
|
157
|
+
const TSharedPtr<FJsonObject>* PointObj = nullptr;
|
|
158
|
+
if (!Params->TryGetObjectField(TEXT("point"), PointObj))
|
|
159
|
+
{
|
|
160
|
+
Result->SetStringField(TEXT("error"), TEXT("Missing 'point' parameter"));
|
|
161
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
162
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
FVector Point;
|
|
166
|
+
Point.X = (*PointObj)->GetNumberField(TEXT("x"));
|
|
167
|
+
Point.Y = (*PointObj)->GetNumberField(TEXT("y"));
|
|
168
|
+
Point.Z = (*PointObj)->GetNumberField(TEXT("z"));
|
|
169
|
+
|
|
170
|
+
UWorld* World = GEditor->GetEditorWorldContext().World();
|
|
171
|
+
if (!World)
|
|
172
|
+
{
|
|
173
|
+
Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
|
|
174
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
175
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Find the first landscape and sample height
|
|
179
|
+
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
180
|
+
{
|
|
181
|
+
ALandscapeProxy* Landscape = *It;
|
|
182
|
+
if (!Landscape) continue;
|
|
183
|
+
|
|
184
|
+
// Use line trace to get the landscape height at the given point
|
|
185
|
+
FVector TraceStart(Point.X, Point.Y, Point.Z + 100000.0f);
|
|
186
|
+
FVector TraceEnd(Point.X, Point.Y, Point.Z - 100000.0f);
|
|
187
|
+
|
|
188
|
+
FHitResult HitResult;
|
|
189
|
+
FCollisionQueryParams QueryParams;
|
|
190
|
+
QueryParams.bTraceComplex = true;
|
|
191
|
+
|
|
192
|
+
if (World->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECC_WorldStatic, QueryParams))
|
|
193
|
+
{
|
|
194
|
+
if (HitResult.GetActor() && HitResult.GetActor()->IsA(ALandscapeProxy::StaticClass()))
|
|
195
|
+
{
|
|
196
|
+
Result->SetNumberField(TEXT("height"), HitResult.Location.Z);
|
|
197
|
+
TSharedPtr<FJsonObject> HitPoint = MakeShared<FJsonObject>();
|
|
198
|
+
HitPoint->SetNumberField(TEXT("x"), HitResult.Location.X);
|
|
199
|
+
HitPoint->SetNumberField(TEXT("y"), HitResult.Location.Y);
|
|
200
|
+
HitPoint->SetNumberField(TEXT("z"), HitResult.Location.Z);
|
|
201
|
+
Result->SetObjectField(TEXT("hitLocation"), HitPoint);
|
|
202
|
+
|
|
203
|
+
TSharedPtr<FJsonObject> Normal = MakeShared<FJsonObject>();
|
|
204
|
+
Normal->SetNumberField(TEXT("x"), HitResult.Normal.X);
|
|
205
|
+
Normal->SetNumberField(TEXT("y"), HitResult.Normal.Y);
|
|
206
|
+
Normal->SetNumberField(TEXT("z"), HitResult.Normal.Z);
|
|
207
|
+
Result->SetObjectField(TEXT("normal"), Normal);
|
|
208
|
+
|
|
209
|
+
Result->SetBoolField(TEXT("hit"), true);
|
|
210
|
+
Result->SetBoolField(TEXT("success"), true);
|
|
211
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
Result->SetBoolField(TEXT("hit"), false);
|
|
217
|
+
Result->SetBoolField(TEXT("success"), true);
|
|
218
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::ListLandscapeSplines(const TSharedPtr<FJsonObject>& Params)
|
|
222
|
+
{
|
|
223
|
+
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
224
|
+
|
|
225
|
+
UWorld* World = GEditor->GetEditorWorldContext().World();
|
|
226
|
+
if (!World)
|
|
227
|
+
{
|
|
228
|
+
Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
|
|
229
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
230
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
TArray<TSharedPtr<FJsonValue>> SplineArray;
|
|
234
|
+
|
|
235
|
+
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
236
|
+
{
|
|
237
|
+
ALandscapeProxy* Landscape = *It;
|
|
238
|
+
if (!Landscape) continue;
|
|
239
|
+
|
|
240
|
+
ULandscapeSplinesComponent* SplinesComp = Landscape->GetSplinesComponent();
|
|
241
|
+
if (!SplinesComp) continue;
|
|
242
|
+
|
|
243
|
+
const TArray<TObjectPtr<ULandscapeSplineControlPoint>>& ControlPoints = SplinesComp->GetControlPoints();
|
|
244
|
+
for (const TObjectPtr<ULandscapeSplineControlPoint>& CP : ControlPoints)
|
|
245
|
+
{
|
|
246
|
+
if (!CP) continue;
|
|
247
|
+
|
|
248
|
+
TSharedPtr<FJsonObject> PointObj = MakeShared<FJsonObject>();
|
|
249
|
+
FVector Location = CP->Location;
|
|
250
|
+
PointObj->SetNumberField(TEXT("x"), Location.X);
|
|
251
|
+
PointObj->SetNumberField(TEXT("y"), Location.Y);
|
|
252
|
+
PointObj->SetNumberField(TEXT("z"), Location.Z);
|
|
253
|
+
PointObj->SetStringField(TEXT("landscapeName"), Landscape->GetName());
|
|
254
|
+
SplineArray.Add(MakeShared<FJsonValueObject>(PointObj));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
Result->SetArrayField(TEXT("controlPoints"), SplineArray);
|
|
259
|
+
Result->SetNumberField(TEXT("count"), SplineArray.Num());
|
|
260
|
+
Result->SetBoolField(TEXT("success"), true);
|
|
261
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::GetLandscapeComponent(const TSharedPtr<FJsonObject>& Params)
|
|
265
|
+
{
|
|
266
|
+
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
267
|
+
|
|
268
|
+
int32 ComponentIndex = 0;
|
|
269
|
+
if (Params->HasField(TEXT("componentIndex")))
|
|
270
|
+
{
|
|
271
|
+
ComponentIndex = static_cast<int32>(Params->GetNumberField(TEXT("componentIndex")));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
UWorld* World = GEditor->GetEditorWorldContext().World();
|
|
275
|
+
if (!World)
|
|
276
|
+
{
|
|
277
|
+
Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
|
|
278
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
279
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Collect all landscape components across all landscape proxies
|
|
283
|
+
TArray<ULandscapeComponent*> AllComponents;
|
|
284
|
+
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
285
|
+
{
|
|
286
|
+
ALandscapeProxy* Landscape = *It;
|
|
287
|
+
if (!Landscape) continue;
|
|
288
|
+
|
|
289
|
+
TArray<ULandscapeComponent*> LandscapeComponents;
|
|
290
|
+
Landscape->GetComponents<ULandscapeComponent>(LandscapeComponents);
|
|
291
|
+
AllComponents.Append(LandscapeComponents);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (ComponentIndex < 0 || ComponentIndex >= AllComponents.Num())
|
|
295
|
+
{
|
|
296
|
+
Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Component index %d out of range (0-%d)"), ComponentIndex, AllComponents.Num() - 1));
|
|
297
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
298
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
ULandscapeComponent* Comp = AllComponents[ComponentIndex];
|
|
302
|
+
if (!Comp)
|
|
303
|
+
{
|
|
304
|
+
Result->SetStringField(TEXT("error"), TEXT("Component is null"));
|
|
305
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
306
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
Result->SetNumberField(TEXT("componentIndex"), ComponentIndex);
|
|
310
|
+
Result->SetStringField(TEXT("name"), Comp->GetName());
|
|
311
|
+
|
|
312
|
+
FVector CompLocation = Comp->GetComponentLocation();
|
|
313
|
+
Result->SetNumberField(TEXT("locationX"), CompLocation.X);
|
|
314
|
+
Result->SetNumberField(TEXT("locationY"), CompLocation.Y);
|
|
315
|
+
Result->SetNumberField(TEXT("locationZ"), CompLocation.Z);
|
|
316
|
+
|
|
317
|
+
Result->SetNumberField(TEXT("sectionBaseX"), Comp->SectionBaseX);
|
|
318
|
+
Result->SetNumberField(TEXT("sectionBaseY"), Comp->SectionBaseY);
|
|
319
|
+
Result->SetNumberField(TEXT("componentSizeQuads"), Comp->ComponentSizeQuads);
|
|
320
|
+
Result->SetNumberField(TEXT("subSections"), Comp->NumSubsections);
|
|
321
|
+
|
|
322
|
+
FBox CompBounds = Comp->Bounds.GetBox();
|
|
323
|
+
if (CompBounds.IsValid)
|
|
324
|
+
{
|
|
325
|
+
TSharedPtr<FJsonObject> BoundsObj = MakeShared<FJsonObject>();
|
|
326
|
+
BoundsObj->SetNumberField(TEXT("minX"), CompBounds.Min.X);
|
|
327
|
+
BoundsObj->SetNumberField(TEXT("minY"), CompBounds.Min.Y);
|
|
328
|
+
BoundsObj->SetNumberField(TEXT("minZ"), CompBounds.Min.Z);
|
|
329
|
+
BoundsObj->SetNumberField(TEXT("maxX"), CompBounds.Max.X);
|
|
330
|
+
BoundsObj->SetNumberField(TEXT("maxY"), CompBounds.Max.Y);
|
|
331
|
+
BoundsObj->SetNumberField(TEXT("maxZ"), CompBounds.Max.Z);
|
|
332
|
+
Result->SetObjectField(TEXT("bounds"), BoundsObj);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
Result->SetBoolField(TEXT("success"), true);
|
|
336
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::SculptLandscape(const TSharedPtr<FJsonObject>& Params)
|
|
340
|
+
{
|
|
341
|
+
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
342
|
+
|
|
343
|
+
const TSharedPtr<FJsonObject>* LocationObj = nullptr;
|
|
344
|
+
if (!Params->TryGetObjectField(TEXT("location"), LocationObj) || !LocationObj || !(*LocationObj).IsValid())
|
|
345
|
+
{
|
|
346
|
+
Result->SetStringField(TEXT("error"), TEXT("Missing 'location' parameter (object with x, y)"));
|
|
347
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
348
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
double LocX = 0, LocY = 0;
|
|
352
|
+
(*LocationObj)->TryGetNumberField(TEXT("x"), LocX);
|
|
353
|
+
(*LocationObj)->TryGetNumberField(TEXT("y"), LocY);
|
|
354
|
+
|
|
355
|
+
double SculptRadius = 500.0;
|
|
356
|
+
Params->TryGetNumberField(TEXT("radius"), SculptRadius);
|
|
357
|
+
|
|
358
|
+
double Strength = 0.5;
|
|
359
|
+
Params->TryGetNumberField(TEXT("strength"), Strength);
|
|
360
|
+
|
|
361
|
+
FString Operation = TEXT("raise");
|
|
362
|
+
Params->TryGetStringField(TEXT("operation"), Operation);
|
|
363
|
+
|
|
364
|
+
double Falloff = 0.5;
|
|
365
|
+
Params->TryGetNumberField(TEXT("falloff"), Falloff);
|
|
366
|
+
|
|
367
|
+
UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
|
|
368
|
+
if (!World)
|
|
369
|
+
{
|
|
370
|
+
Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
|
|
371
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
372
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Verify a landscape exists by line tracing at the target location
|
|
376
|
+
bool bFoundLandscape = false;
|
|
377
|
+
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
378
|
+
{
|
|
379
|
+
if (*It)
|
|
380
|
+
{
|
|
381
|
+
bFoundLandscape = true;
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (!bFoundLandscape)
|
|
387
|
+
{
|
|
388
|
+
Result->SetStringField(TEXT("error"), TEXT("No landscape found in the current level"));
|
|
389
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
390
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Landscape sculpting is not directly exposed as a simple C++ API.
|
|
394
|
+
// The LandscapeEdMode (editor mode) handles sculpting internally.
|
|
395
|
+
// Fall back to console command approach.
|
|
396
|
+
FString Command = FString::Printf(
|
|
397
|
+
TEXT("Landscape.Sculpt X=%.1f Y=%.1f Radius=%.1f Strength=%.2f Op=%s"),
|
|
398
|
+
LocX, LocY, SculptRadius, Strength, *Operation);
|
|
399
|
+
|
|
400
|
+
UKismetSystemLibrary::ExecuteConsoleCommand(World, Command, nullptr);
|
|
401
|
+
|
|
402
|
+
TSharedPtr<FJsonObject> LocationResult = MakeShared<FJsonObject>();
|
|
403
|
+
LocationResult->SetNumberField(TEXT("x"), LocX);
|
|
404
|
+
LocationResult->SetNumberField(TEXT("y"), LocY);
|
|
405
|
+
Result->SetObjectField(TEXT("location"), LocationResult);
|
|
406
|
+
Result->SetNumberField(TEXT("radius"), SculptRadius);
|
|
407
|
+
Result->SetNumberField(TEXT("strength"), Strength);
|
|
408
|
+
Result->SetStringField(TEXT("operation"), Operation);
|
|
409
|
+
Result->SetNumberField(TEXT("falloff"), Falloff);
|
|
410
|
+
Result->SetStringField(TEXT("note"), TEXT("Executed via console command. Verify visually. If the console command is not supported, use execute_python with unreal.LandscapeEditorLibrary.sculpt() instead."));
|
|
411
|
+
Result->SetBoolField(TEXT("success"), true);
|
|
412
|
+
|
|
413
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::PaintLandscapeLayer(const TSharedPtr<FJsonObject>& Params)
|
|
417
|
+
{
|
|
418
|
+
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
419
|
+
|
|
420
|
+
FString LayerName;
|
|
421
|
+
if (!Params->TryGetStringField(TEXT("layerName"), LayerName))
|
|
422
|
+
{
|
|
423
|
+
Result->SetStringField(TEXT("error"), TEXT("Missing 'layerName' parameter"));
|
|
424
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
425
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const TSharedPtr<FJsonObject>* LocationObj = nullptr;
|
|
429
|
+
if (!Params->TryGetObjectField(TEXT("location"), LocationObj) || !LocationObj || !(*LocationObj).IsValid())
|
|
430
|
+
{
|
|
431
|
+
Result->SetStringField(TEXT("error"), TEXT("Missing 'location' parameter (object with x, y)"));
|
|
432
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
433
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
double LocX = 0, LocY = 0;
|
|
437
|
+
(*LocationObj)->TryGetNumberField(TEXT("x"), LocX);
|
|
438
|
+
(*LocationObj)->TryGetNumberField(TEXT("y"), LocY);
|
|
439
|
+
|
|
440
|
+
double PaintRadius = 500.0;
|
|
441
|
+
Params->TryGetNumberField(TEXT("radius"), PaintRadius);
|
|
442
|
+
|
|
443
|
+
double Strength = 1.0;
|
|
444
|
+
Params->TryGetNumberField(TEXT("strength"), Strength);
|
|
445
|
+
|
|
446
|
+
double Falloff = 0.5;
|
|
447
|
+
Params->TryGetNumberField(TEXT("falloff"), Falloff);
|
|
448
|
+
|
|
449
|
+
UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
|
|
450
|
+
if (!World)
|
|
451
|
+
{
|
|
452
|
+
Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
|
|
453
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
454
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Verify a landscape exists
|
|
458
|
+
bool bFoundLandscape = false;
|
|
459
|
+
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
460
|
+
{
|
|
461
|
+
if (*It)
|
|
462
|
+
{
|
|
463
|
+
bFoundLandscape = true;
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (!bFoundLandscape)
|
|
469
|
+
{
|
|
470
|
+
Result->SetStringField(TEXT("error"), TEXT("No landscape found in the current level"));
|
|
471
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
472
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Landscape layer painting is internal to LandscapeEdMode.
|
|
476
|
+
// The C++ API for painting layers requires the landscape editor mode to be active
|
|
477
|
+
// and is not trivially accessible from plugins.
|
|
478
|
+
// Provide the fallback note for using execute_python.
|
|
479
|
+
TSharedPtr<FJsonObject> LocationResult = MakeShared<FJsonObject>();
|
|
480
|
+
LocationResult->SetNumberField(TEXT("x"), LocX);
|
|
481
|
+
LocationResult->SetNumberField(TEXT("y"), LocY);
|
|
482
|
+
Result->SetObjectField(TEXT("location"), LocationResult);
|
|
483
|
+
Result->SetStringField(TEXT("layerName"), LayerName);
|
|
484
|
+
Result->SetNumberField(TEXT("radius"), PaintRadius);
|
|
485
|
+
Result->SetNumberField(TEXT("strength"), Strength);
|
|
486
|
+
Result->SetNumberField(TEXT("falloff"), Falloff);
|
|
487
|
+
|
|
488
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
489
|
+
Result->SetStringField(TEXT("note"),
|
|
490
|
+
TEXT("Landscape layer painting requires LandscapeEdMode which is not accessible from C++ plugins. ")
|
|
491
|
+
TEXT("Use the execute_python handler with unreal.LandscapeEditorLibrary.paint_layer() if available, ")
|
|
492
|
+
TEXT("or manually paint in the editor landscape tool."));
|
|
493
|
+
|
|
494
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::ImportHeightmap(const TSharedPtr<FJsonObject>& Params)
|
|
498
|
+
{
|
|
499
|
+
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
500
|
+
|
|
501
|
+
FString FilePath;
|
|
502
|
+
if (!Params->TryGetStringField(TEXT("filePath"), FilePath))
|
|
503
|
+
{
|
|
504
|
+
Result->SetStringField(TEXT("error"), TEXT("Missing 'filePath' parameter"));
|
|
505
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
506
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Verify the file exists
|
|
510
|
+
if (!FPaths::FileExists(FilePath))
|
|
511
|
+
{
|
|
512
|
+
Result->SetStringField(TEXT("error"), FString::Printf(TEXT("File not found: %s"), *FilePath));
|
|
513
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
514
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
|
|
518
|
+
if (!World)
|
|
519
|
+
{
|
|
520
|
+
Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
|
|
521
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
522
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Find the landscape
|
|
526
|
+
ALandscapeProxy* TargetLandscape = nullptr;
|
|
527
|
+
FString LandscapeName;
|
|
528
|
+
Params->TryGetStringField(TEXT("landscapeName"), LandscapeName);
|
|
529
|
+
|
|
530
|
+
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
531
|
+
{
|
|
532
|
+
ALandscapeProxy* Landscape = *It;
|
|
533
|
+
if (!Landscape) continue;
|
|
534
|
+
|
|
535
|
+
if (LandscapeName.IsEmpty() || Landscape->GetName() == LandscapeName)
|
|
536
|
+
{
|
|
537
|
+
TargetLandscape = Landscape;
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (!TargetLandscape)
|
|
543
|
+
{
|
|
544
|
+
Result->SetStringField(TEXT("error"), TEXT("No landscape found in the current level"));
|
|
545
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
546
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Read the heightmap file
|
|
550
|
+
TArray<uint8> FileData;
|
|
551
|
+
if (!FFileHelper::LoadFileToArray(FileData, *FilePath))
|
|
552
|
+
{
|
|
553
|
+
Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to read file: %s"), *FilePath));
|
|
554
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
555
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Heightmap import requires the landscape editor subsystem which is internal to LandscapeEdMode.
|
|
559
|
+
// The raw heightmap data has been loaded successfully.
|
|
560
|
+
// Provide information about the file and a note about the import path.
|
|
561
|
+
Result->SetStringField(TEXT("filePath"), FilePath);
|
|
562
|
+
Result->SetNumberField(TEXT("fileSizeBytes"), FileData.Num());
|
|
563
|
+
Result->SetStringField(TEXT("landscapeName"), TargetLandscape->GetName());
|
|
564
|
+
|
|
565
|
+
// Determine if this looks like a 16-bit raw heightmap based on file size
|
|
566
|
+
int64 FileSize = FileData.Num();
|
|
567
|
+
bool bLooksLikeRaw16 = false;
|
|
568
|
+
int32 PossibleResolution = 0;
|
|
569
|
+
for (int32 Res = 127; Res <= 8161; Res += 2)
|
|
570
|
+
{
|
|
571
|
+
if (FileSize == (int64)Res * Res * 2)
|
|
572
|
+
{
|
|
573
|
+
bLooksLikeRaw16 = true;
|
|
574
|
+
PossibleResolution = Res;
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (bLooksLikeRaw16)
|
|
580
|
+
{
|
|
581
|
+
Result->SetNumberField(TEXT("possibleResolution"), PossibleResolution);
|
|
582
|
+
Result->SetStringField(TEXT("format"), TEXT("RAW16"));
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
586
|
+
Result->SetStringField(TEXT("note"),
|
|
587
|
+
TEXT("Heightmap file loaded and validated. Direct heightmap import requires LandscapeEditorUtils ")
|
|
588
|
+
TEXT("which is internal to the landscape editor module. Use the execute_python handler with ")
|
|
589
|
+
TEXT("unreal.LandscapeEditorLibrary.import_heightmap() if available, or import through the ")
|
|
590
|
+
TEXT("Landscape editor mode Import tool."));
|
|
591
|
+
|
|
592
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::SetLandscapeMaterial(const TSharedPtr<FJsonObject>& Params)
|
|
596
|
+
{
|
|
597
|
+
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
598
|
+
|
|
599
|
+
FString MaterialPath;
|
|
600
|
+
if (!Params->TryGetStringField(TEXT("materialPath"), MaterialPath) && !Params->TryGetStringField(TEXT("path"), MaterialPath) && !Params->TryGetStringField(TEXT("assetPath"), MaterialPath))
|
|
601
|
+
{
|
|
602
|
+
Result->SetStringField(TEXT("error"), TEXT("Missing 'materialPath', 'path', or 'assetPath' parameter"));
|
|
603
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
604
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
|
|
608
|
+
if (!World)
|
|
609
|
+
{
|
|
610
|
+
Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
|
|
611
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
612
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Find the target landscape
|
|
616
|
+
ALandscapeProxy* TargetLandscape = nullptr;
|
|
617
|
+
FString LandscapeName;
|
|
618
|
+
Params->TryGetStringField(TEXT("landscapeName"), LandscapeName);
|
|
619
|
+
|
|
620
|
+
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
621
|
+
{
|
|
622
|
+
ALandscapeProxy* Landscape = *It;
|
|
623
|
+
if (!Landscape) continue;
|
|
624
|
+
|
|
625
|
+
if (LandscapeName.IsEmpty() || Landscape->GetName() == LandscapeName)
|
|
626
|
+
{
|
|
627
|
+
TargetLandscape = Landscape;
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (!TargetLandscape)
|
|
633
|
+
{
|
|
634
|
+
Result->SetStringField(TEXT("error"), TEXT("No landscape found in the current level"));
|
|
635
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
636
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Load the material
|
|
640
|
+
UMaterialInterface* Material = LoadObject<UMaterialInterface>(nullptr, *MaterialPath);
|
|
641
|
+
if (!Material)
|
|
642
|
+
{
|
|
643
|
+
Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Material not found: %s"), *MaterialPath));
|
|
644
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
645
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Set the landscape material
|
|
649
|
+
TargetLandscape->LandscapeMaterial = Material;
|
|
650
|
+
|
|
651
|
+
// Update all landscape components to use the new material
|
|
652
|
+
TArray<ULandscapeComponent*> LandscapeComponents;
|
|
653
|
+
TargetLandscape->GetComponents<ULandscapeComponent>(LandscapeComponents);
|
|
654
|
+
for (ULandscapeComponent* Comp : LandscapeComponents)
|
|
655
|
+
{
|
|
656
|
+
if (Comp)
|
|
657
|
+
{
|
|
658
|
+
Comp->SetMaterial(0, Material);
|
|
659
|
+
Comp->MarkRenderStateDirty();
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Mark the landscape as modified
|
|
664
|
+
TargetLandscape->MarkPackageDirty();
|
|
665
|
+
|
|
666
|
+
Result->SetStringField(TEXT("landscapeName"), TargetLandscape->GetName());
|
|
667
|
+
Result->SetStringField(TEXT("materialPath"), MaterialPath);
|
|
668
|
+
Result->SetStringField(TEXT("materialName"), Material->GetName());
|
|
669
|
+
Result->SetNumberField(TEXT("componentsUpdated"), LandscapeComponents.Num());
|
|
670
|
+
Result->SetBoolField(TEXT("success"), true);
|
|
671
|
+
|
|
672
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::GetLandscapeBounds(const TSharedPtr<FJsonObject>& Params)
|
|
676
|
+
{
|
|
677
|
+
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
678
|
+
|
|
679
|
+
UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
|
|
680
|
+
if (!World)
|
|
681
|
+
{
|
|
682
|
+
Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
|
|
683
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
684
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
FString LandscapeName;
|
|
688
|
+
Params->TryGetStringField(TEXT("landscapeName"), LandscapeName);
|
|
689
|
+
|
|
690
|
+
TArray<TSharedPtr<FJsonValue>> LandscapeBoundsArray;
|
|
691
|
+
|
|
692
|
+
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
693
|
+
{
|
|
694
|
+
ALandscapeProxy* Landscape = *It;
|
|
695
|
+
if (!Landscape) continue;
|
|
696
|
+
|
|
697
|
+
// Filter by name if specified
|
|
698
|
+
if (!LandscapeName.IsEmpty() && Landscape->GetName() != LandscapeName)
|
|
699
|
+
{
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
TSharedPtr<FJsonObject> LandscapeObj = MakeShared<FJsonObject>();
|
|
704
|
+
LandscapeObj->SetStringField(TEXT("name"), Landscape->GetName());
|
|
705
|
+
|
|
706
|
+
// Get actor bounds using GetActorBounds
|
|
707
|
+
FVector Origin;
|
|
708
|
+
FVector BoxExtent;
|
|
709
|
+
Landscape->GetActorBounds(false, Origin, BoxExtent);
|
|
710
|
+
|
|
711
|
+
TSharedPtr<FJsonObject> OriginObj = MakeShared<FJsonObject>();
|
|
712
|
+
OriginObj->SetNumberField(TEXT("x"), Origin.X);
|
|
713
|
+
OriginObj->SetNumberField(TEXT("y"), Origin.Y);
|
|
714
|
+
OriginObj->SetNumberField(TEXT("z"), Origin.Z);
|
|
715
|
+
LandscapeObj->SetObjectField(TEXT("origin"), OriginObj);
|
|
716
|
+
|
|
717
|
+
TSharedPtr<FJsonObject> ExtentObj = MakeShared<FJsonObject>();
|
|
718
|
+
ExtentObj->SetNumberField(TEXT("x"), BoxExtent.X);
|
|
719
|
+
ExtentObj->SetNumberField(TEXT("y"), BoxExtent.Y);
|
|
720
|
+
ExtentObj->SetNumberField(TEXT("z"), BoxExtent.Z);
|
|
721
|
+
LandscapeObj->SetObjectField(TEXT("boxExtent"), ExtentObj);
|
|
722
|
+
|
|
723
|
+
// Also provide min/max corners for convenience
|
|
724
|
+
FVector BoundsMin = Origin - BoxExtent;
|
|
725
|
+
FVector BoundsMax = Origin + BoxExtent;
|
|
726
|
+
|
|
727
|
+
TSharedPtr<FJsonObject> MinObj = MakeShared<FJsonObject>();
|
|
728
|
+
MinObj->SetNumberField(TEXT("x"), BoundsMin.X);
|
|
729
|
+
MinObj->SetNumberField(TEXT("y"), BoundsMin.Y);
|
|
730
|
+
MinObj->SetNumberField(TEXT("z"), BoundsMin.Z);
|
|
731
|
+
LandscapeObj->SetObjectField(TEXT("min"), MinObj);
|
|
732
|
+
|
|
733
|
+
TSharedPtr<FJsonObject> MaxObj = MakeShared<FJsonObject>();
|
|
734
|
+
MaxObj->SetNumberField(TEXT("x"), BoundsMax.X);
|
|
735
|
+
MaxObj->SetNumberField(TEXT("y"), BoundsMax.Y);
|
|
736
|
+
MaxObj->SetNumberField(TEXT("z"), BoundsMax.Z);
|
|
737
|
+
LandscapeObj->SetObjectField(TEXT("max"), MaxObj);
|
|
738
|
+
|
|
739
|
+
// Size
|
|
740
|
+
FVector Size = BoxExtent * 2.0;
|
|
741
|
+
TSharedPtr<FJsonObject> SizeObj = MakeShared<FJsonObject>();
|
|
742
|
+
SizeObj->SetNumberField(TEXT("x"), Size.X);
|
|
743
|
+
SizeObj->SetNumberField(TEXT("y"), Size.Y);
|
|
744
|
+
SizeObj->SetNumberField(TEXT("z"), Size.Z);
|
|
745
|
+
LandscapeObj->SetObjectField(TEXT("size"), SizeObj);
|
|
746
|
+
|
|
747
|
+
// Location
|
|
748
|
+
FVector Location = Landscape->GetActorLocation();
|
|
749
|
+
TSharedPtr<FJsonObject> LocationResultObj = MakeShared<FJsonObject>();
|
|
750
|
+
LocationResultObj->SetNumberField(TEXT("x"), Location.X);
|
|
751
|
+
LocationResultObj->SetNumberField(TEXT("y"), Location.Y);
|
|
752
|
+
LocationResultObj->SetNumberField(TEXT("z"), Location.Z);
|
|
753
|
+
LandscapeObj->SetObjectField(TEXT("location"), LocationResultObj);
|
|
754
|
+
|
|
755
|
+
LandscapeBoundsArray.Add(MakeShared<FJsonValueObject>(LandscapeObj));
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
if (LandscapeBoundsArray.Num() == 0)
|
|
759
|
+
{
|
|
760
|
+
Result->SetStringField(TEXT("error"), TEXT("No landscape found in the current level"));
|
|
761
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
762
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
Result->SetArrayField(TEXT("landscapes"), LandscapeBoundsArray);
|
|
766
|
+
Result->SetNumberField(TEXT("count"), LandscapeBoundsArray.Num());
|
|
767
|
+
Result->SetBoolField(TEXT("success"), true);
|
|
768
|
+
|
|
769
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
TSharedPtr<FJsonValue> FLandscapeHandlers::AddLandscapeLayerInfo(const TSharedPtr<FJsonObject>& Params)
|
|
773
|
+
{
|
|
774
|
+
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
|
|
775
|
+
|
|
776
|
+
FString LayerName;
|
|
777
|
+
if (!Params->TryGetStringField(TEXT("layerName"), LayerName))
|
|
778
|
+
{
|
|
779
|
+
Result->SetStringField(TEXT("error"), TEXT("Missing 'layerName' parameter"));
|
|
780
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
781
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
|
|
785
|
+
if (!World)
|
|
786
|
+
{
|
|
787
|
+
Result->SetStringField(TEXT("error"), TEXT("No editor world available"));
|
|
788
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
789
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Find the target landscape
|
|
793
|
+
ALandscapeProxy* TargetLandscape = nullptr;
|
|
794
|
+
FString LandscapeName;
|
|
795
|
+
Params->TryGetStringField(TEXT("landscapeName"), LandscapeName);
|
|
796
|
+
|
|
797
|
+
for (TActorIterator<ALandscapeProxy> It(World); It; ++It)
|
|
798
|
+
{
|
|
799
|
+
ALandscapeProxy* Landscape = *It;
|
|
800
|
+
if (!Landscape) continue;
|
|
801
|
+
|
|
802
|
+
if (LandscapeName.IsEmpty() || Landscape->GetName() == LandscapeName)
|
|
803
|
+
{
|
|
804
|
+
TargetLandscape = Landscape;
|
|
805
|
+
break;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
if (!TargetLandscape)
|
|
810
|
+
{
|
|
811
|
+
Result->SetStringField(TEXT("error"), TEXT("No landscape found in the current level"));
|
|
812
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
813
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
ULandscapeInfo* LandscapeInfo = TargetLandscape->GetLandscapeInfo();
|
|
817
|
+
if (!LandscapeInfo)
|
|
818
|
+
{
|
|
819
|
+
Result->SetStringField(TEXT("error"), TEXT("Failed to get landscape info"));
|
|
820
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
821
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Check if a layer with this name already exists
|
|
825
|
+
for (const FLandscapeInfoLayerSettings& ExistingLayer : LandscapeInfo->Layers)
|
|
826
|
+
{
|
|
827
|
+
if (ExistingLayer.LayerInfoObj && ExistingLayer.GetLayerName().ToString() == LayerName)
|
|
828
|
+
{
|
|
829
|
+
Result->SetStringField(TEXT("layerName"), LayerName);
|
|
830
|
+
Result->SetStringField(TEXT("path"), ExistingLayer.LayerInfoObj->GetPathName());
|
|
831
|
+
Result->SetStringField(TEXT("note"), TEXT("Layer already exists on this landscape"));
|
|
832
|
+
Result->SetBoolField(TEXT("success"), true);
|
|
833
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// Create a new ULandscapeLayerInfoObject asset
|
|
838
|
+
FString PackagePath = TEXT("/Game/Landscape/LayerInfos");
|
|
839
|
+
Params->TryGetStringField(TEXT("packagePath"), PackagePath);
|
|
840
|
+
|
|
841
|
+
FString AssetName = FString::Printf(TEXT("LI_%s"), *LayerName);
|
|
842
|
+
FString PackageFullPath = PackagePath / AssetName;
|
|
843
|
+
|
|
844
|
+
// Check if the asset already exists
|
|
845
|
+
ULandscapeLayerInfoObject* LayerInfoObj = LoadObject<ULandscapeLayerInfoObject>(nullptr, *(PackageFullPath + TEXT(".") + AssetName));
|
|
846
|
+
if (!LayerInfoObj)
|
|
847
|
+
{
|
|
848
|
+
UPackage* Package = CreatePackage(*PackageFullPath);
|
|
849
|
+
if (!Package)
|
|
850
|
+
{
|
|
851
|
+
Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to create package: %s"), *PackageFullPath));
|
|
852
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
853
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
LayerInfoObj = NewObject<ULandscapeLayerInfoObject>(Package, *AssetName, RF_Public | RF_Standalone);
|
|
857
|
+
if (!LayerInfoObj)
|
|
858
|
+
{
|
|
859
|
+
Result->SetStringField(TEXT("error"), TEXT("Failed to create LandscapeLayerInfoObject"));
|
|
860
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
861
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
865
|
+
LayerInfoObj->LayerName = FName(*LayerName);
|
|
866
|
+
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
867
|
+
|
|
868
|
+
// Set optional properties
|
|
869
|
+
bool bIsWeightBlended = true;
|
|
870
|
+
if (Params->HasField(TEXT("weightBlended")))
|
|
871
|
+
{
|
|
872
|
+
bIsWeightBlended = Params->GetBoolField(TEXT("weightBlended"));
|
|
873
|
+
}
|
|
874
|
+
// bNoWeightBlend removed in UE 5.7 — weight blending is now controlled per-layer via landscape settings
|
|
875
|
+
|
|
876
|
+
// Notify asset registry and save
|
|
877
|
+
FAssetRegistryModule::AssetCreated(LayerInfoObj);
|
|
878
|
+
Package->MarkPackageDirty();
|
|
879
|
+
UEditorAssetLibrary::SaveAsset(PackageFullPath, false);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// Register the layer info with the landscape
|
|
883
|
+
int32 LayerIndex = LandscapeInfo->Layers.Num();
|
|
884
|
+
FLandscapeInfoLayerSettings NewLayerSettings(LayerInfoObj, TargetLandscape);
|
|
885
|
+
LandscapeInfo->Layers.Add(NewLayerSettings);
|
|
886
|
+
|
|
887
|
+
// Mark the landscape as dirty
|
|
888
|
+
TargetLandscape->MarkPackageDirty();
|
|
889
|
+
|
|
890
|
+
Result->SetStringField(TEXT("layerName"), LayerName);
|
|
891
|
+
Result->SetStringField(TEXT("path"), LayerInfoObj->GetPathName());
|
|
892
|
+
Result->SetStringField(TEXT("landscapeName"), TargetLandscape->GetName());
|
|
893
|
+
Result->SetNumberField(TEXT("layerIndex"), LayerIndex);
|
|
894
|
+
Result->SetBoolField(TEXT("weightBlended"), true);
|
|
895
|
+
Result->SetBoolField(TEXT("success"), true);
|
|
896
|
+
|
|
897
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
898
|
+
}
|