Files
UnrealEngineUWP/Engine/Source/Runtime/Landscape/Private/LandscapeEditInterface.cpp
Matt Kuhlenschmidt 765a83175b Copying //UE4/Dev-Editor to //UE4/Dev-Main (Source: //UE4/Dev-Editor @ 3379190)
#lockdown Nick.Penwarden
#rb none

==========================
MAJOR FEATURES + CHANGES
==========================

Change 3342222 on 2017/03/10 by Nick.Darnell

	UMG - Adding a GetContent to the UContentWidget.

Change 3342228 on 2017/03/10 by Nick.Darnell

	Project Launcher - Always consume mouse wheel vertically so it stops scrolling to the right.

Change 3342310 on 2017/03/10 by Nick.Darnell

	UMG - Cleaning up some extra class references.

Change 3343382 on 2017/03/13 by Jamie.Dale

	Applying optimization to FChunkManifestGenerator::ContainsMap

Change 3343523 on 2017/03/13 by Mike.Fricker

	New details view option:  "Show Hidden Properties while Playing"
	- Enabling this allows you to see every property on selected objects that belong to a simulating world, even non-visible and non-editable properties.  Very useful for inspection and debugging.
	- Remember to change World Outliner to show you actors in the "Play World" if you want to select and inspect those objects first!
	- This setting is saved for your entire project, similar to "Show All Advanced"

Change 3343573 on 2017/03/13 by Mike.Fricker

	New details view option:  "Show Hidden Properties while Playing" (part 2)
	- Fixed missing include / unity issue

Change 3343709 on 2017/03/13 by Jamie.Dale

	Some fixes for gathering cached dependency data

	- We no longer load dependency data that doesn't have the correct package name.
	- We no longer populate the dependency results when bGatherDependsData is false.

Change 3343900 on 2017/03/13 by Alexis.Matte

	fix crash when creating too much LOD at import
	#jira UE-42785

Change 3344104 on 2017/03/13 by Alexis.Matte

	Add a boolean to the static mesh socket so we know if the socket was imported or created in UE4. This allow us to not impact editor socket when we re-import a fbx
	#jira UE-42736

Change 3344802 on 2017/03/14 by Michael.Dupuis

	#jira UE-42244 : added missing nullptr so render thread wont try to access global var when we're no longer in landscape mode
	Changed the sync method between graphic resource from render thread and game thread to prevent desync

Change 3346061 on 2017/03/14 by Jamie.Dale

	Adding const& and && overloads of FText::Format

Change 3346192 on 2017/03/14 by Arciel.Rekman

	Linux: fix VHACD to retain bincompat with the baseline (UE-42895).

	- It is now compiled against libc++ instead of libstdc++ in the toolchain.

Change 3347083 on 2017/03/15 by Andrew.Rodham

	Fixed crash when changing anchors on a background blur widget

Change 3347359 on 2017/03/15 by Michael.Dupuis

	#jira UE-38193:
	Added Rename, Delete, New Folder, Size Map, Show In Explorer for folder and asset in the path view and asset view

Change 3347382 on 2017/03/15 by Michael.Dupuis

	missing include incremental

Change 3347500 on 2017/03/15 by Alex.Delesky

	#jira UE-41231 - Selecting multiple text widgets in UMG will now allow you to set their value correctly, and the "Multiple Values" text will no longer be set in the widgets instead.

Change 3347920 on 2017/03/15 by Jamie.Dale

	Fixing some places passing tooltips as FString rather than FText

	#jira UE-42603

Change 3347925 on 2017/03/15 by Jamie.Dale

	Re-saving some assets so their tooltips can be gathered

	#jira UE-42603

Change 3348788 on 2017/03/15 by Jamie.Dale

	Updated the Windows platform to use the newer Vista+ style browser dialogs, rather than the older XP style dialogs

Change 3349187 on 2017/03/16 by Andrew.Rodham

	Sequencer: Added the ability to specify additional event receivers for level sequence actors
	  - Such actors will receive events from event tracks

Change 3349194 on 2017/03/16 by Andrew.Rodham

	Sequencer: Reset compiled templates on load in the editor, and ensure correct serialization of generation ledger
	  - Resetting on load means that we guarantee up-to-date templates, even if underlying compilation logic changes.

	#jira UE-42198
	#jira UE-40969

Change 3349210 on 2017/03/16 by Andrew.Rodham

	Sequencer: Event tracks can now be defined to trigger events at the start of evaluation, after objects are spawned, or at the end of evaluation

Change 3349211 on 2017/03/16 by Andrew.Rodham

	Sequencer: Add ability to retrieve bound objects from blueprint

Change 3349398 on 2017/03/16 by Nick.Darnell

	UMG - Fixing a flashing hierarchy view.  Looks like assets continuing to stream in causing the object change notification to continue to fire, and the widget designer refreshed any time it happened.  Now limit to only if widgets are changing.

Change 3349420 on 2017/03/16 by Alex.Delesky

	#jira UE-40720 - Multiline editable text boxes can now be set to Read-Only.

Change 3349548 on 2017/03/16 by Alexis.Matte

	Fbx importer, when importing a staticmesh with combine mesh option check and the fbx file contain some "MultiSub Material" the materialinstance are now always hook properly.

Change 3349818 on 2017/03/16 by Cody.Albert

	Fixed constructor for FNavigationMetaData

Change 3350047 on 2017/03/16 by Cody.Albert

	Removed unneeded check so that children actors are never orphaned when their parent is moved into a newly created folder in the world outliner

Change 3350072 on 2017/03/16 by Arciel.Rekman

	ShaderCompiler: make sure strings are at least 4-byte aligned.

	- Can crash wcscpy() under Linux otherwise (reported by a licensee).

Change 3350146 on 2017/03/16 by Arciel.Rekman

	Fix CodeLite project generation (UE-42921).

	- Reportedly causes a crash in CodeLite 10.x

Change 3350235 on 2017/03/16 by Arciel.Rekman

	Fix memory leak in address symbolication on Linux.

	- Makes MallocProfiler work again.
	- Also add progress update in MallocProfiler since symbolication is still slow.

	Merging CL 3338764 from Fortnite to Dev-Editor.

Change 3350382 on 2017/03/16 by Arciel.Rekman

	Linux: fix incorrect cast of rlimit in i686.

Change 3350471 on 2017/03/16 by Jamie.Dale

	Enabling loc dashboard by default for new projects

Change 3350516 on 2017/03/16 by Jamie.Dale

	Enabling content hot-reloading by default

Change 3350582 on 2017/03/16 by Cody.Albert

	Corrected Widget Interaction Component to use current impact point instead of last impact point

Change 3350945 on 2017/03/16 by Jamie.Dale

	Gave FConfigFile::FindOrAddSection API linkage

Change 3351441 on 2017/03/17 by Michael.Dupuis

	#jira UE-42843: Fixed Transaction begin/end order issue happening with min slider passing max slider value
	Add support for multiple selection value display

Change 3351558 on 2017/03/17 by Michael.Dupuis

	#jira UE-42845: Always refresh the detail panel to properly update for selection change, delete, etc.

Change 3351657 on 2017/03/17 by Matt.Kuhlenschmidt

	Adding USD Third Party dependencies

Change 3351665 on 2017/03/17 by Matt.Kuhlenschmidt

	Added experimental USD Importer Plugin
	This plugin supports basic static mesh importing and scene creation of actors using static meshes

Change 3351682 on 2017/03/17 by Matt.Kuhlenschmidt

	Enabling USD importer in engine test project for automation tests

Change 3351749 on 2017/03/17 by Alexis.Matte

	Make sure the selection proxy is off for the skeletal mesh component. UE4 use the selection outline instead

	#jira UE-41677

Change 3351831 on 2017/03/17 by Michael.Dupuis

	#jira UETOOL-1102:
	Added HSV controls to Color Grading
	Some look improvement for RGV/HSV
	Color Grading refactor
	Group Reset bug fix (relevant only to color grading)

Change 3352041 on 2017/03/17 by Matt.Kuhlenschmidt

	Updated USD plugin whitelisting

Change 3352093 on 2017/03/17 by Michael.Dupuis

	when FREEZERENDERING is called, stop the foliage culling too

Change 3352211 on 2017/03/17 by Alexis.Matte

	Fix the physic asset missing skeleton warning
	#jira UE-43006

Change 3352336 on 2017/03/17 by Alexis.Matte

	We now allow a negative W value of the ScreenPoint vector in the ScreenToPixel function. In this case we simply reverse the W value to kept the manipulator direction on the good side.
	#jira UE-37458

Change 3352947 on 2017/03/17 by Phillip.Kavan

	#jira UE-42510 - Instanced static mesh transform edits are now reflected in the Blueprint editor's preview scene.

	Change summary:
	- Added IPropertyHandle::GetValueBaseAddress() (interface).
	- Modified IPropertyHandle::NotifyPostChange() to include EPropertyChangeType as an optional input.
	- Added FPropertyHandleBase::GetValueBaseAddress() (implementation).
	- Modified FPropertyHandleBase::NotifyPostChange() to include the optional input arg in the property change event.
	- Modified FPropertyHandleBase::CreatePropertyNameWidget() to clear the override text after temporarily replacing display name/tooltip text for the creation of the SPropertyNameWidget. This was done to allow for transactions to be named according to the property that's being modified.
	- Modified FMathStructProxyCustomization::OnValueCommitted() to only apply the input value while not interactively editing via spinbox as well as when not post-processing an undo/redo (which can trigger a focus loss).
	- Modified the FMathStructProxyCustomization::OnEndSliderMovement() delegate to include property handle and proxy value input parameters, as well as to call FlushValues() as part of the implementation.
	- Modified FlushValues() for each of FMatrixStructCustomization, FTransformStructCustomization and FQuatStructCustomization to explicitly handle both propagation and transaction processing.
	- Modified UInstancedStaticMeshComponent::UpdateInstanceTransform() to call Modify() prior to applying changes (so that the previous state is recorded when inside a transaction context).
	- Modified FInstanceStaticMeshSCSEditorCustomization::HandleViewportDrag() to propagate changes to all instances of the ISMC archetype.

	Known issues:
	- Using the spinbox to edit instanced mesh transform values in the Blueprint editor will not apply the change to instances in the level editor until after you release the mouse button (i.e. - it will not be shown as a "live" update).

Change 3353678 on 2017/03/20 by Michael.Dupuis

	properly unfreeze the culling of foliage when toggling the freezerendering command

Change 3353747 on 2017/03/20 by Matt.Kuhlenschmidt

	PR #3372: Git plugin: fix update status on directories hotfix (still) slightly broken in master (UE4.16) (Contributed by SRombauts)

Change 3353749 on 2017/03/20 by Matt.Kuhlenschmidt

	PR #3373: Git Plugin: hotfix for regression off Visual Diffs with older version of Git in master (UE4.16) (Contributed by SRombauts)

Change 3353754 on 2017/03/20 by Matt.Kuhlenschmidt

	PR #3390: Allow OBJ imports to change if materials and textures are also imported (Contributed by mmdanggg2)

Change 3353909 on 2017/03/20 by Matt.Kuhlenschmidt

	Fixed actors showing thumbnails in details panel and made a few other tweeks to thumbnail displays in details panels
	- The color of the accepted type is now  shown properly
	- All object based properties now have thumbnails on by default.

Change 3353948 on 2017/03/20 by Nick.Darnell

	UMG - Updating the background blur widget's upgrade code to use the custom version, and handling older cases that were continuing to generate blur slots, even when already upgraded.

Change 3354335 on 2017/03/20 by Nick.Darnell

	Paragon - Excluding Archetype objects from reporting references, which causes crashes in the fast template mode.

Change 3354495 on 2017/03/20 by Nick.Darnell

	Core - Making it so order that outers are discovered does not matter, initializing the chain of outers if hasn't been created when instancing subobjects.

Change 3354578 on 2017/03/20 by Nick.Darnell

	Slate - There's now a console variable option, Slate.VerifyHitTestVisibility (off by default) which enables additional visibility checks for widgets.  Normally this isn't nessesary, but if you're changing the visibility of widgets during a frame, and several hit tests need to be performed that frame there's a chance that a button could be clicked twice in one frame.  Enabling this mode will make all hit testing more expensive, so for now it's off by default, but available for licensees that need the extra testing.

Change 3354737 on 2017/03/20 by Nick.Darnell

	Core - Adding a fix to Dev-Editor from that enables objects in the same package being requested to also be loaded.  This came about during async streaming callbacks alerting that a requested class was done loading, but there were still other assets in the package 'not loaded' but were available, just needed post load called on them.

Change 3355923 on 2017/03/21 by Yannick.Lange

	VR Editor: - Remove unnecessary cleanup functions.
	- Initialize with VR Mode and remove SetOwner function, since it shouldn't be possible to reset the VR Mode afterwards.

Change 3355959 on 2017/03/21 by Yannick.Lange

	VR Editor: - Rename VREditorWorldInteraction to VREditorPlacement, to avoid confusion with ViewportWorldInteraction. VREditorPlacement will only handle placing objects from content browser in the VR Mode.
	- Removed SnapSelectedActorsToGround to VREditorMode.

Change 3355965 on 2017/03/21 by Yannick.Lange

	VR Editor:  Forgot to add files to previous submit 3355959.

Change 3355977 on 2017/03/21 by Yannick.Lange

	VR Editor: Remove function to add a new extension with  TSubclassOf<UEditorWorldExtension>.

Change 3356017 on 2017/03/21 by Yannick.Lange

	VR Editor: - UI system check owner VRMode.
	- UI system fix check on VRMode on shutdown.

Change 3356028 on 2017/03/21 by Nick.Darnell

	Slate - SButton now correctly releases mouse capture even if it becomes disabled while pressed, but before 'click' has been fired.

	#jira UE-42777

Change 3356071 on 2017/03/21 by Yannick.Lange

	VR Editor: Copy of change 3353663.
	- Fix having to press once on the landscape to see the visuals for landscape editing.
	- Fix when sculpting/painting the position wouldn't update.
	- Fix inverted action for brushes while holding down shift or modifier on motioncontroller.
	- Cleanup FLandscapeToolInteractorPosition.

	- Change from 3353663: Use TStrokeClass::UseContinuousApply and TimeSinceLastInteractorMove to decide when to apply ToolStroke on tick.

Change 3356180 on 2017/03/21 by Michael.Dupuis

	Added ShowFlag Foliage Occlusion Bounds
	Fixed non initialized variable
	Expose changing Min Occlusion Bounds instead of assuming 6

	#rn none

Change 3356347 on 2017/03/21 by Nick.Darnell

	UMG - Introducing a faster CreateWidget.  When cooking, the widget compiler now generates a widget template/archetype that is stored in the same package as the generated blueprint class.  During compiling we generate a nearly fully initialized widget tree including all sub userwidgets and their trees, hookup all member variables, initialize named slots, setup any animations...etc.  This nearly fully constructed widget can be instanced using it as an archetype in the NewObject call, and does not have to use the correspondingly slow StaticDuplicateObject path.  There are restrictions on this method, part of the compiling step for widgets now inspects if the instancing would be successful, or if there would be GLEO references after instancing because a user forgot to setup Instanced on a subobject property.  Luckily that should be few and far between, all UVisuals (Widgets & Slots) are now DefaultToInstanced, which takes care of the overwhelming cases that demand the instanced flag.  Especially given the bulk of cases using BindWidget in native code.

	UMG - Removing a lot of deprecated functions from 4.8 on UUserWidget.

Change 3356357 on 2017/03/21 by Nick.Darnell

	Build - Fixing some IWYU issues on the incremental build.

Change 3356461 on 2017/03/21 by Nick.Darnell

	Build - Fixing linux build errors.

Change 3356468 on 2017/03/21 by Jamie.Dale

	STextPropertyEditableTextBox now handles empty texts correctly

Change 3356916 on 2017/03/21 by Matt.Kuhlenschmidt

	Fixed a crash when a material render proxy on a preview node is deleted when it is in flight on the render thread

	#jira UE-40556

Change 3357033 on 2017/03/21 by Alexis.Matte

	Fix crash when importing file with import commandlet
	Make sure path are combine properly to avoid crash
	Add some missing pointer check
	Make sure the asset are save when there is no source control
	#jira UE-42334

Change 3357176 on 2017/03/21 by Alex.Delesky

	#jira UE-42445 - TMaps now support editing the values of structs that act as map keys. TMaps with struct keys will now show the types of their elements in the details panel as well, and structs will now also display numbers next to set elements.

Change 3357197 on 2017/03/21 by Alex.Delesky

	#jira none - Fixing build issue for TMap key struct change.

Change 3357205 on 2017/03/21 by Michael.Dupuis

	Forgot to reset min granularity to 6 from testing

Change 3357340 on 2017/03/21 by Arciel.Rekman

	Mark FMallocAnsi (standard libc malloc) thread-safe on Linux.

Change 3357413 on 2017/03/21 by matt.kuhlenschmidt

	Added '/Game/Effects/Fort_Effects/Materials/Smoke/M_Main_Smoke_Puff.M_Main_Smoke_Puff' to collection 'MattKTest'

	Upgraded collection 'MattKTest' (was version 1, now version 2)

Change 3357505 on 2017/03/21 by Alexis.Matte

	Fix to avoid changing the CDO of FbxAssetImportData. The UI was saving the Config which was saving the CDO. But already serialized data will be reload badly if the CDO change since we serialize only the diff.

	#jira UE-42947

Change 3357825 on 2017/03/21 by Arciel.Rekman

	Clean up the large thread pool on exit.

	- Seems like the destruction was missed in the original CL 2785131 (12/1/15).
	- Fixes problems when threads were allocated in memory that is being cleaned up in another place on exit.

Change 3358086 on 2017/03/22 by Yannick.Lange

	VR Editor: - Fix gizmo scaling down when dragging the world.
	- Fix gizmo scaling down when dragging rotation handle.

Change 3358175 on 2017/03/22 by Andrew.Rodham

	Sequencer: Made ALevelSequenceActor::AdditionalEventReceivers advanced display

Change 3358367 on 2017/03/22 by tim.gautier

	Submitting resaved QAGame assets - Materials, Material Instances, Material Functions and Parameters

Change 3358457 on 2017/03/22 by Yannick.Lange

	VR Editor: Deleting unused UI assets.

Change 3358801 on 2017/03/22 by Matt.Kuhlenschmidt

	Guard against crash if the level editor is shut down when the object system has already been shut down

	#jira UE-35605

Change 3358897 on 2017/03/22 by matt.barnes

	Checking in WIP test content for UEQATC-1635 (UMG Navigation)

Change 3358976 on 2017/03/22 by Alex.Delesky

	#jira none - Fixing an issue where ItemPropertyNode could potentially dereference a null property

Change 3358987 on 2017/03/22 by Yannick.Lange

	VR Editor: Fix warning: Can't find file for asset '/Engine/VREditor/UI/VRButtonBackground' while loading ../../../Engine/Content/VREditor/Devices/Vive/VivePreControllerMaterial.uasset.

Change 3359067 on 2017/03/22 by Yannick.Lange

	VR Editor: Fix Radial Menu remains on controller after exiting VR Preview
	#jira UE-42885

Change 3359179 on 2017/03/22 by Matt.Kuhlenschmidt

	Fixed "Multiple Values" in Body Setup when single bone has multiple bodies

	#jira UE-41546

Change 3359626 on 2017/03/22 by Arciel.Rekman

	Linux: pool OS allocations.

	- Add a TMemoryPool and TMemoryPoolArray classes that can be used with any type of OS allocator functions.
	- Add ability to bypass CachedOSPageAllocator for given sizes. Also, corrected the condition on AllocImpl to match one on FreeImpl.
	- Switch Linux to pool mmap()/munmap() by default (helps 32-bit Linux and also speeds up 64-bit one), except 64-bit servers.
	- Add a test to TestPAL to check performance and thread safety.
	- Misc. fixes.

Change 3359989 on 2017/03/23 by Andrew.Rodham

	Sequencer: Binding overrides improvements
	  - Added the ability to override spawnable bindings
	  - Added the ability to override bindings in sub sequences
	  - Deprecated "Get Sequence Bindings" node in favor of "Get Sequence Binding", which is more robust, and provides a better UI/UX for selecting single bindings

	#jira UE-42470

Change 3360369 on 2017/03/23 by Alexis.Matte

	Fix the staticmesh conversion from UE4 4.13 to earlier UE4 versions
	#jira UE-42731

Change 3360556 on 2017/03/23 by Andrew.Rodham

	Sequencer: Added drag/drop support for binding overrides
	  - You can now drag and drop sequencer object binding nodes into blueprint graphs (to create 'Get Sequence Binding' nodes), and onto binding overrides specified on level sequence actors.

Change 3360618 on 2017/03/23 by Arciel.Rekman

	Make Binned2 work on Mac.

	- Game/server will use Binned2 by default.

Change 3360838 on 2017/03/23 by Nick.Darnell

	CommonUI - Making the SingleMaterialStyleMID property transient.  It had been serialized mistakenly onto several widgets when it appears the intent is to dynamically allocate it upon demand.

Change 3360841 on 2017/03/23 by Nick.Darnell

	UMG - Updating the editor to use DuplicateAndInitializeFromWidgetTree, so that Initialize is properly called when duplicating sub widget trees.

Change 3362561 on 2017/03/24 by Matt.Kuhlenschmidt

	Fixed text outlines being cropped at large sizes

	#jira UE-42647

Change 3362565 on 2017/03/24 by Matt.Kuhlenschmidt

	Added automation test for font outlines

Change 3362567 on 2017/03/24 by Matt.Kuhlenschmidt

	Resaved this file to fix 0 engine version warnings

Change 3362582 on 2017/03/24 by Yannick.Lange

	VR Editor: - Fix log warnings when teleporting.
	- Fix undo/redo when using teleport scaling.
	- Improved teleport scaling and push/pull input.
	#jira UE-43214

Change 3362631 on 2017/03/24 by Jamie.Dale

	Split the monolithic culture concept in UE4

	UE4 has historically only supported the concept of a single monolithic "culture" that applied to both text localization and internationalization, as well as all asset localization. Typically the "culture" was set to the "locale" of the OS, however that could be undesirable or incorrect on platforms (such as newer versions of Windows) that have a distinct concept of "language" (for localization) and "locale" (for internationalization).

	This change splits the concept of "culture" into "language" and "locale", and also adds the concept of "asset groups". The language is now used to work out which localization we should use, and the locale is used to control how numbers/dates/times/etc are formatted within our internationalization library.

	Asset groups expand on the language used by asset localization and allow you to create a group of asset classes that can be assigned a different culture than the main game language. A typical use-case of this would be creating an "audio" group that could, for example, be set to Japanese while the rest of the game runs in English.

	If your game doesn't care about the distinction between language and locale, and doesn't need to use asset groups, then you're able to continue to use "culture" as you always have. If, however, you do care about those things, then you'll likely want to avoid using the "culture" directly (as it's now a very aggressive setting that overrides all others), and instead favor using language/locale (games will typically treat these as the same) and asset groups as separate concepts (both in settings, and in your in-game UI).

	The language or locale for a game can be controlled by settings within the "Internationalization" section of your configs (this would typically be set in your GameUserSettings config, in the same way that "culture" works), eg)

	  [Internationalization]
	  language=fr
	  locale=fr

	The asset groups for a game can be controlled by settings within the "Internationalization.AssetGroupClasses" and "Internationalization.AssetGroupCultures" sections of your configs (the asset group class definition would typically be set in your DefaultGame config, and the cultures the groups use would typically be set in your GameUserSettings config), eg)

	  [Internationalization.AssetGroupClasses]
	  +Audio=SoundWave
	  +Audio=DialogueWave

	  [Internationalization.AssetGroupCultures]
	  +Audio=ja

	#jira UE-38418
	#jira UE-43014

Change 3362798 on 2017/03/24 by Nick.Darnell

	UMG - Putting the finishing touches on the hardware cursor system.  Can now load them from blueprints, and there are options for setting them up in the project settings.

	UMG - Deprecating the old properties for software widget cursors.  They've been moved into a map that can handle any of the mouse cursors as the enum key, which was always the intent/desire but maps couldn't be used as UProperties then.

Change 3362805 on 2017/03/24 by Jamie.Dale

	PR #3397: Allow empty source to override display string (Contributed by jorgenpt)

Change 3363039 on 2017/03/24 by Jamie.Dale

	Use the pre-scaled font height where possible to avoid an extra multiply

Change 3363188 on 2017/03/24 by Joe.Graf

	Added support for -iterate for content plugins that require path remapping during cook/packaging

	#CodeReview: matt.kuhlenschmidt
	#rb: matt.kuhlenschmidt

Change 3363355 on 2017/03/24 by Nick.Darnell

	UMG - Removing the CookAdditionalFiles function in UserInterfaceSettings.

Change 3363672 on 2017/03/24 by Matt.Kuhlenschmidt

	Material thumbnails now respect used particle system sprites flag and show a quad insead of a sphere by default. For this change I added the ability to have per asset type override for the default thumbnail shape and I added a way to reset thumbnails to default.  All existinging particle system materials that have not had a custom thumbnail will have to be reloaded and resaved for this to work

	#jira UE-42410

Change 3363699 on 2017/03/24 by Mike.Fricker

	VR Editor: Improved extensibility (for mesh editor)
	- This was merged from CL 3352612 and re-opened for edit before commit
	- All mesh editor changes were stripped before merging

Change 3363784 on 2017/03/24 by Matt.Barnes

	Adding content for tests following UEQATC-3548

Change 3363872 on 2017/03/24 by Arciel.Rekman

	Linux: require user to setup clang/clang++ for building hlslcc.

	- Earlier we tried to handle most common scenarios since libhlslcc needed to be built during the setup. Now that we supply a prebuilt version we don't need to be as user friendly, especially given that the attempts to second guess the compiler started to look complicated.

Change 3364089 on 2017/03/24 by Matt.Kuhlenschmidt

	Fix CIS

Change 3364381 on 2017/03/24 by JeanMichel.Dignard

	UV Packing optim
	- Use horizontal segments instead of checking texel by texel to fit source chart in layout.
	- Skip a couple of rasterize by flipping either the X texels or the Y texels when possible.
	- Keep the best chart raster so that we don't need to reraster when adding the chart to the layout.
	- Added a lightmap UV version in StaticMesh so that we don't invalidate the lighting cache. Only use the new lightmap UV generation when going through UStaticMesh::Build which invalidates the lighting.

Change 3364587 on 2017/03/24 by Arciel.Rekman

	Fix ordered comparison warning from clang 4.0.

Change 3364596 on 2017/03/24 by Arciel.Rekman

	Linux: fix editor being stuck (hack).

	- Rebuilt hlslcc in Debug.

Change 3364863 on 2017/03/25 by Max.Chen

	Sequencer: Fixed crash when deactivating a section in sequencer

	#jira UE-39880

Change 3364864 on 2017/03/25 by Max.Chen

	Sequencer: Integrating fix from licensee to ensure FVirtualTrackArea::HitTestSection checks the row of the section

Change 3364865 on 2017/03/25 by Max.Chen

	Cine Camera: Default post process depth of field method to CircleDOF and use that setting in UpdateCameraLens.

	#jira UE-40621

Change 3364866 on 2017/03/25 by Max.Chen

	GitHub #3183: Conversion to base class is inaccessible.

Change 3364869 on 2017/03/25 by Max.Chen

	Sequencer: Changed the time snapping interval in the toolbar ui so that it no longer additionally updates the sequencer setting. The setting is only used to initialize the time snapping interval of the level sequence. Added translate keys with ctrl and left-right arrows.

	#jira UE-41009
	#jira UE-41210

Change 3364870 on 2017/03/25 by Max.Chen

	Sequencer: Added translate keys with ctrl and left-right arrows.

	#jira UE-41210

Change 3364871 on 2017/03/25 by Max.Chen

	Sequencer: Add level sequence actor customization to open sequencer from the details panel. For matinee parity.

	#jira UE-41459

Change 3364879 on 2017/03/25 by Max.Chen

	Sequencer: Duplicate shot should put the duplicate on the next available row, keeping the start/end times the same.

	#jira UE-41289

Change 3364880 on 2017/03/25 by Max.Chen

	Sequencer: Opening the API for MovieSceneAudio-related classes along with some minor functionality additions:
	- Adding _API specifiers to MovieSceneAudioTrack, MovieSceneAudioSection, and FAudioTrackEditor so they can be subclassed in other modules.
	- Made GetSoundDuration function in MovieSceneAudioTrack.cpp a member function so it's functionaliy could be reused by subclasses.
	- Adding ability to specify delegates for OnQueueSubtitles, OnAudioFinished, and OnAudioPlaybackPercent in a MovieSceneAudioSection, and have them automatically assigned to any AudioComponents that are played by the MovieSceneAudioTemplate

Change 3364884 on 2017/03/25 by Max.Chen

	Sequencer fbx import
	- Removed the PostRotation compensation as it was setuped for 3ds max.
	- On import, add a rotation to camera and light animation keys like we do on export.
	- Merge the component local transform with the ActorNode transform when exporting only one component that isn't the root component in fbx since we're not creating child nodes in that case.

	#jira UE-34692

Change 3364885 on 2017/03/25 by Max.Chen

	Sequence Recorder: Fix crash when clearing properties to record.

	#jira UE-41873

Change 3364886 on 2017/03/25 by Max.Chen

	Sequencer: Add error when attempting to add a circularly dependent level sequence

	#jira UE-22358

Change 3364890 on 2017/03/26 by Max.Chen

	Sequencer: Added ability to specify a 'notify' function to property instance bindings
	  - When specified, the (parameterless) function will be called after a property is set

Change 3364891 on 2017/03/26 by Max.Chen

	Sequencer: Various fixes to thumbnails
	  - Fixed alpha blending being used when presenting the full screen quad for thumbnails

Change 3364892 on 2017/03/26 by Max.Chen

	Sequencer: PreRoll and PostRoll is now exposed at the section level, for all sections
	  - For the majority of sections this will be unimplemented, but it will allow for some tracks to set up their data ahead of time

Change 3364896 on 2017/03/26 by Max.Chen

	Sequencer: Add segment flags to equality operator for movie scene evaluation segments
	  - This prevents them from being accumulated into adjacent segments of the same index and forced time, but differing flags

Change 3364897 on 2017/03/26 by Max.Chen

	Sequencer: Fixed "Evaluate in preroll" and "Evaluate in Postroll" options
	  - Pre and postroll flags now come through on compiled segments, so the previous manual logic for sub sections is obsolete; we can just use the compiled segment data directly.

Change 3364898 on 2017/03/26 by Max.Chen

	Sequencer: Moved track options to be accessible on all nodes, and operate on all selected tracks

Change 3364902 on 2017/03/26 by Max.Chen

	Sequencer: Ensure evaluation flags are considered when compiling segments from external sequences
	  - This ensures that preroll regions in sub sequences are correctly evaluated when their parent section has preroll
	  - Changed high pass blending to always allow preroll

Change 3364903 on 2017/03/26 by Max.Chen

	Engine: Moved proxy mesh transform update out of camera view computation code
	  - GetCameraView can happen as part of end of frame updates, which will assert if any changes of transform happen during its processing

Change 3364908 on 2017/03/26 by Max.Chen

	Sequencer: Added visualization of pre and postroll on sections

Change 3364909 on 2017/03/26 by Max.Chen

	Sequencer: Prevent MovieSceneCompiler from removing preroll segments

Change 3364910 on 2017/03/26 by Max.Chen

	Sequencer: MediaPlayer PreRoll/PostRoll fix
	- Handle PreRoll/PostRoll on sub scenes that have a start offset

Change 3364922 on 2017/03/26 by Max.Chen

	Sequencer: Add check for valid property before dereferencing.

	#jira UE-40951

Change 3364923 on 2017/03/26 by Max.Chen

	Sequencer: Fix MovieScene preroll so that it seeks to the start correct frame before the preroll.

Change 3364924 on 2017/03/26 by Max.Chen

	Sequencer - change default behavior for pre/post roll evaluation
	- MovieSceneTracks are NOT evaluated by default

Change 3364925 on 2017/03/26 by Max.Chen

	Sequencer: Shot track rows now consider pre and post roll when being compiled

Change 3364926 on 2017/03/26 by Max.Chen

	Sequencer: Added the ability to define shared execution tokens, identifyable with a unique identifier, and sortable based on a sort order (<=0: before standard tokens, >0: after other tokens)

Change 3364927 on 2017/03/26 by Max.Chen

	Sequencer: Added the ability to selectively restore state for specific anim type IDs for a given object
	  - This allows us to specifically restore one particular type of animation for a given object (ie, transform, skeletal animation control, or motion blur)

Change 3364928 on 2017/03/26 by Max.Chen

	Sequencer: Fixed sub-sub tracks not being present in master sequences
	  - In order to correctly handle preroll in inner-inner sequences, we need to have access to those tracks when compiling intermediate sub sections. By caching off all the inner templates, we can have access to these tracks to check whether they want to be evaluated in pre/post roll in the master sequence

Change 3364937 on 2017/03/26 by Max.Chen

	Sequencer: Update cine camera component debug focus plane on tick, rather than in GetCameraView

	#jira UE-41332

Change 3364938 on 2017/03/26 by Max.Chen

	Sequencer: Fix crash inserting a level sequence with an invalid shot.

	#jira UE-41481

Change 3364940 on 2017/03/26 by Max.Chen

	Sequencer: Made handling of pre and post roll more consistent between explicit section pre/post roll and pre/post roll inherited from an outer sub section

Change 3364942 on 2017/03/26 by Max.Chen

	Movie Scene Capture: Move EDL generation to setup instead of close to ensure it gets written out when capturing as a separate process.

	#jira UE-41703

Change 3364943 on 2017/03/26 by Max.Chen

	Sequencer: Prevent capturing movies in editor while a PIE session is running

	#jira UE-41399

Change 3364944 on 2017/03/26 by Max.Chen

	CIS fixes

Change 3364951 on 2017/03/26 by Max.Chen

	Sequencer: Fix autokey not setting a keyframe for slate color with specified color.

	#jira UE-41645

Change 3364952 on 2017/03/26 by Max.Chen

	Sequencer: Level sequence frame snapshots now take account of fixed-frame interval offsets, and overlapping shot sections on the same row

	#jira UE-41684

Change 3364953 on 2017/03/26 by Max.Chen

	Sequencer: Fix edl so that it doesn't write out when a shot is out of range. Also fixed not writing the EDL with the correct frame rate when exporting from the track. Reworked the cmx EDL so that its encoded in the same edit time space, including a blank slug at the beginning of the edit.

	#jira UE-41925

Change 3364954 on 2017/03/26 by Max.Chen

	Sequencer - Allow animating parameters on cascade effect components which aren't owned by an AEmitter.

Change 3364955 on 2017/03/26 by Max.Chen

	Sequencer: Fixed sequencer anim instance not being used in the case where one was requested, but a different anim instance was already set

	This fixes an issue when rendering in seaprate process, animations that were set up to use the sequencer instance would be controlled using montage animation instead.

Change 3364963 on 2017/03/26 by Max.Chen

	Sequencer: Fix filtering to include child nodes.

	#jira UE-42068

Change 3364964 on 2017/03/26 by Max.Chen

	Sequencer: Enable UseCustomStartFrame and UseCustomEndFrame when rendering a single shot from the menu.

	#jira UE-42021

Change 3364965 on 2017/03/26 by Max.Chen

	Sequencer: Set the fade color in the track display

Change 3364966 on 2017/03/26 by Max.Chen

	Sequencer: Show actor attached to label in attach section.

Change 3364967 on 2017/03/26 by Max.Chen

	Sequencer: Fix static analysis warnings

Change 3364968 on 2017/03/26 by Max.Chen

	Sequencer: Fix crash on converting to spawnable.

	The previous implementation purported to allow null objects to set up spawnable defaults but it actually needed to compare the spawned object to the supported type.  This new mechanism now allows the spawner to indicate that it accepts null objects and doesn't crash.

	#jira UE-42069

Change 3364969 on 2017/03/26 by Max.Chen

	Sequencer: Fixed crash caused by holding onto stale properties through a raw ptr

	#jira UE-42072

Change 3364977 on 2017/03/26 by Max.Chen

	Sequencer: Convert FLinearColor to FColor for fade.

	#jira UE-41990

Change 3364978 on 2017/03/26 by Max.Chen

	Sequencer: Limit GetAllSections to the sections that actually correspond to the track

	#jira UE-42167

Change 3364979 on 2017/03/26 by Max.Chen

	Sequencer: Filter root nodes too

	#jira UE-42068

Change 3364980 on 2017/03/26 by Max.Chen

	Sequencer: Filter relevant material parameters

	#jira UE-40712

Change 3364982 on 2017/03/26 by Max.Chen

	Sequencer: Remove audio range bounds which clamps to the section bounds (needed for evaluating in pre and post roll)

Change 3364983 on 2017/03/26 by Max.Chen

	Sequencer: Add socket name to attach track section.

Change 3364984 on 2017/03/26 by Max.Chen

	Sequencer: Fix sub track node deletion so that all the sub tracks aren't deleted, only the row being requested.

	#jira UE-40955

Change 3364988 on 2017/03/26 by Max.Chen

	Sequencer: Invalidate expired objects when blueprints are compiled. Fix actor references now handles sections that need to have their guids updated (ie. attach tracks).

Change 3364994 on 2017/03/26 by Max.Chen

	Sequencer: Audio waveforms now show peak samples with smoothed RMS in the center
	  - Audio row heights are now also resizable by dragging on the bottom end of the track lane in the track area view

Change 3364995 on 2017/03/26 by Max.Chen

	UMG: Fix crash on undo

	#jira UE-42210

Change 3365000 on 2017/03/26 by Max.Chen

	Sequencer: Fix crash from GetCurrentValue.

Change 3365001 on 2017/03/26 by Max.Chen

	Sequencer: Split "Snap to the Dragged Key" option into two options, pressed key and dragged key.

	#jira UE-42382

Change 3365002 on 2017/03/26 by Max.Chen

	Sequencer: Downgraded check to ensure for FMovieSceneEvalTemplateBase::GetScriptStructImpl()

Change 3365003 on 2017/03/26 by Max.Chen

	Sequencer: Fixed section template script struct
	  - Because the cpp is not parsed by UHT, the empty template had its parent struct, rather than its own
	  - We now just return null, and handle empty segments correctly in the segment remapper as part of the track compilation

