Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mavimaec/room service #46

Merged
merged 6 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/merge-and-run.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ jobs:
lfs: ${{ inputs.lfs }}
fetch-depth: 0 # fetches the whole history in order to be able to merge with the base branch

- name: Setup DotNet Environment
uses: actions/setup-dotnet@v1
with:
dotnet-version: |
6.0.x
8.0.x

- name: Setup Git VIM Robot User Info
run: |
git config --global user.email "[email protected]"
Expand Down
67 changes: 67 additions & 0 deletions src/cs/vim/Vim.Format.Tests/RoomServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using NUnit.Framework;
using System.IO;
using System.Linq;
using Vim.Format.Merge;
using Vim.Format.ObjectModel;
using Vim.Format.SceneBuilder;
using Vim.Math3d;
using Vim.Util.Tests;

namespace Vim.Format.Tests;

[TestFixture]
public static class RoomServiceTests
{
/// <summary>
/// Merges two unrelated VIM files and then calculates the inclusion of elements
/// coming from the first VIM file (containing no rooms) in the rooms of the second VIM file.
/// </summary>
[Test]
public static void TestRoomService_ComputeElementsInRoom()
{
var ctx = new CallerTestContext();
var dir = ctx.PrepareDirectory();

// Merge the VIM files.
var vim1 = Path.Combine(VimFormatRepoPaths.DataDir, "Wolford_Residence.r2023.om_v5.0.0.vim");
var vim2 = Path.Combine(VimFormatRepoPaths.DataDir, "RoomTest.vim");
var mergedFilePath = Path.Combine(dir, "merged.vim");

var mergeFiles = new MergeConfigFiles(new[]
{
(vim1, Matrix4x4.Identity),
(vim2, Matrix4x4.Identity)
}, mergedFilePath);

var mergeOptions = new MergeConfigOptions()
{
GeneratorString = nameof(TestRoomService_ComputeElementsInRoom),
VersionString = "0.0.0",
};

MergeService.MergeVimFiles(mergeFiles, mergeOptions);

var mergedVim = VimScene.LoadVim(mergedFilePath);
Assert.DoesNotThrow(() => mergedVim.Validate());

var dm = mergedVim.DocumentModel;

var elementInfos =
Enumerable.Range(0, mergedVim.DocumentModel.NumElement)
.AsParallel()
.Select(elementIndex => dm.GetElementInfo(elementIndex))
.ToArray();

var wolfordFamilyInstances = elementInfos
.Where(ei => ei.BimDocumentFileName.Contains("Wolford") && ei.IsFamilyInstance)
.ToArray();
var wolfordFamilyInstanceElementIndices = wolfordFamilyInstances.Select(ei => ei.ElementIndex).ToHashSet();

// Compute the element association in each room.
var familyInstancesInRooms = RoomService.ComputeElementsInRoom(
mergedVim,
ei => wolfordFamilyInstanceElementIndices.Contains(ei.ElementIndex));

Assert.IsNotEmpty(familyInstancesInRooms);
}
}
4 changes: 4 additions & 0 deletions src/cs/vim/Vim.Format/ObjectModel/ElementInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ public bool IsSystem
public int CategoryIndex => DocumentModel.GetElementCategoryIndex(ElementIndex);
public int LevelIndex => DocumentModel.GetElementLevelIndex(ElementIndex);
public int LevelElementIndex => DocumentModel.GetLevelElementIndex(LevelIndex);
public int RoomIndex => DocumentModel.GetElementRoomIndex(ElementIndex);
public int RoomElementIndex => DocumentModel.GetRoomElementIndex(RoomIndex);
public int BimDocumentIndex => DocumentModel.GetElementBimDocumentIndex(ElementIndex);
public int WorksetIndex => DocumentModel.GetElementWorksetIndex(ElementIndex);
public int FamilyInstanceElementIndex => DocumentModel.GetFamilyInstanceElementIndex(FamilyInstanceIndex);
Expand All @@ -100,6 +102,7 @@ public bool IsSystem
public Element Element => DocumentModel.ElementList.ElementAtOrDefault(ElementIndex);
public Category Category => DocumentModel.CategoryList.ElementAtOrDefault(CategoryIndex);
public Level Level => DocumentModel.LevelList.ElementAtOrDefault(LevelIndex);
public Room Room => DocumentModel.RoomList.ElementAtOrDefault(RoomIndex);
public BimDocument BimDocument => DocumentModel.BimDocumentList.ElementAtOrDefault(BimDocumentIndex);
public Workset Workset => DocumentModel.WorksetList.ElementAtOrDefault(WorksetIndex);
public FamilyInstance FamilyInstance => DocumentModel.FamilyInstanceList.ElementAtOrDefault(FamilyInstanceIndex);
Expand Down Expand Up @@ -164,6 +167,7 @@ public string ElementUniqueIdWithBimScopedElementIdFallback

