wip(assembler): M1 round 1 -- manifest format, block contract, bound audit, native Heightfield2D
Luna, M1 round 1 of 4 (plan in Standalone/ASSEMBLER_M1_REPORT.md): - Versioned fixed-scale world manifest (the shipped plan format) and the Core block contract: point evaluation, interval-or-unknown, influence bound, detail envelope, channels. - Interval audit: 256 boxes / 6,912 samples, 0 missed, 0 unknown; a deliberately false bound is detected (negative control). - Native Heightfield2D with biome input: 4,096-point hash fae2721c052aec5c identical at W1/W8/W16; point queries 2.059 ms vs 62.115 ms legacy -- but NOT a match yet (mean density error 274 voxels, 2,261 sign mismatches vs the legacy surface). 45 mesh-free Core queries match direct evaluation exactly. Existing cave world unchanged (walkability, 64/64 tiles). - Open: collision readiness, a valid full-view timing (viewer captures were occluded/empty; top-down height maps are the only usable pair). Also carries the design doc's owner decisions (fixed water; player edits unrestricted, route law for automatic changes only) and three plugin files + BuildHost scripts that earlier round commits had missed (also fixed on unreal/core-integration as 2574b56). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
d5ce5d675d
commit
874c796c65
20 files changed
+2880
-12
No files matched your search
@@ -0,0 +1,402 @@
|
||||
#include "VoxelForgeCoreAdapter.h"
|
||||
|
||||
#include "Dom/JsonObject.h"
|
||||
#include "Policies/CondensedJsonPrintPolicy.h"
|
||||
#include "Serialization/JsonWriter.h"
|
||||
#include "UObject/UnrealType.h"
|
||||
#include "UObject/TextProperty.h"
|
||||
#include "VoxelSeasonAsset.h"
|
||||
#include "VoxelSettings.h"
|
||||
#include "VoxelStrateDefinition.h"
|
||||
#include "VoxelStrateManager.h"
|
||||
#include "VoxelTerrainOpDefinition.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
using FCoreJsonWriter = TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>;
|
||||
constexpr double CoreVoxelSizeCm = 25.0;
|
||||
|
||||
TSharedPtr<FJsonValue> SerializeCoreStruct(
|
||||
const UStruct* Type, const void* Data, TSet<const UObject*>& ActiveObjects,
|
||||
const UObject* Object = nullptr);
|
||||
|
||||
TSharedPtr<FJsonValue> SerializeCoreProperty(
|
||||
FProperty* Property, const void* Data, TSet<const UObject*>& ActiveObjects)
|
||||
{
|
||||
if (Property == nullptr || Data == nullptr) return MakeShared<FJsonValueNull>();
|
||||
if (const FBoolProperty* Bool = CastField<FBoolProperty>(Property))
|
||||
{
|
||||
return MakeShared<FJsonValueBoolean>(Bool->GetPropertyValue(Data));
|
||||
}
|
||||
if (const FEnumProperty* Enum = CastField<FEnumProperty>(Property))
|
||||
{
|
||||
return MakeShared<FJsonValueNumber>(static_cast<double>(
|
||||
Enum->GetUnderlyingProperty()->GetSignedIntPropertyValue(Data)));
|
||||
}
|
||||
if (const FNumericProperty* Numeric = CastField<FNumericProperty>(Property))
|
||||
{
|
||||
const double Value = Numeric->IsFloatingPoint()
|
||||
? Numeric->GetFloatingPointPropertyValue(Data)
|
||||
: static_cast<double>(Numeric->GetSignedIntPropertyValue(Data));
|
||||
return MakeShared<FJsonValueNumber>(Value);
|
||||
}
|
||||
if (const FStrProperty* String = CastField<FStrProperty>(Property))
|
||||
{
|
||||
return MakeShared<FJsonValueString>(String->GetPropertyValue(Data));
|
||||
}
|
||||
if (const FNameProperty* Name = CastField<FNameProperty>(Property))
|
||||
{
|
||||
return MakeShared<FJsonValueString>(Name->GetPropertyValue(Data).ToString());
|
||||
}
|
||||
if (const FTextProperty* Text = CastField<FTextProperty>(Property))
|
||||
{
|
||||
return MakeShared<FJsonValueString>(Text->GetPropertyValue(Data).ToString());
|
||||
}
|
||||
if (const FSoftObjectProperty* Soft = CastField<FSoftObjectProperty>(Property))
|
||||
{
|
||||
return MakeShared<FJsonValueString>(
|
||||
Soft->GetPropertyValue(Data).ToSoftObjectPath().ToString());
|
||||
}
|
||||
if (const FArrayProperty* Array = CastField<FArrayProperty>(Property))
|
||||
{
|
||||
TArray<TSharedPtr<FJsonValue>> Values;
|
||||
FScriptArrayHelper Helper(Array, Data);
|
||||
Values.Reserve(Helper.Num());
|
||||
for (int32 Index = 0; Index < Helper.Num(); ++Index)
|
||||
{
|
||||
Values.Add(SerializeCoreProperty(Array->Inner, Helper.GetRawPtr(Index), ActiveObjects));
|
||||
}
|
||||
return MakeShared<FJsonValueArray>(MoveTemp(Values));
|
||||
}
|
||||
if (const FSetProperty* Set = CastField<FSetProperty>(Property))
|
||||
{
|
||||
TArray<TPair<FString, TSharedPtr<FJsonValue>>> OrderedValues;
|
||||
FScriptSetHelper Helper(Set, Data);
|
||||
for (int32 Index = 0; Index < Helper.GetMaxIndex(); ++Index)
|
||||
{
|
||||
if (!Helper.IsValidIndex(Index)) continue;
|
||||
FString StableKey;
|
||||
Set->ElementProp->ExportTextItem_Direct(
|
||||
StableKey, Helper.GetElementPtr(Index), nullptr, nullptr, PPF_None);
|
||||
OrderedValues.Emplace(StableKey, SerializeCoreProperty(
|
||||
Set->ElementProp, Helper.GetElementPtr(Index), ActiveObjects));
|
||||
}
|
||||
OrderedValues.Sort([](const auto& A, const auto& B) { return A.Key < B.Key; });
|
||||
TArray<TSharedPtr<FJsonValue>> Values;
|
||||
Values.Reserve(OrderedValues.Num());
|
||||
for (const auto& Entry : OrderedValues) Values.Add(Entry.Value);
|
||||
return MakeShared<FJsonValueArray>(MoveTemp(Values));
|
||||
}
|
||||
if (const FMapProperty* Map = CastField<FMapProperty>(Property))
|
||||
{
|
||||
TArray<TPair<FString, TSharedPtr<FJsonValue>>> OrderedValues;
|
||||
FScriptMapHelper Helper(Map, Data);
|
||||
for (int32 Index = 0; Index < Helper.GetMaxIndex(); ++Index)
|
||||
{
|
||||
if (!Helper.IsValidIndex(Index)) continue;
|
||||
TSharedRef<FJsonObject> Pair = MakeShared<FJsonObject>();
|
||||
Pair->SetField(TEXT("key"), SerializeCoreProperty(
|
||||
Map->KeyProp, Helper.GetKeyPtr(Index), ActiveObjects));
|
||||
Pair->SetField(TEXT("value"), SerializeCoreProperty(
|
||||
Map->ValueProp, Helper.GetValuePtr(Index), ActiveObjects));
|
||||
FString StableKey;
|
||||
Map->KeyProp->ExportTextItem_Direct(
|
||||
StableKey, Helper.GetKeyPtr(Index), nullptr, nullptr, PPF_None);
|
||||
OrderedValues.Emplace(StableKey, MakeShared<FJsonValueObject>(Pair));
|
||||
}
|
||||
OrderedValues.Sort([](const auto& A, const auto& B) { return A.Key < B.Key; });
|
||||
TArray<TSharedPtr<FJsonValue>> Values;
|
||||
Values.Reserve(OrderedValues.Num());
|
||||
for (const auto& Entry : OrderedValues) Values.Add(Entry.Value);
|
||||
return MakeShared<FJsonValueArray>(MoveTemp(Values));
|
||||
}
|
||||
if (const FStructProperty* Struct = CastField<FStructProperty>(Property))
|
||||
{
|
||||
return SerializeCoreStruct(Struct->Struct, Data, ActiveObjects);
|
||||
}
|
||||
if (const FObjectPropertyBase* ObjectProperty = CastField<FObjectPropertyBase>(Property))
|
||||
{
|
||||
const UObject* Object = ObjectProperty->GetObjectPropertyValue(Data);
|
||||
if (Object == nullptr) return MakeShared<FJsonValueNull>();
|
||||
if (Object->GetClass()->GetName().StartsWith(TEXT("Voxel"), ESearchCase::CaseSensitive))
|
||||
{
|
||||
return SerializeCoreStruct(Object->GetClass(), Object, ActiveObjects, Object);
|
||||
}
|
||||
return MakeShared<FJsonValueString>(Object->GetPathName());
|
||||
}
|
||||
return MakeShared<FJsonValueNull>();
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonValue> SerializeCoreStruct(
|
||||
const UStruct* Type, const void* Data, TSet<const UObject*>& ActiveObjects,
|
||||
const UObject* Object)
|
||||
{
|
||||
if (Type == nullptr || Data == nullptr) return MakeShared<FJsonValueNull>();
|
||||
if (Object != nullptr && ActiveObjects.Contains(Object))
|
||||
{
|
||||
return MakeShared<FJsonValueString>(Object->GetPathName());
|
||||
}
|
||||
if (Object != nullptr) ActiveObjects.Add(Object);
|
||||
|
||||
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
|
||||
for (TFieldIterator<FProperty> It(Type); It; ++It)
|
||||
{
|
||||
FProperty* Property = *It;
|
||||
Result->SetField(Property->GetName(), SerializeCoreProperty(
|
||||
Property, Property->ContainerPtrToValuePtr<void>(const_cast<void*>(Data)), ActiveObjects));
|
||||
}
|
||||
if (Object != nullptr) ActiveObjects.Remove(Object);
|
||||
return MakeShared<FJsonValueObject>(Result);
|
||||
}
|
||||
|
||||
TSharedRef<FJsonObject> CoreVectorJson(const FVector& Value)
|
||||
{
|
||||
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
|
||||
Result->SetNumberField(TEXT("x"), Value.X);
|
||||
Result->SetNumberField(TEXT("y"), Value.Y);
|
||||
Result->SetNumberField(TEXT("z"), Value.Z);
|
||||
return Result;
|
||||
}
|
||||
|
||||
TSharedRef<FJsonObject> CoreLandingJson(const FVoxelPassageLanding& Landing)
|
||||
{
|
||||
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
|
||||
Result->SetObjectField(TEXT("standing_point"), CoreVectorJson(Landing.StandingPoint));
|
||||
Result->SetObjectField(TEXT("door_point"), CoreVectorJson(Landing.DoorPoint));
|
||||
Result->SetObjectField(TEXT("door_direction"), CoreVectorJson(Landing.DoorDirection));
|
||||
Result->SetNumberField(TEXT("floor_z"), Landing.FloorZ);
|
||||
Result->SetNumberField(TEXT("ceiling_z"), Landing.CeilingZ);
|
||||
Result->SetNumberField(TEXT("half_width"), Landing.HalfWidth);
|
||||
Result->SetNumberField(TEXT("floor_thickness"), Landing.FloorThickness);
|
||||
Result->SetBoolField(TEXT("source_player_fit"), Landing.bSourcePlayerFit);
|
||||
return Result;
|
||||
}
|
||||
|
||||
TSharedRef<FJsonObject> CorePassageJson(const FVoxelPassage& Passage)
|
||||
{
|
||||
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
|
||||
Result->SetNumberField(TEXT("upper_strate_index"), Passage.UpperStrateIndex);
|
||||
Result->SetNumberField(TEXT("lower_strate_index"), Passage.LowerStrateIndex);
|
||||
Result->SetObjectField(TEXT("upper_point"), CoreVectorJson(Passage.UpperPoint));
|
||||
Result->SetObjectField(TEXT("lower_point"), CoreVectorJson(Passage.LowerPoint));
|
||||
Result->SetObjectField(TEXT("requested_upper_point"), CoreVectorJson(Passage.RequestedUpperPoint));
|
||||
Result->SetObjectField(TEXT("requested_lower_point"), CoreVectorJson(Passage.RequestedLowerPoint));
|
||||
Result->SetNumberField(TEXT("radius"), Passage.Radius);
|
||||
Result->SetNumberField(TEXT("passage_type"), static_cast<int32>(Passage.PassageType));
|
||||
auto WriteVectors = [](const TCHAR* Key, const TArray<FVector>& Source,
|
||||
TSharedRef<FJsonObject> Out)
|
||||
{
|
||||
TArray<TSharedPtr<FJsonValue>> Values;
|
||||
Values.Reserve(Source.Num());
|
||||
for (const FVector& Value : Source)
|
||||
{
|
||||
Values.Add(MakeShared<FJsonValueObject>(CoreVectorJson(Value)));
|
||||
}
|
||||
Out->SetArrayField(Key, MoveTemp(Values));
|
||||
};
|
||||
auto WriteFloats = [](const TCHAR* Key, const TArray<float>& Source,
|
||||
TSharedRef<FJsonObject> Out)
|
||||
{
|
||||
TArray<TSharedPtr<FJsonValue>> Values;
|
||||
Values.Reserve(Source.Num());
|
||||
for (float Value : Source) Values.Add(MakeShared<FJsonValueNumber>(Value));
|
||||
Out->SetArrayField(Key, MoveTemp(Values));
|
||||
};
|
||||
WriteVectors(TEXT("control_points"), Passage.ControlPoints, Result);
|
||||
WriteFloats(TEXT("control_radii"), Passage.ControlRadii, Result);
|
||||
Result->SetObjectField(TEXT("upper_landing"), CoreLandingJson(Passage.UpperLanding));
|
||||
Result->SetObjectField(TEXT("lower_landing"), CoreLandingJson(Passage.LowerLanding));
|
||||
Result->SetBoolField(TEXT("walkable_tunnel_contract"), Passage.bWalkableTunnelContract);
|
||||
Result->SetBoolField(TEXT("native_floor_enabled"), Passage.bNativeFloorEnabled);
|
||||
WriteFloats(TEXT("native_floor_profile_z"), Passage.NativeFloorProfileZ, Result);
|
||||
WriteFloats(TEXT("native_floor_relief_scales"), Passage.NativeFloorReliefScales, Result);
|
||||
Result->SetNumberField(TEXT("native_floor_relief_strength"), Passage.NativeFloorReliefStrength);
|
||||
Result->SetNumberField(TEXT("native_floor_relief_frequency"), Passage.NativeFloorReliefFrequency);
|
||||
Result->SetNumberField(TEXT("native_floor_seed"), static_cast<int64>(Passage.NativeFloorSeed));
|
||||
Result->SetNumberField(TEXT("tunnel_max_gradient_degrees"), Passage.TunnelMaxGradientDegrees);
|
||||
Result->SetNumberField(TEXT("tunnel_vertical_drop_voxels"), Passage.TunnelVerticalDropVoxels);
|
||||
Result->SetNumberField(TEXT("tunnel_required_horizontal_run_voxels"), Passage.TunnelRequiredHorizontalRunVoxels);
|
||||
Result->SetNumberField(TEXT("tunnel_horizontal_path_length_voxels"), Passage.TunnelHorizontalPathLengthVoxels);
|
||||
Result->SetNumberField(TEXT("tunnel_minimum_clear_width_voxels"), Passage.TunnelMinimumClearWidthVoxels);
|
||||
Result->SetNumberField(TEXT("tunnel_clear_height_voxels"), Passage.TunnelClearHeightVoxels);
|
||||
Result->SetObjectField(TEXT("bound_center"), CoreVectorJson(Passage.BoundCenter));
|
||||
Result->SetNumberField(TEXT("bound_radius"), Passage.BoundRadius);
|
||||
Result->SetNumberField(TEXT("bound_radius_sq"), Passage.BoundRadiusSq);
|
||||
return Result;
|
||||
}
|
||||
|
||||
void WriteCoreJsonValue(FCoreJsonWriter& Writer, const TCHAR* Identifier,
|
||||
const TSharedPtr<FJsonValue>& Value)
|
||||
{
|
||||
if (Identifier != nullptr) Writer.WriteIdentifierPrefix(Identifier);
|
||||
if (!Value.IsValid() || Value->Type == EJson::Null)
|
||||
{
|
||||
Writer.WriteNull();
|
||||
}
|
||||
else if (Value->Type == EJson::Boolean)
|
||||
{
|
||||
Writer.WriteValue(Value->AsBool());
|
||||
}
|
||||
else if (Value->Type == EJson::Number)
|
||||
{
|
||||
Writer.WriteValue(Value->AsNumber());
|
||||
}
|
||||
else if (Value->Type == EJson::String)
|
||||
{
|
||||
Writer.WriteValue(Value->AsString());
|
||||
}
|
||||
else if (Value->Type == EJson::Array)
|
||||
{
|
||||
Writer.WriteArrayStart();
|
||||
for (const TSharedPtr<FJsonValue>& Element : Value->AsArray())
|
||||
{
|
||||
WriteCoreJsonValue(Writer, nullptr, Element);
|
||||
}
|
||||
Writer.WriteArrayEnd();
|
||||
}
|
||||
else if (Value->Type == EJson::Object)
|
||||
{
|
||||
Writer.WriteObjectStart();
|
||||
TArray<FString> Keys;
|
||||
Value->AsObject()->Values.GetKeys(Keys);
|
||||
Keys.Sort();
|
||||
for (const FString& Key : Keys)
|
||||
{
|
||||
WriteCoreJsonValue(Writer, *Key, Value->AsObject()->Values.FindRef(Key));
|
||||
}
|
||||
Writer.WriteObjectEnd();
|
||||
}
|
||||
else
|
||||
{
|
||||
Writer.WriteNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
|
||||
const UVoxelSettings* Settings,
|
||||
const UVoxelStrateManager* StrateManager,
|
||||
int32 ClipRadius,
|
||||
int32 MaxClipLevel,
|
||||
FString& OutJson,
|
||||
FString& OutError)
|
||||
{
|
||||
OutJson.Reset();
|
||||
OutError.Reset();
|
||||
if (Settings == nullptr || StrateManager == nullptr || StrateManager->GetLayout().IsEmpty())
|
||||
{
|
||||
OutError = TEXT("Core snapshot export requires initialized settings and a resolved strate layout.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const int32 Seed = StrateManager->GetWorldSeed();
|
||||
const float OriginSpineRadius = Settings->GetEffectiveOriginSpineRadius();
|
||||
const float WorldRadiusVoxels = Settings->GetEffectiveWorldRadiusVoxels();
|
||||
const int32 CoreClipRadius = FMath::Clamp(ClipRadius, 1, 64);
|
||||
const int32 CoreMaxClipLevel = FMath::Clamp(MaxClipLevel, 0, 8);
|
||||
|
||||
TSet<const UObject*> ActiveObjects;
|
||||
TSharedPtr<FJsonValue> SettingsValue = SerializeCoreStruct(
|
||||
Settings->GetClass(), Settings, ActiveObjects, Settings);
|
||||
if (!SettingsValue.IsValid() || SettingsValue->Type != EJson::Object)
|
||||
{
|
||||
OutError = TEXT("Core snapshot export could not serialize the settings asset.");
|
||||
return false;
|
||||
}
|
||||
// Override only the temporary schema copy. Clipmap tuning on the owner's asset is preserved.
|
||||
SettingsValue->AsObject()->SetNumberField(TEXT("ClipRadius"), CoreClipRadius);
|
||||
SettingsValue->AsObject()->SetNumberField(TEXT("MaxClipLevel"), CoreMaxClipLevel);
|
||||
|
||||
TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
|
||||
Root->SetNumberField(TEXT("schema_version"), 1);
|
||||
Root->SetStringField(TEXT("source"), TEXT("VoxelForge Runtime Core snapshot"));
|
||||
Root->SetNumberField(TEXT("voxel_size_cm"), CoreVoxelSizeCm);
|
||||
Root->SetNumberField(TEXT("seed"), Seed);
|
||||
Root->SetBoolField(TEXT("open_surface_entry"), Settings->bOpenSurfaceEntry);
|
||||
Root->SetNumberField(TEXT("origin_spine_radius"), OriginSpineRadius);
|
||||
Root->SetNumberField(TEXT("world_radius_voxels"), WorldRadiusVoxels);
|
||||
Root->SetNumberField(TEXT("edge_seal_thickness"), Settings->EdgeSealThickness);
|
||||
Root->SetNumberField(TEXT("total_strates"), StrateManager->GetNumStrates());
|
||||
Root->SetNumberField(TEXT("inter_strate_gap_chunks"), Settings->InterStrateGapChunks);
|
||||
Root->SetNumberField(TEXT("clip_radius"), CoreClipRadius);
|
||||
Root->SetNumberField(TEXT("max_clip_level"), CoreMaxClipLevel);
|
||||
Root->SetNumberField(TEXT("full_res_clip_levels"), Settings->FullResClipLevels);
|
||||
Root->SetNumberField(TEXT("coarse_tile_cells"), Settings->CoarseTileCells);
|
||||
Root->SetNumberField(TEXT("render_distance_chunks"), Settings->RenderDistanceChunks);
|
||||
Root->SetBoolField(TEXT("generate_skirts"), Settings->bGenerateSkirts);
|
||||
Root->SetNumberField(TEXT("skirt_cells"), Settings->SkirtCells);
|
||||
Root->SetNumberField(TEXT("lod_octave_drop"), Settings->LODOctaveDrop);
|
||||
Root->SetNumberField(TEXT("strate_content_cut_min_level"),
|
||||
Settings->GetEffectiveStrateContentCutMinLevel());
|
||||
Root->SetField(TEXT("settings_asset"), SettingsValue);
|
||||
|
||||
TArray<TSharedPtr<FJsonValue>> Strates;
|
||||
Strates.Reserve(StrateManager->GetLayout().Num());
|
||||
for (const FStrateSlot& Slot : StrateManager->GetLayout())
|
||||
{
|
||||
if (Slot.Definition == nullptr)
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Resolved strate %d has no definition asset."), Slot.StrateIndex);
|
||||
return false;
|
||||
}
|
||||
TSharedRef<FJsonObject> Strate = MakeShared<FJsonObject>();
|
||||
Strate->SetNumberField(TEXT("index"), Slot.StrateIndex);
|
||||
Strate->SetNumberField(TEXT("top_chunk_z"), Slot.TopChunkZ);
|
||||
Strate->SetNumberField(TEXT("bottom_chunk_z"), Slot.BottomChunkZ);
|
||||
Strate->SetNumberField(TEXT("height_chunks"), Slot.HeightInChunks);
|
||||
Strate->SetField(TEXT("asset"), SerializeCoreStruct(
|
||||
Slot.Definition->GetClass(), Slot.Definition, ActiveObjects, Slot.Definition));
|
||||
Strate->SetField(TEXT("generation_params"), SerializeCoreStruct(
|
||||
FStrateGenerationParams::StaticStruct(), &Slot.Definition->GenerationParams, ActiveObjects));
|
||||
Strate->SetField(TEXT("disturbances"), SerializeCoreStruct(
|
||||
FStrateDisturbanceParams::StaticStruct(), &Slot.Definition->Disturbances, ActiveObjects));
|
||||
Strate->SetField(TEXT("passage_config"), SerializeCoreStruct(
|
||||
FStratePassageConfig::StaticStruct(), &Slot.Definition->PassageConfig, ActiveObjects));
|
||||
|
||||
TArray<TSharedPtr<FJsonValue>> Operations;
|
||||
Operations.Reserve(Slot.Definition->TerrainOperations.Num());
|
||||
for (const FStrateTerrainOpEntry& Entry : Slot.Definition->TerrainOperations)
|
||||
{
|
||||
TSharedRef<FJsonObject> Operation = MakeShared<FJsonObject>();
|
||||
Operation->SetNumberField(TEXT("weight"), Entry.Weight);
|
||||
Operation->SetNumberField(TEXT("probability"), Entry.Probability);
|
||||
Operation->SetStringField(TEXT("asset_path"), Entry.Operation.ToString());
|
||||
if (const UVoxelTerrainOpDefinition* Definition = Entry.Operation.LoadSynchronous())
|
||||
{
|
||||
Operation->SetField(TEXT("asset"), SerializeCoreStruct(
|
||||
Definition->GetClass(), Definition, ActiveObjects, Definition));
|
||||
}
|
||||
Operations.Add(MakeShared<FJsonValueObject>(Operation));
|
||||
}
|
||||
Strate->SetArrayField(TEXT("terrain_operation_assets"), MoveTemp(Operations));
|
||||
Strates.Add(MakeShared<FJsonValueObject>(Strate));
|
||||
}
|
||||
Root->SetArrayField(TEXT("strates"), MoveTemp(Strates));
|
||||
|
||||
TArray<TSharedPtr<FJsonValue>> Passages;
|
||||
Passages.Reserve(StrateManager->GetPassages().Num());
|
||||
for (const FVoxelPassage& Passage : StrateManager->GetPassages())
|
||||
{
|
||||
Passages.Add(MakeShared<FJsonValueObject>(CorePassageJson(Passage)));
|
||||
}
|
||||
Root->SetArrayField(TEXT("passages"), MoveTemp(Passages));
|
||||
Root->SetArrayField(TEXT("density_samples"), {});
|
||||
TSharedRef<FJsonObject> ViewerStart = MakeShared<FJsonObject>();
|
||||
ViewerStart->SetBoolField(TEXT("found_open_space"), false);
|
||||
ViewerStart->SetNumberField(TEXT("x"), 0.0);
|
||||
ViewerStart->SetNumberField(TEXT("y"), 0.0);
|
||||
ViewerStart->SetNumberField(TEXT("z"), 0.0);
|
||||
Root->SetObjectField(TEXT("viewer_start"), ViewerStart);
|
||||
|
||||
FString WriterBuffer;
|
||||
TSharedRef<FCoreJsonWriter> Writer = TJsonWriterFactory<TCHAR,
|
||||
TCondensedJsonPrintPolicy<TCHAR>>::Create(&WriterBuffer);
|
||||
WriteCoreJsonValue(*Writer, nullptr, MakeShared<FJsonValueObject>(Root));
|
||||
if (!Writer->Close() || WriterBuffer.IsEmpty())
|
||||
{
|
||||
OutError = TEXT("Core snapshot export failed while writing schema-v1 JSON.");
|
||||
return false;
|
||||
}
|
||||
OutJson = MoveTemp(WriterBuffer);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#include "VoxelForgeCoreAssetAdapter.h"
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
class UVoxelSettings;
|
||||
class UVoxelStrateManager;
|
||||
|
||||
namespace VoxelForgeCoreAdapter
|
||||
{
|
||||
/**
|
||||
* Export the already-resolved Runtime assets through the shared schema-v1 boundary.
|
||||
* The override values are written into the JSON copy only; authored UObject properties stay
|
||||
* untouched. This runs on the game thread because it walks UObjects and soft references.
|
||||
*/
|
||||
VOXELFORGE_API bool SerializeResolvedWorld(const UVoxelSettings* Settings,
|
||||
const UVoxelStrateManager* StrateManager,
|
||||
int32 ClipRadius,
|
||||
int32 MaxClipLevel,
|
||||
FString& OutJson,
|
||||
FString& OutError);
|
||||
}
|
||||
Reference in new issue
Block a user