You're breathtaking!
This commit is contained in:
217
Source/Engine/CSG/Brush.h
Normal file
217
Source/Engine/CSG/Brush.h
Normal file
@@ -0,0 +1,217 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Types.h"
|
||||
#include "Engine/Core/Types/Guid.h"
|
||||
#include "Engine/Core/Math/Plane.h"
|
||||
#include "Engine/Core/Math/AABB.h"
|
||||
#include "Engine/Core/Math/Vector2.h"
|
||||
|
||||
class Scene;
|
||||
|
||||
namespace CSG
|
||||
{
|
||||
/// <summary>
|
||||
/// CSG plane surface
|
||||
/// </summary>
|
||||
struct Surface : Plane
|
||||
{
|
||||
Guid Material;
|
||||
Vector2 TexCoordScale;
|
||||
Vector2 TexCoordOffset;
|
||||
float TexCoordRotation;
|
||||
float ScaleInLightmap;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
Surface()
|
||||
: Plane()
|
||||
, TexCoordScale(1)
|
||||
, TexCoordOffset(0)
|
||||
, TexCoordRotation(0)
|
||||
, ScaleInLightmap(1)
|
||||
{
|
||||
}
|
||||
|
||||
Surface(const Plane& plane)
|
||||
: Plane(plane)
|
||||
, Material(Guid::Empty)
|
||||
, TexCoordScale(1)
|
||||
, TexCoordOffset(0)
|
||||
, TexCoordRotation(0)
|
||||
, ScaleInLightmap(1)
|
||||
{
|
||||
}
|
||||
|
||||
Surface(const Surface& plane)
|
||||
: Plane(plane)
|
||||
, Material(plane.Material)
|
||||
, TexCoordScale(1)
|
||||
, TexCoordOffset(0)
|
||||
, TexCoordRotation(0)
|
||||
, ScaleInLightmap(1)
|
||||
{
|
||||
}
|
||||
|
||||
Surface(const Vector3& normal, float d)
|
||||
: Plane(normal, d)
|
||||
, Material(Guid::Empty)
|
||||
, TexCoordScale(1)
|
||||
, TexCoordOffset(0)
|
||||
, TexCoordRotation(0)
|
||||
, ScaleInLightmap(1)
|
||||
{
|
||||
}
|
||||
|
||||
Surface(const Vector3& point1, const Vector3& point2, const Vector3& point3)
|
||||
: Plane(point1, point2, point3)
|
||||
, Material(Guid::Empty)
|
||||
, TexCoordScale(1)
|
||||
, TexCoordOffset(0)
|
||||
, TexCoordRotation(0)
|
||||
, ScaleInLightmap(1)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
static Vector3 Intersection(const Vector3& start, const Vector3& end, float sdist, float edist)
|
||||
{
|
||||
Vector3 vector = end - start;
|
||||
float length = edist - sdist;
|
||||
float delta = edist / length;
|
||||
|
||||
return end - (delta * vector);
|
||||
}
|
||||
|
||||
Vector3 Intersection(const Vector3& start, const Vector3& end) const
|
||||
{
|
||||
return Intersection(start, end, Distance(start), Distance(end));
|
||||
}
|
||||
|
||||
float Distance(float x, float y, float z) const
|
||||
{
|
||||
return
|
||||
(
|
||||
(Normal.X * x) +
|
||||
(Normal.Y * y) +
|
||||
(Normal.Z * z) -
|
||||
(D)
|
||||
);
|
||||
}
|
||||
|
||||
float Distance(const Vector3& vertex) const
|
||||
{
|
||||
return
|
||||
(
|
||||
(Normal.X * vertex.X) +
|
||||
(Normal.Y * vertex.Y) +
|
||||
(Normal.Z * vertex.Z) -
|
||||
(D)
|
||||
);
|
||||
}
|
||||
|
||||
static PlaneIntersectionType OnSide(float distance)
|
||||
{
|
||||
if (distance > DistanceEpsilon)
|
||||
return PlaneIntersectionType::Front;
|
||||
if (distance < -DistanceEpsilon)
|
||||
return PlaneIntersectionType::Back;
|
||||
return PlaneIntersectionType::Intersecting;
|
||||
}
|
||||
|
||||
PlaneIntersectionType OnSide(float x, float y, float z) const
|
||||
{
|
||||
return OnSide(Distance(x, y, z));
|
||||
}
|
||||
|
||||
PlaneIntersectionType OnSide(const Vector3& vertex) const
|
||||
{
|
||||
return OnSide(Distance(vertex));
|
||||
}
|
||||
|
||||
PlaneIntersectionType OnSide(const AABB& box) const
|
||||
{
|
||||
Vector3 min;
|
||||
Vector3 max;
|
||||
|
||||
max.X = static_cast<float>((Normal.X >= 0.0f) ? box.MinX : box.MaxX);
|
||||
max.Y = static_cast<float>((Normal.Y >= 0.0f) ? box.MinY : box.MaxY);
|
||||
max.Z = static_cast<float>((Normal.Z >= 0.0f) ? box.MinZ : box.MaxZ);
|
||||
|
||||
min.X = static_cast<float>((Normal.X >= 0.0f) ? box.MaxX : box.MinX);
|
||||
min.Y = static_cast<float>((Normal.Y >= 0.0f) ? box.MaxY : box.MinY);
|
||||
min.Z = static_cast<float>((Normal.Z >= 0.0f) ? box.MaxZ : box.MinZ);
|
||||
|
||||
float distance = Vector3::Dot(Normal, max) - D;
|
||||
|
||||
if (distance > DistanceEpsilon)
|
||||
return PlaneIntersectionType::Front;
|
||||
|
||||
distance = Vector3::Dot(Normal, min) - D;
|
||||
|
||||
if (distance < -DistanceEpsilon)
|
||||
return PlaneIntersectionType::Back;
|
||||
|
||||
return PlaneIntersectionType::Intersecting;
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// CSG brush object
|
||||
/// </summary>
|
||||
class FLAXENGINE_API Brush
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the scene.
|
||||
/// </summary>
|
||||
/// <returns>Scene</returns>
|
||||
virtual Scene* GetBrushScene() const = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if brush affects world
|
||||
/// </summary>
|
||||
/// <returns>True if use CSG brush during level geometry building</returns>
|
||||
virtual bool CanUseCSG() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CSG brush object ID.
|
||||
/// </summary>
|
||||
/// <returns>The unique ID.</returns>
|
||||
virtual Guid GetBrushID() const = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets CSG brush mode
|
||||
/// </summary>
|
||||
/// <returns>Mode</returns>
|
||||
virtual Mode GetBrushMode() const = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets brush surfaces
|
||||
/// </summary>
|
||||
/// <param name="surfaces">Result list of surfaces</param>
|
||||
virtual void GetSurfaces(Array<Surface, HeapAllocation>& surfaces) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets brush surfaces amount
|
||||
/// </summary>
|
||||
/// <returns>The mount of the brush surfaces.</returns>
|
||||
virtual int32 GetSurfacesCount() = 0;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Called when brush data gets modified (will request rebuilding in Editor).
|
||||
/// </summary>
|
||||
virtual void OnBrushModified();
|
||||
};
|
||||
};
|
||||
29
Source/Engine/CSG/CSG.Build.cs
Normal file
29
Source/Engine/CSG/CSG.Build.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Flax.Build;
|
||||
using Flax.Build.NativeCpp;
|
||||
|
||||
/// <summary>
|
||||
/// CSG tool module.
|
||||
/// </summary>
|
||||
public class CSG : EngineModule
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Setup(BuildOptions options)
|
||||
{
|
||||
base.Setup(options);
|
||||
|
||||
options.PrivateDependencies.Add("ContentImporters");
|
||||
|
||||
options.PublicDefinitions.Add("COMPILE_WITH_CSG_BUILDER");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void GetFilesToDeploy(List<string> files)
|
||||
{
|
||||
files.Add(Path.Combine(FolderPath, "Brush.h"));
|
||||
files.Add(Path.Combine(FolderPath, "Types.h"));
|
||||
}
|
||||
}
|
||||
423
Source/Engine/CSG/CSGBuilder.cpp
Normal file
423
Source/Engine/CSG/CSGBuilder.cpp
Normal file
@@ -0,0 +1,423 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "CSGBuilder.h"
|
||||
#include "CSGMesh.h"
|
||||
#include "CSGData.h"
|
||||
#include "Engine/Level/Level.h"
|
||||
#include "Engine/Level/SceneQuery.h"
|
||||
#include "Engine/Level/Actor.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Graphics/Models/ModelData.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Content/Assets/Model.h"
|
||||
#include "Engine/Physics/CollisionData.h"
|
||||
#include "Engine/ContentImporters/AssetsImportingManager.h"
|
||||
#include "Engine/ContentImporters/CreateCollisionData.h"
|
||||
#include "Engine/Content/Assets/RawDataAsset.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#if USE_EDITOR
|
||||
#include "Editor/Editor.h"
|
||||
#endif
|
||||
|
||||
#if COMPILE_WITH_CSG_BUILDER
|
||||
|
||||
using namespace CSG;
|
||||
|
||||
// Enable/disable locking scene during building CSG brushes nodes
|
||||
#define CSG_USE_SCENE_LOCKS 0
|
||||
|
||||
namespace CSGBuilderImpl
|
||||
{
|
||||
Array<Scene*> ScenesToRebuild;
|
||||
|
||||
void onSceneUnloading(Scene* scene, const Guid& sceneId);
|
||||
void build(Scene* scene);
|
||||
bool generateRawDataAsset(Scene* scene, RawData& meshData, Guid& assetId, const String& assetPath);
|
||||
}
|
||||
|
||||
using namespace CSGBuilderImpl;
|
||||
|
||||
class CSGBuilderService : public EngineService
|
||||
{
|
||||
public:
|
||||
|
||||
CSGBuilderService()
|
||||
: EngineService(TEXT("CSG Builder"), 90)
|
||||
{
|
||||
}
|
||||
|
||||
bool Init() override;
|
||||
void Update() override;
|
||||
};
|
||||
|
||||
CSGBuilderService CSGBuilderServiceInstance;
|
||||
|
||||
Delegate<Brush*> Builder::OnBrushModified;
|
||||
|
||||
void CSGBuilderImpl::onSceneUnloading(Scene* scene, const Guid& sceneId)
|
||||
{
|
||||
// Ensure to remove scene (prevent crashes)
|
||||
ScenesToRebuild.Remove(scene);
|
||||
}
|
||||
|
||||
bool CSGBuilderService::Init()
|
||||
{
|
||||
Level::SceneUnloading.Bind(onSceneUnloading);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CSGBuilderService::Update()
|
||||
{
|
||||
// Check if build is pending
|
||||
if (ScenesToRebuild.HasItems() && Engine::IsReady())
|
||||
{
|
||||
auto now = DateTime::NowUTC();
|
||||
|
||||
for (int32 i = 0; ScenesToRebuild.HasItems() && i < ScenesToRebuild.Count(); i++)
|
||||
{
|
||||
auto scene = ScenesToRebuild[i];
|
||||
if (now - scene->CSGData.BuildTime >= 0)
|
||||
{
|
||||
scene->CSGData.BuildTime.Ticks = 0;
|
||||
ScenesToRebuild.RemoveAt(i--);
|
||||
build(scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Builder::IsActive()
|
||||
{
|
||||
return ScenesToRebuild.HasItems();
|
||||
}
|
||||
|
||||
void Builder::Build(Scene* scene, float timeoutMs)
|
||||
{
|
||||
if (scene == nullptr)
|
||||
return;
|
||||
|
||||
#if USE_EDITOR
|
||||
// Disable building during play mode
|
||||
if (Editor::IsPlayMode)
|
||||
return;
|
||||
#endif
|
||||
|
||||
// Register building
|
||||
if (!ScenesToRebuild.Contains(scene))
|
||||
{
|
||||
ScenesToRebuild.Add(scene);
|
||||
}
|
||||
scene->CSGData.BuildTime = DateTime::NowUTC() + TimeSpan::FromMilliseconds(timeoutMs);
|
||||
}
|
||||
|
||||
namespace CSG
|
||||
{
|
||||
typedef Dictionary<Actor*, Mesh*> MeshesLookup;
|
||||
|
||||
bool walkTree(Actor* actor, MeshesArray& meshes, MeshesLookup& cache)
|
||||
{
|
||||
// Check if actor is a brush
|
||||
auto brush = dynamic_cast<Brush*>(actor);
|
||||
if (brush)
|
||||
{
|
||||
// Check if can build it
|
||||
if (brush->CanUseCSG())
|
||||
{
|
||||
// Skip subtract/common meshes from the beginning (they have no effect)
|
||||
if (meshes.Count() > 0 || brush->GetBrushMode() == Mode::Additive)
|
||||
{
|
||||
// Create new mesh and build for given brush
|
||||
auto mesh = New<CSG::Mesh>();
|
||||
mesh->Build(brush);
|
||||
|
||||
// Save results
|
||||
meshes.Add(mesh);
|
||||
cache.Add(actor, mesh);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Info
|
||||
LOG(Info, "Skipping CSG brush '{0}'", actor->ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Mesh* Combine(Actor* actor, MeshesLookup& cache, Mesh* combineParent)
|
||||
{
|
||||
ASSERT(actor);
|
||||
Mesh* result = nullptr;
|
||||
Mesh* myBrush = nullptr;
|
||||
cache.TryGet(actor, myBrush);
|
||||
|
||||
// Get first child mesh with valid data (has additive brush)
|
||||
int32 childIndex = 0;
|
||||
while (childIndex < actor->Children.Count())
|
||||
{
|
||||
auto child = Combine(actor->Children[childIndex], cache, combineParent);
|
||||
|
||||
childIndex++;
|
||||
if (child)
|
||||
{
|
||||
// If brush was based on additive brush or current actor is a brush we can stop searching
|
||||
if (child->HasMode(Mode::Additive) || myBrush)
|
||||
{
|
||||
// End searching
|
||||
result = child;
|
||||
break;
|
||||
}
|
||||
|
||||
if (combineParent)
|
||||
{
|
||||
// Combine
|
||||
combineParent->PerformOperation(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if has any child with CSG brush
|
||||
if (result)
|
||||
{
|
||||
// Check if has own brush
|
||||
if (myBrush)
|
||||
{
|
||||
// Combine with first child
|
||||
myBrush->PerformOperation(result);
|
||||
|
||||
// Set this actor brush as a result
|
||||
result = myBrush;
|
||||
}
|
||||
|
||||
// Merge with the other children
|
||||
while (childIndex < actor->Children.Count())
|
||||
{
|
||||
auto child = Combine(actor->Children[childIndex], cache, result);
|
||||
if (child)
|
||||
{
|
||||
// Combine
|
||||
result->PerformOperation(child);
|
||||
}
|
||||
|
||||
childIndex++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use this actor brush (may be empty)
|
||||
result = myBrush;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Mesh* Combine(Scene* scene, MeshesLookup& cache)
|
||||
{
|
||||
#if CSG_USE_SCENE_LOCKS
|
||||
auto Level = Level::Instance();
|
||||
Level->Lock();
|
||||
#endif
|
||||
|
||||
Mesh* result = Combine(scene, cache, nullptr);
|
||||
|
||||
#if CSG_USE_SCENE_LOCKS
|
||||
Level->Unlock();
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
void CSGBuilderImpl::build(Scene* scene)
|
||||
{
|
||||
// Start
|
||||
auto startTime = DateTime::Now();
|
||||
LOG(Info, "Start building CSG...");
|
||||
|
||||
// Temporary data
|
||||
int32 lastBuildMeshesCount = 0; // TODO: cache this
|
||||
MeshesArray meshes(Math::Min(lastBuildMeshesCount, 32));
|
||||
MeshesLookup cache(Math::Min(lastBuildMeshesCount * 4, 32));
|
||||
Guid outputModelAssetId = Guid::Empty;
|
||||
Guid outputRawDataAssetId = Guid::Empty;
|
||||
Guid outputCollisionDataAssetId = Guid::Empty;
|
||||
|
||||
// Setup CSG meshes list and build them
|
||||
{
|
||||
Function<bool(Actor*, MeshesArray&, MeshesLookup&)> treeWalkFunction(walkTree);
|
||||
SceneQuery::TreeExecute<Array<CSG::Mesh*>&, MeshesLookup&>(treeWalkFunction, meshes, cache);
|
||||
}
|
||||
lastBuildMeshesCount = meshes.Count();
|
||||
if (meshes.IsEmpty())
|
||||
goto BUILDING_END;
|
||||
|
||||
// Process all meshes (performs actual CSG opterations on geometry in tree structure)
|
||||
CSG::Mesh* combinedMesh = Combine(scene, cache);
|
||||
if (combinedMesh == nullptr)
|
||||
goto BUILDING_END;
|
||||
|
||||
// TODO: split too big meshes (too many verts, to far parts, etc.)
|
||||
|
||||
// Triangulate meshes
|
||||
{
|
||||
// TODO: setup valid loop for splited meshes
|
||||
|
||||
// Convert CSG meshes into raw triangles data
|
||||
RawData meshData;
|
||||
Array<RawModelVertex> vertexBuffer;
|
||||
combinedMesh->Triangulate(meshData, vertexBuffer);
|
||||
meshData.RemoveEmptySlots();
|
||||
if (meshData.Slots.HasItems())
|
||||
{
|
||||
const auto sceneDataFolderPath = scene->GetDataFolderPath();
|
||||
|
||||
// Convert CSG mesh data to common storage type
|
||||
ModelData modelData;
|
||||
meshData.ToModelData(modelData);
|
||||
|
||||
// Convert CSG mesh to the local transformation of the scene
|
||||
if (!scene->GetTransform().IsIdentity())
|
||||
{
|
||||
Matrix t;
|
||||
scene->GetWorldToLocalMatrix(t);
|
||||
modelData.TransformBuffer(t);
|
||||
}
|
||||
|
||||
// Import model data to the asset
|
||||
{
|
||||
Guid modelDataAssetId = scene->CSGData.Model.GetID();
|
||||
if (!modelDataAssetId.IsValid())
|
||||
modelDataAssetId = Guid::New();
|
||||
const String modelDataAssetPath = sceneDataFolderPath / TEXT("CSG_Mesh") + ASSET_FILES_EXTENSION_WITH_DOT;
|
||||
if (AssetsImportingManager::Create(AssetsImportingManager::CreateModelTag, modelDataAssetPath, modelDataAssetId, &modelData))
|
||||
{
|
||||
LOG(Warning, "Failed to import CSG mesh data");
|
||||
goto BUILDING_END;
|
||||
}
|
||||
outputModelAssetId = modelDataAssetId;
|
||||
}
|
||||
|
||||
// Generate asset with CSG mesh metadata (for collisions and brush queries)
|
||||
{
|
||||
Guid rawDataAssetId = scene->CSGData.Data.GetID();
|
||||
if (!rawDataAssetId.IsValid())
|
||||
rawDataAssetId = Guid::New();
|
||||
const String rawDataAssetPath = sceneDataFolderPath / TEXT("CSG_Data") + ASSET_FILES_EXTENSION_WITH_DOT;
|
||||
if (generateRawDataAsset(scene, meshData, rawDataAssetId, rawDataAssetPath))
|
||||
{
|
||||
LOG(Warning, "Failed to create raw CSG data");
|
||||
goto BUILDING_END;
|
||||
}
|
||||
outputRawDataAssetId = rawDataAssetId;
|
||||
}
|
||||
|
||||
// Generate CSG mesh collision asset
|
||||
{
|
||||
// Convert CSG mesh to scene local space (fix issues when scene has transformation applied)
|
||||
if (!scene->GetTransform().IsIdentity())
|
||||
{
|
||||
Matrix m1, m2;
|
||||
scene->GetTransform().GetWorld(m1);
|
||||
Matrix::Invert(m1, m2);
|
||||
|
||||
for (int32 lodIndex = 0; lodIndex < modelData.LODs.Count(); lodIndex++)
|
||||
{
|
||||
auto lod = &modelData.LODs[lodIndex];
|
||||
for (int32 meshIndex = 0; meshIndex < lod->Meshes.Count(); meshIndex++)
|
||||
{
|
||||
const auto v = &lod->Meshes[meshIndex]->Positions;
|
||||
Vector3::Transform(v->Get(), m2, v->Get(), v->Count());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if COMPILE_WITH_PHYSICS_COOKING
|
||||
CollisionCooking::Argument arg;
|
||||
arg.Type = CollisionDataType::TriangleMesh;
|
||||
arg.OverrideModelData = &modelData;
|
||||
Guid collisionDataAssetId = scene->CSGData.CollisionData.GetID();
|
||||
if (!collisionDataAssetId.IsValid())
|
||||
collisionDataAssetId = Guid::New();
|
||||
const String collisionDataAssetPath = sceneDataFolderPath / TEXT("CSG_Collision") + ASSET_FILES_EXTENSION_WITH_DOT;
|
||||
if (AssetsImportingManager::Create(AssetsImportingManager::CreateCollisionDataTag, collisionDataAssetPath, collisionDataAssetId, &arg))
|
||||
{
|
||||
LOG(Warning, "Failed to cook CSG mesh collision data");
|
||||
goto BUILDING_END;
|
||||
}
|
||||
outputCollisionDataAssetId = collisionDataAssetId;
|
||||
#else
|
||||
outputCollisionDataAssetId = Guid::Empty;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BUILDING_END:
|
||||
|
||||
// Link new (or empty) CSG mesh
|
||||
scene->CSGData.Data = Content::LoadAsync<RawDataAsset>(outputRawDataAssetId);
|
||||
scene->CSGData.Model = Content::LoadAsync<Model>(outputModelAssetId);
|
||||
scene->CSGData.CollisionData = Content::LoadAsync<CollisionData>(outputCollisionDataAssetId);
|
||||
// TODO: also set CSGData.InstanceBuffer - lightmap scales for the entries so csg mesh gets better quality in lightmaps
|
||||
scene->CSGData.PostCSGBuild();
|
||||
|
||||
// End
|
||||
meshes.ClearDelete();
|
||||
auto endTime = DateTime::Now();
|
||||
LOG(Info, "CSG build in {0} ms! {1} brush(es)", (endTime - startTime).GetTotalMilliseconds(), lastBuildMeshesCount);
|
||||
}
|
||||
|
||||
bool CSGBuilderImpl::generateRawDataAsset(Scene* scene, RawData& meshData, Guid& assetId, const String& assetPath)
|
||||
{
|
||||
// Prepare data
|
||||
MemoryWriteStream stream(4096);
|
||||
{
|
||||
// Header (with version number)
|
||||
stream.WriteInt32(1);
|
||||
const int32 brushesCount = meshData.Brushes.Count();
|
||||
stream.WriteInt32(brushesCount);
|
||||
|
||||
// Surfaces data locations
|
||||
int32 surfacesDataOffset = sizeof(int32) * 2 + (sizeof(Guid) + sizeof(int32)) * brushesCount;
|
||||
for (auto brush = meshData.Brushes.Begin(); brush.IsNotEnd(); ++brush)
|
||||
{
|
||||
auto& surfaces = brush->Value.Surfaces;
|
||||
|
||||
stream.Write(&brush->Key);
|
||||
stream.WriteInt32(surfacesDataOffset);
|
||||
|
||||
// Calculate offset in data storage to the next brush data
|
||||
int32 brushDataSize = 0;
|
||||
for (int32 i = 0; i < surfaces.Count(); i++)
|
||||
{
|
||||
brushDataSize += sizeof(int32) + sizeof(Triangle) * surfaces[i].Triangles.Count();
|
||||
}
|
||||
surfacesDataOffset += brushDataSize;
|
||||
}
|
||||
|
||||
// Surfaces data
|
||||
for (auto brush = meshData.Brushes.Begin(); brush.IsNotEnd(); ++brush)
|
||||
{
|
||||
auto& surfaces = brush->Value.Surfaces;
|
||||
|
||||
for (int32 i = 0; i < surfaces.Count(); i++)
|
||||
{
|
||||
auto& triangles = surfaces[i].Triangles;
|
||||
|
||||
stream.WriteInt32(triangles.Count());
|
||||
stream.Write(triangles.Get(), triangles.Count());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize
|
||||
BytesContainer bytesContainer;
|
||||
bytesContainer.Link(stream.GetHandle(), stream.GetPosition());
|
||||
return AssetsImportingManager::Create(AssetsImportingManager::CreateRawDataTag, assetPath, assetId, (void*)&bytesContainer);
|
||||
}
|
||||
|
||||
#endif
|
||||
41
Source/Engine/CSG/CSGBuilder.h
Normal file
41
Source/Engine/CSG/CSGBuilder.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Delegate.h"
|
||||
#include "Brush.h"
|
||||
|
||||
class Scene;
|
||||
|
||||
namespace CSG
|
||||
{
|
||||
class RawData;
|
||||
|
||||
#if COMPILE_WITH_CSG_BUILDER
|
||||
|
||||
/// <summary>
|
||||
/// CSG geometry builder
|
||||
/// </summary>
|
||||
class Builder
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Action fired when any CSG brush on scene gets edited (different dimensions or transformation etc.)
|
||||
/// </summary>
|
||||
static Delegate<Brush*> OnBrushModified;
|
||||
|
||||
public:
|
||||
|
||||
static bool IsActive();
|
||||
|
||||
/// <summary>
|
||||
/// Build CSG geometry for the given scene.
|
||||
/// </summary>
|
||||
/// <param name="scene">The scene.</param>
|
||||
/// <param name="timeoutMs">The timeout to wait before building CSG (in milliseconds).</param>
|
||||
static void Build(Scene* scene, float timeoutMs = 50);
|
||||
};
|
||||
|
||||
#endif
|
||||
};
|
||||
197
Source/Engine/CSG/CSGData.cpp
Normal file
197
Source/Engine/CSG/CSGData.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "CSGData.h"
|
||||
#include "Brush.h"
|
||||
#include "Engine/Graphics/Models/ModelData.h"
|
||||
#include "Engine/Utilities/RectPack.h"
|
||||
|
||||
using namespace CSG;
|
||||
|
||||
namespace CSG
|
||||
{
|
||||
class LightmapUVsPacker
|
||||
{
|
||||
typedef RawData::Surface* ChartType;
|
||||
|
||||
public:
|
||||
|
||||
struct Node : RectPack<Node, float>
|
||||
{
|
||||
Node(float xy, float wh)
|
||||
: RectPack<Node, float>(xy, xy, wh, wh)
|
||||
{
|
||||
}
|
||||
|
||||
Node(float x, float y, float width, float height)
|
||||
: RectPack<Node, float>(x, y, width, height)
|
||||
{
|
||||
}
|
||||
|
||||
void OnInsert(ChartType chart, float atlasSize)
|
||||
{
|
||||
const float invSize = 1.0f / atlasSize;
|
||||
chart->UVsArea = Rectangle(X * invSize, Y * invSize, chart->Size.X * invSize, chart->Size.Y * invSize);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
Node _root;
|
||||
const float _atlasSize;
|
||||
const float _chartsPadding;
|
||||
|
||||
public:
|
||||
|
||||
LightmapUVsPacker(float atlasSize, float chartsPadding)
|
||||
: _root(chartsPadding, atlasSize - chartsPadding)
|
||||
, _atlasSize(atlasSize)
|
||||
, _chartsPadding(chartsPadding)
|
||||
{
|
||||
}
|
||||
|
||||
~LightmapUVsPacker()
|
||||
{
|
||||
}
|
||||
|
||||
Node* Insert(ChartType chart)
|
||||
{
|
||||
return _root.Insert(chart->Size.X, chart->Size.Y, _chartsPadding, chart, _atlasSize);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void RawData::Slot::AddSurface(float scaleInLightmap, const Rectangle& lightmapUVsBox, const RawModelVertex* firstVertex, int32 vertexCount)
|
||||
{
|
||||
auto& surface = Surfaces.AddOne();
|
||||
surface.ScaleInLightmap = scaleInLightmap;
|
||||
surface.LightmapUVsBox = lightmapUVsBox;
|
||||
surface.Size = lightmapUVsBox.Size * scaleInLightmap;
|
||||
surface.UVsArea = Rectangle::Empty;
|
||||
surface.Vertices.Add(firstVertex, vertexCount);
|
||||
}
|
||||
|
||||
void RawData::AddSurface(Brush* brush, int32 brushSurfaceIndex, const Guid& surfaceMaterial, float scaleInLightmap, const Rectangle& lightmapUVsBox, const RawModelVertex* firstVertex, int32 vertexCount)
|
||||
{
|
||||
// Add surface to slot
|
||||
auto slot = GetOrAddSlot(surfaceMaterial);
|
||||
slot->AddSurface(scaleInLightmap, lightmapUVsBox, firstVertex, vertexCount);
|
||||
|
||||
// Add surface to brush
|
||||
auto& brushData = Brushes[brush->GetBrushID()];
|
||||
if (brushData.Surfaces.Count() != brush->GetSurfacesCount())
|
||||
brushData.Surfaces.Resize(brush->GetSurfacesCount());
|
||||
auto& surfaceData = brushData.Surfaces[brushSurfaceIndex];
|
||||
auto& triangles = surfaceData.Triangles;
|
||||
triangles.Resize(vertexCount / 3);
|
||||
|
||||
// Copy triangles
|
||||
for (int32 i = 0; i < vertexCount;)
|
||||
{
|
||||
auto& triangle = triangles[i / 3];
|
||||
triangle.V0 = firstVertex[i++].Position;
|
||||
triangle.V1 = firstVertex[i++].Position;
|
||||
triangle.V2 = firstVertex[i++].Position;
|
||||
}
|
||||
}
|
||||
|
||||
void RawData::ToModelData(ModelData& modelData) const
|
||||
{
|
||||
// Generate lightmap UVs (single chart for the whole mesh)
|
||||
{
|
||||
// Every surface has custom lightmap scale so we have to try to pack all triangles into
|
||||
// single rectangle using fast rectangle pack algorithm. Rectangle size can be calculated from
|
||||
// all triangles surfaces area with margins.
|
||||
|
||||
// Sum the area for the atlas surfaces
|
||||
float areaSum = 0;
|
||||
for (int32 slotIndex = 0; slotIndex < Slots.Count(); slotIndex++)
|
||||
{
|
||||
auto& slot = Slots[slotIndex];
|
||||
for (int32 i = 0; i < slot->Surfaces.Count(); i++)
|
||||
{
|
||||
auto& surface = slot->Surfaces[i];
|
||||
areaSum += surface.Size.MulValues();
|
||||
}
|
||||
}
|
||||
|
||||
// Insert only non-empty surfaces
|
||||
if (areaSum > ZeroTolerance)
|
||||
{
|
||||
// Pack all surfaces into atlas
|
||||
float atlasSize = Math::Sqrt(areaSum) * 1.02f;
|
||||
int32 triesLeft = 10;
|
||||
while (triesLeft--)
|
||||
{
|
||||
bool failed = false;
|
||||
const float chartsPadding = (8.0f / 1024.0f) * atlasSize;
|
||||
LightmapUVsPacker packer(atlasSize, chartsPadding);
|
||||
for (int32 slotIndex = 0; slotIndex < Slots.Count(); slotIndex++)
|
||||
{
|
||||
auto& slot = Slots[slotIndex];
|
||||
for (int32 i = 0; i < slot->Surfaces.Count(); i++)
|
||||
{
|
||||
auto& surface = slot->Surfaces[i];
|
||||
if (packer.Insert(&surface) == nullptr)
|
||||
{
|
||||
// Failed to insert surface, increase atlas size and try again
|
||||
atlasSize *= 1.5f;
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!failed)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer data (use 1-1 mesh-material slot linkage)
|
||||
modelData.MinScreenSize = 0.0f;
|
||||
modelData.LODs.Resize(1);
|
||||
modelData.LODs[0].Meshes.Resize(Slots.Count());
|
||||
modelData.Materials.Resize(Slots.Count());
|
||||
for (int32 slotIndex = 0; slotIndex < Slots.Count(); slotIndex++)
|
||||
{
|
||||
auto& slot = Slots[slotIndex];
|
||||
auto& mesh = modelData.LODs[0].Meshes[slotIndex];
|
||||
auto& materialSlot = modelData.Materials[slotIndex];
|
||||
ASSERT(slot->Surfaces.HasItems());
|
||||
|
||||
// Setup mesh and material slot
|
||||
mesh = New<MeshData>();
|
||||
mesh->Name = materialSlot.Name = String(TEXT("Mesh ")) + StringUtils::ToString(slotIndex);
|
||||
materialSlot.AssetID = slot->Material;
|
||||
mesh->MaterialSlotIndex = slotIndex;
|
||||
|
||||
// Generate vertex and index buffers from the surfaces (don't use vertex colors)
|
||||
int32 vertexCount = 0;
|
||||
for (int32 i = 0; i < slot->Surfaces.Count(); i++)
|
||||
{
|
||||
auto& surface = slot->Surfaces[i];
|
||||
vertexCount += surface.Vertices.Count();
|
||||
}
|
||||
mesh->EnsureCapacity(vertexCount, vertexCount, false, false);
|
||||
|
||||
// Write surfaces into vertex and index buffers
|
||||
int32 index = 0;
|
||||
for (int32 i = 0; i < slot->Surfaces.Count(); i++)
|
||||
{
|
||||
auto& surface = slot->Surfaces[i];
|
||||
|
||||
for (int32 vIndex = 0; vIndex < surface.Vertices.Count(); vIndex++)
|
||||
{
|
||||
auto& v = surface.Vertices[vIndex];
|
||||
|
||||
mesh->Positions.Add(v.Position);
|
||||
mesh->UVs.Add(v.TexCoord);
|
||||
mesh->Normals.Add(v.Normal);
|
||||
mesh->Tangents.Add(v.Tangent);
|
||||
mesh->LightmapUVs.Add(v.LightmapUVs * surface.UVsArea.Size + surface.UVsArea.Location);
|
||||
|
||||
mesh->Indices.Add(index++);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
148
Source/Engine/CSG/CSGData.h
Normal file
148
Source/Engine/CSG/CSGData.h
Normal file
@@ -0,0 +1,148 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Types.h"
|
||||
#include "Engine/Graphics/Models/Types.h"
|
||||
#include "Engine/Core/Types/Guid.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "Engine/Core/Math/Rectangle.h"
|
||||
#include "Engine/Core/Math/Triangle.h"
|
||||
|
||||
class ModelData;
|
||||
|
||||
namespace CSG
|
||||
{
|
||||
class Brush;
|
||||
|
||||
/// <summary>
|
||||
/// Represents raw CSG mesh data after triangulation. Can be used to export it to model vertex/index buffers. Separates triangles by materials.
|
||||
/// </summary>
|
||||
class RawData
|
||||
{
|
||||
public:
|
||||
|
||||
struct Surface
|
||||
{
|
||||
float ScaleInLightmap;
|
||||
Rectangle LightmapUVsBox;
|
||||
Vector2 Size;
|
||||
Rectangle UVsArea;
|
||||
Array<RawModelVertex> Vertices;
|
||||
};
|
||||
|
||||
struct SurfaceData
|
||||
{
|
||||
Array<Triangle> Triangles;
|
||||
};
|
||||
|
||||
struct BrushData
|
||||
{
|
||||
Array<SurfaceData> Surfaces;
|
||||
};
|
||||
|
||||
class Slot
|
||||
{
|
||||
public:
|
||||
|
||||
Guid Material;
|
||||
Array<Surface> Surfaces;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Slot"/> class.
|
||||
/// </summary>
|
||||
/// <param name="material">The material.</param>
|
||||
Slot(const Guid& material)
|
||||
: Material(material)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
bool IsEmpty() const
|
||||
{
|
||||
return Surfaces.IsEmpty();
|
||||
}
|
||||
|
||||
void AddSurface(float scaleInLightmap, const Rectangle& lightmapUVsBox, const RawModelVertex* firstVertex, int32 vertexCount);
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// The slots.
|
||||
/// </summary>
|
||||
Array<Slot*> Slots;
|
||||
|
||||
/// <summary>
|
||||
/// The brushes.
|
||||
/// </summary>
|
||||
Dictionary<Guid, BrushData> Brushes;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RawData"/> class.
|
||||
/// </summary>
|
||||
RawData()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="RawData"/> class.
|
||||
/// </summary>
|
||||
~RawData()
|
||||
{
|
||||
Slots.ClearDelete();
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets or adds the slot for the given material.
|
||||
/// </summary>
|
||||
/// <param name="material">The material.</param>
|
||||
/// <returns>The geometry slot.</returns>
|
||||
Slot* GetOrAddSlot(const Guid& material)
|
||||
{
|
||||
for (int32 i = 0; i < Slots.Count(); i++)
|
||||
{
|
||||
if (Slots[i]->Material == material)
|
||||
return Slots[i];
|
||||
}
|
||||
|
||||
auto slot = New<Slot>(material);
|
||||
Slots.Add(slot);
|
||||
return slot;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
void AddSurface(Brush* brush, int32 brushSurfaceIndex, const Guid& surfaceMaterial, float scaleInLightmap, const Rectangle& lightmapUVsBox, const RawModelVertex* firstVertex, int32 vertexCount);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the empty slots.
|
||||
/// </summary>
|
||||
void RemoveEmptySlots()
|
||||
{
|
||||
for (int32 i = 0; i < Slots.Count() && Slots.HasItems(); i++)
|
||||
{
|
||||
if (Slots[i]->IsEmpty())
|
||||
{
|
||||
Delete(Slots[i]);
|
||||
Slots.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs mesh data to the ModelData storage container.
|
||||
/// </summary>
|
||||
/// <param name="modelData">The result model data.</param>
|
||||
void ToModelData(ModelData& modelData) const;
|
||||
};
|
||||
}
|
||||
318
Source/Engine/CSG/CSGMesh.Build.cpp
Normal file
318
Source/Engine/CSG/CSGMesh.Build.cpp
Normal file
@@ -0,0 +1,318 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "CSGMesh.h"
|
||||
|
||||
#if COMPILE_WITH_CSG_BUILDER
|
||||
|
||||
#include "Engine/Core/Log.h"
|
||||
|
||||
void CSG::Mesh::Build(Brush* parentBrush)
|
||||
{
|
||||
struct EdgeIntersection
|
||||
{
|
||||
int32 EdgeIndex;
|
||||
int32 PlaneIndices[2];
|
||||
|
||||
EdgeIntersection()
|
||||
{
|
||||
EdgeIndex = INVALID_INDEX;
|
||||
}
|
||||
|
||||
EdgeIntersection(int32 edgeIndex, int32 planeIndexA, int32 planeIndexB)
|
||||
{
|
||||
EdgeIndex = edgeIndex;
|
||||
PlaneIndices[0] = planeIndexA;
|
||||
PlaneIndices[1] = planeIndexB;
|
||||
}
|
||||
};
|
||||
|
||||
struct PointIntersection
|
||||
{
|
||||
// TODO: optimize this shit?
|
||||
int32 VertexIndex;
|
||||
Array<EdgeIntersection> Edges;
|
||||
Array<int32> PlaneIndices;
|
||||
|
||||
PointIntersection()
|
||||
{
|
||||
VertexIndex = INVALID_INDEX;
|
||||
}
|
||||
|
||||
PointIntersection(int32 vertexIndex, const Array<int32>& planes)
|
||||
{
|
||||
VertexIndex = vertexIndex;
|
||||
PlaneIndices = planes;
|
||||
}
|
||||
};
|
||||
|
||||
// Clear
|
||||
_bounds.Clear();
|
||||
_surfaces.Clear();
|
||||
_polygons.Clear();
|
||||
_edges.Clear();
|
||||
_vertices.Clear();
|
||||
_brushesMeta.Clear();
|
||||
|
||||
// Get brush planes
|
||||
if (parentBrush == nullptr)
|
||||
return;
|
||||
auto mode = parentBrush->GetBrushMode();
|
||||
parentBrush->GetSurfaces(_surfaces);
|
||||
int32 surfacesCount = _surfaces.Count();
|
||||
if (surfacesCount > 250)
|
||||
{
|
||||
LOG(Error, "CSG brush has too many planes: {0}. Cannot process it!", surfacesCount);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fix duplicated planes (coplanar ones)
|
||||
for (int32 i = 0; i < surfacesCount; i++)
|
||||
{
|
||||
for (int32 j = i + 1; j < surfacesCount; j++)
|
||||
{
|
||||
if (Surface::NearEqual(_surfaces[i], _surfaces[j]))
|
||||
{
|
||||
// Change to blank plane
|
||||
_surfaces[i].Normal = Vector3::Up;
|
||||
_surfaces[i].D = MAX_float;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Array<PointIntersection> pointIntersections(surfacesCount * surfacesCount);
|
||||
Array<int32> intersectingPlanes;
|
||||
|
||||
// Find all point intersections where 3 (or more planes) intersect
|
||||
for (int32 planeIndex1 = 0; planeIndex1 < surfacesCount - 2; planeIndex1++)
|
||||
{
|
||||
Surface plane1 = _surfaces[planeIndex1];
|
||||
for (int32 planeIndex2 = planeIndex1 + 1; planeIndex2 < surfacesCount - 1; planeIndex2++)
|
||||
{
|
||||
Surface plane2 = _surfaces[planeIndex2];
|
||||
for (int32 planeIndex3 = planeIndex2 + 1; planeIndex3 < surfacesCount; planeIndex3++)
|
||||
{
|
||||
Surface plane3 = _surfaces[planeIndex3];
|
||||
|
||||
// Calculate the intersection point
|
||||
Vector3 vertex = Plane::Intersection(plane1, plane2, plane3);
|
||||
|
||||
// Check if the intersection is valid
|
||||
if (vertex.IsNaN() || vertex.IsInfinity())
|
||||
continue;
|
||||
|
||||
intersectingPlanes.Clear();
|
||||
intersectingPlanes.Add(planeIndex1);
|
||||
intersectingPlanes.Add(planeIndex2);
|
||||
intersectingPlanes.Add(planeIndex3);
|
||||
|
||||
for (int32 planeIndex4 = 0; planeIndex4 < surfacesCount; planeIndex4++)
|
||||
{
|
||||
if (planeIndex4 == planeIndex1 || planeIndex4 == planeIndex2 || planeIndex4 == planeIndex3)
|
||||
continue;
|
||||
|
||||
Surface& plane4 = _surfaces[planeIndex4];
|
||||
PlaneIntersectionType side = plane4.OnSide(vertex);
|
||||
if (side == PlaneIntersectionType::Intersecting)
|
||||
{
|
||||
if (planeIndex4 < planeIndex3)
|
||||
// Already found this vertex
|
||||
goto SkipIntersection;
|
||||
|
||||
// We've found another plane which goes trough our found intersection point
|
||||
intersectingPlanes.Add(planeIndex4);
|
||||
}
|
||||
else if (side == PlaneIntersectionType::Front)
|
||||
// Intersection is outside of brush
|
||||
goto SkipIntersection;
|
||||
}
|
||||
|
||||
int32 vertexIndex = _vertices.Count();
|
||||
_vertices.Add(vertex);
|
||||
|
||||
// Add intersection point to our list
|
||||
pointIntersections.Add(PointIntersection(vertexIndex, intersectingPlanes));
|
||||
|
||||
SkipIntersection:
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 foundPlanes[2];
|
||||
|
||||
// Find all our intersection edges which are formed by a pair of planes
|
||||
// (this could probably be done inside the previous loop)
|
||||
for (int32 i = 0; i < pointIntersections.Count(); i++)
|
||||
{
|
||||
PointIntersection& pointIntersectionA = pointIntersections[i];
|
||||
for (int32 j = i + 1; j < pointIntersections.Count(); j++)
|
||||
{
|
||||
auto& pointIntersectionB = pointIntersections[j];
|
||||
auto& planesIndicesA = pointIntersectionA.PlaneIndices;
|
||||
auto& planesIndicesB = pointIntersectionB.PlaneIndices;
|
||||
|
||||
int32 foundPlaneIndex = 0;
|
||||
for (int32 k = 0; k < planesIndicesA.Count(); k++)
|
||||
{
|
||||
int32 currentPlaneIndex = planesIndicesA[k];
|
||||
|
||||
if (!planesIndicesB.Contains(currentPlaneIndex))
|
||||
continue;
|
||||
|
||||
foundPlanes[foundPlaneIndex] = currentPlaneIndex;
|
||||
foundPlaneIndex++;
|
||||
|
||||
if (foundPlaneIndex == 2)
|
||||
break;
|
||||
}
|
||||
|
||||
// If foundPlaneIndex is 0 or 1 then either this combination does not exist, or only goes trough one point
|
||||
if (foundPlaneIndex < 2)
|
||||
continue;
|
||||
|
||||
// Create our found intersection edge
|
||||
HalfEdge halfEdgeA;
|
||||
int32 halfEdgeAIndex = _edges.Count();
|
||||
|
||||
HalfEdge halfEdgeB;
|
||||
int32 halfEdgeBIndex = halfEdgeAIndex + 1;
|
||||
|
||||
halfEdgeA.TwinIndex = halfEdgeBIndex;
|
||||
halfEdgeB.TwinIndex = halfEdgeAIndex;
|
||||
|
||||
halfEdgeA.VertexIndex = pointIntersectionA.VertexIndex;
|
||||
halfEdgeB.VertexIndex = pointIntersectionB.VertexIndex;
|
||||
|
||||
// Add it to our points
|
||||
pointIntersectionA.Edges.Add(EdgeIntersection(
|
||||
halfEdgeAIndex,
|
||||
foundPlanes[0],
|
||||
foundPlanes[1]));
|
||||
pointIntersectionB.Edges.Add(EdgeIntersection(
|
||||
halfEdgeBIndex,
|
||||
foundPlanes[0],
|
||||
foundPlanes[1]));
|
||||
|
||||
_edges.Add(halfEdgeA);
|
||||
_edges.Add(halfEdgeB);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: snap vertices to remove gaps (snapping facctor can be defined by the parent brush)
|
||||
|
||||
for (int32 i = 0; i < surfacesCount; i++)
|
||||
{
|
||||
Polygon polygon;
|
||||
polygon.SurfaceIndex = i;
|
||||
polygon.Visible = true;
|
||||
_polygons.Add(polygon);
|
||||
}
|
||||
|
||||
for (int32 i = pointIntersections.Count() - 1; i >= 0; i--)
|
||||
{
|
||||
auto& pointIntersection = pointIntersections[i];
|
||||
auto& pointEdges = pointIntersection.Edges;
|
||||
|
||||
// Make sure that we have at least 2 edges ...
|
||||
// This may happen when a plane only intersects at a single edge.
|
||||
if (pointEdges.Count() <= 2)
|
||||
{
|
||||
pointIntersections.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto vertexIndex = pointIntersection.VertexIndex;
|
||||
auto& vertex = _vertices[vertexIndex];
|
||||
|
||||
for (int32 j = 0; j < pointEdges.Count() - 1; j++)
|
||||
{
|
||||
auto& edge1 = pointEdges[j];
|
||||
for (int32 k = j + 1; k < pointEdges.Count(); k++)
|
||||
{
|
||||
auto& edge2 = pointEdges[k];
|
||||
|
||||
int32 planeIndex1 = INVALID_INDEX;
|
||||
int32 planeIndex2 = INVALID_INDEX;
|
||||
|
||||
// Determine if and which of our 2 planes are identical
|
||||
if (edge1.PlaneIndices[0] == edge2.PlaneIndices[0])
|
||||
{
|
||||
planeIndex1 = 0;
|
||||
planeIndex2 = 0;
|
||||
}
|
||||
else if (edge1.PlaneIndices[0] == edge2.PlaneIndices[1])
|
||||
{
|
||||
planeIndex1 = 0;
|
||||
planeIndex2 = 1;
|
||||
}
|
||||
else if (edge1.PlaneIndices[1] == edge2.PlaneIndices[0])
|
||||
{
|
||||
planeIndex1 = 1;
|
||||
planeIndex2 = 0;
|
||||
}
|
||||
else if (edge1.PlaneIndices[1] == edge2.PlaneIndices[1])
|
||||
{
|
||||
planeIndex1 = 1;
|
||||
planeIndex2 = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
HalfEdge* ingoing;
|
||||
HalfEdge* outgoing;
|
||||
int32 outgoingIndex;
|
||||
|
||||
Surface& shared_plane = _surfaces[edge1.PlaneIndices[planeIndex1]];
|
||||
Surface& edge1_plane = _surfaces[edge1.PlaneIndices[1 - planeIndex1]];
|
||||
Surface& edge2_plane = _surfaces[edge2.PlaneIndices[1 - planeIndex2]];
|
||||
|
||||
Vector3 direction = Vector3::Cross(shared_plane.Normal, edge1_plane.Normal);
|
||||
|
||||
// Determine the orientation of our two edges to determine which edge is in-going, and which one is out-going
|
||||
if (Vector3::Dot(direction, edge2_plane.Normal) < 0)
|
||||
{
|
||||
ingoing = &_edges[edge2.EdgeIndex];
|
||||
outgoingIndex = _edges[edge1.EdgeIndex].TwinIndex;
|
||||
outgoing = &_edges[outgoingIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
ingoing = &_edges[edge1.EdgeIndex];
|
||||
outgoingIndex = _edges[edge2.EdgeIndex].TwinIndex;
|
||||
outgoing = &_edges[outgoingIndex];
|
||||
}
|
||||
|
||||
// Link the out-going half-edge to the in-going half-edge
|
||||
ingoing->NextIndex = outgoingIndex;
|
||||
|
||||
// Add reference to polygon to half-edge, and make sure our polygon has a reference to a half-edge
|
||||
// Since a half-edge, in this case, serves as a circular linked list this just works.
|
||||
auto polygonIndex = edge1.PlaneIndices[planeIndex1];
|
||||
|
||||
ingoing->PolygonIndex = polygonIndex;
|
||||
outgoing->PolygonIndex = polygonIndex;
|
||||
|
||||
auto& polygon = _polygons[polygonIndex];
|
||||
polygon.FirstEdgeIndex = outgoingIndex;
|
||||
polygon.Bounds.Add(vertex);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the intersection point to the area of our bounding box
|
||||
_bounds.Add(vertex);
|
||||
}
|
||||
|
||||
// Setup base brush meta
|
||||
BrushMeta meta;
|
||||
meta.Mode = mode;
|
||||
meta.StartSurfaceIndex = 0;
|
||||
meta.SurfacesCount = surfacesCount;
|
||||
meta.Bounds = _bounds;
|
||||
meta.Parent = parentBrush;
|
||||
_brushesMeta.Add(meta);
|
||||
}
|
||||
|
||||
#endif
|
||||
377
Source/Engine/CSG/CSGMesh.Split.cpp
Normal file
377
Source/Engine/CSG/CSGMesh.Split.cpp
Normal file
@@ -0,0 +1,377 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "CSGMesh.h"
|
||||
|
||||
#if COMPILE_WITH_CSG_BUILDER
|
||||
|
||||
using namespace CSG;
|
||||
|
||||
void CSG::Mesh::resolvePolygon(Polygon& polygon, PolygonOperation op)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case Keep:
|
||||
break;
|
||||
case Remove:
|
||||
{
|
||||
polygon.Visible = false;
|
||||
break;
|
||||
}
|
||||
case Flip:
|
||||
{
|
||||
polygon.Inverted = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CSG::Mesh::edgeSplit(int32 edgeIndex, const Vector3& vertex)
|
||||
{
|
||||
// Splits a half edge
|
||||
/*
|
||||
original:
|
||||
|
||||
thisEdge
|
||||
*<======================
|
||||
---------------------->*
|
||||
twin
|
||||
|
||||
split:
|
||||
|
||||
newEdge thisEdge
|
||||
*<=========*<===========
|
||||
--------->*----------->*
|
||||
thisTwin newTwin
|
||||
*/
|
||||
|
||||
HalfEdge& thisEdge = _edges[edgeIndex];
|
||||
auto thisTwinIndex = thisEdge.TwinIndex;
|
||||
auto& thisTwin = _edges[thisTwinIndex];
|
||||
auto thisEdgeIndex = thisTwin.TwinIndex;
|
||||
|
||||
HalfEdge newEdge;
|
||||
auto newEdgeIndex = _edges.Count();
|
||||
|
||||
HalfEdge newTwin;
|
||||
auto newTwinIndex = newEdgeIndex + 1;
|
||||
auto vertexIndex = _vertices.Count();
|
||||
|
||||
newEdge.PolygonIndex = thisEdge.PolygonIndex;
|
||||
newTwin.PolygonIndex = thisTwin.PolygonIndex;
|
||||
|
||||
newEdge.VertexIndex = thisEdge.VertexIndex;
|
||||
thisEdge.VertexIndex = vertexIndex;
|
||||
|
||||
newTwin.VertexIndex = thisTwin.VertexIndex;
|
||||
thisTwin.VertexIndex = vertexIndex;
|
||||
|
||||
newEdge.NextIndex = thisEdge.NextIndex;
|
||||
thisEdge.NextIndex = newEdgeIndex;
|
||||
|
||||
newTwin.NextIndex = thisTwin.NextIndex;
|
||||
thisTwin.NextIndex = newTwinIndex;
|
||||
|
||||
newEdge.TwinIndex = thisTwinIndex;
|
||||
thisTwin.TwinIndex = newEdgeIndex;
|
||||
|
||||
thisEdge.TwinIndex = newTwinIndex;
|
||||
newTwin.TwinIndex = thisEdgeIndex;
|
||||
|
||||
_edges.Add(newEdge);
|
||||
_edges.Add(newTwin);
|
||||
_vertices.Add(vertex);
|
||||
}
|
||||
|
||||
PolygonSplitResult CSG::Mesh::polygonSplit(const Surface& cuttingPlane, int32 inputPolygonIndex, Polygon** outputPolygon)
|
||||
{
|
||||
// Splits a polygon into two pieces, or categorizes it as outside, inside or aligned
|
||||
|
||||
// Note: This method is not optimized! Code is simplified for clarity!
|
||||
// for example: Plane.Distance / Plane.OnSide should be inlined manually and shouldn't use enums, but floating point values directly!
|
||||
|
||||
Polygon& inputPolygon = _polygons[inputPolygonIndex];
|
||||
int32 prev = inputPolygon.FirstEdgeIndex;
|
||||
int32 current = _edges[prev].NextIndex;
|
||||
int32 next = _edges[current].NextIndex;
|
||||
int32 last = next;
|
||||
int32 enterEdge = INVALID_INDEX;
|
||||
int32 exitEdge = INVALID_INDEX;
|
||||
|
||||
auto prevVertex = _vertices[_edges[prev].VertexIndex];
|
||||
auto prevDistance = cuttingPlane.Distance(prevVertex); // distance to previous vertex
|
||||
auto prevSide = Surface::OnSide(prevDistance); // side of plane of previous vertex
|
||||
|
||||
auto currentVertex = _vertices[_edges[current].VertexIndex];
|
||||
auto currentDistance = cuttingPlane.Distance(currentVertex); // distance to current vertex
|
||||
auto currentSide = Surface::OnSide(currentDistance); // side of plane of current vertex
|
||||
|
||||
do
|
||||
{
|
||||
auto nextVertex = _vertices[_edges[next].VertexIndex];
|
||||
auto nextDistance = cuttingPlane.Distance(nextVertex); // distance to next vertex
|
||||
auto nextSide = Surface::OnSide(nextDistance); // side of plane of next vertex
|
||||
|
||||
if (prevSide != currentSide) // check if edge crossed the plane ...
|
||||
{
|
||||
if (currentSide != PlaneIntersectionType::Intersecting) // prev:inside/outside - current:inside/outside - next:??
|
||||
{
|
||||
if (prevSide != PlaneIntersectionType::Intersecting) // prev:inside/outside - current:outside - next:??
|
||||
{
|
||||
// Calculate intersection of edge with plane split the edge into two, inserting the new vertex
|
||||
auto newVertex = Surface::Intersection(prevVertex, currentVertex, prevDistance, currentDistance);
|
||||
edgeSplit(current, newVertex);
|
||||
|
||||
if (prevSide == PlaneIntersectionType::Back) // prev:inside - current:outside - next:??
|
||||
{
|
||||
// edge01 exits:
|
||||
//
|
||||
// outside
|
||||
// 1
|
||||
// *
|
||||
// ......./........ intersect
|
||||
// /
|
||||
// 0
|
||||
// inside
|
||||
|
||||
exitEdge = current;
|
||||
}
|
||||
else if (prevSide == PlaneIntersectionType::Front) // prev:outside - current:inside - next:??
|
||||
{
|
||||
// edge01 enters:
|
||||
//
|
||||
// outside
|
||||
// 0
|
||||
// \
|
||||
// .......\........ intersect
|
||||
// *
|
||||
// 1
|
||||
// inside
|
||||
|
||||
enterEdge = current;
|
||||
}
|
||||
|
||||
prev = _edges[prev].NextIndex;
|
||||
prevSide = PlaneIntersectionType::Intersecting;
|
||||
|
||||
if (exitEdge != INVALID_INDEX && enterEdge != INVALID_INDEX)
|
||||
break;
|
||||
|
||||
current = _edges[prev].NextIndex;
|
||||
currentVertex = _vertices[_edges[current].VertexIndex];
|
||||
|
||||
next = _edges[current].NextIndex;
|
||||
nextVertex = _vertices[_edges[next].VertexIndex];
|
||||
}
|
||||
}
|
||||
else // prev:?? - current:intersects - next:??
|
||||
{
|
||||
if (prevSide == PlaneIntersectionType::Intersecting || // prev:intersects - current:intersects - next:??
|
||||
nextSide == PlaneIntersectionType::Intersecting || // prev:?? - current:intersects - next:intersects
|
||||
prevSide == nextSide) // prev:inside/outde - current:intersects - next:inside/outde
|
||||
{
|
||||
if (prevSide == PlaneIntersectionType::Back || // prev:inside - current:intersects - next:intersects/inside
|
||||
nextSide == PlaneIntersectionType::Back) // prev:intersects/inside - current:intersects - next:inside
|
||||
{
|
||||
// outside
|
||||
// 0 1
|
||||
// --------*....... intersect
|
||||
// \
|
||||
// 2
|
||||
// inside
|
||||
//
|
||||
// outside
|
||||
// 1 2
|
||||
// ........*------- intersect
|
||||
// /
|
||||
// 0
|
||||
// inside
|
||||
//
|
||||
// outside
|
||||
// 1
|
||||
//........*....... intersect
|
||||
// / \
|
||||
// 0 2
|
||||
// inside
|
||||
//
|
||||
|
||||
prevSide = PlaneIntersectionType::Back;
|
||||
enterEdge = exitEdge = INVALID_INDEX;
|
||||
break;
|
||||
}
|
||||
else if (prevSide == PlaneIntersectionType::Front || // prev:outside - current:intersects - next:intersects/outside
|
||||
nextSide == PlaneIntersectionType::Front) // prev:intersects/outside - current:intersects - next:outside
|
||||
{
|
||||
// outside
|
||||
// 2
|
||||
// /
|
||||
//..------*....... intersect
|
||||
// 0 1
|
||||
// inside
|
||||
//
|
||||
// outside
|
||||
// 0
|
||||
// \
|
||||
//........*------- intersect
|
||||
// 1 2
|
||||
// inside
|
||||
//
|
||||
// outside
|
||||
// 0 2
|
||||
// \ /
|
||||
//........*....... intersect
|
||||
// 1
|
||||
// inside
|
||||
//
|
||||
|
||||
prevSide = PlaneIntersectionType::Front;
|
||||
enterEdge = exitEdge = INVALID_INDEX;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else // prev:inside/outside - current:intersects - next:inside/outside
|
||||
{
|
||||
if (prevSide == PlaneIntersectionType::Back) // prev:inside - current:intersects - next:outside
|
||||
{
|
||||
// find exit edge:
|
||||
//
|
||||
// outside
|
||||
// 2
|
||||
// 1 /
|
||||
// ........*....... intersect
|
||||
// /
|
||||
// 0
|
||||
// inside
|
||||
|
||||
exitEdge = current;
|
||||
if (enterEdge != INVALID_INDEX)
|
||||
break;
|
||||
}
|
||||
else // prev:outside - current:intersects - next:inside
|
||||
{
|
||||
// find enter edge:
|
||||
//
|
||||
// outside
|
||||
// 0
|
||||
// \ 1
|
||||
// ........*....... intersect
|
||||
// \
|
||||
// 2
|
||||
// inside
|
||||
|
||||
enterEdge = current;
|
||||
if (exitEdge != INVALID_INDEX)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prev = current;
|
||||
current = next;
|
||||
next = _edges[next].NextIndex;
|
||||
|
||||
prevDistance = currentDistance;
|
||||
currentDistance = nextDistance;
|
||||
prevSide = currentSide;
|
||||
currentSide = nextSide;
|
||||
prevVertex = currentVertex;
|
||||
currentVertex = nextVertex;
|
||||
} while (next != last);
|
||||
|
||||
// We should never have only one edge crossing the plane
|
||||
ASSERT((enterEdge == INVALID_INDEX) == (exitEdge == INVALID_INDEX));
|
||||
|
||||
// Check if we have an edge that exits and an edge that enters the plane and split the polygon into two if we do
|
||||
if (enterEdge != INVALID_INDEX && exitEdge != INVALID_INDEX)
|
||||
{
|
||||
// enter .
|
||||
// .
|
||||
// =====>*----->
|
||||
// .
|
||||
//
|
||||
// outside. inside
|
||||
// .
|
||||
// <-----*<=====
|
||||
// .
|
||||
// . exit
|
||||
|
||||
Polygon outsidePolygon;
|
||||
HalfEdge outsideEdge, insideEdge;
|
||||
|
||||
auto outsidePolygonIndex = _polygons.Count();
|
||||
auto outsideEdgeIndex = _edges.Count();
|
||||
auto insideEdgeIndex = outsideEdgeIndex + 1;
|
||||
|
||||
outsideEdge.TwinIndex = insideEdgeIndex;
|
||||
insideEdge.TwinIndex = outsideEdgeIndex;
|
||||
|
||||
insideEdge.PolygonIndex = inputPolygonIndex;
|
||||
outsideEdge.PolygonIndex = outsidePolygonIndex;
|
||||
|
||||
HalfEdge* exitEdgeP = &_edges[exitEdge];
|
||||
HalfEdge* enterEdgeP = &_edges[enterEdge];
|
||||
|
||||
outsideEdge.VertexIndex = exitEdgeP->VertexIndex;
|
||||
insideEdge.VertexIndex = enterEdgeP->VertexIndex;
|
||||
|
||||
outsideEdge.NextIndex = exitEdgeP->NextIndex;
|
||||
insideEdge.NextIndex = enterEdgeP->NextIndex;
|
||||
|
||||
exitEdgeP->NextIndex = insideEdgeIndex;
|
||||
enterEdgeP->NextIndex = outsideEdgeIndex;
|
||||
|
||||
outsidePolygon.FirstEdgeIndex = outsideEdgeIndex;
|
||||
inputPolygon.FirstEdgeIndex = insideEdgeIndex;
|
||||
|
||||
outsidePolygon.Visible = inputPolygon.Visible;
|
||||
outsidePolygon.Inverted = inputPolygon.Inverted;
|
||||
outsidePolygon.SurfaceIndex = inputPolygon.SurfaceIndex;
|
||||
|
||||
_edges.Add(outsideEdge);
|
||||
_edges.Add(insideEdge);
|
||||
|
||||
// calculate the bounds of the polygons
|
||||
outsidePolygon.Bounds.Clear();
|
||||
auto first = _edges[outsidePolygon.FirstEdgeIndex];
|
||||
auto iterator = first;
|
||||
do
|
||||
{
|
||||
outsidePolygon.Bounds.Add(_vertices[iterator.VertexIndex]);
|
||||
iterator.PolygonIndex = outsidePolygonIndex;
|
||||
iterator = _edges[iterator.NextIndex];
|
||||
} while (iterator != first);
|
||||
|
||||
inputPolygon.Bounds.Clear();
|
||||
first = _edges[inputPolygon.FirstEdgeIndex];
|
||||
iterator = first;
|
||||
do
|
||||
{
|
||||
inputPolygon.Bounds.Add(_vertices[iterator.VertexIndex]);
|
||||
iterator = _edges[iterator.NextIndex];
|
||||
} while (iterator != first);
|
||||
|
||||
_polygons.Add(outsidePolygon);
|
||||
*outputPolygon = &_polygons[outsidePolygonIndex];
|
||||
|
||||
return Split;
|
||||
}
|
||||
|
||||
switch (prevSide)
|
||||
{
|
||||
case PlaneIntersectionType::Back:
|
||||
return CompletelyInside;
|
||||
case PlaneIntersectionType::Front:
|
||||
return CompletelyOutside;
|
||||
|
||||
default:
|
||||
{
|
||||
auto polygonPlane = _surfaces[inputPolygon.SurfaceIndex];
|
||||
auto result = Vector3::Dot(polygonPlane.Normal, cuttingPlane.Normal);
|
||||
return result > 0 ? PlaneAligned : PlaneOppositeAligned;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
263
Source/Engine/CSG/CSGMesh.Triangulate.cpp
Normal file
263
Source/Engine/CSG/CSGMesh.Triangulate.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "CSGMesh.h"
|
||||
|
||||
#if COMPILE_WITH_CSG_BUILDER
|
||||
|
||||
#include "Engine/Core/Math/Matrix.h"
|
||||
#include "Engine/Core/Math/Rectangle.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "CSGData.h"
|
||||
|
||||
bool CSG::Mesh::Triangulate(RawData& data, Array<RawModelVertex>& cacheVB) const
|
||||
{
|
||||
// Reject empty meshes
|
||||
int32 verticesCount = _vertices.Count();
|
||||
if (verticesCount == 0)
|
||||
return true;
|
||||
|
||||
// Mesh triangles data
|
||||
cacheVB.Clear();
|
||||
cacheVB.EnsureCapacity(_polygons.Count() * 3);
|
||||
Array<RawModelVertex> surfaceCacheVB(32);
|
||||
|
||||
// Cache submeshes by material to lay them down
|
||||
// key- brush index, value- direcotry for surafecs (key: surface index, value: list with start vertex per triangle)
|
||||
Dictionary<int32, Dictionary<int32, Array<int32>>> polygonsPerBrush(64);
|
||||
|
||||
// Build index buffer
|
||||
int32 firstI, secondI, thirdI;
|
||||
int32 triangleIndices[3];
|
||||
Vector2 uvs[3];
|
||||
RawModelVertex rawVertex;
|
||||
for (int32 i = 0; i < _polygons.Count(); i++)
|
||||
{
|
||||
const Polygon& polygon = _polygons[i];
|
||||
if (!polygon.Visible || polygon.FirstEdgeIndex == INVALID_INDEX)
|
||||
continue;
|
||||
|
||||
HalfEdge iterator = _edges[polygon.FirstEdgeIndex];
|
||||
firstI = iterator.VertexIndex;
|
||||
iterator = _edges[iterator.NextIndex];
|
||||
secondI = iterator.VertexIndex;
|
||||
int32 lastIndex = -1;
|
||||
const HalfEdge& polygonFirst = _edges[polygon.FirstEdgeIndex];
|
||||
|
||||
while (iterator != polygonFirst)
|
||||
{
|
||||
if (lastIndex == iterator.NextIndex)
|
||||
break;
|
||||
lastIndex = iterator.NextIndex;
|
||||
iterator = _edges.At(lastIndex);
|
||||
thirdI = iterator.VertexIndex;
|
||||
|
||||
// Validate triangle
|
||||
if (firstI != secondI && firstI != thirdI && secondI != thirdI)
|
||||
{
|
||||
// Cache triangle parent surface info
|
||||
const Surface& surface = _surfaces[polygon.SurfaceIndex];
|
||||
Vector3 uvPos, centerPos;
|
||||
Matrix trans, transRotation, finalTrans;
|
||||
|
||||
// Build triangle indices
|
||||
if (polygon.Inverted)
|
||||
{
|
||||
triangleIndices[0] = thirdI;
|
||||
triangleIndices[1] = secondI;
|
||||
triangleIndices[2] = firstI;
|
||||
}
|
||||
else
|
||||
{
|
||||
triangleIndices[0] = firstI;
|
||||
triangleIndices[1] = secondI;
|
||||
triangleIndices[2] = thirdI;
|
||||
}
|
||||
|
||||
Vector3 surfaceUp = surface.Normal;
|
||||
Vector3 surfaceForward = -Vector3::Cross(surfaceUp, Vector3::Right);
|
||||
if (surfaceForward.IsZero())
|
||||
surfaceForward = Vector3::Forward;
|
||||
|
||||
Matrix::CreateWorld(Vector3::Zero, surfaceForward, surfaceUp, trans);
|
||||
Matrix::RotationAxis(surfaceUp, surface.TexCoordRotation * DegreesToRadians, transRotation);
|
||||
Matrix::Multiply(transRotation, trans, finalTrans);
|
||||
Vector3::Transform(Vector3::Zero, finalTrans, centerPos);
|
||||
|
||||
// Calculate normal vector
|
||||
Vector3 v0 = _vertices[triangleIndices[0]];
|
||||
Vector3 v1 = _vertices[triangleIndices[1]];
|
||||
Vector3 v2 = _vertices[triangleIndices[2]];
|
||||
Vector3 vd0 = v1 - v0;
|
||||
Vector3 vd1 = v2 - v0;
|
||||
Vector3 normal = Vector3::Normalize(vd0 ^ vd1);
|
||||
|
||||
// Calculate texture uvs based on vertex position
|
||||
for (int32 q = 0; q < 3; q++)
|
||||
{
|
||||
Vector3 pos = _vertices[triangleIndices[q]];
|
||||
Vector3::Transform(pos, finalTrans, uvPos);
|
||||
|
||||
Vector2 texCoord = Vector2(uvPos.X - centerPos.X, uvPos.Z - centerPos.Z);
|
||||
|
||||
// Apply surface uvs transformation
|
||||
uvs[q] = (texCoord * (surface.TexCoordScale * CSG_MESH_UV_SCALE)) + surface.TexCoordOffset;
|
||||
}
|
||||
|
||||
// Calculate tangent (it needs uvs)
|
||||
Vector2 uvd0 = uvs[1] - uvs[0];
|
||||
Vector2 uvd1 = uvs[2] - uvs[0];
|
||||
float dR = (uvd0.X * uvd1.Y - uvd1.X * uvd0.Y);
|
||||
if (Math::IsZero(dR))
|
||||
dR = 1.0f;
|
||||
float r = 1.0f / dR;
|
||||
Vector3 tangent = (vd0 * uvd1.Y - vd1 * uvd0.Y) * r;
|
||||
Vector3 bitangent = (vd1 * uvd0.X - vd0 * uvd1.X) * r;
|
||||
tangent.Normalize();
|
||||
|
||||
// Gram-Schmidt orthogonalize
|
||||
Vector3 newTangentUnormalized = tangent - normal * Vector3::Dot(normal, tangent);
|
||||
const float length = newTangentUnormalized.Length();
|
||||
|
||||
// Workaround to handle degenerated case
|
||||
if (Math::IsZero(length))
|
||||
{
|
||||
tangent = Vector3::Cross(normal, Vector3::UnitX);
|
||||
if (Math::IsZero(tangent.Length()))
|
||||
tangent = Vector3::Cross(normal, Vector3::UnitY);
|
||||
tangent.Normalize();
|
||||
bitangent = Vector3::Cross(normal, tangent);
|
||||
}
|
||||
else
|
||||
{
|
||||
tangent = newTangentUnormalized / length;
|
||||
bitangent.Normalize();
|
||||
}
|
||||
|
||||
// Build triangle
|
||||
int32 vertexIndex = cacheVB.Count();
|
||||
for (int32 q = 0; q < 3; q++)
|
||||
{
|
||||
rawVertex.Position = _vertices[triangleIndices[q]];
|
||||
rawVertex.TexCoord = uvs[q];
|
||||
rawVertex.Normal = normal;
|
||||
rawVertex.Tangent = tangent;
|
||||
rawVertex.Bitangent = bitangent;
|
||||
rawVertex.LightmapUVs = Vector2::Zero;
|
||||
rawVertex.Color = Color::Black;
|
||||
|
||||
cacheVB.Add(rawVertex);
|
||||
}
|
||||
|
||||
// Find brush that produced that surface
|
||||
bool isValid = false;
|
||||
for (int32 brushIndex = 0; brushIndex < _brushesMeta.Count(); brushIndex++)
|
||||
{
|
||||
auto& brushMeta = _brushesMeta[brushIndex];
|
||||
if (Math::IsInRange(polygon.SurfaceIndex, brushMeta.StartSurfaceIndex, brushMeta.StartSurfaceIndex + brushMeta.SurfacesCount - 1))
|
||||
{
|
||||
// Cache triangle
|
||||
polygonsPerBrush[brushIndex][polygon.SurfaceIndex].Add(vertexIndex);
|
||||
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_LOW_LAYER(isValid);
|
||||
}
|
||||
|
||||
secondI = thirdI;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if mesh has no triangles
|
||||
if (cacheVB.IsEmpty())
|
||||
return true;
|
||||
|
||||
// TODO: preallocate data material slots and buffers so data.AddTriangle will be simple copy op
|
||||
|
||||
// Setup result mesh data
|
||||
for (auto iPerBrush = polygonsPerBrush.Begin(); iPerBrush != polygonsPerBrush.End(); ++iPerBrush)
|
||||
{
|
||||
auto& brushMeta = _brushesMeta[iPerBrush->Key];
|
||||
|
||||
for (auto iPerSurface = iPerBrush->Value.Begin(); iPerSurface != iPerBrush->Value.End(); ++iPerSurface)
|
||||
{
|
||||
int32 surfaceIndex = iPerSurface->Key;
|
||||
int32 brushSurfaceIndex = surfaceIndex - brushMeta.StartSurfaceIndex;
|
||||
int32 triangleCount = iPerSurface->Value.Count();
|
||||
int32 vertexCount = triangleCount * 3;
|
||||
Vector2 lightmapUVsMin = Vector2::Maximum;
|
||||
Vector2 lightmapUVsMax = Vector2::Minimum;
|
||||
const Surface& surface = _surfaces[surfaceIndex];
|
||||
|
||||
// Generate lightmap uvs per brush surface
|
||||
{
|
||||
// We just assume that brush surface is now some kind of rectangle (after triangulation)
|
||||
// So we can project vertices locations into texture space uvs [0;1] based on minimum and maximum vertex location
|
||||
|
||||
Vector3 surfaceNormal = surface.Normal;
|
||||
|
||||
// Calculate bounds
|
||||
Vector2 min = Vector2::Maximum, max = Vector2::Minimum;
|
||||
Matrix projection, view, vp;
|
||||
Vector3 up = Vector3::Up;
|
||||
if (Vector3::NearEqual(surfaceNormal, Vector3::Up))
|
||||
up = Vector3::Right;
|
||||
else if (Vector3::NearEqual(surfaceNormal, Vector3::Down))
|
||||
up = Vector3::Forward;
|
||||
Matrix::LookAt(Vector3::Zero, surfaceNormal, up, view);
|
||||
Matrix::Ortho(1000, 1000, 0.00001f, 100000.0f, projection);
|
||||
Matrix::Multiply(view, projection, vp);
|
||||
Array<Vector2> pointsCache(vertexCount);
|
||||
for (int32 triangleIndex = 0; triangleIndex < triangleCount; triangleIndex++)
|
||||
{
|
||||
int32 triangleStartVertex = iPerSurface->Value[triangleIndex];
|
||||
for (int32 k = 0; k < 3; k++)
|
||||
{
|
||||
auto& vertex = cacheVB[triangleStartVertex + k];
|
||||
|
||||
Vector3 projected = Vector3::Project(vertex.Position, 0, 0, 1000, 1000, 0, 1, vp);
|
||||
Vector2 projectecXY = Vector2(projected);
|
||||
|
||||
min = Vector2::Min(projectecXY, min);
|
||||
max = Vector2::Max(projectecXY, max);
|
||||
|
||||
pointsCache.Add(projectecXY);
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize projected positions to get lightmap uvs
|
||||
float projectedSize = (max - min).MaxValue();
|
||||
for (int32 triangleIndex = 0; triangleIndex < triangleCount; triangleIndex++)
|
||||
{
|
||||
int32 triangleStartVertex = iPerSurface->Value[triangleIndex];
|
||||
for (int32 k = 0; k < 3; k++)
|
||||
{
|
||||
auto& vertex = cacheVB[triangleStartVertex + k];
|
||||
|
||||
vertex.LightmapUVs = (pointsCache[triangleIndex * 3 + k] - min) / projectedSize;
|
||||
|
||||
lightmapUVsMin = Vector2::Min(lightmapUVsMin, vertex.LightmapUVs);
|
||||
lightmapUVsMax = Vector2::Max(lightmapUVsMax, vertex.LightmapUVs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write triangles
|
||||
surfaceCacheVB.Clear();
|
||||
for (int32 triangleIndex = 0; triangleIndex < triangleCount; triangleIndex++)
|
||||
{
|
||||
int32 triangleStartVertex = iPerSurface->Value[triangleIndex];
|
||||
surfaceCacheVB.Add(cacheVB[triangleStartVertex + 0]);
|
||||
surfaceCacheVB.Add(cacheVB[triangleStartVertex + 1]);
|
||||
surfaceCacheVB.Add(cacheVB[triangleStartVertex + 2]);
|
||||
}
|
||||
Rectangle lightmapUVsBox(lightmapUVsMin, lightmapUVsMax - lightmapUVsMin);
|
||||
data.AddSurface(brushMeta.Parent, brushSurfaceIndex, surface.Material, surface.ScaleInLightmap, lightmapUVsBox, surfaceCacheVB.Get(), surfaceCacheVB.Count());
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
318
Source/Engine/CSG/CSGMesh.cpp
Normal file
318
Source/Engine/CSG/CSGMesh.cpp
Normal file
@@ -0,0 +1,318 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "CSGMesh.h"
|
||||
|
||||
#if COMPILE_WITH_CSG_BUILDER
|
||||
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
|
||||
using namespace CSG;
|
||||
|
||||
bool CSG::Mesh::HasMode(Mode mode) const
|
||||
{
|
||||
for (int32 i = 0; i < _brushesMeta.Count(); i++)
|
||||
{
|
||||
if (_brushesMeta[i].Mode == mode)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CSG::Mesh::PerformOperation(Mesh* other)
|
||||
{
|
||||
ASSERT(other->_brushesMeta.Count() > 0);
|
||||
|
||||
// Check if has more than one submeshes
|
||||
if (other->_brushesMeta.Count() > 1)
|
||||
{
|
||||
// Just add all subbrushes
|
||||
Add(other);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto mode = other->_brushesMeta[0].Mode;
|
||||
|
||||
// Switch between mesh operation mode
|
||||
switch (mode)
|
||||
{
|
||||
case Mode::Additive:
|
||||
{
|
||||
// Check if both meshes do not intesect
|
||||
if (AABB::IsOutside(_bounds, other->GetBounds())) // TODO: test every sub bounds not whole _bounds
|
||||
{
|
||||
// Add vertices to the mesh without any additional calculations
|
||||
Add(other);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove common part from other mesh and then combine them
|
||||
//intersect(other, Remove, Keep);
|
||||
Add(other);
|
||||
|
||||
// TODO: ogarnac to bo to jest zle xD
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Mode::Subtractive:
|
||||
{
|
||||
// Check if both meshes do not intesect
|
||||
if (AABB::IsOutside(_bounds, other->GetBounds())) // TODO: test every sub bounds not whole _bounds
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
else
|
||||
{
|
||||
// Perform intersection operations
|
||||
intersect(other, Remove, Keep);
|
||||
other->intersect(this, Flip, Remove);
|
||||
|
||||
// Merge meshes
|
||||
Add(other);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CSG::Mesh::Add(const Mesh* other)
|
||||
{
|
||||
ASSERT(this != other && other);
|
||||
|
||||
// Cache data
|
||||
int32 baseIndexVertices = _vertices.Count();
|
||||
int32 baseIndexSurfaces = _surfaces.Count();
|
||||
int32 baseIndexEdges = _edges.Count();
|
||||
int32 baseIndexPolygons = _polygons.Count();
|
||||
auto oVertices = other->GetVertices();
|
||||
auto oSurfaces = other->GetSurfaces();
|
||||
auto oEdges = other->GetEdges();
|
||||
auto oPolygons = other->GetPolygons();
|
||||
auto oMeta = &other->_brushesMeta;
|
||||
|
||||
// Clone vertices
|
||||
for (int32 i = 0; i < oVertices->Count(); i++)
|
||||
{
|
||||
_vertices.Add(oVertices->At(i));
|
||||
}
|
||||
|
||||
// Clone surfaces
|
||||
for (int32 i = 0; i < oSurfaces->Count(); i++)
|
||||
{
|
||||
_surfaces.Add(oSurfaces->At(i));
|
||||
}
|
||||
|
||||
// Clone edges
|
||||
for (int32 i = 0; i < oEdges->Count(); i++)
|
||||
{
|
||||
HalfEdge edge = oEdges->At(i);
|
||||
edge.PolygonIndex += baseIndexPolygons;
|
||||
edge.TwinIndex += baseIndexEdges;
|
||||
edge.NextIndex += baseIndexEdges;
|
||||
edge.VertexIndex += baseIndexVertices;
|
||||
_edges.Add(edge);
|
||||
}
|
||||
|
||||
// Clone polygons
|
||||
for (int32 i = 0; i < oPolygons->Count(); i++)
|
||||
{
|
||||
Polygon polygon = oPolygons->At(i);
|
||||
polygon.SurfaceIndex += baseIndexSurfaces;
|
||||
polygon.FirstEdgeIndex += baseIndexEdges;
|
||||
_polygons.Add(polygon);
|
||||
}
|
||||
|
||||
// Clone meta
|
||||
for (int32 i = 0; i < oMeta->Count(); i++)
|
||||
{
|
||||
BrushMeta meta = oMeta->At(i);
|
||||
meta.StartSurfaceIndex += baseIndexSurfaces;
|
||||
_brushesMeta.Add(meta);
|
||||
}
|
||||
|
||||
// Increase bounds
|
||||
_bounds.Add(other->GetBounds());
|
||||
}
|
||||
|
||||
void CSG::Mesh::intersect(const Mesh* other, PolygonOperation insideOp, PolygonOperation outsideOp)
|
||||
{
|
||||
// insideOp - operation for polygons being inside the other brush
|
||||
// outsideOp - operation for polygons being outside the other brush
|
||||
|
||||
// Prevent from redudant action
|
||||
if (insideOp == Keep && outsideOp == Keep)
|
||||
return;
|
||||
|
||||
// Check every sub brush from other mesh
|
||||
int32 count = other->_brushesMeta.Count();
|
||||
for (int32 subMeshIndex = 0; subMeshIndex < count; subMeshIndex++)
|
||||
{
|
||||
auto& brushMeta = other->_brushesMeta[subMeshIndex];
|
||||
|
||||
// Check if can intersect with that sub mesh data
|
||||
if (!AABB::IsOutside(brushMeta.Bounds, _bounds))
|
||||
{
|
||||
PolygonOperation iOp = insideOp;
|
||||
PolygonOperation oOp = outsideOp;
|
||||
|
||||
if (brushMeta.Mode == Mode::Subtractive)
|
||||
{
|
||||
iOp = Remove;
|
||||
oOp = Keep;
|
||||
}
|
||||
|
||||
intersectSubMesh(other, subMeshIndex, iOp, oOp);
|
||||
}
|
||||
}
|
||||
|
||||
// Update bounds
|
||||
updateBounds();
|
||||
}
|
||||
|
||||
void CSG::Mesh::intersectSubMesh(const Mesh* other, int32 subMeshIndex, PolygonOperation insideOp, PolygonOperation outsideOp)
|
||||
{
|
||||
// Cache data
|
||||
auto oSurfaces = other->GetSurfaces();
|
||||
auto& brushMeta = other->_brushesMeta[subMeshIndex];
|
||||
auto oBounds = brushMeta.Bounds;
|
||||
int32 startBrushSurface = brushMeta.StartSurfaceIndex;
|
||||
int32 endBrushSurface = startBrushSurface + brushMeta.SurfacesCount;
|
||||
|
||||
// Check every polygon (itereate fron end since we can ass new polygons and we don't want to process them)
|
||||
for (int32 i = _polygons.Count() - 1; i >= 0; i--)
|
||||
{
|
||||
auto& polygon = _polygons[i];
|
||||
if (!polygon.Visible || polygon.FirstEdgeIndex == INVALID_INDEX)
|
||||
continue;
|
||||
AABB polygonBounds = polygon.Bounds;
|
||||
|
||||
auto finalResult = CompletelyInside;
|
||||
|
||||
// A quick check if the polygon lies outside the planes we're cutting our polygons with
|
||||
if (!AABB::IsOutside(oBounds, polygonBounds))
|
||||
{
|
||||
PolygonSplitResult intermediateResult;
|
||||
Polygon* outsidePolygon;
|
||||
for (int32 otherIndex = startBrushSurface; otherIndex < endBrushSurface; otherIndex++)
|
||||
{
|
||||
auto& cuttingSurface = oSurfaces->At(otherIndex);
|
||||
|
||||
auto side = cuttingSurface.OnSide(polygonBounds);
|
||||
if (side == PlaneIntersectionType::Front)
|
||||
{
|
||||
finalResult = CompletelyOutside;
|
||||
continue;
|
||||
}
|
||||
if (side == PlaneIntersectionType::Back)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
intermediateResult = polygonSplit(cuttingSurface, i, &outsidePolygon);
|
||||
|
||||
if (intermediateResult == CompletelyOutside)
|
||||
{
|
||||
finalResult = CompletelyOutside;
|
||||
break;
|
||||
}
|
||||
if (intermediateResult == Split)
|
||||
{
|
||||
resolvePolygon(*outsidePolygon, outsideOp);
|
||||
}
|
||||
else if (intermediateResult != CompletelyInside)
|
||||
{
|
||||
finalResult = intermediateResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
finalResult = CompletelyOutside;
|
||||
}
|
||||
|
||||
switch (finalResult)
|
||||
{
|
||||
case CompletelyInside:
|
||||
{
|
||||
resolvePolygon(_polygons[i], insideOp);
|
||||
break;
|
||||
}
|
||||
case CompletelyOutside:
|
||||
{
|
||||
resolvePolygon(_polygons[i], outsideOp);
|
||||
break;
|
||||
}
|
||||
|
||||
// The polygon can only be visible if it's part of the last brush that shares it's surface area,
|
||||
// otherwise we'd get overlapping polygons if two brushes overlap.
|
||||
// When the (final) polygon is aligned with one of the cutting planes, we know it lies on the surface of
|
||||
// the CSG node we're cutting the polygons with. We also know that this node is not the node this polygon belongs to
|
||||
// because we've done that check earlier on. So we flag this polygon as being invisible.
|
||||
case PlaneAligned:
|
||||
// TODO: check this case
|
||||
//polygon.Visible = false;
|
||||
//aligned.Add(inputPolygon);
|
||||
break;
|
||||
case PlaneOppositeAligned:
|
||||
// TODO: check this case
|
||||
//polygon.Visible = false;
|
||||
//revAligned.Add(inputPolygon);
|
||||
break;
|
||||
|
||||
case Split:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CSG::Mesh::updateBounds()
|
||||
{
|
||||
_bounds.Clear();
|
||||
|
||||
int32 triangleIndices[3];
|
||||
for (int32 i = 0; i < _polygons.Count(); i++)
|
||||
{
|
||||
auto& polygon = _polygons[i];
|
||||
if (!polygon.Visible || polygon.FirstEdgeIndex == INVALID_INDEX)
|
||||
continue;
|
||||
|
||||
HalfEdge iterator = _edges[polygon.FirstEdgeIndex];
|
||||
triangleIndices[0] = iterator.VertexIndex;
|
||||
iterator = _edges[iterator.NextIndex];
|
||||
triangleIndices[1] = iterator.VertexIndex;
|
||||
const HalfEdge& polygonFirst = _edges[polygon.FirstEdgeIndex];
|
||||
|
||||
while (iterator != polygonFirst)
|
||||
{
|
||||
iterator = _edges.At(iterator.NextIndex);
|
||||
triangleIndices[2] = iterator.VertexIndex;
|
||||
|
||||
// Validate triangle
|
||||
if (triangleIndices[0] != triangleIndices[1] && triangleIndices[0] != triangleIndices[2] && triangleIndices[1] != triangleIndices[2])
|
||||
{
|
||||
for (int32 q = 0; q < 3; q++)
|
||||
{
|
||||
Vector3 pos = _vertices[triangleIndices[q]];
|
||||
_bounds.Add(pos);
|
||||
}
|
||||
}
|
||||
|
||||
triangleIndices[1] = triangleIndices[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CSG::Mesh::doPolygonsOperation(bool isInverted, bool visibility)
|
||||
{
|
||||
for (int32 i = 0; i < _polygons.Count(); i++)
|
||||
{
|
||||
if (_polygons[i].Inverted == isInverted)
|
||||
_polygons[i].Visible = visibility;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
152
Source/Engine/CSG/CSGMesh.h
Normal file
152
Source/Engine/CSG/CSGMesh.h
Normal file
@@ -0,0 +1,152 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Math/AABB.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Brush.h"
|
||||
#include "HalfEdge.h"
|
||||
#include "Polygon.h"
|
||||
#include "Engine/Graphics/Models/Types.h"
|
||||
|
||||
#define CSG_MESH_UV_SCALE (1.0f / 100.0f)
|
||||
|
||||
#if COMPILE_WITH_CSG_BUILDER
|
||||
|
||||
namespace CSG
|
||||
{
|
||||
class RawData;
|
||||
|
||||
enum PolygonOperation
|
||||
{
|
||||
Keep,
|
||||
Remove,
|
||||
Flip
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// CSG mesh object
|
||||
/// </summary>
|
||||
class Mesh
|
||||
{
|
||||
private:
|
||||
|
||||
struct BrushMeta
|
||||
{
|
||||
Mode Mode;
|
||||
int32 StartSurfaceIndex;
|
||||
int32 SurfacesCount;
|
||||
AABB Bounds;
|
||||
Brush* Parent;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
AABB _bounds;
|
||||
Array<Polygon> _polygons;
|
||||
Array<HalfEdge> _edges;
|
||||
Array<Vector3> _vertices;
|
||||
Array<Surface> _surfaces;
|
||||
Array<BrushMeta> _brushesMeta;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Mesh index (assigned by the builder)
|
||||
/// </summary>
|
||||
int32 Index = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets mesh bounds area
|
||||
/// </summary>
|
||||
/// <returns>Mesh bounds</returns>
|
||||
AABB GetBounds() const
|
||||
{
|
||||
return _bounds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if any of this brush sub-brushes is on given type
|
||||
/// </summary>
|
||||
/// <returns>True if brush is using given mode</returns>
|
||||
bool HasMode(Mode mode) const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets array with polygons
|
||||
/// </summary>
|
||||
/// <returns>Polygon</returns>
|
||||
const Array<Polygon>* GetPolygons() const
|
||||
{
|
||||
return &_polygons;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets array with surfaces
|
||||
/// </summary>
|
||||
/// <returns>Surfaces</returns>
|
||||
const Array<Surface>* GetSurfaces() const
|
||||
{
|
||||
return &_surfaces;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets array with edges
|
||||
/// </summary>
|
||||
/// <returns>Half edges</returns>
|
||||
const Array<HalfEdge>* GetEdges() const
|
||||
{
|
||||
return &_edges;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets array with vertices
|
||||
/// </summary>
|
||||
/// <returns>Vertices</returns>
|
||||
const Array<Vector3>* GetVertices() const
|
||||
{
|
||||
return &_vertices;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Build mesh from brush
|
||||
/// </summary>
|
||||
/// <param name="parentBrush">Parent brush to use</param>
|
||||
void Build(Brush* parentBrush);
|
||||
|
||||
/// <summary>
|
||||
/// Triangulate mesh
|
||||
/// </summary>
|
||||
/// <param name="data">Result data</param>
|
||||
/// <param name="cacheVB">Cache data</param>
|
||||
/// <returns>True if cannot generate valid mesh data (due to missing triangles etc.)</returns>
|
||||
bool Triangulate(RawData& data, Array<RawModelVertex>& cacheVB) const;
|
||||
|
||||
/// <summary>
|
||||
/// Perform CSG operation with another mesh
|
||||
/// </summary>
|
||||
/// <param name="other">Other mesh data to process</param>
|
||||
void PerformOperation(Mesh* other);
|
||||
|
||||
/// <summary>
|
||||
/// Add other mesh data
|
||||
/// </summary>
|
||||
/// <param name="other">Other mesh to merge with</param>
|
||||
void Add(const Mesh* other);
|
||||
|
||||
private:
|
||||
|
||||
void intersect(const Mesh* other, PolygonOperation insideOp, PolygonOperation outsideOp);
|
||||
void intersectSubMesh(const Mesh* other, int32 subMeshIndex, PolygonOperation insideOp, PolygonOperation outsideOp);
|
||||
void updateBounds();
|
||||
static void resolvePolygon(Polygon& polygon, PolygonOperation op);
|
||||
void edgeSplit(int32 edgeIndex, const Vector3& vertex);
|
||||
PolygonSplitResult polygonSplit(const Surface& cuttingPlane, int32 inputPolygonIndex, Polygon** outputPolygon);
|
||||
void doPolygonsOperation(bool isInverted, bool visibility);
|
||||
};
|
||||
|
||||
typedef Array<Mesh*> MeshesArray;
|
||||
};
|
||||
|
||||
#endif
|
||||
51
Source/Engine/CSG/HalfEdge.h
Normal file
51
Source/Engine/CSG/HalfEdge.h
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CSG
|
||||
{
|
||||
/// <summary>
|
||||
/// Half edge structure
|
||||
/// </summary>
|
||||
struct HalfEdge
|
||||
{
|
||||
// ^
|
||||
// | polygon
|
||||
// next |
|
||||
// half-edge |
|
||||
// | half-edge
|
||||
// vertex *<======================
|
||||
// ---------------------->*
|
||||
// twin-half-edge
|
||||
|
||||
int32 NextIndex;
|
||||
int32 TwinIndex;
|
||||
int32 VertexIndex;
|
||||
int32 PolygonIndex;
|
||||
|
||||
// TODO: remove this code for faster performance
|
||||
HalfEdge()
|
||||
{
|
||||
NextIndex = 0;
|
||||
TwinIndex = 0;
|
||||
VertexIndex = 0;
|
||||
PolygonIndex = 0;
|
||||
}
|
||||
|
||||
bool operator==(const HalfEdge& b) const
|
||||
{
|
||||
return NextIndex == b.NextIndex
|
||||
&& TwinIndex == b.TwinIndex
|
||||
&& VertexIndex == b.VertexIndex
|
||||
&& PolygonIndex == b.PolygonIndex;
|
||||
}
|
||||
|
||||
bool operator!=(const HalfEdge& b) const
|
||||
{
|
||||
return NextIndex != b.NextIndex
|
||||
|| TwinIndex != b.TwinIndex
|
||||
|| VertexIndex != b.VertexIndex
|
||||
|| PolygonIndex != b.PolygonIndex;
|
||||
}
|
||||
};
|
||||
};
|
||||
58
Source/Engine/CSG/Polygon.h
Normal file
58
Source/Engine/CSG/Polygon.h
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Math/AABB.h"
|
||||
|
||||
namespace CSG
|
||||
{
|
||||
enum PolygonSplitResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Polygon is completely inside half-space defined by plane
|
||||
/// </summary>
|
||||
CompletelyInside,
|
||||
|
||||
/// <summary>
|
||||
/// Polygon is completely outside half-space defined by plane
|
||||
/// </summary>
|
||||
CompletelyOutside,
|
||||
|
||||
/// <summary>
|
||||
/// Polygon has been split into two parts by plane
|
||||
/// </summary>
|
||||
Split,
|
||||
|
||||
/// <summary>
|
||||
/// Polygon is aligned with cutting plane and the polygons' normal points in the same direction
|
||||
/// </summary>
|
||||
PlaneAligned,
|
||||
|
||||
/// <summary>
|
||||
/// Polygon is aligned with cutting plane and the polygons' normal points in the opposite direction
|
||||
/// </summary>
|
||||
PlaneOppositeAligned
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Polygon structure
|
||||
/// </summary>
|
||||
struct Polygon
|
||||
{
|
||||
int32 FirstEdgeIndex;
|
||||
int32 SurfaceIndex;
|
||||
bool Visible;
|
||||
bool Inverted;
|
||||
AABB Bounds;
|
||||
|
||||
Polygon()
|
||||
{
|
||||
// TODO: remove this constructor to boost performance??
|
||||
FirstEdgeIndex = INVALID_INDEX;
|
||||
SurfaceIndex = INVALID_INDEX;
|
||||
Inverted = false;
|
||||
Visible = false;
|
||||
Bounds = AABB();
|
||||
}
|
||||
};
|
||||
};
|
||||
11
Source/Engine/CSG/Types.h
Normal file
11
Source/Engine/CSG/Types.h
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Config.h"
|
||||
#include "Engine/Level/Actors/BrushMode.h"
|
||||
|
||||
namespace CSG
|
||||
{
|
||||
typedef ::BrushMode Mode;
|
||||
};
|
||||
Reference in New Issue
Block a user