Change 3365013 on 2017/03/26 by Max.Chen

	Sequencer: Added data validation on compiled template loads, and extra guards against misuse of movie scene types

Change 3365014 on 2017/03/26 by Max.Chen

	Sequencer: Sequencer now re-evaluates when starting PIE or Simulate
	  - This can be disabled by disabling "Bind Sequencer to PIE" and "Bind Sequencer to Simulate" in PIE advanced settings

Change 3365015 on 2017/03/26 by Max.Chen

	Sequencer: Fix edl files so that they don't write out empty range shots

Change 3365017 on 2017/03/26 by Max.Chen

	Sequencer: Set max tick rate when in game.

	#jira UE-41078

Change 3365018 on 2017/03/26 by Max.Chen

	Sequencer: When finishing a scrub, playback status is now correctly set to stopped rather than stepping
	  - This fixes a hack that was previously in place from the old PostTickRenderFixup that caused it to run that step after scrubbing bad finished. This is no longer necessary, and actually breaks clicking to set the scrub position, as it now means that we step across the entire range between the previous and current time.

Change 3365022 on 2017/03/26 by Max.Chen

	Sequencer: Insert shot now creates a shot at the current time and puts it on the next available row.

	#jira UE-41480, UE-27699

Change 3365023 on 2017/03/26 by Max.Chen

	Sequencer: Add loop selection range. If there is no selection range, loop mode is restricted to loop or no loop.

	#jira UE-42285

Change 3365029 on 2017/03/26 by Max.Chen

	Sequencer: Add hotkeys to set the selection range to the next and previous shot (page up, page down). Also, added hotkey to set the playback range to all the shots (end)

Change 3365030 on 2017/03/26 by Max.Chen

	Sequencer: Fix particle system restore state so that it gets the proper initial active state of the particle system.

	#jira UE-42861, UE-42859

Change 3365031 on 2017/03/26 by Max.Chen

	Sequencer: Snap time when changing time snapping intervals.

	#jira UE-42590

Change 3365032 on 2017/03/26 by Max.Chen

	Sequencer: Add When Finished state to sections. By default, sections now restore state.

	#jira UE-41991, UE-31569

Change 3365033 on 2017/03/26 by Max.Chen

	#jira UE-42028 "DialogueWave playback calls OnQueueSubtitles multiple times"

	Only queue subtitles once per wave instance playback

Change 3365041 on 2017/03/26 by Max.Chen

	Sequencer: Subscene hierarchical bias

	Tracks can now be prioritized based on their subscene hierarhical bias value. Higher bias values take precedence.

	#jira UE-42078

Change 3365042 on 2017/03/26 by Max.Chen

	Sequencer: Generic paste menu for master (root) tracks.

Change 3365043 on 2017/03/26 by Max.Chen

	Sequencer: Hierarchical bias for level visibility track

	#jira UE-43024

Change 3365044 on 2017/03/26 by Max.Chen

	Sequencer: Prevent throttling on editing keys/sections.

Change 3365045 on 2017/03/26 by Max.Chen

	Sequencer: Set sequencer audio components bIsUISound to false so that they don't continue playing when the game is paused.

	#jira UE-39391

Change 3365046 on 2017/03/26 by Max.Chen

	Sequencer: Add missing BindLevelEditorCommands()

Change 3365049 on 2017/03/26 by Max.Chen

	Sequencer: Set tick prerequites for spawnables when they are spawned.

	#jira UE-43009

Change 3365050 on 2017/03/26 by Max.Chen

	Sequencer: Jump to Start and End of playback shortcuts.

	Rewind renamed to Jump to Start. Shortcut - up arrow.
	Jump to End Shortcut - ctrl up arrow.

	#jira UE-43224

Change 3365051 on 2017/03/26 by Max.Chen

	Sequencer: Add last range to playback

Change 3365057 on 2017/03/26 by Max.Chen

	Sequencer: Fix master sequence subscene generation times.

Change 3365058 on 2017/03/26 by Max.Chen

	Sequencer: Fix paste so that it doesn't paste both onto object nodes and master tracks.

Change 3365059 on 2017/03/26 by Max.Chen

	Sequencer: Fix crash pasting audio track.

Change 3365060 on 2017/03/26 by Max.Chen

	Sequencer: Cache player fade state so that restore state will return the values to the pre animated state.

	#jira UE-43313

Change 3365061 on 2017/03/26 by Max.Chen

	Sequencer: Filter hidden functions. This fixes a bug where the field of view property for a cinematic camera appears to be animatable. It should be hidden just like it is in the property editor.

	#jira UE-41461

Change 3365065 on 2017/03/26 by Max.Chen

	Sequencer: Support component hierarchies when drawing animation paths

	#jira UE-39500

Change 3365066 on 2017/03/26 by Max.Chen

	Sequencer: Refine pause behaviour in sequencer to always evaluate the next frame
	  - This ensures that we get a full frame's worth of evaluation so that the paused frame is of a good quality (and avoids us evaluating a tiny range)

Change 3365075 on 2017/03/26 by Max.Chen

	Sequencer: Fix add shot not setting next row.

Change 3365076 on 2017/03/26 by Max.Chen

	Sequencer: Export MovieSceneTrackEditor

	#jira UE-41641

Change 3365472 on 2017/03/27 by Yannick.Lange

	VR Editor landscape. Back out changelist 3356071 with new proper fixes.
	CL 3356071 introduced another bug and it wasn't correct because of removing FLandscapeToolInteractorPosition. This changelist fixes the same and additional bugs for VREditor Landscape mode.
	- Fix when sculpting/painting the position wouldn't update.
	- Fix inverted action for brushes while holding down shift or modifier on motioncontroller.
	- Fix VREditor Landscape Texture Painting does not paint continuously
	- Fix having to press once on the landscape to see the visuals for landscape editing.
	- Removed Interactor parameter from BeginTool.
	#jira UE-42780, UE-42779

Change 3365497 on 2017/03/27 by Matt.Kuhlenschmidt

	Fix texture importing when an FBX file incorrectly reports absolute path as relative.  First we try absolute, then we try fbx reported relative, then we try relative to parent FBX file.

Change 3365498 on 2017/03/27 by Matt.Kuhlenschmidt

	Fix attempting to load a package in FBX scene import when the import path is empty. This greatly reduces FBX scene import time

Change 3365504 on 2017/03/27 by Yannick.Lange

	VR Editor landscape fix ensure in when starting to paint/sculpt. Mousemove on tool should only be called when the tool is actually active, not when hovering.

Change 3365551 on 2017/03/27 by Matt.Kuhlenschmidt

	PR #3425: Added Scrollbar customization to SComboBox (Contributed by Altrue)

	#jira UE-43338

Change 3365580 on 2017/03/27 by Matt.Kuhlenschmidt

	PR #3409: Add support for per-Category filtering in Output Log (Contributed by thagberg)

Change 3365672 on 2017/03/27 by Andrew.Rodham

	Sequencer: Preanimated state producers can now produce null tokens
	  - Doing so implies no preanimated state should be saved

Change 3365791 on 2017/03/27 by Andrew.Rodham

	Sequencer: Added Material Parameter Collection track

Change 3365806 on 2017/03/27 by Max.Chen

	Sequencer: Add option to instance sub sequences.

	#jira UE-43307

Change 3365822 on 2017/03/27 by Matt.Kuhlenschmidt

	Subdue the output log font color a bit

Change 3365846 on 2017/03/27 by Jamie.Dale

	Added package redirection on load/find

Change 3365852 on 2017/03/27 by Jamie.Dale

	Adding a way to mark a package as no longer missing

Change 3365896 on 2017/03/27 by Jamie.Dale

	Adding GlobalNotification to Slate

	This is the guts of the GlobalEditorNotification, so it can be used by code that doesn't link to UnrealEd.

Change 3365900 on 2017/03/27 by Jamie.Dale

	Prevent the default cooked sandbox from trying to read non-cooked assets

Change 3366550 on 2017/03/27 by Max.Chen

	Sequencer: Fix case

Change 3367301 on 2017/03/28 by Andrew.Rodham

	Tests: Added test actor with a variety of properties for testing purposes

Change 3367303 on 2017/03/28 by Andrew.Rodham

	Tests: Enabled ActorSequenceEditor plugin in EngineTest project

Change 3367304 on 2017/03/28 by Andrew.Rodham

	Tests: Added several functional testing maps for sequencer
	  - SequencerTest_Properties - tests animating various property types
	  - SequencerTest_Events - tests basic event triggering functionality (including additional event receivers and event ordering)
	  - SequencerTest_BindingOverrides - tests overriding possessable and spawnable bindings, along with bindings in sub sequences
	  - SequencerTest_ActorSequence - tests basic actor sequence functionality

Change 3367465 on 2017/03/28 by Max.Chen

	Sequencer: Set Bind Sequencer to PIE off by default, Bind Sequencer to Simulate remains on by default.

Change 3367515 on 2017/03/28 by Matt.Kuhlenschmidt

	Guard against visual studio accessor crash

	#jira UE-43368

Change 3368118 on 2017/03/28 by Alexis.Matte

	Fix the staticmesh conversion from 4.13. There was a error in the LOD loop we where not remapping the LOD 0.
	#jira UE-42731

Change 3368485 on 2017/03/28 by Alex.Delesky

	#jira UE-42207 - Updated SVN Binaries for MacOSX 64-bit:
	- Subversion 1.9.4 -> 1.9.5
	- OpenSSL 1.0.2h -> 1.0.2k
	- BerkeleyDB 5.3.15 -> 6.2.23
	- Java 8u101 -> 8u121
	- Sqlite 3.13.0 -> 3.17.0
	- Serf 1.3.8 -> 1.3.9
	- Swig 3.0.10 -> 3.0.12
	- ZLib 1.2.9 -> 1.2.11

Change 3368495 on 2017/03/28 by Alex.Delesky

	#jira UE-42207 - Updated SVN Binaries for Windows 64-bit:
	- Subversion 1.9.4 -> 1.9.5
	- OpenSSL 1.0.2h -> 1.0.2k
	- BerkeleyDB 5.3.15 -> 6.2.23
	- Java 8u101 -> 8u121
	- Sqlite 3.13.0 -> 3.17.0
	- Serf 1.3.8 -> 1.3.9
	- Swig 3.0.10 -> 3.0.12
	- ZLib 1.2.9 -> 1.2.11

Change 3368501 on 2017/03/28 by Alex.Delesky

	#jira UE-42207 - SVN Build instructions for Windows and Mac 64-bit libraries, and license files for Mac libraries

Change 3368782 on 2017/03/28 by Nick.Darnell

	UMG - Improving some logging for fast widget creation.

Change 3368826 on 2017/03/28 by Nick.Darnell

	Slate - Slate Application now maintains seperate tracking for each pointer being utilized for drag drop, so now multiple fingers on multiple widgets can now simultaneously be attempting a drag, however once one of them becomes successful, we clear all state of all other tracking since only one Drag Drop operation is possible at a time.

	Slate - bFoldTick is now removed from the codebase, we haven't supported the other (non-folded) code path for awhile, so there was no point in maintaining the switch.

	Slate - Users have noticed issues where the cursor does not appear when changing visibility (through toggling the way the cursor appears).  This was rooted in how the OS requested cursor changes, WM_SETCURSOR on Windows only asks for new cursors when the mouse moves, but often cursors change just because mouse capture changes.  So now the path has been centralized in Slate Tick to only handle the cursor changes in one place, and several places that need to refresh the cursor state, now set a flag to handle it on next tick.

	#jira UE-40486

Change 3368917 on 2017/03/28 by Arciel.Rekman

	Linux: allow building with clang 4.0.

Change 3369074 on 2017/03/28 by Nick.Darnell

	UMG - Fixing some spelling on the hardware cursor tip.

	UMG - Changed some checks to ensure now that users can input the wrong data from the editor.  Adding some clamping to the editor interface so that users are not tempted to enter incorrect hotspot ranges for their cursors.

	#jira UE-43419
	#jira UE-43425

Change 3369137 on 2017/03/28 by Max.Chen

	Sequencer: Add given master track sets the outer to the movie scene.

Change 3369360 on 2017/03/29 by Andrew.Rodham

	Sequencer: Reconciled 3349194 and 3365041 with animphys merge

Change 3369410 on 2017/03/29 by Alexis.Matte

	Fix the select filename in the FileDialog "Desktop window platform"
	#jira UE-43319

Change 3369475 on 2017/03/29 by Nick.Darnell

	PR #3413: UE-37710: Proper scaling of WebBrowserViewport (Contributed by projectgheist)

	Modified - you can't use the clip rect to decide on how large you should be.

	#jira UE-37710

Change 3369775 on 2017/03/29 by Max.Chen

	ControlRig: Fix crash on exit.

	#jira UE-43411

Change 3370466 on 2017/03/29 by Nick.Darnell

	AsyncLoading - Adding USoundBase to the set of CDOs that have a particular fixed boot order.

	StreamableManager - Only showing the duplicate load error in debug builds, it's not a real error.

	#jira UE-43409

Change 3370570 on 2017/03/29 by Nick.Darnell

	Slate - Fixing a bug with ZOrder being discarded on the SOverlay Slot.

	#jira UE-43431

Change 3370644 on 2017/03/29 by Andrew.Rodham

	Temporarily disabling sequencer functional test "Event Position"

Change 3370713 on 2017/03/29 by Nick.Darnell

	PR #3399: UE-42831: Anchor text ignores scale (Contributed by projectgheist)

	#jira UE-43156
	#jira UE-42831

Change 3371243 on 2017/03/30 by Arciel.Rekman

	Linux: scale OS allocation pool to match memory size.

	- Number of distinct VMAs (contiguous virtual memory areas, i.e. mappings done via mmap()) is rather low (~64k)
	  and we can run out of VMAs earlier than we will run into available memory. Larger pool makes this less likely.

Change 3371262 on 2017/03/30 by Arciel.Rekman

	Linux: fix custom present.

	- PR #3383 contributed by yaakuro.

Change 3371301 on 2017/03/30 by Arciel.Rekman

	Linux: fix copying to a non-existent directory during Setup.

Change 3371307 on 2017/03/30 by Andrew.Rodham

	Editor: Added "Resave All" functionality to content browser folders

Change 3371364 on 2017/03/30 by Andrew.Rodham

	Sequencer: Level streaming improvements
	  - Tick prerequisites are now set up when any object binding is resolved, not at the start of the sequence. This unifies code between spawnables and possessables, and allows tick prerequisites to still be set up when levels are streamed in
	  - Actor references are no longer resolved when a PIEInstance is specified on the package, and it cannot be fixed up to a different ptr than the original. This stops us resolving actors from one world into another.
	  - Fixed level visibility request getting cleared when the cumulative total was 0 (it should only do this if there are no requests left)

	#jira UE-43225

Change 3371365 on 2017/03/30 by Andrew.Rodham

	Tests: Sequencer level streaming tests

Change 3371493 on 2017/03/30 by Nick.Darnell

	PR #3408: UE-19980: Added FCanExecuteAction to prevent keyboard shortcut. (Contributed by projectgheist)

Change 3371524 on 2017/03/30 by Nick.Darnell

	PR #2938: Minor UMG code fixups (Contributed by projectgheist), accepted most of the changes.

Change 3371545 on 2017/03/30 by Nick.Darnell

	UMG - Fixing some minor issues with WidgetComponents not properly limiting input depending on what is supported with reguard to hardware input.

Change 3371576 on 2017/03/30 by Matt.Kuhlenschmidt

	PR #3433: Fix for the Standalone D3D Slate Shader using the wrong value for the. (Contributed by megasjay)

Change 3371590 on 2017/03/30 by Nick.Darnell

	UMG - Fixing widget alignment in the viewport when using the widget component with screen space, with an aspect ratio lock on the player's camera.  The widgets should now show up in the right locations.

Change 3371625 on 2017/03/30 by Alexis.Matte

	Fix the merge tool material id assignment
	#jira UE-43246

Change 3371666 on 2017/03/30 by Nick.Darnell

	UMG - Reducing logging, don't need to tell everyone all the time we're using the fast widget path.

Change 3371687 on 2017/03/30 by Arciel.Rekman

	Linux: switch to new managed filehandles.

Change 3371778 on 2017/03/30 by Matt.Kuhlenschmidt

	Fixed the animation to play property on skeletal meshes being too small to read anything

	#jira UE-43327

Change 3372709 on 2017/03/30 by Matt.Kuhlenschmidt

	Made slate loading widget / movie play back more thread safe by eliminating Slate applicaiton or the main window from being ticked directly on another thread.  We now have a separate virtual window for ticking and painting the loading screen widgets in isolation

Change 3372757 on 2017/03/30 by Nick.Darnell

	Paragon - Fixing cases where people were using PostLoad() where really it should have done when the widget was constructed or created.  This is a side effect of the FastWidget creation path 'PostLoad()' is not called on newly constructed widgets, though it did before because part of duplicating the WidgetTree, required serialization, which would have called it.

Change 3372777 on 2017/03/30 by Nick.Darnell

	Fixing fast widget template cooking so that it does the same logic as Initialize did, centralizing the code to find the first widgetblueprintclass.

Change 3372949 on 2017/03/30 by Nick.Darnell

	UMG - Fixing some cooking crashes for the super class.

Change 3373139 on 2017/03/30 by Jeff.Farris

	Added TimingPolicy option to WidgetComponent, so widgets can optionally tick in game time rather than real time.

	(Copy of CL 3279699 from Robo Recall to Dev-Editor)

Change 3373235 on 2017/03/30 by Nick.Darnell

	Fixing a cooking issue, accidentally removed code that was properly loading some needed assets.

Change 3373266 on 2017/03/30 by Matt.Kuhlenschmidt

	Made GetMoviePlayer thread safe.  Simply accessing GetMoviePlayer is safe now as is checking IsLoadingFinished.  However, most of the functions on movie player are only safe from the game thread!

Change 3374026 on 2017/03/31 by Andrew.Rodham

	Sequencer: Moved evaluation group registration to IMovieSceneModule

	#jira UE-43420

Change 3374060 on 2017/03/31 by Yannick.Lange

	VR Editor: Collision on motion controllers in simulate.

Change 3374185 on 2017/03/31 by Nick.Darnell

	Attempting to fix the build.

Change 3374232 on 2017/03/31 by Max.Chen

	Sequencer: Fix audio not playing in editor

	#jira UE-43514

Change 3374322 on 2017/03/31 by Nick.Darnell

	UMG - SafeZone widget now has comments, and useful tips.  Using the debugging console commands now trigger the broadcast that will cause controls like the SSafeZone widget to resample the display metrics to learn the new safezone ratio.

Change 3374424 on 2017/03/31 by Max.Chen

	Updated test content so that the door animation is now set to "Keep State" for the When Finished property.

	#jira UE-43519

Change 3374447 on 2017/03/31 by Max.Chen

	Sequencer: Notify streaming system prior to camera cuts

	By default, this does nothing. Users will need to enable the preroll section of camera cuts for the streaming system to activate prior to cutting to cameras.

	#jira UE-42406

Change 3374571 on 2017/03/31 by Andrew.Rodham

	Sequencer: Unified global and object-bound pre animated state, added InitializeObjectForAnimation method to state producers

Change 3374578 on 2017/03/31 by Andrew.Rodham

	Sequencer: Added unit tests for pre-animated state

Change 3374592 on 2017/03/31 by Max.Chen

	Color Customization: Set curve color names.

	#jira UE-43405

Change 3374596 on 2017/03/31 by Andrew.Rodham

	Corrected documentation comment

Change 3374671 on 2017/03/31 by Matt.Kuhlenschmidt

	Fix movie scene audio track not compiling outside of editor

Change 3374689 on 2017/03/31 by Matt.Kuhlenschmidt

	Remove the slate thread masquerading as the game thread in IsInGameThread

Change 3374730 on 2017/03/31 by Max.Chen

	Sequencer: Add check for null loaded level.

Change 3374732 on 2017/03/31 by Max.Chen

	Sequencer: Remove null tracks on postload.

Change 3374737 on 2017/03/31 by tim.gautier

	- Updated UMG_Optimization: Adjusted Variable names to resolve compile errors due to Widget Components and Variables sharing names (cannot be done with new compile improvements)

	- Set Level Blueprint for TM-UMG back to AllPalettes

Change 3374987 on 2017/03/31 by Nick.Darnell

	UMG - Introducing a way to inform the widgets more information about the designer.  There's now a DesignerChanged event sent to all design time widgets letting them know things like the current screen size and DPI scale.

	UMG - The SafeZone widget will now show the correct safe zone amount if you use the safezone command line options, which are now documented in the comment for the USafeZone class.

Change 3375599 on 2017/03/31 by Max.Chen

	Cine Camera: Update camera debug plane when property changes, rather rely soley on tick. This fixes a bug where sliding the value on the details panel doesn't update the debug plane in the viewport simultaneously.

	#jira UE-43543

Change 3375601 on 2017/03/31 by Arciel.Rekman

	Linux: switch to v9 cross-toolchain.

Change 3375856 on 2017/04/01 by Andrew.Rodham

	Sequencer: Fixed 'formal parameter with requested alignment of 16 won't be aligned'

Change 3375870 on 2017/04/01 by Andrew.Rodham

	Sequencer: Fixed explicit template instantiation ocurring before the complete definition of type's members
	  - This resulted such members not being instantiated (and hence exported) when compiled with clang

Change 3376114 on 2017/04/02 by Arciel.Rekman

	Linux: make source code accessor aware of clang 3.9 and 4.0.

Change 3376138 on 2017/04/02 by Arciel.Rekman

	Linux: add clang to fedora deps (UE-42123).

	- PR #3273 submitted by cpyarger.

Change 3376159 on 2017/04/02 by Arciel.Rekman

	Linux: some support for building on Debian Sid or Stretch (UE-35841).

	- Basd on PR #2790 by haimat.

Change 3376163 on 2017/04/02 by Arciel.Rekman

	Linux: install latest clang on Arch (UE-42341).

	- This undoes PR #1905.
	- PR #2897 by SiebenCorgie.
	- PR #3302 by awesomeness872.
	- PR #3341 by patrickelectric.

Change 3376167 on 2017/04/02 by Arciel.Rekman

	Add FreeBSD mem info (courtesy support for the out of tree build) (UE-42994).

	- PR #3378 by mdcasey.

Change 3376168 on 2017/04/02 by Arciel.Rekman

	Linux: fixed VHACD Makefile on a case sensitive fs (UE-42905).

	- PR #3381 by slonopotamus.

Change 3376177 on 2017/04/02 by Arciel.Rekman

	SlateDlg: case-insensitive comparison of filter extensions (UE-39477).

	- PR #3019 by aknarts.

Change 3376178 on 2017/04/02 by Arciel.Rekman

	WebRTC: only x86_64 version exists for Linux.

Change 3376245 on 2017/04/03 by Andrew.Rodham

	Sequencer: Re-enabled event order test

Change 3376339 on 2017/04/03 by Matt.Kuhlenschmidt

	Fix crash during loading movie playback on DX12 due to not ever cleaning up old resources

	#jira UE-27026

Change 3376481 on 2017/04/03 by Alex.Delesky

	#jira UE-43495 - TMaps will now support customized key properties correctly.

Change 3376741 on 2017/04/03 by Matt.Kuhlenschmidt

	Fix crash flushing font cache when loading a movie.  This is no longer save on the slate movie thread

	#jira UE-43567

Change 3376763 on 2017/04/03 by Shaun.Kime

	Material Reroute nodes do not work for Texture Object Parameters as they return a base output type. Modified logic to now support this node type.
	#jira UE-43521

Change 3376836 on 2017/04/03 by Jamie.Dale

	Fixed text format history being clobbered by reference collection

	#jira UE-37513

Change 3376852 on 2017/04/03 by Nick.Darnell

	Paragon - Found a case where a user had marked a BindWidget property as Transient which prevents serializing the property binding now for widget fast mode.

	#jira UE-43564

Change 3377207 on 2017/04/03 by Jamie.Dale

	Desktop platform directory pickers are expected to return absolute paths

	File pickers return relative paths though, and we should make this consistent at some point.

	#jira UE-43588

Change 3377214 on 2017/04/03 by Matt.Kuhlenschmidt

	Fix movie player shutdown crash in non-editor builds

	#jira UE-43577

Change 3377299 on 2017/04/03 by Michael.Dupuis

	#jira UE-43586 : properties should be non transactional
	#jira UE-43559

Change 3378333 on 2017/04/04 by Michael.Dupuis

	#jira UE-43585
	#jira UE-43586
	Revert back to purple color

Change 3378633 on 2017/04/04 by Matt.Kuhlenschmidt

	Resaved this asset to avoid zero engine version warnings

Change 3378958 on 2017/04/04 by Nick.Darnell

	Automation - Fixing the race condition to finish compiling shaders on screenshots for UI.

[CL 3379345 by Matt Kuhlenschmidt in Main branch]
2017-04-04 15:35:21 -04:00

