From 6fb411cee32acb8cb1f1ce5a66af58afb9d9ab1e Mon Sep 17 00:00:00 2001 From: MineBill Date: Thu, 19 Oct 2023 01:05:34 +0300 Subject: [PATCH 01/42] Fix terrain painting. --- .../Tools/Terrain/Paint/SingleLayerMode.cs | 34 +++++-------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs index ec41b5286..3bb014e24 100644 --- a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs +++ b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs @@ -1,6 +1,5 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. -using System; using FlaxEngine; namespace FlaxEditor.Tools.Terrain.Paint @@ -87,32 +86,15 @@ namespace FlaxEditor.Tools.Terrain.Paint var samplePositionLocal = p.PatchPositionLocal + new Vector3(xx * FlaxEngine.Terrain.UnitsPerVertex, 0, zz * FlaxEngine.Terrain.UnitsPerVertex); Vector3.Transform(ref samplePositionLocal, ref p.TerrainWorld, out Vector3 samplePositionWorld); + + var sample = Mathf.Clamp(p.Brush.Sample(ref brushPosition, ref samplePositionWorld), 0f, 1f); + var paintAmount = sample * strength * (1f - src[layerComponent] / 255f); - var paintAmount = p.Brush.Sample(ref brushPosition, ref samplePositionWorld) * strength; - - // Extract layer weight - byte* srcPtr = &src.R; - var srcWeight = *(srcPtr + layerComponent) / 255.0f; - - // Accumulate weight - float dstWeight = srcWeight + paintAmount; - - // Check for solid layer case - if (dstWeight >= 1.0f) - { - // Erase other layers - // TODO: maybe erase only the higher layers? - // TODO: need to erase also weights form the other splatmaps - src = Color32.Transparent; - - // Use limit value - dstWeight = 1.0f; - } - - // Modify packed weight - *(srcPtr + layerComponent) = (byte)(dstWeight * 255.0f); - - // Write back + src[layerComponent] = (byte)Mathf.Clamp(src[layerComponent] + paintAmount * 255, 0, 255); + src[(layerComponent + 1) % 4] = (byte)Mathf.Clamp(src[(layerComponent + 1) % 4] - paintAmount * 255, 0, 255); + src[(layerComponent + 2) % 4] = (byte)Mathf.Clamp(src[(layerComponent + 2) % 4] - paintAmount * 255, 0, 255); + src[(layerComponent + 3) % 4] = (byte)Mathf.Clamp(src[(layerComponent + 3) % 4] - paintAmount * 255, 0, 255); + p.TempBuffer[z * p.ModifiedSize.X + x] = src; } } From 9d1b287cd7792a09e202a2bae6584cf5369bf48e Mon Sep 17 00:00:00 2001 From: Wiktor Kocielski Date: Wed, 18 Oct 2023 13:12:10 +0300 Subject: [PATCH 02/42] Terrain-related classes exposure to scripting API --- .../Engine/ShadowsOfMordor/Builder.Jobs.cpp | 2 +- Source/Engine/Terrain/Terrain.h | 18 +-- Source/Engine/Terrain/TerrainChunk.cpp | 6 + Source/Engine/Terrain/TerrainChunk.h | 38 +++--- Source/Engine/Terrain/TerrainPatch.cpp | 5 + Source/Engine/Terrain/TerrainPatch.h | 109 ++++++++++++------ 6 files changed, 111 insertions(+), 67 deletions(-) diff --git a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp index 11d136d62..8aed89eb3 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp @@ -165,7 +165,7 @@ void ShadowsOfMordor::Builder::onJobRender(GPUContext* context) Matrix::Transpose(world, shaderData.WorldMatrix); shaderData.LightmapArea = chunk->Lightmap.UVsArea; shaderData.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; - chunk->GetHeightmapUVScaleBias(&shaderData.HeightmapUVScaleBias); + shaderData.HeightmapUVScaleBias = chunk->GetHeightmapUVScaleBias(); // Extract per axis scales from LocalToWorld transform const float scaleX = Float3(world.M11, world.M12, world.M13).Length(); diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h index 636f30206..5b8465cd0 100644 --- a/Source/Engine/Terrain/Terrain.h +++ b/Source/Engine/Terrain/Terrain.h @@ -219,7 +219,7 @@ public: /// /// The patch location (x and z). /// The patch. - TerrainPatch* GetPatch(const Int2& patchCoord) const; + API_FUNCTION() TerrainPatch* GetPatch(API_PARAM(Ref) const Int2& patchCoord) const; /// /// Gets the patch at the given location. @@ -227,7 +227,7 @@ public: /// The patch location x. /// The patch location z. /// The patch. - TerrainPatch* GetPatch(int32 x, int32 z) const; + API_FUNCTION() TerrainPatch* GetPatch(int32 x, int32 z) const; /// /// Gets the zero-based index of the terrain patch in the terrain patches collection. @@ -241,7 +241,7 @@ public: /// /// The index. /// The patch. - FORCE_INLINE TerrainPatch* GetPatch(int32 index) const + API_FUNCTION() FORCE_INLINE TerrainPatch* GetPatch(int32 index) const { return _patches[index]; } @@ -344,22 +344,22 @@ public: /// /// Updates the cached bounds of the actor. Updates the cached world bounds for every patch and chunk. /// - void UpdateBounds(); + API_FUNCTION() void UpdateBounds(); /// /// Caches the neighbor chunks of this terrain. /// - void CacheNeighbors(); + API_FUNCTION() void CacheNeighbors(); /// /// Updates the collider shapes collisions/queries layer mask bits. /// - void UpdateLayerBits(); + API_FUNCTION() void UpdateLayerBits(); /// /// Removes the lightmap data from the terrain. /// - void RemoveLightmap(); + API_FUNCTION() void RemoveLightmap(); public: @@ -371,7 +371,7 @@ public: /// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything. /// The maximum distance the ray should check for collisions. /// True if ray hits an object, otherwise false. - API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const; + API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, float maxDistance = MAX_float) const; /// /// Performs a raycast against this terrain collision shape. Returns the hit chunk. @@ -382,7 +382,7 @@ public: /// The raycast result hit chunk. Valid only if raycast hits anything. /// The maximum distance the ray should check for collisions. /// True if ray hits an object, otherwise false. - bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, TerrainChunk*& resultChunk, float maxDistance = MAX_float) const; + API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, API_PARAM(Out) TerrainChunk*& resultChunk, float maxDistance = MAX_float) const; /// /// Performs a raycast against this terrain collision shape. Returns the hit chunk. diff --git a/Source/Engine/Terrain/TerrainChunk.cpp b/Source/Engine/Terrain/TerrainChunk.cpp index 737ce08f8..5088357fe 100644 --- a/Source/Engine/Terrain/TerrainChunk.cpp +++ b/Source/Engine/Terrain/TerrainChunk.cpp @@ -13,6 +13,12 @@ #include "Engine/Level/Scene/Scene.h" #include "Engine/Level/Prefabs/PrefabManager.h" + +TerrainChunk::TerrainChunk(const SpawnParams& params) + : ScriptingObject(params) +{ +} + void TerrainChunk::Init(TerrainPatch* patch, uint16 x, uint16 z) { // Initialize chunk properties diff --git a/Source/Engine/Terrain/TerrainChunk.h b/Source/Engine/Terrain/TerrainChunk.h index 884160b45..15de44a9c 100644 --- a/Source/Engine/Terrain/TerrainChunk.h +++ b/Source/Engine/Terrain/TerrainChunk.h @@ -17,8 +17,9 @@ struct RenderContext; /// /// Represents a single terrain chunk. /// -class FLAXENGINE_API TerrainChunk : public ISerializable +API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API TerrainChunk : public ScriptingObject, public ISerializable { + DECLARE_SCRIPTING_TYPE(TerrainChunk); friend Terrain; friend TerrainPatch; friend TerrainChunk; @@ -45,7 +46,7 @@ public: /// /// The material to override the terrain default one for this chunk. /// - AssetReference OverrideMaterial; + API_FIELD() AssetReference OverrideMaterial; /// /// The baked lightmap entry info for this chunk. @@ -57,7 +58,7 @@ public: /// /// Gets the x coordinate. /// - FORCE_INLINE int32 GetX() const + API_FUNCTION() FORCE_INLINE int32 GetX() const { return _x; } @@ -65,7 +66,7 @@ public: /// /// Gets the z coordinate. /// - FORCE_INLINE int32 GetZ() const + API_FUNCTION() FORCE_INLINE int32 GetZ() const { return _z; } @@ -73,7 +74,7 @@ public: /// /// Gets the patch. /// - FORCE_INLINE TerrainPatch* GetPatch() const + API_FUNCTION() FORCE_INLINE TerrainPatch* GetPatch() const { return _patch; } @@ -81,7 +82,7 @@ public: /// /// Gets the chunk world bounds. /// - FORCE_INLINE const BoundingBox& GetBounds() const + API_FUNCTION() FORCE_INLINE const BoundingBox& GetBounds() const { return _bounds; } @@ -89,7 +90,7 @@ public: /// /// Gets the chunk transformation (world to local). /// - FORCE_INLINE const Transform& GetTransform() const + API_FUNCTION() FORCE_INLINE const Transform& GetTransform() const { return _transform; } @@ -97,16 +98,15 @@ public: /// /// Gets the scale (in XY) and bias (in ZW) applied to the vertex UVs to get the chunk coordinates. /// - /// The result. - FORCE_INLINE void GetHeightmapUVScaleBias(Float4* result) const + API_FUNCTION() FORCE_INLINE const Float4& GetHeightmapUVScaleBias() const { - *result = _heightmapUVScaleBias; + return _heightmapUVScaleBias; } /// /// Determines whether this chunk has valid lightmap data. /// - FORCE_INLINE bool HasLightmap() const + API_FUNCTION() FORCE_INLINE bool HasLightmap() const { return Lightmap.TextureIndex != INVALID_INDEX; } @@ -114,7 +114,7 @@ public: /// /// Removes the lightmap data from the chunk. /// - FORCE_INLINE void RemoveLightmap() + API_FUNCTION() FORCE_INLINE void RemoveLightmap() { Lightmap.TextureIndex = INVALID_INDEX; } @@ -126,13 +126,13 @@ public: /// /// The rendering context. /// True if draw chunk, otherwise false. - bool PrepareDraw(const RenderContext& renderContext); + API_FUNCTION() bool PrepareDraw(API_PARAM(Ref) const RenderContext& renderContext); /// /// Draws the chunk (adds the draw call). Must be called after PrepareDraw. /// /// The rendering context. - void Draw(const RenderContext& renderContext) const; + API_FUNCTION() void Draw(API_PARAM(Ref) const RenderContext& renderContext) const; /// /// Draws the terrain chunk. @@ -140,7 +140,7 @@ public: /// The rendering context. /// The material to use for rendering. /// The LOD index. - void Draw(const RenderContext& renderContext, MaterialBase* material, int32 lodIndex = 0) const; + API_FUNCTION() void Draw(API_PARAM(Ref) const RenderContext& renderContext, MaterialBase* material, int32 lodIndex = 0) const; /// /// Determines if there is an intersection between the terrain chunk and a point @@ -148,22 +148,22 @@ public: /// The ray. /// The output distance. /// True if chunk intersects with the ray, otherwise false. - bool Intersects(const Ray& ray, Real& distance); + API_FUNCTION() bool Intersects(const Ray& ray, API_PARAM(Out) Real& distance); /// /// Updates the cached bounds of the chunk. /// - void UpdateBounds(); + API_FUNCTION() void UpdateBounds(); /// /// Updates the cached transform of the chunk. /// - void UpdateTransform(); + API_FUNCTION() void UpdateTransform(); /// /// Caches the neighbor chunks of this chunk. /// - void CacheNeighbors(); + API_FUNCTION() void CacheNeighbors(); public: diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 8e05062c8..f6421ff97 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -41,6 +41,11 @@ struct TerrainCollisionDataHeader float ScaleXZ; }; +TerrainPatch::TerrainPatch(const SpawnParams& params) + : ScriptingObject(params) +{ +} + void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z) { ScopeLock lock(_collisionLocker); diff --git a/Source/Engine/Terrain/TerrainPatch.h b/Source/Engine/Terrain/TerrainPatch.h index 689c629c5..103ee028b 100644 --- a/Source/Engine/Terrain/TerrainPatch.h +++ b/Source/Engine/Terrain/TerrainPatch.h @@ -15,17 +15,27 @@ class TerrainMaterialShader; /// /// Represents single terrain patch made of 16 terrain chunks. /// -class FLAXENGINE_API TerrainPatch : public ISerializable +API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API TerrainPatch : public ScriptingObject, public ISerializable { + DECLARE_SCRIPTING_TYPE(TerrainPatch); friend Terrain; friend TerrainPatch; friend TerrainChunk; public: - enum + /// + /// Various defines regarding maximum chunk counts + /// + API_ENUM() enum ChunksCount { + /// + /// The maximum allowed amount of chunks per patch + /// CHUNKS_COUNT = 16, + /// + /// The maximum allowed amount of chunks per chunk + /// CHUNKS_COUNT_EDGE = 4, }; @@ -73,12 +83,12 @@ public: /// /// The chunks contained within the patch. Organized in 4x4 square. /// - TerrainChunk Chunks[CHUNKS_COUNT]; + TerrainChunk Chunks[ChunksCount::CHUNKS_COUNT]; /// /// The heightmap texture. /// - AssetReference Heightmap; + API_FIELD() AssetReference Heightmap; /// /// The splatmap textures. @@ -91,7 +101,7 @@ public: /// Gets the Y axis heightmap offset from terrain origin. /// /// The offset. - FORCE_INLINE float GetOffsetY() const + API_FUNCTION() FORCE_INLINE float GetOffsetY() const { return _yOffset; } @@ -100,7 +110,7 @@ public: /// Gets the Y axis heightmap height. /// /// The height. - FORCE_INLINE float GetHeightY() const + API_FUNCTION() FORCE_INLINE float GetHeightY() const { return _yHeight; } @@ -109,7 +119,7 @@ public: /// Gets the x coordinate. /// /// The x position. - FORCE_INLINE int32 GetX() const + API_FUNCTION() FORCE_INLINE int32 GetX() const { return _x; } @@ -118,7 +128,7 @@ public: /// Gets the z coordinate. /// /// The z position. - FORCE_INLINE int32 GetZ() const + API_FUNCTION() FORCE_INLINE int32 GetZ() const { return _z; } @@ -127,7 +137,7 @@ public: /// Gets the terrain. /// /// The terrain, - FORCE_INLINE Terrain* GetTerrain() const + API_FUNCTION() FORCE_INLINE Terrain* GetTerrain() const { return _terrain; } @@ -137,7 +147,7 @@ public: /// /// The chunk zero-based index. /// The chunk. - TerrainChunk* GetChunk(int32 index) + API_FUNCTION() TerrainChunk* GetChunk(int32 index) { if (index < 0 || index >= CHUNKS_COUNT) return nullptr; @@ -149,7 +159,7 @@ public: /// /// The chunk location (x and z). /// The chunk. - TerrainChunk* GetChunk(const Int2& chunkCoord) + API_FUNCTION() TerrainChunk* GetChunk(API_PARAM(Ref) const Int2& chunkCoord) { return GetChunk(chunkCoord.Y * CHUNKS_COUNT_EDGE + chunkCoord.X); } @@ -160,16 +170,28 @@ public: /// The chunk location x. /// The chunk location z. /// The chunk. - TerrainChunk* GetChunk(int32 x, int32 z) + API_FUNCTION() TerrainChunk* GetChunk(int32 x, int32 z) { return GetChunk(z * CHUNKS_COUNT_EDGE + x); } + /// + /// Gets the splatmap assigned to this patch. + /// + /// Splatmap index. + /// Splatmap texture. + API_FUNCTION() AssetReference GetSplatmap(int32 index) + { + if (index < 0 || index >= TERRAIN_MAX_SPLATMAPS_COUNT) + return nullptr; + return Splatmap[index]; + } + /// /// Gets the patch world bounds. /// /// The bounding box. - FORCE_INLINE const BoundingBox& GetBounds() const + API_FUNCTION() FORCE_INLINE const BoundingBox& GetBounds() const { return _bounds; } @@ -179,17 +201,28 @@ public: /// /// Removes the lightmap data from the terrain patch. /// - void RemoveLightmap(); + API_FUNCTION() void RemoveLightmap(); /// /// Updates the cached bounds of the patch and child chunks. /// - void UpdateBounds(); + API_FUNCTION() void UpdateBounds(); /// /// Updates the cached transform of the patch and child chunks. /// - void UpdateTransform(); + API_FUNCTION() void UpdateTransform(); + + /// + /// Assigns a splatmap to this patch. + /// + /// Splatmap index. + /// Splatmap texture. + API_FUNCTION() void SetSplatmap(int32 index, const AssetReference& splatMap) + { + if (index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT) + Splatmap[index] = splatMap; + } #if TERRAIN_EDITING @@ -197,7 +230,7 @@ public: /// Initializes the patch heightmap and collision to the default flat level. /// /// True if failed, otherwise false. - bool InitializeHeightMap(); + API_FUNCTION() bool InitializeHeightMap(); /// /// Setups the terrain patch using the specified heightmap data. @@ -207,7 +240,7 @@ public: /// The holes mask (optional). Normalized to 0-1 range values with holes mask per-vertex. Must match the heightmap dimensions. /// If set to true patch will use virtual storage by force. Otherwise it can use normal texture asset storage on drive (valid only during Editor). Runtime-created terrain can only use virtual storage (in RAM). /// True if failed, otherwise false. - bool SetupHeightMap(int32 heightMapLength, const float* heightMap, const byte* holesMask = nullptr, bool forceUseVirtualStorage = false); + API_FUNCTION() bool SetupHeightMap(int32 heightMapLength, API_PARAM(Ref) const float* heightMap, API_PARAM(Ref) const byte* holesMask = nullptr, bool forceUseVirtualStorage = false); /// /// Setups the terrain patch layer weights using the specified splatmaps data. @@ -217,7 +250,7 @@ public: /// The splat map. Each array item contains 4 layer weights. /// If set to true patch will use virtual storage by force. Otherwise it can use normal texture asset storage on drive (valid only during Editor). Runtime-created terrain can only use virtual storage (in RAM). /// True if failed, otherwise false. - bool SetupSplatMap(int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage = false); + API_FUNCTION() bool SetupSplatMap(int32 index, int32 splatMapLength, API_PARAM(Ref) const Color32* splatMap, bool forceUseVirtualStorage = false); #endif @@ -227,40 +260,40 @@ public: /// Gets the raw pointer to the heightmap data. /// /// The heightmap data. - float* GetHeightmapData(); + API_FUNCTION() float* GetHeightmapData(); /// /// Clears cache of the heightmap data. /// - void ClearHeightmapCache(); + API_FUNCTION() void ClearHeightmapCache(); /// /// Gets the raw pointer to the holes mask data. /// /// The holes mask data. - byte* GetHolesMaskData(); + API_FUNCTION() byte* GetHolesMaskData(); /// /// Clears cache of the holes mask data. /// - void ClearHolesMaskCache(); + API_FUNCTION() void ClearHolesMaskCache(); /// /// Gets the raw pointer to the splat map data. /// /// The zero-based index of the splatmap texture. /// The splat map data. - Color32* GetSplatMapData(int32 index); + API_FUNCTION() Color32* GetSplatMapData(int32 index); /// /// Clears cache of the splat map data. /// - void ClearSplatMapCache(); + API_FUNCTION() void ClearSplatMapCache(); /// /// Clears all caches. /// - void ClearCache(); + API_FUNCTION() void ClearCache(); /// /// Modifies the terrain patch heightmap with the given samples. @@ -269,7 +302,7 @@ public: /// The offset from the first row and column of the heightmap data (offset destination x and z start position). /// The size of the heightmap to modify (x and z). Amount of samples in each direction. /// True if failed, otherwise false. - bool ModifyHeightMap(const float* samples, const Int2& modifiedOffset, const Int2& modifiedSize); + API_FUNCTION() bool ModifyHeightMap(API_PARAM(Ref) const float* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize); /// /// Modifies the terrain patch holes mask with the given samples. @@ -278,7 +311,7 @@ public: /// The offset from the first row and column of the holes map data (offset destination x and z start position). /// The size of the holes map to modify (x and z). Amount of samples in each direction. /// True if failed, otherwise false. - bool ModifyHolesMask(const byte* samples, const Int2& modifiedOffset, const Int2& modifiedSize); + API_FUNCTION() bool ModifyHolesMask(API_PARAM(Ref) const byte* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize); /// /// Modifies the terrain patch splat map (layers mask) with the given samples. @@ -288,7 +321,7 @@ public: /// The offset from the first row and column of the splat map data (offset destination x and z start position). /// The size of the splat map to modify (x and z). Amount of samples in each direction. /// True if failed, otherwise false. - bool ModifySplatMap(int32 index, const Color32* samples, const Int2& modifiedOffset, const Int2& modifiedSize); + API_FUNCTION() bool ModifySplatMap(int32 index, API_PARAM(Ref) const Color32* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize); private: @@ -311,7 +344,7 @@ public: /// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything. /// The maximum distance the ray should check for collisions. /// True if ray hits an object, otherwise false. - bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const; + API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, float maxDistance = MAX_float) const; /// /// Performs a raycast against this terrain collision shape. @@ -322,7 +355,7 @@ public: /// The raycast result hit position normal vector. Valid only if raycast hits anything. /// The maximum distance the ray should check for collisions. /// True if ray hits an object, otherwise false. - bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, Vector3& resultHitNormal, float maxDistance = MAX_float) const; + API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, API_PARAM(Out) Vector3& resultHitNormal, float maxDistance = MAX_float) const; /// /// Performs a raycast against this terrain collision shape. Returns the hit chunk. @@ -333,7 +366,7 @@ public: /// The raycast result hit chunk. Valid only if raycast hits anything. /// The maximum distance the ray should check for collisions. /// True if ray hits an object, otherwise false. - bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, TerrainChunk*& resultChunk, float maxDistance = MAX_float) const; + API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, API_PARAM(Out) TerrainChunk*& resultChunk, float maxDistance = MAX_float) const; /// /// Performs a raycast against terrain collision, returns results in a RaycastHit structure. @@ -343,21 +376,21 @@ public: /// The result hit information. Valid only when method returns true. /// The maximum distance the ray should check for collisions. /// True if ray hits an object, otherwise false. - bool RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, float maxDistance = MAX_float) const; + API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const; /// /// Gets a point on the terrain collider that is closest to a given location. Can be used to find a hit location or position to apply explosion force or any other special effects. /// /// The position to find the closest point to it. /// The result point on the collider that is closest to the specified location. - void ClosestPoint(const Vector3& position, Vector3& result) const; + API_FUNCTION() void ClosestPoint(API_PARAM(Ref) const Vector3& position, API_PARAM(Out) Vector3& result) const; #if USE_EDITOR /// /// Updates the patch data after manual deserialization called at runtime (eg. by editor undo). /// - void UpdatePostManualDeserialization(); + API_FUNCTION() void UpdatePostManualDeserialization(); #endif @@ -369,14 +402,14 @@ public: /// Gets the collision mesh triangles array (3 vertices per triangle in linear list). Cached internally to reuse data. /// /// The collision triangles vertices list (in world-space). - const Array& GetCollisionTriangles(); + API_FUNCTION() const Array& GetCollisionTriangles(); /// /// Gets the collision mesh triangles array (3 vertices per triangle in linear list) that intersect with the given bounds. /// /// The world-space bounds to find terrain triangles that intersect with it. /// The result triangles that intersect with the given bounds (in world-space). - void GetCollisionTriangles(const BoundingSphere& bounds, Array& result); + API_FUNCTION() void GetCollisionTriangles(API_PARAM(Ref) const BoundingSphere& bounds, API_PARAM(Out) Array& result); #endif @@ -385,7 +418,7 @@ public: /// /// The output vertex buffer. /// The output index buffer. - void ExtractCollisionGeometry(Array& vertexBuffer, Array& indexBuffer); + API_FUNCTION() void ExtractCollisionGeometry(API_PARAM(Out) Array& vertexBuffer, API_PARAM(Out) Array& indexBuffer); private: From cf5f501ab7edca8ca4cc6ba541f2f1996a9d3db3 Mon Sep 17 00:00:00 2001 From: MineBill Date: Tue, 24 Oct 2023 14:31:54 +0300 Subject: [PATCH 03/42] Allow erasing layers/painting over layers from the other splatmap. Since Layers are hardcoded to 8, this commit also assumes that there will always be 2 splatmaps. --- Source/Editor/Tools/Terrain/Paint/Mode.cs | 30 +++++++++- .../Tools/Terrain/Paint/SingleLayerMode.cs | 30 +++++++--- .../Tools/Terrain/PaintTerrainGizmoMode.cs | 60 ++++++++++++++----- 3 files changed, 95 insertions(+), 25 deletions(-) diff --git a/Source/Editor/Tools/Terrain/Paint/Mode.cs b/Source/Editor/Tools/Terrain/Paint/Mode.cs index 3647691b3..bf9857bea 100644 --- a/Source/Editor/Tools/Terrain/Paint/Mode.cs +++ b/Source/Editor/Tools/Terrain/Paint/Mode.cs @@ -67,11 +67,13 @@ namespace FlaxEditor.Tools.Terrain.Paint // Prepare var splatmapIndex = ActiveSplatmapIndex; + var splatmapIndexOther = (splatmapIndex + 1) % 2; var chunkSize = terrain.ChunkSize; var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1; var heightmapLength = heightmapSize * heightmapSize; var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount; - var tempBuffer = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes).ToPointer(); + var tempBuffer = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, splatmapIndex).ToPointer(); + var tempBufferOther = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, (splatmapIndex + 1) % 2).ToPointer(); var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex; ApplyParams p = new ApplyParams { @@ -81,8 +83,10 @@ namespace FlaxEditor.Tools.Terrain.Paint Options = options, Strength = strength, SplatmapIndex = splatmapIndex, + SplatmapIndexOther = splatmapIndexOther, HeightmapSize = heightmapSize, TempBuffer = tempBuffer, + TempBufferOther = tempBufferOther, }; // Get brush bounds in terrain local space @@ -131,11 +135,16 @@ namespace FlaxEditor.Tools.Terrain.Paint var sourceData = TerrainTools.GetSplatMapData(terrain, ref patch.PatchCoord, splatmapIndex); if (sourceData == null) throw new Exception("Cannot modify terrain. Loading splatmap failed. See log for more info."); + + var sourceDataOther = TerrainTools.GetSplatMapData(terrain, ref patch.PatchCoord, splatmapIndexOther); + if (sourceDataOther == null) + throw new Exception("Cannot modify terrain. Loading splatmap failed. See log for more info."); // Record patch data before editing it if (!gizmo.CurrentEditUndoAction.HashPatch(ref patch.PatchCoord)) { gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord, splatmapIndex); + gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord, splatmapIndexOther); } // Apply modification @@ -144,6 +153,7 @@ namespace FlaxEditor.Tools.Terrain.Paint p.PatchCoord = patch.PatchCoord; p.PatchPositionLocal = patchPositionLocal; p.SourceData = sourceData; + p.SourceDataOther = sourceDataOther; Apply(ref p); } } @@ -197,16 +207,34 @@ namespace FlaxEditor.Tools.Terrain.Paint /// The splatmap texture index. /// public int SplatmapIndex; + + /// + /// The splatmap texture index. If is 0, this will be 1. + /// If is 1, this will be 0. + /// + public int SplatmapIndexOther; /// /// The temporary data buffer (for modified data). /// public Color32* TempBuffer; + + /// + /// The 'other" temporary data buffer (for modified data). If refers + /// to the splatmap with index 0, this one will refer to the one with index 1. + /// + public Color32* TempBufferOther; /// /// The source data buffer. /// public Color32* SourceData; + + /// + /// The 'other' source data buffer. If refers + /// to the splatmap with index 0, this one will refer to the one with index 1. + /// + public Color32* SourceDataOther; /// /// The heightmap size (edge). diff --git a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs index 3bb014e24..d0c76a830 100644 --- a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs +++ b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs @@ -72,7 +72,7 @@ namespace FlaxEditor.Tools.Terrain.Paint var strength = p.Strength; var layer = (int)Layer; var brushPosition = p.Gizmo.CursorPosition; - var layerComponent = layer % 4; + var c = layer % 4; // Apply brush modification Profiler.BeginEvent("Apply Brush"); @@ -82,26 +82,38 @@ namespace FlaxEditor.Tools.Terrain.Paint for (int x = 0; x < p.ModifiedSize.X; x++) { var xx = x + p.ModifiedOffset.X; - var src = p.SourceData[zz * p.HeightmapSize + xx]; + var src = (Color)p.SourceData[zz * p.HeightmapSize + xx]; var samplePositionLocal = p.PatchPositionLocal + new Vector3(xx * FlaxEngine.Terrain.UnitsPerVertex, 0, zz * FlaxEngine.Terrain.UnitsPerVertex); Vector3.Transform(ref samplePositionLocal, ref p.TerrainWorld, out Vector3 samplePositionWorld); - - var sample = Mathf.Clamp(p.Brush.Sample(ref brushPosition, ref samplePositionWorld), 0f, 1f); - var paintAmount = sample * strength * (1f - src[layerComponent] / 255f); - src[layerComponent] = (byte)Mathf.Clamp(src[layerComponent] + paintAmount * 255, 0, 255); - src[(layerComponent + 1) % 4] = (byte)Mathf.Clamp(src[(layerComponent + 1) % 4] - paintAmount * 255, 0, 255); - src[(layerComponent + 2) % 4] = (byte)Mathf.Clamp(src[(layerComponent + 2) % 4] - paintAmount * 255, 0, 255); - src[(layerComponent + 3) % 4] = (byte)Mathf.Clamp(src[(layerComponent + 3) % 4] - paintAmount * 255, 0, 255); + var sample = Mathf.Clamp(p.Brush.Sample(ref brushPosition, ref samplePositionWorld), 0f, 1f); + var paintAmount = sample * strength * (1f - src[c]); + // Paint on the active splatmap texture + src[c] = Mathf.Clamp(src[c] + paintAmount, 0, 1f); + src[(c + 1) % 4] = Mathf.Clamp(src[(c + 1) % 4] - paintAmount, 0, 1f); + src[(c + 2) % 4] = Mathf.Clamp(src[(c + 2) % 4] - paintAmount, 0, 1f); + src[(c + 3) % 4] = Mathf.Clamp(src[(c + 3) % 4] - paintAmount, 0, 1f); + p.TempBuffer[z * p.ModifiedSize.X + x] = src; + + // Remove 'paint' from the other splatmap texture + var other = (Color)p.SourceDataOther[zz * p.HeightmapSize + xx]; + + other[c] = Mathf.Clamp(other[c] - paintAmount, 0, 1f); + other[(c + 1) % 4] = Mathf.Clamp(other[(c + 1) % 4] - paintAmount, 0, 1f); + other[(c + 2) % 4] = Mathf.Clamp(other[(c + 2) % 4] - paintAmount, 0, 1f); + other[(c + 3) % 4] = Mathf.Clamp(other[(c + 3) % 4] - paintAmount, 0, 1f); + + p.TempBufferOther[z * p.ModifiedSize.X + x] = other; } } Profiler.EndEvent(); // Update terrain patch TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndex, p.TempBuffer, ref p.ModifiedOffset, ref p.ModifiedSize); + TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndexOther, p.TempBufferOther, ref p.ModifiedOffset, ref p.ModifiedSize); } } } diff --git a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs index 1d1bf87ca..05aaa48b5 100644 --- a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs +++ b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs @@ -36,9 +36,43 @@ namespace FlaxEditor.Tools.Terrain "Layer 7", }; - private IntPtr _cachedSplatmapData; - private int _cachedSplatmapDataSize; + private class SplatmapData + { + public IntPtr DataPtr { get; set; } = IntPtr.Zero; + public int Size { get; set; } = 0; + + public SplatmapData(int size) + { + EnsureCapacity(size); + } + + public void EnsureCapacity(int size) + { + if (Size < size) + { + if (DataPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(DataPtr); + } + DataPtr = Marshal.AllocHGlobal(size); + Size = size; + } + } + + /// + public void Free() + { + if (DataPtr == IntPtr.Zero) + return; + + Marshal.FreeHGlobal(DataPtr); + DataPtr = IntPtr.Zero; + Size = 0; + } + } + private EditTerrainMapAction _activeAction; + private List _cachedSplatmapData = new(); /// /// The terrain painting gizmo. @@ -230,20 +264,18 @@ namespace FlaxEditor.Tools.Terrain /// Gets the splatmap temporary scratch memory buffer used to modify terrain samples. Allocated memory is unmanaged by GC. /// /// The minimum buffer size (in bytes). + /// The splatmap index for which to return/create the temp buffer. /// The allocated memory using interface. - public IntPtr GetSplatmapTempBuffer(int size) + public IntPtr GetSplatmapTempBuffer(int size, int splatmapIndex) { - if (_cachedSplatmapDataSize < size) + if (_cachedSplatmapData.Count <= splatmapIndex) { - if (_cachedSplatmapData != IntPtr.Zero) - { - Marshal.FreeHGlobal(_cachedSplatmapData); - } - _cachedSplatmapData = Marshal.AllocHGlobal(size); - _cachedSplatmapDataSize = size; + _cachedSplatmapData.Add(new SplatmapData(size)); + return _cachedSplatmapData[splatmapIndex].DataPtr; } - return _cachedSplatmapData; + _cachedSplatmapData[splatmapIndex].EnsureCapacity(size); + return _cachedSplatmapData[splatmapIndex].DataPtr; } /// @@ -276,11 +308,9 @@ namespace FlaxEditor.Tools.Terrain base.OnDeactivated(); // Free temporary memory buffer - if (_cachedSplatmapData != IntPtr.Zero) + foreach (var splatmapData in _cachedSplatmapData) { - Marshal.FreeHGlobal(_cachedSplatmapData); - _cachedSplatmapData = IntPtr.Zero; - _cachedSplatmapDataSize = 0; + splatmapData.Free(); } } From 41d513e5d25a44bf871c5dece69124384bfb534e Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 3 Nov 2023 21:22:00 -0500 Subject: [PATCH 04/42] Add directions to slider control. --- Source/Engine/UI/GUI/Common/Slider.cs | 235 +++++++++++++++++++++----- 1 file changed, 189 insertions(+), 46 deletions(-) diff --git a/Source/Engine/UI/GUI/Common/Slider.cs b/Source/Engine/UI/GUI/Common/Slider.cs index 6dbd5082c..6332088b4 100644 --- a/Source/Engine/UI/GUI/Common/Slider.cs +++ b/Source/Engine/UI/GUI/Common/Slider.cs @@ -7,6 +7,32 @@ namespace FlaxEngine.GUI; /// public class Slider : ContainerControl { + /// + /// The slider direction + /// + public enum SliderDirection + { + /// + /// Slider direction, horizontal right + /// + HorizontalRight, + + /// + /// Slider direction, horizontal left + /// + HorizontalLeft, + + /// + /// Slider direction, vertical up + /// + VerticalUp, + + /// + /// Slider direction, vertical down + /// + VerticalDown, + } + /// /// The minimum value. /// @@ -15,26 +41,7 @@ public class Slider : ContainerControl /// /// The maximum value. /// - protected float _maximum = 100f; - - /// - /// Gets or sets the minimum value. - /// - [EditorOrder(20), Tooltip("The minimum value.")] - public float Minimum - { - get => _minimum; - set - { - if (value > _maximum) - throw new ArgumentOutOfRangeException(); - if (WholeNumbers) - value = Mathf.RoundToInt(value); - _minimum = value; - if (Value < _minimum) - Value = _minimum; - } - } + protected float _maximum = 100; /// /// Gets or sets the maximum value. @@ -45,8 +52,8 @@ public class Slider : ContainerControl get => _maximum; set { - if (value < _minimum || Mathf.IsZero(value)) - throw new ArgumentOutOfRangeException(); + //if (value < _minimum || Mathf.IsZero(value)) + //throw new ArgumentOutOfRangeException(); if (WholeNumbers) value = Mathf.RoundToInt(value); _maximum = value; @@ -55,6 +62,40 @@ public class Slider : ContainerControl } } + /// + /// Gets or sets the minimum value. + /// + [EditorOrder(20), Tooltip("The minimum value.")] + public float Minimum + { + get => _minimum; + set + { + //if (value > _maximum) + //throw new ArgumentOutOfRangeException(); + if (WholeNumbers) + value = Mathf.RoundToInt(value); + _minimum = value; + if (Value < _minimum) + Value = _minimum; + } + } + + /// + /// Gets or sets the slider direction. + /// + [EditorOrder(40), Tooltip("Slider Direction.")] + public SliderDirection Direction + { + get => _direction; + set + { + _direction = value; + UpdateThumb(); + } + } + + private SliderDirection _direction = SliderDirection.HorizontalRight; private float _value = 100f; private Rectangle _thumbRect; private float _thumbCenter; @@ -89,25 +130,61 @@ public class Slider : ContainerControl /// The local position of the thumb center /// [HideInEditor] - public Float2 ThumbCenter => new(_thumbCenter, Height / 2); + public Float2 ThumbCenter => (Direction is SliderDirection.HorizontalLeft or SliderDirection.HorizontalRight) ? new Float2(_thumbCenter, Height / 2) : new Float2(Width / 2, _thumbCenter); /// /// The local position of the beginning of the track. /// [HideInEditor] - public Float2 TrackBeginning => new(_thumbSize.X / 2, Height / 2); + public Float2 TrackBeginning + { + get + { + switch (Direction) + { + case SliderDirection.HorizontalRight: + return new Float2(_thumbSize.X / 2, Height / 2); + case SliderDirection.HorizontalLeft: + return new Float2(Width - _thumbSize.X / 2, Height / 2); + case SliderDirection.VerticalUp: + return new Float2(Width / 2, Height - _thumbSize.Y / 2); + case SliderDirection.VerticalDown: + return new Float2(Width / 2, _thumbSize.Y / 2); + default: break; + } + return Float2.Zero; + } + } /// /// The local position of the end of the track. /// [HideInEditor] - public Float2 TrackEnd => new(Width - _thumbSize.X / 2, Height / 2); - + public Float2 TrackEnd + { + get + { + switch (Direction) + { + case SliderDirection.HorizontalRight: + return new Float2(Width - _thumbSize.X / 2, Height / 2); + case SliderDirection.HorizontalLeft: + return new Float2(_thumbSize.X / 2, Height / 2); + case SliderDirection.VerticalUp: + return new Float2(Width / 2, _thumbSize.Y / 2); + case SliderDirection.VerticalDown: + return new Float2(Width / 2, Height - _thumbSize.Y / 2); + default: break; + } + return Float2.Zero; + } + } + /// /// The height of the track. /// [EditorOrder(40), Tooltip("The track height.")] - public int TrackHeight { get; set; } = 2; + public int TrackThickness { get; set; } = 2; /// /// The thumb size. @@ -147,9 +224,14 @@ public class Slider : ContainerControl public Color TrackFillLineColor { get; set; } /// - /// Gets the size of the track. + /// Gets the width of the track. /// - private float TrackWidth => Width; + private float TrackWidth => (Direction is SliderDirection.HorizontalLeft or SliderDirection.HorizontalRight) ? Width - _thumbSize.X : TrackThickness; + + /// + /// Gets the height of the track. + /// + private float TrackHeight => (Direction is SliderDirection.HorizontalLeft or SliderDirection.HorizontalRight) ? TrackThickness : Height - _thumbSize.Y; /// /// Gets or sets the brush used for slider track drawing. @@ -236,13 +318,32 @@ public class Slider : ContainerControl private void UpdateThumb() { // Cache data - float trackSize = TrackWidth; + var isHorizontal = Direction is SliderDirection.HorizontalRight or SliderDirection.HorizontalLeft; + float trackSize = isHorizontal ? Width : Height; float range = Maximum - Minimum; - float pixelRange = trackSize - _thumbSize.X; + float pixelRange = trackSize - (isHorizontal ? _thumbSize.X : _thumbSize.Y); float perc = (_value - Minimum) / range; float thumbPosition = (int)(perc * pixelRange); - _thumbCenter = thumbPosition + _thumbSize.X / 2; - _thumbRect = new Rectangle(thumbPosition, (Height - _thumbSize.Y) / 2, _thumbSize.X, _thumbSize.Y); + switch (Direction) + { + case SliderDirection.HorizontalRight: + _thumbCenter = thumbPosition + _thumbSize.X / 2; + _thumbRect = new Rectangle(thumbPosition, (Height - _thumbSize.Y) / 2, _thumbSize.X, _thumbSize.Y); + break; + case SliderDirection.VerticalDown: + _thumbCenter = thumbPosition + _thumbSize.Y / 2; + _thumbRect = new Rectangle((Width - _thumbSize.X) / 2, thumbPosition, _thumbSize.X, _thumbSize.Y); + break; + case SliderDirection.HorizontalLeft: + _thumbCenter = Width - thumbPosition - _thumbSize.X / 2; + _thumbRect = new Rectangle(Width - thumbPosition - _thumbSize.X, (Height - _thumbSize.Y) / 2, _thumbSize.X, _thumbSize.Y); + break; + case SliderDirection.VerticalUp: + _thumbCenter = Height - thumbPosition - _thumbSize.Y / 2; + _thumbRect = new Rectangle((Width - _thumbSize.X) / 2, Height - thumbPosition - _thumbSize.Y, _thumbSize.X, _thumbSize.Y); + break; + default: break; + } } private void EndSliding() @@ -256,19 +357,37 @@ public class Slider : ContainerControl public override void Draw() { base.Draw(); - + + // Set rectangles + var lineRect = new Rectangle(_thumbSize.X / 2, (Height - TrackThickness) / 2, Width - _thumbSize.X, TrackThickness); + var fillLineRect = new Rectangle(_thumbSize.X / 2 - 1, (Height - TrackThickness - 2) / 2, Width - (Width - _thumbCenter) - _thumbSize.X / 2 + 1, TrackThickness + 2); + switch (Direction) + { + case SliderDirection.HorizontalRight: + break; + case SliderDirection.VerticalDown: + lineRect = new Rectangle((Width - TrackThickness) / 2 , _thumbSize.Y / 2, TrackThickness, Height - _thumbSize.Y); + fillLineRect = new Rectangle((Width - TrackThickness - 2) / 2, _thumbSize.Y / 2 - 1, TrackThickness + 2, Height - (Height - _thumbCenter) - _thumbSize.Y / 2 + 1); + break; + case SliderDirection.HorizontalLeft: + fillLineRect = new Rectangle(Width - (Width - _thumbCenter) - 1, (Height - TrackThickness - 2) / 2, Width - _thumbCenter + 1, TrackThickness + 2); + break; + case SliderDirection.VerticalUp: + lineRect = new Rectangle((Width - TrackThickness) / 2 , _thumbSize.Y / 2, TrackThickness, Height - _thumbSize.Y); + fillLineRect = new Rectangle((Width - TrackThickness - 2) / 2, Height - (Height - _thumbCenter) - 1, TrackThickness + 2, Height - _thumbCenter + 1); + break; + default: break; + } + // Draw track line - //var lineRect = new Rectangle(4, (Height - TrackHeight) / 2, Width - 8, TrackHeight); - var lineRect = new Rectangle(_thumbSize.X / 2, (Height - TrackHeight) / 2, Width - _thumbSize.X, TrackHeight); if (TrackBrush != null) TrackBrush.Draw(lineRect, TrackLineColor); else - Render2D.FillRectangle(lineRect, TrackLineColor); - + Render2D.FillRectangle(lineRect, TrackLineColor); + // Draw track fill if (FillTrack) { - var fillLineRect = new Rectangle(_thumbSize.X / 2 - 1, (Height - TrackHeight - 2) / 2, Width - (Width - _thumbCenter) - _thumbSize.X / 2, TrackHeight + 2); Render2D.PushClip(ref fillLineRect); if (FillTrackBrush != null) FillTrackBrush.Draw(lineRect, TrackFillLineColor); @@ -276,13 +395,13 @@ public class Slider : ContainerControl Render2D.FillRectangle(lineRect, TrackFillLineColor); Render2D.PopClip(); } - + // Draw thumb - var thumbColor = _isSliding ? ThumbColorSelected : (_mouseOverThumb ? ThumbColorHighlighted : ThumbColor); + var thumbColorV = _isSliding ? ThumbColorSelected : (_mouseOverThumb ? ThumbColorHighlighted : ThumbColor); if (ThumbBrush != null) - ThumbBrush.Draw(_thumbRect, thumbColor); + ThumbBrush.Draw(_thumbRect, thumbColorV); else - Render2D.FillRectangle(_thumbRect, thumbColor); + Render2D.FillRectangle(_thumbRect, thumbColorV); } /// @@ -302,7 +421,7 @@ public class Slider : ContainerControl if (button == MouseButton.Left) { Focus(); - float mousePosition = location.X; + float mousePosition = Direction is SliderDirection.HorizontalRight or SliderDirection.HorizontalLeft ? location.X : location.Y; if (_thumbRect.Contains(ref location)) { @@ -315,7 +434,16 @@ public class Slider : ContainerControl else { // Click change - Value += (mousePosition < _thumbCenter ? -1 : 1) * 10; + switch (Direction) + { + case SliderDirection.HorizontalRight or SliderDirection.VerticalDown: + Value += (mousePosition < _thumbCenter ? -1 : 1) * 10; + break; + case SliderDirection.HorizontalLeft or SliderDirection.VerticalUp: + Value -= (mousePosition < _thumbCenter ? -1 : 1) * 10; + break; + default: break; + } } } @@ -330,7 +458,22 @@ public class Slider : ContainerControl { // Update sliding var slidePosition = location + Root.TrackingMouseOffset; - Value = Mathf.Remap(slidePosition.X, 4, TrackWidth - 4, Minimum, Maximum); + switch (Direction) + { + case SliderDirection.HorizontalRight: + Value = Mathf.Remap(slidePosition.X, 4, Width - 4, Minimum, Maximum); + break; + case SliderDirection.VerticalDown: + Value = Mathf.Remap(slidePosition.Y, 4, Height - 4, Minimum, Maximum); + break; + case SliderDirection.HorizontalLeft: + Value = Mathf.Remap(slidePosition.X, Width - 4, 4, Minimum, Maximum); + break; + case SliderDirection.VerticalUp: + Value = Mathf.Remap(slidePosition.Y, Height - 4, 4, Minimum, Maximum); + break; + default: break; + } } else { From 253c4c27ab6f6901960839ff2131bb6530247c49 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Fri, 3 Nov 2023 21:23:09 -0500 Subject: [PATCH 05/42] Remove out of range exceptions due to serialization issues. --- Source/Engine/UI/GUI/Common/Slider.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Source/Engine/UI/GUI/Common/Slider.cs b/Source/Engine/UI/GUI/Common/Slider.cs index 6332088b4..d9d998bc2 100644 --- a/Source/Engine/UI/GUI/Common/Slider.cs +++ b/Source/Engine/UI/GUI/Common/Slider.cs @@ -52,8 +52,6 @@ public class Slider : ContainerControl get => _maximum; set { - //if (value < _minimum || Mathf.IsZero(value)) - //throw new ArgumentOutOfRangeException(); if (WholeNumbers) value = Mathf.RoundToInt(value); _maximum = value; @@ -71,8 +69,6 @@ public class Slider : ContainerControl get => _minimum; set { - //if (value > _maximum) - //throw new ArgumentOutOfRangeException(); if (WholeNumbers) value = Mathf.RoundToInt(value); _minimum = value; From 3c19262574bc83a7a523951f4a929325063056fd Mon Sep 17 00:00:00 2001 From: "Mr. Capybara" Date: Sun, 17 Dec 2023 10:15:41 -0400 Subject: [PATCH 06/42] Add parameter to change arrow cap size --- Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs | 2 +- Source/Editor/SceneGraph/Actors/SpotLightNode.cs | 2 +- Source/Editor/Viewport/ViewportDraggingHelper.cs | 2 +- Source/Engine/Debug/DebugDraw.cpp | 6 +++--- Source/Engine/Debug/DebugDraw.cs | 3 ++- Source/Engine/Debug/DebugDraw.h | 7 ++++--- Source/Engine/Physics/Joints/D6Joint.cpp | 3 ++- Source/Engine/Physics/Joints/HingeJoint.cpp | 5 +++-- Source/Engine/Physics/Joints/SphericalJoint.cpp | 3 ++- 9 files changed, 19 insertions(+), 14 deletions(-) diff --git a/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs b/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs index 5363d1f55..1eabc946d 100644 --- a/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs +++ b/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs @@ -23,7 +23,7 @@ namespace FlaxEditor.SceneGraph.Actors base.OnDebugDraw(data); var transform = Actor.Transform; - DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 1.0f, Color.Red, 0.0f, false); + DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 1.0f, 0.5f, Color.Red, 0.0f, false); } } } diff --git a/Source/Editor/SceneGraph/Actors/SpotLightNode.cs b/Source/Editor/SceneGraph/Actors/SpotLightNode.cs index b961ce205..567e8e430 100644 --- a/Source/Editor/SceneGraph/Actors/SpotLightNode.cs +++ b/Source/Editor/SceneGraph/Actors/SpotLightNode.cs @@ -23,7 +23,7 @@ namespace FlaxEditor.SceneGraph.Actors base.OnDebugDraw(data); var transform = Actor.Transform; - DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 0.3f, Color.Red, 0.0f, false); + DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 0.3f, 0.15f, Color.Red, 0.0f, false); } } } diff --git a/Source/Editor/Viewport/ViewportDraggingHelper.cs b/Source/Editor/Viewport/ViewportDraggingHelper.cs index 8a1b4f183..8cbbc5a4f 100644 --- a/Source/Editor/Viewport/ViewportDraggingHelper.cs +++ b/Source/Editor/Viewport/ViewportDraggingHelper.cs @@ -218,7 +218,7 @@ namespace FlaxEditor.Viewport LocalOrientation = RootNode.RaycastNormalRotation(ref hitNormal), Name = item.ShortName }; - DebugDraw.DrawWireArrow(PostProcessSpawnedActorLocation(actor, ref hitNormal), actor.LocalOrientation, 1.0f, Color.Red, 1000000); + DebugDraw.DrawWireArrow(PostProcessSpawnedActorLocation(actor, ref hitNormal), actor.LocalOrientation, 1.0f, 0.5f, Color.Red, 1000000); Spawn(actor, ref hitLocation, ref hitNormal); } else if (hit is StaticModelNode staticModelNode) diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 059ebbd5d..bd7a218a7 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -1940,15 +1940,15 @@ void DebugDraw::DrawWireArc(const Vector3& position, const Quaternion& orientati DrawLine(prevPos, world.GetTranslation(), color, duration, depthTest); } -void DebugDraw::DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, const Color& color, float duration, bool depthTest) +void DebugDraw::DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, float capScale, const Color& color, float duration, bool depthTest) { Float3 direction, up, right; Float3::Transform(Float3::Forward, orientation, direction); Float3::Transform(Float3::Up, orientation, up); Float3::Transform(Float3::Right, orientation, right); const Vector3 end = position + direction * (100.0f * scale); - const Vector3 capEnd = position + direction * (70.0f * scale); - const float arrowSidesRatio = scale * 30.0f; + const Vector3 capEnd = end - (direction * (100 * Math::Min(capScale, scale * 0.5f))); + const float arrowSidesRatio = Math::Min(capScale, scale * 0.5f) * 30.0f; DrawLine(position, end, color, duration, depthTest); DrawLine(end, capEnd + up * arrowSidesRatio, color, duration, depthTest); diff --git a/Source/Engine/Debug/DebugDraw.cs b/Source/Engine/Debug/DebugDraw.cs index bc000e03d..8ff04d072 100644 --- a/Source/Engine/Debug/DebugDraw.cs +++ b/Source/Engine/Debug/DebugDraw.cs @@ -218,10 +218,11 @@ namespace FlaxEngine /// The arrow origin position. /// The orientation (defines the arrow direction). /// The arrow scale (used to adjust the arrow size). + /// The arrow cap scale. /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - public static void DrawWireArrow(Vector3 position, Quaternion orientation, float scale, Color color, float duration = 0.0f, bool depthTest = true) + public static void DrawWireArrow(Vector3 position, Quaternion orientation, float scale, float capScale, Color color, float duration = 0.0f, bool depthTest = true) { } diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index b02b94475..30feb4440 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -570,10 +570,11 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The arrow origin position. /// The orientation (defines the arrow direction). /// The arrow scale (used to adjust the arrow size). + /// The arrow cap scale. /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, float capScale, const Color& color, float duration = 0.0f, bool depthTest = true); /// /// Draws the box. @@ -650,7 +651,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw #define DEBUG_DRAW_WIRE_CYLINDER(position, orientation, radius, height, color, duration, depthTest) DebugDraw::DrawWireCylinder(position, orientation, radius, height, color, duration, depthTest) #define DEBUG_DRAW_WIRE_CONE(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) DebugDraw::DrawWireCone(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) #define DEBUG_DRAW_WIRE_ARC(position, orientation, radius, angle, color, duration, depthTest) DebugDraw::DrawWireArc(position, orientation, radius, angle, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, color, duration, depthTest) DebugDraw::DrawWireArrow(position, orientation, scale, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, capScale, color, duration, depthTest) DebugDraw::DrawWireArrow(position, orientation, scale, capScale, color, duration, depthTest) #define DEBUG_DRAW_TEXT(text, position, color, size, duration) DebugDraw::DrawText(text, position, color, size, duration) #else @@ -679,7 +680,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw #define DEBUG_DRAW_WIRE_CYLINDER(position, orientation, radius, height, color, duration, depthTest) #define DEBUG_DRAW_WIRE_CONE(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) #define DEBUG_DRAW_WIRE_ARC(position, orientation, radius, angle, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, capScale, color, duration, depthTest) #define DEBUG_DRAW_TEXT(text, position, color, size, duration) #endif diff --git a/Source/Engine/Physics/Joints/D6Joint.cpp b/Source/Engine/Physics/Joints/D6Joint.cpp index a5ef035a3..685fa760a 100644 --- a/Source/Engine/Physics/Joints/D6Joint.cpp +++ b/Source/Engine/Physics/Joints/D6Joint.cpp @@ -159,7 +159,8 @@ void D6Joint::OnDebugDrawSelected() const float twistSize = 9.0f; const Color swingColor = Color::Green.AlphaMultiplied(0.6f); const Color twistColor = Color::Yellow.AlphaMultiplied(0.5f); - DEBUG_DRAW_WIRE_ARROW(target, targetRotation, swingSize / 100.0f * 0.5f, Color::Red, 0, false); + const float arrowSize = swingSize / 100.0f * 0.5f; + DEBUG_DRAW_WIRE_ARROW(target, targetRotation, arrowSize, arrowSize * 0.5f, Color::Red, 0, false); if (_motion[(int32)D6JointAxis::SwingY] == D6JointMotion::Locked && _motion[(int32)D6JointAxis::SwingZ] == D6JointMotion::Locked) { // Swing is locked diff --git a/Source/Engine/Physics/Joints/HingeJoint.cpp b/Source/Engine/Physics/Joints/HingeJoint.cpp index 7f85941ef..6952711e8 100644 --- a/Source/Engine/Physics/Joints/HingeJoint.cpp +++ b/Source/Engine/Physics/Joints/HingeJoint.cpp @@ -63,8 +63,9 @@ void HingeJoint::OnDebugDrawSelected() const Quaternion targetRotation = GetTargetOrientation() * xRotation; const float size = 15.0f; const Color color = Color::Green.AlphaMultiplied(0.6f); - DEBUG_DRAW_WIRE_ARROW(source, sourceRotation, size / 100.0f * 0.5f, Color::Red, 0, false); - DEBUG_DRAW_WIRE_ARROW(target, targetRotation, size / 100.0f * 0.5f, Color::Blue, 0, false); + const float arrowSize = size / 100.0f * 0.5f; + DEBUG_DRAW_WIRE_ARROW(source, sourceRotation, arrowSize, arrowSize * 0.5f, Color::Red, 0, false); + DEBUG_DRAW_WIRE_ARROW(target, targetRotation, arrowSize, arrowSize * 0.5f, Color::Blue, 0, false); if (EnumHasAnyFlags(_flags, HingeJointFlag::Limit)) { const float upper = Math::Max(_limit.Upper, _limit.Lower); diff --git a/Source/Engine/Physics/Joints/SphericalJoint.cpp b/Source/Engine/Physics/Joints/SphericalJoint.cpp index c44d4bc6e..84ab9d6e9 100644 --- a/Source/Engine/Physics/Joints/SphericalJoint.cpp +++ b/Source/Engine/Physics/Joints/SphericalJoint.cpp @@ -38,8 +38,9 @@ void SphericalJoint::OnDebugDrawSelected() const Vector3 source = GetPosition(); const Vector3 target = GetTargetPosition(); const float size = 15.0f; + const float arrowSize = size / 100.0f * 0.5f; const Color color = Color::Green.AlphaMultiplied(0.6f); - DEBUG_DRAW_WIRE_ARROW(source, GetOrientation(), size / 100.0f * 0.5f, Color::Red, 0, false); + DEBUG_DRAW_WIRE_ARROW(source, GetOrientation(), arrowSize, arrowSize * 0.5f, Color::Red, 0, false); if (EnumHasAnyFlags(_flags, SphericalJointFlag::Limit)) { DEBUG_DRAW_CONE(source, GetOrientation(), size, _limit.YLimitAngle * DegreesToRadians, _limit.ZLimitAngle * DegreesToRadians, color, 0, false); From 4e2f0cd22cf910ed8abe3151f77d71ca23c2408b Mon Sep 17 00:00:00 2001 From: whocares77 <97740209+whocares77@users.noreply.github.com> Date: Tue, 19 Dec 2023 11:02:18 +0300 Subject: [PATCH 07/42] Added "Create parent for selected actors" context menu button --- Source/Editor/Modules/SceneEditingModule.cs | 32 +++++++++++++++++++ Source/Editor/Modules/UIModule.cs | 4 +++ .../Windows/SceneTreeWindow.ContextMenu.cs | 7 ++++ Source/Editor/Windows/SceneTreeWindow.cs | 2 ++ 4 files changed, 45 insertions(+) diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 970ca8e99..3b982d7ad 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -534,6 +534,38 @@ namespace FlaxEditor.Modules Delete(); } + /// + /// Create parent for selected actors. + /// + public void CreateParentForSelectedActors() + { + Actor actor = new EmptyActor(); + Editor.SceneEditing.Spawn(actor, null, false); + List selection = Editor.SceneEditing.Selection; + for (int i = 0; i < selection.Count; i++) + { + if (selection[i] is ActorNode node) + { + if (node.ParentNode != node.ParentScene) // if parent node is not Scene + { + if (selection.Contains(node.ParentNode)) + { + return; // if parent and child nodes selected together, don't touch child nodes + } + else + { // put created node as child of the Parent Node of node + int parentOrder = node.Actor.OrderInParent; + actor.Parent = node.Actor.Parent; + actor.OrderInParent = parentOrder; + } + } + node.Actor.Parent = actor; + } + } + Editor.SceneEditing.Select(actor); + Editor.Scene.GetActorNode(actor).TreeNode.StartRenaming(Editor.Windows.SceneWin, Editor.Windows.SceneWin.SceneTreePanel); + } + /// /// Duplicates the selected objects. Supports undo/redo. /// diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 3386d411c..7920d5de4 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -50,6 +50,7 @@ namespace FlaxEditor.Modules private ContextMenuButton _menuEditCut; private ContextMenuButton _menuEditCopy; private ContextMenuButton _menuEditPaste; + private ContextMenuButton _menuCreateParentForSelectedActors; private ContextMenuButton _menuEditDelete; private ContextMenuButton _menuEditDuplicate; private ContextMenuButton _menuEditSelectAll; @@ -549,6 +550,8 @@ namespace FlaxEditor.Modules _menuEditCopy = cm.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy); _menuEditPaste = cm.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste); cm.AddSeparator(); + _menuCreateParentForSelectedActors = cm.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors); + cm.AddSeparator(); _menuEditDelete = cm.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete); _menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate); cm.AddSeparator(); @@ -858,6 +861,7 @@ namespace FlaxEditor.Modules _menuEditCut.Enabled = hasSthSelected; _menuEditCopy.Enabled = hasSthSelected; _menuEditPaste.Enabled = canEditScene; + _menuCreateParentForSelectedActors.Enabled = canEditScene && hasSthSelected; _menuEditDelete.Enabled = hasSthSelected; _menuEditDuplicate.Enabled = hasSthSelected; _menuEditSelectAll.Enabled = Level.IsAnySceneLoaded; diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index 0c8e0f283..fa4abfeca 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -132,6 +132,13 @@ namespace FlaxEditor.Windows b = contextMenu.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut); b.Enabled = canEditScene; + // Create a new hierarchy from selected actors + + contextMenu.AddSeparator(); + + b = contextMenu.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors); + b.Enabled = canEditScene && hasSthSelected; + // Prefab options contextMenu.AddSeparator(); diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index 63ba7b960..e4af43dee 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -23,6 +23,8 @@ namespace FlaxEditor.Windows /// public partial class SceneTreeWindow : SceneEditorWindow { + public Panel SceneTreePanel => _sceneTreePanel; + private TextBox _searchBox; private Tree _tree; private Panel _sceneTreePanel; From 36beac51546991d778fb5ed55eef5d81ee9f52d7 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 11 Feb 2024 12:44:40 +0100 Subject: [PATCH 08/42] Fix deadlock when stack overflows in the Anim Graph update --- Source/Engine/Animations/Graph/AnimGraph.cpp | 4 ++++ Source/Engine/Animations/Graph/AnimGraph.h | 1 + 2 files changed, 5 insertions(+) diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index 93bfc2609..2fc9332ba 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -221,6 +221,7 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) context.NodePath.Clear(); context.Data = &data; context.DeltaTime = dt; + context.StackOverFlow = false; context.CurrentFrameIndex = ++data.CurrentFrame; context.CallStack.Clear(); context.Functions.Clear(); @@ -411,9 +412,12 @@ VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box) auto& context = *Context.Get(); // Check if graph is looped or is too deep + if (context.StackOverFlow) + return Value::Zero; if (context.CallStack.Count() >= ANIM_GRAPH_MAX_CALL_STACK) { OnError(caller, box, TEXT("Graph is looped or too deep!")); + context.StackOverFlow = true; return Value::Zero; } #if !BUILD_RELEASE diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index b570cf9fe..c069ddb06 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -796,6 +796,7 @@ struct AnimGraphContext AnimGraphInstanceData* Data; AnimGraphImpulse EmptyNodes; AnimGraphTransitionData TransitionData; + bool StackOverFlow; Array> CallStack; Array> GraphStack; Array > NodePath; From 07e93e261eb0626a3c4c79cc556f7af923ec334d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 13 Feb 2024 09:52:26 +0100 Subject: [PATCH 09/42] Fix physics simulation result collection to happen before draw or next update --- Source/Engine/Engine/Engine.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 259649062..fe7541adb 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -214,9 +214,6 @@ int32 Engine::Main(const Char* cmdLine) Time::OnEndDraw(); FrameMark; } - - // Collect physics simulation results (does nothing if Simulate hasn't been called in the previous loop step) - Physics::CollectResults(); } // Call on exit event @@ -288,6 +285,9 @@ void Engine::OnLateFixedUpdate() // Update services EngineService::OnLateFixedUpdate(); + + // Collect physics simulation results (does nothing if Simulate hasn't been called in the previous loop step) + Physics::CollectResults(); } void Engine::OnUpdate() From 6548ca1148a4164cac9c6799b35430ddd041bf3f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 13 Feb 2024 10:43:20 +0100 Subject: [PATCH 10/42] Fix `JsonAsset::GetInstance` to properly check base class #2224 --- Source/Engine/Content/JsonAsset.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Content/JsonAsset.h b/Source/Engine/Content/JsonAsset.h index a8e89cec0..21b68c691 100644 --- a/Source/Engine/Content/JsonAsset.h +++ b/Source/Engine/Content/JsonAsset.h @@ -139,7 +139,8 @@ public: T* GetInstance() const { const_cast(this)->CreateInstance(); - return Instance && InstanceType.IsAssignableFrom(T::TypeInitializer) ? (T*)Instance : nullptr; + const ScriptingTypeHandle& type = T::TypeInitializer; + return Instance && type.IsAssignableFrom(InstanceType) ? (T*)Instance : nullptr; } public: From 31437e6ddef3ed8963631d6e2b5576eb8add07c3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 14 Feb 2024 12:17:02 +0100 Subject: [PATCH 11/42] Fix copy/paste for UI brushes --- Source/Editor/CustomEditors/CustomEditor.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index a519b1da1..6ae0f0562 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -543,10 +543,11 @@ namespace FlaxEditor.CustomEditors try { string text; + var value = Values[0]; if (ParentEditor is Dedicated.ScriptsEditor) { // Script - text = JsonSerializer.Serialize(Values[0]); + text = JsonSerializer.Serialize(value); // Remove properties that should be ignored when copy/pasting data if (text == null) @@ -576,12 +577,12 @@ namespace FlaxEditor.CustomEditors else if (ScriptType.FlaxObject.IsAssignableFrom(Values.Type)) { // Object reference - text = JsonSerializer.GetStringID(Values[0] as FlaxEngine.Object); + text = JsonSerializer.GetStringID(value as FlaxEngine.Object); } else { // Default - text = JsonSerializer.Serialize(Values[0]); + text = JsonSerializer.Serialize(value, TypeUtils.GetType(Values.Type)); } Clipboard.Text = text; } From 3958a4740f6b388e16e7f2fac474b0528d893897 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 14 Feb 2024 12:36:04 +0100 Subject: [PATCH 12/42] Add option to enable Depth Test on cloth painting debug preview (enabled by default) --- Source/Editor/Tools/ClothPainting.cs | 15 +++++++++++++++ Source/Engine/Physics/Actors/Cloth.cpp | 6 +++--- Source/Engine/Physics/Actors/Cloth.h | 5 +++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Tools/ClothPainting.cs b/Source/Editor/Tools/ClothPainting.cs index 28536ea83..6a98a3b5f 100644 --- a/Source/Editor/Tools/ClothPainting.cs +++ b/Source/Editor/Tools/ClothPainting.cs @@ -43,6 +43,19 @@ namespace FlaxEngine.Tools /// Enables continuous painting, otherwise single paint on click. /// public bool ContinuousPaint; + + /// + /// Enables drawing cloth paint debugging with Depth Test enabled (skips occluded vertices). + /// + public bool DebugDrawDepthTest + { + get => Gizmo.Cloth?.DebugDrawDepthTest ?? true; + set + { + if (Gizmo.Cloth != null) + Gizmo.Cloth.DebugDrawDepthTest = value; + } + } #pragma warning restore CS0649 public override void Init(IGizmoOwner owner) @@ -62,6 +75,7 @@ namespace FlaxEngine.Tools public override void Dispose() { Owner.Gizmos.Remove(Gizmo); + Gizmo = null; base.Dispose(); } @@ -83,6 +97,7 @@ namespace FlaxEngine.Tools private EditClothPaintAction _undoAction; public bool IsPainting => _isPainting; + public Cloth Cloth => _cloth; public ClothPaintingGizmo(IGizmoOwner owner, ClothPaintingGizmoMode mode) : base(owner) diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index b2db80b0b..a35acb066 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -415,9 +415,9 @@ void Cloth::OnDebugDrawSelected() c1 = Color::Lerp(Color::Red, Color::White, _paint[i1]); c2 = Color::Lerp(Color::Red, Color::White, _paint[i2]); } - DebugDraw::DrawLine(v0, v1, c0, c1, 0, false); - DebugDraw::DrawLine(v1, v2, c1, c2, 0, false); - DebugDraw::DrawLine(v2, v0, c2, c0, 0, false); + DebugDraw::DrawLine(v0, v1, c0, c1, 0, DebugDrawDepthTest); + DebugDraw::DrawLine(v1, v2, c1, c2, 0, DebugDrawDepthTest); + DebugDraw::DrawLine(v2, v0, c2, c0, 0, DebugDrawDepthTest); } PhysicsBackend::UnlockClothParticles(_cloth); } diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h index 6137ec3b6..1b97330e0 100644 --- a/Source/Engine/Physics/Actors/Cloth.h +++ b/Source/Engine/Physics/Actors/Cloth.h @@ -332,6 +332,11 @@ public: bool OnPreUpdate(); void OnPostUpdate(); +private: +#if USE_EDITOR + API_FIELD(Internal) bool DebugDrawDepthTest = true; +#endif + public: // [Actor] void Draw(RenderContext& renderContext) override; From ebcc864b06b5ffe3fdbc734698e7cf5d1653c67a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 14 Feb 2024 12:47:34 +0100 Subject: [PATCH 13/42] Fix missing file error in `Content::GetAssetInfo` --- Source/Engine/Content/Content.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 897817850..2be4df29d 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -277,6 +277,8 @@ bool Content::GetAssetInfo(const StringView& path, AssetInfo& info) // Find asset in registry if (Cache.FindAsset(path, info)) return true; + if (!FileSystem::FileExists(path)) + return false; PROFILE_CPU(); const auto extension = FileSystem::GetExtension(path).ToLower(); From 42363e411e3a74a80747b5fa6b585f2f7ad0a1f5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 14 Feb 2024 18:21:30 +0100 Subject: [PATCH 14/42] Various tweaks --- Source/Engine/Physics/Collisions.h | 8 +--- .../Physics/PhysX/PhysicsBackendPhysX.cpp | 44 +++++++++---------- .../PhysX/SimulationEventCallbackPhysX.cpp | 22 +++------- .../PhysX/SimulationEventCallbackPhysX.h | 5 --- Source/Engine/Physics/PhysicalMaterial.h | 3 +- Source/Engine/Physics/Types.h | 10 ++--- Source/Engine/Tools/ModelTool/ModelTool.cpp | 8 ++-- 7 files changed, 38 insertions(+), 62 deletions(-) diff --git a/Source/Engine/Physics/Collisions.h b/Source/Engine/Physics/Collisions.h index 230337bc9..c1a2679a4 100644 --- a/Source/Engine/Physics/Collisions.h +++ b/Source/Engine/Physics/Collisions.h @@ -58,9 +58,7 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API Collision /// /// The total impulse applied to this contact pair to resolve the collision. /// - /// - /// The total impulse is obtained by summing up impulses applied at all contact points in this collision pair. - /// + /// The total impulse is obtained by summing up impulses applied at all contact points in this collision pair. API_FIELD() Vector3 Impulse; /// @@ -87,9 +85,7 @@ public: /// /// Gets the relative linear velocity of the two colliding objects. /// - /// - /// Can be used to detect stronger collisions. - /// + /// Can be used to detect stronger collisions. Vector3 GetRelativeVelocity() const { return ThisVelocity - OtherVelocity; diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 99a5abc56..f3e7baed1 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -228,9 +228,7 @@ class QueryFilterPhysX : public PxQueryFilterCallback // Check mask const PxFilterData shapeFilter = shape->getQueryFilterData(); if ((filterData.word0 & shapeFilter.word0) == 0) - { return PxQueryHitType::eNONE; - } // Check if skip triggers const bool hitTriggers = filterData.word2 != 0; @@ -483,8 +481,10 @@ protected: } }; +#define PxHitFlagEmpty (PxHitFlags)0 +#define SCENE_QUERY_FLAGS (PxHitFlag::ePOSITION | PxHitFlag::eNORMAL | PxHitFlag::eFACE_INDEX | PxHitFlag::eUV) + #define SCENE_QUERY_SETUP(blockSingle) auto scenePhysX = (ScenePhysX*)scene; if (scene == nullptr) return false; \ - const PxHitFlags hitFlags = PxHitFlag::ePOSITION | PxHitFlag::eNORMAL | PxHitFlag::eUV; \ PxQueryFilterData filterData; \ filterData.flags |= PxQueryFlag::ePREFILTER; \ filterData.data.word0 = layerMask; \ @@ -1735,8 +1735,6 @@ void PhysicsBackend::EndSimulateScene(void* scene) { PROFILE_CPU_NAMED("Physics.SendEvents"); - - scenePhysX->EventsCallback.CollectResults(); scenePhysX->EventsCallback.SendTriggerEvents(); scenePhysX->EventsCallback.SendCollisionEvents(); scenePhysX->EventsCallback.SendJointEvents(); @@ -1880,14 +1878,14 @@ bool PhysicsBackend::RayCast(void* scene, const Vector3& origin, const Vector3& { SCENE_QUERY_SETUP(true); PxRaycastBuffer buffer; - return scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter); + return scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter); } bool PhysicsBackend::RayCast(void* scene, const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, const float maxDistance, uint32 layerMask, bool hitTriggers) { SCENE_QUERY_SETUP(true); PxRaycastBuffer buffer; - if (!scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_SINGLE(); return true; @@ -1897,7 +1895,7 @@ bool PhysicsBackend::RayCastAll(void* scene, const Vector3& origin, const Vector { SCENE_QUERY_SETUP(false); DynamicHitBuffer buffer; - if (!scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_ALL(); return true; @@ -1908,7 +1906,7 @@ bool PhysicsBackend::BoxCast(void* scene, const Vector3& center, const Vector3& SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxBoxGeometry geometry(C2P(halfExtents)); - return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter); + return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter); } bool PhysicsBackend::BoxCast(void* scene, const Vector3& center, const Vector3& halfExtents, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers) @@ -1916,7 +1914,7 @@ bool PhysicsBackend::BoxCast(void* scene, const Vector3& center, const Vector3& SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxBoxGeometry geometry(C2P(halfExtents)); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_SINGLE(); return true; @@ -1927,7 +1925,7 @@ bool PhysicsBackend::BoxCastAll(void* scene, const Vector3& center, const Vector SCENE_QUERY_SETUP_SWEEP(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxBoxGeometry geometry(C2P(halfExtents)); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_ALL(); return true; @@ -1938,7 +1936,7 @@ bool PhysicsBackend::SphereCast(void* scene, const Vector3& center, const float SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin)); const PxSphereGeometry geometry(radius); - return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter); + return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter); } bool PhysicsBackend::SphereCast(void* scene, const Vector3& center, const float radius, const Vector3& direction, RayCastHit& hitInfo, const float maxDistance, uint32 layerMask, bool hitTriggers) @@ -1946,7 +1944,7 @@ bool PhysicsBackend::SphereCast(void* scene, const Vector3& center, const float SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin)); const PxSphereGeometry geometry(radius); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_SINGLE(); return true; @@ -1957,7 +1955,7 @@ bool PhysicsBackend::SphereCastAll(void* scene, const Vector3& center, const flo SCENE_QUERY_SETUP_SWEEP(); const PxTransform pose(C2P(center - scenePhysX->Origin)); const PxSphereGeometry geometry(radius); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_ALL(); return true; @@ -1968,7 +1966,7 @@ bool PhysicsBackend::CapsuleCast(void* scene, const Vector3& center, const float SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxCapsuleGeometry geometry(radius, height * 0.5f); - return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter); + return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter); } bool PhysicsBackend::CapsuleCast(void* scene, const Vector3& center, const float radius, const float height, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers) @@ -1976,7 +1974,7 @@ bool PhysicsBackend::CapsuleCast(void* scene, const Vector3& center, const float SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxCapsuleGeometry geometry(radius, height * 0.5f); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_SINGLE(); return true; @@ -1987,7 +1985,7 @@ bool PhysicsBackend::CapsuleCastAll(void* scene, const Vector3& center, const fl SCENE_QUERY_SETUP_SWEEP(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxCapsuleGeometry geometry(radius, height * 0.5f); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_ALL(); return true; @@ -1999,7 +1997,7 @@ bool PhysicsBackend::ConvexCast(void* scene, const Vector3& center, const Collis SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxConvexMeshGeometry geometry((PxConvexMesh*)convexMesh->GetConvex(), PxMeshScale(C2P(scale))); - return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter); + return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter); } bool PhysicsBackend::ConvexCast(void* scene, const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers) @@ -2008,7 +2006,7 @@ bool PhysicsBackend::ConvexCast(void* scene, const Vector3& center, const Collis SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxConvexMeshGeometry geometry((PxConvexMesh*)convexMesh->GetConvex(), PxMeshScale(C2P(scale))); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_SINGLE(); return true; @@ -2020,7 +2018,7 @@ bool PhysicsBackend::ConvexCastAll(void* scene, const Vector3& center, const Col SCENE_QUERY_SETUP_SWEEP(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxConvexMeshGeometry geometry((PxConvexMesh*)convexMesh->GetConvex(), PxMeshScale(C2P(scale))); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_ALL(); return true; @@ -2608,9 +2606,8 @@ bool PhysicsBackend::RayCastShape(void* shape, const Vector3& position, const Qu auto shapePhysX = (PxShape*)shape; const Vector3 sceneOrigin = SceneOrigins[shapePhysX->getActor() ? shapePhysX->getActor()->getScene() : nullptr]; const PxTransform trans(C2P(position - sceneOrigin), C2P(orientation)); - const PxHitFlags hitFlags = (PxHitFlags)0; PxRaycastHit hit; - if (PxGeometryQuery::raycast(C2P(origin - sceneOrigin), C2P(direction), shapePhysX->getGeometry(), trans, maxDistance, hitFlags, 1, &hit) != 0) + if (PxGeometryQuery::raycast(C2P(origin - sceneOrigin), C2P(direction), shapePhysX->getGeometry(), trans, maxDistance, PxHitFlagEmpty, 1, &hit) != 0) { resultHitDistance = hit.distance; return true; @@ -2623,9 +2620,8 @@ bool PhysicsBackend::RayCastShape(void* shape, const Vector3& position, const Qu auto shapePhysX = (PxShape*)shape; const Vector3 sceneOrigin = SceneOrigins[shapePhysX->getActor() ? shapePhysX->getActor()->getScene() : nullptr]; const PxTransform trans(C2P(position - sceneOrigin), C2P(orientation)); - const PxHitFlags hitFlags = PxHitFlag::ePOSITION | PxHitFlag::eNORMAL | PxHitFlag::eFACE_INDEX | PxHitFlag::eUV; PxRaycastHit hit; - if (PxGeometryQuery::raycast(C2P(origin - sceneOrigin), C2P(direction), shapePhysX->getGeometry(), trans, maxDistance, hitFlags, 1, &hit) == 0) + if (PxGeometryQuery::raycast(C2P(origin - sceneOrigin), C2P(direction), shapePhysX->getGeometry(), trans, maxDistance, SCENE_QUERY_FLAGS, 1, &hit) == 0) return false; P2C(hit, hitInfo); hitInfo.Point += sceneOrigin; diff --git a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp index 5781e4641..fc33b0dfe 100644 --- a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp +++ b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp @@ -38,10 +38,6 @@ void SimulationEventCallback::Clear() BrokenJoints.Clear(); } -void SimulationEventCallback::CollectResults() -{ -} - void SimulationEventCallback::SendCollisionEvents() { for (auto& c : RemovedCollisions) @@ -132,7 +128,6 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c //const PxU32 flippedContacts = (pair.flags & PxContactPairFlag::eINTERNAL_CONTACTS_ARE_FLIPPED); const bool hasImpulses = pair.flags.isSet(PxContactPairFlag::eINTERNAL_HAS_IMPULSES); const bool hasPostVelocities = !pair.flags.isSet(PxContactPairFlag::eACTOR_PAIR_LOST_TOUCH); - PxU32 nbContacts = 0; PxVec3 totalImpulse(0.0f); c.ThisActor = static_cast(pair.shapes[0]->userData); @@ -144,29 +139,25 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c } // Extract contact points + c.ContactsCount = 0; while (i.hasNextPatch()) { i.nextPatch(); - while (i.hasNextContact() && nbContacts < COLLISION_NAX_CONTACT_POINTS) + while (i.hasNextContact() && c.ContactsCount < COLLISION_NAX_CONTACT_POINTS) { i.nextContact(); - const PxVec3 point = i.getContactPoint(); const PxVec3 normal = i.getContactNormal(); if (hasImpulses) - totalImpulse += normal * impulses[nbContacts]; + totalImpulse += normal * impulses[c.ContactsCount]; - //PxU32 internalFaceIndex0 = flippedContacts ? iter.getFaceIndex1() : iter.getFaceIndex0(); - //PxU32 internalFaceIndex1 = flippedContacts ? iter.getFaceIndex0() : iter.getFaceIndex1(); - - ContactPoint& contact = c.Contacts[nbContacts]; + ContactPoint& contact = c.Contacts[c.ContactsCount++]; contact.Point = P2C(point); contact.Normal = P2C(normal); contact.Separation = i.getSeparation(); - - nbContacts++; } } + c.Impulse = P2C(totalImpulse); // Extract velocities c.ThisVelocity = c.OtherVelocity = Vector3::Zero; @@ -183,9 +174,6 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c } } - c.ContactsCount = nbContacts; - c.Impulse = P2C(totalImpulse); - if (pair.flags & PxContactPairFlag::eACTOR_PAIR_HAS_FIRST_TOUCH) { NewCollisions.Add(c); diff --git a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.h b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.h index f9081df66..f10f926eb 100644 --- a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.h +++ b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.h @@ -49,11 +49,6 @@ public: /// void Clear(); - /// - /// Generates the new/old/removed collisions and a valid trigger pairs. - /// - void CollectResults(); - /// /// Sends the collision events to the managed objects. /// diff --git a/Source/Engine/Physics/PhysicalMaterial.h b/Source/Engine/Physics/PhysicalMaterial.h index b9c2e4554..ff27437e8 100644 --- a/Source/Engine/Physics/PhysicalMaterial.h +++ b/Source/Engine/Physics/PhysicalMaterial.h @@ -9,7 +9,8 @@ /// /// Physical materials are used to define the response of a physical object when interacting dynamically with the world. /// -API_CLASS(Attributes = "ContentContextMenu(\"New/Physics/Physical Material\")") class FLAXENGINE_API PhysicalMaterial final : public ISerializable +API_CLASS(Attributes = "ContentContextMenu(\"New/Physics/Physical Material\")") +class FLAXENGINE_API PhysicalMaterial final : public ISerializable { API_AUTO_SERIALIZATION(); DECLARE_SCRIPTING_TYPE_MINIMAL(PhysicalMaterial); diff --git a/Source/Engine/Physics/Types.h b/Source/Engine/Physics/Types.h index ed12f8611..1631f694b 100644 --- a/Source/Engine/Physics/Types.h +++ b/Source/Engine/Physics/Types.h @@ -151,17 +151,17 @@ API_STRUCT() struct RayCastHit /// API_FIELD() float Distance; + /// + /// The point in the world space where ray hit the collider. + /// + API_FIELD() Vector3 Point; + /// /// The index of the face that was hit. Valid only for convex mesh (polygon index), triangle mesh (triangle index) and height field (triangle index). /// /// API_FIELD() uint32 FaceIndex; - /// - /// The point in the world space where ray hit the collider. - /// - API_FIELD() Vector3 Point; - /// /// The barycentric coordinates of hit triangle. Valid only for triangle mesh and height field. /// diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 447301f46..9578f6406 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1431,7 +1431,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option Transform srcNode = data.Skeleton.Nodes[nodeIndex].LocalTransform; auto& node = data.Skeleton.Nodes[nodeIndex]; if (auto* channel = animation.GetChannel(node.Name)) - channel->Evaluate(frame, &srcNode, false); + channel->Evaluate((float)frame, &srcNode, false); pose.Nodes[nodeIndex] = srcNode; } @@ -1439,7 +1439,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option key = Float3::Zero; for (int32 nodeIndex = 0; nodeIndex < nodes; nodeIndex++) key += pose.GetNodeModelTransformation(data.Skeleton, nodeIndex).Translation; - key /= nodes; + key /= (float)nodes; } // Calculate skeleton center of mass movement over the animation frames @@ -1448,7 +1448,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option for (int32 frame = 0; frame < frames; frame++) { auto& key = rootChannel.Position[frame]; - key.Time = frame; + key.Time = (float)frame; key.Value = centerOfMass[frame] - centerOfMassRefPose; } @@ -1494,7 +1494,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option Transform srcNode = data.Skeleton.Nodes[nodeIndex].LocalTransform; auto& node = data.Skeleton.Nodes[nodeIndex]; if (auto* channel = animation.GetChannel(node.Name)) - channel->Evaluate(frame, &srcNode, false); + channel->Evaluate((float)frame, &srcNode, false); pose.Nodes[nodeIndex] = srcNode; } From db7dfdb0b14c1d28013a6eefdfe1903c8c981255 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 14 Feb 2024 18:55:48 +0100 Subject: [PATCH 15/42] Add support for structure and script types in `JsonAsset` --- Source/Engine/Content/JsonAsset.cpp | 66 +++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/Source/Engine/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp index 9977a28e1..60ff29be1 100644 --- a/Source/Engine/Content/JsonAsset.cpp +++ b/Source/Engine/Content/JsonAsset.cpp @@ -393,34 +393,57 @@ bool JsonAsset::CreateInstance() if (typeHandle) { auto& type = typeHandle.GetType(); + + // Ensure that object can deserialized + const ScriptingType::InterfaceImplementation* interface = type.GetInterface(ISerializable::TypeInitializer); + if (!interface) + { + LOG(Warning, "Cannot deserialize {0} from Json Asset because it doesn't implement ISerializable interface.", type.ToString()); + return false; + } + auto modifier = Cache::ISerializeModifier.Get(); + modifier->EngineBuild = DataEngineBuild; + + // Create object switch (type.Type) { case ScriptingTypes::Class: + case ScriptingTypes::Structure: { - // Ensure that object can deserialized - const ScriptingType::InterfaceImplementation* interface = type.GetInterface(ISerializable::TypeInitializer); - if (!interface) - { - LOG(Warning, "Cannot deserialize {0} from Json Asset because it doesn't implement ISerializable interface.", type.ToString()); - break; - } - - // Allocate object const auto instance = Allocator::Allocate(type.Size); if (!instance) return true; Instance = instance; - InstanceType = typeHandle; - _dtor = type.Class.Dtor; - type.Class.Ctor(instance); + if (type.Type == ScriptingTypes::Class) + { + _dtor = type.Class.Dtor; + type.Class.Ctor(instance); + } + else + { + _dtor = type.Struct.Dtor; + type.Struct.Ctor(instance); + } // Deserialize object - auto modifier = Cache::ISerializeModifier.Get(); - modifier->EngineBuild = DataEngineBuild; ((ISerializable*)((byte*)instance + interface->VTableOffset))->Deserialize(*Data, modifier.Value); break; } + case ScriptingTypes::Script: + { + const ScriptingObjectSpawnParams params(Guid::New(), typeHandle); + const auto instance = type.Script.Spawn(params); + if (!instance) + return true; + Instance = instance; + _dtor = nullptr; + + // Deserialize object + ToInterface(instance)->Deserialize(*Data, modifier.Value); + break; } + } + InstanceType = typeHandle; } return false; @@ -441,13 +464,20 @@ void JsonAsset::DeleteInstance() } // C++ instance - if (!Instance || !_dtor) + if (!Instance) return; - _dtor(Instance); + if (_dtor) + { + _dtor(Instance); + _dtor = nullptr; + Allocator::Free(Instance); + } + else + { + Delete((ScriptingObject*)Instance); + } InstanceType = ScriptingTypeHandle(); - Allocator::Free(Instance); Instance = nullptr; - _dtor = nullptr; } #if USE_EDITOR From f730657518374f3cbce5b56a8966f8b41fe7b5b3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 15 Feb 2024 10:47:45 +0100 Subject: [PATCH 16/42] Add support for using pointer in `MarshalAs` in scripting types --- Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs | 2 +- .../Flax.Build/Bindings/BindingsGenerator.Api.cs | 2 +- .../Flax.Build/Bindings/BindingsGenerator.CSharp.cs | 4 ++-- .../Flax.Build/Bindings/BindingsGenerator.Cache.cs | 2 +- .../Flax.Build/Bindings/BindingsGenerator.Cpp.cs | 4 ++-- .../Flax.Build/Bindings/BindingsGenerator.Parsing.cs | 9 +++++++-- Source/Tools/Flax.Build/Bindings/TypeInfo.cs | 11 +++++++++++ 7 files changed, 25 insertions(+), 9 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs index b0167c024..72653900c 100644 --- a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs @@ -22,7 +22,7 @@ namespace Flax.Build.Bindings public string[] Comment; public bool IsInBuild; public bool IsDeprecated; - public string MarshalAs; + public TypeInfo MarshalAs; internal bool IsInited; internal TypedefInfo Instigator; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs index 4a0070710..1849513f9 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs @@ -197,7 +197,7 @@ namespace Flax.Build.Bindings if (apiType != null) { if (apiType.MarshalAs != null) - return UsePassByReference(buildData, new TypeInfo(apiType.MarshalAs), caller); + return UsePassByReference(buildData, apiType.MarshalAs, caller); // Skip for scripting objects if (apiType.IsScriptingObject) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 845e4e62b..dc9f74369 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -439,7 +439,7 @@ namespace Flax.Build.Bindings } if (apiType.MarshalAs != null) - return GenerateCSharpManagedToNativeType(buildData, new TypeInfo(apiType.MarshalAs), caller); + return GenerateCSharpManagedToNativeType(buildData, apiType.MarshalAs, caller); if (apiType.IsScriptingObject || apiType.IsInterface) return "IntPtr"; } @@ -531,7 +531,7 @@ namespace Flax.Build.Bindings { var apiType = FindApiTypeInfo(buildData, functionInfo.ReturnType, caller); if (apiType != null && apiType.MarshalAs != null) - returnValueType = GenerateCSharpNativeToManaged(buildData, new TypeInfo(apiType.MarshalAs), caller); + returnValueType = GenerateCSharpNativeToManaged(buildData, apiType.MarshalAs, caller); else returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, caller); } diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs index 079a5839f..d78af861f 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs @@ -19,7 +19,7 @@ namespace Flax.Build.Bindings partial class BindingsGenerator { private static readonly Dictionary TypeCache = new Dictionary(); - private const int CacheVersion = 21; + private const int CacheVersion = 22; internal static void Write(BinaryWriter writer, string e) { diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 4e632014a..c9c80fe87 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -597,7 +597,7 @@ namespace Flax.Build.Bindings CppReferencesFiles.Add(apiType.File); if (apiType.MarshalAs != null) - return GenerateCppWrapperNativeToManaged(buildData, new TypeInfo(apiType.MarshalAs), caller, out type, functionInfo); + return GenerateCppWrapperNativeToManaged(buildData, apiType.MarshalAs, caller, out type, functionInfo); // Scripting Object if (apiType.IsScriptingObject) @@ -801,7 +801,7 @@ namespace Flax.Build.Bindings if (apiType != null) { if (apiType.MarshalAs != null) - return GenerateCppWrapperManagedToNative(buildData, new TypeInfo(apiType.MarshalAs), caller, out type, out apiType, functionInfo, out needLocalVariable); + return GenerateCppWrapperManagedToNative(buildData, apiType.MarshalAs, caller, out type, out apiType, functionInfo, out needLocalVariable); // Scripting Object (for non-pod types converting only, other API converts managed to unmanaged object in C# wrapper code) if (CppNonPodTypesConvertingGeneration && apiType.IsScriptingObject && typeInfo.IsPtr) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index 0b71af086..3db43edf0 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -185,6 +185,11 @@ namespace Flax.Build.Bindings tag.Value = tag.Value.Substring(1, tag.Value.Length - 2); if (tag.Value.Contains("\\\"")) tag.Value = tag.Value.Replace("\\\"", "\""); + token = context.Tokenizer.NextToken(); + if (token.Type == TokenType.Multiply) + tag.Value += token.Value; + else + context.Tokenizer.PreviousToken(); parameters.Add(tag); break; case TokenType.Whitespace: @@ -647,7 +652,7 @@ namespace Flax.Build.Bindings desc.Namespace = tag.Value; break; case "marshalas": - desc.MarshalAs = tag.Value; + desc.MarshalAs = TypeInfo.FromString(tag.Value); break; case "tag": ParseTag(ref desc.Tags, tag); @@ -1236,7 +1241,7 @@ namespace Flax.Build.Bindings desc.Namespace = tag.Value; break; case "marshalas": - desc.MarshalAs = tag.Value; + desc.MarshalAs = TypeInfo.FromString(tag.Value); break; case "tag": ParseTag(ref desc.Tags, tag); diff --git a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs index cdf293085..61946bae2 100644 --- a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs @@ -180,6 +180,17 @@ namespace Flax.Build.Bindings return sb.ToString(); } + public static TypeInfo FromString(string text) + { + var result = new TypeInfo(text); + if (result.Type.EndsWith('*')) + { + result.IsPtr = true; + result.Type = result.Type.Substring(0, result.Type.Length - 1); + } + return result; + } + public string ToString(bool canRef = true) { var sb = new StringBuilder(64); From 43f344ee138d0500bd222ed77c674d02c9053972 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 15 Feb 2024 10:54:33 +0100 Subject: [PATCH 17/42] Simplify bindings code for object refs --- .../Bindings/BindingsGenerator.Cpp.cs | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index c9c80fe87..8e1836b2f 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -167,11 +167,7 @@ namespace Flax.Build.Bindings return $"Variant(StringView({value}))"; if (typeInfo.Type == "StringAnsi") return $"Variant(StringAnsiView({value}))"; - if (typeInfo.Type == "AssetReference" || - typeInfo.Type == "WeakAssetReference" || - typeInfo.Type == "SoftAssetReference" || - typeInfo.Type == "ScriptingObjectReference" || - typeInfo.Type == "SoftObjectReference") + if (typeInfo.IsObjectRef) return $"Variant({value}.Get())"; if (typeInfo.IsArray) { @@ -227,10 +223,10 @@ namespace Flax.Build.Bindings return $"(StringAnsiView){value}"; if (typeInfo.IsPtr && typeInfo.IsConst && typeInfo.Type == "Char") return $"((StringView){value}).GetText()"; // (StringView)Variant, if not empty, is guaranteed to point to a null-terminated buffer. - if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftAssetReference") - return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})"; if (typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference") return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})"; + if (typeInfo.IsObjectRef) + return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})"; if (typeInfo.IsArray) throw new Exception($"Not supported type to convert from the Variant to fixed-size array '{typeInfo}[{typeInfo.ArraySize}]'."); if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) @@ -514,11 +510,7 @@ namespace Flax.Build.Bindings return "MUtils::ToManaged({0})"; default: // Object reference property - if ((typeInfo.Type == "ScriptingObjectReference" || - typeInfo.Type == "AssetReference" || - typeInfo.Type == "WeakAssetReference" || - typeInfo.Type == "SoftAssetReference" || - typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) + if (typeInfo.IsObjectRef) { type = "MObject*"; return "{0}.GetManagedInstance()"; @@ -704,11 +696,7 @@ namespace Flax.Build.Bindings return "MUtils::ToNative({0})"; default: // Object reference property - if ((typeInfo.Type == "ScriptingObjectReference" || - typeInfo.Type == "AssetReference" || - typeInfo.Type == "WeakAssetReference" || - typeInfo.Type == "SoftAssetReference" || - typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) + if (typeInfo.IsObjectRef) { // For non-pod types converting only, other API converts managed to unmanaged object in C# wrapper code) if (CppNonPodTypesConvertingGeneration) From fe7cc62728f52ba323be0355b505c32ec679ca03 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 15 Feb 2024 11:22:01 +0100 Subject: [PATCH 18/42] Add `JsonAssetReference` type for scripting --- .../CustomEditors/Editors/AssetRefEditor.cs | 24 ++++-- Source/Engine/Content/JsonAssetReference.cs | 83 +++++++++++++++++++ Source/Engine/Content/JsonAssetReference.h | 25 ++++++ 3 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 Source/Engine/Content/JsonAssetReference.cs create mode 100644 Source/Engine/Content/JsonAssetReference.h diff --git a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs index 1f3359fd5..db8ec1152 100644 --- a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs +++ b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs @@ -51,10 +51,13 @@ namespace FlaxEditor.CustomEditors.Editors return; Picker = layout.Custom().CustomControl; - _valueType = Values.Type.Type != typeof(object) || Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]); + var value = Values[0]; + _valueType = Values.Type.Type != typeof(object) || value == null ? Values.Type : TypeUtils.GetObjectType(value); var assetType = _valueType; if (assetType == typeof(string)) assetType = new ScriptType(typeof(Asset)); + else if (_valueType.Type != null && _valueType.Type.Name == typeof(JsonAssetReference<>).Name) + assetType = new ScriptType(_valueType.Type.GenericTypeArguments[0]); float height = 48; var attributes = Values.GetAttributes(); @@ -102,6 +105,12 @@ namespace FlaxEditor.CustomEditors.Editors SetValue(new SceneReference(Picker.Validator.SelectedID)); else if (_valueType.Type == typeof(string)) SetValue(Picker.Validator.SelectedPath); + else if (_valueType.Type.Name == typeof(JsonAssetReference<>).Name) + { + var value = Values[0]; + value.GetType().GetField("Asset").SetValue(value, Picker.Validator.SelectedAsset as JsonAsset); + SetValue(value); + } else SetValue(Picker.Validator.SelectedAsset); } @@ -114,16 +123,19 @@ namespace FlaxEditor.CustomEditors.Editors if (!HasDifferentValues) { _isRefreshing = true; - if (Values[0] is AssetItem assetItem) + var value = Values[0]; + if (value is AssetItem assetItem) Picker.Validator.SelectedItem = assetItem; - else if (Values[0] is Guid guid) + else if (value is Guid guid) Picker.Validator.SelectedID = guid; - else if (Values[0] is SceneReference sceneAsset) + else if (value is SceneReference sceneAsset) Picker.Validator.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID); - else if (Values[0] is string path) + else if (value is string path) Picker.Validator.SelectedPath = path; + else if (value != null && value.GetType().Name == typeof(JsonAssetReference<>).Name) + Picker.Validator.SelectedAsset = value.GetType().GetField("Asset").GetValue(value) as JsonAsset; else - Picker.Validator.SelectedAsset = Values[0] as Asset; + Picker.Validator.SelectedAsset = value as Asset; _isRefreshing = false; } } diff --git a/Source/Engine/Content/JsonAssetReference.cs b/Source/Engine/Content/JsonAssetReference.cs new file mode 100644 index 000000000..adfaa5179 --- /dev/null +++ b/Source/Engine/Content/JsonAssetReference.cs @@ -0,0 +1,83 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System; + +namespace FlaxEngine +{ + /// + /// Json asset reference utility. References resource with a typed data type. + /// + /// Type of the asset instance type. +#if FLAX_EDITOR + [CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.AssetRefEditor))] +#endif + public struct JsonAssetReference + { + /// + /// Gets or sets the referenced asset. + /// + public JsonAsset Asset; + + /// + /// Gets the instance of the Json Asset. Null if unset. + /// + /// instance of the Json Asset or null if unset. + public T Get() + { + return (T)Asset?.Instance; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The Json Asset. + public JsonAssetReference(JsonAsset asset) + { + Asset = asset; + } + + /// + /// Implicit cast operator. + /// + public static implicit operator JsonAsset(JsonAssetReference value) + { + return value.Asset; + } + + /// + /// Implicit cast operator. + /// + public static implicit operator IntPtr(JsonAssetReference value) + { + return Object.GetUnmanagedPtr(value.Asset); + } + + /// + /// Implicit cast operator. + /// + public static implicit operator JsonAssetReference(JsonAsset value) + { + return new JsonAssetReference(value); + } + + /// + /// Implicit cast operator. + /// + public static implicit operator JsonAssetReference(IntPtr valuePtr) + { + return new JsonAssetReference(Object.FromUnmanagedPtr(valuePtr) as JsonAsset); + } + + /// + public override string ToString() + { + return Asset?.ToString(); + } + + /// + public override int GetHashCode() + { + return Asset?.GetHashCode() ?? 0; + } + } +} diff --git a/Source/Engine/Content/JsonAssetReference.h b/Source/Engine/Content/JsonAssetReference.h new file mode 100644 index 000000000..82fbe47f1 --- /dev/null +++ b/Source/Engine/Content/JsonAssetReference.h @@ -0,0 +1,25 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Content/JsonAsset.h" +#include "Engine/Content/AssetReference.h" + +/// +/// Json asset reference utility. References resource with a typed data type. +/// +/// Type of the asset instance type. +template +API_STRUCT(NoDefault, Template, MarshalAs=JsonAsset*) struct JsonAssetReference : AssetReference +{ + JsonAssetReference& operator=(JsonAsset* asset) noexcept + { + OnSet(asset); + return *this; + } + + operator JsonAsset*() const + { + return Get(); + } +}; From 753829677515d78348f8739db8a3a7f528064075 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 15 Feb 2024 11:45:50 +0100 Subject: [PATCH 19/42] Refactor `PhysicalMaterial` usage to utilize `JsonAssetReference` struct --- .../Engine/Physics/Colliders/CharacterController.cpp | 2 +- Source/Engine/Physics/Colliders/Collider.cpp | 4 ++-- Source/Engine/Physics/Colliders/Collider.h | 6 +++--- Source/Engine/Physics/PhysicalMaterial.h | 10 +--------- Source/Engine/Physics/Physics.cpp | 5 ----- Source/Engine/Terrain/Terrain.cpp | 2 +- Source/Engine/Terrain/Terrain.h | 6 +++--- Source/Engine/Terrain/TerrainPatch.cpp | 2 +- 8 files changed, 12 insertions(+), 25 deletions(-) diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 9b36a92ff..38ab0394d 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -206,7 +206,7 @@ void CharacterController::CreateController() _cachedScale = GetScale(); const float scaling = _cachedScale.GetAbsolute().MaxValue(); const Vector3 position = _transform.LocalToWorld(_center); - _controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, position, _slopeLimit, (int32)_nonWalkableMode, Material.Get(), Math::Abs(_radius) * scaling, Math::Abs(_height) * scaling, _stepOffset, _shape); + _controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, position, _slopeLimit, (int32)_nonWalkableMode, Material, Math::Abs(_radius) * scaling, Math::Abs(_height) * scaling, _stepOffset, _shape); // Setup PhysicsBackend::SetControllerUpDirection(_controller, _upDirection); diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index 4326a033d..e354057d2 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -201,7 +201,7 @@ void Collider::CreateShape() // Create shape const bool isTrigger = _isTrigger && CanBeTrigger(); - _shape = PhysicsBackend::CreateShape(this, shape, Material.Get(), IsActiveInHierarchy(), isTrigger); + _shape = PhysicsBackend::CreateShape(this, shape, Material, IsActiveInHierarchy(), isTrigger); PhysicsBackend::SetShapeContactOffset(_shape, _contactOffset); UpdateLayerBits(); } @@ -288,7 +288,7 @@ void Collider::OnMaterialChanged() { // Update the shape material if (_shape) - PhysicsBackend::SetShapeMaterial(_shape, Material.Get()); + PhysicsBackend::SetShapeMaterial(_shape, Material); } void Collider::BeginPlay(SceneBeginData* data) diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index bd17aa27a..cbbb7e522 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -4,7 +4,7 @@ #include "Engine/Physics/Types.h" #include "Engine/Content/JsonAsset.h" -#include "Engine/Content/AssetReference.h" +#include "Engine/Content/JsonAssetReference.h" #include "Engine/Physics/Actors/PhysicsColliderActor.h" struct RayCastHit; @@ -80,8 +80,8 @@ public: /// /// The physical material used to define the collider physical properties. /// - API_FIELD(Attributes="EditorOrder(2), DefaultValue(null), AssetReference(typeof(PhysicalMaterial), true), EditorDisplay(\"Collider\")") - AssetReference Material; + API_FIELD(Attributes="EditorOrder(2), DefaultValue(null), EditorDisplay(\"Collider\")") + JsonAssetReference Material; public: /// diff --git a/Source/Engine/Physics/PhysicalMaterial.h b/Source/Engine/Physics/PhysicalMaterial.h index ff27437e8..f1aa0c1fd 100644 --- a/Source/Engine/Physics/PhysicalMaterial.h +++ b/Source/Engine/Physics/PhysicalMaterial.h @@ -15,17 +15,9 @@ class FLAXENGINE_API PhysicalMaterial final : public ISerializable API_AUTO_SERIALIZATION(); DECLARE_SCRIPTING_TYPE_MINIMAL(PhysicalMaterial); private: - void* _material; + void* _material = nullptr; public: - /// - /// Initializes a new instance of the class. - /// - PhysicalMaterial(); - - /// - /// Finalizes an instance of the class. - /// ~PhysicalMaterial(); public: diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index 5d9b218ec..942f1b79b 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -78,11 +78,6 @@ void PhysicsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* } } -PhysicalMaterial::PhysicalMaterial() - : _material(nullptr) -{ -} - PhysicalMaterial::~PhysicalMaterial() { if (_material) diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index f347e3732..772abf4d3 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -239,7 +239,7 @@ void Terrain::OnPhysicalMaterialChanged() const auto patch = _patches[pathIndex]; if (patch->HasCollision()) { - PhysicsBackend::SetShapeMaterial(patch->_physicsShape, PhysicalMaterial.Get()); + PhysicsBackend::SetShapeMaterial(patch->_physicsShape, PhysicalMaterial); } } } diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h index 636f30206..1f495c5ab 100644 --- a/Source/Engine/Terrain/Terrain.h +++ b/Source/Engine/Terrain/Terrain.h @@ -2,7 +2,7 @@ #pragma once -#include "Engine/Content/JsonAsset.h" +#include "Engine/Content/JsonAssetReference.h" #include "Engine/Content/Assets/MaterialBase.h" #include "Engine/Physics/Actors/PhysicsColliderActor.h" @@ -79,8 +79,8 @@ public: /// /// The physical material used to define the terrain collider physical properties. /// - API_FIELD(Attributes="EditorOrder(520), DefaultValue(null), Limit(-1, 100, 0.1f), EditorDisplay(\"Collision\"), AssetReference(typeof(PhysicalMaterial), true)") - AssetReference PhysicalMaterial; + API_FIELD(Attributes="EditorOrder(520), DefaultValue(null), Limit(-1, 100, 0.1f), EditorDisplay(\"Collision\")") + JsonAssetReference<::PhysicalMaterial> PhysicalMaterial; /// /// The draw passes to use for rendering this object. diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 8e05062c8..7fa449601 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -2125,7 +2125,7 @@ void TerrainPatch::CreateCollision() shape.SetHeightField(_physicsHeightField, heightScale, rowScale, columnScale); // Create shape - _physicsShape = PhysicsBackend::CreateShape(_terrain, shape, _terrain->PhysicalMaterial.Get(), _terrain->IsActiveInHierarchy(), false); + _physicsShape = PhysicsBackend::CreateShape(_terrain, shape, _terrain->PhysicalMaterial, _terrain->IsActiveInHierarchy(), false); PhysicsBackend::SetShapeLocalPose(_physicsShape, Vector3(0, _yOffset * terrainTransform.Scale.Y, 0), Quaternion::Identity); // Create static actor From b5e23f0096a6438659f362bce115a00101bb80d5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 15 Feb 2024 11:46:17 +0100 Subject: [PATCH 20/42] Improve JsonAssetReference --- Source/Engine/AI/BehaviorKnowledgeSelector.cs | 4 +- Source/Engine/Content/JsonAssetReference.cs | 38 ++++++++++++++++--- Source/Engine/Content/JsonAssetReference.h | 9 +++++ 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/Source/Engine/AI/BehaviorKnowledgeSelector.cs b/Source/Engine/AI/BehaviorKnowledgeSelector.cs index 5c642e92a..cdcaf6c40 100644 --- a/Source/Engine/AI/BehaviorKnowledgeSelector.cs +++ b/Source/Engine/AI/BehaviorKnowledgeSelector.cs @@ -146,7 +146,7 @@ namespace FlaxEngine public string Path; /// - /// Initializes a new instance of the structure. + /// Initializes a new instance of the structure. /// /// The selector path. public BehaviorKnowledgeSelector(string path) @@ -155,7 +155,7 @@ namespace FlaxEngine } /// - /// Initializes a new instance of the structure. + /// Initializes a new instance of the structure. /// /// The other selector. public BehaviorKnowledgeSelector(BehaviorKnowledgeSelectorAny other) diff --git a/Source/Engine/Content/JsonAssetReference.cs b/Source/Engine/Content/JsonAssetReference.cs index adfaa5179..7c8a8902f 100644 --- a/Source/Engine/Content/JsonAssetReference.cs +++ b/Source/Engine/Content/JsonAssetReference.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; +using System.Runtime.CompilerServices; namespace FlaxEngine { @@ -19,13 +20,9 @@ namespace FlaxEngine public JsonAsset Asset; /// - /// Gets the instance of the Json Asset. Null if unset. + /// Gets the instance of the serialized object from the json asset data. Cached internally. /// - /// instance of the Json Asset or null if unset. - public T Get() - { - return (T)Asset?.Instance; - } + public T Instance => (T)Asset?.Instance; /// /// Initializes a new instance of the structure. @@ -68,6 +65,35 @@ namespace FlaxEngine return new JsonAssetReference(Object.FromUnmanagedPtr(valuePtr) as JsonAsset); } + /// + /// Checks if the object exists (reference is not null and the unmanaged object pointer is valid). + /// + /// The object to check. + /// True if object is valid, otherwise false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator bool(JsonAssetReference obj) + { + return obj.Asset; + } + + /// + /// Checks whether the two objects are equal. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(JsonAssetReference left, JsonAssetReference right) + { + return left.Asset == right.Asset; + } + + /// + /// Checks whether the two objects are not equal. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(JsonAssetReference left, JsonAssetReference right) + { + return left.Asset != right.Asset; + } + /// public override string ToString() { diff --git a/Source/Engine/Content/JsonAssetReference.h b/Source/Engine/Content/JsonAssetReference.h index 82fbe47f1..d84c44926 100644 --- a/Source/Engine/Content/JsonAssetReference.h +++ b/Source/Engine/Content/JsonAssetReference.h @@ -12,6 +12,15 @@ template API_STRUCT(NoDefault, Template, MarshalAs=JsonAsset*) struct JsonAssetReference : AssetReference { + /// + /// Gets the deserialized native object instance of the given type. Returns null if asset is not loaded or loaded object has different type. + /// + /// The asset instance object or null. + FORCE_INLINE T* GetInstance() const + { + return _asset ? Get()->GetInstance() : nullptr; + } + JsonAssetReference& operator=(JsonAsset* asset) noexcept { OnSet(asset); From 5fc7c6e190500b109b8d540420f6b7e1ba26c57f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 15 Feb 2024 18:26:12 +0100 Subject: [PATCH 21/42] Add `MinCount` and `MaxCount` to `Collection` attribute --- .../CustomEditors/Editors/CollectionEditor.cs | 29 +++++++++++-------- .../Attributes/CollectionAttribute.cs | 10 +++++++ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index 00322fc81..38dce0a25 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -176,8 +176,8 @@ namespace FlaxEditor.CustomEditors.Editors private IntValueBox _sizeBox; private Color _background; - private int _elementsCount; - private bool _readOnly; + private int _elementsCount, _minCount, _maxCount; + private bool _canResize; private bool _canReorderItems; private CollectionAttribute.DisplayType _displayType; @@ -209,8 +209,10 @@ namespace FlaxEditor.CustomEditors.Editors return; var size = Count; - _readOnly = false; + _canResize = true; _canReorderItems = true; + _minCount = 0; + _maxCount = 0; _background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor; _displayType = CollectionAttribute.DisplayType.Header; NotNullItems = false; @@ -222,7 +224,9 @@ namespace FlaxEditor.CustomEditors.Editors var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute); if (collection != null) { - _readOnly = collection.ReadOnly; + _canResize = !collection.ReadOnly; + _minCount = collection.MinCount; + _maxCount = collection.MaxCount; _canReorderItems = collection.CanReorderItems; NotNullItems = collection.NotNullItems; if (collection.BackgroundColor.HasValue) @@ -231,6 +235,9 @@ namespace FlaxEditor.CustomEditors.Editors spacing = collection.Spacing; _displayType = collection.Display; } + if (_maxCount == 0) + _maxCount = ushort.MaxValue; + _canResize &= _minCount < _maxCount; var dragArea = layout.CustomContainer(); dragArea.CustomControl.Editor = this; @@ -268,8 +275,8 @@ namespace FlaxEditor.CustomEditors.Editors var y = -dropPanel.HeaderHeight + dropPanel.HeaderTextMargin.Top; _sizeBox = new IntValueBox(size) { - MinValue = 0, - MaxValue = ushort.MaxValue, + MinValue = _minCount, + MaxValue = _maxCount, AnchorPreset = AnchorPresets.TopRight, Bounds = new Rectangle(-40 - dropPanel.ItemsMargin.Right, y, 40, height), Parent = dropPanel, @@ -283,7 +290,7 @@ namespace FlaxEditor.CustomEditors.Editors Parent = dropPanel }; - if (_readOnly || (NotNullItems && size == 0)) + if (!_canResize || (NotNullItems && size == 0)) { _sizeBox.IsReadOnly = true; _sizeBox.Enabled = false; @@ -339,7 +346,7 @@ namespace FlaxEditor.CustomEditors.Editors _elementsCount = size; // Add/Remove buttons - if (!_readOnly) + if (_canResize) { var panel = dragArea.HorizontalPanel(); panel.Panel.Size = new Float2(0, 20); @@ -347,25 +354,23 @@ namespace FlaxEditor.CustomEditors.Editors var removeButton = panel.Button("-", "Remove last item"); removeButton.Button.Size = new Float2(16, 16); - removeButton.Button.Enabled = size > 0; + removeButton.Button.Enabled = size > _minCount; removeButton.Button.AnchorPreset = AnchorPresets.TopRight; removeButton.Button.Clicked += () => { if (IsSetBlocked) return; - Resize(Count - 1); }; var addButton = panel.Button("+", "Add new item"); addButton.Button.Size = new Float2(16, 16); - addButton.Button.Enabled = !NotNullItems || size > 0; + addButton.Button.Enabled = (!NotNullItems || size > 0) && size < _maxCount; addButton.Button.AnchorPreset = AnchorPresets.TopRight; addButton.Button.Clicked += () => { if (IsSetBlocked) return; - Resize(Count + 1); }; } diff --git a/Source/Engine/Scripting/Attributes/CollectionAttribute.cs b/Source/Engine/Scripting/Attributes/CollectionAttribute.cs index cd2e962e6..0603a9390 100644 --- a/Source/Engine/Scripting/Attributes/CollectionAttribute.cs +++ b/Source/Engine/Scripting/Attributes/CollectionAttribute.cs @@ -61,6 +61,16 @@ namespace FlaxEngine /// public float Spacing; + /// + /// The minimum size of the collection. + /// + public int MinCount; + + /// + /// The maximum size of the collection. Zero if unlimited. + /// + public int MaxCount; + /// /// The collection background color. /// From c7a449fe1c63bcb1a444345e0da7ded499202f70 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 15 Feb 2024 18:28:51 +0100 Subject: [PATCH 22/42] Fix marshaling custom type array to C# with `MarshalAs` used --- .../Bindings/BindingsGenerator.CSharp.cs | 72 ++++++++++++++----- .../Bindings/BindingsGenerator.Cpp.cs | 43 ++++++++--- 2 files changed, 88 insertions(+), 27 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index dc9f74369..e1d4df083 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -270,7 +270,7 @@ namespace Flax.Build.Bindings return value; } - private static string GenerateCSharpNativeToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller) + private static string GenerateCSharpNativeToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, bool marshalling = false) { string result; if (typeInfo?.Type == null) @@ -280,7 +280,7 @@ namespace Flax.Build.Bindings if (typeInfo.IsArray) { typeInfo.IsArray = false; - result = GenerateCSharpNativeToManaged(buildData, typeInfo, caller); + result = GenerateCSharpNativeToManaged(buildData, typeInfo, caller, marshalling); typeInfo.IsArray = true; return result + "[]"; } @@ -307,7 +307,7 @@ namespace Flax.Build.Bindings // Object reference property if (typeInfo.IsObjectRef) - return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller); + return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling); if (typeInfo.Type == "SoftTypeReference" || typeInfo.Type == "SoftObjectReference") return typeInfo.Type; @@ -317,15 +317,25 @@ namespace Flax.Build.Bindings #else if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null) #endif - return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller) + "[]"; + { + var arrayTypeInfo = typeInfo.GenericArgs[0]; + if (marshalling) + { + // Convert array that uses different type for marshalling + var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); + if (arrayApiType != null && arrayApiType.MarshalAs != null) + arrayTypeInfo = arrayApiType.MarshalAs; + } + return GenerateCSharpNativeToManaged(buildData, arrayTypeInfo, caller) + "[]"; + } // Dictionary if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) - return string.Format("System.Collections.Generic.Dictionary<{0}, {1}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller), GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[1], caller)); + return string.Format("System.Collections.Generic.Dictionary<{0}, {1}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling), GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[1], caller, marshalling)); // HashSet if (typeInfo.Type == "HashSet" && typeInfo.GenericArgs != null) - return string.Format("System.Collections.Generic.HashSet<{0}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller)); + return string.Format("System.Collections.Generic.HashSet<{0}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling)); // BitArray if (typeInfo.Type == "BitArray" && typeInfo.GenericArgs != null) @@ -348,16 +358,16 @@ namespace Flax.Build.Bindings // TODO: generate delegates globally in the module namespace to share more code (smaller binary size) var key = string.Empty; for (int i = 0; i < typeInfo.GenericArgs.Count; i++) - key += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller); + key += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller, marshalling); if (!CSharpAdditionalCodeCache.TryGetValue(key, out var delegateName)) { delegateName = "Delegate" + CSharpAdditionalCodeCache.Count; - var signature = $"public delegate {GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller)} {delegateName}("; + var signature = $"public delegate {GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling)} {delegateName}("; for (int i = 1; i < typeInfo.GenericArgs.Count; i++) { if (i != 1) signature += ", "; - signature += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller); + signature += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller, marshalling); signature += $" arg{(i - 1)}"; } signature += ");"; @@ -390,11 +400,14 @@ namespace Flax.Build.Bindings { typeName += '<'; foreach (var arg in typeInfo.GenericArgs) - typeName += GenerateCSharpNativeToManaged(buildData, arg, caller); + typeName += GenerateCSharpNativeToManaged(buildData, arg, caller, marshalling); typeName += '>'; } if (apiType != null) { + if (marshalling && apiType.MarshalAs != null) + return GenerateCSharpNativeToManaged(buildData, apiType.MarshalAs, caller); + // Add reference to the namespace CSharpUsedNamespaces.Add(apiType.Namespace); var apiTypeParent = apiType.Parent; @@ -419,11 +432,11 @@ namespace Flax.Build.Bindings return typeName; } - private static string GenerateCSharpManagedToNativeType(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller) + private static string GenerateCSharpManagedToNativeType(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, bool marshalling = false) { // Fixed-size array if (typeInfo.IsArray) - return GenerateCSharpNativeToManaged(buildData, typeInfo, caller); + return GenerateCSharpNativeToManaged(buildData, typeInfo, caller, marshalling); // Find API type info var apiType = FindApiTypeInfo(buildData, typeInfo, caller); @@ -439,7 +452,7 @@ namespace Flax.Build.Bindings } if (apiType.MarshalAs != null) - return GenerateCSharpManagedToNativeType(buildData, apiType.MarshalAs, caller); + return GenerateCSharpManagedToNativeType(buildData, apiType.MarshalAs, caller, marshalling); if (apiType.IsScriptingObject || apiType.IsInterface) return "IntPtr"; } @@ -452,7 +465,7 @@ namespace Flax.Build.Bindings if (typeInfo.Type == "Function" && typeInfo.GenericArgs != null) return "IntPtr"; - return GenerateCSharpNativeToManaged(buildData, typeInfo, caller); + return GenerateCSharpNativeToManaged(buildData, typeInfo, caller, marshalling); } private static string GenerateCSharpManagedToNativeConverter(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller) @@ -485,6 +498,18 @@ namespace Flax.Build.Bindings case "Function": // delegate return "NativeInterop.GetFunctionPointerForDelegate({0})"; + case "Array": + case "Span": + case "DataContainer": + if (typeInfo.GenericArgs != null) + { + // Convert array that uses different type for marshalling + var arrayTypeInfo = typeInfo.GenericArgs[0]; + var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); + if (arrayApiType != null && arrayApiType.MarshalAs != null) + return $"{{0}}.ConvertArray(x => ({GenerateCSharpNativeToManaged(buildData, arrayApiType.MarshalAs, caller)})x)"; + } + return string.Empty; default: var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) @@ -531,9 +556,9 @@ namespace Flax.Build.Bindings { var apiType = FindApiTypeInfo(buildData, functionInfo.ReturnType, caller); if (apiType != null && apiType.MarshalAs != null) - returnValueType = GenerateCSharpNativeToManaged(buildData, apiType.MarshalAs, caller); + returnValueType = GenerateCSharpNativeToManaged(buildData, apiType.MarshalAs, caller, true); else - returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, caller); + returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, caller, true); } #if USE_NETCORE @@ -594,7 +619,7 @@ namespace Flax.Build.Bindings contents.Append(", "); separator = true; - var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller); + var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller, true); #if USE_NETCORE string parameterMarshalType = ""; if (nativeType == "System.Type") @@ -643,7 +668,7 @@ namespace Flax.Build.Bindings contents.Append(", "); separator = true; - var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller); + var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller, true); #if USE_NETCORE string parameterMarshalType = ""; if (parameterInfo.IsOut && parameterInfo.DefaultValue == "var __resultAsRef") @@ -756,7 +781,16 @@ namespace Flax.Build.Bindings } } - contents.Append(");"); + contents.Append(')'); + if ((functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer") && functionInfo.ReturnType.GenericArgs != null) + { + // Convert array that uses different type for marshalling + var arrayTypeInfo = functionInfo.ReturnType.GenericArgs[0]; + var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); + if (arrayApiType != null && arrayApiType.MarshalAs != null) + contents.Append($".ConvertArray(x => ({GenerateCSharpNativeToManaged(buildData, arrayTypeInfo, caller)})x)"); + } + contents.Append(';'); // Return result if (functionInfo.Glue.UseReferenceForResult) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 8e1836b2f..35d1d32fa 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -305,7 +305,7 @@ namespace Flax.Build.Bindings private static string GenerateCppGetMClass(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, FunctionInfo functionInfo) { // Optimal path for in-build types - var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller); + var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller, true); switch (managedType) { // In-built types (cached by the engine on startup) @@ -388,7 +388,7 @@ namespace Flax.Build.Bindings CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h"); // Optimal path for in-build types - var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller); + var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller, true); switch (managedType) { case "bool": @@ -519,16 +519,28 @@ namespace Flax.Build.Bindings // Array or DataContainer if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null) { + var arrayTypeInfo = typeInfo.GenericArgs[0]; #if USE_NETCORE // Boolean arrays does not support custom marshalling for some unknown reason - if (typeInfo.GenericArgs[0].Type == "bool") + if (arrayTypeInfo.Type == "bool") { type = "bool*"; return "MUtils::ToBoolArray({0})"; } + var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); #endif type = "MArray*"; - return "MUtils::ToArray({0}, " + GenerateCppGetMClass(buildData, typeInfo.GenericArgs[0], caller, functionInfo) + ")"; + if (arrayApiType != null && arrayApiType.MarshalAs != null) + { + // Convert array that uses different type for marshalling + if (arrayApiType != null && arrayApiType.MarshalAs != null) + arrayTypeInfo = arrayApiType.MarshalAs; // Convert array that uses different type for marshalling + var genericArgs = arrayApiType.MarshalAs.GetFullNameNative(buildData, caller); + if (typeInfo.GenericArgs.Count != 1) + genericArgs += ", " + typeInfo.GenericArgs[1]; + return "MUtils::ToArray(Array<" + genericArgs + ">({0}), " + GenerateCppGetMClass(buildData, arrayTypeInfo, caller, functionInfo) + ")"; + } + return "MUtils::ToArray({0}, " + GenerateCppGetMClass(buildData, arrayTypeInfo, caller, functionInfo) + ")"; } // Span @@ -719,11 +731,26 @@ namespace Flax.Build.Bindings // Array if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) { - var T = typeInfo.GenericArgs[0].GetFullNameNative(buildData, caller); - type = "MArray*"; + var arrayTypeInfo = typeInfo.GenericArgs[0]; + var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); + if (arrayApiType != null && arrayApiType.MarshalAs != null) + arrayTypeInfo = arrayApiType.MarshalAs; + var genericArgs = arrayTypeInfo.GetFullNameNative(buildData, caller); if (typeInfo.GenericArgs.Count != 1) - return "MUtils::ToArray<" + T + ", " + typeInfo.GenericArgs[1] + ">({0})"; - return "MUtils::ToArray<" + T + ">({0})"; + genericArgs += ", " + typeInfo.GenericArgs[1]; + + type = "MArray*"; + var result = "MUtils::ToArray<" + genericArgs + ">({0})"; + + if (arrayApiType != null && arrayApiType.MarshalAs != null) + { + // Convert array that uses different type for marshalling + genericArgs = typeInfo.GenericArgs[0].GetFullNameNative(buildData, caller); + if (typeInfo.GenericArgs.Count != 1) + genericArgs += ", " + typeInfo.GenericArgs[1]; + result = $"Array<{genericArgs}>({result})"; + } + return result; } // Span or DataContainer From f0f8da3c09a871160ffe7a3d34f4ace3d50ec52e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 16 Feb 2024 12:29:59 +0100 Subject: [PATCH 23/42] Various fixes --- Source/Engine/AI/BehaviorKnowledgeSelector.h | 7 +++++ Source/Engine/Content/JsonAssetReference.cs | 28 ++++++++++++++++++-- Source/Engine/Content/JsonAssetReference.h | 7 +++++ Source/Engine/Engine/NativeInterop.cs | 4 +-- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/Source/Engine/AI/BehaviorKnowledgeSelector.h b/Source/Engine/AI/BehaviorKnowledgeSelector.h index 976711282..092d42b1c 100644 --- a/Source/Engine/AI/BehaviorKnowledgeSelector.h +++ b/Source/Engine/AI/BehaviorKnowledgeSelector.h @@ -91,6 +91,13 @@ API_STRUCT(InBuild, Template, MarshalAs=StringAnsi) struct FLAXENGINE_API Behavi return false; } + BehaviorKnowledgeSelector() = default; + + BehaviorKnowledgeSelector(const StringAnsi& other) + { + Path = other; + } + BehaviorKnowledgeSelector& operator=(const StringAnsiView& other) noexcept { Path = other; diff --git a/Source/Engine/Content/JsonAssetReference.cs b/Source/Engine/Content/JsonAssetReference.cs index 7c8a8902f..766645161 100644 --- a/Source/Engine/Content/JsonAssetReference.cs +++ b/Source/Engine/Content/JsonAssetReference.cs @@ -12,7 +12,7 @@ namespace FlaxEngine #if FLAX_EDITOR [CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.AssetRefEditor))] #endif - public struct JsonAssetReference + public struct JsonAssetReference : IComparable, IComparable>, IEquatable> { /// /// Gets or sets the referenced asset. @@ -94,16 +94,40 @@ namespace FlaxEngine return left.Asset != right.Asset; } + /// + public bool Equals(JsonAssetReference other) + { + return Asset == other.Asset; + } + + /// + public int CompareTo(JsonAssetReference other) + { + return Object.GetUnmanagedPtr(Asset).CompareTo(Object.GetUnmanagedPtr(other.Asset)); + } + + /// + public override bool Equals(object obj) + { + return obj is JsonAssetReference other && Asset == other.Asset; + } + /// public override string ToString() { return Asset?.ToString(); } + /// + public int CompareTo(object obj) + { + return obj is JsonAssetReference other ? CompareTo(other) : 1; + } + /// public override int GetHashCode() { - return Asset?.GetHashCode() ?? 0; + return (Asset != null ? Asset.GetHashCode() : 0); } } } diff --git a/Source/Engine/Content/JsonAssetReference.h b/Source/Engine/Content/JsonAssetReference.h index d84c44926..965a0aaa0 100644 --- a/Source/Engine/Content/JsonAssetReference.h +++ b/Source/Engine/Content/JsonAssetReference.h @@ -12,6 +12,13 @@ template API_STRUCT(NoDefault, Template, MarshalAs=JsonAsset*) struct JsonAssetReference : AssetReference { + JsonAssetReference() = default; + + JsonAssetReference(JsonAsset* asset) + { + OnSet(asset); + } + /// /// Gets the deserialized native object instance of the given type. Returns null if asset is not loaded or loaded object has different type. /// diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 026f54fec..7dfd52d22 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -249,7 +249,7 @@ namespace FlaxEngine.Interop /// The input array. /// Converter callback. /// The output array. - public static TDst[] ConvertArray(Span src, Func convertFunc) + public static TDst[] ConvertArray(this Span src, Func convertFunc) { TDst[] dst = new TDst[src.Length]; for (int i = 0; i < src.Length; i++) @@ -265,7 +265,7 @@ namespace FlaxEngine.Interop /// The input array. /// Converter callback. /// The output array. - public static TDst[] ConvertArray(TSrc[] src, Func convertFunc) + public static TDst[] ConvertArray(this TSrc[] src, Func convertFunc) { TDst[] dst = new TDst[src.Length]; for (int i = 0; i < src.Length; i++) From f04f1cc90e74ae3368831b85cced31e5746a3bac Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 16 Feb 2024 12:46:35 +0100 Subject: [PATCH 24/42] Add `ScriptingEnum::ToStringFlags` for printing flag enums into readable text --- Source/Engine/Graphics/GPUBuffer.cpp | 32 ++++--------------- .../Engine/Graphics/Textures/GPUTexture.cpp | 31 ++++-------------- Source/Engine/Scripting/Enums.h | 26 +++++++++++++++ 3 files changed, 38 insertions(+), 51 deletions(-) diff --git a/Source/Engine/Graphics/GPUBuffer.cpp b/Source/Engine/Graphics/GPUBuffer.cpp index 6e400bbdd..6d1ceea41 100644 --- a/Source/Engine/Graphics/GPUBuffer.cpp +++ b/Source/Engine/Graphics/GPUBuffer.cpp @@ -12,6 +12,7 @@ #include "Engine/Debug/Exceptions/InvalidOperationException.h" #include "Engine/Debug/Exceptions/ArgumentNullException.h" #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/Enums.h" #include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Threading/Threading.h" @@ -81,33 +82,10 @@ bool GPUBufferDescription::Equals(const GPUBufferDescription& other) const String GPUBufferDescription::ToString() const { - // TODO: add tool to Format to string - - String flags; - if (Flags == GPUBufferFlags::None) - { - flags = TEXT("None"); - } - else - { - // TODO: create tool to auto convert flag enums to string - -#define CONVERT_FLAGS_FLAGS_2_STR(value) if (EnumHasAnyFlags(Flags, GPUBufferFlags::value)) { if (flags.HasChars()) flags += TEXT('|'); flags += TEXT(#value); } - CONVERT_FLAGS_FLAGS_2_STR(ShaderResource); - CONVERT_FLAGS_FLAGS_2_STR(VertexBuffer); - CONVERT_FLAGS_FLAGS_2_STR(IndexBuffer); - CONVERT_FLAGS_FLAGS_2_STR(UnorderedAccess); - CONVERT_FLAGS_FLAGS_2_STR(Append); - CONVERT_FLAGS_FLAGS_2_STR(Counter); - CONVERT_FLAGS_FLAGS_2_STR(Argument); - CONVERT_FLAGS_FLAGS_2_STR(Structured); -#undef CONVERT_FLAGS_FLAGS_2_STR - } - return String::Format(TEXT("Size: {0}, Stride: {1}, Flags: {2}, Format: {3}, Usage: {4}"), Size, Stride, - flags, + ScriptingEnum::ToStringFlags(Flags), ScriptingEnum::ToString(Format), (int32)Usage); } @@ -212,7 +190,7 @@ GPUBuffer* GPUBuffer::ToStagingUpload() const bool GPUBuffer::Resize(uint32 newSize) { - // Validate input + PROFILE_CPU(); if (!IsAllocated()) { Log::InvalidOperationException(TEXT("Buffer.Resize")); @@ -236,12 +214,12 @@ bool GPUBuffer::DownloadData(BytesContainer& result) LOG(Warning, "Cannot download GPU buffer data from an empty buffer."); return true; } - if (_desc.Usage == GPUResourceUsage::StagingReadback || _desc.Usage == GPUResourceUsage::Dynamic) { // Use faster path for staging resources return GetData(result); } + PROFILE_CPU(); // Ensure not running on main thread if (IsInMainThread()) @@ -358,6 +336,7 @@ Task* GPUBuffer::DownloadDataAsync(BytesContainer& result) bool GPUBuffer::GetData(BytesContainer& output) { + PROFILE_CPU(); void* mapped = Map(GPUResourceMapMode::Read); if (!mapped) return true; @@ -368,6 +347,7 @@ bool GPUBuffer::GetData(BytesContainer& output) void GPUBuffer::SetData(const void* data, uint32 size) { + PROFILE_CPU(); if (size == 0 || data == nullptr) { Log::ArgumentNullException(TEXT("Buffer.SetData")); diff --git a/Source/Engine/Graphics/Textures/GPUTexture.cpp b/Source/Engine/Graphics/Textures/GPUTexture.cpp index 403f545ba..93642f00a 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.cpp +++ b/Source/Engine/Graphics/Textures/GPUTexture.cpp @@ -15,6 +15,7 @@ #include "Engine/Graphics/GPULimits.h" #include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Graphics/GPUDevice.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/Enums.h" namespace @@ -158,29 +159,6 @@ bool GPUTextureDescription::Equals(const GPUTextureDescription& other) const String GPUTextureDescription::ToString() const { - // TODO: add tool to Format to string - - String flags; - if (Flags == GPUTextureFlags::None) - { - flags = TEXT("None"); - } - else - { - // TODO: create tool to auto convert flag enums to string - -#define CONVERT_FLAGS_FLAGS_2_STR(value) if (EnumHasAnyFlags(Flags, GPUTextureFlags::value)) { if (flags.HasChars()) flags += TEXT('|'); flags += TEXT(#value); } - CONVERT_FLAGS_FLAGS_2_STR(ShaderResource); - CONVERT_FLAGS_FLAGS_2_STR(RenderTarget); - CONVERT_FLAGS_FLAGS_2_STR(UnorderedAccess); - CONVERT_FLAGS_FLAGS_2_STR(DepthStencil); - CONVERT_FLAGS_FLAGS_2_STR(PerMipViews); - CONVERT_FLAGS_FLAGS_2_STR(PerSliceViews); - CONVERT_FLAGS_FLAGS_2_STR(ReadOnlyDepthView); - CONVERT_FLAGS_FLAGS_2_STR(BackBuffer); -#undef CONVERT_FLAGS_FLAGS_2_STR - } - return String::Format(TEXT("Size: {0}x{1}x{2}[{3}], Type: {4}, Mips: {5}, Format: {6}, MSAA: {7}, Flags: {8}, Usage: {9}"), Width, Height, @@ -190,7 +168,7 @@ String GPUTextureDescription::ToString() const MipLevels, ScriptingEnum::ToString(Format), ::ToString(MultiSampleLevel), - flags, + ScriptingEnum::ToStringFlags(Flags), (int32)Usage); } @@ -544,7 +522,7 @@ GPUTexture* GPUTexture::ToStagingUpload() const bool GPUTexture::Resize(int32 width, int32 height, int32 depth, PixelFormat format) { - // Validate texture is created + PROFILE_CPU(); if (!IsAllocated()) { LOG(Warning, "Cannot resize not created textures."); @@ -608,6 +586,7 @@ GPUTask* GPUTexture::UploadMipMapAsync(const BytesContainer& data, int32 mipInde GPUTask* GPUTexture::UploadMipMapAsync(const BytesContainer& data, int32 mipIndex, int32 rowPitch, int32 slicePitch, bool copyData) { + PROFILE_CPU(); ASSERT(IsAllocated()); ASSERT(mipIndex < MipLevels() && data.IsValid()); ASSERT(data.Length() >= slicePitch); @@ -699,6 +678,7 @@ bool GPUTexture::DownloadData(TextureData& result) { MISSING_CODE("support volume texture data downloading."); } + PROFILE_CPU(); // Use faster path for staging resources if (IsStaging()) @@ -780,6 +760,7 @@ Task* GPUTexture::DownloadDataAsync(TextureData& result) { MISSING_CODE("support volume texture data downloading."); } + PROFILE_CPU(); // Use faster path for staging resources if (IsStaging()) diff --git a/Source/Engine/Scripting/Enums.h b/Source/Engine/Scripting/Enums.h index dd605d303..7c8d36f24 100644 --- a/Source/Engine/Scripting/Enums.h +++ b/Source/Engine/Scripting/Enums.h @@ -68,4 +68,30 @@ public: { return FromString(StringAnsi(name)); } + + // Gets the name of the enum value as separated flags + template + static String ToStringFlags(EnumType value, Char separator = '|') + { + String result; + if (const auto items = GetItems()) + { + for (int32 i = 0; items[i].Name; i++) + { + const uint64 itemValue = items[i].Value; + if ((uint64)value == 0 && itemValue == 0) + { + result = items[i].Name; + break; + } + if (itemValue != 0 && EnumHasAllFlags(value, (EnumType)itemValue)) + { + if (result.HasChars()) + result += separator; + result += items[i].Name; + } + } + } + return result; + } }; From 42b4443e14030baca6658eb525b1e064041dec4a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 16 Feb 2024 17:11:40 +0100 Subject: [PATCH 25/42] Add support for multiple physical materials in terrain - one for each painted layer #1112 #2159 --- .../Tools/Terrain/CreateTerrainDialog.cs | 4 - .../Physics/PhysX/PhysicsBackendPhysX.cpp | 46 +- Source/Engine/Physics/PhysicsBackend.h | 16 +- Source/Engine/Physics/PhysicsBackendEmpty.cpp | 11 +- Source/Engine/Terrain/Terrain.cpp | 46 +- Source/Engine/Terrain/Terrain.h | 36 +- Source/Engine/Terrain/TerrainPatch.cpp | 411 +++++++++--------- Source/Engine/Terrain/TerrainPatch.h | 34 +- 8 files changed, 306 insertions(+), 298 deletions(-) diff --git a/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs b/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs index 252891d44..fd28c302b 100644 --- a/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs +++ b/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs @@ -45,9 +45,6 @@ namespace FlaxEditor.Tools.Terrain [EditorOrder(130), EditorDisplay("Layout"), DefaultValue(null), Tooltip("The default material used for terrain rendering (chunks can override this). It must have Domain set to terrain.")] public MaterialBase Material; - [EditorOrder(200), EditorDisplay("Collision"), DefaultValue(null), AssetReference(typeof(PhysicalMaterial), true), Tooltip("Terrain default physical material used to define the collider physical properties.")] - public JsonAsset PhysicalMaterial; - [EditorOrder(210), EditorDisplay("Collision", "Collision LOD"), DefaultValue(-1), Limit(-1, 100, 0.1f), Tooltip("Terrain geometry LOD index used for collision.")] public int CollisionLOD = -1; @@ -152,7 +149,6 @@ namespace FlaxEditor.Tools.Terrain terrain.Setup(_options.LODCount, (int)_options.ChunkSize); terrain.Transform = new Transform(_options.Position, _options.Orientation, _options.Scale); terrain.Material = _options.Material; - terrain.PhysicalMaterial = _options.PhysicalMaterial; terrain.CollisionLOD = _options.CollisionLOD; if (_options.Heightmap) terrain.Position -= new Vector3(0, _options.HeightmapScale * 0.5f, 0); diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index f3e7baed1..cb2f3e736 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -644,6 +644,19 @@ void GetShapeGeometry(const CollisionShape& shape, PxGeometryHolder& geometry) } } +void GetShapeMaterials(Array>& materialsPhysX, Span materials) +{ + materialsPhysX.Resize(materials.Length()); + for (int32 i = 0; i < materials.Length(); i++) + { + PxMaterial* materialPhysX = DefaultMaterial; + const JsonAsset* material = materials.Get()[i]; + if (material && !material->WaitForLoaded() && material->Instance) + materialPhysX = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial(); + materialsPhysX.Get()[i] = materialPhysX; + } +} + PxFilterFlags FilterShader( PxFilterObjectAttributes attributes0, PxFilterData filterData0, PxFilterObjectAttributes attributes1, PxFilterData filterData1, @@ -2449,17 +2462,14 @@ void PhysicsBackend::AddRigidDynamicActorTorque(void* actor, const Vector3& torq actorPhysX->addTorque(C2P(torque), static_cast(mode)); } -void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger) +void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, Span materials, bool enabled, bool trigger) { const PxShapeFlags shapeFlags = GetShapeFlags(trigger, enabled); - PxMaterial* materialPhysX = DefaultMaterial; - if (material && !material->WaitForLoaded() && material->Instance) - { - materialPhysX = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial(); - } + Array> materialsPhysX; + GetShapeMaterials(materialsPhysX, materials); PxGeometryHolder geometryPhysX; GetShapeGeometry(geometry, geometryPhysX); - PxShape* shapePhysX = PhysX->createShape(geometryPhysX.any(), *materialPhysX, true, shapeFlags); + PxShape* shapePhysX = PhysX->createShape(geometryPhysX.any(), materialsPhysX.Get(), materialsPhysX.Count(), true, shapeFlags); shapePhysX->userData = collider; #if PHYSX_DEBUG_NAMING shapePhysX->setName("Shape"); @@ -2549,15 +2559,12 @@ void PhysicsBackend::SetShapeContactOffset(void* shape, float value) shapePhysX->setContactOffset(Math::Max(shapePhysX->getRestOffset() + ZeroTolerance, value)); } -void PhysicsBackend::SetShapeMaterial(void* shape, JsonAsset* material) +void PhysicsBackend::SetShapeMaterials(void* shape, Span materials) { auto shapePhysX = (PxShape*)shape; - PxMaterial* materialPhysX = DefaultMaterial; - if (material && !material->WaitForLoaded() && material->Instance) - { - materialPhysX = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial(); - } - shapePhysX->setMaterials(&materialPhysX, 1); + Array> materialsPhysX; + GetShapeMaterials(materialsPhysX, materials); + shapePhysX->setMaterials(materialsPhysX.Get(), materialsPhysX.Count()); } void PhysicsBackend::SetShapeGeometry(void* shape, const CollisionShape& geometry) @@ -4077,10 +4084,17 @@ void PhysicsBackend::GetHeightFieldSize(void* heightField, int32& rows, int32& c columns = (int32)heightFieldPhysX->getNbColumns(); } -float PhysicsBackend::GetHeightFieldHeight(void* heightField, float x, float z) +float PhysicsBackend::GetHeightFieldHeight(void* heightField, int32 x, int32 z) { auto heightFieldPhysX = (PxHeightField*)heightField; - return heightFieldPhysX->getHeight(x, z); + return heightFieldPhysX->getHeight((float)x, (float)z); +} + +PhysicsBackend::HeightFieldSample PhysicsBackend::GetHeightFieldSample(void* heightField, int32 x, int32 z) +{ + auto heightFieldPhysX = (PxHeightField*)heightField; + auto sample = heightFieldPhysX->getSample(x, z); + return { sample.height, sample.materialIndex0, sample.materialIndex1 }; } bool PhysicsBackend::ModifyHeightField(void* heightField, int32 startCol, int32 startRow, int32 cols, int32 rows, const HeightFieldSample* data) diff --git a/Source/Engine/Physics/PhysicsBackend.h b/Source/Engine/Physics/PhysicsBackend.h index f20683e31..d7393df2c 100644 --- a/Source/Engine/Physics/PhysicsBackend.h +++ b/Source/Engine/Physics/PhysicsBackend.h @@ -4,6 +4,7 @@ #include "Physics.h" #include "PhysicsSettings.h" +#include "Engine/Core/Types/Span.h" struct HingeJointDrive; struct SpringParameters; @@ -182,7 +183,7 @@ public: static void AddRigidDynamicActorTorque(void* actor, const Vector3& torque, ForceMode mode); // Shapes - static void* CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger); + static void* CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, Span materials, bool enabled, bool trigger); static void SetShapeState(void* shape, bool enabled, bool trigger); static void SetShapeFilterMask(void* shape, uint32 mask0, uint32 mask1); static void* GetShapeActor(void* shape); @@ -191,7 +192,7 @@ public: static void GetShapeLocalPose(void* shape, Vector3& position, Quaternion& orientation); static void SetShapeLocalPose(void* shape, const Vector3& position, const Quaternion& orientation); static void SetShapeContactOffset(void* shape, float value); - static void SetShapeMaterial(void* shape, JsonAsset* material); + static void SetShapeMaterials(void* shape, Span materials); static void SetShapeGeometry(void* shape, const CollisionShape& geometry); static void AttachShape(void* shape, void* actor); static void DetachShape(void* shape, void* actor); @@ -303,7 +304,8 @@ public: static void GetTriangleMeshTriangles(void* triangleMesh, Array& vertexBuffer, Array& indexBuffer); static const uint32* GetTriangleMeshRemap(void* triangleMesh, uint32& count); static void GetHeightFieldSize(void* heightField, int32& rows, int32& columns); - static float GetHeightFieldHeight(void* heightField, float x, float z); + static float GetHeightFieldHeight(void* heightField, int32 x, int32 z); + static HeightFieldSample GetHeightFieldSample(void* heightField, int32 x, int32 z); static bool ModifyHeightField(void* heightField, int32 startCol, int32 startRow, int32 cols, int32 rows, const HeightFieldSample* data); static void FlushRequests(); static void FlushRequests(void* scene); @@ -330,6 +332,14 @@ public: flags = (RigidDynamicFlags)(((uint32)flags & ~(uint32)flag) | (value ? (uint32)flag : 0)); SetRigidDynamicActorFlags(actor, flags); } + FORCE_INLINE static void* CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger) + { + return CreateShape(collider, geometry, Span(&material, 1), enabled, trigger); + } + FORCE_INLINE static void SetShapeMaterial(void* shape, JsonAsset* material) + { + SetShapeMaterials(shape, Span(&material, 1)); + } }; DECLARE_ENUM_OPERATORS(PhysicsBackend::ActorFlags); diff --git a/Source/Engine/Physics/PhysicsBackendEmpty.cpp b/Source/Engine/Physics/PhysicsBackendEmpty.cpp index 1a418af0c..e26898494 100644 --- a/Source/Engine/Physics/PhysicsBackendEmpty.cpp +++ b/Source/Engine/Physics/PhysicsBackendEmpty.cpp @@ -408,7 +408,7 @@ void PhysicsBackend::AddRigidDynamicActorTorque(void* actor, const Vector3& torq { } -void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger) +void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, Span materials, bool enabled, bool trigger) { return DUMY_HANDLE; } @@ -447,7 +447,7 @@ void PhysicsBackend::SetShapeContactOffset(void* shape, float value) { } -void PhysicsBackend::SetShapeMaterial(void* shape, JsonAsset* material) +void PhysicsBackend::SetShapeMaterials(void* shape, Span materials) { } @@ -826,11 +826,16 @@ void PhysicsBackend::GetHeightFieldSize(void* heightField, int32& rows, int32& c columns = 0; } -float PhysicsBackend::GetHeightFieldHeight(void* heightField, float x, float z) +float PhysicsBackend::GetHeightFieldHeight(void* heightField, int32 x, int32 z) { return 0.0f; } +PhysicsBackend::HeightFieldSample PhysicsBackend::GetHeightFieldSample(void* heightField, int32 x, int32 z) +{ + return HeightFieldSample(); +} + bool PhysicsBackend::ModifyHeightField(void* heightField, int32 startCol, int32 startRow, int32 cols, int32 rows, const HeightFieldSample* data) { return true; diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index 772abf4d3..3f71760c3 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -29,7 +29,7 @@ Terrain::Terrain(const SpawnParams& params) , _cachedScale(1.0f) { _drawCategory = SceneRendering::SceneDrawAsync; - PhysicalMaterial.Changed.Bind(this); + _physicalMaterials.Resize(8); } Terrain::~Terrain() @@ -228,22 +228,6 @@ void Terrain::DrawChunk(const RenderContext& renderContext, const Int2& patchCoo } } -void Terrain::OnPhysicalMaterialChanged() -{ - if (_patches.IsEmpty()) - return; - - // Update the shapes material - for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++) - { - const auto patch = _patches[pathIndex]; - if (patch->HasCollision()) - { - PhysicsBackend::SetShapeMaterial(patch->_physicsShape, PhysicalMaterial); - } - } -} - #if TERRAIN_USE_PHYSICS_DEBUG void Terrain::DrawPhysicsDebug(RenderView& view) @@ -295,6 +279,21 @@ void Terrain::SetCollisionLOD(int32 value) #endif } +void Terrain::SetPhysicalMaterials(const Array, FixedAllocation<8>>& value) +{ + _physicalMaterials = value; + _physicalMaterials.Resize(8); + JsonAsset* materials[8]; + for (int32 i = 0;i<8;i++) + materials[i] = _physicalMaterials[i]; + for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++) + { + const auto patch = _patches.Get()[pathIndex]; + if (patch->HasCollision()) + PhysicsBackend::SetShapeMaterials(patch->_physicsShape, ToSpan(materials, 8)); + } +} + TerrainPatch* Terrain::GetPatch(const Int2& patchCoord) const { return GetPatch(patchCoord.X, patchCoord.Y); @@ -667,8 +666,8 @@ void Terrain::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_MEMBER(ScaleInLightmap, _scaleInLightmap); SERIALIZE_MEMBER(BoundsExtent, _boundsExtent); SERIALIZE_MEMBER(CollisionLOD, _collisionLod); + SERIALIZE_MEMBER(PhysicalMaterials, _physicalMaterials); SERIALIZE(Material); - SERIALIZE(PhysicalMaterial); SERIALIZE(DrawModes); SERIALIZE_MEMBER(LODCount, _lodCount); @@ -714,8 +713,8 @@ void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie DESERIALIZE_MEMBER(LODDistribution, _lodDistribution); DESERIALIZE_MEMBER(ScaleInLightmap, _scaleInLightmap); DESERIALIZE_MEMBER(BoundsExtent, _boundsExtent); + DESERIALIZE_MEMBER(PhysicalMaterials, _physicalMaterials); DESERIALIZE(Material); - DESERIALIZE(PhysicalMaterial); DESERIALIZE(DrawModes); member = stream.FindMember("LODCount"); @@ -780,6 +779,15 @@ void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie // [Deprecated on 27.04.2022, expires on 27.04.2024] if (modifier->EngineBuild <= 6331) DrawModes |= DrawPass::GlobalSurfaceAtlas; + + // [Deprecated on 15.02.2024, expires on 15.02.2026] + JsonAssetReference PhysicalMaterial; + DESERIALIZE(PhysicalMaterial); + if (PhysicalMaterial) + { + for (auto& e : _physicalMaterials) + e = PhysicalMaterial; + } } RigidBody* Terrain::GetAttachedRigidBody() const diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h index 1f495c5ab..aad0e4e1b 100644 --- a/Source/Engine/Terrain/Terrain.h +++ b/Source/Engine/Terrain/Terrain.h @@ -41,13 +41,12 @@ struct RenderView; /// API_CLASS(Sealed) class FLAXENGINE_API Terrain : public PhysicsColliderActor { -DECLARE_SCENE_OBJECT(Terrain); + DECLARE_SCENE_OBJECT(Terrain); friend Terrain; friend TerrainPatch; friend TerrainChunk; private: - char _lodBias; char _forcedLod; char _collisionLod; @@ -60,28 +59,21 @@ private: Float3 _cachedScale; Array> _patches; Array _drawChunks; + Array, FixedAllocation<8>> _physicalMaterials; public: - /// /// Finalizes an instance of the class. /// ~Terrain(); public: - /// /// The default material used for terrain rendering (chunks can override this). /// API_FIELD(Attributes="EditorOrder(100), DefaultValue(null), EditorDisplay(\"Terrain\")") AssetReference Material; - /// - /// The physical material used to define the terrain collider physical properties. - /// - API_FIELD(Attributes="EditorOrder(520), DefaultValue(null), Limit(-1, 100, 0.1f), EditorDisplay(\"Collision\")") - JsonAssetReference<::PhysicalMaterial> PhysicalMaterial; - /// /// The draw passes to use for rendering this object. /// @@ -89,7 +81,6 @@ public: DrawPass DrawModes = DrawPass::Default; public: - /// /// Gets the terrain Level Of Detail bias value. Allows to increase or decrease rendered terrain quality. /// @@ -180,6 +171,21 @@ public: /// API_PROPERTY() void SetCollisionLOD(int32 value); + /// + /// Gets the list with physical materials used to define the terrain collider physical properties - each for terrain layer (layer index matches index in this array). + /// + API_PROPERTY(Attributes="EditorOrder(520), EditorDisplay(\"Collision\"), Collection(MinCount = 8, MaxCount = 8)") + FORCE_INLINE const Array, FixedAllocation<8>>& GetPhysicalMaterials() const + { + return _physicalMaterials; + } + + /// + /// Sets the list with physical materials used to define the terrain collider physical properties - each for terrain layer (layer index matches index in this array). + /// + API_PROPERTY() + void SetPhysicalMaterials(const Array, FixedAllocation<8>>& value); + /// /// Gets the terrain Level Of Detail count. /// @@ -311,7 +317,6 @@ public: #endif public: - #if TERRAIN_EDITING /// @@ -362,7 +367,6 @@ public: void RemoveLightmap(); public: - /// /// Performs a raycast against this terrain collision shape. /// @@ -432,14 +436,11 @@ public: API_FUNCTION() void DrawChunk(API_PARAM(Ref) const RenderContext& renderContext, API_PARAM(Ref) const Int2& patchCoord, API_PARAM(Ref) const Int2& chunkCoord, MaterialBase* material, int32 lodIndex = 0) const; private: - - void OnPhysicalMaterialChanged(); #if TERRAIN_USE_PHYSICS_DEBUG - void DrawPhysicsDebug(RenderView& view); + void DrawPhysicsDebug(RenderView& view); #endif public: - // [PhysicsColliderActor] void Draw(RenderContext& renderContext) override; #if USE_EDITOR @@ -452,7 +453,6 @@ public: RigidBody* GetAttachedRigidBody() const override; protected: - // [PhysicsColliderActor] void OnEnable() override; void OnDisable() override; diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 7fa449601..d30f65ec7 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -27,6 +27,9 @@ #include "Engine/ContentImporters/AssetsImportingManager.h" #endif #endif +#if TERRAIN_UPDATING +#include "Engine/Core/Collections/ArrayExtensions.h" +#endif #if USE_EDITOR #include "Engine/Debug/DebugDraw.h" #endif @@ -105,6 +108,7 @@ void TerrainPatch::RemoveLightmap() void TerrainPatch::UpdateBounds() { + PROFILE_CPU(); Chunks[0].UpdateBounds(); _bounds = Chunks[0]._bounds; for (int32 i = 1; i < CHUNKS_COUNT; i++) @@ -116,6 +120,8 @@ void TerrainPatch::UpdateBounds() void TerrainPatch::UpdateTransform() { + PROFILE_CPU(); + // Update physics if (_physicsActor) { @@ -138,8 +144,14 @@ void TerrainPatch::UpdateTransform() #if TERRAIN_EDITING || TERRAIN_UPDATING +bool IsValidMaterial(const JsonAssetReference& e) +{ + return e; +} + struct TerrainDataUpdateInfo { + TerrainPatch* Patch; int32 ChunkSize; int32 VertexCountEdge; int32 HeightmapSize; @@ -147,6 +159,36 @@ struct TerrainDataUpdateInfo int32 TextureSize; float PatchOffset; float PatchHeight; + Color32* SplatMaps[TERRAIN_MAX_SPLATMAPS_COUNT] = {}; + + TerrainDataUpdateInfo(TerrainPatch* patch, float patchOffset = 0.0f, float patchHeight = 1.0f) + : Patch(patch) + , PatchOffset(patchOffset) + , PatchHeight(patchHeight) + { + ChunkSize = patch->GetTerrain()->GetChunkSize(); + VertexCountEdge = ChunkSize + 1; + HeightmapSize = ChunkSize * TerrainPatch::CHUNKS_COUNT_EDGE + 1; + HeightmapLength = HeightmapSize * HeightmapSize; + TextureSize = VertexCountEdge * TerrainPatch::CHUNKS_COUNT_EDGE; + } + + bool UsePhysicalMaterials() const + { + return ArrayExtensions::Any>(Patch->GetTerrain()->GetPhysicalMaterials(), IsValidMaterial); + } + + // When using physical materials, then get splatmaps data required for per-triangle material indices + void GetSplatMaps() + { + if (SplatMaps[0]) + return; + if (UsePhysicalMaterials()) + { + for (int32 i = 0; i < TERRAIN_MAX_SPLATMAPS_COUNT; i++) + SplatMaps[i] = Patch->GetSplatMapData(i); + } + } }; // Shared data container for the terrain data updating shared by the normals and collision generation logic @@ -321,10 +363,9 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh PROFILE_CPU_NAMED("Terrain.CalculateNormals"); // Expand the area for the normals to prevent issues on the edges (for the averaged normals) - const int32 heightMapSize = info.HeightmapSize; const Int2 modifiedEnd = modifiedOffset + modifiedSize; const Int2 normalsStart = Int2::Max(Int2::Zero, modifiedOffset - 1); - const Int2 normalsEnd = Int2::Min(heightMapSize, modifiedEnd + 1); + const Int2 normalsEnd = Int2::Min(info.HeightmapSize, modifiedEnd + 1); const Int2 normalsSize = normalsEnd - normalsStart; // Prepare memory @@ -342,7 +383,7 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh // Get four vertices from the quad #define GET_VERTEX(a, b) \ int32 i##a##b = (z + (b) - normalsStart.Y) * normalsSize.X + (x + (a) - normalsStart.X); \ - int32 h##a##b = (z + (b)) * heightMapSize + (x + (a)); \ + int32 h##a##b = (z + (b)) * info.HeightmapSize + (x + (a)); \ Float3 v##a##b; v##a##b.X = (x + (a)) * TERRAIN_UNITS_PER_VERTEX; \ v##a##b.Y = heightmap[h##a##b]; \ v##a##b.Z = (z + (b)) * TERRAIN_UNITS_PER_VERTEX @@ -428,7 +469,7 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh const int32 dz = chunkHeightmapZ + z - modifiedOffset.Y; if (dz < 0 || dz >= modifiedSize.Y) continue; - const int32 hz = (chunkHeightmapZ + z) * heightMapSize; + const int32 hz = (chunkHeightmapZ + z) * info.HeightmapSize; const int32 sz = (chunkHeightmapZ + z - normalsStart.Y) * normalsSize.X; const int32 tz = (chunkTextureZ + z) * info.TextureSize; @@ -546,13 +587,52 @@ void FixMips(const TerrainDataUpdateInfo& info, TextureBase::InitData* initData, } } -bool CookCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, Array* collisionData) +FORCE_INLINE byte GetPhysicalMaterial(const Color32& raw, const TerrainDataUpdateInfo& info, int32 chunkZ, int32 chunkX, int32 z, int32 x) +{ + byte result = 0; + if (ReadIsHole(raw)) + { + // Hole + result = (uint8)PhysicsBackend::HeightFieldMaterial::Hole; + } + else if (info.SplatMaps[0]) + { + // Use the layer with the highest influence (splatmap data is Mip0 so convert x/z coords back to LOD0) + uint8 layer = 0; + uint8 layerWeight = 0; + const int32 splatmapTextureIndex = (chunkZ * info.ChunkSize + z) * info.HeightmapSize + chunkX * info.ChunkSize + x; + for (int32 splatIndex = 0; splatIndex < TERRAIN_MAX_SPLATMAPS_COUNT; splatIndex++) + { + for (int32 channelIndex = 0; channelIndex < 4; channelIndex++) + { + // Assume splatmap data pitch matches the row size and shift by channel index to simply sample at R chanel + const Color32* splatmap = (const Color32*)((const byte*)info.SplatMaps[splatIndex] + channelIndex); + const uint8 splat = splatmap[splatmapTextureIndex].R; + if (splat > layerWeight) + { + layer = splatIndex * 4 + channelIndex; + layerWeight = splat; + if (layerWeight == MAX_uint8) + break; + } + } + if (layerWeight == MAX_uint8) + break; + } + result = layer; + } + return result; +} + +bool CookCollision(TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, Array* collisionData) { #if COMPILE_WITH_PHYSICS_COOKING + info.GetSplatMaps(); PROFILE_CPU_NAMED("Terrain.CookCollision"); // Prepare data const int32 collisionLOD = Math::Clamp(collisionLod, 0, initData->Mips.Count() - 1); + const int32 collisionLODInv = (int32)Math::Pow(2.0f, (float)collisionLOD); const int32 heightFieldChunkSize = ((info.ChunkSize + 1) >> collisionLOD) - 1; const int32 heightFieldSize = heightFieldChunkSize * TerrainPatch::CHUNKS_COUNT_EDGE + 1; const int32 heightFieldLength = heightFieldSize * heightFieldSize; @@ -562,36 +642,30 @@ bool CookCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* ini Platform::MemoryClear(heightFieldData, sizeof(PhysicsBackend::HeightFieldSample) * heightFieldLength); // Setup terrain collision information - auto& mip = initData->Mips[collisionLOD]; + const auto& mip = initData->Mips[collisionLOD]; const int32 vertexCountEdgeMip = info.VertexCountEdge >> collisionLOD; const int32 textureSizeMip = info.TextureSize >> collisionLOD; for (int32 chunkX = 0; chunkX < TerrainPatch::CHUNKS_COUNT_EDGE; chunkX++) { const int32 chunkTextureX = chunkX * vertexCountEdgeMip; const int32 chunkStartX = chunkX * heightFieldChunkSize; - for (int32 chunkZ = 0; chunkZ < TerrainPatch::CHUNKS_COUNT_EDGE; chunkZ++) { const int32 chunkTextureZ = chunkZ * vertexCountEdgeMip; const int32 chunkStartZ = chunkZ * heightFieldChunkSize; - for (int32 z = 0; z < vertexCountEdgeMip; z++) { + const int32 heightmapZ = chunkStartZ + z; for (int32 x = 0; x < vertexCountEdgeMip; x++) { - const int32 textureIndex = (chunkTextureZ + z) * textureSizeMip + chunkTextureX + x; - - const Color32 raw = mip.Data.Get()[textureIndex]; - const float normalizedHeight = ReadNormalizedHeight(raw); - const bool isHole = ReadIsHole(raw); - const int32 heightmapX = chunkStartX + x; - const int32 heightmapZ = chunkStartZ + z; + + const int32 textureIndex = (chunkTextureZ + z) * textureSizeMip + chunkTextureX + x; + const Color32 raw = mip.Data.Get()[textureIndex]; + sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * ReadNormalizedHeight(raw)); + sample.MaterialIndex0 = sample.MaterialIndex1 = GetPhysicalMaterial(raw, info, chunkZ, chunkX, z * collisionLODInv, x * collisionLODInv); + const int32 dstIndex = (heightmapX * heightFieldSize) + heightmapZ; - - sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * normalizedHeight); - sample.MaterialIndex0 = sample.MaterialIndex1 = isHole ? (uint8)PhysicsBackend::HeightFieldMaterial::Hole : 0; - heightFieldData[dstIndex] = sample; } } @@ -620,14 +694,16 @@ bool CookCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* ini #endif } -bool ModifyCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, const Int2& modifiedOffset, const Int2& modifiedSize, void* heightField) +bool ModifyCollision(TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, const Int2& modifiedOffset, const Int2& modifiedSize, void* heightField) { + info.GetSplatMaps(); PROFILE_CPU_NAMED("Terrain.ModifyCollision"); // Prepare data const Vector2 modifiedOffsetRatio((float)modifiedOffset.X / info.HeightmapSize, (float)modifiedOffset.Y / info.HeightmapSize); const Vector2 modifiedSizeRatio((float)modifiedSize.X / info.HeightmapSize, (float)modifiedSize.Y / info.HeightmapSize); const int32 collisionLOD = Math::Clamp(collisionLod, 0, initData->Mips.Count() - 1); + const int32 collisionLODInv = (int32)Math::Pow(2.0f, (float)collisionLOD); const int32 heightFieldChunkSize = ((info.ChunkSize + 1) >> collisionLOD) - 1; const int32 heightFieldSize = heightFieldChunkSize * TerrainPatch::CHUNKS_COUNT_EDGE + 1; const Int2 samplesOffset(Vector2::Floor(modifiedOffsetRatio * (float)heightFieldSize)); @@ -646,56 +722,45 @@ bool ModifyCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* i Platform::MemoryClear(heightFieldData, sizeof(PhysicsBackend::HeightFieldSample) * heightFieldDataLength); // Setup terrain collision information - auto& mip = initData->Mips[collisionLOD]; + const auto& mip = initData->Mips[collisionLOD]; const int32 vertexCountEdgeMip = info.VertexCountEdge >> collisionLOD; const int32 textureSizeMip = info.TextureSize >> collisionLOD; for (int32 chunkX = 0; chunkX < TerrainPatch::CHUNKS_COUNT_EDGE; chunkX++) { const int32 chunkTextureX = chunkX * vertexCountEdgeMip; const int32 chunkStartX = chunkX * heightFieldChunkSize; - - // Skip unmodified chunks if (chunkStartX >= samplesEnd.X || chunkStartX + vertexCountEdgeMip < samplesOffset.X) - continue; + continue; // Skip unmodified chunks for (int32 chunkZ = 0; chunkZ < TerrainPatch::CHUNKS_COUNT_EDGE; chunkZ++) { const int32 chunkTextureZ = chunkZ * vertexCountEdgeMip; const int32 chunkStartZ = chunkZ * heightFieldChunkSize; - - // Skip unmodified chunks if (chunkStartZ >= samplesEnd.Y || chunkStartZ + vertexCountEdgeMip < samplesOffset.Y) - continue; + continue; // Skip unmodified chunks // TODO: adjust loop range to reduce iterations count for edge cases (skip checking unmodified samples) for (int32 z = 0; z < vertexCountEdgeMip; z++) { - // Skip unmodified columns const int32 heightmapZ = chunkStartZ + z; const int32 heightmapLocalZ = heightmapZ - samplesOffset.Y; if (heightmapLocalZ < 0 || heightmapLocalZ >= samplesSize.Y) - continue; + continue; // Skip unmodified columns // TODO: adjust loop range to reduce iterations count for edge cases (skip checking unmodified samples) for (int32 x = 0; x < vertexCountEdgeMip; x++) { - // Skip unmodified rows const int32 heightmapX = chunkStartX + x; const int32 heightmapLocalX = heightmapX - samplesOffset.X; if (heightmapLocalX < 0 || heightmapLocalX >= samplesSize.X) - continue; + continue; // Skip unmodified rows const int32 textureIndex = (chunkTextureZ + z) * textureSizeMip + chunkTextureX + x; - const Color32 raw = mip.Data.Get()[textureIndex]; - const float normalizedHeight = ReadNormalizedHeight(raw); - const bool isHole = ReadIsHole(raw); + sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * ReadNormalizedHeight(raw)); + sample.MaterialIndex0 = sample.MaterialIndex1 = GetPhysicalMaterial(raw, info, chunkZ, chunkX, z * collisionLODInv, x * collisionLODInv); const int32 dstIndex = (heightmapLocalX * samplesSize.Y) + heightmapLocalZ; - - sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * normalizedHeight); - sample.MaterialIndex0 = sample.MaterialIndex1 = isHole ? (uint8)PhysicsBackend::HeightFieldMaterial::Hole : 0; - heightFieldData[dstIndex] = sample; } } @@ -718,38 +783,23 @@ bool ModifyCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* i bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap, const byte* holesMask, bool forceUseVirtualStorage) { - // Validate input + PROFILE_CPU_NAMED("Terrain.Setup"); if (heightMap == nullptr) { LOG(Warning, "Cannot create terrain without a heightmap specified."); return true; } - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; - if (heightMapLength != heightMapSize * heightMapSize) + TerrainDataUpdateInfo info(this); + if (heightMapLength != info.HeightmapLength) { - LOG(Warning, "Invalid heightmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", chunkSize, heightMapSize, heightMapSize * heightMapSize, heightMapLength); + LOG(Warning, "Invalid heightmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", info.ChunkSize, info.HeightmapSize, info.HeightmapLength, heightMapLength); return true; } const PixelFormat pixelFormat = PixelFormat::R8G8B8A8_UNorm; - PROFILE_CPU_NAMED("Terrain.Setup"); - // Input heightmap data overlaps on chunk edges but it needs to be duplicated for chunks (each chunk has own scale-bias for height values normalization) - const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; const int32 pixelStride = PixelFormatExtensions::SizeInBytes(pixelFormat); - const int32 lodCount = Math::Min(_terrain->_lodCount, MipLevelsCount(vertexCountEdge) - 2); - - // Setup patch data info - TerrainDataUpdateInfo info; - info.ChunkSize = chunkSize; - info.VertexCountEdge = vertexCountEdge; - info.HeightmapSize = heightMapSize; - info.HeightmapLength = heightMapLength; - info.TextureSize = textureSize; - info.PatchOffset = 0.0f; - info.PatchHeight = 1.0f; + const int32 lodCount = Math::Min(_terrain->_lodCount, MipLevelsCount(info.VertexCountEdge) - 2); // Process heightmap to get per-patch height normalization values float chunkOffsets[CHUNKS_COUNT]; @@ -782,18 +832,17 @@ bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap, // Create heightmap texture data source container auto initData = New(); initData->Format = pixelFormat; - initData->Width = textureSize; - initData->Height = textureSize; + initData->Width = info.TextureSize; + initData->Height = info.TextureSize; initData->ArraySize = 1; initData->Mips.Resize(lodCount); // Allocate top mip data { PROFILE_CPU_NAMED("Terrain.AllocateHeightmap"); - auto& mip = initData->Mips[0]; - mip.RowPitch = textureSize * pixelStride; - mip.SlicePitch = mip.RowPitch * textureSize; + mip.RowPitch = info.TextureSize * pixelStride; + mip.SlicePitch = mip.RowPitch * info.TextureSize; mip.Data.Allocate(mip.SlicePitch); } @@ -945,21 +994,17 @@ bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap, bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage) { + PROFILE_CPU_NAMED("Terrain.SetupSplatMap"); CHECK_RETURN(index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT, true); - - // Validate input if (splatMap == nullptr) { LOG(Warning, "Cannot create terrain without any splatmap specified."); return true; } - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; - const int32 heightMapLength = heightMapSize * heightMapSize; - if (splatMapLength != heightMapLength) + TerrainDataUpdateInfo info(this, _yOffset, _yHeight); + if (splatMapLength != info.HeightmapLength) { - LOG(Warning, "Invalid splatmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", chunkSize, heightMapSize, heightMapLength, splatMapLength); + LOG(Warning, "Invalid splatmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", info.ChunkSize, info.HeightmapSize, info.HeightmapLength, splatMapLength); return true; } const PixelFormat pixelFormat = PixelFormat::R8G8B8A8_UNorm; @@ -974,22 +1019,9 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3 } } - PROFILE_CPU_NAMED("Terrain.SetupSplatMap"); - // Input splatmap data overlaps on chunk edges but it needs to be duplicated for chunks - const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; const int32 pixelStride = PixelFormatExtensions::SizeInBytes(pixelFormat); - const int32 lodCount = Math::Min(_terrain->_lodCount, MipLevelsCount(vertexCountEdge) - 2); - - // Setup patch data info - TerrainDataUpdateInfo info; - info.ChunkSize = chunkSize; - info.VertexCountEdge = vertexCountEdge; - info.HeightmapSize = heightMapSize; - info.HeightmapLength = heightMapLength; - info.TextureSize = textureSize; - info.PatchOffset = _yOffset; - info.PatchHeight = _yHeight; + const int32 lodCount = Math::Min(_terrain->_lodCount, MipLevelsCount(info.VertexCountEdge) - 2); // Prepare #if USE_EDITOR @@ -1016,18 +1048,17 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3 // Create heightmap texture data source container auto initData = New(); initData->Format = pixelFormat; - initData->Width = textureSize; - initData->Height = textureSize; + initData->Width = info.TextureSize; + initData->Height = info.TextureSize; initData->ArraySize = 1; initData->Mips.Resize(lodCount); // Allocate top mip data { PROFILE_CPU_NAMED("Terrain.AllocateSplatmap"); - auto& mip = initData->Mips[0]; - mip.RowPitch = textureSize * pixelStride; - mip.SlicePitch = mip.RowPitch * textureSize; + mip.RowPitch = info.TextureSize * pixelStride; + mip.SlicePitch = mip.RowPitch * info.TextureSize; mip.Data.Allocate(mip.SlicePitch); } @@ -1112,8 +1143,6 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3 bool TerrainPatch::InitializeHeightMap() { PROFILE_CPU_NAMED("Terrain.InitializeHeightMap"); - - // Initialize with flat heightmap data const auto heightmapSize = _terrain->GetChunkSize() * TerrainPatch::CHUNKS_COUNT_EDGE + 1; Array heightmap; heightmap.Resize(heightmapSize * heightmapSize); @@ -1179,6 +1208,7 @@ void TerrainPatch::ClearCache() void TerrainPatch::CacheHeightData() { PROFILE_CPU_NAMED("Terrain.CacheHeightData"); + const TerrainDataUpdateInfo info(this); // Ensure that heightmap data is all loaded // TODO: disable streaming for heightmap texture if it's being modified by the editor @@ -1198,16 +1228,9 @@ void TerrainPatch::CacheHeightData() return; } - // Get texture input (note: this must match Setup method) - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; - // Allocate data - const int32 heightMapLength = heightMapSize * heightMapSize; - _cachedHeightMap.Resize(heightMapLength); - _cachedHolesMask.Resize(heightMapLength); + _cachedHeightMap.Resize(info.HeightmapLength); + _cachedHolesMask.Resize(info.HeightmapLength); _wasHeightModified = false; // Extract heightmap data and denormalize it to get the pure height field @@ -1217,18 +1240,18 @@ void TerrainPatch::CacheHeightData() const auto holesMaskPtr = _cachedHolesMask.Get(); for (int32 chunkIndex = 0; chunkIndex < CHUNKS_COUNT; chunkIndex++) { - const int32 chunkTextureX = Chunks[chunkIndex]._x * vertexCountEdge; - const int32 chunkTextureZ = Chunks[chunkIndex]._z * vertexCountEdge; + const int32 chunkTextureX = Chunks[chunkIndex]._x * info.VertexCountEdge; + const int32 chunkTextureZ = Chunks[chunkIndex]._z * info.VertexCountEdge; - const int32 chunkHeightmapX = Chunks[chunkIndex]._x * chunkSize; - const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * chunkSize; + const int32 chunkHeightmapX = Chunks[chunkIndex]._x * info.ChunkSize; + const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * info.ChunkSize; - for (int32 z = 0; z < vertexCountEdge; z++) + for (int32 z = 0; z < info.VertexCountEdge; z++) { - const int32 tz = (chunkTextureZ + z) * textureSize; - const int32 sz = (chunkHeightmapZ + z) * heightMapSize; + const int32 tz = (chunkTextureZ + z) * info.TextureSize; + const int32 sz = (chunkHeightmapZ + z) * info.HeightmapSize; - for (int32 x = 0; x < vertexCountEdge; x++) + for (int32 x = 0; x < info.VertexCountEdge; x++) { const int32 tx = chunkTextureX + x; const int32 sx = chunkHeightmapX + x; @@ -1249,18 +1272,14 @@ void TerrainPatch::CacheHeightData() void TerrainPatch::CacheSplatData() { - // Prepare - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; - const int32 heightMapLength = heightMapSize * heightMapSize; + PROFILE_CPU_NAMED("Terrain.CacheSplatData"); + const TerrainDataUpdateInfo info(this); // Cache all the splatmaps for (int32 index = 0; index < TERRAIN_MAX_SPLATMAPS_COUNT; index++) { // Allocate data - _cachedSplatMap[index].Resize(heightMapLength); + _cachedSplatMap[index].Resize(info.HeightmapLength); _wasSplatmapModified[index] = false; // Skip if has missing splatmap asset @@ -1272,8 +1291,6 @@ void TerrainPatch::CacheSplatData() continue; } - PROFILE_CPU_NAMED("Terrain.CacheSplatData"); - // Ensure that splatmap data is all loaded // TODO: disable streaming for heightmap texture if it's being modified by the editor if (Splatmap[index]->WaitForLoaded()) @@ -1296,18 +1313,18 @@ void TerrainPatch::CacheSplatData() const auto splatMapPtr = static_cast(_cachedSplatMap[index].Get()); for (int32 chunkIndex = 0; chunkIndex < CHUNKS_COUNT; chunkIndex++) { - const int32 chunkTextureX = Chunks[chunkIndex]._x * vertexCountEdge; - const int32 chunkTextureZ = Chunks[chunkIndex]._z * vertexCountEdge; + const int32 chunkTextureX = Chunks[chunkIndex]._x * info.VertexCountEdge; + const int32 chunkTextureZ = Chunks[chunkIndex]._z * info.VertexCountEdge; - const int32 chunkHeightmapX = Chunks[chunkIndex]._x * chunkSize; - const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * chunkSize; + const int32 chunkHeightmapX = Chunks[chunkIndex]._x * info.ChunkSize; + const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * info.ChunkSize; - for (int32 z = 0; z < vertexCountEdge; z++) + for (int32 z = 0; z < info.VertexCountEdge; z++) { - const int32 tz = (chunkTextureZ + z) * textureSize; - const int32 sz = (chunkHeightmapZ + z) * heightMapSize; + const int32 tz = (chunkTextureZ + z) * info.TextureSize; + const int32 sz = (chunkHeightmapZ + z) * info.HeightmapSize; - for (int32 x = 0; x < vertexCountEdge; x++) + for (int32 x = 0; x < info.VertexCountEdge; x++) { const int32 tx = chunkTextureX + x; const int32 sx = chunkHeightmapX + x; @@ -1324,9 +1341,7 @@ void TerrainPatch::CacheSplatData() bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOffset, const Int2& modifiedSize) { // Validate input samples range - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; + TerrainDataUpdateInfo info(this); if (samples == nullptr) { LOG(Warning, "Missing heightmap samples data."); @@ -1334,13 +1349,12 @@ bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOff } if (modifiedOffset.X < 0 || modifiedOffset.Y < 0 || modifiedSize.X <= 0 || modifiedSize.Y <= 0 || - modifiedOffset.X + modifiedSize.X > heightMapSize || - modifiedOffset.Y + modifiedSize.Y > heightMapSize) + modifiedOffset.X + modifiedSize.X > info.HeightmapSize || + modifiedOffset.Y + modifiedSize.Y > info.HeightmapSize) { LOG(Warning, "Invalid heightmap samples range."); return true; } - PROFILE_CPU_NAMED("Terrain.ModifyHeightMap"); // Check if has no heightmap @@ -1364,28 +1378,17 @@ bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOff // Modify heightmap data { PROFILE_CPU_NAMED("Terrain.WrtieCache"); - for (int32 z = 0; z < modifiedSize.Y; z++) { // TODO: use batches row mem copy for (int32 x = 0; x < modifiedSize.X; x++) { - heightMap[(z + modifiedOffset.Y) * heightMapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x]; + heightMap[(z + modifiedOffset.Y) * info.HeightmapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x]; } } } - // Setup patch data info - TerrainDataUpdateInfo info; - info.ChunkSize = chunkSize; - info.VertexCountEdge = vertexCountEdge; - info.HeightmapSize = heightMapSize; - info.HeightmapLength = heightMapSize * heightMapSize; - info.TextureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; - info.PatchOffset = 0.0f; - info.PatchHeight = 1.0f; - // Process heightmap to get per-patch height normalization values float chunkOffsets[CHUNKS_COUNT]; float chunkHeights[CHUNKS_COUNT]; @@ -1426,15 +1429,13 @@ bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOff chunk.UpdateTransform(); } _terrain->UpdateBounds(); - return UpdateHeightData(info, modifiedOffset, modifiedSize, wasHeightRangeChanged); + return UpdateHeightData(info, modifiedOffset, modifiedSize, wasHeightRangeChanged, true); } bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffset, const Int2& modifiedSize) { // Validate input samples range - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; + TerrainDataUpdateInfo info(this, _yOffset, _yHeight); if (samples == nullptr) { LOG(Warning, "Missing holes mask samples data."); @@ -1442,13 +1443,12 @@ bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffs } if (modifiedOffset.X < 0 || modifiedOffset.Y < 0 || modifiedSize.X <= 0 || modifiedSize.Y <= 0 || - modifiedOffset.X + modifiedSize.X > heightMapSize || - modifiedOffset.Y + modifiedSize.Y > heightMapSize) + modifiedOffset.X + modifiedSize.X > info.HeightmapSize || + modifiedOffset.Y + modifiedSize.Y > info.HeightmapSize) { LOG(Warning, "Invalid holes mask samples range."); return true; } - PROFILE_CPU_NAMED("Terrain.ModifyHolesMask"); // Check if has no heightmap @@ -1472,28 +1472,17 @@ bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffs // Modify holes mask data { PROFILE_CPU_NAMED("Terrain.WrtieCache"); - for (int32 z = 0; z < modifiedSize.Y; z++) { // TODO: use batches row mem copy for (int32 x = 0; x < modifiedSize.X; x++) { - holesMask[(z + modifiedOffset.Y) * heightMapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x]; + holesMask[(z + modifiedOffset.Y) * info.HeightmapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x]; } } } - // Setup patch data info - TerrainDataUpdateInfo info; - info.ChunkSize = chunkSize; - info.VertexCountEdge = vertexCountEdge; - info.HeightmapSize = heightMapSize; - info.HeightmapLength = heightMapSize * heightMapSize; - info.TextureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; - info.PatchOffset = _yOffset; - info.PatchHeight = _yHeight; - // Check if has allocated texture if (_dataHeightmap) { @@ -1505,7 +1494,7 @@ bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffs } // Update all the stuff - return UpdateHeightData(info, modifiedOffset, modifiedSize, false); + return UpdateHeightData(info, modifiedOffset, modifiedSize, false, true); } bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int2& modifiedOffset, const Int2& modifiedSize) @@ -1523,9 +1512,7 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int } // Validate input samples range - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; + TerrainDataUpdateInfo info(this, _yOffset, _yHeight); if (samples == nullptr) { LOG(Warning, "Missing splatmap samples data."); @@ -1533,13 +1520,12 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int } if (modifiedOffset.X < 0 || modifiedOffset.Y < 0 || modifiedSize.X <= 0 || modifiedSize.Y <= 0 || - modifiedOffset.X + modifiedSize.X > heightMapSize || - modifiedOffset.Y + modifiedSize.Y > heightMapSize) + modifiedOffset.X + modifiedSize.X > info.HeightmapSize || + modifiedOffset.Y + modifiedSize.Y > info.HeightmapSize) { LOG(Warning, "Invalid heightmap samples range."); return true; } - PROFILE_CPU_NAMED("Terrain.ModifySplatMap"); // Get the current data to modify it @@ -1552,14 +1538,13 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int // Modify splat map data { PROFILE_CPU_NAMED("Terrain.WrtieCache"); - for (int32 z = 0; z < modifiedSize.Y; z++) { // TODO: use batches row mem copy for (int32 x = 0; x < modifiedSize.X; x++) { - splatMap[(z + modifiedOffset.Y) * heightMapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x]; + splatMap[(z + modifiedOffset.Y) * info.HeightmapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x]; } } } @@ -1570,7 +1555,6 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int if (dataSplatmap == nullptr) { PROFILE_CPU_NAMED("Terrain.InitDataStorage"); - if (Heightmap->WaitForLoaded()) { LOG(Error, "Failed to load heightmap."); @@ -1597,16 +1581,6 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int mip.Data.Allocate(mip.SlicePitch); } - // Setup patch data info - TerrainDataUpdateInfo info; - info.ChunkSize = chunkSize; - info.VertexCountEdge = vertexCountEdge; - info.HeightmapSize = heightMapSize; - info.HeightmapLength = heightMapSize * heightMapSize; - info.TextureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; - info.PatchOffset = _yOffset; - info.PatchHeight = _yHeight; - // Update splat map storage data const bool hasSplatmap = splatmap; const auto splatmapData = dataSplatmap->Mips[0].Data.Get(); @@ -1712,12 +1686,18 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int // TODO: disable splatmap dynamic streaming - data on a GPU was modified and we don't want to override it with the old data stored in the asset container + // Update heightfield to reflect physical materials layering + if (info.UsePhysicalMaterials() && HasCollision()) + { + UpdateHeightData(info, modifiedOffset, modifiedSize, false, false); + } + return false; } -bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged) +bool TerrainPatch::UpdateHeightData(TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged, bool wasHeightChanged) { - // Prepare + PROFILE_CPU(); float* heightMap = GetHeightmapData(); byte* holesMask = GetHolesMaskData(); ASSERT(heightMap && holesMask); @@ -1753,9 +1733,7 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int // Downscale mip data for all lower LODs if (GenerateMips(_dataHeightmap)) - { return true; - } // Fix generated mip maps to keep the same values for chunk edges (reduce cracks on continuous LOD transitions) FixMips(info, _dataHeightmap, pixelStride); @@ -1779,9 +1757,7 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int } const auto collisionData = &_heightfield->Data; if (CookCollision(info, _dataHeightmap, _terrain->_collisionLod, collisionData)) - { return true; - } UpdateCollision(); } else @@ -1789,7 +1765,8 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int ScopeLock lock(_collisionLocker); if (ModifyCollision(info, _dataHeightmap, _terrain->_collisionLod, modifiedOffset, modifiedSize, _physicsHeightField)) return true; - UpdateCollisionScale(); + if (wasHeightChanged) + UpdateCollisionScale(); } #else // Modify heightfield samples (without cooking collision which is done on a separate async task) @@ -1811,6 +1788,9 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int } #endif + if (!wasHeightChanged) + return false; + // Invalidate cache #if TERRAIN_USE_PHYSICS_DEBUG _debugLines.Resize(0); @@ -1843,18 +1823,8 @@ void TerrainPatch::SaveHeightData() { return; } - PROFILE_CPU_NAMED("Terrain.Save"); - - // Setup patch data info - TerrainDataUpdateInfo info; - info.ChunkSize = _terrain->_chunkSize; - info.VertexCountEdge = info.ChunkSize + 1; - info.HeightmapSize = info.ChunkSize * CHUNKS_COUNT_EDGE + 1; - info.HeightmapLength = info.HeightmapSize * info.HeightmapSize; - info.TextureSize = info.VertexCountEdge * CHUNKS_COUNT_EDGE; - info.PatchOffset = _yOffset; - info.PatchHeight = _yHeight; + TerrainDataUpdateInfo info(this, _yOffset, _yHeight); // Save heightmap to asset if (Heightmap->WaitForLoaded()) @@ -1913,7 +1883,6 @@ void TerrainPatch::SaveSplatData(int32 index) { return; } - PROFILE_CPU_NAMED("Terrain.Save"); // Save splatmap to asset @@ -1937,6 +1906,7 @@ void TerrainPatch::SaveSplatData(int32 index) bool TerrainPatch::UpdateCollision() { + PROFILE_CPU(); ScopeLock lock(_collisionLocker); // Update collision @@ -2111,6 +2081,7 @@ void TerrainPatch::UpdatePostManualDeserialization() void TerrainPatch::CreateCollision() { + PROFILE_CPU(); ASSERT(!HasCollision()); if (CreateHeightField()) return; @@ -2125,7 +2096,10 @@ void TerrainPatch::CreateCollision() shape.SetHeightField(_physicsHeightField, heightScale, rowScale, columnScale); // Create shape - _physicsShape = PhysicsBackend::CreateShape(_terrain, shape, _terrain->PhysicalMaterial, _terrain->IsActiveInHierarchy(), false); + JsonAsset* materials[8]; + for (int32 i = 0;i<8;i++) + materials[i] = _terrain->GetPhysicalMaterials()[i]; + _physicsShape = PhysicsBackend::CreateShape(_terrain, shape, ToSpan(materials, 8), _terrain->IsActiveInHierarchy(), false); PhysicsBackend::SetShapeLocalPose(_physicsShape, Vector3(0, _yOffset * terrainTransform.Scale.Y, 0), Quaternion::Identity); // Create static actor @@ -2137,6 +2111,7 @@ void TerrainPatch::CreateCollision() bool TerrainPatch::CreateHeightField() { + PROFILE_CPU(); ASSERT(_physicsHeightField == nullptr); // Skip if height field data is missing but warn on loading failed @@ -2162,6 +2137,7 @@ bool TerrainPatch::CreateHeightField() void TerrainPatch::UpdateCollisionScale() const { + PROFILE_CPU(); ASSERT(HasCollision()); // Create geometry @@ -2179,6 +2155,7 @@ void TerrainPatch::UpdateCollisionScale() const void TerrainPatch::DestroyCollision() { + PROFILE_CPU(); ScopeLock lock(_collisionLocker); ASSERT(HasCollision()); @@ -2205,6 +2182,7 @@ void TerrainPatch::DestroyCollision() void TerrainPatch::CacheDebugLines() { + PROFILE_CPU(); ASSERT(_debugLines.IsEmpty() && _physicsHeightField); int32 rows, cols; @@ -2213,12 +2191,21 @@ void TerrainPatch::CacheDebugLines() _debugLines.Resize((rows - 1) * (cols - 1) * 6 + (cols + rows - 2) * 2); Vector3* data = _debugLines.Get(); -#define GET_VERTEX(x, y) const Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, (float)(row + (x)), (float)(col + (y))) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))) +#define GET_VERTEX(x, y) const Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, row + (x), col + (y)) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))) for (int32 row = 0; row < rows - 1; row++) { for (int32 col = 0; col < cols - 1; col++) { + // Skip holes + const auto sample = PhysicsBackend::GetHeightFieldSample(_physicsHeightField, row, col); + if (sample.MaterialIndex0 == (uint8)PhysicsBackend::HeightFieldMaterial::Hole) + { + for (int32 i = 0; i < 6; i++) + *data++ = Vector3::Zero; + continue; + } + GET_VERTEX(0, 0); GET_VERTEX(0, 1); GET_VERTEX(1, 0); @@ -2294,6 +2281,7 @@ const Array& TerrainPatch::GetCollisionTriangles() ScopeLock lock(_collisionLocker); if (!_physicsShape || _collisionTriangles.HasItems()) return _collisionTriangles; + PROFILE_CPU(); int32 rows, cols; PhysicsBackend::GetHeightFieldSize(_physicsHeightField, rows, cols); @@ -2301,7 +2289,7 @@ const Array& TerrainPatch::GetCollisionTriangles() _collisionTriangles.Resize((rows - 1) * (cols - 1) * 6); Vector3* data = _collisionTriangles.Get(); -#define GET_VERTEX(x, y) Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, (float)(row + (x)), (float)(col + (y))) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))); Vector3::Transform(v##x##y, world, v##x##y) +#define GET_VERTEX(x, y) Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, row + (x), col + (y)) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))); Vector3::Transform(v##x##y, world, v##x##y) const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * CHUNKS_COUNT_EDGE; const Transform terrainTransform = _terrain->_transform; @@ -2312,6 +2300,15 @@ const Array& TerrainPatch::GetCollisionTriangles() { for (int32 col = 0; col < cols - 1; col++) { + // Skip holes + const auto sample = PhysicsBackend::GetHeightFieldSample(_physicsHeightField, row, col); + if (sample.MaterialIndex0 == (uint8)PhysicsBackend::HeightFieldMaterial::Hole) + { + for (int32 i = 0; i < 6; i++) + *data++ = Vector3::Zero; + continue; + } + GET_VERTEX(0, 0); GET_VERTEX(0, 1); GET_VERTEX(1, 0); @@ -2334,6 +2331,7 @@ const Array& TerrainPatch::GetCollisionTriangles() void TerrainPatch::GetCollisionTriangles(const BoundingSphere& bounds, Array& result) { + PROFILE_CPU(); result.Clear(); // Skip if no intersection with patch @@ -2430,6 +2428,7 @@ void TerrainPatch::GetCollisionTriangles(const BoundingSphere& bounds, Array& vertexBuffer, Array& indexBuffer) { + PROFILE_CPU(); vertexBuffer.Clear(); indexBuffer.Clear(); @@ -2459,7 +2458,7 @@ void TerrainPatch::ExtractCollisionGeometry(Array& vertexBuffer, Array _debugLines; // TODO: large-worlds + Array _debugLines; // TODO: large-worlds #endif #if USE_EDITOR Array _collisionTriangles; // TODO: large-worlds @@ -62,14 +60,12 @@ private: void Init(Terrain* terrain, int16 x, int16 z); public: - /// /// Finalizes an instance of the class. /// ~TerrainPatch(); public: - /// /// The chunks contained within the patch. Organized in 4x4 square. /// @@ -86,11 +82,9 @@ public: AssetReference Splatmap[TERRAIN_MAX_SPLATMAPS_COUNT]; public: - /// /// Gets the Y axis heightmap offset from terrain origin. /// - /// The offset. FORCE_INLINE float GetOffsetY() const { return _yOffset; @@ -99,7 +93,6 @@ public: /// /// Gets the Y axis heightmap height. /// - /// The height. FORCE_INLINE float GetHeightY() const { return _yHeight; @@ -108,7 +101,6 @@ public: /// /// Gets the x coordinate. /// - /// The x position. FORCE_INLINE int32 GetX() const { return _x; @@ -117,7 +109,6 @@ public: /// /// Gets the z coordinate. /// - /// The z position. FORCE_INLINE int32 GetZ() const { return _z; @@ -126,7 +117,6 @@ public: /// /// Gets the terrain. /// - /// The terrain, FORCE_INLINE Terrain* GetTerrain() const { return _terrain; @@ -168,14 +158,12 @@ public: /// /// Gets the patch world bounds. /// - /// The bounding box. FORCE_INLINE const BoundingBox& GetBounds() const { return _bounds; } public: - /// /// Removes the lightmap data from the terrain patch. /// @@ -192,7 +180,6 @@ public: void UpdateTransform(); #if TERRAIN_EDITING - /// /// Initializes the patch heightmap and collision to the default flat level. /// @@ -218,11 +205,9 @@ public: /// If set to true patch will use virtual storage by force. Otherwise it can use normal texture asset storage on drive (valid only during Editor). Runtime-created terrain can only use virtual storage (in RAM). /// True if failed, otherwise false. bool SetupSplatMap(int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage = false); - #endif #if TERRAIN_UPDATING - /// /// Gets the raw pointer to the heightmap data. /// @@ -291,18 +276,15 @@ public: bool ModifySplatMap(int32 index, const Color32* samples, const Int2& modifiedOffset, const Int2& modifiedSize); private: - - bool UpdateHeightData(const struct TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged); + bool UpdateHeightData(struct TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged, bool wasHeightChanged); void SaveHeightData(); void CacheHeightData(); void SaveSplatData(); void SaveSplatData(int32 index); void CacheSplatData(); - #endif public: - /// /// Performs a raycast against this terrain collision shape. /// @@ -353,18 +335,14 @@ public: void ClosestPoint(const Vector3& position, Vector3& result) const; #if USE_EDITOR - /// /// Updates the patch data after manual deserialization called at runtime (eg. by editor undo). /// void UpdatePostManualDeserialization(); - #endif public: - #if USE_EDITOR - /// /// Gets the collision mesh triangles array (3 vertices per triangle in linear list). Cached internally to reuse data. /// @@ -377,7 +355,6 @@ public: /// The world-space bounds to find terrain triangles that intersect with it. /// The result triangles that intersect with the given bounds (in world-space). void GetCollisionTriangles(const BoundingSphere& bounds, Array& result); - #endif /// @@ -388,7 +365,6 @@ public: void ExtractCollisionGeometry(Array& vertexBuffer, Array& indexBuffer); private: - /// /// Determines whether this patch has created collision representation. /// @@ -419,8 +395,8 @@ private: void DestroyCollision(); #if TERRAIN_USE_PHYSICS_DEBUG - void CacheDebugLines(); - void DrawPhysicsDebug(RenderView& view); + void CacheDebugLines(); + void DrawPhysicsDebug(RenderView& view); #endif /// @@ -430,8 +406,8 @@ private: bool UpdateCollision(); void OnPhysicsSceneChanged(PhysicsScene* previous); -public: +public: // [ISerializable] void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; From 85f291071878701cd6b56ea8076cf3e717911eac Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 16 Feb 2024 17:19:51 +0100 Subject: [PATCH 26/42] Move collider shape raycasting utilities to the `PhysicsColliderActor` class --- .../Physics/Actors/PhysicsColliderActor.h | 35 +++++++++++++++++ Source/Engine/Physics/Colliders/Collider.h | 38 ++----------------- .../Physics/PhysX/PhysicsBackendPhysX.cpp | 1 + .../PhysX/SimulationEventCallbackPhysX.cpp | 9 ++--- Source/Engine/Terrain/Terrain.cpp | 11 ++++-- Source/Engine/Terrain/Terrain.h | 31 ++------------- 6 files changed, 55 insertions(+), 70 deletions(-) diff --git a/Source/Engine/Physics/Actors/PhysicsColliderActor.h b/Source/Engine/Physics/Actors/PhysicsColliderActor.h index caea76c79..92693695d 100644 --- a/Source/Engine/Physics/Actors/PhysicsColliderActor.h +++ b/Source/Engine/Physics/Actors/PhysicsColliderActor.h @@ -5,6 +5,7 @@ #include "Engine/Level/Actor.h" #include "Engine/Physics/Collisions.h" +struct RayCastHit; struct Collision; /// @@ -42,6 +43,40 @@ public: /// The rigid body or null. API_PROPERTY() virtual RigidBody* GetAttachedRigidBody() const = 0; + /// + /// Performs a raycast against this collider shape. + /// + /// The origin of the ray. + /// The normalized direction of the ray. + /// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything. + /// The maximum distance the ray should check for collisions. + /// True if ray hits an object, otherwise false. + API_FUNCTION(Sealed) virtual bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, float maxDistance = MAX_float) const = 0; + + /// + /// Performs a raycast against this collider, returns results in a RaycastHit structure. + /// + /// The origin of the ray. + /// The normalized direction of the ray. + /// The result hit information. Valid only when method returns true. + /// The maximum distance the ray should check for collisions. + /// True if ray hits an object, otherwise false. + API_FUNCTION(Sealed) virtual bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const = 0; + + /// + /// Gets a point on the collider that is closest to a given location. Can be used to find a hit location or position to apply explosion force or any other special effects. + /// + /// The position to find the closest point to it. + /// The result point on the collider that is closest to the specified location. + API_FUNCTION(Sealed) virtual void ClosestPoint(const Vector3& point, API_PARAM(Out) Vector3& result) const = 0; + + /// + /// Checks if a point is inside the collider. + /// + /// The point to check if is contained by the collider shape (in world-space). + /// True if collider shape contains a given point, otherwise false. + API_FUNCTION(Sealed) virtual bool ContainsPoint(const Vector3& point) const = 0; + public: /// /// Called when a collision start gets registered for this collider (it collides with something). diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index cbbb7e522..68f5fe346 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -84,40 +84,6 @@ public: JsonAssetReference Material; public: - /// - /// Performs a raycast against this collider shape. - /// - /// The origin of the ray. - /// The normalized direction of the ray. - /// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything. - /// The maximum distance the ray should check for collisions. - /// True if ray hits an object, otherwise false. - API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, float maxDistance = MAX_float) const; - - /// - /// Performs a raycast against this collider, returns results in a RaycastHit structure. - /// - /// The origin of the ray. - /// The normalized direction of the ray. - /// The result hit information. Valid only when method returns true. - /// The maximum distance the ray should check for collisions. - /// True if ray hits an object, otherwise false. - API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const; - - /// - /// Gets a point on the collider that is closest to a given location. Can be used to find a hit location or position to apply explosion force or any other special effects. - /// - /// The position to find the closest point to it. - /// The result point on the collider that is closest to the specified location. - API_FUNCTION() void ClosestPoint(const Vector3& point, API_PARAM(Out) Vector3& result) const; - - /// - /// Checks if a point is inside the collider. - /// - /// The point to check if is contained by the collider shape (in world-space). - /// True if collider shape contains a given point, otherwise false. - API_FUNCTION() bool ContainsPoint(const Vector3& point) const; - /// /// Computes minimum translational distance between two geometry objects. /// Translating the first collider by direction * distance will separate the colliders apart if the function returned true. Otherwise, direction and distance are not defined. @@ -198,6 +164,10 @@ private: public: // [PhysicsColliderActor] RigidBody* GetAttachedRigidBody() const override; + bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const final; + bool RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, float maxDistance = MAX_float) const final; + void ClosestPoint(const Vector3& point, Vector3& result) const final; + bool ContainsPoint(const Vector3& point) const final; protected: // [PhysicsColliderActor] diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index cb2f3e736..9d0d2ea16 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -2630,6 +2630,7 @@ bool PhysicsBackend::RayCastShape(void* shape, const Vector3& position, const Qu PxRaycastHit hit; if (PxGeometryQuery::raycast(C2P(origin - sceneOrigin), C2P(direction), shapePhysX->getGeometry(), trans, maxDistance, SCENE_QUERY_FLAGS, 1, &hit) == 0) return false; + hit.shape = shapePhysX; P2C(hit, hitInfo); hitInfo.Point += sceneOrigin; return true; diff --git a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp index fc33b0dfe..62e588771 100644 --- a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp +++ b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp @@ -163,14 +163,11 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c c.ThisVelocity = c.OtherVelocity = Vector3::Zero; if (hasPostVelocities && j.nextItemSet()) { - ASSERT(j.contactPairIndex == pairIndex); + ASSERT_LOW_LAYER(j.contactPairIndex == pairIndex); if (j.postSolverVelocity) { - const PxVec3 linearVelocityActor0 = j.postSolverVelocity->linearVelocity[0]; - const PxVec3 linearVelocityActor1 = j.postSolverVelocity->linearVelocity[1]; - - c.ThisVelocity = P2C(linearVelocityActor0); - c.OtherVelocity = P2C(linearVelocityActor1); + c.ThisVelocity = P2C(j.postSolverVelocity->linearVelocity[0]); + c.OtherVelocity = P2C(j.postSolverVelocity->linearVelocity[1]); } } diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index 3f71760c3..497e9bf80 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -185,7 +185,7 @@ bool Terrain::RayCast(const Vector3& origin, const Vector3& direction, RayCastHi return result; } -void Terrain::ClosestPoint(const Vector3& position, Vector3& result) const +void Terrain::ClosestPoint(const Vector3& point, Vector3& result) const { Real minDistance = MAX_Real; Vector3 tmp; @@ -194,8 +194,8 @@ void Terrain::ClosestPoint(const Vector3& position, Vector3& result) const const auto patch = _patches[pathIndex]; if (patch->HasCollision()) { - patch->ClosestPoint(position, tmp); - const auto distance = Vector3::DistanceSquared(position, tmp); + patch->ClosestPoint(point, tmp); + const auto distance = Vector3::DistanceSquared(point, tmp); if (distance < minDistance) { minDistance = distance; @@ -205,6 +205,11 @@ void Terrain::ClosestPoint(const Vector3& position, Vector3& result) const } } +bool Terrain::ContainsPoint(const Vector3& point) const +{ + return false; +} + void Terrain::DrawPatch(const RenderContext& renderContext, const Int2& patchCoord, MaterialBase* material, int32 lodIndex) const { auto patch = GetPatch(patchCoord); diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h index aad0e4e1b..dd9dc1926 100644 --- a/Source/Engine/Terrain/Terrain.h +++ b/Source/Engine/Terrain/Terrain.h @@ -367,16 +367,6 @@ public: void RemoveLightmap(); public: - /// - /// Performs a raycast against this terrain collision shape. - /// - /// The origin of the ray. - /// The normalized direction of the ray. - /// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything. - /// The maximum distance the ray should check for collisions. - /// True if ray hits an object, otherwise false. - API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const; - /// /// Performs a raycast against this terrain collision shape. Returns the hit chunk. /// @@ -399,23 +389,6 @@ public: /// True if ray hits an object, otherwise false. API_FUNCTION() bool RayCast(const Ray& ray, API_PARAM(Out) float& resultHitDistance, API_PARAM(Out) Int2& resultPatchCoord, API_PARAM(Out) Int2& resultChunkCoord, float maxDistance = MAX_float) const; - /// - /// Performs a raycast against terrain collision, returns results in a RayCastHit structure. - /// - /// The origin of the ray. - /// The normalized direction of the ray. - /// The result hit information. Valid only when method returns true. - /// The maximum distance the ray should check for collisions. - /// True if ray hits an object, otherwise false. - API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const; - - /// - /// Gets a point on the terrain collider that is closest to a given location. Can be used to find a hit location or position to apply explosion force or any other special effects. - /// - /// The position to find the closest point to it. - /// The result point on the collider that is closest to the specified location. - API_FUNCTION() void ClosestPoint(const Vector3& position, API_PARAM(Out) Vector3& result) const; - /// /// Draws the terrain patch. /// @@ -451,6 +424,10 @@ public: void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; RigidBody* GetAttachedRigidBody() const override; + bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const final; + bool RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, float maxDistance = MAX_float) const final; + void ClosestPoint(const Vector3& point, Vector3& result) const final; + bool ContainsPoint(const Vector3& point) const final; protected: // [PhysicsColliderActor] From c140cc4e7c755c0ce432041d0fc70a5fc8738903 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 16 Feb 2024 17:39:35 +0100 Subject: [PATCH 27/42] Add `Material` to `RayCastHit` for surface detection logic --- Source/Engine/Physics/PhysX/Types.h | 18 ++++++++++++++++++ Source/Engine/Physics/PhysicalMaterial.h | 5 +++-- Source/Engine/Physics/Types.h | 8 +++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Physics/PhysX/Types.h b/Source/Engine/Physics/PhysX/Types.h index d95d6b140..c86a03a62 100644 --- a/Source/Engine/Physics/PhysX/Types.h +++ b/Source/Engine/Physics/PhysX/Types.h @@ -17,6 +17,7 @@ #include #include #include +#include #include namespace physx @@ -233,12 +234,28 @@ inline float RadPerSToRpm(float v) return v * (30.0f / PI); } +inline PhysicalMaterial* GetMaterial(const PxShape* shape, PxU32 faceIndex) +{ + if (faceIndex != 0xFFFFffff) + { + PxBaseMaterial* mat = shape->getMaterialFromInternalFaceIndex(faceIndex); + return mat ? (PhysicalMaterial*)mat->userData : nullptr; + } + else + { + PxMaterial* mat; + shape->getMaterials(&mat, 1); + return mat ? (PhysicalMaterial*)mat->userData : nullptr; + } +} + inline void P2C(const PxRaycastHit& hit, RayCastHit& result) { result.Point = P2C(hit.position); result.Normal = P2C(hit.normal); result.Distance = hit.distance; result.Collider = hit.shape ? static_cast(hit.shape->userData) : nullptr; + result.Material = hit.shape ? GetMaterial(hit.shape, hit.faceIndex) : nullptr; result.FaceIndex = hit.faceIndex; result.UV.X = hit.u; result.UV.Y = hit.v; @@ -250,6 +267,7 @@ inline void P2C(const PxSweepHit& hit, RayCastHit& result) result.Normal = P2C(hit.normal); result.Distance = hit.distance; result.Collider = hit.shape ? static_cast(hit.shape->userData) : nullptr; + result.Material = hit.shape ? GetMaterial(hit.shape, hit.faceIndex) : nullptr; result.FaceIndex = hit.faceIndex; result.UV = Vector2::Zero; } diff --git a/Source/Engine/Physics/PhysicalMaterial.h b/Source/Engine/Physics/PhysicalMaterial.h index f1aa0c1fd..cb8202876 100644 --- a/Source/Engine/Physics/PhysicalMaterial.h +++ b/Source/Engine/Physics/PhysicalMaterial.h @@ -4,16 +4,17 @@ #include "Types.h" #include "Engine/Core/ISerializable.h" +#include "Engine/Scripting/ScriptingObject.h" #include "Engine/Level/Tags.h" /// /// Physical materials are used to define the response of a physical object when interacting dynamically with the world. /// API_CLASS(Attributes = "ContentContextMenu(\"New/Physics/Physical Material\")") -class FLAXENGINE_API PhysicalMaterial final : public ISerializable +class FLAXENGINE_API PhysicalMaterial final : public ScriptingObject, public ISerializable { API_AUTO_SERIALIZATION(); - DECLARE_SCRIPTING_TYPE_MINIMAL(PhysicalMaterial); + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(PhysicalMaterial, ScriptingObject); private: void* _material = nullptr; diff --git a/Source/Engine/Physics/Types.h b/Source/Engine/Physics/Types.h index 1631f694b..f7cb9f89c 100644 --- a/Source/Engine/Physics/Types.h +++ b/Source/Engine/Physics/Types.h @@ -10,6 +10,7 @@ struct PhysicsStatistics; class PhysicsColliderActor; class PhysicsScene; +class PhysicalMaterial; class Joint; class Collider; class CollisionData; @@ -132,7 +133,7 @@ DECLARE_ENUM_OPERATORS(RigidbodyConstraints); /// /// Raycast hit result data. /// -API_STRUCT() struct RayCastHit +API_STRUCT(NoDefault) struct RayCastHit { DECLARE_SCRIPTING_TYPE_NO_SPAWN(RayCastHit); @@ -141,6 +142,11 @@ API_STRUCT() struct RayCastHit /// API_FIELD() PhysicsColliderActor* Collider = nullptr; + /// + /// The physical material of the surface that was hit. + /// + API_FIELD() PhysicalMaterial* Material = nullptr; + /// /// The normal of the surface the ray hit. /// From 12f7370caffe4893f8d25744ea47a8cb5fd1e0e3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 16 Feb 2024 23:16:18 +0100 Subject: [PATCH 28/42] Fix missing forward type decl --- Source/Engine/Terrain/Terrain.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h index dd9dc1926..b5bf0ae5c 100644 --- a/Source/Engine/Terrain/Terrain.h +++ b/Source/Engine/Terrain/Terrain.h @@ -10,6 +10,7 @@ class Terrain; class TerrainChunk; class TerrainPatch; class TerrainManager; +class PhysicalMaterial; struct RayCastHit; struct RenderView; From c3faabaa0f241863754f8bbd8b4a357b84440fdc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 16 Feb 2024 23:29:01 +0100 Subject: [PATCH 29/42] Fix game build --- Source/Engine/Terrain/TerrainPatch.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index d30f65ec7..aa369f2ed 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -27,7 +27,7 @@ #include "Engine/ContentImporters/AssetsImportingManager.h" #endif #endif -#if TERRAIN_UPDATING +#if TERRAIN_EDITING || TERRAIN_UPDATING #include "Engine/Core/Collections/ArrayExtensions.h" #endif #if USE_EDITOR @@ -181,6 +181,7 @@ struct TerrainDataUpdateInfo // When using physical materials, then get splatmaps data required for per-triangle material indices void GetSplatMaps() { +#if TERRAIN_UPDATING if (SplatMaps[0]) return; if (UsePhysicalMaterials()) @@ -188,6 +189,9 @@ struct TerrainDataUpdateInfo for (int32 i = 0; i < TERRAIN_MAX_SPLATMAPS_COUNT; i++) SplatMaps[i] = Patch->GetSplatMapData(i); } +#else + LOG(Warning, "Splatmaps reading not implemented for physical layers updating."); +#endif } }; From b931020e5c97aab4385e11104d72d4de6bf54518 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 16 Feb 2024 23:32:46 +0100 Subject: [PATCH 30/42] Enable terrain updating in cooked build #2068 --- Source/Engine/Terrain/Terrain.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h index b5bf0ae5c..7db9b8500 100644 --- a/Source/Engine/Terrain/Terrain.h +++ b/Source/Engine/Terrain/Terrain.h @@ -24,10 +24,7 @@ struct RenderView; #define TERRAIN_EDITING 1 // Enable/disable terrain heightmap samples modification and gather. Used by the editor to modify the terrain with the brushes. -#define TERRAIN_UPDATING (USE_EDITOR) - -// Enable/disable precise terrain geometry collision testing (with in-build vertex buffer caching, this will increase memory usage) -#define USE_PRECISE_TERRAIN_INTERSECTS (USE_EDITOR) +#define TERRAIN_UPDATING 1 // Enable/disable terrain physics collision drawing #define TERRAIN_USE_PHYSICS_DEBUG (USE_EDITOR && 1) From 09532acf29f4ddf2175ab132deff39d5e76eb52e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 17 Feb 2024 07:12:52 +0100 Subject: [PATCH 31/42] Fix compilation with Clang --- Source/Engine/Content/JsonAssetReference.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Content/JsonAssetReference.h b/Source/Engine/Content/JsonAssetReference.h index 965a0aaa0..201d0b3a3 100644 --- a/Source/Engine/Content/JsonAssetReference.h +++ b/Source/Engine/Content/JsonAssetReference.h @@ -25,7 +25,7 @@ API_STRUCT(NoDefault, Template, MarshalAs=JsonAsset*) struct JsonAssetReference /// The asset instance object or null. FORCE_INLINE T* GetInstance() const { - return _asset ? Get()->GetInstance() : nullptr; + return _asset ? Get()->template GetInstance() : nullptr; } JsonAssetReference& operator=(JsonAsset* asset) noexcept From 368dac5e4b1cae13c1487015227b93f724b4e6f4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 17 Feb 2024 11:24:56 +0100 Subject: [PATCH 32/42] Simplify splatmap data management #1739 --- .../Tools/Terrain/PaintTerrainGizmoMode.cs | 30 +++++-------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs index 05aaa48b5..4e7925dd9 100644 --- a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs +++ b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs @@ -36,35 +36,26 @@ namespace FlaxEditor.Tools.Terrain "Layer 7", }; - private class SplatmapData + private struct SplatmapData { - public IntPtr DataPtr { get; set; } = IntPtr.Zero; - public int Size { get; set; } = 0; - - public SplatmapData(int size) - { - EnsureCapacity(size); - } + public IntPtr DataPtr; + public int Size; public void EnsureCapacity(int size) { if (Size < size) { if (DataPtr != IntPtr.Zero) - { Marshal.FreeHGlobal(DataPtr); - } DataPtr = Marshal.AllocHGlobal(size); Size = size; } } - /// public void Free() { if (DataPtr == IntPtr.Zero) return; - Marshal.FreeHGlobal(DataPtr); DataPtr = IntPtr.Zero; Size = 0; @@ -72,7 +63,7 @@ namespace FlaxEditor.Tools.Terrain } private EditTerrainMapAction _activeAction; - private List _cachedSplatmapData = new(); + private SplatmapData[] _cachedSplatmapData = new SplatmapData[2]; /// /// The terrain painting gizmo. @@ -268,14 +259,9 @@ namespace FlaxEditor.Tools.Terrain /// The allocated memory using interface. public IntPtr GetSplatmapTempBuffer(int size, int splatmapIndex) { - if (_cachedSplatmapData.Count <= splatmapIndex) - { - _cachedSplatmapData.Add(new SplatmapData(size)); - return _cachedSplatmapData[splatmapIndex].DataPtr; - } - - _cachedSplatmapData[splatmapIndex].EnsureCapacity(size); - return _cachedSplatmapData[splatmapIndex].DataPtr; + ref var splatmapData = ref _cachedSplatmapData[splatmapIndex]; + splatmapData.EnsureCapacity(size); + return splatmapData.DataPtr; } /// @@ -309,9 +295,7 @@ namespace FlaxEditor.Tools.Terrain // Free temporary memory buffer foreach (var splatmapData in _cachedSplatmapData) - { splatmapData.Free(); - } } /// From 0cf39c9f8d78ce2efec36ed51f73a514078575dc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 17 Feb 2024 11:36:13 +0100 Subject: [PATCH 33/42] Further improve terrain painting logic #1739r --- Source/Editor/Tools/Terrain/Paint/Mode.cs | 6 +-- .../Tools/Terrain/Paint/SingleLayerMode.cs | 39 +++++++++++-------- Source/Engine/Core/Math/Color.cs | 15 +++++++ 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/Source/Editor/Tools/Terrain/Paint/Mode.cs b/Source/Editor/Tools/Terrain/Paint/Mode.cs index bf9857bea..03e4a4690 100644 --- a/Source/Editor/Tools/Terrain/Paint/Mode.cs +++ b/Source/Editor/Tools/Terrain/Paint/Mode.cs @@ -209,8 +209,7 @@ namespace FlaxEditor.Tools.Terrain.Paint public int SplatmapIndex; /// - /// The splatmap texture index. If is 0, this will be 1. - /// If is 1, this will be 0. + /// The splatmap texture index. If is 0, this will be 1. If is 1, this will be 0. /// public int SplatmapIndexOther; @@ -220,8 +219,7 @@ namespace FlaxEditor.Tools.Terrain.Paint public Color32* TempBuffer; /// - /// The 'other" temporary data buffer (for modified data). If refers - /// to the splatmap with index 0, this one will refer to the one with index 1. + /// The 'other' temporary data buffer (for modified data). If refersto the splatmap with index 0, this one will refer to the one with index 1. /// public Color32* TempBufferOther; diff --git a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs index d0c76a830..5921f7d10 100644 --- a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs +++ b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs @@ -76,6 +76,7 @@ namespace FlaxEditor.Tools.Terrain.Paint // Apply brush modification Profiler.BeginEvent("Apply Brush"); + bool otherModified = false; for (int z = 0; z < p.ModifiedSize.Y; z++) { var zz = z + p.ModifiedOffset.Y; @@ -86,34 +87,38 @@ namespace FlaxEditor.Tools.Terrain.Paint var samplePositionLocal = p.PatchPositionLocal + new Vector3(xx * FlaxEngine.Terrain.UnitsPerVertex, 0, zz * FlaxEngine.Terrain.UnitsPerVertex); Vector3.Transform(ref samplePositionLocal, ref p.TerrainWorld, out Vector3 samplePositionWorld); + var sample = Mathf.Saturate(p.Brush.Sample(ref brushPosition, ref samplePositionWorld)); + + var paintAmount = sample * strength; + if (paintAmount < 0.0f) + continue; // Skip when pixel won't be affected - var sample = Mathf.Clamp(p.Brush.Sample(ref brushPosition, ref samplePositionWorld), 0f, 1f); - var paintAmount = sample * strength * (1f - src[c]); - // Paint on the active splatmap texture - src[c] = Mathf.Clamp(src[c] + paintAmount, 0, 1f); - src[(c + 1) % 4] = Mathf.Clamp(src[(c + 1) % 4] - paintAmount, 0, 1f); - src[(c + 2) % 4] = Mathf.Clamp(src[(c + 2) % 4] - paintAmount, 0, 1f); - src[(c + 3) % 4] = Mathf.Clamp(src[(c + 3) % 4] - paintAmount, 0, 1f); - + src[c] = Mathf.Saturate(src[c] + paintAmount); + src[(c + 1) % 4] = Mathf.Saturate(src[(c + 1) % 4] - paintAmount); + src[(c + 2) % 4] = Mathf.Saturate(src[(c + 2) % 4] - paintAmount); + src[(c + 3) % 4] = Mathf.Saturate(src[(c + 3) % 4] - paintAmount); p.TempBuffer[z * p.ModifiedSize.X + x] = src; - // Remove 'paint' from the other splatmap texture var other = (Color)p.SourceDataOther[zz * p.HeightmapSize + xx]; - - other[c] = Mathf.Clamp(other[c] - paintAmount, 0, 1f); - other[(c + 1) % 4] = Mathf.Clamp(other[(c + 1) % 4] - paintAmount, 0, 1f); - other[(c + 2) % 4] = Mathf.Clamp(other[(c + 2) % 4] - paintAmount, 0, 1f); - other[(c + 3) % 4] = Mathf.Clamp(other[(c + 3) % 4] - paintAmount, 0, 1f); - - p.TempBufferOther[z * p.ModifiedSize.X + x] = other; + //if (other.ValuesSum > 0.0f) // Skip editing the other splatmap if it's empty + { + // Remove 'paint' from the other splatmap texture + other[c] = Mathf.Saturate(other[c] - paintAmount); + other[(c + 1) % 4] = Mathf.Saturate(other[(c + 1) % 4] - paintAmount); + other[(c + 2) % 4] = Mathf.Saturate(other[(c + 2) % 4] - paintAmount); + other[(c + 3) % 4] = Mathf.Saturate(other[(c + 3) % 4] - paintAmount); + p.TempBufferOther[z * p.ModifiedSize.X + x] = other; + otherModified = true; + } } } Profiler.EndEvent(); // Update terrain patch TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndex, p.TempBuffer, ref p.ModifiedOffset, ref p.ModifiedSize); - TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndexOther, p.TempBufferOther, ref p.ModifiedOffset, ref p.ModifiedSize); + if (otherModified) + TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndexOther, p.TempBufferOther, ref p.ModifiedOffset, ref p.ModifiedSize); } } } diff --git a/Source/Engine/Core/Math/Color.cs b/Source/Engine/Core/Math/Color.cs index d585177a0..7474b913d 100644 --- a/Source/Engine/Core/Math/Color.cs +++ b/Source/Engine/Core/Math/Color.cs @@ -92,6 +92,21 @@ namespace FlaxEngine /// public float MaxColorComponent => Mathf.Max(Mathf.Max(R, G), B); + /// + /// Gets a minimum component value (max of r,g,b,a). + /// + public float MinValue => Math.Min(R, Math.Min(G, Math.Min(B, A))); + + /// + /// Gets a maximum component value (min of r,g,b,a). + /// + public float MaxValue => Math.Max(R, Math.Max(G, Math.Max(B, A))); + + /// + /// Gets a sum of the component values. + /// + public float ValuesSum => R + G + B + A; + /// /// Constructs a new Color with given r,g,b,a component. /// From 37dfdad7e2d45403ded63e37026c0c3cfe8d8222 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 17 Feb 2024 20:22:40 +0100 Subject: [PATCH 34/42] Minor improvements to character controller #1935 --- Source/Engine/Physics/Colliders/CharacterController.cpp | 8 ++------ Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 38ab0394d..e9f86ebab 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -280,12 +280,8 @@ void CharacterController::OnActiveTransformChanged() // Change actor transform (but with locking) ASSERT(!_isUpdatingTransform); _isUpdatingTransform = true; - Transform transform; - PhysicsBackend::GetRigidActorPose(PhysicsBackend::GetShapeActor(_shape), transform.Translation, transform.Orientation); - transform.Translation -= _center; - transform.Orientation = _transform.Orientation; - transform.Scale = _transform.Scale; - SetTransform(transform); + const Vector3 position = PhysicsBackend::GetControllerPosition(_controller) - _center; + SetPosition(position); _isUpdatingTransform = false; UpdateBounds(); diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 9d0d2ea16..75c088db7 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -3008,7 +3008,7 @@ void* PhysicsBackend::CreateController(void* scene, IPhysicsActor* actor, Physic desc.material = DefaultMaterial; const float minSize = 0.001f; desc.height = Math::Max(height, minSize); - desc.radius = Math::Max(radius - desc.contactOffset, minSize); + desc.radius = Math::Max(radius - Math::Max(contactOffset, 0.0f), minSize); desc.stepOffset = Math::Min(stepOffset, desc.height + desc.radius * 2.0f - minSize); auto controllerPhysX = (PxCapsuleController*)scenePhysX->ControllerManager->createController(desc); PxRigidActor* actorPhysX = controllerPhysX->getActor(); From c305bed829f8a973337e054b87b1af898ff371f3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 17 Feb 2024 23:42:57 +0100 Subject: [PATCH 35/42] Add sub-groups expanded state restoring in Properties window #1049 --- .../CustomEditors/Dedicated/ScriptsEditor.cs | 10 ++-- .../CustomEditors/LayoutElementsContainer.cs | 60 ++++++++++++++----- Source/Editor/Modules/ProjectCacheModule.cs | 39 ++++++------ 3 files changed, 67 insertions(+), 42 deletions(-) diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index f783822a5..e1a358b6e 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -591,14 +591,14 @@ namespace FlaxEditor.CustomEditors.Dedicated var group = layout.Group(title, editor); if ((Presenter.Features & FeatureFlags.CacheExpandedGroups) != 0) { - if (Editor.Instance.ProjectCache.IsCollapsedGroup(title)) - group.Panel.Close(false); + if (Editor.Instance.ProjectCache.IsGroupToggled(title)) + group.Panel.Close(); else - group.Panel.Open(false); - group.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetCollapsedGroup(panel.HeaderText, panel.IsClosed); + group.Panel.Open(); + group.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetGroupToggle(panel.HeaderText, panel.IsClosed); } else - group.Panel.Open(false); + group.Panel.Open(); // Customize group.Panel.TooltipText = Editor.Instance.CodeDocs.GetTooltip(scriptType); diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index 936851b15..e9a501bec 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -25,6 +25,11 @@ namespace FlaxEditor.CustomEditors /// internal bool isRootGroup = true; + /// + /// Parent container who created this one. + /// + internal LayoutElementsContainer _parent; + /// /// The children. /// @@ -40,6 +45,24 @@ namespace FlaxEditor.CustomEditors /// public abstract ContainerControl ContainerControl { get; } + /// + /// Gets the Custom Editors layout presenter. + /// + internal CustomEditorPresenter Presenter + { + get + { + CustomEditorPresenter result; + var container = this; + do + { + result = container as CustomEditorPresenter; + container = container._parent; + } while (container != null); + return result; + } + } + /// /// Adds new group element. /// @@ -81,17 +104,31 @@ namespace FlaxEditor.CustomEditors public GroupElement Group(string title, bool useTransparentHeader = false) { var element = new GroupElement(); - if (!isRootGroup) + var presenter = Presenter; + var isSubGroup = !isRootGroup; + if (isSubGroup) + element.Panel.Close(); + if (presenter != null && (presenter.Features & FeatureFlags.CacheExpandedGroups) != 0) { - element.Panel.Close(false); - } - else if (this is CustomEditorPresenter presenter && (presenter.Features & FeatureFlags.CacheExpandedGroups) != 0) - { - if (Editor.Instance.ProjectCache.IsCollapsedGroup(title)) - element.Panel.Close(false); - element.Panel.IsClosedChanged += OnPanelIsClosedChanged; + // Build group identifier (made of path from group titles) + var expandPath = title; + var container = this; + while (container != null && !(container is CustomEditorPresenter)) + { + if (container.ContainerControl is DropPanel dropPanel) + expandPath = dropPanel.HeaderText + "/" + expandPath; + container = container._parent; + } + + // Caching/restoring expanded groups (non-root groups cache expanded state so invert boolean expression) + if (Editor.Instance.ProjectCache.IsGroupToggled(expandPath) ^ isSubGroup) + element.Panel.Close(); + else + element.Panel.Open(); + element.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetGroupToggle(expandPath, panel.IsClosed ^ isSubGroup); } element.isRootGroup = false; + element._parent = this; element.Panel.HeaderText = title; if (useTransparentHeader) { @@ -103,11 +140,6 @@ namespace FlaxEditor.CustomEditors return element; } - private void OnPanelIsClosedChanged(DropPanel panel) - { - Editor.Instance.ProjectCache.SetCollapsedGroup(panel.HeaderText, panel.IsClosed); - } - /// /// Adds new horizontal panel element. /// @@ -627,7 +659,6 @@ namespace FlaxEditor.CustomEditors if (style == DisplayStyle.Group) { var group = Group(name, editor, true); - group.Panel.Close(false); group.Panel.TooltipText = tooltip; return group.Object(values, editor); } @@ -657,7 +688,6 @@ namespace FlaxEditor.CustomEditors if (style == DisplayStyle.Group) { var group = Group(label.Text, editor, true); - group.Panel.Close(false); group.Panel.TooltipText = tooltip; return group.Object(values, editor); } diff --git a/Source/Editor/Modules/ProjectCacheModule.cs b/Source/Editor/Modules/ProjectCacheModule.cs index acb6e997e..eebea3ba0 100644 --- a/Source/Editor/Modules/ProjectCacheModule.cs +++ b/Source/Editor/Modules/ProjectCacheModule.cs @@ -18,7 +18,7 @@ namespace FlaxEditor.Modules private DateTime _lastSaveTime; private readonly HashSet _expandedActors = new HashSet(); - private readonly HashSet _collapsedGroups = new HashSet(); + private readonly HashSet _toggledGroups = new HashSet(); private readonly Dictionary _customData = new Dictionary(); /// @@ -62,26 +62,26 @@ namespace FlaxEditor.Modules } /// - /// Determines whether group identified by the given title is collapsed in the UI. + /// Determines whether group identified by the given title is collapsed/opened in the UI. /// /// The group title. - /// true if group is collapsed; otherwise, false. - public bool IsCollapsedGroup(string title) + /// true if group is toggled; otherwise, false. + public bool IsGroupToggled(string title) { - return _collapsedGroups.Contains(title); + return _toggledGroups.Contains(title); } /// - /// Sets the group collapsed cached value. + /// Sets the group collapsed/opened cached value. /// /// The group title. - /// If set to true group will be cached as an collapsed, otherwise false. - public void SetCollapsedGroup(string title, bool isCollapsed) + /// If set to true group will be cached as a toggled, otherwise false. + public void SetGroupToggle(string title, bool isToggled) { - if (isCollapsed) - _collapsedGroups.Add(title); + if (isToggled) + _toggledGroups.Add(title); else - _collapsedGroups.Remove(title); + _toggledGroups.Remove(title); _isDirty = true; } @@ -160,7 +160,7 @@ namespace FlaxEditor.Modules _expandedActors.Add(new Guid(bytes16)); } - _collapsedGroups.Clear(); + _toggledGroups.Clear(); _customData.Clear(); break; @@ -176,7 +176,7 @@ namespace FlaxEditor.Modules _expandedActors.Add(new Guid(bytes16)); } - _collapsedGroups.Clear(); + _toggledGroups.Clear(); _customData.Clear(); int customDataCount = reader.ReadInt32(); @@ -201,11 +201,9 @@ namespace FlaxEditor.Modules } int collapsedGroupsCount = reader.ReadInt32(); - _collapsedGroups.Clear(); + _toggledGroups.Clear(); for (int i = 0; i < collapsedGroupsCount; i++) - { - _collapsedGroups.Add(reader.ReadString()); - } + _toggledGroups.Add(reader.ReadString()); _customData.Clear(); int customDataCount = reader.ReadInt32(); @@ -259,11 +257,9 @@ namespace FlaxEditor.Modules writer.Write(e.ToByteArray()); } - writer.Write(_collapsedGroups.Count); - foreach (var e in _collapsedGroups) - { + writer.Write(_toggledGroups.Count); + foreach (var e in _toggledGroups) writer.Write(e); - } writer.Write(_customData.Count); foreach (var e in _customData) @@ -284,7 +280,6 @@ namespace FlaxEditor.Modules try { SaveGuarded(); - _isDirty = false; } catch (Exception ex) From c81ef9b26f3be4d86861c317f4e396c5bfed3e04 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 17 Feb 2024 23:48:30 +0100 Subject: [PATCH 36/42] Add hiding Actor's Transform for `UIControl`Actor #382 --- Source/Engine/Level/Actor.cs | 6 +++++- Source/Engine/Level/Actor.h | 10 ++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index dbe8a89b5..1d64ff5ae 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -269,7 +269,7 @@ namespace FlaxEngine { return FindActor(typeof(T), name) as T; } - + /// /// Tries to find actor of the given type and tag in this actor hierarchy (checks this actor and all children hierarchy). /// @@ -386,5 +386,9 @@ namespace FlaxEngine { return $"{Name} ({GetType().Name})"; } + +#if FLAX_EDITOR + internal bool ShowTransform => !(this is UIControl); +#endif } } diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 0ce9a0dbc..f66d13f40 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -534,9 +534,7 @@ public: /// /// Gets actor direction vector (forward vector). /// - /// The result value. - API_PROPERTY(Attributes="HideInEditor, NoSerialize") - FORCE_INLINE Float3 GetDirection() const + API_PROPERTY(Attributes="HideInEditor, NoSerialize") FORCE_INLINE Float3 GetDirection() const { return Float3::Transform(Float3::Forward, GetOrientation()); } @@ -571,7 +569,7 @@ public: /// /// Gets local position of the actor in parent actor space. /// - API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Position\"), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorOrder(-30), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+PositionEditor\")") + API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Position\"), VisibleIf(\"ShowTransform\"), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorOrder(-30), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+PositionEditor\")") FORCE_INLINE Vector3 GetLocalPosition() const { return _localTransform.Translation; @@ -587,7 +585,7 @@ public: /// Gets local rotation of the actor in parent actor space. /// /// Actor.LocalOrientation *= Quaternion.Euler(0, 10 * Time.DeltaTime, 0) - API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Rotation\"), DefaultValue(typeof(Quaternion), \"0,0,0,1\"), EditorOrder(-20), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+OrientationEditor\")") + API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Rotation\"), VisibleIf(\"ShowTransform\"), DefaultValue(typeof(Quaternion), \"0,0,0,1\"), EditorOrder(-20), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+OrientationEditor\")") FORCE_INLINE Quaternion GetLocalOrientation() const { return _localTransform.Orientation; @@ -602,7 +600,7 @@ public: /// /// Gets local scale vector of the actor in parent actor space. /// - API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Scale\"), DefaultValue(typeof(Float3), \"1,1,1\"), Limit(float.MinValue, float.MaxValue, 0.01f), EditorOrder(-10), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+ScaleEditor\")") + API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Scale\"), VisibleIf(\"ShowTransform\"), DefaultValue(typeof(Float3), \"1,1,1\"), Limit(float.MinValue, float.MaxValue, 0.01f), EditorOrder(-10), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+ScaleEditor\")") FORCE_INLINE Float3 GetLocalScale() const { return _localTransform.Scale; From 5e218c8da92b942e18849f3f0c62596c0214cc82 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 18 Feb 2024 00:03:27 +0100 Subject: [PATCH 37/42] Improve #2100 and fix undo --- Source/Editor/Modules/SceneEditingModule.cs | 21 +++++++++++-------- Source/Editor/Modules/UIModule.cs | 4 +--- .../Windows/SceneTreeWindow.ContextMenu.cs | 6 +----- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 3b982d7ad..35b85bfa8 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -542,24 +542,27 @@ namespace FlaxEditor.Modules Actor actor = new EmptyActor(); Editor.SceneEditing.Spawn(actor, null, false); List selection = Editor.SceneEditing.Selection; - for (int i = 0; i < selection.Count; i++) + var actors = selection.Where(x => x is ActorNode).Select(x => ((ActorNode)x).Actor); + using (new UndoMultiBlock(Undo, actors, "Reparent actors")) { - if (selection[i] is ActorNode node) + for (int i = 0; i < selection.Count; i++) { - if (node.ParentNode != node.ParentScene) // if parent node is not Scene + if (selection[i] is ActorNode node) { - if (selection.Contains(node.ParentNode)) + if (node.ParentNode != node.ParentScene) // If parent node is not a scene { - return; // if parent and child nodes selected together, don't touch child nodes - } - else - { // put created node as child of the Parent Node of node + if (selection.Contains(node.ParentNode)) + { + return; // If parent and child nodes selected together, don't touch child nodes + } + + // Put created node as child of the Parent Node of node int parentOrder = node.Actor.OrderInParent; actor.Parent = node.Actor.Parent; actor.OrderInParent = parentOrder; } + node.Actor.Parent = actor; } - node.Actor.Parent = actor; } } Editor.SceneEditing.Select(actor); diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 7920d5de4..4537d732e 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -549,13 +549,11 @@ namespace FlaxEditor.Modules _menuEditCut = cm.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut); _menuEditCopy = cm.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy); _menuEditPaste = cm.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste); - cm.AddSeparator(); - _menuCreateParentForSelectedActors = cm.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors); - cm.AddSeparator(); _menuEditDelete = cm.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete); _menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate); cm.AddSeparator(); _menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll, Editor.SceneEditing.SelectAllScenes); + _menuCreateParentForSelectedActors = cm.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors); _menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search); cm.AddSeparator(); cm.AddButton("Game Settings", () => diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index fa4abfeca..abad36829 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -132,17 +132,13 @@ namespace FlaxEditor.Windows b = contextMenu.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut); b.Enabled = canEditScene; - // Create a new hierarchy from selected actors + // Create option contextMenu.AddSeparator(); b = contextMenu.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors); b.Enabled = canEditScene && hasSthSelected; - // Prefab options - - contextMenu.AddSeparator(); - b = contextMenu.AddButton("Create Prefab", Editor.Prefabs.CreatePrefab); b.Enabled = isSingleActorSelected && ((ActorNode)Editor.SceneEditing.Selection[0]).CanCreatePrefab && From e165c87e5a1d5088b278861f8dc3bd2eb1cf92b3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 18 Feb 2024 00:03:54 +0100 Subject: [PATCH 38/42] Format code #1871 --- Source/Engine/UI/GUI/Common/Slider.cs | 56 ++++++++++++--------------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/Source/Engine/UI/GUI/Common/Slider.cs b/Source/Engine/UI/GUI/Common/Slider.cs index d9d998bc2..efffa6728 100644 --- a/Source/Engine/UI/GUI/Common/Slider.cs +++ b/Source/Engine/UI/GUI/Common/Slider.cs @@ -1,4 +1,6 @@ -using System; +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System; namespace FlaxEngine.GUI; @@ -81,14 +83,14 @@ public class Slider : ContainerControl /// Gets or sets the slider direction. /// [EditorOrder(40), Tooltip("Slider Direction.")] - public SliderDirection Direction - { + public SliderDirection Direction + { get => _direction; set { _direction = value; UpdateThumb(); - } + } } private SliderDirection _direction = SliderDirection.HorizontalRight; @@ -138,14 +140,10 @@ public class Slider : ContainerControl { switch (Direction) { - case SliderDirection.HorizontalRight: - return new Float2(_thumbSize.X / 2, Height / 2); - case SliderDirection.HorizontalLeft: - return new Float2(Width - _thumbSize.X / 2, Height / 2); - case SliderDirection.VerticalUp: - return new Float2(Width / 2, Height - _thumbSize.Y / 2); - case SliderDirection.VerticalDown: - return new Float2(Width / 2, _thumbSize.Y / 2); + case SliderDirection.HorizontalRight: return new Float2(_thumbSize.X / 2, Height / 2); + case SliderDirection.HorizontalLeft: return new Float2(Width - _thumbSize.X / 2, Height / 2); + case SliderDirection.VerticalUp: return new Float2(Width / 2, Height - _thumbSize.Y / 2); + case SliderDirection.VerticalDown: return new Float2(Width / 2, _thumbSize.Y / 2); default: break; } return Float2.Zero; @@ -162,14 +160,10 @@ public class Slider : ContainerControl { switch (Direction) { - case SliderDirection.HorizontalRight: - return new Float2(Width - _thumbSize.X / 2, Height / 2); - case SliderDirection.HorizontalLeft: - return new Float2(_thumbSize.X / 2, Height / 2); - case SliderDirection.VerticalUp: - return new Float2(Width / 2, _thumbSize.Y / 2); - case SliderDirection.VerticalDown: - return new Float2(Width / 2, Height - _thumbSize.Y / 2); + case SliderDirection.HorizontalRight: return new Float2(Width - _thumbSize.X / 2, Height / 2); + case SliderDirection.HorizontalLeft: return new Float2(_thumbSize.X / 2, Height / 2); + case SliderDirection.VerticalUp: return new Float2(Width / 2, _thumbSize.Y / 2); + case SliderDirection.VerticalDown: return new Float2(Width / 2, Height - _thumbSize.Y / 2); default: break; } return Float2.Zero; @@ -186,7 +180,8 @@ public class Slider : ContainerControl /// The thumb size. /// [EditorOrder(41), Tooltip("The size of the thumb.")] - public Float2 ThumbSize { + public Float2 ThumbSize + { get => _thumbSize; set { @@ -200,7 +195,7 @@ public class Slider : ContainerControl /// [EditorOrder(42), Tooltip("Fill the track.")] public bool FillTrack = true; - + /// /// Whether to use whole numbers. /// @@ -223,7 +218,7 @@ public class Slider : ContainerControl /// Gets the width of the track. /// private float TrackWidth => (Direction is SliderDirection.HorizontalLeft or SliderDirection.HorizontalRight) ? Width - _thumbSize.X : TrackThickness; - + /// /// Gets the height of the track. /// @@ -246,7 +241,7 @@ public class Slider : ContainerControl /// [EditorDisplay("Thumb Style"), EditorOrder(2030), Tooltip("The color of the slider thumb when it's not selected."), ExpandGroups] public Color ThumbColor { get; set; } - + /// /// The color of the slider thumb when it's highlighted. /// @@ -280,12 +275,12 @@ public class Slider : ContainerControl /// Occurs when sliding ends. /// public event Action SlidingEnd; - + /// /// Occurs when value gets changed. /// public event Action ValueChanged; - + /// /// Initializes a new instance of the class. /// @@ -359,17 +354,16 @@ public class Slider : ContainerControl var fillLineRect = new Rectangle(_thumbSize.X / 2 - 1, (Height - TrackThickness - 2) / 2, Width - (Width - _thumbCenter) - _thumbSize.X / 2 + 1, TrackThickness + 2); switch (Direction) { - case SliderDirection.HorizontalRight: - break; + case SliderDirection.HorizontalRight: break; case SliderDirection.VerticalDown: - lineRect = new Rectangle((Width - TrackThickness) / 2 , _thumbSize.Y / 2, TrackThickness, Height - _thumbSize.Y); + lineRect = new Rectangle((Width - TrackThickness) / 2, _thumbSize.Y / 2, TrackThickness, Height - _thumbSize.Y); fillLineRect = new Rectangle((Width - TrackThickness - 2) / 2, _thumbSize.Y / 2 - 1, TrackThickness + 2, Height - (Height - _thumbCenter) - _thumbSize.Y / 2 + 1); break; case SliderDirection.HorizontalLeft: fillLineRect = new Rectangle(Width - (Width - _thumbCenter) - 1, (Height - TrackThickness - 2) / 2, Width - _thumbCenter + 1, TrackThickness + 2); break; case SliderDirection.VerticalUp: - lineRect = new Rectangle((Width - TrackThickness) / 2 , _thumbSize.Y / 2, TrackThickness, Height - _thumbSize.Y); + lineRect = new Rectangle((Width - TrackThickness) / 2, _thumbSize.Y / 2, TrackThickness, Height - _thumbSize.Y); fillLineRect = new Rectangle((Width - TrackThickness - 2) / 2, Height - (Height - _thumbCenter) - 1, TrackThickness + 2, Height - _thumbCenter + 1); break; default: break; @@ -379,7 +373,7 @@ public class Slider : ContainerControl if (TrackBrush != null) TrackBrush.Draw(lineRect, TrackLineColor); else - Render2D.FillRectangle(lineRect, TrackLineColor); + Render2D.FillRectangle(lineRect, TrackLineColor); // Draw track fill if (FillTrack) From 912437c456a85fc104d373fc2928de5d71d50a27 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 18 Feb 2024 10:27:17 +0100 Subject: [PATCH 39/42] Fix `CreateParentForSelectedActors` --- Source/Editor/Modules/SceneEditingModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 35b85bfa8..1657cb3a1 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -553,7 +553,7 @@ namespace FlaxEditor.Modules { if (selection.Contains(node.ParentNode)) { - return; // If parent and child nodes selected together, don't touch child nodes + continue; // If parent and child nodes selected together, don't touch child nodes } // Put created node as child of the Parent Node of node From dfbde5f8ebd07e1bc04985197eef5eaf3be90607 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 18 Feb 2024 10:47:24 +0100 Subject: [PATCH 40/42] Remove debug draw --- Source/Editor/Viewport/ViewportDraggingHelper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Editor/Viewport/ViewportDraggingHelper.cs b/Source/Editor/Viewport/ViewportDraggingHelper.cs index 9f8977b10..dce2867c3 100644 --- a/Source/Editor/Viewport/ViewportDraggingHelper.cs +++ b/Source/Editor/Viewport/ViewportDraggingHelper.cs @@ -234,7 +234,6 @@ namespace FlaxEditor.Viewport LocalOrientation = RootNode.RaycastNormalRotation(ref hitNormal), Name = item.ShortName }; - DebugDraw.DrawWireArrow(PostProcessSpawnedActorLocation(actor, ref hitNormal), actor.LocalOrientation, 1.0f, 0.5f, Color.Red, 1000000); Spawn(actor, ref hitLocation, ref hitNormal); } else if (hit is StaticModelNode staticModelNode) From d76b5234c5f5b5855d8b7e32e1f104ca91ccf6ef Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 18 Feb 2024 11:06:42 +0100 Subject: [PATCH 41/42] Expose and document various APIs of Visject for plugins to use --- .../Archetypes/Animation.StateMachine.cs | 40 ++----------------- Source/Editor/Surface/BehaviorTreeSurface.cs | 7 +--- Source/Editor/Surface/SurfaceStyle.cs | 33 +++++++++++++++ .../Surface/VisjectSurface.ContextMenu.cs | 23 ++++++++++- Source/Editor/Surface/VisjectSurfaceWindow.cs | 2 +- Source/Editor/Windows/SceneTreeWindow.cs | 9 +++-- Source/Engine/Visject/VisjectGraph.h | 2 +- Source/Engine/Visject/VisjectMeta.cpp | 4 -- Source/Engine/Visject/VisjectMeta.h | 15 +------ 9 files changed, 68 insertions(+), 67 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs index bb4bc724d..f96ac8e4b 100644 --- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs +++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs @@ -482,7 +482,7 @@ namespace FlaxEditor.Surface.Archetypes var startPos = PointToParent(ref center); targetState.GetConnectionEndPoint(ref startPos, out var endPos); var color = style.Foreground; - StateMachineState.DrawConnection(ref startPos, ref endPos, ref color); + SurfaceStyle.DrawStraightConnection(startPos, endPos, color); } } @@ -512,7 +512,7 @@ namespace FlaxEditor.Surface.Archetypes /// public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color) { - StateMachineState.DrawConnection(ref startPos, ref endPos, ref color); + SurfaceStyle.DrawStraightConnection(startPos, endPos, color); } /// @@ -676,38 +676,6 @@ namespace FlaxEditor.Surface.Archetypes { } - /// - /// Draws the connection between two state machine nodes. - /// - /// The start position. - /// The end position. - /// The line color. - public static void DrawConnection(ref Float2 startPos, ref Float2 endPos, ref Color color) - { - var sub = endPos - startPos; - var length = sub.Length; - if (length > Mathf.Epsilon) - { - var dir = sub / length; - var arrowRect = new Rectangle(0, 0, 16.0f, 16.0f); - float rotation = Float2.Dot(dir, Float2.UnitY); - if (endPos.X < startPos.X) - rotation = 2 - rotation; - var sprite = Editor.Instance.Icons.VisjectArrowClosed32; - var arrowTransform = - Matrix3x3.Translation2D(-6.5f, -8) * - Matrix3x3.RotationZ(rotation * Mathf.PiOverTwo) * - Matrix3x3.Translation2D(endPos - dir * 8); - - Render2D.PushTransform(ref arrowTransform); - Render2D.DrawSprite(sprite, arrowRect, color); - Render2D.PopTransform(); - - endPos -= dir * 4.0f; - } - Render2D.DrawLine(startPos, endPos, color); - } - /// /// Gets the connection end point for the given input position. Puts the end point near the edge of the node bounds. /// @@ -1308,7 +1276,7 @@ namespace FlaxEditor.Surface.Archetypes isMouseOver = Float2.DistanceSquared(ref mousePosition, ref point) < 25.0f; } var color = isMouseOver ? Color.Wheat : t.LineColor; - DrawConnection(ref t.StartPos, ref t.EndPos, ref color); + SurfaceStyle.DrawStraightConnection(t.StartPos, t.EndPos, color); } } @@ -1337,7 +1305,7 @@ namespace FlaxEditor.Surface.Archetypes /// public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color) { - DrawConnection(ref startPos, ref endPos, ref color); + SurfaceStyle.DrawStraightConnection(startPos, endPos, color); } /// diff --git a/Source/Editor/Surface/BehaviorTreeSurface.cs b/Source/Editor/Surface/BehaviorTreeSurface.cs index 9b201d3f8..760110769 100644 --- a/Source/Editor/Surface/BehaviorTreeSurface.cs +++ b/Source/Editor/Surface/BehaviorTreeSurface.cs @@ -31,7 +31,7 @@ namespace FlaxEditor.Surface var editor = Editor.Instance; var style = SurfaceStyle.CreateStyleHandler(editor); style.DrawBox = DrawBox; - style.DrawConnection = DrawConnection; + style.DrawConnection = SurfaceStyle.DrawStraightConnection; return style; } @@ -49,11 +49,6 @@ namespace FlaxEditor.Surface Render2D.FillRectangle(rect, color); } - private static void DrawConnection(Float2 start, Float2 end, Color color, float thickness) - { - Archetypes.Animation.StateMachineStateBase.DrawConnection(ref start, ref end, ref color); - } - private void OnActiveContextMenuVisibleChanged(Control activeCM) { _nodesCache.Wait(); diff --git a/Source/Editor/Surface/SurfaceStyle.cs b/Source/Editor/Surface/SurfaceStyle.cs index 2b8e97f62..5a67d6fb2 100644 --- a/Source/Editor/Surface/SurfaceStyle.cs +++ b/Source/Editor/Surface/SurfaceStyle.cs @@ -295,5 +295,38 @@ namespace FlaxEditor.Surface Background = editor.UI.VisjectSurfaceBackground, }; } + + /// + /// Draws a simple straight connection between two locations. + /// + /// The start position. + /// The end position. + /// The line color. + /// The line thickness. + public static void DrawStraightConnection(Float2 startPos, Float2 endPos, Color color, float thickness = 1.0f) + { + var sub = endPos - startPos; + var length = sub.Length; + if (length > Mathf.Epsilon) + { + var dir = sub / length; + var arrowRect = new Rectangle(0, 0, 16.0f, 16.0f); + float rotation = Float2.Dot(dir, Float2.UnitY); + if (endPos.X < startPos.X) + rotation = 2 - rotation; + var sprite = Editor.Instance.Icons.VisjectArrowClosed32; + var arrowTransform = + Matrix3x3.Translation2D(-6.5f, -8) * + Matrix3x3.RotationZ(rotation * Mathf.PiOverTwo) * + Matrix3x3.Translation2D(endPos - dir * 8); + + Render2D.PushTransform(ref arrowTransform); + Render2D.DrawSprite(sprite, arrowRect, color); + Render2D.PopTransform(); + + endPos -= dir * 4.0f; + } + Render2D.DrawLine(startPos, endPos, color); + } } } diff --git a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs index 3ca7ddfc7..51339d6bc 100644 --- a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs +++ b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs @@ -20,8 +20,15 @@ namespace FlaxEditor.Surface /// /// Utility for easy nodes archetypes generation for Visject Surface based on scripting types. /// - internal class NodesCache + [HideInEditor] + public class NodesCache { + /// + /// Delegate for scripting types filtering into cache. + /// + /// The input type to process. + /// Node groups cache that can be used for reusing groups for different nodes. + /// The cache version number. Can be used to reject any cached data after rebuilt. public delegate void IterateType(ScriptType scriptType, Dictionary, GroupArchetype> cache, int version); internal static readonly List Caches = new List(8); @@ -33,11 +40,18 @@ namespace FlaxEditor.Surface private VisjectCM _taskContextMenu; private Dictionary, GroupArchetype> _cache; + /// + /// Initializes a new instance of the class. + /// + /// The iterator callback to build node types from Scripting. public NodesCache(IterateType iterator) { _iterator = iterator; } + /// + /// Waits for the async caching job to finish. + /// public void Wait() { if (_task != null) @@ -48,6 +62,9 @@ namespace FlaxEditor.Surface } } + /// + /// Clears cache. + /// public void Clear() { Wait(); @@ -62,6 +79,10 @@ namespace FlaxEditor.Surface } } + /// + /// Updates the Visject Context Menu to contain current nodes. + /// + /// The output context menu to setup. public void Get(VisjectCM contextMenu) { Profiler.BeginEvent("Setup Context Menu"); diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs index cce86d5c0..6a9cba841 100644 --- a/Source/Editor/Surface/VisjectSurfaceWindow.cs +++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs @@ -25,7 +25,7 @@ namespace FlaxEditor.Surface /// The base interface for editor windows that use for content editing. /// /// - interface IVisjectSurfaceWindow : IVisjectSurfaceOwner + public interface IVisjectSurfaceWindow : IVisjectSurfaceOwner { /// /// Gets the asset edited by the window. diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index 1eb80c9ea..c5d790a3d 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Linq; using FlaxEditor.Gizmo; using FlaxEditor.Content; using FlaxEditor.GUI.Tree; @@ -14,7 +13,6 @@ using FlaxEditor.Scripting; using FlaxEditor.States; using FlaxEngine; using FlaxEngine.GUI; -using static FlaxEditor.GUI.ItemsListContextMenu; namespace FlaxEditor.Windows { @@ -24,8 +22,6 @@ namespace FlaxEditor.Windows /// public partial class SceneTreeWindow : SceneEditorWindow { - public Panel SceneTreePanel => _sceneTreePanel; - private TextBox _searchBox; private Tree _tree; private Panel _sceneTreePanel; @@ -37,6 +33,11 @@ namespace FlaxEditor.Windows private DragScriptItems _dragScriptItems; private DragHandlers _dragHandlers; + /// + /// Scene tree panel. + /// + public Panel SceneTreePanel => _sceneTreePanel; + /// /// Initializes a new instance of the class. /// diff --git a/Source/Engine/Visject/VisjectGraph.h b/Source/Engine/Visject/VisjectGraph.h index 085c05de0..5054bd54d 100644 --- a/Source/Engine/Visject/VisjectGraph.h +++ b/Source/Engine/Visject/VisjectGraph.h @@ -101,7 +101,7 @@ public: /// Visject graph parameter. /// /// -API_CLASS() class VisjectGraphParameter : public GraphParameter +API_CLASS() class FLAXENGINE_API VisjectGraphParameter : public GraphParameter { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(VisjectGraphParameter, GraphParameter); public: diff --git a/Source/Engine/Visject/VisjectMeta.cpp b/Source/Engine/Visject/VisjectMeta.cpp index 37631df55..624345813 100644 --- a/Source/Engine/Visject/VisjectMeta.cpp +++ b/Source/Engine/Visject/VisjectMeta.cpp @@ -5,10 +5,6 @@ #include "Engine/Serialization/ReadStream.h" #include "Engine/Serialization/WriteStream.h" -VisjectMeta::VisjectMeta() -{ -} - bool VisjectMeta::Load(ReadStream* stream, bool loadData) { Release(); diff --git a/Source/Engine/Visject/VisjectMeta.h b/Source/Engine/Visject/VisjectMeta.h index c417b7030..b35add815 100644 --- a/Source/Engine/Visject/VisjectMeta.h +++ b/Source/Engine/Visject/VisjectMeta.h @@ -8,7 +8,7 @@ /// /// Visject metadata container /// -class VisjectMeta +class FLAXENGINE_API VisjectMeta { public: /// @@ -27,19 +27,6 @@ public: /// Array> Entries; -public: - /// - /// Initializes a new instance of the class. - /// - VisjectMeta(); - - /// - /// Finalizes an instance of the class. - /// - ~VisjectMeta() - { - } - public: /// /// Load from the stream From 636b2c91ccfe2d7e0483a2ac7cf6ca35ab0cac67 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 18 Feb 2024 11:22:35 +0100 Subject: [PATCH 42/42] Refactor `Editor.CreateAsset` to use named tags for better extensibility with custom assets in plugins --- .../Create/ParticleEmitterCreateEntry.cs | 2 +- .../Proxy/AnimationGraphFunctionProxy.cs | 2 +- .../Content/Proxy/AnimationGraphProxy.cs | 2 +- Source/Editor/Content/Proxy/AnimationProxy.cs | 2 +- .../Editor/Content/Proxy/BehaviorTreeProxy.cs | 2 +- .../Content/Proxy/CollisionDataProxy.cs | 2 +- .../Content/Proxy/MaterialFunctionProxy.cs | 2 +- .../Content/Proxy/MaterialInstanceProxy.cs | 2 +- Source/Editor/Content/Proxy/MaterialProxy.cs | 2 +- .../Proxy/ParticleEmitterFunctionProxy.cs | 2 +- .../Content/Proxy/ParticleSystemProxy.cs | 2 +- .../Content/Proxy/SceneAnimationProxy.cs | 2 +- .../Editor/Content/Proxy/SkeletonMaskProxy.cs | 2 +- Source/Editor/Editor.cs | 55 ++++++++++-- .../Editor/Managed/ManagedEditor.Internal.cpp | 83 ++----------------- Source/Editor/Managed/ManagedEditor.h | 7 ++ .../AssetsImportingManager.cpp | 1 - Source/Engine/ContentImporters/Types.h | 4 +- 18 files changed, 79 insertions(+), 97 deletions(-) diff --git a/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs b/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs index 1be5330e7..76edde777 100644 --- a/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs +++ b/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs @@ -81,7 +81,7 @@ namespace FlaxEditor.Content.Create switch (_options.Template) { case Templates.Empty: - return Editor.CreateAsset(Editor.NewAssetType.ParticleEmitter, ResultUrl); + return Editor.CreateAsset("ParticleEmitter", ResultUrl); case Templates.ConstantBurst: templateName = "Constant Burst"; break; diff --git a/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs b/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs index f714ddbeb..7efc02368 100644 --- a/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs +++ b/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.AnimationGraphFunction, outputPath)) + if (Editor.CreateAsset("AnimationGraphFunction", outputPath)) throw new Exception("Failed to create new asset."); } } diff --git a/Source/Editor/Content/Proxy/AnimationGraphProxy.cs b/Source/Editor/Content/Proxy/AnimationGraphProxy.cs index 3e6c35c6d..20d3c5a2c 100644 --- a/Source/Editor/Content/Proxy/AnimationGraphProxy.cs +++ b/Source/Editor/Content/Proxy/AnimationGraphProxy.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.AnimationGraph, outputPath)) + if (Editor.CreateAsset("AnimationGraph", outputPath)) throw new Exception("Failed to create new asset."); } } diff --git a/Source/Editor/Content/Proxy/AnimationProxy.cs b/Source/Editor/Content/Proxy/AnimationProxy.cs index 2cf46d3a3..6636fccb8 100644 --- a/Source/Editor/Content/Proxy/AnimationProxy.cs +++ b/Source/Editor/Content/Proxy/AnimationProxy.cs @@ -47,7 +47,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.Animation, outputPath)) + if (Editor.CreateAsset("Animation", outputPath)) throw new Exception("Failed to create new asset."); } diff --git a/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs b/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs index 33ad0862f..234dfaf7d 100644 --- a/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs +++ b/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs @@ -47,7 +47,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.BehaviorTree, outputPath)) + if (Editor.CreateAsset("BehaviorTree", outputPath)) throw new Exception("Failed to create new asset."); } diff --git a/Source/Editor/Content/Proxy/CollisionDataProxy.cs b/Source/Editor/Content/Proxy/CollisionDataProxy.cs index 55e8c6327..df26dca75 100644 --- a/Source/Editor/Content/Proxy/CollisionDataProxy.cs +++ b/Source/Editor/Content/Proxy/CollisionDataProxy.cs @@ -71,7 +71,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.CollisionData, outputPath)) + if (Editor.CreateAsset("CollisionData", outputPath)) throw new Exception("Failed to create new asset."); } diff --git a/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs b/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs index ab59f11f3..ca012f70a 100644 --- a/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs +++ b/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.MaterialFunction, outputPath)) + if (Editor.CreateAsset("MaterialFunction", outputPath)) throw new Exception("Failed to create new asset."); } } diff --git a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs index 331ff81c3..cd245b149 100644 --- a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs +++ b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs @@ -43,7 +43,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.MaterialInstance, outputPath)) + if (Editor.CreateAsset("MaterialInstance", outputPath)) throw new Exception("Failed to create new asset."); } diff --git a/Source/Editor/Content/Proxy/MaterialProxy.cs b/Source/Editor/Content/Proxy/MaterialProxy.cs index a7fcfecc8..f7db2c83d 100644 --- a/Source/Editor/Content/Proxy/MaterialProxy.cs +++ b/Source/Editor/Content/Proxy/MaterialProxy.cs @@ -44,7 +44,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.Material, outputPath)) + if (Editor.CreateAsset("Material", outputPath)) throw new Exception("Failed to create new asset."); } diff --git a/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs b/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs index 3a2ed749f..aaf9445e2 100644 --- a/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs +++ b/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.ParticleEmitterFunction, outputPath)) + if (Editor.CreateAsset("ParticleEmitterFunction", outputPath)) throw new Exception("Failed to create new asset."); } } diff --git a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs index 047853f0b..74f391513 100644 --- a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs +++ b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs @@ -75,7 +75,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.ParticleSystem, outputPath)) + if (Editor.CreateAsset("ParticleSystem", outputPath)) throw new Exception("Failed to create new asset."); } diff --git a/Source/Editor/Content/Proxy/SceneAnimationProxy.cs b/Source/Editor/Content/Proxy/SceneAnimationProxy.cs index fe31f0e34..5f7fa800a 100644 --- a/Source/Editor/Content/Proxy/SceneAnimationProxy.cs +++ b/Source/Editor/Content/Proxy/SceneAnimationProxy.cs @@ -69,7 +69,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.SceneAnimation, outputPath)) + if (Editor.CreateAsset("SceneAnimation", outputPath)) throw new Exception("Failed to create new asset."); } } diff --git a/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs b/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs index f300a4c61..560df0a7c 100644 --- a/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs +++ b/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.SkeletonMask, outputPath)) + if (Editor.CreateAsset("SkeletonMask", outputPath)) throw new Exception("Failed to create new asset."); } } diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 393bf564e..266d8235f 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -869,7 +869,9 @@ namespace FlaxEditor /// /// New asset types allowed to create. + /// [Deprecated in v1.8] /// + [Obsolete("Use CreateAsset with named tag.")] public enum NewAssetType { /// @@ -1046,12 +1048,59 @@ namespace FlaxEditor /// /// Creates new asset at the target location. + /// [Deprecated in v1.8] /// /// New asset type. /// Output asset path. + [Obsolete("Use CreateAsset with named tag.")] public static bool CreateAsset(NewAssetType type, string outputPath) { - return Internal_CreateAsset(type, outputPath); + // [Deprecated on 18.02.2024, expires on 18.02.2025] + string tag; + switch (type) + { + case NewAssetType.Material: + tag = "Material"; + break; + case NewAssetType.MaterialInstance: + tag = "MaterialInstance"; + break; + case NewAssetType.CollisionData: + tag = "CollisionData"; + break; + case NewAssetType.AnimationGraph: + tag = "AnimationGraph"; + break; + case NewAssetType.SkeletonMask: + tag = "SkeletonMask"; + break; + case NewAssetType.ParticleEmitter: + tag = "ParticleEmitter"; + break; + case NewAssetType.ParticleSystem: + tag = "ParticleSystem"; + break; + case NewAssetType.SceneAnimation: + tag = "SceneAnimation"; + break; + case NewAssetType.MaterialFunction: + tag = "MaterialFunction"; + break; + case NewAssetType.ParticleEmitterFunction: + tag = "ParticleEmitterFunction"; + break; + case NewAssetType.AnimationGraphFunction: + tag = "AnimationGraphFunction"; + break; + case NewAssetType.Animation: + tag = "Animation"; + break; + case NewAssetType.BehaviorTree: + tag = "BehaviorTree"; + break; + default: return true; + } + return CreateAsset(tag, outputPath); } /// @@ -1588,10 +1637,6 @@ namespace FlaxEditor [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CloseSplashScreen", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_CloseSplashScreen(); - [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CreateAsset", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] - [return: MarshalAs(UnmanagedType.U1)] - internal static partial bool Internal_CreateAsset(NewAssetType type, string outputPath); - [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CreateVisualScript", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] [return: MarshalAs(UnmanagedType.U1)] internal static partial bool Internal_CreateVisualScript(string outputPath, string baseTypename); diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index ae8b71ee2..53c0fbab2 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -170,78 +170,6 @@ DEFINE_INTERNAL_CALL(bool) EditorInternal_CloneAssetFile(MString* dstPathObj, MS return Content::CloneAssetFile(dstPath, srcPath, *dstId); } -enum class NewAssetType -{ - Material = 0, - MaterialInstance = 1, - CollisionData = 2, - AnimationGraph = 3, - SkeletonMask = 4, - ParticleEmitter = 5, - ParticleSystem = 6, - SceneAnimation = 7, - MaterialFunction = 8, - ParticleEmitterFunction = 9, - AnimationGraphFunction = 10, - Animation = 11, - BehaviorTree = 12, -}; - -DEFINE_INTERNAL_CALL(bool) EditorInternal_CreateAsset(NewAssetType type, MString* outputPathObj) -{ - String tag; - switch (type) - { - case NewAssetType::Material: - tag = AssetsImportingManager::CreateMaterialTag; - break; - case NewAssetType::MaterialInstance: - tag = AssetsImportingManager::CreateMaterialInstanceTag; - break; - case NewAssetType::CollisionData: - tag = AssetsImportingManager::CreateCollisionDataTag; - break; - case NewAssetType::AnimationGraph: - tag = AssetsImportingManager::CreateAnimationGraphTag; - break; - case NewAssetType::SkeletonMask: - tag = AssetsImportingManager::CreateSkeletonMaskTag; - break; - case NewAssetType::ParticleEmitter: - tag = AssetsImportingManager::CreateParticleEmitterTag; - break; - case NewAssetType::ParticleSystem: - tag = AssetsImportingManager::CreateParticleSystemTag; - break; - case NewAssetType::SceneAnimation: - tag = AssetsImportingManager::CreateSceneAnimationTag; - break; - case NewAssetType::MaterialFunction: - tag = AssetsImportingManager::CreateMaterialFunctionTag; - break; - case NewAssetType::ParticleEmitterFunction: - tag = AssetsImportingManager::CreateParticleEmitterFunctionTag; - break; - case NewAssetType::AnimationGraphFunction: - tag = AssetsImportingManager::CreateAnimationGraphFunctionTag; - break; - case NewAssetType::Animation: - tag = AssetsImportingManager::CreateAnimationTag; - break; - case NewAssetType::BehaviorTree: - tag = AssetsImportingManager::CreateBehaviorTreeTag; - break; - default: - return true; - } - - String outputPath; - MUtils::ToString(outputPathObj, outputPath); - FileSystem::NormalizePath(outputPath); - - return AssetsImportingManager::Create(tag, outputPath); -} - DEFINE_INTERNAL_CALL(bool) EditorInternal_CreateVisualScript(MString* outputPathObj, MString* baseTypenameObj) { String outputPath; @@ -634,13 +562,11 @@ bool ManagedEditor::Import(const String& inputPath, const String& outputPath, co bool ManagedEditor::TryRestoreImportOptions(ModelTool::Options& options, String assetPath) { - // Initialize defaults + // Initialize defaults if (const auto* graphicsSettings = GraphicsSettings::Get()) { options.GenerateSDF = graphicsSettings->GenerateSDFOnModelImport; } - - // Get options from model FileSystem::NormalizePath(assetPath); return ImportModel::TryGetImportOptions(assetPath, options); } @@ -652,7 +578,12 @@ bool ManagedEditor::Import(const String& inputPath, const String& outputPath, co bool ManagedEditor::TryRestoreImportOptions(AudioTool::Options& options, String assetPath) { - // Get options from model FileSystem::NormalizePath(assetPath); return ImportAudio::TryGetImportOptions(assetPath, options); } + +bool ManagedEditor::CreateAsset(const String& tag, String outputPath) +{ + FileSystem::NormalizePath(outputPath); + return AssetsImportingManager::Create(tag, outputPath); +} diff --git a/Source/Editor/Managed/ManagedEditor.h b/Source/Editor/Managed/ManagedEditor.h index 8c9571cfd..6aa7514b0 100644 --- a/Source/Editor/Managed/ManagedEditor.h +++ b/Source/Editor/Managed/ManagedEditor.h @@ -210,6 +210,13 @@ public: API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) AudioTool::Options& options, String assetPath); #endif + /// + /// Creates a new asset at the target location. + /// + /// New asset type. + /// Output asset path. + API_FUNCTION() static bool CreateAsset(const String& tag, String outputPath); + public: API_STRUCT(Internal, NoDefault) struct VisualScriptStackFrame { diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.cpp b/Source/Engine/ContentImporters/AssetsImportingManager.cpp index a0b28f4f8..5fb5ff0c0 100644 --- a/Source/Engine/ContentImporters/AssetsImportingManager.cpp +++ b/Source/Engine/ContentImporters/AssetsImportingManager.cpp @@ -234,7 +234,6 @@ bool AssetsImportingManager::Create(const String& tag, const StringView& outputP LOG(Warning, "Cannot find asset creator object for tag \'{0}\'.", tag); return true; } - return Create(creator->Callback, outputPath, assetId, arg); } diff --git a/Source/Engine/ContentImporters/Types.h b/Source/Engine/ContentImporters/Types.h index 388c533a8..0275edc35 100644 --- a/Source/Engine/ContentImporters/Types.h +++ b/Source/Engine/ContentImporters/Types.h @@ -113,7 +113,7 @@ private: /// /// Asset importer entry /// -struct AssetImporter +struct FLAXENGINE_API AssetImporter { public: /// @@ -135,7 +135,7 @@ public: /// /// Asset creator entry /// -struct AssetCreator +struct FLAXENGINE_API AssetCreator { public: ///