public string ElementName => DocumentModel.GetElementName(ElementIndex);
public string CategoryName => DocumentModel.GetCategoryName(CategoryIndex);
public string CategoryBuiltInName => DocumentModel.GetCategoryBuiltInCategory(CategoryIndex);
public string LevelName => DocumentModel.GetElementName(LevelElementIndex);
public string FamilyName => DocumentModel.GetElementName(FamilyElementIndex, DocumentModel.GetElementFamilyName(ElementIndex));
public string FamilyTypeName => DocumentModel.GetElementName(FamilyTypeElementIndex);
Expand Down
145 changes: 145 additions & 0 deletions src/cs/vim/Vim.Format/RoomService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Vim.Format.Geometry;
using Vim.Format.ObjectModel;
using Vim.LinqArray;
using Vim.Math3d;

namespace Vim.Format
{
public class ElementInRoom
{
public int ElementIndex { get; }
public int RoomIndex { get; }

public ElementInRoom(int elementIndex, int roomIndex)
{
ElementIndex = elementIndex;
RoomIndex = roomIndex;
}
}

public static class RoomService
{
public delegate bool GeometricElementInfoFilter(ElementInfo elementInfo);

public static ElementInRoom[] ComputeElementsInRoom(VimScene vim, GeometricElementInfoFilter geometricElementInfoFilter)
{
var dm = vim.DocumentModel;

var roomElementIndices = new HashSet<int>(dm.RoomElementIndex.ToEnumerable());
if (roomElementIndices.Count == 0)
return Array.Empty<ElementInRoom>(); // no rooms found.

// Collect the bounding boxes of geometric elements
var geometricNodesGroupedByElementIndex = vim.VimNodes
.Where(n => n.HasMesh)
.GroupBy(n => n.ElementIndex)
.ToArray();

var roomGeometryCollection = geometricNodesGroupedByElementIndex
.AsParallel()
.Where(g =>
{
var elementIndex = g.Key;
return roomElementIndices.Contains(elementIndex);
})
.Select(g =>
{
// ASSUMPTION: Rooms are typically represented by a single geometric node, so take the first item in the group.
var worldSpaceMesh = g.First().TransformedMesh();
var roomElementIndex = g.Key;

var roomIndexFound = dm.ElementIndexMaps.RoomIndexFromElementIndex.TryGetValue(roomElementIndex, out var roomIndex);
roomIndex = roomIndexFound ? roomIndex : -1;

return new RoomGeometry(roomIndex, worldSpaceMesh.Vertices.ToArray(), worldSpaceMesh.Indices.ToArray());
})
.ToArray();

if (roomGeometryCollection.Length == 0)
return Array.Empty<ElementInRoom>(); // no rooms with geometry found.

var filteredElementGeometricNodes = geometricNodesGroupedByElementIndex
.AsParallel()
.Where(g =>
{
// Filter the elements
var elementInfo = g.First();
if (!geometricElementInfoFilter(elementInfo))
return false;

// Ignore room elements.
if (roomElementIndices.Contains(elementInfo.ElementIndex))
return false;

return true;
}).Select(g =>
{
var elementIndex = g.Key;

// Determine whether the element geometry's bounding box center is contained in one of the rooms.
var boundingBox = g.Select(n => n.TransformedBoundingBox()).Aggregate((acc, cur) => acc.Merge(cur));
var boxCenter = boundingBox.Center;

foreach (var roomGeometry in roomGeometryCollection)
{
// Broad-phase bounding box check.
var containmentType = roomGeometry.AABox.Contains(boundingBox);
if (containmentType == ContainmentType.Disjoint)
continue;

// Refined point-in-mesh check
if (roomGeometry.ContainsPoint(boxCenter))
return new ElementInRoom(elementIndex, roomGeometry.RoomIndex); // early return on the first room which contains the bottom center of the box.
}

return null;
}).Where(eir => eir != null)
.ToArray();

return filteredElementGeometricNodes;
}

private class RoomGeometry
{
private readonly Vector3[] _vertices;
private readonly int[] _indices;

public int RoomIndex { get; }
public AABox AABox { get; }

public RoomGeometry(int roomIndex, Vector3[] vertices, int[] indices)
{
RoomIndex = roomIndex;
_vertices = vertices;
_indices = indices;
AABox = AABox.Create(vertices);
}

public bool ContainsPoint(Vector3 point)
{
var intersections = 0;

var ray = new Ray(point, Vector3.UnitZ);

for (var i = 0; i < _indices.Length; i += 3)
{
var v0 = _vertices[_indices[i]];
var v1 = _vertices[_indices[i + 1]];
var v2 = _vertices[_indices[i + 2]];

var triangle = new Triangle(v0, v1, v2);

if (ray.Intersects(triangle) != null)
{
intersections++;
}
}

return intersections % 2 == 1; // Inside if odd intersections
}
}
}
}
Loading