5238 lines
211 KiB
C++

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
/*=============================================================================
LandscapeEditInterface.cpp: Landscape editing interface
=============================================================================*/
#include "CoreMinimal.h"
#include "GenericPlatform/GenericPlatformStackWalk.h"
#include "Templates/Greater.h"
#include "Containers/ArrayView.h"
#include "RenderingThread.h"
#include "LandscapeProxy.h"
#include "Landscape.h"
#include "LandscapeInfo.h"
#include "LandscapeComponent.h"
#include "LandscapeLayerInfoObject.h"
#if WITH_EDITOR
#include "LandscapeDataAccess.h"
#include "LandscapeEdit.h"
#include "LandscapeRender.h"
#include "ComponentReregisterContext.h"
#include "Containers/Algo/Transform.h"
// Channel remapping
extern const size_t ChannelOffsets[4] = {STRUCT_OFFSET(FColor,R), STRUCT_OFFSET(FColor,G), STRUCT_OFFSET(FColor,B), STRUCT_OFFSET(FColor,A)};
//
// FLandscapeEditDataInterface
//
FLandscapeEditDataInterface::FLandscapeEditDataInterface(ULandscapeInfo* InLandscapeInfo)
{
if (InLandscapeInfo)
{
LandscapeInfo = InLandscapeInfo;
ComponentSizeQuads = InLandscapeInfo->ComponentSizeQuads;
SubsectionSizeQuads = InLandscapeInfo->SubsectionSizeQuads;
ComponentNumSubsections = InLandscapeInfo->ComponentNumSubsections;
DrawScale = InLandscapeInfo->DrawScale;
}
}
FLandscapeTextureDataInterface::~FLandscapeTextureDataInterface()
{
Flush();
}
void FLandscapeTextureDataInterface::Flush()
{
bool bNeedToWaitForUpdate = false;
// Update all textures
for( TMap<UTexture2D*, FLandscapeTextureDataInfo*>::TIterator It(TextureDataMap); It; ++It )
{
if( It.Value()->UpdateTextureData() )
{
bNeedToWaitForUpdate = true;
}
}
if( bNeedToWaitForUpdate )
{
FlushRenderingCommands();
}
// delete all the FLandscapeTextureDataInfo allocations
for( TMap<UTexture2D*, FLandscapeTextureDataInfo*>::TIterator It(TextureDataMap); It; ++It )
{
delete It.Value(); // FLandscapeTextureDataInfo destructors will unlock any texture data
}
TextureDataMap.Empty();
}
#include "LevelUtils.h"
void ALandscape::CalcComponentIndicesOverlap(const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, const int32 ComponentSizeQuads,
int32& ComponentIndexX1, int32& ComponentIndexY1, int32& ComponentIndexX2, int32& ComponentIndexY2)
{
// Find component range for this block of data
ComponentIndexX1 = (X1-1 >= 0) ? (X1-1) / ComponentSizeQuads : (X1) / ComponentSizeQuads - 1; // -1 because we need to pick up vertices shared between components
ComponentIndexY1 = (Y1-1 >= 0) ? (Y1-1) / ComponentSizeQuads : (Y1) / ComponentSizeQuads - 1;
ComponentIndexX2 = (X2 >= 0) ? X2 / ComponentSizeQuads : (X2+1) / ComponentSizeQuads - 1;
ComponentIndexY2 = (Y2 >= 0) ? Y2 / ComponentSizeQuads : (Y2+1) / ComponentSizeQuads - 1;
}
void ALandscape::CalcComponentIndicesNoOverlap(const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, const int32 ComponentSizeQuads,
int32& ComponentIndexX1, int32& ComponentIndexY1, int32& ComponentIndexX2, int32& ComponentIndexY2)
{
// Find component range for this block of data
ComponentIndexX1 = (X1 >= 0) ? X1 / ComponentSizeQuads : (X1+1) / ComponentSizeQuads - 1; // -1 because we need to pick up vertices shared between components
ComponentIndexY1 = (Y1 >= 0) ? Y1 / ComponentSizeQuads : (Y1+1) / ComponentSizeQuads - 1;
ComponentIndexX2 = (X2-1 >= 0) ? (X2-1) / ComponentSizeQuads : (X2) / ComponentSizeQuads - 1;
ComponentIndexY2 = (Y2-1 >= 0) ? (Y2-1) / ComponentSizeQuads : (Y2) / ComponentSizeQuads - 1;
// Shrink indices for shared values
if ( ComponentIndexX2 < ComponentIndexX1)
{
ComponentIndexX2 = ComponentIndexX1;
}
if ( ComponentIndexY2 < ComponentIndexY1)
{
ComponentIndexY2 = ComponentIndexY1;
}
}
namespace
{
// Ugly helper function, all arrays should be only size 4
template<typename T, typename F>
FORCEINLINE void CalcInterpValue(const int32* Dist, const bool* Exist, const T* Value, F& ValueX, F& ValueY)
{
if (Exist[0] && Exist[1])
{
ValueX = (F)(Dist[1] * Value[0] + Dist[0] * Value[1]) / (Dist[0] + Dist[1]);
}
else
{
if (Exist[0])
{
ValueX = Value[0];
}
else if (Exist[1])
{
ValueX = Value[1];
}
}
if (Exist[2] && Exist[3])
{
ValueY = (F)(Dist[3] * Value[2] + Dist[2] * Value[3]) / (Dist[2] + Dist[3]);
}
else
{
if (Exist[2])
{
ValueY = Value[2];
}
else if (Exist[3])
{
ValueY = Value[3];
}
}
}
template<typename T>
FORCEINLINE T CalcValueFromValueXY( const int32* Dist, const T& ValueX, const T& ValueY, const uint8& CornerSet, const T* CornerValues )
{
T FinalValue;
int32 DistX = FMath::Min(Dist[0], Dist[1]);
int32 DistY = FMath::Min(Dist[2], Dist[3]);
if (DistX+DistY > 0)
{
FinalValue = ((ValueX * DistY) + (ValueY * DistX)) / (float)(DistX + DistY);
}
else
{
if ((CornerSet & 1) && Dist[0] == 0 && Dist[2] == 0)
{
FinalValue = CornerValues[0];
}
else if ((CornerSet & 1 << 1) && Dist[1] == 0 && Dist[2] == 0)
{
FinalValue = CornerValues[1];
}
else if ((CornerSet & 1 << 2) && Dist[0] == 0 && Dist[3] == 0)
{
FinalValue = CornerValues[2];
}
else if ((CornerSet & 1 << 3) && Dist[1] == 0 && Dist[3] == 0)
{
FinalValue = CornerValues[3];
}
else
{
FinalValue = ValueX;
}
}
return FinalValue;
}
};
bool FLandscapeEditDataInterface::GetComponentsInRegion(int32 X1, int32 Y1, int32 X2, int32 Y2, TSet<ULandscapeComponent*>* OutComponents /*= nullptr*/)
{
if (ComponentSizeQuads <= 0 || !LandscapeInfo)
{
return false;
}
// Find component range for this block of data
int32 ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2;
ALandscape::CalcComponentIndicesOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2);
bool bNotLocked = true;
for (int32 ComponentIndexY = ComponentIndexY1; ComponentIndexY <= ComponentIndexY2; ComponentIndexY++)
{
for (int32 ComponentIndexX = ComponentIndexX1; ComponentIndexX <= ComponentIndexX2; ComponentIndexX++)
{
ULandscapeComponent* Component = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX, ComponentIndexY));
if (Component)
{
bNotLocked = bNotLocked && (!FLevelUtils::IsLevelLocked(Component->GetLandscapeProxy()->GetLevel())) && FLevelUtils::IsLevelVisible(Component->GetLandscapeProxy()->GetLevel());
if (OutComponents)
{
OutComponents->Add(Component);
}
}
}
}
return bNotLocked;
}
void FLandscapeEditDataInterface::SetHeightData(int32 X1, int32 Y1, int32 X2, int32 Y2, const uint16* Data, int32 Stride, bool CalcNormals, const uint16* NormalData /*= NULL*/, bool CreateComponents /*= false*/)
{
const int32 NumVertsX = 1 + X2 - X1;
const int32 NumVertsY = 1 + Y2 - Y1;
if (Stride == 0)
{
Stride = NumVertsX;
}
check(ComponentSizeQuads > 0);
// Find component range for this block of data
int32 ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2;
ALandscape::CalcComponentIndicesOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2);
FVector* VertexNormals = nullptr;
if (CalcNormals)
{
// Calculate the normals for each of the two triangles per quad.
// Note that the normals at the edges are not correct because they include normals
// from triangles outside the current area. They are not updated
VertexNormals = new FVector[NumVertsX*NumVertsY];
FMemory::Memzero(VertexNormals, NumVertsX*NumVertsY*sizeof(FVector));
// Need to consider XYOffset for XY displacemented map
FVector2D* XYOffsets = new FVector2D[NumVertsX*NumVertsY];
FMemory::Memzero(XYOffsets, NumVertsX*NumVertsY*sizeof(FVector2D));
GetXYOffsetDataFast(X1, Y1, X2, Y2, XYOffsets, 0);
for (int32 Y = 0; Y < NumVertsY - 1; Y++)
{
for (int32 X = 0; X < NumVertsX - 1; X++)
{
FVector Vert00 = FVector(XYOffsets[(X+0) + NumVertsX*(Y+0)].X, XYOffsets[(X+0) + NumVertsX*(Y+0)].Y, ((float)Data[(X+0) + Stride*(Y+0)] - 32768.0f) * LANDSCAPE_ZSCALE) * DrawScale;
FVector Vert01 = FVector(XYOffsets[(X+0) + NumVertsX*(Y+0)].X, XYOffsets[(X+0) + NumVertsX*(Y+0)].Y+1.0f, ((float)Data[(X+0) + Stride*(Y+1)] - 32768.0f) * LANDSCAPE_ZSCALE) * DrawScale;
FVector Vert10 = FVector(XYOffsets[(X+0) + NumVertsX*(Y+0)].X+1.0f, XYOffsets[(X+0) + NumVertsX*(Y+0)].Y, ((float)Data[(X+1) + Stride*(Y+0)] - 32768.0f) * LANDSCAPE_ZSCALE) * DrawScale;
FVector Vert11 = FVector(XYOffsets[(X+0) + NumVertsX*(Y+0)].X+1.0f, XYOffsets[(X+0) + NumVertsX*(Y+0)].Y+1.0f, ((float)Data[(X+1) + Stride*(Y+1)] - 32768.0f) * LANDSCAPE_ZSCALE) * DrawScale;
FVector FaceNormal1 = ((Vert00-Vert10) ^ (Vert10-Vert11)).GetSafeNormal();
FVector FaceNormal2 = ((Vert11-Vert01) ^ (Vert01-Vert00)).GetSafeNormal();
// contribute to the vertex normals.
VertexNormals[(X+1 + NumVertsX*(Y+0))] += FaceNormal1;
VertexNormals[(X+0 + NumVertsX*(Y+1))] += FaceNormal2;
VertexNormals[(X+0 + NumVertsX*(Y+0))] += FaceNormal1 + FaceNormal2;
VertexNormals[(X+1 + NumVertsX*(Y+1))] += FaceNormal1 + FaceNormal2;
}
}
delete[] XYOffsets;
}
for (int32 ComponentIndexY = ComponentIndexY1; ComponentIndexY <= ComponentIndexY2; ComponentIndexY++)
{
for (int32 ComponentIndexX = ComponentIndexX1; ComponentIndexX <= ComponentIndexX2; ComponentIndexX++)
{
FIntPoint ComponentKey(ComponentIndexX, ComponentIndexY);
ULandscapeComponent* Component = LandscapeInfo->XYtoComponentMap.FindRef(ComponentKey);
// if nullptr, it was painted away
if (Component == nullptr)
{
if (CreateComponents)
{
// not yet implemented
continue;
}
else
{
continue;
}
}
Component->Modify();
FLandscapeTextureDataInfo* TexDataInfo = GetTextureDataInfo(Component->HeightmapTexture);
FColor* HeightmapTextureData = (FColor*)TexDataInfo->GetMipData(0);
FColor* XYOffsetMipData = nullptr;
if (Component->XYOffsetmapTexture)
{
FLandscapeTextureDataInfo* XYTexDataInfo = GetTextureDataInfo(Component->XYOffsetmapTexture);
XYOffsetMipData = (FColor*)XYTexDataInfo->GetMipData(Component->CollisionMipLevel);
}
// Find the texture data corresponding to this vertex
int32 SizeU = Component->HeightmapTexture->Source.GetSizeX();
int32 SizeV = Component->HeightmapTexture->Source.GetSizeY();
int32 HeightmapOffsetX = Component->HeightmapScaleBias.Z * (float)SizeU;
int32 HeightmapOffsetY = Component->HeightmapScaleBias.W * (float)SizeV;
// Find coordinates of box that lies inside component
int32 ComponentX1 = FMath::Clamp<int32>(X1-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY1 = FMath::Clamp<int32>(Y1-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentX2 = FMath::Clamp<int32>(X2-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY2 = FMath::Clamp<int32>(Y2-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
// Find subsection range for this box
int32 SubIndexX1 = FMath::Clamp<int32>((ComponentX1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1); // -1 because we need to pick up vertices shared between subsections
int32 SubIndexY1 = FMath::Clamp<int32>((ComponentY1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexX2 = FMath::Clamp<int32>(ComponentX2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexY2 = FMath::Clamp<int32>(ComponentY2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
// To adjust bounding box
uint16 MinHeight = MAX_uint16;
uint16 MaxHeight = 0;
for (int32 SubIndexY = SubIndexY1; SubIndexY <= SubIndexY2; SubIndexY++)
{
for (int32 SubIndexX = SubIndexX1; SubIndexX <= SubIndexX2; SubIndexX++)
{
// Find coordinates of box that lies inside subsection
int32 SubX1 = FMath::Clamp<int32>(ComponentX1-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY1 = FMath::Clamp<int32>(ComponentY1-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
int32 SubX2 = FMath::Clamp<int32>(ComponentX2-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY2 = FMath::Clamp<int32>(ComponentY2-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
// Update texture data for the box that lies inside subsection
for (int32 SubY = SubY1; SubY <= SubY2; SubY++)
{
for (int32 SubX = SubX1; SubX <= SubX2; SubX++)
{
int32 LandscapeX = SubIndexX*SubsectionSizeQuads + ComponentIndexX*ComponentSizeQuads + SubX;
int32 LandscapeY = SubIndexY*SubsectionSizeQuads + ComponentIndexY*ComponentSizeQuads + SubY;
checkSlow(LandscapeX >= X1 && LandscapeX <= X2);
checkSlow(LandscapeY >= Y1 && LandscapeY <= Y2);
// Find the input data corresponding to this vertex
int32 DataIndex = (LandscapeX-X1) + Stride * (LandscapeY-Y1);
const uint16& Height = Data[DataIndex];
// for bounding box
if (Height < MinHeight)
{
MinHeight = Height;
}
if (Height > MaxHeight)
{
MaxHeight = Height;
}
int32 TexX = HeightmapOffsetX + (SubsectionSizeQuads+1) * SubIndexX + SubX;
int32 TexY = HeightmapOffsetY + (SubsectionSizeQuads+1) * SubIndexY + SubY;
FColor& TexData = HeightmapTextureData[TexX + TexY * SizeU];
// Update the texture
TexData.R = Height >> 8;
TexData.G = Height & 255;
// Update normals if we're not on an edge vertex
if (VertexNormals && LandscapeX > X1 && LandscapeX < X2 && LandscapeY > Y1 && LandscapeY < Y2)
{
const int32 NormalDataIndex = (LandscapeX-X1) + NumVertsX * (LandscapeY-Y1);
FVector Normal = VertexNormals[NormalDataIndex].GetSafeNormal();
TexData.B = FMath::RoundToInt(127.5f * (Normal.X + 1.0f));
TexData.A = FMath::RoundToInt(127.5f * (Normal.Y + 1.0f));
}
else if (NormalData)
{
// Need data validation?
const uint16& Normal = NormalData[DataIndex];
TexData.B = Normal >> 8;
TexData.A = Normal & 255;
}
}
}
// Record the areas of the texture we need to re-upload
int32 TexX1 = HeightmapOffsetX + (SubsectionSizeQuads+1) * SubIndexX + SubX1;
int32 TexY1 = HeightmapOffsetY + (SubsectionSizeQuads+1) * SubIndexY + SubY1;
int32 TexX2 = HeightmapOffsetX + (SubsectionSizeQuads+1) * SubIndexX + SubX2;
int32 TexY2 = HeightmapOffsetY + (SubsectionSizeQuads+1) * SubIndexY + SubY2;
TexDataInfo->AddMipUpdateRegion(0,TexX1,TexY1,TexX2,TexY2);
}
}
// See if we need to adjust the bounds. Note we never shrink the bounding box at this point
float MinLocalZ = LandscapeDataAccess::GetLocalHeight(MinHeight);
float MaxLocalZ = LandscapeDataAccess::GetLocalHeight(MaxHeight);
bool bUpdateBoxSphereBounds = false;
if (MinLocalZ < Component->CachedLocalBox.Min.Z)
{
Component->CachedLocalBox.Min.Z = MinLocalZ;
bUpdateBoxSphereBounds = true;
}
if (MaxLocalZ > Component->CachedLocalBox.Max.Z)
{
Component->CachedLocalBox.Max.Z = MaxLocalZ;
bUpdateBoxSphereBounds = true;
}
if (bUpdateBoxSphereBounds)
{
Component->UpdateComponentToWorld();
}
// Update mipmaps
// Work out how many mips should be calculated directly from one component's data.
// The remaining mips are calculated on a per texture basis.
// eg if subsection is 7x7 quads, we need one 3 mips total: (8x8, 4x4, 2x2 verts)
int32 BaseNumMips = FMath::CeilLogTwo(SubsectionSizeQuads+1);
TArray<FColor*> MipData;
MipData.AddUninitialized(BaseNumMips);
MipData[0] = HeightmapTextureData;
for (int32 MipIdx = 1; MipIdx < BaseNumMips; MipIdx++)
{
MipData[MipIdx] = (FColor*)TexDataInfo->GetMipData(MipIdx);
}
Component->GenerateHeightmapMips(MipData, ComponentX1, ComponentY1, ComponentX2, ComponentY2, TexDataInfo);
// Update collision
Component->UpdateCollisionHeightData(
MipData[Component->CollisionMipLevel],
Component->SimpleCollisionMipLevel > Component->CollisionMipLevel ? MipData[Component->SimpleCollisionMipLevel] : nullptr,
ComponentX1, ComponentY1, ComponentX2, ComponentY2, bUpdateBoxSphereBounds,
XYOffsetMipData);
// Update GUID for Platform Data
FPlatformMisc::CreateGuid(Component->StateId);
}
}
if (VertexNormals)
{
delete[] VertexNormals;
}
}
//
// RecalculateNormals - Regenerate normals for the entire landscape. Called after modifying DrawScale3D.
//
void FLandscapeEditDataInterface::RecalculateNormals()
{
if (!LandscapeInfo) return;
// Recalculate normals for each component in turn
for( auto It = LandscapeInfo->XYtoComponentMap.CreateIterator(); It; ++It )
{
ULandscapeComponent* Component = It.Value();
// one extra row of vertex either side of the component
int32 X1 = Component->GetSectionBase().X-1;
int32 Y1 = Component->GetSectionBase().Y-1;
int32 X2 = Component->GetSectionBase().X+ComponentSizeQuads+1;
int32 Y2 = Component->GetSectionBase().Y+ComponentSizeQuads+1;
int32 Stride = ComponentSizeQuads+3;
uint16* HeightData = new uint16[FMath::Square(Stride)];
FVector* VertexNormals = new FVector[FMath::Square(Stride)];
FMemory::Memzero(VertexNormals, FMath::Square(Stride)*sizeof(FVector));
FVector2D* XYOffsets = new FVector2D[FMath::Square(Stride)];
FMemory::Memzero(XYOffsets, FMath::Square(Stride)*sizeof(FVector2D));
// Get XY offset
GetXYOffsetDataFast(X1,Y1,X2,Y2,XYOffsets,0);
// Get the vertex positions for entire quad
GetHeightData(X1,Y1,X2,Y2,HeightData,0);
// Contribute face normals for all triangles contributing to this components' normals
for( int32 Y=0;Y<Stride-1;Y++ )
{
for( int32 X=0;X<Stride-1;X++ )
{
FVector Vert00 = FVector(XYOffsets[(X+0) + Stride*(Y+0)].X, XYOffsets[(X+0) + Stride*(Y+0)].Y, ((float)HeightData[(X+0) + Stride*(Y+0)] - 32768.0f)*LANDSCAPE_ZSCALE) * DrawScale;
FVector Vert01 = FVector(XYOffsets[(X+0) + Stride*(Y+0)].X, XYOffsets[(X+0) + Stride*(Y+0)].Y+1.0f, ((float)HeightData[(X+0) + Stride*(Y+1)] - 32768.0f)*LANDSCAPE_ZSCALE) * DrawScale;
FVector Vert10 = FVector(XYOffsets[(X+0) + Stride*(Y+0)].X+1.0f, XYOffsets[(X+0) + Stride*(Y+0)].Y, ((float)HeightData[(X+1) + Stride*(Y+0)] - 32768.0f)*LANDSCAPE_ZSCALE) * DrawScale;
FVector Vert11 = FVector(XYOffsets[(X+0) + Stride*(Y+0)].X+1.0f, XYOffsets[(X+0) + Stride*(Y+0)].Y+1.0f,((float)HeightData[(X+1) + Stride*(Y+1)] - 32768.0f)*LANDSCAPE_ZSCALE) * DrawScale;
FVector FaceNormal1 = ((Vert00-Vert10) ^ (Vert10-Vert11)).GetSafeNormal();
FVector FaceNormal2 = ((Vert11-Vert01) ^ (Vert01-Vert00)).GetSafeNormal();
// contribute to the vertex normals.
VertexNormals[(X+1 + Stride*(Y+0))] += FaceNormal1;
VertexNormals[(X+0 + Stride*(Y+1))] += FaceNormal2;
VertexNormals[(X+0 + Stride*(Y+0))] += FaceNormal1 + FaceNormal2;
VertexNormals[(X+1 + Stride*(Y+1))] += FaceNormal1 + FaceNormal2;
}
}
// Find the texture data corresponding to this vertex
int32 SizeU = Component->HeightmapTexture->Source.GetSizeX();
int32 SizeV = Component->HeightmapTexture->Source.GetSizeY();
int32 HeightmapOffsetX = Component->HeightmapScaleBias.Z * (float)SizeU;
int32 HeightmapOffsetY = Component->HeightmapScaleBias.W * (float)SizeV;
FLandscapeTextureDataInfo* TexDataInfo = GetTextureDataInfo(Component->HeightmapTexture);
FColor* HeightmapTextureData = (FColor*)TexDataInfo->GetMipData(0);
// Apply vertex normals to the component
for( int32 SubIndexY=0;SubIndexY<Component->NumSubsections;SubIndexY++ )
{
for( int32 SubIndexX=0;SubIndexX<Component->NumSubsections;SubIndexX++ )
{
for( int32 SubY=0;SubY<=SubsectionSizeQuads;SubY++ )
{
for( int32 SubX=0;SubX<=SubsectionSizeQuads;SubX++ )
{
int32 X = (SubsectionSizeQuads+1) * SubIndexX + SubX;
int32 Y = (SubsectionSizeQuads+1) * SubIndexY + SubY;
int32 DataIndex = (X+1) + (Y+1) * Stride;
int32 TexX = HeightmapOffsetX + X;
int32 TexY = HeightmapOffsetY + Y;
FColor& TexData = HeightmapTextureData[ TexX + TexY * SizeU ];
// Update the texture
FVector Normal = VertexNormals[DataIndex].GetSafeNormal();
TexData.B = FMath::RoundToInt( 127.5f * (Normal.X + 1.0f) );
TexData.A = FMath::RoundToInt( 127.5f * (Normal.Y + 1.0f) );
}
}
}
}
delete[] XYOffsets;
delete[] HeightData;
delete[] VertexNormals;
// Record the areas of the texture we need to re-upload
int32 TexX1 = HeightmapOffsetX;
int32 TexY1 = HeightmapOffsetY;
int32 TexX2 = HeightmapOffsetX + (SubsectionSizeQuads+1) * Component->NumSubsections - 1;
int32 TexY2 = HeightmapOffsetY + (SubsectionSizeQuads+1) * Component->NumSubsections - 1;
TexDataInfo->AddMipUpdateRegion(0,TexX1,TexY1,TexX2,TexY2);
// Work out how many mips should be calculated directly from one component's data.
// The remaining mips are calculated on a per texture basis.
// eg if subsection is 7x7 quads, we need one 3 mips total: (8x8, 4x4, 2x2 verts)
int32 BaseNumMips = FMath::CeilLogTwo(SubsectionSizeQuads+1);
TArray<FColor*> MipData;
MipData.AddUninitialized(BaseNumMips);
MipData[0] = HeightmapTextureData;
for( int32 MipIdx=1;MipIdx<BaseNumMips;MipIdx++ )
{
MipData[MipIdx] = (FColor*)TexDataInfo->GetMipData(MipIdx);
}
Component->GenerateHeightmapMips( MipData, 0, 0, ComponentSizeQuads, ComponentSizeQuads, TexDataInfo );
}
}
template<typename TStoreData>
void FLandscapeEditDataInterface::GetHeightDataTemplFast(const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, TStoreData& StoreData, TStoreData* NormalData /*= NULL*/)
{
if (!LandscapeInfo) return;
int32 ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2;
ALandscape::CalcComponentIndicesNoOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2);
for( int32 ComponentIndexY=ComponentIndexY1;ComponentIndexY<=ComponentIndexY2;ComponentIndexY++ )
{
for( int32 ComponentIndexX=ComponentIndexX1;ComponentIndexX<=ComponentIndexX2;ComponentIndexX++ )
{
ULandscapeComponent* Component = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX,ComponentIndexY));
FLandscapeTextureDataInfo* TexDataInfo = NULL;
FColor* HeightmapTextureData = NULL;
if( Component )
{
TexDataInfo = GetTextureDataInfo(Component->HeightmapTexture);
HeightmapTextureData = (FColor*)TexDataInfo->GetMipData(0);
}
else
{
continue;
}
// Find coordinates of box that lies inside component
int32 ComponentX1 = FMath::Clamp<int32>(X1-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY1 = FMath::Clamp<int32>(Y1-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentX2 = FMath::Clamp<int32>(X2-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY2 = FMath::Clamp<int32>(Y2-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
// Find subsection range for this box
int32 SubIndexX1 = FMath::Clamp<int32>((ComponentX1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1); // -1 because we need to pick up vertices shared between subsections
int32 SubIndexY1 = FMath::Clamp<int32>((ComponentY1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexX2 = FMath::Clamp<int32>(ComponentX2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexY2 = FMath::Clamp<int32>(ComponentY2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
for( int32 SubIndexY=SubIndexY1;SubIndexY<=SubIndexY2;SubIndexY++ )
{
for( int32 SubIndexX=SubIndexX1;SubIndexX<=SubIndexX2;SubIndexX++ )
{
// Find coordinates of box that lies inside subsection
int32 SubX1 = FMath::Clamp<int32>(ComponentX1-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY1 = FMath::Clamp<int32>(ComponentY1-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
int32 SubX2 = FMath::Clamp<int32>(ComponentX2-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY2 = FMath::Clamp<int32>(ComponentY2-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
// Update texture data for the box that lies inside subsection
for( int32 SubY=SubY1;SubY<=SubY2;SubY++ )
{
for( int32 SubX=SubX1;SubX<=SubX2;SubX++ )
{
int32 LandscapeX = SubIndexX*SubsectionSizeQuads + ComponentIndexX*ComponentSizeQuads + SubX;
int32 LandscapeY = SubIndexY*SubsectionSizeQuads + ComponentIndexY*ComponentSizeQuads + SubY;
// Find the texture data corresponding to this vertex
int32 SizeU = Component->HeightmapTexture->Source.GetSizeX();
int32 SizeV = Component->HeightmapTexture->Source.GetSizeY();
int32 HeightmapOffsetX = Component->HeightmapScaleBias.Z * (float)SizeU;
int32 HeightmapOffsetY = Component->HeightmapScaleBias.W * (float)SizeV;
int32 TexX = HeightmapOffsetX + (SubsectionSizeQuads+1) * SubIndexX + SubX;
int32 TexY = HeightmapOffsetY + (SubsectionSizeQuads+1) * SubIndexY + SubY;
FColor& TexData = HeightmapTextureData[ TexX + TexY * SizeU ];
uint16 Height = (((uint16)TexData.R) << 8) | TexData.G;
StoreData.Store(LandscapeX, LandscapeY, Height);
if (NormalData)
{
uint16 Normals = (((uint16)TexData.B) << 8) | TexData.A;
NormalData->Store(LandscapeX, LandscapeY, Normals);
}
}
}
}
}
}
}
}
template<typename TData, typename TStoreData, typename FType>
void FLandscapeEditDataInterface::CalcMissingValues(const int32& X1, const int32& X2, const int32& Y1, const int32& Y2,
const int32& ComponentIndexX1, const int32& ComponentIndexX2, const int32& ComponentIndexY1, const int32& ComponentIndexY2,
const int32& ComponentSizeX, const int32& ComponentSizeY, TData* CornerValues,
TArray<bool>& NoBorderY1, TArray<bool>& NoBorderY2, TArray<bool>& ComponentDataExist, TStoreData& StoreData)
{
bool NoBorderX1 = false, NoBorderX2 = false;
// Init data...
FMemory::Memzero(NoBorderY1.GetData(), ComponentSizeX*sizeof(bool));
FMemory::Memzero(NoBorderY2.GetData(), ComponentSizeX*sizeof(bool));
int32 BorderX1 = INT_MAX, BorderX2 = INT_MIN;
TArray<int32> BorderY1, BorderY2;
BorderY1.Empty(ComponentSizeX);
BorderY2.Empty(ComponentSizeX);
for( int32 ComponentIndexX=ComponentIndexX1;ComponentIndexX<=ComponentIndexX2;ComponentIndexX++ )
{
new (BorderY1) int32(INT_MAX);
new (BorderY2) int32(INT_MIN);
}
// fill up missing values...
for( int32 ComponentIndexY=ComponentIndexY1;ComponentIndexY<=ComponentIndexY2;ComponentIndexY++ )
{
NoBorderX1 = false;
NoBorderX2 = false;
BorderX1 = INT_MAX;
BorderX2 = INT_MIN;
for( int32 ComponentIndexX=ComponentIndexX1;ComponentIndexX<=ComponentIndexX2;ComponentIndexX++ )
{
int32 ComponentIndexXY = ComponentSizeX*(ComponentIndexY-ComponentIndexY1) + ComponentIndexX-ComponentIndexX1;
if (!ComponentDataExist[ComponentIndexXY])
{
int32 ComponentIndexXX = ComponentIndexX - ComponentIndexX1;
int32 ComponentIndexYY = ComponentIndexY - ComponentIndexY1;
uint8 CornerSet = 0;
bool ExistLeft = ComponentIndexXX > 0 && ComponentDataExist[ ComponentIndexXX-1 + ComponentIndexYY * ComponentSizeX ];
bool ExistUp = ComponentIndexYY > 0 && ComponentDataExist[ ComponentIndexXX + (ComponentIndexYY-1) * ComponentSizeX ];
// Search for neighbor component for interpolation
bool bShouldSearchX = (BorderX2 <= ComponentIndexX);
bool bShouldSearchY = (BorderY2[ComponentIndexXX] <= ComponentIndexY);
// Search for left-closest component
if ( bShouldSearchX || (!NoBorderX1 && BorderX1 == INT_MAX) )
{
NoBorderX1 = true;
BorderX1 = INT_MAX;
for (int32 X = ComponentIndexX-1; X >= ComponentIndexX1; X--)
{
if (ComponentDataExist[ComponentSizeX*(ComponentIndexY-ComponentIndexY1) + X-ComponentIndexX1])
{
NoBorderX1 = false;
BorderX1 = X;
break;
}
}
}
// Search for right-closest component
if ( bShouldSearchX || (!NoBorderX2 && BorderX2 == INT_MIN) )
{
NoBorderX2 = true;
BorderX2 = INT_MIN;
for (int32 X = ComponentIndexX+1; X <= ComponentIndexX2; X++)
{
if (ComponentDataExist[ComponentSizeX*(ComponentIndexY-ComponentIndexY1) + X-ComponentIndexX1])
{
NoBorderX2 = false;
BorderX2 = X;
break;
}
}
}
// Search for up-closest component
if ( bShouldSearchY || (!NoBorderY1[ComponentIndexXX] && BorderY1[ComponentIndexXX] == INT_MAX))
{
NoBorderY1[ComponentIndexXX] = true;
BorderY1[ComponentIndexXX] = INT_MAX;
for (int32 Y = ComponentIndexY-1; Y >= ComponentIndexY1; Y--)
{
if (ComponentDataExist[ComponentSizeX*(Y-ComponentIndexY1) + ComponentIndexX-ComponentIndexX1])
{
NoBorderY1[ComponentIndexXX] = false;
BorderY1[ComponentIndexXX] = Y;
break;
}
}
}
// Search for bottom-closest component
if ( bShouldSearchY || (!NoBorderY2[ComponentIndexXX] && BorderY2[ComponentIndexXX] == INT_MIN))
{
NoBorderY2[ComponentIndexXX] = true;
BorderY2[ComponentIndexXX] = INT_MIN;
for (int32 Y = ComponentIndexY+1; Y <= ComponentIndexY2; Y++)
{
if (ComponentDataExist[ComponentSizeX*(Y-ComponentIndexY1) + ComponentIndexX-ComponentIndexX1])
{
NoBorderY2[ComponentIndexXX] = false;
BorderY2[ComponentIndexXX] = Y;
break;
}
}
}
if (((ComponentIndexX == ComponentIndexX1) || (ComponentIndexY == ComponentIndexY1)) ? false : ComponentDataExist[ComponentSizeX*(ComponentIndexY-1-ComponentIndexY1) + ComponentIndexX-1-ComponentIndexX1])
{
CornerSet |= 1;
CornerValues[0] = TData(StoreData.Load(ComponentIndexX*ComponentSizeQuads, ComponentIndexY*ComponentSizeQuads));
}
if (((ComponentIndexX == ComponentIndexX2) || (ComponentIndexY == ComponentIndexY1)) ? false : ComponentDataExist[ComponentSizeX*(ComponentIndexY-1-ComponentIndexY1) + ComponentIndexX+1-ComponentIndexX1])
{
CornerSet |= 1 << 1;
CornerValues[1] = TData(StoreData.Load((ComponentIndexX + 1)*ComponentSizeQuads, ComponentIndexY*ComponentSizeQuads));
}
if (((ComponentIndexX == ComponentIndexX1) || (ComponentIndexY == ComponentIndexY2)) ? false : ComponentDataExist[ComponentSizeX*(ComponentIndexY+1-ComponentIndexY1) + ComponentIndexX-1-ComponentIndexX1])
{
CornerSet |= 1 << 2;
CornerValues[2] = TData(StoreData.Load(ComponentIndexX*ComponentSizeQuads, (ComponentIndexY + 1)*ComponentSizeQuads));
}
if (((ComponentIndexX == ComponentIndexX2) || (ComponentIndexY == ComponentIndexY2)) ? false : ComponentDataExist[ComponentSizeX*(ComponentIndexY+1-ComponentIndexY1) + ComponentIndexX+1-ComponentIndexX1])
{
CornerSet |= 1 << 3;
CornerValues[3] = TData(StoreData.Load((ComponentIndexX + 1)*ComponentSizeQuads, (ComponentIndexY + 1)*ComponentSizeQuads));
}
FillCornerValues(CornerSet, CornerValues);
// Find coordinates of box that lies inside component
int32 ComponentX1 = FMath::Clamp<int32>(X1-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY1 = FMath::Clamp<int32>(Y1-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentX2 = FMath::Clamp<int32>(X2-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY2 = FMath::Clamp<int32>(Y2-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
// Find subsection range for this box
int32 SubIndexX1 = FMath::Clamp<int32>((ComponentX1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1); // -1 because we need to pick up vertices shared between subsections
int32 SubIndexY1 = FMath::Clamp<int32>((ComponentY1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexX2 = FMath::Clamp<int32>(ComponentX2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexY2 = FMath::Clamp<int32>(ComponentY2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
for( int32 SubIndexY=SubIndexY1;SubIndexY<=SubIndexY2;SubIndexY++ )
{
for( int32 SubIndexX=SubIndexX1;SubIndexX<=SubIndexX2;SubIndexX++ )
{
// Find coordinates of box that lies inside subsection
int32 SubX1 = FMath::Clamp<int32>(ComponentX1-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY1 = FMath::Clamp<int32>(ComponentY1-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
int32 SubX2 = FMath::Clamp<int32>(ComponentX2-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY2 = FMath::Clamp<int32>(ComponentY2-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
// Update texture data for the box that lies inside subsection
for( int32 SubY=SubY1;SubY<=SubY2;SubY++ )
{
for( int32 SubX=SubX1;SubX<=SubX2;SubX++ )
{
int32 LandscapeX = SubIndexX*SubsectionSizeQuads + ComponentIndexX*ComponentSizeQuads + SubX;
int32 LandscapeY = SubIndexY*SubsectionSizeQuads + ComponentIndexY*ComponentSizeQuads + SubY;
// Find the texture data corresponding to this vertex
TData Value[4];
FMemory::Memzero(Value, sizeof(TData)* 4);
int32 Dist[4] = {INT_MAX, INT_MAX, INT_MAX, INT_MAX};
FType ValueX, ValueY;
FMemory::Memzero(&ValueX, sizeof(FType));
FMemory::Memzero(&ValueY, sizeof(FType));
bool Exist[4] = {false, false, false, false};
if (ExistLeft)
{
Value[0] = TData(StoreData.Load(ComponentIndexX*ComponentSizeQuads, LandscapeY));
Dist[0] = LandscapeX - (ComponentIndexX*ComponentSizeQuads);
Exist[0] = true;
}
else if ( BorderX1 != INT_MAX )
{
int32 BorderIdxX = (BorderX1+1)*ComponentSizeQuads;
Value[0] = TData(StoreData.Load(BorderIdxX, LandscapeY));
Dist[0] = LandscapeX - (BorderIdxX-1);
Exist[0] = true;
}
else
{
if ((CornerSet & 1) && (CornerSet & 1 << 2))
{
int32 Dist1 = LandscapeY - (ComponentIndexY*ComponentSizeQuads);
int32 Dist2 = ((ComponentIndexY+1)*ComponentSizeQuads) - LandscapeY;
Value[0] = (FType)(Dist2 * CornerValues[0] + Dist1 * CornerValues[2]) / (Dist1 + Dist2);
Dist[0] = LandscapeX - (ComponentIndexX*ComponentSizeQuads);
Exist[0] = true;
}
}
if ( BorderX2 != INT_MIN )
{
int32 BorderIdxX = BorderX2*ComponentSizeQuads;
Value[1] = TData(StoreData.Load(BorderIdxX, LandscapeY));
Dist[1] = (BorderIdxX+1) - LandscapeX;
Exist[1] = true;
}
else
{
if ((CornerSet & 1 << 1) && (CornerSet & 1 << 3))
{
int32 Dist1 = LandscapeY - (ComponentIndexY*ComponentSizeQuads);
int32 Dist2 = ((ComponentIndexY+1)*ComponentSizeQuads) - LandscapeY;
Value[1] = (FType)(Dist2 * CornerValues[1] + Dist1 * CornerValues[3]) / (Dist1 + Dist2);
Dist[1] = (ComponentIndexX+1)*ComponentSizeQuads - LandscapeX;
Exist[1] = true;
}
}
if (ExistUp)
{
Value[2] = TData(StoreData.Load(LandscapeX, ComponentIndexY*ComponentSizeQuads));
Dist[2] = LandscapeY - (ComponentIndexY*ComponentSizeQuads);
Exist[2] = true;
}
else if ( BorderY1[ComponentIndexXX] != INT_MAX )
{
int32 BorderIdxY = (BorderY1[ComponentIndexXX]+1)*ComponentSizeQuads;
Value[2] = TData(StoreData.Load(LandscapeX, BorderIdxY));
Dist[2] = LandscapeY - BorderIdxY;
Exist[2] = true;
}
else
{
if ((CornerSet & 1) && (CornerSet & 1 << 1))
{
int32 Dist1 = LandscapeX - (ComponentIndexX*ComponentSizeQuads);
int32 Dist2 = (ComponentIndexX+1)*ComponentSizeQuads - LandscapeX;
Value[2] = (FType)(Dist2 * CornerValues[0] + Dist1 * CornerValues[1]) / (Dist1 + Dist2);
Dist[2] = LandscapeY - (ComponentIndexY*ComponentSizeQuads);
Exist[2] = true;
}
}
if ( BorderY2[ComponentIndexXX] != INT_MIN )
{
int32 BorderIdxY = BorderY2[ComponentIndexXX]*ComponentSizeQuads;
Value[3] = TData(StoreData.Load(LandscapeX, BorderIdxY));
Dist[3] = BorderIdxY - LandscapeY;
Exist[3] = true;
}
else
{
if ((CornerSet & 1 << 2) && (CornerSet & 1 << 3))
{
int32 Dist1 = LandscapeX - (ComponentIndexX*ComponentSizeQuads);
int32 Dist2 = (ComponentIndexX+1)*ComponentSizeQuads - LandscapeX;
Value[3] = (FType)(Dist2 * CornerValues[2] + Dist1 * CornerValues[3]) / (Dist1 + Dist2);
Dist[3] = (ComponentIndexY+1)*ComponentSizeQuads - LandscapeY;
Exist[3] = true;
}
}
CalcInterpValue<TData, FType>(Dist, Exist, Value, ValueX, ValueY);
TData FinalValue; // Default Value
FMemory::Memzero(&FinalValue, sizeof(TData));
if ( (Exist[0] || Exist[1]) && (Exist[2] || Exist[3]) )
{
FinalValue = CalcValueFromValueXY<TData>(Dist, ValueX, ValueY, CornerSet, CornerValues);
}
else if ( (Exist[0] || Exist[1]) )
{
FinalValue = ValueX;
}
else if ( (Exist[2] || Exist[3]) )
{
FinalValue = ValueY;
}
StoreData.Store(LandscapeX, LandscapeY, FinalValue);
}
}
}
}
}
}
}
}
uint16 FLandscapeEditDataInterface::GetHeightMapData(const ULandscapeComponent* Component, int32 TexU, int32 TexV, FColor* TextureData /*= NULL*/)
{
check(Component);
if (!TextureData)
{
FLandscapeTextureDataInfo* TexDataInfo = GetTextureDataInfo(Component->HeightmapTexture);
TextureData = (FColor*)TexDataInfo->GetMipData(0);
}
int32 SizeU = Component->HeightmapTexture->Source.GetSizeX();
int32 SizeV = Component->HeightmapTexture->Source.GetSizeY();
int32 HeightmapOffsetX = Component->HeightmapScaleBias.Z * (float)SizeU;
int32 HeightmapOffsetY = Component->HeightmapScaleBias.W * (float)SizeV;
int32 TexX = HeightmapOffsetX + TexU;
int32 TexY = HeightmapOffsetY + TexV;
FColor& TexData = TextureData[ TexX + TexY * SizeU ];
return ((((uint16)TexData.R) << 8) | TexData.G);
}
template<typename TStoreData>
void FLandscapeEditDataInterface::GetHeightDataTempl(int32& ValidX1, int32& ValidY1, int32& ValidX2, int32& ValidY2, TStoreData& StoreData)
{
// Copy variables
int32 X1 = ValidX1, X2 = ValidX2, Y1 = ValidY1, Y2 = ValidY2;
ValidX1 = INT_MAX; ValidX2 = INT_MIN; ValidY1 = INT_MAX; ValidY2 = INT_MIN;
int32 ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2;
ALandscape::CalcComponentIndicesOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2);
int32 ComponentSizeX = ComponentIndexX2-ComponentIndexX1+1;
int32 ComponentSizeY = ComponentIndexY2-ComponentIndexY1+1;
// Neighbor Components
ULandscapeComponent* BorderComponent[4] = {0, 0, 0, 0};
ULandscapeComponent* CornerComponent[4] = {0, 0, 0, 0};
bool NoBorderX1 = false, NoBorderX2 = false;
TArray<bool> NoBorderY1, NoBorderY2, ComponentDataExist;
TArray<ULandscapeComponent*> BorderComponentY1, BorderComponentY2;
ComponentDataExist.Empty(ComponentSizeX*ComponentSizeY);
ComponentDataExist.AddZeroed(ComponentSizeX*ComponentSizeY);
bool bHasMissingValue = false;
FLandscapeTextureDataInfo* NeighborTexDataInfo[4] = {0, 0, 0, 0};
FColor* NeighborHeightmapTextureData[4] = {0, 0, 0, 0};
uint16 CornerValues[4] = {0, 0, 0, 0};
int32 EdgeCoord = (SubsectionSizeQuads+1) * ComponentNumSubsections - 1; //ComponentSizeQuads;
// initial loop....
for( int32 ComponentIndexY=ComponentIndexY1;ComponentIndexY<=ComponentIndexY2;ComponentIndexY++ )
{
NoBorderX1 = false;
NoBorderX2 = false;
BorderComponent[0] = BorderComponent[1] = NULL;
for( int32 ComponentIndexX=ComponentIndexX1;ComponentIndexX<=ComponentIndexX2;ComponentIndexX++ )
{
BorderComponent[2] = BorderComponent[3] = NULL;
int32 ComponentIndexXY = ComponentSizeX*(ComponentIndexY-ComponentIndexY1) + ComponentIndexX-ComponentIndexX1;
int32 ComponentIndexXX = ComponentIndexX - ComponentIndexX1;
int32 ComponentIndexYY = ComponentIndexY - ComponentIndexY1;
ComponentDataExist[ComponentIndexXY] = false;
ULandscapeComponent* Component = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX,ComponentIndexY));
FLandscapeTextureDataInfo* TexDataInfo = NULL;
FColor* HeightmapTextureData = NULL;
uint8 CornerSet = 0;
bool ExistLeft = ComponentIndexXX > 0 && ComponentDataExist[ ComponentIndexXX-1 + ComponentIndexYY * ComponentSizeX ];
bool ExistUp = ComponentIndexYY > 0 && ComponentDataExist[ ComponentIndexXX + (ComponentIndexYY-1) * ComponentSizeX ];
if( Component )
{
TexDataInfo = GetTextureDataInfo(Component->HeightmapTexture);
HeightmapTextureData = (FColor*)TexDataInfo->GetMipData(0);
ComponentDataExist[ComponentIndexXY] = true;
// Update valid region
ValidX1 = FMath::Min<int32>(Component->GetSectionBase().X, ValidX1);
ValidX2 = FMath::Max<int32>(Component->GetSectionBase().X+ComponentSizeQuads, ValidX2);
ValidY1 = FMath::Min<int32>(Component->GetSectionBase().Y, ValidY1);
ValidY2 = FMath::Max<int32>(Component->GetSectionBase().Y+ComponentSizeQuads, ValidY2);
}
else
{
if (!bHasMissingValue)
{
NoBorderY1.Empty(ComponentSizeX);
NoBorderY2.Empty(ComponentSizeX);
NoBorderY1.AddZeroed(ComponentSizeX);
NoBorderY2.AddZeroed(ComponentSizeX);
BorderComponentY1.Empty(ComponentSizeX);
BorderComponentY2.Empty(ComponentSizeX);
BorderComponentY1.AddZeroed(ComponentSizeX);
BorderComponentY2.AddZeroed(ComponentSizeX);
bHasMissingValue = true;
}
// Search for neighbor component for interpolation
bool bShouldSearchX = (BorderComponent[1] && BorderComponent[1]->GetSectionBase().X / ComponentSizeQuads <= ComponentIndexX);
bool bShouldSearchY = (BorderComponentY2[ComponentIndexXX] && BorderComponentY2[ComponentIndexXX]->GetSectionBase().Y / ComponentSizeQuads <= ComponentIndexY);
// Search for left-closest component
if ( bShouldSearchX || (!NoBorderX1 && !BorderComponent[0]))
{
NoBorderX1 = true;
for (int32 X = ComponentIndexX-1; X >= ComponentIndexX1; X--)
{
BorderComponent[0] = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(X,ComponentIndexY));
if (BorderComponent[0])
{
NoBorderX1 = false;
NeighborTexDataInfo[0] = GetTextureDataInfo(BorderComponent[0]->HeightmapTexture);
NeighborHeightmapTextureData[0] = (FColor*)NeighborTexDataInfo[0]->GetMipData(0);
break;
}
}
}
// Search for right-closest component
if ( bShouldSearchX || (!NoBorderX2 && !BorderComponent[1]))
{
NoBorderX2 = true;
for (int32 X = ComponentIndexX+1; X <= ComponentIndexX2; X++)
{
BorderComponent[1] = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(X,ComponentIndexY));
if (BorderComponent[1])
{
NoBorderX2 = false;
NeighborTexDataInfo[1] = GetTextureDataInfo(BorderComponent[1]->HeightmapTexture);
NeighborHeightmapTextureData[1] = (FColor*)NeighborTexDataInfo[1]->GetMipData(0);
break;
}
}
}
// Search for up-closest component
if ( bShouldSearchY || (!NoBorderY1[ComponentIndexXX] && !BorderComponentY1[ComponentIndexXX]))
{
NoBorderY1[ComponentIndexXX] = true;
for (int32 Y = ComponentIndexY-1; Y >= ComponentIndexY1; Y--)
{
BorderComponentY1[ComponentIndexXX] = BorderComponent[2] = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX,Y));
if (BorderComponent[2])
{
NoBorderY1[ComponentIndexXX] = false;
NeighborTexDataInfo[2] = GetTextureDataInfo(BorderComponent[2]->HeightmapTexture);
NeighborHeightmapTextureData[2] = (FColor*)NeighborTexDataInfo[2]->GetMipData(0);
break;
}
}
}
else
{
BorderComponent[2] = BorderComponentY1[ComponentIndexXX];
if (BorderComponent[2])
{
NeighborTexDataInfo[2] = GetTextureDataInfo(BorderComponent[2]->HeightmapTexture);
NeighborHeightmapTextureData[2] = (FColor*)NeighborTexDataInfo[2]->GetMipData(0);
}
}
// Search for bottom-closest component
if ( bShouldSearchY || (!NoBorderY2[ComponentIndexXX] && !BorderComponentY2[ComponentIndexXX]))
{
NoBorderY2[ComponentIndexXX] = true;
for (int32 Y = ComponentIndexY+1; Y <= ComponentIndexY2; Y++)
{
BorderComponentY2[ComponentIndexXX] = BorderComponent[3] = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX,Y));
if (BorderComponent[3])
{
NoBorderY2[ComponentIndexXX] = false;
NeighborTexDataInfo[3] = GetTextureDataInfo(BorderComponent[3]->HeightmapTexture);
NeighborHeightmapTextureData[3] = (FColor*)NeighborTexDataInfo[3]->GetMipData(0);
break;
}
}
}
else
{
BorderComponent[3] = BorderComponentY2[ComponentIndexXX];
if (BorderComponent[3])
{
NeighborTexDataInfo[3] = GetTextureDataInfo(BorderComponent[3]->HeightmapTexture);
NeighborHeightmapTextureData[3] = (FColor*)NeighborTexDataInfo[3]->GetMipData(0);
}
}
CornerComponent[0] = ComponentIndexX >= ComponentIndexX1 && ComponentIndexY >= ComponentIndexY1 ?
LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint((ComponentIndexX-1),(ComponentIndexY-1))) : NULL;
CornerComponent[1] = ComponentIndexX <= ComponentIndexX2 && ComponentIndexY >= ComponentIndexY1 ?
LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint((ComponentIndexX+1),(ComponentIndexY-1))) : NULL;
CornerComponent[2] = ComponentIndexX >= ComponentIndexX1 && ComponentIndexY <= ComponentIndexY2 ?
LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint((ComponentIndexX-1),(ComponentIndexY+1))) : NULL;
CornerComponent[3] = ComponentIndexX <= ComponentIndexX2 && ComponentIndexY <= ComponentIndexY2 ?
LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint((ComponentIndexX+1),(ComponentIndexY+1))) : NULL;
if (CornerComponent[0])
{
CornerSet |= 1;
CornerValues[0] = GetHeightMapData(CornerComponent[0], EdgeCoord, EdgeCoord);
}
else if ((ExistLeft || ExistUp) && X1 <= ComponentIndexX*ComponentSizeQuads && Y1 <= ComponentIndexY*ComponentSizeQuads )
{
CornerSet |= 1;
CornerValues[0] = StoreData.Load( ComponentIndexX*ComponentSizeQuads, ComponentIndexY*ComponentSizeQuads);
}
else if (BorderComponent[0])
{
CornerSet |= 1;
CornerValues[0] = GetHeightMapData(BorderComponent[0], EdgeCoord, 0, NeighborHeightmapTextureData[0]);
}
else if (BorderComponent[2])
{
CornerSet |= 1;
CornerValues[0] = GetHeightMapData(BorderComponent[2], 0, EdgeCoord, NeighborHeightmapTextureData[2]);
}
if (CornerComponent[1])
{
CornerSet |= 1 << 1;
CornerValues[1] = GetHeightMapData(CornerComponent[1], 0, EdgeCoord);
}
else if (ExistUp && X2 >= (ComponentIndexX+1)*ComponentSizeQuads)
{
CornerSet |= 1 << 1;
CornerValues[1] = StoreData.Load( (ComponentIndexX+1)*ComponentSizeQuads, ComponentIndexY*ComponentSizeQuads);
}
else if (BorderComponent[1])
{
CornerSet |= 1 << 1;
CornerValues[1] = GetHeightMapData(BorderComponent[1], 0, 0, NeighborHeightmapTextureData[1]);
}
else if (BorderComponent[2])
{
CornerSet |= 1 << 1;
CornerValues[1] = GetHeightMapData(BorderComponent[2], EdgeCoord, EdgeCoord, NeighborHeightmapTextureData[2]);
}
if (CornerComponent[2])
{
CornerSet |= 1 << 2;
CornerValues[2] = GetHeightMapData(CornerComponent[2], EdgeCoord, 0);
}
else if (ExistLeft && Y2 >= (ComponentIndexY+1)*ComponentSizeQuads) // Use data already stored for 0, 2
{
CornerSet |= 1 << 2;
CornerValues[2] = StoreData.Load( ComponentIndexX*ComponentSizeQuads, (ComponentIndexY+1)*ComponentSizeQuads);
}
else if (BorderComponent[0])
{
CornerSet |= 1 << 2;
CornerValues[2] = GetHeightMapData(BorderComponent[0], EdgeCoord, EdgeCoord, NeighborHeightmapTextureData[0]);
}
else if (BorderComponent[3])
{
CornerSet |= 1 << 2;
CornerValues[2] = GetHeightMapData(BorderComponent[3], 0, 0, NeighborHeightmapTextureData[3]);
}
if (CornerComponent[3])
{
CornerSet |= 1 << 3;
CornerValues[3] = GetHeightMapData(CornerComponent[3], 0, 0);
}
else if (BorderComponent[1])
{
CornerSet |= 1 << 3;
CornerValues[3] = GetHeightMapData(BorderComponent[1], 0, EdgeCoord, NeighborHeightmapTextureData[1]);
}
else if (BorderComponent[3])
{
CornerSet |= 1 << 3;
CornerValues[3] = GetHeightMapData(BorderComponent[3], EdgeCoord, 0, NeighborHeightmapTextureData[3]);
}
FillCornerValues(CornerSet, CornerValues);
ComponentDataExist[ComponentIndexXY] = ExistLeft || ExistUp || (BorderComponent[0] || BorderComponent[1] || BorderComponent[2] || BorderComponent[3]) || CornerSet;
}
if (!ComponentDataExist[ComponentIndexXY])
{
continue;
}
// Find coordinates of box that lies inside component
int32 ComponentX1 = FMath::Clamp<int32>(X1-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY1 = FMath::Clamp<int32>(Y1-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentX2 = FMath::Clamp<int32>(X2-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY2 = FMath::Clamp<int32>(Y2-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
// Find subsection range for this box
int32 SubIndexX1 = FMath::Clamp<int32>((ComponentX1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1); // -1 because we need to pick up vertices shared between subsections
int32 SubIndexY1 = FMath::Clamp<int32>((ComponentY1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexX2 = FMath::Clamp<int32>(ComponentX2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexY2 = FMath::Clamp<int32>(ComponentY2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
for( int32 SubIndexY=SubIndexY1;SubIndexY<=SubIndexY2;SubIndexY++ )
{
for( int32 SubIndexX=SubIndexX1;SubIndexX<=SubIndexX2;SubIndexX++ )
{
// Find coordinates of box that lies inside subsection
int32 SubX1 = FMath::Clamp<int32>(ComponentX1-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY1 = FMath::Clamp<int32>(ComponentY1-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
int32 SubX2 = FMath::Clamp<int32>(ComponentX2-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY2 = FMath::Clamp<int32>(ComponentY2-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
// Update texture data for the box that lies inside subsection
for( int32 SubY=SubY1;SubY<=SubY2;SubY++ )
{
for( int32 SubX=SubX1;SubX<=SubX2;SubX++ )
{
int32 LandscapeX = SubIndexX*SubsectionSizeQuads + ComponentIndexX*ComponentSizeQuads + SubX;
int32 LandscapeY = SubIndexY*SubsectionSizeQuads + ComponentIndexY*ComponentSizeQuads + SubY;
// Find the input data corresponding to this vertex
if( Component )
{
// Find the texture data corresponding to this vertex
uint16 Height = GetHeightMapData(Component, (SubsectionSizeQuads+1) * SubIndexX + SubX, (SubsectionSizeQuads+1) * SubIndexY + SubY, HeightmapTextureData);
StoreData.Store(LandscapeX, LandscapeY, Height);
}
else
{
// Find the texture data corresponding to this vertex
uint16 Value[4] = {0, 0, 0, 0};
int32 Dist[4] = {INT_MAX, INT_MAX, INT_MAX, INT_MAX};
float ValueX = 0.0f, ValueY = 0.0f;
bool Exist[4] = {false, false, false, false};
// Use data already stored for 0, 2
if (ExistLeft)
{
Value[0] = StoreData.Load( ComponentIndexX*ComponentSizeQuads, LandscapeY);
Dist[0] = LandscapeX - (ComponentIndexX*ComponentSizeQuads);
Exist[0] = true;
}
else if (BorderComponent[0])
{
Value[0] = GetHeightMapData(BorderComponent[0], EdgeCoord, (SubsectionSizeQuads+1) * SubIndexY + SubY, NeighborHeightmapTextureData[0]);
Dist[0] = LandscapeX - (BorderComponent[0]->GetSectionBase().X + ComponentSizeQuads);
Exist[0] = true;
}
else
{
if ((CornerSet & 1) && (CornerSet & 1 << 2))
{
int32 Dist1 = LandscapeY - (ComponentIndexY*ComponentSizeQuads);
int32 Dist2 = ((ComponentIndexY+1)*ComponentSizeQuads) - LandscapeY;
Value[0] = (float)(Dist2 * CornerValues[0] + Dist1 * CornerValues[2]) / (Dist1 + Dist2);
Dist[0] = LandscapeX - (ComponentIndexX*ComponentSizeQuads);
Exist[0] = true;
}
}
if (BorderComponent[1])
{
Value[1] = GetHeightMapData(BorderComponent[1], 0, (SubsectionSizeQuads+1) * SubIndexY + SubY, NeighborHeightmapTextureData[1]);
Dist[1] = (BorderComponent[1]->GetSectionBase().X) - LandscapeX;
Exist[1] = true;
}
else
{
if ((CornerSet & 1 << 1) && (CornerSet & 1 << 3))
{
int32 Dist1 = LandscapeY - (ComponentIndexY*ComponentSizeQuads);
int32 Dist2 = ((ComponentIndexY+1)*ComponentSizeQuads) - LandscapeY;
Value[1] = (float)(Dist2 * CornerValues[1] + Dist1 * CornerValues[3]) / (Dist1 + Dist2);
Dist[1] = (ComponentIndexX+1)*ComponentSizeQuads - LandscapeX;
Exist[1] = true;
}
}
if (ExistUp)
{
Value[2] = StoreData.Load( LandscapeX, ComponentIndexY*ComponentSizeQuads);
Dist[2] = LandscapeY - (ComponentIndexY*ComponentSizeQuads);
Exist[2] = true;
}
else if (BorderComponent[2])
{
Value[2] = GetHeightMapData(BorderComponent[2], (SubsectionSizeQuads+1) * SubIndexX + SubX, EdgeCoord, NeighborHeightmapTextureData[2]);
Dist[2] = LandscapeY - (BorderComponent[2]->GetSectionBase().Y + ComponentSizeQuads);
Exist[2] = true;
}
else
{
if ((CornerSet & 1) && (CornerSet & 1 << 1))
{
int32 Dist1 = LandscapeX - (ComponentIndexX*ComponentSizeQuads);
int32 Dist2 = (ComponentIndexX+1)*ComponentSizeQuads - LandscapeX;
Value[2] = (float)(Dist2 * CornerValues[0] + Dist1 * CornerValues[1]) / (Dist1 + Dist2);
Dist[2] = LandscapeY - (ComponentIndexY*ComponentSizeQuads);
Exist[2] = true;
}
}
if (BorderComponent[3])
{
Value[3] = GetHeightMapData(BorderComponent[3], (SubsectionSizeQuads+1) * SubIndexX + SubX, 0, NeighborHeightmapTextureData[3]);
Dist[3] = (BorderComponent[3]->GetSectionBase().Y) - LandscapeY;
Exist[3] = true;
}
else
{
if ((CornerSet & 1 << 2) && (CornerSet & 1 << 3))
{
int32 Dist1 = LandscapeX - (ComponentIndexX*ComponentSizeQuads);
int32 Dist2 = (ComponentIndexX+1)*ComponentSizeQuads - LandscapeX;
Value[3] = (float)(Dist2 * CornerValues[2] + Dist1 * CornerValues[3]) / (Dist1 + Dist2);
Dist[3] = (ComponentIndexY+1)*ComponentSizeQuads - LandscapeY;
Exist[3] = true;
}
}
CalcInterpValue<uint16>(Dist, Exist, Value, ValueX, ValueY);
uint16 FinalValue = 0; // Default Value
if ( (Exist[0] || Exist[1]) && (Exist[2] || Exist[3]) )
{
FinalValue = CalcValueFromValueXY<uint16>(Dist, ValueX, ValueY, CornerSet, CornerValues);
}
else if ( (BorderComponent[0] || BorderComponent[1]) )
{
FinalValue = ValueX;
}
else if ( (BorderComponent[2] || BorderComponent[3]) )
{
FinalValue = ValueY;
}
else if ( (Exist[0] || Exist[1]) )
{
FinalValue = ValueX;
}
else if ( (Exist[2] || Exist[3]) )
{
FinalValue = ValueY;
}
StoreData.Store(LandscapeX, LandscapeY, FinalValue);
//StoreData.StoreDefault(LandscapeX, LandscapeY);
}
}
}
}
}
}
}
if (bHasMissingValue)
{
CalcMissingValues<uint16, TStoreData, float>( X1, X2, Y1, Y2,
ComponentIndexX1, ComponentIndexX2, ComponentIndexY1, ComponentIndexY2,
ComponentSizeX, ComponentSizeY, CornerValues,
NoBorderY1, NoBorderY2, ComponentDataExist, StoreData );
// Update valid region
ValidX1 = FMath::Max<int32>(X1, ValidX1);
ValidX2 = FMath::Min<int32>(X2, ValidX2);
ValidY1 = FMath::Max<int32>(Y1, ValidY1);
ValidY2 = FMath::Min<int32>(Y2, ValidY2);
}
else
{
ValidX1 = X1;
ValidX2 = X2;
ValidY1 = Y1;
ValidY2 = Y2;
}
}
namespace
{
struct FArrayStoreData
{
int32 X1;
int32 Y1;
uint16* Data;
int32 Stride;
FArrayStoreData(int32 InX1, int32 InY1, uint16* InData, int32 InStride)
: X1(InX1)
, Y1(InY1)
, Data(InData)
, Stride(InStride)
{}
inline void Store(int32 LandscapeX, int32 LandscapeY, uint16 Height)
{
Data[ (LandscapeY-Y1) * Stride + (LandscapeX-X1) ] = Height;
}
// for interpolation
inline uint16 Load(int32 LandscapeX, int32 LandscapeY)
{
return Data[ (LandscapeY-Y1) * Stride + (LandscapeX-X1) ];
}
inline void StoreDefault(int32 LandscapeX, int32 LandscapeY)
{
Data[ (LandscapeY-Y1) * Stride + (LandscapeX-X1) ] = 0;
}
};
struct FSparseStoreData
{
TMap<FIntPoint, uint16>& SparseData;
FSparseStoreData(TMap<FIntPoint, uint16>& InSparseData)
: SparseData(InSparseData)
{}
inline void Store(int32 LandscapeX, int32 LandscapeY, uint16 Height)
{
SparseData.Add(FIntPoint(LandscapeX,LandscapeY), Height);
}
inline uint16 Load(int32 LandscapeX, int32 LandscapeY)
{
return SparseData.FindRef(FIntPoint(LandscapeX,LandscapeY));
}
inline void StoreDefault(int32 LandscapeX, int32 LandscapeY)
{
}
};
};
void FLandscapeEditDataInterface::GetHeightData(int32& X1, int32& Y1, int32& X2, int32& Y2, uint16* Data, int32 Stride)
{
if( Stride==0 )
{
Stride = (1+X2-X1);
}
FArrayStoreData ArrayStoreData(X1, Y1, Data, Stride);
GetHeightDataTempl(X1, Y1, X2, Y2, ArrayStoreData);
}
void FLandscapeEditDataInterface::GetHeightDataFast(const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, uint16* Data, int32 Stride, uint16* NormalData /*= NULL*/)
{
if( Stride==0 )
{
Stride = (1+X2-X1);
}
FArrayStoreData ArrayStoreData(X1, Y1, Data, Stride);
if (NormalData)
{
FArrayStoreData ArrayNormalData(X1, Y1, NormalData, Stride);
GetHeightDataTemplFast(X1, Y1, X2, Y2, ArrayStoreData, &ArrayNormalData);
}
else
{
GetHeightDataTemplFast(X1, Y1, X2, Y2, ArrayStoreData);
}
}
void FLandscapeEditDataInterface::GetHeightData(int32& X1, int32& Y1, int32& X2, int32& Y2, TMap<FIntPoint, uint16>& SparseData)
{
FSparseStoreData SparseStoreData(SparseData);
GetHeightDataTempl(X1, Y1, X2, Y2, SparseStoreData);
}
void FLandscapeEditDataInterface::GetHeightDataFast(const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, TMap<FIntPoint, uint16>& SparseData, TMap<FIntPoint, uint16>* NormalData /*= NULL*/)
{
FSparseStoreData SparseStoreData(SparseData);
if (NormalData)
{
FSparseStoreData SparseNormalData(*NormalData);
GetHeightDataTemplFast(X1, Y1, X2, Y2, SparseStoreData, &SparseNormalData);
}
else
{
GetHeightDataTemplFast(X1, Y1, X2, Y2, SparseStoreData);
}
}
void ULandscapeComponent::DeleteLayer(ULandscapeLayerInfoObject* LayerInfo, FLandscapeEditDataInterface& LandscapeEdit)
{
ULandscapeComponent* Component = this;
// Find the index for this layer in this component.
const int32 DeleteLayerIdx = Component->WeightmapLayerAllocations.IndexOfByPredicate(
[LayerInfo](const FWeightmapLayerAllocationInfo& Allocation) { return Allocation.LayerInfo == LayerInfo; });
if (DeleteLayerIdx == INDEX_NONE)
{
// Layer not used for this component.
return;
}
FWeightmapLayerAllocationInfo& DeleteLayerAllocation = Component->WeightmapLayerAllocations[DeleteLayerIdx];
int32 DeleteLayerWeightmapTextureIndex = DeleteLayerAllocation.WeightmapTextureIndex;
// See if we'll be able to remove the texture completely.
bool bCanRemoveLayerTexture = true;
for (int32 LayerIdx = 0; LayerIdx < Component->WeightmapLayerAllocations.Num(); LayerIdx++)
{
FWeightmapLayerAllocationInfo& Allocation = Component->WeightmapLayerAllocations[LayerIdx];
// check if we will be able to remove the texture also
if (LayerIdx != DeleteLayerIdx && Allocation.WeightmapTextureIndex == DeleteLayerWeightmapTextureIndex)
{
bCanRemoveLayerTexture = false;
}
}
// See if the deleted layer is a NoWeightBlend layer - if not, we don't have to worry about normalization
bool bDeleteLayerIsNoWeightBlend = (LayerInfo && LayerInfo->bNoWeightBlend);
if (!bDeleteLayerIsNoWeightBlend)
{
// Lock data for all the weightmaps
TArray<FLandscapeTextureDataInfo*> TexDataInfos;
for (int32 WeightmapIdx = 0; WeightmapIdx < Component->WeightmapTextures.Num(); WeightmapIdx++)
{
TexDataInfos.Add(LandscapeEdit.GetTextureDataInfo(Component->WeightmapTextures[WeightmapIdx]));
}
TArray<bool> LayerNoWeightBlends; // Array of NoWeightBlend flags
TArray<uint8*> LayerDataPtrs; // Pointers to all layers' data
// Get the data for each layer
for (int32 LayerIdx = 0; LayerIdx < Component->WeightmapLayerAllocations.Num(); LayerIdx++)
{
FWeightmapLayerAllocationInfo& Allocation = Component->WeightmapLayerAllocations[LayerIdx];
LayerDataPtrs.Add((uint8*)TexDataInfos[Allocation.WeightmapTextureIndex]->GetMipData(0) + ChannelOffsets[Allocation.WeightmapTextureChannel]);
// Find the layer info and record if it is a bNoWeightBlend layer.
LayerNoWeightBlends.Add(Allocation.LayerInfo && Allocation.LayerInfo->bNoWeightBlend);
}
// Find the texture data corresponding to this vertex
const int32 SizeU = (SubsectionSizeQuads + 1) * NumSubsections;
const int32 SizeV = (SubsectionSizeQuads + 1) * NumSubsections;
const int32 WeightmapOffsetX = Component->WeightmapScaleBias.Z * (float)SizeU;
const int32 WeightmapOffsetY = Component->WeightmapScaleBias.W * (float)SizeV;
for (int32 SubIndexY = 0; SubIndexY < NumSubsections; SubIndexY++)
{
for (int32 SubIndexX = 0; SubIndexX < NumSubsections; SubIndexX++)
{
for (int32 SubY = 0; SubY <= SubsectionSizeQuads; SubY++)
{
for (int32 SubX = 0; SubX <= SubsectionSizeQuads; SubX++)
{
const int32 TexX = WeightmapOffsetX + (SubsectionSizeQuads + 1) * SubIndexX + SubX;
const int32 TexY = WeightmapOffsetY + (SubsectionSizeQuads + 1) * SubIndexY + SubY;
const int32 TexDataIndex = 4 * (TexX + TexY * SizeU);
// Calculate the sum of other layer weights
int32 OtherLayerWeightSum = 0;
for (int32 LayerIdx = 0; LayerIdx < Component->WeightmapLayerAllocations.Num(); LayerIdx++)
{
if (LayerIdx != DeleteLayerIdx && LayerNoWeightBlends[LayerIdx] == false)
{
OtherLayerWeightSum += LayerDataPtrs[LayerIdx][TexDataIndex];
}
}
if (OtherLayerWeightSum == 0)
{
// Set the first other weight-blend layer we can find to 255 to avoid a black hole
// This isn't ideal but it's the best option
// There's nothing we can easily do if this was the only weight-blend layer on this component
for (int32 LayerIdx = 0; LayerIdx < Component->WeightmapLayerAllocations.Num(); LayerIdx++)
{
if (LayerIdx != DeleteLayerIdx && LayerNoWeightBlends[LayerIdx] == false)
{
uint8& Weight = LayerDataPtrs[LayerIdx][TexDataIndex];
Weight = 255;
break;
}
}
}
else
{
// Adjust other layer weights
for (int32 LayerIdx = 0; LayerIdx < Component->WeightmapLayerAllocations.Num(); LayerIdx++)
{
if (LayerIdx != DeleteLayerIdx && LayerNoWeightBlends[LayerIdx] == false)
{
uint8& Weight = LayerDataPtrs[LayerIdx][TexDataIndex];
Weight = FMath::Clamp<int32>(FMath::RoundToInt(255.0f * (float)Weight / (float)OtherLayerWeightSum), 0, 255);
}
}
}
}
}
}
}
// Update all the textures and mips
for (int32 Idx = 0; Idx < Component->WeightmapTextures.Num(); Idx++)
{
if (bCanRemoveLayerTexture && Idx == DeleteLayerWeightmapTextureIndex)
{
// We're going to remove this texture anyway, so don't bother updating
continue;
}
UTexture2D* WeightmapTexture = Component->WeightmapTextures[Idx];
FLandscapeTextureDataInfo* WeightmapDataInfo = TexDataInfos[Idx];
const int32 NumMips = WeightmapTexture->Source.GetNumMips();
TArray<FColor*> WeightmapTextureMipData;
WeightmapTextureMipData.AddUninitialized(NumMips);
for (int32 MipIdx = 0; MipIdx < NumMips; MipIdx++)
{
WeightmapTextureMipData[MipIdx] = (FColor*)WeightmapDataInfo->GetMipData(MipIdx);
}
ULandscapeComponent::UpdateWeightmapMips(Component->NumSubsections, Component->SubsectionSizeQuads, WeightmapTexture, WeightmapTextureMipData, 0, 0, MAX_int32, MAX_int32, WeightmapDataInfo);
WeightmapDataInfo->AddMipUpdateRegion(0, 0, 0, WeightmapTexture->Source.GetSizeX() - 1, WeightmapTexture->Source.GetSizeY() - 1);
}
}
// Mark the channel as unallocated, so we can reuse it later
ALandscapeProxy* Proxy = Component->GetLandscapeProxy();
Component->Modify();
Proxy->Modify();
FLandscapeWeightmapUsage* Usage = Proxy->WeightmapUsageMap.Find(Component->WeightmapTextures[DeleteLayerAllocation.WeightmapTextureIndex]);
if (Usage) // can be null if WeightmapUsageMap hasn't been built yet
{
Usage->ChannelUsage[DeleteLayerAllocation.WeightmapTextureChannel] = nullptr;
}
// Remove the layer
Component->WeightmapLayerAllocations.RemoveAt(DeleteLayerIdx);
// If this layer was the last usage for this channel in this layer, we can remove it.
if (bCanRemoveLayerTexture)
{
Component->WeightmapTextures[DeleteLayerWeightmapTextureIndex]->SetFlags(RF_Transactional);
Component->WeightmapTextures[DeleteLayerWeightmapTextureIndex]->Modify();
Component->WeightmapTextures[DeleteLayerWeightmapTextureIndex]->MarkPackageDirty();
Component->WeightmapTextures[DeleteLayerWeightmapTextureIndex]->ClearFlags(RF_Standalone);
Component->WeightmapTextures.RemoveAt(DeleteLayerWeightmapTextureIndex);
// Adjust WeightmapTextureIndex index for other layers
for (int32 LayerIdx = 0; LayerIdx < Component->WeightmapLayerAllocations.Num(); LayerIdx++)
{
FWeightmapLayerAllocationInfo& Allocation = Component->WeightmapLayerAllocations[LayerIdx];
if (Allocation.WeightmapTextureIndex > DeleteLayerWeightmapTextureIndex)
{
Allocation.WeightmapTextureIndex--;
}
check(Allocation.WeightmapTextureIndex < Component->WeightmapTextures.Num());
}
}
// Update the shaders for this component
Component->UpdateMaterialInstances();
// Update dominant layer info stored in collision component
TArray<FColor*> CollisionWeightmapMipData;
for (int32 WeightmapIdx = 0; WeightmapIdx < Component->WeightmapTextures.Num(); WeightmapIdx++)
{
CollisionWeightmapMipData.Add((FColor*)LandscapeEdit.GetTextureDataInfo(Component->WeightmapTextures[WeightmapIdx])->GetMipData(Component->CollisionMipLevel));
}
TArray<FColor*> SimpleCollisionWeightmapMipData;
if (Component->SimpleCollisionMipLevel > Component->CollisionMipLevel)
{
for (int32 WeightmapIdx = 0; WeightmapIdx < Component->WeightmapTextures.Num(); WeightmapIdx++)
{
SimpleCollisionWeightmapMipData.Add((FColor*)LandscapeEdit.GetTextureDataInfo(Component->WeightmapTextures[WeightmapIdx])->GetMipData(Component->SimpleCollisionMipLevel));
}
}
Component->UpdateCollisionLayerData(
CollisionWeightmapMipData.GetData(),
Component->SimpleCollisionMipLevel > Component->CollisionMipLevel ? SimpleCollisionWeightmapMipData.GetData() : nullptr);
}
void FLandscapeEditDataInterface::DeleteLayer(ULandscapeLayerInfoObject* LayerInfo)
{
if (!LandscapeInfo)
{
return;
}
for (auto& XYComponentPair : LandscapeInfo->XYtoComponentMap)
{
ULandscapeComponent* Component = XYComponentPair.Value;
Component->DeleteLayer(LayerInfo, *this);
}
// Flush dynamic data (e.g. grass)
TSet<ULandscapeComponent*> Components;
Algo::Transform(LandscapeInfo->XYtoComponentMap, Components, &TPair<FIntPoint, ULandscapeComponent*>::Value);
ALandscapeProxy::InvalidateGeneratedComponentData(Components);
}
void ULandscapeComponent::FillLayer(ULandscapeLayerInfoObject* LayerInfo, FLandscapeEditDataInterface& LandscapeEdit)
{
check(LayerInfo);
ULandscapeComponent* Component = this;
ALandscapeProxy* Proxy = Component->GetLandscapeProxy();
Component->Modify();
Proxy->Modify();
const bool bFillLayerIsNoWeightBlend = LayerInfo->bNoWeightBlend;
bool bClearOtherWeightBlendLayers = !bFillLayerIsNoWeightBlend;
// Find the index for this layer in this component.
int32 FillLayerIdx = Component->WeightmapLayerAllocations.IndexOfByPredicate(
[LayerInfo](const FWeightmapLayerAllocationInfo& Allocation) { return Allocation.LayerInfo == LayerInfo; });
// if the layer isn't used on this component yet but is a weight-blend layer, then simply steal the allocation of another weight-blend layer!
if (FillLayerIdx == INDEX_NONE && !bFillLayerIsNoWeightBlend)
{
FillLayerIdx = Component->WeightmapLayerAllocations.IndexOfByPredicate(
[](const FWeightmapLayerAllocationInfo& Allocation) { return !Allocation.LayerInfo || !Allocation.LayerInfo->bNoWeightBlend; });
if (FillLayerIdx != INDEX_NONE)
{
Component->WeightmapLayerAllocations[FillLayerIdx].LayerInfo = LayerInfo;
}
else
{
// no other weight-blend layers exist
bClearOtherWeightBlendLayers = false;
}
}
// if the layer is still not found then we are forced to make a new allocation
if (FillLayerIdx == INDEX_NONE)
{
FillLayerIdx = Component->WeightmapLayerAllocations.Num();
Component->WeightmapLayerAllocations.Add(FWeightmapLayerAllocationInfo(LayerInfo));
Component->ReallocateWeightmaps(&LandscapeEdit);
}
check(FillLayerIdx != INDEX_NONE);
// fill the layer
{
// Find the texture data corresponding to this vertex
const int32 SizeU = (SubsectionSizeQuads + 1) * NumSubsections;
const int32 SizeV = (SubsectionSizeQuads + 1) * NumSubsections;
const int32 WeightmapOffsetX = Component->WeightmapScaleBias.Z * (float)SizeU;
const int32 WeightmapOffsetY = Component->WeightmapScaleBias.W * (float)SizeV;
FWeightmapLayerAllocationInfo& FillLayerAllocation = Component->WeightmapLayerAllocations[FillLayerIdx];
uint8* LayerData = (uint8*)LandscapeEdit.GetTextureDataInfo(Component->WeightmapTextures[FillLayerAllocation.WeightmapTextureIndex])->GetMipData(0);
for (int32 Y = 0; Y < SizeV; ++Y)
{
const int32 TexY = WeightmapOffsetY + Y;
uint8* RowData = LayerData + ((WeightmapOffsetY + Y) * SizeU + WeightmapOffsetX) * 4 + ChannelOffsets[FillLayerAllocation.WeightmapTextureChannel];
for (int32 X = 0; X < SizeU; ++X)
{
RowData[X * 4] = 255;
}
}
}
// clear other layers
if (bClearOtherWeightBlendLayers)
{
for (int32 LayerIdx = 0; LayerIdx < Component->WeightmapLayerAllocations.Num(); ++LayerIdx)
{
if (LayerIdx == FillLayerIdx)
{
continue;
}
FWeightmapLayerAllocationInfo& Allocation = Component->WeightmapLayerAllocations[LayerIdx];
if (Allocation.LayerInfo->bNoWeightBlend)
{
continue;
}
FLandscapeWeightmapUsage* Usage = Proxy->WeightmapUsageMap.Find(Component->WeightmapTextures[Allocation.WeightmapTextureIndex]);
if (Usage) // can be null if WeightmapUsageMap hasn't been built yet
{
Usage->ChannelUsage[Allocation.WeightmapTextureChannel] = nullptr;
}
Allocation.WeightmapTextureIndex = 255;
}
Component->WeightmapLayerAllocations.RemoveAll(
[](const FWeightmapLayerAllocationInfo& Allocation) { return Allocation.WeightmapTextureIndex == 255; });
// remove any textures we're no longer using
for (int32 TextureIdx = 0; TextureIdx < Component->WeightmapTextures.Num(); ++TextureIdx)
{
if (!Component->WeightmapLayerAllocations.ContainsByPredicate(
[TextureIdx](const FWeightmapLayerAllocationInfo& Allocation) { return Allocation.WeightmapTextureIndex == TextureIdx; }))
{
Component->WeightmapTextures[TextureIdx]->Modify();
for (int32 LayerIdx = 0; LayerIdx < Component->WeightmapLayerAllocations.Num(); ++LayerIdx)
{
FWeightmapLayerAllocationInfo& Allocation = Component->WeightmapLayerAllocations[LayerIdx];
if (Allocation.WeightmapTextureIndex > TextureIdx)
{
--Allocation.WeightmapTextureIndex;
}
}
Component->WeightmapTextures.RemoveAt(TextureIdx--);
}
}
}
// todo - normalize texture usage: it's possible to end up with two textures each using one channel at this point
// e.g. if you start with 4 blended layers (in one texture) and a non-blended layer (in a 2nd), and fill one weight-blended layer (deleting the other three)
// this can also happen with normal painting I believe
// update mips
for (int32 TextureIdx = 0; TextureIdx < Component->WeightmapTextures.Num(); ++TextureIdx)
{
UTexture2D* WeightmapTexture = WeightmapTextures[TextureIdx];
FLandscapeTextureDataInfo* WeightmapDataInfo = LandscapeEdit.GetTextureDataInfo(WeightmapTexture);
const int32 NumMips = WeightmapTexture->Source.GetNumMips();
TArray<FColor*> WeightmapTextureMipData;
WeightmapTextureMipData.AddUninitialized(NumMips);
for (int32 MipIdx = 0; MipIdx < NumMips; MipIdx++)
{
WeightmapTextureMipData[MipIdx] = (FColor*)WeightmapDataInfo->GetMipData(MipIdx);
}
ULandscapeComponent::UpdateWeightmapMips(NumSubsections, SubsectionSizeQuads, WeightmapTexture, WeightmapTextureMipData, 0, 0, MAX_int32, MAX_int32, WeightmapDataInfo);
WeightmapDataInfo->AddMipUpdateRegion(0, 0, 0, WeightmapTexture->Source.GetSizeX() - 1, WeightmapTexture->Source.GetSizeY() - 1);
}
// Update the shaders for this component
Component->UpdateMaterialInstances();
Component->InvalidateLightingCache();
// Update dominant layer info stored in collision component
TArray<FColor*> CollisionWeightmapMipData;
for (int32 WeightmapIdx = 0; WeightmapIdx < Component->WeightmapTextures.Num(); WeightmapIdx++)
{
CollisionWeightmapMipData.Add((FColor*)LandscapeEdit.GetTextureDataInfo(Component->WeightmapTextures[WeightmapIdx])->GetMipData(Component->CollisionMipLevel));
}
TArray<FColor*> SimpleCollisionWeightmapMipData;
if (Component->SimpleCollisionMipLevel > Component->CollisionMipLevel)
{
for (int32 WeightmapIdx = 0; WeightmapIdx < Component->WeightmapTextures.Num(); WeightmapIdx++)
{
SimpleCollisionWeightmapMipData.Add((FColor*)LandscapeEdit.GetTextureDataInfo(Component->WeightmapTextures[WeightmapIdx])->GetMipData(Component->SimpleCollisionMipLevel));
}
}
Component->UpdateCollisionLayerData(
CollisionWeightmapMipData.GetData(),
Component->SimpleCollisionMipLevel > Component->CollisionMipLevel ? SimpleCollisionWeightmapMipData.GetData() : nullptr);
}
void FLandscapeEditDataInterface::FillLayer(ULandscapeLayerInfoObject* LayerInfo)
{
if (!LandscapeInfo)
{
return;
}
LayerInfo->IsReferencedFromLoadedData = true;
for (auto& XYComponentPair : LandscapeInfo->XYtoComponentMap)
{
ULandscapeComponent* Component = XYComponentPair.Value;
Component->FillLayer(LayerInfo, *this);
}
// Flush dynamic data (e.g. grass)
TSet<ULandscapeComponent*> Components;
for (auto& XYComponentPair : LandscapeInfo->XYtoComponentMap)
{
Components.Add(XYComponentPair.Value);
}
ALandscapeProxy::InvalidateGeneratedComponentData(Components);
}
void FLandscapeEditDataInterface::FillEmptyLayers(ULandscapeLayerInfoObject* LayerInfo)
{
if (!LandscapeInfo)
{
return;
}
LayerInfo->IsReferencedFromLoadedData = true;
for (auto& XYComponentPair : LandscapeInfo->XYtoComponentMap)
{
ULandscapeComponent* Component = XYComponentPair.Value;
if (Component->WeightmapLayerAllocations.Num() == 0)
{
Component->FillLayer(LayerInfo, *this);
}
}
// Flush dynamic data (e.g. grass)
TSet<ULandscapeComponent*> Components;
for (auto& XYComponentPair : LandscapeInfo->XYtoComponentMap)
{
Components.Add(XYComponentPair.Value);
}
ALandscapeProxy::InvalidateGeneratedComponentData(Components);
}
void ULandscapeComponent::ReplaceLayer(ULandscapeLayerInfoObject* FromLayerInfo, ULandscapeLayerInfoObject* ToLayerInfo, FLandscapeEditDataInterface& LandscapeEdit)
{
check(FromLayerInfo && ToLayerInfo);
if (FromLayerInfo == ToLayerInfo)
{
return;
}
// Find the index for this layer in this component.
int32 FromLayerIdx = INDEX_NONE;
for (int32 LayerIdx = 0; LayerIdx < WeightmapLayerAllocations.Num(); LayerIdx++)
{
FWeightmapLayerAllocationInfo& Allocation = WeightmapLayerAllocations[LayerIdx];
if (Allocation.LayerInfo == FromLayerInfo)
{
FromLayerIdx = LayerIdx;
}
}
if (FromLayerIdx == INDEX_NONE)
{
// Layer not used for this component, nothing to do.
return;
}
bool bMerging = true;
// Find the index for this layer in this component.
int32 ToLayerIdx = INDEX_NONE;
for (int32 LayerIdx = 0; LayerIdx < WeightmapLayerAllocations.Num(); LayerIdx++)
{
FWeightmapLayerAllocationInfo& Allocation = WeightmapLayerAllocations[LayerIdx];
if (Allocation.LayerInfo == ToLayerInfo)
{
ToLayerIdx = LayerIdx;
}
}
if (ToLayerIdx == INDEX_NONE)
{
// Layer not used for this component, so do trivial replace.
WeightmapLayerAllocations[FromLayerIdx].LayerInfo = ToLayerInfo;
bMerging = false;
}
FWeightmapLayerAllocationInfo& FromLayerAllocation = WeightmapLayerAllocations[FromLayerIdx];
// See if we'll be able to remove the texture completely.
bool bCanRemoveLayerTexture = false;
if (bMerging)
{
bCanRemoveLayerTexture = true;
for (int32 LayerIdx = 0; LayerIdx < WeightmapLayerAllocations.Num(); LayerIdx++)
{
FWeightmapLayerAllocationInfo& Allocation = WeightmapLayerAllocations[LayerIdx];
// check if we will be able to remove the texture also
if (LayerIdx != FromLayerIdx && Allocation.WeightmapTextureIndex == FromLayerAllocation.WeightmapTextureIndex)
{
bCanRemoveLayerTexture = false;
break;
}
}
}
// See if the deleted layer is a NoWeightBlend layer - if not, we don't have to worry about normalization
const bool bFromLayerIsNoWeightBlend = (FromLayerInfo && FromLayerInfo->bNoWeightBlend);
const bool bToLayerIsNoWeightBlend = (ToLayerInfo && ToLayerInfo->bNoWeightBlend);
const bool bRequireNormalization = (bFromLayerIsNoWeightBlend != bToLayerIsNoWeightBlend);
checkf(!bRequireNormalization, TEXT("It is not yet supported to replace a layer with another whose bNoWeightBlend setting does not match"))
if (bMerging)
{
FWeightmapLayerAllocationInfo& ToLayerAllocation = WeightmapLayerAllocations[ToLayerIdx];
// Lock data for all the weightmaps
FLandscapeTextureDataInfo* FromTexDataInfo = LandscapeEdit.GetTextureDataInfo(WeightmapTextures[FromLayerAllocation.WeightmapTextureIndex]);
FLandscapeTextureDataInfo* ToTexDataInfo = LandscapeEdit.GetTextureDataInfo(WeightmapTextures[ToLayerAllocation.WeightmapTextureIndex]);
check(FromTexDataInfo->GetMipSizeX(0) == FromTexDataInfo->GetMipSizeY(0));
check(ToTexDataInfo->GetMipSizeX(0) == ToTexDataInfo->GetMipSizeY(0));
check(FromTexDataInfo->GetMipSizeX(0) == ToTexDataInfo->GetMipSizeX(0));
const int32 MipSize = FromTexDataInfo->GetMipSizeX(0);
uint8* const SrcTextureData = (uint8*)FromTexDataInfo->GetMipData(0) + ChannelOffsets[FromLayerAllocation.WeightmapTextureChannel];
uint8* const DestTextureData = (uint8*)ToTexDataInfo->GetMipData(0) + ChannelOffsets[ToLayerAllocation.WeightmapTextureChannel];
for (int32 i = 0; i < FMath::Square(MipSize); i++)
{
DestTextureData[i*4] = FMath::Min(255, (uint16)DestTextureData[i*4] + (uint16)SrcTextureData[i*4]);
}
// Update all mips
if (!bCanRemoveLayerTexture)
{
UTexture2D* WeightmapTexture = WeightmapTextures[FromLayerAllocation.WeightmapTextureIndex];
FLandscapeTextureDataInfo* WeightmapDataInfo = FromTexDataInfo;
const int32 NumMips = WeightmapTexture->Source.GetNumMips();
TArray<FColor*> WeightmapTextureMipData;
WeightmapTextureMipData.AddUninitialized(NumMips);
for (int32 MipIdx = 0; MipIdx < NumMips; MipIdx++)
{
WeightmapTextureMipData[MipIdx] = (FColor*)WeightmapDataInfo->GetMipData(MipIdx);
}
ULandscapeComponent::UpdateWeightmapMips(NumSubsections, SubsectionSizeQuads, WeightmapTexture, WeightmapTextureMipData, 0, 0, MAX_int32, MAX_int32, WeightmapDataInfo);
WeightmapDataInfo->AddMipUpdateRegion(0, 0, 0, WeightmapTexture->Source.GetSizeX() - 1, WeightmapTexture->Source.GetSizeY() - 1);
}
if (FromTexDataInfo != ToTexDataInfo)
{
UTexture2D* WeightmapTexture = WeightmapTextures[ToLayerAllocation.WeightmapTextureIndex];
FLandscapeTextureDataInfo* WeightmapDataInfo = ToTexDataInfo;
const int32 NumMips = WeightmapTexture->Source.GetNumMips();
TArray<FColor*> WeightmapTextureMipData;
WeightmapTextureMipData.AddUninitialized(NumMips);
for (int32 MipIdx = 0; MipIdx < NumMips; MipIdx++)
{
WeightmapTextureMipData[MipIdx] = (FColor*)WeightmapDataInfo->GetMipData(MipIdx);
}
ULandscapeComponent::UpdateWeightmapMips(NumSubsections, SubsectionSizeQuads, WeightmapTexture, WeightmapTextureMipData, 0, 0, MAX_int32, MAX_int32, WeightmapDataInfo);
WeightmapDataInfo->AddMipUpdateRegion(0, 0, 0, WeightmapTexture->Source.GetSizeX() - 1, WeightmapTexture->Source.GetSizeY() - 1);
}
}
if (bRequireNormalization)
{
// TODO
}
// if merging into an existing layer, remove the layer and potentially the texture
if (bMerging)
{
ALandscapeProxy* Proxy = GetLandscapeProxy();
// Mark the channel as unallocated, so we can reuse it later
FLandscapeWeightmapUsage* Usage = Proxy->WeightmapUsageMap.Find(WeightmapTextures[FromLayerAllocation.WeightmapTextureIndex]);
//check(Usage);
if (Usage)
{
Usage->ChannelUsage[FromLayerAllocation.WeightmapTextureChannel] = NULL;
}
// If this layer was the last usage for this texture, we can remove it.
if (bCanRemoveLayerTexture)
{
WeightmapTextures[FromLayerAllocation.WeightmapTextureIndex]->SetFlags(RF_Transactional);
WeightmapTextures[FromLayerAllocation.WeightmapTextureIndex]->Modify();
WeightmapTextures[FromLayerAllocation.WeightmapTextureIndex]->MarkPackageDirty();
WeightmapTextures[FromLayerAllocation.WeightmapTextureIndex]->ClearFlags(RF_Standalone);
WeightmapTextures.RemoveAt(FromLayerAllocation.WeightmapTextureIndex);
// Adjust WeightmapTextureIndex index for other layers
for (int32 LayerIdx = 0; LayerIdx < WeightmapLayerAllocations.Num(); LayerIdx++)
{
if (LayerIdx == FromLayerIdx)
{
continue;
}
FWeightmapLayerAllocationInfo& Allocation = WeightmapLayerAllocations[LayerIdx];
if (Allocation.WeightmapTextureIndex > FromLayerAllocation.WeightmapTextureIndex)
{
Allocation.WeightmapTextureIndex--;
}
check(Allocation.WeightmapTextureIndex < WeightmapTextures.Num());
}
}
// Remove the layer
WeightmapLayerAllocations.RemoveAt(FromLayerIdx);
// Update the shaders for this component
UpdateMaterialInstances();
}
}
void FLandscapeEditDataInterface::ReplaceLayer(ULandscapeLayerInfoObject* FromLayerInfo, ULandscapeLayerInfoObject* ToLayerInfo)
{
if (!LandscapeInfo) return;
for( auto It = LandscapeInfo->XYtoComponentMap.CreateIterator(); It; ++It )
{
ULandscapeComponent* Component = It.Value();
Component->ReplaceLayer(FromLayerInfo, ToLayerInfo, *this);
// Update dominant layer info stored in collision component
TArray<FColor*> CollisionWeightmapMipData;
for( int32 WeightmapIdx=0;WeightmapIdx < Component->WeightmapTextures.Num();WeightmapIdx++ )
{
CollisionWeightmapMipData.Add((FColor*)GetTextureDataInfo(Component->WeightmapTextures[WeightmapIdx])->GetMipData(Component->CollisionMipLevel));
}
TArray<FColor*> SimpleCollisionWeightmapMipData;
if (Component->SimpleCollisionMipLevel > Component->CollisionMipLevel)
{
for (int32 WeightmapIdx = 0; WeightmapIdx < Component->WeightmapTextures.Num(); WeightmapIdx++)
{
SimpleCollisionWeightmapMipData.Add((FColor*)GetTextureDataInfo(Component->WeightmapTextures[WeightmapIdx])->GetMipData(Component->SimpleCollisionMipLevel));
}
}
Component->UpdateCollisionLayerData(
CollisionWeightmapMipData.GetData(),
Component->SimpleCollisionMipLevel > Component->CollisionMipLevel ? SimpleCollisionWeightmapMipData.GetData() : nullptr);
}
}
// simple classes for the template....
namespace
{
template<typename TDataType>
struct TArrayStoreData
{
int32 X1;
int32 Y1;
TDataType* Data;
int32 Stride;
int32 ArraySize;
TArrayStoreData(int32 InX1, int32 InY1, TDataType* InData, int32 InStride)
: X1(InX1)
, Y1(InY1)
, Data(InData)
, Stride(InStride)
, ArraySize(1)
{}
inline void Store(int32 LandscapeX, int32 LandscapeY, uint8 Weight) {}
inline void Store(int32 LandscapeX, int32 LandscapeY, uint8 Weight, int32 LayerIdx) {}
inline void Store(int32 LandscapeX, int32 LandscapeY, FVector2D Offset) {}
inline TDataType Load(int32 LandscapeX, int32 LandscapeY) { return 0; }
inline void PreInit(int32 InArraySize) { ArraySize = InArraySize; }
};
template<> void TArrayStoreData<uint8>::Store(int32 LandscapeX, int32 LandscapeY, uint8 Weight)
{
Data[ (LandscapeY-Y1) * Stride + (LandscapeX-X1) ] = Weight;
}
template<> uint8 TArrayStoreData<uint8>::Load(int32 LandscapeX, int32 LandscapeY)
{
return Data[ (LandscapeY-Y1) * Stride + (LandscapeX-X1) ];
}
template<> FVector2D TArrayStoreData<FVector2D>::Load(int32 LandscapeX, int32 LandscapeY)
{
return Data[(LandscapeY - Y1) * Stride + (LandscapeX - X1)];
}
template<> FVector TArrayStoreData<FVector>::Load(int32 LandscapeX, int32 LandscapeY)
{
return Data[(LandscapeY - Y1) * Stride + (LandscapeX - X1)];
}
template<> void TArrayStoreData<FVector2D>::Store(int32 LandscapeX, int32 LandscapeY, FVector2D Offset)
{
Data[ (LandscapeY-Y1) * Stride + (LandscapeX-X1) ] = Offset;
}
template<> void TArrayStoreData<FVector>::Store(int32 LandscapeX, int32 LandscapeY, FVector2D Offset)
{
Data[ (LandscapeY-Y1) * Stride + (LandscapeX-X1) ] = FVector(Offset.X, Offset.Y, 0.0f);
}
// Data items should be initialized with ArraySize
template<> void TArrayStoreData<TArray<uint8>>::Store(int32 LandscapeX, int32 LandscapeY, uint8 Weight, int32 LayerIdx)
{
TArray<uint8>& Value = Data[ ((LandscapeY-Y1) * Stride + (LandscapeX-X1)) ];
if (Value.Num() != ArraySize)
{
Value.Empty(ArraySize);
Value.AddZeroed(ArraySize);
}
Value[LayerIdx] = Weight;
}
template<typename TDataType>
struct TSparseStoreData
{
TMap<FIntPoint, TDataType>& SparseData;
int32 ArraySize;
TSparseStoreData(TMap<FIntPoint, TDataType>& InSparseData)
: SparseData(InSparseData)
, ArraySize(1)
{}
inline void Store(int32 LandscapeX, int32 LandscapeY, uint8 Weight) {}
inline void Store(int32 LandscapeX, int32 LandscapeY, uint8 Weight, int32 LayerIdx) {}
inline void Store(int32 LandscapeX, int32 LandscapeY, FVector2D Offset) {}
inline TDataType Load(int32 LandscapeX, int32 LandscapeY) { return 0; }
inline void PreInit(int32 InArraySize) { ArraySize = InArraySize; }
};
template<> void TSparseStoreData<uint8>::Store(int32 LandscapeX, int32 LandscapeY, uint8 Weight)
{
SparseData.Add(FIntPoint(LandscapeX,LandscapeY), Weight);
}
template<> uint8 TSparseStoreData<uint8>::Load(int32 LandscapeX, int32 LandscapeY)
{
return SparseData.FindRef(FIntPoint(LandscapeX,LandscapeY));
}
template<> FVector2D TSparseStoreData<FVector2D>::Load(int32 LandscapeX, int32 LandscapeY)
{
return SparseData.FindRef(FIntPoint(LandscapeX, LandscapeY));
}
template<> FVector TSparseStoreData<FVector>::Load(int32 LandscapeX, int32 LandscapeY)
{
return SparseData.FindRef(FIntPoint(LandscapeX, LandscapeY));
}
template<> void TSparseStoreData<TArray<uint8>>::Store(int32 LandscapeX, int32 LandscapeY, uint8 Weight, int32 LayerIdx)
{
TArray<uint8>* Value = SparseData.Find(FIntPoint(LandscapeX,LandscapeY));
if (Value)
{
(*Value)[LayerIdx] = Weight;
}
else
{
TArray<uint8> Values;
Values.Empty(ArraySize);
Values.AddZeroed(ArraySize);
Values[LayerIdx] = Weight;
SparseData.Add(FIntPoint(LandscapeX, LandscapeY), Values);
}
}
template<> void TSparseStoreData<FVector2D>::Store(int32 LandscapeX, int32 LandscapeY, FVector2D Offset)
{
SparseData.Add(FIntPoint(LandscapeX,LandscapeY), Offset);
}
template<> void TSparseStoreData<FVector>::Store(int32 LandscapeX, int32 LandscapeY, FVector2D Offset)
{
// Preserve old Z value
FVector* PrevValue = SparseData.Find(FIntPoint(LandscapeX,LandscapeY));
if (PrevValue != NULL)
{
PrevValue->X = Offset.X;
PrevValue->Y = Offset.Y;
}
else
{
SparseData.Add(FIntPoint(LandscapeX,LandscapeY), FVector(Offset.X, Offset.Y, 0.0f));
}
}
};
bool DeleteLayerIfAllZero(ULandscapeComponent* const Component, const uint8* const TexDataPtr, int32 TexSize, int32 LayerIdx)
{
// Check the data for the entire component and to see if it's all zero
for (int32 TexY = 0; TexY < TexSize; TexY++)
{
for (int32 TexX = 0; TexX < TexSize; TexX++)
{
const int32 TexDataIndex = 4 * (TexX + TexY * TexSize);
// Stop the first time we see any non-zero data
uint8 Weight = TexDataPtr[TexDataIndex];
if (Weight != 0)
{
return false;
}
}
}
ALandscapeProxy* Proxy = Component->GetLandscapeProxy();
Component->Modify();
Proxy->Modify();
// Mark the channel as unallocated, so we can reuse it later
const int32 DeleteLayerWeightmapTextureIndex = Component->WeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex;
FLandscapeWeightmapUsage& Usage = Proxy->WeightmapUsageMap.FindChecked(Component->WeightmapTextures[DeleteLayerWeightmapTextureIndex]);
Usage.ChannelUsage[Component->WeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel] = NULL;
// Remove the layer as it's totally painted away.
Component->WeightmapLayerAllocations.RemoveAt(LayerIdx);
// Check if the weightmap texture used by the layer we just removed is used by any other layer, and if so, remove the texture too
bool bCanRemoveLayerTexture = !Component->WeightmapLayerAllocations.ContainsByPredicate([DeleteLayerWeightmapTextureIndex](const FWeightmapLayerAllocationInfo& Allocation){ return Allocation.WeightmapTextureIndex == DeleteLayerWeightmapTextureIndex; });
if (bCanRemoveLayerTexture)
{
Component->WeightmapTextures[DeleteLayerWeightmapTextureIndex]->MarkPackageDirty();
Component->WeightmapTextures[DeleteLayerWeightmapTextureIndex]->ClearFlags(RF_Standalone);
Component->WeightmapTextures.RemoveAt(DeleteLayerWeightmapTextureIndex);
// Adjust WeightmapTextureChannel index for other layers
for (auto It = Component->WeightmapLayerAllocations.CreateIterator(); It; ++It)
{
FWeightmapLayerAllocationInfo& Allocation = *It;
if (Allocation.WeightmapTextureIndex > DeleteLayerWeightmapTextureIndex)
{
Allocation.WeightmapTextureIndex--;
}
}
}
return true;
}
inline bool FLandscapeEditDataInterface::IsWhitelisted(const ULandscapeLayerInfoObject* const LayerInfo, const int32 ComponentIndexX, const int32 SubIndexX, const int32 SubX, const int32 ComponentIndexY, const int32 SubIndexY, const int32 SubY)
{
// left / right
if (SubIndexX == 0 && SubX == 0)
{
ULandscapeComponent* EdgeComponent = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX - 1, ComponentIndexY));
if (EdgeComponent && !EdgeComponent->LayerWhitelist.Contains(LayerInfo))
{
return false;
}
}
else if (SubIndexX == ComponentNumSubsections - 1 && SubX == SubsectionSizeQuads)
{
ULandscapeComponent* EdgeComponent = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX + 1, ComponentIndexY));
if (EdgeComponent && !EdgeComponent->LayerWhitelist.Contains(LayerInfo))
{
return false;
}
}
// up / down
if (SubIndexY == 0 && SubY == 0)
{
ULandscapeComponent* EdgeComponent = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX, ComponentIndexY - 1));
if (EdgeComponent && !EdgeComponent->LayerWhitelist.Contains(LayerInfo))
{
return false;
}
}
else if (SubIndexY == ComponentNumSubsections - 1 && SubY == SubsectionSizeQuads)
{
ULandscapeComponent* EdgeComponent = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX, ComponentIndexY + 1));
if (EdgeComponent && !EdgeComponent->LayerWhitelist.Contains(LayerInfo))
{
return false;
}
}
// diagonals
if (SubIndexY == 0 && SubY == 0 && SubIndexX == 0 && SubX == 0)
{
ULandscapeComponent* CornerComponent = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX - 1, ComponentIndexY - 1));
if (CornerComponent && !CornerComponent->LayerWhitelist.Contains(LayerInfo))
{
return false;
}
}
else if (SubIndexY == 0 && SubY == 0 && SubIndexX == ComponentNumSubsections - 1 && SubX == SubsectionSizeQuads)
{
ULandscapeComponent* CornerComponent = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX + 1, ComponentIndexY - 1));
if (CornerComponent && !CornerComponent->LayerWhitelist.Contains(LayerInfo))
{
return false;
}
}
else if (SubIndexY == ComponentNumSubsections - 1 && SubY == SubsectionSizeQuads && SubIndexX == 0 && SubX == 0)
{
ULandscapeComponent* CornerComponent = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX - 1, ComponentIndexY + 1));
if (CornerComponent && !CornerComponent->LayerWhitelist.Contains(LayerInfo))
{
return false;
}
}
else if (SubIndexY == ComponentNumSubsections - 1 && SubY == SubsectionSizeQuads && SubIndexX == ComponentNumSubsections - 1 && SubX == SubsectionSizeQuads)
{
ULandscapeComponent* CornerComponent = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX + 1, ComponentIndexY + 1));
if (CornerComponent && !CornerComponent->LayerWhitelist.Contains(LayerInfo))
{
return false;
}
}
return true;
}
inline TMap<const ULandscapeLayerInfoObject*, uint32> FLandscapeEditDataInterface::CountWeightBlendedLayerInfluence(const int32 ComponentIndexX, const int32 ComponentIndexY, TOptional<TArrayView<const uint8* const>> InOptionalLayerDataPtrs)
{
// the counts should easily fit in a uint32, a 255x255 x2x2 Component with weights of all 255 only totals 26 bits
checkSlow(FMath::CeilLogTwo(ComponentSizeQuads + 1) * 2 + 8 /*ceillog2(255)*/ <= 32);
TMap<const ULandscapeLayerInfoObject*, uint32> LayerInfluenceMap;
ULandscapeComponent* Component = LandscapeInfo->XYtoComponentMap.FindChecked(FIntPoint(ComponentIndexX,ComponentIndexY));
// used if InOptionalLayerDataPtrs is null
TArray<FLandscapeTextureDataInfo*, TInlineAllocator<2>> InternalTexDataInfos;
TArray<const uint8*, TInlineAllocator<8>> InternalLayerDataPtrs;
TArrayView<const uint8* const> LayerDataPtrs;
if (InOptionalLayerDataPtrs)
{
check(InOptionalLayerDataPtrs->Num() == Component->WeightmapLayerAllocations.Num());
LayerDataPtrs = InOptionalLayerDataPtrs.GetValue();
}
else
{
InternalTexDataInfos.AddUninitialized(Component->WeightmapTextures.Num());
for (int32 WeightmapIdx = 0; WeightmapIdx < Component->WeightmapTextures.Num(); ++WeightmapIdx)
{
InternalTexDataInfos[WeightmapIdx] = GetTextureDataInfo(Component->WeightmapTextures[WeightmapIdx]);
}
InternalLayerDataPtrs.AddUninitialized(Component->WeightmapLayerAllocations.Num());
for (int32 LayerIdx = 0; LayerIdx < Component->WeightmapLayerAllocations.Num(); LayerIdx++)
{
const FWeightmapLayerAllocationInfo& Allocation = Component->WeightmapLayerAllocations[LayerIdx];
InternalLayerDataPtrs[LayerIdx] = (uint8*)InternalTexDataInfos[Allocation.WeightmapTextureIndex]->GetMipData(0) + ChannelOffsets[Allocation.WeightmapTextureChannel];
}
LayerDataPtrs = InternalLayerDataPtrs;
}
const int32 ScanlineSize = (SubsectionSizeQuads + 1) * ComponentNumSubsections * 4;
for (int32 LayerIdx = 0; LayerIdx < Component->WeightmapLayerAllocations.Num(); LayerIdx++)
{
const FWeightmapLayerAllocationInfo& Allocation = Component->WeightmapLayerAllocations[LayerIdx];
if (Allocation.LayerInfo->bNoWeightBlend)
{
continue;
}
auto& Count = LayerInfluenceMap.Add(Allocation.LayerInfo, 0);
for (int32 SubIndexY = 0; SubIndexY < ComponentNumSubsections; ++SubIndexY)
{
const int32 YStart = SubIndexY * (SubsectionSizeQuads + 1);
const int32 YEnd = YStart + (SubsectionSizeQuads + 1);
for (int32 Y = YStart; Y < YEnd; ++Y)
{
for (int32 SubIndexX = 0; SubIndexX < ComponentNumSubsections; ++SubIndexX)
{
const int32 XStart = SubIndexX * (SubsectionSizeQuads + 1);
const int32 XEnd = XStart + (SubsectionSizeQuads + 1);
for (int32 X = XStart; X < XEnd; ++X)
{
const int32 TexDataIndex = Y * ScanlineSize + X * 4;
const uint8 Weight = LayerDataPtrs[LayerIdx][TexDataIndex];
Count += Weight;
}
}
}
}
}
LayerInfluenceMap.ValueSort(TGreater<uint32>());
return LayerInfluenceMap;
}
const ULandscapeLayerInfoObject* FLandscapeEditDataInterface::ChooseReplacementLayer(const ULandscapeLayerInfoObject* const LayerInfo, const int32 ComponentIndexX, const int32 SubIndexX, const int32 SubX, const int32 ComponentIndexY, const int32 SubIndexY, const int32 SubY, TMap<FIntPoint, TMap<const ULandscapeLayerInfoObject*, uint32>>& LayerInfluenceCache, TArrayView<const uint8* const> LayerDataPtrs)
{
const TMap<const ULandscapeLayerInfoObject*, uint32>* LayerInfluenceMapCacheEntry = LayerInfluenceCache.Find(FIntPoint(ComponentIndexX, ComponentIndexY));
if (!LayerInfluenceMapCacheEntry)
{
LayerInfluenceMapCacheEntry = &LayerInfluenceCache.Add(FIntPoint(ComponentIndexX, ComponentIndexY), CountWeightBlendedLayerInfluence(ComponentIndexX, ComponentIndexY, LayerDataPtrs));
}
if (!(SubIndexX == 0 && SubX == 0) &&
!(SubIndexX == ComponentNumSubsections - 1 && SubX == SubsectionSizeQuads) &&
!(SubIndexY == 0 && SubY == 0) &&
!(SubIndexY == ComponentNumSubsections - 1 && SubY == SubsectionSizeQuads))
{
for (const auto& LayerInfluenceMapPair : *LayerInfluenceMapCacheEntry)
{
if (LayerInfluenceMapPair.Key != LayerInfo)
{
return LayerInfluenceMapPair.Key;
}
}
return nullptr;
}
TMap<const ULandscapeLayerInfoObject*, uint32, TInlineSetAllocator<8>> LayerInfluenceMap = *LayerInfluenceMapCacheEntry;
const int32 ComponentXStart = (SubIndexX == 0 && SubX == 0) ? ComponentIndexX - 1 : ComponentIndexX;
const int32 ComponentXEnd = (SubIndexX == ComponentNumSubsections - 1 && SubX == SubsectionSizeQuads) ? ComponentIndexX + 1 : ComponentIndexX;
const int32 ComponentYStart = (SubIndexY == 0 && SubY == 0) ? ComponentIndexY - 1 : ComponentIndexY;
const int32 ComponentYEnd = (SubIndexY == ComponentNumSubsections - 1 && SubY == SubsectionSizeQuads) ? ComponentIndexY + 1 : ComponentIndexY;
for (int32 Y = ComponentYStart; Y <= ComponentYEnd; ++Y)
{
for (int32 X = ComponentXStart; X <= ComponentXEnd; ++X)
{
if (X == ComponentIndexX && Y == ComponentIndexY)
{
// skip the current component, it is already included above
continue;
}
ULandscapeComponent* Component = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(X, Y));
if (!Component)
{
// skip missing components
continue;
}
const TMap<const ULandscapeLayerInfoObject*, uint32>* OtherLayerInfluenceMapCacheEntry = LayerInfluenceCache.Find(FIntPoint(X, Y));
if (!OtherLayerInfluenceMapCacheEntry)
{
OtherLayerInfluenceMapCacheEntry = &LayerInfluenceCache.Add(FIntPoint(X, Y), CountWeightBlendedLayerInfluence(X, Y, {}));
}
for (auto LayerInfluenceMapIt = LayerInfluenceMap.CreateIterator(); LayerInfluenceMapIt; ++LayerInfluenceMapIt)
{
const uint32* Value = OtherLayerInfluenceMapCacheEntry->Find(LayerInfluenceMapIt->Key);
if (Value)
{
LayerInfluenceMapIt->Value += *Value;
}
else
{
// only allow layers that exist in *all* the touched components
LayerInfluenceMapIt.RemoveCurrent();
}
}
}
}
LayerInfluenceMap.ValueSort(TGreater<uint32>());
for (const auto& LayerInfluenceMapPair : LayerInfluenceMap)
{
if (LayerInfluenceMapPair.Key != LayerInfo)
{
return LayerInfluenceMapPair.Key;
}
}
return nullptr;
}
void FLandscapeEditDataInterface::SetAlphaData(ULandscapeLayerInfoObject* const LayerInfo, const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, const uint8* Data, int32 Stride, ELandscapeLayerPaintingRestriction PaintingRestriction /*= None*/, bool bWeightAdjust /*= true*/, bool bTotalWeightAdjust /*= false*/)
{
check(LayerInfo != NULL);
if (LayerInfo->bNoWeightBlend)
{
bWeightAdjust = false;
}
if (Stride == 0)
{
Stride = (1+X2-X1);
}
check(ComponentSizeQuads > 0);
// Find component range for this block of data
int32 ComponentIndexX1 = (X1-1 >= 0) ? (X1-1) / ComponentSizeQuads : (X1) / ComponentSizeQuads - 1; // -1 because we need to pick up vertices shared between components
int32 ComponentIndexY1 = (Y1-1 >= 0) ? (Y1-1) / ComponentSizeQuads : (Y1) / ComponentSizeQuads - 1;
int32 ComponentIndexX2 = (X2 >= 0) ? X2 / ComponentSizeQuads : (X2+1) / ComponentSizeQuads - 1;
int32 ComponentIndexY2 = (Y2 >= 0) ? Y2 / ComponentSizeQuads : (Y2+1) / ComponentSizeQuads - 1;
TArray<FLandscapeTextureDataInfo*, TInlineAllocator<2>> TexDataInfos;
TArray<uint8*, TInlineAllocator<8>> LayerDataPtrs; // Pointers to all layers' data
TArray<bool, TInlineAllocator<8>> LayerNoWeightBlends; // NoWeightBlend flags
TArray<bool, TInlineAllocator<8>> LayerEditDataAllZero; // Whether the data we are editing for this layer is all zero
TArray<FColor*> CollisionWeightmapMipData;
TArray<FColor*> SimpleCollisionWeightmapMipData;
TArray<FColor*> WeightmapTextureMipData;
TMap<FIntPoint, TMap<const ULandscapeLayerInfoObject*, uint32>> LayerInfluenceCache;
for (int32 ComponentIndexY = ComponentIndexY1; ComponentIndexY <= ComponentIndexY2; ComponentIndexY++)
{
for (int32 ComponentIndexX = ComponentIndexX1; ComponentIndexX <= ComponentIndexX2; ComponentIndexX++)
{
FIntPoint ComponentKey(ComponentIndexX,ComponentIndexY);
ULandscapeComponent* Component = LandscapeInfo->XYtoComponentMap.FindRef(ComponentKey);
// if NULL, there is no component at this location
if (Component == NULL)
{
continue;
}
if (PaintingRestriction == ELandscapeLayerPaintingRestriction::UseComponentWhitelist && !Component->LayerWhitelist.Contains(LayerInfo))
{
continue;
}
Component->Modify();
int32 UpdateLayerIdx = Component->WeightmapLayerAllocations.IndexOfByPredicate([LayerInfo](const FWeightmapLayerAllocationInfo& Allocation){ return Allocation.LayerInfo == LayerInfo; });
// Need allocation for weightmap
if (UpdateLayerIdx == INDEX_NONE)
{
const int32 LayerLimit = Component->GetLandscapeProxy()->MaxPaintedLayersPerComponent;
// if we can't allocate a layer, then there is nothing to paint
if (PaintingRestriction == ELandscapeLayerPaintingRestriction::ExistingOnly ||
(PaintingRestriction == ELandscapeLayerPaintingRestriction::UseMaxLayers && LayerLimit > 0 && Component->WeightmapLayerAllocations.Num() >= LayerLimit))
{
continue;
}
UpdateLayerIdx = Component->WeightmapLayerAllocations.Num();
new (Component->WeightmapLayerAllocations) FWeightmapLayerAllocationInfo(LayerInfo);
Component->ReallocateWeightmaps(this);
Component->UpdateMaterialInstances();
Component->EditToolRenderData.UpdateDebugColorMaterial(Component);
Component->UpdateEditToolRenderData();
}
// Lock data for all the weightmaps
TexDataInfos.Reset();
TexDataInfos.AddUninitialized(Component->WeightmapTextures.Num());
for (int32 WeightmapIdx = 0; WeightmapIdx < Component->WeightmapTextures.Num(); ++WeightmapIdx)
{
TexDataInfos[WeightmapIdx] = GetTextureDataInfo(Component->WeightmapTextures[WeightmapIdx]);
}
LayerDataPtrs.Reset(); // Pointers to all layers' data
LayerDataPtrs.AddUninitialized(Component->WeightmapLayerAllocations.Num());
LayerNoWeightBlends.Reset(); // NoWeightBlend flags
LayerNoWeightBlends.AddUninitialized(Component->WeightmapLayerAllocations.Num());
LayerEditDataAllZero.Reset(); // Whether the data we are editing for this layer is all zero
LayerEditDataAllZero.AddUninitialized(Component->WeightmapLayerAllocations.Num());
for (int32 LayerIdx = 0; LayerIdx < Component->WeightmapLayerAllocations.Num(); LayerIdx++)
{
FWeightmapLayerAllocationInfo& Allocation = Component->WeightmapLayerAllocations[LayerIdx];
LayerDataPtrs[LayerIdx] = (uint8*)TexDataInfos[Allocation.WeightmapTextureIndex]->GetMipData(0) + ChannelOffsets[Allocation.WeightmapTextureChannel];
LayerNoWeightBlends[LayerIdx] = Allocation.LayerInfo->bNoWeightBlend;
LayerEditDataAllZero[LayerIdx] = true;
}
// Find the texture data corresponding to this vertex
const int32 TexSize = (SubsectionSizeQuads+1) * ComponentNumSubsections;
// Find coordinates of box that lies inside component
const int32 ComponentX1 = FMath::Clamp<int32>(X1-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
const int32 ComponentY1 = FMath::Clamp<int32>(Y1-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
const int32 ComponentX2 = FMath::Clamp<int32>(X2-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
const int32 ComponentY2 = FMath::Clamp<int32>(Y2-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
// Find subsection range for this box
const int32 SubIndexX1 = FMath::Clamp<int32>((ComponentX1-1) / SubsectionSizeQuads, 0, ComponentNumSubsections-1); // -1 because we need to pick up vertices shared between subsections
const int32 SubIndexY1 = FMath::Clamp<int32>((ComponentY1-1) / SubsectionSizeQuads, 0, ComponentNumSubsections-1);
const int32 SubIndexX2 = FMath::Clamp<int32>(ComponentX2 / SubsectionSizeQuads, 0, ComponentNumSubsections-1);
const int32 SubIndexY2 = FMath::Clamp<int32>(ComponentY2 / SubsectionSizeQuads, 0, ComponentNumSubsections-1);
for (int32 SubIndexY = SubIndexY1; SubIndexY <= SubIndexY2; SubIndexY++)
{
for (int32 SubIndexX = SubIndexX1; SubIndexX <= SubIndexX2; SubIndexX++)
{
// Find coordinates of box that lies inside subsection
const int32 SubX1 = FMath::Clamp<int32>(ComponentX1-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
const int32 SubY1 = FMath::Clamp<int32>(ComponentY1-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
const int32 SubX2 = FMath::Clamp<int32>(ComponentX2-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
const int32 SubY2 = FMath::Clamp<int32>(ComponentY2-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
// Update texture data for the box that lies inside subsection
for (int32 SubY = SubY1; SubY <= SubY2; SubY++)
{
for (int32 SubX = SubX1; SubX <= SubX2; SubX++)
{
const int32 LandscapeX = SubIndexX*SubsectionSizeQuads + ComponentIndexX*ComponentSizeQuads + SubX;
const int32 LandscapeY = SubIndexY*SubsectionSizeQuads + ComponentIndexY*ComponentSizeQuads + SubY;
checkSlow( LandscapeX >= X1 && LandscapeX <= X2 );
checkSlow( LandscapeY >= Y1 && LandscapeY <= Y2 );
// Find the input data corresponding to this vertex
const int32 DataIndex = (LandscapeX-X1) + Stride * (LandscapeY-Y1);
uint8 NewWeight = Data[DataIndex];
const int32 TexX = (SubsectionSizeQuads+1) * SubIndexX + SubX;
const int32 TexY = (SubsectionSizeQuads+1) * SubIndexY + SubY;
const int32 TexDataIndex = 4 * (TexX + TexY * TexSize);
uint8 CurrentWeight = LayerDataPtrs[UpdateLayerIdx][TexDataIndex];
if (NewWeight == CurrentWeight)
{
continue;
}
if (PaintingRestriction == ELandscapeLayerPaintingRestriction::UseComponentWhitelist && NewWeight != 0)
{
bool bWhitelisted = IsWhitelisted(LayerInfo, ComponentIndexX, SubIndexX, SubX, ComponentIndexY, SubIndexY, SubY);
if (!bWhitelisted)
{
NewWeight = 0;
}
}
// Adjust all layer weights
// (bWeightAdjust implies that this is a weight-blended layer)
int32 OtherLayerWeightSum = 0;
if (bWeightAdjust)
{
// Normalize all layers including the painted one
// gmartin: this isn't used. TODO: Remove
if (bTotalWeightAdjust)
{
int32 MaxLayerIdx = -1;
int32 MaxWeight = INT_MIN;
// Adjust other layers' weights accordingly
for (int32 LayerIdx = 0; LayerIdx < Component->WeightmapLayerAllocations.Num(); LayerIdx++)
{
uint8& ExistingWeight = LayerDataPtrs[LayerIdx][TexDataIndex];
if (LayerIdx == UpdateLayerIdx)
{
ExistingWeight = NewWeight;
}
// Exclude bNoWeightBlend layers
if (LayerNoWeightBlends[LayerIdx] == false)
{
OtherLayerWeightSum += ExistingWeight;
if (MaxWeight < ExistingWeight)
{
MaxWeight = ExistingWeight;
MaxLayerIdx = LayerIdx;
}
}
}
if (OtherLayerWeightSum != 255)
{
const float Factor = 255.0f / OtherLayerWeightSum;
OtherLayerWeightSum = 0;
// Normalize
for (int32 LayerIdx = 0; LayerIdx<Component->WeightmapLayerAllocations.Num(); LayerIdx++)
{
uint8& ExistingWeight = LayerDataPtrs[LayerIdx][TexDataIndex];
if (LayerNoWeightBlends[LayerIdx] == false)
{
// normalization...
ExistingWeight = (uint8)(Factor * ExistingWeight);
OtherLayerWeightSum += ExistingWeight;
if (ExistingWeight != 0)
{
LayerEditDataAllZero[LayerIdx] = false;
}
}
}
if ((255 - OtherLayerWeightSum) && MaxLayerIdx >= 0)
{
LayerDataPtrs[MaxLayerIdx][TexDataIndex] += 255 - OtherLayerWeightSum;
}
}
}
else
{
// Adjust other layers' weights accordingly
for (int32 LayerIdx = 0; LayerIdx < Component->WeightmapLayerAllocations.Num(); LayerIdx++)
{
const uint8 ExistingWeight = LayerDataPtrs[LayerIdx][TexDataIndex];
// Exclude bNoWeightBlend layers
if (LayerIdx != UpdateLayerIdx && LayerNoWeightBlends[LayerIdx] == false)
{
OtherLayerWeightSum += ExistingWeight;
}
}
if (OtherLayerWeightSum == 0 && NewWeight < 255)
{
if (NewWeight < CurrentWeight)
{
// When reducing the layer weight from 255, we need to choose another layer to fill to avoid a black hole
const ULandscapeLayerInfoObject* ReplacementLayer = ChooseReplacementLayer(LayerInfo, ComponentIndexX, SubIndexX, SubX, ComponentIndexY, SubIndexY, SubY, LayerInfluenceCache, LayerDataPtrs);
if (ReplacementLayer)
{
const int32 ReplacementLayerIndex = Component->WeightmapLayerAllocations.IndexOfByPredicate([&](const FWeightmapLayerAllocationInfo& AllocationInfo) { return AllocationInfo.LayerInfo == ReplacementLayer; });
LayerDataPtrs[ReplacementLayerIndex][TexDataIndex] = 255 - NewWeight;
LayerEditDataAllZero[ReplacementLayerIndex] = false;
}
else
{
// if we didn't find a suitable replacement we just have to leave it at 255, unfortunately
NewWeight = 255;
}
}
else if (NewWeight > CurrentWeight)
{
// if weight is increasing on a black spot then go straight to 255
NewWeight = 255;
}
LayerDataPtrs[UpdateLayerIdx][TexDataIndex] = NewWeight;
}
else
{
for (int32 LayerIdx = 0; LayerIdx < Component->WeightmapLayerAllocations.Num(); LayerIdx++)
{
uint8& Weight = LayerDataPtrs[LayerIdx][TexDataIndex];
if (LayerIdx == UpdateLayerIdx)
{
Weight = NewWeight;
}
else
{
// Exclude bNoWeightBlend layers
if (LayerNoWeightBlends[LayerIdx] == false)
{
Weight = FMath::Clamp<uint8>(FMath::RoundToInt((float)(255 - NewWeight) * (float)Weight / (float)OtherLayerWeightSum), 0, 255);
}
}
if (Weight != 0)
{
LayerEditDataAllZero[LayerIdx] = false;
}
}
}
}
}
else
{
// Weight value set without adjusting other layers' weights
uint8& Weight = LayerDataPtrs[UpdateLayerIdx][TexDataIndex];
Weight = NewWeight;
if (Weight != 0)
{
LayerEditDataAllZero[UpdateLayerIdx] = false;
}
}
}
}
// Record the areas of the texture we need to re-upload
const int32 TexX1 = (SubsectionSizeQuads+1) * SubIndexX + SubX1;
const int32 TexY1 = (SubsectionSizeQuads+1) * SubIndexY + SubY1;
const int32 TexX2 = (SubsectionSizeQuads+1) * SubIndexX + SubX2;
const int32 TexY2 = (SubsectionSizeQuads+1) * SubIndexY + SubY2;
for (int32 WeightmapIdx = 0; WeightmapIdx < Component->WeightmapTextures.Num(); WeightmapIdx++)
{
if (TexDataInfos[WeightmapIdx] != NULL)
{
TexDataInfos[WeightmapIdx]->AddMipUpdateRegion(0,TexX1,TexY1,TexX2,TexY2);
}
}
}
}
// Update mipmaps
CollisionWeightmapMipData.Reset();
CollisionWeightmapMipData.AddUninitialized(Component->WeightmapTextures.Num());
for (int32 WeightmapIdx = 0; WeightmapIdx < Component->WeightmapTextures.Num(); WeightmapIdx++)
{
UTexture2D* const WeightmapTexture = Component->WeightmapTextures[WeightmapIdx];
const int32 NumMips = WeightmapTexture->Source.GetNumMips();
WeightmapTextureMipData.Reset();
WeightmapTextureMipData.AddUninitialized(NumMips);
for (int32 MipIdx = 0; MipIdx < NumMips; MipIdx++)
{
FColor* const MipData = (FColor*)TexDataInfos[WeightmapIdx]->GetMipData(MipIdx);
WeightmapTextureMipData[MipIdx] = MipData;
}
CollisionWeightmapMipData[WeightmapIdx] = WeightmapTextureMipData[Component->CollisionMipLevel];
ULandscapeComponent::UpdateWeightmapMips(ComponentNumSubsections, SubsectionSizeQuads, WeightmapTexture, WeightmapTextureMipData, ComponentX1, ComponentY1, ComponentX2, ComponentY2, TexDataInfos[WeightmapIdx]);
WeightmapTextureMipData.Reset();
}
if (Component->SimpleCollisionMipLevel > Component->CollisionMipLevel)
{
for (int32 WeightmapIdx = 0; WeightmapIdx < Component->WeightmapTextures.Num(); WeightmapIdx++)
{
SimpleCollisionWeightmapMipData.Add((FColor*)TexDataInfos[WeightmapIdx]->GetMipData(Component->SimpleCollisionMipLevel));
}
}
// Update dominant layer info stored in collision component
Component->UpdateCollisionLayerData(
CollisionWeightmapMipData.GetData(),
Component->SimpleCollisionMipLevel > Component->CollisionMipLevel ? SimpleCollisionWeightmapMipData.GetData() : nullptr,
ComponentX1, ComponentY1, ComponentX2, ComponentY2);
CollisionWeightmapMipData.Reset();
SimpleCollisionWeightmapMipData.Reset();
// Check if we need to remove weightmap allocations for layers that were completely painted away
bool bRemovedLayer = false;
for (int32 LayerIdx = 0; LayerIdx < Component->WeightmapLayerAllocations.Num(); LayerIdx++)
{
if (LayerEditDataAllZero[LayerIdx])
{
bool bLayerDeleted = DeleteLayerIfAllZero(Component, LayerDataPtrs[LayerIdx], TexSize, LayerIdx);
if (bLayerDeleted)
{
LayerEditDataAllZero.RemoveAt(LayerIdx);
LayerDataPtrs.RemoveAt(LayerIdx);
LayerIdx--;
bRemovedLayer = true;
}
}
}
if (bRemovedLayer)
{
Component->UpdateMaterialInstances();
Component->EditToolRenderData.UpdateDebugColorMaterial(Component);
Component->UpdateEditToolRenderData();
}
}
}
}
void FLandscapeEditDataInterface::SetAlphaData(const TSet<ULandscapeLayerInfoObject*>& DirtyLayerInfos, const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, const uint8* Data, int32 Stride, ELandscapeLayerPaintingRestriction PaintingRestriction /*= None*/)
{
if (DirtyLayerInfos.Num() == 0)
{
return;
}
for (ULandscapeLayerInfoObject* LayerInfo : DirtyLayerInfos)
{
// The Data[] array passed in is indexed by LandscapeInfo->GetLayerInfoIndex(),
// so if we're trying to write a layer which isn't in the LandscapeInfo,
// its data is either missing or written where another layer's data should be.
// Either way it's *very bad*.
check(LandscapeInfo->GetLayerInfoIndex(LayerInfo) != INDEX_NONE);
}
if (Stride == 0)
{
Stride = (1+X2-X1) * LandscapeInfo->Layers.Num();
}
check(ComponentSizeQuads > 0);
// Find component range for this block of data
int32 ComponentIndexX1 = (X1-1 >= 0) ? (X1-1) / ComponentSizeQuads : (X1) / ComponentSizeQuads - 1; // -1 because we need to pick up vertices shared between components
int32 ComponentIndexY1 = (Y1-1 >= 0) ? (Y1-1) / ComponentSizeQuads : (Y1) / ComponentSizeQuads - 1;
int32 ComponentIndexX2 = (X2 >= 0) ? X2 / ComponentSizeQuads : (X2+1) / ComponentSizeQuads - 1;
int32 ComponentIndexY2 = (Y2 >= 0) ? Y2 / ComponentSizeQuads : (Y2+1) / ComponentSizeQuads - 1;
TArray<ULandscapeLayerInfoObject*, TInlineAllocator<8>> NeedAllocationInfos;
TArray<FLandscapeTextureDataInfo*, TInlineAllocator<2>> TexDataInfos;
struct FLayerDataInfo
{
const uint8* InDataPtr;
uint8* TexDataPtr;
};
TArray<FLayerDataInfo, TInlineAllocator<8>> LayerDataInfos; // Pointers to all layers' data
TArray<bool, TInlineAllocator<8>> LayerEditDataAllZero; // Whether the data we are editing for this layer is all zero
TArray<FColor*> CollisionWeightmapMipData;
TArray<FColor*> SimpleCollisionWeightmapMipData;
TArray<FColor*> WeightmapTextureMipData;
for (int32 ComponentIndexY = ComponentIndexY1; ComponentIndexY <= ComponentIndexY2; ComponentIndexY++)
{
for (int32 ComponentIndexX = ComponentIndexX1; ComponentIndexX <= ComponentIndexX2; ComponentIndexX++)
{
FIntPoint ComponentKey(ComponentIndexX,ComponentIndexY);
ULandscapeComponent* Component = LandscapeInfo->XYtoComponentMap.FindRef(ComponentKey);
// if NULL, there is no component at this location
if (Component == NULL)
{
continue;
}
const int32 LayerLimit = Component->GetLandscapeProxy()->MaxPaintedLayersPerComponent;
NeedAllocationInfos.Reset();
for (ULandscapeLayerInfoObject* LayerInfo : DirtyLayerInfos)
{
const bool bFound = Component->WeightmapLayerAllocations.ContainsByPredicate([LayerInfo](const FWeightmapLayerAllocationInfo& Allocation){ return Allocation.LayerInfo == LayerInfo; });
if (!bFound)
{
NeedAllocationInfos.Add(LayerInfo);
}
}
// Need allocation for weightmaps
if (NeedAllocationInfos.Num() > 0)
{
if (NeedAllocationInfos.Num() == DirtyLayerInfos.Num())
{
if (PaintingRestriction == ELandscapeLayerPaintingRestriction::ExistingOnly ||
(PaintingRestriction == ELandscapeLayerPaintingRestriction::UseMaxLayers &&
Component->WeightmapLayerAllocations.Num() >= LayerLimit))
{
// nothing to paint to this component due to layer limit
continue;
}
}
if (PaintingRestriction != ELandscapeLayerPaintingRestriction::ExistingOnly)
{
Component->Modify();
for (ULandscapeLayerInfoObject* LayerInfoNeedingAllocation : NeedAllocationInfos)
{
if (PaintingRestriction == ELandscapeLayerPaintingRestriction::UseMaxLayers &&
LayerLimit > 0 && Component->WeightmapLayerAllocations.Num() >= LayerLimit)
{
break;
}
Component->WeightmapLayerAllocations.Emplace(LayerInfoNeedingAllocation);
}
Component->ReallocateWeightmaps(this);
Component->UpdateMaterialInstances();
Component->EditToolRenderData.UpdateDebugColorMaterial(Component);
Component->UpdateEditToolRenderData();
}
}
// Lock data for all the weightmaps
TexDataInfos.Reset();
TexDataInfos.AddUninitialized(Component->WeightmapTextures.Num());
for (int32 WeightmapIdx = 0; WeightmapIdx < Component->WeightmapTextures.Num(); ++WeightmapIdx)
{
TexDataInfos[WeightmapIdx] = GetTextureDataInfo(Component->WeightmapTextures[WeightmapIdx]);
}
LayerDataInfos.Reset(); // Pointers to all layers' data
LayerDataInfos.AddUninitialized(Component->WeightmapLayerAllocations.Num());
LayerEditDataAllZero.Reset(); // Whether the data we are editing for this layer is all zero
LayerEditDataAllZero.AddUninitialized(Component->WeightmapLayerAllocations.Num());
for (int32 LayerIdx = 0; LayerIdx < Component->WeightmapLayerAllocations.Num(); LayerIdx++)
{
FWeightmapLayerAllocationInfo& Allocation = Component->WeightmapLayerAllocations[LayerIdx];
const int32 LayerDataIdx = LandscapeInfo->GetLayerInfoIndex(Component->WeightmapLayerAllocations[LayerIdx].LayerInfo);
check(LayerDataIdx != INDEX_NONE);
LayerDataInfos[LayerIdx].InDataPtr = Data + LayerDataIdx;
LayerDataInfos[LayerIdx].TexDataPtr = (uint8*)TexDataInfos[Allocation.WeightmapTextureIndex]->GetMipData(0) + ChannelOffsets[Allocation.WeightmapTextureChannel];
LayerEditDataAllZero[LayerIdx] = true;
}
// Find the texture data corresponding to this vertex
const int32 TexSize = (Component->SubsectionSizeQuads+1) * Component->NumSubsections;
// Find coordinates of box that lies inside component
const int32 ComponentX1 = FMath::Clamp<int32>(X1-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
const int32 ComponentY1 = FMath::Clamp<int32>(Y1-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
const int32 ComponentX2 = FMath::Clamp<int32>(X2-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
const int32 ComponentY2 = FMath::Clamp<int32>(Y2-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
// Find subsection range for this box
const int32 SubIndexX1 = FMath::Clamp<int32>((ComponentX1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1); // -1 because we need to pick up vertices shared between subsections
const int32 SubIndexY1 = FMath::Clamp<int32>((ComponentY1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1);
const int32 SubIndexX2 = FMath::Clamp<int32>(ComponentX2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
const int32 SubIndexY2 = FMath::Clamp<int32>(ComponentY2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
for (int32 SubIndexY = SubIndexY1; SubIndexY <= SubIndexY2; SubIndexY++)
{
for (int32 SubIndexX = SubIndexX1; SubIndexX <= SubIndexX2; SubIndexX++)
{
// Find coordinates of box that lies inside subsection
const int32 SubX1 = FMath::Clamp<int32>(ComponentX1-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
const int32 SubY1 = FMath::Clamp<int32>(ComponentY1-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
const int32 SubX2 = FMath::Clamp<int32>(ComponentX2-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
const int32 SubY2 = FMath::Clamp<int32>(ComponentY2-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
// Update texture data for the box that lies inside subsection
for (int32 SubY = SubY1; SubY <= SubY2; SubY++)
{
for (int32 SubX = SubX1; SubX <= SubX2; SubX++)
{
const int32 LandscapeX = SubIndexX*SubsectionSizeQuads + ComponentIndexX*ComponentSizeQuads + SubX;
const int32 LandscapeY = SubIndexY*SubsectionSizeQuads + ComponentIndexY*ComponentSizeQuads + SubY;
checkSlow( LandscapeX >= X1 && LandscapeX <= X2 );
checkSlow( LandscapeY >= Y1 && LandscapeY <= Y2 );
// Find the input data corresponding to this vertex
const int32 DataIndex = (LandscapeY-Y1) * Stride + (LandscapeX-X1) * LandscapeInfo->Layers.Num();
// Adjust all layer weights
const int32 TexX = (SubsectionSizeQuads+1) * SubIndexX + SubX;
const int32 TexY = (SubsectionSizeQuads+1) * SubIndexY + SubY;
const int32 TexDataIndex = 4 * (TexX + TexY * TexSize);
int32 OtherLayerWeightSum = 0;
// Apply weights to all layers simultaneously.
for (int32 LayerIdx = 0; LayerIdx < Component->WeightmapLayerAllocations.Num(); LayerIdx++)
{
// this is equivalent to saying if (DirtyLayerInfos.Contains(Allocation.LayerInfo))
// which is what we really mean here, but this is quicker
// and I've lost count of the depth we've nested for loops at this point
if (LayerDataInfos[LayerIdx].TexDataPtr != NULL)
{
uint8& Weight = LayerDataInfos[LayerIdx].TexDataPtr[TexDataIndex];
Weight = LayerDataInfos[LayerIdx].InDataPtr[DataIndex]; // Only for whole weight
if (Weight != 0)
{
LayerEditDataAllZero[LayerIdx] = false;
}
}
}
}
}
// Record the areas of the texture we need to re-upload
const int32 TexX1 = (SubsectionSizeQuads+1) * SubIndexX + SubX1;
const int32 TexY1 = (SubsectionSizeQuads+1) * SubIndexY + SubY1;
const int32 TexX2 = (SubsectionSizeQuads+1) * SubIndexX + SubX2;
const int32 TexY2 = (SubsectionSizeQuads+1) * SubIndexY + SubY2;
for (int32 WeightmapIdx = 0; WeightmapIdx < Component->WeightmapTextures.Num(); WeightmapIdx++)
{
if (TexDataInfos[WeightmapIdx] != NULL)
{
TexDataInfos[WeightmapIdx]->AddMipUpdateRegion(0,TexX1,TexY1,TexX2,TexY2);
}
}
}
}
// Update mipmaps
CollisionWeightmapMipData.Reset();
CollisionWeightmapMipData.AddUninitialized(Component->WeightmapTextures.Num());
for (int32 WeightmapIdx = 0; WeightmapIdx < Component->WeightmapTextures.Num(); WeightmapIdx++)
{
UTexture2D* const WeightmapTexture = Component->WeightmapTextures[WeightmapIdx];
const int32 NumMips = WeightmapTexture->Source.GetNumMips();
WeightmapTextureMipData.Reset();
WeightmapTextureMipData.AddUninitialized(NumMips);
for (int32 MipIdx = 0; MipIdx < NumMips; MipIdx++)
{
FColor* const MipData = (FColor*)TexDataInfos[WeightmapIdx]->GetMipData(MipIdx);
WeightmapTextureMipData[MipIdx] = MipData;
}
CollisionWeightmapMipData[WeightmapIdx] = WeightmapTextureMipData[Component->CollisionMipLevel];
ULandscapeComponent::UpdateWeightmapMips(ComponentNumSubsections, SubsectionSizeQuads, WeightmapTexture, WeightmapTextureMipData, ComponentX1, ComponentY1, ComponentX2, ComponentY2, TexDataInfos[WeightmapIdx]);
WeightmapTextureMipData.Reset();
}
if (Component->SimpleCollisionMipLevel > Component->CollisionMipLevel)
{
for (int32 WeightmapIdx = 0; WeightmapIdx < Component->WeightmapTextures.Num(); WeightmapIdx++)
{
SimpleCollisionWeightmapMipData.Add((FColor*)TexDataInfos[WeightmapIdx]->GetMipData(Component->SimpleCollisionMipLevel));
}
}
// Update dominant layer info stored in collision component
Component->UpdateCollisionLayerData(
CollisionWeightmapMipData.GetData(),
Component->SimpleCollisionMipLevel > Component->CollisionMipLevel ? SimpleCollisionWeightmapMipData.GetData() : nullptr,
ComponentX1, ComponentY1, ComponentX2, ComponentY2);
CollisionWeightmapMipData.Reset();
SimpleCollisionWeightmapMipData.Reset();
// Check if we need to remove weightmap allocations for layers that were completely painted away
bool bRemovedLayer = false;
for (int32 LayerIdx = 0; LayerIdx < Component->WeightmapLayerAllocations.Num(); LayerIdx++)
{
if (LayerEditDataAllZero[LayerIdx])
{
bool bLayerDeleted = DeleteLayerIfAllZero(Component, LayerDataInfos[LayerIdx].TexDataPtr, TexSize, LayerIdx);
if (bLayerDeleted)
{
LayerEditDataAllZero.RemoveAt(LayerIdx);
LayerDataInfos.RemoveAt(LayerIdx);
LayerIdx--;
bRemovedLayer = true;
}
}
}
if (bRemovedLayer)
{
Component->UpdateMaterialInstances();
Component->EditToolRenderData.UpdateDebugColorMaterial(Component);
Component->UpdateEditToolRenderData();
}
}
}
}
template<typename TStoreData>
void FLandscapeEditDataInterface::GetWeightDataTemplFast(ULandscapeLayerInfoObject* LayerInfo, const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, TStoreData& StoreData)
{
int32 ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2;
ALandscape::CalcComponentIndicesNoOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2);
for( int32 ComponentIndexY=ComponentIndexY1;ComponentIndexY<=ComponentIndexY2;ComponentIndexY++ )
{
for( int32 ComponentIndexX=ComponentIndexX1;ComponentIndexX<=ComponentIndexX2;ComponentIndexX++ )
{
ULandscapeComponent* Component = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX,ComponentIndexY));
if( !Component )
{
continue;
}
UTexture2D* WeightmapTexture = NULL;
FLandscapeTextureDataInfo* TexDataInfo = NULL;
uint8* WeightmapTextureData = NULL;
uint8 WeightmapChannelOffset = 0;
TArray<FLandscapeTextureDataInfo*> TexDataInfos; // added for whole weight case...
if (LayerInfo != NULL)
{
for( int32 LayerIdx=0;LayerIdx<Component->WeightmapLayerAllocations.Num();LayerIdx++ )
{
if( Component->WeightmapLayerAllocations[LayerIdx].LayerInfo == LayerInfo )
{
WeightmapTexture = Component->WeightmapTextures[Component->WeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex];
TexDataInfo = GetTextureDataInfo(WeightmapTexture);
WeightmapTextureData = (uint8*)TexDataInfo->GetMipData(0);
WeightmapChannelOffset = ChannelOffsets[Component->WeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel];
break;
}
}
}
else
{
// Lock data for all the weightmaps
for( int32 WeightmapIdx=0;WeightmapIdx < Component->WeightmapTextures.Num();WeightmapIdx++ )
{
TexDataInfos.Add(GetTextureDataInfo(Component->WeightmapTextures[WeightmapIdx]));
}
}
// Find coordinates of box that lies inside component
int32 ComponentX1 = FMath::Clamp<int32>(X1-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY1 = FMath::Clamp<int32>(Y1-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentX2 = FMath::Clamp<int32>(X2-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY2 = FMath::Clamp<int32>(Y2-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
// Find subsection range for this box
int32 SubIndexX1 = FMath::Clamp<int32>((ComponentX1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1); // -1 because we need to pick up vertices shared between subsections
int32 SubIndexY1 = FMath::Clamp<int32>((ComponentY1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexX2 = FMath::Clamp<int32>(ComponentX2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexY2 = FMath::Clamp<int32>(ComponentY2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
for( int32 SubIndexY=SubIndexY1;SubIndexY<=SubIndexY2;SubIndexY++ )
{
for( int32 SubIndexX=SubIndexX1;SubIndexX<=SubIndexX2;SubIndexX++ )
{
// Find coordinates of box that lies inside subsection
int32 SubX1 = FMath::Clamp<int32>(ComponentX1-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY1 = FMath::Clamp<int32>(ComponentY1-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
int32 SubX2 = FMath::Clamp<int32>(ComponentX2-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY2 = FMath::Clamp<int32>(ComponentY2-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
// Update texture data for the box that lies inside subsection
for( int32 SubY=SubY1;SubY<=SubY2;SubY++ )
{
for( int32 SubX=SubX1;SubX<=SubX2;SubX++ )
{
int32 LandscapeX = SubIndexX*SubsectionSizeQuads + ComponentIndexX*ComponentSizeQuads + SubX;
int32 LandscapeY = SubIndexY*SubsectionSizeQuads + ComponentIndexY*ComponentSizeQuads + SubY;
if (LayerInfo != NULL)
{
// Find the input data corresponding to this vertex
uint8 Weight;
if( WeightmapTexture )
{
// Find the texture data corresponding to this vertex
int32 SizeU = WeightmapTexture->Source.GetSizeX();
int32 SizeV = WeightmapTexture->Source.GetSizeY();
int32 WeightmapOffsetX = Component->WeightmapScaleBias.Z * (float)SizeU;
int32 WeightmapOffsetY = Component->WeightmapScaleBias.W * (float)SizeV;
int32 TexX = WeightmapOffsetX + (SubsectionSizeQuads+1) * SubIndexX + SubX;
int32 TexY = WeightmapOffsetY + (SubsectionSizeQuads+1) * SubIndexY + SubY;
Weight = WeightmapTextureData[ 4 * (TexX + TexY * SizeU) + WeightmapChannelOffset ];
}
else
{
Weight = 0;
}
StoreData.Store(LandscapeX, LandscapeY, Weight);
}
else // Whole weight map case...
{
StoreData.PreInit(LandscapeInfo->Layers.Num());
for( int32 LayerIdx=0;LayerIdx<Component->WeightmapLayerAllocations.Num();LayerIdx++ )
{
int32 Idx = Component->WeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex;
UTexture2D* ComponentWeightmapTexture = Component->WeightmapTextures[Idx];
uint8* ComponentWeightmapTextureData = (uint8*)TexDataInfos[Idx]->GetMipData(0);
uint8 ComponentWeightmapChannelOffset = ChannelOffsets[Component->WeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel];
// Find the texture data corresponding to this vertex
int32 SizeU = ComponentWeightmapTexture->Source.GetSizeX();
int32 SizeV = ComponentWeightmapTexture->Source.GetSizeY();
int32 WeightmapOffsetX = Component->WeightmapScaleBias.Z * (float)SizeU;
int32 WeightmapOffsetY = Component->WeightmapScaleBias.W * (float)SizeV;
int32 TexX = WeightmapOffsetX + (SubsectionSizeQuads+1) * SubIndexX + SubX;
int32 TexY = WeightmapOffsetY + (SubsectionSizeQuads+1) * SubIndexY + SubY;
uint8 Weight = ComponentWeightmapTextureData[ 4 * (TexX + TexY * SizeU) + ComponentWeightmapChannelOffset ];
// Find index in LayerInfos
{
int32 LayerInfoIdx = LandscapeInfo->GetLayerInfoIndex(Component->WeightmapLayerAllocations[LayerIdx].LayerInfo);
if (LayerInfoIdx != INDEX_NONE)
{
StoreData.Store(LandscapeX, LandscapeY, Weight, LayerInfoIdx);
}
}
}
}
}
}
}
}
}
}
}
uint8 FLandscapeEditDataInterface::GetWeightMapData(const ULandscapeComponent* Component, ULandscapeLayerInfoObject* LayerInfo, int32 TexU, int32 TexV, uint8 Offset /*= 0*/, UTexture2D* Texture /*= NULL*/, uint8* TextureData /*= NULL*/)
{
check(Component);
if (!Texture || !TextureData)
{
if (LayerInfo != NULL)
{
for( int32 LayerIdx=0;LayerIdx<Component->WeightmapLayerAllocations.Num();LayerIdx++ )
{
if( Component->WeightmapLayerAllocations[LayerIdx].LayerInfo == LayerInfo )
{
Texture = Component->WeightmapTextures[Component->WeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex];
FLandscapeTextureDataInfo* TexDataInfo = GetTextureDataInfo(Texture);
TextureData = (uint8*)TexDataInfo->GetMipData(0);
Offset = ChannelOffsets[Component->WeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel];
break;
}
}
}
}
if (Texture && TextureData)
{
int32 SizeU = Texture->Source.GetSizeX();
int32 SizeV = Texture->Source.GetSizeY();
int32 WeightmapOffsetX = Component->WeightmapScaleBias.Z * (float)SizeU;
int32 WeightmapOffsetY = Component->WeightmapScaleBias.W * (float)SizeV;
int32 TexX = WeightmapOffsetX + TexU;
int32 TexY = WeightmapOffsetY + TexV;
return TextureData[ 4 * (TexX + TexY * SizeU) + Offset ];
}
return 0;
}
template<typename TStoreData>
void FLandscapeEditDataInterface::GetWeightDataTempl(ULandscapeLayerInfoObject* LayerInfo, int32& ValidX1, int32& ValidY1, int32& ValidX2, int32& ValidY2, TStoreData& StoreData)
{
// Copy variables
int32 X1 = ValidX1, X2 = ValidX2, Y1 = ValidY1, Y2 = ValidY2;
ValidX1 = INT_MAX; ValidX2 = INT_MIN; ValidY1 = INT_MAX; ValidY2 = INT_MIN;
int32 ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2;
ALandscape::CalcComponentIndicesNoOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2);
int32 ComponentSizeX = ComponentIndexX2-ComponentIndexX1+1;
int32 ComponentSizeY = ComponentIndexY2-ComponentIndexY1+1;
// Neighbor Components
ULandscapeComponent* BorderComponent[4] = {0, 0, 0, 0};
ULandscapeComponent* CornerComponent[4] = {0, 0, 0, 0};
bool NoBorderX1 = false, NoBorderX2 = false;
TArray<bool> NoBorderY1, NoBorderY2, ComponentDataExist;
TArray<ULandscapeComponent*> BorderComponentY1, BorderComponentY2;
ComponentDataExist.Empty(ComponentSizeX*ComponentSizeY);
ComponentDataExist.AddZeroed(ComponentSizeX*ComponentSizeY);
bool bHasMissingValue = false;
UTexture2D* NeighborWeightmapTexture[4] = {0, 0, 0, 0};
FLandscapeTextureDataInfo* NeighborTexDataInfo[4] = {0, 0, 0, 0};
uint8* NeighborWeightmapTextureData[4] = {0, 0, 0, 0};
uint8 NeighborWeightmapChannelOffset[4] = {0, 0, 0, 0};
uint8 CornerValues[4] = {0, 0, 0, 0};
int32 EdgeCoord = (SubsectionSizeQuads+1) * ComponentNumSubsections - 1; //ComponentSizeQuads;
// initial loop....
for( int32 ComponentIndexY=ComponentIndexY1;ComponentIndexY<=ComponentIndexY2;ComponentIndexY++ )
{
NoBorderX1 = false;
NoBorderX2 = false;
BorderComponent[0] = BorderComponent[1] = NULL;
for( int32 ComponentIndexX=ComponentIndexX1;ComponentIndexX<=ComponentIndexX2;ComponentIndexX++ )
{
BorderComponent[2] = BorderComponent[3] = NULL;
int32 ComponentIndexXY = ComponentSizeX*(ComponentIndexY-ComponentIndexY1) + ComponentIndexX-ComponentIndexX1;
int32 ComponentIndexXX = ComponentIndexX - ComponentIndexX1;
int32 ComponentIndexYY = ComponentIndexY - ComponentIndexY1;
ComponentDataExist[ComponentIndexXY] = false;
ULandscapeComponent* Component = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX,ComponentIndexY));
UTexture2D* WeightmapTexture = NULL;
FLandscapeTextureDataInfo* TexDataInfo = NULL;
uint8* WeightmapTextureData = NULL;
uint8 WeightmapChannelOffset = 0;
TArray<FLandscapeTextureDataInfo*> TexDataInfos; // added for whole weight case...
uint8 CornerSet = 0;
bool ExistLeft = ComponentIndexXX > 0 && ComponentDataExist[ ComponentIndexXX-1 + ComponentIndexYY * ComponentSizeX ];
bool ExistUp = ComponentIndexYY > 0 && ComponentDataExist[ ComponentIndexXX + (ComponentIndexYY-1) * ComponentSizeX ];
if( Component )
{
if (LayerInfo != NULL)
{
for( int32 LayerIdx=0;LayerIdx<Component->WeightmapLayerAllocations.Num();LayerIdx++ )
{
if( Component->WeightmapLayerAllocations[LayerIdx].LayerInfo == LayerInfo )
{
WeightmapTexture = Component->WeightmapTextures[Component->WeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex];
TexDataInfo = GetTextureDataInfo(WeightmapTexture);
WeightmapTextureData = (uint8*)TexDataInfo->GetMipData(0);
WeightmapChannelOffset = ChannelOffsets[Component->WeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel];
break;
}
}
}
else
{
// Lock data for all the weightmaps
for( int32 WeightmapIdx=0;WeightmapIdx < Component->WeightmapTextures.Num();WeightmapIdx++ )
{
TexDataInfos.Add(GetTextureDataInfo(Component->WeightmapTextures[WeightmapIdx]));
}
}
ComponentDataExist[ComponentIndexXY] = true;
// Update valid region
ValidX1 = FMath::Min<int32>(Component->GetSectionBase().X, ValidX1);
ValidX2 = FMath::Max<int32>(Component->GetSectionBase().X+ComponentSizeQuads, ValidX2);
ValidY1 = FMath::Min<int32>(Component->GetSectionBase().Y, ValidY1);
ValidY2 = FMath::Max<int32>(Component->GetSectionBase().Y+ComponentSizeQuads, ValidY2);
}
else
{
if (!bHasMissingValue)
{
NoBorderY1.Empty(ComponentSizeX);
NoBorderY2.Empty(ComponentSizeX);
NoBorderY1.AddZeroed(ComponentSizeX);
NoBorderY2.AddZeroed(ComponentSizeX);
BorderComponentY1.Empty(ComponentSizeX);
BorderComponentY2.Empty(ComponentSizeX);
BorderComponentY1.AddZeroed(ComponentSizeX);
BorderComponentY2.AddZeroed(ComponentSizeX);
bHasMissingValue = true;
}
// Search for neighbor component for interpolation
bool bShouldSearchX = (BorderComponent[1] && BorderComponent[1]->GetSectionBase().X / ComponentSizeQuads <= ComponentIndexX);
bool bShouldSearchY = (BorderComponentY2[ComponentIndexXX] && BorderComponentY2[ComponentIndexXX]->GetSectionBase().Y / ComponentSizeQuads <= ComponentIndexY);
// Search for left-closest component
if ( bShouldSearchX || (!NoBorderX1 && !BorderComponent[0]))
{
NoBorderX1 = true;
for (int32 X = ComponentIndexX-1; X >= ComponentIndexX1; X--)
{
BorderComponent[0] = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(X,ComponentIndexY));
if (BorderComponent[0])
{
NoBorderX1 = false;
if (LayerInfo != NULL)
{
for( int32 LayerIdx=0;LayerIdx<BorderComponent[0]->WeightmapLayerAllocations.Num();LayerIdx++ )
{
if( BorderComponent[0]->WeightmapLayerAllocations[LayerIdx].LayerInfo == LayerInfo )
{
NeighborWeightmapTexture[0] = BorderComponent[0]->WeightmapTextures[BorderComponent[0]->WeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex];
NeighborTexDataInfo[0] = GetTextureDataInfo(NeighborWeightmapTexture[0]);
NeighborWeightmapTextureData[0] = (uint8*)NeighborTexDataInfo[0]->GetMipData(0);
NeighborWeightmapChannelOffset[0] = ChannelOffsets[BorderComponent[0]->WeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel];
break;
}
}
}
break;
}
}
}
// Search for right-closest component
if ( bShouldSearchX || (!NoBorderX2 && !BorderComponent[1]))
{
NoBorderX2 = true;
for (int32 X = ComponentIndexX+1; X <= ComponentIndexX2; X++)
{
BorderComponent[1] = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(X,ComponentIndexY));
if (BorderComponent[1])
{
NoBorderX2 = false;
if (LayerInfo != NULL)
{
for( int32 LayerIdx=0;LayerIdx<BorderComponent[1]->WeightmapLayerAllocations.Num();LayerIdx++ )
{
if( BorderComponent[1]->WeightmapLayerAllocations[LayerIdx].LayerInfo == LayerInfo )
{
NeighborWeightmapTexture[1] = BorderComponent[1]->WeightmapTextures[BorderComponent[1]->WeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex];
NeighborTexDataInfo[1] = GetTextureDataInfo(NeighborWeightmapTexture[1]);
NeighborWeightmapTextureData[1] = (uint8*)NeighborTexDataInfo[1]->GetMipData(0);
NeighborWeightmapChannelOffset[1] = ChannelOffsets[BorderComponent[1]->WeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel];
break;
}
}
}
}
}
}
// Search for up-closest component
if ( bShouldSearchY || (!NoBorderY1[ComponentIndexXX] && !BorderComponentY1[ComponentIndexXX]))
{
NoBorderY1[ComponentIndexXX] = true;
for (int32 Y = ComponentIndexY-1; Y >= ComponentIndexY1; Y--)
{
BorderComponentY1[ComponentIndexXX] = BorderComponent[2] = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX,Y));
if (BorderComponent[2])
{
NoBorderY1[ComponentIndexXX] = false;
if (LayerInfo != NULL)
{
for( int32 LayerIdx=0;LayerIdx<BorderComponent[2]->WeightmapLayerAllocations.Num();LayerIdx++ )
{
if( BorderComponent[2]->WeightmapLayerAllocations[LayerIdx].LayerInfo == LayerInfo )
{
NeighborWeightmapTexture[2] = BorderComponent[2]->WeightmapTextures[BorderComponent[2]->WeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex];
NeighborTexDataInfo[2] = GetTextureDataInfo(NeighborWeightmapTexture[2]);
NeighborWeightmapTextureData[2] = (uint8*)NeighborTexDataInfo[2]->GetMipData(0);
NeighborWeightmapChannelOffset[2] = ChannelOffsets[BorderComponent[2]->WeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel];
break;
}
}
}
}
}
}
else
{
BorderComponent[2] = BorderComponentY1[ComponentIndexXX];
if (BorderComponent[2])
{
if (LayerInfo != NULL)
{
for( int32 LayerIdx=0;LayerIdx<BorderComponent[2]->WeightmapLayerAllocations.Num();LayerIdx++ )
{
if( BorderComponent[2]->WeightmapLayerAllocations[LayerIdx].LayerInfo == LayerInfo )
{
NeighborWeightmapTexture[2] = BorderComponent[2]->WeightmapTextures[BorderComponent[2]->WeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex];
NeighborTexDataInfo[2] = GetTextureDataInfo(NeighborWeightmapTexture[2]);
NeighborWeightmapTextureData[2] = (uint8*)NeighborTexDataInfo[2]->GetMipData(0);
NeighborWeightmapChannelOffset[2] = ChannelOffsets[BorderComponent[2]->WeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel];
break;
}
}
}
}
}
// Search for bottom-closest component
if ( bShouldSearchY || (!NoBorderY2[ComponentIndexXX] && !BorderComponentY2[ComponentIndexXX]))
{
NoBorderY2[ComponentIndexXX] = true;
for (int32 Y = ComponentIndexY+1; Y <= ComponentIndexY2; Y++)
{
BorderComponentY2[ComponentIndexXX] = BorderComponent[3] = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX,Y));
if (BorderComponent[3])
{
NoBorderY2[ComponentIndexXX] = false;
if (LayerInfo != NULL)
{
for( int32 LayerIdx=0;LayerIdx<BorderComponent[3]->WeightmapLayerAllocations.Num();LayerIdx++ )
{
if( BorderComponent[3]->WeightmapLayerAllocations[LayerIdx].LayerInfo == LayerInfo )
{
NeighborWeightmapTexture[3] = BorderComponent[3]->WeightmapTextures[BorderComponent[3]->WeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex];
NeighborTexDataInfo[3] = GetTextureDataInfo(NeighborWeightmapTexture[3]);
NeighborWeightmapTextureData[3] = (uint8*)NeighborTexDataInfo[3]->GetMipData(0);
NeighborWeightmapChannelOffset[3] = ChannelOffsets[BorderComponent[3]->WeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel];
break;
}
}
}
break;
}
}
}
else
{
BorderComponent[3] = BorderComponentY2[ComponentIndexXX];
if (BorderComponent[3])
{
if (LayerInfo != NULL)
{
for( int32 LayerIdx=0;LayerIdx<BorderComponent[3]->WeightmapLayerAllocations.Num();LayerIdx++ )
{
if( BorderComponent[3]->WeightmapLayerAllocations[LayerIdx].LayerInfo == LayerInfo )
{
NeighborWeightmapTexture[3] = BorderComponent[3]->WeightmapTextures[BorderComponent[3]->WeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex];
NeighborTexDataInfo[3] = GetTextureDataInfo(NeighborWeightmapTexture[3]);
NeighborWeightmapTextureData[3] = (uint8*)NeighborTexDataInfo[3]->GetMipData(0);
NeighborWeightmapChannelOffset[3] = ChannelOffsets[BorderComponent[3]->WeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel];
break;
}
}
}
}
}
CornerComponent[0] = ComponentIndexX >= ComponentIndexX1 && ComponentIndexY >= ComponentIndexY1 ?
LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint((ComponentIndexX-1),(ComponentIndexY-1))) : NULL;
CornerComponent[1] = ComponentIndexX <= ComponentIndexX2 && ComponentIndexY >= ComponentIndexY1 ?
LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint((ComponentIndexX+1),(ComponentIndexY-1))) : NULL;
CornerComponent[2] = ComponentIndexX >= ComponentIndexX1 && ComponentIndexY <= ComponentIndexY2 ?
LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint((ComponentIndexX-1),(ComponentIndexY+1))) : NULL;
CornerComponent[3] = ComponentIndexX <= ComponentIndexX2 && ComponentIndexY <= ComponentIndexY2 ?
LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint((ComponentIndexX+1),(ComponentIndexY+1))) : NULL;
if (CornerComponent[0])
{
CornerSet |= 1;
CornerValues[0] = GetWeightMapData(CornerComponent[0], LayerInfo, EdgeCoord, EdgeCoord);
}
else if ((ExistLeft || ExistUp) && X1 <= ComponentIndexX*ComponentSizeQuads && Y1 <= ComponentIndexY*ComponentSizeQuads )
{
CornerSet |= 1;
CornerValues[0] = StoreData.Load( ComponentIndexX*ComponentSizeQuads, ComponentIndexY*ComponentSizeQuads);
}
else if (BorderComponent[0])
{
CornerSet |= 1;
CornerValues[0] = GetWeightMapData(BorderComponent[0], LayerInfo, EdgeCoord, 0, NeighborWeightmapChannelOffset[0], NeighborWeightmapTexture[0], NeighborWeightmapTextureData[0]);
}
else if (BorderComponent[2])
{
CornerSet |= 1;
CornerValues[0] = GetWeightMapData(BorderComponent[2], LayerInfo, 0, EdgeCoord, NeighborWeightmapChannelOffset[2], NeighborWeightmapTexture[2], NeighborWeightmapTextureData[2]);
}
if (CornerComponent[1])
{
CornerSet |= 1 << 1;
CornerValues[1] = GetWeightMapData(CornerComponent[1], LayerInfo, 0, EdgeCoord);
}
else if (ExistUp && X2 >= (ComponentIndexX+1)*ComponentSizeQuads)
{
CornerSet |= 1 << 1;
CornerValues[1] = StoreData.Load( (ComponentIndexX+1)*ComponentSizeQuads, ComponentIndexY*ComponentSizeQuads);
}
else if (BorderComponent[1])
{
CornerSet |= 1 << 1;
CornerValues[1] = GetWeightMapData(BorderComponent[1], LayerInfo, 0, 0, NeighborWeightmapChannelOffset[1], NeighborWeightmapTexture[1], NeighborWeightmapTextureData[1]);
}
else if (BorderComponent[2])
{
CornerSet |= 1 << 1;
CornerValues[1] = GetWeightMapData(BorderComponent[2], LayerInfo, EdgeCoord, EdgeCoord, NeighborWeightmapChannelOffset[2], NeighborWeightmapTexture[2], NeighborWeightmapTextureData[2]);
}
if (CornerComponent[2])
{
CornerSet |= 1 << 2;
CornerValues[2] = GetWeightMapData(CornerComponent[2], LayerInfo, EdgeCoord, 0);
}
else if (ExistLeft && Y2 >= (ComponentIndexY+1)*ComponentSizeQuads) // Use data already stored for 0, 2
{
CornerSet |= 1 << 2;
CornerValues[2] = StoreData.Load( ComponentIndexX*ComponentSizeQuads, (ComponentIndexY+1)*ComponentSizeQuads);
}
else if (BorderComponent[0])
{
CornerSet |= 1 << 2;
CornerValues[2] = GetWeightMapData(BorderComponent[0], LayerInfo, EdgeCoord, EdgeCoord, NeighborWeightmapChannelOffset[0], NeighborWeightmapTexture[0], NeighborWeightmapTextureData[0]);
}
else if (BorderComponent[3])
{
CornerSet |= 1 << 2;
CornerValues[2] = GetWeightMapData(BorderComponent[3], LayerInfo, 0, 0, NeighborWeightmapChannelOffset[3], NeighborWeightmapTexture[3], NeighborWeightmapTextureData[3]);
}
if (CornerComponent[3])
{
CornerSet |= 1 << 3;
CornerValues[3] = GetWeightMapData(CornerComponent[3], LayerInfo, 0, 0);
}
else if (BorderComponent[1])
{
CornerSet |= 1 << 3;
CornerValues[3] = GetWeightMapData(BorderComponent[1], LayerInfo, 0, EdgeCoord, NeighborWeightmapChannelOffset[1], NeighborWeightmapTexture[1], NeighborWeightmapTextureData[1]);
}
else if (BorderComponent[3])
{
CornerSet |= 1 << 3;
CornerValues[3] = GetWeightMapData(BorderComponent[3], LayerInfo, EdgeCoord, 0, NeighborWeightmapChannelOffset[3], NeighborWeightmapTexture[3], NeighborWeightmapTextureData[3]);
}
FillCornerValues(CornerSet, CornerValues);
ComponentDataExist[ComponentIndexXY] = ExistLeft || ExistUp || (BorderComponent[0] || BorderComponent[1] || BorderComponent[2] || BorderComponent[3]) || CornerSet;
}
if (!ComponentDataExist[ComponentIndexXY])
{
continue;
}
// Find coordinates of box that lies inside component
int32 ComponentX1 = FMath::Clamp<int32>(X1-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY1 = FMath::Clamp<int32>(Y1-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentX2 = FMath::Clamp<int32>(X2-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY2 = FMath::Clamp<int32>(Y2-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
// Find subsection range for this box
int32 SubIndexX1 = FMath::Clamp<int32>((ComponentX1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1); // -1 because we need to pick up vertices shared between subsections
int32 SubIndexY1 = FMath::Clamp<int32>((ComponentY1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexX2 = FMath::Clamp<int32>(ComponentX2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexY2 = FMath::Clamp<int32>(ComponentY2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
for( int32 SubIndexY=SubIndexY1;SubIndexY<=SubIndexY2;SubIndexY++ )
{
for( int32 SubIndexX=SubIndexX1;SubIndexX<=SubIndexX2;SubIndexX++ )
{
// Find coordinates of box that lies inside subsection
int32 SubX1 = FMath::Clamp<int32>(ComponentX1-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY1 = FMath::Clamp<int32>(ComponentY1-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
int32 SubX2 = FMath::Clamp<int32>(ComponentX2-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY2 = FMath::Clamp<int32>(ComponentY2-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
// Update texture data for the box that lies inside subsection
for( int32 SubY=SubY1;SubY<=SubY2;SubY++ )
{
for( int32 SubX=SubX1;SubX<=SubX2;SubX++ )
{
int32 LandscapeX = SubIndexX*SubsectionSizeQuads + ComponentIndexX*ComponentSizeQuads + SubX;
int32 LandscapeY = SubIndexY*SubsectionSizeQuads + ComponentIndexY*ComponentSizeQuads + SubY;
if (LayerInfo != NULL)
{
// Find the input data corresponding to this vertex
uint8 Weight;
if( WeightmapTexture )
{
// Find the texture data corresponding to this vertex
Weight = GetWeightMapData(Component, LayerInfo, (SubsectionSizeQuads+1) * SubIndexX + SubX, (SubsectionSizeQuads+1) * SubIndexY + SubY, WeightmapChannelOffset, WeightmapTexture, WeightmapTextureData );
StoreData.Store(LandscapeX, LandscapeY, Weight);
}
else
{
// Find the texture data corresponding to this vertex
uint8 Value[4] = {0, 0, 0, 0};
int32 Dist[4] = {INT_MAX, INT_MAX, INT_MAX, INT_MAX};
float ValueX = 0.0f, ValueY = 0.0f;
bool Exist[4] = {false, false, false, false};
// Use data already stored for 0, 2
if (ExistLeft && SubX == 0)
{
Value[0] = StoreData.Load( ComponentIndexX*ComponentSizeQuads, LandscapeY);
Dist[0] = LandscapeX - (ComponentIndexX*ComponentSizeQuads);
Exist[0] = true;
}
else if (BorderComponent[0] && NeighborWeightmapTexture[0])
{
Value[0] = GetWeightMapData(BorderComponent[0], LayerInfo, EdgeCoord, (SubsectionSizeQuads+1) * SubIndexY + SubY, NeighborWeightmapChannelOffset[0], NeighborWeightmapTexture[0], NeighborWeightmapTextureData[0]);
Dist[0] = LandscapeX - (BorderComponent[0]->GetSectionBase().X + ComponentSizeQuads);
Exist[0] = true;
}
else
{
if ((CornerSet & 1) && (CornerSet & 1 << 2))
{
int32 Dist1 = LandscapeY - (ComponentIndexY*ComponentSizeQuads);
int32 Dist2 = ((ComponentIndexY+1)*ComponentSizeQuads) - LandscapeY;
Value[0] = (float)(Dist2 * CornerValues[0] + Dist1 * CornerValues[2]) / (Dist1 + Dist2);
Dist[0] = LandscapeX - (ComponentIndexX*ComponentSizeQuads);
Exist[0] = true;
}
}
if (BorderComponent[1] && NeighborWeightmapTexture[1])
{
Value[1] = GetWeightMapData(BorderComponent[1], LayerInfo, 0, (SubsectionSizeQuads+1) * SubIndexY + SubY, NeighborWeightmapChannelOffset[1], NeighborWeightmapTexture[1], NeighborWeightmapTextureData[1]);
Dist[1] = (BorderComponent[1]->GetSectionBase().X) - LandscapeX;
Exist[1] = true;
}
else
{
if ((CornerSet & 1 << 1) && (CornerSet & 1 << 3))
{
int32 Dist1 = LandscapeY - (ComponentIndexY*ComponentSizeQuads);
int32 Dist2 = ((ComponentIndexY+1)*ComponentSizeQuads) - LandscapeY;
Value[1] = (float)(Dist2 * CornerValues[1] + Dist1 * CornerValues[3]) / (Dist1 + Dist2);
Dist[1] = (ComponentIndexX+1)*ComponentSizeQuads - LandscapeX;
Exist[1] = true;
}
}
if (ExistUp && SubY == 0)
{
Value[2] = StoreData.Load( LandscapeX, ComponentIndexY*ComponentSizeQuads);
Dist[2] = LandscapeY - (ComponentIndexY*ComponentSizeQuads);
Exist[2] = true;
}
else if (BorderComponent[2] && NeighborWeightmapTexture[2])
{
Value[2] = GetWeightMapData(BorderComponent[2], LayerInfo, (SubsectionSizeQuads+1) * SubIndexX + SubX, EdgeCoord, NeighborWeightmapChannelOffset[2], NeighborWeightmapTexture[2], NeighborWeightmapTextureData[2]);
Dist[2] = LandscapeY - (BorderComponent[2]->GetSectionBase().Y + ComponentSizeQuads);
Exist[2] = true;
}
else
{
if ((CornerSet & 1) && (CornerSet & 1 << 1))
{
int32 Dist1 = LandscapeX - (ComponentIndexX*ComponentSizeQuads);
int32 Dist2 = (ComponentIndexX+1)*ComponentSizeQuads - LandscapeX;
Value[2] = (float)(Dist2 * CornerValues[0] + Dist1 * CornerValues[1]) / (Dist1 + Dist2);
Dist[2] = LandscapeY - (ComponentIndexY*ComponentSizeQuads);
Exist[2] = true;
}
}
if (BorderComponent[3] && NeighborWeightmapTexture[3])
{
Value[3] = GetWeightMapData(BorderComponent[3], LayerInfo, (SubsectionSizeQuads+1) * SubIndexX + SubX, 0, NeighborWeightmapChannelOffset[3], NeighborWeightmapTexture[3], NeighborWeightmapTextureData[3]);
Dist[3] = (BorderComponent[3]->GetSectionBase().Y) - LandscapeY;
Exist[3] = true;
}
else
{
if ((CornerSet & 1 << 2) && (CornerSet & 1 << 3))
{
int32 Dist1 = LandscapeX - (ComponentIndexX*ComponentSizeQuads);
int32 Dist2 = (ComponentIndexX+1)*ComponentSizeQuads - LandscapeX;
Value[3] = (float)(Dist2 * CornerValues[2] + Dist1 * CornerValues[3]) / (Dist1 + Dist2);
Dist[3] = (ComponentIndexY+1)*ComponentSizeQuads - LandscapeY;
Exist[3] = true;
}
}
CalcInterpValue<uint8>(Dist, Exist, Value, ValueX, ValueY);
uint8 FinalValue = 0; // Default Value
if ( (Exist[0] || Exist[1]) && (Exist[2] || Exist[3]) )
{
FinalValue = CalcValueFromValueXY<uint8>(Dist, ValueX, ValueY, CornerSet, CornerValues);
}
else if ( (Exist[0] || Exist[1]) )
{
FinalValue = ValueX;
}
else if ( (Exist[2] || Exist[3]) )
{
FinalValue = ValueY;
}
Weight = FinalValue;
}
StoreData.Store(LandscapeX, LandscapeY, Weight);
}
else // Whole weight map case... no interpolation now...
{
StoreData.PreInit(LandscapeInfo->Layers.Num());
for( int32 LayerIdx=0;LayerIdx<Component->WeightmapLayerAllocations.Num();LayerIdx++ )
{
int32 Idx = Component->WeightmapLayerAllocations[LayerIdx].WeightmapTextureIndex;
UTexture2D* ComponentWeightmapTexture = Component->WeightmapTextures[Idx];
uint8* ComponentWeightmapTextureData = (uint8*)TexDataInfos[Idx]->GetMipData(0);
uint8 ComponentWeightmapChannelOffset = ChannelOffsets[Component->WeightmapLayerAllocations[LayerIdx].WeightmapTextureChannel];
// Find the texture data corresponding to this vertex
int32 SizeU = ComponentWeightmapTexture->Source.GetSizeX();
int32 SizeV = ComponentWeightmapTexture->Source.GetSizeY();
int32 WeightmapOffsetX = Component->WeightmapScaleBias.Z * (float)SizeU;
int32 WeightmapOffsetY = Component->WeightmapScaleBias.W * (float)SizeV;
int32 TexX = WeightmapOffsetX + (SubsectionSizeQuads+1) * SubIndexX + SubX;
int32 TexY = WeightmapOffsetY + (SubsectionSizeQuads+1) * SubIndexY + SubY;
uint8 Weight = ComponentWeightmapTextureData[ 4 * (TexX + TexY * SizeU) + ComponentWeightmapChannelOffset ];
// Find index in LayerInfos
{
int32 LayerInfoIdx = LandscapeInfo->GetLayerInfoIndex(Component->WeightmapLayerAllocations[LayerIdx].LayerInfo);
if (LayerInfoIdx != INDEX_NONE)
{
StoreData.Store(LandscapeX, LandscapeY, Weight, LayerInfoIdx);
}
}
}
}
}
}
}
}
}
}
if (bHasMissingValue)
{
CalcMissingValues<uint8, TStoreData, float>( X1, X2, Y1, Y2,
ComponentIndexX1, ComponentIndexX2, ComponentIndexY1, ComponentIndexY2,
ComponentSizeX, ComponentSizeY, CornerValues,
NoBorderY1, NoBorderY2, ComponentDataExist, StoreData );
// Update valid region
ValidX1 = FMath::Max<int32>(X1, ValidX1);
ValidX2 = FMath::Min<int32>(X2, ValidX2);
ValidY1 = FMath::Max<int32>(Y1, ValidY1);
ValidY2 = FMath::Min<int32>(Y2, ValidY2);
}
else
{
ValidX1 = X1;
ValidX2 = X2;
ValidY1 = Y1;
ValidY2 = Y2;
}
}
void FLandscapeEditDataInterface::GetWeightData(ULandscapeLayerInfoObject* LayerInfo, int32& X1, int32& Y1, int32& X2, int32& Y2, uint8* Data, int32 Stride)
{
if( Stride==0 )
{
Stride = (1+X2-X1);
}
TArrayStoreData<uint8> ArrayStoreData(X1, Y1, Data, Stride);
GetWeightDataTempl(LayerInfo, X1, Y1, X2, Y2, ArrayStoreData);
}
void FLandscapeEditDataInterface::GetWeightDataFast(ULandscapeLayerInfoObject* LayerInfo, const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, uint8* Data, int32 Stride)
{
if( Stride==0 )
{
Stride = (1+X2-X1);
}
TArrayStoreData<uint8> ArrayStoreData(X1, Y1, Data, Stride);
GetWeightDataTemplFast(LayerInfo, X1, Y1, X2, Y2, ArrayStoreData);
}
void FLandscapeEditDataInterface::GetWeightData(ULandscapeLayerInfoObject* LayerInfo, int32& X1, int32& Y1, int32& X2, int32& Y2, TMap<FIntPoint, uint8>& SparseData)
{
TSparseStoreData<uint8> SparseStoreData(SparseData);
GetWeightDataTempl(LayerInfo, X1, Y1, X2, Y2, SparseStoreData);
}
void FLandscapeEditDataInterface::GetWeightDataFast(ULandscapeLayerInfoObject* LayerInfo, const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, TMap<FIntPoint, uint8>& SparseData)
{
TSparseStoreData<uint8> SparseStoreData(SparseData);
GetWeightDataTemplFast(LayerInfo, X1, Y1, X2, Y2, SparseStoreData);
}
void FLandscapeEditDataInterface::GetWeightDataFast(ULandscapeLayerInfoObject* LayerInfo, const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, TArray<uint8>* Data, int32 Stride)
{
if( Stride==0 )
{
Stride = (1+X2-X1);
}
TArrayStoreData<TArray<uint8>> ArrayStoreData(X1, Y1, Data, Stride);
GetWeightDataTemplFast(LayerInfo, X1, Y1, X2, Y2, ArrayStoreData);
}
void FLandscapeEditDataInterface::GetWeightDataFast(ULandscapeLayerInfoObject* LayerInfo, const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, TMap<FIntPoint, TArray<uint8>>& SparseData)
{
TSparseStoreData<TArray<uint8>> SparseStoreData(SparseData);
GetWeightDataTemplFast(LayerInfo, X1, Y1, X2, Y2, SparseStoreData);
}
FLandscapeTextureDataInfo* FLandscapeTextureDataInterface::GetTextureDataInfo(UTexture2D* Texture)
{
FLandscapeTextureDataInfo* Result = TextureDataMap.FindRef(Texture);
if( !Result )
{
Result = TextureDataMap.Add(Texture, new FLandscapeTextureDataInfo(Texture));
}
return Result;
}
void FLandscapeTextureDataInterface::CopyTextureChannel(UTexture2D* Dest, int32 DestChannel, UTexture2D* Src, int32 SrcChannel)
{
FLandscapeTextureDataInfo* DestDataInfo = GetTextureDataInfo(Dest);
FLandscapeTextureDataInfo* SrcDataInfo = GetTextureDataInfo(Src);
int32 MipSize = Dest->Source.GetSizeX();
check(Dest->Source.GetSizeX() == Dest->Source.GetSizeY() && Src->Source.GetSizeX() == Dest->Source.GetSizeX());
for( int32 MipIdx=0;MipIdx<DestDataInfo->NumMips();MipIdx++ )
{
uint8* DestTextureData = (uint8*)DestDataInfo->GetMipData(MipIdx) + ChannelOffsets[DestChannel];
uint8* SrcTextureData = (uint8*)SrcDataInfo->GetMipData(MipIdx) + ChannelOffsets[SrcChannel];
for( int32 i=0;i<FMath::Square(MipSize);i++ )
{
DestTextureData[i*4] = SrcTextureData[i*4];
}
DestDataInfo->AddMipUpdateRegion(MipIdx, 0, 0, MipSize-1, MipSize-1);
MipSize >>= 1;
}
}
void FLandscapeTextureDataInterface::CopyTextureFromHeightmap(UTexture2D* Dest, int32 DestChannel, ULandscapeComponent* Comp, int32 SrcChannel)
{
FLandscapeTextureDataInfo* DestDataInfo = GetTextureDataInfo(Dest);
int32 MipSize = Dest->Source.GetSizeX();
check(Dest->Source.GetSizeX() == Dest->Source.GetSizeY());
for( int32 MipIdx=0;MipIdx<DestDataInfo->NumMips();MipIdx++ )
{
FLandscapeComponentDataInterface DataInterface(Comp, MipIdx);
TArray<FColor> Heightmap;
DataInterface.GetHeightmapTextureData(Heightmap);
uint8* DestTextureData = (uint8*)DestDataInfo->GetMipData(MipIdx) + ChannelOffsets[DestChannel];
uint8* SrcTextureData = (uint8*)Heightmap.GetData() + ChannelOffsets[SrcChannel];
for( int32 i=0;i<FMath::Square(MipSize);i++ )
{
DestTextureData[i*4] = SrcTextureData[i*4];
}
DestDataInfo->AddMipUpdateRegion(MipIdx, 0, 0, MipSize-1, MipSize-1);
MipSize >>= 1;
}
}
void FLandscapeTextureDataInterface::CopyTextureFromWeightmap(UTexture2D* Dest, int32 DestChannel, ULandscapeComponent* Comp, ULandscapeLayerInfoObject* LayerInfo)
{
FLandscapeTextureDataInfo* DestDataInfo = GetTextureDataInfo(Dest);
int32 MipSize = Dest->Source.GetSizeX();
check(Dest->Source.GetSizeX() == Dest->Source.GetSizeY());
for (int32 MipIdx = 0; MipIdx < DestDataInfo->NumMips(); MipIdx++)
{
FLandscapeComponentDataInterface DataInterface(Comp, MipIdx);
TArray<uint8> WeightData;
DataInterface.GetWeightmapTextureData(LayerInfo, WeightData);
uint8* DestTextureData = (uint8*)DestDataInfo->GetMipData(MipIdx) + ChannelOffsets[DestChannel];
for (int32 i = 0; i < FMath::Square(MipSize); i++)
{
DestTextureData[i * 4] = WeightData[i];
}
DestDataInfo->AddMipUpdateRegion(MipIdx, 0, 0, MipSize - 1, MipSize - 1);
MipSize >>= 1;
}
}
void FLandscapeTextureDataInterface::ZeroTextureChannel(UTexture2D* Dest, int32 DestChannel)
{
FLandscapeTextureDataInfo* DestDataInfo = GetTextureDataInfo(Dest);
int32 MipSize = Dest->Source.GetSizeX();
check(Dest->Source.GetSizeX() == Dest->Source.GetSizeY());
for( int32 MipIdx=0;MipIdx<DestDataInfo->NumMips();MipIdx++ )
{
uint8* DestTextureData = (uint8*)DestDataInfo->GetMipData(MipIdx) + ChannelOffsets[DestChannel];
for( int32 i=0;i<FMath::Square(MipSize);i++ )
{
DestTextureData[i*4] = 0;
}
DestDataInfo->AddMipUpdateRegion(MipIdx, 0, 0, MipSize-1, MipSize-1);
MipSize >>= 1;
}
}
template<typename TData>
void FLandscapeTextureDataInterface::SetTextureValueTempl(UTexture2D* Dest, TData Value)
{
FLandscapeTextureDataInfo* DestDataInfo = GetTextureDataInfo(Dest);
int32 MipSize = Dest->Source.GetSizeX();
check(Dest->Source.GetSizeX() == Dest->Source.GetSizeY());
for( int32 MipIdx=0;MipIdx<DestDataInfo->NumMips();MipIdx++ )
{
TData* DestTextureData = (TData*)DestDataInfo->GetMipData(MipIdx);
for( int32 i=0;i<FMath::Square(MipSize);i++ )
{
DestTextureData[i] = Value;
}
DestDataInfo->AddMipUpdateRegion(MipIdx, 0, 0, MipSize-1, MipSize-1);
MipSize >>= 1;
}
}
void FLandscapeTextureDataInterface::ZeroTexture(UTexture2D* Dest)
{
SetTextureValueTempl<uint8>(Dest, 0);
}
void FLandscapeTextureDataInterface::SetTextureValue(UTexture2D* Dest, FColor Value)
{
SetTextureValueTempl<FColor>(Dest, Value);
}
template<typename TData>
bool FLandscapeTextureDataInterface::EqualTextureValueTempl(UTexture2D* Src, TData Value)
{
FLandscapeTextureDataInfo* DestDataInfo = GetTextureDataInfo(Src);
TData* DestTextureData = (TData*)DestDataInfo->GetMipData(0);
int32 Size = Src->Source.GetSizeX() * Src->Source.GetSizeY();
for( int32 i = 0 ;i < Size; ++i )
{
if (DestTextureData[i] != Value)
{
return false;
}
}
return true;
}
bool FLandscapeTextureDataInterface::EqualTextureValue(UTexture2D* Src, FColor Value)
{
return EqualTextureValueTempl<FColor>(Src, Value);
}
template<typename TStoreData>
void FLandscapeEditDataInterface::GetSelectDataTempl(const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, TStoreData& StoreData)
{
int32 ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2;
ALandscape::CalcComponentIndicesNoOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2);
for( int32 ComponentIndexY=ComponentIndexY1;ComponentIndexY<=ComponentIndexY2;ComponentIndexY++ )
{
for( int32 ComponentIndexX=ComponentIndexX1;ComponentIndexX<=ComponentIndexX2;ComponentIndexX++ )
{
ULandscapeComponent* Component = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX,ComponentIndexY));
FLandscapeTextureDataInfo* TexDataInfo = NULL;
uint8* SelectTextureData = NULL;
if( Component && Component->EditToolRenderData.DataTexture )
{
TexDataInfo = GetTextureDataInfo(Component->EditToolRenderData.DataTexture);
SelectTextureData = (uint8*)TexDataInfo->GetMipData(0);
}
// Find coordinates of box that lies inside component
int32 ComponentX1 = FMath::Clamp<int32>(X1-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY1 = FMath::Clamp<int32>(Y1-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentX2 = FMath::Clamp<int32>(X2-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY2 = FMath::Clamp<int32>(Y2-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
// Find subsection range for this box
int32 SubIndexX1 = FMath::Clamp<int32>((ComponentX1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1); // -1 because we need to pick up vertices shared between subsections
int32 SubIndexY1 = FMath::Clamp<int32>((ComponentY1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexX2 = FMath::Clamp<int32>(ComponentX2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexY2 = FMath::Clamp<int32>(ComponentY2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
for( int32 SubIndexY=SubIndexY1;SubIndexY<=SubIndexY2;SubIndexY++ )
{
for( int32 SubIndexX=SubIndexX1;SubIndexX<=SubIndexX2;SubIndexX++ )
{
// Find coordinates of box that lies inside subsection
int32 SubX1 = FMath::Clamp<int32>(ComponentX1-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY1 = FMath::Clamp<int32>(ComponentY1-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
int32 SubX2 = FMath::Clamp<int32>(ComponentX2-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY2 = FMath::Clamp<int32>(ComponentY2-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
// Update texture data for the box that lies inside subsection
for( int32 SubY=SubY1;SubY<=SubY2;SubY++ )
{
for( int32 SubX=SubX1;SubX<=SubX2;SubX++ )
{
int32 LandscapeX = SubIndexX*SubsectionSizeQuads + ComponentIndexX*ComponentSizeQuads + SubX;
int32 LandscapeY = SubIndexY*SubsectionSizeQuads + ComponentIndexY*ComponentSizeQuads + SubY;
// Find the input data corresponding to this vertex
if( Component && SelectTextureData )
{
// Find the texture data corresponding to this vertex
int32 SizeU = Component->EditToolRenderData.DataTexture->Source.GetSizeX();
int32 SizeV = Component->EditToolRenderData.DataTexture->Source.GetSizeY();
int32 WeightmapOffsetX = Component->WeightmapScaleBias.Z * (float)SizeU;
int32 WeightmapOffsetY = Component->WeightmapScaleBias.W * (float)SizeV;
int32 TexX = WeightmapOffsetX + (SubsectionSizeQuads+1) * SubIndexX + SubX;
int32 TexY = WeightmapOffsetY + (SubsectionSizeQuads+1) * SubIndexY + SubY;
uint8& TexData = SelectTextureData[ TexX + TexY * SizeU ];
StoreData.Store(LandscapeX, LandscapeY, TexData);
}
else
{
StoreData.Store(LandscapeX, LandscapeY, 0);
}
}
}
}
}
}
}
}
void FLandscapeEditDataInterface::GetSelectData(const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, TMap<FIntPoint, uint8>& SparseData)
{
TSparseStoreData<uint8> SparseStoreData(SparseData);
GetSelectDataTempl(X1, Y1, X2, Y2, SparseStoreData);
}
void FLandscapeEditDataInterface::GetSelectData(const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, uint8* Data, int32 Stride)
{
if( Stride==0 )
{
Stride = (1+X2-X1);
}
TArrayStoreData<uint8> ArrayStoreData(X1, Y1, Data, Stride);
GetSelectDataTempl(X1, Y1, X2, Y2, ArrayStoreData);
}
void FLandscapeEditDataInterface::SetSelectData(int32 X1, int32 Y1, int32 X2, int32 Y2, const uint8* Data, int32 Stride)
{
if( Stride==0 )
{
Stride = (1+X2-X1);
}
check(ComponentSizeQuads > 0);
// Find component range for this block of data
int32 ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2;
ALandscape::CalcComponentIndicesNoOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2);
for( int32 ComponentIndexY=ComponentIndexY1;ComponentIndexY<=ComponentIndexY2;ComponentIndexY++ )
{
for( int32 ComponentIndexX=ComponentIndexX1;ComponentIndexX<=ComponentIndexX2;ComponentIndexX++ )
{
FIntPoint ComponentKey(ComponentIndexX,ComponentIndexY);
ULandscapeComponent* Component = LandscapeInfo->XYtoComponentMap.FindRef(ComponentKey);
UTexture2D* DataTexture = NULL;
// if NULL, it was painted away
if( Component==NULL)
{
continue;
}
else if (Component->EditToolRenderData.DataTexture == NULL)
{
//FlushRenderingCommands();
// Construct Texture...
int32 WeightmapSize = (Component->SubsectionSizeQuads+1) * Component->NumSubsections;
DataTexture = Component->GetLandscapeProxy()->CreateLandscapeTexture(WeightmapSize, WeightmapSize, TEXTUREGROUP_Terrain_Weightmap, TSF_G8);
// Alloc dummy mips
ULandscapeComponent::CreateEmptyTextureMips(DataTexture, true);
DataTexture->PostEditChange();
//FlushRenderingCommands();
ZeroTexture(DataTexture);
FLandscapeTextureDataInfo* TexDataInfo = GetTextureDataInfo(DataTexture);
int32 NumMips = DataTexture->Source.GetNumMips();
TArray<uint8*> TextureMipData;
TextureMipData.AddUninitialized(NumMips);
for( int32 MipIdx=0;MipIdx<NumMips;MipIdx++ )
{
TextureMipData[MipIdx] = (uint8*)TexDataInfo->GetMipData(MipIdx);
}
ULandscapeComponent::UpdateDataMips(ComponentNumSubsections, SubsectionSizeQuads, DataTexture, TextureMipData, 0, 0, MAX_int32, MAX_int32, TexDataInfo);
Component->EditToolRenderData.DataTexture = DataTexture;
Component->UpdateEditToolRenderData();
}
else
{
DataTexture = Component->EditToolRenderData.DataTexture;
}
FLandscapeTextureDataInfo* TexDataInfo = GetTextureDataInfo(DataTexture);
uint8* SelectTextureData = (uint8*)TexDataInfo->GetMipData(0);
// Find the texture data corresponding to this vertex
int32 SizeU = DataTexture->Source.GetSizeX();
int32 SizeV = DataTexture->Source.GetSizeY();
int32 WeightmapOffsetX = Component->WeightmapScaleBias.Z * (float)SizeU;
int32 WeightmapOffsetY = Component->WeightmapScaleBias.W * (float)SizeV;
// Find coordinates of box that lies inside component
int32 ComponentX1 = FMath::Clamp<int32>(X1-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY1 = FMath::Clamp<int32>(Y1-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentX2 = FMath::Clamp<int32>(X2-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY2 = FMath::Clamp<int32>(Y2-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
// Find subsection range for this box
int32 SubIndexX1 = FMath::Clamp<int32>((ComponentX1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1); // -1 because we need to pick up vertices shared between subsections
int32 SubIndexY1 = FMath::Clamp<int32>((ComponentY1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexX2 = FMath::Clamp<int32>(ComponentX2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexY2 = FMath::Clamp<int32>(ComponentY2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
for( int32 SubIndexY=SubIndexY1;SubIndexY<=SubIndexY2;SubIndexY++ )
{
for( int32 SubIndexX=SubIndexX1;SubIndexX<=SubIndexX2;SubIndexX++ )
{
// Find coordinates of box that lies inside subsection
int32 SubX1 = FMath::Clamp<int32>(ComponentX1-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY1 = FMath::Clamp<int32>(ComponentY1-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
int32 SubX2 = FMath::Clamp<int32>(ComponentX2-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY2 = FMath::Clamp<int32>(ComponentY2-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
// Update texture data for the box that lies inside subsection
for( int32 SubY=SubY1;SubY<=SubY2;SubY++ )
{
for( int32 SubX=SubX1;SubX<=SubX2;SubX++ )
{
int32 LandscapeX = SubIndexX*SubsectionSizeQuads + ComponentIndexX*ComponentSizeQuads + SubX;
int32 LandscapeY = SubIndexY*SubsectionSizeQuads + ComponentIndexY*ComponentSizeQuads + SubY;
checkSlow( LandscapeX >= X1 && LandscapeX <= X2 );
checkSlow( LandscapeY >= Y1 && LandscapeY <= Y2 );
// Find the input data corresponding to this vertex
int32 DataIndex = (LandscapeX-X1) + Stride * (LandscapeY-Y1);
const uint8& Value = Data[DataIndex];
int32 TexX = WeightmapOffsetX + (SubsectionSizeQuads+1) * SubIndexX + SubX;
int32 TexY = WeightmapOffsetY + (SubsectionSizeQuads+1) * SubIndexY + SubY;
uint8& TexData = SelectTextureData[ TexX + TexY * SizeU ];
TexData = Value;
}
}
// Record the areas of the texture we need to re-upload
int32 TexX1 = WeightmapOffsetX + (SubsectionSizeQuads+1) * SubIndexX + SubX1;
int32 TexY1 = WeightmapOffsetY + (SubsectionSizeQuads+1) * SubIndexY + SubY1;
int32 TexX2 = WeightmapOffsetX + (SubsectionSizeQuads+1) * SubIndexX + SubX2;
int32 TexY2 = WeightmapOffsetY + (SubsectionSizeQuads+1) * SubIndexY + SubY2;
TexDataInfo->AddMipUpdateRegion(0,TexX1,TexY1,TexX2,TexY2);
}
}
// Update mipmaps
int32 NumMips = DataTexture->Source.GetNumMips();
TArray<uint8*> TextureMipData;
TextureMipData.AddUninitialized(NumMips);
for( int32 MipIdx=0;MipIdx<NumMips;MipIdx++ )
{
TextureMipData[MipIdx] = (uint8*)TexDataInfo->GetMipData(MipIdx);
}
ULandscapeComponent::UpdateDataMips(ComponentNumSubsections, SubsectionSizeQuads, DataTexture, TextureMipData, ComponentX1, ComponentY1, ComponentX2, ComponentY2, TexDataInfo);
}
}
}
template<typename T>
void FLandscapeEditDataInterface::SetXYOffsetDataTempl(int32 X1, int32 Y1, int32 X2, int32 Y2, const T* Data, int32 Stride)
{
if( Stride==0 )
{
Stride = (1+X2-X1);
}
check(ComponentSizeQuads > 0);
// Find component range for this block of data
int32 ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2;
ALandscape::CalcComponentIndicesNoOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2);
const FColor DefaultValue(128, 0, 128, 0);
for( int32 ComponentIndexY=ComponentIndexY1;ComponentIndexY<=ComponentIndexY2;ComponentIndexY++ )
{
for( int32 ComponentIndexX=ComponentIndexX1;ComponentIndexX<=ComponentIndexX2;ComponentIndexX++ )
{
FIntPoint ComponentKey(ComponentIndexX,ComponentIndexY);
ULandscapeComponent* Component = LandscapeInfo->XYtoComponentMap.FindRef(ComponentKey);
UTexture2D* XYOffsetTexture = NULL;
if( Component==NULL)
{
continue;
}
else
{
if (Component->XYOffsetmapTexture == NULL)
{
Component->Modify();
//FlushRenderingCommands();
// Construct Texture...
int32 WeightmapSize = (Component->SubsectionSizeQuads+1) * Component->NumSubsections;
XYOffsetTexture = Component->GetLandscapeProxy()->CreateLandscapeTexture(WeightmapSize, WeightmapSize, TEXTUREGROUP_Terrain_Weightmap, TSF_BGRA8);
// Alloc dummy mips
ULandscapeComponent::CreateEmptyTextureMips(XYOffsetTexture, true);
XYOffsetTexture->PostEditChange();
//FlushRenderingCommands();
SetTextureValue(XYOffsetTexture, DefaultValue);
FLandscapeTextureDataInfo* TexDataInfo = GetTextureDataInfo(XYOffsetTexture);
int32 NumMips = XYOffsetTexture->Source.GetNumMips();
TArray<FColor*> TextureMipData;
TextureMipData.AddUninitialized(NumMips);
for( int32 MipIdx=0;MipIdx<NumMips;MipIdx++ )
{
TextureMipData[MipIdx] = (FColor*)TexDataInfo->GetMipData(MipIdx);
}
ULandscapeComponent::UpdateWeightmapMips(ComponentNumSubsections, SubsectionSizeQuads, XYOffsetTexture, TextureMipData, 0, 0, MAX_int32, MAX_int32, TexDataInfo);
Component->XYOffsetmapTexture = XYOffsetTexture;
FComponentReregisterContext ReregisterContext(Component);
}
else
{
XYOffsetTexture = Component->XYOffsetmapTexture;
}
}
FLandscapeTextureDataInfo* TexDataInfo = GetTextureDataInfo(XYOffsetTexture);
FColor* XYOffsetTextureData = (FColor*)TexDataInfo->GetMipData(0);
// Find the texture data corresponding to this vertex
int32 SizeU = XYOffsetTexture->Source.GetSizeX();
int32 SizeV = XYOffsetTexture->Source.GetSizeY();
int32 WeightmapOffsetX = Component->WeightmapScaleBias.Z * (float)SizeU;
int32 WeightmapOffsetY = Component->WeightmapScaleBias.W * (float)SizeV;
// Find coordinates of box that lies inside component
int32 ComponentX1 = FMath::Clamp<int32>(X1-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY1 = FMath::Clamp<int32>(Y1-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentX2 = FMath::Clamp<int32>(X2-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY2 = FMath::Clamp<int32>(Y2-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
// Find subsection range for this box
int32 SubIndexX1 = FMath::Clamp<int32>((ComponentX1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1); // -1 because we need to pick up vertices shared between subsections
int32 SubIndexY1 = FMath::Clamp<int32>((ComponentY1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexX2 = FMath::Clamp<int32>(ComponentX2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexY2 = FMath::Clamp<int32>(ComponentY2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
for( int32 SubIndexY=SubIndexY1;SubIndexY<=SubIndexY2;SubIndexY++ )
{
for( int32 SubIndexX=SubIndexX1;SubIndexX<=SubIndexX2;SubIndexX++ )
{
// Find coordinates of box that lies inside subsection
int32 SubX1 = FMath::Clamp<int32>(ComponentX1-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY1 = FMath::Clamp<int32>(ComponentY1-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
int32 SubX2 = FMath::Clamp<int32>(ComponentX2-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY2 = FMath::Clamp<int32>(ComponentY2-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
// Update texture data for the box that lies inside subsection
for( int32 SubY=SubY1;SubY<=SubY2;SubY++ )
{
for( int32 SubX=SubX1;SubX<=SubX2;SubX++ )
{
int32 LandscapeX = SubIndexX*SubsectionSizeQuads + ComponentIndexX*ComponentSizeQuads + SubX;
int32 LandscapeY = SubIndexY*SubsectionSizeQuads + ComponentIndexY*ComponentSizeQuads + SubY;
checkSlow( LandscapeX >= X1 && LandscapeX <= X2 );
checkSlow( LandscapeY >= Y1 && LandscapeY <= Y2 );
// Find the input data corresponding to this vertex
int32 DataIndex = (LandscapeX-X1) + Stride * (LandscapeY-Y1);
const T& Value = Data[DataIndex];
int32 TexX = WeightmapOffsetX + (SubsectionSizeQuads+1) * SubIndexX + SubX;
int32 TexY = WeightmapOffsetY + (SubsectionSizeQuads+1) * SubIndexY + SubY;
FColor& TexData = XYOffsetTextureData[ TexX + TexY * SizeU ];
uint16 XOffset = FMath::Clamp<uint16>(Value.X * LANDSCAPE_INV_XYOFFSET_SCALE + 32768.0f, 0, 65535);
uint16 YOffset = FMath::Clamp<uint16>(Value.Y * LANDSCAPE_INV_XYOFFSET_SCALE + 32768.0f, 0, 65535);
TexData.R = XOffset >> 8;
TexData.G = XOffset & 255;
TexData.B = YOffset >> 8;
TexData.A = YOffset & 255;
}
}
// Record the areas of the texture we need to re-upload
int32 TexX1 = WeightmapOffsetX + (SubsectionSizeQuads+1) * SubIndexX + SubX1;
int32 TexY1 = WeightmapOffsetY + (SubsectionSizeQuads+1) * SubIndexY + SubY1;
int32 TexX2 = WeightmapOffsetX + (SubsectionSizeQuads+1) * SubIndexX + SubX2;
int32 TexY2 = WeightmapOffsetY + (SubsectionSizeQuads+1) * SubIndexY + SubY2;
TexDataInfo->AddMipUpdateRegion(0,TexX1,TexY1,TexX2,TexY2);
}
}
// Update mipmaps
int32 NumMips = XYOffsetTexture->Source.GetNumMips();
TArray<FColor*> TextureMipData;
TextureMipData.AddUninitialized(NumMips);
for( int32 MipIdx=0;MipIdx<NumMips;MipIdx++ )
{
TextureMipData[MipIdx] = (FColor*)TexDataInfo->GetMipData(MipIdx);
}
ULandscapeComponent::UpdateWeightmapMips(ComponentNumSubsections, SubsectionSizeQuads, XYOffsetTexture, TextureMipData, ComponentX1, ComponentY1, ComponentX2, ComponentY2, TexDataInfo);
}
}
}
void FLandscapeEditDataInterface::SetXYOffsetData(int32 X1, int32 Y1, int32 X2, int32 Y2, const FVector2D* Data, int32 Stride)
{
SetXYOffsetDataTempl<FVector2D>(X1, Y1, X2, Y2, Data, Stride);
}
void FLandscapeEditDataInterface::SetXYOffsetData(int32 X1, int32 Y1, int32 X2, int32 Y2, const FVector* Data, int32 Stride)
{
SetXYOffsetDataTempl<FVector>(X1, Y1, X2, Y2, Data, Stride);
}
FVector2D FLandscapeEditDataInterface::GetXYOffsetmapData(const ULandscapeComponent* Component, int32 TexU, int32 TexV, FColor* TextureData/* = NULL*/)
{
check(Component);
if (!TextureData && Component->XYOffsetmapTexture)
{
FLandscapeTextureDataInfo* TexDataInfo = GetTextureDataInfo(Component->XYOffsetmapTexture);
TextureData = (FColor*)TexDataInfo->GetMipData(0);
}
if (TextureData)
{
int32 SizeU = Component->NumSubsections * (Component->SubsectionSizeQuads + 1);
int32 SizeV = Component->NumSubsections * (Component->SubsectionSizeQuads + 1);
int32 TexX = TexU;
int32 TexY = TexV;
FColor& TexData = TextureData[ TexX + TexY * SizeU ];
return FVector2D(((TexData.R * 256.0 + TexData.G) - 32768.0) * LANDSCAPE_XYOFFSET_SCALE, ((TexData.B * 256.0 + TexData.A) - 32768.0) * LANDSCAPE_XYOFFSET_SCALE );
}
return FVector2D::ZeroVector;
}
// XYOffset Interpolation version
template<typename TStoreData>
void FLandscapeEditDataInterface::GetXYOffsetDataTempl(int32& ValidX1, int32& ValidY1, int32& ValidX2, int32& ValidY2, TStoreData& StoreData)
{
// Copy variables
int32 X1 = ValidX1, X2 = ValidX2, Y1 = ValidY1, Y2 = ValidY2;
ValidX1 = INT_MAX; ValidX2 = INT_MIN; ValidY1 = INT_MAX; ValidY2 = INT_MIN;
int32 ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2;
ALandscape::CalcComponentIndicesOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2);
int32 ComponentSizeX = ComponentIndexX2 - ComponentIndexX1 + 1;
int32 ComponentSizeY = ComponentIndexY2 - ComponentIndexY1 + 1;
// Neighbor Components
ULandscapeComponent* BorderComponent[4] = { 0, 0, 0, 0 };
ULandscapeComponent* CornerComponent[4] = { 0, 0, 0, 0 };
bool NoBorderX1 = false, NoBorderX2 = false;
TArray<bool> NoBorderY1, NoBorderY2, ComponentDataExist;
TArray<ULandscapeComponent*> BorderComponentY1, BorderComponentY2;
ComponentDataExist.Empty(ComponentSizeX*ComponentSizeY);
ComponentDataExist.AddZeroed(ComponentSizeX*ComponentSizeY);
bool bHasMissingValue = false;
FLandscapeTextureDataInfo* NeighborTexDataInfo[4] = { 0, 0, 0, 0 };
FColor* NeighborXYOffsetmapTextureData[4] = { 0, 0, 0, 0 };
FVector2D CornerValues[4] = { FVector2D::ZeroVector, FVector2D::ZeroVector, FVector2D::ZeroVector, FVector2D::ZeroVector };
int32 EdgeCoord = (SubsectionSizeQuads + 1) * ComponentNumSubsections - 1; //ComponentSizeQuads;
TArray<FColor> EmptyXYOffset;
int32 XYOffsetSize = (LandscapeInfo->SubsectionSizeQuads + 1) * LandscapeInfo->ComponentNumSubsections;
XYOffsetSize = XYOffsetSize * XYOffsetSize;
EmptyXYOffset.Empty(XYOffsetSize);
for (int32 i = 0; i < XYOffsetSize; ++i)
{
EmptyXYOffset.Add(FColor(128, 0, 128, 0));
}
// initial loop....
for (int32 ComponentIndexY = ComponentIndexY1; ComponentIndexY <= ComponentIndexY2; ComponentIndexY++)
{
NoBorderX1 = false;
NoBorderX2 = false;
BorderComponent[0] = BorderComponent[1] = NULL;
for (int32 ComponentIndexX = ComponentIndexX1; ComponentIndexX <= ComponentIndexX2; ComponentIndexX++)
{
BorderComponent[2] = BorderComponent[3] = NULL;
int32 ComponentIndexXY = ComponentSizeX*(ComponentIndexY - ComponentIndexY1) + ComponentIndexX - ComponentIndexX1;
int32 ComponentIndexXX = ComponentIndexX - ComponentIndexX1;
int32 ComponentIndexYY = ComponentIndexY - ComponentIndexY1;
ComponentDataExist[ComponentIndexXY] = false;
ULandscapeComponent* Component = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX, ComponentIndexY));
FLandscapeTextureDataInfo* TexDataInfo = NULL;
FColor* XYOffsetmapTextureData = NULL;
uint8 CornerSet = 0;
bool ExistLeft = ComponentIndexXX > 0 && ComponentDataExist[ComponentIndexXX - 1 + ComponentIndexYY * ComponentSizeX];
bool ExistUp = ComponentIndexYY > 0 && ComponentDataExist[ComponentIndexXX + (ComponentIndexYY - 1) * ComponentSizeX];
if (Component)
{
if (Component->XYOffsetmapTexture)
{
TexDataInfo = GetTextureDataInfo(Component->XYOffsetmapTexture);
XYOffsetmapTextureData = (FColor*)TexDataInfo->GetMipData(0);
}
else
{
XYOffsetmapTextureData = EmptyXYOffset.GetData();
}
ComponentDataExist[ComponentIndexXY] = true;
// Update valid region
ValidX1 = FMath::Min<int32>(Component->GetSectionBase().X, ValidX1);
ValidX2 = FMath::Max<int32>(Component->GetSectionBase().X + ComponentSizeQuads, ValidX2);
ValidY1 = FMath::Min<int32>(Component->GetSectionBase().Y, ValidY1);
ValidY2 = FMath::Max<int32>(Component->GetSectionBase().Y + ComponentSizeQuads, ValidY2);
}
else
{
if (!bHasMissingValue)
{
NoBorderY1.Empty(ComponentSizeX);
NoBorderY2.Empty(ComponentSizeX);
NoBorderY1.AddZeroed(ComponentSizeX);
NoBorderY2.AddZeroed(ComponentSizeX);
BorderComponentY1.Empty(ComponentSizeX);
BorderComponentY2.Empty(ComponentSizeX);
BorderComponentY1.AddZeroed(ComponentSizeX);
BorderComponentY2.AddZeroed(ComponentSizeX);
bHasMissingValue = true;
}
// Search for neighbor component for interpolation
bool bShouldSearchX = (BorderComponent[1] && BorderComponent[1]->GetSectionBase().X / ComponentSizeQuads <= ComponentIndexX);
bool bShouldSearchY = (BorderComponentY2[ComponentIndexXX] && BorderComponentY2[ComponentIndexXX]->GetSectionBase().Y / ComponentSizeQuads <= ComponentIndexY);
// Search for left-closest component
if (bShouldSearchX || (!NoBorderX1 && !BorderComponent[0]))
{
NoBorderX1 = true;
for (int32 X = ComponentIndexX - 1; X >= ComponentIndexX1; X--)
{
BorderComponent[0] = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(X, ComponentIndexY));
if (BorderComponent[0])
{
NoBorderX1 = false;
if (BorderComponent[0]->XYOffsetmapTexture)
{
NeighborTexDataInfo[0] = GetTextureDataInfo(BorderComponent[0]->XYOffsetmapTexture);
NeighborXYOffsetmapTextureData[0] = (FColor*)NeighborTexDataInfo[0]->GetMipData(0);
}
else
{
NeighborXYOffsetmapTextureData[0] = EmptyXYOffset.GetData();
}
break;
}
}
}
// Search for right-closest component
if (bShouldSearchX || (!NoBorderX2 && !BorderComponent[1]))
{
NoBorderX2 = true;
for (int32 X = ComponentIndexX + 1; X <= ComponentIndexX2; X++)
{
BorderComponent[1] = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(X, ComponentIndexY));
if (BorderComponent[1])
{
NoBorderX2 = false;
if (BorderComponent[1]->XYOffsetmapTexture)
{
NeighborTexDataInfo[1] = GetTextureDataInfo(BorderComponent[1]->XYOffsetmapTexture);
NeighborXYOffsetmapTextureData[1] = (FColor*)NeighborTexDataInfo[1]->GetMipData(0);
}
else
{
NeighborXYOffsetmapTextureData[1] = EmptyXYOffset.GetData();
}
break;
}
}
}
// Search for up-closest component
if (bShouldSearchY || (!NoBorderY1[ComponentIndexXX] && !BorderComponentY1[ComponentIndexXX]))
{
NoBorderY1[ComponentIndexXX] = true;
for (int32 Y = ComponentIndexY - 1; Y >= ComponentIndexY1; Y--)
{
BorderComponentY1[ComponentIndexXX] = BorderComponent[2] = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX, Y));
if (BorderComponent[2])
{
NoBorderY1[ComponentIndexXX] = false;
if (BorderComponent[2]->XYOffsetmapTexture)
{
NeighborTexDataInfo[2] = GetTextureDataInfo(BorderComponent[2]->XYOffsetmapTexture);
NeighborXYOffsetmapTextureData[2] = (FColor*)NeighborTexDataInfo[2]->GetMipData(0);
}
else
{
NeighborXYOffsetmapTextureData[2] = EmptyXYOffset.GetData();
}
break;
}
}
}
else
{
BorderComponent[2] = BorderComponentY1[ComponentIndexXX];
if (BorderComponent[2])
{
if (BorderComponent[2]->XYOffsetmapTexture)
{
NeighborTexDataInfo[2] = GetTextureDataInfo(BorderComponent[2]->XYOffsetmapTexture);
NeighborXYOffsetmapTextureData[2] = (FColor*)NeighborTexDataInfo[2]->GetMipData(0);
}
else
{
NeighborXYOffsetmapTextureData[2] = EmptyXYOffset.GetData();
}
}
}
// Search for bottom-closest component
if (bShouldSearchY || (!NoBorderY2[ComponentIndexXX] && !BorderComponentY2[ComponentIndexXX]))
{
NoBorderY2[ComponentIndexXX] = true;
for (int32 Y = ComponentIndexY + 1; Y <= ComponentIndexY2; Y++)
{
BorderComponentY2[ComponentIndexXX] = BorderComponent[3] = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX, Y));
if (BorderComponent[3])
{
NoBorderY2[ComponentIndexXX] = false;
if (BorderComponent[3]->XYOffsetmapTexture)
{
NeighborTexDataInfo[3] = GetTextureDataInfo(BorderComponent[3]->XYOffsetmapTexture);
NeighborXYOffsetmapTextureData[3] = (FColor*)NeighborTexDataInfo[3]->GetMipData(0);
}
else
{
NeighborXYOffsetmapTextureData[3] = EmptyXYOffset.GetData();
}
break;
}
}
}
else
{
BorderComponent[3] = BorderComponentY2[ComponentIndexXX];
if (BorderComponent[3])
{
if (BorderComponent[3]->XYOffsetmapTexture)
{
NeighborTexDataInfo[3] = GetTextureDataInfo(BorderComponent[3]->XYOffsetmapTexture);
NeighborXYOffsetmapTextureData[3] = (FColor*)NeighborTexDataInfo[3]->GetMipData(0);
}
else
{
NeighborXYOffsetmapTextureData[3] = EmptyXYOffset.GetData();
}
}
}
CornerComponent[0] = ComponentIndexX >= ComponentIndexX1 && ComponentIndexY >= ComponentIndexY1 ?
LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint((ComponentIndexX - 1), (ComponentIndexY - 1))) : NULL;
CornerComponent[1] = ComponentIndexX <= ComponentIndexX2 && ComponentIndexY >= ComponentIndexY1 ?
LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint((ComponentIndexX + 1), (ComponentIndexY - 1))) : NULL;
CornerComponent[2] = ComponentIndexX >= ComponentIndexX1 && ComponentIndexY <= ComponentIndexY2 ?
LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint((ComponentIndexX - 1), (ComponentIndexY + 1))) : NULL;
CornerComponent[3] = ComponentIndexX <= ComponentIndexX2 && ComponentIndexY <= ComponentIndexY2 ?
LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint((ComponentIndexX + 1), (ComponentIndexY + 1))) : NULL;
if (CornerComponent[0])
{
CornerSet |= 1;
CornerValues[0] = GetXYOffsetmapData(CornerComponent[0], EdgeCoord, EdgeCoord);
}
else if ((ExistLeft || ExistUp) && X1 <= ComponentIndexX*ComponentSizeQuads && Y1 <= ComponentIndexY*ComponentSizeQuads)
{
CornerSet |= 1;
CornerValues[0] = FVector2D(StoreData.Load(ComponentIndexX*ComponentSizeQuads, ComponentIndexY*ComponentSizeQuads));
}
else if (BorderComponent[0])
{
CornerSet |= 1;
CornerValues[0] = GetXYOffsetmapData(BorderComponent[0], EdgeCoord, 0, NeighborXYOffsetmapTextureData[0]);
}
else if (BorderComponent[2])
{
CornerSet |= 1;
CornerValues[0] = GetXYOffsetmapData(BorderComponent[2], 0, EdgeCoord, NeighborXYOffsetmapTextureData[2]);
}
if (CornerComponent[1])
{
CornerSet |= 1 << 1;
CornerValues[1] = GetXYOffsetmapData(CornerComponent[1], 0, EdgeCoord);
}
else if (ExistUp && X2 >= (ComponentIndexX + 1)*ComponentSizeQuads)
{
CornerSet |= 1 << 1;
CornerValues[1] = FVector2D(StoreData.Load((ComponentIndexX + 1)*ComponentSizeQuads, ComponentIndexY*ComponentSizeQuads));
}
else if (BorderComponent[1])
{
CornerSet |= 1 << 1;
CornerValues[1] = GetXYOffsetmapData(BorderComponent[1], 0, 0, NeighborXYOffsetmapTextureData[1]);
}
else if (BorderComponent[2])
{
CornerSet |= 1 << 1;
CornerValues[1] = GetXYOffsetmapData(BorderComponent[2], EdgeCoord, EdgeCoord, NeighborXYOffsetmapTextureData[2]);
}
if (CornerComponent[2])
{
CornerSet |= 1 << 2;
CornerValues[2] = GetXYOffsetmapData(CornerComponent[2], EdgeCoord, 0);
}
else if (ExistLeft && Y2 >= (ComponentIndexY + 1)*ComponentSizeQuads) // Use data already stored for 0, 2
{
CornerSet |= 1 << 2;
CornerValues[2] = FVector2D(StoreData.Load(ComponentIndexX*ComponentSizeQuads, (ComponentIndexY + 1)*ComponentSizeQuads));
}
else if (BorderComponent[0])
{
CornerSet |= 1 << 2;
CornerValues[2] = GetXYOffsetmapData(BorderComponent[0], EdgeCoord, EdgeCoord, NeighborXYOffsetmapTextureData[0]);
}
else if (BorderComponent[3])
{
CornerSet |= 1 << 2;
CornerValues[2] = GetXYOffsetmapData(BorderComponent[3], 0, 0, NeighborXYOffsetmapTextureData[3]);
}
if (CornerComponent[3])
{
CornerSet |= 1 << 3;
CornerValues[3] = GetXYOffsetmapData(CornerComponent[3], 0, 0);
}
else if (BorderComponent[1])
{
CornerSet |= 1 << 3;
CornerValues[3] = GetXYOffsetmapData(BorderComponent[1], 0, EdgeCoord, NeighborXYOffsetmapTextureData[1]);
}
else if (BorderComponent[3])
{
CornerSet |= 1 << 3;
CornerValues[3] = GetXYOffsetmapData(BorderComponent[3], EdgeCoord, 0, NeighborXYOffsetmapTextureData[3]);
}
FillCornerValues(CornerSet, CornerValues);
ComponentDataExist[ComponentIndexXY] = ExistLeft || ExistUp || (BorderComponent[0] || BorderComponent[1] || BorderComponent[2] || BorderComponent[3]) || CornerSet;
}
if (!ComponentDataExist[ComponentIndexXY])
{
continue;
}
// Find coordinates of box that lies inside component
int32 ComponentX1 = FMath::Clamp<int32>(X1 - ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY1 = FMath::Clamp<int32>(Y1 - ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentX2 = FMath::Clamp<int32>(X2 - ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY2 = FMath::Clamp<int32>(Y2 - ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
// Find subsection range for this box
int32 SubIndexX1 = FMath::Clamp<int32>((ComponentX1 - 1) / SubsectionSizeQuads, 0, ComponentNumSubsections - 1); // -1 because we need to pick up vertices shared between subsections
int32 SubIndexY1 = FMath::Clamp<int32>((ComponentY1 - 1) / SubsectionSizeQuads, 0, ComponentNumSubsections - 1);
int32 SubIndexX2 = FMath::Clamp<int32>(ComponentX2 / SubsectionSizeQuads, 0, ComponentNumSubsections - 1);
int32 SubIndexY2 = FMath::Clamp<int32>(ComponentY2 / SubsectionSizeQuads, 0, ComponentNumSubsections - 1);
for (int32 SubIndexY = SubIndexY1; SubIndexY <= SubIndexY2; SubIndexY++)
{
for (int32 SubIndexX = SubIndexX1; SubIndexX <= SubIndexX2; SubIndexX++)
{
// Find coordinates of box that lies inside subsection
int32 SubX1 = FMath::Clamp<int32>(ComponentX1 - SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY1 = FMath::Clamp<int32>(ComponentY1 - SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
int32 SubX2 = FMath::Clamp<int32>(ComponentX2 - SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY2 = FMath::Clamp<int32>(ComponentY2 - SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
// Update texture data for the box that lies inside subsection
for (int32 SubY = SubY1; SubY <= SubY2; SubY++)
{
for (int32 SubX = SubX1; SubX <= SubX2; SubX++)
{
int32 LandscapeX = SubIndexX*SubsectionSizeQuads + ComponentIndexX*ComponentSizeQuads + SubX;
int32 LandscapeY = SubIndexY*SubsectionSizeQuads + ComponentIndexY*ComponentSizeQuads + SubY;
// Find the input data corresponding to this vertex
if (Component)
{
// Find the texture data corresponding to this vertex
FVector2D XYOffset = GetXYOffsetmapData(Component, (SubsectionSizeQuads + 1) * SubIndexX + SubX, (SubsectionSizeQuads + 1) * SubIndexY + SubY, XYOffsetmapTextureData);
StoreData.Store(LandscapeX, LandscapeY, XYOffset);
}
else
{
// Find the texture data corresponding to this vertex
FVector2D Value[4] = { FVector2D::ZeroVector, FVector2D::ZeroVector, FVector2D::ZeroVector, FVector2D::ZeroVector };
int32 Dist[4] = { INT_MAX, INT_MAX, INT_MAX, INT_MAX };
FVector2D ValueX = FVector2D::ZeroVector, ValueY = FVector2D::ZeroVector;
bool Exist[4] = { false, false, false, false };
// Use data already stored for 0, 2
if (ExistLeft)
{
Value[0] = FVector2D(StoreData.Load(ComponentIndexX*ComponentSizeQuads, LandscapeY));
Dist[0] = LandscapeX - (ComponentIndexX*ComponentSizeQuads);
Exist[0] = true;
}
else if (BorderComponent[0])
{
Value[0] = GetXYOffsetmapData(BorderComponent[0], EdgeCoord, (SubsectionSizeQuads + 1) * SubIndexY + SubY, NeighborXYOffsetmapTextureData[0]);
Dist[0] = LandscapeX - (BorderComponent[0]->GetSectionBase().X + ComponentSizeQuads);
Exist[0] = true;
}
else
{
if ((CornerSet & 1) && (CornerSet & 1 << 2))
{
int32 Dist1 = LandscapeY - (ComponentIndexY*ComponentSizeQuads);
int32 Dist2 = ((ComponentIndexY + 1)*ComponentSizeQuads) - LandscapeY;
Value[0] = (Dist2 * CornerValues[0] + Dist1 * CornerValues[2]) / (Dist1 + Dist2);
Dist[0] = LandscapeX - (ComponentIndexX*ComponentSizeQuads);
Exist[0] = true;
}
}
if (BorderComponent[1])
{
Value[1] = GetXYOffsetmapData(BorderComponent[1], 0, (SubsectionSizeQuads + 1) * SubIndexY + SubY, NeighborXYOffsetmapTextureData[1]);
Dist[1] = (BorderComponent[1]->GetSectionBase().X) - LandscapeX;
Exist[1] = true;
}
else
{
if ((CornerSet & 1 << 1) && (CornerSet & 1 << 3))
{
int32 Dist1 = LandscapeY - (ComponentIndexY*ComponentSizeQuads);
int32 Dist2 = ((ComponentIndexY + 1)*ComponentSizeQuads) - LandscapeY;
Value[1] = (Dist2 * CornerValues[1] + Dist1 * CornerValues[3]) / (Dist1 + Dist2);
Dist[1] = (ComponentIndexX + 1)*ComponentSizeQuads - LandscapeX;
Exist[1] = true;
}
}
if (ExistUp)
{
Value[2] = FVector2D(StoreData.Load(LandscapeX, ComponentIndexY*ComponentSizeQuads));
Dist[2] = LandscapeY - (ComponentIndexY*ComponentSizeQuads);
Exist[2] = true;
}
else if (BorderComponent[2])
{
Value[2] = GetXYOffsetmapData(BorderComponent[2], (SubsectionSizeQuads + 1) * SubIndexX + SubX, EdgeCoord, NeighborXYOffsetmapTextureData[2]);
Dist[2] = LandscapeY - (BorderComponent[2]->GetSectionBase().Y + ComponentSizeQuads);
Exist[2] = true;
}
else
{
if ((CornerSet & 1) && (CornerSet & 1 << 1))
{
int32 Dist1 = LandscapeX - (ComponentIndexX*ComponentSizeQuads);
int32 Dist2 = (ComponentIndexX + 1)*ComponentSizeQuads - LandscapeX;
Value[2] = (Dist2 * CornerValues[0] + Dist1 * CornerValues[1]) / (Dist1 + Dist2);
Dist[2] = LandscapeY - (ComponentIndexY*ComponentSizeQuads);
Exist[2] = true;
}
}
if (BorderComponent[3])
{
Value[3] = GetXYOffsetmapData(BorderComponent[3], (SubsectionSizeQuads + 1) * SubIndexX + SubX, 0, NeighborXYOffsetmapTextureData[3]);
Dist[3] = (BorderComponent[3]->GetSectionBase().Y) - LandscapeY;
Exist[3] = true;
}
else
{
if ((CornerSet & 1 << 2) && (CornerSet & 1 << 3))
{
int32 Dist1 = LandscapeX - (ComponentIndexX*ComponentSizeQuads);
int32 Dist2 = (ComponentIndexX + 1)*ComponentSizeQuads - LandscapeX;
Value[3] = (Dist2 * CornerValues[2] + Dist1 * CornerValues[3]) / (Dist1 + Dist2);
Dist[3] = (ComponentIndexY + 1)*ComponentSizeQuads - LandscapeY;
Exist[3] = true;
}
}
CalcInterpValue<FVector2D>(Dist, Exist, Value, ValueX, ValueY);
FVector2D FinalValue = FVector2D::ZeroVector; // Default Value
if ((Exist[0] || Exist[1]) && (Exist[2] || Exist[3]))
{
FinalValue = CalcValueFromValueXY<FVector2D>(Dist, ValueX, ValueY, CornerSet, CornerValues);
}
else if ((BorderComponent[0] || BorderComponent[1]))
{
FinalValue = ValueX;
}
else if ((BorderComponent[2] || BorderComponent[3]))
{
FinalValue = ValueY;
}
else if ((Exist[0] || Exist[1]))
{
FinalValue = ValueX;
}
else if ((Exist[2] || Exist[3]))
{
FinalValue = ValueY;
}
StoreData.Store(LandscapeX, LandscapeY, FinalValue);
//StoreData.StoreDefault(LandscapeX, LandscapeY);
}
}
}
}
}
}
}
if (bHasMissingValue)
{
CalcMissingValues<FVector2D, TStoreData, FVector2D>(X1, X2, Y1, Y2,
ComponentIndexX1, ComponentIndexX2, ComponentIndexY1, ComponentIndexY2,
ComponentSizeX, ComponentSizeY, CornerValues,
NoBorderY1, NoBorderY2, ComponentDataExist, StoreData);
// Update valid region
ValidX1 = FMath::Max<int32>(X1, ValidX1);
ValidX2 = FMath::Min<int32>(X2, ValidX2);
ValidY1 = FMath::Max<int32>(Y1, ValidY1);
ValidY2 = FMath::Min<int32>(Y2, ValidY2);
}
else
{
ValidX1 = X1;
ValidX2 = X2;
ValidY1 = Y1;
ValidY2 = Y2;
}
}
void FLandscapeEditDataInterface::GetXYOffsetData(int32& X1, int32& Y1, int32& X2, int32& Y2, FVector2D* Data, int32 Stride)
{
if (Stride == 0)
{
Stride = (1 + X2 - X1);
}
TArrayStoreData<FVector2D> ArrayStoreData(X1, Y1, Data, Stride);
GetXYOffsetDataTempl(X1, Y1, X2, Y2, ArrayStoreData);
}
void FLandscapeEditDataInterface::GetXYOffsetData(int32& X1, int32& Y1, int32& X2, int32& Y2, TMap<FIntPoint, FVector2D>& SparseData)
{
TSparseStoreData<FVector2D> SparseStoreData(SparseData);
GetXYOffsetDataTempl(X1, Y1, X2, Y2, SparseStoreData);
}
void FLandscapeEditDataInterface::GetXYOffsetData(int32& X1, int32& Y1, int32& X2, int32& Y2, FVector* Data, int32 Stride)
{
if (Stride == 0)
{
Stride = (1 + X2 - X1);
}
TArrayStoreData<FVector> ArrayStoreData(X1, Y1, Data, Stride);
GetXYOffsetDataTempl(X1, Y1, X2, Y2, ArrayStoreData);
}
void FLandscapeEditDataInterface::GetXYOffsetData(int32& X1, int32& Y1, int32& X2, int32& Y2, TMap<FIntPoint, FVector>& SparseData)
{
TSparseStoreData<FVector> SparseStoreData(SparseData);
GetXYOffsetDataTempl(X1, Y1, X2, Y2, SparseStoreData);
}
template<typename TStoreData>
void FLandscapeEditDataInterface::GetXYOffsetDataTemplFast(const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, TStoreData& StoreData)
{
int32 ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2;
ALandscape::CalcComponentIndicesNoOverlap(X1, Y1, X2, Y2, ComponentSizeQuads, ComponentIndexX1, ComponentIndexY1, ComponentIndexX2, ComponentIndexY2);
for( int32 ComponentIndexY=ComponentIndexY1;ComponentIndexY<=ComponentIndexY2;ComponentIndexY++ )
{
for( int32 ComponentIndexX=ComponentIndexX1;ComponentIndexX<=ComponentIndexX2;ComponentIndexX++ )
{
ULandscapeComponent* Component = LandscapeInfo->XYtoComponentMap.FindRef(FIntPoint(ComponentIndexX,ComponentIndexY));
FLandscapeTextureDataInfo* TexDataInfo = NULL;
FColor* OffsetTextureData = NULL;
if( Component && Component->XYOffsetmapTexture )
{
TexDataInfo = GetTextureDataInfo(Component->XYOffsetmapTexture);
OffsetTextureData = (FColor*)TexDataInfo->GetMipData(0);
}
// Find coordinates of box that lies inside component
int32 ComponentX1 = FMath::Clamp<int32>(X1-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY1 = FMath::Clamp<int32>(Y1-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentX2 = FMath::Clamp<int32>(X2-ComponentIndexX*ComponentSizeQuads, 0, ComponentSizeQuads);
int32 ComponentY2 = FMath::Clamp<int32>(Y2-ComponentIndexY*ComponentSizeQuads, 0, ComponentSizeQuads);
// Find subsection range for this box
int32 SubIndexX1 = FMath::Clamp<int32>((ComponentX1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1); // -1 because we need to pick up vertices shared between subsections
int32 SubIndexY1 = FMath::Clamp<int32>((ComponentY1-1) / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexX2 = FMath::Clamp<int32>(ComponentX2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
int32 SubIndexY2 = FMath::Clamp<int32>(ComponentY2 / SubsectionSizeQuads,0,ComponentNumSubsections-1);
for( int32 SubIndexY=SubIndexY1;SubIndexY<=SubIndexY2;SubIndexY++ )
{
for( int32 SubIndexX=SubIndexX1;SubIndexX<=SubIndexX2;SubIndexX++ )
{
// Find coordinates of box that lies inside subsection
int32 SubX1 = FMath::Clamp<int32>(ComponentX1-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY1 = FMath::Clamp<int32>(ComponentY1-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
int32 SubX2 = FMath::Clamp<int32>(ComponentX2-SubsectionSizeQuads*SubIndexX, 0, SubsectionSizeQuads);
int32 SubY2 = FMath::Clamp<int32>(ComponentY2-SubsectionSizeQuads*SubIndexY, 0, SubsectionSizeQuads);
// Update texture data for the box that lies inside subsection
for( int32 SubY=SubY1;SubY<=SubY2;SubY++ )
{
for( int32 SubX=SubX1;SubX<=SubX2;SubX++ )
{
int32 LandscapeX = SubIndexX*SubsectionSizeQuads + ComponentIndexX*ComponentSizeQuads + SubX;
int32 LandscapeY = SubIndexY*SubsectionSizeQuads + ComponentIndexY*ComponentSizeQuads + SubY;
// Find the input data corresponding to this vertex
if( Component && OffsetTextureData )
{
FVector2D Value = GetXYOffsetmapData(Component, SubX, SubY, OffsetTextureData);
StoreData.Store(LandscapeX, LandscapeY, Value);
}
else
{
StoreData.Store(LandscapeX, LandscapeY, FVector2D(0.0f, 0.0f) );
}
}
}
}
}
}
}
}
void FLandscapeEditDataInterface::GetXYOffsetDataFast(const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, FVector2D* Data, int32 Stride)
{
if( Stride==0 )
{
Stride = (1+X2-X1);
}
TArrayStoreData<FVector2D> ArrayStoreData(X1, Y1, Data, Stride);
GetXYOffsetDataTemplFast(X1, Y1, X2, Y2, ArrayStoreData);
}
void FLandscapeEditDataInterface::GetXYOffsetDataFast(const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, TMap<FIntPoint, FVector2D>& SparseData)
{
TSparseStoreData<FVector2D> SparseStoreData(SparseData);
GetXYOffsetDataTemplFast(X1, Y1, X2, Y2, SparseStoreData);
}
void FLandscapeEditDataInterface::GetXYOffsetDataFast(const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, FVector* Data, int32 Stride)
{
if( Stride==0 )
{
Stride = (1+X2-X1);
}
TArrayStoreData<FVector> ArrayStoreData(X1, Y1, Data, Stride);
GetXYOffsetDataTemplFast(X1, Y1, X2, Y2, ArrayStoreData);
}
void FLandscapeEditDataInterface::GetXYOffsetDataFast(const int32 X1, const int32 Y1, const int32 X2, const int32 Y2, TMap<FIntPoint, FVector>& SparseData)
{
TSparseStoreData<FVector> SparseStoreData(SparseData);
GetXYOffsetDataTemplFast(X1, Y1, X2, Y2, SparseStoreData);
}
//
// FLandscapeTextureDataInfo
//
FLandscapeTextureDataInfo::FLandscapeTextureDataInfo(UTexture2D* InTexture)
: Texture(InTexture)
{
MipInfo.AddZeroed(Texture->Source.GetNumMips());
Texture->SetFlags(RF_Transactional);
Texture->TemporarilyDisableStreaming();
Texture->Modify();
}
bool FLandscapeTextureDataInfo::UpdateTextureData()
{
bool bNeedToWaitForUpdate = false;
int32 DataSize = sizeof(FColor);
if (Texture->GetPixelFormat() == PF_G8)
{
DataSize = sizeof(uint8);
}
for( int32 i=0;i<MipInfo.Num();i++ )
{
if( MipInfo[i].MipData && MipInfo[i].MipUpdateRegions.Num()>0 )
{
Texture->UpdateTextureRegions( i, MipInfo[i].MipUpdateRegions.Num(), &MipInfo[i].MipUpdateRegions[0], ((Texture->Source.GetSizeX())>>i)*DataSize, DataSize, (uint8*)MipInfo[i].MipData);
bNeedToWaitForUpdate = true;
}
}
return bNeedToWaitForUpdate;
}
FLandscapeTextureDataInfo::~FLandscapeTextureDataInfo()
{
// Unlock any mips still locked.
for( int32 i=0;i<MipInfo.Num();i++ )
{
if( MipInfo[i].MipData )
{
Texture->Source.UnlockMip(i);
MipInfo[i].MipData = NULL;
}
}
Texture->ClearFlags(RF_Transactional);
}
#endif // WITH_EDITOR