From ea79b838371034cafe1987324146f03a4b405b40 Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Fri, 1 Dec 2023 13:24:45 +0200 Subject: [PATCH 01/21] Initial implementation of TravelDistanceAnalyzer function --- TravelDistanceAnalyzer/.gitignore | 9 + TravelDistanceAnalyzer/README.md | 19 + .../TravelDistanceAnalyzer.sln | 34 ++ .../dependencies/CirculationSegment.g.cs | 49 ++ .../dependencies/DoorType.g.cs | 34 ++ .../dependencies/RouteDistanceOverride.g.cs | 153 +++++ .../RouteDistanceOverrideAddition.g.cs | 32 ++ .../RouteDistanceOverrideRemoval.g.cs | 32 ++ .../dependencies/SpaceBoundary.g.cs | 107 ++++ ...TravelDistanceAnalyzer.Dependencies.csproj | 14 + .../TravelDistanceAnalyzerInputs.g.cs | 540 ++++++++++++++++++ .../TravelDistanceAnalyzerOutputs.g.cs | 29 + .../dependencies/WalkingDistanceOverride.g.cs | 153 +++++ .../WalkingDistanceOverrideAddition.g.cs | 32 ++ .../WalkingDistanceOverrideRemoval.g.cs | 32 ++ .../dependencies/WallCandidate.g.cs | 56 ++ TravelDistanceAnalyzer/global.json | 7 + TravelDistanceAnalyzer/hypar.json | 140 +++++ .../src/AdaptiveGridBuilder.cs | 509 +++++++++++++++++ .../src/AdaptiveGridExtensions.cs | 38 ++ TravelDistanceAnalyzer/src/ColorFactory.cs | 28 + TravelDistanceAnalyzer/src/Function.g.cs | 73 +++ .../src/RouteDistanceConfiguration.cs | 198 +++++++ .../src/ThickenedPolylineExtensions.cs | 17 + .../src/TravelDistanceAnalyzer.cs | 137 +++++ .../src/TravelDistanceAnalyzer.csproj | 13 + .../src/WalkingDistanceConfiguration.cs | 188 ++++++ .../src/WalkingDistanceStatistics.cs | 29 + TravelDistanceAnalyzer/test/FunctionTest.g.cs | 24 + .../test/TravelDistanceAnalyzer.Tests.csproj | 28 + TravelDistanceAnalyzer/test/Usings.cs | 1 + 31 files changed, 2755 insertions(+) create mode 100644 TravelDistanceAnalyzer/.gitignore create mode 100644 TravelDistanceAnalyzer/README.md create mode 100644 TravelDistanceAnalyzer/TravelDistanceAnalyzer.sln create mode 100644 TravelDistanceAnalyzer/dependencies/CirculationSegment.g.cs create mode 100644 TravelDistanceAnalyzer/dependencies/DoorType.g.cs create mode 100644 TravelDistanceAnalyzer/dependencies/RouteDistanceOverride.g.cs create mode 100644 TravelDistanceAnalyzer/dependencies/RouteDistanceOverrideAddition.g.cs create mode 100644 TravelDistanceAnalyzer/dependencies/RouteDistanceOverrideRemoval.g.cs create mode 100644 TravelDistanceAnalyzer/dependencies/SpaceBoundary.g.cs create mode 100644 TravelDistanceAnalyzer/dependencies/TravelDistanceAnalyzer.Dependencies.csproj create mode 100644 TravelDistanceAnalyzer/dependencies/TravelDistanceAnalyzerInputs.g.cs create mode 100644 TravelDistanceAnalyzer/dependencies/TravelDistanceAnalyzerOutputs.g.cs create mode 100644 TravelDistanceAnalyzer/dependencies/WalkingDistanceOverride.g.cs create mode 100644 TravelDistanceAnalyzer/dependencies/WalkingDistanceOverrideAddition.g.cs create mode 100644 TravelDistanceAnalyzer/dependencies/WalkingDistanceOverrideRemoval.g.cs create mode 100644 TravelDistanceAnalyzer/dependencies/WallCandidate.g.cs create mode 100644 TravelDistanceAnalyzer/global.json create mode 100644 TravelDistanceAnalyzer/hypar.json create mode 100644 TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs create mode 100644 TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs create mode 100644 TravelDistanceAnalyzer/src/ColorFactory.cs create mode 100644 TravelDistanceAnalyzer/src/Function.g.cs create mode 100644 TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs create mode 100644 TravelDistanceAnalyzer/src/ThickenedPolylineExtensions.cs create mode 100644 TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs create mode 100644 TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.csproj create mode 100644 TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs create mode 100644 TravelDistanceAnalyzer/src/WalkingDistanceStatistics.cs create mode 100644 TravelDistanceAnalyzer/test/FunctionTest.g.cs create mode 100644 TravelDistanceAnalyzer/test/TravelDistanceAnalyzer.Tests.csproj create mode 100644 TravelDistanceAnalyzer/test/Usings.cs diff --git a/TravelDistanceAnalyzer/.gitignore b/TravelDistanceAnalyzer/.gitignore new file mode 100644 index 00000000..f1d4f671 --- /dev/null +++ b/TravelDistanceAnalyzer/.gitignore @@ -0,0 +1,9 @@ + +bin/ +obj/ +*.glb +output.json +input.json +.vs/ +server/ +test/Generated/ \ No newline at end of file diff --git a/TravelDistanceAnalyzer/README.md b/TravelDistanceAnalyzer/README.md new file mode 100644 index 00000000..c4256667 --- /dev/null +++ b/TravelDistanceAnalyzer/README.md @@ -0,0 +1,19 @@ + + +# Travel Distance Analyzer + +The Travel Distance Analyzer function. + +|Input Name|Type|Description| +|---|---|---| + + +
+ +|Output Name|Type|Description| +|---|---|---| + + +
+ +## Additional Information \ No newline at end of file diff --git a/TravelDistanceAnalyzer/TravelDistanceAnalyzer.sln b/TravelDistanceAnalyzer/TravelDistanceAnalyzer.sln new file mode 100644 index 00000000..07c43bc0 --- /dev/null +++ b/TravelDistanceAnalyzer/TravelDistanceAnalyzer.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TravelDistanceAnalyzer", "src\TravelDistanceAnalyzer.csproj", "{732A2ECC-586C-43EA-9CE9-79C719E18BF9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TravelDistanceAnalyzer.Dependencies", "dependencies\TravelDistanceAnalyzer.Dependencies.csproj", "{0CB6C856-E6D1-462C-951D-23043D7BCF88}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TravelDistanceAnalyzer.Tests", "test\TravelDistanceAnalyzer.Tests.csproj", "{695D9D11-8B3A-42C5-930A-4DE906364BB7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {732A2ECC-586C-43EA-9CE9-79C719E18BF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {732A2ECC-586C-43EA-9CE9-79C719E18BF9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {732A2ECC-586C-43EA-9CE9-79C719E18BF9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {732A2ECC-586C-43EA-9CE9-79C719E18BF9}.Release|Any CPU.Build.0 = Release|Any CPU + {0CB6C856-E6D1-462C-951D-23043D7BCF88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0CB6C856-E6D1-462C-951D-23043D7BCF88}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0CB6C856-E6D1-462C-951D-23043D7BCF88}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0CB6C856-E6D1-462C-951D-23043D7BCF88}.Release|Any CPU.Build.0 = Release|Any CPU + {695D9D11-8B3A-42C5-930A-4DE906364BB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {695D9D11-8B3A-42C5-930A-4DE906364BB7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {695D9D11-8B3A-42C5-930A-4DE906364BB7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {695D9D11-8B3A-42C5-930A-4DE906364BB7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/TravelDistanceAnalyzer/dependencies/CirculationSegment.g.cs b/TravelDistanceAnalyzer/dependencies/CirculationSegment.g.cs new file mode 100644 index 00000000..8ebae248 --- /dev/null +++ b/TravelDistanceAnalyzer/dependencies/CirculationSegment.g.cs @@ -0,0 +1,49 @@ +//---------------------- +// +// Generated using the NJsonSchema v10.1.21.0 (Newtonsoft.Json v13.0.0.0) (http://NJsonSchema.org) +// +//---------------------- +using Elements; +using Elements.GeoJSON; +using Elements.Geometry; +using Elements.Geometry.Solids; +using Elements.Spatial; +using Elements.Validators; +using Elements.Serialization.JSON; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using Line = Elements.Geometry.Line; +using Polygon = Elements.Geometry.Polygon; + +namespace Elements +{ + #pragma warning disable // Disable all warnings + + /// Represents a section of a circulation network, such as a corridor. + [JsonConverter(typeof(Elements.Serialization.JSON.JsonInheritanceConverter), "discriminator")] + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + public partial class CirculationSegment : Floor + { + [JsonConstructor] + public CirculationSegment(ThickenedPolyline @geometry, Profile @profile, double @thickness, System.Guid? @level, Transform @transform, Material @material, Representation @representation, bool @isElementDefinition, System.Guid @id, string @name) + : base(profile, thickness, level, transform, material, representation, isElementDefinition, id, name) + { + this.Geometry = @geometry; + } + + + // Empty constructor + public CirculationSegment() + : base() + { + } + + /// The geometry of this circulation segment + [JsonProperty("Geometry", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public ThickenedPolyline Geometry { get; set; } + + + } +} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/dependencies/DoorType.g.cs b/TravelDistanceAnalyzer/dependencies/DoorType.g.cs new file mode 100644 index 00000000..a2f7547a --- /dev/null +++ b/TravelDistanceAnalyzer/dependencies/DoorType.g.cs @@ -0,0 +1,34 @@ +//---------------------- +// +// Generated using the NJsonSchema v10.1.21.0 (Newtonsoft.Json v13.0.0.0) (http://NJsonSchema.org) +// +//---------------------- +using Elements; +using Elements.GeoJSON; +using Elements.Geometry; +using Elements.Geometry.Solids; +using Elements.Spatial; +using Elements.Validators; +using Elements.Serialization.JSON; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using Line = Elements.Geometry.Line; +using Polygon = Elements.Geometry.Polygon; + +namespace Elements +{ + #pragma warning disable // Disable all warnings + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + public enum DoorType + { + [System.Runtime.Serialization.EnumMember(Value = @"Single")] + Single = 0, + + [System.Runtime.Serialization.EnumMember(Value = @"Double")] + Double = 1, + + } +} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/dependencies/RouteDistanceOverride.g.cs b/TravelDistanceAnalyzer/dependencies/RouteDistanceOverride.g.cs new file mode 100644 index 00000000..fd1781d3 --- /dev/null +++ b/TravelDistanceAnalyzer/dependencies/RouteDistanceOverride.g.cs @@ -0,0 +1,153 @@ +using Elements; +using System.Collections.Generic; +using System; +using System.Linq; + +namespace TravelDistanceAnalyzer +{ + /// + /// Override metadata for RouteDistanceOverride + /// + public partial class RouteDistanceOverride : IOverride + { + public static string Name = "Route Distance"; + public static string Dependency = null; + public static string Context = "[*discriminator=Elements.RouteDistanceConfiguration]"; + public static string Paradigm = "Edit"; + + /// + /// Get the override name for this override. + /// + public string GetName() { + return Name; + } + + public object GetIdentity() { + + return Identity; + } + + } + public static class RouteDistanceOverrideExtensions + { + /// + /// Apply Route Distance edit overrides to a collection of existing elements + /// + /// The Route Distance Overrides to apply + /// A collection of existing elements to which to apply the overrides. + /// A function returning a boolean which indicates whether an element is a match for an override's identity. + /// A function to modify a matched element, returning the modified element. + /// The element type this override applies to. Should match the type(s) in the override's context. + /// A collection of elements, including unmodified and modified elements from the supplied collection. + public static List Apply( + this IList overrideData, + IEnumerable existingElements, + Func identityMatch, + Func modifyElement) where T : Element + { + var resultElements = new List(existingElements); + if (overrideData != null) + { + foreach (var overrideValue in overrideData) + { + // Assuming there will only be one match per identity, find the first element that matches. + var matchingElement = existingElements.FirstOrDefault(e => identityMatch(e, overrideValue.Identity)); + // if we found a match, + if (matchingElement != null) + { + // remove the old matching element + resultElements.Remove(matchingElement); + // apply the modification function to it + var modifiedElement = modifyElement(matchingElement, overrideValue); + // set the identity + Identity.AddOverrideIdentity(modifiedElement, overrideValue); + //and re-add it to the collection + resultElements.Add(modifiedElement); + } + } + } + return resultElements; + } + + /// + /// Apply Route Distance edit overrides to a collection of existing elements + /// + /// A collection of existing elements to which to apply the overrides. + /// The Route Distance Overrides to apply — typically `input.Overrides.RouteDistance` + /// A function returning a boolean which indicates whether an element is a match for an override's identity. + /// A function to modify a matched element, returning the modified element. + /// The element type this override applies to. Should match the type(s) in the override's context. + /// A collection of elements, including unmodified and modified elements from the supplied collection. + public static void ApplyOverrides( + this List existingElements, + IList overrideData, + Func identityMatch, + Func modifyElement + ) where T : Element + { + var updatedElements = overrideData.Apply(existingElements, identityMatch, modifyElement); + existingElements.Clear(); + existingElements.AddRange(updatedElements); + } + + /// + /// Create elements from add/removeoverrides, and apply any edits. + /// + /// The collection of edit overrides (Overrides.RouteDistance) + /// The collection of add overrides (Overrides.Additions.RouteDistance) + /// The collection of remove overrides (Overrides.Removals.RouteDistance) /// A function returning a boolean which indicates whether an element is a match for an override's identity. + /// A function to create a new element, returning the created element. + /// A function to modify a matched element, returning the modified element. + /// An optional collection of existing elements to which to apply any edit overrides, or remove if remove overrides are found. + /// The element type this override applies to. Should match the type(s) in the override's context. + /// A collection of elements, including new, unmodified, and modified elements from the supplied collection. + public static List CreateElements( + this IList edits, + IList additions, + IList removals, Func createElement, + Func identityMatch, + Func modifyElement, + IEnumerable existingElements = null + ) where T : Element + { + List resultElements = existingElements == null ? new List() : new List(existingElements); + if (removals != null) + { + foreach (var removedElement in removals) + { + var elementToRemove = resultElements.FirstOrDefault(e => identityMatch(e, removedElement.Identity)); + if (elementToRemove != null) + { + resultElements.Remove(elementToRemove); + } + } + } if (additions != null) + { + foreach (var addedElement in additions) + { + var elementToAdd = createElement(addedElement); + resultElements.Add(elementToAdd); + Identity.AddOverrideIdentity(elementToAdd, addedElement); + } + } + if (edits != null) + { + foreach (var editedElement in edits) + { + var elementToEdit = resultElements.FirstOrDefault(e => identityMatch(e, editedElement.Identity)); + if (elementToEdit != null) + { + resultElements.Remove(elementToEdit); + var newElement = modifyElement(elementToEdit, editedElement); + resultElements.Add(newElement); + Identity.AddOverrideIdentity(newElement, editedElement); + } + } + } + return resultElements; + } + + } + + +} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/dependencies/RouteDistanceOverrideAddition.g.cs b/TravelDistanceAnalyzer/dependencies/RouteDistanceOverrideAddition.g.cs new file mode 100644 index 00000000..d1dc44fa --- /dev/null +++ b/TravelDistanceAnalyzer/dependencies/RouteDistanceOverrideAddition.g.cs @@ -0,0 +1,32 @@ +using Elements; +using System.Collections.Generic; +using System; +using System.Linq; + +namespace TravelDistanceAnalyzer +{ + /// + /// Override metadata for RouteDistanceOverrideAddition + /// + public partial class RouteDistanceOverrideAddition : IOverride + { + public static string Name = "Route Distance Addition"; + public static string Dependency = null; + public static string Context = "[*discriminator=Elements.RouteDistanceConfiguration]"; + public static string Paradigm = "Edit"; + + /// + /// Get the override name for this override. + /// + public string GetName() { + return Name; + } + + public object GetIdentity() { + + return Identity; + } + + } + +} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/dependencies/RouteDistanceOverrideRemoval.g.cs b/TravelDistanceAnalyzer/dependencies/RouteDistanceOverrideRemoval.g.cs new file mode 100644 index 00000000..651f0efb --- /dev/null +++ b/TravelDistanceAnalyzer/dependencies/RouteDistanceOverrideRemoval.g.cs @@ -0,0 +1,32 @@ +using Elements; +using System.Collections.Generic; +using System; +using System.Linq; + +namespace TravelDistanceAnalyzer +{ + /// + /// Override metadata for RouteDistanceOverrideRemoval + /// + public partial class RouteDistanceOverrideRemoval : IOverride + { + public static string Name = "Route Distance Removal"; + public static string Dependency = null; + public static string Context = "[*discriminator=Elements.RouteDistanceConfiguration]"; + public static string Paradigm = "Edit"; + + /// + /// Get the override name for this override. + /// + public string GetName() { + return Name; + } + + public object GetIdentity() { + + return Identity; + } + + } + +} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/dependencies/SpaceBoundary.g.cs b/TravelDistanceAnalyzer/dependencies/SpaceBoundary.g.cs new file mode 100644 index 00000000..8ce5974d --- /dev/null +++ b/TravelDistanceAnalyzer/dependencies/SpaceBoundary.g.cs @@ -0,0 +1,107 @@ +//---------------------- +// +// Generated using the NJsonSchema v10.1.21.0 (Newtonsoft.Json v13.0.0.0) (http://NJsonSchema.org) +// +//---------------------- +using Elements; +using Elements.GeoJSON; +using Elements.Geometry; +using Elements.Geometry.Solids; +using Elements.Spatial; +using Elements.Validators; +using Elements.Serialization.JSON; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using Line = Elements.Geometry.Line; +using Polygon = Elements.Geometry.Polygon; + +namespace Elements +{ + #pragma warning disable // Disable all warnings + + /// A profile with a program assigned to it, and optional internal cell geometry. + [JsonConverter(typeof(Elements.Serialization.JSON.JsonInheritanceConverter), "discriminator")] + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + public partial class SpaceBoundary : GeometricElement + { + [JsonConstructor] + public SpaceBoundary(Profile @boundary, IList @cells, double @area, double? @length, double? @depth, double @height, string @programGroup, string @programType, System.Guid? @programRequirement, System.Guid? @level, System.Guid? @levelLayout, string @hyparSpaceType, string @defaultWallType, Transform @transform = null, Material @material = null, Representation @representation = null, bool @isElementDefinition = false, System.Guid @id = default, string @name = null) + : base(transform, material, representation, isElementDefinition, id, name) + { + this.Boundary = @boundary; + this.Cells = @cells; + this.Area = @area; + this.Length = @length; + this.Depth = @depth; + this.Height = @height; + this.ProgramGroup = @programGroup; + this.ProgramType = @programType; + this.ProgramRequirement = @programRequirement; + this.Level = @level; + this.LevelLayout = @levelLayout; + this.HyparSpaceType = @hyparSpaceType; + this.DefaultWallType = @defaultWallType; + } + + + // Empty constructor + public SpaceBoundary() + : base() + { + } + + /// The boundary of the space + [JsonProperty("Boundary", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Profile Boundary { get; set; } + + /// Component cells making up the boundary + [JsonProperty("Cells", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList Cells { get; set; } + + /// The area of the boundary + [JsonProperty("Area", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double Area { get; set; } + + /// The rough length of this space boundary, parallel to the accessible edge + [JsonProperty("Length", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double? Length { get; set; } + + /// The rough depth of the space boundary, perpendicular to the accessible edge + [JsonProperty("Depth", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double? Depth { get; set; } + + /// The height of this space boundary + [JsonProperty("Height", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double Height { get; set; } + + /// A program grouping, like a department. + [JsonProperty("Program Group", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string ProgramGroup { get; set; } + + /// The name of the program type assigned to this space (like "Open Office" or "Meeting Room") + [JsonProperty("Program Type", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string ProgramType { get; set; } + + [JsonProperty("Program Requirement", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Guid? ProgramRequirement { get; set; } + + [JsonProperty("Level", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Guid? Level { get; set; } + + /// The layout, if any, which generated this space boundary. + [JsonProperty("Level Layout", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Guid? LevelLayout { get; set; } + + /// The hypar-recognized space type name which will be used to determine which layout function to apply. In older space boundaries, this may not be set — fall back to the Name property for this purpose if not provided. + [JsonProperty("Hypar Space Type", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string HyparSpaceType { get; set; } + + /// What wall type should generally be created for this space type? This may get overridden later on for a specific wall. + [JsonProperty("Default Wall Type", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string DefaultWallType { get; set; } + + + } +} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/dependencies/TravelDistanceAnalyzer.Dependencies.csproj b/TravelDistanceAnalyzer/dependencies/TravelDistanceAnalyzer.Dependencies.csproj new file mode 100644 index 00000000..69448574 --- /dev/null +++ b/TravelDistanceAnalyzer/dependencies/TravelDistanceAnalyzer.Dependencies.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + + + + + + + + diff --git a/TravelDistanceAnalyzer/dependencies/TravelDistanceAnalyzerInputs.g.cs b/TravelDistanceAnalyzer/dependencies/TravelDistanceAnalyzerInputs.g.cs new file mode 100644 index 00000000..5a4d4321 --- /dev/null +++ b/TravelDistanceAnalyzer/dependencies/TravelDistanceAnalyzerInputs.g.cs @@ -0,0 +1,540 @@ +// This code was generated by Hypar. +// Edits to this code will be overwritten the next time you run 'hypar init'. +// DO NOT EDIT THIS FILE. + +using Elements; +using Elements.GeoJSON; +using Elements.Geometry; +using Elements.Geometry.Solids; +using Elements.Validators; +using Elements.Serialization.JSON; +using Hypar.Functions; +using Hypar.Functions.Execution; +using Hypar.Functions.Execution.AWS; +using Hypar.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using Line = Elements.Geometry.Line; +using Polygon = Elements.Geometry.Polygon; + +namespace TravelDistanceAnalyzer +{ + #pragma warning disable // Disable all warnings + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public class TravelDistanceAnalyzerInputs : ArgsBase + + { + [Newtonsoft.Json.JsonConstructor] + + public TravelDistanceAnalyzerInputs(Overrides @overrides, Dictionary modelInputKeys, string gltfKey, string elementsKey, string ifcKey): + base(modelInputKeys, gltfKey, elementsKey, ifcKey) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @overrides}); + } + + this.Overrides = @overrides ?? this.Overrides; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("overrides", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Overrides Overrides { get; set; } = new Overrides(); + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class Overrides + + { + public Overrides() { } + + [Newtonsoft.Json.JsonConstructor] + public Overrides(OverrideAdditions @additions, OverrideRemovals @removals, IList @routeDistance, IList @walkingDistance) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @additions, @removals, @routeDistance, @walkingDistance}); + } + + this.Additions = @additions ?? this.Additions; + this.Removals = @removals ?? this.Removals; + this.RouteDistance = @routeDistance ?? this.RouteDistance; + this.WalkingDistance = @walkingDistance ?? this.WalkingDistance; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("Additions", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public OverrideAdditions Additions { get; set; } = new OverrideAdditions(); + + [Newtonsoft.Json.JsonProperty("Removals", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public OverrideRemovals Removals { get; set; } = new OverrideRemovals(); + + [Newtonsoft.Json.JsonProperty("Route Distance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList RouteDistance { get; set; } = new List(); + + [Newtonsoft.Json.JsonProperty("Walking Distance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList WalkingDistance { get; set; } = new List(); + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class OverrideAdditions + + { + public OverrideAdditions() { } + + [Newtonsoft.Json.JsonConstructor] + public OverrideAdditions(IList @routeDistance, IList @walkingDistance) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @routeDistance, @walkingDistance}); + } + + this.RouteDistance = @routeDistance ?? this.RouteDistance; + this.WalkingDistance = @walkingDistance ?? this.WalkingDistance; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("Route Distance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList RouteDistance { get; set; } = new List(); + + [Newtonsoft.Json.JsonProperty("Walking Distance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList WalkingDistance { get; set; } = new List(); + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class OverrideRemovals + + { + public OverrideRemovals() { } + + [Newtonsoft.Json.JsonConstructor] + public OverrideRemovals(IList @routeDistance, IList @walkingDistance) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @routeDistance, @walkingDistance}); + } + + this.RouteDistance = @routeDistance ?? this.RouteDistance; + this.WalkingDistance = @walkingDistance ?? this.WalkingDistance; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("Route Distance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList RouteDistance { get; set; } = new List(); + + [Newtonsoft.Json.JsonProperty("Walking Distance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList WalkingDistance { get; set; } = new List(); + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class RouteDistanceOverride + + { + [Newtonsoft.Json.JsonConstructor] + public RouteDistanceOverride(string @id, RouteDistanceIdentity @identity, RouteDistanceValue @value) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @id, @identity, @value}); + } + + this.Id = @id; + this.Identity = @identity; + this.Value = @value; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Id { get; set; } + + [Newtonsoft.Json.JsonProperty("Identity", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public RouteDistanceIdentity Identity { get; set; } + + [Newtonsoft.Json.JsonProperty("Value", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public RouteDistanceValue Value { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class WalkingDistanceOverride + + { + [Newtonsoft.Json.JsonConstructor] + public WalkingDistanceOverride(string @id, WalkingDistanceIdentity @identity, WalkingDistanceValue @value) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @id, @identity, @value}); + } + + this.Id = @id; + this.Identity = @identity; + this.Value = @value; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Id { get; set; } + + [Newtonsoft.Json.JsonProperty("Identity", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public WalkingDistanceIdentity Identity { get; set; } + + [Newtonsoft.Json.JsonProperty("Value", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public WalkingDistanceValue Value { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class RouteDistanceOverrideAddition + + { + [Newtonsoft.Json.JsonConstructor] + public RouteDistanceOverrideAddition(string @id, RouteDistanceIdentity @identity, RouteDistanceOverrideAdditionValue @value) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @id, @identity, @value}); + } + + this.Id = @id; + this.Identity = @identity; + this.Value = @value; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Id { get; set; } + + [Newtonsoft.Json.JsonProperty("Identity", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public RouteDistanceIdentity Identity { get; set; } + + [Newtonsoft.Json.JsonProperty("Value", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public RouteDistanceOverrideAdditionValue Value { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class WalkingDistanceOverrideAddition + + { + [Newtonsoft.Json.JsonConstructor] + public WalkingDistanceOverrideAddition(string @id, WalkingDistanceIdentity @identity, WalkingDistanceOverrideAdditionValue @value) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @id, @identity, @value}); + } + + this.Id = @id; + this.Identity = @identity; + this.Value = @value; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Id { get; set; } + + [Newtonsoft.Json.JsonProperty("Identity", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public WalkingDistanceIdentity Identity { get; set; } + + [Newtonsoft.Json.JsonProperty("Value", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public WalkingDistanceOverrideAdditionValue Value { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class RouteDistanceOverrideRemoval + + { + [Newtonsoft.Json.JsonConstructor] + public RouteDistanceOverrideRemoval(string @id, RouteDistanceIdentity @identity) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @id, @identity}); + } + + this.Id = @id; + this.Identity = @identity; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Id { get; set; } + + [Newtonsoft.Json.JsonProperty("Identity", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public RouteDistanceIdentity Identity { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class WalkingDistanceOverrideRemoval + + { + [Newtonsoft.Json.JsonConstructor] + public WalkingDistanceOverrideRemoval(string @id, WalkingDistanceIdentity @identity) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @id, @identity}); + } + + this.Id = @id; + this.Identity = @identity; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Id { get; set; } + + [Newtonsoft.Json.JsonProperty("Identity", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public WalkingDistanceIdentity Identity { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class RouteDistanceIdentity + + { + [Newtonsoft.Json.JsonConstructor] + public RouteDistanceIdentity(string @addId) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @addId}); + } + + this.AddId = @addId; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("AddId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string AddId { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class RouteDistanceValue + + { + [Newtonsoft.Json.JsonConstructor] + public RouteDistanceValue(IList @destinations, Color @color) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @destinations, @color}); + } + + this.Destinations = @destinations; + this.Color = @color; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("Destinations", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList Destinations { get; set; } + + [Newtonsoft.Json.JsonProperty("Color", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Color Color { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class WalkingDistanceIdentity + + { + [Newtonsoft.Json.JsonConstructor] + public WalkingDistanceIdentity(string @addId) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @addId}); + } + + this.AddId = @addId; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("AddId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string AddId { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class WalkingDistanceValue + + { + [Newtonsoft.Json.JsonConstructor] + public WalkingDistanceValue(Transform @transform, IList @programTypes, Color @color) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @transform, @programTypes, @color}); + } + + this.Transform = @transform; + this.ProgramTypes = @programTypes; + this.Color = @color; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("Transform", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Transform Transform { get; set; } + + [Newtonsoft.Json.JsonProperty("Program Types", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList ProgramTypes { get; set; } + + [Newtonsoft.Json.JsonProperty("Color", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Color Color { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class RouteDistanceOverrideAdditionValue + + { + [Newtonsoft.Json.JsonConstructor] + public RouteDistanceOverrideAdditionValue(IList @destinations) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @destinations}); + } + + this.Destinations = @destinations; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("Destinations", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList Destinations { get; set; } + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + + public partial class WalkingDistanceOverrideAdditionValue + + { + [Newtonsoft.Json.JsonConstructor] + public WalkingDistanceOverrideAdditionValue(Transform @transform, IList @programTypes) + { + var validator = Validator.Instance.GetFirstValidatorForType(); + if(validator != null) + { + validator.PreConstruct(new object[]{ @transform, @programTypes}); + } + + this.Transform = @transform; + this.ProgramTypes = @programTypes; + + if(validator != null) + { + validator.PostConstruct(this); + } + } + + [Newtonsoft.Json.JsonProperty("Transform", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Transform Transform { get; set; } + + [Newtonsoft.Json.JsonProperty("Program Types", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList ProgramTypes { get; set; } + + } +} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/dependencies/TravelDistanceAnalyzerOutputs.g.cs b/TravelDistanceAnalyzer/dependencies/TravelDistanceAnalyzerOutputs.g.cs new file mode 100644 index 00000000..2a52707d --- /dev/null +++ b/TravelDistanceAnalyzer/dependencies/TravelDistanceAnalyzerOutputs.g.cs @@ -0,0 +1,29 @@ +// This code was generated by Hypar. +// Edits to this code will be overwritten the next time you run 'hypar init'. +// DO NOT EDIT THIS FILE. + +using Elements; +using Elements.GeoJSON; +using Elements.Geometry; +using Hypar.Functions; +using Hypar.Functions.Execution; +using Hypar.Functions.Execution.AWS; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System.Collections.Generic; + +namespace TravelDistanceAnalyzer +{ + public class TravelDistanceAnalyzerOutputs: SystemResults + { + + /// + /// Construct a TravelDistanceAnalyzerOutputs with default inputs. + /// This should be used for testing only. + /// + public TravelDistanceAnalyzerOutputs() : base() + { + } + + } +} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/dependencies/WalkingDistanceOverride.g.cs b/TravelDistanceAnalyzer/dependencies/WalkingDistanceOverride.g.cs new file mode 100644 index 00000000..0eb7c70a --- /dev/null +++ b/TravelDistanceAnalyzer/dependencies/WalkingDistanceOverride.g.cs @@ -0,0 +1,153 @@ +using Elements; +using System.Collections.Generic; +using System; +using System.Linq; + +namespace TravelDistanceAnalyzer +{ + /// + /// Override metadata for WalkingDistanceOverride + /// + public partial class WalkingDistanceOverride : IOverride + { + public static string Name = "Walking Distance"; + public static string Dependency = null; + public static string Context = "[*discriminator=Elements.WalkingDistanceConfiguration]"; + public static string Paradigm = "Edit"; + + /// + /// Get the override name for this override. + /// + public string GetName() { + return Name; + } + + public object GetIdentity() { + + return Identity; + } + + } + public static class WalkingDistanceOverrideExtensions + { + /// + /// Apply Walking Distance edit overrides to a collection of existing elements + /// + /// The Walking Distance Overrides to apply + /// A collection of existing elements to which to apply the overrides. + /// A function returning a boolean which indicates whether an element is a match for an override's identity. + /// A function to modify a matched element, returning the modified element. + /// The element type this override applies to. Should match the type(s) in the override's context. + /// A collection of elements, including unmodified and modified elements from the supplied collection. + public static List Apply( + this IList overrideData, + IEnumerable existingElements, + Func identityMatch, + Func modifyElement) where T : Element + { + var resultElements = new List(existingElements); + if (overrideData != null) + { + foreach (var overrideValue in overrideData) + { + // Assuming there will only be one match per identity, find the first element that matches. + var matchingElement = existingElements.FirstOrDefault(e => identityMatch(e, overrideValue.Identity)); + // if we found a match, + if (matchingElement != null) + { + // remove the old matching element + resultElements.Remove(matchingElement); + // apply the modification function to it + var modifiedElement = modifyElement(matchingElement, overrideValue); + // set the identity + Identity.AddOverrideIdentity(modifiedElement, overrideValue); + //and re-add it to the collection + resultElements.Add(modifiedElement); + } + } + } + return resultElements; + } + + /// + /// Apply Walking Distance edit overrides to a collection of existing elements + /// + /// A collection of existing elements to which to apply the overrides. + /// The Walking Distance Overrides to apply — typically `input.Overrides.WalkingDistance` + /// A function returning a boolean which indicates whether an element is a match for an override's identity. + /// A function to modify a matched element, returning the modified element. + /// The element type this override applies to. Should match the type(s) in the override's context. + /// A collection of elements, including unmodified and modified elements from the supplied collection. + public static void ApplyOverrides( + this List existingElements, + IList overrideData, + Func identityMatch, + Func modifyElement + ) where T : Element + { + var updatedElements = overrideData.Apply(existingElements, identityMatch, modifyElement); + existingElements.Clear(); + existingElements.AddRange(updatedElements); + } + + /// + /// Create elements from add/removeoverrides, and apply any edits. + /// + /// The collection of edit overrides (Overrides.WalkingDistance) + /// The collection of add overrides (Overrides.Additions.WalkingDistance) + /// The collection of remove overrides (Overrides.Removals.WalkingDistance) /// A function returning a boolean which indicates whether an element is a match for an override's identity. + /// A function to create a new element, returning the created element. + /// A function to modify a matched element, returning the modified element. + /// An optional collection of existing elements to which to apply any edit overrides, or remove if remove overrides are found. + /// The element type this override applies to. Should match the type(s) in the override's context. + /// A collection of elements, including new, unmodified, and modified elements from the supplied collection. + public static List CreateElements( + this IList edits, + IList additions, + IList removals, Func createElement, + Func identityMatch, + Func modifyElement, + IEnumerable existingElements = null + ) where T : Element + { + List resultElements = existingElements == null ? new List() : new List(existingElements); + if (removals != null) + { + foreach (var removedElement in removals) + { + var elementToRemove = resultElements.FirstOrDefault(e => identityMatch(e, removedElement.Identity)); + if (elementToRemove != null) + { + resultElements.Remove(elementToRemove); + } + } + } if (additions != null) + { + foreach (var addedElement in additions) + { + var elementToAdd = createElement(addedElement); + resultElements.Add(elementToAdd); + Identity.AddOverrideIdentity(elementToAdd, addedElement); + } + } + if (edits != null) + { + foreach (var editedElement in edits) + { + var elementToEdit = resultElements.FirstOrDefault(e => identityMatch(e, editedElement.Identity)); + if (elementToEdit != null) + { + resultElements.Remove(elementToEdit); + var newElement = modifyElement(elementToEdit, editedElement); + resultElements.Add(newElement); + Identity.AddOverrideIdentity(newElement, editedElement); + } + } + } + return resultElements; + } + + } + + +} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/dependencies/WalkingDistanceOverrideAddition.g.cs b/TravelDistanceAnalyzer/dependencies/WalkingDistanceOverrideAddition.g.cs new file mode 100644 index 00000000..c0a60756 --- /dev/null +++ b/TravelDistanceAnalyzer/dependencies/WalkingDistanceOverrideAddition.g.cs @@ -0,0 +1,32 @@ +using Elements; +using System.Collections.Generic; +using System; +using System.Linq; + +namespace TravelDistanceAnalyzer +{ + /// + /// Override metadata for WalkingDistanceOverrideAddition + /// + public partial class WalkingDistanceOverrideAddition : IOverride + { + public static string Name = "Walking Distance Addition"; + public static string Dependency = null; + public static string Context = "[*discriminator=Elements.WalkingDistanceConfiguration]"; + public static string Paradigm = "Edit"; + + /// + /// Get the override name for this override. + /// + public string GetName() { + return Name; + } + + public object GetIdentity() { + + return Identity; + } + + } + +} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/dependencies/WalkingDistanceOverrideRemoval.g.cs b/TravelDistanceAnalyzer/dependencies/WalkingDistanceOverrideRemoval.g.cs new file mode 100644 index 00000000..40f9fafa --- /dev/null +++ b/TravelDistanceAnalyzer/dependencies/WalkingDistanceOverrideRemoval.g.cs @@ -0,0 +1,32 @@ +using Elements; +using System.Collections.Generic; +using System; +using System.Linq; + +namespace TravelDistanceAnalyzer +{ + /// + /// Override metadata for WalkingDistanceOverrideRemoval + /// + public partial class WalkingDistanceOverrideRemoval : IOverride + { + public static string Name = "Walking Distance Removal"; + public static string Dependency = null; + public static string Context = "[*discriminator=Elements.WalkingDistanceConfiguration]"; + public static string Paradigm = "Edit"; + + /// + /// Get the override name for this override. + /// + public string GetName() { + return Name; + } + + public object GetIdentity() { + + return Identity; + } + + } + +} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/dependencies/WallCandidate.g.cs b/TravelDistanceAnalyzer/dependencies/WallCandidate.g.cs new file mode 100644 index 00000000..0df991fb --- /dev/null +++ b/TravelDistanceAnalyzer/dependencies/WallCandidate.g.cs @@ -0,0 +1,56 @@ +//---------------------- +// +// Generated using the NJsonSchema v10.1.21.0 (Newtonsoft.Json v13.0.0.0) (http://NJsonSchema.org) +// +//---------------------- +using Elements; +using Elements.GeoJSON; +using Elements.Geometry; +using Elements.Geometry.Solids; +using Elements.Spatial; +using Elements.Validators; +using Elements.Serialization.JSON; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using Line = Elements.Geometry.Line; +using Polygon = Elements.Geometry.Polygon; + +namespace Elements +{ + #pragma warning disable // Disable all warnings + + /// Wall candidate + [JsonConverter(typeof(Elements.Serialization.JSON.JsonInheritanceConverter), "discriminator")] + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + public partial class WallCandidate : Element + { + [JsonConstructor] + public WallCandidate(Line @line, string @type, IList @spaceAdjacencies, System.Guid @id = default, string @name = null) + : base(id, name) + { + this.Line = @line; + this.Type = @type; + this.SpaceAdjacencies = @spaceAdjacencies; + } + + + // Empty constructor + public WallCandidate() + : base() + { + } + + [JsonProperty("Line", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public Line Line { get; set; } + + [JsonProperty("Type", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Type { get; set; } + + [JsonProperty("Space Adjacencies", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public IList SpaceAdjacencies { get; set; } + + + } +} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/global.json b/TravelDistanceAnalyzer/global.json new file mode 100644 index 00000000..4aef4472 --- /dev/null +++ b/TravelDistanceAnalyzer/global.json @@ -0,0 +1,7 @@ + +{ + "sdk": { + "version": "6.0.400", + "rollForward": "latestMinor" + } +} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/hypar.json b/TravelDistanceAnalyzer/hypar.json new file mode 100644 index 00000000..5e47b0a0 --- /dev/null +++ b/TravelDistanceAnalyzer/hypar.json @@ -0,0 +1,140 @@ +{ + "$schema": "https://hypar.io/Schemas/Function.json", + "id": "d9f36eba-8038-4a2e-929b-bae2edb824b1", + "name": "Travel Distance Analyzer", + "description": "The Travel Distance Analyzer function.", + "language": "C#", + "model_dependencies": [ + { + "autohide": false, + "name": "Space Planning Zones", + "optional": false + }, + { + "autohide": false, + "name": "Circulation", + "optional": false + }, + { + "autohide": false, + "name": "Doors", + "optional": true + }, + { + "autohide": false, + "name": "Interior Partitions", + "optional": true + } + ], + "input_schema": { + "type": "object", + "properties": {} + }, + "overrides": { + "Route Distance": { + "context": "[*discriminator=Elements.RouteDistanceConfiguration]", + "identity": { + "AddId": { + "type": "string" + } + }, + "paradigm": "edit", + "schema": { + "Destinations": { + "type": "array", + "items": { + "$ref": "https://hypar.io/Schemas/Geometry/Vector3.json" + } + }, + "Color": { + "$ref": "https://hypar.io/Schemas/Geometry/Color.json" + } + }, + "behaviors": { + "add": { + "schema": { + "Destinations": { + "type": "array", + "items": { + "$ref": "https://hypar.io/Schemas/Geometry/Vector3.json" + } + } + } + }, + "remove": true, + "revert": false + } + }, + "Walking Distance": { + "context": "[*discriminator=Elements.WalkingDistanceConfiguration]", + "identity": { + "AddId": { + "type": "string" + } + }, + "paradigm": "edit", + "schema": { + "Transform": { + "$ref": "https://hypar.io/Schemas/Geometry/Transform.json", + "$hyparConstraints": { + "enableRotation": false + } + }, + "Program Types": { + "type": "array", + "items": { + "type": [ + "string", + "null" + ], + "$hyparEnumQuery": { + "dependency": "Space Planning Zones", + "query": "[*discriminator=Elements.SpaceBoundary].Program Type" + } + } + }, + "Color": { + "$ref": "https://hypar.io/Schemas/Geometry/Color.json" + } + }, + "behaviors": { + "add": { + "schema": { + "Transform": { + "$ref": "https://hypar.io/Schemas/Geometry/Transform.json", + "$hyparConstraints": { + "enableRotation": false + } + }, + "Program Types": { + "type": "array", + "items": { + "type": [ + "string", + "null" + ], + "$hyparEnumQuery": { + "dependency": "Space Planning Zones", + "query": "[*discriminator=Elements.SpaceBoundary].Program Type" + } + } + } + } + }, + "remove": true, + "revert": false + } + } + }, + "outputs": [], + "element_types": [ + "https://schemas.hypar.io/SpaceBoundary.json", + "https://schemas.hypar.io/CirculationSegment.json", + "https://schemas.hypar.io/Door.json", + "https://schemas.hypar.io/WallCandidate.json", + "https://schemas.hypar.io/ThickenedPolyline.json" + ], + "repository_url": "https://github.com/hypar-io/function", + "last_updated": "0001-01-01T00:00:00", + "cli_version": "1.10.0" +} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs new file mode 100644 index 00000000..37bce0c3 --- /dev/null +++ b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs @@ -0,0 +1,509 @@ +using Elements; +using Elements.Geometry; +using Elements.Spatial.AdaptiveGrid; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using GridVertex = Elements.Spatial.AdaptiveGrid.Vertex; + +namespace TravelDistanceAnalyzer +{ + internal class AdaptiveGridBuilder + { + private const double RoomToWallTolerance = 1e-3; + + private AdaptiveGrid _grid; + + private Dictionary> _roomExits; + + public AdaptiveGrid Build(IEnumerable corridors, + IEnumerable rooms, + List? walls = null, + List? doors = null) + { + var centerlines = new List<(CirculationSegment Segment, Polyline Centerline)>(); + foreach (var item in corridors) + { + var centerLine = CorridorCenterLine(item); + if (centerLine != null && centerLine.Vertices.Count > 1) + { + centerlines.Add((item, centerLine)); + } + } + + _grid = new AdaptiveGrid(new Transform()); + + foreach (var line in centerlines) + { + _grid.AddVertices(line.Centerline.Vertices, + AdaptiveGrid.VerticesInsertionMethod.ConnectAndSelfIntersect); + } + + Intersect(centerlines); + Extend(centerlines); + + _roomExits = new Dictionary>(); + foreach (var room in rooms) + { + var exits = AddRoom(room, centerlines, walls, doors, _grid); + _roomExits.Add(room, exits); + } + + return _grid; + } + + + /// + /// Add end point to the grid that are close enough to any of existing edges. + /// + /// Exit points positions. + /// AdaptiveGrid to insert new vertices and edge into. + /// Ids of exit vertices that are added to the grid. + public ulong AddEndPoint(Vector3 exit, double snapDistance, out GridVertex closestVertex) + { + var edge = ClosestEdgeOnElevation(exit, out var closest); + closestVertex = null; + if (edge == null) + { + return 0u; + } + + var startVertex = _grid.GetVertex(edge.StartId); + var endVertex = _grid.GetVertex(edge.EndId); + + var exitOnLevel = new Vector3(exit.X, exit.Y, closest.Z); + var distance = exitOnLevel.DistanceTo(closest); + + if (closest.IsAlmostEqualTo(startVertex.Point, _grid.Tolerance)) + { + closestVertex = startVertex; + } + else if (closest.IsAlmostEqualTo(endVertex.Point, _grid.Tolerance)) + { + closestVertex = endVertex; + } + else + { + closestVertex = _grid.CutEdge(edge, closest); + } + + //Snap to existing vertex if it's close enough. + if (distance < snapDistance) + { + return closestVertex.Id; + } + else + { + var vertex = _grid.AddVertex(exitOnLevel, new ConnectVertexStrategy(closestVertex), cut: false); + return vertex.Id; + } + } + + public AdaptiveGrid Grid + { + get { return _grid; } + } + + public Dictionary> RoomExits + { + get { return _roomExits; } + } + + private Edge ClosestEdgeOnElevation(Vector3 location, out Vector3 point) + { + double lowestDist = double.MaxValue; + Edge closestEdge = null; + point = Vector3.Origin; + foreach (var e in Grid.GetEdges()) + { + var start = Grid.GetVertex(e.StartId); + var end = Grid.GetVertex(e.EndId); + if (Math.Abs(start.Point.Z - location.Z) > 0.5 || + Math.Abs(end.Point.Z - location.Z) > 0.5) + { + continue; + } + + double dist = location.DistanceTo((start.Point, end.Point), out var closest); + if (dist < lowestDist) + { + lowestDist = dist; + closestEdge = e; + point = closest; + } + } + return closestEdge; + } + + private Polyline CorridorCenterLine(CirculationSegment corridor) + { + double offsetDistance = corridor.Geometry.GetOffset(); + var corridorPolyline = corridor.Geometry.Polyline; + if (!offsetDistance.ApproximatelyEquals(0)) + { + corridorPolyline = corridorPolyline.OffsetOpen(offsetDistance); + } + corridorPolyline = corridorPolyline.TransformedPolyline(corridor.Transform); + return corridorPolyline; + } + + + /// + /// Create connection edges between corridors. + /// Corridors itself are represented as middle lines without width. + /// For each line points are found with other corridors and itself that are closer that their combined width. + /// + /// Corridor segments with precalculated center lines. + /// AdaptiveGrid to insert new vertices and edge into. + private void Intersect(List<(CirculationSegment Segment, Polyline Centerline)> centerlines) + { + foreach (var item in centerlines) + { + var leftVertices = item.Centerline.Vertices; + foreach (var candidate in centerlines) + { + var rightVertices = candidate.Centerline.Vertices; + var maxDistance = item.Segment.Geometry.GetWidth() + candidate.Segment.Geometry.GetWidth(); + for (int i = 0; i < leftVertices.Count - 1; i++) + { + Vector3 closestLeftItem = Vector3.Origin, closestRightItem = Vector3.Origin; + int closestLeftProximity = -1, closestRightProximity = -1; + double closestDistance = double.PositiveInfinity; + + Action check = + (Line line, Vector3 point, int leftIndex, int rightIndex, bool left) => + { + if (CanConnect(point, line, Math.Min(maxDistance, closestDistance), out var closest, out var d)) + { + closestDistance = d; + (closestLeftItem, closestRightItem) = left ? (closest, point) : (point, closest); + closestLeftProximity = leftIndex; + closestRightProximity = rightIndex; + } + }; + + Line leftLine = new Line(leftVertices[i], leftVertices[i + 1]); + for (int j = 0; j < rightVertices.Count - 1; j++) + { + if (item == candidate && Math.Abs(i - j) < 2) + { + continue; + } + + Line rightLine = new Line(rightVertices[j], rightVertices[j + 1]); + if (!leftLine.Intersects(rightLine, out var intersection)) + { + check(rightLine, leftLine.Start, i, j, false); + check(rightLine, leftLine.End, i, j, false); + check(leftLine, rightLine.Start, i, j, true); + check(leftLine, rightLine.End, i, j, true); + } + else + { + closestLeftItem = intersection; + closestRightItem = intersection; + closestLeftProximity = i; + closestRightProximity = j; + } + } + + if (closestLeftProximity == -1 || closestRightProximity == -1) + { + continue; + } + + bool leftExist = _grid.TryGetVertexIndex(closestLeftItem, out var leftId); + bool rightExist = _grid.TryGetVertexIndex(closestRightItem, out var rightId); + if (leftExist && rightExist) + { + if (leftId != rightId) + { + _grid.AddEdge(leftId, rightId); + } + } + else + { + GridVertex leftVertex = null; + if (!leftExist) + { + _grid.TryGetVertexIndex(leftVertices[closestLeftProximity], out var leftCon); + _grid.TryGetVertexIndex(leftVertices[closestLeftProximity + 1], out var rightCon); + var segment = new Line(leftVertices[closestLeftProximity], leftVertices[closestLeftProximity + 1]); + var vertex = _grid.GetVertex(leftCon); + while (vertex.Id != rightCon) + { + GridVertex otherVertex = null; + Edge edge = null; + foreach (var e in vertex.Edges) + { + otherVertex = _grid.GetVertex(e.OtherVertexId(vertex.Id)); + var edgeDirection = (otherVertex.Point - vertex.Point).Unitized(); + if (edgeDirection.Dot(segment.Direction()).ApproximatelyEquals(1)) + { + edge = e; + break; + } + } + + if (edge == null) + { + throw new Exception("End edge is not reached"); + } + + var edgeLine = new Line(vertex.Point, otherVertex.Point); + if (edgeLine.PointOnLine(closestLeftItem)) + { + leftVertex = _grid.CutEdge(edge, closestLeftItem); + break; + } + + vertex = otherVertex; + } + } + else + { + leftVertex = _grid.GetVertex(leftId); + } + + if (!rightExist) + { + _grid.TryGetVertexIndex(rightVertices[closestRightProximity], out var leftCon); + _grid.TryGetVertexIndex(rightVertices[closestRightProximity + 1], out var rightCon); + var vertex = _grid.GetVertex(leftCon); + var connections = new List(); + if (!closestLeftItem.IsAlmostEqualTo(closestRightItem, _grid.Tolerance)) + { + connections.Add(leftVertex); + } + + var segment = new Line(rightVertices[closestRightProximity], rightVertices[closestRightProximity + 1]); + while (vertex.Id != rightCon) + { + GridVertex otherVertex = null; + Edge edge = null; + foreach (var e in vertex.Edges) + { + otherVertex = _grid.GetVertex(e.OtherVertexId(vertex.Id)); + var edgeDirection = (otherVertex.Point - vertex.Point).Unitized(); + if (edgeDirection.Dot(segment.Direction()).ApproximatelyEquals(1)) + { + edge = e; + break; + } + } + + if (edge == null) + { + throw new Exception("End edge is not reached"); + } + + var edgeLine = new Line(vertex.Point, otherVertex.Point); + if (edgeLine.PointOnLine(closestRightItem)) + { + connections.Add(vertex); + connections.Add(otherVertex); + _grid.AddVertex(closestRightItem, + new ConnectVertexStrategy(connections.ToArray()), + cut: false); + _grid.RemoveEdge(edge); + break; + } + + vertex = otherVertex; + } + } + else if (leftVertex.Id != rightId) + { + _grid.AddEdge(leftVertex.Id, rightId); + } + } + } + } + } + } + private void Extend(List<(CirculationSegment Segment, Polyline Centerline)> centerlines) + { + foreach (var item in centerlines) + { + foreach (var candidate in centerlines) + { + if (item == candidate) + { + continue; + } + + var maxDistance = item.Segment.Geometry.GetWidth() + candidate.Segment.Geometry.GetWidth(); + foreach (var segment in item.Centerline.Segments()) + { + ExtendToCorridor(segment, candidate.Segment, maxDistance); + } + } + } + } + + private void ExtendToCorridor(Line l, CirculationSegment segment, double maxDistance) + { + foreach (var polygon in segment.Geometry.GetPolygons()) + { + var trimLine = new Line(l.Start - l.Direction() * maxDistance, + l.End + l.Direction() * maxDistance); + var inside = trimLine.Trim(polygon.offsetPolygon, out _); + foreach (var line in inside) + { + if (l.PointOnLine(line.Start) || l.PointOnLine(line.End)) + { + Grid.AddEdge(line.Start, line.End); + } + } + } + } + + /// + /// Add SpaceBoundary, representing a room, to the grid. + /// There are no defined exits. in the room. Every segment middle point is considered. + /// This is very simple approaches that ignores voids or obstacles inside room and won't work for complex rooms. + /// + /// Room geometry. + /// Corridor segments with precalculated center lines. + /// AdaptiveGrid to insert new vertices and edge into. + /// + private List AddRoom( + SpaceBoundary room, + List<(CirculationSegment Segment, Polyline Centerline)> centerlines, + List? walls, + List? doors, + AdaptiveGrid grid) + { + var roomExits = new List(); + var perimeter = room.Boundary.Perimeter.CollinearPointsRemoved().TransformedPolygon(room.Transform); + foreach (var roomEdge in perimeter.Segments()) + { + var exitVertex = FindRoomExit(roomEdge, centerlines, walls, doors, grid); + if (exitVertex != null) + { + roomExits.Add(exitVertex); + } + } + return roomExits; + } + + /// + /// Find if edge middle point is close enough to any corridor to be considered connected. + /// If point is closer then half corridor width then it's connected to closest point by new edge. + /// + /// Line representing room wall. + /// Corridor segments with precalculated center lines. + /// AdaptiveGrid to insert new vertices and edge into. + /// New Vertex on room edge midpoint. + private GridVertex FindRoomExit( + Line roomEdge, + List<(CirculationSegment Segment, Polyline Centerline)> centerlines, + List? walls, + List? doors, + AdaptiveGrid grid) + { + var door = doors?.FirstOrDefault(d => roomEdge.PointOnLine(d.Transform.Origin, false, RoomToWallTolerance)); + var wall = walls?.FirstOrDefault(w => w.Line.PointOnLine(roomEdge.Start, true, RoomToWallTolerance) && + w.Line.PointOnLine(roomEdge.End, true, RoomToWallTolerance)); + + // There are doors in the workflow and this segment is a wall without a door. + if (wall != null && doors != null && door == null) + { + return null; + } + + var midpoint = door?.Transform.Origin ?? roomEdge.Mid(); + + foreach (var line in centerlines) + { + for (int i = 0; i < line.Centerline.Vertices.Count - 1; i++) + { + var segment = new Line(line.Centerline.Vertices[i], line.Centerline.Vertices[i + 1]); + if (CanConnect(midpoint, segment, line.Segment.Geometry.GetWidth() / 2 + 0.1, out var closest, out _)) + { + GridVertex exitVertex = null; + grid.TryGetVertexIndex(segment.Start, out var id); + var vertex = grid.GetVertex(id); + + //We know corridor line but it can already be split into several edges. + //Need to find exact edge to insert new vertex into. + //First vertex corresponding start of the segment is found. + //Then, edges that do in the same direction as segment is traversed + //until target edge is found or end vertex is reached. + //This is much faster than traverse every single edge in the grid. + if (vertex.Point.IsAlmostEqualTo(closest, grid.Tolerance)) + { + exitVertex = vertex; + } + else + { + while (!vertex.Point.IsAlmostEqualTo(segment.End, grid.Tolerance)) + { + Edge edge = null; + GridVertex otherVertex = null; + foreach (var e in vertex.Edges) + { + otherVertex = grid.GetVertex(e.OtherVertexId(vertex.Id)); + var edgeDirection = (otherVertex.Point - vertex.Point).Unitized(); + if (edgeDirection.Dot(segment.Direction()).ApproximatelyEquals(1)) + { + edge = e; + break; + } + } + + if (edge == null) + { + break; + } + + if (otherVertex.Point.IsAlmostEqualTo(closest, grid.Tolerance)) + { + exitVertex = otherVertex; + break; + } + + var edgeLine = new Line(vertex.Point, otherVertex.Point); + if (edgeLine.PointOnLine(closest)) + { + exitVertex = grid.AddVertex(closest, + new ConnectVertexStrategy(vertex, otherVertex), + cut: false); + grid.RemoveEdge(edge); + } + vertex = otherVertex; + } + } + + if (exitVertex != null) + { + if (!exitVertex.Point.IsAlmostEqualTo(midpoint, grid.Tolerance)) + { + return grid.AddVertex(midpoint, new ConnectVertexStrategy(exitVertex)); + } + else + { + return exitVertex; + } + } + } + } + } + return null; + } + + private bool CanConnect(Vector3 point, + Line segment, + double maxDistance, + out Vector3 closest, + out double dist) + { + dist = point.DistanceTo(segment, out closest); + var dot = (closest - point).Unitized().Dot(segment.Direction()); + bool aligned = dot.ApproximatelyEquals(0) || Math.Abs(dot).ApproximatelyEquals(1); + return dist < maxDistance && aligned; + } + } +} diff --git a/TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs b/TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs new file mode 100644 index 00000000..a6658758 --- /dev/null +++ b/TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs @@ -0,0 +1,38 @@ +using Elements.Spatial.AdaptiveGrid; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using GridVertex = Elements.Spatial.AdaptiveGrid.Vertex; + +namespace TravelDistanceAnalyzer +{ + internal static class AdaptiveGridExtensions + { + public static double CalculateDistanceRecursive( + this AdaptiveGrid grid, + GridVertex head, + IDictionary tree, + Dictionary accumulatedDistances) + { + var node = tree[head.Id]; + if (node == null || node.Trunk == null) + { + return 0; + } + + var edge = head.GetEdge(node.Trunk.Id); + if (accumulatedDistances.TryGetValue(edge, out double distance)) + { + return distance; + } + + var tail = grid.GetVertex(node.Trunk.Id); + var d = CalculateDistanceRecursive(grid, tail, tree, accumulatedDistances); + d += tail.Point.DistanceTo(head.Point); + accumulatedDistances[edge] = d; + return d; + } + } +} diff --git a/TravelDistanceAnalyzer/src/ColorFactory.cs b/TravelDistanceAnalyzer/src/ColorFactory.cs new file mode 100644 index 00000000..24c42381 --- /dev/null +++ b/TravelDistanceAnalyzer/src/ColorFactory.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using Elements.Geometry; + +namespace TravelDistanceAnalyzer +{ + internal static class ColorFactory + { + public static Color FromGuid(string guid) + { + using (MD5 md5 = MD5.Create()) + { + byte[] inputBytes = Encoding.UTF8.GetBytes(guid); + byte[] hashBytes = md5.ComputeHash(inputBytes); + + byte r = hashBytes[0]; + byte g = hashBytes[1]; + byte b = hashBytes[2]; + + return new Color(r / 255.0, g / 255.0, b / 255.0, 1d); + } + } + } +} diff --git a/TravelDistanceAnalyzer/src/Function.g.cs b/TravelDistanceAnalyzer/src/Function.g.cs new file mode 100644 index 00000000..a504ddd4 --- /dev/null +++ b/TravelDistanceAnalyzer/src/Function.g.cs @@ -0,0 +1,73 @@ +// This code was generated by Hypar. +// Edits to this code will be overwritten the next time you run 'hypar init'. +// DO NOT EDIT THIS FILE. + +using Amazon.Lambda.Core; +using Hypar.Functions.Execution; +using Hypar.Functions.Execution.AWS; +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] +namespace TravelDistanceAnalyzer +{ + public class Function + { + // Cache the model store for use by subsequent + // executions of this lambda. + private UrlModelStore store; + + public async Task Handler(TravelDistanceAnalyzerInputs args) + { + // Preload dependencies (if they exist), + // so that they are available during model deserialization. + + var sw = System.Diagnostics.Stopwatch.StartNew(); + var asmLocation = this.GetType().Assembly.Location; + var asmDir = Path.GetDirectoryName(asmLocation); + + // Explicitly load the dependencies project, it might have types + // that aren't used in the function but are necessary for correct + // deserialization. + var asmName = Path.GetFileNameWithoutExtension(asmLocation); + var depPath = Path.Combine(asmDir, $"{asmName}.Dependencies.dll"); + if(File.Exists(depPath)) + { + Console.WriteLine($"Loading dependencies assembly from: {depPath}..."); + Assembly.LoadFrom(depPath); + Console.WriteLine("Dependencies assembly loaded."); + } + + // Load all reference assemblies. + Console.WriteLine($"Loading all referenced assemblies."); + foreach (var asm in this.GetType().Assembly.GetReferencedAssemblies()) + { + try + { + Console.WriteLine($"Assembly Name: {asm.FullName}"); + Assembly.Load(asm); + } + catch (Exception e) + { + Console.WriteLine($"Failed to load {asm.FullName}"); + Console.WriteLine(e.Message); + } + } + sw.Stop(); + Console.WriteLine($"Time to load assemblies: {sw.Elapsed.TotalSeconds})"); + + if(this.store == null) + { + this.store = new UrlModelStore(); + } + + + var l = new InvocationWrapper (store, TravelDistanceAnalyzer.Execute); + var output = await l.InvokeAsync(args); + return output; + } + } +} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs b/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs new file mode 100644 index 00000000..af9baac1 --- /dev/null +++ b/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs @@ -0,0 +1,198 @@ +using Elements; +using Elements.Geometry; +using Elements.Spatial.AdaptiveGrid; +using TravelDistanceAnalyzer; +using GridVertex = Elements.Spatial.AdaptiveGrid.Vertex; + +namespace Elements +{ + internal class RouteDistanceConfiguration : GeometricElement + { + public string AddId; + + public List Destinations; + + public RouteDistanceConfiguration(string addId, IList destinations) + { + AddId = addId; + Destinations = destinations.ToList(); + var color = ColorFactory.FromGuid(AddId); + Material = new Material("Route", color); + } + + public override void UpdateRepresentations() + { + Representation = new Representation(); + + foreach (var point in Destinations) + { + var shape = new Circle(point, 0.25).ToPolygon(8); + Representation.SolidOperations.Add(new Geometry.Solids.Extrude( + shape, 0.5, Vector3.ZAxis)); + } + } + + public double Distance { get; set; } + + public Color Color + { + get { return Material.Color; } + set { Material.Color = value; } + } + + public List Compute(AdaptiveGridBuilder builder) + { + List additionalVisualization = new List(); + if (Destinations.Count < 2) + { + return additionalVisualization; + } + + ulong start = builder.AddEndPoint(Destinations[0], 0.25, out var connection); + var grid = builder.Grid; + var startVertex = grid.GetVertex(start); + AdditionalConnections(grid, startVertex, connection); + ulong end; + double distance = 0; + //Update positions is case exit is snapped + Destinations[0] = startVertex.Point; + + for (int i = 1; i < Destinations.Count; i++) + { + end = builder.AddEndPoint(Destinations[i], 0.25, out connection); + var endVertex = grid.GetVertex(end); + AdditionalConnections(grid, endVertex, connection); + Destinations[i] = endVertex.Point; + var alg = new AdaptiveGraphRouting(grid, new RoutingConfiguration()); + var leafs = new List { new RoutingVertex(start, 0) }; + var trunks = new List { end }; + var tree = alg.BuildSimpleNetwork(leafs, trunks, null); + + //Distances are caches in Dictionary. It's mostly to draw edge lines only once as + //distance calculations are cheap. + startVertex = grid.GetVertex(start); + Dictionary accumulatedDistances = new Dictionary(); + grid.CalculateDistanceRecursive(startVertex, tree, accumulatedDistances); + var next = tree[startVertex.Id]; + if (next != null && next.Trunk != null) + { + var edge = startVertex.GetEdge(tree[startVertex.Id].Trunk.Id); + distance += accumulatedDistances[edge]; + } + + double VisualizationHeight = 1.5; + var t = new Transform(0, 0, VisualizationHeight); + foreach (var item in accumulatedDistances) + { + var v0 = grid.GetVertex(item.Key.StartId); + var v1 = grid.GetVertex(item.Key.EndId); + var shape = new Line(v0.Point, v1.Point); + var modelCurve = new ModelCurve(shape, Material, t); + modelCurve.SetSelectable(false); + additionalVisualization.Add(modelCurve); + } + + start = end; + } + Distance = distance; + additionalVisualization.Add(DestinationLabels()); + return additionalVisualization; + } + + private static void AdditionalConnections(AdaptiveGrid grid, + GridVertex exit, + GridVertex mainConnection) + { + var basePoint = exit.Point; + var maxDist = basePoint.DistanceTo(mainConnection.Point) * 2; + var additionalConnections = new (double Distance, Vector3 Point)[4] + { + (double.MaxValue, Vector3.Origin), + (double.MaxValue, Vector3.Origin), + (double.MaxValue, Vector3.Origin), + (double.MaxValue, Vector3.Origin) + }; + + var xDir = (mainConnection.Point - basePoint).Unitized(); + var yDir = xDir.Cross(Vector3.ZAxis); + + foreach (var edge in grid.GetEdges()) + { + if (edge.StartId == mainConnection.Id || edge.EndId == mainConnection.Id) + { + continue; + } + + var start = grid.GetVertex(edge.StartId); + var end = grid.GetVertex(edge.EndId); + + var line = new Line(start.Point, end.Point); + var closest = exit.Point.ClosestPointOn(line); + var delta = closest - basePoint; + var length = delta.Length(); + if (length > maxDist) + { + continue; + } + + var directionIndex = -1; + //if (Vector3.AreCollinearByAngle(basePoint + xDir, basePoint, closest, 0.01)) + { + var dot = delta.Unitized().Dot(xDir); + if (dot.ApproximatelyEquals(1, 0.01)) + { + directionIndex = 0; + } + else if (dot.ApproximatelyEquals(-1, 0.01)) + { + directionIndex = 1; + } + } + //else if (Vector3.AreCollinearByAngle(basePoint + yDir, basePoint, closest, 0.01)) + { + var dot = delta.Unitized().Dot(yDir); + if (dot.ApproximatelyEquals(1, 0.01)) + { + directionIndex = 2; + } + else if (dot.ApproximatelyEquals(-1, 0.01)) + { + directionIndex = 3; + } + } + + if (directionIndex >= 0) + { + var connection = additionalConnections[directionIndex]; + if (length < connection.Distance) + { + additionalConnections[directionIndex] = (length, closest); + } + } + } + + foreach (var connection in additionalConnections) + { + if (connection.Distance != double.MaxValue) + { + grid.AddEdge(exit.Point, connection.Point); + } + } + } + + private ModelText DestinationLabels() + { + var texts = new List<(Vector3 Location, Vector3 FacingDirection, Vector3 LineDirection, string Text, Color? Color)>(); + for (int i = 0; i < Destinations.Count; i++) + { + texts.Add((Destinations[i] + new Vector3(0, 0, 0.5), + Vector3.ZAxis, + Vector3.XAxis, + (i + 1).ToString(), + Colors.Black)); + } + return new ModelText(texts, FontSize.PT72); + } + + } +} diff --git a/TravelDistanceAnalyzer/src/ThickenedPolylineExtensions.cs b/TravelDistanceAnalyzer/src/ThickenedPolylineExtensions.cs new file mode 100644 index 00000000..e6ac5bcb --- /dev/null +++ b/TravelDistanceAnalyzer/src/ThickenedPolylineExtensions.cs @@ -0,0 +1,17 @@ +using Elements.Geometry; + +namespace TravelDistanceAnalyzer +{ + public static class ThickenedPolylineExtensions + { + public static double GetWidth(this ThickenedPolyline polyline) + { + return polyline.LeftWidth + polyline.RightWidth; + } + + public static double GetOffset(this ThickenedPolyline polyline) + { + return (polyline.RightWidth - polyline.LeftWidth) / 2; + } + } +} diff --git a/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs b/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs new file mode 100644 index 00000000..9fa00c93 --- /dev/null +++ b/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs @@ -0,0 +1,137 @@ +using Elements; +using Elements.Geometry; +using Elements.Spatial.AdaptiveGrid; +using AdaptiveGraphRouting = Elements.Spatial.AdaptiveGrid.AdaptiveGraphRouting; + +namespace TravelDistanceAnalyzer +{ + public static class TravelDistanceAnalyzer + { + /// + /// The TravelDistanceAnalyzer function. + /// + /// The input model. + /// The arguments to the execution. + /// A TravelDistanceAnalyzerOutputs instance containing computed results and the model with any new elements. + public static TravelDistanceAnalyzerOutputs Execute(Dictionary inputModels, TravelDistanceAnalyzerInputs input) + { + var output = new TravelDistanceAnalyzerOutputs(); + if (!inputModels.TryGetValue("Space Planning Zones", out var spaceZonesModel)) + { + output.Errors.Add("The model output named 'Space Planning Zones' could not be found. Check the upstream functions for errors."); + return output; + } + + if (!inputModels.TryGetValue("Circulation", out var circulationModel)) + { + output.Errors.Add("The model output named 'Circulation' could not be found. Check the upstream functions for errors."); + return output; + } + + var corridors = circulationModel.AllElementsOfType(); + var rooms = spaceZonesModel.AllElementsOfType(); + + List? doors = null; + if (inputModels.TryGetValue("Doors", out var doorsModel)) + { + doors = doorsModel.AllElementsOfType().ToList(); + } + + List? walls = null; + if (inputModels.TryGetValue("Interior Partitions", out var wallsModel)) + { + walls = wallsModel.AllElementsOfType().ToList(); + } + + var corridorsByLevel = corridors.GroupBy(c => c.Level); + var roomsByLevel = rooms.GroupBy(r => r.Level); + + var walkingDistanceConfigs = CreateWalkingDistanceConfigurations(input.Overrides); + var routeDistanceConfigs = CreateRoutingDistanceConfigurations(input.Overrides); + + foreach (var levelRooms in roomsByLevel) + { + var levelCorridors = corridorsByLevel.Where(c => c.Key == levelRooms.Key).FirstOrDefault(); + if (levelCorridors == null) + { + continue; + } + + var builder = new AdaptiveGridBuilder(); + builder.Build(levelCorridors, levelRooms, walls, doors); + + foreach (var config in walkingDistanceConfigs) + { + output.Model.AddElements(config.Compute(builder)); + } + + foreach (var config in routeDistanceConfigs) + { + output.Model.AddElements(config.Compute(builder)); + } + + //Grid visualization for debug purposes + var a = new AdaptiveGraphRouting(builder.Grid, new RoutingConfiguration()); + var elements = a.RenderElements( + new List(), + new List()); + output.Model.AddElements(elements); + } + + output.Model.AddElements(walkingDistanceConfigs); + output.Model.AddElements(routeDistanceConfigs); + return output; + } + + private static List CreateRoutingDistanceConfigurations(Overrides overrides) + { + var routeDistanceConfigs = RouteDistanceOverrideExtensions.CreateElements( + overrides.RouteDistance, + overrides.Additions.RouteDistance, + overrides.Removals.RouteDistance, + (add) => + { + RouteDistanceConfiguration config = new RouteDistanceConfiguration( + add.Id, add.Value.Destinations); + return config; + }, + (elem, identity) => + { + return elem.AddId == identity.AddId; + }, + (elem, edit) => + { + elem.Destinations = edit.Value.Destinations.ToList(); + elem.Color = edit.Value.Color; + return elem; + }); + return routeDistanceConfigs; + } + + private static List CreateWalkingDistanceConfigurations(Overrides overrides) + { + var walkingDistanceConfigs = WalkingDistanceOverrideExtensions.CreateElements( + overrides.WalkingDistance, + overrides.Additions.WalkingDistance, + overrides.Removals.WalkingDistance, + (add) => + { + WalkingDistanceConfiguration config = new WalkingDistanceConfiguration( + add.Id, add.Value.ProgramTypes, add.Value.Transform); + return config; + }, + (elem, identity) => + { + return elem.AddId == identity.AddId; + }, + (elem, edit) => + { + elem.Transform = edit.Value.Transform; + elem.ProgramTypes = edit.Value.ProgramTypes.ToList(); + elem.Color = edit.Value.Color; + return elem; + }); + return walkingDistanceConfigs; + } + } +} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.csproj b/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.csproj new file mode 100644 index 00000000..3436a84d --- /dev/null +++ b/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.csproj @@ -0,0 +1,13 @@ + + + + + + + + net6.0 + enable + enable + + + diff --git a/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs b/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs new file mode 100644 index 00000000..80038071 --- /dev/null +++ b/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs @@ -0,0 +1,188 @@ +using Elements; +using Elements.Geometry; +using Elements.Spatial.AdaptiveGrid; +using Newtonsoft.Json; +using TravelDistanceAnalyzer; +using GridVertex = Elements.Spatial.AdaptiveGrid.Vertex; + +namespace Elements +{ + internal class WalkingDistanceConfiguration : GeometricElement + { + public string AddId; + + [JsonProperty("Program Types")] + public List ProgramTypes; + + public WalkingDistanceConfiguration(string addId, IList programTypes, Transform transform) + { + AddId = addId; + Transform = transform; + + if (programTypes != null) + { + ProgramTypes = programTypes.ToList(); + } + else + { + ProgramTypes = new List(); + } + + var color = ColorFactory.FromGuid(AddId); + Material = new Material("Route", color); + } + + public override void UpdateRepresentations() + { + Representation = new Representation(); + + var shape = new Polygon((-0.1, -0.1), (-0.25, 0), (-0.1, 0.1), (0, 0.25), + (0.1, 0.1), (0.25, 0), (0.1, -0.1), (0, -0.25)); + Representation.SolidOperations.Add(new Geometry.Solids.Extrude( + shape, 0.5, Vector3.ZAxis)); + } + + public List Statistics { get; set; } + + public Color Color + { + get { return Material.Color; } + set { Material.Color = value; } + } + + public List Compute(AdaptiveGridBuilder builder) + { + List additionalVisualization = new List(); + var exit = builder.AddEndPoint(Transform.Origin, 0.25, out _); + if (exit == 0) + { + return additionalVisualization; + } + + var grid = builder.Grid; + + //Update positions is case exit is snapped + Transform = new Transform(grid.GetVertex(exit).Point); + + var alg = new AdaptiveGraphRouting(grid, new RoutingConfiguration()); + + var exits = new List { exit }; + var filteredRooms = builder.RoomExits.Where( + kvp => !ProgramTypes.Any() || ProgramTypes.Contains(kvp.Key.ProgramType)); + var roomExits = filteredRooms.SelectMany(kvp => kvp.Value.Select( + v => new RoutingVertex(v.Id, 0))); + + var tree = alg.BuildSimpleNetwork(roomExits.ToList(), exits, null); + + List<(SpaceBoundary Room, GridVertex Exit)> bestExits = new(); + foreach (var room in filteredRooms) + { + var bestExit = ChooseExit(grid, tree, room.Value); + if (bestExit != null) + { + bestExits.Add((room.Key, bestExit)); + } + } + + additionalVisualization.AddRange(CalculateDistances(grid, bestExits, tree)); + return additionalVisualization; + } + + private List CalculateDistances( + AdaptiveGrid grid, + List<(SpaceBoundary Room, GridVertex Exit)> inputs, + IDictionary tree) + { + Dictionary accumulatedDistances = new Dictionary(); + Dictionary statisticsByType = new(); + foreach (var input in inputs) + { + //Distances are caches in Dictionary. It's mostly to draw edge lines only once as + //distance calculations are cheap. + grid.CalculateDistanceRecursive(input.Exit, tree, accumulatedDistances); + var next = tree[input.Exit.Id]; + if (next != null && next.Trunk != null) + { + var edge = input.Exit.GetEdge(tree[input.Exit.Id].Trunk.Id); + var distance = accumulatedDistances[edge]; + var type = input.Room.ProgramType; + if (statisticsByType.TryGetValue(type, out var value)) + { + value.Num++; + value.Stat.LongestDistance = Math.Max(value.Stat.LongestDistance, distance); + value.Stat.ShortestDistance = Math.Min(value.Stat.ShortestDistance, distance); + value.Stat.AverageDistance += distance; + } + else + { + statisticsByType[type] = (new WalkingDistanceStatistics(type, distance, distance, distance), 1); + } + } + } + + foreach (var item in statisticsByType) + { + item.Value.Stat.AverageDistance /= item.Value.Num; + } + + Statistics = statisticsByType.Select(s => s.Value.Stat).ToList(); + + double VisualizationHeight = 1.5; + var t = new Transform(0, 0, VisualizationHeight); + List visualizations = new List(); + foreach (var item in accumulatedDistances) + { + var start = grid.GetVertex(item.Key.StartId); + var end = grid.GetVertex(item.Key.EndId); + var shape = new Line(start.Point, end.Point); + var modelCurve = new ModelCurve(shape, Material, t); + modelCurve.SetSelectable(false); + visualizations.Add(modelCurve); + } + return visualizations; + } + + /// + /// For each room find exit that provides smallest distance. + /// + /// AdaptiveGrid to traverse. + /// Traveling tree from rooms corners to exits. + /// Combinations of exits and their corresponding corners for each room. + /// Most distance efficient exit. + private static GridVertex ChooseExit( + AdaptiveGrid grid, + IDictionary tree, + List exits) + { + if (exits.Count == 1) + { + return exits.First(); + } + else + { + double bestLength = double.MaxValue; + GridVertex bestExit = null; + foreach (var exit in exits) + { + double accumulatedLength = 0; + var current = tree[exit.Id]; + while (current.Trunk != null) + { + var p0 = grid.GetVertex(current.Id).Point; + var p1 = grid.GetVertex(current.Trunk.Id).Point; + accumulatedLength += p0.DistanceTo(p1); + current = current.Trunk; + } + + if (accumulatedLength < bestLength) + { + bestLength = accumulatedLength; + bestExit = exit; + } + } + + return bestExit; + } + } + } +} diff --git a/TravelDistanceAnalyzer/src/WalkingDistanceStatistics.cs b/TravelDistanceAnalyzer/src/WalkingDistanceStatistics.cs new file mode 100644 index 00000000..6a897f0e --- /dev/null +++ b/TravelDistanceAnalyzer/src/WalkingDistanceStatistics.cs @@ -0,0 +1,29 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace Elements +{ + public class WalkingDistanceStatistics : Element + { + public WalkingDistanceStatistics(string spaceType, double closest, double longest, double average) + { + ProgramType = spaceType; + ShortestDistance = closest; + LongestDistance = longest; + AverageDistance = average; + } + + public string ProgramType { get; set; } + + public double ShortestDistance { get; set; } + + public double LongestDistance { get; set; } + + public double AverageDistance { get; set; } + } +} diff --git a/TravelDistanceAnalyzer/test/FunctionTest.g.cs b/TravelDistanceAnalyzer/test/FunctionTest.g.cs new file mode 100644 index 00000000..a879ca5d --- /dev/null +++ b/TravelDistanceAnalyzer/test/FunctionTest.g.cs @@ -0,0 +1,24 @@ +// This code was generated by Hypar. +// Edits to this code will be overwritten the next time you run 'hypar init'. +// DO NOT EDIT THIS FILE. + +using Xunit; +using System.IO; +using System.Reflection; +using System.Threading.Tasks; +using Xunit.Abstractions; +using System; +using System.Collections.Generic; + +namespace TravelDistanceAnalyzer.Tests +{ + public class FunctionTests + { + private readonly ITestOutputHelper output; + + public FunctionTests(ITestOutputHelper output) + { + this.output = output; + } + } +} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/test/TravelDistanceAnalyzer.Tests.csproj b/TravelDistanceAnalyzer/test/TravelDistanceAnalyzer.Tests.csproj new file mode 100644 index 00000000..9c88130c --- /dev/null +++ b/TravelDistanceAnalyzer/test/TravelDistanceAnalyzer.Tests.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + enable + enable + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/TravelDistanceAnalyzer/test/Usings.cs b/TravelDistanceAnalyzer/test/Usings.cs new file mode 100644 index 00000000..8c927eb7 --- /dev/null +++ b/TravelDistanceAnalyzer/test/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file From d28941e8733effd8a8534433cc8e8c4b5869c2f7 Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Fri, 1 Dec 2023 14:22:45 +0200 Subject: [PATCH 02/21] Add minimal cost to the turns --- TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs | 2 +- TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs b/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs index af9baac1..084c649f 100644 --- a/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs +++ b/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs @@ -63,7 +63,7 @@ public List Compute(AdaptiveGridBuilder builder) var endVertex = grid.GetVertex(end); AdditionalConnections(grid, endVertex, connection); Destinations[i] = endVertex.Point; - var alg = new AdaptiveGraphRouting(grid, new RoutingConfiguration()); + var alg = new AdaptiveGraphRouting(grid, new RoutingConfiguration(turnCost: 0.01)); var leafs = new List { new RoutingVertex(start, 0) }; var trunks = new List { end }; var tree = alg.BuildSimpleNetwork(leafs, trunks, null); diff --git a/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs b/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs index 80038071..5bd265b1 100644 --- a/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs +++ b/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs @@ -64,7 +64,7 @@ public List Compute(AdaptiveGridBuilder builder) //Update positions is case exit is snapped Transform = new Transform(grid.GetVertex(exit).Point); - var alg = new AdaptiveGraphRouting(grid, new RoutingConfiguration()); + var alg = new AdaptiveGraphRouting(grid, new RoutingConfiguration(turnCost: 0.01)); var exits = new List { exit }; var filteredRooms = builder.RoomExits.Where( From 58248b28211046ebf99d07fa97f4c971fde30da1 Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Fri, 1 Dec 2023 17:51:55 +0200 Subject: [PATCH 03/21] All Level support --- .../src/AdaptiveGridBuilder.cs | 3 +- .../src/AdaptiveGridExtensions.cs | 35 +++++++- .../src/RouteDistanceConfiguration.cs | 73 ++++++++-------- .../src/TravelDistanceAnalyzer.cs | 9 +- .../src/WalkingDistanceConfiguration.cs | 84 +++++++++---------- 5 files changed, 117 insertions(+), 87 deletions(-) diff --git a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs index 37bce0c3..6cdfff6b 100644 --- a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs +++ b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs @@ -347,9 +347,10 @@ private void ExtendToCorridor(Line l, CirculationSegment segment, double maxDist { foreach (var polygon in segment.Geometry.GetPolygons()) { + var transformedPolygon = polygon.offsetPolygon.TransformedPolygon(segment.Transform); var trimLine = new Line(l.Start - l.Direction() * maxDistance, l.End + l.Direction() * maxDistance); - var inside = trimLine.Trim(polygon.offsetPolygon, out _); + var inside = trimLine.Trim(transformedPolygon, out _); foreach (var line in inside) { if (l.PointOnLine(line.Start) || l.PointOnLine(line.End)) diff --git a/TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs b/TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs index a6658758..7c40b6ee 100644 --- a/TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs +++ b/TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs @@ -1,4 +1,6 @@ -using Elements.Spatial.AdaptiveGrid; +using Elements; +using Elements.Geometry; +using Elements.Spatial.AdaptiveGrid; using System; using System.Collections.Generic; using System.Linq; @@ -10,6 +12,19 @@ namespace TravelDistanceAnalyzer { internal static class AdaptiveGridExtensions { + public static Dictionary ComputeDistances( + this AdaptiveGrid grid, + IEnumerable leafs, + IDictionary tree) + { + Dictionary accumulatedDistances = new Dictionary(); + foreach (var leaf in leafs) + { + grid.CalculateDistanceRecursive(leaf, tree, accumulatedDistances); + } + return accumulatedDistances; + } + public static double CalculateDistanceRecursive( this AdaptiveGrid grid, GridVertex head, @@ -34,5 +49,23 @@ public static double CalculateDistanceRecursive( accumulatedDistances[edge] = d; return d; } + + public static ModelLines TreeVisualization(this AdaptiveGrid grid, + IEnumerable edges, + double elevation, + Material material) + { + List lines = new List(); + foreach (var item in edges) + { + var start = grid.GetVertex(item.StartId); + var end = grid.GetVertex(item.EndId); + var shape = new Line(start.Point, end.Point); + lines.Add(shape); + } + ModelLines modelLines = new ModelLines(lines, material, new Transform(0, 0, elevation)); + modelLines.SetSelectable(false); + return modelLines; + } } } diff --git a/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs b/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs index 084c649f..95e36994 100644 --- a/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs +++ b/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs @@ -12,6 +12,9 @@ internal class RouteDistanceConfiguration : GeometricElement public List Destinations; + private double _snapingDistance = 0.25; + private double _routeHeight = 1; + public RouteDistanceConfiguration(string addId, IList destinations) { AddId = addId; @@ -28,7 +31,7 @@ public override void UpdateRepresentations() { var shape = new Circle(point, 0.25).ToPolygon(8); Representation.SolidOperations.Add(new Geometry.Solids.Extrude( - shape, 0.5, Vector3.ZAxis)); + shape, _routeHeight, Vector3.ZAxis)); } } @@ -48,8 +51,10 @@ public List Compute(AdaptiveGridBuilder builder) return additionalVisualization; } - ulong start = builder.AddEndPoint(Destinations[0], 0.25, out var connection); + ulong start = builder.AddEndPoint(Destinations[0], _snapingDistance, out var connection); var grid = builder.Grid; + + var startVertex = grid.GetVertex(start); AdditionalConnections(grid, startVertex, connection); ulong end; @@ -59,7 +64,7 @@ public List Compute(AdaptiveGridBuilder builder) for (int i = 1; i < Destinations.Count; i++) { - end = builder.AddEndPoint(Destinations[i], 0.25, out connection); + end = builder.AddEndPoint(Destinations[i], _snapingDistance, out connection); var endVertex = grid.GetVertex(end); AdditionalConnections(grid, endVertex, connection); Destinations[i] = endVertex.Point; @@ -80,17 +85,9 @@ public List Compute(AdaptiveGridBuilder builder) distance += accumulatedDistances[edge]; } - double VisualizationHeight = 1.5; - var t = new Transform(0, 0, VisualizationHeight); - foreach (var item in accumulatedDistances) - { - var v0 = grid.GetVertex(item.Key.StartId); - var v1 = grid.GetVertex(item.Key.EndId); - var shape = new Line(v0.Point, v1.Point); - var modelCurve = new ModelCurve(shape, Material, t); - modelCurve.SetSelectable(false); - additionalVisualization.Add(modelCurve); - } + //TODO: height on level can have extra elevation. + additionalVisualization.Add(grid.TreeVisualization( + accumulatedDistances.Keys, _routeHeight, Material)); start = end; } @@ -99,9 +96,14 @@ public List Compute(AdaptiveGridBuilder builder) return additionalVisualization; } - private static void AdditionalConnections(AdaptiveGrid grid, - GridVertex exit, - GridVertex mainConnection) + public bool OnElevation(double elevation) + { + return Destinations.All(d => d.Z.ApproximatelyEquals(elevation)); + } + + private void AdditionalConnections(AdaptiveGrid grid, + GridVertex exit, + GridVertex mainConnection) { var basePoint = exit.Point; var maxDist = basePoint.DistanceTo(mainConnection.Point) * 2; @@ -136,29 +138,24 @@ private static void AdditionalConnections(AdaptiveGrid grid, } var directionIndex = -1; - //if (Vector3.AreCollinearByAngle(basePoint + xDir, basePoint, closest, 0.01)) + var dot = delta.Unitized().Dot(xDir); + if (dot.ApproximatelyEquals(1, 0.01)) { - var dot = delta.Unitized().Dot(xDir); - if (dot.ApproximatelyEquals(1, 0.01)) - { - directionIndex = 0; - } - else if (dot.ApproximatelyEquals(-1, 0.01)) - { - directionIndex = 1; - } + directionIndex = 0; } - //else if (Vector3.AreCollinearByAngle(basePoint + yDir, basePoint, closest, 0.01)) + else if (dot.ApproximatelyEquals(-1, 0.01)) { - var dot = delta.Unitized().Dot(yDir); - if (dot.ApproximatelyEquals(1, 0.01)) - { - directionIndex = 2; - } - else if (dot.ApproximatelyEquals(-1, 0.01)) - { - directionIndex = 3; - } + directionIndex = 1; + } + + dot = delta.Unitized().Dot(yDir); + if (dot.ApproximatelyEquals(1, 0.01)) + { + directionIndex = 2; + } + else if (dot.ApproximatelyEquals(-1, 0.01)) + { + directionIndex = 3; } if (directionIndex >= 0) @@ -185,7 +182,7 @@ private ModelText DestinationLabels() var texts = new List<(Vector3 Location, Vector3 FacingDirection, Vector3 LineDirection, string Text, Color? Color)>(); for (int i = 0; i < Destinations.Count; i++) { - texts.Add((Destinations[i] + new Vector3(0, 0, 0.5), + texts.Add((Destinations[i] + new Vector3(0, 0, _routeHeight + Vector3.EPSILON), Vector3.ZAxis, Vector3.XAxis, (i + 1).ToString(), diff --git a/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs b/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs index 9fa00c93..347136d7 100644 --- a/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs +++ b/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs @@ -52,7 +52,8 @@ public static TravelDistanceAnalyzerOutputs Execute(Dictionary in foreach (var levelRooms in roomsByLevel) { var levelCorridors = corridorsByLevel.Where(c => c.Key == levelRooms.Key).FirstOrDefault(); - if (levelCorridors == null) + if (levelCorridors == null || !levelCorridors.Any() || + roomsByLevel == null || !roomsByLevel.Any()) { continue; } @@ -60,12 +61,14 @@ public static TravelDistanceAnalyzerOutputs Execute(Dictionary in var builder = new AdaptiveGridBuilder(); builder.Build(levelCorridors, levelRooms, walls, doors); - foreach (var config in walkingDistanceConfigs) + var elevation = levelCorridors.First().Elevation; + + foreach (var config in walkingDistanceConfigs.Where(c => c.OnElevation(elevation))) { output.Model.AddElements(config.Compute(builder)); } - foreach (var config in routeDistanceConfigs) + foreach (var config in routeDistanceConfigs.Where(c => c.OnElevation(elevation))) { output.Model.AddElements(config.Compute(builder)); } diff --git a/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs b/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs index 5bd265b1..96438493 100644 --- a/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs +++ b/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs @@ -14,6 +14,9 @@ internal class WalkingDistanceConfiguration : GeometricElement [JsonProperty("Program Types")] public List ProgramTypes; + private double _snapingDistance = 0.25; + private double _routeHeight = 1; + public WalkingDistanceConfiguration(string addId, IList programTypes, Transform transform) { AddId = addId; @@ -30,6 +33,7 @@ public WalkingDistanceConfiguration(string addId, IList programTypes, Tr var color = ColorFactory.FromGuid(AddId); Material = new Material("Route", color); + Statistics = new(); } public override void UpdateRepresentations() @@ -39,7 +43,7 @@ public override void UpdateRepresentations() var shape = new Polygon((-0.1, -0.1), (-0.25, 0), (-0.1, 0.1), (0, 0.25), (0.1, 0.1), (0.25, 0), (0.1, -0.1), (0, -0.25)); Representation.SolidOperations.Add(new Geometry.Solids.Extrude( - shape, 0.5, Vector3.ZAxis)); + shape, _routeHeight, Vector3.ZAxis)); } public List Statistics { get; set; } @@ -53,7 +57,7 @@ public Color Color public List Compute(AdaptiveGridBuilder builder) { List additionalVisualization = new List(); - var exit = builder.AddEndPoint(Transform.Origin, 0.25, out _); + var exit = builder.AddEndPoint(Transform.Origin, _snapingDistance, out _); if (exit == 0) { return additionalVisualization; @@ -84,22 +88,30 @@ public List Compute(AdaptiveGridBuilder builder) } } - additionalVisualization.AddRange(CalculateDistances(grid, bestExits, tree)); + var distances = grid.ComputeDistances(bestExits.Select(e => e.Exit), tree); + RecordStatistics(grid, distances, bestExits, tree); + + additionalVisualization.Add(grid.TreeVisualization( + distances.Keys, _routeHeight, Material)); return additionalVisualization; } - private List CalculateDistances( - AdaptiveGrid grid, - List<(SpaceBoundary Room, GridVertex Exit)> inputs, - IDictionary tree) + public bool OnElevation(double elevation) + { + return Transform.Origin.Z.ApproximatelyEquals(elevation); + } + + private void RecordStatistics(AdaptiveGrid grid, + Dictionary accumulatedDistances, + List<(SpaceBoundary Room, GridVertex Exit)> inputs, + IDictionary tree) { - Dictionary accumulatedDistances = new Dictionary(); + Dictionary statisticsByType = new(); foreach (var input in inputs) { //Distances are caches in Dictionary. It's mostly to draw edge lines only once as //distance calculations are cheap. - grid.CalculateDistanceRecursive(input.Exit, tree, accumulatedDistances); var next = tree[input.Exit.Id]; if (next != null && next.Trunk != null) { @@ -126,20 +138,6 @@ private List CalculateDistances( } Statistics = statisticsByType.Select(s => s.Value.Stat).ToList(); - - double VisualizationHeight = 1.5; - var t = new Transform(0, 0, VisualizationHeight); - List visualizations = new List(); - foreach (var item in accumulatedDistances) - { - var start = grid.GetVertex(item.Key.StartId); - var end = grid.GetVertex(item.Key.EndId); - var shape = new Line(start.Point, end.Point); - var modelCurve = new ModelCurve(shape, Material, t); - modelCurve.SetSelectable(false); - visualizations.Add(modelCurve); - } - return visualizations; } /// @@ -149,7 +147,7 @@ private List CalculateDistances( /// Traveling tree from rooms corners to exits. /// Combinations of exits and their corresponding corners for each room. /// Most distance efficient exit. - private static GridVertex ChooseExit( + private static GridVertex? ChooseExit( AdaptiveGrid grid, IDictionary tree, List exits) @@ -158,31 +156,29 @@ private static GridVertex ChooseExit( { return exits.First(); } - else + + double bestLength = double.MaxValue; + GridVertex? bestExit = null; + foreach (var exit in exits) { - double bestLength = double.MaxValue; - GridVertex bestExit = null; - foreach (var exit in exits) + double accumulatedLength = 0; + var current = tree[exit.Id]; + while (current.Trunk != null) { - double accumulatedLength = 0; - var current = tree[exit.Id]; - while (current.Trunk != null) - { - var p0 = grid.GetVertex(current.Id).Point; - var p1 = grid.GetVertex(current.Trunk.Id).Point; - accumulatedLength += p0.DistanceTo(p1); - current = current.Trunk; - } - - if (accumulatedLength < bestLength) - { - bestLength = accumulatedLength; - bestExit = exit; - } + var p0 = grid.GetVertex(current.Id).Point; + var p1 = grid.GetVertex(current.Trunk.Id).Point; + accumulatedLength += p0.DistanceTo(p1); + current = current.Trunk; } - return bestExit; + if (accumulatedLength < bestLength) + { + bestLength = accumulatedLength; + bestExit = exit; + } } + + return bestExit; } } } From 5ef8dad776586f7af4c2ec6f2b3a34fd28616386 Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Mon, 4 Dec 2023 10:19:14 +0200 Subject: [PATCH 04/21] Use RepsentationInstances for visualization --- .../src/AdaptiveGridExtensions.cs | 15 +++--- .../src/LinesRepresentation.cs | 49 +++++++++++++++++++ .../src/RouteDistanceConfiguration.cs | 35 +++++++------ .../src/TravelDistanceAnalyzer.cs | 5 +- .../src/WalkingDistanceConfiguration.cs | 29 ++++++----- 5 files changed, 96 insertions(+), 37 deletions(-) create mode 100644 TravelDistanceAnalyzer/src/LinesRepresentation.cs diff --git a/TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs b/TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs index 7c40b6ee..3ec8f985 100644 --- a/TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs +++ b/TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs @@ -50,22 +50,19 @@ public static double CalculateDistanceRecursive( return d; } - public static ModelLines TreeVisualization(this AdaptiveGrid grid, + public static List TreeVisualization(this AdaptiveGrid grid, IEnumerable edges, - double elevation, - Material material) + Transform transform) { List lines = new List(); foreach (var item in edges) { - var start = grid.GetVertex(item.StartId); - var end = grid.GetVertex(item.EndId); - var shape = new Line(start.Point, end.Point); + var start = grid.GetVertex(item.StartId).Point; + var end = grid.GetVertex(item.EndId).Point; + var shape = new Line(start, end).TransformedLine(transform); lines.Add(shape); } - ModelLines modelLines = new ModelLines(lines, material, new Transform(0, 0, elevation)); - modelLines.SetSelectable(false); - return modelLines; + return lines; } } } diff --git a/TravelDistanceAnalyzer/src/LinesRepresentation.cs b/TravelDistanceAnalyzer/src/LinesRepresentation.cs new file mode 100644 index 00000000..603bfa30 --- /dev/null +++ b/TravelDistanceAnalyzer/src/LinesRepresentation.cs @@ -0,0 +1,49 @@ +using Elements; +using Elements.Geometry; +using glTFLoader.Schema; + +namespace Elements +{ + /// + /// An element representation displayed as set of lines. + /// + public class LinesRepresentation : ElementRepresentation + { + private List _lines; + private bool _isSelectable = true; + + /// + /// Initializes a new instance of CurveRepresentation. + /// + /// The curve. + /// If curve is selectable. + public LinesRepresentation(List lines, bool isSelectable) + { + _lines = lines; + _isSelectable = isSelectable; + } + + /// + /// Indicates if curve is selectable. + /// + public bool IsSelectable => _isSelectable; + + /// + public override bool TryToGraphicsBuffers(GeometricElement element, out List graphicsBuffers, out string id, out MeshPrimitive.ModeEnum? mode) + { + id = _isSelectable ? $"{element.Id}_lines" : $"unselectable_{element.Id}_lines"; + mode = glTFLoader.Schema.MeshPrimitive.ModeEnum.LINES; + graphicsBuffers = new List(); + + List points = new List(); + foreach (var line in _lines) + { + points.Add(line.Start); + points.Add(line.End); + } + + graphicsBuffers.Add(points.ToGraphicsBuffers()); + return true; + } + } +} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs b/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs index 95e36994..e1d7b5f8 100644 --- a/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs +++ b/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs @@ -14,6 +14,7 @@ internal class RouteDistanceConfiguration : GeometricElement private double _snapingDistance = 0.25; private double _routeHeight = 1; + private List _lineRepresentation = new(); public RouteDistanceConfiguration(string addId, IList destinations) { @@ -25,13 +26,21 @@ public RouteDistanceConfiguration(string addId, IList destinations) public override void UpdateRepresentations() { - Representation = new Representation(); - - foreach (var point in Destinations) + if (RepresentationInstances.Count == 0) { - var shape = new Circle(point, 0.25).ToPolygon(8); - Representation.SolidOperations.Add(new Geometry.Solids.Extrude( - shape, _routeHeight, Vector3.ZAxis)); + foreach (var point in Destinations) + { + var shape = new Circle(point, 0.25).ToPolygon(8); + var extrude = new Geometry.Solids.Extrude(shape, _routeHeight, Vector3.ZAxis); + SolidRepresentation sr = new SolidRepresentation(extrude); + RepresentationInstances.Add(new RepresentationInstance(sr, Material)); + } + + if (_lineRepresentation.Any()) + { + LinesRepresentation r = new LinesRepresentation(_lineRepresentation, true); + RepresentationInstances.Add(new RepresentationInstance(r, Material)); + } } } @@ -43,12 +52,11 @@ public Color Color set { Material.Color = value; } } - public List Compute(AdaptiveGridBuilder builder) + public void Compute(AdaptiveGridBuilder builder) { - List additionalVisualization = new List(); if (Destinations.Count < 2) { - return additionalVisualization; + return; } ulong start = builder.AddEndPoint(Destinations[0], _snapingDistance, out var connection); @@ -85,15 +93,12 @@ public List Compute(AdaptiveGridBuilder builder) distance += accumulatedDistances[edge]; } - //TODO: height on level can have extra elevation. - additionalVisualization.Add(grid.TreeVisualization( - accumulatedDistances.Keys, _routeHeight, Material)); + _lineRepresentation.AddRange( + grid.TreeVisualization(accumulatedDistances.Keys, new Transform(0, 0, _routeHeight))); start = end; } Distance = distance; - additionalVisualization.Add(DestinationLabels()); - return additionalVisualization; } public bool OnElevation(double elevation) @@ -177,7 +182,7 @@ private void AdditionalConnections(AdaptiveGrid grid, } } - private ModelText DestinationLabels() + public ModelText DestinationLabels() { var texts = new List<(Vector3 Location, Vector3 FacingDirection, Vector3 LineDirection, string Text, Color? Color)>(); for (int i = 0; i < Destinations.Count; i++) diff --git a/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs b/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs index 347136d7..aeba2089 100644 --- a/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs +++ b/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs @@ -65,12 +65,13 @@ public static TravelDistanceAnalyzerOutputs Execute(Dictionary in foreach (var config in walkingDistanceConfigs.Where(c => c.OnElevation(elevation))) { - output.Model.AddElements(config.Compute(builder)); + config.Compute(builder); } foreach (var config in routeDistanceConfigs.Where(c => c.OnElevation(elevation))) { - output.Model.AddElements(config.Compute(builder)); + config.Compute(builder); + output.Model.AddElement(config.DestinationLabels()); } //Grid visualization for debug purposes diff --git a/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs b/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs index 96438493..02df9ddd 100644 --- a/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs +++ b/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs @@ -16,6 +16,7 @@ internal class WalkingDistanceConfiguration : GeometricElement private double _snapingDistance = 0.25; private double _routeHeight = 1; + private LinesRepresentation? _lineRepresentation = null; public WalkingDistanceConfiguration(string addId, IList programTypes, Transform transform) { @@ -38,12 +39,19 @@ public WalkingDistanceConfiguration(string addId, IList programTypes, Tr public override void UpdateRepresentations() { - Representation = new Representation(); + if (RepresentationInstances.Count == 0) + { + var shape = new Polygon((-0.1, -0.1), (-0.25, 0), (-0.1, 0.1), (0, 0.25), + (0.1, 0.1), (0.25, 0), (0.1, -0.1), (0, -0.25)); + var extrude = new Geometry.Solids.Extrude(shape, _routeHeight, Vector3.ZAxis); + SolidRepresentation sr = new SolidRepresentation(extrude); + RepresentationInstances.Add(new RepresentationInstance(sr, Material)); - var shape = new Polygon((-0.1, -0.1), (-0.25, 0), (-0.1, 0.1), (0, 0.25), - (0.1, 0.1), (0.25, 0), (0.1, -0.1), (0, -0.25)); - Representation.SolidOperations.Add(new Geometry.Solids.Extrude( - shape, _routeHeight, Vector3.ZAxis)); + if (_lineRepresentation != null) + { + RepresentationInstances.Add(new RepresentationInstance(_lineRepresentation, Material)); + } + } } public List Statistics { get; set; } @@ -54,13 +62,12 @@ public Color Color set { Material.Color = value; } } - public List Compute(AdaptiveGridBuilder builder) + public void Compute(AdaptiveGridBuilder builder) { - List additionalVisualization = new List(); var exit = builder.AddEndPoint(Transform.Origin, _snapingDistance, out _); if (exit == 0) { - return additionalVisualization; + return; } var grid = builder.Grid; @@ -91,9 +98,9 @@ public List Compute(AdaptiveGridBuilder builder) var distances = grid.ComputeDistances(bestExits.Select(e => e.Exit), tree); RecordStatistics(grid, distances, bestExits, tree); - additionalVisualization.Add(grid.TreeVisualization( - distances.Keys, _routeHeight, Material)); - return additionalVisualization; + //Representation instance will apply transformation so everything need to be in its local frame. + Transform t = Transform.Inverted().Moved(z: _routeHeight); + _lineRepresentation = new LinesRepresentation(grid.TreeVisualization(distances.Keys, t), true); } public bool OnElevation(double elevation) From 0dc80e3aef91427e14c345c7bd096790f7bf475f Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Mon, 4 Dec 2023 17:30:59 +0200 Subject: [PATCH 05/21] Set of improvements Consumed AdaptiveGrid fix Added hack for WallCandidates with incorrect line elevation. Make so corridors are connected by a line with the same direction as one of two corridors. Consumed latest Door changes from elements Move common code into FindOnColinearEdges --- .../dependencies/DoorType.g.cs | 34 ---- .../dependencies/Level.g.cs | 58 +++++++ ...TravelDistanceAnalyzer.Dependencies.csproj | 4 +- TravelDistanceAnalyzer/hypar.json | 9 +- .../src/AdaptiveGridBuilder.cs | 157 +++++++++--------- .../src/TravelDistanceAnalyzer.cs | 50 +++++- 6 files changed, 196 insertions(+), 116 deletions(-) delete mode 100644 TravelDistanceAnalyzer/dependencies/DoorType.g.cs create mode 100644 TravelDistanceAnalyzer/dependencies/Level.g.cs diff --git a/TravelDistanceAnalyzer/dependencies/DoorType.g.cs b/TravelDistanceAnalyzer/dependencies/DoorType.g.cs deleted file mode 100644 index a2f7547a..00000000 --- a/TravelDistanceAnalyzer/dependencies/DoorType.g.cs +++ /dev/null @@ -1,34 +0,0 @@ -//---------------------- -// -// Generated using the NJsonSchema v10.1.21.0 (Newtonsoft.Json v13.0.0.0) (http://NJsonSchema.org) -// -//---------------------- -using Elements; -using Elements.GeoJSON; -using Elements.Geometry; -using Elements.Geometry.Solids; -using Elements.Spatial; -using Elements.Validators; -using Elements.Serialization.JSON; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using Line = Elements.Geometry.Line; -using Polygon = Elements.Geometry.Polygon; - -namespace Elements -{ - #pragma warning disable // Disable all warnings - - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] - public enum DoorType - { - [System.Runtime.Serialization.EnumMember(Value = @"Single")] - Single = 0, - - [System.Runtime.Serialization.EnumMember(Value = @"Double")] - Double = 1, - - } -} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/dependencies/Level.g.cs b/TravelDistanceAnalyzer/dependencies/Level.g.cs new file mode 100644 index 00000000..268525c0 --- /dev/null +++ b/TravelDistanceAnalyzer/dependencies/Level.g.cs @@ -0,0 +1,58 @@ +//---------------------- +// +// Generated using the NJsonSchema v10.1.21.0 (Newtonsoft.Json v13.0.0.0) (http://NJsonSchema.org) +// +//---------------------- +using Elements; +using Elements.GeoJSON; +using Elements.Geometry; +using Elements.Geometry.Solids; +using Elements.Spatial; +using Elements.Validators; +using Elements.Serialization.JSON; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using Line = Elements.Geometry.Line; +using Polygon = Elements.Geometry.Polygon; + +namespace Elements +{ + #pragma warning disable // Disable all warnings + + /// A horizontal datum representing a building level at a specific elevation. + [JsonConverter(typeof(Elements.Serialization.JSON.JsonInheritanceConverter), "discriminator")] + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.1.21.0 (Newtonsoft.Json v13.0.0.0)")] + public partial class Level : Element + { + [JsonConstructor] + public Level(double @elevation, double? @height, System.Guid? @planView, System.Guid @id = default, string @name = null) + : base(id, name) + { + this.Elevation = @elevation; + this.Height = @height; + this.PlanView = @planView; + } + + + // Empty constructor + public Level() + : base() + { + } + + [JsonProperty("Elevation", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double Elevation { get; set; } + + /// The vertical distance from this level to the next. May be null for a top level, like a roof. + [JsonProperty("Height", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double? Height { get; set; } + + /// The default plan view for this level + [JsonProperty("Plan View", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Guid? PlanView { get; set; } + + + } +} \ No newline at end of file diff --git a/TravelDistanceAnalyzer/dependencies/TravelDistanceAnalyzer.Dependencies.csproj b/TravelDistanceAnalyzer/dependencies/TravelDistanceAnalyzer.Dependencies.csproj index 69448574..9cf130f2 100644 --- a/TravelDistanceAnalyzer/dependencies/TravelDistanceAnalyzer.Dependencies.csproj +++ b/TravelDistanceAnalyzer/dependencies/TravelDistanceAnalyzer.Dependencies.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -7,7 +7,7 @@ - + diff --git a/TravelDistanceAnalyzer/hypar.json b/TravelDistanceAnalyzer/hypar.json index 5e47b0a0..eec32d7b 100644 --- a/TravelDistanceAnalyzer/hypar.json +++ b/TravelDistanceAnalyzer/hypar.json @@ -15,6 +15,11 @@ "name": "Circulation", "optional": false }, + { + "autohide": false, + "name": "Levels", + "optional": true + }, { "autohide": false, "name": "Doors", @@ -130,9 +135,9 @@ "element_types": [ "https://schemas.hypar.io/SpaceBoundary.json", "https://schemas.hypar.io/CirculationSegment.json", - "https://schemas.hypar.io/Door.json", "https://schemas.hypar.io/WallCandidate.json", - "https://schemas.hypar.io/ThickenedPolyline.json" + "https://schemas.hypar.io/ThickenedPolyline.json", + "https://schemas.hypar.io/Level.json" ], "repository_url": "https://github.com/hypar-io/function", "last_updated": "0001-01-01T00:00:00", diff --git a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs index 6cdfff6b..07dc1414 100644 --- a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs +++ b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Elements.Geometry; using GridVertex = Elements.Spatial.AdaptiveGrid.Vertex; namespace TravelDistanceAnalyzer @@ -171,11 +172,13 @@ private void Intersect(List<(CirculationSegment Segment, Polyline Centerline)> c Vector3 closestLeftItem = Vector3.Origin, closestRightItem = Vector3.Origin; int closestLeftProximity = -1, closestRightProximity = -1; double closestDistance = double.PositiveInfinity; + Line leftLine = new Line(leftVertices[i], leftVertices[i + 1]); - Action check = - (Line line, Vector3 point, int leftIndex, int rightIndex, bool left) => + Action check = + (Line line, Vector3 point, Vector3 direction, int leftIndex, int rightIndex, bool left) => { - if (CanConnect(point, line, Math.Min(maxDistance, closestDistance), out var closest, out var d)) + if (CanConnectDirectional(point, direction, line, Math.Min(maxDistance, closestDistance), + out var closest, out var d)) { closestDistance = d; (closestLeftItem, closestRightItem) = left ? (closest, point) : (point, closest); @@ -184,7 +187,6 @@ private void Intersect(List<(CirculationSegment Segment, Polyline Centerline)> c } }; - Line leftLine = new Line(leftVertices[i], leftVertices[i + 1]); for (int j = 0; j < rightVertices.Count - 1; j++) { if (item == candidate && Math.Abs(i - j) < 2) @@ -195,10 +197,10 @@ private void Intersect(List<(CirculationSegment Segment, Polyline Centerline)> c Line rightLine = new Line(rightVertices[j], rightVertices[j + 1]); if (!leftLine.Intersects(rightLine, out var intersection)) { - check(rightLine, leftLine.Start, i, j, false); - check(rightLine, leftLine.End, i, j, false); - check(leftLine, rightLine.Start, i, j, true); - check(leftLine, rightLine.End, i, j, true); + check(rightLine, leftLine.Start, leftLine.Direction(), i, j, false); + check(rightLine, leftLine.End, leftLine.Direction(), i, j, false); + check(leftLine, rightLine.Start, rightLine.Direction(), i, j, true); + check(leftLine, rightLine.End, rightLine.Direction(), i, j, true); } else { @@ -225,41 +227,17 @@ private void Intersect(List<(CirculationSegment Segment, Polyline Centerline)> c } else { - GridVertex leftVertex = null; + GridVertex? leftVertex = null; if (!leftExist) { _grid.TryGetVertexIndex(leftVertices[closestLeftProximity], out var leftCon); _grid.TryGetVertexIndex(leftVertices[closestLeftProximity + 1], out var rightCon); var segment = new Line(leftVertices[closestLeftProximity], leftVertices[closestLeftProximity + 1]); var vertex = _grid.GetVertex(leftCon); - while (vertex.Id != rightCon) + var edge = FindOnColinearEdges(vertex, rightCon, segment.Direction(), closestLeftItem); + if (edge != null) { - GridVertex otherVertex = null; - Edge edge = null; - foreach (var e in vertex.Edges) - { - otherVertex = _grid.GetVertex(e.OtherVertexId(vertex.Id)); - var edgeDirection = (otherVertex.Point - vertex.Point).Unitized(); - if (edgeDirection.Dot(segment.Direction()).ApproximatelyEquals(1)) - { - edge = e; - break; - } - } - - if (edge == null) - { - throw new Exception("End edge is not reached"); - } - - var edgeLine = new Line(vertex.Point, otherVertex.Point); - if (edgeLine.PointOnLine(closestLeftItem)) - { - leftVertex = _grid.CutEdge(edge, closestLeftItem); - break; - } - - vertex = otherVertex; + leftVertex = _grid.CutEdge(edge, closestLeftItem); } } else @@ -279,39 +257,15 @@ private void Intersect(List<(CirculationSegment Segment, Polyline Centerline)> c } var segment = new Line(rightVertices[closestRightProximity], rightVertices[closestRightProximity + 1]); - while (vertex.Id != rightCon) + var edge = FindOnColinearEdges(vertex, rightCon, segment.Direction(), closestRightItem); + if (edge != null) { - GridVertex otherVertex = null; - Edge edge = null; - foreach (var e in vertex.Edges) - { - otherVertex = _grid.GetVertex(e.OtherVertexId(vertex.Id)); - var edgeDirection = (otherVertex.Point - vertex.Point).Unitized(); - if (edgeDirection.Dot(segment.Direction()).ApproximatelyEquals(1)) - { - edge = e; - break; - } - } - - if (edge == null) - { - throw new Exception("End edge is not reached"); - } - - var edgeLine = new Line(vertex.Point, otherVertex.Point); - if (edgeLine.PointOnLine(closestRightItem)) - { - connections.Add(vertex); - connections.Add(otherVertex); - _grid.AddVertex(closestRightItem, - new ConnectVertexStrategy(connections.ToArray()), - cut: false); - _grid.RemoveEdge(edge); - break; - } - - vertex = otherVertex; + connections.Add(Grid.GetVertex(edge.StartId)); + connections.Add(Grid.GetVertex(edge.EndId)); + _grid.AddVertex(closestRightItem, + new ConnectVertexStrategy(connections.ToArray()), + cut: false); + _grid.RemoveEdge(edge); } } else if (leftVertex.Id != rightId) @@ -323,6 +277,41 @@ private void Intersect(List<(CirculationSegment Segment, Polyline Centerline)> c } } } + + private Edge? FindOnColinearEdges(GridVertex start, ulong endId, Vector3 direction, Vector3 destination) + { + while (start.Id != endId) + { + GridVertex otherVertex = null; + Edge edge = null; + foreach (var e in start.Edges) + { + otherVertex = _grid.GetVertex(e.OtherVertexId(start.Id)); + var edgeDirection = (otherVertex.Point - start.Point).Unitized(); + if (edgeDirection.Dot(direction).ApproximatelyEquals(1)) + { + edge = e; + break; + } + } + + if (edge == null) + { + throw new Exception("End edge is not reached"); + } + + var edgeLine = new Line(start.Point, otherVertex.Point); + if (edgeLine.PointOnLine(destination)) + { + return edge; + } + + start = otherVertex; + } + + return null; + } + private void Extend(List<(CirculationSegment Segment, Polyline Centerline)> centerlines) { foreach (var item in centerlines) @@ -334,19 +323,19 @@ private void Extend(List<(CirculationSegment Segment, Polyline Centerline)> cent continue; } - var maxDistance = item.Segment.Geometry.GetWidth() + candidate.Segment.Geometry.GetWidth(); foreach (var segment in item.Centerline.Segments()) { - ExtendToCorridor(segment, candidate.Segment, maxDistance); + ExtendToCorridor(segment, candidate.Segment); } } } } - private void ExtendToCorridor(Line l, CirculationSegment segment, double maxDistance) + private void ExtendToCorridor(Line l, CirculationSegment segment) { foreach (var polygon in segment.Geometry.GetPolygons()) { + var maxDistance = polygon.offsetPolygon.Segments().Max(s => s.Length()); var transformedPolygon = polygon.offsetPolygon.TransformedPolygon(segment.Transform); var trimLine = new Line(l.Start - l.Direction() * maxDistance, l.End + l.Direction() * maxDistance); @@ -355,7 +344,7 @@ private void ExtendToCorridor(Line l, CirculationSegment segment, double maxDist { if (l.PointOnLine(line.Start) || l.PointOnLine(line.End)) { - Grid.AddEdge(line.Start, line.End); + Grid.AddEdge(line.Start, line.End); } } } @@ -406,8 +395,8 @@ private GridVertex FindRoomExit( AdaptiveGrid grid) { var door = doors?.FirstOrDefault(d => roomEdge.PointOnLine(d.Transform.Origin, false, RoomToWallTolerance)); - var wall = walls?.FirstOrDefault(w => w.Line.PointOnLine(roomEdge.Start, true, RoomToWallTolerance) && - w.Line.PointOnLine(roomEdge.End, true, RoomToWallTolerance)); + var wall = walls?.FirstOrDefault(w => w.Line.PointOnLine(roomEdge.PointAtNormalized(0.25), true, RoomToWallTolerance) && + w.Line.PointOnLine(roomEdge.PointAtNormalized(0.75), true, RoomToWallTolerance)); // There are doors in the workflow and this segment is a wall without a door. if (wall != null && doors != null && door == null) @@ -506,5 +495,25 @@ private bool CanConnect(Vector3 point, bool aligned = dot.ApproximatelyEquals(0) || Math.Abs(dot).ApproximatelyEquals(1); return dist < maxDistance && aligned; } + + private bool CanConnectDirectional(Vector3 point, + Vector3 direction, + Line segment, + double maxDistance, + out Vector3 closest, + out double dist) + { + InfiniteLine a = new InfiniteLine(point, direction); + if (a.Intersects(segment, out var result)) + { + closest = result.First(); + dist = closest.DistanceTo(point); + return dist < maxDistance; + } + + closest = Vector3.Origin; + dist = double.MaxValue; + return false; + } } } diff --git a/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs b/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs index aeba2089..76b6804e 100644 --- a/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs +++ b/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs @@ -43,6 +43,12 @@ public static TravelDistanceAnalyzerOutputs Execute(Dictionary in walls = wallsModel.AllElementsOfType().ToList(); } + List? levels = null; + if (inputModels.TryGetValue("Levels", out var levelModel)) + { + levels = levelModel.AllElementsOfType().ToList(); + } + var corridorsByLevel = corridors.GroupBy(c => c.Level); var roomsByLevel = rooms.GroupBy(r => r.Level); @@ -58,17 +64,23 @@ public static TravelDistanceAnalyzerOutputs Execute(Dictionary in continue; } + var level = levels?.FirstOrDefault(l => l.Id == levelRooms.Key); + if (level == null) + { + level = new Level(levelCorridors.First().Elevation, null, null); + } + + var levelWalls = WallsForLevel(walls, level); + var builder = new AdaptiveGridBuilder(); builder.Build(levelCorridors, levelRooms, walls, doors); - var elevation = levelCorridors.First().Elevation; - - foreach (var config in walkingDistanceConfigs.Where(c => c.OnElevation(elevation))) + foreach (var config in walkingDistanceConfigs.Where(c => c.OnElevation(level.Elevation))) { config.Compute(builder); } - foreach (var config in routeDistanceConfigs.Where(c => c.OnElevation(elevation))) + foreach (var config in routeDistanceConfigs.Where(c => c.OnElevation(level.Elevation))) { config.Compute(builder); output.Model.AddElement(config.DestinationLabels()); @@ -87,6 +99,36 @@ public static TravelDistanceAnalyzerOutputs Execute(Dictionary in return output; } + private static List? WallsForLevel(List? allWalls, Level level) + { + List? levelWalls = null; + if (allWalls != null) + { + levelWalls = new List(); + foreach (var item in allWalls) + { + if (item.Line.Start.Z.ApproximatelyEquals(level.Elevation)) + { + levelWalls.Add(item); + } + // Some walls have their lines set to 0. + // This is hack not to ignore them while the issue is not fixed. + else if (item.AdditionalProperties.TryGetValue("Height", out var height)) + { + if (height is double H && + item.Line.Start.Z < level.Elevation && + item.Line.Start.Z + H - Vector3.EPSILON > level.Elevation) + { + item.Line = new Line(new Vector3(item.Line.Start.X, item.Line.Start.Y, level.Elevation), + new Vector3(item.Line.End.X, item.Line.End.Y, level.Elevation)); + levelWalls.Add(item); + } + } + } + } + return levelWalls; + } + private static List CreateRoutingDistanceConfigurations(Overrides overrides) { var routeDistanceConfigs = RouteDistanceOverrideExtensions.CreateElements( From 3608c38d20559d550bb521f639e4ff62c3f7735f Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Wed, 6 Dec 2023 13:13:15 +0200 Subject: [PATCH 06/21] Add hack for WallCandidates with 0 thickness --- .../src/TravelDistanceAnalyzer.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs b/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs index 76b6804e..796af741 100644 --- a/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs +++ b/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs @@ -1,6 +1,8 @@ using Elements; using Elements.Geometry; using Elements.Spatial.AdaptiveGrid; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using AdaptiveGraphRouting = Elements.Spatial.AdaptiveGrid.AdaptiveGraphRouting; namespace TravelDistanceAnalyzer @@ -73,7 +75,7 @@ public static TravelDistanceAnalyzerOutputs Execute(Dictionary in var levelWalls = WallsForLevel(walls, level); var builder = new AdaptiveGridBuilder(); - builder.Build(levelCorridors, levelRooms, walls, doors); + builder.Build(levelCorridors, levelRooms, levelWalls, doors); foreach (var config in walkingDistanceConfigs.Where(c => c.OnElevation(level.Elevation))) { @@ -107,6 +109,24 @@ public static TravelDistanceAnalyzerOutputs Execute(Dictionary in levelWalls = new List(); foreach (var item in allWalls) { + // Ignore wall candidates with zero thickness as they produce no walls. + // Previously open walls did not have wall candidates, but now they are. + // Its either transition to use actual walls or do this thickness check. + // Thickness is only available in additional properties and require ugly code. + if (item.AdditionalProperties.TryGetValue("Thickness", out var obj)) + { + if (obj is JObject jobj) + { + var tuple = jobj.ToObject<(double, double)?>(); + if (tuple != null && + tuple.Value.Item1.ApproximatelyEquals(0) && + tuple.Value.Item2.ApproximatelyEquals(0)) + { + continue; + } + } + } + if (item.Line.Start.Z.ApproximatelyEquals(level.Elevation)) { levelWalls.Add(item); From 4d21bd7337c6c2c689356991083a1a3e75fe6616 Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Wed, 6 Dec 2023 15:13:44 +0200 Subject: [PATCH 07/21] Connect non collinear exits --- .../src/AdaptiveGridBuilder.cs | 148 +++++++++--------- 1 file changed, 73 insertions(+), 75 deletions(-) diff --git a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs index 07dc1414..21a9c73a 100644 --- a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs +++ b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs @@ -234,7 +234,7 @@ private void Intersect(List<(CirculationSegment Segment, Polyline Centerline)> c _grid.TryGetVertexIndex(leftVertices[closestLeftProximity + 1], out var rightCon); var segment = new Line(leftVertices[closestLeftProximity], leftVertices[closestLeftProximity + 1]); var vertex = _grid.GetVertex(leftCon); - var edge = FindOnColinearEdges(vertex, rightCon, segment.Direction(), closestLeftItem); + var edge = FindOnCollinearEdges(vertex, rightCon, segment.Direction(), closestLeftItem); if (edge != null) { leftVertex = _grid.CutEdge(edge, closestLeftItem); @@ -257,15 +257,21 @@ private void Intersect(List<(CirculationSegment Segment, Polyline Centerline)> c } var segment = new Line(rightVertices[closestRightProximity], rightVertices[closestRightProximity + 1]); - var edge = FindOnColinearEdges(vertex, rightCon, segment.Direction(), closestRightItem); + var edge = FindOnCollinearEdges(vertex, rightCon, segment.Direction(), closestRightItem); if (edge != null) { - connections.Add(Grid.GetVertex(edge.StartId)); - connections.Add(Grid.GetVertex(edge.EndId)); - _grid.AddVertex(closestRightItem, - new ConnectVertexStrategy(connections.ToArray()), - cut: false); - _grid.RemoveEdge(edge); + var start = Grid.GetVertex(edge.StartId); + var end = Grid.GetVertex(edge.EndId); + if (!closestRightItem.IsAlmostEqualTo(start.Point) && + !closestRightItem.IsAlmostEqualTo(end.Point)) + { + connections.Add(start); + connections.Add(end); + _grid.AddVertex(closestRightItem, + new ConnectVertexStrategy(connections.ToArray()), + cut: false); + _grid.RemoveEdge(edge); + } } } else if (leftVertex.Id != rightId) @@ -278,7 +284,7 @@ private void Intersect(List<(CirculationSegment Segment, Polyline Centerline)> c } } - private Edge? FindOnColinearEdges(GridVertex start, ulong endId, Vector3 direction, Vector3 destination) + private Edge? FindOnCollinearEdges(GridVertex start, ulong endId, Vector3 direction, Vector3 destination) { while (start.Id != endId) { @@ -301,7 +307,7 @@ private void Intersect(List<(CirculationSegment Segment, Polyline Centerline)> c } var edgeLine = new Line(start.Point, otherVertex.Point); - if (edgeLine.PointOnLine(destination)) + if (edgeLine.PointOnLine(destination, true)) { return edge; } @@ -405,97 +411,89 @@ private GridVertex FindRoomExit( } var midpoint = door?.Transform.Origin ?? roomEdge.Mid(); + var exitDirection = door?.Transform.XAxis ?? roomEdge.Direction(); foreach (var line in centerlines) { for (int i = 0; i < line.Centerline.Vertices.Count - 1; i++) { var segment = new Line(line.Centerline.Vertices[i], line.Centerline.Vertices[i + 1]); - if (CanConnect(midpoint, segment, line.Segment.Geometry.GetWidth() / 2 + 0.1, out var closest, out _)) + var distance = midpoint.DistanceTo(segment, out var closest); + if (distance > line.Segment.Geometry.GetWidth() / 2 + 0.10) { - GridVertex exitVertex = null; - grid.TryGetVertexIndex(segment.Start, out var id); - var vertex = grid.GetVertex(id); - - //We know corridor line but it can already be split into several edges. - //Need to find exact edge to insert new vertex into. - //First vertex corresponding start of the segment is found. - //Then, edges that do in the same direction as segment is traversed - //until target edge is found or end vertex is reached. - //This is much faster than traverse every single edge in the grid. - if (vertex.Point.IsAlmostEqualTo(closest, grid.Tolerance)) - { - exitVertex = vertex; - } - else - { - while (!vertex.Point.IsAlmostEqualTo(segment.End, grid.Tolerance)) - { - Edge edge = null; - GridVertex otherVertex = null; - foreach (var e in vertex.Edges) - { - otherVertex = grid.GetVertex(e.OtherVertexId(vertex.Id)); - var edgeDirection = (otherVertex.Point - vertex.Point).Unitized(); - if (edgeDirection.Dot(segment.Direction()).ApproximatelyEquals(1)) - { - edge = e; - break; - } - } - - if (edge == null) - { - break; - } + continue; + } - if (otherVertex.Point.IsAlmostEqualTo(closest, grid.Tolerance)) - { - exitVertex = otherVertex; - break; - } + GridVertex exitVertex = null; + grid.TryGetVertexIndex(segment.Start, out var id); + var vertex = grid.GetVertex(id); + + //We know corridor line but it can already be split into several edges. + //Need to find exact edge to insert new vertex into. + //First vertex corresponding start of the segment is found. + //Then, edges that do in the same direction as segment is traversed + //until target edge is found or end vertex is reached. + //This is much faster than traverse every single edge in the grid. + if (vertex.Point.IsAlmostEqualTo(closest, grid.Tolerance)) + { + exitVertex = vertex; + } + else + { + grid.TryGetVertexIndex(segment.End, out var endId); + var edge = FindOnCollinearEdges(vertex, endId, segment.Direction(), closest); + if (edge != null) + { + var start = grid.GetVertex(edge.StartId); + var end = grid.GetVertex(edge.EndId); - var edgeLine = new Line(vertex.Point, otherVertex.Point); - if (edgeLine.PointOnLine(closest)) - { - exitVertex = grid.AddVertex(closest, - new ConnectVertexStrategy(vertex, otherVertex), - cut: false); - grid.RemoveEdge(edge); - } - vertex = otherVertex; + if (start.Point.IsAlmostEqualTo(closest, grid.Tolerance)) + { + exitVertex = start; + } + else if (end.Point.IsAlmostEqualTo(closest, grid.Tolerance)) + { + exitVertex = end; + } + else + { + exitVertex = grid.AddVertex(closest, new ConnectVertexStrategy(start, end), cut: false); + grid.RemoveEdge(edge); } } + } - if (exitVertex != null) + if (exitVertex != null) + { + if (!exitVertex.Point.IsAlmostEqualTo(midpoint, grid.Tolerance)) { - if (!exitVertex.Point.IsAlmostEqualTo(midpoint, grid.Tolerance)) + var delta = closest - midpoint; + var dot = delta.Dot(segment.Direction()); + if (dot.ApproximatelyEquals(0) || dot.ApproximatelyEquals(delta.Length())) { return grid.AddVertex(midpoint, new ConnectVertexStrategy(exitVertex)); } else { - return exitVertex; + var cornerPoint = Math.Abs(exitDirection.Dot(segment.Direction())) > 1 / Math.Sqrt(2) ? + closest - dot * segment.Direction() : midpoint + dot * segment.Direction(); + + var strip = grid.AddVertices( + new List { midpoint, cornerPoint, closest }, + AdaptiveGrid.VerticesInsertionMethod.ConnectAndCut); + return strip.First(); } } + else + { + return exitVertex; + } } } } return null; } - private bool CanConnect(Vector3 point, - Line segment, - double maxDistance, - out Vector3 closest, - out double dist) - { - dist = point.DistanceTo(segment, out closest); - var dot = (closest - point).Unitized().Dot(segment.Direction()); - bool aligned = dot.ApproximatelyEquals(0) || Math.Abs(dot).ApproximatelyEquals(1); - return dist < maxDistance && aligned; - } - private bool CanConnectDirectional(Vector3 point, Vector3 direction, Line segment, From d3f91ea41c91d6d179ae72fc25b0f8ddf5283beb Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Thu, 7 Dec 2023 10:59:09 +0200 Subject: [PATCH 08/21] Add support for multiple Door on a single line. Check for sections on the wall not covered with walls. --- .../src/AdaptiveGridBuilder.cs | 146 ++++++++++++++---- 1 file changed, 113 insertions(+), 33 deletions(-) diff --git a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs index 21a9c73a..cd4ab3f1 100644 --- a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs +++ b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs @@ -8,12 +8,14 @@ using System.Threading.Tasks; using Elements.Geometry; using GridVertex = Elements.Spatial.AdaptiveGrid.Vertex; +using DotLiquid.Tags; namespace TravelDistanceAnalyzer { internal class AdaptiveGridBuilder { private const double RoomToWallTolerance = 1e-3; + private const double MinExitWidth = 0.5; private AdaptiveGrid _grid; @@ -376,10 +378,9 @@ private List AddRoom( var perimeter = room.Boundary.Perimeter.CollinearPointsRemoved().TransformedPolygon(room.Transform); foreach (var roomEdge in perimeter.Segments()) { - var exitVertex = FindRoomExit(roomEdge, centerlines, walls, doors, grid); - if (exitVertex != null) + foreach(var exit in FindRoomExits(roomEdge, centerlines, walls, doors)) { - roomExits.Add(exitVertex); + roomExits.Add(exit); } } return roomExits; @@ -393,40 +394,119 @@ private List AddRoom( /// Corridor segments with precalculated center lines. /// AdaptiveGrid to insert new vertices and edge into. /// New Vertex on room edge midpoint. - private GridVertex FindRoomExit( + private List FindRoomExits( Line roomEdge, List<(CirculationSegment Segment, Polyline Centerline)> centerlines, List? walls, - List? doors, - AdaptiveGrid grid) + List? doors) + { + var doorsOnWall = doors?.Where(d => roomEdge.PointOnLine(d.Transform.Origin, false, RoomToWallTolerance)); + var openSections = GetOpenPassages(roomEdge, walls); + + // There are no doors in the workflow and this segment covered by walls. + // Take middle point to give user at least some exits. + if (doors == null && !openSections.Any()) + { + openSections.Add(roomEdge); + } + + List exitLocations = new List(); + if (doorsOnWall != null && doorsOnWall.Any()) + { + exitLocations.AddRange(doorsOnWall.Select(d => d.Transform)); + } + + foreach (var item in openSections) + { + exitLocations.Add(new Transform(item.Mid(), item.Direction(), Vector3.ZAxis)); + } + + List exitVertices = new(); + foreach (var t in exitLocations) + { + var v = AddRoomExit(t, centerlines); + if (v != null) + { + exitVertices.Add(v); + } + } + return exitVertices; + } + + public List GetOpenPassages(Line roomSide, List? walls) + { + if (walls == null) + { + return new List() { roomSide }; + } + + List coveredRanges = new(); + foreach (var wall in walls) + { + if (roomSide.TryGetOverlap(wall.Line, RoomToWallTolerance, out Line overlap)) + { + var d0 = roomSide.GetParameterAt(overlap.Start); + var d1 = roomSide.GetParameterAt(overlap.End); + coveredRanges.Add(d0 < d1 ? new Domain1d(d0, d1) : new Domain1d(d1, d0)); + } + } + + var domains = FindUncoveredParametricRanges(roomSide.Domain, coveredRanges); + List openExits = new(); + foreach (var domain in domains) + { + var a = roomSide.PointAt(domain.Min); + var b = roomSide.PointAt(domain.Max); + if (a.DistanceTo(b) > MinExitWidth) + { + openExits.Add(new Line(a, b)); + } + } + return openExits; + } + + private List FindUncoveredParametricRanges(Domain1d lineDomain, List wallDomains) { - var door = doors?.FirstOrDefault(d => roomEdge.PointOnLine(d.Transform.Origin, false, RoomToWallTolerance)); - var wall = walls?.FirstOrDefault(w => w.Line.PointOnLine(roomEdge.PointAtNormalized(0.25), true, RoomToWallTolerance) && - w.Line.PointOnLine(roomEdge.PointAtNormalized(0.75), true, RoomToWallTolerance)); + List uncoveredRanges = new (); + + wallDomains.Sort((a, b) => a.Min.CompareTo(b.Min)); - // There are doors in the workflow and this segment is a wall without a door. - if (wall != null && doors != null && door == null) + double current = lineDomain.Min; + foreach (var domain in wallDomains) { - return null; + if (current < domain.Min) + { + uncoveredRanges.Add(new Domain1d(current, domain.Min)); + } + + current = Math.Max(current, domain.Max); } - var midpoint = door?.Transform.Origin ?? roomEdge.Mid(); - var exitDirection = door?.Transform.XAxis ?? roomEdge.Direction(); + if (current < lineDomain.Max) + { + uncoveredRanges.Add(new Domain1d(current, lineDomain.Max)); + } + + return uncoveredRanges; + } + private GridVertex? AddRoomExit(Transform location, + List<(CirculationSegment Segment, Polyline Centerline)> centerlines) + { foreach (var line in centerlines) { for (int i = 0; i < line.Centerline.Vertices.Count - 1; i++) { var segment = new Line(line.Centerline.Vertices[i], line.Centerline.Vertices[i + 1]); - var distance = midpoint.DistanceTo(segment, out var closest); + var distance = location.Origin.DistanceTo(segment, out var closest); if (distance > line.Segment.Geometry.GetWidth() / 2 + 0.10) { continue; } GridVertex exitVertex = null; - grid.TryGetVertexIndex(segment.Start, out var id); - var vertex = grid.GetVertex(id); + _grid.TryGetVertexIndex(segment.Start, out var id); + var vertex = _grid.GetVertex(id); //We know corridor line but it can already be split into several edges. //Need to find exact edge to insert new vertex into. @@ -434,52 +514,52 @@ private GridVertex FindRoomExit( //Then, edges that do in the same direction as segment is traversed //until target edge is found or end vertex is reached. //This is much faster than traverse every single edge in the grid. - if (vertex.Point.IsAlmostEqualTo(closest, grid.Tolerance)) + if (vertex.Point.IsAlmostEqualTo(closest, _grid.Tolerance)) { exitVertex = vertex; } else { - grid.TryGetVertexIndex(segment.End, out var endId); + _grid.TryGetVertexIndex(segment.End, out var endId); var edge = FindOnCollinearEdges(vertex, endId, segment.Direction(), closest); if (edge != null) { - var start = grid.GetVertex(edge.StartId); - var end = grid.GetVertex(edge.EndId); + var start = _grid.GetVertex(edge.StartId); + var end = _grid.GetVertex(edge.EndId); - if (start.Point.IsAlmostEqualTo(closest, grid.Tolerance)) + if (start.Point.IsAlmostEqualTo(closest, _grid.Tolerance)) { exitVertex = start; } - else if (end.Point.IsAlmostEqualTo(closest, grid.Tolerance)) + else if (end.Point.IsAlmostEqualTo(closest, _grid.Tolerance)) { exitVertex = end; } else { - exitVertex = grid.AddVertex(closest, new ConnectVertexStrategy(start, end), cut: false); - grid.RemoveEdge(edge); + exitVertex = _grid.AddVertex(closest, new ConnectVertexStrategy(start, end), cut: false); + _grid.RemoveEdge(edge); } } } if (exitVertex != null) { - if (!exitVertex.Point.IsAlmostEqualTo(midpoint, grid.Tolerance)) + if (!exitVertex.Point.IsAlmostEqualTo(location.Origin, _grid.Tolerance)) { - var delta = closest - midpoint; + var delta = closest - location.Origin; var dot = delta.Dot(segment.Direction()); if (dot.ApproximatelyEquals(0) || dot.ApproximatelyEquals(delta.Length())) { - return grid.AddVertex(midpoint, new ConnectVertexStrategy(exitVertex)); + return _grid.AddVertex(location.Origin, new ConnectVertexStrategy(exitVertex)); } else { - var cornerPoint = Math.Abs(exitDirection.Dot(segment.Direction())) > 1 / Math.Sqrt(2) ? - closest - dot * segment.Direction() : midpoint + dot * segment.Direction(); - - var strip = grid.AddVertices( - new List { midpoint, cornerPoint, closest }, + var cornerPoint = Math.Abs(location.XAxis.Dot(segment.Direction())) > 1 / Math.Sqrt(2) ? + closest - dot * segment.Direction() : location.Origin + dot * segment.Direction(); + + var strip = _grid.AddVertices( + new List { location.Origin, cornerPoint, closest }, AdaptiveGrid.VerticesInsertionMethod.ConnectAndCut); return strip.First(); } From 6239e53b70dc31324027f6d1c7c6373879126697 Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Thu, 7 Dec 2023 11:36:20 +0200 Subject: [PATCH 09/21] Extend connections from exit to corridor until the exit of the corridor --- TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs index cd4ab3f1..59139c82 100644 --- a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs +++ b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using Elements.Geometry; using GridVertex = Elements.Spatial.AdaptiveGrid.Vertex; using DotLiquid.Tags; @@ -350,7 +349,7 @@ private void ExtendToCorridor(Line l, CirculationSegment segment) var inside = trimLine.Trim(transformedPolygon, out _); foreach (var line in inside) { - if (l.PointOnLine(line.Start) || l.PointOnLine(line.End)) + if (l.PointOnLine(line.Start, true) || l.PointOnLine(line.End, true)) { Grid.AddEdge(line.Start, line.End); } @@ -551,7 +550,9 @@ private List FindUncoveredParametricRanges(Domain1d lineDomain, List FindUncoveredParametricRanges(Domain1d lineDomain, List { location.Origin, cornerPoint, closest }, AdaptiveGrid.VerticesInsertionMethod.ConnectAndCut); + ExtendToCorridor(new Line(strip.First().Point, cornerPoint), line.Segment); return strip.First(); } } From c054f3f72b5ae363aac9920f9b8e480815e29652 Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Thu, 7 Dec 2023 13:26:49 +0200 Subject: [PATCH 10/21] Distinguish end points inside and outside of a room --- .../src/AdaptiveGridBuilder.cs | 170 +++++++++++++----- .../src/RouteDistanceConfiguration.cs | 82 +-------- .../src/WalkingDistanceConfiguration.cs | 2 +- 3 files changed, 127 insertions(+), 127 deletions(-) diff --git a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs index 59139c82..d032d073 100644 --- a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs +++ b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs @@ -19,90 +19,168 @@ internal class AdaptiveGridBuilder private AdaptiveGrid _grid; private Dictionary> _roomExits; + private List<(CirculationSegment Segment, Polyline Centerline)> _centerlines = new(); public AdaptiveGrid Build(IEnumerable corridors, IEnumerable rooms, List? walls = null, List? doors = null) { - var centerlines = new List<(CirculationSegment Segment, Polyline Centerline)>(); foreach (var item in corridors) { var centerLine = CorridorCenterLine(item); if (centerLine != null && centerLine.Vertices.Count > 1) { - centerlines.Add((item, centerLine)); + _centerlines.Add((item, centerLine)); } } _grid = new AdaptiveGrid(new Transform()); - foreach (var line in centerlines) + foreach (var line in _centerlines) { _grid.AddVertices(line.Centerline.Vertices, AdaptiveGrid.VerticesInsertionMethod.ConnectAndSelfIntersect); } - Intersect(centerlines); - Extend(centerlines); + Intersect(_centerlines); + Extend(_centerlines); _roomExits = new Dictionary>(); foreach (var room in rooms) { - var exits = AddRoom(room, centerlines, walls, doors, _grid); + var exits = AddRoom(room, _centerlines, walls, doors); _roomExits.Add(room, exits); } return _grid; } - - /// - /// Add end point to the grid that are close enough to any of existing edges. - /// - /// Exit points positions. - /// AdaptiveGrid to insert new vertices and edge into. - /// Ids of exit vertices that are added to the grid. - public ulong AddEndPoint(Vector3 exit, double snapDistance, out GridVertex closestVertex) + public ulong AddEndPoint(Vector3 exit, double snapDistance) { - var edge = ClosestEdgeOnElevation(exit, out var closest); - closestVertex = null; - if (edge == null) + List verticesToConnect = new(); + foreach (var room in _roomExits) { - return 0u; + var p = room.Key.Boundary.Perimeter.TransformedPolygon(room.Key.Transform); + if (p.Covers(exit)) + { + verticesToConnect.AddRange(room.Value); + break; + } } - var startVertex = _grid.GetVertex(edge.StartId); - var endVertex = _grid.GetVertex(edge.EndId); - - var exitOnLevel = new Vector3(exit.X, exit.Y, closest.Z); - var distance = exitOnLevel.DistanceTo(closest); - - if (closest.IsAlmostEqualTo(startVertex.Point, _grid.Tolerance)) + if (!verticesToConnect.Any()) { - closestVertex = startVertex; + ClosestEdgeOnElevation(exit, out var closest); + if (exit.DistanceTo(closest) < snapDistance) + { + exit = closest; + } + var vertex = LinkToCenterlines(new Transform(exit), snapDistance); + if (vertex != null) + { + AdditionalConnections(vertex); + return vertex.Id; + } + return 0u; } - else if (closest.IsAlmostEqualTo(endVertex.Point, _grid.Tolerance)) + + GridVertex? exitVertex = null; + foreach (var item in verticesToConnect) { - closestVertex = endVertex; + if (item.Point.IsAlmostEqualTo(exit, _grid.Tolerance)) + { + exitVertex = item; + } + else + { + exitVertex = _grid.AddVertex(exit, new ConnectVertexStrategy(item), cut: false); + } } - else + return exitVertex == null ? 0u : exitVertex.Id; + } + + private void AdditionalConnections(GridVertex exit) + { + if (exit.Edges.Count > 2) { - closestVertex = _grid.CutEdge(edge, closest); + return; } - //Snap to existing vertex if it's close enough. - if (distance < snapDistance) + var mainConnection = _grid.GetVertex(exit.Edges.First().OtherVertexId(exit.Id)); + var basePoint = exit.Point; + var maxDist = basePoint.DistanceTo(mainConnection.Point) * 2; + var additionalConnections = new (double Distance, Vector3 Point)[4] + { + (double.MaxValue, Vector3.Origin), + (double.MaxValue, Vector3.Origin), + (double.MaxValue, Vector3.Origin), + (double.MaxValue, Vector3.Origin) + }; + + var xDir = (mainConnection.Point - basePoint).Unitized(); + var yDir = xDir.Cross(Vector3.ZAxis); + + foreach (var edge in _grid.GetEdges()) { - return closestVertex.Id; + if (edge.StartId == mainConnection.Id || edge.EndId == mainConnection.Id) + { + continue; + } + + var start = _grid.GetVertex(edge.StartId); + var end = _grid.GetVertex(edge.EndId); + + var line = new Line(start.Point, end.Point); + var closest = exit.Point.ClosestPointOn(line); + var delta = closest - basePoint; + var length = delta.Length(); + if (length > maxDist) + { + continue; + } + + var directionIndex = -1; + var dot = delta.Unitized().Dot(xDir); + if (dot.ApproximatelyEquals(1, 0.01)) + { + directionIndex = 0; + } + else if (dot.ApproximatelyEquals(-1, 0.01)) + { + directionIndex = 1; + } + + dot = delta.Unitized().Dot(yDir); + if (dot.ApproximatelyEquals(1, 0.01)) + { + directionIndex = 2; + } + else if (dot.ApproximatelyEquals(-1, 0.01)) + { + directionIndex = 3; + } + + if (directionIndex >= 0) + { + var connection = additionalConnections[directionIndex]; + if (length < connection.Distance) + { + additionalConnections[directionIndex] = (length, closest); + } + } } - else + + foreach (var connection in additionalConnections) { - var vertex = _grid.AddVertex(exitOnLevel, new ConnectVertexStrategy(closestVertex), cut: false); - return vertex.Id; + if (connection.Distance != double.MaxValue) + { + _grid.AddEdge(exit.Point, connection.Point); + } } } + public AdaptiveGrid Grid { get { return _grid; } @@ -263,8 +341,8 @@ private void Intersect(List<(CirculationSegment Segment, Polyline Centerline)> c { var start = Grid.GetVertex(edge.StartId); var end = Grid.GetVertex(edge.EndId); - if (!closestRightItem.IsAlmostEqualTo(start.Point) && - !closestRightItem.IsAlmostEqualTo(end.Point)) + if (!closestRightItem.IsAlmostEqualTo(start.Point, _grid.Tolerance) && + !closestRightItem.IsAlmostEqualTo(end.Point, _grid.Tolerance)) { connections.Add(start); connections.Add(end); @@ -349,7 +427,8 @@ private void ExtendToCorridor(Line l, CirculationSegment segment) var inside = trimLine.Trim(transformedPolygon, out _); foreach (var line in inside) { - if (l.PointOnLine(line.Start, true) || l.PointOnLine(line.End, true)) + if (l.PointOnLine(line.Start, true) || l.PointOnLine(line.End, true) || + line.PointOnLine(l.Start, true) || line.PointOnLine(l.End, true)) { Grid.AddEdge(line.Start, line.End); } @@ -370,8 +449,7 @@ private List AddRoom( SpaceBoundary room, List<(CirculationSegment Segment, Polyline Centerline)> centerlines, List? walls, - List? doors, - AdaptiveGrid grid) + List? doors) { var roomExits = new List(); var perimeter = room.Boundary.Perimeter.CollinearPointsRemoved().TransformedPolygon(room.Transform); @@ -423,7 +501,7 @@ private List FindRoomExits( List exitVertices = new(); foreach (var t in exitLocations) { - var v = AddRoomExit(t, centerlines); + var v = LinkToCenterlines(t, _grid.Tolerance); if (v != null) { exitVertices.Add(v); @@ -489,10 +567,10 @@ private List FindUncoveredParametricRanges(Domain1d lineDomain, List centerlines) + private GridVertex? LinkToCenterlines(Transform location, + double snapDistance) { - foreach (var line in centerlines) + foreach (var line in _centerlines) { for (int i = 0; i < line.Centerline.Vertices.Count - 1; i++) { @@ -544,7 +622,7 @@ private List FindUncoveredParametricRanges(Domain1d lineDomain, List { new RoutingVertex(start, 0) }; @@ -106,82 +104,6 @@ public bool OnElevation(double elevation) return Destinations.All(d => d.Z.ApproximatelyEquals(elevation)); } - private void AdditionalConnections(AdaptiveGrid grid, - GridVertex exit, - GridVertex mainConnection) - { - var basePoint = exit.Point; - var maxDist = basePoint.DistanceTo(mainConnection.Point) * 2; - var additionalConnections = new (double Distance, Vector3 Point)[4] - { - (double.MaxValue, Vector3.Origin), - (double.MaxValue, Vector3.Origin), - (double.MaxValue, Vector3.Origin), - (double.MaxValue, Vector3.Origin) - }; - - var xDir = (mainConnection.Point - basePoint).Unitized(); - var yDir = xDir.Cross(Vector3.ZAxis); - - foreach (var edge in grid.GetEdges()) - { - if (edge.StartId == mainConnection.Id || edge.EndId == mainConnection.Id) - { - continue; - } - - var start = grid.GetVertex(edge.StartId); - var end = grid.GetVertex(edge.EndId); - - var line = new Line(start.Point, end.Point); - var closest = exit.Point.ClosestPointOn(line); - var delta = closest - basePoint; - var length = delta.Length(); - if (length > maxDist) - { - continue; - } - - var directionIndex = -1; - var dot = delta.Unitized().Dot(xDir); - if (dot.ApproximatelyEquals(1, 0.01)) - { - directionIndex = 0; - } - else if (dot.ApproximatelyEquals(-1, 0.01)) - { - directionIndex = 1; - } - - dot = delta.Unitized().Dot(yDir); - if (dot.ApproximatelyEquals(1, 0.01)) - { - directionIndex = 2; - } - else if (dot.ApproximatelyEquals(-1, 0.01)) - { - directionIndex = 3; - } - - if (directionIndex >= 0) - { - var connection = additionalConnections[directionIndex]; - if (length < connection.Distance) - { - additionalConnections[directionIndex] = (length, closest); - } - } - } - - foreach (var connection in additionalConnections) - { - if (connection.Distance != double.MaxValue) - { - grid.AddEdge(exit.Point, connection.Point); - } - } - } - public ModelText DestinationLabels() { var texts = new List<(Vector3 Location, Vector3 FacingDirection, Vector3 LineDirection, string Text, Color? Color)>(); diff --git a/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs b/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs index 02df9ddd..cb9d4360 100644 --- a/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs +++ b/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs @@ -64,7 +64,7 @@ public Color Color public void Compute(AdaptiveGridBuilder builder) { - var exit = builder.AddEndPoint(Transform.Origin, _snapingDistance, out _); + var exit = builder.AddEndPoint(Transform.Origin, _snapingDistance); if (exit == 0) { return; From 94c3fd2d53f0adbf71af7b7ed7e2dd370a7ea590 Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Thu, 7 Dec 2023 16:08:32 +0200 Subject: [PATCH 11/21] Use only wall parts, adjacent to corridor if doors not present. --- .../src/AdaptiveGridBuilder.cs | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs index d032d073..a222f66a 100644 --- a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs +++ b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs @@ -15,6 +15,8 @@ internal class AdaptiveGridBuilder { private const double RoomToWallTolerance = 1e-3; private const double MinExitWidth = 0.5; + private const double RoomToCorridorDistanceTolerance = 0.1; + private const double RoomToCorridorDotTolerance = 1e-3; private AdaptiveGrid _grid; @@ -484,7 +486,11 @@ private List FindRoomExits( // Take middle point to give user at least some exits. if (doors == null && !openSections.Any()) { - openSections.Add(roomEdge); + openSections = GetCorridorAdjacentSegments(roomEdge); + if (openSections.Count > 1) + { + openSections = CombineLines(roomEdge, openSections); + } } List exitLocations = new List(); @@ -510,6 +516,63 @@ private List FindRoomExits( return exitVertices; } + public List GetCorridorAdjacentSegments(Line roomSide) + { + List exitLines = new List(); + foreach (var line in _centerlines) + { + foreach (var polygon in line.Segment.Geometry.GetPolygons()) + { + foreach (var side in polygon.offsetPolygon.Segments()) + { + if (side.Direction().IsParallelTo(roomSide.Direction(), RoomToCorridorDotTolerance) && + side.DistanceTo(roomSide) < RoomToCorridorDistanceTolerance && + side.TryGetOverlap(roomSide, RoomToCorridorDistanceTolerance, out Line exitLine)) + { + var v0 = exitLine.Start.ClosestPointOn(roomSide); + var v1 = exitLine.End.ClosestPointOn(roomSide); + exitLines.Add(new Line(v0, v1)); + } + } + } + } + return exitLines; + } + + public List CombineLines(Line roomSide, List corridorAdjacent) + { + List parameters = new List(); + foreach (var c in corridorAdjacent) + { + var p0 = roomSide.GetParameterAt(c.Start); + var p1 = roomSide.GetParameterAt(c.End); + parameters.Add(new Domain1d(p0 < p1 ? p0 : p1, p0 < p1 ? p1 : p0)); + } + parameters = parameters.OrderBy(p => p.Min).ToList(); + + List adjacentRanges = new(); + + var min = Math.Max(roomSide.Domain.Min, parameters.First().Min); + var max = Math.Min(roomSide.Domain.Max, parameters.First().Max); + Domain1d current = new Domain1d(min, max); + foreach (var domain in parameters.Skip(1)) + { + if (domain.Min <= current.Max) + { + var newMax = Math.Max(domain.Max, current.Max); + current = new Domain1d(current.Min, newMax); + } + else + { + adjacentRanges.Add(current); + current = domain; + } + } + + adjacentRanges.Add(current); + return adjacentRanges.Select(r => new Line(roomSide.PointAt(r.Min), roomSide.PointAt(r.Max))).ToList(); + } + public List GetOpenPassages(Line roomSide, List? walls) { if (walls == null) From c19982470464a11eb7e737228ff00a7ac023e4b8 Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Thu, 7 Dec 2023 16:11:22 +0200 Subject: [PATCH 12/21] Don't show grid. --- .../src/TravelDistanceAnalyzer.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs b/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs index 796af741..3825d0a7 100644 --- a/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs +++ b/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs @@ -87,13 +87,6 @@ public static TravelDistanceAnalyzerOutputs Execute(Dictionary in config.Compute(builder); output.Model.AddElement(config.DestinationLabels()); } - - //Grid visualization for debug purposes - var a = new AdaptiveGraphRouting(builder.Grid, new RoutingConfiguration()); - var elements = a.RenderElements( - new List(), - new List()); - output.Model.AddElements(elements); } output.Model.AddElements(walkingDistanceConfigs); @@ -199,5 +192,11 @@ private static List CreateWalkingDistanceConfigura }); return walkingDistanceConfigs; } + + private static IList GridDebugVisualization(AdaptiveGrid grid) + { + var a = new AdaptiveGraphRouting(grid, new RoutingConfiguration()); + return a.RenderElements(new List(), new List()); + } } } \ No newline at end of file From f7917b0794bae6d15aafc83983959e6bc32816d5 Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Thu, 7 Dec 2023 16:18:57 +0200 Subject: [PATCH 13/21] Eliminate warnings --- .../src/AdaptiveGridBuilder.cs | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs index a222f66a..4f562dc2 100644 --- a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs +++ b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs @@ -7,7 +7,6 @@ using System.Text; using System.Threading.Tasks; using GridVertex = Elements.Spatial.AdaptiveGrid.Vertex; -using DotLiquid.Tags; namespace TravelDistanceAnalyzer { @@ -20,9 +19,14 @@ internal class AdaptiveGridBuilder private AdaptiveGrid _grid; - private Dictionary> _roomExits; + private Dictionary> _roomExits = new(); private List<(CirculationSegment Segment, Polyline Centerline)> _centerlines = new(); + public AdaptiveGridBuilder() + { + _grid = new AdaptiveGrid(new Transform()); + } + public AdaptiveGrid Build(IEnumerable corridors, IEnumerable rooms, List? walls = null, @@ -37,8 +41,6 @@ public AdaptiveGrid Build(IEnumerable corridors, } } - _grid = new AdaptiveGrid(new Transform()); - foreach (var line in _centerlines) { _grid.AddVertices(line.Centerline.Vertices, @@ -48,7 +50,6 @@ public AdaptiveGrid Build(IEnumerable corridors, Intersect(_centerlines); Extend(_centerlines); - _roomExits = new Dictionary>(); foreach (var room in rooms) { var exits = AddRoom(room, _centerlines, walls, doors); @@ -193,10 +194,10 @@ public Dictionary> RoomExits get { return _roomExits; } } - private Edge ClosestEdgeOnElevation(Vector3 location, out Vector3 point) + private Edge? ClosestEdgeOnElevation(Vector3 location, out Vector3 point) { double lowestDist = double.MaxValue; - Edge closestEdge = null; + Edge? closestEdge = null; point = Vector3.Origin; foreach (var e in Grid.GetEdges()) { @@ -326,7 +327,7 @@ private void Intersect(List<(CirculationSegment Segment, Polyline Centerline)> c leftVertex = _grid.GetVertex(leftId); } - if (!rightExist) + if (leftVertex != null && !rightExist) { _grid.TryGetVertexIndex(rightVertices[closestRightProximity], out var leftCon); _grid.TryGetVertexIndex(rightVertices[closestRightProximity + 1], out var rightCon); @@ -355,7 +356,7 @@ private void Intersect(List<(CirculationSegment Segment, Polyline Centerline)> c } } } - else if (leftVertex.Id != rightId) + else if (leftVertex != null && leftVertex.Id != rightId) { _grid.AddEdge(leftVertex.Id, rightId); } @@ -369,8 +370,8 @@ private void Intersect(List<(CirculationSegment Segment, Polyline Centerline)> c { while (start.Id != endId) { - GridVertex otherVertex = null; - Edge edge = null; + GridVertex otherVertex = start; + Edge? edge = null; foreach (var e in start.Edges) { otherVertex = _grid.GetVertex(e.OtherVertexId(start.Id)); @@ -644,7 +645,7 @@ private List FindUncoveredParametricRanges(Domain1d lineDomain, List Date: Thu, 7 Dec 2023 17:04:06 +0200 Subject: [PATCH 14/21] Add Epsiplon check to negate noise in CombineLines --- TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs index 4f562dc2..948c9739 100644 --- a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs +++ b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs @@ -558,7 +558,8 @@ public List CombineLines(Line roomSide, List corridorAdjacent) Domain1d current = new Domain1d(min, max); foreach (var domain in parameters.Skip(1)) { - if (domain.Min <= current.Max) + // Line domain is between 0 and length so EPSILON can be used here without scaling concerns. + if (domain.Min <= current.Max + Vector3.EPSILON) { var newMax = Math.Max(domain.Max, current.Max); current = new Domain1d(current.Min, newMax); From 9bd53a188f7c448021cb3467c7d12727c5a9c27e Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Thu, 7 Dec 2023 17:06:53 +0200 Subject: [PATCH 15/21] Prepare to PR --- .../src/AdaptiveGridExtensions.cs | 4 +++- TravelDistanceAnalyzer/src/LinesRepresentation.cs | 6 +++--- .../src/RouteDistanceConfiguration.cs | 1 - .../src/WalkingDistanceConfiguration.cs | 13 +++---------- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs b/TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs index 3ec8f985..7906ed25 100644 --- a/TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs +++ b/TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs @@ -12,7 +12,7 @@ namespace TravelDistanceAnalyzer { internal static class AdaptiveGridExtensions { - public static Dictionary ComputeDistances( + public static Dictionary CalculateDistances( this AdaptiveGrid grid, IEnumerable leafs, IDictionary tree) @@ -44,6 +44,8 @@ public static double CalculateDistanceRecursive( } var tail = grid.GetVertex(node.Trunk.Id); + // With large grids, stack overflow is possible here. + // TODO: replace with stack based function. var d = CalculateDistanceRecursive(grid, tail, tree, accumulatedDistances); d += tail.Point.DistanceTo(head.Point); accumulatedDistances[edge] = d; diff --git a/TravelDistanceAnalyzer/src/LinesRepresentation.cs b/TravelDistanceAnalyzer/src/LinesRepresentation.cs index 603bfa30..e49a78b4 100644 --- a/TravelDistanceAnalyzer/src/LinesRepresentation.cs +++ b/TravelDistanceAnalyzer/src/LinesRepresentation.cs @@ -13,9 +13,9 @@ public class LinesRepresentation : ElementRepresentation private bool _isSelectable = true; /// - /// Initializes a new instance of CurveRepresentation. + /// Initializes a new instance of LinesRepresentation. /// - /// The curve. + /// The lines. /// If curve is selectable. public LinesRepresentation(List lines, bool isSelectable) { @@ -24,7 +24,7 @@ public LinesRepresentation(List lines, bool isSelectable) } /// - /// Indicates if curve is selectable. + /// Indicates if lines are selectable. /// public bool IsSelectable => _isSelectable; diff --git a/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs b/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs index 9e71d088..2d73c73d 100644 --- a/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs +++ b/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs @@ -2,7 +2,6 @@ using Elements.Geometry; using Elements.Spatial.AdaptiveGrid; using TravelDistanceAnalyzer; -using GridVertex = Elements.Spatial.AdaptiveGrid.Vertex; namespace Elements { diff --git a/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs b/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs index cb9d4360..01d3cc73 100644 --- a/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs +++ b/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs @@ -88,14 +88,14 @@ public void Compute(AdaptiveGridBuilder builder) List<(SpaceBoundary Room, GridVertex Exit)> bestExits = new(); foreach (var room in filteredRooms) { - var bestExit = ChooseExit(grid, tree, room.Value); + var bestExit = ChooseClosestExit(grid, tree, room.Value); if (bestExit != null) { bestExits.Add((room.Key, bestExit)); } } - var distances = grid.ComputeDistances(bestExits.Select(e => e.Exit), tree); + var distances = grid.CalculateDistances(bestExits.Select(e => e.Exit), tree); RecordStatistics(grid, distances, bestExits, tree); //Representation instance will apply transformation so everything need to be in its local frame. @@ -147,14 +147,7 @@ private void RecordStatistics(AdaptiveGrid grid, Statistics = statisticsByType.Select(s => s.Value.Stat).ToList(); } - /// - /// For each room find exit that provides smallest distance. - /// - /// AdaptiveGrid to traverse. - /// Traveling tree from rooms corners to exits. - /// Combinations of exits and their corresponding corners for each room. - /// Most distance efficient exit. - private static GridVertex? ChooseExit( + private static GridVertex? ChooseClosestExit( AdaptiveGrid grid, IDictionary tree, List exits) From 806920abe76a50625ecd87a65e57d035ccadb52d Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Thu, 7 Dec 2023 17:10:21 +0200 Subject: [PATCH 16/21] Mention repository URL --- TravelDistanceAnalyzer/hypar.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TravelDistanceAnalyzer/hypar.json b/TravelDistanceAnalyzer/hypar.json index eec32d7b..f67b4679 100644 --- a/TravelDistanceAnalyzer/hypar.json +++ b/TravelDistanceAnalyzer/hypar.json @@ -139,7 +139,7 @@ "https://schemas.hypar.io/ThickenedPolyline.json", "https://schemas.hypar.io/Level.json" ], - "repository_url": "https://github.com/hypar-io/function", + "repository_url": "https://github.com/hypar-io/HyparSpace/TravelDistanceAnalyzer", "last_updated": "0001-01-01T00:00:00", "cli_version": "1.10.0" } \ No newline at end of file From 9dad486422ba29ac9f28f8d7f517d15bd84b90c3 Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Thu, 7 Dec 2023 17:33:03 +0200 Subject: [PATCH 17/21] Fix average distance calculation --- TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs b/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs index 01d3cc73..227a4706 100644 --- a/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs +++ b/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs @@ -127,10 +127,10 @@ private void RecordStatistics(AdaptiveGrid grid, var type = input.Room.ProgramType; if (statisticsByType.TryGetValue(type, out var value)) { - value.Num++; value.Stat.LongestDistance = Math.Max(value.Stat.LongestDistance, distance); value.Stat.ShortestDistance = Math.Min(value.Stat.ShortestDistance, distance); value.Stat.AverageDistance += distance; + statisticsByType[type] = (value.Stat, value.Num + 1); } else { From e85ee87d1eb715013ae98bfc10547d7e641ccd5f Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Wed, 13 Dec 2023 18:30:43 +0200 Subject: [PATCH 18/21] Code review changes --- .../src/AdaptiveGridBuilder.cs | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs index 948c9739..ee4a5379 100644 --- a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs +++ b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs @@ -16,6 +16,7 @@ internal class AdaptiveGridBuilder private const double MinExitWidth = 0.5; private const double RoomToCorridorDistanceTolerance = 0.1; private const double RoomToCorridorDotTolerance = 1e-3; + private const double AxisGroupingDotTolerance = 0.01; private AdaptiveGrid _grid; @@ -29,8 +30,8 @@ public AdaptiveGridBuilder() public AdaptiveGrid Build(IEnumerable corridors, IEnumerable rooms, - List? walls = null, - List? doors = null) + IEnumerable? walls = null, + IEnumerable? doors = null) { foreach (var item in corridors) { @@ -47,12 +48,12 @@ public AdaptiveGrid Build(IEnumerable corridors, AdaptiveGrid.VerticesInsertionMethod.ConnectAndSelfIntersect); } - Intersect(_centerlines); - Extend(_centerlines); + CreateConnectionEdges(_centerlines); + ExtendOntoClosestCorridor(_centerlines); foreach (var room in rooms) { - var exits = AddRoom(room, _centerlines, walls, doors); + var exits = AddRoom(room, walls, doors); _roomExits.Add(room, exits); } @@ -64,8 +65,8 @@ public ulong AddEndPoint(Vector3 exit, double snapDistance) List verticesToConnect = new(); foreach (var room in _roomExits) { - var p = room.Key.Boundary.Perimeter.TransformedPolygon(room.Key.Transform); - if (p.Covers(exit)) + var boundaryOnLevel = room.Key.Boundary.Perimeter.TransformedPolygon(room.Key.Transform); + if (boundaryOnLevel.Covers(exit)) { verticesToConnect.AddRange(room.Value); break; @@ -74,7 +75,7 @@ public ulong AddEndPoint(Vector3 exit, double snapDistance) if (!verticesToConnect.Any()) { - ClosestEdgeOnElevation(exit, out var closest); + FindClosestEdgeOnElevation(exit, out var closest); if (exit.DistanceTo(closest) < snapDistance) { exit = closest; @@ -145,21 +146,21 @@ private void AdditionalConnections(GridVertex exit) var directionIndex = -1; var dot = delta.Unitized().Dot(xDir); - if (dot.ApproximatelyEquals(1, 0.01)) + if (dot.ApproximatelyEquals(1, AxisGroupingDotTolerance)) { directionIndex = 0; } - else if (dot.ApproximatelyEquals(-1, 0.01)) + else if (dot.ApproximatelyEquals(-1, AxisGroupingDotTolerance)) { directionIndex = 1; } dot = delta.Unitized().Dot(yDir); - if (dot.ApproximatelyEquals(1, 0.01)) + if (dot.ApproximatelyEquals(1, AxisGroupingDotTolerance)) { directionIndex = 2; } - else if (dot.ApproximatelyEquals(-1, 0.01)) + else if (dot.ApproximatelyEquals(-1, AxisGroupingDotTolerance)) { directionIndex = 3; } @@ -194,7 +195,7 @@ public Dictionary> RoomExits get { return _roomExits; } } - private Edge? ClosestEdgeOnElevation(Vector3 location, out Vector3 point) + private Edge? FindClosestEdgeOnElevation(Vector3 location, out Vector3 point) { double lowestDist = double.MaxValue; Edge? closestEdge = null; @@ -240,7 +241,7 @@ private Polyline CorridorCenterLine(CirculationSegment corridor) /// /// Corridor segments with precalculated center lines. /// AdaptiveGrid to insert new vertices and edge into. - private void Intersect(List<(CirculationSegment Segment, Polyline Centerline)> centerlines) + private void CreateConnectionEdges(List<(CirculationSegment Segment, Polyline Centerline)> centerlines) { foreach (var item in centerlines) { @@ -400,7 +401,7 @@ private void Intersect(List<(CirculationSegment Segment, Polyline Centerline)> c return null; } - private void Extend(List<(CirculationSegment Segment, Polyline Centerline)> centerlines) + private void ExtendOntoClosestCorridor(List<(CirculationSegment Segment, Polyline Centerline)> centerlines) { foreach (var item in centerlines) { @@ -450,15 +451,14 @@ private void ExtendToCorridor(Line l, CirculationSegment segment) /// private List AddRoom( SpaceBoundary room, - List<(CirculationSegment Segment, Polyline Centerline)> centerlines, - List? walls, - List? doors) + IEnumerable? walls, + IEnumerable? doors) { var roomExits = new List(); var perimeter = room.Boundary.Perimeter.CollinearPointsRemoved().TransformedPolygon(room.Transform); foreach (var roomEdge in perimeter.Segments()) { - foreach(var exit in FindRoomExits(roomEdge, centerlines, walls, doors)) + foreach(var exit in FindRoomExits(roomEdge, walls, doors)) { roomExits.Add(exit); } @@ -476,9 +476,8 @@ private List AddRoom( /// New Vertex on room edge midpoint. private List FindRoomExits( Line roomEdge, - List<(CirculationSegment Segment, Polyline Centerline)> centerlines, - List? walls, - List? doors) + IEnumerable? walls, + IEnumerable? doors) { var doorsOnWall = doors?.Where(d => roomEdge.PointOnLine(d.Transform.Origin, false, RoomToWallTolerance)); var openSections = GetOpenPassages(roomEdge, walls); @@ -575,7 +574,7 @@ public List CombineLines(Line roomSide, List corridorAdjacent) return adjacentRanges.Select(r => new Line(roomSide.PointAt(r.Min), roomSide.PointAt(r.Max))).ToList(); } - public List GetOpenPassages(Line roomSide, List? walls) + public List GetOpenPassages(Line roomSide, IEnumerable? walls) { if (walls == null) { From a7b694451c83c460c6e343886004524ff214796e Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Thu, 14 Dec 2023 11:13:08 +0200 Subject: [PATCH 19/21] Add Exception, remove unused default parameters --- TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs index ee4a5379..73a1d4ab 100644 --- a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs +++ b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs @@ -30,8 +30,8 @@ public AdaptiveGridBuilder() public AdaptiveGrid Build(IEnumerable corridors, IEnumerable rooms, - IEnumerable? walls = null, - IEnumerable? doors = null) + IEnumerable? walls, + IEnumerable? doors) { foreach (var item in corridors) { @@ -106,6 +106,11 @@ public ulong AddEndPoint(Vector3 exit, double snapDistance) private void AdditionalConnections(GridVertex exit) { + if (!exit.Edges.Any()) + { + throw new Exception("Free vertices should not be present in the grid"); + } + if (exit.Edges.Count > 2) { return; From aaca8f04cf3649c71cca827737c4a9a6ea8cfe50 Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Fri, 15 Dec 2023 16:54:50 +0200 Subject: [PATCH 20/21] Rename several functions --- TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs | 8 ++++---- TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs | 6 +++--- TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs | 4 ++-- TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs | 8 ++++---- .../src/WalkingDistanceConfiguration.cs | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs index 73a1d4ab..1d7d8993 100644 --- a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs +++ b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs @@ -35,7 +35,7 @@ public AdaptiveGrid Build(IEnumerable corridors, { foreach (var item in corridors) { - var centerLine = CorridorCenterLine(item); + var centerLine = GetCorridorCenterLine(item); if (centerLine != null && centerLine.Vertices.Count > 1) { _centerlines.Add((item, centerLine)); @@ -83,7 +83,7 @@ public ulong AddEndPoint(Vector3 exit, double snapDistance) var vertex = LinkToCenterlines(new Transform(exit), snapDistance); if (vertex != null) { - AdditionalConnections(vertex); + AddAdditionalConnections(vertex); return vertex.Id; } return 0u; @@ -104,7 +104,7 @@ public ulong AddEndPoint(Vector3 exit, double snapDistance) return exitVertex == null ? 0u : exitVertex.Id; } - private void AdditionalConnections(GridVertex exit) + private void AddAdditionalConnections(GridVertex exit) { if (!exit.Edges.Any()) { @@ -226,7 +226,7 @@ public Dictionary> RoomExits return closestEdge; } - private Polyline CorridorCenterLine(CirculationSegment corridor) + private Polyline GetCorridorCenterLine(CirculationSegment corridor) { double offsetDistance = corridor.Geometry.GetOffset(); var corridorPolyline = corridor.Geometry.Polyline; diff --git a/TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs b/TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs index 7906ed25..26ab018a 100644 --- a/TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs +++ b/TravelDistanceAnalyzer/src/AdaptiveGridExtensions.cs @@ -52,9 +52,9 @@ public static double CalculateDistanceRecursive( return d; } - public static List TreeVisualization(this AdaptiveGrid grid, - IEnumerable edges, - Transform transform) + public static List VisualizeTree(this AdaptiveGrid grid, + IEnumerable edges, + Transform transform) { List lines = new List(); foreach (var item in edges) diff --git a/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs b/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs index 2d73c73d..14c4770a 100644 --- a/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs +++ b/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs @@ -91,7 +91,7 @@ public void Compute(AdaptiveGridBuilder builder) } _lineRepresentation.AddRange( - grid.TreeVisualization(accumulatedDistances.Keys, new Transform(0, 0, _routeHeight))); + grid.VisualizeTree(accumulatedDistances.Keys, new Transform(0, 0, _routeHeight))); start = end; } @@ -103,7 +103,7 @@ public bool OnElevation(double elevation) return Destinations.All(d => d.Z.ApproximatelyEquals(elevation)); } - public ModelText DestinationLabels() + public ModelText GrawDestinationLabels() { var texts = new List<(Vector3 Location, Vector3 FacingDirection, Vector3 LineDirection, string Text, Color? Color)>(); for (int i = 0; i < Destinations.Count; i++) diff --git a/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs b/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs index 3825d0a7..61a88815 100644 --- a/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs +++ b/TravelDistanceAnalyzer/src/TravelDistanceAnalyzer.cs @@ -72,7 +72,7 @@ public static TravelDistanceAnalyzerOutputs Execute(Dictionary in level = new Level(levelCorridors.First().Elevation, null, null); } - var levelWalls = WallsForLevel(walls, level); + var levelWalls = CollectWallsForLevel(walls, level); var builder = new AdaptiveGridBuilder(); builder.Build(levelCorridors, levelRooms, levelWalls, doors); @@ -85,7 +85,7 @@ public static TravelDistanceAnalyzerOutputs Execute(Dictionary in foreach (var config in routeDistanceConfigs.Where(c => c.OnElevation(level.Elevation))) { config.Compute(builder); - output.Model.AddElement(config.DestinationLabels()); + output.Model.AddElement(config.GrawDestinationLabels()); } } @@ -94,7 +94,7 @@ public static TravelDistanceAnalyzerOutputs Execute(Dictionary in return output; } - private static List? WallsForLevel(List? allWalls, Level level) + private static List? CollectWallsForLevel(List? allWalls, Level level) { List? levelWalls = null; if (allWalls != null) @@ -193,7 +193,7 @@ private static List CreateWalkingDistanceConfigura return walkingDistanceConfigs; } - private static IList GridDebugVisualization(AdaptiveGrid grid) + private static IList GetGridDebugVisualization(AdaptiveGrid grid) { var a = new AdaptiveGraphRouting(grid, new RoutingConfiguration()); return a.RenderElements(new List(), new List()); diff --git a/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs b/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs index 227a4706..58613d04 100644 --- a/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs +++ b/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs @@ -100,7 +100,7 @@ public void Compute(AdaptiveGridBuilder builder) //Representation instance will apply transformation so everything need to be in its local frame. Transform t = Transform.Inverted().Moved(z: _routeHeight); - _lineRepresentation = new LinesRepresentation(grid.TreeVisualization(distances.Keys, t), true); + _lineRepresentation = new LinesRepresentation(grid.VisualizeTree(distances.Keys, t), true); } public bool OnElevation(double elevation) From 3bf40dfac0eb79331770364062044fb8ff9152c7 Mon Sep 17 00:00:00 2001 From: Dmytro Muravskyi Date: Tue, 19 Dec 2023 16:07:40 +0200 Subject: [PATCH 21/21] Restructure classes for better readability --- .../src/AdaptiveGridBuilder.cs | 73 +++++++++---------- .../src/RouteDistanceConfiguration.cs | 24 +++--- .../src/WalkingDistanceConfiguration.cs | 24 +++--- 3 files changed, 60 insertions(+), 61 deletions(-) diff --git a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs index 1d7d8993..476dd129 100644 --- a/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs +++ b/TravelDistanceAnalyzer/src/AdaptiveGridBuilder.cs @@ -28,6 +28,16 @@ public AdaptiveGridBuilder() _grid = new AdaptiveGrid(new Transform()); } + public AdaptiveGrid Grid + { + get { return _grid; } + } + + public Dictionary> RoomExits + { + get { return _roomExits; } + } + public AdaptiveGrid Build(IEnumerable corridors, IEnumerable rooms, IEnumerable? walls, @@ -101,7 +111,7 @@ public ulong AddEndPoint(Vector3 exit, double snapDistance) exitVertex = _grid.AddVertex(exit, new ConnectVertexStrategy(item), cut: false); } } - return exitVertex == null ? 0u : exitVertex.Id; + return exitVertex?.Id ?? 0u; } private void AddAdditionalConnections(GridVertex exit) @@ -189,17 +199,6 @@ private void AddAdditionalConnections(GridVertex exit) } } - - public AdaptiveGrid Grid - { - get { return _grid; } - } - - public Dictionary> RoomExits - { - get { return _roomExits; } - } - private Edge? FindClosestEdgeOnElevation(Vector3 location, out Vector3 point) { double lowestDist = double.MaxValue; @@ -245,9 +244,11 @@ private Polyline GetCorridorCenterLine(CirculationSegment corridor) /// For each line points are found with other corridors and itself that are closer that their combined width. /// /// Corridor segments with precalculated center lines. - /// AdaptiveGrid to insert new vertices and edge into. private void CreateConnectionEdges(List<(CirculationSegment Segment, Polyline Centerline)> centerlines) { + const int InvalidProximity = -1; + const int MaxProximityDifference = 2; + foreach (var item in centerlines) { var leftVertices = item.Centerline.Vertices; @@ -258,7 +259,7 @@ private void CreateConnectionEdges(List<(CirculationSegment Segment, Polyline Ce for (int i = 0; i < leftVertices.Count - 1; i++) { Vector3 closestLeftItem = Vector3.Origin, closestRightItem = Vector3.Origin; - int closestLeftProximity = -1, closestRightProximity = -1; + int closestLeftProximity = InvalidProximity, closestRightProximity = InvalidProximity; double closestDistance = double.PositiveInfinity; Line leftLine = new Line(leftVertices[i], leftVertices[i + 1]); @@ -277,7 +278,7 @@ private void CreateConnectionEdges(List<(CirculationSegment Segment, Polyline Ce for (int j = 0; j < rightVertices.Count - 1; j++) { - if (item == candidate && Math.Abs(i - j) < 2) + if (item == candidate && Math.Abs(i - j) < MaxProximityDifference) { continue; } @@ -299,7 +300,7 @@ private void CreateConnectionEdges(List<(CirculationSegment Segment, Polyline Ce } } - if (closestLeftProximity == -1 || closestRightProximity == -1) + if (closestLeftProximity == InvalidProximity || closestRightProximity == InvalidProximity) { continue; } @@ -376,12 +377,12 @@ private void CreateConnectionEdges(List<(CirculationSegment Segment, Polyline Ce { while (start.Id != endId) { - GridVertex otherVertex = start; + GridVertex nextVertex = start; Edge? edge = null; foreach (var e in start.Edges) { - otherVertex = _grid.GetVertex(e.OtherVertexId(start.Id)); - var edgeDirection = (otherVertex.Point - start.Point).Unitized(); + nextVertex = _grid.GetVertex(e.OtherVertexId(start.Id)); + var edgeDirection = (nextVertex.Point - start.Point).Unitized(); if (edgeDirection.Dot(direction).ApproximatelyEquals(1)) { edge = e; @@ -394,13 +395,13 @@ private void CreateConnectionEdges(List<(CirculationSegment Segment, Polyline Ce throw new Exception("End edge is not reached"); } - var edgeLine = new Line(start.Point, otherVertex.Point); + var edgeLine = new Line(start.Point, nextVertex.Point); if (edgeLine.PointOnLine(destination, true)) { return edge; } - start = otherVertex; + start = nextVertex; } return null; @@ -425,21 +426,21 @@ private void ExtendOntoClosestCorridor(List<(CirculationSegment Segment, Polylin } } - private void ExtendToCorridor(Line l, CirculationSegment segment) + private void ExtendToCorridor(Line line, CirculationSegment segment) { foreach (var polygon in segment.Geometry.GetPolygons()) { var maxDistance = polygon.offsetPolygon.Segments().Max(s => s.Length()); var transformedPolygon = polygon.offsetPolygon.TransformedPolygon(segment.Transform); - var trimLine = new Line(l.Start - l.Direction() * maxDistance, - l.End + l.Direction() * maxDistance); - var inside = trimLine.Trim(transformedPolygon, out _); - foreach (var line in inside) + var trimLine = new Line(line.Start - line.Direction() * maxDistance, + line.End + line.Direction() * maxDistance); + var insideLines = trimLine.Trim(transformedPolygon, out _); + foreach (var il in insideLines) { - if (l.PointOnLine(line.Start, true) || l.PointOnLine(line.End, true) || - line.PointOnLine(l.Start, true) || line.PointOnLine(l.End, true)) + if (il.PointOnLine(il.Start, true) || il.PointOnLine(il.End, true) || + il.PointOnLine(il.Start, true) || il.PointOnLine(il.End, true)) { - Grid.AddEdge(line.Start, line.End); + Grid.AddEdge(il.Start, il.End); } } } @@ -451,24 +452,22 @@ private void ExtendToCorridor(Line l, CirculationSegment segment) /// This is very simple approaches that ignores voids or obstacles inside room and won't work for complex rooms. /// /// Room geometry. - /// Corridor segments with precalculated center lines. - /// AdaptiveGrid to insert new vertices and edge into. /// private List AddRoom( SpaceBoundary room, IEnumerable? walls, IEnumerable? doors) { - var roomExits = new List(); + var roomExitVertices = new List(); var perimeter = room.Boundary.Perimeter.CollinearPointsRemoved().TransformedPolygon(room.Transform); foreach (var roomEdge in perimeter.Segments()) { foreach(var exit in FindRoomExits(roomEdge, walls, doors)) { - roomExits.Add(exit); + roomExitVertices.Add(exit); } } - return roomExits; + return roomExitVertices; } /// @@ -521,7 +520,7 @@ private List FindRoomExits( return exitVertices; } - public List GetCorridorAdjacentSegments(Line roomSide) + private List GetCorridorAdjacentSegments(Line roomSide) { List exitLines = new List(); foreach (var line in _centerlines) @@ -544,7 +543,7 @@ public List GetCorridorAdjacentSegments(Line roomSide) return exitLines; } - public List CombineLines(Line roomSide, List corridorAdjacent) + private List CombineLines(Line roomSide, List corridorAdjacent) { List parameters = new List(); foreach (var c in corridorAdjacent) @@ -579,7 +578,7 @@ public List CombineLines(Line roomSide, List corridorAdjacent) return adjacentRanges.Select(r => new Line(roomSide.PointAt(r.Min), roomSide.PointAt(r.Max))).ToList(); } - public List GetOpenPassages(Line roomSide, IEnumerable? walls) + private List GetOpenPassages(Line roomSide, IEnumerable? walls) { if (walls == null) { diff --git a/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs b/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs index 14c4770a..35de10eb 100644 --- a/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs +++ b/TravelDistanceAnalyzer/src/RouteDistanceConfiguration.cs @@ -7,14 +7,14 @@ namespace Elements { internal class RouteDistanceConfiguration : GeometricElement { - public string AddId; - - public List Destinations; - private double _snapingDistance = 0.25; private double _routeHeight = 1; private List _lineRepresentation = new(); + public string AddId; + + public List Destinations; + public RouteDistanceConfiguration(string addId, IList destinations) { AddId = addId; @@ -23,6 +23,14 @@ public RouteDistanceConfiguration(string addId, IList destinations) Material = new Material("Route", color); } + public double Distance { get; set; } + + public Color Color + { + get { return Material.Color; } + set { Material.Color = value; } + } + public override void UpdateRepresentations() { if (RepresentationInstances.Count == 0) @@ -43,14 +51,6 @@ public override void UpdateRepresentations() } } - public double Distance { get; set; } - - public Color Color - { - get { return Material.Color; } - set { Material.Color = value; } - } - public void Compute(AdaptiveGridBuilder builder) { if (Destinations.Count < 2) diff --git a/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs b/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs index 58613d04..4b6b5795 100644 --- a/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs +++ b/TravelDistanceAnalyzer/src/WalkingDistanceConfiguration.cs @@ -9,15 +9,15 @@ namespace Elements { internal class WalkingDistanceConfiguration : GeometricElement { + private double _snapingDistance = 0.25; + private double _routeHeight = 1; + private LinesRepresentation? _lineRepresentation = null; + public string AddId; [JsonProperty("Program Types")] public List ProgramTypes; - private double _snapingDistance = 0.25; - private double _routeHeight = 1; - private LinesRepresentation? _lineRepresentation = null; - public WalkingDistanceConfiguration(string addId, IList programTypes, Transform transform) { AddId = addId; @@ -37,6 +37,14 @@ public WalkingDistanceConfiguration(string addId, IList programTypes, Tr Statistics = new(); } + public List Statistics { get; set; } + + public Color Color + { + get { return Material.Color; } + set { Material.Color = value; } + } + public override void UpdateRepresentations() { if (RepresentationInstances.Count == 0) @@ -54,14 +62,6 @@ public override void UpdateRepresentations() } } - public List Statistics { get; set; } - - public Color Color - { - get { return Material.Color; } - set { Material.Color = value; } - } - public void Compute(AdaptiveGridBuilder builder) { var exit = builder.AddEndPoint(Transform.Origin, _snapingDistance);