diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8a30d25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,398 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml diff --git a/BuildProcessTemplates/DefaultTemplate.11.1.xaml b/BuildProcessTemplates/DefaultTemplate.11.1.xaml deleted file mode 100644 index 60eac4b..0000000 --- a/BuildProcessTemplates/DefaultTemplate.11.1.xaml +++ /dev/null @@ -1,543 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - [New Microsoft.TeamFoundation.Build.Workflow.Activities.BuildSettings()] - [False] - [New Microsoft.TeamFoundation.Build.Workflow.Activities.TestSpecList(New Microsoft.TeamFoundation.Build.Workflow.Activities.AgileTestPlatformSpec("**\*test*.dll"))] - ["$(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.r)"] - [False] - [True] - [True] - [Microsoft.TeamFoundation.Build.Workflow.Activities.CleanWorkspaceOption.All] - - - - [Microsoft.TeamFoundation.Build.Workflow.Activities.CodeAnalysisOption.AsConfigured] - [True] - [Microsoft.TeamFoundation.Build.Workflow.Activities.ToolPlatform.Auto] - [True] - [New Microsoft.TeamFoundation.Build.Workflow.Activities.SourceAndSymbolServerSettings(True, Nothing)] - [True] - - - - [New Microsoft.TeamFoundation.Build.Workflow.Activities.AgentSettings() With {.MaxWaitTime = New System.TimeSpan(4, 0, 0), .MaxExecutionTime = New System.TimeSpan(0, 0, 0), .TagComparison = Microsoft.TeamFoundation.Build.Workflow.Activities.TagComparison.MatchExactly }] - [Microsoft.TeamFoundation.Build.Workflow.BuildVerbosity.Normal] - - - - - - - All - 11.0 - Assembly references and imported namespaces serialized as XML namespaces - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/BuildProcessTemplates/LabDefaultTemplate.11.xaml b/BuildProcessTemplates/LabDefaultTemplate.11.xaml deleted file mode 100644 index 542717f..0000000 --- a/BuildProcessTemplates/LabDefaultTemplate.11.xaml +++ /dev/null @@ -1,208 +0,0 @@ - - - - - - - - - - - - - - - 11.0 - - - - - - 920,3702 - Assembly references and imported namespaces serialized as XML namespaces - - - - - - - - - - - - - - - - - - - - - True - - - - - - - [LabWorkflowParameters.BuildDetails.BuildUri] - - - [ChildBuildDetail.Uri] - - - - - - - - - - - - [BuildLocation] - - - [If(LabWorkflowParameters.BuildDetails.Configuration Is Nothing, BuildLocation, If(LabWorkflowParameters.BuildDetails.Configuration.IsEmpty Or (SelectedBuildDetail.Information.GetNodesByType(Microsoft.TeamFoundation.Build.Common.InformationTypes.ConfigurationSummary, True)).Count = 1, BuildLocation, If(LabWorkflowParameters.BuildDetails.Configuration.IsPlatformEmptyOrAnyCpu, BuildLocation + "\" + LabWorkflowParameters.BuildDetails.Configuration.Configuration, BuildLocation + "\" + LabWorkflowParameters.BuildDetails.Configuration.Platform + "\" + LabWorkflowParameters.BuildDetails.Configuration.Configuration)))] - - - - - - - - - - - - [LabEnvironmentUri] - - - [LabWorkflowParameters.EnvironmentDetails.LabEnvironmentUri.ToString()] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - [PostDeploymentSnapshotName] - - - [If(LabWorkflowParameters.BuildDetails.IsTeamSystemBuild = True,String.Format("{0}_{1}_{2}", LabWorkflowParameters.DeploymentDetails.PostDeploymentSnapshotName, BuildNumber,BuildDetail.BuildNumber),String.Format("{0}_{1}", LabWorkflowParameters.DeploymentDetails.PostDeploymentSnapshotName, BuildDetail.BuildNumber))] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - [BuildStatus] - - - [Microsoft.TeamFoundation.Build.Client.BuildStatus.PartiallySucceeded] - - - - - - - [BuildStatus] - - - [Microsoft.TeamFoundation.Build.Client.BuildStatus.Failed] - - - - - - - - - - - - \ No newline at end of file diff --git a/BuildProcessTemplates/UpgradeTemplate.xaml b/BuildProcessTemplates/UpgradeTemplate.xaml deleted file mode 100644 index 8ae6923..0000000 --- a/BuildProcessTemplates/UpgradeTemplate.xaml +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - [New Microsoft.TeamFoundation.Build.Workflow.Activities.AgentSettings() With {.MaxWaitTime = New System.TimeSpan(4, 0, 0), .MaxExecutionTime = New System.TimeSpan(0, 0, 0), .TagComparison = Microsoft.TeamFoundation.Build.Workflow.Activities.TagComparison.MatchExactly }] - - - - [Microsoft.TeamFoundation.Build.Workflow.Activities.ToolPlatform.Auto] - [False] - [False] - - - - - - - - - - [Microsoft.TeamFoundation.VersionControl.Client.RecursionType.OneLevel] - [Microsoft.TeamFoundation.Build.Workflow.BuildVerbosity.Normal] - - - - All - Assembly references and imported namespaces serialized as XML namespaces - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index 5d1259f..0932436 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,23 @@ -# **Triangle.NET** (beta 4) by Christian Woltering +# Triangle.NET -Triangle.NET generates 2D (constrained) Delaunay triangulations and high-quality meshes of point sets or planar straight line graphs. It is a C# port of Jonathan Shewchuk's [Triangle](http://www.cs.cmu.edu/~quake/triangle.html) software. +Triangle.NET generates 2D (constrained) Delaunay triangulations and high-quality meshes of point sets or planar straight line graphs. It is a C# port of Jonathan Shewchuk's [Triangle](https://www.cs.cmu.edu/~quake/triangle.html) software. -## Attribution +## Features -This C# libray is created and maintained by Christian Woltering. This repository is a fork of the original [**Triangle.NET** repository at CodePlex](https://triangle.codeplex.com). +* Constrained Delaunay triangulation of planar straight line graphs +* Incremental, sweepline and divide & conquer Delaunay triangulation algorithms +* High-quality triangular meshes with minimum/maximum angle constraints +* Mesh refinement +* Mesh smoothing using centroidal Voronoi tessellation (CVT) +* Node renumbering (Cuthill-McKee) +* Read and write Triangle format files (.node, .poly, .ele) + +## Usage + +Please refer to the [Examples](https://github.com/wo80/Triangle.NET/tree/master/src/Triangle.Examples) project. [Geri Borbás](https://github.com/Geri-Borbas) has prepared a "Release" branch containing the main project source only for submodule usage. ## License -> Licensed under the [**MIT License**](https://en.wikipedia.org/wiki/MIT_License). \ No newline at end of file +The original C code published by Jonathan Shewchuk comes with a proprietary license (see [Triangle README](https://github.com/wo80/Triangle/blob/master/src/Triangle/README)) which, unfortunately, isn't very clear about how a derived work like Triangle.NET should be handled. Though Triangle.NET was published on Codeplex (https://triangle.codeplex.com, no longer available) under the MIT license in 2012, I recommend not using this code in a commercial context. Due to the unclear licensing situation, there will also be no Nuget package release. + +For further discussion, please refer to the open issue [License Confusion](https://github.com/wo80/Triangle.NET/issues/6). diff --git a/Triangle.NET/TestApp/Mesh Explorer.csproj b/Triangle.NET/TestApp/Mesh Explorer.csproj deleted file mode 100644 index ce945f7..0000000 --- a/Triangle.NET/TestApp/Mesh Explorer.csproj +++ /dev/null @@ -1,198 +0,0 @@ - - - - Debug - x86 - 8.0.30703 - 2.0 - {336AAF8A-5316-4303-9E73-5E38BD0B28AF} - WinExe - Properties - MeshExplorer - Mesh Explorer - v4.0 - Client - 512 - SAK - SAK - SAK - SAK - - - x86 - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - x86 - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - Component - - - Component - - - Component - - - Component - - - Component - - - Component - - - Component - - - - Form - - - Form - - - FormExport.cs - - - Form - - - FormGenerator.cs - - - Form - - - FormLog.cs - - - Form - - - FormMain.cs - - - Form - - - FormTopology.cs - - - - - - - - - - - - - - - - - - - - - UserControl - - - TopologyControlView.cs - - - Component - - - - - UserControl - - - AboutView.cs - - - - UserControl - - - MeshControlView.cs - - - UserControl - - - StatisticView.cs - - - FormExport.cs - - - FormGenerator.cs - - - FormLog.cs - - - FormMain.cs - - - FormTopology.cs - - - TopologyControlView.cs - - - AboutView.cs - - - MeshControlView.cs - - - StatisticView.cs - - - - - {41022e0e-bd0f-439e-bc3a-aabb1b43471b} - Triangle.Rendering - - - {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B} - Triangle - - - - - - \ No newline at end of file diff --git a/Triangle.NET/TestApp/Mesh Explorer.csproj.vspscc b/Triangle.NET/TestApp/Mesh Explorer.csproj.vspscc deleted file mode 100644 index feffdec..0000000 --- a/Triangle.NET/TestApp/Mesh Explorer.csproj.vspscc +++ /dev/null @@ -1,10 +0,0 @@ -"" -{ -"FILE_VERSION" = "9237" -"ENLISTMENT_CHOICE" = "NEVER" -"PROJECT_FILE_RELATIVE_PATH" = "" -"NUMBER_OF_EXCLUDED_FILES" = "0" -"ORIGINAL_PROJECT_FILE_PATH" = "" -"NUMBER_OF_NESTED_PROJECTS" = "0" -"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" -} diff --git a/Triangle.NET/TestApp/Properties/AssemblyInfo.cs b/Triangle.NET/TestApp/Properties/AssemblyInfo.cs deleted file mode 100644 index d240e61..0000000 --- a/Triangle.NET/TestApp/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Mesh Explorer")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Mesh Explorer")] -[assembly: AssemblyCopyright("Copyright © 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("7368d676-5415-47a5-b1a7-3d517e418b21")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Triangle.NET/TestApp/Views/AboutView.resx b/Triangle.NET/TestApp/Views/AboutView.resx deleted file mode 100644 index 29dcb1b..0000000 --- a/Triangle.NET/TestApp/Views/AboutView.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/Triangle.NET/Triangle.Rendering/BoundingBox.cs b/Triangle.NET/Triangle.Rendering/BoundingBox.cs deleted file mode 100644 index 73a4ae5..0000000 --- a/Triangle.NET/Triangle.Rendering/BoundingBox.cs +++ /dev/null @@ -1,68 +0,0 @@ - -namespace TriangleNet.Rendering -{ - using System.Drawing; - - public class BoundingBox - { - public float Left; - public float Right; - public float Bottom; - public float Top; - - public float Width - { - get { return this.Right - this.Left; } - } - - public float Height - { - get { return this.Top - this.Bottom; } - } - - public BoundingBox() - { - Reset(); - } - - public BoundingBox(float left, float right, float bottom, float top) - { - this.Left = left; - this.Right = right; - this.Bottom = bottom; - this.Top = top; - } - - public void Update(Point pt) - { - this.Update(pt.X, pt.Y); - } - - public void Update(PointF pt) - { - this.Update(pt.X, pt.Y); - } - - public void Update(double x, double y) - { - Update((float)x, (float)y); - } - - public void Update(float x, float y) - { - // Update bounding box - if (this.Left > x) this.Left = x; - if (this.Right < x) this.Right = x; - if (this.Bottom > y) this.Bottom = y; - if (this.Top < y) this.Top = y; - } - - public void Reset() - { - this.Left = float.MaxValue; - this.Right = -float.MaxValue; - this.Bottom = float.MaxValue; - this.Top = -float.MaxValue; - } - } -} diff --git a/Triangle.NET/Triangle.Rendering/Buffer/BufferBase.cs b/Triangle.NET/Triangle.Rendering/Buffer/BufferBase.cs deleted file mode 100644 index 27e9974..0000000 --- a/Triangle.NET/Triangle.Rendering/Buffer/BufferBase.cs +++ /dev/null @@ -1,41 +0,0 @@ - -namespace TriangleNet.Rendering.Buffer -{ - public abstract class BufferBase : IBuffer where T : struct - { - protected T[] data; - protected int size; - - public BufferBase(int capacity, int size) - { - this.data = new T[capacity]; - this.size = size; - } - - public BufferBase(T[] data, int size) - { - this.data = data; - this.size = size; - } - - public T[] Data - { - get { return data; } - } - - public int Count - { - get { return data == null ? 0 : data.Length; } - } - - public abstract int Size - { - get; - } - - public abstract BufferTarget Target - { - get; - } - } -} diff --git a/Triangle.NET/Triangle.Rendering/Buffer/ColorBuffer.cs b/Triangle.NET/Triangle.Rendering/Buffer/ColorBuffer.cs deleted file mode 100644 index 6ae7643..0000000 --- a/Triangle.NET/Triangle.Rendering/Buffer/ColorBuffer.cs +++ /dev/null @@ -1,29 +0,0 @@ - -namespace TriangleNet.Rendering.Buffer -{ - using System; - using System.Drawing; - - public class ColorBuffer : BufferBase - { - public ColorBuffer(int capacity, int size) - : base(capacity, size) - { - } - - public ColorBuffer(Color[] data, int size) - : base(data, size) - { - } - - public override int Size - { - get { return 1; } - } - - public override BufferTarget Target - { - get { return BufferTarget.ColorBuffer; } - } - } -} diff --git a/Triangle.NET/Triangle.Rendering/Buffer/IndexBuffer.cs b/Triangle.NET/Triangle.Rendering/Buffer/IndexBuffer.cs deleted file mode 100644 index a98526f..0000000 --- a/Triangle.NET/Triangle.Rendering/Buffer/IndexBuffer.cs +++ /dev/null @@ -1,30 +0,0 @@ - -namespace TriangleNet.Rendering.Buffer -{ - public class IndexBuffer : BufferBase - { - public IndexBuffer(int capacity, int size) - : base(capacity, size) - { - } - - public IndexBuffer(int[] data, int size) - : base(data, size) - { - } - - /// - /// Gets the number of indices for one element (i.e. 2 for segments - /// or 3 for triangles). - /// - public override int Size - { - get { return size; } - } - - public override BufferTarget Target - { - get { return BufferTarget.IndexBuffer; } - } - } -} diff --git a/Triangle.NET/Triangle.Rendering/Buffer/VertexBuffer.cs b/Triangle.NET/Triangle.Rendering/Buffer/VertexBuffer.cs deleted file mode 100644 index fb8ec45..0000000 --- a/Triangle.NET/Triangle.Rendering/Buffer/VertexBuffer.cs +++ /dev/null @@ -1,30 +0,0 @@ - -namespace TriangleNet.Rendering.Buffer -{ - public class VertexBuffer : BufferBase - { - public VertexBuffer(int capacity, int size = 2) - : base(capacity, size) - { - } - - public VertexBuffer(float[] data, int size = 2) - : base(data, size) - { - } - - /// - /// Gets the number of coordinates of one vertex in the buffer (i.e. 2 for - /// 2D points or 3D points). - /// - public override int Size - { - get { return size; } - } - - public override BufferTarget Target - { - get { return BufferTarget.VertexBuffer; } - } - } -} diff --git a/Triangle.NET/Triangle.Rendering/ColorManager.cs b/Triangle.NET/Triangle.Rendering/ColorManager.cs deleted file mode 100644 index b3d2b20..0000000 --- a/Triangle.NET/Triangle.Rendering/ColorManager.cs +++ /dev/null @@ -1,181 +0,0 @@ - -namespace TriangleNet.Rendering -{ - using System.Collections.Generic; - using System.Drawing; - using TriangleNet.Rendering.Util; - - public class ColorManager - { - Color background; - SolidBrush point; - SolidBrush steinerPoint; - Pen line; - Pen segment; - Pen voronoiLine; - - #region Public properties - - /// - /// Gets or sets the background color. - /// - public Color Background - { - get { return background; } - set { background = value; } - } - - /// - /// Gets or sets the brush used for points. - /// - public SolidBrush Point - { - get { return point; } - set - { - if (point != null) point.Dispose(); - point = value; - } - } - - /// - /// Gets or sets the brush used for steiner points. - /// - public SolidBrush SteinerPoint - { - get { return steinerPoint; } - set - { - if (steinerPoint != null) steinerPoint.Dispose(); - steinerPoint = value; - } - } - - /// - /// Gets or sets the pen used for mesh edges. - /// - public Pen Line - { - get { return line; } - set - { - if (line != null) line.Dispose(); - line = value; - } - } - - /// - /// Gets or sets the pen used for mesh segments. - /// - public Pen Segment - { - get { return segment; } - set - { - if (segment != null) segment.Dispose(); - segment = value; - } - } - - /// - /// Gets or sets the pen used for Voronoi edges. - /// - public Pen VoronoiLine - { - get { return voronoiLine; } - set - { - if (voronoiLine != null) voronoiLine.Dispose(); - voronoiLine = value; - } - } - - #endregion - - /// - /// Gets or sets a dictionary which maps region ids (or partition indices) to a color. - /// - public Dictionary ColorDictionary { get; set; } - - /// - /// Gets or sets a colormap which is used for function plotting. - /// - public ColorMap ColorMap { get; set; } - - /// - /// Creates an instance of the class with default (dark) color scheme. - /// - public static ColorManager Default() - { - var colors = new ColorManager(); - - colors.Background = Color.FromArgb(0, 0, 0); - colors.Point = new SolidBrush(Color.Green); - colors.SteinerPoint = new SolidBrush(Color.Peru); - colors.Line = new Pen(Color.FromArgb(30, 30, 30)); - colors.Segment = new Pen(Color.DarkBlue); - colors.VoronoiLine = new Pen(Color.FromArgb(40, 50, 60)); - - return colors; - } - - public void CreateColorDictionary(int length) - { - var keys = new int[length]; - - for (int i = 0; i < length; i++) - { - keys[i] = i; - } - - CreateColorDictionary(keys, length); - } - - public void CreateColorDictionary(IEnumerable keys, int length) - { - this.ColorDictionary = new Dictionary(); - - int i = 0, n = regionColors.Length; - - foreach (var key in keys) - { - this.ColorDictionary.Add(key, regionColors[i]); - - i = (i + 1) % n; - } - } - - internal void Dispose(Dictionary brushes) - { - foreach (var brush in brushes.Values) - { - brush.Dispose(); - } - } - - internal Dictionary GetBrushDictionary() - { - var brushes = new Dictionary(); - - foreach (var item in ColorDictionary) - { - brushes.Add(item.Key, new SolidBrush(item.Value)); - } - - return brushes; - } - - // Change or add as many colors as you like... - private static Color[] regionColors = { - Color.Transparent, - Color.FromArgb(200, 0, 255, 0), - Color.FromArgb(200, 255, 0, 0), - Color.FromArgb(200, 0, 0, 255), - Color.FromArgb(200, 0, 255, 255), - Color.FromArgb(200, 255, 255, 0), - Color.FromArgb(200, 255, 0, 255), - Color.FromArgb(200, 127, 0, 255), - Color.FromArgb(200, 0, 127, 255) - }; - } -} diff --git a/Triangle.NET/Triangle.Rendering/GDI/ImageRenderer.cs b/Triangle.NET/Triangle.Rendering/GDI/ImageRenderer.cs deleted file mode 100644 index e499521..0000000 --- a/Triangle.NET/Triangle.Rendering/GDI/ImageRenderer.cs +++ /dev/null @@ -1,154 +0,0 @@ - -namespace TriangleNet.Rendering.GDI -{ - using System; - using System.Collections.Generic; - using System.Drawing; - using System.Drawing.Drawing2D; - using System.Drawing.Imaging; - using System.IO; - using TriangleNet.Meshing; - - /// - /// Enables rendering of polygons or meshes to a bitmap. - /// - public class ImageRenderer - { - ColorManager colors = LightScheme(); - - public ColorManager ColorScheme - { - get { return colors; } - set { colors = value; } - } - - public bool EnableRegions { get; set; } - - public bool EnablePoints { get; set; } - - /// - /// Export the mesh to PNG format. - /// - /// The current mesh. - /// The desired width (pixel) of the image. - /// The PNG filename. - /// Enable rendering of regions. - /// Enable rendering of points. - public static void Save(IMesh mesh, string file = null, int width = 800, - bool regions = false, bool points = true) - { - // Check file name - if (string.IsNullOrWhiteSpace(file)) - { - file = string.Format("mesh-{0}.png", DateTime.Now.ToString("yyyy-M-d-hh-mm-ss")); - } - - // Ensure .png extension. - if (!file.EndsWith(".png", StringComparison.OrdinalIgnoreCase)) - { - Path.ChangeExtension(file, ".png"); - } - - var renderer = new ImageRenderer(); - - renderer.EnableRegions = regions; - renderer.EnablePoints = points; - - var bitmap = renderer.Render(mesh, width); - - bitmap.Save(file, ImageFormat.Png); - } - - /// - /// Renders the mesh to a bitmap. - /// - /// The current mesh. - /// The desired width (pixel) of the image. - /// The bitmap. - /// - /// The width has to be at least 2 * sqrt(n), n the number of vertices. - /// Otherwise, an empty bitmap - /// - public Bitmap Render(IMesh mesh, int width = 800) - { - Bitmap bitmap; - - // Check if the specified width is reasonable - if (width < 2 * Math.Sqrt(mesh.Vertices.Count)) - { - return new Bitmap(1, 1); - } - - var bounds = mesh.Bounds; - - // World margin on each side - float margin = (float)bounds.Height * 0.05f; - float scale = width / ((float)bounds.Width + 2 * margin); - - var target = new Rectangle(0, 0, width, (int)((bounds.Height + 2 * margin) * scale)); - - bitmap = new Bitmap(width, target.Height, PixelFormat.Format32bppPArgb); - - using (var g = Graphics.FromImage(bitmap)) - { - g.Clear(colors.Background); - g.SmoothingMode = SmoothingMode.HighQuality; - - var context = new RenderContext(new Projection(target), colors); - context.Add(mesh, true); - - if (EnableRegions) - { - context.Add(GetRegions(mesh)); - } - - if (!EnablePoints) - { - context.Enable(3, false); - } - - var renderer = new LayerRenderer(); - renderer.Context = context; - renderer.RenderTarget = g; - renderer.Render(); - } - - return bitmap; - } - - private int[] GetRegions(IMesh mesh) - { - mesh.Renumber(); - - var labels = new int[mesh.Triangles.Count]; - var regions = new SortedSet(); - - foreach (var t in mesh.Triangles) - { - labels[t.ID] = t.Label; - regions.Add(t.Label); - } - - if (colors.ColorDictionary == null) - { - colors.CreateColorDictionary(regions, regions.Count); - } - - return labels; - } - - public static ColorManager LightScheme() - { - var colors = new ColorManager(); - - colors.Background = Color.White; - colors.Point = new SolidBrush(Color.FromArgb(60, 80, 120)); - colors.SteinerPoint = new SolidBrush(Color.DarkGreen); - colors.Line = new Pen(Color.FromArgb(200, 200, 200)); - colors.Segment = new Pen(Color.SteelBlue); - colors.VoronoiLine = new Pen(Color.FromArgb(160, 170, 180)); - - return colors; - } - } -} diff --git a/Triangle.NET/Triangle.Rendering/IRenderContext.cs b/Triangle.NET/Triangle.Rendering/IRenderContext.cs deleted file mode 100644 index 6b1b638..0000000 --- a/Triangle.NET/Triangle.Rendering/IRenderContext.cs +++ /dev/null @@ -1,32 +0,0 @@ - -namespace TriangleNet.Rendering -{ - using System.Collections.Generic; - using TriangleNet.Geometry; - using TriangleNet.Meshing; - using TriangleNet.Voronoi.Legacy; - - public interface IRenderContext - { - ColorManager ColorManager { get; } - - BoundingBox Bounds { get; } - - IList RenderLayers { get; } - - Projection Zoom { get; } - - IMesh Mesh { get; } - - bool HasData { get; } - - void Add(IPolygon data); - void Add(IMesh data, bool reset); - void Add(ICollection points, IEnumerable edges, bool reset); - - void Add(float[] values); - void Add(int[] partition); - - void Enable(int layer, bool enabled); - } -} diff --git a/Triangle.NET/Triangle.Rendering/IRenderLayer.cs b/Triangle.NET/Triangle.Rendering/IRenderLayer.cs deleted file mode 100644 index 4367a17..0000000 --- a/Triangle.NET/Triangle.Rendering/IRenderLayer.cs +++ /dev/null @@ -1,44 +0,0 @@ - -namespace TriangleNet.Rendering -{ - using System.Collections.Generic; - using TriangleNet.Geometry; - using TriangleNet.Meshing; - using TriangleNet.Rendering.Buffer; - using TriangleNet.Rendering.Util; - - using Color = System.Drawing.Color; - - public interface IRenderLayer - { - int Count { get; } - - // Points can be set, because layers may share vertices. - IBuffer Points { get; } - IBuffer Indices { get; } - - bool IsEnabled { get; set; } - - bool IsEmpty(); - - void Reset(bool clear); - - // TODO: add boolean: reset - BoundingBox SetPoints(IBuffer buffer); - BoundingBox SetPoints(IPolygon poly); - BoundingBox SetPoints(IMesh mesh); - BoundingBox SetPoints(ICollection points); - void SetPolygon(IPolygon poly); - void SetPolygon(IMesh mesh); - void SetMesh(IMesh mesh, bool elements); - void SetMesh(IEnumerable edges); - - - // TODO: better put these into a subclass. - IBuffer Partition { get; } - IBuffer Colors { get; } - - void AttachLayerData(float[] values, ColorMap colormap); - void AttachLayerData(int[] partition); - } -} diff --git a/Triangle.NET/Triangle.Rendering/Projection.cs b/Triangle.NET/Triangle.Rendering/Projection.cs deleted file mode 100644 index 6dd8118..0000000 --- a/Triangle.NET/Triangle.Rendering/Projection.cs +++ /dev/null @@ -1,271 +0,0 @@ -// ----------------------------------------------------------------------- -// -// TODO: Update copyright text. -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Rendering -{ - using System; - using System.Drawing; - - /// - /// Manages a world to screen transformation (2D orthographic projection). - /// - public class Projection - { - // The screen. - Rectangle screen; - - // The complete mesh. - RectangleF world; - - /// - /// Gets or sets the current viewport (visible mesh). - /// - public RectangleF Viewport { get; set; } - - /// - /// Gets the current scale. - /// - public float Scale - { - get { return screen.Width / Viewport.Width; } - } - - /// - /// Gets the zoom level. - /// - public int Level { get; private set; } - - /// - /// Gets or sets a clip margin (default is 5% of viewport width on each side). - /// - public float ClipMargin { get; set; } - - // The y-direction of windows screen coordinates is upside down, - // so inverY must be set to true. - bool invertY = false; - - int maxZoomLevel = 100; - - public Projection(Rectangle screen, bool invertY = true) - { - this.screen = screen; - this.world = screen; - this.Viewport = screen; - - this.Level = 1; - - this.ClipMargin = this.Viewport.Width * 0.05f; - - this.invertY = invertY; - } - - /// - /// Inititialize the projection. - /// - /// The world that should be transformed to screen coordinates. - public void Initialize(BoundingBox world) - { - this.Level = 1; - - // Add a margin so there's some space around the border - float worldMargin = (world.Width < world.Height) ? world.Height * 0.05f : world.Width * 0.05f; - - // Get the initial viewport (complete mesh centered on the screen) - float screenRatio = screen.Width / (float)screen.Height; - float worldRatio = world.Width / world.Height; - - float scale = (world.Width + worldMargin) / screen.Width; - - if (screenRatio > worldRatio) - { - scale = (world.Height + worldMargin) / screen.Height; - } - - float centerX = world.Left + world.Width / 2; - float centerY = world.Bottom + world.Height / 2; - - // TODO: Add initial margin - this.Viewport = new RectangleF(centerX - screen.Width * scale / 2, - centerY - screen.Height * scale / 2, - screen.Width * scale, - screen.Height * scale); - - this.ClipMargin = this.Viewport.Width * 0.05f; - - this.world = this.Viewport; - } - - /// - /// Handle resize of the screen. - /// - /// The new screen dimensions. - public void Resize(Rectangle newScreen) - { - // The viewport has to be updated, but we want to keep - // the scaling and the center. - - // Get the screen scaling. - float scaleX = newScreen.Width / (float)screen.Width; - float scaleY = newScreen.Height / (float)screen.Height; - - this.screen = newScreen; - - var view = this.Viewport; - - // Center of the viewport - float centerX = (view.Left + view.Right) / 2; - float centerY = (view.Bottom + view.Top) / 2; - - // The new viewport dimensions. - float width = view.Width * scaleX; - float height = view.Height * scaleY; - - this.Viewport = new RectangleF( - centerX - width / 2, - centerY - height / 2, - width, height); - - // Do the same for the world: - centerX = (world.Left + world.Right) / 2; - centerY = (world.Bottom + world.Top) / 2; - - width = world.Width * scaleX; - height = world.Height * scaleY; - - this.world = new RectangleF( - centerX - width / 2, - centerY - height / 2, - width, height); - } - - public bool Translate(int dx, int dy) - { - if (Level == 1) - { - return false; - } - - var view = this.Viewport; - - float x = view.X + dx * view.Width / 4; - float y = view.Y + dy * view.Height / 4; - - this.Viewport = new RectangleF(x, y, view.Width, view.Height); - - return true; - } - - /// - /// Zoom in or out of the viewport. - /// - /// Zoom amount - /// Relative x point position - /// Relative y point position - public bool Zoom(int amount, float focusX, float focusY) - { - float width, height; - - if (invertY) - { - focusY = 1 - focusY; - } - - if (amount > 0) // Zoom in - { - this.Level++; - - if (this.Level > maxZoomLevel) - { - this.Level = maxZoomLevel; - return false; - } - - width = Viewport.Width / 1.1f; - height = Viewport.Height / 1.1f; - } - else - { - this.Level--; - - if (this.Level < 1) - { - this.Level = 1; - this.Viewport = this.world; - return false; - } - - width = Viewport.Width * 1.1f; - height = Viewport.Height * 1.1f; - } - - // Current focus on viewport - float x = Viewport.X + Viewport.Width * focusX; - float y = Viewport.Y + Viewport.Height * focusY; - - // New left and top positions - x = x - width * focusX; - y = y - height * focusY; - - // Check if outside of world - if (x < world.X) - { - x = world.X; - } - else if (x + width > world.Right) - { - x = world.Right - width; - } - - if (y < world.Y) - { - y = world.Y; - } - else if (y + height > world.Bottom) - { - y = world.Bottom - height; - } - - // Set new viewport - this.Viewport = new RectangleF(x, y, width, height); - - this.ClipMargin = this.Viewport.Width * 0.05f; - - return true; - } - - public void Reset() - { - this.Viewport = this.world; - this.Level = 1; - } - - public void WorldToScreen(ref PointF pt) - { - pt.X = (pt.X - Viewport.X) / Viewport.Width * screen.Width; - pt.Y = (1 - (pt.Y - Viewport.Y) / Viewport.Height) * screen.Height; - } - - public void ScreenToWorld(ref PointF pt) - { - pt.X = Viewport.X + Viewport.Width * pt.X; - pt.Y = Viewport.Y + Viewport.Height * (1 - pt.Y); - } - - [Obsolete] - public PointF WorldToScreen(float x, float y) - { - return new PointF((x - Viewport.X) / Viewport.Width * screen.Width, - (1 - (y - Viewport.Y) / Viewport.Height) * screen.Height); - } - - [Obsolete] - public PointF ScreenToWorld(float x, float y) - { - return new PointF(Viewport.X + Viewport.Width * x, - Viewport.Y + Viewport.Height * (1 - y)); - } - } -} diff --git a/Triangle.NET/Triangle.Rendering/Properties/AssemblyInfo.cs b/Triangle.NET/Triangle.Rendering/Properties/AssemblyInfo.cs deleted file mode 100644 index 2c12083..0000000 --- a/Triangle.NET/Triangle.Rendering/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Triangle.Rendering")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Triangle.Rendering")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("14f2491b-ee62-41e4-ab93-206540302ece")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Triangle.NET/Triangle.Rendering/RenderLayer.cs b/Triangle.NET/Triangle.Rendering/RenderLayer.cs deleted file mode 100644 index 4b1b1e9..0000000 --- a/Triangle.NET/Triangle.Rendering/RenderLayer.cs +++ /dev/null @@ -1,188 +0,0 @@ - -namespace TriangleNet.Rendering -{ - using System.Collections.Generic; - using TriangleNet.Geometry; - using TriangleNet.Meshing; - using TriangleNet.Rendering.Buffer; - using TriangleNet.Rendering.Util; - - using Color = System.Drawing.Color; - - public class RenderLayer : IRenderLayer - { - int count; - - protected IBuffer points; - protected IBuffer indices; - - protected IBuffer partition; - protected IBuffer colors; - - public RenderLayer() - { - this.IsEnabled = false; - } - - public int Count - { - get { return count; } - } - - public IBuffer Points - { - get { return points; } - set { points = value; } - } - - public IBuffer Indices - { - get { return indices; } - } - - public IBuffer Partition - { - get { return partition; } - } - - public IBuffer Colors - { - get { return colors; } - } - - public bool IsEnabled { get; set; } - - public bool IsEmpty() - { - return (points == null || points.Count == 0); - } - - public void Reset(bool clear) - { - if (clear) - { - count = 0; - points = null; - } - - indices = null; - partition = null; - colors = null; - } - - public BoundingBox SetPoints(IBuffer buffer) - { - BoundingBox bounds = new BoundingBox(); - - if (points != null && points.Count < buffer.Count) - { - count = points.Count / points.Size; - } - else - { - count = buffer.Count / buffer.Size; - } - - this.points = buffer; - - return bounds; - } - - public BoundingBox SetPoints(IPolygon poly) - { - BoundingBox bounds = new BoundingBox(); - - points = BufferHelper.CreateVertexBuffer(poly.Points, ref bounds); - count = points.Count / points.Size; - - return bounds; - } - - public BoundingBox SetPoints(IMesh mesh) - { - BoundingBox bounds = new BoundingBox(); - - points = BufferHelper.CreateVertexBuffer(mesh.Vertices, ref bounds); - count = points.Count / points.Size; - - return bounds; - } - - public BoundingBox SetPoints(ICollection vertices) - { - BoundingBox bounds = new BoundingBox(); - - points = BufferHelper.CreateVertexBuffer(vertices, ref bounds); - count = points.Count / points.Size; - - return bounds; - } - - public void SetPolygon(IPolygon poly) - { - indices = BufferHelper.CreateIndexBuffer(poly.Segments, 2); - } - - public void SetPolygon(IMesh mesh) - { - indices = BufferHelper.CreateIndexBuffer(mesh.Segments, 2); - } - - public void SetMesh(IEnumerable edges) - { - indices = BufferHelper.CreateIndexBuffer(edges, 2); - } - - public void SetMesh(IMesh mesh, bool elements) - { - mesh.Renumber(); - - if (!elements) - { - indices = BufferHelper.CreateIndexBuffer(mesh.Edges, 2); - } - - if (elements || indices.Count == 0) - { - indices = BufferHelper.CreateIndexBuffer(mesh.Triangles, 3); - } - } - - // TODO: remove colormap argument - public void AttachLayerData(float[] values, ColorMap colormap) - { - int length = values.Length; - - Color[] data = new Color[length]; - - double min = double.MaxValue; - double max = double.MinValue; - - // Find min and max of given values. - for (int i = 0; i < length; i++) - { - if (values[i] < min) - { - min = values[i]; - } - - if (values[i] > max) - { - max = values[i]; - } - } - - for (int i = 0; i < length; i++) - { - data[i] = colormap.GetColor(values[i], min, max); - } - - colors = new ColorBuffer(data, 1); - } - - public void AttachLayerData(int[] partition) - { - this.partition = new IndexBuffer(partition, 1); - } - } -} diff --git a/Triangle.NET/Triangle.Rendering/Text/EdgeIterator.cs b/Triangle.NET/Triangle.Rendering/Text/EdgeIterator.cs deleted file mode 100644 index cb02260..0000000 --- a/Triangle.NET/Triangle.Rendering/Text/EdgeIterator.cs +++ /dev/null @@ -1,58 +0,0 @@ - -namespace TriangleNet.Rendering.Text -{ - using System.Collections.Generic; - using TriangleNet.Geometry; - - static class EdgeIterator - { - /// - /// Enumerate the edges of the mesh. - /// - /// - /// - /// - /// - /// In contrast to the this - /// method will return objects that include the vertex information (and not only the - /// indices). - /// - public static IEnumerable EnumerateEdges(Mesh mesh, bool skipSegments = true) - { - foreach (var t in mesh.Triangles) - { - for (int i = 0; i < 3; i++) - { - int nid = t.GetNeighborID(i); - - if ((t.ID < nid) || (nid < 0)) - { - var s = t.GetSegment(i); - - if (skipSegments && s == null) - { - // Since segments will be processed separately, don't - // include them in the enumeration. - yield return new Segment( - t.GetVertex((i + 1) % 3), - t.GetVertex((i + 2) % 3)); - } - else - { - if (s == null) - { - yield return new Segment( - t.GetVertex((i + 1) % 3), - t.GetVertex((i + 2) % 3)); - } - else - { - yield return s; - } - } - } - } - } - } - } -} diff --git a/Triangle.NET/Triangle.Rendering/Triangle.Rendering.csproj b/Triangle.NET/Triangle.Rendering/Triangle.Rendering.csproj deleted file mode 100644 index 7df27fb..0000000 --- a/Triangle.NET/Triangle.Rendering/Triangle.Rendering.csproj +++ /dev/null @@ -1,98 +0,0 @@ - - - - - Debug - AnyCPU - {41022E0E-BD0F-439E-BC3A-AABB1B43471B} - Library - Properties - TriangleNet.Rendering - Triangle.Rendering - v4.0 - 512 - SAK - SAK - SAK - SAK - Client - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - Component - - - - - - - - - - - - - - - - - - - - - - - - {f7907a0a-b75f-400b-9e78-bfad00db4d6b} - Triangle - - - - - \ No newline at end of file diff --git a/Triangle.NET/Triangle.Rendering/Triangle.Rendering.csproj.vspscc b/Triangle.NET/Triangle.Rendering/Triangle.Rendering.csproj.vspscc deleted file mode 100644 index feffdec..0000000 --- a/Triangle.NET/Triangle.Rendering/Triangle.Rendering.csproj.vspscc +++ /dev/null @@ -1,10 +0,0 @@ -"" -{ -"FILE_VERSION" = "9237" -"ENLISTMENT_CHOICE" = "NEVER" -"PROJECT_FILE_RELATIVE_PATH" = "" -"NUMBER_OF_EXCLUDED_FILES" = "0" -"ORIGINAL_PROJECT_FILE_PATH" = "" -"NUMBER_OF_NESTED_PROJECTS" = "0" -"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" -} diff --git a/Triangle.NET/Triangle.Rendering/Util/BufferHelper.cs b/Triangle.NET/Triangle.Rendering/Util/BufferHelper.cs deleted file mode 100644 index 7919293..0000000 --- a/Triangle.NET/Triangle.Rendering/Util/BufferHelper.cs +++ /dev/null @@ -1,142 +0,0 @@ - -namespace TriangleNet.Rendering.Util -{ - using System.Collections.Generic; - using TriangleNet.Topology; - using TriangleNet.Geometry; - using TriangleNet.Rendering.Buffer; - - internal static class BufferHelper - { - public static IBuffer CreateVertexBuffer(double[] points, ref BoundingBox bounds) - { - int length = points.Length; - - var buffer = new VertexBuffer(length); - - bounds.Reset(); - - var data = buffer.Data; - - float x, y; - - length = length >> 1; - - for (int i = 0; i < length; i++) - { - x = (float)points[2 * i]; - y = (float)points[2 * i + 1]; - - data[2 * i] = x; - data[2 * i + 1] = y; - - bounds.Update(x, y); - } - - return buffer as IBuffer; - } - - public static IBuffer CreateVertexBuffer(ICollection points, ref BoundingBox bounds) - { - var buffer = new VertexBuffer(2 * points.Count); - - bounds.Reset(); - - var data = buffer.Data; - - float x, y; - - int i = 0; - - foreach (var p in points) - { - x = (float)p.X; - y = (float)p.Y; - - data[2 * i] = x; - data[2 * i + 1] = y; - - bounds.Update(x, y); - - i++; - } - - return buffer as IBuffer; - } - - public static IBuffer CreateVertexBuffer(ICollection points, ref BoundingBox bounds) - { - var buffer = new VertexBuffer(2 * points.Count); - - bounds.Reset(); - - var data = buffer.Data; - - int i = 0; - - foreach (var p in points) - { - data[2 * i] = (float)p.X; - data[2 * i + 1] = (float)p.Y; - - bounds.Update(p.X, p.Y); - - i++; - } - - return buffer as IBuffer; - } - - public static IBuffer CreateIndexBuffer(IList segments, int size) - { - var buffer = new IndexBuffer(size * segments.Count, size); - - var data = buffer.Data; - - int i = 0; - - foreach (var e in segments) - { - data[size * i + 0] = e.P0; - data[size * i + 1] = e.P1; - - i++; - } - - return buffer as IBuffer; - } - - public static IBuffer CreateIndexBuffer(IEnumerable edges, int size) - { - var data = new List(); - - foreach (var e in edges) - { - data.Add(e.P0); - data.Add(e.P1); - } - - return new IndexBuffer(data.ToArray(), size) as IBuffer; - } - - public static IBuffer CreateIndexBuffer(ICollection elements, int size) - { - var buffer = new IndexBuffer(size * elements.Count, size); - - var data = buffer.Data; - - int i = 0; - - foreach (var e in elements) - { - data[size * i + 0] = e.GetVertexID(0); - data[size * i + 1] = e.GetVertexID(1); - data[size * i + 2] = e.GetVertexID(2); - - i++; - } - - return buffer as IBuffer; - } - } -} diff --git a/Triangle.NET/Triangle.sln b/Triangle.NET/Triangle.sln deleted file mode 100644 index 745beda..0000000 --- a/Triangle.NET/Triangle.sln +++ /dev/null @@ -1,69 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Triangle", "Triangle\Triangle.csproj", "{F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mesh Explorer", "TestApp\Mesh Explorer.csproj", "{336AAF8A-5316-4303-9E73-5E38BD0B28AF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Triangle.Rendering", "Triangle.Rendering\Triangle.Rendering.csproj", "{41022E0E-BD0F-439E-BC3A-AABB1B43471B}" -EndProject -Global - GlobalSection(TeamFoundationVersionControl) = preSolution - SccNumberOfProjects = 4 - SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C} - SccTeamFoundationServer = https://tfs.codeplex.com/tfs/tfs06 - SccLocalPath0 = . - SccProjectUniqueName1 = Triangle\\Triangle.csproj - SccProjectName1 = Triangle - SccLocalPath1 = Triangle - SccProjectUniqueName2 = TestApp\\Mesh\u0020Explorer.csproj - SccProjectName2 = TestApp - SccLocalPath2 = TestApp - SccProjectUniqueName3 = Triangle.Rendering\\Triangle.Rendering.csproj - SccProjectName3 = Triangle.Rendering - SccLocalPath3 = Triangle.Rendering - EndGlobalSection - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|Mixed Platforms = Debug|Mixed Platforms - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|Mixed Platforms = Release|Mixed Platforms - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Debug|x86.ActiveCfg = Debug|Any CPU - {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Release|Any CPU.Build.0 = Release|Any CPU - {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Release|x86.ActiveCfg = Release|Any CPU - {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Debug|Any CPU.ActiveCfg = Debug|x86 - {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 - {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Debug|Mixed Platforms.Build.0 = Debug|x86 - {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Debug|x86.ActiveCfg = Debug|x86 - {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Debug|x86.Build.0 = Debug|x86 - {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Release|Any CPU.ActiveCfg = Release|x86 - {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Release|Mixed Platforms.ActiveCfg = Release|x86 - {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Release|Mixed Platforms.Build.0 = Release|x86 - {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Release|x86.ActiveCfg = Release|x86 - {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Release|x86.Build.0 = Release|x86 - {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Debug|x86.ActiveCfg = Debug|Any CPU - {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Release|Any CPU.Build.0 = Release|Any CPU - {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Release|x86.ActiveCfg = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/Triangle.NET/Triangle.vssscc b/Triangle.NET/Triangle.vssscc deleted file mode 100644 index 794f014..0000000 --- a/Triangle.NET/Triangle.vssscc +++ /dev/null @@ -1,10 +0,0 @@ -"" -{ -"FILE_VERSION" = "9237" -"ENLISTMENT_CHOICE" = "NEVER" -"PROJECT_FILE_RELATIVE_PATH" = "" -"NUMBER_OF_EXCLUDED_FILES" = "0" -"ORIGINAL_PROJECT_FILE_PATH" = "" -"NUMBER_OF_NESTED_PROJECTS" = "0" -"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROJECT" -} diff --git a/Triangle.NET/Triangle/Geometry/Polygon.cs b/Triangle.NET/Triangle/Geometry/Polygon.cs deleted file mode 100644 index 96d7106..0000000 --- a/Triangle.NET/Triangle/Geometry/Polygon.cs +++ /dev/null @@ -1,183 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Geometry -{ - using System; - using System.Collections.Generic; - - /// - /// A polygon represented as a planar straight line graph. - /// - public class Polygon : IPolygon - { - List points; - List holes; - List regions; - - List segments; - - /// - public List Points - { - get { return points; } - } - - /// - public List Holes - { - get { return holes; } - } - - /// - public List Regions - { - get { return regions; } - } - - /// - public List Segments - { - get { return segments; } - } - - /// - public bool HasPointMarkers { get; set; } - - /// - public bool HasSegmentMarkers { get; set; } - - /// - public int Count - { - get { return points.Count; } - } - - /// - /// Initializes a new instance of the class. - /// - public Polygon() - : this(3, false) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The default capacity for the points list. - public Polygon(int capacity) - : this(3, false) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The default capacity for the points list. - /// Use point and segment markers. - public Polygon(int capacity, bool markers) - { - points = new List(capacity); - holes = new List(); - regions = new List(); - - segments = new List(); - - HasPointMarkers = markers; - HasSegmentMarkers = markers; - } - - [Obsolete("Use polygon.Add(contour) method instead.")] - public void AddContour(IEnumerable points, int marker = 0, - bool hole = false, bool convex = false) - { - this.Add(new Contour(points, marker, convex), hole); - } - - [Obsolete("Use polygon.Add(contour) method instead.")] - public void AddContour(IEnumerable points, int marker, Point hole) - { - this.Add(new Contour(points, marker), hole); - } - - /// - public Rectangle Bounds() - { - var bounds = new Rectangle(); - bounds.Expand(this.points); - - return bounds; - } - - /// - /// Add a vertex to the polygon. - /// - /// The vertex to insert. - public void Add(Vertex vertex) - { - this.points.Add(vertex); - } - - /// - /// Add a segment to the polygon. - /// - /// The segment to insert. - /// If true, both endpoints will be added to the points list. - public void Add(ISegment segment, bool insert = false) - { - this.segments.Add(segment); - - if (insert) - { - this.points.Add(segment.GetVertex(0)); - this.points.Add(segment.GetVertex(1)); - } - } - - /// - /// Add a segment to the polygon. - /// - /// The segment to insert. - /// The index of the segment endpoint to add to the points list (must be 0 or 1). - public void Add(ISegment segment, int index) - { - this.segments.Add(segment); - - this.points.Add(segment.GetVertex(index)); - } - - /// - /// Add a contour to the polygon. - /// - /// The contour to insert. - /// Treat contour as a hole. - public void Add(Contour contour, bool hole = false) - { - if (hole) - { - this.Add(contour, contour.FindInteriorPoint()); - } - else - { - this.points.AddRange(contour.Points); - this.segments.AddRange(contour.GetSegments()); - } - } - - /// - /// Add a contour to the polygon. - /// - /// The contour to insert. - /// Point inside the contour, making it a hole. - public void Add(Contour contour, Point hole) - { - this.points.AddRange(contour.Points); - this.segments.AddRange(contour.GetSegments()); - - this.holes.Add(hole); - } - } -} diff --git a/Triangle.NET/Triangle/Log.cs b/Triangle.NET/Triangle/Log.cs deleted file mode 100644 index b5e577b..0000000 --- a/Triangle.NET/Triangle/Log.cs +++ /dev/null @@ -1,84 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet -{ - using System.Collections.Generic; - using TriangleNet.Logging; - - /// - /// A simple logger, which logs messages to a List. - /// - /// Using singleton pattern as proposed by Jon Skeet. - /// http://csharpindepth.com/Articles/General/Singleton.aspx - /// - public sealed class Log : ILog - { - /// - /// Log detailed information. - /// - public static bool Verbose { get; set; } - - private List log = new List(); - - private LogLevel level = LogLevel.Info; - - #region Singleton pattern - - private static readonly Log instance = new Log(); - - // Explicit static constructor to tell C# compiler - // not to mark type as beforefieldinit - static Log() { } - - private Log() { } - - public static ILog Instance - { - get - { - return instance; - } - } - - #endregion - - public void Add(LogItem item) - { - log.Add(item); - } - - public void Clear() - { - log.Clear(); - } - - public void Info(string message) - { - log.Add(new LogItem(LogLevel.Info, message)); - } - - public void Warning(string message, string location) - { - log.Add(new LogItem(LogLevel.Warning, message, location)); - } - - public void Error(string message, string location) - { - log.Add(new LogItem(LogLevel.Error, message, location)); - } - - public IList Data - { - get { return log; } - } - - public LogLevel Level - { - get { return level; } - } - } -} diff --git a/Triangle.NET/Triangle/Logging/ILog.cs b/Triangle.NET/Triangle/Logging/ILog.cs deleted file mode 100644 index f0184a9..0000000 --- a/Triangle.NET/Triangle/Logging/ILog.cs +++ /dev/null @@ -1,34 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Logging -{ - using System.Collections.Generic; - - public enum LogLevel - { - Info = 0, - Warning = 1, - Error = 2 - } - - /// - /// A basic log interface. - /// - public interface ILog where T : ILogItem - { - void Add(T item); - void Clear(); - - void Info(string message); - void Error(string message, string info); - void Warning(string message, string info); - - IList Data { get; } - - LogLevel Level { get; } - } -} diff --git a/Triangle.NET/Triangle/Logging/ILogItem.cs b/Triangle.NET/Triangle/Logging/ILogItem.cs deleted file mode 100644 index 7ed0761..0000000 --- a/Triangle.NET/Triangle/Logging/ILogItem.cs +++ /dev/null @@ -1,21 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Logging -{ - using System; - - /// - /// A basic log item interface. - /// - public interface ILogItem - { - DateTime Time { get; } - LogLevel Level { get; } - string Message { get; } - string Info { get; } - } -} diff --git a/Triangle.NET/Triangle/Logging/LogItem.cs b/Triangle.NET/Triangle/Logging/LogItem.cs deleted file mode 100644 index 1be74bc..0000000 --- a/Triangle.NET/Triangle/Logging/LogItem.cs +++ /dev/null @@ -1,53 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Logging -{ - using System; - - /// - /// Represents an item stored in the log. - /// - public class LogItem : ILogItem - { - DateTime time; - LogLevel level; - string message; - string info; - - public DateTime Time - { - get { return time; } - } - - public LogLevel Level - { - get { return level; } - } - - public string Message - { - get { return message; } - } - - public string Info - { - get { return info; } - } - - public LogItem(LogLevel level, string message) - : this(level, message, "") - { } - - public LogItem(LogLevel level, string message, string info) - { - this.time = DateTime.Now; - this.level = level; - this.message = message; - this.info = info; - } - } -} diff --git a/Triangle.NET/Triangle/Meshing/Iterators/EdgeIterator.cs b/Triangle.NET/Triangle/Meshing/Iterators/EdgeIterator.cs deleted file mode 100644 index 1b247f4..0000000 --- a/Triangle.NET/Triangle/Meshing/Iterators/EdgeIterator.cs +++ /dev/null @@ -1,101 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Meshing.Iterators -{ - using System.Collections.Generic; - using TriangleNet.Topology; - using TriangleNet.Geometry; - - /// - /// Enumerates the edges of a triangulation. - /// - public class EdgeIterator : IEnumerator - { - IEnumerator triangles; - Otri tri = default(Otri); - Otri neighbor = default(Otri); - Osub sub = default(Osub); - Edge current; - Vertex p1, p2; - - /// - /// Initializes a new instance of the class. - /// - public EdgeIterator(Mesh mesh) - { - triangles = mesh.triangles.GetEnumerator(); - triangles.MoveNext(); - - tri.tri = triangles.Current; - tri.orient = 0; - } - - public Edge Current - { - get { return current; } - } - - public void Dispose() - { - this.triangles.Dispose(); - } - - object System.Collections.IEnumerator.Current - { - get { return current; } - } - - public bool MoveNext() - { - if (tri.tri == null) - { - return false; - } - - current = null; - - while (current == null) - { - if (tri.orient == 3) - { - if (triangles.MoveNext()) - { - tri.tri = triangles.Current; - tri.orient = 0; - } - else - { - // Finally no more triangles - return false; - } - } - - tri.Sym(ref neighbor); - - if ((tri.tri.id < neighbor.tri.id) || (neighbor.tri.id == Mesh.DUMMY)) - { - p1 = tri.Org(); - p2 = tri.Dest(); - - tri.Pivot(ref sub); - - // Boundary mark of dummysub is 0, so we don't need to worry about that. - current = new Edge(p1.id, p2.id, sub.seg.boundary); - } - - tri.orient++; - } - - return true; - } - - public void Reset() - { - this.triangles.Reset(); - } - } -} diff --git a/Triangle.NET/Triangle/Properties/AssemblyInfo.cs b/Triangle.NET/Triangle/Properties/AssemblyInfo.cs deleted file mode 100644 index 330e970..0000000 --- a/Triangle.NET/Triangle/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Triangle")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Triangle")] -[assembly: AssemblyCopyright("Copyright © 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("96a540d0-1772-4bed-8d25-ef5fa23cd1bc")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Triangle.NET/Triangle/Smoothing/ISmoother.cs b/Triangle.NET/Triangle/Smoothing/ISmoother.cs deleted file mode 100644 index 621a2c5..0000000 --- a/Triangle.NET/Triangle/Smoothing/ISmoother.cs +++ /dev/null @@ -1,19 +0,0 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Smoothing -{ - using TriangleNet.Meshing; - - /// - /// Interface for mesh smoothers. - /// - public interface ISmoother - { - void Smooth(IMesh mesh); - void Smooth(IMesh mesh, int limit); - } -} \ No newline at end of file diff --git a/Triangle.NET/Triangle/Tools/Interpolation.cs b/Triangle.NET/Triangle/Tools/Interpolation.cs deleted file mode 100644 index f315b03..0000000 --- a/Triangle.NET/Triangle/Tools/Interpolation.cs +++ /dev/null @@ -1,105 +0,0 @@ - -namespace TriangleNet.Tools -{ - using TriangleNet.Geometry; - - public static class Interpolation - { -#if USE_ATTRIBS - /// - /// Linear interpolation of vertex attributes. - /// - /// The interpolation vertex. - /// The triangle containing the vertex. - /// The number of vertex attributes. - /// - /// The vertex is expected to lie inside the triangle. - /// - public static void InterpolateAttributes(Vertex vertex, ITriangle triangle, int n) - { - Vertex org = triangle.GetVertex(0); - Vertex dest = triangle.GetVertex(1); - Vertex apex = triangle.GetVertex(2); - - double xdo, ydo, xao, yao; - double denominator; - double dx, dy; - double xi, eta; - - // Compute the circumcenter of the triangle. - xdo = dest.x - org.x; - ydo = dest.y - org.y; - xao = apex.x - org.x; - yao = apex.y - org.y; - - denominator = 0.5 / (xdo * yao - xao * ydo); - - //dx = (yao * dodist - ydo * aodist) * denominator; - //dy = (xdo * aodist - xao * dodist) * denominator; - - dx = vertex.x - org.x; - dy = vertex.y - org.y; - - // To interpolate vertex attributes for the new vertex, define a - // coordinate system with a xi-axis directed from the triangle's - // origin to its destination, and an eta-axis, directed from its - // origin to its apex. - xi = (yao * dx - xao * dy) * (2.0 * denominator); - eta = (xdo * dy - ydo * dx) * (2.0 * denominator); - - for (int i = 0; i < n; i++) - { - // Interpolate the vertex attributes. - vertex.attributes[i] = org.attributes[i] - + xi * (dest.attributes[i] - org.attributes[i]) - + eta * (apex.attributes[i] - org.attributes[i]); - } - } -#endif - -#if USE_Z - /// - /// Linear interpolation of a scalar value. - /// - /// The interpolation point. - /// The triangle containing the point. - /// - /// The point is expected to lie inside the triangle. - /// - public static void InterpolateZ(Point p, ITriangle triangle) - { - Vertex org = triangle.GetVertex(0); - Vertex dest = triangle.GetVertex(1); - Vertex apex = triangle.GetVertex(2); - - double xdo, ydo, xao, yao; - double denominator; - double dx, dy; - double xi, eta; - - // Compute the circumcenter of the triangle. - xdo = dest.x - org.x; - ydo = dest.y - org.y; - xao = apex.x - org.x; - yao = apex.y - org.y; - - denominator = 0.5 / (xdo * yao - xao * ydo); - - //dx = (yao * dodist - ydo * aodist) * denominator; - //dy = (xdo * aodist - xao * dodist) * denominator; - - dx = p.x - org.x; - dy = p.y - org.y; - - // To interpolate z value for the given point inserted, define a - // coordinate system with a xi-axis, directed from the triangle's - // origin to its destination, and an eta-axis, directed from its - // origin to its apex. - xi = (yao * dx - xao * dy) * (2.0 * denominator); - eta = (xdo * dy - ydo * dx) * (2.0 * denominator); - - p.z = org.z + xi * (dest.z - org.z) + eta * (apex.z - org.z); - } -#endif - } -} diff --git a/Triangle.NET/Triangle/Triangle.csproj b/Triangle.NET/Triangle/Triangle.csproj deleted file mode 100644 index d0a8638..0000000 --- a/Triangle.NET/Triangle/Triangle.csproj +++ /dev/null @@ -1,139 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B} - Library - Properties - TriangleNet - Triangle - v4.0 - 512 - Client - SAK - SAK - SAK - SAK - - - true - full - false - bin\Debug\ - TRACE;DEBUG;USE_ATTRIBS - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE;USE_ATTRIBS - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Triangle.NET/Triangle/Triangle.csproj.vspscc b/Triangle.NET/Triangle/Triangle.csproj.vspscc deleted file mode 100644 index feffdec..0000000 --- a/Triangle.NET/Triangle/Triangle.csproj.vspscc +++ /dev/null @@ -1,10 +0,0 @@ -"" -{ -"FILE_VERSION" = "9237" -"ENLISTMENT_CHOICE" = "NEVER" -"PROJECT_FILE_RELATIVE_PATH" = "" -"NUMBER_OF_EXCLUDED_FILES" = "0" -"ORIGINAL_PROJECT_FILE_PATH" = "" -"NUMBER_OF_NESTED_PROJECTS" = "0" -"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" -} diff --git a/src/Triangle.Examples/Examples/Example1.cs b/src/Triangle.Examples/Examples/Example1.cs new file mode 100644 index 0000000..82d09d5 --- /dev/null +++ b/src/Triangle.Examples/Examples/Example1.cs @@ -0,0 +1,33 @@ + +namespace TriangleNet.Examples +{ + using TriangleNet.Geometry; + using TriangleNet.Meshing; + using TriangleNet.Meshing.Algorithm; + using TriangleNet.Rendering.Text; + + /// + /// Simple point set triangulation. + /// + public class Example1 + { + public static bool Run(bool print = false) + { + // Generate points. + var points = Generate.RandomPoints(50, new Rectangle(0, 0, 100, 100)); + + // Choose triangulator: Incremental, SweepLine or Dwyer. + var triangulator = new Dwyer(); + + // Generate a default mesher. + var mesher = new GenericMesher(triangulator); + + // Generate mesh. + var mesh = mesher.Triangulate(points); + + if (print) SvgImage.Save(mesh, "example-1.svg", 500); + + return mesh.Triangles.Count > 0; + } + } +} diff --git a/src/Triangle.Examples/Examples/Example10.cs b/src/Triangle.Examples/Examples/Example10.cs new file mode 100644 index 0000000..ff13379 --- /dev/null +++ b/src/Triangle.Examples/Examples/Example10.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using TriangleNet.Geometry; +using TriangleNet.Meshing; +using TriangleNet.Meshing.Algorithm; +using TriangleNet.Rendering.Text; +using TriangleNet.Tools; + +namespace TriangleNet.Examples +{ + /// + /// Scattered data interpolation without USE_Z or USE_ATTRIBS. + /// + internal class Example10 + { + // The function we are sampling. + private static readonly Func F = p => Math.Sin(p.X) * Math.Cos(p.Y); + + public static bool Run(bool print = false) + { + // The input domain. + var r = new Rectangle(0d, 0d, 10d, 10d); + + var mesh = GetScatteredDataMesh(r, out double[] data); + //var mesh = GetStructuredDataMesh(r, out double[] data); + + if (print) SvgImage.Save(mesh, "example-10.svg", 500); + + // The points to interpolate. + var xy = Generate.RandomPoints(50, r); + + var xyData = InterpolateData((Mesh)mesh, data, xy); + + double error = xy.Max(p => Math.Abs(xyData[p.ID] - F(p))); + + // L2 error + // double error = Math.Sqrt(xy.Sum(p => Math.Pow(xyData[p.ID] - F(p), 2))); + + return error < 0.5; + } + + private static IMesh GetStructuredDataMesh(Rectangle domain, out double[] data) + { + var mesh = GenericMesher.StructuredMesh(domain, 20, 20); + + mesh.Renumber(); + + // Generate function values for mesh points. + data = new double[mesh.Vertices.Count]; + + foreach (var item in mesh.Vertices) + { + data[item.ID] = F(item); + } + + return mesh; + } + + private static IMesh GetScatteredDataMesh(Rectangle domain, out double[] data) + { + var r = new Rectangle(domain); + + double h = domain.Width / 20; + + // Generate a rectangle boundary point set (20 points on each side). + var input = Generate.Rectangle(r, 0.5); + + // Making sure we add some margin to the boundary. + h = -h / 2; + r.Resize(h, h); + + // Add more input points (more sampling points, better interpolation). + input.Points.AddRange(Generate.RandomPoints(350, r)); + + var mesher = new GenericMesher(new Dwyer()); + + // Generate mesh. + var mesh = mesher.Triangulate(input.Points); + + mesh.Renumber(); + + // Generate function values for mesh points. + data = new double[mesh.Vertices.Count]; + + foreach (var item in mesh.Vertices) + { + data[item.ID] = F(item); + } + + return mesh; + } + + private static double[] InterpolateData(Mesh mesh, double[] data, IEnumerable xy) + { + // The interpolated values. + var values = new double[xy.Count()]; + + var qtree = new TriangleQuadTree(mesh); + + int i = 0; + + foreach (var p in xy) + { + var tri = qtree.Query(p.X, p.Y); + + // For easy access of the interpolated values. + p.ID = i; + + if (tri == null) + { + values[i] = float.NaN; + } + else + { + values[i] = Interpolation.InterpolatePoint(tri, p, data); + } + + i++; + } + + return values; + } + } +} \ No newline at end of file diff --git a/src/Triangle.Examples/Examples/Example2.cs b/src/Triangle.Examples/Examples/Example2.cs new file mode 100644 index 0000000..c497ccf --- /dev/null +++ b/src/Triangle.Examples/Examples/Example2.cs @@ -0,0 +1,48 @@ +namespace TriangleNet.Examples +{ + using TriangleNet.Geometry; + using TriangleNet.Meshing; + using TriangleNet.Rendering.Text; + + /// + /// Triangulate a polygon with hole and set minimum angle constraint. + /// + public static class Example2 + { + public static bool Run(bool print = false) + { + // Generate the input geometry. + var poly = CreatePolygon(); + + // Set minimum angle quality option. + var quality = new QualityOptions() { MinimumAngle = 30.0 }; + + // Generate mesh using the polygons Triangulate extension method. + var mesh = poly.Triangulate(quality); + + if (print) SvgImage.Save(mesh, "example-2.svg", 500); + + return mesh.Triangles.Count > 0; + } + + public static IPolygon CreatePolygon(double h = 0.2) + { + // Generate the input geometry. + var poly = new Polygon(); + + // Center point. + var center = new Point(0, 0); + + // Inner contour (hole). + poly.Add(Generate.Circle(1.0, center, h, 1), center); + + // Internal contour. + poly.Add(Generate.Circle(2.0, center, h, 2)); + + // Outer contour. + poly.Add(Generate.Circle(3.0, center, h, 3)); + + return poly; + } + } +} \ No newline at end of file diff --git a/src/Triangle.Examples/Examples/Example3.cs b/src/Triangle.Examples/Examples/Example3.cs new file mode 100644 index 0000000..7e0182d --- /dev/null +++ b/src/Triangle.Examples/Examples/Example3.cs @@ -0,0 +1,54 @@ + +namespace TriangleNet.Examples +{ + using System; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + using TriangleNet.Rendering.Text; + using TriangleNet.Smoothing; + + /// + /// Triangulate a polygon with hole with maximum area constraint, followed by mesh smoothing. + /// + public class Example3 + { + public static bool Run(bool print = false) + { + // Generate mesh. + var mesh = CreateMesh(); + + if (print) SvgImage.Save(mesh, "example-3.svg", 500); + + return mesh.Triangles.Count > 0; + } + + public static IMesh CreateMesh() + { + // Generate the input geometry. + var poly = Example2.CreatePolygon(); + + // Since we want to do CVT smoothing, ensure that the mesh + // is conforming Delaunay. + var options = new ConstraintOptions() { ConformingDelaunay = true }; + + // Set maximum area quality option (we don't need to set a minimum + // angle, since smoothing will improve the triangle shapes). + var quality = new QualityOptions() + { + // The boundary segments have a length of 0.2, so we set a + // maximum area constraint assuming equilateral triangles. + MaximumArea = (Math.Sqrt(3) / 4 * 0.2 * 0.2) * 1.45 + }; + + // Generate mesh using the polygons Triangulate extension method. + var mesh = poly.Triangulate(options, quality); + + var smoother = new SimpleSmoother(); + + // Smooth mesh. + smoother.Smooth(mesh, 25); + + return mesh; + } + } +} diff --git a/src/Triangle.Examples/Examples/Example4.cs b/src/Triangle.Examples/Examples/Example4.cs new file mode 100644 index 0000000..d3c122a --- /dev/null +++ b/src/Triangle.Examples/Examples/Example4.cs @@ -0,0 +1,71 @@ + +namespace TriangleNet.Examples +{ + using TriangleNet; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + using TriangleNet.Rendering.Text; + using TriangleNet.Smoothing; + + /// + /// Refine only a part of a polygon mesh by using region pointers and an area constraint. + /// + public class Example4 + { + public static bool Run(bool print = false) + { + // Generate the input geometry. + var poly = CreatePolygon(); + + // Define regions (first one defines the area constraint). + poly.Regions.Add(new RegionPointer(1.5, 0.0, 1, 0.01)); + poly.Regions.Add(new RegionPointer(2.5, 0.0, 2)); + + // Set quality and constraint options. + var options = new ConstraintOptions() + { + ConformingDelaunay = true + }; + + var quality = new QualityOptions() + { + MinimumAngle = 25.0, + VariableArea = true + }; + + //quality.UserTest = (t, area) => t.Label == 1 && area > 0.01; + + var mesh = poly.Triangulate(options, quality); + + var smoother = new SimpleSmoother(); + + smoother.Smooth(mesh, 5); + + if (print) SvgImage.Save(mesh, "example-4.svg", 500); + + return mesh.Triangles.Count > 0; + } + + public static IPolygon CreatePolygon() + { + // Generate three concentric circles. + var poly = new Polygon(); + + // Center point. + var center = new Point(0, 0); + + // Inner contour (hole). + poly.Add(Generate.Circle(1.0, center, 0.1, 1), center); + + // Internal contour. + poly.Add(Generate.Circle(2.0, center, 0.1, 2)); + + // Outer contour. + poly.Add(Generate.Circle(3.0, center, 0.3, 3)); + + // Note that the outer contour has a larger segment size! + + return poly; + } + } +} diff --git a/src/Triangle.Examples/Examples/Example5.cs b/src/Triangle.Examples/Examples/Example5.cs new file mode 100644 index 0000000..17540af --- /dev/null +++ b/src/Triangle.Examples/Examples/Example5.cs @@ -0,0 +1,98 @@ + +namespace TriangleNet.Examples +{ + using System.Collections.Generic; + using TriangleNet; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + using TriangleNet.Meshing.Iterators; + using TriangleNet.Rendering.Text; + + /// + /// Two ways finding boundary triangles. + /// + public static class Example5 + { + public static bool Run(bool print = false) + { + var mesh = Example3.CreateMesh(); + + FindBoundary1(mesh); + + if (print) SvgImage.Save(mesh, "example-5-1.svg", 500, true, false); + + FindBoundary2(mesh); + + if (print) SvgImage.Save(mesh, "example-5-2.svg", 500, true, false); + + return mesh.Triangles.Count > 0; + } + + /// + /// Find boundary triangles using segments. + /// + private static void FindBoundary1(IMesh mesh, bool neigbours = true) + { + mesh.Renumber(); + + var cache = new List(mesh.Segments.Count + 1); + + var circulator = new VertexCirculator((Mesh)mesh); + + foreach (var s in mesh.Segments) + { + int label = s.Label; + + for (int i = 0; i < 2; i++) + { + var vertex = s.GetVertex(i); + + // Check the vertex ID to see if it was processed already. + if (vertex.ID >= 0) + { + var star = circulator.EnumerateTriangles(vertex); + + foreach (var triangle in star) + { + triangle.Label = label; + } + + // Mark the vertex as "processed". + vertex.ID = -vertex.ID; + + cache.Add(vertex); + } + } + } + + // Undo the vertex ID changes. + foreach (var vertex in cache) + { + vertex.ID = -vertex.ID; + } + } + + /// + /// Find boundary triangles using vertices. + /// + private static void FindBoundary2(IMesh mesh) + { + var circulator = new VertexCirculator((Mesh)mesh); + + foreach (var vertex in mesh.Vertices) + { + int label = vertex.Label; + + if (label > 0) + { + var star = circulator.EnumerateTriangles(vertex); + + foreach (var triangle in star) + { + triangle.Label = label; + } + } + } + } + } +} diff --git a/src/Triangle.Examples/Examples/Example6.cs b/src/Triangle.Examples/Examples/Example6.cs new file mode 100644 index 0000000..0ed1b2c --- /dev/null +++ b/src/Triangle.Examples/Examples/Example6.cs @@ -0,0 +1,56 @@ +namespace TriangleNet.Examples +{ + using System.Linq; + using TriangleNet; + using TriangleNet.Geometry; + using TriangleNet.Meshing.Iterators; + using TriangleNet.Rendering.Text; + using TriangleNet.Tools; + using TriangleNet.Topology; + + /// + /// Boolean operations on mesh regions (intersection, difference, xor). + /// + public static class Example6 + { + public static bool Run(bool print = true) + { + // Generate the input geometry. + var polygon = new Polygon(8, true); + + // Two intersecting rectangles. + var A = Generate.Rectangle(0d, 0d, 4d, 4d, label: 1); + var B = Generate.Rectangle(1d, 1d, 4d, 4d, label: 2); + + polygon.Add(A); + polygon.Add(B); + + // Generate mesh. + var mesh = (Mesh)polygon.Triangulate(); + + if (print) SvgImage.Save(mesh, "example-6.svg", 500); + + // Find a seeding triangle (in this case, the point (2, 2) lies in + // both rectangles). + var seed = (new TriangleQuadTree(mesh)).Query(2.0, 2.0) as Triangle; + + var iterator = new RegionIterator(mesh); + + iterator.Process(seed, t => t.Label ^= 1, 1); + iterator.Process(seed, t => t.Label ^= 2, 2); + + // At this point, all triangles will have label 1, 2 or 3 (= 1 xor 2). + + // The intersection of A and B. + var intersection = mesh.Triangles.Where(t => t.Label == 3); + + // The difference A \ B. + var difference = mesh.Triangles.Where(t => t.Label == 1); + + // The xor of A and B. + var xor = mesh.Triangles.Where(t => t.Label == 1 || t.Label == 2); + + return intersection.Any() && difference.Any() && xor.Any(); + } + } +} \ No newline at end of file diff --git a/src/Triangle.Examples/Examples/Example7.cs b/src/Triangle.Examples/Examples/Example7.cs new file mode 100644 index 0000000..cdc2f8f --- /dev/null +++ b/src/Triangle.Examples/Examples/Example7.cs @@ -0,0 +1,73 @@ + +namespace TriangleNet.Examples +{ + using System; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + using TriangleNet.Meshing.Iterators; + using TriangleNet.Rendering.Text; + + /// + /// Using a user test function to define a maximum edge length constraint. + /// + public static class Example7 + { + const double MAX_EDGE_LENGTH = 0.2; + + public static bool Run(bool print = false) + { + var poly = new Polygon(); + + // Generate the input geometry. + poly.Add(Generate.Rectangle(0.0, 0.0, 1.0, 1.0)); + + // Set minimum angle quality option, ignoring holes. + var quality = new QualityOptions() + { + UserTest = MaxEdgeLength + }; + + // Generate mesh using the polygons Triangulate extension method. + var mesh = (Mesh)poly.Triangulate(quality); + + // Validate. + foreach (var e in EdgeIterator.EnumerateEdges(mesh)) + { + double length = Math.Sqrt(DistSqr(e.GetVertex(0), e.GetVertex(1))); + + if (length > MAX_EDGE_LENGTH) + { + return false; + } + } + + if (print) SvgImage.Save(mesh, "example-7.svg", 500); + + return true; + } + + static bool MaxEdgeLength(ITriangle tri, double area) + { + var p0 = tri.GetVertex(0); + var p1 = tri.GetVertex(1); + var p2 = tri.GetVertex(2); + + var s1 = DistSqr(p0, p1); + var s2 = DistSqr(p1, p2); + var s3 = DistSqr(p2, p0); + + // Comparing against squared max leg length. + var maxlen = MAX_EDGE_LENGTH * MAX_EDGE_LENGTH; + + return s1 > maxlen || s2 > maxlen || s3 > maxlen; + } + + static double DistSqr(Vertex a, Vertex b) + { + var dx = a.X - b.X; + var dy = a.Y - b.Y; + + return dx * dx + dy * dy; + } + } +} diff --git a/src/Triangle.Examples/Examples/Example8.cs b/src/Triangle.Examples/Examples/Example8.cs new file mode 100644 index 0000000..f0a16a5 --- /dev/null +++ b/src/Triangle.Examples/Examples/Example8.cs @@ -0,0 +1,84 @@ + +namespace TriangleNet.Examples +{ + using System; + using System.Collections.Generic; + using TriangleNet; + using TriangleNet.Meshing.Iterators; + using TriangleNet.Tools; + + /// + /// Compute the adjacency matrix of the mesh vertices. + /// + public class Example8 + { + public static bool Run() + { + var mesh = (Mesh)Example3.CreateMesh(); + + return FindAdjacencyMatrix(mesh); + } + + private static bool FindAdjacencyMatrix(Mesh mesh) + { + mesh.Renumber(); + + var ap = new List(mesh.Vertices.Count); // Column pointers. + var ai = new List(4 * mesh.Vertices.Count); // Row indices. + + var circulator = new VertexCirculator(mesh); + + int k = 0; + + foreach (var vertex in mesh.Vertices) + { + var star = circulator.EnumerateVertices(vertex); + + ap.Add(k); + + // Each vertex is adjacent to itself. + ai.Add(vertex.ID); + k++; + + foreach (var item in star) + { + ai.Add(item.ID); + k++; + } + } + + ap.Add(k); + + var matrix1 = new AdjacencyMatrix(ap.ToArray(), ai.ToArray()); + var matrix2 = new AdjacencyMatrix(mesh); + + // Column pointers should be exactly the same. + if (!CompareArray(matrix1.ColumnPointers, matrix2.ColumnPointers)) + { + return false; + } + + return true; + } + + private static bool CompareArray(int[] a, int[] b) + { + int length = a.Length; + + if (b.Length != length) + { + return false; + } + + for (int i = 0; i < length; i++) + { + if (a[i] != b[i]) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/Triangle.Examples/Examples/Example9.cs b/src/Triangle.Examples/Examples/Example9.cs new file mode 100644 index 0000000..a02664b --- /dev/null +++ b/src/Triangle.Examples/Examples/Example9.cs @@ -0,0 +1,89 @@ + +namespace TriangleNet.Examples +{ + using System; + using System.Collections.Generic; + using System.Linq; + using TriangleNet.Geometry; + + /// + /// Troubleshooting: finding degenerate boundary triangles. + /// + public class Example9 + { + public static bool Run(bool print = false) + { + var pts = new List + { + // The 4 corners of the rectangle. + new Vertex(1.5, 1.0), + new Vertex(1.5, -1.0), + new Vertex(-1.5, -1.0), + new Vertex(-1.5, 1.0), + + // The edge midpoints. + new Vertex(0.0, 1.0), + new Vertex(0.0, -1.0), + new Vertex(1.5, 0.0), + new Vertex(-1.5, 0.0) + }; + + var r = new Random(78403); + + // The original rectangle. + var poly = Rotate(pts, 0); + + for (int i = 0; i < 10; i++) + { + var mesh = poly.Triangulate(); + + var list = MeshValidator.GetDegenerateBoundaryTriangles(mesh); + + if (print && list.Any()) + { + Console.WriteLine("Iteration {0}: found {1} degenerate triangle(s) of {2}.", + i, list.Count(), mesh.Triangles.Count); + + foreach (var t in list) + { + Console.WriteLine(" [{0} {1} {2}]", + t.GetVertexID(0), + t.GetVertexID(1), + t.GetVertexID(2)); + } + } + + // Random rotation. + poly = Rotate(pts, Math.PI * r.NextDouble()); + } + + return true; + } + + /// + /// Rotate given point set around the origin. + /// + private static IPolygon Rotate(List points, double radians) + { + var poly = new Polygon(points.Count); + + int id = 0; + + foreach (var p in points) + { + double x = p.X; + double y = p.Y; + + double s = Math.Sin(radians); + double c = Math.Cos(radians); + + double xr = c * x - s * y; + double yr = s * x + c * y; + + poly.Points.Add(new Vertex(xr, yr) { ID = id++ }); + } + + return poly; + } + } +} diff --git a/src/Triangle.Examples/Examples/ExamplePar.cs b/src/Triangle.Examples/Examples/ExamplePar.cs new file mode 100644 index 0000000..e53cf39 --- /dev/null +++ b/src/Triangle.Examples/Examples/ExamplePar.cs @@ -0,0 +1,98 @@ + +namespace TriangleNet.Examples +{ + using System; + using System.Collections.Concurrent; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + using TriangleNet; + using TriangleNet.IO; + using TriangleNet.Meshing; + + /// + /// Processing meshes in parallel. + /// + public class ExamplePar + { + /// + /// Reads all .poly files from given directory and processes them in parallel. + /// + public static bool Run(string dir) + { + var files = Directory.EnumerateFiles(dir, "*.poly", SearchOption.AllDirectories); + + var queue = new ConcurrentQueue(files); + + int concurrencyLevel = Environment.ProcessorCount / 2; + + var tasks = new Task[concurrencyLevel]; + + for (int i = 0; i < concurrencyLevel; i++) + { + tasks[i] = Task.Run(() => + { + // Each task has it's own triangle pool and predicates instance. + var pool = new TrianglePool(); + var predicates = new RobustPredicates(); + + // The factory methods return the above instances. + var config = new Configuration() + { + Predicates = () => predicates, + TrianglePool = () => pool.Restart() + }; + + var mesher = new GenericMesher(config); + var result = new MeshResult(); + + while (queue.Count > 0) + { + if (queue.TryDequeue(out var file)) + { + var poly = FileProcessor.Read(file); + + var mesh = mesher.Triangulate(poly); + + ProcessMesh(mesh, result); + } + } + + pool.Clear(); + + return result; + }); + } + + Task.WaitAll(tasks); + + int numberOfTriangles = tasks.Sum(t => t.Result.NumberOfTriangles); + int invalid = tasks.Sum(t => t.Result.Invalid); + + Console.WriteLine("Total number of triangles processed: {0}", numberOfTriangles); + + if (invalid > 0) + { + Console.WriteLine(" Number of invalid triangulations: {0}", invalid); + } + + return true; + } + + private static void ProcessMesh(IMesh mesh, MeshResult result) + { + result.NumberOfTriangles += mesh.Triangles.Count; + + if (!MeshValidator.IsConsistent((Mesh)mesh)) + { + result.Invalid += 1; + } + } + + class MeshResult + { + public int NumberOfTriangles; + public int Invalid; + } + } +} diff --git a/src/Triangle.Examples/Generate.cs b/src/Triangle.Examples/Generate.cs new file mode 100644 index 0000000..e3864af --- /dev/null +++ b/src/Triangle.Examples/Generate.cs @@ -0,0 +1,123 @@ + +namespace TriangleNet +{ + using System; + using System.Collections.Generic; + using TriangleNet.Geometry; + + static class Generate + { + private static readonly Random random = new Random(63841); + + public static List RandomPoints(int n, Rectangle bounds) + { + var points = new List(n); + + var xmin = bounds.Left; + var ymin = bounds.Bottom; + + var width = bounds.Width; + var height = bounds.Height; + + for (int i = 0; i < n; i++) + { + double x = random.NextDouble(); + double y = random.NextDouble(); + points.Add(new Vertex(xmin + x * width, ymin + y * height)); + } + + return points; + } + + /// + /// Creates a rectangle contour. + /// + public static Contour Rectangle(Rectangle rect, double size = 0d, int label = 0) + { + return Rectangle(rect.X, rect.Y, rect.Width, rect.Height, size, label); + } + + /// + /// Creates a rectangle contour. + /// + public static Contour Rectangle(double x, double y, double width, double height, + double size = 0d, int label = 0) + { + // Horizontal and vertical step sizes. + double stepH = 0d; + double stepV = 0d; + + int nH = 1; + int nV = 1; + + if (size > 0d) + { + size = Math.Min(size, Math.Min(width, height)); + + nH = (int)Math.Ceiling(width / size); + nV = (int)Math.Ceiling(height / size); + + stepH = width / nH; + stepV = height / nV; + } + + var points = new List(2 * nH + 2 * nV); + + double right = x + width; + double top = y + height; + + // Left box boundary points + for (int i = 0; i < nV; i++) + { + points.Add(new Vertex(x, y + i * stepV, label)); + } + + // Top box boundary points + for (int i = 0; i < nH; i++) + { + points.Add(new Vertex(x + i * stepH, top, label)); + } + + // Right box boundary points + for (int i = 0; i < nV; i++) + { + points.Add(new Vertex(right, top - i * stepV, label)); + } + + // Bottom box boundary points + for (int i = 0; i < nH; i++) + { + points.Add(new Vertex(right - i * stepH, y, label)); + } + + return new Contour(points, label, true); + } + + /// + /// Create a circular contour. + /// + /// The radius. + /// The center point. + /// The desired segment length. + /// The boundary label. + /// A circular contour. + public static Contour Circle(double r, Point center, double h, int label = 0) + { + int n = (int)(2 * Math.PI * r / h); + + var points = new List(n); + + double x, y, dphi = 2 * Math.PI / n; + + for (int i = 0; i < n; i++) + { + x = center.X + r * Math.Cos(i * dphi); + y = center.Y + r * Math.Sin(i * dphi); + + points.Add(new Vertex(x, y, label)); + } + + return new Contour(points, label, true); + } + } +} diff --git a/src/Triangle.Examples/Program.cs b/src/Triangle.Examples/Program.cs new file mode 100644 index 0000000..05269a1 --- /dev/null +++ b/src/Triangle.Examples/Program.cs @@ -0,0 +1,33 @@ + +namespace TriangleNet +{ + using System; + using TriangleNet.Examples; + + class Program + { + static void Main(string[] args) + { + Check("Example 1", Example1.Run()); + Check("Example 2", Example2.Run()); + Check("Example 3", Example3.Run()); + Check("Example 4", Example4.Run()); + Check("Example 5", Example5.Run()); + Check("Example 6", Example6.Run()); + Check("Example 7", Example7.Run()); + Check("Example 8", Example8.Run()); + Check("Example 9", Example9.Run()); + Check("Example 10", Example10.Run()); + } + + static void Check(string item, bool success) + { + var color = Console.ForegroundColor; + + Console.Write(item + " "); + Console.ForegroundColor = success ? ConsoleColor.DarkGreen : ConsoleColor.DarkRed; + Console.WriteLine(success ? "OK" : "Failed"); + Console.ForegroundColor = color; + } + } +} \ No newline at end of file diff --git a/src/Triangle.Examples/Triangle.Examples.csproj b/src/Triangle.Examples/Triangle.Examples.csproj new file mode 100644 index 0000000..e5cf28f --- /dev/null +++ b/src/Triangle.Examples/Triangle.Examples.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + TriangleNet + + + + + + + + diff --git a/Triangle.NET/Triangle.Rendering/ExtensionMethods.cs b/src/Triangle.Rendering.GDI/ExtensionMethods.cs similarity index 96% rename from Triangle.NET/Triangle.Rendering/ExtensionMethods.cs rename to src/Triangle.Rendering.GDI/ExtensionMethods.cs index 175d185..6f3b9c8 100644 --- a/Triangle.NET/Triangle.Rendering/ExtensionMethods.cs +++ b/src/Triangle.Rendering.GDI/ExtensionMethods.cs @@ -1,26 +1,26 @@ - -namespace TriangleNet.Rendering -{ - using System.Drawing; - - internal static class ExtensionMethods - { - /// - /// Check if segment (a, b) intersects rectangle. - /// - public static bool Intersects(this RectangleF rect, PointF a, PointF b) - { - // TODO: implement intersection. - return rect.Contains(a) || rect.Contains(b); - } - - /// - /// Check if triangle (a, b, c) intersects rectangle. - /// - public static bool Intersects(this RectangleF rect, PointF a, PointF b, PointF c) - { - // TODO: implement intersection. - return rect.Contains(a) || rect.Contains(b) || rect.Contains(c); - } - } -} + +namespace TriangleNet.Rendering +{ + using System.Drawing; + + internal static class ExtensionMethods + { + /// + /// Check if segment (a, b) intersects rectangle. + /// + public static bool Intersects(this RectangleF rect, PointF a, PointF b) + { + // TODO: implement intersection. + return rect.Contains(a) || rect.Contains(b); + } + + /// + /// Check if triangle (a, b, c) intersects rectangle. + /// + public static bool Intersects(this RectangleF rect, PointF a, PointF b, PointF c) + { + // TODO: implement intersection. + return rect.Contains(a) || rect.Contains(b) || rect.Contains(c); + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/GDI/FunctionRenderer.cs b/src/Triangle.Rendering.GDI/FunctionRenderer.cs similarity index 93% rename from Triangle.NET/Triangle.Rendering/GDI/FunctionRenderer.cs rename to src/Triangle.Rendering.GDI/FunctionRenderer.cs index 7320b29..2af4cac 100644 --- a/Triangle.NET/Triangle.Rendering/GDI/FunctionRenderer.cs +++ b/src/Triangle.Rendering.GDI/FunctionRenderer.cs @@ -1,101 +1,101 @@ - -namespace TriangleNet.Rendering.GDI -{ - using System; - using System.Drawing; - using TriangleNet.Rendering.GDI.Native; - - public class FunctionRenderer - { - TriVertex[] points; - GradientTriangle[] elements; - - public Graphics RenderTarget { get; set; } - - public IRenderContext Context { get; set; } - - public void Render(IRenderLayer layer) - { - Create(layer); - - var hdc = RenderTarget.GetHdc(); - - NativeMethods.GradientFill(hdc, - points, (uint)points.Length, elements, (uint)elements.Length, - GradientFillMode.GRADIENT_FILL_TRIANGLE); - - RenderTarget.ReleaseHdc(hdc); - } - - private void Create(IRenderLayer layer) - { - var zoom = Context.Zoom; - var colors = layer.Colors.Data; - - int length = colors.Length; - - int size = layer.Points.Size; - var data = layer.Points.Data; - - if (length != data.Length / size) - { - throw new Exception(); - } - - this.points = new TriVertex[length]; - - TriVertex vertex; - Color color; - PointF p = new PointF((float)data[0], (float)data[1]); - - zoom.WorldToScreen(ref p); - - // Get correction distance - float dx = (p.X - (int)p.X) * 2.0f; - float dy = (p.Y - (int)p.Y) * 2.0f; - - // Create vertices. - for (int i = 0; i < length; i++) - { - p.X = (float)data[size * i]; - p.Y = (float)data[size * i + 1]; - - zoom.WorldToScreen(ref p); - - color = colors[i]; - - vertex = new TriVertex(); - - vertex.x = (int)(p.X + dx); - vertex.y = (int)(p.Y + dy); - - vertex.Red = (ushort)(color.R << 8); - vertex.Green = (ushort)(color.G << 8); - vertex.Blue = (ushort)(color.B << 8); - vertex.Alpha = (ushort)(color.A << 8); - - this.points[i] = vertex; - } - - var triangles = layer.Indices.Data; - - length = triangles.Length / 3; - - this.elements = new GradientTriangle[length]; - - GradientTriangle e; - - // Create triangles. - for (int i = 0; i < length; i++) - { - e = new GradientTriangle(); - - e.Vertex1 = (uint)triangles[3 * i]; - e.Vertex2 = (uint)triangles[3 * i + 1]; - e.Vertex3 = (uint)triangles[3 * i + 2]; - - this.elements[i] = e; - } - } - } -} + +namespace TriangleNet.Rendering.GDI +{ + using System; + using System.Drawing; + using TriangleNet.Rendering.GDI.Native; + + public class FunctionRenderer + { + TriVertex[] points; + GradientTriangle[] elements; + + public Graphics RenderTarget { get; set; } + + public IRenderContext Context { get; set; } + + public void Render(IRenderLayer layer) + { + Create(layer); + + var hdc = RenderTarget.GetHdc(); + + NativeMethods.GradientFill(hdc, + points, (uint)points.Length, elements, (uint)elements.Length, + GradientFillMode.GRADIENT_FILL_TRIANGLE); + + RenderTarget.ReleaseHdc(hdc); + } + + private void Create(IRenderLayer layer) + { + var zoom = Context.Zoom; + var colors = layer.Colors.Data; + + int length = colors.Length; + + int size = layer.Points.Size; + var data = layer.Points.Data; + + if (length != data.Length / size) + { + throw new Exception(); + } + + this.points = new TriVertex[length]; + + TriVertex vertex; + Color color; + PointF p = new PointF((float)data[0], (float)data[1]); + + zoom.NdcToScreen(ref p); + + // Get correction distance + float dx = (p.X - (int)p.X) * 2.0f; + float dy = (p.Y - (int)p.Y) * 2.0f; + + // Create vertices. + for (int i = 0; i < length; i++) + { + p.X = (float)data[size * i]; + p.Y = (float)data[size * i + 1]; + + zoom.NdcToScreen(ref p); + + color = colors[i]; + + vertex = new TriVertex(); + + vertex.x = (int)(p.X + dx); + vertex.y = (int)(p.Y + dy); + + vertex.Red = (ushort)(color.R << 8); + vertex.Green = (ushort)(color.G << 8); + vertex.Blue = (ushort)(color.B << 8); + vertex.Alpha = (ushort)(color.A << 8); + + this.points[i] = vertex; + } + + var triangles = layer.Indices.Data; + + length = triangles.Length / 3; + + this.elements = new GradientTriangle[length]; + + GradientTriangle e; + + // Create triangles. + for (int i = 0; i < length; i++) + { + e = new GradientTriangle(); + + e.Vertex1 = (uint)triangles[3 * i]; + e.Vertex2 = (uint)triangles[3 * i + 1]; + e.Vertex3 = (uint)triangles[3 * i + 2]; + + this.elements[i] = e; + } + } + } +} diff --git a/src/Triangle.Rendering.GDI/Helper.cs b/src/Triangle.Rendering.GDI/Helper.cs new file mode 100644 index 0000000..81f71cd --- /dev/null +++ b/src/Triangle.Rendering.GDI/Helper.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Drawing; + +namespace TriangleNet.Rendering.GDI +{ + internal class Helper + { + public static void Dispose(Dictionary brushes) + { + foreach (var brush in brushes.Values) + { + brush.Dispose(); + } + } + + public static Dictionary GetBrushDictionary(Dictionary ColorDictionary) + { + var brushes = new Dictionary(); + + foreach (var item in ColorDictionary) + { + brushes.Add(item.Key, new SolidBrush(item.Value)); + } + + return brushes; + } + } +} diff --git a/src/Triangle.Rendering.GDI/ImageRenderer.cs b/src/Triangle.Rendering.GDI/ImageRenderer.cs new file mode 100644 index 0000000..3011273 --- /dev/null +++ b/src/Triangle.Rendering.GDI/ImageRenderer.cs @@ -0,0 +1,287 @@ + +namespace TriangleNet.Rendering.GDI +{ + using System; + using System.Collections.Generic; + using System.Drawing; + using System.Drawing.Drawing2D; + using System.Drawing.Imaging; + using System.IO; + using TriangleNet.Meshing; + + /// + /// Enables rendering of polygons or meshes to a bitmap. + /// + public class ImageRenderer + { + ColorManager colors = LightScheme(); + + public ColorManager ColorScheme + { + get { return colors; } + set { colors = value; } + } + + public bool EnableRegions { get; set; } + + public bool EnablePoints { get; set; } + + /// + /// Exports a polygon to PNG format. + /// + /// The polygon. + /// The desired width (pixel) of the image. + /// The PNG filename. + /// Enable rendering of regions. + /// Enable rendering of points. + public static void Save(Geometry.IPolygon poly, string file = null, int width = 800, + bool points = true) + { + // Check file name + if (string.IsNullOrWhiteSpace(file)) + { + file = string.Format("poly-{0}.png", DateTime.Now.ToString("yyyy-M-d-hh-mm-ss")); + } + + // Ensure .png extension. + if (file.EndsWith(".png", StringComparison.OrdinalIgnoreCase)) + { + Path.ChangeExtension(file, ".png"); + } + + var renderer = new ImageRenderer(); + + renderer.EnableRegions = false; + renderer.EnablePoints = points; + + var bitmap = renderer.Render(poly, width); + + bitmap.Save(file, ImageFormat.Png); + } + + /// + /// Exports a mesh to PNG format. + /// + /// The mesh. + /// The desired width (pixel) of the image. + /// The PNG filename. + /// Enable rendering of regions. + /// Enable rendering of points. + public static void Save(IMesh mesh, string file = null, int width = 800, + bool regions = false, bool points = true) + { + // Check file name + if (string.IsNullOrWhiteSpace(file)) + { + file = string.Format("mesh-{0}.png", DateTime.Now.ToString("yyyy-M-d-hh-mm-ss")); + } + + // Ensure .png extension. + if (!file.EndsWith(".png", StringComparison.OrdinalIgnoreCase)) + { + Path.ChangeExtension(file, ".png"); + } + + var renderer = new ImageRenderer(); + + renderer.EnableRegions = regions; + renderer.EnablePoints = points; + + var bitmap = renderer.Render(mesh, width); + + bitmap.Save(file, ImageFormat.Png); + } + + /// + /// Renders the polygon to a bitmap. + /// + /// The polygon. + /// The desired width (pixel) of the image. + /// The bitmap. + /// + /// The width has to be at least 2 * sqrt(n), n the number of vertices. + /// Otherwise, an empty bitmap + /// + public Bitmap Render(Geometry.IPolygon poly, int width = 800) + { + Bitmap bitmap; + + // Check if the specified width is reasonable + if (width < 2 * Math.Sqrt(poly.Points.Count)) + { + return new Bitmap(1, 1); + } + + var bounds = poly.Bounds(); + + // World margin on each side + float margin = (float)bounds.Height * 0.05f; + float scale = width / ((float)bounds.Width + 2 * margin); + + var target = new Rectangle(0, 0, width, (int)((bounds.Height + 2 * margin) * scale)); + + bitmap = new Bitmap(width, target.Height, PixelFormat.Format32bppPArgb); + + using (var g = Graphics.FromImage(bitmap)) + { + g.Clear(colors.Background); + g.SmoothingMode = SmoothingMode.HighQuality; + + var context = new RenderContext(new Projection(target), colors); + context.Add(poly); + + if (!EnablePoints) + { + context.Enable(3, false); + } + + var renderer = new LayerRenderer(); + renderer.Context = context; + renderer.RenderTarget = g; + renderer.Render(); + } + + return bitmap; + } + + /// + /// Renders the mesh to a bitmap. + /// + /// The current mesh. + /// The desired width (pixel) of the image. + /// The bitmap. + /// + /// The width has to be at least 2 * sqrt(n), n the number of vertices. + /// Otherwise, an empty bitmap + /// + public Bitmap Render(IMesh mesh, int width = 800) + { + Bitmap bitmap; + + // Check if the specified width is reasonable + if (width < 2 * Math.Sqrt(mesh.Vertices.Count)) + { + return new Bitmap(1, 1); + } + + var bounds = mesh.Bounds; + + // World margin on each side + float margin = (float)bounds.Height * 0.05f; + float scale = width / ((float)bounds.Width + 2 * margin); + + var target = new Rectangle(0, 0, width, (int)((bounds.Height + 2 * margin) * scale)); + + bitmap = new Bitmap(width, target.Height, PixelFormat.Format32bppPArgb); + + using (var g = Graphics.FromImage(bitmap)) + { + g.Clear(colors.Background); + g.SmoothingMode = SmoothingMode.HighQuality; + + var context = new RenderContext(new Projection(target), colors); + context.Add(mesh, true); + + if (EnableRegions) + { + context.Add(GetRegions(mesh)); + } + + if (!EnablePoints) + { + context.Enable(3, false); + } + + var renderer = new LayerRenderer(); + renderer.Context = context; + renderer.RenderTarget = g; + renderer.Render(); + } + + return bitmap; + } + + public Bitmap Render(IMesh mesh, Topology.DCEL.DcelMesh dcel, int width = 800) + { + Bitmap bitmap; + + // Check if the specified width is reasonable + if (width < 2 * Math.Sqrt(mesh.Vertices.Count)) + { + return new Bitmap(1, 1); + } + + var bounds = mesh.Bounds; + + // World margin on each side + float margin = (float)bounds.Height * 0.05f; + float scale = width / ((float)bounds.Width + 2 * margin); + + var target = new Rectangle(0, 0, width, (int)((bounds.Height + 2 * margin) * scale)); + + bitmap = new Bitmap(width, target.Height, PixelFormat.Format32bppPArgb); + + using (var g = Graphics.FromImage(bitmap)) + { + g.Clear(colors.Background); + g.SmoothingMode = SmoothingMode.HighQuality; + + var context = new RenderContext(new Projection(target), colors); + context.Add(mesh, true); + context.Add(dcel.Vertices.ToArray(), dcel.Edges, false); + + if (EnableRegions) + { + context.Add(GetRegions(mesh)); + } + + if (!EnablePoints) + { + context.Enable(3, false); + } + + var renderer = new LayerRenderer(); + renderer.Context = context; + renderer.RenderTarget = g; + renderer.Render(); + } + + return bitmap; + } + + private int[] GetRegions(IMesh mesh) + { + mesh.Renumber(); + + var labels = new int[mesh.Triangles.Count]; + var regions = new SortedSet(); + + foreach (var t in mesh.Triangles) + { + labels[t.ID] = t.Label; + regions.Add(t.Label); + } + + if (colors.ColorDictionary == null) + { + colors.CreateColorDictionary(regions); + } + + return labels; + } + + public static ColorManager LightScheme() + { + var colors = new ColorManager(); + + colors.Background = Color.White; + colors.Point = Color.FromArgb(60, 80, 120); + colors.SteinerPoint = Color.DarkGreen; + colors.Line = Color.FromArgb(200, 200, 200); + colors.Segment = Color.SteelBlue; + colors.VoronoiLine = Color.FromArgb(160, 170, 180); + + return colors; + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/GDI/LayerRenderer.cs b/src/Triangle.Rendering.GDI/LayerRenderer.cs similarity index 92% rename from Triangle.NET/Triangle.Rendering/GDI/LayerRenderer.cs rename to src/Triangle.Rendering.GDI/LayerRenderer.cs index b70dda2..6a9aa78 100644 --- a/Triangle.NET/Triangle.Rendering/GDI/LayerRenderer.cs +++ b/src/Triangle.Rendering.GDI/LayerRenderer.cs @@ -1,118 +1,124 @@ - -namespace TriangleNet.Rendering.GDI -{ - using System.Drawing; - - public class LayerRenderer : IRenderer - { - MeshRenderer meshRenderer; - FunctionRenderer functionRenderer; - - public IRenderContext Context { get; set; } - - public Graphics RenderTarget { get; set; } - - public LayerRenderer() - { - meshRenderer = new MeshRenderer(); - functionRenderer = new FunctionRenderer(); - } - - public void Render() - { - meshRenderer.Context = Context; - meshRenderer.RenderTarget = RenderTarget; - - functionRenderer.Context = Context; - functionRenderer.RenderTarget = RenderTarget; - - // 0 = mesh (filled) - // 1 = mesh (wireframe) - // 2 = polygon - // 3 = points - // 4 = voronoi overlay - // 5 = vector field - // 6 = contour lines - - int i = 0; - - foreach (var layer in this.Context.RenderLayers) - { - if (!layer.IsEmpty() && layer.IsEnabled) - { - switch (i) - { - case 0: - RenderFilledMesh(layer); - break; - case 1: - RenderMesh(layer); - break; - case 2: - RenderPolygon(layer); - break; - case 3: - RenderPoints(layer); - break; - case 4: - RenderVoronoi(layer); - break; - case 5: - case 6: - default: - break; - } - } - - i++; - } - } - - private void RenderFilledMesh(IRenderLayer layer) - { - if (layer.Partition != null) - { - meshRenderer.RenderElements(layer.Points.Data, layer.Indices.Data, 3, layer.Partition.Data); - } - else if (layer.Colors != null) - { - functionRenderer.Render(layer); - } - } - - private void RenderMesh(IRenderLayer layer) - { - if (layer.Indices.Size == 3) - { - meshRenderer.RenderElements(layer.Points.Data, layer.Indices.Data, 3, null); - } - else - { - meshRenderer.RenderEdges(layer.Points.Data, layer.Indices.Data, Context.ColorManager.Line); - } - } - - private void RenderPolygon(IRenderLayer layer) - { - meshRenderer.RenderSegments(layer.Points.Data, layer.Indices.Data, Context.ColorManager.Segment); - } - - private void RenderPoints(IRenderLayer layer) - { - meshRenderer.RenderPoints(layer.Points.Data, layer.Points.Size, layer.Count); - } - - private void RenderVoronoi(IRenderLayer layer) - { - if (RenderManager.VORONOI_DEBUG) - { - meshRenderer.RenderEdges(layer.Points.Data, layer.Indices.Data, Pens.Purple); - meshRenderer.RenderPoints(layer.Points.Data, layer.Points.Size, 0, layer.Count, Brushes.Red); - } - else - { - meshRenderer.RenderEdges(layer.Points.Data, layer.Indices.Data, Context.ColorManager.VoronoiLine); - } - } - } -} + +namespace TriangleNet.Rendering.GDI +{ + using System.Drawing; + + public class LayerRenderer : IRenderer + { + MeshRenderer meshRenderer; + FunctionRenderer functionRenderer; + + public IRenderContext Context { get; set; } + + public Graphics RenderTarget { get; set; } + + public LayerRenderer() + { + meshRenderer = new MeshRenderer(); + functionRenderer = new FunctionRenderer(); + } + + public void Render() + { + meshRenderer.Context = Context; + meshRenderer.RenderTarget = RenderTarget; + + functionRenderer.Context = Context; + functionRenderer.RenderTarget = RenderTarget; + + // 0 = mesh (filled) + // 1 = mesh (wireframe) + // 2 = polygon + // 3 = points + // 4 = voronoi overlay + // 5 = vector field + // 6 = contour lines + + int i = 0; + + foreach (var layer in this.Context.RenderLayers) + { + if (!layer.IsEmpty() && layer.IsEnabled) + { + switch (i) + { + case 0: + RenderFilledMesh(layer); + break; + case 1: + RenderMesh(layer); + break; + case 2: + RenderPolygon(layer); + break; + case 3: + RenderPoints(layer); + break; + case 4: + RenderVoronoi(layer); + break; + case 5: + case 6: + default: + break; + } + } + + i++; + } + } + + private void RenderFilledMesh(IRenderLayer layer) + { + if (layer.Partition != null) + { + meshRenderer.RenderElements(layer.Points.Data, layer.Indices.Data, 3, layer.Partition.Data); + } + else if (layer.Colors != null) + { + functionRenderer.Render(layer); + } + } + + private void RenderMesh(IRenderLayer layer) + { + if (layer.Indices.Size == 3) + { + meshRenderer.RenderElements(layer.Points.Data, layer.Indices.Data, 3, null); + } + else + { + using var pen = new Pen(Context.ColorManager.Line); + + meshRenderer.RenderEdges(layer.Points.Data, layer.Indices.Data, pen); + } + } + + private void RenderPolygon(IRenderLayer layer) + { + using var pen = new Pen(Context.ColorManager.Segment); + + meshRenderer.RenderSegments(layer.Points.Data, layer.Indices.Data, pen); + } + + private void RenderPoints(IRenderLayer layer) + { + meshRenderer.RenderPoints(layer.Points.Data, layer.Points.Size, layer.Count); + } + + private void RenderVoronoi(IRenderLayer layer) + { + if (RenderManager.VORONOI_DEBUG) + { + meshRenderer.RenderEdges(layer.Points.Data, layer.Indices.Data, Pens.Purple); + meshRenderer.RenderPoints(layer.Points.Data, layer.Points.Size, 0, layer.Count, Brushes.Red); + } + else + { + using var pen = new Pen(Context.ColorManager.VoronoiLine); + + meshRenderer.RenderEdges(layer.Points.Data, layer.Indices.Data, pen); + } + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/GDI/MeshRenderer.cs b/src/Triangle.Rendering.GDI/MeshRenderer.cs similarity index 83% rename from Triangle.NET/Triangle.Rendering/GDI/MeshRenderer.cs rename to src/Triangle.Rendering.GDI/MeshRenderer.cs index 38c3614..71e9d48 100644 --- a/Triangle.NET/Triangle.Rendering/GDI/MeshRenderer.cs +++ b/src/Triangle.Rendering.GDI/MeshRenderer.cs @@ -1,173 +1,175 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Rendering.GDI -{ - using System.Drawing; - using TriangleNet.Rendering.GDI.Native; - - /// - /// Renders a mesh. - /// - public class MeshRenderer - { - /// - /// Initializes a new instance of the class. - /// - public MeshRenderer() - { - } - - public Graphics RenderTarget { get; set; } - - public IRenderContext Context { get; set; } - - public void RenderPoints(float[] points, int size, int limit = 0) - { - int n = points.Length / size; - int m = limit > 0 ? limit : n; - - // Draw unchanged points - RenderPoints(points, size, 0, m, Context.ColorManager.Point); - - // Draw new (Steiner) points - if (limit > 0) - { - RenderPoints(points, size, m, n, Context.ColorManager.SteinerPoint); - } - } - - public void RenderPoints(float[] points, int size, int start, int end, Brush brush) - { - var g = this.RenderTarget; - var zoom = this.Context.Zoom; - - int i, k, n = points.Length / size; - PointF p = new PointF(); - - // Render points - for (i = start; i < end; i++) - { - k = size * i; - - p.X = points[k]; - p.Y = points[k + 1]; - - if (zoom.Viewport.Contains(p)) - { - zoom.WorldToScreen(ref p); - g.FillEllipse(brush, p.X - 1.5f, p.Y - 1.5f, 3, 3); - } - } - } - - public void RenderSegments(float[] points, int[] indices, Pen pen) - { - RenderLines(points, indices, pen); - } - - public void RenderEdges(float[] points, int[] indices, Pen pen) - { - RenderLines(points, indices, pen); - } - - public void RenderElements(float[] points, int[] indices, int size, int[] partition) - { - var g = this.RenderTarget; - var zoom = this.Context.Zoom; - - int n = indices.Length / size; - int k0, k1, k2; - - var tri = new PointF[size]; - - bool filled = partition != null; - - var brushes = filled ? Context.ColorManager.GetBrushDictionary() : null; - - // TODO: remove hardcoded color - var pen = new Pen(Color.FromArgb(20, 20, 20)); - - // Draw triangles - for (int i = 0; i < n; i++) - { - k0 = 2 * indices[3 * i]; - k1 = 2 * indices[3 * i + 1]; - k2 = 2 * indices[3 * i + 2]; - - tri[0].X = points[k0]; - tri[0].Y = points[k0 + 1]; - - tri[1].X = points[k1]; - tri[1].Y = points[k1 + 1]; - - tri[2].X = points[k2]; - tri[2].Y = points[k2 + 1]; - - if (zoom.Viewport.Intersects(tri[0], tri[1], tri[2])) - { - zoom.WorldToScreen(ref tri[0]); - zoom.WorldToScreen(ref tri[1]); - zoom.WorldToScreen(ref tri[2]); - - if (filled) - { - var b = brushes[partition[i]]; - - if (b.Color.A > 0) - { - g.FillPolygon(b, tri); - } - } - else - { - g.DrawPolygon(pen, tri); - } - } - } - - pen.Dispose(); - - if (filled) - { - Context.ColorManager.Dispose(brushes); - } - } - - public void RenderLines(float[] points, int[] indices, Pen pen) - { - var g = this.RenderTarget; - var zoom = this.Context.Zoom; - - int n = indices.Length / 2; - int k0, k1; - - PointF p0 = new PointF(); - PointF p1 = new PointF(); - - // Draw edges - for (int i = 0; i < n; i++) - { - k0 = 2 * indices[2 * i]; - k1 = 2 * indices[2 * i + 1]; - - p0.X = points[k0]; - p0.Y = points[k0 + 1]; - - p1.X = points[k1]; - p1.Y = points[k1 + 1]; - - if (zoom.Viewport.Intersects(p0, p1)) - { - zoom.WorldToScreen(ref p0); - zoom.WorldToScreen(ref p1); - - g.DrawLine(pen, p0, p1); - } - } - } - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Rendering.GDI +{ + using System.Drawing; + + /// + /// Renders a mesh. + /// + public class MeshRenderer + { + /// + /// Initializes a new instance of the class. + /// + public MeshRenderer() + { + } + + public Graphics RenderTarget { get; set; } + + public IRenderContext Context { get; set; } + + public void RenderPoints(float[] points, int size, int limit = 0) + { + int n = points.Length / size; + int m = limit > 0 ? limit : n; + + using var Point = new SolidBrush(Context.ColorManager.Point); + using var SteinerPoint = new SolidBrush(Context.ColorManager.SteinerPoint); + + // Draw unchanged points + RenderPoints(points, size, 0, m, Point); + + // Draw new (Steiner) points + if (limit > 0) + { + RenderPoints(points, size, m, n, SteinerPoint); + } + } + + public void RenderPoints(float[] points, int size, int start, int end, Brush brush) + { + var g = this.RenderTarget; + var zoom = this.Context.Zoom; + + int i, k, n = points.Length / size; + PointF p = new PointF(); + + // Render points + for (i = start; i < end; i++) + { + k = size * i; + + p.X = points[k]; + p.Y = points[k + 1]; + + if (zoom.Viewport.Contains(p)) + { + zoom.NdcToScreen(ref p); + g.FillEllipse(brush, p.X - 1.5f, p.Y - 1.5f, 3, 3); + } + } + } + + public void RenderSegments(float[] points, int[] indices, Pen pen) + { + RenderLines(points, indices, pen); + } + + public void RenderEdges(float[] points, int[] indices, Pen pen) + { + RenderLines(points, indices, pen); + } + + public void RenderElements(float[] points, int[] indices, int size, int[] partition) + { + var g = this.RenderTarget; + var zoom = this.Context.Zoom; + + int n = indices.Length / size; + int k0, k1, k2; + + var tri = new PointF[size]; + + bool filled = partition != null; + + var brushes = filled ? Helper.GetBrushDictionary(Context.ColorManager.ColorDictionary) : null; + + // TODO: remove hard-coded color + var pen = new Pen(Color.FromArgb(20, 20, 20)); + + // Draw triangles + for (int i = 0; i < n; i++) + { + k0 = 2 * indices[3 * i]; + k1 = 2 * indices[3 * i + 1]; + k2 = 2 * indices[3 * i + 2]; + + tri[0].X = points[k0]; + tri[0].Y = points[k0 + 1]; + + tri[1].X = points[k1]; + tri[1].Y = points[k1 + 1]; + + tri[2].X = points[k2]; + tri[2].Y = points[k2 + 1]; + + if (zoom.Viewport.Intersects(tri[0], tri[1], tri[2])) + { + zoom.NdcToScreen(ref tri[0]); + zoom.NdcToScreen(ref tri[1]); + zoom.NdcToScreen(ref tri[2]); + + if (filled) + { + var b = brushes[partition[i]]; + + if (b.Color.A > 0) + { + g.FillPolygon(b, tri); + } + } + else + { + g.DrawPolygon(pen, tri); + } + } + } + + pen.Dispose(); + + if (filled) + { + Helper.Dispose(brushes); + } + } + + public void RenderLines(float[] points, int[] indices, Pen pen) + { + var g = this.RenderTarget; + var zoom = this.Context.Zoom; + + int n = indices.Length / 2; + int k0, k1; + + PointF p0 = new PointF(); + PointF p1 = new PointF(); + + // Draw edges + for (int i = 0; i < n; i++) + { + k0 = 2 * indices[2 * i]; + k1 = 2 * indices[2 * i + 1]; + + p0.X = points[k0]; + p0.Y = points[k0 + 1]; + + p1.X = points[k1]; + p1.Y = points[k1 + 1]; + + if (zoom.Viewport.Intersects(p0, p1)) + { + zoom.NdcToScreen(ref p0); + zoom.NdcToScreen(ref p1); + + g.DrawLine(pen, p0, p1); + } + } + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/GDI/Native/GradientFillMode.cs b/src/Triangle.Rendering.GDI/Native/GradientFillMode.cs similarity index 97% rename from Triangle.NET/Triangle.Rendering/GDI/Native/GradientFillMode.cs rename to src/Triangle.Rendering.GDI/Native/GradientFillMode.cs index 7b6ba25..9db4ac9 100644 --- a/Triangle.NET/Triangle.Rendering/GDI/Native/GradientFillMode.cs +++ b/src/Triangle.Rendering.GDI/Native/GradientFillMode.cs @@ -1,35 +1,35 @@ - -namespace TriangleNet.Rendering.GDI.Native -{ - using System; - - /// - /// Specifies gradient fill mode - /// - [Flags] - internal enum GradientFillMode : uint - { - /// - /// In this mode, two endpoints describe a rectangle. The rectangle is defined - /// to have a constant color (specified by the TRIVERTEX structure) for the - /// left and right edges. GDI interpolates the color from the left to right - /// edge and fills the interior - /// - GRADIENT_FILL_RECT_H = 0, - /// - /// In this mode, two endpoints describe a rectangle. The rectangle is - /// defined to have a constant color (specified by the TRIVERTEX structure) - /// for the top and bottom edges. GDI interpolates the color from the top - /// to bottom edge and fills the interior - /// - GRADIENT_FILL_RECT_V = 1, - /// - /// In this mode, an array of TRIVERTEX structures is passed to GDI - /// along with a list of array indexes that describe separate triangles. - /// GDI performs linear interpolation between triangle vertices and fills - /// the interior. Drawing is done directly in 24- and 32-bpp modes. - /// Dithering is performed in 16-, 8-, 4-, and 1-bpp mode - /// - GRADIENT_FILL_TRIANGLE = 2 - } -} + +namespace TriangleNet.Rendering.GDI.Native +{ + using System; + + /// + /// Specifies gradient fill mode + /// + [Flags] + internal enum GradientFillMode : uint + { + /// + /// In this mode, two endpoints describe a rectangle. The rectangle is defined + /// to have a constant color (specified by the TRIVERTEX structure) for the + /// left and right edges. GDI interpolates the color from the left to right + /// edge and fills the interior + /// + GRADIENT_FILL_RECT_H = 0, + /// + /// In this mode, two endpoints describe a rectangle. The rectangle is + /// defined to have a constant color (specified by the TRIVERTEX structure) + /// for the top and bottom edges. GDI interpolates the color from the top + /// to bottom edge and fills the interior + /// + GRADIENT_FILL_RECT_V = 1, + /// + /// In this mode, an array of TRIVERTEX structures is passed to GDI + /// along with a list of array indexes that describe separate triangles. + /// GDI performs linear interpolation between triangle vertices and fills + /// the interior. Drawing is done directly in 24- and 32-bpp modes. + /// Dithering is performed in 16-, 8-, 4-, and 1-bpp mode + /// + GRADIENT_FILL_TRIANGLE = 2 + } +} diff --git a/Triangle.NET/Triangle.Rendering/GDI/Native/GradientRect.cs b/src/Triangle.Rendering.GDI/Native/GradientRect.cs similarity index 96% rename from Triangle.NET/Triangle.Rendering/GDI/Native/GradientRect.cs rename to src/Triangle.Rendering.GDI/Native/GradientRect.cs index f617645..9511c00 100644 --- a/Triangle.NET/Triangle.Rendering/GDI/Native/GradientRect.cs +++ b/src/Triangle.Rendering.GDI/Native/GradientRect.cs @@ -1,28 +1,28 @@ - -namespace TriangleNet.Rendering.GDI.Native -{ - using System.Runtime.InteropServices; - - /// - /// The GRADIENT_RECT structure specifies the index of two vertices in the - /// pVertex array in the GradientFill function. These two vertices form the - /// upper-left and lower-right boundaries of a rectangle. - /// - /// - /// http://msdn.microsoft.com/en-us/library/windows/desktop/dd144958.aspx - /// - [StructLayout(LayoutKind.Sequential)] - internal struct GradientRect - { - /// - /// The upper-left corner of a rectangle. - /// - public uint UpperLeft; - - /// - /// The lower-right corner of a rectangle. - /// - public uint LowerRight; - } - -} + +namespace TriangleNet.Rendering.GDI.Native +{ + using System.Runtime.InteropServices; + + /// + /// The GRADIENT_RECT structure specifies the index of two vertices in the + /// pVertex array in the GradientFill function. These two vertices form the + /// upper-left and lower-right boundaries of a rectangle. + /// + /// + /// http://msdn.microsoft.com/en-us/library/windows/desktop/dd144958.aspx + /// + [StructLayout(LayoutKind.Sequential)] + internal struct GradientRect + { + /// + /// The upper-left corner of a rectangle. + /// + public uint UpperLeft; + + /// + /// The lower-right corner of a rectangle. + /// + public uint LowerRight; + } + +} diff --git a/Triangle.NET/Triangle.Rendering/GDI/Native/GradientTriangle.cs b/src/Triangle.Rendering.GDI/Native/GradientTriangle.cs similarity index 96% rename from Triangle.NET/Triangle.Rendering/GDI/Native/GradientTriangle.cs rename to src/Triangle.Rendering.GDI/Native/GradientTriangle.cs index dd64867..f6ae91b 100644 --- a/Triangle.NET/Triangle.Rendering/GDI/Native/GradientTriangle.cs +++ b/src/Triangle.Rendering.GDI/Native/GradientTriangle.cs @@ -1,32 +1,32 @@ - -namespace TriangleNet.Rendering.GDI.Native -{ - using System.Runtime.InteropServices; - - /// - /// The GRADIENT_TRIANGLE structure specifies the index of three - /// vertices in the pVertex array in the GradientFill function. - /// These three vertices form one triangle - /// - /// - /// http://msdn.microsoft.com/en-us/library/windows/desktop/dd144959.aspx - /// - [StructLayout(LayoutKind.Sequential)] - internal struct GradientTriangle - { - /// - /// The first point of the triangle where sides intersect. - /// - public uint Vertex1; - - /// - /// The second point of the triangle where sides intersect. - /// - public uint Vertex2; - - /// - /// The third point of the triangle where sides intersect. - /// - public uint Vertex3; - } -} + +namespace TriangleNet.Rendering.GDI.Native +{ + using System.Runtime.InteropServices; + + /// + /// The GRADIENT_TRIANGLE structure specifies the index of three + /// vertices in the pVertex array in the GradientFill function. + /// These three vertices form one triangle + /// + /// + /// http://msdn.microsoft.com/en-us/library/windows/desktop/dd144959.aspx + /// + [StructLayout(LayoutKind.Sequential)] + internal struct GradientTriangle + { + /// + /// The first point of the triangle where sides intersect. + /// + public uint Vertex1; + + /// + /// The second point of the triangle where sides intersect. + /// + public uint Vertex2; + + /// + /// The third point of the triangle where sides intersect. + /// + public uint Vertex3; + } +} diff --git a/Triangle.NET/Triangle.Rendering/GDI/Native/NativeMethods.cs b/src/Triangle.Rendering.GDI/Native/NativeMethods.cs similarity index 97% rename from Triangle.NET/Triangle.Rendering/GDI/Native/NativeMethods.cs rename to src/Triangle.Rendering.GDI/Native/NativeMethods.cs index 2a625f4..46e4dea 100644 --- a/Triangle.NET/Triangle.Rendering/GDI/Native/NativeMethods.cs +++ b/src/Triangle.Rendering.GDI/Native/NativeMethods.cs @@ -1,90 +1,90 @@ - -namespace TriangleNet.Rendering.GDI.Native -{ - using System; - using System.Runtime.InteropServices; - - /// - /// PInvoke signatures for GradientFill methods. - /// - /// - /// Minimum requirements: Windows 2000 Professional - /// - /// http://msdn.microsoft.com/en-us/library/windows/desktop/dd144957.aspx - /// - internal static class NativeMethods - { - /// - /// The GradientFill function fills rectangle and triangle structures - /// - /// Handle to the destination device contex - /// Array of TRIVERTEX structures that each define a triangle vertex - /// The number of vertices in pVertex - /// Array of elements - /// The number of elements in pMesh - /// Specifies gradient fill mode - /// If the function succeeds, the return value is true, false - public static bool GradientFill([In] IntPtr hdc, TriVertex[] pVertex, uint nVertex, uint[] pMesh, uint nMesh, - GradientFillMode ulMode) - { - return Native.GradientFill(hdc, pVertex, nVertex, pMesh, nMesh, ulMode); - } - - /// - /// The GradientFill function fills rectangle and triangle structures - /// - /// Handle to the destination device contex - /// Array of TRIVERTEX structures that each define a triangle vertex - /// The number of vertices in pVertex - /// Array of GRADIENT_TRIANGLE structures in triangle mode - /// The number of elements in pMesh - /// Specifies gradient fill mode - /// If the function succeeds, the return value is true, false - public static bool GradientFill([In] IntPtr hdc, TriVertex[] pVertex, uint nVertex, GradientTriangle[] pMesh, - uint nMesh, GradientFillMode ulMode) - { - return Native.GradientFill(hdc, pVertex, nVertex, pMesh, nMesh, ulMode); - } - - /// - /// The GradientFill function fills rectangle and triangle structures - /// - /// Handle to the destination device contex - /// Array of TRIVERTEX structures that each define a triangle vertex - /// The number of vertices in pVertex - /// an array of GRADIENT_RECT structures in rectangle mode - /// The number of elements in pMesh - /// Specifies gradient fill mode - /// If the function succeeds, the return value is true, false - public static bool GradientFill([In] IntPtr hdc, TriVertex[] pVertex, uint nVertex, GradientRect[] pMesh, - uint nMesh, GradientFillMode ulMode) - { - return Native.GradientFill(hdc, pVertex, nVertex, pMesh, nMesh, ulMode); - } - - #region Nested type: Native - - internal class Native - { - [DllImport("msimg32.dll", EntryPoint = "GradientFill", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool GradientFill([In] IntPtr hdc, - [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct, SizeParamIndex = 2)] TriVertex[] pVertex, - uint nVertex, uint[] pMesh, uint nMesh, GradientFillMode ulMode); - - [DllImport("msimg32.dll", EntryPoint = "GradientFill", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool GradientFill([In] IntPtr hdc, - [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct, SizeParamIndex = 2)] TriVertex[] pVertex, - uint nVertex, GradientRect[] pMesh, uint nMesh, GradientFillMode ulMode); - - [DllImport("msimg32.dll", EntryPoint = "GradientFill", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool GradientFill([In] IntPtr hdc, - [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct, SizeParamIndex = 2)] TriVertex[] pVertex, - uint nVertex, GradientTriangle[] pMesh, uint nMesh, GradientFillMode ulMode); - } - - #endregion - } + +namespace TriangleNet.Rendering.GDI.Native +{ + using System; + using System.Runtime.InteropServices; + + /// + /// PInvoke signatures for GradientFill methods. + /// + /// + /// Minimum requirements: Windows 2000 Professional + /// + /// http://msdn.microsoft.com/en-us/library/windows/desktop/dd144957.aspx + /// + internal static class NativeMethods + { + /// + /// The GradientFill function fills rectangle and triangle structures + /// + /// Handle to the destination device context + /// Array of TRIVERTEX structures that each define a triangle vertex + /// The number of vertices in pVertex + /// Array of elements + /// The number of elements in pMesh + /// Specifies gradient fill mode + /// If the function succeeds, the return value is true, false + public static bool GradientFill([In] IntPtr hdc, TriVertex[] pVertex, uint nVertex, uint[] pMesh, uint nMesh, + GradientFillMode ulMode) + { + return Native.GradientFill(hdc, pVertex, nVertex, pMesh, nMesh, ulMode); + } + + /// + /// The GradientFill function fills rectangle and triangle structures + /// + /// Handle to the destination device context + /// Array of TRIVERTEX structures that each define a triangle vertex + /// The number of vertices in pVertex + /// Array of GRADIENT_TRIANGLE structures in triangle mode + /// The number of elements in pMesh + /// Specifies gradient fill mode + /// If the function succeeds, the return value is true, false + public static bool GradientFill([In] IntPtr hdc, TriVertex[] pVertex, uint nVertex, GradientTriangle[] pMesh, + uint nMesh, GradientFillMode ulMode) + { + return Native.GradientFill(hdc, pVertex, nVertex, pMesh, nMesh, ulMode); + } + + /// + /// The GradientFill function fills rectangle and triangle structures + /// + /// Handle to the destination device context + /// Array of TRIVERTEX structures that each define a triangle vertex + /// The number of vertices in pVertex + /// an array of GRADIENT_RECT structures in rectangle mode + /// The number of elements in pMesh + /// Specifies gradient fill mode + /// If the function succeeds, the return value is true, false + public static bool GradientFill([In] IntPtr hdc, TriVertex[] pVertex, uint nVertex, GradientRect[] pMesh, + uint nMesh, GradientFillMode ulMode) + { + return Native.GradientFill(hdc, pVertex, nVertex, pMesh, nMesh, ulMode); + } + + #region Nested type: Native + + internal class Native + { + [DllImport("msimg32.dll", EntryPoint = "GradientFill", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GradientFill([In] IntPtr hdc, + [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct, SizeParamIndex = 2)] TriVertex[] pVertex, + uint nVertex, uint[] pMesh, uint nMesh, GradientFillMode ulMode); + + [DllImport("msimg32.dll", EntryPoint = "GradientFill", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GradientFill([In] IntPtr hdc, + [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct, SizeParamIndex = 2)] TriVertex[] pVertex, + uint nVertex, GradientRect[] pMesh, uint nMesh, GradientFillMode ulMode); + + [DllImport("msimg32.dll", EntryPoint = "GradientFill", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GradientFill([In] IntPtr hdc, + [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct, SizeParamIndex = 2)] TriVertex[] pVertex, + uint nVertex, GradientTriangle[] pMesh, uint nMesh, GradientFillMode ulMode); + } + + #endregion + } } \ No newline at end of file diff --git a/Triangle.NET/Triangle.Rendering/GDI/Native/TriVertex.cs b/src/Triangle.Rendering.GDI/Native/TriVertex.cs similarity index 96% rename from Triangle.NET/Triangle.Rendering/GDI/Native/TriVertex.cs rename to src/Triangle.Rendering.GDI/Native/TriVertex.cs index 0279447..121c637 100644 --- a/Triangle.NET/Triangle.Rendering/GDI/Native/TriVertex.cs +++ b/src/Triangle.Rendering.GDI/Native/TriVertex.cs @@ -1,45 +1,45 @@ - -namespace TriangleNet.Rendering.GDI.Native -{ - using System.Runtime.InteropServices; - - /// - /// The TRIVERTEX structure contains color information and position information. - /// - /// - /// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145142.aspx - /// - [StructLayout(LayoutKind.Sequential)] - internal struct TriVertex - { - /// - /// The x-coordinate, in logical units, of the upper-left corner of the rectangle - /// - public int x; - - /// - /// The y-coordinate, in logical units, of the upper-left corner of the rectangle - /// - public int y; - - /// - /// The color information at the point of x, y - /// - public ushort Red; - - /// - /// The color information at the point of x, y - /// - public ushort Green; - - /// - /// The color information at the point of x, y - /// - public ushort Blue; - - /// - /// The color information at the point of x, y - /// - public ushort Alpha; - } -} + +namespace TriangleNet.Rendering.GDI.Native +{ + using System.Runtime.InteropServices; + + /// + /// The TRIVERTEX structure contains color information and position information. + /// + /// + /// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145142.aspx + /// + [StructLayout(LayoutKind.Sequential)] + internal struct TriVertex + { + /// + /// The x-coordinate, in logical units, of the upper-left corner of the rectangle + /// + public int x; + + /// + /// The y-coordinate, in logical units, of the upper-left corner of the rectangle + /// + public int y; + + /// + /// The color information at the point of x, y + /// + public ushort Red; + + /// + /// The color information at the point of x, y + /// + public ushort Green; + + /// + /// The color information at the point of x, y + /// + public ushort Blue; + + /// + /// The color information at the point of x, y + /// + public ushort Alpha; + } +} diff --git a/Triangle.NET/Triangle.Rendering/GDI/RenderControl.cs b/src/Triangle.Rendering.GDI/RenderControl.cs similarity index 90% rename from Triangle.NET/Triangle.Rendering/GDI/RenderControl.cs rename to src/Triangle.Rendering.GDI/RenderControl.cs index 2dfda4a..c85e680 100644 --- a/Triangle.NET/Triangle.Rendering/GDI/RenderControl.cs +++ b/src/Triangle.Rendering.GDI/RenderControl.cs @@ -1,252 +1,251 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Rendering.GDI -{ - using System; - using System.Drawing; - using System.Drawing.Drawing2D; - using System.Drawing.Text; - using System.Globalization; - using System.Windows.Forms; - - /// - /// Renders a mesh using GDI. - /// - public class RenderControl : Control, IRenderControl - { - // Rendering stuff - private BufferedGraphics buffer; - private BufferedGraphicsContext context; - - //ColorManager renderColors; - - bool initialized = false; - - string coordinate = String.Empty; - - Timer timer; - - /// - /// Initializes a new instance of the class. - /// - public RenderControl() - { - //this.SetStyle(ControlStyles.UserPaint, true); - //this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false); - //this.SetStyle(ControlStyles.Selectable, true); - this.SetStyle(ControlStyles.ResizeRedraw, true); - - //renderColors = ColorManager.Default(); - - this.BackColor = Color.Black; - - context = BufferedGraphicsManager.Current;// new BufferedGraphicsContext(); - - timer = new Timer(); - timer.Interval = 3000; - timer.Tick += (sender, e) => - { - timer.Stop(); - coordinate = String.Empty; - this.Invalidate(); - }; - } - - public IRenderer Renderer { get; set; } - - /// - /// Initialize the graphics buffer (should be called in the forms load event). - /// - public void Initialize() - { - //zoom.Initialize(this.ClientRectangle); - InitializeBuffer(); - - initialized = true; - - this.Invalidate(); - } - - public override void Refresh() - { - this.Render(); - } - - /// - /// Update graphics buffer and zoom after a resize. - /// - public void HandleResize() - { - var zoom = this.Renderer.Context.Zoom; - - zoom.Resize(this.ClientRectangle); - InitializeBuffer(); - } - - private void InitializeBuffer() - { - if (this.Width > 0 && this.Height > 0) - { - if (buffer != null) - { - if (this.ClientRectangle == buffer.Graphics.VisibleClipBounds) - { - this.Invalidate(); - - // Bounds didn't change. Probably we just restored the - // window from minimized state. - return; - } - - buffer.Dispose(); - } - - //buffer = context.Allocate(Graphics.FromHwnd(this.Handle), this.ClientRectangle); - buffer = context.Allocate(this.CreateGraphics(), this.ClientRectangle); - - if (initialized) - { - this.Render(); - } - } - } - - private void Render() - { - coordinate = String.Empty; - - if (buffer == null) - { - return; - } - - var g = buffer.Graphics; - var renderer = this.Renderer as LayerRenderer; - - g.Clear(renderer.Context.ColorManager.Background); - - if (!initialized || renderer == null) - { - return; - } - - g.SmoothingMode = SmoothingMode.AntiAlias; - - renderer.RenderTarget = g; - renderer.Render(); - - this.Invalidate(); - } - - #region Protected overrides - - protected override void OnPaint(PaintEventArgs e) - { - if (!initialized) - { - base.OnPaint(e); - return; - } - - buffer.Render(); - - if (!String.IsNullOrEmpty(coordinate) && Renderer.Context.HasData) - { - Graphics g = e.Graphics; - g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; - g.DrawString(coordinate, this.Font, Brushes.White, 10, 10); - } - } - - protected override void OnPaintBackground(PaintEventArgs pevent) - { - // Do nothing - if (!initialized) - { - base.OnPaintBackground(pevent); - } - } - - protected override void OnMouseWheel(MouseEventArgs e) - { - if (!initialized) return; - - var zoom = this.Renderer.Context.Zoom; - - if (zoom.Zoom(e.Delta, (float)e.X / Width, (float)e.Y / Height)) - { - // Redraw - this.Render(); - } - } - - protected override void OnMouseClick(MouseEventArgs e) - { - // We need to manually set the focus to get proper handling of - // the KeyUp and MouseWheel events. - this.Focus(); - - if (!initialized) return; - - var zoom = this.Renderer.Context.Zoom; - - if (e.Button == MouseButtons.Middle) - { - zoom.Reset(); - this.Render(); - } - else if (e.Button == MouseButtons.Left) - { - timer.Stop(); - - PointF c = new PointF((float)e.X / Width, (float)e.Y / Height); - zoom.ScreenToWorld(ref c); - coordinate = String.Format(NumberFormatInfo.InvariantInfo, - "X:{0} Y:{1}", c.X, c.Y); - - this.Invalidate(); - - timer.Start(); - } - } - - protected override void OnKeyUp(KeyEventArgs e) - { - if (!initialized) return; - - var zoom = this.Renderer.Context.Zoom; - - bool redraw = false; - - if (e.KeyCode == Keys.Up) - { - redraw = zoom.Translate(0, 1); - } - else if (e.KeyCode == Keys.Down) - { - redraw = zoom.Translate(0, -1); - } - else if (e.KeyCode == Keys.Left) - { - redraw = zoom.Translate(-1, 0); - } - else if (e.KeyCode == Keys.Right) - { - redraw = zoom.Translate(1, 0); - } - - if (redraw) - { - this.Render(); - } - - e.Handled = true; - } - - #endregion - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Rendering.GDI +{ + using System.Drawing; + using System.Drawing.Drawing2D; + using System.Drawing.Text; + using System.Globalization; + using System.Windows.Forms; + + /// + /// Renders a mesh using GDI. + /// + public class RenderControl : Control, IRenderControl + { + // Rendering stuff + private BufferedGraphics buffer; + private BufferedGraphicsContext context; + + //ColorManager renderColors; + + bool initialized = false; + + string coordinate = string.Empty; + + Timer timer; + + /// + /// Initializes a new instance of the class. + /// + public RenderControl() + { + //this.SetStyle(ControlStyles.UserPaint, true); + //this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false); + //this.SetStyle(ControlStyles.Selectable, true); + this.SetStyle(ControlStyles.ResizeRedraw, true); + + //renderColors = ColorManager.Default(); + + this.BackColor = Color.Black; + + context = BufferedGraphicsManager.Current;// new BufferedGraphicsContext(); + + timer = new Timer(); + timer.Interval = 3000; + timer.Tick += (sender, e) => + { + timer.Stop(); + coordinate = string.Empty; + this.Invalidate(); + }; + } + + public IRenderer Renderer { get; set; } + + /// + /// Initialize the graphics buffer (should be called in the forms load event). + /// + public void Initialize() + { + //zoom.Initialize(this.ClientRectangle); + InitializeBuffer(); + + initialized = true; + + this.Invalidate(); + } + + public override void Refresh() + { + this.Render(); + } + + /// + /// Update graphics buffer and zoom after a resize. + /// + public void HandleResize() + { + var zoom = this.Renderer.Context.Zoom; + + zoom.Resize(this.ClientRectangle); + InitializeBuffer(); + } + + private void InitializeBuffer() + { + if (this.Width > 0 && this.Height > 0) + { + if (buffer != null) + { + if (this.ClientRectangle == buffer.Graphics.VisibleClipBounds) + { + this.Invalidate(); + + // Bounds didn't change. Probably we just restored the + // window from minimized state. + return; + } + + buffer.Dispose(); + } + + //buffer = context.Allocate(Graphics.FromHwnd(this.Handle), this.ClientRectangle); + buffer = context.Allocate(this.CreateGraphics(), this.ClientRectangle); + + if (initialized) + { + this.Render(); + } + } + } + + private void Render() + { + coordinate = string.Empty; + + if (buffer == null) + { + return; + } + + var g = buffer.Graphics; + var renderer = this.Renderer as LayerRenderer; + + g.Clear(renderer.Context.ColorManager.Background); + + if (!initialized || renderer == null) + { + return; + } + + g.SmoothingMode = SmoothingMode.AntiAlias; + + renderer.RenderTarget = g; + renderer.Render(); + + this.Invalidate(); + } + + #region Protected overrides + + protected override void OnPaint(PaintEventArgs e) + { + if (!initialized) + { + base.OnPaint(e); + return; + } + + buffer.Render(); + + if (!string.IsNullOrEmpty(coordinate) && Renderer.Context.HasData) + { + Graphics g = e.Graphics; + g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; + TextRenderer.DrawText(g, coordinate, Font, new Point(10, 10), Color.White); + } + } + + protected override void OnPaintBackground(PaintEventArgs pevent) + { + // Do nothing + if (!initialized) + { + base.OnPaintBackground(pevent); + } + } + + protected override void OnMouseWheel(MouseEventArgs e) + { + if (!initialized) return; + + var zoom = this.Renderer.Context.Zoom; + + if (zoom.Zoom(e.Delta, (float)e.X / Width, (float)e.Y / Height)) + { + // Redraw + this.Render(); + } + } + + protected override void OnMouseClick(MouseEventArgs e) + { + // We need to manually set the focus to get proper handling of + // the KeyUp and MouseWheel events. + this.Focus(); + + if (!initialized) return; + + var zoom = this.Renderer.Context.Zoom; + + if (e.Button == MouseButtons.Middle) + { + zoom.Reset(); + this.Render(); + } + else if (e.Button == MouseButtons.Left) + { + timer.Stop(); + + PointF c = new PointF((float)e.X / Width, (float)e.Y / Height); + zoom.ScreenToWorld(c, out double x, out double y); + coordinate = string.Format(NumberFormatInfo.InvariantInfo, + "X:{0} Y:{1}", x, y); + + this.Invalidate(); + + timer.Start(); + } + } + + protected override void OnKeyUp(KeyEventArgs e) + { + if (!initialized) return; + + var zoom = this.Renderer.Context.Zoom; + + bool redraw = false; + + if (e.KeyCode == Keys.Up) + { + redraw = zoom.Translate(0, 1); + } + else if (e.KeyCode == Keys.Down) + { + redraw = zoom.Translate(0, -1); + } + else if (e.KeyCode == Keys.Left) + { + redraw = zoom.Translate(-1, 0); + } + else if (e.KeyCode == Keys.Right) + { + redraw = zoom.Translate(1, 0); + } + + if (redraw) + { + this.Render(); + } + + e.Handled = true; + } + + #endregion + } +} diff --git a/src/Triangle.Rendering.GDI/Triangle.Rendering.GDI.csproj b/src/Triangle.Rendering.GDI/Triangle.Rendering.GDI.csproj new file mode 100644 index 0000000..d1db388 --- /dev/null +++ b/src/Triangle.Rendering.GDI/Triangle.Rendering.GDI.csproj @@ -0,0 +1,17 @@ + + + + Library + net6.0-windows + true + TriangleNet.Rendering.GDI + Triangle.Rendering.GDI + AnyCPU;x64 + + + + + + + + diff --git a/src/Triangle.Rendering/Buffer/BufferBase.cs b/src/Triangle.Rendering/Buffer/BufferBase.cs new file mode 100644 index 0000000..fe14212 --- /dev/null +++ b/src/Triangle.Rendering/Buffer/BufferBase.cs @@ -0,0 +1,32 @@ + +namespace TriangleNet.Rendering.Buffer +{ + public abstract class BufferBase : IBuffer where T : struct + { + protected T[] data; + protected int size; + + public BufferBase(int capacity, int size) + : this(new T[capacity], size) + { + } + + public BufferBase(T[] data, int size) + { + this.data = data; + this.size = size; + } + + /// + public T[] Data => data; + + /// + public int Count => data == null ? 0 : data.Length; + + /// + public abstract int Size { get; } + + /// + public abstract BufferTarget Target { get; } + } +} diff --git a/src/Triangle.Rendering/Buffer/ColorBuffer.cs b/src/Triangle.Rendering/Buffer/ColorBuffer.cs new file mode 100644 index 0000000..e074f1e --- /dev/null +++ b/src/Triangle.Rendering/Buffer/ColorBuffer.cs @@ -0,0 +1,34 @@ + +namespace TriangleNet.Rendering.Buffer +{ + using System.Drawing; + + public class ColorBuffer : BufferBase + { + /// + /// Initializes a new instance of the class. + /// + /// The buffer capacity. + /// The size of one element in the buffer (i.e. 2 for 2D points) + public ColorBuffer(int capacity, int size) + : base(capacity, size) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The buffer data. + /// The size of one element in the buffer (i.e. 2 for 2D points) + public ColorBuffer(Color[] data, int size) + : base(data, size) + { + } + + /// + public override int Size => 1; + + /// + public override BufferTarget Target => BufferTarget.ColorBuffer; + } +} diff --git a/Triangle.NET/Triangle.Rendering/Buffer/IBuffer.cs b/src/Triangle.Rendering/Buffer/IBuffer.cs similarity index 95% rename from Triangle.NET/Triangle.Rendering/Buffer/IBuffer.cs rename to src/Triangle.Rendering/Buffer/IBuffer.cs index a39ab3e..06caeb3 100644 --- a/Triangle.NET/Triangle.Rendering/Buffer/IBuffer.cs +++ b/src/Triangle.Rendering/Buffer/IBuffer.cs @@ -1,34 +1,34 @@ - -namespace TriangleNet.Rendering.Buffer -{ - public enum BufferTarget : byte - { - ColorBuffer, - IndexBuffer, - VertexBuffer - } - - public interface IBuffer where T : struct - { - /// - /// Gets the contents of the buffer. - /// - T[] Data { get; } - - /// - /// Gets the size of the buffer. - /// - int Count { get; } - - /// - /// Gets the size of one element in the buffer (i.e. 2 for 2D points - /// or 3 for triangles). - /// - int Size { get; } - - /// - /// Gets the buffer target (vertices or indices). - /// - BufferTarget Target { get; } - } -} + +namespace TriangleNet.Rendering.Buffer +{ + public enum BufferTarget : byte + { + ColorBuffer, + IndexBuffer, + VertexBuffer + } + + public interface IBuffer where T : struct + { + /// + /// Gets the contents of the buffer. + /// + T[] Data { get; } + + /// + /// Gets the size of the buffer. + /// + int Count { get; } + + /// + /// Gets the size of one element in the buffer (i.e. 2 for 2D points + /// or 3 for triangles). + /// + int Size { get; } + + /// + /// Gets the buffer target (vertices or indices). + /// + BufferTarget Target { get; } + } +} diff --git a/src/Triangle.Rendering/Buffer/IndexBuffer.cs b/src/Triangle.Rendering/Buffer/IndexBuffer.cs new file mode 100644 index 0000000..a9a2152 --- /dev/null +++ b/src/Triangle.Rendering/Buffer/IndexBuffer.cs @@ -0,0 +1,80 @@ + +using System.Collections.Generic; +using System.Linq; +using TriangleNet.Geometry; +using TriangleNet.Topology; + +namespace TriangleNet.Rendering.Buffer +{ + public class IndexBuffer : BufferBase + { + #region Static methods + + public static IBuffer Create(IEnumerable edges, int size) + { + var buffer = new IndexBuffer(size * edges.Count(), size); + + var data = buffer.Data; + + int i = 0; + + foreach (var e in edges) + { + data[size * i + 0] = e.P0; + data[size * i + 1] = e.P1; + + i++; + } + + return buffer; + } + + public static IBuffer Create(ICollection elements, int size) + { + var buffer = new IndexBuffer(size * elements.Count, size); + + var data = buffer.Data; + + int i = 0; + + foreach (var e in elements) + { + data[size * i + 0] = e.GetVertexID(0); + data[size * i + 1] = e.GetVertexID(1); + data[size * i + 2] = e.GetVertexID(2); + + i++; + } + + return buffer; + } + + #endregion + + /// + /// Initializes a new instance of the class. + /// + /// The buffer capacity. + /// The size of one element in the buffer (i.e. 2 for 2D points) + public IndexBuffer(int capacity, int size) + : base(capacity, size) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The buffer data. + /// The size of one element in the buffer (i.e. 2 for 2D points) + public IndexBuffer(int[] data, int size) + : base(data, size) + { + } + + /// + public override int Size => size; + + /// + public override BufferTarget Target => BufferTarget.IndexBuffer; + } +} diff --git a/src/Triangle.Rendering/Buffer/VertexBuffer.cs b/src/Triangle.Rendering/Buffer/VertexBuffer.cs new file mode 100644 index 0000000..b8b8815 --- /dev/null +++ b/src/Triangle.Rendering/Buffer/VertexBuffer.cs @@ -0,0 +1,130 @@ + +using System; +using System.Collections.Generic; +using TriangleNet.Geometry; + +namespace TriangleNet.Rendering.Buffer +{ + public class VertexBuffer : BufferBase + { + #region Static methods + + /// + /// Create a vertex buffer from given point collection. + /// + /// The points to render. + /// Returns the vertex buffer. + public static IBuffer Create(ICollection points) + { + return Create(points, new Rectangle(0d, 0d, 1d, 1d)); + } + + /// + /// Create a normalized vertex buffer from given point collection. + /// + /// The points to render. + /// The bounding box used for normalization. + /// Returns a buffer of normalized coordinates. + public static IBuffer Create(ICollection points, Rectangle bounds) + { + var buffer = new VertexBuffer(2 * points.Count); + + var data = buffer.Data; + + double dx = bounds.X; + double dy = bounds.Y; + + double scale = 1.0 / Math.Max(bounds.Width, bounds.Height); + + int i = 0; + + double x, y; + + foreach (var p in points) + { + x = (p.X - dx) * scale; + y = (p.Y - dy) * scale; + + data[2 * i] = (float)x; + data[2 * i + 1] = (float)y; + + i++; + } + + return buffer; + } + + /// + /// Create a vertex buffer from given point collection. + /// + /// The points to render. + /// Returns the vertex buffer. + public static IBuffer Create(ICollection points) + { + return Create(points, new Rectangle(0d, 0d, 1d, 1d)); + } + + /// + /// Create a normalized vertex buffer from given vertex collection. + /// + /// The vertices to render. + /// The bounding box used for normalization. + /// Returns a buffer of normalized coordinates. + public static IBuffer Create(ICollection points, Rectangle bounds) + { + var buffer = new VertexBuffer(2 * points.Count); + + var data = buffer.Data; + + double dx = bounds.X; + double dy = bounds.Y; + + double scale = 1.0 / Math.Max(bounds.Width, bounds.Height); + + int i = 0; + + double x, y; + + foreach (var p in points) + { + x = (p.X - dx) * scale; + y = (p.Y - dy) * scale; + + data[2 * i] = (float)x; + data[2 * i + 1] = (float)y; + + i++; + } + + return buffer; + } + + #endregion + + /// + /// Initializes a new instance of the class. + /// + /// The buffer capacity. + /// The size of one element in the buffer (i.e. 2 for 2D points) + public VertexBuffer(int capacity, int size = 2) + : base(capacity, size) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The buffer data. + /// The size of one element in the buffer (i.e. 2 for 2D points) + public VertexBuffer(float[] data, int size = 2) + : base(data, size) + { + } + + /// + public override int Size => size; + + /// + public override BufferTarget Target => BufferTarget.VertexBuffer; + } +} diff --git a/src/Triangle.Rendering/ColorManager.cs b/src/Triangle.Rendering/ColorManager.cs new file mode 100644 index 0000000..bee0b2c --- /dev/null +++ b/src/Triangle.Rendering/ColorManager.cs @@ -0,0 +1,110 @@ + +namespace TriangleNet.Rendering +{ + using System.Collections.Generic; + using System.Drawing; + using TriangleNet.Rendering.Util; + + public class ColorManager + { + #region Public properties + + /// + /// Gets or sets the background color. + /// + public Color Background { get; set; } + + /// + /// Gets or sets the brush used for points. + /// + public Color Point { get; set; } + + /// + /// Gets or sets the brush used for steiner points. + /// + public Color SteinerPoint { get; set; } + + /// + /// Gets or sets the pen used for mesh edges. + /// + public Color Line { get; set; } + + /// + /// Gets or sets the pen used for mesh segments. + /// + public Color Segment { get; set; } + + /// + /// Gets or sets the pen used for Voronoi edges. + /// + public Color VoronoiLine { get; set; } + + #endregion + + /// + /// Gets or sets a dictionary which maps region ids (or partition indices) to a color. + /// + public Dictionary ColorDictionary { get; set; } + + /// + /// Gets or sets a color map used for function plotting. + /// + public ColorMap ColorMap { get; set; } + + /// + /// Creates an instance of the class with default (dark) color scheme. + /// + public static ColorManager Default() + { + var colors = new ColorManager(); + + colors.Background = Color.FromArgb(0, 0, 0); + colors.Point = Color.Green; + colors.SteinerPoint = Color.Peru; + colors.Line = Color.FromArgb(30, 30, 30); + colors.Segment = Color.DarkBlue; + colors.VoronoiLine = Color.FromArgb(40, 50, 60); + + return colors; + } + + public void CreateColorDictionary(int length) + { + var keys = new int[length]; + + for (int i = 0; i < length; i++) + { + keys[i] = i; + } + + CreateColorDictionary(keys); + } + + public void CreateColorDictionary(IEnumerable keys) + { + this.ColorDictionary = new Dictionary(); + + int i = 0, n = regionColors.Length; + + foreach (var key in keys) + { + this.ColorDictionary.Add(key, regionColors[i]); + + i = (i + 1) % n; + } + } + + // Change or add as many colors as you like... + private static Color[] regionColors = { + Color.Transparent, + Color.FromArgb(200, 0, 255, 0), + Color.FromArgb(200, 255, 0, 0), + Color.FromArgb(200, 0, 0, 255), + Color.FromArgb(200, 0, 255, 255), + Color.FromArgb(200, 255, 255, 0), + Color.FromArgb(200, 255, 0, 255), + Color.FromArgb(200, 127, 0, 255), + Color.FromArgb(200, 0, 127, 255) + }; + } +} diff --git a/src/Triangle.Rendering/IRenderContext.cs b/src/Triangle.Rendering/IRenderContext.cs new file mode 100644 index 0000000..c8c8b82 --- /dev/null +++ b/src/Triangle.Rendering/IRenderContext.cs @@ -0,0 +1,89 @@ + +namespace TriangleNet.Rendering +{ + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + + public interface IRenderContext + { + /// + /// Gets the color manager. + /// + ColorManager ColorManager { get; } + + /// + /// Gets the list of s. + /// + IList RenderLayers { get; } + + /// + /// Gets the . + /// + Projection Zoom { get; } + + /// + /// Gets the . + /// + IMesh Mesh { get; } + + /// + /// Gets a value indicating whether the context has data to render. + /// + bool HasData { get; } + + /// + /// Add polygon data. + /// + /// + void Add(IPolygon data); + + /// + /// Add mesh data. + /// + /// + /// + void Add(IMesh data, bool reset); + + /// + /// Add edge data (used for Voronoi). + /// + /// + /// + /// + void Add(ICollection points, IEnumerable edges, bool reset); + + /// + /// Add mesh function values z=f(x,y). + /// + /// + void Add(float[] values); + + /// + /// Add mesh partitioning data. + /// + /// + void Add(int[] partition); + + /// + /// Enable or disable a layer for rendering. + /// + /// The layer index. + /// If true, enable layer, otherwise disable. + /// + /// 0 = mesh (filled) + /// 1 = mesh (wireframe) + /// 2 = polygon + /// 3 = points + /// 4 = voronoi overlay + /// 5 = vector field + /// 6 = contour lines + /// + void Enable(int layer, bool enabled); + + /// + /// Clear data from all layers. + /// + void Clear(); + } +} diff --git a/Triangle.NET/Triangle.Rendering/IRenderControl.cs b/src/Triangle.Rendering/IRenderControl.cs similarity index 88% rename from Triangle.NET/Triangle.Rendering/IRenderControl.cs rename to src/Triangle.Rendering/IRenderControl.cs index da85e8e..477ee70 100644 --- a/Triangle.NET/Triangle.Rendering/IRenderControl.cs +++ b/src/Triangle.Rendering/IRenderControl.cs @@ -1,26 +1,26 @@ -// ----------------------------------------------------------------------- -// -// TODO: Update copyright text. -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Rendering -{ - using System; - using System.Windows.Forms; - using System.Drawing; - - /// - /// TODO: Update summary. - /// - public interface IRenderControl - { - IRenderer Renderer { get; set; } - Rectangle ClientRectangle { get; } - - void Initialize(); - void Refresh(); - - void HandleResize(); - } -} +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Rendering +{ + using System.Drawing; + + /// + /// TODO: Update summary. + /// + public interface IRenderControl + { + IRenderer Renderer { get; set; } + + Rectangle ClientRectangle { get; } + + void Initialize(); + + void Refresh(); + + void HandleResize(); + } +} diff --git a/src/Triangle.Rendering/IRenderLayer.cs b/src/Triangle.Rendering/IRenderLayer.cs new file mode 100644 index 0000000..b83e457 --- /dev/null +++ b/src/Triangle.Rendering/IRenderLayer.cs @@ -0,0 +1,83 @@ + +namespace TriangleNet.Rendering +{ + using TriangleNet.Rendering.Buffer; + using TriangleNet.Rendering.Util; + + using Color = System.Drawing.Color; + + /// + /// Interface for managing the data of a render layer. + /// + public interface IRenderLayer + { + /// + /// Gets the number of points in the point buffer. + /// + int Count { get; } + + /// + /// Gets the points buffer. + /// + IBuffer Points { get; } + + /// + /// Gets the indices buffer. + /// + IBuffer Indices { get; } + + /// + /// Gets or sets a value indicating whether the layer is enabled. + /// + bool IsEnabled { get; set; } + + /// + /// Indicates whether this layer contains data to render. + /// + /// Returns true, if the points buffer contains data. + bool IsEmpty(); + + /// + /// Resets this layer to an empty state. + /// + /// If true, all buffers will be set to null. + void Reset(bool clear); + + void SetPoints(IBuffer buffer, bool reset = true); + + void SetIndices(IBuffer buffer); + + #region Attached data (mesh partitioning and heat map rendering) + + // TODO: better put attached data into a subclass? + + /// + /// Gets the mesh partition. + /// + /// + /// Triangle i given by indices [3 * i, 3 * i + 1, 3 * i + 2] + /// belongs to Partition[i]. + /// + IBuffer Partition { get; } + + /// + /// Gets the color attached to a point in the points buffer. + /// + IBuffer Colors { get; } + + /// + /// Attach function values z=f(x,y) for all points (x,y) in the point buffer. + /// + /// The function values. + /// The color map. + void AttachLayerData(float[] values, ColorMap colormap); + + /// + /// Attach partitioning data to each triangle in the index buffer. + /// + /// The mesh partition. + void AttachLayerData(int[] partition); + + #endregion + } +} diff --git a/Triangle.NET/Triangle.Rendering/IRenderer.cs b/src/Triangle.Rendering/IRenderer.cs similarity index 93% rename from Triangle.NET/Triangle.Rendering/IRenderer.cs rename to src/Triangle.Rendering/IRenderer.cs index a77c7c5..6c78a2a 100644 --- a/Triangle.NET/Triangle.Rendering/IRenderer.cs +++ b/src/Triangle.Rendering/IRenderer.cs @@ -1,10 +1,10 @@ - -namespace TriangleNet.Rendering -{ - public interface IRenderer - { - IRenderContext Context { get; set; } - - void Render(); - } -} + +namespace TriangleNet.Rendering +{ + public interface IRenderer + { + IRenderContext Context { get; set; } + + void Render(); + } +} diff --git a/src/Triangle.Rendering/Projection.cs b/src/Triangle.Rendering/Projection.cs new file mode 100644 index 0000000..6741b14 --- /dev/null +++ b/src/Triangle.Rendering/Projection.cs @@ -0,0 +1,282 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Rendering +{ + using System; + using System.Drawing; + + using TRectangle = Geometry.Rectangle; + + /// + /// Manages a world to screen transformation (2D orthographic projection). + /// + /// + /// + /// The projection implementation is actually not world-to-screen, but NDC-to-screen + /// (Normalized-Device-Coordinates). NDC here is - in contrast for example to OpenGL, the + /// transformation of world coordinates to a unit rectangle with origin (0,0) and a max + /// side length 1 (the width/height ratio is preserved). It's a simple translate-scale + /// transform, which is automatically applied in VertexBuffer.Create(points, bounds). + /// + /// + /// Since the upper-left corner of the display is usually the screen coordinate origin + /// (0,0), the projection will automatically invert the y-axis. + /// + /// + public class Projection + { + // The original mesh bounds (needed for screen-to-world projection). + TRectangle world_; + + // Precomputed scaling factor for normalized coordinates. + double scale_; + + // The screen dimensions. + Rectangle screen; + + // The original mesh and the viewport in normalized coordinates. + RectangleF world, viewport; + + /// + /// Gets or sets the current viewport (normalized coordinates). + /// + public RectangleF Viewport => viewport; + + /// + /// Gets the zoom level. + /// + public int Level { get; private set; } + + private const int MAX_ZOOM = 100; + + /// + /// Initializes a new instance of the class. + /// + /// The current screen (viewport) dimensions. + public Projection(Rectangle screen) + { + this.screen = screen; + + world = viewport = new RectangleF(screen.X, screen.Y, screen.Width, screen.Height); + + world_ = new TRectangle(); + scale_ = 0; + + Level = 1; + } + + /// + /// Initialize the projection. + /// + /// The world that should be transformed to screen coordinates. + public void Initialize(TRectangle world) + { + Level = 1; + + // Bounding box of original (non-normalized) coordinates. + world_ = world; + + // Scaling factor for normalized coordinates. + scale_ = Math.Max(world.Width, world.Height); + + // Dimensions in normalized coordinates. + float ww = (float)(world.Width / scale_); + float wh = (float)(world.Height / scale_); + + // Add a margin so there's some space around the screen borders. + float margin = (ww < wh) ? wh * 0.05f : ww * 0.05f; + + int sw = screen.Width; + int sh = screen.Height; + + float wRatio = ww / wh; + float sRatio = sw / (float)sh; + + float scale = (sRatio < wRatio) ? (ww + margin) / sw : (wh + margin) / sh; + + // Center in normalized coordinates (left = bottom = 0) + float centerX = ww / 2; + float centerY = wh / 2; + + // Get the initial viewport (complete mesh centered on the screen) + this.world = viewport = new RectangleF( + centerX - sw * scale / 2, + centerY - sh * scale / 2, + sw * scale, + sh * scale); + } + + /// + /// Handle resize of the screen (viewport). + /// + /// The new screen (viewport) dimensions. + public void Resize(Rectangle newScreen) + { + // The viewport has to be updated, but we want to keep + // the scaling and the center. + + // Get the screen scaling. + float scaleX = newScreen.Width / (float)screen.Width; + float scaleY = newScreen.Height / (float)screen.Height; + + screen = newScreen; + + var view = viewport; + + // Center of the viewport + float centerX = (view.Left + view.Right) / 2; + float centerY = (view.Bottom + view.Top) / 2; + + // The new viewport dimensions. + float width = view.Width * scaleX; + float height = view.Height * scaleY; + + viewport = new RectangleF( + centerX - width / 2, + centerY - height / 2, + width, height); + + // Do the same for the world: + centerX = (world.Left + world.Right) / 2; + centerY = (world.Bottom + world.Top) / 2; + + width = world.Width * scaleX; + height = world.Height * scaleY; + + world = new RectangleF( + centerX - width / 2, + centerY - height / 2, + width, height); + } + + public bool Translate(int dx, int dy) + { + if (Level == 1) + { + return false; + } + + var view = viewport; + + float x = view.X + dx * view.Width / 4; + float y = view.Y + dy * view.Height / 4; + + viewport = new RectangleF(x, y, view.Width, view.Height); + + return true; + } + + /// + /// Zoom in or out of the viewport. + /// + /// Zoom amount. + /// Relative x point position (in [0..1] range). + /// Relative y point position (in [0..1] range). + public bool Zoom(int amount, float focusX, float focusY) + { + float width, height; + + // Invert y coordinate. + focusY = 1 - focusY; + + if (amount > 0) // Zoom in + { + Level++; + + if (Level > MAX_ZOOM) + { + Level = MAX_ZOOM; + return false; + } + + width = viewport.Width / 1.1f; + height = viewport.Height / 1.1f; + } + else + { + Level--; + + if (Level < 1) + { + Reset(); + return false; + } + + width = viewport.Width * 1.1f; + height = viewport.Height * 1.1f; + } + + // Current focus on viewport + float x = viewport.X + viewport.Width * focusX; + float y = viewport.Y + viewport.Height * focusY; + + // New left and top positions + x = x - width * focusX; + y = y - height * focusY; + + // Check if outside of world + if (x < world.X) + { + x = world.X; + } + else if (x + width > world.Right) + { + x = world.Right - width; + } + + if (y < world.Y) + { + y = world.Y; + } + else if (y + height > world.Bottom) + { + y = world.Bottom - height; + } + + // Set new viewport + viewport = new RectangleF(x, y, width, height); + + return true; + } + + /// + /// Reset the zoom to initial state. + /// + public void Reset() + { + viewport = world; + Level = 1; + } + + /// + /// Project a normalized device coordinate to screen coordinates. + /// + /// Input normalized device coordinate, output screen coordinate. + public void NdcToScreen(ref PointF pt) + { + pt.X = (pt.X - viewport.X) / viewport.Width * screen.Width; + pt.Y = (1 - (pt.Y - viewport.Y) / viewport.Height) * screen.Height; + } + + /// + /// Project a screen coordinate to world coordinates. + /// + /// Normalized position on screen (both coordinates in [0..1] range). + /// The world x-coordinate. + /// The world y-coordinate. + public void ScreenToWorld(PointF pt, out double x, out double y) + { + // Position in normalized coordinates. + var nx = viewport.X + viewport.Width * pt.X; + var ny = viewport.Y + viewport.Height * (1 - pt.Y); + + // Translate and scale to world coordinates. + x = world_.X + nx * scale_; + y = world_.Y + ny * scale_; + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/RenderContext.cs b/src/Triangle.Rendering/RenderContext.cs similarity index 63% rename from Triangle.NET/Triangle.Rendering/RenderContext.cs rename to src/Triangle.Rendering/RenderContext.cs index 66e3ede..703af06 100644 --- a/Triangle.NET/Triangle.Rendering/RenderContext.cs +++ b/src/Triangle.Rendering/RenderContext.cs @@ -1,162 +1,160 @@ - -namespace TriangleNet.Rendering -{ - using System.Collections.Generic; - using System.Linq; - using TriangleNet.Geometry; - using TriangleNet.Meshing; - using TriangleNet.Voronoi.Legacy; - - /// - /// The RenderContext class brings all the rendering parts together. - /// - public class RenderContext : IRenderContext - { - private ColorManager colorManager; - private BoundingBox bounds; - private Projection zoom; - private IMesh mesh; - - private List renderLayers; - - public RenderContext(Projection zoom, ColorManager colorManager) - { - bounds = new BoundingBox(); - - renderLayers = new List(6); - - renderLayers.Add(new RenderLayer()); // 0 = mesh (filled) - renderLayers.Add(new RenderLayer()); // 1 = mesh (wireframe) - renderLayers.Add(new RenderLayer()); // 2 = polygon - renderLayers.Add(new RenderLayer()); // 3 = points - renderLayers.Add(new RenderLayer()); // 4 = voronoi overlay - renderLayers.Add(new RenderLayer()); // 5 = vector field - renderLayers.Add(new RenderLayer()); // 6 = contour lines - - RenderLayers[1].IsEnabled = true; - RenderLayers[2].IsEnabled = true; - RenderLayers[3].IsEnabled = true; - - this.zoom = zoom; - this.colorManager = colorManager; - } - - public ColorManager ColorManager - { - get { return colorManager; } - } - - public BoundingBox Bounds - { - get { return bounds; } - } - - public IList RenderLayers - { - get { return renderLayers; } - } - - public Projection Zoom - { - get { return zoom; } - } - - public IMesh Mesh - { - get { return mesh; } - } - - public bool HasData - { - get - { - return renderLayers.Any(layer => !layer.IsEmpty()); - } - } - - public void Add(IPolygon data) - { - foreach (var layer in RenderLayers) - { - layer.Reset(true); - } - - // Always clear voronoi layer. - RenderLayers[4].Reset(true); - - int i = 0; - - // Ensure linear numbering of polygon vertices. - foreach (var p in data.Points) - { - p.ID = i++; - } - - this.bounds = RenderLayers[2].SetPoints(data); - this.zoom.Initialize(bounds); - - RenderLayers[2].SetPolygon(data); - RenderLayers[3].SetPoints(RenderLayers[2].Points); - } - - public void Add(IMesh data, bool reset) - { - foreach (var layer in RenderLayers) - { - layer.Reset(reset); - } - - // Always clear voronoi layer. - RenderLayers[4].Reset(true); - - // Save reference to mesh. - this.mesh = data; - - this.bounds = RenderLayers[1].SetPoints(data); - this.zoom.Initialize(bounds); - - RenderLayers[1].SetMesh(data, false); - - RenderLayers[2].SetPoints(RenderLayers[1].Points); - RenderLayers[2].SetPolygon(data); - - RenderLayers[3].SetPoints(RenderLayers[1].Points); - } - - public void Add(ICollection points, IEnumerable edges, bool reset) - { - RenderLayers[4].SetPoints(points); - RenderLayers[4].SetMesh(edges); - RenderLayers[4].IsEnabled = true; - } - - public void Add(float[] data) - { - // Add function values for filled mesh. - RenderLayers[0].SetPoints(RenderLayers[1].Points); - RenderLayers[0].SetMesh(this.mesh, true); - RenderLayers[0].AttachLayerData(data, colorManager.ColorMap); - - RenderLayers[0].IsEnabled = true; - } - - public void Add(int[] data) - { - // Add partition data for filled mesh. - RenderLayers[0].SetPoints(RenderLayers[1].Points); - RenderLayers[0].SetMesh(this.mesh, true); - RenderLayers[0].AttachLayerData(data); - - RenderLayers[0].IsEnabled = true; - } - - public void Enable(int layer, bool enabled) - { - renderLayers[layer].IsEnabled = enabled; - } - - public void Clear() - { - } - } -} + +namespace TriangleNet.Rendering +{ + using System.Collections.Generic; + using System.Linq; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + using TriangleNet.Rendering.Buffer; + + /// + /// The RenderContext class brings all the rendering parts together. + /// + public class RenderContext : IRenderContext + { + private ColorManager colorManager; + private Projection zoom; + private Rectangle bounds; + private IMesh mesh; + + private List renderLayers; + + public RenderContext(Projection zoom, ColorManager colorManager) + { + renderLayers = new List(6); + + renderLayers.Add(new RenderLayer()); // 0 = mesh (filled) + renderLayers.Add(new RenderLayer()); // 1 = mesh (wireframe) + renderLayers.Add(new RenderLayer()); // 2 = polygon + renderLayers.Add(new RenderLayer()); // 3 = points + renderLayers.Add(new RenderLayer()); // 4 = voronoi overlay + renderLayers.Add(new RenderLayer()); // 5 = vector field + renderLayers.Add(new RenderLayer()); // 6 = contour lines + + RenderLayers[1].IsEnabled = true; + RenderLayers[2].IsEnabled = true; + RenderLayers[3].IsEnabled = true; + + this.zoom = zoom; + this.colorManager = colorManager; + } + + /// + public ColorManager ColorManager => colorManager; + + /// + public IList RenderLayers => renderLayers; + + /// + public Projection Zoom => zoom; + + /// + public IMesh Mesh => mesh; + + /// + public bool HasData => renderLayers.Any(layer => !layer.IsEmpty()); + + /// + public void Add(IPolygon data) + { + foreach (var layer in RenderLayers) + { + layer.Reset(true); + } + + // Always clear Voronoi layer. + RenderLayers[4].Reset(true); + + int i = 0; + + // Ensure linear numbering of polygon vertices. + foreach (var p in data.Points) + { + p.ID = i++; + } + + bounds = data.Bounds(); + + zoom.Initialize(bounds); + + RenderLayers[2].SetPoints(VertexBuffer.Create(data.Points, bounds)); + RenderLayers[2].SetIndices(IndexBuffer.Create(data.Segments, 2)); + + RenderLayers[3].SetPoints(RenderLayers[2].Points); + } + + /// + public void Add(IMesh data, bool reset) + { + foreach (var layer in RenderLayers) + { + layer.Reset(reset); + } + + // Always clear voronoi layer. + RenderLayers[4].Reset(true); + + // Save reference to mesh. + mesh = data; + bounds = data.Bounds; + + // Ensure linear numbering of vertices. + mesh.Renumber(); + + zoom.Initialize(bounds); + + RenderLayers[1].SetPoints(VertexBuffer.Create(data.Vertices, bounds)); + RenderLayers[1].SetIndices(IndexBuffer.Create(data.Edges, 2)); + + RenderLayers[2].SetPoints(RenderLayers[1].Points); + RenderLayers[2].SetIndices(IndexBuffer.Create(data.Segments, 2)); + + RenderLayers[3].SetPoints(RenderLayers[1].Points, false); + } + + /// + public void Add(ICollection points, IEnumerable edges, bool reset) + { + RenderLayers[4].SetPoints(VertexBuffer.Create(points, bounds)); + RenderLayers[4].SetIndices(IndexBuffer.Create(edges, 2)); + RenderLayers[4].IsEnabled = true; + } + + /// + public void Add(float[] data) + { + // Add function values for filled mesh. + RenderLayers[0].SetPoints(RenderLayers[1].Points); + RenderLayers[0].SetIndices(IndexBuffer.Create(mesh.Triangles, 3)); + RenderLayers[0].AttachLayerData(data, colorManager.ColorMap); + + RenderLayers[0].IsEnabled = true; + } + + /// + public void Add(int[] data) + { + // Add partition data for filled mesh. + RenderLayers[0].SetPoints(RenderLayers[1].Points); + RenderLayers[0].SetIndices(IndexBuffer.Create(mesh.Triangles, 3)); + RenderLayers[0].AttachLayerData(data); + + RenderLayers[0].IsEnabled = true; + } + + /// + public void Enable(int layer, bool enabled) + { + renderLayers[layer].IsEnabled = enabled; + } + + /// + public void Clear() + { + foreach (var layer in RenderLayers) + { + layer.Reset(true); + } + } + } +} diff --git a/src/Triangle.Rendering/RenderLayer.cs b/src/Triangle.Rendering/RenderLayer.cs new file mode 100644 index 0000000..a590db6 --- /dev/null +++ b/src/Triangle.Rendering/RenderLayer.cs @@ -0,0 +1,126 @@ + +namespace TriangleNet.Rendering +{ + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + using TriangleNet.Rendering.Buffer; + using TriangleNet.Rendering.Util; + + using Color = System.Drawing.Color; + + public class RenderLayer : IRenderLayer + { + int count; + + protected IBuffer points; + protected IBuffer indices; + + protected IBuffer partition; + protected IBuffer colors; + + public RenderLayer() + { + this.IsEnabled = false; + } + + /// + public int Count => count; + + /// + public IBuffer Points => points; + + /// + public IBuffer Indices => indices; + + /// + public IBuffer Partition => partition; + + /// + public IBuffer Colors => colors; + + /// + public bool IsEnabled { get; set; } + + /// + public bool IsEmpty() + { + return (points == null || points.Count == 0); + } + + /// + public void Reset(bool clear) + { + if (clear) + { + count = 0; + points = null; + } + + indices = null; + partition = null; + colors = null; + } + + /// + public void SetPoints(IBuffer buffer, bool reset = true) + { + if (!reset && points != null && points.Count < buffer.Count) + { + // NOTE: we keep the old size to be able to render new Steiner + // points in a different color than existing points. + count = points.Count / points.Size; + } + else + { + count = buffer.Count / buffer.Size; + } + + points = buffer; + } + + /// + public void SetIndices(IBuffer buffer) + { + indices = buffer; + } + + /// + public void AttachLayerData(float[] values, ColorMap colormap) + { + int length = values.Length; + + double min = double.MaxValue; + double max = double.MinValue; + + // Find min and max of given values. + for (int i = 0; i < length; i++) + { + if (values[i] < min) + { + min = values[i]; + } + + if (values[i] > max) + { + max = values[i]; + } + } + + var colorData = new Color[length]; + + for (int i = 0; i < length; i++) + { + colorData[i] = colormap.GetColor(values[i], min, max); + } + + colors = new ColorBuffer(colorData, 1); + } + + /// + public void AttachLayerData(int[] partition) + { + this.partition = new IndexBuffer(partition, 1); + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/RenderManager.cs b/src/Triangle.Rendering/RenderManager.cs similarity index 75% rename from Triangle.NET/Triangle.Rendering/RenderManager.cs rename to src/Triangle.Rendering/RenderManager.cs index 1337185..86a07b6 100644 --- a/Triangle.NET/Triangle.Rendering/RenderManager.cs +++ b/src/Triangle.Rendering/RenderManager.cs @@ -1,132 +1,115 @@ - -namespace TriangleNet.Rendering -{ - using System.Collections.Generic; - using System.Windows.Forms; - using TriangleNet.Geometry; - using TriangleNet.Meshing; - using TriangleNet.Rendering.GDI; - using TriangleNet.Rendering.Util; - - public class RenderManager - { - // TODO: delete - public static bool VORONOI_DEBUG = false; - - IRenderControl control; - IRenderContext context; - IRenderer renderer; - Projection zoom; - - public IRenderControl Control - { - get { return control; } - } - - public IRenderContext Context - { - get { return context; } - } - - public RenderManager() - { - } - - public RenderManager(IRenderControl control) - { - Initialize(control); - } - - public RenderManager(IRenderControl control, IRenderer renderer) - { - Initialize(control, renderer); - } - - public void Initialize(IRenderControl control) - { - Initialize(control, new LayerRenderer()); - } - - public void Initialize(IRenderControl control, IRenderer renderer) - { - this.zoom = new Projection(control.ClientRectangle); - - this.context = new RenderContext(zoom, ColorManager.Default()); - - this.renderer = renderer; - this.renderer.Context = context; - - this.control = control; - this.control.Initialize(); - this.control.Renderer = renderer; - } - - public bool TryCreateControl(string assemblyName, IEnumerable dependencies, - out IRenderControl control) - { - if (!ReflectionHelper.TryCreateControl(assemblyName, dependencies, out control)) - { - return false; - } - - return control is Control; - } - - public void Resize() - { - control.HandleResize(); - } - - public void Enable(int layer, bool enabled) - { - context.Enable(layer, enabled); - - control.Refresh(); - } - - public void Set(IPolygon data, bool refresh = true) - { - context.Add(data); - - if (refresh) - { - control.Refresh(); - } - } - - public void Set(IMesh data, bool reset, bool refresh = true) - { - context.Add(data, reset); - - if (refresh) - { - control.Refresh(); - } - } - - /// - /// Set data for Voronoi layer. - /// - public void Set(ICollection points, IEnumerable edges, bool reset, bool refresh = true) - { - context.Add(points, edges, reset); - - if (refresh) - { - control.Refresh(); - } - } - - public void Update(float[] values) - { - context.Add(values); - control.Refresh(); - } - - public void Update(int[] partition) - { - context.Add(partition); - control.Refresh(); - } - } -} + +namespace TriangleNet.Rendering +{ + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + using TriangleNet.Rendering.Util; + + public class RenderManager + { + // TODO: delete + public static bool VORONOI_DEBUG = false; + + IRenderControl control; + IRenderContext context; + IRenderer renderer; + Projection zoom; + + public IRenderControl Control => control; + + public IRenderContext Context => context; + + public RenderManager() + { + } + + public RenderManager(IRenderControl control, IRenderer renderer) + { + Initialize(control, renderer); + } + + public void Initialize(IRenderControl control, IRenderer renderer) + { + this.zoom = new Projection(control.ClientRectangle); + + this.context = new RenderContext(zoom, ColorManager.Default()); + + this.renderer = renderer; + this.renderer.Context = context; + + this.control = control; + this.control.Initialize(); + this.control.Renderer = renderer; + } + + public bool TryCreateControl(string assemblyName, IEnumerable dependencies, + out IRenderControl control) + { + return ReflectionHelper.TryCreateControl(assemblyName, dependencies, out control); + } + + public void Resize() + { + control.HandleResize(); + } + + public void Clear() + { + context.Clear(); + control.Refresh(); + } + + public void Enable(int layer, bool enabled) + { + context.Enable(layer, enabled); + + control.Refresh(); + } + + public void Set(IPolygon data, bool refresh = true) + { + context.Add(data); + + if (refresh) + { + control.Refresh(); + } + } + + public void Set(IMesh data, bool reset, bool refresh = true) + { + context.Add(data, reset); + + if (refresh) + { + control.Refresh(); + } + } + + /// + /// Set data for Voronoi layer. + /// + public void Set(ICollection points, IEnumerable edges, bool reset, bool refresh = true) + { + context.Add(points, edges, reset); + + if (refresh) + { + control.Refresh(); + } + } + + public void Update(float[] values) + { + context.Add(values); + control.Refresh(); + } + + public void Update(int[] partition) + { + context.Add(partition); + control.Refresh(); + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/Text/EpsDocument.cs b/src/Triangle.Rendering/Text/EpsDocument.cs similarity index 96% rename from Triangle.NET/Triangle.Rendering/Text/EpsDocument.cs rename to src/Triangle.Rendering/Text/EpsDocument.cs index 3db8888..94bd5a8 100644 --- a/Triangle.NET/Triangle.Rendering/Text/EpsDocument.cs +++ b/src/Triangle.Rendering/Text/EpsDocument.cs @@ -1,208 +1,208 @@ - -namespace TriangleNet.Rendering.Text -{ - using System; - using System.Drawing; - using System.IO; - - public class EpsDocument : IDisposable - { - // Constant to convert from millimeters to PostScript units (1/72th inch). - private const double UNITS_PER_MM = 72.0 / 25.4; - - private FormattingStreamWriter _w; - private PageSize _size; - - /// - /// Gets or sets the document name. - /// - public string Name { get; set; } - - /// - /// Gets or sets the default point size (default = 1). - /// - public int DefaultPointSize { get; set; } - - public EpsDocument(string filename, PageSize pageSize) - : this(File.Create(filename), pageSize) - { - Name = Path.GetFileName(filename); - } - - public EpsDocument(Stream stream, PageSize pageSize) - { - _w = new FormattingStreamWriter(stream); - _w.NewLine = "\n"; - - _size = pageSize; - - DefaultPointSize = 1; - } - - public void AddComment(string comment, int line = 1) - { - for (int i = 0; i < line; i++) - { - _w.WriteLine("%"); - } - - var t = comment.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries ); - - for (int i = 0; i < t.Length; i++) - { - _w.WriteLine("% " + t[i]); - } - - for (int i = 0; i < line; i++) - { - _w.WriteLine("%"); - } - } - - public void DrawPoint(Point p) - { - _w.WriteLine("{0} {1} P", p.X, p.Y); - } - - public void DrawLine(Point p1, Point p2) - { - _w.WriteLine("{0} {1} {2} {3} L", p1.X, p1.Y, p2.X, p2.Y); - } - - public void DrawRectangle(Rectangle rect) - { - _w.WriteLine("newpath"); - _w.WriteLine(" {0} {1} moveto", rect.X, rect.Y); - _w.WriteLine(" {0} {1} lineto", rect.Right, rect.Y); - _w.WriteLine(" {0} {1} lineto", rect.Right, rect.Bottom); - _w.WriteLine(" {0} {1} lineto", rect.X, rect.Bottom); - _w.WriteLine(" {0} {1} lineto", rect.X, rect.Y); - _w.WriteLine("stroke"); - - } - - public void SetClip(Rectangle rect) - { - _w.WriteLine("newpath"); - _w.WriteLine(" {0} {1} moveto", rect.X, rect.Y); - _w.WriteLine(" {0} {1} lineto", rect.Right, rect.Y); - _w.WriteLine(" {0} {1} lineto", rect.Right, rect.Bottom); - _w.WriteLine(" {0} {1} lineto", rect.X, rect.Bottom); - _w.WriteLine(" {0} {1} lineto", rect.X, rect.Y); - _w.WriteLine("clip newpath"); - } - - public void SetColor(Color color) - { - _w.WriteLine("{0:0.###} {1:0.###} {2:0.###} setrgbcolor", - ((float)color.R) / 255f, - ((float)color.G) / 255f, - ((float)color.B) / 255f); - } - - public void SetStroke(float width) - { - _w.WriteLine("{0:0.###} setlinewidth", width); - } - - public void SetStroke(float width, Color color) - { - SetColor(color); - SetStroke(width); - } - - public void WriteHeader() - { - var x = _size.X; // * UNITS_PER_MM - var y = _size.Y; - var right = _size.Right; - var bottom = _size.Bottom; - - // Write document header. - - _w.WriteLine("%!PS-Adobe-3.0 EPSF-3.0"); - _w.WriteLine("%%Creator: Triangle.NET"); - _w.WriteLine("%%Title: {0}", Name); - _w.WriteLine("%%Pages: 1"); - _w.WriteLine("%%BoundingBox: {0} {1} {2} {3}", (int)x, (int)y, (int)right, (int)bottom); - _w.WriteLine("%%HiResBoundingBox: {0:0.#####} {1:0.#####} {2:0.#####} {3:0.#####}", x, y, right, bottom); - _w.WriteLine("%%Document-Fonts: Times-Roman"); - _w.WriteLine("%%LanguageLevel: 3"); - _w.WriteLine("%%EndComments"); - _w.WriteLine("%%Page: 1 1"); - _w.WriteLine("save"); - - // Define points. - _w.WriteLine("% Define points."); - _w.WriteLine("/P {"); - _w.WriteLine("2 dict begin"); - _w.WriteLine("/y exch def"); - _w.WriteLine("/x exch def"); - _w.WriteLine("gsave"); - _w.WriteLine("newpath x y {0} 0 360 arc fill", DefaultPointSize); - _w.WriteLine("grestore"); - _w.WriteLine("end"); - _w.WriteLine("} def"); - - // Define lines. - _w.WriteLine("% Define lines."); - _w.WriteLine("/L {"); - _w.WriteLine("2 dict begin"); - _w.WriteLine("/y2 exch def"); - _w.WriteLine("/x2 exch def"); - _w.WriteLine("/y1 exch def"); - _w.WriteLine("/x1 exch def"); - _w.WriteLine("gsave"); - _w.WriteLine("newpath x1 y1 moveto x2 y2 lineto stroke"); - _w.WriteLine("grestore"); - _w.WriteLine("end"); - _w.WriteLine("} def"); - } - - private void Close() - { - _w.WriteLine("%"); - _w.WriteLine("restore showpage"); - _w.WriteLine("%%Trailer"); - _w.WriteLine("%%EOF"); - } - - #region IDisposable implementation - - // Has Dispose already been called? - bool disposed = false; - - // Public implementation of Dispose pattern callable by consumers. - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - // Protected implementation of Dispose pattern. - protected virtual void Dispose(bool disposing) - { - if (disposed) - return; - - Close(); - - if (disposing) - { - _w.Dispose(); - _w = null; - } - - // Free any unmanaged objects here. - // - disposed = true; - } - - ~EpsDocument() - { - Dispose(false); - } - - #endregion - } -} + +namespace TriangleNet.Rendering.Text +{ + using System; + using System.Drawing; + using System.IO; + + public class EpsDocument : IDisposable + { + // Constant to convert from millimeters to PostScript units (1/72th inch). + private const double UNITS_PER_MM = 72.0 / 25.4; + + private FormattingStreamWriter _w; + private PageSize _size; + + /// + /// Gets or sets the document name. + /// + public string Name { get; set; } + + /// + /// Gets or sets the default point size (default = 1). + /// + public int DefaultPointSize { get; set; } + + public EpsDocument(string filename, PageSize pageSize) + : this(File.Create(filename), pageSize) + { + Name = Path.GetFileName(filename); + } + + public EpsDocument(Stream stream, PageSize pageSize) + { + _w = new FormattingStreamWriter(stream); + _w.NewLine = "\n"; + + _size = pageSize; + + DefaultPointSize = 1; + } + + public void AddComment(string comment, int line = 1) + { + for (int i = 0; i < line; i++) + { + _w.WriteLine("%"); + } + + var t = comment.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries ); + + for (int i = 0; i < t.Length; i++) + { + _w.WriteLine("% " + t[i]); + } + + for (int i = 0; i < line; i++) + { + _w.WriteLine("%"); + } + } + + public void DrawPoint(Point p) + { + _w.WriteLine("{0} {1} P", p.X, p.Y); + } + + public void DrawLine(Point p1, Point p2) + { + _w.WriteLine("{0} {1} {2} {3} L", p1.X, p1.Y, p2.X, p2.Y); + } + + public void DrawRectangle(Rectangle rect) + { + _w.WriteLine("newpath"); + _w.WriteLine(" {0} {1} moveto", rect.X, rect.Y); + _w.WriteLine(" {0} {1} lineto", rect.Right, rect.Y); + _w.WriteLine(" {0} {1} lineto", rect.Right, rect.Bottom); + _w.WriteLine(" {0} {1} lineto", rect.X, rect.Bottom); + _w.WriteLine(" {0} {1} lineto", rect.X, rect.Y); + _w.WriteLine("stroke"); + + } + + public void SetClip(Rectangle rect) + { + _w.WriteLine("newpath"); + _w.WriteLine(" {0} {1} moveto", rect.X, rect.Y); + _w.WriteLine(" {0} {1} lineto", rect.Right, rect.Y); + _w.WriteLine(" {0} {1} lineto", rect.Right, rect.Bottom); + _w.WriteLine(" {0} {1} lineto", rect.X, rect.Bottom); + _w.WriteLine(" {0} {1} lineto", rect.X, rect.Y); + _w.WriteLine("clip newpath"); + } + + public void SetColor(Color color) + { + _w.WriteLine("{0:0.###} {1:0.###} {2:0.###} setrgbcolor", + ((float)color.R) / 255f, + ((float)color.G) / 255f, + ((float)color.B) / 255f); + } + + public void SetStroke(float width) + { + _w.WriteLine("{0:0.###} setlinewidth", width); + } + + public void SetStroke(float width, Color color) + { + SetColor(color); + SetStroke(width); + } + + public void WriteHeader() + { + var x = _size.X; // * UNITS_PER_MM + var y = _size.Y; + var right = _size.Right; + var bottom = _size.Bottom; + + // Write document header. + + _w.WriteLine("%!PS-Adobe-3.0 EPSF-3.0"); + _w.WriteLine("%%Creator: Triangle.NET"); + _w.WriteLine("%%Title: {0}", Name); + _w.WriteLine("%%Pages: 1"); + _w.WriteLine("%%BoundingBox: {0} {1} {2} {3}", (int)x, (int)y, (int)right, (int)bottom); + _w.WriteLine("%%HiResBoundingBox: {0:0.#####} {1:0.#####} {2:0.#####} {3:0.#####}", x, y, right, bottom); + _w.WriteLine("%%Document-Fonts: Times-Roman"); + _w.WriteLine("%%LanguageLevel: 3"); + _w.WriteLine("%%EndComments"); + _w.WriteLine("%%Page: 1 1"); + _w.WriteLine("save"); + + // Define points. + _w.WriteLine("% Define points."); + _w.WriteLine("/P {"); + _w.WriteLine("2 dict begin"); + _w.WriteLine("/y exch def"); + _w.WriteLine("/x exch def"); + _w.WriteLine("gsave"); + _w.WriteLine("newpath x y {0} 0 360 arc fill", DefaultPointSize); + _w.WriteLine("grestore"); + _w.WriteLine("end"); + _w.WriteLine("} def"); + + // Define lines. + _w.WriteLine("% Define lines."); + _w.WriteLine("/L {"); + _w.WriteLine("2 dict begin"); + _w.WriteLine("/y2 exch def"); + _w.WriteLine("/x2 exch def"); + _w.WriteLine("/y1 exch def"); + _w.WriteLine("/x1 exch def"); + _w.WriteLine("gsave"); + _w.WriteLine("newpath x1 y1 moveto x2 y2 lineto stroke"); + _w.WriteLine("grestore"); + _w.WriteLine("end"); + _w.WriteLine("} def"); + } + + private void Close() + { + _w.WriteLine("%"); + _w.WriteLine("restore showpage"); + _w.WriteLine("%%Trailer"); + _w.WriteLine("%%EOF"); + } + + #region IDisposable implementation + + // Has Dispose already been called? + bool disposed = false; + + // Public implementation of Dispose pattern callable by consumers. + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + // Protected implementation of Dispose pattern. + protected virtual void Dispose(bool disposing) + { + if (disposed) + return; + + Close(); + + if (disposing) + { + _w.Dispose(); + _w = null; + } + + // Free any unmanaged objects here. + // + disposed = true; + } + + ~EpsDocument() + { + Dispose(false); + } + + #endregion + } +} diff --git a/Triangle.NET/Triangle.Rendering/Text/EpsImage.cs b/src/Triangle.Rendering/Text/EpsImage.cs similarity index 97% rename from Triangle.NET/Triangle.Rendering/Text/EpsImage.cs rename to src/Triangle.Rendering/Text/EpsImage.cs index aeb1f3c..5de9176 100644 --- a/Triangle.NET/Triangle.Rendering/Text/EpsImage.cs +++ b/src/Triangle.Rendering/Text/EpsImage.cs @@ -1,157 +1,157 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// Original Matlab code by John Burkardt, Florida State University -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Rendering.Text -{ - using System; - using System.IO; - using TriangleNet; - using TriangleNet.Geometry; - - using Color = System.Drawing.Color; - using IntPoint = System.Drawing.Point; - using IntRectangle = System.Drawing.Rectangle; - - /// - /// Writes a mesh to an EPS file. - /// - public class EpsImage - { - // EPS page metrics - PageSize ps = new PageSize(36, 126, 576, 666); - PageSize clip = new PageSize(18, 108, 594, 684); - - // Mesh metrics - double x_max, x_min; - double y_max, y_min; - - // TODO: use color manager - private static Color ColorPoints = Color.FromArgb(0, 100, 0); - private static Color ColorLines = Color.FromArgb(150, 150, 150); - private static Color ColorSegments = Color.FromArgb(70, 130, 180); - private static Color ColorBorder = Color.FromArgb(230, 230, 230); - - /// - /// Export the mesh to EPS format. - /// - /// The current mesh. - /// The EPS filename. - /// The desired width of the image (currently ignored). - public void Export(Mesh mesh, string filename, int width) - { - // Check file name - if (String.IsNullOrWhiteSpace(filename)) - { - filename = String.Format("mesh-{0}.eps", DateTime.Now.ToString("yyyy-M-d-hh-mm-ss")); - } - - if (!filename.EndsWith(".eps")) - { - filename = Path.ChangeExtension(filename, ".eps"); - } - - UpdateMetrics(mesh.Bounds); - - using (var eps = new EpsDocument(filename, ps)) - { - int n = mesh.Vertices.Count; - - // Size of the points. - eps.DefaultPointSize = (n < 100) ? 3 : ((n < 500) ? 2 : 1); - - eps.WriteHeader(); - - // Draw a gray border around the page. - eps.SetColor(ColorBorder); - eps.DrawRectangle(GetRectangle(ps)); - - // Define a clipping polygon. - eps.SetClip(GetRectangle(clip)); - - // Draw edges. - eps.AddComment("Draw edges."); - eps.SetStroke(0.4f, ColorLines); - - foreach (var e in EdgeIterator.EnumerateEdges(mesh)) - { - eps.DrawLine(Transform(e.GetVertex(0)), Transform(e.GetVertex(1))); - } - - // Draw Segments. - eps.AddComment("Draw Segments."); - eps.SetStroke(0.8f, ColorSegments); - - foreach (var s in mesh.Segments) - { - eps.DrawLine(Transform(s.GetVertex(0)), Transform(s.GetVertex(1))); - } - - // Draw points. - eps.AddComment("Draw points."); - eps.SetColor(ColorPoints); - - foreach (var node in mesh.Vertices) - { - eps.DrawPoint(Transform(node)); - } - } - } - - private IntRectangle GetRectangle(PageSize size) - { - return new IntRectangle((int)size.X, (int)size.Y, (int)size.Width, (int)size.Height); - } - - private IntPoint Transform(Point p) - { - return Transform(p.X, p.Y); - } - - private IntPoint Transform(double x, double y) - { - return new IntPoint( - (int)Math.Floor(((x_max - x) * ps.X + (x - x_min) * ps.Right) / (x_max - x_min)), - (int)Math.Floor(((y_max - y) * ps.Y + (y - y_min) * ps.Bottom) / (y_max - y_min)) - ); - } - - private void UpdateMetrics(Rectangle bounds) - { - x_max = bounds.Right; - x_min = bounds.Left; - y_max = bounds.Top; - y_min = bounds.Bottom; - - // Enlarge width 5% on each side - double x_scale = x_max - x_min; - x_max = x_max + 0.05 * x_scale; - x_min = x_min - 0.05 * x_scale; - x_scale = x_max - x_min; - - // Enlarge height 5% on each side - double y_scale = y_max - y_min; - y_max = y_max + 0.05 * y_scale; - y_min = y_min - 0.05 * y_scale; - y_scale = y_max - y_min; - - if (x_scale < y_scale) - { - int delta = (int)Math.Round((ps.Right - ps.X) * (y_scale - x_scale) / (2.0 * y_scale)); - - ps.Expand(-delta, 0); - clip.Expand(-delta, 0); - } - else - { - int delta = (int)Math.Round((ps.Bottom - ps.Y) * (x_scale - y_scale) / (2.0 * x_scale)); - - ps.Expand(0, -delta); - clip.Expand(0, -delta); - } - } - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// Original Matlab code by John Burkardt, Florida State University +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Rendering.Text +{ + using System; + using System.IO; + using TriangleNet; + using TriangleNet.Geometry; + using TriangleNet.Meshing.Iterators; + using Color = System.Drawing.Color; + using IntPoint = System.Drawing.Point; + using IntRectangle = System.Drawing.Rectangle; + + /// + /// Writes a mesh to an EPS file. + /// + public class EpsImage + { + // EPS page metrics + PageSize ps = new PageSize(36, 126, 576, 666); + PageSize clip = new PageSize(18, 108, 594, 684); + + // Mesh metrics + double x_max, x_min; + double y_max, y_min; + + // TODO: use color manager + private static Color ColorPoints = Color.FromArgb(0, 100, 0); + private static Color ColorLines = Color.FromArgb(150, 150, 150); + private static Color ColorSegments = Color.FromArgb(70, 130, 180); + private static Color ColorBorder = Color.FromArgb(230, 230, 230); + + /// + /// Export the mesh to EPS format. + /// + /// The current mesh. + /// The EPS filename. + /// The desired width of the image (currently ignored). + public void Export(Mesh mesh, string filename, int width) + { + // Check file name + if (String.IsNullOrWhiteSpace(filename)) + { + filename = String.Format("mesh-{0}.eps", DateTime.Now.ToString("yyyy-M-d-hh-mm-ss")); + } + + if (!filename.EndsWith(".eps")) + { + filename = Path.ChangeExtension(filename, ".eps"); + } + + UpdateMetrics(mesh.Bounds); + + using (var eps = new EpsDocument(filename, ps)) + { + int n = mesh.Vertices.Count; + + // Size of the points. + eps.DefaultPointSize = (n < 100) ? 3 : ((n < 500) ? 2 : 1); + + eps.WriteHeader(); + + // Draw a gray border around the page. + eps.SetColor(ColorBorder); + eps.DrawRectangle(GetRectangle(ps)); + + // Define a clipping polygon. + eps.SetClip(GetRectangle(clip)); + + // Draw edges. + eps.AddComment("Draw edges."); + eps.SetStroke(0.4f, ColorLines); + + foreach (var e in EdgeIterator.EnumerateEdges(mesh)) + { + eps.DrawLine(Transform(e.GetVertex(0)), Transform(e.GetVertex(1))); + } + + // Draw Segments. + eps.AddComment("Draw Segments."); + eps.SetStroke(0.8f, ColorSegments); + + foreach (var s in mesh.Segments) + { + eps.DrawLine(Transform(s.GetVertex(0)), Transform(s.GetVertex(1))); + } + + // Draw points. + eps.AddComment("Draw points."); + eps.SetColor(ColorPoints); + + foreach (var node in mesh.Vertices) + { + eps.DrawPoint(Transform(node)); + } + } + } + + private IntRectangle GetRectangle(PageSize size) + { + return new IntRectangle((int)size.X, (int)size.Y, (int)size.Width, (int)size.Height); + } + + private IntPoint Transform(Point p) + { + return Transform(p.X, p.Y); + } + + private IntPoint Transform(double x, double y) + { + return new IntPoint( + (int)Math.Floor(((x_max - x) * ps.X + (x - x_min) * ps.Right) / (x_max - x_min)), + (int)Math.Floor(((y_max - y) * ps.Y + (y - y_min) * ps.Bottom) / (y_max - y_min)) + ); + } + + private void UpdateMetrics(Rectangle bounds) + { + x_max = bounds.Right; + x_min = bounds.Left; + y_max = bounds.Top; + y_min = bounds.Bottom; + + // Enlarge width 5% on each side + double x_scale = x_max - x_min; + x_max = x_max + 0.05 * x_scale; + x_min = x_min - 0.05 * x_scale; + x_scale = x_max - x_min; + + // Enlarge height 5% on each side + double y_scale = y_max - y_min; + y_max = y_max + 0.05 * y_scale; + y_min = y_min - 0.05 * y_scale; + y_scale = y_max - y_min; + + if (x_scale < y_scale) + { + int delta = (int)Math.Round((ps.Right - ps.X) * (y_scale - x_scale) / (2.0 * y_scale)); + + ps.Expand(-delta, 0); + clip.Expand(-delta, 0); + } + else + { + int delta = (int)Math.Round((ps.Bottom - ps.Y) * (x_scale - y_scale) / (2.0 * x_scale)); + + ps.Expand(0, -delta); + clip.Expand(0, -delta); + } + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/Text/FormattingStreamWriter.cs b/src/Triangle.Rendering/Text/FormattingStreamWriter.cs similarity index 90% rename from Triangle.NET/Triangle.Rendering/Text/FormattingStreamWriter.cs rename to src/Triangle.Rendering/Text/FormattingStreamWriter.cs index b3cdd1d..e134a7c 100644 --- a/Triangle.NET/Triangle.Rendering/Text/FormattingStreamWriter.cs +++ b/src/Triangle.Rendering/Text/FormattingStreamWriter.cs @@ -1,73 +1,67 @@ - -namespace TriangleNet.Rendering.Text -{ - using System; - using System.Globalization; - using System.IO; - - /// - /// - /// - /// - /// From http://stackoverflow.com/questions/12011789/streamwriter-and-iformatprovider - /// - public class FormattingStreamWriter : StreamWriter - { - private readonly IFormatProvider formatProvider; - - /// - /// Initializes a new instance of the StreamWriter class for the specified file - /// by using the default encoding and buffer size. - /// - /// The complete file path to write to. - public FormattingStreamWriter(string path) - : this(path, CultureInfo.InvariantCulture) - { - } - - /// - /// Initializes a new instance of the StreamWriter class for the specified stream - /// by using UTF-8 encoding and the default buffer size. - /// - /// The stream to write to. - public FormattingStreamWriter(Stream stream) - : this(stream, CultureInfo.InvariantCulture) - { - } - - /// - /// Initializes a new instance of the StreamWriter class for the specified file - /// by using the default encoding and buffer size. - /// - /// The complete file path to write to. - /// The format provider. - public FormattingStreamWriter(string path, IFormatProvider formatProvider) - : base(path) - { - this.formatProvider = formatProvider; - } - - /// - /// Initializes a new instance of the StreamWriter class for the specified stream - /// by using UTF-8 encoding and the default buffer size. - /// - /// The stream to write to. - /// The format provider. - public FormattingStreamWriter(Stream stream, IFormatProvider formatProvider) - : base(stream) - { - this.formatProvider = formatProvider; - } - - /// - /// Gets an object that controls formatting. - /// - public override IFormatProvider FormatProvider - { - get - { - return this.formatProvider; - } - } - } -} + +namespace TriangleNet.Rendering.Text +{ + using System; + using System.Globalization; + using System.IO; + + /// + /// A class allowing to specify the . + /// + /// + /// From http://stackoverflow.com/questions/12011789/streamwriter-and-iformatprovider + /// + public class FormattingStreamWriter : StreamWriter + { + private readonly IFormatProvider formatProvider; + + /// + /// Initializes a new instance of the StreamWriter class for the specified file + /// by using the default encoding and buffer size. + /// + /// The complete file path to write to. + public FormattingStreamWriter(string path) + : this(path, CultureInfo.InvariantCulture) + { + } + + /// + /// Initializes a new instance of the StreamWriter class for the specified stream + /// by using UTF-8 encoding and the default buffer size. + /// + /// The stream to write to. + public FormattingStreamWriter(Stream stream) + : this(stream, CultureInfo.InvariantCulture) + { + } + + /// + /// Initializes a new instance of the StreamWriter class for the specified file + /// by using the default encoding and buffer size. + /// + /// The complete file path to write to. + /// The format provider. + public FormattingStreamWriter(string path, IFormatProvider formatProvider) + : base(path) + { + this.formatProvider = formatProvider; + } + + /// + /// Initializes a new instance of the StreamWriter class for the specified stream + /// by using UTF-8 encoding and the default buffer size. + /// + /// The stream to write to. + /// The format provider. + public FormattingStreamWriter(Stream stream, IFormatProvider formatProvider) + : base(stream) + { + this.formatProvider = formatProvider; + } + + /// + /// Gets an object that controls formatting. + /// + public override IFormatProvider FormatProvider => formatProvider; + } +} diff --git a/Triangle.NET/Triangle.Rendering/Text/PageSize.cs b/src/Triangle.Rendering/Text/PageSize.cs similarity index 95% rename from Triangle.NET/Triangle.Rendering/Text/PageSize.cs rename to src/Triangle.Rendering/Text/PageSize.cs index 9d2f5c9..594235b 100644 --- a/Triangle.NET/Triangle.Rendering/Text/PageSize.cs +++ b/src/Triangle.Rendering/Text/PageSize.cs @@ -1,81 +1,81 @@ - -namespace TriangleNet.Rendering.Text -{ - using System.Drawing; - - /// - /// Page size in millimeters. - /// - public struct PageSize - { - private const float MM_PER_INCH = 2.54f; - - public static readonly PageSize A3 = new PageSize(297.0f, 420.0f); - public static readonly PageSize A4 = new PageSize(210.0f, 297.0f); - public static readonly PageSize A5 = new PageSize(148.0f, 210.0f); - public static readonly PageSize LETTER = new PageSize(8.5f * MM_PER_INCH, 11.0f * MM_PER_INCH); - public static readonly PageSize LEGAL = new PageSize(8.5f * MM_PER_INCH, 14.0f * MM_PER_INCH); - - private float left; - private float top; - private float right; - private float bottom; - - public float X - { - get { return left; } - } - - public float Y - { - get { return top; } - } - - public float Width - { - get { return right - left; } - } - - public float Height - { - get { return bottom - top; } - } - - public float Right - { - get { return right; } - } - - public float Bottom - { - get { return bottom; } - } - - public PageSize(float left, float top, float right, float bottom) - { - this.left = left; - this.top = top; - this.right = right; - this.bottom = bottom; - } - - public PageSize(float width, float height) - : this(0.0f, 0.0f, width, height) - { - } - - public PageSize(Rectangle size) - : this(size.Left, size.Right, size.Top, size.Bottom) - { - } - - public void Expand(float dx, float dy) - { - left -= dx; - top -= dy; - - right += dx; - bottom += dy; - } - } -} + +namespace TriangleNet.Rendering.Text +{ + using System.Drawing; + + /// + /// Page size in millimeters. + /// + public struct PageSize + { + private const float MM_PER_INCH = 2.54f; + + public static readonly PageSize A3 = new PageSize(297.0f, 420.0f); + public static readonly PageSize A4 = new PageSize(210.0f, 297.0f); + public static readonly PageSize A5 = new PageSize(148.0f, 210.0f); + public static readonly PageSize LETTER = new PageSize(8.5f * MM_PER_INCH, 11.0f * MM_PER_INCH); + public static readonly PageSize LEGAL = new PageSize(8.5f * MM_PER_INCH, 14.0f * MM_PER_INCH); + + private float left; + private float top; + private float right; + private float bottom; + + public float X + { + get { return left; } + } + + public float Y + { + get { return top; } + } + + public float Width + { + get { return right - left; } + } + + public float Height + { + get { return bottom - top; } + } + + public float Right + { + get { return right; } + } + + public float Bottom + { + get { return bottom; } + } + + public PageSize(float left, float top, float right, float bottom) + { + this.left = left; + this.top = top; + this.right = right; + this.bottom = bottom; + } + + public PageSize(float width, float height) + : this(0.0f, 0.0f, width, height) + { + } + + public PageSize(Rectangle size) + : this(size.Left, size.Right, size.Top, size.Bottom) + { + } + + public void Expand(float dx, float dy) + { + left -= dx; + top -= dy; + + right += dx; + bottom += dy; + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/Text/SvgImage.cs b/src/Triangle.Rendering/Text/SvgImage.cs similarity index 83% rename from Triangle.NET/Triangle.Rendering/Text/SvgImage.cs rename to src/Triangle.Rendering/Text/SvgImage.cs index d1e2ce4..5b63777 100644 --- a/Triangle.NET/Triangle.Rendering/Text/SvgImage.cs +++ b/src/Triangle.Rendering/Text/SvgImage.cs @@ -1,254 +1,266 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Rendering.Text -{ - using System; - using System.IO; - using System.Text; - using TriangleNet; - using TriangleNet.Geometry; - - /// - /// Writes a mesh to an SVG file. - /// - public class SvgImage - { - // Iterations to insert a linebreak in SVG path. - private const int LINEBREAK_COUNT = 10; - - float scale = 1f; - - /// - /// Export the mesh to SVG format. - /// - /// The current mesh. - /// The SVG filename. - /// The desired width of the image. - public void Export(Mesh mesh, string filename, int width) - { - // Check file name - if (String.IsNullOrWhiteSpace(filename)) - { - filename = String.Format("mesh-{0}.svg", DateTime.Now.ToString("yyyy-M-d-hh-mm-ss")); - } - - if (!filename.EndsWith(".svg")) - { - filename = Path.ChangeExtension(filename, ".svg"); - } - - if (width < 200) - { - width = 200; - } - - var bounds = mesh.Bounds; - - float margin = 0.05f * (float)bounds.Width; - - scale = width / ((float)bounds.Width + 2 * margin); - - int x_offset = -(int)((bounds.Left - margin) * scale); - int y_offset = (int)((bounds.Top + margin) * scale); - - int height = (int)((bounds.Height + 2 * margin) * scale); - - using (var svg = new FormattingStreamWriter(filename)) - { - svg.WriteLine("", width, height); - - svg.WriteLine("", x_offset, y_offset); - - DrawTriangles(svg, mesh, false); - //DrawEdges(svg, mesh); - - DrawSegments(svg, mesh); - - DrawPoints(svg, mesh, false); - - svg.WriteLine(""); - - svg.WriteLine(""); - } - } - - private void DrawTriangles(StreamWriter svg, Mesh mesh, bool label) - { - svg.Write("\t{2}", - xa, ya, tri.ID); - labels.AppendLine(); - } - } - - svg.WriteLine("\" style=\"stroke:#c2c2c2; fill:none; stroke-linejoin:bevel;\"/>"); - - // Label the triangles. - if (label) - { - svg.WriteLine("\t"); - svg.Write(labels.ToString()); - svg.WriteLine("\t"); - } - } - - private void DrawEdges(StreamWriter svg, Mesh mesh) - { - svg.Write("\t"); - } - - private void DrawSegments(StreamWriter svg, Mesh mesh) - { - svg.Write("\t"); - } - - private void DrawPoints(StreamWriter svg, Mesh mesh, bool label) - { - int n = mesh.Vertices.Count; - - float circle_size = 1.5f; - - if (n < 100) - { - circle_size = 3; - } - else if (n < 500) - { - circle_size = 2; - } - - svg.WriteLine("\t"); - - double x, y; - - StringBuilder labels = new StringBuilder(); - - foreach (var node in mesh.Vertices) - { - x = scale * node.X; - y = scale * node.Y; - - svg.WriteLine("\t\t", - x, y, circle_size); - - if (label) - { - labels.AppendFormat("{2}", - x, y, node.ID); - labels.AppendLine(); - } - } - - svg.WriteLine("\t"); - - // Label the nodes. - if (label) - { - svg.WriteLine("\t"); - svg.Write(labels.ToString()); - svg.WriteLine("\t"); - } - } - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Rendering.Text +{ + using System; + using System.IO; + using System.Text; + using TriangleNet; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + using TriangleNet.Meshing.Iterators; + + /// + /// Writes a mesh to an SVG file. + /// + public class SvgImage + { + // Iterations to insert a linebreak in SVG path. + private const int LINEBREAK_COUNT = 10; + + float scale = 1f; + + /// + /// Exports a mesh to SVG format. + /// + /// The mesh. + /// The SVG filename. + /// The desired width (pixel) of the image. + /// Enable rendering of regions. + /// Enable rendering of points. + public static void Save(IMesh mesh, string file = null, int width = 800, + bool regions = false, bool points = true) + { + new SvgImage().Export(mesh, file, width); + } + + /// + /// Export a mesh to SVG format. + /// + /// The current mesh. + /// The SVG filename. + /// The desired width of the image. + public void Export(IMesh mesh, string filename, int width) + { + // Check file name + if (string.IsNullOrWhiteSpace(filename)) + { + filename = string.Format("mesh-{0}.svg", DateTime.Now.ToString("yyyy-M-d-hh-mm-ss")); + } + + if (!filename.EndsWith(".svg")) + { + filename = Path.ChangeExtension(filename, ".svg"); + } + + if (width < 200) + { + width = 200; + } + + var bounds = mesh.Bounds; + + float margin = 0.05f * (float)bounds.Width; + + scale = width / ((float)bounds.Width + 2 * margin); + + int x_offset = -(int)((bounds.Left - margin) * scale); + int y_offset = (int)((bounds.Top + margin) * scale); + + int height = (int)((bounds.Height + 2 * margin) * scale); + + using (var svg = new FormattingStreamWriter(filename)) + { + svg.WriteLine("", width, height); + + svg.WriteLine("", x_offset, y_offset); + + DrawTriangles(svg, mesh, false); + //DrawEdges(svg, mesh); + + DrawSegments(svg, mesh); + + DrawPoints(svg, mesh, false); + + svg.WriteLine(""); + + svg.WriteLine(""); + } + } + + private void DrawTriangles(StreamWriter svg, IMesh mesh, bool label) + { + svg.Write("\t{2}", + xa, ya, tri.ID); + labels.AppendLine(); + } + } + + svg.WriteLine("\" style=\"stroke:#c2c2c2; fill:none; stroke-linejoin:bevel;\"/>"); + + // Label the triangles. + if (label) + { + svg.WriteLine("\t"); + svg.Write(labels.ToString()); + svg.WriteLine("\t"); + } + } + + private void DrawEdges(StreamWriter svg, IMesh mesh) + { + svg.Write("\t"); + } + + private void DrawSegments(StreamWriter svg, IMesh mesh) + { + svg.Write("\t"); + } + + private void DrawPoints(StreamWriter svg, IMesh mesh, bool label) + { + int n = mesh.Vertices.Count; + + float circle_size = 1.5f; + + if (n < 100) + { + circle_size = 3; + } + else if (n < 500) + { + circle_size = 2; + } + + svg.WriteLine("\t"); + + double x, y; + + var labels = new StringBuilder(); + + foreach (var node in mesh.Vertices) + { + x = scale * node.X; + y = scale * node.Y; + + svg.WriteLine("\t\t", + x, y, circle_size); + + if (label) + { + labels.AppendFormat("{2}", + x, y, node.ID); + labels.AppendLine(); + } + } + + svg.WriteLine("\t"); + + // Label the nodes. + if (label) + { + svg.WriteLine("\t"); + svg.Write(labels.ToString()); + svg.WriteLine("\t"); + } + } + } +} diff --git a/src/Triangle.Rendering/Triangle.Rendering.csproj b/src/Triangle.Rendering/Triangle.Rendering.csproj new file mode 100644 index 0000000..9618430 --- /dev/null +++ b/src/Triangle.Rendering/Triangle.Rendering.csproj @@ -0,0 +1,15 @@ + + + + Library + net6.0 + TriangleNet.Rendering + Triangle.Rendering + AnyCPU;x64 + + + + + + + diff --git a/Triangle.NET/Triangle.Rendering/Util/ColorMap.cs b/src/Triangle.Rendering/Util/ColorMap.cs similarity index 95% rename from Triangle.NET/Triangle.Rendering/Util/ColorMap.cs rename to src/Triangle.Rendering/Util/ColorMap.cs index ca53def..37adde3 100644 --- a/Triangle.NET/Triangle.Rendering/Util/ColorMap.cs +++ b/src/Triangle.Rendering/Util/ColorMap.cs @@ -1,112 +1,112 @@ - -namespace TriangleNet.Rendering.Util -{ - using System; - using System.Drawing; - - public class ColorMap - { - #region Colormap definitions - - public static ColorMap Jet(int size) - { - ColorMap map = new ColorMap(size); - float v, step = 1.0f / (size - 1); - float[] rgb = new float[3]; - - for (int i = 0; i < size; i += 1) - { - v = 4 * i * step; - - rgb[0] = Math.Min(v - 1.5f, 4.5f - v); - rgb[1] = Math.Min(v - 0.5f, 3.5f - v); - rgb[2] = Math.Min(v + 0.5f, 2.5f - v); - - Clamp(rgb, 0.0f, 1.0f); - - map.colors[size - i - 1] = ColorFromRgb(rgb[0], rgb[1], rgb[2]); - } - - return map; - } - - public static ColorMap Hot(int size) - { - ColorMap map = new ColorMap(size); - float v, step = 1.0f / (size - 1); - float[] rgb = new float[3]; - - for (int i = 0; i < size; i += 1) - { - v = 2.5f * i * step; - - rgb[0] = v; - rgb[1] = v - 1; - rgb[2] = 2 * v - 4; - - Clamp(rgb, 0.0f, 1.0f); - - map.colors[i] = ColorFromRgb(rgb[0], rgb[1], rgb[2]); - } - - return map; - } - - #endregion - - #region Helper - - private static Color ColorFromRgb(float r, float g, float b) - { - byte max = byte.MaxValue; - - return Color.FromArgb((byte)(r * max), (byte)(g * max), (byte)(b * max)); - } - - private static void Clamp(float[] values, float min, float max) - { - int n = values.Length; - - for (int i = 0; i < n; i += 1) - { - values[i] = Math.Min(max, Math.Max(min, values[i])); - } - } - - private static int Clamp(int index, int max) - { - if (index < 0) - { - index = 0; - } - else if (index > max) - { - index = max; - } - - return index; - } - - #endregion - - private Color[] colors; - - private ColorMap(int size) - { - this.colors = new Color[size]; - } - - public ColorMap(Color[] colors) - { - this.colors = colors; - } - - public Color GetColor(double value, double min, double max) - { - int n = this.colors.Length; - int i = (int)Math.Floor(n * (max - value) / (max - min)); - - return this.colors[Clamp(i, n - 1)]; - } - } -} + +namespace TriangleNet.Rendering.Util +{ + using System; + using System.Drawing; + + public class ColorMap + { + #region Colormap definitions + + public static ColorMap Jet(int size) + { + ColorMap map = new ColorMap(size); + float v, step = 1.0f / (size - 1); + float[] rgb = new float[3]; + + for (int i = 0; i < size; i += 1) + { + v = 4 * i * step; + + rgb[0] = Math.Min(v - 1.5f, 4.5f - v); + rgb[1] = Math.Min(v - 0.5f, 3.5f - v); + rgb[2] = Math.Min(v + 0.5f, 2.5f - v); + + Clamp(rgb, 0.0f, 1.0f); + + map.colors[size - i - 1] = ColorFromRgb(rgb[0], rgb[1], rgb[2]); + } + + return map; + } + + public static ColorMap Hot(int size) + { + ColorMap map = new ColorMap(size); + float v, step = 1.0f / (size - 1); + float[] rgb = new float[3]; + + for (int i = 0; i < size; i += 1) + { + v = 2.5f * i * step; + + rgb[0] = v; + rgb[1] = v - 1; + rgb[2] = 2 * v - 4; + + Clamp(rgb, 0.0f, 1.0f); + + map.colors[i] = ColorFromRgb(rgb[0], rgb[1], rgb[2]); + } + + return map; + } + + #endregion + + #region Helper + + private static Color ColorFromRgb(float r, float g, float b) + { + byte max = byte.MaxValue; + + return Color.FromArgb((byte)(r * max), (byte)(g * max), (byte)(b * max)); + } + + private static void Clamp(float[] values, float min, float max) + { + int n = values.Length; + + for (int i = 0; i < n; i += 1) + { + values[i] = Math.Min(max, Math.Max(min, values[i])); + } + } + + private static int Clamp(int index, int max) + { + if (index < 0) + { + index = 0; + } + else if (index > max) + { + index = max; + } + + return index; + } + + #endregion + + private Color[] colors; + + private ColorMap(int size) + { + this.colors = new Color[size]; + } + + public ColorMap(Color[] colors) + { + this.colors = colors; + } + + public Color GetColor(double value, double min, double max) + { + int n = this.colors.Length; + int i = (int)Math.Floor(n * (max - value) / (max - min)); + + return this.colors[Clamp(i, n - 1)]; + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/Util/ReflectionHelper.cs b/src/Triangle.Rendering/Util/ReflectionHelper.cs similarity index 96% rename from Triangle.NET/Triangle.Rendering/Util/ReflectionHelper.cs rename to src/Triangle.Rendering/Util/ReflectionHelper.cs index 528fea3..6ed1a65 100644 --- a/Triangle.NET/Triangle.Rendering/Util/ReflectionHelper.cs +++ b/src/Triangle.Rendering/Util/ReflectionHelper.cs @@ -1,81 +1,81 @@ - -namespace TriangleNet.Rendering.Util -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - - internal static class ReflectionHelper - { - public static bool TryCreateControl(string assemblyName, IEnumerable dependencies, - out IRenderControl control) - { - return TryCreateControl(assemblyName, dependencies, null, out control); - } - - public static bool TryCreateControl(string assemblyName, IEnumerable dependencies, - string className, out IRenderControl control) - { - control = null; - - if (!FilesExist(assemblyName, dependencies)) - { - return false; - } - - assemblyName = Path.GetFileNameWithoutExtension(assemblyName); - - // Try create render control instance. - try - { - // Load the assembly into the current application domain. - var assembly = Assembly.Load(assemblyName); - - // Get all types implementing the IRenderControl interface. - var type = typeof(IRenderControl); - var matches = assembly.GetTypes().Where(s => type.IsAssignableFrom(s)); - - var match = string.IsNullOrEmpty(className) ? matches.FirstOrDefault() - : matches.Where(s => s.Name == className).FirstOrDefault(); - - if (match != null) - { - // Create an instance. - control = (IRenderControl)Activator.CreateInstance(match); - } - } - catch (Exception) - { - return false; - } - - // Return true if render control was successfully created. - return (control != null); - } - - private static bool FilesExist(string assemblyName, IEnumerable dependencies) - { - // Check if assembly exists - if (!File.Exists(assemblyName)) - { - return false; - } - - // Check if dependencies exists - if (dependencies != null) - { - foreach (var item in dependencies) - { - if (!File.Exists(item)) - { - return false; - } - } - } - - return true; - } - } -} + +namespace TriangleNet.Rendering.Util +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + + internal static class ReflectionHelper + { + public static bool TryCreateControl(string assemblyName, IEnumerable dependencies, + out IRenderControl control) + { + return TryCreateControl(assemblyName, dependencies, null, out control); + } + + public static bool TryCreateControl(string assemblyName, IEnumerable dependencies, + string className, out IRenderControl control) + { + control = null; + + if (!FilesExist(assemblyName, dependencies)) + { + return false; + } + + assemblyName = Path.GetFileNameWithoutExtension(assemblyName); + + // Try create render control instance. + try + { + // Load the assembly into the current application domain. + var assembly = Assembly.Load(assemblyName); + + // Get all types implementing the IRenderControl interface. + var type = typeof(IRenderControl); + var matches = assembly.GetTypes().Where(s => type.IsAssignableFrom(s)); + + var match = string.IsNullOrEmpty(className) ? matches.FirstOrDefault() + : matches.Where(s => s.Name == className).FirstOrDefault(); + + if (match != null) + { + // Create an instance. + control = (IRenderControl)Activator.CreateInstance(match); + } + } + catch (Exception) + { + return false; + } + + // Return true if render control was successfully created. + return (control != null); + } + + private static bool FilesExist(string assemblyName, IEnumerable dependencies) + { + // Check if assembly exists + if (!File.Exists(assemblyName)) + { + return false; + } + + // Check if dependencies exists + if (dependencies != null) + { + foreach (var item in dependencies) + { + if (!File.Exists(item)) + { + return false; + } + } + } + + return true; + } + } +} diff --git a/src/Triangle.Tests/Geomerty/ContourTest.cs b/src/Triangle.Tests/Geomerty/ContourTest.cs new file mode 100644 index 0000000..087c23f --- /dev/null +++ b/src/Triangle.Tests/Geomerty/ContourTest.cs @@ -0,0 +1,86 @@ +using NUnit.Framework; +using TriangleNet.Geometry; +using System; +using System.Collections.Generic; + +namespace TriangleNet.Tests.Geometry +{ + public class ContourTest + { + [Test] + public void TestFindInteriorPoint() + { + // The vertices that define the contour (have to be in order, obviously). + var vertices = new Vertex[] + { + new Vertex(0d, 0d), + new Vertex(1d ,0d), + new Vertex(1d, 1d), + new Vertex(0d, 1d), + }; + + var contour = new Contour(vertices); + + var p = contour.FindInteriorPoint(); + + Assert.IsTrue(p.X > 0d && p.X < 1d && p.Y > 0d && p.Y < 1d); + } + + [Test] + public void TestFindInteriorPointL() + { + // L-shaped contour (FindPointInPolygon() produces a test candidate + // which lies exactly on a segment where IsPointInPolygon() returns + // true, so IsPointOnSegment() is actually needed here). + var points = new List() + { + new Vertex(3, 1), + new Vertex(1, 1), + new Vertex(1, 3), + new Vertex(2, 3), + new Vertex(2, 2), + new Vertex(3, 2) + }; + + var contour = new Contour(points); + + var poly = new Polygon(6); + + poly.Add(contour, true); + + var h = poly.Holes[0]; + var p = RobustPredicates.Default; + + int count = points.Count; + int i = count - 1; + + for (int j = 0; j < count; j++) + { + double ccw = p.CounterClockwise(points[i], h, points[j]); + + Assert.Greater(Math.Abs(ccw), 1e-12); + + i = j; + } + } + + [Test] + public void TestFindInteriorPointDup() + { + // Rectangle contour with duplicate point. + var points = new List() + { + new Vertex(0.0, 0.0), + new Vertex(0.0, 1.0), + new Vertex(2.0, 1.0), + new Vertex(2.0, 0.5), + new Vertex(2.0, 0.5), // duplicate + new Vertex(2.0, 0.0) + }; + + var contour = new Contour(points); + + Assert.DoesNotThrow(() => contour.FindInteriorPoint()); + } + } +} diff --git a/src/Triangle.Tests/Helper.cs b/src/Triangle.Tests/Helper.cs new file mode 100644 index 0000000..23c675f --- /dev/null +++ b/src/Triangle.Tests/Helper.cs @@ -0,0 +1,35 @@ + +namespace TriangleNet.Tests +{ + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Topology; + + static class Helper + { + public static Contour Rectangle(double left, double top, + double right, double bottom, int mark = 0) + { + var points = new List(4); + + points.Add(new Vertex(left, top, mark)); + points.Add(new Vertex(right, top, mark)); + points.Add(new Vertex(right, bottom, mark)); + points.Add(new Vertex(left, bottom, mark)); + + return new Contour(points, mark, true); + } + + public static Triangle CreateTriangle(int id, Vertex org, Vertex dest, Vertex apex) + { + var t = new Triangle() { id = id, hash = id }; + + // Node ordering 'plus 1 mod 3'. + t.vertices[0] = apex; + t.vertices[1] = org; + t.vertices[2] = dest; + + return t; + } + } +} diff --git a/src/Triangle.Tests/Meshing/Algorithm/TriangulatorTest.cs b/src/Triangle.Tests/Meshing/Algorithm/TriangulatorTest.cs new file mode 100644 index 0000000..fa7f762 --- /dev/null +++ b/src/Triangle.Tests/Meshing/Algorithm/TriangulatorTest.cs @@ -0,0 +1,72 @@ +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; +using TriangleNet.Geometry; +using TriangleNet.Meshing.Algorithm; + +namespace TriangleNet.Tests.Meshing.Algorithm +{ + public class TriangulatorTest + { + [Test] + public void TestTriangulateIncremental() + { + var t = new Incremental(); + + var vertices = GetVertices(); + + var mesh = t.Triangulate(vertices, new Configuration()); + + Assert.AreEqual(6, vertices.Count); + Assert.AreEqual(6, mesh.Vertices.Count); + Assert.AreEqual(1, mesh.Vertices + .Where(v => v.Type == VertexType.UndeadVertex) + .Count()); + } + + [Test] + public void TestTriangulateSweepLine() + { + var t = new SweepLine(); + + var vertices = GetVertices(); + + var mesh = t.Triangulate(vertices, new Configuration()); + + Assert.AreEqual(6, vertices.Count); + Assert.AreEqual(6, mesh.Vertices.Count); + Assert.AreEqual(1, mesh.Vertices + .Where(v => v.Type == VertexType.UndeadVertex) + .Count()); + } + + [Test] + public void TestTriangulateDwyer() + { + var t = new Dwyer(); + + var vertices = GetVertices(); + + var mesh = t.Triangulate(vertices, new Configuration()); + + Assert.AreEqual(6, vertices.Count); + Assert.AreEqual(6, mesh.Vertices.Count); + Assert.AreEqual(1, mesh.Vertices + .Where(v => v.Type == VertexType.UndeadVertex) + .Count()); + } + + private List GetVertices() + { + return new List() + { + new Vertex(0.0, 0.0), + new Vertex(1.0, 0.0), + new Vertex(1.0, 1.0), + new Vertex(1.0, 1.0), // duplicate + new Vertex(0.0, 1.0), + new Vertex(0.5, 0.5) + }; + } + } +} diff --git a/src/Triangle.Tests/Meshing/Iterators/RegionIteratorTest.cs b/src/Triangle.Tests/Meshing/Iterators/RegionIteratorTest.cs new file mode 100644 index 0000000..1d799e8 --- /dev/null +++ b/src/Triangle.Tests/Meshing/Iterators/RegionIteratorTest.cs @@ -0,0 +1,35 @@ +using NUnit.Framework; +using TriangleNet.Geometry; +using TriangleNet.Meshing.Iterators; +using TriangleNet.Tools; +using TriangleNet.Topology; + +namespace TriangleNet.Tests.Meshing.Iterators +{ + public class RegionIteratorTest + { + public void TestProcessRegionProtected() + { + var poly = new Polygon(); + + // Outer region. + poly.Add(Helper.Rectangle(-2d, 2d, 2d, -2d, 2)); + + // Inner region. + poly.Add(Helper.Rectangle(-1d, 1d, 1d, -1d, 1)); + + poly.Regions.Add(new RegionPointer(0d, 0d, 1)); + + var mesh = (Mesh)poly.Triangulate(); + + var iterator = new RegionIterator(mesh); + + var qtree = new TriangleQuadTree(mesh); + + // Find a seeding triangle in region 1. + var seed = (Triangle)qtree.Query(0.0, 0.0); + + iterator.Process(seed, t => Assert.AreEqual(1, t.Label)); + } + } +} diff --git a/src/Triangle.Tests/RobustPredicatesTest.cs b/src/Triangle.Tests/RobustPredicatesTest.cs new file mode 100644 index 0000000..8f0b0c4 --- /dev/null +++ b/src/Triangle.Tests/RobustPredicatesTest.cs @@ -0,0 +1,55 @@ +using NUnit.Framework; +using TriangleNet.Geometry; + +namespace TriangleNet.Tests +{ + public class RobustPredicatesTest + { + [Test] + public void TestCounterClockwise() + { + var robust = RobustPredicates.Default; + + var a = new Point(-1d, 0d); + var b = new Point( 0d, 1d); + + Assert.IsTrue(robust.CounterClockwise(a, b, new Point(1d, 0d)) < 0d); + Assert.IsTrue(robust.CounterClockwise(a, b, new Point(0d, 2d)) > 0d); + Assert.IsTrue(robust.CounterClockwise(a, b, new Point(1d, 2d)) == 0d); + } + + [Test] + public void TestInCircle() + { + var robust = RobustPredicates.Default; + + var a = new Point(-1d, 0d); + var b = new Point(0d, 1d); + var c = new Point(1d, 0d); + + Assert.IsTrue(robust.InCircle(a, b, c, new Point(0d, 0.5)) < 0d); + Assert.IsTrue(robust.InCircle(a, b, c, new Point(0d, 1.5)) > 0d); + Assert.IsTrue(robust.InCircle(a, b, c, new Point(0d, 1d)) == 0d); + } + + [Test] + public void TestFindCircumcenter() + { + var robust = RobustPredicates.Default; + + var a = new Point(-1d, 0d); + var b = new Point(0d, 1d); + var c = new Point(1d, 0d); + + double xi = 0d, eta = 0d; + + var actual = robust.FindCircumcenter(a, b, c, ref xi, ref eta); + var expected = new Point(0d, 0d); + + Assert.AreEqual(expected.X, actual.X); + Assert.AreEqual(expected.Y, actual.Y); + Assert.AreEqual(0.0, xi); + Assert.AreEqual(0.5, eta); + } + } +} diff --git a/src/Triangle.Tests/Smoothing/SimpleSmootherTest.cs b/src/Triangle.Tests/Smoothing/SimpleSmootherTest.cs new file mode 100644 index 0000000..318cb87 --- /dev/null +++ b/src/Triangle.Tests/Smoothing/SimpleSmootherTest.cs @@ -0,0 +1,71 @@ +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; +using TriangleNet.Geometry; +using TriangleNet.Meshing; +using TriangleNet.Smoothing; + +namespace TriangleNet.Tests.Smoothing +{ + public class SimpleSmootherTest + { + [Test] + public void TestSmoothWithDuplicate() + { + var poly = GetPolygon(); + + var options = new ConstraintOptions() { ConformingDelaunay = true }; + + var quality = new QualityOptions() + { + MinimumAngle = 30.0 + }; + + var mesh = poly.Triangulate(options, quality); + + Assert.AreEqual(1, mesh.Vertices + .Where(v => v.Type == VertexType.UndeadVertex) + .Count()); + + quality.MaximumArea = 0.2; + + mesh.Refine(quality, true); + + Assert.AreEqual(1, mesh.Vertices + .Where(v => v.Type == VertexType.UndeadVertex) + .Count()); + + var smoother = new SimpleSmoother(); + + // Smooth mesh. + smoother.Smooth(mesh, 25); + } + + private Polygon GetPolygon() + { + var poly = new Polygon(7); + + var p = new List() + { + new Vertex(0.0, 0.0, 1), + new Vertex(2.0, 0.0, 1), + new Vertex(2.0, 1.0, 1), + new Vertex(2.0, 2.0, 1), + new Vertex(0.0, 2.0, 1), + new Vertex(1.5, 1.6, 1), + new Vertex(2.0, 0.0, 1) // duplicate + }; + + poly.Points.AddRange(p); + + poly.Add(new Segment(p[0], p[1], 1)); + poly.Add(new Segment(p[1], p[2], 1)); + poly.Add(new Segment(p[2], p[3], 1)); + poly.Add(new Segment(p[3], p[4], 1)); + poly.Add(new Segment(p[4], p[0], 1)); + poly.Add(new Segment(p[2], p[5], 2)); + + return poly; + } + } +} diff --git a/src/Triangle.Tests/Tools/IntersectionHelperTest.cs b/src/Triangle.Tests/Tools/IntersectionHelperTest.cs new file mode 100644 index 0000000..d8f2c69 --- /dev/null +++ b/src/Triangle.Tests/Tools/IntersectionHelperTest.cs @@ -0,0 +1,41 @@ +using NUnit.Framework; +using TriangleNet.Geometry; +using TriangleNet.Tools; + +namespace TriangleNet.Tests.Tools +{ + public class IntersectionHelperTest + { + [Test] + public void TestIsPointOnSegment() + { + var a = new Vertex(1.0, 1.0); + var b = new Vertex(2.0, 2.0); + + // Test point = segment start point. + Assert.IsTrue(IntersectionHelper.IsPointOnSegment(a, b, new Vertex(1.0, 1.0))); + + // Test point = segment end point. + Assert.IsTrue(IntersectionHelper.IsPointOnSegment(a, b, new Vertex(2.0, 2.0))); + + // Test point on segment. + Assert.IsTrue(IntersectionHelper.IsPointOnSegment(a, b, new Vertex(1.5, 1.5))); + + // Test point collinear, but not on segment. + Assert.IsFalse(IntersectionHelper.IsPointOnSegment(a, b, new Vertex(0.0, 0.0))); + Assert.IsFalse(IntersectionHelper.IsPointOnSegment(a, b, new Vertex(3.0, 3.0))); + + // Test point not on segment. + Assert.IsFalse(IntersectionHelper.IsPointOnSegment(a, b, new Vertex(1.5, 0.5))); + + double eps = 1e-12; + + // Test point collinear near endpoint, but not on segment. + Assert.IsFalse(IntersectionHelper.IsPointOnSegment(a, b, new Vertex(2.0 + eps, 2.0 + eps))); + Assert.IsFalse(IntersectionHelper.IsPointOnSegment(a, b, new Vertex(2.0 - eps, 2.0 + eps))); + + // Test point collinear near endpoint on segment. + Assert.IsTrue(IntersectionHelper.IsPointOnSegment(a, b, new Vertex(2.0 - eps, 2.0 - eps))); + } + } +} diff --git a/src/Triangle.Tests/Topology/OtriTest.cs b/src/Triangle.Tests/Topology/OtriTest.cs new file mode 100644 index 0000000..6719bfa --- /dev/null +++ b/src/Triangle.Tests/Topology/OtriTest.cs @@ -0,0 +1,496 @@ + +namespace TriangleNet.Tests +{ + using NUnit.Framework; + using TriangleNet.Geometry; + using TriangleNet.Topology; + + // The Otri (orientent triangle) struct is the heart of Triangle's mesh + // datastructure. It basically represents one of the three edges of the + // triangle by associating an orientation to the triangle object. + // + // For testing, a minimal example mesh is considered. It consists of + // 6 vertices and 4 triangles. + // + // Vertices: + // X [-2] [0] [2] [-1] [1] [0] + // Y [ 0] [0] [0] [ 1] [1] [2] + // + // Triangles and neighbors and neighbor orientations: + // [3 0 1] [-1 1 -1] [- 2 -] + // [3 1 4] [ 2 3 0] [2 0 1] + // [4 1 2] [-1 -1 1] [- - 0] + // [5 3 4] [ 1 -1 -1] [1 - -] + // + // The shape is one large triangle, split into four smaller ones. + + public class OtriTest + { + // The vertices of the mesh. + Vertex[] vertices; + + private Triangle[] CreateExampleMesh() + { + var triangles = new Triangle[4]; + + // Outer space triangle. + var dummy = new Triangle() { id = -1 }; + + // Setup triangles. Keep in mind the ordering: + // + // The CreateTriangle method takes vertex arguments in range org-dest-apex, + // but internally the vertices get stored in an array with ordering + // + // [0] = apex + // [1] = org + // [2] = dest + // + // This is because, for example, and oriented triange will return as its + // origin (Org() method) the element at index [(orientation + 1) % 3]. + var t0 = triangles[0] = Helper.CreateTriangle(0, vertices[0], vertices[1], vertices[3]); + var t1 = triangles[1] = Helper.CreateTriangle(1, vertices[1], vertices[4], vertices[3]); + var t2 = triangles[2] = Helper.CreateTriangle(2, vertices[1], vertices[2], vertices[4]); + var t3 = triangles[3] = Helper.CreateTriangle(3, vertices[3], vertices[4], vertices[5]); + + // Setup connectivity of triangle 0. + t0.neighbors[0].tri = dummy; + t0.neighbors[1].tri = t1; + t0.neighbors[1].orient = 2; + t0.neighbors[2].tri = dummy; + + // Setup connectivity of triangle 1. + t1.neighbors[0].tri = t2; + t1.neighbors[0].orient = 2; + t1.neighbors[1].tri = t3; + t1.neighbors[1].orient = 0; + t1.neighbors[2].tri = t0; + t1.neighbors[2].orient = 1; + + // Setup connectivity of triangle 2. + t2.neighbors[0].tri = dummy; + t2.neighbors[1].tri = dummy; + t2.neighbors[2].tri = t1; + t2.neighbors[2].orient = 0; + + // Setup connectivity of triangle 3. + t3.neighbors[0].tri = t1; + t3.neighbors[0].orient = 1; + t3.neighbors[1].tri = dummy; + t3.neighbors[2].tri = dummy; + + return triangles; + } + + [SetUp] + public void Initialize() + { + vertices = new Vertex[6]; + + vertices[0] = new Vertex(-2.0, 0.0) { id = 0 }; + vertices[1] = new Vertex( 0.0, 0.0) { id = 1 }; + vertices[2] = new Vertex( 2.0, 0.0) { id = 2 }; + vertices[3] = new Vertex(-1.0, 1.0) { id = 3 }; + vertices[4] = new Vertex( 1.0, 1.0) { id = 4 }; + vertices[5] = new Vertex( 0.0, 2.0) { id = 5 }; + } + + [Test] + public void TestOrg() + { + Otri t = default; + + t.tri = Helper.CreateTriangle(0, vertices[1], vertices[4], vertices[3]); + + t.orient = 0; + Assert.AreEqual(1, t.Org().ID); + + t.orient = 1; + Assert.AreEqual(4, t.Org().ID); + + t.orient = 2; + Assert.AreEqual(3, t.Org().ID); + } + + [Test] + public void TestDest() + { + Otri t = default; + + t.tri = Helper.CreateTriangle(0, vertices[1], vertices[4], vertices[3]); + + t.orient = 0; + Assert.AreEqual(4, t.Dest().ID); + + t.orient = 1; + Assert.AreEqual(3, t.Dest().ID); + + t.orient = 2; + Assert.AreEqual(1, t.Dest().ID); + } + + [Test] + public void TestApex() + { + Otri t = default; + + t.tri = Helper.CreateTriangle(0, vertices[1], vertices[4], vertices[3]); + + t.orient = 0; + Assert.AreEqual(3, t.Apex().ID); + + t.orient = 1; + Assert.AreEqual(1, t.Apex().ID); + + t.orient = 2; + Assert.AreEqual(4, t.Apex().ID); + } + + [Test] + public void TestSym() + { + var triangles = CreateExampleMesh(); + + Otri t = default; + Otri s = default; + + // The center triangle. + t.tri = triangles[1]; + + t.orient = 0; + t.Sym(ref s); + Assert.AreEqual(2, s.tri.ID); + + t.orient = 1; + t.Sym(ref s); + Assert.AreEqual(3, s.tri.ID); + + t.orient = 2; + t.Sym(ref s); + Assert.AreEqual(0, s.tri.ID); + } + + [Test] + public void TestLnext() + { + var triangles = CreateExampleMesh(); + + Otri t = default; + + // The center triangle. + t.tri = triangles[1]; + t.orient = 0; + + t.Lnext(); + Assert.AreEqual(4, t.Org().ID); + + t.Lnext(); + Assert.AreEqual(3, t.Org().ID); + + t.Lnext(); + Assert.AreEqual(1, t.Org().ID); + } + + [Test] + public void TestLprev() + { + var triangles = CreateExampleMesh(); + + Otri t = default; + + // The center triangle. + t.tri = triangles[1]; + t.orient = 0; + + t.Lprev(); + Assert.AreEqual(3, t.Org().ID); + + t.Lprev(); + Assert.AreEqual(4, t.Org().ID); + + t.Lprev(); + Assert.AreEqual(1, t.Org().ID); + } + + [Test] + public void TestOnext() + { + var triangles = CreateExampleMesh(); + + Otri t = default; + + // Start with the bottom right triangle. + t.tri = triangles[2]; + + // Start with edge 1 -> 2. + t.orient = 0; + + // Make sure we're on the correct edge. + Assert.AreEqual(1, t.Org().ID); + Assert.AreEqual(2, t.Dest().ID); + + t.Onext(); + Assert.AreEqual(1, t.Org().ID); + Assert.AreEqual(4, t.Dest().ID); + Assert.AreEqual(1, t.tri.ID); + + t.Onext(); + Assert.AreEqual(1, t.Org().ID); + Assert.AreEqual(3, t.Dest().ID); + Assert.AreEqual(0, t.tri.ID); + + // Out of mesh. + t.Onext(); + Assert.AreEqual(-1, t.tri.ID); + } + + [Test] + public void TestOprev() + { + var triangles = CreateExampleMesh(); + + Otri t = default; + + // Start with the bottom left triangle. + t.tri = triangles[0]; + + // Start with edge 1 -> 3. + t.orient = 1; + + // Make sure we're on the correct edge. + Assert.AreEqual(1, t.Org().ID); + Assert.AreEqual(3, t.Dest().ID); + + t.Oprev(); + Assert.AreEqual(1, t.Org().ID); + Assert.AreEqual(4, t.Dest().ID); + Assert.AreEqual(1, t.tri.ID); + + t.Oprev(); + Assert.AreEqual(1, t.Org().ID); + Assert.AreEqual(2, t.Dest().ID); + Assert.AreEqual(2, t.tri.ID); + + // Out of mesh. + t.Oprev(); + Assert.AreEqual(-1, t.tri.ID); + } + + [Test] + public void TestDnext() + { + var triangles = CreateExampleMesh(); + + Otri t = default; + + // Start with the bottom left triangle. + t.tri = triangles[0]; + + // Start with edge 1 -> 3. + t.orient = 1; + + // Make sure we're on the correct edge. + Assert.AreEqual(1, t.Org().ID); + Assert.AreEqual(3, t.Dest().ID); + + t.Dnext(); + Assert.AreEqual(4, t.Org().ID); + Assert.AreEqual(3, t.Dest().ID); + Assert.AreEqual(1, t.tri.ID); + + t.Dnext(); + Assert.AreEqual(5, t.Org().ID); + Assert.AreEqual(3, t.Dest().ID); + Assert.AreEqual(3, t.tri.ID); + + // Out of mesh. + t.Dnext(); + Assert.AreEqual(-1, t.tri.ID); + } + + [Test] + public void TestDprev() + { + var triangles = CreateExampleMesh(); + + Otri t = default; + + // Start with the top triangle. + t.tri = triangles[3]; + + // Start with edge 5 -> 3. + t.orient = 2; + + // Make sure we're on the correct edge. + Assert.AreEqual(5, t.Org().ID); + Assert.AreEqual(3, t.Dest().ID); + + t.Dprev(); + Assert.AreEqual(4, t.Org().ID); + Assert.AreEqual(3, t.Dest().ID); + Assert.AreEqual(1, t.tri.ID); + + t.Dprev(); + Assert.AreEqual(1, t.Org().ID); + Assert.AreEqual(3, t.Dest().ID); + Assert.AreEqual(0, t.tri.ID); + + // Out of mesh. + t.Dprev(); + Assert.AreEqual(-1, t.tri.ID); + } + + [Test] + public void TestRnext() + { + var triangles = CreateExampleMesh(); + + Otri t = default; + + // Start with the bottom left triangle. + t.tri = triangles[0]; + + // Start with edge 1 -> 3. + t.orient = 1; + + // Make sure we're on the correct edge. + Assert.AreEqual(1, t.Org().ID); + Assert.AreEqual(3, t.Dest().ID); + + t.Rnext(); + Assert.AreEqual(4, t.Org().ID); + Assert.AreEqual(1, t.Dest().ID); + Assert.AreEqual(2, t.tri.ID); + + t.Rnext(); + Assert.AreEqual(3, t.Org().ID); + Assert.AreEqual(4, t.Dest().ID); + Assert.AreEqual(3, t.tri.ID); + + // Back where we started. + t.Rnext(); + Assert.AreEqual(0, t.tri.ID); + } + + [Test] + public void TestRprev() + { + var triangles = CreateExampleMesh(); + + Otri t = default; + + // Start with the top triangle. + t.tri = triangles[3]; + + // Start with edge 3 -> 4. + t.orient = 0; + + // Make sure we're on the correct edge. + Assert.AreEqual(3, t.Org().ID); + Assert.AreEqual(4, t.Dest().ID); + + t.Rprev(); + Assert.AreEqual(4, t.Org().ID); + Assert.AreEqual(1, t.Dest().ID); + Assert.AreEqual(2, t.tri.ID); + + t.Rprev(); + Assert.AreEqual(1, t.Org().ID); + Assert.AreEqual(3, t.Dest().ID); + Assert.AreEqual(0, t.tri.ID); + + // Back where we started. + t.Rprev(); + Assert.AreEqual(3, t.tri.ID); + } + + [Test] + public void TestBond() + { + Otri s = default; + Otri t = default; + + Otri tmp = default; + + s.tri = Helper.CreateTriangle(0, vertices[0], vertices[1], vertices[3]); + t.tri = Helper.CreateTriangle(1, vertices[1], vertices[4], vertices[3]); + + s.orient = 1; // Edge 1 -> 3. + t.orient = 2; // Edge 3 -> 1. + + // Make sure we're on the correct edges. + Assert.AreEqual(1, s.Org().ID); + Assert.AreEqual(3, s.Dest().ID); + Assert.AreEqual(3, t.Org().ID); + Assert.AreEqual(1, t.Dest().ID); + + // Check that the triangles don't have neighbors. + s.Sym(ref tmp); + //Assert.AreEqual(-1, tmp.tri.ID); + Assert.IsNull(tmp.tri); + t.Sym(ref tmp); + //Assert.AreEqual(-1, tmp.tri.ID); + Assert.IsNull(tmp.tri); + + // Bond the two triangles. + s.Bond(ref t); + + // Check that neighbors are properly set. + s.Sym(ref tmp); + Assert.AreEqual(1, tmp.tri.ID); + t.Sym(ref tmp); + Assert.AreEqual(0, tmp.tri.ID); + } + + [Test] + public void TestDissolve() + { + // Outer space triangle. + var dummy = new Triangle() { id = -1 }; + + var triangles = CreateExampleMesh(); + + Otri s = default; + Otri t = default; + + Otri tmp = default; + + // The bottom left triangle with edge 1 -> 3. + s.tri = triangles[0]; + s.orient = 1; + + // The center triangle with edge 3 -> 1. + t.tri = triangles[1]; + t.orient = 2; + + // Make sure we're on the correct edges. + Assert.AreEqual(1, s.Org().ID); + Assert.AreEqual(3, s.Dest().ID); + Assert.AreEqual(3, t.Org().ID); + Assert.AreEqual(1, t.Dest().ID); + + // Check that neighbors are properly set. + s.Sym(ref tmp); + Assert.AreEqual(1, tmp.tri.ID); + t.Sym(ref tmp); + Assert.AreEqual(0, tmp.tri.ID); + + // Now dissolve the bond from one side. + s.Dissolve(dummy); + + // Check neighbors. + s.Sym(ref tmp); + Assert.AreEqual(-1, tmp.tri.ID); + t.Sym(ref tmp); + Assert.AreEqual(0, tmp.tri.ID); + + // And dissolve the bond from the other side. + t.Dissolve(dummy); + + // Check neighbors. + s.Sym(ref tmp); + Assert.AreEqual(-1, tmp.tri.ID); + t.Sym(ref tmp); + Assert.AreEqual(-1, tmp.tri.ID); + } + } +} diff --git a/src/Triangle.Tests/Triangle.Tests.csproj b/src/Triangle.Tests/Triangle.Tests.csproj new file mode 100644 index 0000000..47b78b0 --- /dev/null +++ b/src/Triangle.Tests/Triangle.Tests.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + TriangleNet.Tests + Triangle.Tests + false + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Triangle.Viewer.sln b/src/Triangle.Viewer.sln new file mode 100644 index 0000000..23fae3d --- /dev/null +++ b/src/Triangle.Viewer.sln @@ -0,0 +1,51 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32126.317 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Triangle.Viewer", "Triangle.Viewer\Triangle.Viewer.csproj", "{336AAF8A-5316-4303-9E73-5E38BD0B28AF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Triangle.Rendering", "Triangle.Rendering\Triangle.Rendering.csproj", "{41022E0E-BD0F-439E-BC3A-AABB1B43471B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Triangle.Rendering.GDI", "Triangle.Rendering.GDI\Triangle.Rendering.GDI.csproj", "{D535E102-92B4-4F92-B284-A7170AE00CD6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Debug|x64.ActiveCfg = Debug|x64 + {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Debug|x64.Build.0 = Debug|x64 + {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Release|Any CPU.Build.0 = Release|Any CPU + {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Release|x64.ActiveCfg = Release|x64 + {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Release|x64.Build.0 = Release|x64 + {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Debug|x64.ActiveCfg = Debug|x64 + {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Debug|x64.Build.0 = Debug|x64 + {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Release|Any CPU.Build.0 = Release|Any CPU + {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Release|x64.ActiveCfg = Release|x64 + {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Release|x64.Build.0 = Release|x64 + {D535E102-92B4-4F92-B284-A7170AE00CD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D535E102-92B4-4F92-B284-A7170AE00CD6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D535E102-92B4-4F92-B284-A7170AE00CD6}.Debug|x64.ActiveCfg = Debug|x64 + {D535E102-92B4-4F92-B284-A7170AE00CD6}.Debug|x64.Build.0 = Debug|x64 + {D535E102-92B4-4F92-B284-A7170AE00CD6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D535E102-92B4-4F92-B284-A7170AE00CD6}.Release|Any CPU.Build.0 = Release|Any CPU + {D535E102-92B4-4F92-B284-A7170AE00CD6}.Release|x64.ActiveCfg = Release|x64 + {D535E102-92B4-4F92-B284-A7170AE00CD6}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {90F4F9E9-43D6-4A3D-91D5-B2DF113E13AE} + EndGlobalSection +EndGlobal diff --git a/Triangle.NET/TestApp/Controls/AngleHistogram.cs b/src/Triangle.Viewer/Controls/AngleHistogram.cs similarity index 68% rename from Triangle.NET/TestApp/Controls/AngleHistogram.cs rename to src/Triangle.Viewer/Controls/AngleHistogram.cs index 3837e40..d6a6fc4 100644 --- a/Triangle.NET/TestApp/Controls/AngleHistogram.cs +++ b/src/Triangle.Viewer/Controls/AngleHistogram.cs @@ -1,207 +1,207 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.Controls -{ - using System; - using System.Collections.Generic; - using System.Text; - using System.Drawing; - using System.Drawing.Drawing2D; - using System.Windows.Forms; - using System.Drawing.Text; - - /// - /// Displays an angle histogram. - /// - /// - /// The angle histogram is divided into two parts: the minimum angles - /// on the left side (0 to 60 degrees) and the maximum angles on the - /// right (60 to 180 degrees). - /// - public class AngleHistogram : Control - { - #region Designer - - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Component Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - components = new System.ComponentModel.Container(); - } - - #endregion - - #endregion - - int[] maxAngles; - int[] minAngles; - - Brush fillBlue1 = new SolidBrush(Color.FromArgb(60, 100, 140)); - Brush fillBlue2 = new SolidBrush(Color.FromArgb(110, 150, 200)); - - Brush textBack = new SolidBrush(Color.FromArgb(72, 0, 0, 0)); - - // The maximum number of angles - int maxAngleCount = 0; - - /// - /// Initializes a new instance of the control. - /// - public AngleHistogram() - { - this.BackColor = ColorScheme.ColorGray78; - InitializeComponent(); - } - - /// - /// Updates the histogram data and invalidates the control. - /// - public void SetData(int[] dataMin, int[] dataMax) - { - maxAngleCount = 0; - - this.minAngles = dataMin; - this.maxAngles = dataMax; - - ParseData(dataMin); - ParseData(dataMax); - - if (maxAngleCount == 0) - { - this.maxAngles = null; - return; - } - - this.Invalidate(); - } - - private void ParseData(int[] data) - { - if (data != null) - { - for (int i = 0; i < data.Length; i++) - { - if (data[i] > maxAngleCount) - { - maxAngleCount = data[i]; - } - } - } - } - - int padding = 1; - int paddingBottom = 0; - int paddingTop = 15; - - private void DrawHistogram(Graphics g, int offset, int left, int size, int[] data, Brush brush, Brush brushTop) - { - int count = maxAngleCount; - int totalHeight = this.Height - paddingBottom - paddingTop; - - int n = offset == 0 ? data.Length / 3 : data.Length; - float value = 0; - - for (int i = offset; i < n; i++) - { - if (data[i] > 0) - { - // Scale to control height - value = totalHeight * data[i] / count; - - // Fill bar - g.FillRectangle(brush, - left + i * size, this.Height - paddingBottom - value, - size - 1, value); - - // Draw top of bar (just a little effect ...) - if (value > 2) - { - g.FillRectangle(brushTop, - left + i * size, this.Height - paddingBottom - value, - size - 1, 2); - } - } - } - } - - /// - /// Draws the labels on the bottom. - /// - private void DrawStrings(Graphics g, SizeF fSize, int size, int middle) - { - int fHeight = (int)(fSize.Height + 2); - g.FillRectangle(textBack, 0, this.Height - fHeight, this.Width, fHeight); - - g.DrawString("0", this.Font, Brushes.White, padding, this.Height - fSize.Height - 1); - g.DrawString("60", this.Font, Brushes.White, - this.minAngles.Length * size / 3.0f - 2 * fSize.Width, - this.Height - fSize.Height - 1); - - g.DrawString("60", this.Font, Brushes.White, middle, this.Height - fSize.Height - 1); - g.DrawString("180", this.Font, Brushes.White, - this.Width - 3 * fSize.Width, - this.Height - fSize.Height - 1); - } - - protected override void OnPaint(PaintEventArgs e) - { - Graphics g = e.Graphics; - - g.FillRectangle(new SolidBrush(this.BackColor), this.ClientRectangle); - - if (this.minAngles == null || this.maxAngles == null) - { - return; - } - - SizeF fSize = g.MeasureString("0", this.Font, this.Width); - - int n = this.minAngles.Length; - - // Hack --- TODO: Change stats class - if (n != this.maxAngles.Length) - { - n = this.minAngles.Length + this.maxAngles.Length; - } - - // Each bar takes up this space - int size = (this.Width - 2 * padding) / (n + 1); - - // Make pixel align - int middle = this.Width - padding - n * size; - - DrawHistogram(g, 0, padding, size, this.minAngles, Brushes.DarkGreen, Brushes.Green); - DrawHistogram(g, n / 3, middle, size, this.maxAngles, fillBlue1, fillBlue2); - - g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; - - DrawStrings(g, fSize, size, middle + n / 3 * size); - } - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.Controls +{ + using System.Drawing; + using System.Drawing.Text; + using System.Windows.Forms; + + /// + /// Displays an angle histogram. + /// + /// + /// The angle histogram is divided into two parts: the minimum angles + /// on the left side (0 to 60 degrees) and the maximum angles on the + /// right (60 to 180 degrees). + /// + public class AngleHistogram : Control + { + #region Designer + + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + } + + #endregion + + #endregion + + int[] maxAngles; + int[] minAngles; + + Brush fillBlue1 = new SolidBrush(Color.FromArgb(60, 100, 140)); + Brush fillBlue2 = new SolidBrush(Color.FromArgb(110, 150, 200)); + + Brush textBack = new SolidBrush(Color.FromArgb(72, 0, 0, 0)); + + // The maximum number of angles + int maxAngleCount = 0; + + /// + /// Initializes a new instance of the control. + /// + public AngleHistogram() + { + BackColor = ColorScheme.ColorGray78; + InitializeComponent(); + } + + /// + /// Updates the histogram data and invalidates the control. + /// + public void SetData(int[] dataMin, int[] dataMax) + { + maxAngleCount = 0; + + minAngles = dataMin; + maxAngles = dataMax; + + ParseData(dataMin); + ParseData(dataMax); + + if (maxAngleCount == 0) + { + maxAngles = null; + return; + } + + Invalidate(); + } + + private void ParseData(int[] data) + { + if (data != null) + { + for (int i = 0; i < data.Length; i++) + { + if (data[i] > maxAngleCount) + { + maxAngleCount = data[i]; + } + } + } + } + + int padding = 1; + int paddingBottom = 0; + int paddingTop = 15; + + private void DrawHistogram(Graphics g, int offset, int left, int size, int[] data, Brush brush, Brush brushTop) + { + int count = maxAngleCount; + int totalHeight = Height - paddingBottom - paddingTop; + + int n = offset == 0 ? data.Length / 3 : data.Length; + + for (int i = offset; i < n; i++) + { + if (data[i] > 0) + { + // Scale to control height + float value = totalHeight * data[i] / count; + + // Fill bar + g.FillRectangle(brush, + left + i * size, Height - paddingBottom - value, + size - 1, value); + + // Draw top of bar (just a little effect ...) + if (value > 2) + { + g.FillRectangle(brushTop, + left + i * size, Height - paddingBottom - value, + size - 1, 2); + } + } + } + } + + /// + /// Draws the labels on the bottom. + /// + private void DrawStrings(Graphics g, SizeF fSize, int size, int middle) + { + int fHeight = (int)(fSize.Height + 2); + + g.FillRectangle(textBack, 0, Height - fHeight, Width, fHeight); + + var p = new Point(0, Height - (int)fSize.Height - 1); + + p.X = padding; + TextRenderer.DrawText(g, "0", Font, p, Color.White); + + p.X = (int)(minAngles.Length * size / 3.0f - 2 * fSize.Width); + TextRenderer.DrawText(g, "60", Font, p, Color.White); + + p.X = middle; + TextRenderer.DrawText(g, "60", Font, p, Color.White); + + p.X = (int)(Width - 3 * fSize.Width); + TextRenderer.DrawText(g, "180", Font, p, Color.White); + } + + protected override void OnPaint(PaintEventArgs e) + { + Graphics g = e.Graphics; + + g.FillRectangle(new SolidBrush(BackColor), ClientRectangle); + + if (minAngles == null || maxAngles == null) + { + return; + } + + int n = minAngles.Length; + + // Hack --- TODO: Change stats class + if (n != maxAngles.Length) + { + n = minAngles.Length + maxAngles.Length; + } + + // Each bar takes up this space + int size = (Width - 2 * padding) / (n + 1); + + // Make pixel align + int middle = Width - padding - n * size; + + DrawHistogram(g, 0, padding, size, minAngles, Brushes.DarkGreen, Brushes.Green); + DrawHistogram(g, n / 3, middle, size, maxAngles, fillBlue1, fillBlue2); + + g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; + + SizeF fSize = g.MeasureString("0", Font, Width); + + DrawStrings(g, fSize, size, middle + n / 3 * size); + } + } +} diff --git a/Triangle.NET/TestApp/Controls/ColorScheme.cs b/src/Triangle.Viewer/Controls/ColorScheme.cs similarity index 91% rename from Triangle.NET/TestApp/Controls/ColorScheme.cs rename to src/Triangle.Viewer/Controls/ColorScheme.cs index 3486b90..80dd676 100644 --- a/Triangle.NET/TestApp/Controls/ColorScheme.cs +++ b/src/Triangle.Viewer/Controls/ColorScheme.cs @@ -1,38 +1,34 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.Controls -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Drawing; - - /// - /// Dark user interface color scheme. - /// - public static class ColorScheme - { - public static Color ColorGray13 = Color.FromArgb(13, 13, 13); - public static Color ColorGray46 = Color.FromArgb(46, 46, 46); - public static Color ColorGray64 = Color.FromArgb(64, 64, 64); - public static Color ColorGray68 = Color.FromArgb(68, 68, 68); - public static Color ColorGray78 = Color.FromArgb(78, 78, 78); - public static Color ColorGray89 = Color.FromArgb(89, 89, 89); - public static Color ColorGray98 = Color.FromArgb(98, 98, 98); - public static Color ColorGray107 = Color.FromArgb(107, 107, 107); - public static Color ColorGray110 = Color.FromArgb(110, 110, 110); - public static Color ColorGray122 = Color.FromArgb(122, 122, 122); - - public static Brush BrushGray68 = new SolidBrush(ColorGray68); - public static Brush BrushGray78 = new SolidBrush(ColorGray78); - - // Linear gradient horizontal - public static Brush SliderBorderBrush = new SolidBrush(ColorGray46); - public static Brush SliderFillBrush = new SolidBrush(ColorGray89); - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.Controls +{ + using System.Drawing; + + /// + /// Dark user interface color scheme. + /// + public static class ColorScheme + { + public static Color ColorGray13 = Color.FromArgb(13, 13, 13); + public static Color ColorGray46 = Color.FromArgb(46, 46, 46); + public static Color ColorGray64 = Color.FromArgb(64, 64, 64); + public static Color ColorGray68 = Color.FromArgb(68, 68, 68); + public static Color ColorGray78 = Color.FromArgb(78, 78, 78); + public static Color ColorGray89 = Color.FromArgb(89, 89, 89); + public static Color ColorGray98 = Color.FromArgb(98, 98, 98); + public static Color ColorGray107 = Color.FromArgb(107, 107, 107); + public static Color ColorGray110 = Color.FromArgb(110, 110, 110); + public static Color ColorGray122 = Color.FromArgb(122, 122, 122); + + public static Brush BrushGray68 = new SolidBrush(ColorGray68); + public static Brush BrushGray78 = new SolidBrush(ColorGray78); + + // Linear gradient horizontal + public static Brush SliderBorderBrush = new SolidBrush(ColorGray46); + public static Brush SliderFillBrush = new SolidBrush(ColorGray89); + } +} diff --git a/Triangle.NET/TestApp/Controls/DarkButton.cs b/src/Triangle.Viewer/Controls/DarkButton.cs similarity index 95% rename from Triangle.NET/TestApp/Controls/DarkButton.cs rename to src/Triangle.Viewer/Controls/DarkButton.cs index 69d4f07..1c2dd91 100644 --- a/Triangle.NET/TestApp/Controls/DarkButton.cs +++ b/src/Triangle.Viewer/Controls/DarkButton.cs @@ -1,190 +1,186 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.Controls -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Drawing; - using System.Drawing.Drawing2D; - using System.Text; - using System.Windows.Forms; - - public class DarkButton : Button - { - #region Designer - - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Component Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - components = new System.ComponentModel.Container(); - } - - #endregion - - #endregion - - enum eButtonState { Normal, MouseOver, Down } - eButtonState m_State = eButtonState.Normal; - - // Make sure the control is invalidated when the text is changed. - public override string Text - { - get { return base.Text; } - set { base.Text = value; this.Invalidate(); } - } - - /// - /// Initializes a new instance of the control. - /// - public DarkButton() - { - InitializeComponent(); - } - - #region Control overrides - - protected override void OnPaint(PaintEventArgs e) - { - e.Graphics.SmoothingMode = SmoothingMode.HighQuality; - e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; - - float down = 0.0f; - - // Colors and brushes - Pen brushBorder = null; - LinearGradientMode mode = LinearGradientMode.Vertical; - LinearGradientBrush brushOuter = null; - LinearGradientBrush brushInner = null; - - Rectangle newRect = new Rectangle(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width - 1, ClientRectangle.Height - 1); - Color text_color = Color.White; - - if (Enabled) - { - if (base.Focused) - brushBorder = new Pen(Color.FromArgb(24, 24, 24), 1f); - else - brushBorder = new Pen(Color.FromArgb(56, 56, 56), 1f); - - switch (m_State) - { - case eButtonState.Normal: - brushOuter = new LinearGradientBrush(newRect, Color.FromArgb(123, 123, 123), Color.FromArgb(77, 77, 77), mode); - brushInner = new LinearGradientBrush(newRect, Color.FromArgb(104, 104, 104), Color.FromArgb(71, 71, 71), mode); - e.Graphics.FillRectangle(brushOuter, newRect); - newRect.Inflate(-1, -1); - e.Graphics.FillRectangle(brushInner, newRect); - newRect.Inflate(1, 1); - break; - - case eButtonState.MouseOver: - brushOuter = new LinearGradientBrush(newRect, Color.FromArgb(140, 140, 140), Color.FromArgb(87, 87, 87), mode); - brushInner = new LinearGradientBrush(newRect, Color.FromArgb(118, 118, 118), Color.FromArgb(81, 81, 81), mode); - e.Graphics.FillRectangle(brushOuter, newRect); - newRect.Inflate(-1, -1); - e.Graphics.FillRectangle(brushInner, newRect); - newRect.Inflate(1, 1); - break; - - case eButtonState.Down: - down = 1.0f; - brushOuter = new LinearGradientBrush(newRect, Color.FromArgb(108, 108, 108), Color.FromArgb(68, 68, 68), mode); - brushInner = new LinearGradientBrush(newRect, Color.FromArgb(92, 92, 92), Color.FromArgb(62, 62, 62), mode); - e.Graphics.FillRectangle(brushOuter, newRect); - newRect.Inflate(-1, -1); - e.Graphics.FillRectangle(brushInner, newRect); - newRect.Inflate(1, 1); - break; - } - - e.Graphics.DrawRectangle(brushBorder, newRect); - } - else - { - text_color = Color.FromArgb(110, 110, 110); - brushBorder = new Pen(Color.FromArgb(48, 48, 48), 1f); - brushOuter = new LinearGradientBrush(newRect, Color.FromArgb(82, 82, 82), Color.FromArgb(67, 67, 67), mode); - brushInner = new LinearGradientBrush(newRect, Color.FromArgb(76, 76, 76), Color.FromArgb(65, 65, 65), mode); - e.Graphics.FillRectangle(brushOuter, newRect); - newRect.Inflate(-1, -1); - e.Graphics.FillRectangle(brushInner, newRect); - newRect.Inflate(1, 1); - e.Graphics.DrawRectangle(brushBorder, newRect); - } - - - string largetext = this.Text; - - SizeF szL = e.Graphics.MeasureString(this.Text, base.Font, this.Width); - if (Enabled) - { - e.Graphics.DrawString(largetext, base.Font, Brushes.Black, - new RectangleF(new PointF((this.Width - szL.Width) / 2, (this.Height - szL.Height) / 2 + 1 + down), szL)); - } - e.Graphics.DrawString(largetext, base.Font, new SolidBrush(text_color), - new RectangleF(new PointF((this.Width - szL.Width) / 2, (this.Height - szL.Height) / 2 + down), szL)); - - brushOuter.Dispose(); - brushInner.Dispose(); - brushBorder.Dispose(); - } - - protected override void OnMouseLeave(System.EventArgs e) - { - m_State = eButtonState.Normal; - this.Invalidate(); - base.OnMouseLeave(e); - } - - protected override void OnMouseEnter(System.EventArgs e) - { - m_State = eButtonState.MouseOver; - this.Invalidate(); - base.OnMouseEnter(e); - } - - protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e) - { - m_State = eButtonState.MouseOver; - this.Invalidate(); - base.OnMouseUp(e); - } - - protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e) - { - m_State = eButtonState.Down; - this.Invalidate(); - base.OnMouseDown(e); - } - - #endregion - } +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.Controls +{ + using System.Drawing; + using System.Drawing.Drawing2D; + using System.Windows.Forms; + + public class DarkButton : Button + { + #region Designer + + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + } + + #endregion + + #endregion + + enum eButtonState { Normal, MouseOver, Down } + eButtonState m_State = eButtonState.Normal; + + // Make sure the control is invalidated when the text is changed. + public override string Text + { + get { return base.Text; } + set { base.Text = value; this.Invalidate(); } + } + + /// + /// Initializes a new instance of the control. + /// + public DarkButton() + { + InitializeComponent(); + } + + #region Control overrides + + protected override void OnPaint(PaintEventArgs e) + { + e.Graphics.SmoothingMode = SmoothingMode.HighQuality; + e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + + float down = 0.0f; + + // Colors and brushes + Pen brushBorder = null; + LinearGradientMode mode = LinearGradientMode.Vertical; + LinearGradientBrush brushOuter = null; + LinearGradientBrush brushInner = null; + + Rectangle newRect = new Rectangle(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width - 1, ClientRectangle.Height - 1); + Color text_color = Color.White; + + if (Enabled) + { + if (base.Focused) + brushBorder = new Pen(Color.FromArgb(24, 24, 24), 1f); + else + brushBorder = new Pen(Color.FromArgb(56, 56, 56), 1f); + + switch (m_State) + { + case eButtonState.Normal: + brushOuter = new LinearGradientBrush(newRect, Color.FromArgb(123, 123, 123), Color.FromArgb(77, 77, 77), mode); + brushInner = new LinearGradientBrush(newRect, Color.FromArgb(104, 104, 104), Color.FromArgb(71, 71, 71), mode); + e.Graphics.FillRectangle(brushOuter, newRect); + newRect.Inflate(-1, -1); + e.Graphics.FillRectangle(brushInner, newRect); + newRect.Inflate(1, 1); + break; + + case eButtonState.MouseOver: + brushOuter = new LinearGradientBrush(newRect, Color.FromArgb(140, 140, 140), Color.FromArgb(87, 87, 87), mode); + brushInner = new LinearGradientBrush(newRect, Color.FromArgb(118, 118, 118), Color.FromArgb(81, 81, 81), mode); + e.Graphics.FillRectangle(brushOuter, newRect); + newRect.Inflate(-1, -1); + e.Graphics.FillRectangle(brushInner, newRect); + newRect.Inflate(1, 1); + break; + + case eButtonState.Down: + down = 1.0f; + brushOuter = new LinearGradientBrush(newRect, Color.FromArgb(108, 108, 108), Color.FromArgb(68, 68, 68), mode); + brushInner = new LinearGradientBrush(newRect, Color.FromArgb(92, 92, 92), Color.FromArgb(62, 62, 62), mode); + e.Graphics.FillRectangle(brushOuter, newRect); + newRect.Inflate(-1, -1); + e.Graphics.FillRectangle(brushInner, newRect); + newRect.Inflate(1, 1); + break; + } + + e.Graphics.DrawRectangle(brushBorder, newRect); + } + else + { + text_color = Color.FromArgb(110, 110, 110); + brushBorder = new Pen(Color.FromArgb(48, 48, 48), 1f); + brushOuter = new LinearGradientBrush(newRect, Color.FromArgb(82, 82, 82), Color.FromArgb(67, 67, 67), mode); + brushInner = new LinearGradientBrush(newRect, Color.FromArgb(76, 76, 76), Color.FromArgb(65, 65, 65), mode); + e.Graphics.FillRectangle(brushOuter, newRect); + newRect.Inflate(-1, -1); + e.Graphics.FillRectangle(brushInner, newRect); + newRect.Inflate(1, 1); + e.Graphics.DrawRectangle(brushBorder, newRect); + } + + + string largetext = this.Text; + + SizeF szL = e.Graphics.MeasureString(this.Text, base.Font, this.Width); + if (Enabled) + { + e.Graphics.DrawString(largetext, base.Font, Brushes.Black, + new RectangleF(new PointF((this.Width - szL.Width) / 2, (this.Height - szL.Height) / 2 + 1 + down), szL)); + } + e.Graphics.DrawString(largetext, base.Font, new SolidBrush(text_color), + new RectangleF(new PointF((this.Width - szL.Width) / 2, (this.Height - szL.Height) / 2 + down), szL)); + + brushOuter.Dispose(); + brushInner.Dispose(); + brushBorder.Dispose(); + } + + protected override void OnMouseLeave(System.EventArgs e) + { + m_State = eButtonState.Normal; + this.Invalidate(); + base.OnMouseLeave(e); + } + + protected override void OnMouseEnter(System.EventArgs e) + { + m_State = eButtonState.MouseOver; + this.Invalidate(); + base.OnMouseEnter(e); + } + + protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e) + { + m_State = eButtonState.MouseOver; + this.Invalidate(); + base.OnMouseUp(e); + } + + protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e) + { + m_State = eButtonState.Down; + this.Invalidate(); + base.OnMouseDown(e); + } + + #endregion + } } \ No newline at end of file diff --git a/Triangle.NET/TestApp/Controls/DarkCheckBox.cs b/src/Triangle.Viewer/Controls/DarkCheckBox.cs similarity index 96% rename from Triangle.NET/TestApp/Controls/DarkCheckBox.cs rename to src/Triangle.Viewer/Controls/DarkCheckBox.cs index 726d43d..71793af 100644 --- a/Triangle.NET/TestApp/Controls/DarkCheckBox.cs +++ b/src/Triangle.Viewer/Controls/DarkCheckBox.cs @@ -1,239 +1,237 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.Controls -{ - using System; - using System.Collections.Generic; - using System.Text; - using System.Drawing; - using System.Drawing.Drawing2D; - using System.Windows.Forms; - using System.Drawing.Text; - - /// - /// Dark checkbox control. - /// - public class DarkCheckBox : ButtonBase - { - #region Designer - - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Component Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - components = new System.ComponentModel.Container(); - } - - #endregion - - #endregion - - enum eButtonState { Normal, MouseOver, Down } - eButtonState m_State = eButtonState.Normal; - - // Make sure the control is invalidated when the text is changed. - public override string Text - { - get { return base.Text; } - set { base.Text = value; this.Invalidate(); } - } - - int boxSize = 13; - - bool isChecked = false; - public bool Checked - { - get { return isChecked; } - set { isChecked = value; this.Invalidate(); } - } - - /// - /// Initializes a new instance of the control. - /// - public DarkCheckBox() - { - this.BackColor = Color.FromArgb(76, 76, 76); - InitializeComponent(); - } - - private void DrawText(Graphics g, Color forecolor, Point location) - { - if (this.UseCompatibleTextRendering) - { - //using (StringFormat stringFormat = this.CreateStringFormat()) - { - if (this.Enabled) - { - g.DrawString(this.Text, base.Font, new SolidBrush(forecolor), location.X, location.Y); - } - else - { - g.DrawString(this.Text, base.Font, new SolidBrush(forecolor), location.X, location.Y); - } - } - } - else - { - //TextFormatFlags textFormatFlags = this.CreateTextFormatFlags(); - if (this.Enabled) - { - TextRenderer.DrawText((IDeviceContext)g, this.Text, this.Font, location, forecolor); - } - else - { - //forecolor = TextRenderer.DisabledTextColor(this.BackColor); - TextRenderer.DrawText((IDeviceContext)g, this.Text, this.Font, location, forecolor); - } - } - } - - #region Control overrides - - protected override void OnPaint(PaintEventArgs e) - { - //base.OnPaint(e); - e.Graphics.FillRectangle(new SolidBrush(this.BackColor), this.ClientRectangle); - - Pen checkMark = new Pen(Color.White, 1.8f); - checkMark.StartCap = LineCap.Round; - checkMark.EndCap = LineCap.Round; - - // Colors and brushes - Pen brushBorder = null; - LinearGradientMode mode = LinearGradientMode.Vertical; - LinearGradientBrush brushOuter = null; - LinearGradientBrush brushInner = null; - - int y = (this.Height - boxSize) / 2; - - Rectangle newRect = new Rectangle(1, y, boxSize, boxSize); - Color text_color = Color.White; - - brushOuter = new LinearGradientBrush(newRect, ColorScheme.ColorGray107, ColorScheme.ColorGray110, mode); - e.Graphics.FillRectangle(brushOuter, newRect); - - newRect = new Rectangle(2, y + 1, boxSize - 3, boxSize - 3); - - if (Enabled) - { - if (base.Focused) - brushBorder = new Pen(Color.FromArgb(60, 60, 60), 1f); - else - brushBorder = new Pen(Color.FromArgb(38, 38, 38), 1f); - - switch (m_State) - { - case eButtonState.Normal: - brushInner = new LinearGradientBrush(newRect, Color.FromArgb(111, 111, 111), Color.FromArgb(80, 80, 80), mode); - e.Graphics.FillRectangle(brushInner, newRect); - break; - - case eButtonState.MouseOver: - brushInner = new LinearGradientBrush(newRect, Color.FromArgb(118, 118, 118), Color.FromArgb(81, 81, 81), mode); - e.Graphics.FillRectangle(brushInner, newRect); - break; - - case eButtonState.Down: - brushInner = new LinearGradientBrush(newRect, Color.FromArgb(92, 92, 92), Color.FromArgb(62, 62, 62), mode); - e.Graphics.FillRectangle(brushInner, newRect); - break; - } - - e.Graphics.DrawRectangle(brushBorder, newRect); - - } - else - { - brushInner = new LinearGradientBrush(newRect, Color.FromArgb(76, 76, 76), Color.FromArgb(65, 65, 65), mode); - e.Graphics.FillRectangle(brushInner, newRect); - - brushBorder = new Pen(Color.FromArgb(48, 48, 48), 1f); - e.Graphics.DrawRectangle(brushBorder, newRect); - - text_color = Color.FromArgb(160, 160, 160); - checkMark.Color = Color.FromArgb(180, 180, 180); - } - - e.Graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; - - SizeF szL = e.Graphics.MeasureString(this.Text, base.Font, this.Width); - DrawText(e.Graphics, text_color, new Point(boxSize + 4, (int)((this.Height - szL.Height) / 2) + 1)); - - if (this.isChecked) - { - e.Graphics.SmoothingMode = SmoothingMode.HighQuality; - - e.Graphics.DrawLine(checkMark, 4, newRect.Bottom - boxSize / 2, newRect.Left + boxSize / 2.5f, newRect.Bottom - 2); - e.Graphics.DrawLine(checkMark, newRect.Left + boxSize / 2.6f, newRect.Bottom - 2, newRect.Right, newRect.Top); - } - - if (brushOuter != null) brushOuter.Dispose(); - if (brushInner != null) brushInner.Dispose(); - if (brushBorder != null) brushBorder.Dispose(); - if (checkMark != null) checkMark.Dispose(); - } - - protected override void OnMouseLeave(System.EventArgs e) - { - m_State = eButtonState.Normal; - this.Invalidate(); - base.OnMouseLeave(e); - } - - protected override void OnMouseEnter(System.EventArgs e) - { - m_State = eButtonState.MouseOver; - this.Invalidate(); - base.OnMouseEnter(e); - } - - protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e) - { - m_State = eButtonState.MouseOver; - this.Invalidate(); - base.OnMouseUp(e); - } - - protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e) - { - m_State = eButtonState.Down; - this.Invalidate(); - base.OnMouseDown(e); - } - - protected override void OnClick(EventArgs e) - { - this.isChecked = !this.isChecked; - this.Invalidate(); - base.OnClick(e); - } - - #endregion - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.Controls +{ + using System; + using System.Drawing; + using System.Drawing.Drawing2D; + using System.Drawing.Text; + using System.Windows.Forms; + + /// + /// Dark checkbox control. + /// + public class DarkCheckBox : ButtonBase + { + #region Designer + + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + } + + #endregion + + #endregion + + enum eButtonState { Normal, MouseOver, Down } + eButtonState m_State = eButtonState.Normal; + + // Make sure the control is invalidated when the text is changed. + public override string Text + { + get { return base.Text; } + set { base.Text = value; this.Invalidate(); } + } + + int boxSize = 13; + + bool isChecked = false; + public bool Checked + { + get { return isChecked; } + set { isChecked = value; this.Invalidate(); } + } + + /// + /// Initializes a new instance of the control. + /// + public DarkCheckBox() + { + this.BackColor = Color.FromArgb(76, 76, 76); + InitializeComponent(); + } + + private void DrawText(Graphics g, Color forecolor, Point location) + { + if (this.UseCompatibleTextRendering) + { + //using (StringFormat stringFormat = this.CreateStringFormat()) + { + if (this.Enabled) + { + g.DrawString(this.Text, base.Font, new SolidBrush(forecolor), location.X, location.Y); + } + else + { + g.DrawString(this.Text, base.Font, new SolidBrush(forecolor), location.X, location.Y); + } + } + } + else + { + //TextFormatFlags textFormatFlags = this.CreateTextFormatFlags(); + if (this.Enabled) + { + TextRenderer.DrawText((IDeviceContext)g, this.Text, this.Font, location, forecolor); + } + else + { + //forecolor = TextRenderer.DisabledTextColor(this.BackColor); + TextRenderer.DrawText((IDeviceContext)g, this.Text, this.Font, location, forecolor); + } + } + } + + #region Control overrides + + protected override void OnPaint(PaintEventArgs e) + { + //base.OnPaint(e); + e.Graphics.FillRectangle(new SolidBrush(this.BackColor), this.ClientRectangle); + + Pen checkMark = new Pen(Color.White, 1.8f); + checkMark.StartCap = LineCap.Round; + checkMark.EndCap = LineCap.Round; + + // Colors and brushes + Pen brushBorder = null; + LinearGradientMode mode = LinearGradientMode.Vertical; + LinearGradientBrush brushOuter = null; + LinearGradientBrush brushInner = null; + + int y = (this.Height - boxSize) / 2; + + Rectangle newRect = new Rectangle(1, y, boxSize, boxSize); + Color text_color = Color.White; + + brushOuter = new LinearGradientBrush(newRect, ColorScheme.ColorGray107, ColorScheme.ColorGray110, mode); + e.Graphics.FillRectangle(brushOuter, newRect); + + newRect = new Rectangle(2, y + 1, boxSize - 3, boxSize - 3); + + if (Enabled) + { + if (base.Focused) + brushBorder = new Pen(Color.FromArgb(60, 60, 60), 1f); + else + brushBorder = new Pen(Color.FromArgb(38, 38, 38), 1f); + + switch (m_State) + { + case eButtonState.Normal: + brushInner = new LinearGradientBrush(newRect, Color.FromArgb(111, 111, 111), Color.FromArgb(80, 80, 80), mode); + e.Graphics.FillRectangle(brushInner, newRect); + break; + + case eButtonState.MouseOver: + brushInner = new LinearGradientBrush(newRect, Color.FromArgb(118, 118, 118), Color.FromArgb(81, 81, 81), mode); + e.Graphics.FillRectangle(brushInner, newRect); + break; + + case eButtonState.Down: + brushInner = new LinearGradientBrush(newRect, Color.FromArgb(92, 92, 92), Color.FromArgb(62, 62, 62), mode); + e.Graphics.FillRectangle(brushInner, newRect); + break; + } + + e.Graphics.DrawRectangle(brushBorder, newRect); + + } + else + { + brushInner = new LinearGradientBrush(newRect, Color.FromArgb(76, 76, 76), Color.FromArgb(65, 65, 65), mode); + e.Graphics.FillRectangle(brushInner, newRect); + + brushBorder = new Pen(Color.FromArgb(48, 48, 48), 1f); + e.Graphics.DrawRectangle(brushBorder, newRect); + + text_color = Color.FromArgb(160, 160, 160); + checkMark.Color = Color.FromArgb(180, 180, 180); + } + + e.Graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; + + SizeF szL = e.Graphics.MeasureString(this.Text, base.Font, this.Width); + DrawText(e.Graphics, text_color, new Point(boxSize + 4, (int)((this.Height - szL.Height) / 2) + 1)); + + if (this.isChecked) + { + e.Graphics.SmoothingMode = SmoothingMode.HighQuality; + + e.Graphics.DrawLine(checkMark, 4, newRect.Bottom - boxSize / 2, newRect.Left + boxSize / 2.5f, newRect.Bottom - 2); + e.Graphics.DrawLine(checkMark, newRect.Left + boxSize / 2.6f, newRect.Bottom - 2, newRect.Right, newRect.Top); + } + + if (brushOuter != null) brushOuter.Dispose(); + if (brushInner != null) brushInner.Dispose(); + if (brushBorder != null) brushBorder.Dispose(); + if (checkMark != null) checkMark.Dispose(); + } + + protected override void OnMouseLeave(System.EventArgs e) + { + m_State = eButtonState.Normal; + this.Invalidate(); + base.OnMouseLeave(e); + } + + protected override void OnMouseEnter(System.EventArgs e) + { + m_State = eButtonState.MouseOver; + this.Invalidate(); + base.OnMouseEnter(e); + } + + protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e) + { + m_State = eButtonState.MouseOver; + this.Invalidate(); + base.OnMouseUp(e); + } + + protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e) + { + m_State = eButtonState.Down; + this.Invalidate(); + base.OnMouseDown(e); + } + + protected override void OnClick(EventArgs e) + { + this.isChecked = !this.isChecked; + this.Invalidate(); + base.OnClick(e); + } + + #endregion + } +} diff --git a/Triangle.NET/TestApp/Controls/DarkListBox.cs b/src/Triangle.Viewer/Controls/DarkListBox.cs similarity index 85% rename from Triangle.NET/TestApp/Controls/DarkListBox.cs rename to src/Triangle.Viewer/Controls/DarkListBox.cs index a927deb..c5a8555 100644 --- a/Triangle.NET/TestApp/Controls/DarkListBox.cs +++ b/src/Triangle.Viewer/Controls/DarkListBox.cs @@ -1,82 +1,76 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.Controls -{ - using System; - using System.Collections.Generic; - using System.Text; - using System.Windows.Forms; - using System.Drawing; - - /// - /// Dark listbox control. - /// - public class DarkListBox : ListBox - { - Font _boldFont; - - /// - /// Initializes a new instance of the control. - /// - public DarkListBox() - { - _boldFont = new Font(base.Font.FontFamily, base.Font.Size, FontStyle.Bold); - - this.DrawMode = DrawMode.OwnerDrawVariable; - this.ItemHeight = 22; - this.FontChanged += new EventHandler(ListBoxFontChanged); - this.BackColor = Color.FromArgb(96, 96, 96); - } - - void ListBoxFontChanged(object sender, EventArgs e) - { - _boldFont = new Font(base.Font.FontFamily, base.Font.Size, FontStyle.Bold); - } - - protected override void OnMeasureItem(MeasureItemEventArgs e) - { - e.ItemHeight = 22; - } - - protected override void OnDrawItem(DrawItemEventArgs e) - { - if (this.Items.Count == 0) - { - return; - } - - e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; - - int index = e.Index; - - string content = "[Error]"; - - if (index < this.Items.Count && index >= 0) - { - content = this.Items[index].ToString(); - } - - Color color = (e.Index % 2) == 0 ? Color.FromArgb(85, 85, 85) : Color.FromArgb(90, 90, 90); - - if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) - { - color = Color.FromArgb(100, 105, 110); - } - - using (SolidBrush background = new SolidBrush(color)) - { - e.Graphics.FillRectangle(background, e.Bounds); - } - - using (SolidBrush pen = new SolidBrush(Color.White)) - { - e.Graphics.DrawString(content, this.Font, pen, - new PointF(10, e.Bounds.Y + 3), StringFormat.GenericDefault); - } - } - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.Controls +{ + using System; + using System.Drawing; + using System.Windows.Forms; + + /// + /// Dark listbox control. + /// + public class DarkListBox : ListBox + { + Font _boldFont; + + /// + /// Initializes a new instance of the control. + /// + public DarkListBox() + { + _boldFont = new Font(base.Font.FontFamily, base.Font.Size, FontStyle.Bold); + + this.DrawMode = DrawMode.OwnerDrawVariable; + this.ItemHeight = 22; + this.FontChanged += new EventHandler(ListBoxFontChanged); + this.BackColor = Color.FromArgb(96, 96, 96); + } + + void ListBoxFontChanged(object sender, EventArgs e) + { + _boldFont = new Font(base.Font.FontFamily, base.Font.Size, FontStyle.Bold); + } + + protected override void OnMeasureItem(MeasureItemEventArgs e) + { + e.ItemHeight = 22; + } + + protected override void OnDrawItem(DrawItemEventArgs e) + { + if (this.Items.Count == 0) + { + return; + } + + e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + + int index = e.Index; + + string content = "[Error]"; + + if (index < this.Items.Count && index >= 0) + { + content = this.Items[index].ToString(); + } + + Color color = (e.Index % 2) == 0 ? Color.FromArgb(85, 85, 85) : Color.FromArgb(90, 90, 90); + + if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) + { + color = Color.FromArgb(100, 105, 110); + } + + using (SolidBrush background = new SolidBrush(color)) + { + e.Graphics.FillRectangle(background, e.Bounds); + } + + TextRenderer.DrawText(e.Graphics, content, Font, new Point(10, e.Bounds.Y + 3), Color.White, TextFormatFlags.EndEllipsis); + } + } +} diff --git a/Triangle.NET/TestApp/Controls/DarkSlider.cs b/src/Triangle.Viewer/Controls/DarkSlider.cs similarity index 96% rename from Triangle.NET/TestApp/Controls/DarkSlider.cs rename to src/Triangle.Viewer/Controls/DarkSlider.cs index d2f67ac..6f6f83a 100644 --- a/Triangle.NET/TestApp/Controls/DarkSlider.cs +++ b/src/Triangle.Viewer/Controls/DarkSlider.cs @@ -1,453 +1,450 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// Original code on CodeProject: Owner-drawn trackbar (slider), Michal Brylka -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.Controls -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Windows.Forms; - using System.Drawing; - using System.Drawing.Drawing2D; - - /// - /// Encapsulates control that visualy displays certain integer value and allows user to change - /// it within desired range. It imitates as far as - /// mouse usage is concerned. - /// - public class DarkSlider : Control - { - #region Designer - - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Component Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - components = new System.ComponentModel.Container(); - } - - #endregion - - #endregion - - #region Events - - /// - /// Fires when Slider position has changed - /// - public event EventHandler ValueChanging; - - private void OnValueChanging() - { - var evt = ValueChanging; - - if (evt != null) - { - evt(this, EventArgs.Empty); - } - } - - /// - /// Fires when Slider position has changed - /// - public event EventHandler ValueChanged; - - private void OnValueChanged() - { - var evt = ValueChanged; - - if (evt != null) - { - evt(this, EventArgs.Empty); - } - } - - #endregion - - #region Properties - - int thumbSize = 7; - private Rectangle thumbRect; //bounding rectangle of thumb area - private Rectangle barRect; //bounding rectangle of bar area - private bool mouseInThumbRegion = false; - - private int trackerValue = 50; - /// - /// Gets or sets the value of Slider. - /// - /// The value. - public int Value - { - get { return trackerValue; } - set - { - if (value >= barMinimum & value <= barMaximum) - { - trackerValue = value; - if (ValueChanged != null) ValueChanged(this, new EventArgs()); - Invalidate(); - } - // ArgumentOutOfRangeException("Value is outside appropriate range (min, max)"); - } - } - - - private int barMinimum = 0; - /// - /// Gets or sets the minimum value. - /// - /// The minimum value. - public int Minimum - { - get { return barMinimum; } - set - { - if (value < barMaximum) - { - barMinimum = value; - if (trackerValue < barMinimum) - { - trackerValue = barMinimum; - if (ValueChanged != null) ValueChanged(this, new EventArgs()); - } - Invalidate(); - } - // ArgumentOutOfRangeException("Minimal value is greather than maximal one"); - } - } - - - private int barMaximum = 100; - /// - /// Gets or sets the maximum value. - /// - /// The maximum value. - public int Maximum - { - get { return barMaximum; } - set - { - if (value > barMinimum) - { - barMaximum = value; - if (trackerValue > barMaximum) - { - trackerValue = barMaximum; - if (ValueChanged != null) ValueChanged(this, new EventArgs()); - } - Invalidate(); - } - // ArgumentOutOfRangeException("Maximal value is lower than minimal one"); - } - } - - private uint criticalPercent = 0; - /// - /// Gets or sets trackbar's small change. It affects how to behave when directional keys are pressed - /// - /// The small change value. - public uint CriticalPercent - { - get { return criticalPercent; } - set { criticalPercent = value; } - } - - private Color thumbOuterColor = Color.White; - private Color thumbInnerColor = Color.Gainsboro; - private Color thumbPenColor = Color.Silver; - private Color barOuterColor = Color.SkyBlue; - private Color barInnerColor = Color.DarkSlateBlue; - private Color barPenColor = Color.Gainsboro; - private Color elapsedOuterColor = Color.DarkGreen; - private Color elapsedInnerColor = Color.Chartreuse; - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the control. - /// - public DarkSlider() - { - InitializeComponent(); - SetStyle(ControlStyles.AllPaintingInWmPaint - | ControlStyles.OptimizedDoubleBuffer - | ControlStyles.ResizeRedraw - | ControlStyles.Selectable - | ControlStyles.SupportsTransparentBackColor - | ControlStyles.UserMouse - | ControlStyles.UserPaint, true); - - BackColor = Color.Transparent; - - Minimum = 0; - Maximum = 100; - Value = 50; - } - - #endregion - - #region Paint - - /// - /// Raises the event. - /// - /// A that contains the event data. - protected override void OnPaint(PaintEventArgs e) - { - if (!Enabled) - { - DrawDisabledSlider(e.Graphics); - } - else - { - //if (mouseEffects && mouseInRegion) - //{ - // Color[] lightenedColors = LightenColors(thumbOuterColor, thumbInnerColor, thumbPenColor, - // barOuterColor, barInnerColor, barPenColor, - // elapsedOuterColor, elapsedInnerColor); - // DrawColorSlider(e, lightenedColors[0], lightenedColors[1], lightenedColors[2], lightenedColors[3], - // lightenedColors[4], lightenedColors[5], lightenedColors[6], lightenedColors[7]); - //} - //else - { - DrawColorSlider(e.Graphics); - } - } - } - - private void DrawDisabledSlider(Graphics g) - { - try - { - //adjust drawing rects - barRect = new Rectangle(1, this.Height / 2, this.Width - 2, 5); - - Brush sliderLGBrushH = new LinearGradientBrush(barRect, ColorScheme.ColorGray122, - ColorScheme.ColorGray107, LinearGradientMode.Horizontal); - - //draw bar - { - // Background gradient - g.FillRectangle(sliderLGBrushH, barRect); - // Background fill - g.FillRectangle(ColorScheme.SliderBorderBrush, - barRect.Left + 1, barRect.Top, barRect.Width - 2, barRect.Height - 1); - // Bar fill - g.FillRectangle(ColorScheme.SliderFillBrush, - barRect.Left + 2, barRect.Top + 1, barRect.Width - 4, barRect.Height - 3); - } - - sliderLGBrushH.Dispose(); - } - catch (Exception) - { } - finally - { } - } - - /// - /// Draws the colorslider control using passed colors. - /// - private void DrawColorSlider(Graphics g) - { - try - { - //set up thumbRect aproprietly - int track = (((trackerValue - barMinimum) * (ClientRectangle.Width - thumbSize)) / (barMaximum - barMinimum)); - thumbRect = new Rectangle(track, this.Height / 2 - 3, thumbSize - 1, 10); - - //adjust drawing rects - barRect = new Rectangle(1, this.Height / 2, this.Width - 2, 5); - - //get thumb shape path - GraphicsPath thumbPath = new GraphicsPath(); - thumbPath.AddPolygon(new Point[] { - new Point(thumbRect.Left, thumbRect.Top), - new Point(thumbRect.Right, thumbRect.Top), - new Point(thumbRect.Right, thumbRect.Bottom - 4), - new Point(thumbRect.Left + thumbRect.Width / 2, thumbRect.Bottom), - new Point(thumbRect.Left, thumbRect.Bottom - 4) - }); - - Brush sliderLGBrushH = new LinearGradientBrush(barRect, ColorScheme.ColorGray122, - ColorScheme.ColorGray107, LinearGradientMode.Horizontal); - - Brush barFill = (criticalPercent > 0 && trackerValue > criticalPercent) ? Brushes.Peru : Brushes.Green; - - //draw bar - { - // Background gradient - g.FillRectangle(sliderLGBrushH, barRect); - // Background fill - g.FillRectangle(ColorScheme.SliderBorderBrush, - barRect.Left + 1, barRect.Top, barRect.Width - 2, barRect.Height - 1); - // Bar fill - g.FillRectangle(ColorScheme.SliderFillBrush, - barRect.Left + 2, barRect.Top + 1, barRect.Width - 4, barRect.Height - 3); - // Elapsed bar fill - - g.FillRectangle(barFill, - barRect.Left + 2, barRect.Top + 1, thumbRect.Left + thumbSize / 2 - 2, barRect.Height - 3); - - //draw bar band - //g.DrawRectangle(barPen, barRect); - } - - sliderLGBrushH.Dispose(); - - //draw thumb - Brush brushInner = new LinearGradientBrush(thumbRect, - Color.FromArgb(111, 111, 111), Color.FromArgb(80, 80, 80), - LinearGradientMode.Vertical); - - g.SmoothingMode = SmoothingMode.AntiAlias; - g.FillPath(brushInner, thumbPath); - g.DrawPath(Pens.Black, thumbPath); - - brushInner.Dispose(); - //draw thumb band - //Color newThumbPenColor = thumbPenColorPaint; - //if (mouseEffects && (Capture || mouseInThumbRegion)) - // newThumbPenColor = ControlPaint.Dark(newThumbPenColor); - //g.DrawPath(thumbPen, thumbPath); - } - catch (Exception) - { } - finally - { } - } - - #endregion - - #region Overided events - - /// - /// Raises the event. - /// - /// An that contains the event data. - protected override void OnEnabledChanged(EventArgs e) - { - base.OnEnabledChanged(e); - Invalidate(); - } - - /// - /// Raises the event. - /// - /// An that contains the event data. - protected override void OnMouseLeave(EventArgs e) - { - base.OnMouseLeave(e); - mouseInThumbRegion = false; - } - - /// - /// Raises the event. - /// - /// A that contains the event data. - protected override void OnMouseDown(MouseEventArgs e) - { - base.OnMouseDown(e); - if (e.Button == MouseButtons.Left && this.Enabled) - { - this.Capture = true; - OnValueChanging(); - OnMouseMove(e); - } - } - - /// - /// Raises the event. - /// - /// A that contains the event data. - protected override void OnMouseMove(MouseEventArgs e) - { - base.OnMouseMove(e); - mouseInThumbRegion = thumbRect.Contains(e.Location); - if (Capture & e.Button == MouseButtons.Left) - { - Point pt = e.Location; - int p = pt.X; - int margin = thumbSize >> 1; - p -= margin; - float coef = (float)(barMaximum - barMinimum) / - (float)(ClientSize.Width - 2 * margin); - trackerValue = (int)(p * coef + barMinimum); - - if (trackerValue <= barMinimum) - { - trackerValue = barMinimum; - } - else if (trackerValue >= barMaximum) - { - trackerValue = barMaximum; - } - - OnValueChanging(); - } - Invalidate(); - } - - /// - /// Raises the event. - /// - /// A that contains the event data. - protected override void OnMouseUp(MouseEventArgs e) - { - if (this.Enabled) - { - base.OnMouseUp(e); - this.Capture = false; - mouseInThumbRegion = thumbRect.Contains(e.Location); - OnValueChanged(); - Invalidate(); - } - } - - #endregion - - #region Help routines - - /// - /// Sets the trackbar value so that it wont exceed allowed range. - /// - /// The value. - private void SetProperValue(int val) - { - if (val < barMinimum) Value = barMinimum; - else if (val > barMaximum) Value = barMaximum; - else Value = val; - } - - #endregion - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// Original code on CodeProject: Owner-drawn trackbar (slider), Michal Brylka +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.Controls +{ + using System; + using System.Drawing; + using System.Drawing.Drawing2D; + using System.Windows.Forms; + + /// + /// Encapsulates control that visualy displays certain integer value and allows user to change + /// it within desired range. It imitates as far as + /// mouse usage is concerned. + /// + public class DarkSlider : Control + { + #region Designer + + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + } + + #endregion + + #endregion + + #region Events + + /// + /// Fires when Slider position has changed + /// + public event EventHandler ValueChanging; + + private void OnValueChanging() + { + var evt = ValueChanging; + + if (evt != null) + { + evt(this, EventArgs.Empty); + } + } + + /// + /// Fires when Slider position has changed + /// + public event EventHandler ValueChanged; + + private void OnValueChanged() + { + var evt = ValueChanged; + + if (evt != null) + { + evt(this, EventArgs.Empty); + } + } + + #endregion + + #region Properties + + int thumbSize = 7; + private Rectangle thumbRect; //bounding rectangle of thumb area + private Rectangle barRect; //bounding rectangle of bar area + private bool mouseInThumbRegion = false; + + private int trackerValue = 50; + /// + /// Gets or sets the value of Slider. + /// + /// The value. + public int Value + { + get { return trackerValue; } + set + { + if (value >= barMinimum & value <= barMaximum) + { + trackerValue = value; + if (ValueChanged != null) ValueChanged(this, new EventArgs()); + Invalidate(); + } + // ArgumentOutOfRangeException("Value is outside appropriate range (min, max)"); + } + } + + + private int barMinimum = 0; + /// + /// Gets or sets the minimum value. + /// + /// The minimum value. + public int Minimum + { + get { return barMinimum; } + set + { + if (value < barMaximum) + { + barMinimum = value; + if (trackerValue < barMinimum) + { + trackerValue = barMinimum; + if (ValueChanged != null) ValueChanged(this, new EventArgs()); + } + Invalidate(); + } + // ArgumentOutOfRangeException("Minimal value is greather than maximal one"); + } + } + + + private int barMaximum = 100; + /// + /// Gets or sets the maximum value. + /// + /// The maximum value. + public int Maximum + { + get { return barMaximum; } + set + { + if (value > barMinimum) + { + barMaximum = value; + if (trackerValue > barMaximum) + { + trackerValue = barMaximum; + if (ValueChanged != null) ValueChanged(this, new EventArgs()); + } + Invalidate(); + } + // ArgumentOutOfRangeException("Maximal value is lower than minimal one"); + } + } + + private uint criticalPercent = 0; + /// + /// Gets or sets trackbar's small change. It affects how to behave when directional keys are pressed + /// + /// The small change value. + public uint CriticalPercent + { + get { return criticalPercent; } + set { criticalPercent = value; } + } + + private Color thumbOuterColor = Color.White; + private Color thumbInnerColor = Color.Gainsboro; + private Color thumbPenColor = Color.Silver; + private Color barOuterColor = Color.SkyBlue; + private Color barInnerColor = Color.DarkSlateBlue; + private Color barPenColor = Color.Gainsboro; + private Color elapsedOuterColor = Color.DarkGreen; + private Color elapsedInnerColor = Color.Chartreuse; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the control. + /// + public DarkSlider() + { + InitializeComponent(); + SetStyle(ControlStyles.AllPaintingInWmPaint + | ControlStyles.OptimizedDoubleBuffer + | ControlStyles.ResizeRedraw + | ControlStyles.Selectable + | ControlStyles.SupportsTransparentBackColor + | ControlStyles.UserMouse + | ControlStyles.UserPaint, true); + + BackColor = Color.Transparent; + + Minimum = 0; + Maximum = 100; + Value = 50; + } + + #endregion + + #region Paint + + /// + /// Raises the event. + /// + /// A that contains the event data. + protected override void OnPaint(PaintEventArgs e) + { + if (!Enabled) + { + DrawDisabledSlider(e.Graphics); + } + else + { + //if (mouseEffects && mouseInRegion) + //{ + // Color[] lightenedColors = LightenColors(thumbOuterColor, thumbInnerColor, thumbPenColor, + // barOuterColor, barInnerColor, barPenColor, + // elapsedOuterColor, elapsedInnerColor); + // DrawColorSlider(e, lightenedColors[0], lightenedColors[1], lightenedColors[2], lightenedColors[3], + // lightenedColors[4], lightenedColors[5], lightenedColors[6], lightenedColors[7]); + //} + //else + { + DrawColorSlider(e.Graphics); + } + } + } + + private void DrawDisabledSlider(Graphics g) + { + try + { + //adjust drawing rects + barRect = new Rectangle(1, this.Height / 2, this.Width - 2, 5); + + Brush sliderLGBrushH = new LinearGradientBrush(barRect, ColorScheme.ColorGray122, + ColorScheme.ColorGray107, LinearGradientMode.Horizontal); + + //draw bar + { + // Background gradient + g.FillRectangle(sliderLGBrushH, barRect); + // Background fill + g.FillRectangle(ColorScheme.SliderBorderBrush, + barRect.Left + 1, barRect.Top, barRect.Width - 2, barRect.Height - 1); + // Bar fill + g.FillRectangle(ColorScheme.SliderFillBrush, + barRect.Left + 2, barRect.Top + 1, barRect.Width - 4, barRect.Height - 3); + } + + sliderLGBrushH.Dispose(); + } + catch (Exception) + { } + finally + { } + } + + /// + /// Draws the colorslider control using passed colors. + /// + private void DrawColorSlider(Graphics g) + { + try + { + //set up thumbRect aproprietly + int track = (((trackerValue - barMinimum) * (ClientRectangle.Width - thumbSize)) / (barMaximum - barMinimum)); + thumbRect = new Rectangle(track, this.Height / 2 - 3, thumbSize - 1, 10); + + //adjust drawing rects + barRect = new Rectangle(1, this.Height / 2, this.Width - 2, 5); + + //get thumb shape path + GraphicsPath thumbPath = new GraphicsPath(); + thumbPath.AddPolygon(new Point[] { + new Point(thumbRect.Left, thumbRect.Top), + new Point(thumbRect.Right, thumbRect.Top), + new Point(thumbRect.Right, thumbRect.Bottom - 4), + new Point(thumbRect.Left + thumbRect.Width / 2, thumbRect.Bottom), + new Point(thumbRect.Left, thumbRect.Bottom - 4) + }); + + Brush sliderLGBrushH = new LinearGradientBrush(barRect, ColorScheme.ColorGray122, + ColorScheme.ColorGray107, LinearGradientMode.Horizontal); + + Brush barFill = (criticalPercent > 0 && trackerValue > criticalPercent) ? Brushes.Peru : Brushes.Green; + + //draw bar + { + // Background gradient + g.FillRectangle(sliderLGBrushH, barRect); + // Background fill + g.FillRectangle(ColorScheme.SliderBorderBrush, + barRect.Left + 1, barRect.Top, barRect.Width - 2, barRect.Height - 1); + // Bar fill + g.FillRectangle(ColorScheme.SliderFillBrush, + barRect.Left + 2, barRect.Top + 1, barRect.Width - 4, barRect.Height - 3); + // Elapsed bar fill + + g.FillRectangle(barFill, + barRect.Left + 2, barRect.Top + 1, thumbRect.Left + thumbSize / 2 - 2, barRect.Height - 3); + + //draw bar band + //g.DrawRectangle(barPen, barRect); + } + + sliderLGBrushH.Dispose(); + + //draw thumb + Brush brushInner = new LinearGradientBrush(thumbRect, + Color.FromArgb(111, 111, 111), Color.FromArgb(80, 80, 80), + LinearGradientMode.Vertical); + + g.SmoothingMode = SmoothingMode.AntiAlias; + g.FillPath(brushInner, thumbPath); + g.DrawPath(Pens.Black, thumbPath); + + brushInner.Dispose(); + //draw thumb band + //Color newThumbPenColor = thumbPenColorPaint; + //if (mouseEffects && (Capture || mouseInThumbRegion)) + // newThumbPenColor = ControlPaint.Dark(newThumbPenColor); + //g.DrawPath(thumbPen, thumbPath); + } + catch (Exception) + { } + finally + { } + } + + #endregion + + #region Overided events + + /// + /// Raises the event. + /// + /// An that contains the event data. + protected override void OnEnabledChanged(EventArgs e) + { + base.OnEnabledChanged(e); + Invalidate(); + } + + /// + /// Raises the event. + /// + /// An that contains the event data. + protected override void OnMouseLeave(EventArgs e) + { + base.OnMouseLeave(e); + mouseInThumbRegion = false; + } + + /// + /// Raises the event. + /// + /// A that contains the event data. + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + if (e.Button == MouseButtons.Left && this.Enabled) + { + this.Capture = true; + OnValueChanging(); + OnMouseMove(e); + } + } + + /// + /// Raises the event. + /// + /// A that contains the event data. + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + mouseInThumbRegion = thumbRect.Contains(e.Location); + if (Capture & e.Button == MouseButtons.Left) + { + Point pt = e.Location; + int p = pt.X; + int margin = thumbSize >> 1; + p -= margin; + float coef = (float)(barMaximum - barMinimum) / + (float)(ClientSize.Width - 2 * margin); + trackerValue = (int)(p * coef + barMinimum); + + if (trackerValue <= barMinimum) + { + trackerValue = barMinimum; + } + else if (trackerValue >= barMaximum) + { + trackerValue = barMaximum; + } + + OnValueChanging(); + } + Invalidate(); + } + + /// + /// Raises the event. + /// + /// A that contains the event data. + protected override void OnMouseUp(MouseEventArgs e) + { + if (this.Enabled) + { + base.OnMouseUp(e); + this.Capture = false; + mouseInThumbRegion = thumbRect.Contains(e.Location); + OnValueChanged(); + Invalidate(); + } + } + + #endregion + + #region Help routines + + /// + /// Sets the trackbar value so that it wont exceed allowed range. + /// + /// The value. + private void SetProperValue(int val) + { + if (val < barMinimum) Value = barMinimum; + else if (val > barMaximum) Value = barMaximum; + else Value = val; + } + + #endregion + } +} diff --git a/Triangle.NET/TestApp/Controls/DarkTabControl.cs b/src/Triangle.Viewer/Controls/DarkTabControl.cs similarity index 95% rename from Triangle.NET/TestApp/Controls/DarkTabControl.cs rename to src/Triangle.Viewer/Controls/DarkTabControl.cs index 831ee07..50b58f5 100644 --- a/Triangle.NET/TestApp/Controls/DarkTabControl.cs +++ b/src/Triangle.Viewer/Controls/DarkTabControl.cs @@ -1,190 +1,188 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// Original code on CodeProject: A .NET Flat TabControl (CustomDraw), Oscar Londono -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.Controls -{ - using System; - using System.ComponentModel; - using System.Drawing; - using System.Windows.Forms; - using System.Drawing.Text; - - /// - /// Summary description for FlatTabControl. - /// - public class DarkTabControl : System.Windows.Forms.TabControl - { - #region Designer - - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Component Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - components = new System.ComponentModel.Container(); - } - - #endregion - - #endregion - - private const int margin = 5; - private Color backColor = ColorScheme.ColorGray68; - - /// - /// Initializes a new instance of the control. - /// - public DarkTabControl() - { - // This call is required by the Windows.Forms Form Designer. - InitializeComponent(); - - base.Multiline = false; - - // double buffering - this.SetStyle(ControlStyles.UserPaint, true); - this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); - this.SetStyle(ControlStyles.DoubleBuffer, true); - this.SetStyle(ControlStyles.ResizeRedraw, true); - this.SetStyle(ControlStyles.SupportsTransparentBackColor, true); - - this.SelectedIndexChanged += (obj, evt) => { Invalidate(); }; - } - - #region Properties - - new public TabAlignment Alignment - { - get { return base.Alignment; } - set - { - TabAlignment ta = value; - if ((ta != TabAlignment.Top) && (ta != TabAlignment.Bottom)) - { - ta = TabAlignment.Top; - } - - base.Alignment = ta; - } - } - - public override Color BackColor - { - get - { - return backColor; - } - set - { - base.BackColor = backColor; - } - } - - #endregion - - protected override void OnPaint(PaintEventArgs e) - { - base.OnPaint(e); - - DrawControl(e.Graphics); - } - - private void DrawControl(Graphics g) - { - if (!Visible) - { - return; - } - - Rectangle controlBounds = this.ClientRectangle; - Rectangle tabBounds = this.DisplayRectangle; - - // Fill client area - Brush br = new SolidBrush(this.BackColor); - g.FillRectangle(br, controlBounds); - br.Dispose(); - - int width = tabBounds.Width + margin; - - // Clip region for drawing tabs - Region clip = g.Clip; - Rectangle region = new Rectangle(tabBounds.Left, controlBounds.Top, width - margin, controlBounds.Height); - - g.SetClip(region); - - // Draw tabs - for (int i = 0; i < this.TabCount; i++) - { - DrawTab(g, this.TabPages[i], i); - } - - g.Clip = clip; - } - - private void DrawTab(Graphics g, TabPage tabPage, int index) - { - Rectangle tabBounds = this.GetTabRect(index); - - bool selected = (this.SelectedIndex == index); - - // Fill this tab with background color - g.FillRectangle(selected ? Brushes.DimGray : ColorScheme.BrushGray68, tabBounds); - - if (selected) - { - // Clear bottom lines - Pen pen = new Pen(tabPage.BackColor); - - switch (this.Alignment) - { - case TabAlignment.Top: - g.DrawLine(pen, tabBounds.Left, tabBounds.Bottom, tabBounds.Right - 1, tabBounds.Bottom); - g.DrawLine(pen, tabBounds.Left, tabBounds.Bottom + 1, tabBounds.Right - 1, tabBounds.Bottom + 1); - break; - - case TabAlignment.Bottom: - g.DrawLine(pen, tabBounds.Left, tabBounds.Top, tabBounds.Right - 1, tabBounds.Top); - g.DrawLine(pen, tabBounds.Left, tabBounds.Top - 1, tabBounds.Right - 1, tabBounds.Top - 1); - g.DrawLine(pen, tabBounds.Left, tabBounds.Top - 2, tabBounds.Right - 1, tabBounds.Top - 2); - break; - } - - pen.Dispose(); - } - - // Draw string - StringFormat stringFormat = new StringFormat(); - stringFormat.Alignment = StringAlignment.Center; - stringFormat.LineAlignment = StringAlignment.Center; - - g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; - - g.DrawString(tabPage.Text, Font, Brushes.White, tabBounds, stringFormat); - } - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// Original code on CodeProject: A .NET Flat TabControl (CustomDraw), Oscar Londono +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.Controls +{ + using System.Drawing; + using System.Drawing.Text; + using System.Windows.Forms; + + /// + /// Summary description for FlatTabControl. + /// + public class DarkTabControl : System.Windows.Forms.TabControl + { + #region Designer + + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + } + + #endregion + + #endregion + + private const int margin = 5; + private Color backColor = ColorScheme.ColorGray68; + + /// + /// Initializes a new instance of the control. + /// + public DarkTabControl() + { + // This call is required by the Windows.Forms Form Designer. + InitializeComponent(); + + base.Multiline = false; + + // double buffering + this.SetStyle(ControlStyles.UserPaint, true); + this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); + this.SetStyle(ControlStyles.DoubleBuffer, true); + this.SetStyle(ControlStyles.ResizeRedraw, true); + this.SetStyle(ControlStyles.SupportsTransparentBackColor, true); + + this.SelectedIndexChanged += (obj, evt) => { Invalidate(); }; + } + + #region Properties + + new public TabAlignment Alignment + { + get { return base.Alignment; } + set + { + TabAlignment ta = value; + if ((ta != TabAlignment.Top) && (ta != TabAlignment.Bottom)) + { + ta = TabAlignment.Top; + } + + base.Alignment = ta; + } + } + + public override Color BackColor + { + get + { + return backColor; + } + set + { + base.BackColor = backColor; + } + } + + #endregion + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + + DrawControl(e.Graphics); + } + + private void DrawControl(Graphics g) + { + if (!Visible) + { + return; + } + + Rectangle controlBounds = this.ClientRectangle; + Rectangle tabBounds = this.DisplayRectangle; + + // Fill client area + Brush br = new SolidBrush(this.BackColor); + g.FillRectangle(br, controlBounds); + br.Dispose(); + + int width = tabBounds.Width + margin; + + // Clip region for drawing tabs + Region clip = g.Clip; + Rectangle region = new Rectangle(tabBounds.Left, controlBounds.Top, width - margin, controlBounds.Height); + + g.SetClip(region); + + // Draw tabs + for (int i = 0; i < this.TabCount; i++) + { + DrawTab(g, this.TabPages[i], i); + } + + g.Clip = clip; + } + + private void DrawTab(Graphics g, TabPage tabPage, int index) + { + Rectangle tabBounds = this.GetTabRect(index); + + bool selected = (this.SelectedIndex == index); + + // Fill this tab with background color + g.FillRectangle(selected ? Brushes.DimGray : ColorScheme.BrushGray68, tabBounds); + + if (selected) + { + // Clear bottom lines + Pen pen = new Pen(tabPage.BackColor); + + switch (this.Alignment) + { + case TabAlignment.Top: + g.DrawLine(pen, tabBounds.Left, tabBounds.Bottom, tabBounds.Right - 1, tabBounds.Bottom); + g.DrawLine(pen, tabBounds.Left, tabBounds.Bottom + 1, tabBounds.Right - 1, tabBounds.Bottom + 1); + break; + + case TabAlignment.Bottom: + g.DrawLine(pen, tabBounds.Left, tabBounds.Top, tabBounds.Right - 1, tabBounds.Top); + g.DrawLine(pen, tabBounds.Left, tabBounds.Top - 1, tabBounds.Right - 1, tabBounds.Top - 1); + g.DrawLine(pen, tabBounds.Left, tabBounds.Top - 2, tabBounds.Right - 1, tabBounds.Top - 2); + break; + } + + pen.Dispose(); + } + + // Draw string + StringFormat stringFormat = new StringFormat(); + stringFormat.Alignment = StringAlignment.Center; + stringFormat.LineAlignment = StringAlignment.Center; + + g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; + + g.DrawString(tabPage.Text, Font, Brushes.White, tabBounds, stringFormat); + } + } +} diff --git a/Triangle.NET/TestApp/Controls/DarkTextBox.cs b/src/Triangle.Viewer/Controls/DarkTextBox.cs similarity index 94% rename from Triangle.NET/TestApp/Controls/DarkTextBox.cs rename to src/Triangle.Viewer/Controls/DarkTextBox.cs index bfd759b..8e72def 100644 --- a/Triangle.NET/TestApp/Controls/DarkTextBox.cs +++ b/src/Triangle.Viewer/Controls/DarkTextBox.cs @@ -1,209 +1,220 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.Controls -{ - using System; - using System.Collections.Generic; - using System.Text; - using System.Drawing; - using System.Drawing.Drawing2D; - using System.Windows.Forms; - - /// - /// Dark textbox control. - /// - public class DarkTextBox : Control - { - #region Designer - - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Component Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - components = new System.ComponentModel.Container(); - - this.textBox = new System.Windows.Forms.TextBox(); - this.SuspendLayout(); - // - // textBox - // - this.textBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this.textBox.BorderStyle = System.Windows.Forms.BorderStyle.None; - this.textBox.Location = new System.Drawing.Point(4, 2); - this.textBox.Name = "textBox"; - this.textBox.TabIndex = 0; - // - // DarkTextBox - // - this.BackColor = System.Drawing.Color.White; - this.Controls.Add(this.textBox); - this.Cursor = Cursors.IBeam; - this.Size = new System.Drawing.Size(150, 22); - this.ResumeLayout(false); - this.PerformLayout(); - } - - #endregion - - #endregion - - TextBox textBox; - - /// - /// Initializes a new instance of the control. - /// - public DarkTextBox() - { - InitializeComponent(); - - this.MouseClick += delegate(object sender, MouseEventArgs e) - { - if (e.Button == MouseButtons.Left) textBox.Focus(); - }; - - textBox.Font = this.Font; - textBox.Location = new Point(4, (this.Height - textBox.Height) / 2); - textBox.Width = this.Width - 8; - textBox.TextAlign = HorizontalAlignment.Left; - textBox.ForeColor = this.ForeColor; - //textBox.MaxLength = 6; - - textBox.GotFocus += delegate(object sender, EventArgs e) - { - textBox.ForeColor = this.ForeColor; - }; - - textBox.LostFocus += delegate(object sender, EventArgs e) - { - textBox.ForeColor = ColorScheme.ColorGray68; - }; - } - - protected override void OnPaint(PaintEventArgs e) - { - Graphics g = e.Graphics; - - Rectangle rect = this.ClientRectangle; - - //Brush brushOuter = new LinearGradientBrush(rect, Color.FromArgb(82, 82, 82), - // Color.FromArgb(96, 96, 96), LinearGradientMode.Vertical); - - Pen borderTop = new Pen(Color.FromArgb(76, 76, 76), 1f); - Pen borderBottom = new Pen(Color.FromArgb(128, 128, 128), 1f); - - //e.Graphics.FillRectangle(brushOuter, rect); - - rect = new Rectangle(1, 1, this.Width - 3, this.Height - 3); - g.FillRectangle(new SolidBrush(this.BackColor), rect); - - g.DrawLine(borderTop, 0, 0, this.Width - 1, 0); - g.DrawLine(borderTop, 0, 0, 0, this.Height - 1); - g.DrawLine(borderBottom, 1, this.Height - 1, this.Width - 1, this.Height - 1); - g.DrawLine(borderBottom, this.Width - 1, this.Height - 1, this.Width - 1, this.Height - 1); - - - //brushOuter.Dispose(); - borderTop.Dispose(); - borderBottom.Dispose(); - - base.OnPaint(e); - } - - #region Property overrides - - public override Font Font - { - get - { - return base.Font; - } - set - { - textBox.Font = value; - base.Font = value; - } - } - - public override String Text - { - get - { - return textBox.Text; - } - set - { - textBox.Text = value; - } - } - - public override Color ForeColor - { - get - { - return base.ForeColor; - } - set - { - textBox.ForeColor = value; - base.ForeColor = value; - } - } - - public override Color BackColor - { - get - { - return base.BackColor; - } - set - { - textBox.BackColor = value; - base.BackColor = value; - } - } - - #endregion - - #region Textbox properties - - public HorizontalAlignment TextAlign - { - get - { - return textBox.TextAlign; - } - set - { - textBox.TextAlign = value; - } - } - - #endregion - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.Controls +{ + using System; + using System.Drawing; + using System.Windows.Forms; + + /// + /// Dark textbox control. + /// + public class DarkTextBox : Control + { + #region Designer + + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + + this.textBox = new System.Windows.Forms.TextBox(); + this.SuspendLayout(); + // + // textBox + // + this.textBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.textBox.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.textBox.Location = new System.Drawing.Point(4, 2); + this.textBox.Name = "textBox"; + this.textBox.TabIndex = 0; + // + // DarkTextBox + // + this.BackColor = System.Drawing.Color.White; + this.Controls.Add(this.textBox); + this.Cursor = Cursors.IBeam; + this.Size = new System.Drawing.Size(150, 22); + this.ResumeLayout(false); + this.PerformLayout(); + } + + #endregion + + #endregion + + TextBox textBox; + + /// + /// Initializes a new instance of the control. + /// + public DarkTextBox() + { + InitializeComponent(); + + this.MouseClick += delegate(object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) textBox.Focus(); + }; + + textBox.Font = this.Font; + textBox.Location = new Point(4, (this.Height - textBox.Height) / 2); + textBox.Width = this.Width - 8; + textBox.TextAlign = HorizontalAlignment.Left; + textBox.ForeColor = this.ForeColor; + //textBox.MaxLength = 6; + + textBox.GotFocus += delegate(object sender, EventArgs e) + { + textBox.ForeColor = this.ForeColor; + }; + + textBox.LostFocus += delegate(object sender, EventArgs e) + { + textBox.ForeColor = ColorScheme.ColorGray68; + }; + + textBox.KeyDown += delegate(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) + { + e.SuppressKeyPress = true; + OnKeyDown(e); + } + }; + } + + protected override void OnKeyDown(KeyEventArgs e) + { + base.OnKeyDown(e); + } + + protected override void OnPaint(PaintEventArgs e) + { + Graphics g = e.Graphics; + + Rectangle rect = this.ClientRectangle; + + //Brush brushOuter = new LinearGradientBrush(rect, Color.FromArgb(82, 82, 82), + // Color.FromArgb(96, 96, 96), LinearGradientMode.Vertical); + + Pen borderTop = new Pen(Color.FromArgb(76, 76, 76), 1f); + Pen borderBottom = new Pen(Color.FromArgb(128, 128, 128), 1f); + + //e.Graphics.FillRectangle(brushOuter, rect); + + rect = new Rectangle(1, 1, this.Width - 3, this.Height - 3); + g.FillRectangle(new SolidBrush(this.BackColor), rect); + + g.DrawLine(borderTop, 0, 0, this.Width - 1, 0); + g.DrawLine(borderTop, 0, 0, 0, this.Height - 1); + g.DrawLine(borderBottom, 1, this.Height - 1, this.Width - 1, this.Height - 1); + g.DrawLine(borderBottom, this.Width - 1, this.Height - 1, this.Width - 1, this.Height - 1); + + + //brushOuter.Dispose(); + borderTop.Dispose(); + borderBottom.Dispose(); + + base.OnPaint(e); + } + + #region Property overrides + + public override Font Font + { + get + { + return base.Font; + } + set + { + textBox.Font = value; + base.Font = value; + } + } + + public override String Text + { + get + { + return textBox.Text; + } + set + { + textBox.Text = value; + } + } + + public override Color ForeColor + { + get + { + return base.ForeColor; + } + set + { + textBox.ForeColor = value; + base.ForeColor = value; + } + } + + public override Color BackColor + { + get + { + return base.BackColor; + } + set + { + textBox.BackColor = value; + base.BackColor = value; + } + } + + #endregion + + #region Textbox properties + + public HorizontalAlignment TextAlign + { + get + { + return textBox.TextAlign; + } + set + { + textBox.TextAlign = value; + } + } + + #endregion + } +} diff --git a/Triangle.NET/TestApp/Controls/DarkToolStripRenderer.cs b/src/Triangle.Viewer/Controls/DarkToolStripRenderer.cs similarity index 93% rename from Triangle.NET/TestApp/Controls/DarkToolStripRenderer.cs rename to src/Triangle.Viewer/Controls/DarkToolStripRenderer.cs index c0ce39e..331ed22 100644 --- a/Triangle.NET/TestApp/Controls/DarkToolStripRenderer.cs +++ b/src/Triangle.Viewer/Controls/DarkToolStripRenderer.cs @@ -1,85 +1,81 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.Controls -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Windows.Forms; - using System.Drawing; - using System.Drawing.Drawing2D; - - /// - /// Toolstrip render for dark menu. - /// - public class DarkToolStripRenderer : ToolStripRenderer - { - protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e) - { - e.TextColor = e.Item.Enabled ? Color.White : Color.Gray; - - base.OnRenderItemText(e); - } - - protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) - { - int h = e.Item.Height; - int size = h / 2 - 2; // Box size - Rectangle rect = new Rectangle(10, (h - size) / 2, size - 2, size); - - var mode = e.Graphics.SmoothingMode; - - using (Pen pen = new Pen(Color.White, 1.6f)) - { - e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; - e.Graphics.DrawLine(pen, rect.Left, rect.Bottom - size / 2, rect.Left + size / 2.5f, rect.Bottom - 2); - e.Graphics.DrawLine(pen, rect.Left + size / 2.6f, rect.Bottom - 2, rect.Right, rect.Top); - } - - e.Graphics.SmoothingMode = mode; - } - - protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) - { - if (!e.Item.Selected && !e.Item.Pressed) - { - e.ArrowColor = ColorScheme.ColorGray89; - } - - base.OnRenderArrow(e); - } - - protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e) - { - e.Graphics.FillRectangle(ColorScheme.BrushGray78, 0, 2, e.Item.Width, 1); - } - - protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e) - { - if (e.Item.Enabled) - { - if (e.Item.Selected || e.Item.Pressed) - { - e.Graphics.FillRectangle(Brushes.DimGray, 0, 0, e.Item.Width, e.Item.Height); - } - else - { - e.Graphics.FillRectangle(ColorScheme.BrushGray68, 0, 0, e.Item.Width, e.Item.Height); - } - } - - //base.OnRenderMenuItemBackground(e); - } - - protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e) - { - e.Graphics.FillRectangle(ColorScheme.BrushGray68, e.AffectedBounds); - //base.OnRenderToolStripBackground(e); - } - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.Controls +{ + using System.Drawing; + using System.Drawing.Drawing2D; + using System.Windows.Forms; + + /// + /// Toolstrip render for dark menu. + /// + public class DarkToolStripRenderer : ToolStripRenderer + { + protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e) + { + e.TextColor = e.Item.Enabled ? Color.White : Color.Gray; + + base.OnRenderItemText(e); + } + + protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e) + { + int h = e.Item.Height; + int size = h / 2 - 2; // Box size + Rectangle rect = new Rectangle(10, (h - size) / 2, size - 2, size); + + var mode = e.Graphics.SmoothingMode; + + using (Pen pen = new Pen(Color.White, 1.6f)) + { + e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; + e.Graphics.DrawLine(pen, rect.Left, rect.Bottom - size / 2, rect.Left + size / 2.5f, rect.Bottom - 2); + e.Graphics.DrawLine(pen, rect.Left + size / 2.6f, rect.Bottom - 2, rect.Right, rect.Top); + } + + e.Graphics.SmoothingMode = mode; + } + + protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e) + { + if (!e.Item.Selected && !e.Item.Pressed) + { + e.ArrowColor = ColorScheme.ColorGray89; + } + + base.OnRenderArrow(e); + } + + protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e) + { + e.Graphics.FillRectangle(ColorScheme.BrushGray78, 0, 2, e.Item.Width, 1); + } + + protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e) + { + if (e.Item.Enabled) + { + if (e.Item.Selected || e.Item.Pressed) + { + e.Graphics.FillRectangle(Brushes.DimGray, 0, 0, e.Item.Width, e.Item.Height); + } + else + { + e.Graphics.FillRectangle(ColorScheme.BrushGray68, 0, 0, e.Item.Width, e.Item.Height); + } + } + + //base.OnRenderMenuItemBackground(e); + } + + protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e) + { + e.Graphics.FillRectangle(ColorScheme.BrushGray68, e.AffectedBounds); + //base.OnRenderToolStripBackground(e); + } + } +} diff --git a/Triangle.NET/TestApp/DarkMessageBox.cs b/src/Triangle.Viewer/DarkMessageBox.cs similarity index 97% rename from Triangle.NET/TestApp/DarkMessageBox.cs rename to src/Triangle.Viewer/DarkMessageBox.cs index b1ad107..2c65c20 100644 --- a/Triangle.NET/TestApp/DarkMessageBox.cs +++ b/src/Triangle.Viewer/DarkMessageBox.cs @@ -1,179 +1,179 @@ -using System.Drawing; -using System.Windows.Forms; - -namespace MeshExplorer -{ - class DarkMessageBox : Form - { - #region Designer - - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.btnCancel = new MeshExplorer.Controls.DarkButton(); - this.btnOk = new MeshExplorer.Controls.DarkButton(); - this.lbMessage = new System.Windows.Forms.Label(); - this.lbInfo = new System.Windows.Forms.Label(); - this.SuspendLayout(); - // - // btnCancel - // - this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.btnCancel.Location = new System.Drawing.Point(336, 87); - this.btnCancel.Name = "btnCancel"; - this.btnCancel.Size = new System.Drawing.Size(92, 23); - this.btnCancel.TabIndex = 0; - this.btnCancel.Text = "Cancel"; - this.btnCancel.UseVisualStyleBackColor = true; - // - // btnOk - // - this.btnOk.DialogResult = System.Windows.Forms.DialogResult.OK; - this.btnOk.Location = new System.Drawing.Point(234, 87); - this.btnOk.Name = "btnOk"; - this.btnOk.Size = new System.Drawing.Size(92, 23); - this.btnOk.TabIndex = 1; - this.btnOk.Text = "Ok"; - this.btnOk.UseVisualStyleBackColor = true; - // - // lbMessage - // - this.lbMessage.BackColor = System.Drawing.Color.Transparent; - this.lbMessage.ForeColor = System.Drawing.Color.White; - this.lbMessage.Location = new System.Drawing.Point(12, 9); - this.lbMessage.Name = "lbMessage"; - this.lbMessage.Size = new System.Drawing.Size(412, 30); - this.lbMessage.TabIndex = 2; - this.lbMessage.Text = "Message"; - // - // lbInfo - // - this.lbInfo.AutoSize = true; - this.lbInfo.BackColor = System.Drawing.Color.Transparent; - this.lbInfo.ForeColor = System.Drawing.Color.White; - this.lbInfo.Location = new System.Drawing.Point(12, 47); - this.lbInfo.Name = "lbInfo"; - this.lbInfo.Size = new System.Drawing.Size(28, 13); - this.lbInfo.TabIndex = 3; - this.lbInfo.Text = "Info"; - // - // DarkMessageBox - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76))))); - this.ClientSize = new System.Drawing.Size(436, 118); - this.Controls.Add(this.lbInfo); - this.Controls.Add(this.lbMessage); - this.Controls.Add(this.btnOk); - this.Controls.Add(this.btnCancel); - this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; - this.MaximizeBox = false; - this.MinimizeBox = false; - this.Name = "DarkMessageBox"; - this.ShowInTaskbar = false; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; - this.Text = "Message Box"; - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private Controls.DarkButton btnCancel; - private Label lbMessage; - private Label lbInfo; - private Controls.DarkButton btnOk; - - #endregion - - public DarkMessageBox() - { - InitializeComponent(); - } - - protected override void OnPaint(PaintEventArgs e) - { - base.OnPaint(e); - - var rect = this.ClientRectangle; - rect.Height -= 40; - - e.Graphics.FillRectangle(Brushes.DimGray, rect); - } - - public static DialogResult Show(string title, string message, string info, MessageBoxButtons buttons) - { - DarkMessageBox dialog = new DarkMessageBox(); - - SetButtonsText(dialog, buttons); - - dialog.Text = title; - - dialog.lbInfo.Text = info; - dialog.lbMessage.Text = message; - - return dialog.ShowDialog(); - } - - public static DialogResult Show(string title, string message, string info) - { - return Show(title, message, info, MessageBoxButtons.OKCancel); - } - - public static DialogResult Show(string title, string message, MessageBoxButtons buttons) - { - return Show(title, message, "", buttons); - } - - public static DialogResult Show(string title, string message) - { - return Show(title, message, "", MessageBoxButtons.OKCancel); - } - - private static void SetButtonsText(DarkMessageBox dialog, MessageBoxButtons buttons) - { - if (buttons == MessageBoxButtons.OKCancel) - { - dialog.btnOk.Text = "OK"; - dialog.btnCancel.Text = "Cancel"; - } - else if (buttons == MessageBoxButtons.YesNo) - { - dialog.btnOk.Text = "Yes"; - dialog.btnCancel.Text = "No"; - } - else - { - dialog.btnCancel.Text = "Close"; - dialog.btnOk.Visible = false; - dialog.btnOk.Enabled = false; - } - } - } -} +using System.Drawing; +using System.Windows.Forms; + +namespace MeshExplorer +{ + class DarkMessageBox : Form + { + #region Designer + + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.btnCancel = new MeshExplorer.Controls.DarkButton(); + this.btnOk = new MeshExplorer.Controls.DarkButton(); + this.lbMessage = new System.Windows.Forms.Label(); + this.lbInfo = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // btnCancel + // + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.Location = new System.Drawing.Point(336, 87); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(92, 23); + this.btnCancel.TabIndex = 0; + this.btnCancel.Text = "Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + // + // btnOk + // + this.btnOk.DialogResult = System.Windows.Forms.DialogResult.OK; + this.btnOk.Location = new System.Drawing.Point(234, 87); + this.btnOk.Name = "btnOk"; + this.btnOk.Size = new System.Drawing.Size(92, 23); + this.btnOk.TabIndex = 1; + this.btnOk.Text = "Ok"; + this.btnOk.UseVisualStyleBackColor = true; + // + // lbMessage + // + this.lbMessage.BackColor = System.Drawing.Color.Transparent; + this.lbMessage.ForeColor = System.Drawing.Color.White; + this.lbMessage.Location = new System.Drawing.Point(12, 9); + this.lbMessage.Name = "lbMessage"; + this.lbMessage.Size = new System.Drawing.Size(412, 30); + this.lbMessage.TabIndex = 2; + this.lbMessage.Text = "Message"; + // + // lbInfo + // + this.lbInfo.AutoSize = true; + this.lbInfo.BackColor = System.Drawing.Color.Transparent; + this.lbInfo.ForeColor = System.Drawing.Color.White; + this.lbInfo.Location = new System.Drawing.Point(12, 47); + this.lbInfo.Name = "lbInfo"; + this.lbInfo.Size = new System.Drawing.Size(28, 13); + this.lbInfo.TabIndex = 3; + this.lbInfo.Text = "Info"; + // + // DarkMessageBox + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76))))); + this.ClientSize = new System.Drawing.Size(436, 118); + this.Controls.Add(this.lbInfo); + this.Controls.Add(this.lbMessage); + this.Controls.Add(this.btnOk); + this.Controls.Add(this.btnCancel); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "DarkMessageBox"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Message Box"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private Controls.DarkButton btnCancel; + private Label lbMessage; + private Label lbInfo; + private Controls.DarkButton btnOk; + + #endregion + + public DarkMessageBox() + { + InitializeComponent(); + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + + var rect = this.ClientRectangle; + rect.Height -= 40; + + e.Graphics.FillRectangle(Brushes.DimGray, rect); + } + + public static DialogResult Show(string title, string message, string info, MessageBoxButtons buttons) + { + DarkMessageBox dialog = new DarkMessageBox(); + + SetButtonsText(dialog, buttons); + + dialog.Text = title; + + dialog.lbInfo.Text = info; + dialog.lbMessage.Text = message; + + return dialog.ShowDialog(); + } + + public static DialogResult Show(string title, string message, string info) + { + return Show(title, message, info, MessageBoxButtons.OKCancel); + } + + public static DialogResult Show(string title, string message, MessageBoxButtons buttons) + { + return Show(title, message, "", buttons); + } + + public static DialogResult Show(string title, string message) + { + return Show(title, message, "", MessageBoxButtons.OKCancel); + } + + private static void SetButtonsText(DarkMessageBox dialog, MessageBoxButtons buttons) + { + if (buttons == MessageBoxButtons.OKCancel) + { + dialog.btnOk.Text = "OK"; + dialog.btnCancel.Text = "Cancel"; + } + else if (buttons == MessageBoxButtons.YesNo) + { + dialog.btnOk.Text = "Yes"; + dialog.btnCancel.Text = "No"; + } + else + { + dialog.btnCancel.Text = "Close"; + dialog.btnOk.Visible = false; + dialog.btnOk.Enabled = false; + } + } + } +} diff --git a/Triangle.NET/TestApp/FormExport.Designer.cs b/src/Triangle.Viewer/FormExport.Designer.cs similarity index 97% rename from Triangle.NET/TestApp/FormExport.Designer.cs rename to src/Triangle.Viewer/FormExport.Designer.cs index eba850d..0a12657 100644 --- a/Triangle.NET/TestApp/FormExport.Designer.cs +++ b/src/Triangle.Viewer/FormExport.Designer.cs @@ -1,202 +1,202 @@ -namespace MeshExplorer -{ - partial class FormExport - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.label2 = new System.Windows.Forms.Label(); - this.label3 = new System.Windows.Forms.Label(); - this.label1 = new System.Windows.Forms.Label(); - this.lbSize = new System.Windows.Forms.Label(); - this.cbUseCompression = new MeshExplorer.Controls.DarkCheckBox(); - this.darkSlider1 = new MeshExplorer.Controls.DarkSlider(); - this.darkTextBox1 = new MeshExplorer.Controls.DarkTextBox(); - this.darkListBox1 = new MeshExplorer.Controls.DarkListBox(); - this.darkButton1 = new MeshExplorer.Controls.DarkButton(); - this.btnExport = new MeshExplorer.Controls.DarkButton(); - this.SuspendLayout(); - // - // label2 - // - this.label2.AutoSize = true; - this.label2.BackColor = System.Drawing.Color.Transparent; - this.label2.Location = new System.Drawing.Point(12, 9); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(65, 13); - this.label2.TabIndex = 2; - this.label2.Text = "File fromat:\r\n"; - // - // label3 - // - this.label3.AutoSize = true; - this.label3.BackColor = System.Drawing.Color.Transparent; - this.label3.Location = new System.Drawing.Point(12, 190); - this.label3.Name = "label3"; - this.label3.Size = new System.Drawing.Size(59, 13); - this.label3.TabIndex = 2; - this.label3.Text = "File name:"; - // - // label1 - // - this.label1.AutoSize = true; - this.label1.BackColor = System.Drawing.Color.Transparent; - this.label1.Location = new System.Drawing.Point(12, 148); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(74, 13); - this.label1.TabIndex = 2; - this.label1.Text = "Image width:"; - // - // lbSize - // - this.lbSize.AutoSize = true; - this.lbSize.BackColor = System.Drawing.Color.Transparent; - this.lbSize.ForeColor = System.Drawing.Color.Gray; - this.lbSize.Location = new System.Drawing.Point(272, 148); - this.lbSize.Name = "lbSize"; - this.lbSize.Size = new System.Drawing.Size(40, 13); - this.lbSize.TabIndex = 2; - this.lbSize.Text = "800 px"; - // - // cbUseCompression - // - this.cbUseCompression.BackColor = System.Drawing.Color.DimGray; - this.cbUseCompression.Checked = false; - this.cbUseCompression.Enabled = false; - this.cbUseCompression.Location = new System.Drawing.Point(15, 231); - this.cbUseCompression.Name = "cbUseCompression"; - this.cbUseCompression.Size = new System.Drawing.Size(297, 23); - this.cbUseCompression.TabIndex = 6; - this.cbUseCompression.Text = "Use GZip compression"; - this.cbUseCompression.UseVisualStyleBackColor = false; - // - // darkSlider1 - // - this.darkSlider1.BackColor = System.Drawing.Color.Transparent; - this.darkSlider1.CriticalPercent = ((uint)(0u)); - this.darkSlider1.Location = new System.Drawing.Point(15, 161); - this.darkSlider1.Maximum = 100; - this.darkSlider1.Minimum = 0; - this.darkSlider1.Name = "darkSlider1"; - this.darkSlider1.Size = new System.Drawing.Size(297, 17); - this.darkSlider1.TabIndex = 5; - this.darkSlider1.Text = "darkSlider1"; - this.darkSlider1.Value = 35; - this.darkSlider1.ValueChanging += new System.EventHandler(this.darkSlider1_ValueChanging); - // - // darkTextBox1 - // - this.darkTextBox1.BackColor = System.Drawing.Color.White; - this.darkTextBox1.Cursor = System.Windows.Forms.Cursors.IBeam; - this.darkTextBox1.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.darkTextBox1.ForeColor = System.Drawing.Color.Black; - this.darkTextBox1.Location = new System.Drawing.Point(12, 206); - this.darkTextBox1.Name = "darkTextBox1"; - this.darkTextBox1.Size = new System.Drawing.Size(300, 21); - this.darkTextBox1.TabIndex = 4; - this.darkTextBox1.TextAlign = System.Windows.Forms.HorizontalAlignment.Left; - // - // darkListBox1 - // - this.darkListBox1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(96)))), ((int)(((byte)(96)))), ((int)(((byte)(96))))); - this.darkListBox1.BorderStyle = System.Windows.Forms.BorderStyle.None; - this.darkListBox1.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable; - this.darkListBox1.FormattingEnabled = true; - this.darkListBox1.ItemHeight = 22; - this.darkListBox1.Items.AddRange(new object[] { - "Portable Network Graphics (*.png)", - "Encapsulated PostScript (*.eps)", - "Scalable Vector Graphics (*.svg)"}); - this.darkListBox1.Location = new System.Drawing.Point(12, 25); - this.darkListBox1.Name = "darkListBox1"; - this.darkListBox1.Size = new System.Drawing.Size(302, 110); - this.darkListBox1.TabIndex = 3; - this.darkListBox1.SelectedIndexChanged += new System.EventHandler(this.darkListBox1_SelectedIndexChanged); - // - // darkButton1 - // - this.darkButton1.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.darkButton1.Location = new System.Drawing.Point(144, 270); - this.darkButton1.Name = "darkButton1"; - this.darkButton1.Size = new System.Drawing.Size(82, 23); - this.darkButton1.TabIndex = 1; - this.darkButton1.Text = "Cancel"; - this.darkButton1.UseVisualStyleBackColor = true; - // - // btnExport - // - this.btnExport.DialogResult = System.Windows.Forms.DialogResult.OK; - this.btnExport.Location = new System.Drawing.Point(232, 270); - this.btnExport.Name = "btnExport"; - this.btnExport.Size = new System.Drawing.Size(82, 23); - this.btnExport.TabIndex = 0; - this.btnExport.Text = "Save"; - this.btnExport.UseVisualStyleBackColor = true; - // - // FormExport - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76))))); - this.ClientSize = new System.Drawing.Size(324, 299); - this.Controls.Add(this.cbUseCompression); - this.Controls.Add(this.darkSlider1); - this.Controls.Add(this.darkTextBox1); - this.Controls.Add(this.darkListBox1); - this.Controls.Add(this.label2); - this.Controls.Add(this.lbSize); - this.Controls.Add(this.label1); - this.Controls.Add(this.label3); - this.Controls.Add(this.darkButton1); - this.Controls.Add(this.btnExport); - this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ForeColor = System.Drawing.Color.White; - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; - this.MaximizeBox = false; - this.MinimizeBox = false; - this.Name = "FormExport"; - this.ShowInTaskbar = false; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; - this.Text = "Export Image"; - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private Controls.DarkButton btnExport; - private Controls.DarkButton darkButton1; - private Controls.DarkListBox darkListBox1; - private System.Windows.Forms.Label label2; - private Controls.DarkTextBox darkTextBox1; - private System.Windows.Forms.Label label3; - private System.Windows.Forms.Label label1; - private Controls.DarkSlider darkSlider1; - private System.Windows.Forms.Label lbSize; - private Controls.DarkCheckBox cbUseCompression; - } +namespace MeshExplorer +{ + partial class FormExport + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label2 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.lbSize = new System.Windows.Forms.Label(); + this.cbUseCompression = new MeshExplorer.Controls.DarkCheckBox(); + this.darkSlider1 = new MeshExplorer.Controls.DarkSlider(); + this.darkTextBox1 = new MeshExplorer.Controls.DarkTextBox(); + this.darkListBox1 = new MeshExplorer.Controls.DarkListBox(); + this.darkButton1 = new MeshExplorer.Controls.DarkButton(); + this.btnExport = new MeshExplorer.Controls.DarkButton(); + this.SuspendLayout(); + // + // label2 + // + this.label2.AutoSize = true; + this.label2.BackColor = System.Drawing.Color.Transparent; + this.label2.Location = new System.Drawing.Point(12, 9); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(65, 13); + this.label2.TabIndex = 2; + this.label2.Text = "File fromat:\r\n"; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.BackColor = System.Drawing.Color.Transparent; + this.label3.Location = new System.Drawing.Point(12, 190); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(59, 13); + this.label3.TabIndex = 2; + this.label3.Text = "File name:"; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.BackColor = System.Drawing.Color.Transparent; + this.label1.Location = new System.Drawing.Point(12, 148); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(74, 13); + this.label1.TabIndex = 2; + this.label1.Text = "Image width:"; + // + // lbSize + // + this.lbSize.AutoSize = true; + this.lbSize.BackColor = System.Drawing.Color.Transparent; + this.lbSize.ForeColor = System.Drawing.Color.Gray; + this.lbSize.Location = new System.Drawing.Point(272, 148); + this.lbSize.Name = "lbSize"; + this.lbSize.Size = new System.Drawing.Size(40, 13); + this.lbSize.TabIndex = 2; + this.lbSize.Text = "800 px"; + // + // cbUseCompression + // + this.cbUseCompression.BackColor = System.Drawing.Color.DimGray; + this.cbUseCompression.Checked = false; + this.cbUseCompression.Enabled = false; + this.cbUseCompression.Location = new System.Drawing.Point(15, 231); + this.cbUseCompression.Name = "cbUseCompression"; + this.cbUseCompression.Size = new System.Drawing.Size(297, 23); + this.cbUseCompression.TabIndex = 6; + this.cbUseCompression.Text = "Use GZip compression"; + this.cbUseCompression.UseVisualStyleBackColor = false; + // + // darkSlider1 + // + this.darkSlider1.BackColor = System.Drawing.Color.Transparent; + this.darkSlider1.CriticalPercent = ((uint)(0u)); + this.darkSlider1.Location = new System.Drawing.Point(15, 161); + this.darkSlider1.Maximum = 100; + this.darkSlider1.Minimum = 0; + this.darkSlider1.Name = "darkSlider1"; + this.darkSlider1.Size = new System.Drawing.Size(297, 17); + this.darkSlider1.TabIndex = 5; + this.darkSlider1.Text = "darkSlider1"; + this.darkSlider1.Value = 35; + this.darkSlider1.ValueChanging += new System.EventHandler(this.darkSlider1_ValueChanging); + // + // darkTextBox1 + // + this.darkTextBox1.BackColor = System.Drawing.Color.White; + this.darkTextBox1.Cursor = System.Windows.Forms.Cursors.IBeam; + this.darkTextBox1.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.darkTextBox1.ForeColor = System.Drawing.Color.Black; + this.darkTextBox1.Location = new System.Drawing.Point(12, 206); + this.darkTextBox1.Name = "darkTextBox1"; + this.darkTextBox1.Size = new System.Drawing.Size(300, 21); + this.darkTextBox1.TabIndex = 4; + this.darkTextBox1.TextAlign = System.Windows.Forms.HorizontalAlignment.Left; + // + // darkListBox1 + // + this.darkListBox1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(96)))), ((int)(((byte)(96)))), ((int)(((byte)(96))))); + this.darkListBox1.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.darkListBox1.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable; + this.darkListBox1.FormattingEnabled = true; + this.darkListBox1.ItemHeight = 22; + this.darkListBox1.Items.AddRange(new object[] { + "Portable Network Graphics (*.png)", + "Encapsulated PostScript (*.eps)", + "Scalable Vector Graphics (*.svg)"}); + this.darkListBox1.Location = new System.Drawing.Point(12, 25); + this.darkListBox1.Name = "darkListBox1"; + this.darkListBox1.Size = new System.Drawing.Size(302, 110); + this.darkListBox1.TabIndex = 3; + this.darkListBox1.SelectedIndexChanged += new System.EventHandler(this.darkListBox1_SelectedIndexChanged); + // + // darkButton1 + // + this.darkButton1.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.darkButton1.Location = new System.Drawing.Point(144, 270); + this.darkButton1.Name = "darkButton1"; + this.darkButton1.Size = new System.Drawing.Size(82, 23); + this.darkButton1.TabIndex = 1; + this.darkButton1.Text = "Cancel"; + this.darkButton1.UseVisualStyleBackColor = true; + // + // btnExport + // + this.btnExport.DialogResult = System.Windows.Forms.DialogResult.OK; + this.btnExport.Location = new System.Drawing.Point(232, 270); + this.btnExport.Name = "btnExport"; + this.btnExport.Size = new System.Drawing.Size(82, 23); + this.btnExport.TabIndex = 0; + this.btnExport.Text = "Save"; + this.btnExport.UseVisualStyleBackColor = true; + // + // FormExport + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76))))); + this.ClientSize = new System.Drawing.Size(324, 299); + this.Controls.Add(this.cbUseCompression); + this.Controls.Add(this.darkSlider1); + this.Controls.Add(this.darkTextBox1); + this.Controls.Add(this.darkListBox1); + this.Controls.Add(this.label2); + this.Controls.Add(this.lbSize); + this.Controls.Add(this.label1); + this.Controls.Add(this.label3); + this.Controls.Add(this.darkButton1); + this.Controls.Add(this.btnExport); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ForeColor = System.Drawing.Color.White; + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "FormExport"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Export Image"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private Controls.DarkButton btnExport; + private Controls.DarkButton darkButton1; + private Controls.DarkListBox darkListBox1; + private System.Windows.Forms.Label label2; + private Controls.DarkTextBox darkTextBox1; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label1; + private Controls.DarkSlider darkSlider1; + private System.Windows.Forms.Label lbSize; + private Controls.DarkCheckBox cbUseCompression; + } } \ No newline at end of file diff --git a/Triangle.NET/TestApp/FormExport.cs b/src/Triangle.Viewer/FormExport.cs similarity index 96% rename from Triangle.NET/TestApp/FormExport.cs rename to src/Triangle.Viewer/FormExport.cs index 58ad249..8d8fad1 100644 --- a/Triangle.NET/TestApp/FormExport.cs +++ b/src/Triangle.Viewer/FormExport.cs @@ -1,82 +1,82 @@ -using System; -using System.Drawing; -using System.IO; -using System.Windows.Forms; - -namespace MeshExplorer -{ - public partial class FormExport : Form - { - public FormExport() - { - InitializeComponent(); - } - - public int ImageFormat - { - get { return darkListBox1.SelectedIndex; } - } - - public int ImageSize - { - get - { - int size = (int)((2000.0 - 200.0) / 100.0 * darkSlider1.Value + 200.0); - return size - (size % 50); - } - } - - public string ImageName - { - get { return darkTextBox1.Text; } - set { darkTextBox1.Text = value; } - } - - public bool UseCompression - { - get { return cbUseCompression.Enabled && cbUseCompression.Checked; } - } - - protected override void OnPaint(PaintEventArgs e) - { - base.OnPaint(e); - - var rect = this.ClientRectangle; - rect.Height -= 40; - - e.Graphics.FillRectangle(Brushes.DimGray, rect); - } - - private void darkListBox1_SelectedIndexChanged(object sender, EventArgs e) - { - string filename = darkTextBox1.Text; - - if (!String.IsNullOrWhiteSpace(filename)) - { - string ext = ".png"; - - cbUseCompression.Enabled = darkListBox1.SelectedIndex > 0; - - if (darkListBox1.SelectedIndex == 1) - { - ext = ".eps"; - } - else if (darkListBox1.SelectedIndex == 2) - { - ext = ".svg"; - } - - darkTextBox1.Text = Path.ChangeExtension(filename, ext); - } - } - - private void darkSlider1_ValueChanging(object sender, EventArgs e) - { - int size = (int)((2000.0 - 200.0) / 100.0 * darkSlider1.Value + 200.0); - - size = size - (size % 50); - - lbSize.Text = size + " px"; - } - } -} +using System; +using System.Drawing; +using System.IO; +using System.Windows.Forms; + +namespace MeshExplorer +{ + public partial class FormExport : Form + { + public FormExport() + { + InitializeComponent(); + } + + public int ImageFormat + { + get { return darkListBox1.SelectedIndex; } + } + + public int ImageSize + { + get + { + int size = (int)((2000.0 - 200.0) / 100.0 * darkSlider1.Value + 200.0); + return size - (size % 50); + } + } + + public string ImageName + { + get { return darkTextBox1.Text; } + set { darkTextBox1.Text = value; } + } + + public bool UseCompression + { + get { return cbUseCompression.Enabled && cbUseCompression.Checked; } + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + + var rect = this.ClientRectangle; + rect.Height -= 40; + + e.Graphics.FillRectangle(Brushes.DimGray, rect); + } + + private void darkListBox1_SelectedIndexChanged(object sender, EventArgs e) + { + string filename = darkTextBox1.Text; + + if (!String.IsNullOrWhiteSpace(filename)) + { + string ext = ".png"; + + cbUseCompression.Enabled = darkListBox1.SelectedIndex > 0; + + if (darkListBox1.SelectedIndex == 1) + { + ext = ".eps"; + } + else if (darkListBox1.SelectedIndex == 2) + { + ext = ".svg"; + } + + darkTextBox1.Text = Path.ChangeExtension(filename, ext); + } + } + + private void darkSlider1_ValueChanging(object sender, EventArgs e) + { + int size = (int)((2000.0 - 200.0) / 100.0 * darkSlider1.Value + 200.0); + + size = size - (size % 50); + + lbSize.Text = size + " px"; + } + } +} diff --git a/Triangle.NET/TestApp/FormExport.resx b/src/Triangle.Viewer/FormExport.resx similarity index 97% rename from Triangle.NET/TestApp/FormExport.resx rename to src/Triangle.Viewer/FormExport.resx index 29dcb1b..1af7de1 100644 --- a/Triangle.NET/TestApp/FormExport.resx +++ b/src/Triangle.Viewer/FormExport.resx @@ -1,120 +1,120 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + \ No newline at end of file diff --git a/Triangle.NET/TestApp/FormGenerator.Designer.cs b/src/Triangle.Viewer/FormGenerator.Designer.cs similarity index 97% rename from Triangle.NET/TestApp/FormGenerator.Designer.cs rename to src/Triangle.Viewer/FormGenerator.Designer.cs index efc2b44..ae1855c 100644 --- a/Triangle.NET/TestApp/FormGenerator.Designer.cs +++ b/src/Triangle.Viewer/FormGenerator.Designer.cs @@ -1,242 +1,242 @@ -namespace MeshExplorer -{ - partial class FormGenerator - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.lbParam1 = new System.Windows.Forms.Label(); - this.lbParam2 = new System.Windows.Forms.Label(); - this.lbParam3 = new System.Windows.Forms.Label(); - this.lbParam1Val = new System.Windows.Forms.Label(); - this.lbParam2Val = new System.Windows.Forms.Label(); - this.lbParam3Val = new System.Windows.Forms.Label(); - this.lbDescription = new System.Windows.Forms.Label(); - this.sliderParam3 = new MeshExplorer.Controls.DarkSlider(); - this.sliderParam2 = new MeshExplorer.Controls.DarkSlider(); - this.sliderParam1 = new MeshExplorer.Controls.DarkSlider(); - this.darkListBox1 = new MeshExplorer.Controls.DarkListBox(); - this.btnClose = new MeshExplorer.Controls.DarkButton(); - this.btnGenerate = new MeshExplorer.Controls.DarkButton(); - this.SuspendLayout(); - // - // lbParam1 - // - this.lbParam1.BackColor = System.Drawing.Color.DimGray; - this.lbParam1.ForeColor = System.Drawing.Color.White; - this.lbParam1.Location = new System.Drawing.Point(171, 24); - this.lbParam1.Name = "lbParam1"; - this.lbParam1.Size = new System.Drawing.Size(114, 13); - this.lbParam1.TabIndex = 4; - this.lbParam1.Text = "Param 1:"; - // - // lbParam2 - // - this.lbParam2.BackColor = System.Drawing.Color.DimGray; - this.lbParam2.ForeColor = System.Drawing.Color.White; - this.lbParam2.Location = new System.Drawing.Point(171, 47); - this.lbParam2.Name = "lbParam2"; - this.lbParam2.Size = new System.Drawing.Size(114, 13); - this.lbParam2.TabIndex = 4; - this.lbParam2.Text = "Param 2:"; - // - // lbParam3 - // - this.lbParam3.BackColor = System.Drawing.Color.DimGray; - this.lbParam3.ForeColor = System.Drawing.Color.White; - this.lbParam3.Location = new System.Drawing.Point(171, 70); - this.lbParam3.Name = "lbParam3"; - this.lbParam3.Size = new System.Drawing.Size(114, 13); - this.lbParam3.TabIndex = 4; - this.lbParam3.Text = "Param 3:"; - // - // lbParam1Val - // - this.lbParam1Val.BackColor = System.Drawing.Color.DimGray; - this.lbParam1Val.ForeColor = System.Drawing.Color.White; - this.lbParam1Val.Location = new System.Drawing.Point(436, 24); - this.lbParam1Val.Name = "lbParam1Val"; - this.lbParam1Val.Size = new System.Drawing.Size(40, 13); - this.lbParam1Val.TabIndex = 4; - this.lbParam1Val.Text = "-"; - // - // lbParam2Val - // - this.lbParam2Val.BackColor = System.Drawing.Color.DimGray; - this.lbParam2Val.ForeColor = System.Drawing.Color.White; - this.lbParam2Val.Location = new System.Drawing.Point(436, 47); - this.lbParam2Val.Name = "lbParam2Val"; - this.lbParam2Val.Size = new System.Drawing.Size(40, 13); - this.lbParam2Val.TabIndex = 4; - this.lbParam2Val.Text = "-"; - // - // lbParam3Val - // - this.lbParam3Val.BackColor = System.Drawing.Color.DimGray; - this.lbParam3Val.ForeColor = System.Drawing.Color.White; - this.lbParam3Val.Location = new System.Drawing.Point(436, 70); - this.lbParam3Val.Name = "lbParam3Val"; - this.lbParam3Val.Size = new System.Drawing.Size(40, 13); - this.lbParam3Val.TabIndex = 4; - this.lbParam3Val.Text = "-"; - // - // lbDescription - // - this.lbDescription.BackColor = System.Drawing.Color.DimGray; - this.lbDescription.ForeColor = System.Drawing.Color.White; - this.lbDescription.Location = new System.Drawing.Point(171, 104); - this.lbDescription.Name = "lbDescription"; - this.lbDescription.Size = new System.Drawing.Size(94, 13); - this.lbDescription.TabIndex = 4; - this.lbDescription.Text = "Description"; - // - // sliderParam3 - // - this.sliderParam3.BackColor = System.Drawing.Color.Transparent; - this.sliderParam3.CriticalPercent = ((uint)(0u)); - this.sliderParam3.Enabled = false; - this.sliderParam3.Location = new System.Drawing.Point(291, 64); - this.sliderParam3.Maximum = 100; - this.sliderParam3.Minimum = 0; - this.sliderParam3.Name = "sliderParam3"; - this.sliderParam3.Size = new System.Drawing.Size(138, 23); - this.sliderParam3.TabIndex = 3; - this.sliderParam3.Text = "sliderParam3"; - this.sliderParam3.Value = 50; - this.sliderParam3.ValueChanging += new System.EventHandler(this.sliderParam3_ValueChanging); - // - // sliderParam2 - // - this.sliderParam2.BackColor = System.Drawing.Color.Transparent; - this.sliderParam2.CriticalPercent = ((uint)(0u)); - this.sliderParam2.Enabled = false; - this.sliderParam2.Location = new System.Drawing.Point(291, 41); - this.sliderParam2.Maximum = 100; - this.sliderParam2.Minimum = 0; - this.sliderParam2.Name = "sliderParam2"; - this.sliderParam2.Size = new System.Drawing.Size(138, 23); - this.sliderParam2.TabIndex = 3; - this.sliderParam2.Text = "sliderParam2"; - this.sliderParam2.Value = 50; - this.sliderParam2.ValueChanging += new System.EventHandler(this.sliderParam2_ValueChanging); - // - // sliderParam1 - // - this.sliderParam1.BackColor = System.Drawing.Color.Transparent; - this.sliderParam1.CriticalPercent = ((uint)(0u)); - this.sliderParam1.Enabled = false; - this.sliderParam1.Location = new System.Drawing.Point(291, 18); - this.sliderParam1.Maximum = 100; - this.sliderParam1.Minimum = 0; - this.sliderParam1.Name = "sliderParam1"; - this.sliderParam1.Size = new System.Drawing.Size(138, 23); - this.sliderParam1.TabIndex = 3; - this.sliderParam1.Text = "sliderParam1"; - this.sliderParam1.Value = 50; - this.sliderParam1.ValueChanging += new System.EventHandler(this.sliderParam1_ValueChanging); - // - // darkListBox1 - // - this.darkListBox1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(96)))), ((int)(((byte)(96)))), ((int)(((byte)(96))))); - this.darkListBox1.BorderStyle = System.Windows.Forms.BorderStyle.None; - this.darkListBox1.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable; - this.darkListBox1.FormattingEnabled = true; - this.darkListBox1.ItemHeight = 22; - this.darkListBox1.Location = new System.Drawing.Point(5, 5); - this.darkListBox1.Name = "darkListBox1"; - this.darkListBox1.Size = new System.Drawing.Size(160, 228); - this.darkListBox1.TabIndex = 2; - this.darkListBox1.SelectedIndexChanged += new System.EventHandler(this.darkListBox1_SelectedIndexChanged); - // - // btnClose - // - this.btnClose.Location = new System.Drawing.Point(291, 247); - this.btnClose.Name = "btnClose"; - this.btnClose.Size = new System.Drawing.Size(94, 23); - this.btnClose.TabIndex = 1; - this.btnClose.Text = "Close"; - this.btnClose.UseVisualStyleBackColor = true; - this.btnClose.Click += new System.EventHandler(this.btnClose_Click); - // - // btnGenerate - // - this.btnGenerate.Location = new System.Drawing.Point(391, 247); - this.btnGenerate.Name = "btnGenerate"; - this.btnGenerate.Size = new System.Drawing.Size(94, 23); - this.btnGenerate.TabIndex = 0; - this.btnGenerate.Text = "Generate"; - this.btnGenerate.UseVisualStyleBackColor = true; - this.btnGenerate.Click += new System.EventHandler(this.btnGenerate_Click); - // - // FormGenerator - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76))))); - this.ClientSize = new System.Drawing.Size(495, 278); - this.Controls.Add(this.lbParam3Val); - this.Controls.Add(this.lbDescription); - this.Controls.Add(this.lbParam3); - this.Controls.Add(this.lbParam2Val); - this.Controls.Add(this.lbParam2); - this.Controls.Add(this.lbParam1Val); - this.Controls.Add(this.lbParam1); - this.Controls.Add(this.sliderParam3); - this.Controls.Add(this.sliderParam2); - this.Controls.Add(this.sliderParam1); - this.Controls.Add(this.darkListBox1); - this.Controls.Add(this.btnClose); - this.Controls.Add(this.btnGenerate); - this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; - this.MaximizeBox = false; - this.MinimizeBox = false; - this.Name = "FormGenerator"; - this.ShowInTaskbar = false; - this.Text = "Input Generator"; - this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormGenerator_FormClosing); - this.Load += new System.EventHandler(this.FormGenerator_Load); - this.ResumeLayout(false); - - } - - #endregion - - private Controls.DarkButton btnGenerate; - private Controls.DarkButton btnClose; - private Controls.DarkListBox darkListBox1; - private Controls.DarkSlider sliderParam1; - private Controls.DarkSlider sliderParam2; - private Controls.DarkSlider sliderParam3; - private System.Windows.Forms.Label lbParam1; - private System.Windows.Forms.Label lbParam2; - private System.Windows.Forms.Label lbParam3; - private System.Windows.Forms.Label lbParam1Val; - private System.Windows.Forms.Label lbParam2Val; - private System.Windows.Forms.Label lbParam3Val; - private System.Windows.Forms.Label lbDescription; - } +namespace MeshExplorer +{ + partial class FormGenerator + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.lbParam1 = new System.Windows.Forms.Label(); + this.lbParam2 = new System.Windows.Forms.Label(); + this.lbParam3 = new System.Windows.Forms.Label(); + this.lbParam1Val = new System.Windows.Forms.Label(); + this.lbParam2Val = new System.Windows.Forms.Label(); + this.lbParam3Val = new System.Windows.Forms.Label(); + this.lbDescription = new System.Windows.Forms.Label(); + this.sliderParam3 = new MeshExplorer.Controls.DarkSlider(); + this.sliderParam2 = new MeshExplorer.Controls.DarkSlider(); + this.sliderParam1 = new MeshExplorer.Controls.DarkSlider(); + this.darkListBox1 = new MeshExplorer.Controls.DarkListBox(); + this.btnClose = new MeshExplorer.Controls.DarkButton(); + this.btnGenerate = new MeshExplorer.Controls.DarkButton(); + this.SuspendLayout(); + // + // lbParam1 + // + this.lbParam1.BackColor = System.Drawing.Color.DimGray; + this.lbParam1.ForeColor = System.Drawing.Color.White; + this.lbParam1.Location = new System.Drawing.Point(171, 24); + this.lbParam1.Name = "lbParam1"; + this.lbParam1.Size = new System.Drawing.Size(114, 13); + this.lbParam1.TabIndex = 4; + this.lbParam1.Text = "Param 1:"; + // + // lbParam2 + // + this.lbParam2.BackColor = System.Drawing.Color.DimGray; + this.lbParam2.ForeColor = System.Drawing.Color.White; + this.lbParam2.Location = new System.Drawing.Point(171, 47); + this.lbParam2.Name = "lbParam2"; + this.lbParam2.Size = new System.Drawing.Size(114, 13); + this.lbParam2.TabIndex = 4; + this.lbParam2.Text = "Param 2:"; + // + // lbParam3 + // + this.lbParam3.BackColor = System.Drawing.Color.DimGray; + this.lbParam3.ForeColor = System.Drawing.Color.White; + this.lbParam3.Location = new System.Drawing.Point(171, 70); + this.lbParam3.Name = "lbParam3"; + this.lbParam3.Size = new System.Drawing.Size(114, 13); + this.lbParam3.TabIndex = 4; + this.lbParam3.Text = "Param 3:"; + // + // lbParam1Val + // + this.lbParam1Val.BackColor = System.Drawing.Color.DimGray; + this.lbParam1Val.ForeColor = System.Drawing.Color.White; + this.lbParam1Val.Location = new System.Drawing.Point(436, 24); + this.lbParam1Val.Name = "lbParam1Val"; + this.lbParam1Val.Size = new System.Drawing.Size(40, 13); + this.lbParam1Val.TabIndex = 4; + this.lbParam1Val.Text = "-"; + // + // lbParam2Val + // + this.lbParam2Val.BackColor = System.Drawing.Color.DimGray; + this.lbParam2Val.ForeColor = System.Drawing.Color.White; + this.lbParam2Val.Location = new System.Drawing.Point(436, 47); + this.lbParam2Val.Name = "lbParam2Val"; + this.lbParam2Val.Size = new System.Drawing.Size(40, 13); + this.lbParam2Val.TabIndex = 4; + this.lbParam2Val.Text = "-"; + // + // lbParam3Val + // + this.lbParam3Val.BackColor = System.Drawing.Color.DimGray; + this.lbParam3Val.ForeColor = System.Drawing.Color.White; + this.lbParam3Val.Location = new System.Drawing.Point(436, 70); + this.lbParam3Val.Name = "lbParam3Val"; + this.lbParam3Val.Size = new System.Drawing.Size(40, 13); + this.lbParam3Val.TabIndex = 4; + this.lbParam3Val.Text = "-"; + // + // lbDescription + // + this.lbDescription.BackColor = System.Drawing.Color.DimGray; + this.lbDescription.ForeColor = System.Drawing.Color.White; + this.lbDescription.Location = new System.Drawing.Point(171, 104); + this.lbDescription.Name = "lbDescription"; + this.lbDescription.Size = new System.Drawing.Size(94, 13); + this.lbDescription.TabIndex = 4; + this.lbDescription.Text = "Description"; + // + // sliderParam3 + // + this.sliderParam3.BackColor = System.Drawing.Color.Transparent; + this.sliderParam3.CriticalPercent = ((uint)(0u)); + this.sliderParam3.Enabled = false; + this.sliderParam3.Location = new System.Drawing.Point(291, 64); + this.sliderParam3.Maximum = 100; + this.sliderParam3.Minimum = 0; + this.sliderParam3.Name = "sliderParam3"; + this.sliderParam3.Size = new System.Drawing.Size(138, 23); + this.sliderParam3.TabIndex = 3; + this.sliderParam3.Text = "sliderParam3"; + this.sliderParam3.Value = 50; + this.sliderParam3.ValueChanging += new System.EventHandler(this.sliderParam3_ValueChanging); + // + // sliderParam2 + // + this.sliderParam2.BackColor = System.Drawing.Color.Transparent; + this.sliderParam2.CriticalPercent = ((uint)(0u)); + this.sliderParam2.Enabled = false; + this.sliderParam2.Location = new System.Drawing.Point(291, 41); + this.sliderParam2.Maximum = 100; + this.sliderParam2.Minimum = 0; + this.sliderParam2.Name = "sliderParam2"; + this.sliderParam2.Size = new System.Drawing.Size(138, 23); + this.sliderParam2.TabIndex = 3; + this.sliderParam2.Text = "sliderParam2"; + this.sliderParam2.Value = 50; + this.sliderParam2.ValueChanging += new System.EventHandler(this.sliderParam2_ValueChanging); + // + // sliderParam1 + // + this.sliderParam1.BackColor = System.Drawing.Color.Transparent; + this.sliderParam1.CriticalPercent = ((uint)(0u)); + this.sliderParam1.Enabled = false; + this.sliderParam1.Location = new System.Drawing.Point(291, 18); + this.sliderParam1.Maximum = 100; + this.sliderParam1.Minimum = 0; + this.sliderParam1.Name = "sliderParam1"; + this.sliderParam1.Size = new System.Drawing.Size(138, 23); + this.sliderParam1.TabIndex = 3; + this.sliderParam1.Text = "sliderParam1"; + this.sliderParam1.Value = 50; + this.sliderParam1.ValueChanging += new System.EventHandler(this.sliderParam1_ValueChanging); + // + // darkListBox1 + // + this.darkListBox1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(96)))), ((int)(((byte)(96)))), ((int)(((byte)(96))))); + this.darkListBox1.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.darkListBox1.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable; + this.darkListBox1.FormattingEnabled = true; + this.darkListBox1.ItemHeight = 22; + this.darkListBox1.Location = new System.Drawing.Point(5, 5); + this.darkListBox1.Name = "darkListBox1"; + this.darkListBox1.Size = new System.Drawing.Size(160, 228); + this.darkListBox1.TabIndex = 2; + this.darkListBox1.SelectedIndexChanged += new System.EventHandler(this.darkListBox1_SelectedIndexChanged); + // + // btnClose + // + this.btnClose.Location = new System.Drawing.Point(291, 247); + this.btnClose.Name = "btnClose"; + this.btnClose.Size = new System.Drawing.Size(94, 23); + this.btnClose.TabIndex = 1; + this.btnClose.Text = "Close"; + this.btnClose.UseVisualStyleBackColor = true; + this.btnClose.Click += new System.EventHandler(this.btnClose_Click); + // + // btnGenerate + // + this.btnGenerate.Location = new System.Drawing.Point(391, 247); + this.btnGenerate.Name = "btnGenerate"; + this.btnGenerate.Size = new System.Drawing.Size(94, 23); + this.btnGenerate.TabIndex = 0; + this.btnGenerate.Text = "Generate"; + this.btnGenerate.UseVisualStyleBackColor = true; + this.btnGenerate.Click += new System.EventHandler(this.btnGenerate_Click); + // + // FormGenerator + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76))))); + this.ClientSize = new System.Drawing.Size(495, 278); + this.Controls.Add(this.lbParam3Val); + this.Controls.Add(this.lbDescription); + this.Controls.Add(this.lbParam3); + this.Controls.Add(this.lbParam2Val); + this.Controls.Add(this.lbParam2); + this.Controls.Add(this.lbParam1Val); + this.Controls.Add(this.lbParam1); + this.Controls.Add(this.sliderParam3); + this.Controls.Add(this.sliderParam2); + this.Controls.Add(this.sliderParam1); + this.Controls.Add(this.darkListBox1); + this.Controls.Add(this.btnClose); + this.Controls.Add(this.btnGenerate); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "FormGenerator"; + this.ShowInTaskbar = false; + this.Text = "Input Generator"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormGenerator_FormClosing); + this.Load += new System.EventHandler(this.FormGenerator_Load); + this.ResumeLayout(false); + + } + + #endregion + + private Controls.DarkButton btnGenerate; + private Controls.DarkButton btnClose; + private Controls.DarkListBox darkListBox1; + private Controls.DarkSlider sliderParam1; + private Controls.DarkSlider sliderParam2; + private Controls.DarkSlider sliderParam3; + private System.Windows.Forms.Label lbParam1; + private System.Windows.Forms.Label lbParam2; + private System.Windows.Forms.Label lbParam3; + private System.Windows.Forms.Label lbParam1Val; + private System.Windows.Forms.Label lbParam2Val; + private System.Windows.Forms.Label lbParam3Val; + private System.Windows.Forms.Label lbDescription; + } } \ No newline at end of file diff --git a/Triangle.NET/TestApp/FormGenerator.cs b/src/Triangle.Viewer/FormGenerator.cs similarity index 96% rename from Triangle.NET/TestApp/FormGenerator.cs rename to src/Triangle.Viewer/FormGenerator.cs index e88145e..61776f3 100644 --- a/Triangle.NET/TestApp/FormGenerator.cs +++ b/src/Triangle.Viewer/FormGenerator.cs @@ -1,150 +1,150 @@ -using System; -using System.Drawing; -using System.Windows.Forms; -using MeshExplorer.Generators; -using TriangleNet.Geometry; - -namespace MeshExplorer -{ - public partial class FormGenerator : Form - { - public event EventHandler InputGenerated; - - IGenerator currentGenerator; - - public FormGenerator() - { - InitializeComponent(); - } - - private void UpdateControls() - { - if (currentGenerator.ParameterCount > 0) - { - sliderParam1.Enabled = true; - lbParam1.Text = currentGenerator.ParameterDescription(0); - lbParam1Val.Text = currentGenerator.ParameterDescription(0, sliderParam1.Value); - } - else - { - sliderParam1.Enabled = false; - lbParam1.Text = ""; - lbParam1Val.Text = ""; - } - - if (currentGenerator.ParameterCount > 1) - { - sliderParam2.Enabled = true; - lbParam2.Text = currentGenerator.ParameterDescription(1); - lbParam2Val.Text = currentGenerator.ParameterDescription(1, sliderParam2.Value); - } - else - { - sliderParam2.Enabled = false; - lbParam2.Text = ""; - lbParam2Val.Text = ""; - } - - if (currentGenerator.ParameterCount > 2) - { - sliderParam3.Enabled = true; - lbParam3.Text = currentGenerator.ParameterDescription(2); - lbParam3Val.Text = currentGenerator.ParameterDescription(2, sliderParam3.Value); - } - else - { - sliderParam3.Enabled = false; - lbParam3.Text = ""; - lbParam3Val.Text = ""; - } - } - - private void btnGenerate_Click(object sender, EventArgs e) - { - if (currentGenerator != null && InputGenerated != null) - { - try - { - var input = currentGenerator.Generate(sliderParam1.Value, - sliderParam2.Value, sliderParam3.Value); - - InputGenerated(input, EventArgs.Empty); - } - catch (Exception ex) - { - DarkMessageBox.Show("Exception", ex.Message); - } - } - } - - private void btnClose_Click(object sender, EventArgs e) - { - this.Hide(); - } - - private void FormGenerator_FormClosing(object sender, FormClosingEventArgs e) - { - if (e.CloseReason == CloseReason.UserClosing) - { - e.Cancel = true; - this.Hide(); - } - } - - protected override void OnPaint(PaintEventArgs e) - { - base.OnPaint(e); - - var rect = this.ClientRectangle; - rect.Height -= 40; - - e.Graphics.FillRectangle(Brushes.DimGray, rect); - } - - private void darkListBox1_SelectedIndexChanged(object sender, EventArgs e) - { - currentGenerator = darkListBox1.SelectedItem as IGenerator; - - if (currentGenerator != null) - { - UpdateControls(); - } - } - - private void sliderParam1_ValueChanging(object sender, EventArgs e) - { - if (currentGenerator != null) - { - lbParam1Val.Text = currentGenerator.ParameterDescription(0, sliderParam1.Value); - } - } - - private void sliderParam2_ValueChanging(object sender, EventArgs e) - { - if (currentGenerator != null) - { - lbParam2Val.Text = currentGenerator.ParameterDescription(1, sliderParam2.Value); - } - } - - private void sliderParam3_ValueChanging(object sender, EventArgs e) - { - if (currentGenerator != null) - { - lbParam3Val.Text = currentGenerator.ParameterDescription(2, sliderParam3.Value); - } - } - - private void FormGenerator_Load(object sender, EventArgs e) - { - darkListBox1.Items.Add(new RandomPoints()); - darkListBox1.Items.Add(new RandomPointsCircle()); - darkListBox1.Items.Add(new StarInBox()); - darkListBox1.Items.Add(new RingPolygon()); - darkListBox1.Items.Add(new BoxWithHole()); - darkListBox1.Items.Add(new CircleWithHole()); - - darkListBox1.SelectedIndex = 0; - } - } -} +using System; +using System.Drawing; +using System.Windows.Forms; +using MeshExplorer.Generators; +using TriangleNet.Geometry; + +namespace MeshExplorer +{ + public partial class FormGenerator : Form + { + public event EventHandler InputGenerated; + + IGenerator currentGenerator; + + public FormGenerator() + { + InitializeComponent(); + } + + private void UpdateControls() + { + if (currentGenerator.ParameterCount > 0) + { + sliderParam1.Enabled = true; + lbParam1.Text = currentGenerator.ParameterDescription(0); + lbParam1Val.Text = currentGenerator.ParameterDescription(0, sliderParam1.Value); + } + else + { + sliderParam1.Enabled = false; + lbParam1.Text = ""; + lbParam1Val.Text = ""; + } + + if (currentGenerator.ParameterCount > 1) + { + sliderParam2.Enabled = true; + lbParam2.Text = currentGenerator.ParameterDescription(1); + lbParam2Val.Text = currentGenerator.ParameterDescription(1, sliderParam2.Value); + } + else + { + sliderParam2.Enabled = false; + lbParam2.Text = ""; + lbParam2Val.Text = ""; + } + + if (currentGenerator.ParameterCount > 2) + { + sliderParam3.Enabled = true; + lbParam3.Text = currentGenerator.ParameterDescription(2); + lbParam3Val.Text = currentGenerator.ParameterDescription(2, sliderParam3.Value); + } + else + { + sliderParam3.Enabled = false; + lbParam3.Text = ""; + lbParam3Val.Text = ""; + } + } + + private void btnGenerate_Click(object sender, EventArgs e) + { + if (currentGenerator != null && InputGenerated != null) + { + try + { + var input = currentGenerator.Generate(sliderParam1.Value, + sliderParam2.Value, sliderParam3.Value); + + InputGenerated(input, EventArgs.Empty); + } + catch (Exception ex) + { + DarkMessageBox.Show("Exception", ex.Message); + } + } + } + + private void btnClose_Click(object sender, EventArgs e) + { + this.Hide(); + } + + private void FormGenerator_FormClosing(object sender, FormClosingEventArgs e) + { + if (e.CloseReason == CloseReason.UserClosing) + { + e.Cancel = true; + this.Hide(); + } + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + + var rect = this.ClientRectangle; + rect.Height -= 40; + + e.Graphics.FillRectangle(Brushes.DimGray, rect); + } + + private void darkListBox1_SelectedIndexChanged(object sender, EventArgs e) + { + currentGenerator = darkListBox1.SelectedItem as IGenerator; + + if (currentGenerator != null) + { + UpdateControls(); + } + } + + private void sliderParam1_ValueChanging(object sender, EventArgs e) + { + if (currentGenerator != null) + { + lbParam1Val.Text = currentGenerator.ParameterDescription(0, sliderParam1.Value); + } + } + + private void sliderParam2_ValueChanging(object sender, EventArgs e) + { + if (currentGenerator != null) + { + lbParam2Val.Text = currentGenerator.ParameterDescription(1, sliderParam2.Value); + } + } + + private void sliderParam3_ValueChanging(object sender, EventArgs e) + { + if (currentGenerator != null) + { + lbParam3Val.Text = currentGenerator.ParameterDescription(2, sliderParam3.Value); + } + } + + private void FormGenerator_Load(object sender, EventArgs e) + { + darkListBox1.Items.Add(new RandomPoints()); + darkListBox1.Items.Add(new RandomPointsCircle()); + darkListBox1.Items.Add(new StarInBox()); + darkListBox1.Items.Add(new RingPolygon()); + darkListBox1.Items.Add(new BoxWithHole()); + darkListBox1.Items.Add(new CircleWithHole()); + + darkListBox1.SelectedIndex = 0; + } + } +} diff --git a/Triangle.NET/TestApp/FormGenerator.resx b/src/Triangle.Viewer/FormGenerator.resx similarity index 97% rename from Triangle.NET/TestApp/FormGenerator.resx rename to src/Triangle.Viewer/FormGenerator.resx index 29dcb1b..1af7de1 100644 --- a/Triangle.NET/TestApp/FormGenerator.resx +++ b/src/Triangle.Viewer/FormGenerator.resx @@ -1,120 +1,120 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + \ No newline at end of file diff --git a/Triangle.NET/TestApp/FormLog.Designer.cs b/src/Triangle.Viewer/FormLog.Designer.cs similarity index 97% rename from Triangle.NET/TestApp/FormLog.Designer.cs rename to src/Triangle.Viewer/FormLog.Designer.cs index f680174..bf0cf74 100644 --- a/Triangle.NET/TestApp/FormLog.Designer.cs +++ b/src/Triangle.Viewer/FormLog.Designer.cs @@ -1,90 +1,90 @@ -namespace MeshExplorer -{ - partial class FormLog - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.listLog = new System.Windows.Forms.ListView(); - this.colMessage = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.colInfo = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.SuspendLayout(); - // - // listLog - // - this.listLog.BackColor = System.Drawing.Color.White; - this.listLog.BorderStyle = System.Windows.Forms.BorderStyle.None; - this.listLog.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.colMessage, - this.colInfo}); - this.listLog.Dock = System.Windows.Forms.DockStyle.Fill; - this.listLog.ForeColor = System.Drawing.Color.White; - this.listLog.FullRowSelect = true; - this.listLog.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; - this.listLog.Location = new System.Drawing.Point(0, 0); - this.listLog.Name = "listLog"; - this.listLog.Size = new System.Drawing.Size(584, 262); - this.listLog.TabIndex = 2; - this.listLog.UseCompatibleStateImageBehavior = false; - this.listLog.View = System.Windows.Forms.View.Details; - this.listLog.DoubleClick += new System.EventHandler(this.listLog_DoubleClick); - this.listLog.KeyDown += new System.Windows.Forms.KeyEventHandler(this.listLog_KeyDown); - // - // colMessage - // - this.colMessage.Text = "Message"; - this.colMessage.Width = 350; - // - // colInfo - // - this.colInfo.Text = "Info"; - this.colInfo.Width = 200; - // - // FormLog - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(584, 262); - this.Controls.Add(this.listLog); - this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.MaximizeBox = false; - this.MinimizeBox = false; - this.Name = "FormLog"; - this.ShowIcon = false; - this.ShowInTaskbar = false; - this.Text = "Log"; - this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormLog_FormClosing); - this.ResumeLayout(false); - - } - - #endregion - - private System.Windows.Forms.ListView listLog; - private System.Windows.Forms.ColumnHeader colMessage; - private System.Windows.Forms.ColumnHeader colInfo; - } +namespace MeshExplorer +{ + partial class FormLog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.listLog = new System.Windows.Forms.ListView(); + this.colMessage = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.colInfo = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.SuspendLayout(); + // + // listLog + // + this.listLog.BackColor = System.Drawing.Color.White; + this.listLog.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.listLog.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.colMessage, + this.colInfo}); + this.listLog.Dock = System.Windows.Forms.DockStyle.Fill; + this.listLog.ForeColor = System.Drawing.Color.White; + this.listLog.FullRowSelect = true; + this.listLog.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; + this.listLog.Location = new System.Drawing.Point(0, 0); + this.listLog.Name = "listLog"; + this.listLog.Size = new System.Drawing.Size(584, 262); + this.listLog.TabIndex = 2; + this.listLog.UseCompatibleStateImageBehavior = false; + this.listLog.View = System.Windows.Forms.View.Details; + this.listLog.DoubleClick += new System.EventHandler(this.listLog_DoubleClick); + this.listLog.KeyDown += new System.Windows.Forms.KeyEventHandler(this.listLog_KeyDown); + // + // colMessage + // + this.colMessage.Text = "Message"; + this.colMessage.Width = 350; + // + // colInfo + // + this.colInfo.Text = "Info"; + this.colInfo.Width = 200; + // + // FormLog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(584, 262); + this.Controls.Add(this.listLog); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "FormLog"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.Text = "Log"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormLog_FormClosing); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.ListView listLog; + private System.Windows.Forms.ColumnHeader colMessage; + private System.Windows.Forms.ColumnHeader colInfo; + } } \ No newline at end of file diff --git a/Triangle.NET/TestApp/FormLog.cs b/src/Triangle.Viewer/FormLog.cs similarity index 94% rename from Triangle.NET/TestApp/FormLog.cs rename to src/Triangle.Viewer/FormLog.cs index 1d2677b..ca38d8d 100644 --- a/Triangle.NET/TestApp/FormLog.cs +++ b/src/Triangle.Viewer/FormLog.cs @@ -1,139 +1,138 @@ -using System; -using System.Drawing; -using System.Text; -using System.Windows.Forms; -using TriangleNet; -using TriangleNet.Logging; - -namespace MeshExplorer -{ - public partial class FormLog : Form - { - public FormLog() - { - InitializeComponent(); - } - - public void AddItem(string message, bool warning) - { - var log = Log.Instance; - - if (warning) - { - log.Warning(message, "Mesh Explorer"); - } - else - { - log.Info(message); - } - } - - public void UpdateItems() - { - listLog.Items.Clear(); - - var log = Log.Instance; - - foreach (var item in log.Data) - { - listLog.Items.Add(CreateListViewItem(item)); - } - } - - private ListViewItem CreateListViewItem(LogItem item) - { - ListViewItem lvi = new ListViewItem(new string[] { item.Message, item.Info }); - - if (item.Level == LogLevel.Error) - { - lvi.ForeColor = Color.DarkRed; - } - else if (item.Level == LogLevel.Warning) - { - lvi.ForeColor = Color.Peru; - } - else - { - lvi.ForeColor = Color.Black; - } - - lvi.UseItemStyleForSubItems = true; - - return lvi; - } - - private void FormLog_FormClosing(object sender, FormClosingEventArgs e) - { - if (e.CloseReason == CloseReason.UserClosing) - { - e.Cancel = true; - this.Hide(); - } - } - - private void listLog_KeyDown(object sender, KeyEventArgs e) - { - if (e.KeyCode == Keys.C) - { - if (ModifierKeys == Keys.Control) - { - var selection = listLog.SelectedItems; - - if (selection != null && selection.Count > 0) - { - StringBuilder sb = new StringBuilder(); - - foreach (var item in selection) - { - GetRowText(sb, item); - } - - Clipboard.SetText(sb.ToString()); - } - } - } - else if (e.KeyCode == Keys.Delete) - { - if (ModifierKeys == Keys.Control) - { - listLog.Items.Clear(); - Log.Instance.Clear(); - } - } - } - - private void listLog_DoubleClick(object sender, EventArgs e) - { - StringBuilder sb = new StringBuilder(); - - foreach (var item in listLog.SelectedItems) - { - GetRowText(sb, item); - } - - if (sb.Length > 0) - { - Clipboard.SetText(sb.ToString()); - } - } - - private void GetRowText(StringBuilder sb, object item) - { - var row = item as ListViewItem; - - if (row != null) - { - foreach (var col in row.SubItems) - { - var lvi = col as ListViewItem.ListViewSubItem; - - if (lvi != null) - { - sb.Append(lvi.Text); - sb.Append("; "); - } - } - } - } - } -} +using System; +using System.Drawing; +using System.Text; +using System.Windows.Forms; +using TriangleNet; + +namespace MeshExplorer +{ + public partial class FormLog : Form + { + public FormLog() + { + InitializeComponent(); + } + + public void AddItem(string message, bool warning) + { + var log = Log.Instance; + + if (warning) + { + log.Warning(message, "Mesh Explorer"); + } + else + { + log.Info(message); + } + } + + public void UpdateItems() + { + listLog.Items.Clear(); + + var log = Log.Instance; + + foreach (var item in log.Data) + { + listLog.Items.Add(CreateListViewItem(item)); + } + } + + private ListViewItem CreateListViewItem(LogItem item) + { + ListViewItem lvi = new ListViewItem(new string[] { item.Message, item.Details }); + + if (item.Level == LogLevel.Error) + { + lvi.ForeColor = Color.DarkRed; + } + else if (item.Level == LogLevel.Warning) + { + lvi.ForeColor = Color.Peru; + } + else + { + lvi.ForeColor = Color.Black; + } + + lvi.UseItemStyleForSubItems = true; + + return lvi; + } + + private void FormLog_FormClosing(object sender, FormClosingEventArgs e) + { + if (e.CloseReason == CloseReason.UserClosing) + { + e.Cancel = true; + this.Hide(); + } + } + + private void listLog_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.C) + { + if (ModifierKeys == Keys.Control) + { + var selection = listLog.SelectedItems; + + if (selection != null && selection.Count > 0) + { + StringBuilder sb = new StringBuilder(); + + foreach (var item in selection) + { + GetRowText(sb, item); + } + + Clipboard.SetText(sb.ToString()); + } + } + } + else if (e.KeyCode == Keys.Delete) + { + if (ModifierKeys == Keys.Control) + { + listLog.Items.Clear(); + Log.Instance.Clear(); + } + } + } + + private void listLog_DoubleClick(object sender, EventArgs e) + { + StringBuilder sb = new StringBuilder(); + + foreach (var item in listLog.SelectedItems) + { + GetRowText(sb, item); + } + + if (sb.Length > 0) + { + Clipboard.SetText(sb.ToString()); + } + } + + private void GetRowText(StringBuilder sb, object item) + { + var row = item as ListViewItem; + + if (row != null) + { + foreach (var col in row.SubItems) + { + var lvi = col as ListViewItem.ListViewSubItem; + + if (lvi != null) + { + sb.Append(lvi.Text); + sb.Append("; "); + } + } + } + } + } +} diff --git a/Triangle.NET/TestApp/FormLog.resx b/src/Triangle.Viewer/FormLog.resx similarity index 97% rename from Triangle.NET/TestApp/FormLog.resx rename to src/Triangle.Viewer/FormLog.resx index 29dcb1b..1af7de1 100644 --- a/Triangle.NET/TestApp/FormLog.resx +++ b/src/Triangle.Viewer/FormLog.resx @@ -1,120 +1,120 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + \ No newline at end of file diff --git a/Triangle.NET/TestApp/FormMain.Designer.cs b/src/Triangle.Viewer/FormMain.Designer.cs similarity index 98% rename from Triangle.NET/TestApp/FormMain.Designer.cs rename to src/Triangle.Viewer/FormMain.Designer.cs index ac0e044..f76312c 100644 --- a/Triangle.NET/TestApp/FormMain.Designer.cs +++ b/src/Triangle.Viewer/FormMain.Designer.cs @@ -1,418 +1,418 @@ -namespace MeshExplorer -{ - partial class FormMain - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.splitContainer = new System.Windows.Forms.SplitContainer(); - this.btnSmooth = new MeshExplorer.Controls.DarkButton(); - this.flatTabControl1 = new MeshExplorer.Controls.DarkTabControl(); - this.tabPage1 = new System.Windows.Forms.TabPage(); - this.meshControlView = new MeshExplorer.Views.MeshControlView(); - this.tabPage2 = new System.Windows.Forms.TabPage(); - this.statisticView = new MeshExplorer.Views.StatisticView(); - this.tabPage3 = new System.Windows.Forms.TabPage(); - this.aboutView = new MeshExplorer.Views.AboutView(); - this.menuStrip1 = new System.Windows.Forms.MenuStrip(); - this.menuFile = new System.Windows.Forms.ToolStripMenuItem(); - this.menuFileOpen = new System.Windows.Forms.ToolStripMenuItem(); - this.menuFileSave = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); - this.menuFileExport = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); - this.menuFileQuit = new System.Windows.Forms.ToolStripMenuItem(); - this.menuView = new System.Windows.Forms.ToolStripMenuItem(); - this.menuViewVoronoi = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); - this.menuViewLog = new System.Windows.Forms.ToolStripMenuItem(); - this.menuTools = new System.Windows.Forms.ToolStripMenuItem(); - this.menuToolsGen = new System.Windows.Forms.ToolStripMenuItem(); - this.menuToolsCheck = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator(); - this.menuToolsTopology = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator(); - this.menuToolsRcm = new System.Windows.Forms.ToolStripMenuItem(); - this.btnMesh = new MeshExplorer.Controls.DarkButton(); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit(); - this.splitContainer.Panel1.SuspendLayout(); - this.splitContainer.SuspendLayout(); - this.flatTabControl1.SuspendLayout(); - this.tabPage1.SuspendLayout(); - this.tabPage2.SuspendLayout(); - this.tabPage3.SuspendLayout(); - this.menuStrip1.SuspendLayout(); - this.SuspendLayout(); - // - // splitContainer - // - this.splitContainer.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(68)))), ((int)(((byte)(68)))), ((int)(((byte)(68))))); - this.splitContainer.Dock = System.Windows.Forms.DockStyle.Fill; - this.splitContainer.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; - this.splitContainer.IsSplitterFixed = true; - this.splitContainer.Location = new System.Drawing.Point(0, 0); - this.splitContainer.Name = "splitContainer"; - // - // splitContainer.Panel1 - // - this.splitContainer.Panel1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76))))); - this.splitContainer.Panel1.Controls.Add(this.btnSmooth); - this.splitContainer.Panel1.Controls.Add(this.flatTabControl1); - this.splitContainer.Panel1.Controls.Add(this.menuStrip1); - this.splitContainer.Panel1.Controls.Add(this.btnMesh); - // - // splitContainer.Panel2 - // - this.splitContainer.Panel2.BackColor = System.Drawing.Color.Black; - this.splitContainer.Size = new System.Drawing.Size(992, 623); - this.splitContainer.SplitterDistance = 280; - this.splitContainer.SplitterWidth = 1; - this.splitContainer.TabIndex = 0; - // - // btnSmooth - // - this.btnSmooth.Enabled = false; - this.btnSmooth.Location = new System.Drawing.Point(146, 44); - this.btnSmooth.Name = "btnSmooth"; - this.btnSmooth.Size = new System.Drawing.Size(130, 23); - this.btnSmooth.TabIndex = 12; - this.btnSmooth.Text = "Smooth"; - this.btnSmooth.UseVisualStyleBackColor = true; - this.btnSmooth.Click += new System.EventHandler(this.btnSmooth_Click); - // - // flatTabControl1 - // - this.flatTabControl1.Alignment = System.Windows.Forms.TabAlignment.Bottom; - this.flatTabControl1.Controls.Add(this.tabPage1); - this.flatTabControl1.Controls.Add(this.tabPage2); - this.flatTabControl1.Controls.Add(this.tabPage3); - this.flatTabControl1.Location = new System.Drawing.Point(0, 73); - this.flatTabControl1.Name = "flatTabControl1"; - this.flatTabControl1.SelectedIndex = 0; - this.flatTabControl1.Size = new System.Drawing.Size(280, 538); - this.flatTabControl1.TabIndex = 1; - // - // tabPage1 - // - this.tabPage1.BackColor = System.Drawing.Color.DimGray; - this.tabPage1.Controls.Add(this.meshControlView); - this.tabPage1.ForeColor = System.Drawing.Color.DarkGray; - this.tabPage1.Location = new System.Drawing.Point(4, 4); - this.tabPage1.Name = "tabPage1"; - this.tabPage1.Size = new System.Drawing.Size(272, 509); - this.tabPage1.TabIndex = 0; - this.tabPage1.Text = "Mesh Control"; - // - // meshControlView - // - this.meshControlView.BackColor = System.Drawing.Color.DimGray; - this.meshControlView.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.meshControlView.ForeColor = System.Drawing.Color.DarkGray; - this.meshControlView.Location = new System.Drawing.Point(0, 0); - this.meshControlView.Name = "meshControlView"; - this.meshControlView.Size = new System.Drawing.Size(272, 509); - this.meshControlView.TabIndex = 0; - // - // tabPage2 - // - this.tabPage2.BackColor = System.Drawing.Color.DimGray; - this.tabPage2.Controls.Add(this.statisticView); - this.tabPage2.ForeColor = System.Drawing.Color.White; - this.tabPage2.Location = new System.Drawing.Point(4, 4); - this.tabPage2.Name = "tabPage2"; - this.tabPage2.Padding = new System.Windows.Forms.Padding(3); - this.tabPage2.Size = new System.Drawing.Size(272, 509); - this.tabPage2.TabIndex = 1; - this.tabPage2.Text = "Statistic"; - // - // statisticView - // - this.statisticView.BackColor = System.Drawing.Color.DimGray; - this.statisticView.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.statisticView.ForeColor = System.Drawing.Color.DarkGray; - this.statisticView.Location = new System.Drawing.Point(0, 0); - this.statisticView.Name = "statisticView"; - this.statisticView.Size = new System.Drawing.Size(272, 509); - this.statisticView.TabIndex = 0; - // - // tabPage3 - // - this.tabPage3.BackColor = System.Drawing.Color.DimGray; - this.tabPage3.Controls.Add(this.aboutView); - this.tabPage3.Location = new System.Drawing.Point(4, 4); - this.tabPage3.Name = "tabPage3"; - this.tabPage3.Padding = new System.Windows.Forms.Padding(3); - this.tabPage3.Size = new System.Drawing.Size(272, 509); - this.tabPage3.TabIndex = 2; - this.tabPage3.Text = "About"; - // - // aboutView - // - this.aboutView.BackColor = System.Drawing.Color.DimGray; - this.aboutView.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.aboutView.ForeColor = System.Drawing.Color.DarkGray; - this.aboutView.Location = new System.Drawing.Point(0, 0); - this.aboutView.Name = "aboutView"; - this.aboutView.Size = new System.Drawing.Size(272, 509); - this.aboutView.TabIndex = 0; - // - // menuStrip1 - // - this.menuStrip1.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None; - this.menuStrip1.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.menuStrip1.GripMargin = new System.Windows.Forms.Padding(0); - this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.menuFile, - this.menuView, - this.menuTools}); - this.menuStrip1.Location = new System.Drawing.Point(0, 0); - this.menuStrip1.Name = "menuStrip1"; - this.menuStrip1.Padding = new System.Windows.Forms.Padding(0); - this.menuStrip1.Size = new System.Drawing.Size(280, 24); - this.menuStrip1.TabIndex = 0; - this.menuStrip1.Text = "menuStrip1"; - // - // menuFile - // - this.menuFile.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None; - this.menuFile.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; - this.menuFile.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.menuFileOpen, - this.menuFileSave, - this.toolStripSeparator3, - this.menuFileExport, - this.toolStripSeparator2, - this.menuFileQuit}); - this.menuFile.Name = "menuFile"; - this.menuFile.Size = new System.Drawing.Size(37, 24); - this.menuFile.Text = "File"; - // - // menuFileOpen - // - this.menuFileOpen.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; - this.menuFileOpen.Name = "menuFileOpen"; - this.menuFileOpen.Size = new System.Drawing.Size(141, 22); - this.menuFileOpen.Text = "Open"; - this.menuFileOpen.Click += new System.EventHandler(this.menuFileOpen_Click); - // - // menuFileSave - // - this.menuFileSave.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; - this.menuFileSave.Enabled = false; - this.menuFileSave.Name = "menuFileSave"; - this.menuFileSave.Size = new System.Drawing.Size(141, 22); - this.menuFileSave.Text = "Save"; - this.menuFileSave.Click += new System.EventHandler(this.menuFileSave_Click); - // - // toolStripSeparator3 - // - this.toolStripSeparator3.Name = "toolStripSeparator3"; - this.toolStripSeparator3.Size = new System.Drawing.Size(138, 6); - // - // menuFileExport - // - this.menuFileExport.Enabled = false; - this.menuFileExport.Name = "menuFileExport"; - this.menuFileExport.Size = new System.Drawing.Size(141, 22); - this.menuFileExport.Text = "Export Image"; - this.menuFileExport.Click += new System.EventHandler(this.menuFileExport_Click); - // - // toolStripSeparator2 - // - this.toolStripSeparator2.Name = "toolStripSeparator2"; - this.toolStripSeparator2.Size = new System.Drawing.Size(138, 6); - // - // menuFileQuit - // - this.menuFileQuit.Name = "menuFileQuit"; - this.menuFileQuit.Size = new System.Drawing.Size(141, 22); - this.menuFileQuit.Text = "Quit"; - this.menuFileQuit.Click += new System.EventHandler(this.menuFileQuit_Click); - // - // menuView - // - this.menuView.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.menuViewVoronoi, - this.toolStripSeparator1, - this.menuViewLog}); - this.menuView.Name = "menuView"; - this.menuView.Size = new System.Drawing.Size(44, 24); - this.menuView.Text = "View"; - // - // menuViewVoronoi - // - this.menuViewVoronoi.Enabled = false; - this.menuViewVoronoi.Name = "menuViewVoronoi"; - this.menuViewVoronoi.Size = new System.Drawing.Size(162, 22); - this.menuViewVoronoi.Text = "Voronoi Diagram"; - this.menuViewVoronoi.Click += new System.EventHandler(this.menuViewVoronoi_Click); - // - // toolStripSeparator1 - // - this.toolStripSeparator1.Name = "toolStripSeparator1"; - this.toolStripSeparator1.Size = new System.Drawing.Size(159, 6); - // - // menuViewLog - // - this.menuViewLog.Name = "menuViewLog"; - this.menuViewLog.Size = new System.Drawing.Size(162, 22); - this.menuViewLog.Text = "Show Log"; - this.menuViewLog.Click += new System.EventHandler(this.menuViewLog_Click); - // - // menuTools - // - this.menuTools.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.menuToolsGen, - this.menuToolsCheck, - this.toolStripSeparator5, - this.menuToolsTopology, - this.toolStripSeparator4, - this.menuToolsRcm}); - this.menuTools.Name = "menuTools"; - this.menuTools.Size = new System.Drawing.Size(46, 24); - this.menuTools.Text = "Tools"; - // - // menuToolsGen - // - this.menuToolsGen.Name = "menuToolsGen"; - this.menuToolsGen.Size = new System.Drawing.Size(195, 22); - this.menuToolsGen.Text = "Input Generator"; - this.menuToolsGen.Click += new System.EventHandler(this.menuToolsGenerator_Click); - // - // menuToolsCheck - // - this.menuToolsCheck.Enabled = false; - this.menuToolsCheck.Name = "menuToolsCheck"; - this.menuToolsCheck.Size = new System.Drawing.Size(195, 22); - this.menuToolsCheck.Text = "Check Mesh"; - this.menuToolsCheck.Click += new System.EventHandler(this.menuToolsCheck_Click); - // - // toolStripSeparator5 - // - this.toolStripSeparator5.Name = "toolStripSeparator5"; - this.toolStripSeparator5.Size = new System.Drawing.Size(192, 6); - // - // menuToolsTopology - // - this.menuToolsTopology.Name = "menuToolsTopology"; - this.menuToolsTopology.Size = new System.Drawing.Size(195, 22); - this.menuToolsTopology.Text = "Topology Explorer"; - this.menuToolsTopology.Click += new System.EventHandler(this.menuToolsTopology_Click); - // - // toolStripSeparator4 - // - this.toolStripSeparator4.Name = "toolStripSeparator4"; - this.toolStripSeparator4.Size = new System.Drawing.Size(192, 6); - // - // menuToolsRcm - // - this.menuToolsRcm.Enabled = false; - this.menuToolsRcm.Name = "menuToolsRcm"; - this.menuToolsRcm.Size = new System.Drawing.Size(195, 22); - this.menuToolsRcm.Text = "Renumber nodes (RCM)"; - this.menuToolsRcm.Click += new System.EventHandler(this.menuToolsRcm_Click); - // - // btnMesh - // - this.btnMesh.Enabled = false; - this.btnMesh.Location = new System.Drawing.Point(4, 44); - this.btnMesh.Name = "btnMesh"; - this.btnMesh.Size = new System.Drawing.Size(130, 23); - this.btnMesh.TabIndex = 12; - this.btnMesh.Text = "Triangulate"; - this.btnMesh.UseVisualStyleBackColor = true; - this.btnMesh.Click += new System.EventHandler(this.btnMesh_Click); - // - // FormMain - // - this.AllowDrop = true; - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76))))); - this.ClientSize = new System.Drawing.Size(992, 623); - this.Controls.Add(this.splitContainer); - this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.KeyPreview = true; - this.MainMenuStrip = this.menuStrip1; - this.MinimumSize = new System.Drawing.Size(1000, 650); - this.Name = "FormMain"; - this.Text = "Triangle.NET - Mesh Explorer"; - this.Load += new System.EventHandler(this.Form1_Load); - this.ResizeBegin += new System.EventHandler(this.ResizeBeginHandler); - this.ResizeEnd += new System.EventHandler(this.ResizeEndHandler); - this.DragDrop += new System.Windows.Forms.DragEventHandler(this.frmDragDrop); - this.DragOver += new System.Windows.Forms.DragEventHandler(this.frmDragOver); - this.KeyUp += new System.Windows.Forms.KeyEventHandler(this.Form1_KeyUp); - this.Resize += new System.EventHandler(this.ResizeHandler); - this.splitContainer.Panel1.ResumeLayout(false); - this.splitContainer.Panel1.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit(); - this.splitContainer.ResumeLayout(false); - this.flatTabControl1.ResumeLayout(false); - this.tabPage1.ResumeLayout(false); - this.tabPage2.ResumeLayout(false); - this.tabPage3.ResumeLayout(false); - this.menuStrip1.ResumeLayout(false); - this.menuStrip1.PerformLayout(); - this.ResumeLayout(false); - - } - - #endregion - - private System.Windows.Forms.SplitContainer splitContainer; - private System.Windows.Forms.MenuStrip menuStrip1; - private System.Windows.Forms.ToolStripMenuItem menuFile; - private System.Windows.Forms.ToolStripMenuItem menuFileOpen; - private System.Windows.Forms.ToolStripMenuItem menuFileSave; - private Controls.DarkTabControl flatTabControl1; - private System.Windows.Forms.TabPage tabPage1; - private System.Windows.Forms.TabPage tabPage2; - private Controls.DarkButton btnSmooth; - private Controls.DarkButton btnMesh; - private System.Windows.Forms.TabPage tabPage3; - private System.Windows.Forms.ToolStripMenuItem menuView; - private System.Windows.Forms.ToolStripMenuItem menuViewVoronoi; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; - private System.Windows.Forms.ToolStripMenuItem menuViewLog; - private System.Windows.Forms.ToolStripMenuItem menuTools; - private System.Windows.Forms.ToolStripMenuItem menuToolsGen; - private System.Windows.Forms.ToolStripMenuItem menuToolsCheck; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; - private System.Windows.Forms.ToolStripMenuItem menuFileQuit; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator3; - private System.Windows.Forms.ToolStripMenuItem menuFileExport; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator4; - private System.Windows.Forms.ToolStripMenuItem menuToolsRcm; - private Views.MeshControlView meshControlView; - private Views.StatisticView statisticView; - private Views.AboutView aboutView; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator5; - private System.Windows.Forms.ToolStripMenuItem menuToolsTopology; - - } -} - +namespace MeshExplorer +{ + partial class FormMain + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.splitContainer = new System.Windows.Forms.SplitContainer(); + this.btnSmooth = new MeshExplorer.Controls.DarkButton(); + this.flatTabControl1 = new MeshExplorer.Controls.DarkTabControl(); + this.tabPage1 = new System.Windows.Forms.TabPage(); + this.meshControlView = new MeshExplorer.Views.MeshControlView(); + this.tabPage2 = new System.Windows.Forms.TabPage(); + this.statisticView = new MeshExplorer.Views.StatisticView(); + this.tabPage3 = new System.Windows.Forms.TabPage(); + this.aboutView = new MeshExplorer.Views.AboutView(); + this.menuStrip1 = new System.Windows.Forms.MenuStrip(); + this.menuFile = new System.Windows.Forms.ToolStripMenuItem(); + this.menuFileOpen = new System.Windows.Forms.ToolStripMenuItem(); + this.menuFileSave = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); + this.menuFileExport = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); + this.menuFileQuit = new System.Windows.Forms.ToolStripMenuItem(); + this.menuView = new System.Windows.Forms.ToolStripMenuItem(); + this.menuViewVoronoi = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + this.menuViewLog = new System.Windows.Forms.ToolStripMenuItem(); + this.menuTools = new System.Windows.Forms.ToolStripMenuItem(); + this.menuToolsGen = new System.Windows.Forms.ToolStripMenuItem(); + this.menuToolsCheck = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator(); + this.menuToolsTopology = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator(); + this.menuToolsRcm = new System.Windows.Forms.ToolStripMenuItem(); + this.btnMesh = new MeshExplorer.Controls.DarkButton(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit(); + this.splitContainer.Panel1.SuspendLayout(); + this.splitContainer.SuspendLayout(); + this.flatTabControl1.SuspendLayout(); + this.tabPage1.SuspendLayout(); + this.tabPage2.SuspendLayout(); + this.tabPage3.SuspendLayout(); + this.menuStrip1.SuspendLayout(); + this.SuspendLayout(); + // + // splitContainer + // + this.splitContainer.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(68)))), ((int)(((byte)(68)))), ((int)(((byte)(68))))); + this.splitContainer.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; + this.splitContainer.IsSplitterFixed = true; + this.splitContainer.Location = new System.Drawing.Point(0, 0); + this.splitContainer.Name = "splitContainer"; + // + // splitContainer.Panel1 + // + this.splitContainer.Panel1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76))))); + this.splitContainer.Panel1.Controls.Add(this.btnSmooth); + this.splitContainer.Panel1.Controls.Add(this.flatTabControl1); + this.splitContainer.Panel1.Controls.Add(this.menuStrip1); + this.splitContainer.Panel1.Controls.Add(this.btnMesh); + // + // splitContainer.Panel2 + // + this.splitContainer.Panel2.BackColor = System.Drawing.Color.Black; + this.splitContainer.Size = new System.Drawing.Size(992, 623); + this.splitContainer.SplitterDistance = 280; + this.splitContainer.SplitterWidth = 1; + this.splitContainer.TabIndex = 0; + // + // btnSmooth + // + this.btnSmooth.Enabled = false; + this.btnSmooth.Location = new System.Drawing.Point(146, 44); + this.btnSmooth.Name = "btnSmooth"; + this.btnSmooth.Size = new System.Drawing.Size(130, 23); + this.btnSmooth.TabIndex = 12; + this.btnSmooth.Text = "Smooth"; + this.btnSmooth.UseVisualStyleBackColor = true; + this.btnSmooth.Click += new System.EventHandler(this.btnSmooth_Click); + // + // flatTabControl1 + // + this.flatTabControl1.Alignment = System.Windows.Forms.TabAlignment.Bottom; + this.flatTabControl1.Controls.Add(this.tabPage1); + this.flatTabControl1.Controls.Add(this.tabPage2); + this.flatTabControl1.Controls.Add(this.tabPage3); + this.flatTabControl1.Location = new System.Drawing.Point(0, 73); + this.flatTabControl1.Name = "flatTabControl1"; + this.flatTabControl1.SelectedIndex = 0; + this.flatTabControl1.Size = new System.Drawing.Size(280, 538); + this.flatTabControl1.TabIndex = 1; + // + // tabPage1 + // + this.tabPage1.BackColor = System.Drawing.Color.DimGray; + this.tabPage1.Controls.Add(this.meshControlView); + this.tabPage1.ForeColor = System.Drawing.Color.DarkGray; + this.tabPage1.Location = new System.Drawing.Point(4, 4); + this.tabPage1.Name = "tabPage1"; + this.tabPage1.Size = new System.Drawing.Size(272, 509); + this.tabPage1.TabIndex = 0; + this.tabPage1.Text = "Mesh Control"; + // + // meshControlView + // + this.meshControlView.BackColor = System.Drawing.Color.DimGray; + this.meshControlView.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.meshControlView.ForeColor = System.Drawing.Color.DarkGray; + this.meshControlView.Location = new System.Drawing.Point(0, 0); + this.meshControlView.Name = "meshControlView"; + this.meshControlView.Size = new System.Drawing.Size(272, 509); + this.meshControlView.TabIndex = 0; + // + // tabPage2 + // + this.tabPage2.BackColor = System.Drawing.Color.DimGray; + this.tabPage2.Controls.Add(this.statisticView); + this.tabPage2.ForeColor = System.Drawing.Color.White; + this.tabPage2.Location = new System.Drawing.Point(4, 4); + this.tabPage2.Name = "tabPage2"; + this.tabPage2.Padding = new System.Windows.Forms.Padding(3); + this.tabPage2.Size = new System.Drawing.Size(272, 509); + this.tabPage2.TabIndex = 1; + this.tabPage2.Text = "Statistic"; + // + // statisticView + // + this.statisticView.BackColor = System.Drawing.Color.DimGray; + this.statisticView.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.statisticView.ForeColor = System.Drawing.Color.DarkGray; + this.statisticView.Location = new System.Drawing.Point(0, 0); + this.statisticView.Name = "statisticView"; + this.statisticView.Size = new System.Drawing.Size(272, 509); + this.statisticView.TabIndex = 0; + // + // tabPage3 + // + this.tabPage3.BackColor = System.Drawing.Color.DimGray; + this.tabPage3.Controls.Add(this.aboutView); + this.tabPage3.Location = new System.Drawing.Point(4, 4); + this.tabPage3.Name = "tabPage3"; + this.tabPage3.Padding = new System.Windows.Forms.Padding(3); + this.tabPage3.Size = new System.Drawing.Size(272, 509); + this.tabPage3.TabIndex = 2; + this.tabPage3.Text = "About"; + // + // aboutView + // + this.aboutView.BackColor = System.Drawing.Color.DimGray; + this.aboutView.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.aboutView.ForeColor = System.Drawing.Color.DarkGray; + this.aboutView.Location = new System.Drawing.Point(0, 0); + this.aboutView.Name = "aboutView"; + this.aboutView.Size = new System.Drawing.Size(272, 509); + this.aboutView.TabIndex = 0; + // + // menuStrip1 + // + this.menuStrip1.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None; + this.menuStrip1.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.menuStrip1.GripMargin = new System.Windows.Forms.Padding(0); + this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.menuFile, + this.menuView, + this.menuTools}); + this.menuStrip1.Location = new System.Drawing.Point(0, 0); + this.menuStrip1.Name = "menuStrip1"; + this.menuStrip1.Padding = new System.Windows.Forms.Padding(0); + this.menuStrip1.Size = new System.Drawing.Size(280, 24); + this.menuStrip1.TabIndex = 0; + this.menuStrip1.Text = "menuStrip1"; + // + // menuFile + // + this.menuFile.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None; + this.menuFile.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; + this.menuFile.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.menuFileOpen, + this.menuFileSave, + this.toolStripSeparator3, + this.menuFileExport, + this.toolStripSeparator2, + this.menuFileQuit}); + this.menuFile.Name = "menuFile"; + this.menuFile.Size = new System.Drawing.Size(37, 24); + this.menuFile.Text = "File"; + // + // menuFileOpen + // + this.menuFileOpen.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; + this.menuFileOpen.Name = "menuFileOpen"; + this.menuFileOpen.Size = new System.Drawing.Size(141, 22); + this.menuFileOpen.Text = "Open"; + this.menuFileOpen.Click += new System.EventHandler(this.menuFileOpen_Click); + // + // menuFileSave + // + this.menuFileSave.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; + this.menuFileSave.Enabled = false; + this.menuFileSave.Name = "menuFileSave"; + this.menuFileSave.Size = new System.Drawing.Size(141, 22); + this.menuFileSave.Text = "Save"; + this.menuFileSave.Click += new System.EventHandler(this.menuFileSave_Click); + // + // toolStripSeparator3 + // + this.toolStripSeparator3.Name = "toolStripSeparator3"; + this.toolStripSeparator3.Size = new System.Drawing.Size(138, 6); + // + // menuFileExport + // + this.menuFileExport.Enabled = false; + this.menuFileExport.Name = "menuFileExport"; + this.menuFileExport.Size = new System.Drawing.Size(141, 22); + this.menuFileExport.Text = "Export Image"; + this.menuFileExport.Click += new System.EventHandler(this.menuFileExport_Click); + // + // toolStripSeparator2 + // + this.toolStripSeparator2.Name = "toolStripSeparator2"; + this.toolStripSeparator2.Size = new System.Drawing.Size(138, 6); + // + // menuFileQuit + // + this.menuFileQuit.Name = "menuFileQuit"; + this.menuFileQuit.Size = new System.Drawing.Size(141, 22); + this.menuFileQuit.Text = "Quit"; + this.menuFileQuit.Click += new System.EventHandler(this.menuFileQuit_Click); + // + // menuView + // + this.menuView.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.menuViewVoronoi, + this.toolStripSeparator1, + this.menuViewLog}); + this.menuView.Name = "menuView"; + this.menuView.Size = new System.Drawing.Size(44, 24); + this.menuView.Text = "View"; + // + // menuViewVoronoi + // + this.menuViewVoronoi.Enabled = false; + this.menuViewVoronoi.Name = "menuViewVoronoi"; + this.menuViewVoronoi.Size = new System.Drawing.Size(162, 22); + this.menuViewVoronoi.Text = "Voronoi Diagram"; + this.menuViewVoronoi.Click += new System.EventHandler(this.menuViewVoronoi_Click); + // + // toolStripSeparator1 + // + this.toolStripSeparator1.Name = "toolStripSeparator1"; + this.toolStripSeparator1.Size = new System.Drawing.Size(159, 6); + // + // menuViewLog + // + this.menuViewLog.Name = "menuViewLog"; + this.menuViewLog.Size = new System.Drawing.Size(162, 22); + this.menuViewLog.Text = "Show Log"; + this.menuViewLog.Click += new System.EventHandler(this.menuViewLog_Click); + // + // menuTools + // + this.menuTools.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.menuToolsGen, + this.menuToolsCheck, + this.toolStripSeparator5, + this.menuToolsTopology, + this.toolStripSeparator4, + this.menuToolsRcm}); + this.menuTools.Name = "menuTools"; + this.menuTools.Size = new System.Drawing.Size(46, 24); + this.menuTools.Text = "Tools"; + // + // menuToolsGen + // + this.menuToolsGen.Name = "menuToolsGen"; + this.menuToolsGen.Size = new System.Drawing.Size(195, 22); + this.menuToolsGen.Text = "Input Generator"; + this.menuToolsGen.Click += new System.EventHandler(this.menuToolsGenerator_Click); + // + // menuToolsCheck + // + this.menuToolsCheck.Enabled = false; + this.menuToolsCheck.Name = "menuToolsCheck"; + this.menuToolsCheck.Size = new System.Drawing.Size(195, 22); + this.menuToolsCheck.Text = "Check Mesh"; + this.menuToolsCheck.Click += new System.EventHandler(this.menuToolsCheck_Click); + // + // toolStripSeparator5 + // + this.toolStripSeparator5.Name = "toolStripSeparator5"; + this.toolStripSeparator5.Size = new System.Drawing.Size(192, 6); + // + // menuToolsTopology + // + this.menuToolsTopology.Name = "menuToolsTopology"; + this.menuToolsTopology.Size = new System.Drawing.Size(195, 22); + this.menuToolsTopology.Text = "Topology Explorer"; + this.menuToolsTopology.Click += new System.EventHandler(this.menuToolsTopology_Click); + // + // toolStripSeparator4 + // + this.toolStripSeparator4.Name = "toolStripSeparator4"; + this.toolStripSeparator4.Size = new System.Drawing.Size(192, 6); + // + // menuToolsRcm + // + this.menuToolsRcm.Enabled = false; + this.menuToolsRcm.Name = "menuToolsRcm"; + this.menuToolsRcm.Size = new System.Drawing.Size(195, 22); + this.menuToolsRcm.Text = "Renumber nodes (RCM)"; + this.menuToolsRcm.Click += new System.EventHandler(this.menuToolsRcm_Click); + // + // btnMesh + // + this.btnMesh.Enabled = false; + this.btnMesh.Location = new System.Drawing.Point(4, 44); + this.btnMesh.Name = "btnMesh"; + this.btnMesh.Size = new System.Drawing.Size(130, 23); + this.btnMesh.TabIndex = 12; + this.btnMesh.Text = "Triangulate"; + this.btnMesh.UseVisualStyleBackColor = true; + this.btnMesh.Click += new System.EventHandler(this.btnMesh_Click); + // + // FormMain + // + this.AllowDrop = true; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76))))); + this.ClientSize = new System.Drawing.Size(992, 623); + this.Controls.Add(this.splitContainer); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.KeyPreview = true; + this.MainMenuStrip = this.menuStrip1; + this.MinimumSize = new System.Drawing.Size(1000, 650); + this.Name = "FormMain"; + this.Text = "Triangle.NET - Mesh Explorer"; + this.Load += new System.EventHandler(this.Form1_Load); + this.ResizeBegin += new System.EventHandler(this.ResizeBeginHandler); + this.ResizeEnd += new System.EventHandler(this.ResizeEndHandler); + this.DragDrop += new System.Windows.Forms.DragEventHandler(this.frmDragDrop); + this.DragOver += new System.Windows.Forms.DragEventHandler(this.frmDragOver); + this.KeyUp += new System.Windows.Forms.KeyEventHandler(this.Form1_KeyUp); + this.Resize += new System.EventHandler(this.ResizeHandler); + this.splitContainer.Panel1.ResumeLayout(false); + this.splitContainer.Panel1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit(); + this.splitContainer.ResumeLayout(false); + this.flatTabControl1.ResumeLayout(false); + this.tabPage1.ResumeLayout(false); + this.tabPage2.ResumeLayout(false); + this.tabPage3.ResumeLayout(false); + this.menuStrip1.ResumeLayout(false); + this.menuStrip1.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.SplitContainer splitContainer; + private System.Windows.Forms.MenuStrip menuStrip1; + private System.Windows.Forms.ToolStripMenuItem menuFile; + private System.Windows.Forms.ToolStripMenuItem menuFileOpen; + private System.Windows.Forms.ToolStripMenuItem menuFileSave; + private Controls.DarkTabControl flatTabControl1; + private System.Windows.Forms.TabPage tabPage1; + private System.Windows.Forms.TabPage tabPage2; + private Controls.DarkButton btnSmooth; + private Controls.DarkButton btnMesh; + private System.Windows.Forms.TabPage tabPage3; + private System.Windows.Forms.ToolStripMenuItem menuView; + private System.Windows.Forms.ToolStripMenuItem menuViewVoronoi; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; + private System.Windows.Forms.ToolStripMenuItem menuViewLog; + private System.Windows.Forms.ToolStripMenuItem menuTools; + private System.Windows.Forms.ToolStripMenuItem menuToolsGen; + private System.Windows.Forms.ToolStripMenuItem menuToolsCheck; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; + private System.Windows.Forms.ToolStripMenuItem menuFileQuit; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator3; + private System.Windows.Forms.ToolStripMenuItem menuFileExport; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator4; + private System.Windows.Forms.ToolStripMenuItem menuToolsRcm; + private Views.MeshControlView meshControlView; + private Views.StatisticView statisticView; + private Views.AboutView aboutView; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator5; + private System.Windows.Forms.ToolStripMenuItem menuToolsTopology; + + } +} + diff --git a/Triangle.NET/TestApp/FormMain.cs b/src/Triangle.Viewer/FormMain.cs similarity index 96% rename from Triangle.NET/TestApp/FormMain.cs rename to src/Triangle.Viewer/FormMain.cs index 93d652d..96191b4 100644 --- a/Triangle.NET/TestApp/FormMain.cs +++ b/src/Triangle.Viewer/FormMain.cs @@ -1,795 +1,795 @@ -using System; -using System.Drawing; -using System.IO; -using System.Windows.Forms; -using MeshExplorer.Controls; -using MeshExplorer.IO; -using TriangleNet; -using TriangleNet.Geometry; -using TriangleNet.Meshing; -using TriangleNet.Meshing.Algorithm; -using TriangleNet.Rendering; -using TriangleNet.Smoothing; -using TriangleNet.Voronoi; - -namespace MeshExplorer -{ - public partial class FormMain : Form - { - Settings settings; - - Mesh mesh; - IPolygon input; - VoronoiBase voronoi; - - FormLog frmLog; - FormGenerator frmGenerator; - - RenderManager renderManager; - - public FormMain() - { - InitializeComponent(); - - ToolStripManager.Renderer = new DarkToolStripRenderer(); - } - - private void Form1_Load(object sender, EventArgs e) - { - oldClientSize = this.ClientSize; - - settings = new Settings(); - - renderManager = new RenderManager(); - - IRenderControl control = new TriangleNet.Rendering.GDI.RenderControl(); - - /* - if (!renderManager.TryCreateControl("Triangle.Rendering.SharpGL.dll", - new string[] { "SharpGL.dll" }, out control)) - { - control = new TriangleNet.Rendering.GDI.RenderControl(); - - if (frmLog == null) - { - frmLog = new FormLog(); - } - - frmLog.AddItem("Failed to initialize OpenGL.", true); - } - //*/ - - if (control != null) - { - InitializeRenderControl((Control)control); - renderManager.Initialize(control); - } - else - { - DarkMessageBox.Show("Ooops ...", "Failed to initialize renderer."); - } - } - - private void InitializeRenderControl(Control control) - { - this.splitContainer.SuspendLayout(); - this.splitContainer.Panel2.Controls.Add(control); - - var size = this.splitContainer.Panel2.ClientRectangle; - - // Initialize control - control.BackColor = Color.Black; - control.Dock = DockStyle.Fill; - control.Font = new Font("Consolas", 8.25F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0))); - control.Location = new System.Drawing.Point(0, 0); - control.Name = "renderControl1"; - control.Size = new Size(size.Width, size.Height); - control.TabIndex = 0; - control.Text = "renderControl1"; - - this.splitContainer.ResumeLayout(); - } - - protected override void OnMouseWheel(MouseEventArgs e) - { - if (splitContainer.Panel2.Bounds.Contains(e.Location)) - { - var control = renderManager.Control as Control; - - // Set focus on the render control. - if (control != null && !control.Focused) - { - control.Focus(); - } - } - } - - private void Form1_KeyUp(object sender, KeyEventArgs e) - { - switch (e.KeyCode) - { - case Keys.F3: - OpenWithDialog(); - break; - case Keys.F4: - Save(); - break; - case Keys.F5: - Reload(); - break; - case Keys.F8: - TriangulateOrRefine(); - break; - case Keys.F9: - Smooth(); - break; - case Keys.F12: - ShowLog(); - break; - } - } - - void frmGenerator_InputGenerated(object sender, EventArgs e) - { - this.input = sender as IPolygon; - - if (input != null) - { - settings.CurrentFile = "tmp-" + DateTime.Now.ToString("HH-mm-ss"); - HandleNewInput(); - } - } - - private void btnMesh_Click(object sender, EventArgs e) - { - TriangulateOrRefine(); - } - - private void btnSmooth_Click(object sender, EventArgs e) - { - Smooth(); - } - - #region Drag and drop - - private void frmDragDrop(object sender, DragEventArgs e) - { - if (e.Data.GetDataPresent(DataFormats.FileDrop)) - { - string[] args = (string[])e.Data.GetData(DataFormats.FileDrop); - - Open(args[0]); - } - } - - private void frmDragOver(object sender, DragEventArgs e) - { - if (e.Data.GetDataPresent(DataFormats.FileDrop)) - { - string[] args = (string[])e.Data.GetData(DataFormats.FileDrop); - - if (args.Length > 1) - { - e.Effect = DragDropEffects.None; - return; - } - - string file = args[0].ToLower(); - - // Check if file extension is known - if (FileProcessor.CanHandleFile(file)) - { - e.Effect = DragDropEffects.Copy; - } - } - else - { - e.Effect = DragDropEffects.None; - } - } - - #endregion - - #region Resize event handler - - bool isResizing = false; - Size oldClientSize; - - private void ResizeHandler(object sender, EventArgs e) - { - // Handle window minimize and maximize - if (!isResizing) - { - renderManager.Resize(); - } - } - - private void ResizeEndHandler(object sender, EventArgs e) - { - isResizing = false; - - if (this.ClientSize != this.oldClientSize) - { - this.oldClientSize = this.ClientSize; - renderManager.Resize(); - } - } - - private void ResizeBeginHandler(object sender, EventArgs e) - { - isResizing = true; - } - - #endregion - - #region State changes - - private void LockOnException() - { - btnMesh.Enabled = false; - btnSmooth.Enabled = false; - - //menuFileSave.Enabled = false; - //menuFileExport.Enabled = false; - menuViewVoronoi.Enabled = false; - menuToolsCheck.Enabled = false; - menuToolsRcm.Enabled = false; - - settings.ExceptionThrown = true; - } - - private void HandleNewInput() - { - // Reset mesh - mesh = null; - voronoi = null; - - // Reset state - settings.RefineMode = false; - settings.ExceptionThrown = false; - - // Reset buttons - btnMesh.Enabled = true; - btnMesh.Text = "Triangulate"; - btnSmooth.Enabled = false; - - // Update Statistic view - statisticView.HandleNewInput(input); - - // Clear voronoi - menuViewVoronoi.Checked = false; - - // Disable menu items - menuFileSave.Enabled = false; - menuFileExport.Enabled = false; - menuViewVoronoi.Enabled = false; - menuToolsCheck.Enabled = false; - menuToolsRcm.Enabled = false; - - // Render input - renderManager.Set(input); - - // Update window caption - this.Text = "Triangle.NET - Mesh Explorer - " + settings.CurrentFile; - } - - private void HandleMeshImport() - { - voronoi = null; - - // Render mesh - renderManager.Set(mesh, true); - - // Update window caption - this.Text = "Triangle.NET - Mesh Explorer - " + settings.CurrentFile; - - // Update Statistic view - statisticView.HandleMeshImport(input, mesh); - - // Set refine mode - btnMesh.Enabled = true; - btnMesh.Text = "Refine"; - - settings.RefineMode = true; - - HandleMeshChange(); - } - - private void HandleMeshUpdate() - { - // Render mesh - renderManager.Set(mesh, false); - - // Update Statistic view - statisticView.HandleMeshUpdate(mesh); - - HandleMeshChange(); - } - - private void HandleMeshChange() - { - // Update Statistic view - statisticView.HandleMeshChange(mesh); - - // TODO: Should the Voronoi diagram automatically update? - voronoi = null; - menuViewVoronoi.Checked = false; - - // Enable menu items - menuFileSave.Enabled = true; - menuFileExport.Enabled = true; - menuViewVoronoi.Enabled = true; - menuToolsCheck.Enabled = true; - menuToolsRcm.Enabled = true; - } - - #endregion - - #region Commands - - private void OpenWithDialog() - { - OpenFileDialog ofd = new OpenFileDialog(); - - ofd.Filter = settings.OfdFilter; - ofd.FilterIndex = settings.OfdFilterIndex; - ofd.InitialDirectory = settings.OfdDirectory; - ofd.FileName = ""; - - if (ofd.ShowDialog() == DialogResult.OK) - { - if (Open(ofd.FileName)) - { - // Update folder settings - settings.OfdFilterIndex = ofd.FilterIndex; - settings.OfdDirectory = Path.GetDirectoryName(ofd.FileName); - } - } - } - - private bool Open(string filename) - { - if (!FileProcessor.CanHandleFile(filename)) - { - // TODO: show message. - } - else - { - if (FileProcessor.ContainsMeshData(filename)) - { - if (filename.EndsWith(".ele") || DarkMessageBox.Show("Import mesh", Settings.ImportString, - "Do you want to import the mesh?", MessageBoxButtons.YesNo) == DialogResult.OK) - { - input = null; - - try - { - mesh = FileProcessor.Import(filename); - } - catch (Exception e) - { - DarkMessageBox.Show("Import mesh error", e.Message, MessageBoxButtons.OK); - return false; - } - - if (mesh != null) - { - statisticView.UpdateStatistic(mesh); - - // Update settings - settings.CurrentFile = Path.GetFileName(filename); - - HandleMeshImport(); - btnSmooth.Enabled = true; // TODO: Remove - } - // else Message - - return true; - } - } - - input = FileProcessor.Read(filename); - } - - if (input != null) - { - // Update settings - settings.CurrentFile = Path.GetFileName(filename); - - HandleNewInput(); - } - // else Message - - return true; - } - - private void Save() - { - SaveFileDialog sfd = new SaveFileDialog(); - - sfd.Filter = settings.SfdFilter; - sfd.FilterIndex = settings.SfdFilterIndex; - sfd.InitialDirectory = settings.SfdDirectory; - sfd.FileName = ""; - - if (sfd.ShowDialog() == DialogResult.OK) - { - FileProcessor.Save(sfd.FileName, mesh); - } - } - - private void Reload() - { - if (input != null) - { - mesh = null; - settings.RefineMode = false; - settings.ExceptionThrown = false; - - HandleNewInput(); - } - } - - private void TriangulateOrRefine() - { - if ((input == null && !settings.RefineMode) || settings.ExceptionThrown) - { - return; - } - - if (settings.RefineMode == false) - { - Triangulate(); - - if (meshControlView.ParamQualityChecked) - { - btnMesh.Text = "Refine"; - btnSmooth.Enabled = mesh.IsPolygon; - } - } - else if (meshControlView.ParamQualityChecked) - { - Refine(); - } - } - - private void Triangulate() - { - if (input == null) return; - - var options = new ConstraintOptions(); - var quality = new QualityOptions(); - - if (meshControlView.ParamConformDelChecked) - { - options.ConformingDelaunay = true; - } - - if (meshControlView.ParamQualityChecked) - { - quality.MinimumAngle = meshControlView.ParamMinAngleValue; - - double maxAngle = meshControlView.ParamMaxAngleValue; - - if (maxAngle < 180) - { - quality.MaximumAngle = maxAngle; - } - - // Ignore area constraints on initial triangulation. - - //double area = slMaxArea.Value * 0.01; - //if (area > 0 && area < 1) - //{ - // var size = input.Bounds; - // double min = Math.Min(size.Width, size.Height); - // mesh.SetOption(Options.MaxArea, area * min); - //} - } - - if (meshControlView.ParamConvexChecked) - { - options.Convex = true; - } - - try - { - if (meshControlView.ParamSweeplineChecked) - { - mesh = (Mesh)input.Triangulate(options, quality, new SweepLine()); - } - else - { - mesh = (Mesh)input.Triangulate(options, quality); - } - - statisticView.UpdateStatistic(mesh); - - HandleMeshUpdate(); - - if (meshControlView.ParamQualityChecked) - { - settings.RefineMode = true; - } - } - catch (Exception ex) - { - LockOnException(); - DarkMessageBox.Show("Exception - Triangulate", ex.Message, MessageBoxButtons.OK); - } - - UpdateLog(); - } - - private void Refine() - { - if (mesh == null) return; - - double area = meshControlView.ParamMaxAreaValue; - - var quality = new QualityOptions(); - - if (area > 0 && area < 1) - { - quality.MaximumArea = area * statisticView.Statistic.LargestArea; - } - - quality.MinimumAngle = meshControlView.ParamMinAngleValue; - - double maxAngle = meshControlView.ParamMaxAngleValue; - - if (maxAngle < 180) - { - quality.MaximumAngle = maxAngle; - } - - try - { - mesh.Refine(quality, meshControlView.ParamConformDelChecked); - - statisticView.UpdateStatistic(mesh); - - HandleMeshUpdate(); - } - catch (Exception ex) - { - LockOnException(); - DarkMessageBox.Show("Exception - Refine", ex.Message, MessageBoxButtons.OK); - } - - UpdateLog(); - } - - private void Renumber() - { - if (mesh == null || settings.ExceptionThrown) return; - - bool tmp = Log.Verbose; - Log.Verbose = true; - - mesh.Renumber(NodeNumbering.CuthillMcKee); - ShowLog(); - - Log.Verbose = tmp; - } - - private void Smooth() - { - if (mesh == null || settings.ExceptionThrown) return; - - if (!mesh.IsPolygon) - { - return; - } - - var smoother = new SimpleSmoother(); - - try - { - smoother.Smooth(this.mesh); - - statisticView.UpdateStatistic(mesh); - - HandleMeshUpdate(); - } - catch (Exception ex) - { - LockOnException(); - DarkMessageBox.Show("Exception - Smooth", ex.Message, MessageBoxButtons.OK); - } - - UpdateLog(); - } - - private bool CreateVoronoi() - { - if (mesh == null) - { - return false; - } - - if (mesh.IsPolygon) - { - try - { - this.voronoi = new BoundedVoronoi(mesh); - } - catch (Exception ex) - { - if (!meshControlView.ParamConformDelChecked) - { - DarkMessageBox.Show("Exception - Bounded Voronoi", Settings.VoronoiString, MessageBoxButtons.OK); - } - else - { - DarkMessageBox.Show("Exception - Bounded Voronoi", ex.Message, MessageBoxButtons.OK); - } - - return false; - } - } - else - { - this.voronoi = new StandardVoronoi(mesh); - } - - // HACK: List -> ICollection ? Nope, no way. - // Vertex[] -> ICollection ? Well, ok. - renderManager.Set(voronoi.Vertices.ToArray(), voronoi.Edges, false); - - return true; - } - - private void ShowLog() - { - if (frmLog == null) - { - frmLog = new FormLog(); - } - - UpdateLog(); - - if (!frmLog.Visible) - { - frmLog.Show(this); - } - } - - private void UpdateLog() - { - if (frmLog != null) - { - frmLog.UpdateItems(); - } - } - - #endregion - - #region Menu Handler - - private void menuFileOpen_Click(object sender, EventArgs e) - { - OpenWithDialog(); - } - - private void menuFileSave_Click(object sender, EventArgs ev) - { - if (mesh != null) - { - Save(); - } - } - - private void menuFileExport_Click(object sender, EventArgs e) - { - if (mesh != null) - { - FormExport export = new FormExport(); - - string file = settings.OfdDirectory; - - if (!file.EndsWith("\\")) - { - file += "\\"; - } - - file += settings.CurrentFile; - - export.ImageName = Path.ChangeExtension(file, ".png"); - - if (export.ShowDialog() == DialogResult.OK) - { - int format = export.ImageFormat; - int size = export.ImageSize; - bool compress = export.UseCompression; - - var writer = new ImageWriter(); - - writer.Export(this.mesh, export.ImageName, format, size, compress); - } - } - } - - private void menuFileQuit_Click(object sender, EventArgs e) - { - this.Close(); - } - - private void menuViewVoronoi_Click(object sender, EventArgs e) - { - if (this.voronoi == null) - { - menuViewVoronoi.Checked = CreateVoronoi(); - } - else - { - bool visible = menuViewVoronoi.Checked; - - renderManager.Enable(4, !visible); - menuViewVoronoi.Checked = !visible; - } - } - - private void menuViewLog_Click(object sender, EventArgs e) - { - ShowLog(); - } - - private void menuToolsGenerator_Click(object sender, EventArgs e) - { - if (frmGenerator == null || frmGenerator.IsDisposed) - { - frmGenerator = new FormGenerator(); - frmGenerator.InputGenerated += new EventHandler(frmGenerator_InputGenerated); - } - - if (!frmGenerator.Visible) - { - frmGenerator.Show(); - } - else - { - frmGenerator.Activate(); - } - } - - private void menuToolsCheck_Click(object sender, EventArgs e) - { - if (mesh != null) - { - bool save = Log.Verbose; - - Log.Verbose = true; - - bool isConsistent = MeshValidator.IsConsistent(mesh); - bool isDelaunay = MeshValidator.IsDelaunay(mesh); - - Log.Verbose = save; - - if (isConsistent) - { - Log.Instance.Info("Mesh topology appears to be consistent."); - } - - if (isDelaunay) - { - Log.Instance.Info("Mesh is (conforming) Delaunay."); - } - - ShowLog(); - } - } - - private void menuToolsTopology_Click(object sender, EventArgs e) - { - (new FormTopology()).ShowDialog(this); - } - - private void menuToolsRcm_Click(object sender, EventArgs e) - { - Renumber(); - } - - #endregion - } -} +using System; +using System.Drawing; +using System.IO; +using System.Windows.Forms; +using MeshExplorer.Controls; +using MeshExplorer.IO; +using TriangleNet; +using TriangleNet.Geometry; +using TriangleNet.Meshing; +using TriangleNet.Meshing.Algorithm; +using TriangleNet.Rendering; +using TriangleNet.Smoothing; +using TriangleNet.Voronoi; + +namespace MeshExplorer +{ + public partial class FormMain : Form + { + Settings settings; + + Mesh mesh; + IPolygon input; + VoronoiBase voronoi; + + FormLog frmLog; + FormGenerator frmGenerator; + + RenderManager renderManager; + + public FormMain() + { + InitializeComponent(); + + ToolStripManager.Renderer = new DarkToolStripRenderer(); + } + + private void Form1_Load(object sender, EventArgs e) + { + oldClientSize = this.ClientSize; + + settings = new Settings(); + + renderManager = new RenderManager(); + + IRenderControl control = new TriangleNet.Rendering.GDI.RenderControl(); + + /* + if (!renderManager.TryCreateControl("Triangle.Rendering.SharpGL.dll", + new string[] { "SharpGL.dll" }, out control)) + { + control = new TriangleNet.Rendering.GDI.RenderControl(); + + if (frmLog == null) + { + frmLog = new FormLog(); + } + + frmLog.AddItem("Failed to initialize OpenGL.", true); + } + //*/ + + if (control != null) + { + InitializeRenderControl((Control)control); + renderManager.Initialize(control, new TriangleNet.Rendering.GDI.LayerRenderer()); + } + else + { + DarkMessageBox.Show("Ooops ...", "Failed to initialize renderer."); + } + } + + private void InitializeRenderControl(Control control) + { + this.splitContainer.SuspendLayout(); + this.splitContainer.Panel2.Controls.Add(control); + + var size = this.splitContainer.Panel2.ClientRectangle; + + // Initialize control + control.BackColor = Color.Black; + control.Dock = DockStyle.Fill; + control.Font = new Font("Consolas", 8.25F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0))); + control.Location = new System.Drawing.Point(0, 0); + control.Name = "renderControl1"; + control.Size = new Size(size.Width, size.Height); + control.TabIndex = 0; + control.Text = "renderControl1"; + + this.splitContainer.ResumeLayout(); + } + + protected override void OnMouseWheel(MouseEventArgs e) + { + if (splitContainer.Panel2.Bounds.Contains(e.Location)) + { + var control = renderManager.Control as Control; + + // Set focus on the render control. + if (control != null && !control.Focused) + { + control.Focus(); + } + } + } + + private void Form1_KeyUp(object sender, KeyEventArgs e) + { + switch (e.KeyCode) + { + case Keys.F3: + OpenWithDialog(); + break; + case Keys.F4: + Save(); + break; + case Keys.F5: + Reload(); + break; + case Keys.F8: + TriangulateOrRefine(); + break; + case Keys.F9: + Smooth(); + break; + case Keys.F12: + ShowLog(); + break; + } + } + + void frmGenerator_InputGenerated(object sender, EventArgs e) + { + this.input = sender as IPolygon; + + if (input != null) + { + settings.CurrentFile = "tmp-" + DateTime.Now.ToString("HH-mm-ss"); + HandleNewInput(); + } + } + + private void btnMesh_Click(object sender, EventArgs e) + { + TriangulateOrRefine(); + } + + private void btnSmooth_Click(object sender, EventArgs e) + { + Smooth(); + } + + #region Drag and drop + + private void frmDragDrop(object sender, DragEventArgs e) + { + if (e.Data.GetDataPresent(DataFormats.FileDrop)) + { + string[] args = (string[])e.Data.GetData(DataFormats.FileDrop); + + Open(args[0]); + } + } + + private void frmDragOver(object sender, DragEventArgs e) + { + if (e.Data.GetDataPresent(DataFormats.FileDrop)) + { + string[] args = (string[])e.Data.GetData(DataFormats.FileDrop); + + if (args.Length > 1) + { + e.Effect = DragDropEffects.None; + return; + } + + string file = args[0].ToLower(); + + // Check if file extension is known + if (FileProcessor.CanHandleFile(file)) + { + e.Effect = DragDropEffects.Copy; + } + } + else + { + e.Effect = DragDropEffects.None; + } + } + + #endregion + + #region Resize event handler + + bool isResizing = false; + Size oldClientSize; + + private void ResizeHandler(object sender, EventArgs e) + { + // Handle window minimize and maximize + if (!isResizing) + { + renderManager.Resize(); + } + } + + private void ResizeEndHandler(object sender, EventArgs e) + { + isResizing = false; + + if (this.ClientSize != this.oldClientSize) + { + this.oldClientSize = this.ClientSize; + renderManager.Resize(); + } + } + + private void ResizeBeginHandler(object sender, EventArgs e) + { + isResizing = true; + } + + #endregion + + #region State changes + + private void LockOnException() + { + btnMesh.Enabled = false; + btnSmooth.Enabled = false; + + //menuFileSave.Enabled = false; + //menuFileExport.Enabled = false; + menuViewVoronoi.Enabled = false; + menuToolsCheck.Enabled = false; + menuToolsRcm.Enabled = false; + + settings.ExceptionThrown = true; + } + + private void HandleNewInput() + { + // Reset mesh + mesh = null; + voronoi = null; + + // Reset state + settings.RefineMode = false; + settings.ExceptionThrown = false; + + // Reset buttons + btnMesh.Enabled = true; + btnMesh.Text = "Triangulate"; + btnSmooth.Enabled = false; + + // Update Statistic view + statisticView.HandleNewInput(input); + + // Clear voronoi + menuViewVoronoi.Checked = false; + + // Disable menu items + menuFileSave.Enabled = false; + menuFileExport.Enabled = false; + menuViewVoronoi.Enabled = false; + menuToolsCheck.Enabled = false; + menuToolsRcm.Enabled = false; + + // Render input + renderManager.Set(input); + + // Update window caption + this.Text = "Triangle.NET - Mesh Explorer - " + settings.CurrentFile; + } + + private void HandleMeshImport() + { + voronoi = null; + + // Render mesh + renderManager.Set(mesh, true); + + // Update window caption + this.Text = "Triangle.NET - Mesh Explorer - " + settings.CurrentFile; + + // Update Statistic view + statisticView.HandleMeshImport(input, mesh); + + // Set refine mode + btnMesh.Enabled = true; + btnMesh.Text = "Refine"; + + settings.RefineMode = true; + + HandleMeshChange(); + } + + private void HandleMeshUpdate() + { + // Render mesh + renderManager.Set(mesh, false); + + // Update Statistic view + statisticView.HandleMeshUpdate(mesh); + + HandleMeshChange(); + } + + private void HandleMeshChange() + { + // Update Statistic view + statisticView.HandleMeshChange(mesh); + + // TODO: Should the Voronoi diagram automatically update? + voronoi = null; + menuViewVoronoi.Checked = false; + + // Enable menu items + menuFileSave.Enabled = true; + menuFileExport.Enabled = true; + menuViewVoronoi.Enabled = true; + menuToolsCheck.Enabled = true; + menuToolsRcm.Enabled = true; + } + + #endregion + + #region Commands + + private void OpenWithDialog() + { + OpenFileDialog ofd = new OpenFileDialog(); + + ofd.Filter = settings.OfdFilter; + ofd.FilterIndex = settings.OfdFilterIndex; + ofd.InitialDirectory = settings.OfdDirectory; + ofd.FileName = ""; + + if (ofd.ShowDialog() == DialogResult.OK) + { + if (Open(ofd.FileName)) + { + // Update folder settings + settings.OfdFilterIndex = ofd.FilterIndex; + settings.OfdDirectory = Path.GetDirectoryName(ofd.FileName); + } + } + } + + private bool Open(string filename) + { + if (!FileProcessor.CanHandleFile(filename)) + { + // TODO: show message. + } + else + { + if (FileProcessor.ContainsMeshData(filename)) + { + if (filename.EndsWith(".ele") || DarkMessageBox.Show("Import mesh", Settings.ImportString, + "Do you want to import the mesh?", MessageBoxButtons.YesNo) == DialogResult.OK) + { + input = null; + + try + { + mesh = FileProcessor.Import(filename); + } + catch (Exception e) + { + DarkMessageBox.Show("Import mesh error", e.Message, MessageBoxButtons.OK); + return false; + } + + if (mesh != null) + { + statisticView.UpdateStatistic(mesh); + + // Update settings + settings.CurrentFile = Path.GetFileName(filename); + + HandleMeshImport(); + btnSmooth.Enabled = true; // TODO: Remove + } + // else Message + + return true; + } + } + + input = FileProcessor.Read(filename); + } + + if (input != null) + { + // Update settings + settings.CurrentFile = Path.GetFileName(filename); + + HandleNewInput(); + } + // else Message + + return true; + } + + private void Save() + { + SaveFileDialog sfd = new SaveFileDialog(); + + sfd.Filter = settings.SfdFilter; + sfd.FilterIndex = settings.SfdFilterIndex; + sfd.InitialDirectory = settings.SfdDirectory; + sfd.FileName = ""; + + if (sfd.ShowDialog() == DialogResult.OK) + { + FileProcessor.Save(sfd.FileName, mesh); + } + } + + private void Reload() + { + if (input != null) + { + mesh = null; + settings.RefineMode = false; + settings.ExceptionThrown = false; + + HandleNewInput(); + } + } + + private void TriangulateOrRefine() + { + if ((input == null && !settings.RefineMode) || settings.ExceptionThrown) + { + return; + } + + if (settings.RefineMode == false) + { + Triangulate(); + + if (meshControlView.ParamQualityChecked) + { + btnMesh.Text = "Refine"; + btnSmooth.Enabled = mesh.IsPolygon; + } + } + else if (meshControlView.ParamQualityChecked) + { + Refine(); + } + } + + private void Triangulate() + { + if (input == null) return; + + var options = new ConstraintOptions(); + var quality = new QualityOptions(); + + if (meshControlView.ParamConformDelChecked) + { + options.ConformingDelaunay = true; + } + + if (meshControlView.ParamQualityChecked) + { + quality.MinimumAngle = meshControlView.ParamMinAngleValue; + + double maxAngle = meshControlView.ParamMaxAngleValue; + + if (maxAngle < 180) + { + quality.MaximumAngle = maxAngle; + } + + // Ignore area constraints on initial triangulation. + + //double area = slMaxArea.Value * 0.01; + //if (area > 0 && area < 1) + //{ + // var size = input.Bounds; + // double min = Math.Min(size.Width, size.Height); + // mesh.SetOption(Options.MaxArea, area * min); + //} + } + + if (meshControlView.ParamConvexChecked) + { + options.Convex = true; + } + + try + { + if (meshControlView.ParamSweeplineChecked) + { + mesh = (Mesh)input.Triangulate(options, quality, new SweepLine()); + } + else + { + mesh = (Mesh)input.Triangulate(options, quality); + } + + statisticView.UpdateStatistic(mesh); + + HandleMeshUpdate(); + + if (meshControlView.ParamQualityChecked) + { + settings.RefineMode = true; + } + } + catch (Exception ex) + { + LockOnException(); + DarkMessageBox.Show("Exception - Triangulate", ex.Message, MessageBoxButtons.OK); + } + + UpdateLog(); + } + + private void Refine() + { + if (mesh == null) return; + + double area = meshControlView.ParamMaxAreaValue; + + var quality = new QualityOptions(); + + if (area > 0 && area < 1) + { + quality.MaximumArea = area * statisticView.Statistic.LargestArea; + } + + quality.MinimumAngle = meshControlView.ParamMinAngleValue; + + double maxAngle = meshControlView.ParamMaxAngleValue; + + if (maxAngle < 180) + { + quality.MaximumAngle = maxAngle; + } + + try + { + mesh.Refine(quality, meshControlView.ParamConformDelChecked); + + statisticView.UpdateStatistic(mesh); + + HandleMeshUpdate(); + } + catch (Exception ex) + { + LockOnException(); + DarkMessageBox.Show("Exception - Refine", ex.Message, MessageBoxButtons.OK); + } + + UpdateLog(); + } + + private void Renumber() + { + if (mesh == null || settings.ExceptionThrown) return; + + bool tmp = Log.Verbose; + Log.Verbose = true; + + mesh.Renumber(NodeNumbering.CuthillMcKee); + ShowLog(); + + Log.Verbose = tmp; + } + + private void Smooth() + { + if (mesh == null || settings.ExceptionThrown) return; + + if (!mesh.IsPolygon) + { + return; + } + + var smoother = new SimpleSmoother(); + + try + { + smoother.Smooth(this.mesh); + + statisticView.UpdateStatistic(mesh); + + HandleMeshUpdate(); + } + catch (Exception ex) + { + LockOnException(); + DarkMessageBox.Show("Exception - Smooth", ex.Message, MessageBoxButtons.OK); + } + + UpdateLog(); + } + + private bool CreateVoronoi() + { + if (mesh == null) + { + return false; + } + + if (mesh.IsPolygon) + { + try + { + this.voronoi = new BoundedVoronoi(mesh); + } + catch (Exception ex) + { + if (!meshControlView.ParamConformDelChecked) + { + DarkMessageBox.Show("Exception - Bounded Voronoi", Settings.VoronoiString, MessageBoxButtons.OK); + } + else + { + DarkMessageBox.Show("Exception - Bounded Voronoi", ex.Message, MessageBoxButtons.OK); + } + + return false; + } + } + else + { + this.voronoi = new StandardVoronoi(mesh); + } + + // HACK: List -> ICollection ? Nope, no way. + // Vertex[] -> ICollection ? Well, ok. + renderManager.Set(voronoi.Vertices.ToArray(), voronoi.Edges, false); + + return true; + } + + private void ShowLog() + { + if (frmLog == null) + { + frmLog = new FormLog(); + } + + UpdateLog(); + + if (!frmLog.Visible) + { + frmLog.Show(this); + } + } + + private void UpdateLog() + { + if (frmLog != null) + { + frmLog.UpdateItems(); + } + } + + #endregion + + #region Menu Handler + + private void menuFileOpen_Click(object sender, EventArgs e) + { + OpenWithDialog(); + } + + private void menuFileSave_Click(object sender, EventArgs ev) + { + if (mesh != null) + { + Save(); + } + } + + private void menuFileExport_Click(object sender, EventArgs e) + { + if (mesh != null) + { + FormExport export = new FormExport(); + + string file = settings.OfdDirectory; + + if (!file.EndsWith("\\")) + { + file += "\\"; + } + + file += settings.CurrentFile; + + export.ImageName = Path.ChangeExtension(file, ".png"); + + if (export.ShowDialog() == DialogResult.OK) + { + int format = export.ImageFormat; + int size = export.ImageSize; + bool compress = export.UseCompression; + + var writer = new ImageWriter(); + + writer.Export(this.mesh, export.ImageName, format, size, compress); + } + } + } + + private void menuFileQuit_Click(object sender, EventArgs e) + { + this.Close(); + } + + private void menuViewVoronoi_Click(object sender, EventArgs e) + { + if (this.voronoi == null) + { + menuViewVoronoi.Checked = CreateVoronoi(); + } + else + { + bool visible = menuViewVoronoi.Checked; + + renderManager.Enable(4, !visible); + menuViewVoronoi.Checked = !visible; + } + } + + private void menuViewLog_Click(object sender, EventArgs e) + { + ShowLog(); + } + + private void menuToolsGenerator_Click(object sender, EventArgs e) + { + if (frmGenerator == null || frmGenerator.IsDisposed) + { + frmGenerator = new FormGenerator(); + frmGenerator.InputGenerated += new EventHandler(frmGenerator_InputGenerated); + } + + if (!frmGenerator.Visible) + { + frmGenerator.Show(); + } + else + { + frmGenerator.Activate(); + } + } + + private void menuToolsCheck_Click(object sender, EventArgs e) + { + if (mesh != null) + { + bool save = Log.Verbose; + + Log.Verbose = true; + + bool isConsistent = MeshValidator.IsConsistent(mesh); + bool isDelaunay = MeshValidator.IsDelaunay(mesh); + + Log.Verbose = save; + + if (isConsistent) + { + Log.Instance.Info("Mesh topology appears to be consistent."); + } + + if (isDelaunay) + { + Log.Instance.Info("Mesh is (conforming) Delaunay."); + } + + ShowLog(); + } + } + + private void menuToolsTopology_Click(object sender, EventArgs e) + { + (new FormTopology()).ShowDialog(this); + } + + private void menuToolsRcm_Click(object sender, EventArgs e) + { + Renumber(); + } + + #endregion + } +} diff --git a/Triangle.NET/TestApp/FormMain.resx b/src/Triangle.Viewer/FormMain.resx similarity index 97% rename from Triangle.NET/TestApp/FormMain.resx rename to src/Triangle.Viewer/FormMain.resx index 0f6d8eb..d5494e3 100644 --- a/Triangle.NET/TestApp/FormMain.resx +++ b/src/Triangle.Viewer/FormMain.resx @@ -1,123 +1,123 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 17 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + \ No newline at end of file diff --git a/Triangle.NET/TestApp/FormTopology.Designer.cs b/src/Triangle.Viewer/FormTopology.Designer.cs similarity index 97% rename from Triangle.NET/TestApp/FormTopology.Designer.cs rename to src/Triangle.Viewer/FormTopology.Designer.cs index 5d95f85..0d2c35d 100644 --- a/Triangle.NET/TestApp/FormTopology.Designer.cs +++ b/src/Triangle.Viewer/FormTopology.Designer.cs @@ -1,112 +1,112 @@ -namespace MeshExplorer -{ - partial class FormTopology - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.splitContainer1 = new System.Windows.Forms.SplitContainer(); - this.renderControl = new MeshExplorer.Topology.TopologyRenderControl(); - this.topoControlView = new MeshExplorer.Topology.TopologyControlView(); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); - this.splitContainer1.Panel1.SuspendLayout(); - this.splitContainer1.Panel2.SuspendLayout(); - this.splitContainer1.SuspendLayout(); - this.SuspendLayout(); - // - // splitContainer1 - // - this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; - this.splitContainer1.IsSplitterFixed = true; - this.splitContainer1.Location = new System.Drawing.Point(0, 0); - this.splitContainer1.Name = "splitContainer1"; - // - // splitContainer1.Panel1 - // - this.splitContainer1.Panel1.Controls.Add(this.renderControl); - // - // splitContainer1.Panel2 - // - this.splitContainer1.Panel2.Controls.Add(this.topoControlView); - this.splitContainer1.Size = new System.Drawing.Size(674, 455); - this.splitContainer1.SplitterDistance = 475; - this.splitContainer1.SplitterWidth = 1; - this.splitContainer1.TabIndex = 0; - // - // renderControl - // - this.renderControl.BackColor = System.Drawing.Color.Black; - this.renderControl.Dock = System.Windows.Forms.DockStyle.Fill; - this.renderControl.Location = new System.Drawing.Point(0, 0); - this.renderControl.Name = "renderControl"; - this.renderControl.Size = new System.Drawing.Size(475, 455); - this.renderControl.TabIndex = 0; - this.renderControl.Text = "topologyRenderControl"; - this.renderControl.MouseClick += new System.Windows.Forms.MouseEventHandler(this.renderControl_MouseClick); - // - // topoControlView - // - this.topoControlView.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76))))); - this.topoControlView.Dock = System.Windows.Forms.DockStyle.Fill; - this.topoControlView.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.topoControlView.ForeColor = System.Drawing.Color.White; - this.topoControlView.Location = new System.Drawing.Point(0, 0); - this.topoControlView.Name = "topoControlView"; - this.topoControlView.Size = new System.Drawing.Size(198, 455); - this.topoControlView.TabIndex = 2; - // - // FormTopology - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76))))); - this.ClientSize = new System.Drawing.Size(674, 455); - this.Controls.Add(this.splitContainer1); - this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ForeColor = System.Drawing.Color.White; - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; - this.MaximizeBox = false; - this.MinimizeBox = false; - this.Name = "FormTopology"; - this.ShowIcon = false; - this.ShowInTaskbar = false; - this.Text = "Triangle.NET - Topology Explorer"; - this.Load += new System.EventHandler(this.FormTopology_Load); - this.splitContainer1.Panel1.ResumeLayout(false); - this.splitContainer1.Panel2.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); - this.splitContainer1.ResumeLayout(false); - this.ResumeLayout(false); - - } - - #endregion - - private System.Windows.Forms.SplitContainer splitContainer1; - private Topology.TopologyRenderControl renderControl; - private Topology.TopologyControlView topoControlView; - } +namespace MeshExplorer +{ + partial class FormTopology + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.renderControl = new MeshExplorer.Topology.TopologyRenderControl(); + this.topoControlView = new MeshExplorer.Topology.TopologyControlView(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + this.SuspendLayout(); + // + // splitContainer1 + // + this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer1.IsSplitterFixed = true; + this.splitContainer1.Location = new System.Drawing.Point(0, 0); + this.splitContainer1.Name = "splitContainer1"; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.renderControl); + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.Controls.Add(this.topoControlView); + this.splitContainer1.Size = new System.Drawing.Size(674, 455); + this.splitContainer1.SplitterDistance = 475; + this.splitContainer1.SplitterWidth = 1; + this.splitContainer1.TabIndex = 0; + // + // renderControl + // + this.renderControl.BackColor = System.Drawing.Color.Black; + this.renderControl.Dock = System.Windows.Forms.DockStyle.Fill; + this.renderControl.Location = new System.Drawing.Point(0, 0); + this.renderControl.Name = "renderControl"; + this.renderControl.Size = new System.Drawing.Size(475, 455); + this.renderControl.TabIndex = 0; + this.renderControl.Text = "topologyRenderControl"; + this.renderControl.MouseClick += new System.Windows.Forms.MouseEventHandler(this.renderControl_MouseClick); + // + // topoControlView + // + this.topoControlView.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76))))); + this.topoControlView.Dock = System.Windows.Forms.DockStyle.Fill; + this.topoControlView.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.topoControlView.ForeColor = System.Drawing.Color.White; + this.topoControlView.Location = new System.Drawing.Point(0, 0); + this.topoControlView.Name = "topoControlView"; + this.topoControlView.Size = new System.Drawing.Size(198, 455); + this.topoControlView.TabIndex = 2; + // + // FormTopology + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76))))); + this.ClientSize = new System.Drawing.Size(674, 455); + this.Controls.Add(this.splitContainer1); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ForeColor = System.Drawing.Color.White; + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "FormTopology"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.Text = "Triangle.NET - Topology Explorer"; + this.Load += new System.EventHandler(this.FormTopology_Load); + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.SplitContainer splitContainer1; + private Topology.TopologyRenderControl renderControl; + private Topology.TopologyControlView topoControlView; + } } \ No newline at end of file diff --git a/Triangle.NET/TestApp/FormTopology.cs b/src/Triangle.Viewer/FormTopology.cs similarity index 86% rename from Triangle.NET/TestApp/FormTopology.cs rename to src/Triangle.Viewer/FormTopology.cs index 3600993..bc09e0d 100644 --- a/Triangle.NET/TestApp/FormTopology.cs +++ b/src/Triangle.Viewer/FormTopology.cs @@ -1,114 +1,113 @@ -using System; -using System.Windows.Forms; -using MeshExplorer.Topology; -using TriangleNet; -using TriangleNet.Geometry; -using TriangleNet.Meshing; -using TriangleNet.Tools; -using TriangleNet.Topology; - -namespace MeshExplorer -{ - public partial class FormTopology : Form - { - Mesh mesh; - TriangleQuadTree tree; - Otri current; - - public FormTopology() - { - InitializeComponent(); - } - - private void FormTopology_Load(object sender, EventArgs e) - { - mesh = (Mesh)GenericMesher.StructuredMesh(new Rectangle(0.0, 0.0, 4.0, 4.0), 4, 4); - - renderControl.Initialize(mesh); - - topoControlView.PrimitiveCommandInvoked += PrimitiveCommandHandler; - - current = default(Otri); - } - - void PrimitiveCommandHandler(object sender, GenericEventArgs e) - { - if (current.Triangle != null) - { - InvokePrimitive(e.Argument); - } - } - - private void renderControl_MouseClick(object sender, MouseEventArgs e) - { - var p = e.Location; - var size = renderControl.Size; - - var tri = FindTriangleAt(((float)p.X) / size.Width, ((float)p.Y) / size.Height); - - current.Triangle = (Triangle)tri; - - renderControl.Update(current); - topoControlView.SetTriangle(current.Triangle); - } - - private ITriangle FindTriangleAt(float x, float y) - { - // Get mesh coordinates - var p = new System.Drawing.PointF(x, y); - renderControl.Zoom.ScreenToWorld(ref p); - - topoControlView.SetPosition(p); - - if (tree == null) - { - tree = new TriangleQuadTree(mesh, 5, 2); - } - - return tree.Query(p.X, p.Y); - } - - private void InvokePrimitive(string name) - { - if (name == "sym") - { - current.Sym(); - } - else if (name == "lnext") - { - current.Lnext(); - } - else if (name == "lprev") - { - current.Lprev(); - } - else if (name == "onext") - { - current.Onext(); - } - else if (name == "oprev") - { - current.Oprev(); - } - else if (name == "dnext") - { - current.Dnext(); - } - else if (name == "dprev") - { - current.Dprev(); - } - else if (name == "rnext") - { - current.Rnext(); - } - else if (name == "rprev") - { - current.Rprev(); - } - - renderControl.Update(current); - topoControlView.SetTriangle(current.Triangle); - } - } -} +using System; +using System.Windows.Forms; +using TriangleNet; +using TriangleNet.Geometry; +using TriangleNet.Meshing; +using TriangleNet.Tools; +using TriangleNet.Topology; + +namespace MeshExplorer +{ + public partial class FormTopology : Form + { + Mesh mesh; + TriangleQuadTree tree; + Otri current; + + public FormTopology() + { + InitializeComponent(); + } + + private void FormTopology_Load(object sender, EventArgs e) + { + mesh = (Mesh)GenericMesher.StructuredMesh(new Rectangle(0.0, 0.0, 4.0, 4.0), 4, 4); + + renderControl.Initialize(mesh); + + topoControlView.PrimitiveCommandInvoked += PrimitiveCommandHandler; + + current = default(Otri); + } + + void PrimitiveCommandHandler(object sender, GenericEventArgs e) + { + if (current.Triangle != null) + { + InvokePrimitive(e.Argument); + } + } + + private void renderControl_MouseClick(object sender, MouseEventArgs e) + { + var p = e.Location; + var size = renderControl.Size; + + var tri = FindTriangleAt(((float)p.X) / size.Width, ((float)p.Y) / size.Height); + + current.Triangle = (Triangle)tri; + + renderControl.Update(current); + topoControlView.SetTriangle(current.Triangle); + } + + private ITriangle FindTriangleAt(float xr, float yr) + { + // Get mesh coordinates + var p = new System.Drawing.PointF(xr, yr); + renderControl.Zoom.ScreenToWorld(p, out double x, out double y); + + topoControlView.SetPosition(x, y); + + if (tree == null) + { + tree = new TriangleQuadTree(mesh, 5, 2); + } + + return tree.Query(x, y); + } + + private void InvokePrimitive(string name) + { + if (name == "sym") + { + current.Sym(); + } + else if (name == "lnext") + { + current.Lnext(); + } + else if (name == "lprev") + { + current.Lprev(); + } + else if (name == "onext") + { + current.Onext(); + } + else if (name == "oprev") + { + current.Oprev(); + } + else if (name == "dnext") + { + current.Dnext(); + } + else if (name == "dprev") + { + current.Dprev(); + } + else if (name == "rnext") + { + current.Rnext(); + } + else if (name == "rprev") + { + current.Rprev(); + } + + renderControl.Update(current); + topoControlView.SetTriangle(current.Triangle); + } + } +} diff --git a/Triangle.NET/TestApp/FormTopology.resx b/src/Triangle.Viewer/FormTopology.resx similarity index 97% rename from Triangle.NET/TestApp/FormTopology.resx rename to src/Triangle.Viewer/FormTopology.resx index 29dcb1b..1af7de1 100644 --- a/Triangle.NET/TestApp/FormTopology.resx +++ b/src/Triangle.Viewer/FormTopology.resx @@ -1,120 +1,120 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + \ No newline at end of file diff --git a/Triangle.NET/TestApp/Generators/BaseGenerator.cs b/src/Triangle.Viewer/Generators/BaseGenerator.cs similarity index 96% rename from Triangle.NET/TestApp/Generators/BaseGenerator.cs rename to src/Triangle.Viewer/Generators/BaseGenerator.cs index 48d231d..6ded8ab 100644 --- a/Triangle.NET/TestApp/Generators/BaseGenerator.cs +++ b/src/Triangle.Viewer/Generators/BaseGenerator.cs @@ -1,158 +1,158 @@ -// ----------------------------------------------------------------------- -// -// TODO: Update copyright text. -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.Generators -{ - using System; - using System.Collections.Generic; - using TriangleNet.Geometry; - - /// - /// TODO: Update summary. - /// - public abstract class BaseGenerator : IGenerator - { - private static int MAX_PARAMS = 3; - - protected string name = "Name"; - protected string description = "Description"; - protected int parameter = 0; - - protected string[] descriptions = new string[MAX_PARAMS]; - protected int[][] ranges = new int[MAX_PARAMS][]; - - public virtual string Name { get { return name; } } - public virtual string Description { get { return description; } } - public virtual int ParameterCount { get { return parameter; } } - - public virtual string ParameterDescription(int paramIndex) - { - if (descriptions[paramIndex] == null) - { - return String.Empty; - } - - return descriptions[paramIndex]; - } - - public virtual string ParameterDescription(int paramIndex, double paramValue) - { - int[] range = ranges[paramIndex]; - - if (range == null) - { - return String.Empty; - } - - int num = GetParamValueInt(paramIndex, paramValue); - return num.ToString(); - } - - public abstract IPolygon Generate(double param0, double param1, double param2); - - #region Contour helpers - - protected List CreateCircle(double r, int n, int boundary = 0) - { - return CreateCircle(0.0, 0.0, r, n, boundary); - } - - protected List CreateCircle(double x, double y, double r, int n, int boundary = 0) - { - return CreateEllipse(0.0, 0.0, r, 1.0, 1.0, n, boundary); - } - - protected List CreateEllipse(double r, double a, double b, int n, int boundary = 0) - { - return CreateEllipse(0.0, 0.0, r, a, b, n, boundary); - } - - protected List CreateEllipse(double x, double y, double r, double a, double b, int n, int boundary = 0) - { - var contour = new List(n); - - double dphi = 2 * Math.PI / n; - - for (int i = 0; i < n; i++) - { - contour.Add(new Vertex(x + a * r * Math.Cos(i * dphi), y + b * r * Math.Sin(i * dphi), boundary)); - } - - return contour; - } - - protected List CreateRectangle(Rectangle rect, int n, int boundary = 0) - { - return CreateRectangle(rect, n, n, boundary); - } - - protected List CreateRectangle(Rectangle rect, int nH, int nV, int boundary = 0) - { - var contour = new List(2 * nH + 2 * nV); - - // Horizontal and vertical step sizes. - double stepH = rect.Width / nH; - double stepV = rect.Height / nV; - - // Left box boundary points - for (int i = 0; i < nV; i++) - { - contour.Add(new Vertex(rect.Left, rect.Bottom + i * stepV, 1)); - } - - // Top box boundary points - for (int i = 0; i < nH; i++) - { - contour.Add(new Vertex(rect.Left + i * stepH, rect.Top, 1)); - } - - // Right box boundary points - for (int i = 0; i < nV; i++) - { - contour.Add(new Vertex(rect.Right, rect.Top - i * stepV, 1)); - } - - // Bottom box boundary points - for (int i = 0; i < nH; i++) - { - contour.Add(new Vertex(rect.Right - i * stepH, rect.Bottom, 1)); - } - - return contour; - } - - #endregion - - protected int GetParamValueInt(int paramIndex, double paramOffset) - { - int[] range = ranges[paramIndex]; - - if (range == null) - { - return 0; - } - - return (int)((range[1] - range[0]) / 100.0 * paramOffset + range[0]); - } - - protected double GetParamValueDouble(int paramIndex, double paramOffset) - { - int[] range = ranges[paramIndex]; - - if (range == null) - { - return 0; - } - - return ((range[1] - range[0]) / 100.0 * paramOffset + range[0]); - } - - public override string ToString() - { - return this.Name; - } - } -} +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.Generators +{ + using System; + using System.Collections.Generic; + using TriangleNet.Geometry; + + /// + /// TODO: Update summary. + /// + public abstract class BaseGenerator : IGenerator + { + private static int MAX_PARAMS = 3; + + protected string name = "Name"; + protected string description = "Description"; + protected int parameter = 0; + + protected string[] descriptions = new string[MAX_PARAMS]; + protected int[][] ranges = new int[MAX_PARAMS][]; + + public virtual string Name { get { return name; } } + public virtual string Description { get { return description; } } + public virtual int ParameterCount { get { return parameter; } } + + public virtual string ParameterDescription(int paramIndex) + { + if (descriptions[paramIndex] == null) + { + return String.Empty; + } + + return descriptions[paramIndex]; + } + + public virtual string ParameterDescription(int paramIndex, double paramValue) + { + int[] range = ranges[paramIndex]; + + if (range == null) + { + return String.Empty; + } + + int num = GetParamValueInt(paramIndex, paramValue); + return num.ToString(); + } + + public abstract IPolygon Generate(double param0, double param1, double param2); + + #region Contour helpers + + protected List CreateCircle(double r, int n, int boundary = 0) + { + return CreateCircle(0.0, 0.0, r, n, boundary); + } + + protected List CreateCircle(double x, double y, double r, int n, int boundary = 0) + { + return CreateEllipse(0.0, 0.0, r, 1.0, 1.0, n, boundary); + } + + protected List CreateEllipse(double r, double a, double b, int n, int boundary = 0) + { + return CreateEllipse(0.0, 0.0, r, a, b, n, boundary); + } + + protected List CreateEllipse(double x, double y, double r, double a, double b, int n, int boundary = 0) + { + var contour = new List(n); + + double dphi = 2 * Math.PI / n; + + for (int i = 0; i < n; i++) + { + contour.Add(new Vertex(x + a * r * Math.Cos(i * dphi), y + b * r * Math.Sin(i * dphi), boundary)); + } + + return contour; + } + + protected List CreateRectangle(Rectangle rect, int n, int boundary = 0) + { + return CreateRectangle(rect, n, n, boundary); + } + + protected List CreateRectangle(Rectangle rect, int nH, int nV, int boundary = 0) + { + var contour = new List(2 * nH + 2 * nV); + + // Horizontal and vertical step sizes. + double stepH = rect.Width / nH; + double stepV = rect.Height / nV; + + // Left box boundary points + for (int i = 0; i < nV; i++) + { + contour.Add(new Vertex(rect.Left, rect.Bottom + i * stepV, 1)); + } + + // Top box boundary points + for (int i = 0; i < nH; i++) + { + contour.Add(new Vertex(rect.Left + i * stepH, rect.Top, 1)); + } + + // Right box boundary points + for (int i = 0; i < nV; i++) + { + contour.Add(new Vertex(rect.Right, rect.Top - i * stepV, 1)); + } + + // Bottom box boundary points + for (int i = 0; i < nH; i++) + { + contour.Add(new Vertex(rect.Right - i * stepH, rect.Bottom, 1)); + } + + return contour; + } + + #endregion + + protected int GetParamValueInt(int paramIndex, double paramOffset) + { + int[] range = ranges[paramIndex]; + + if (range == null) + { + return 0; + } + + return (int)((range[1] - range[0]) / 100.0 * paramOffset + range[0]); + } + + protected double GetParamValueDouble(int paramIndex, double paramOffset) + { + int[] range = ranges[paramIndex]; + + if (range == null) + { + return 0; + } + + return ((range[1] - range[0]) / 100.0 * paramOffset + range[0]); + } + + public override string ToString() + { + return this.Name; + } + } +} diff --git a/Triangle.NET/TestApp/Generators/BoxWithHole.cs b/src/Triangle.Viewer/Generators/BoxWithHole.cs similarity index 86% rename from Triangle.NET/TestApp/Generators/BoxWithHole.cs rename to src/Triangle.Viewer/Generators/BoxWithHole.cs index 2a08cd7..dedc425 100644 --- a/Triangle.NET/TestApp/Generators/BoxWithHole.cs +++ b/src/Triangle.Viewer/Generators/BoxWithHole.cs @@ -1,51 +1,51 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.Generators -{ - using System; - using TriangleNet.Geometry; - - /// - /// Generates a star contained in a box. - /// - public class BoxWithHole : BaseGenerator - { - public BoxWithHole() - { - name = "Box with Hole"; - description = ""; - parameter = 3; - - descriptions[0] = "Points on box sides:"; - descriptions[1] = "Points on hole:"; - descriptions[2] = "Radius:"; - - ranges[0] = new int[] { 5, 50 }; - ranges[1] = new int[] { 10, 200 }; - ranges[2] = new int[] { 5, 20 }; - } - - public override IPolygon Generate(double param0, double param1, double param2) - { - int n = GetParamValueInt(1, param1); - - var input = new Polygon(n + 4); - - double r = GetParamValueInt(2, param2); - - // Generate circle (hole) - input.AddContour(CreateCircle(r, n, 1), 1, new Point(0, 0)); - - n = GetParamValueInt(0, param0); - - // Generate box - input.AddContour(CreateRectangle(new Rectangle(-50, -50, 100, 100), n, 2), 2); - - return input; - } - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.Generators +{ + using System; + using TriangleNet.Geometry; + + /// + /// Generates a star contained in a box. + /// + public class BoxWithHole : BaseGenerator + { + public BoxWithHole() + { + name = "Box with Hole"; + description = ""; + parameter = 3; + + descriptions[0] = "Points on box sides:"; + descriptions[1] = "Points on hole:"; + descriptions[2] = "Radius:"; + + ranges[0] = new int[] { 5, 50 }; + ranges[1] = new int[] { 10, 200 }; + ranges[2] = new int[] { 5, 20 }; + } + + public override IPolygon Generate(double param0, double param1, double param2) + { + int n = GetParamValueInt(1, param1); + + var input = new Polygon(n + 4); + + double r = GetParamValueInt(2, param2); + + // Generate circle (hole) + input.Add(new Contour(CreateCircle(r, n, 1), 1), new Point(0, 0)); + + n = GetParamValueInt(0, param0); + + // Generate box + input.Add(new Contour(CreateRectangle(new Rectangle(-50, -50, 100, 100), n, 2), 2)); + + return input; + } + } +} diff --git a/Triangle.NET/TestApp/Generators/CircleWithHole.cs b/src/Triangle.Viewer/Generators/CircleWithHole.cs similarity index 85% rename from Triangle.NET/TestApp/Generators/CircleWithHole.cs rename to src/Triangle.Viewer/Generators/CircleWithHole.cs index d0e5325..22dad3d 100644 --- a/Triangle.NET/TestApp/Generators/CircleWithHole.cs +++ b/src/Triangle.Viewer/Generators/CircleWithHole.cs @@ -1,64 +1,64 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.Generators -{ - using TriangleNet.Geometry; - - /// - /// Generates a ring polygon. - /// - public class CircleWithHole : BaseGenerator - { - public CircleWithHole() - { - name = "Circle with Hole"; - description = ""; - parameter = 2; - - descriptions[0] = "Number of points:"; - descriptions[1] = "Outer radius:"; - - ranges[0] = new int[] { 100, 250 }; - ranges[1] = new int[] { 2, 15 }; - } - - public override IPolygon Generate(double param0, double param1, double param2) - { - // Number of points on the outer circle - int n = GetParamValueInt(0, param0); - - double radius = GetParamValueInt(1, param1); - - // Current radius and step size - double r, h = radius / n; - - var input = new Polygon(n + 1); - - // Inner cirlce (radius = 1) (hole) - r = 1; - input.AddContour(CreateCircle(r, (int)(r / h), 1), 1, new Point(0, 0)); - - // Center cirlce - r = (radius + 1.0) / 2.0; - input.AddContour(CreateCircle(r, (int)(r / h), 2), 2); - - //count = input.Count; - - // Outer cirlce - r = radius; - input.AddContour(CreateCircle(r, (int)(r / h), 3), 3); - - // Regions: |++++++|++++++|---| - // r 1 0 - - input.Regions.Add(new RegionPointer((r + 3.0) / 4.0, 0, 1)); - input.Regions.Add(new RegionPointer((3 * r + 1.0) / 4.0, 0, 2)); - - return input; - } - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.Generators +{ + using TriangleNet.Geometry; + + /// + /// Generates a ring polygon. + /// + public class CircleWithHole : BaseGenerator + { + public CircleWithHole() + { + name = "Circle with Hole"; + description = ""; + parameter = 2; + + descriptions[0] = "Number of points:"; + descriptions[1] = "Outer radius:"; + + ranges[0] = new int[] { 100, 250 }; + ranges[1] = new int[] { 2, 15 }; + } + + public override IPolygon Generate(double param0, double param1, double param2) + { + // Number of points on the outer circle + int n = GetParamValueInt(0, param0); + + double radius = GetParamValueInt(1, param1); + + // Current radius and step size + double r, h = radius / n; + + var input = new Polygon(n + 1); + + // Inner cirlce (radius = 1) (hole) + r = 1; + input.Add(new Contour(CreateCircle(r, (int)(r / h), 1), 1), new Point(0, 0)); + + // Center cirlce + r = (radius + 1.0) / 2.0; + input.Add(new Contour(CreateCircle(r, (int)(r / h), 2), 2)); + + //count = input.Count; + + // Outer cirlce + r = radius; + input.Add(new Contour(CreateCircle(r, (int)(r / h), 3), 3)); + + // Regions: |++++++|++++++|---| + // r 1 0 + + input.Regions.Add(new RegionPointer((r + 3.0) / 4.0, 0, 1)); + input.Regions.Add(new RegionPointer((3 * r + 1.0) / 4.0, 0, 2)); + + return input; + } + } +} diff --git a/Triangle.NET/TestApp/Generators/IGenerator.cs b/src/Triangle.Viewer/Generators/IGenerator.cs similarity index 97% rename from Triangle.NET/TestApp/Generators/IGenerator.cs rename to src/Triangle.Viewer/Generators/IGenerator.cs index 116ae45..1f7498f 100644 --- a/Triangle.NET/TestApp/Generators/IGenerator.cs +++ b/src/Triangle.Viewer/Generators/IGenerator.cs @@ -1,23 +1,23 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.Generators -{ - using TriangleNet.Geometry; - - /// - /// Interface for generating input geometries. - /// - public interface IGenerator - { - string Name { get; } - string Description { get; } - int ParameterCount { get; } - string ParameterDescription(int paramIndex); - string ParameterDescription(int paramIndex, double paramValue); - IPolygon Generate(double param1, double param2, double param3); - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.Generators +{ + using TriangleNet.Geometry; + + /// + /// Interface for generating input geometries. + /// + public interface IGenerator + { + string Name { get; } + string Description { get; } + int ParameterCount { get; } + string ParameterDescription(int paramIndex); + string ParameterDescription(int paramIndex, double paramValue); + IPolygon Generate(double param1, double param2, double param3); + } +} diff --git a/Triangle.NET/TestApp/Generators/RandomPoints.cs b/src/Triangle.Viewer/Generators/RandomPoints.cs similarity index 96% rename from Triangle.NET/TestApp/Generators/RandomPoints.cs rename to src/Triangle.Viewer/Generators/RandomPoints.cs index f0441f5..3dcb82c 100644 --- a/Triangle.NET/TestApp/Generators/RandomPoints.cs +++ b/src/Triangle.Viewer/Generators/RandomPoints.cs @@ -1,55 +1,55 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.Generators -{ - using TriangleNet.Geometry; - - /// - /// Simple random points generator. - /// - public class RandomPoints : BaseGenerator - { - public RandomPoints() - { - name = "Random Points"; - description = ""; - parameter = 3; - - descriptions[0] = "Number of points:"; - descriptions[1] = "Width:"; - descriptions[2] = "Height:"; - - ranges[0] = new int[] { 10, 5000 }; - ranges[1] = new int[] { 10, 200 }; - ranges[2] = new int[] { 10, 200 }; - } - - public override IPolygon Generate(double param0, double param1, double param2) - { - int numPoints = GetParamValueInt(0, param0); - numPoints = (numPoints / 10) * 10; - - if (numPoints < ranges[0][0]) - { - numPoints = ranges[0][0]; - } - - var input = new Polygon(numPoints); - - int width = GetParamValueInt(1, param1); - int height = GetParamValueInt(2, param2); - - for (int i = 0; i < numPoints; i++) - { - input.Add(new Vertex(Util.Random.NextDouble() * width, - Util.Random.NextDouble() * height)); - } - - return input; - } - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.Generators +{ + using TriangleNet.Geometry; + + /// + /// Simple random points generator. + /// + public class RandomPoints : BaseGenerator + { + public RandomPoints() + { + name = "Random Points"; + description = ""; + parameter = 3; + + descriptions[0] = "Number of points:"; + descriptions[1] = "Width:"; + descriptions[2] = "Height:"; + + ranges[0] = new int[] { 10, 5000 }; + ranges[1] = new int[] { 10, 200 }; + ranges[2] = new int[] { 10, 200 }; + } + + public override IPolygon Generate(double param0, double param1, double param2) + { + int numPoints = GetParamValueInt(0, param0); + numPoints = (numPoints / 10) * 10; + + if (numPoints < ranges[0][0]) + { + numPoints = ranges[0][0]; + } + + var input = new Polygon(numPoints); + + int width = GetParamValueInt(1, param1); + int height = GetParamValueInt(2, param2); + + for (int i = 0; i < numPoints; i++) + { + input.Add(new Vertex(Util.Random.NextDouble() * width, + Util.Random.NextDouble() * height)); + } + + return input; + } + } +} diff --git a/Triangle.NET/TestApp/Generators/RandomPointsCircle.cs b/src/Triangle.Viewer/Generators/RandomPointsCircle.cs similarity index 96% rename from Triangle.NET/TestApp/Generators/RandomPointsCircle.cs rename to src/Triangle.Viewer/Generators/RandomPointsCircle.cs index 7dd3ef7..d988f9e 100644 --- a/Triangle.NET/TestApp/Generators/RandomPointsCircle.cs +++ b/src/Triangle.Viewer/Generators/RandomPointsCircle.cs @@ -1,100 +1,100 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.Generators -{ - using System; - using TriangleNet.Geometry; - - /// - /// Simple random points generator (points distributed in a circle). - /// - public class RandomPointsCircle : BaseGenerator - { - public RandomPointsCircle() - { - name = "Random Points (Circle)"; - description = ""; - parameter = 2; - - descriptions[0] = "Number of points:"; - descriptions[1] = "Distribution:"; - - ranges[0] = new int[] { 5, 5000 }; - ranges[1] = new int[] { 0, 1 }; - } - - public override string ParameterDescription(int paramIndex, double paramValue) - { - if (paramIndex == 0) - { - int numPoints = GetParamValueInt(paramIndex, paramValue); - numPoints = (numPoints / 10) * 10; - - if (numPoints < 5) - { - numPoints = 5; - } - - return numPoints.ToString(); - } - - if (paramIndex == 1) - { - double exp = (paramValue + 10) / 100; - - if (exp > 1.092) - { - exp = 1.1; - } - - return exp.ToString("0.00", Util.Nfi); - } - - return ""; - } - - public override IPolygon Generate(double param0, double param1, double param2) - { - int numPoints = GetParamValueInt(0, param0); - numPoints = (numPoints / 10) * 10; - - if (numPoints < 5) - { - numPoints = 5; - } - - double exp = (param1 + 10) / 100; - - var input = new Polygon(numPoints); - - int i = 0, cNum = 2 * (int)Math.Floor(Math.Sqrt(numPoints)); - - double r, phi, radius = 100, step = 2 * Math.PI / cNum; - - // Distrubute points equally on circle border - for (; i < cNum; i++) - { - // Add a little error - r = Util.Random.NextDouble(); - - input.Add(new Vertex((radius + r) * Math.Cos(i * step), - (radius + r) * Math.Sin(i * step))); - } - - for (; i < numPoints; i++) - { - // Use sqrt(rand) to get normal distribution right. - r = Math.Pow(Util.Random.NextDouble(), exp) * radius; - phi = Util.Random.NextDouble() * Math.PI * 2; - - input.Add(new Vertex(r * Math.Cos(phi), r * Math.Sin(phi))); - } - - return input; - } - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.Generators +{ + using System; + using TriangleNet.Geometry; + + /// + /// Simple random points generator (points distributed in a circle). + /// + public class RandomPointsCircle : BaseGenerator + { + public RandomPointsCircle() + { + name = "Random Points (Circle)"; + description = ""; + parameter = 2; + + descriptions[0] = "Number of points:"; + descriptions[1] = "Distribution:"; + + ranges[0] = new int[] { 5, 5000 }; + ranges[1] = new int[] { 0, 1 }; + } + + public override string ParameterDescription(int paramIndex, double paramValue) + { + if (paramIndex == 0) + { + int numPoints = GetParamValueInt(paramIndex, paramValue); + numPoints = (numPoints / 10) * 10; + + if (numPoints < 5) + { + numPoints = 5; + } + + return numPoints.ToString(); + } + + if (paramIndex == 1) + { + double exp = (paramValue + 10) / 100; + + if (exp > 1.092) + { + exp = 1.1; + } + + return exp.ToString("0.00", Util.Nfi); + } + + return ""; + } + + public override IPolygon Generate(double param0, double param1, double param2) + { + int numPoints = GetParamValueInt(0, param0); + numPoints = (numPoints / 10) * 10; + + if (numPoints < 5) + { + numPoints = 5; + } + + double exp = (param1 + 10) / 100; + + var input = new Polygon(numPoints); + + int i = 0, cNum = 2 * (int)Math.Floor(Math.Sqrt(numPoints)); + + double r, phi, radius = 100, step = 2 * Math.PI / cNum; + + // Distrubute points equally on circle border + for (; i < cNum; i++) + { + // Add a little error + r = Util.Random.NextDouble(); + + input.Add(new Vertex((radius + r) * Math.Cos(i * step), + (radius + r) * Math.Sin(i * step))); + } + + for (; i < numPoints; i++) + { + // Use sqrt(rand) to get normal distribution right. + r = Math.Pow(Util.Random.NextDouble(), exp) * radius; + phi = Util.Random.NextDouble() * Math.PI * 2; + + input.Add(new Vertex(r * Math.Cos(phi), r * Math.Sin(phi))); + } + + return input; + } + } +} diff --git a/Triangle.NET/TestApp/Generators/RingPolygon.cs b/src/Triangle.Viewer/Generators/RingPolygon.cs similarity index 93% rename from Triangle.NET/TestApp/Generators/RingPolygon.cs rename to src/Triangle.Viewer/Generators/RingPolygon.cs index 08ded0a..658f89a 100644 --- a/Triangle.NET/TestApp/Generators/RingPolygon.cs +++ b/src/Triangle.Viewer/Generators/RingPolygon.cs @@ -1,100 +1,100 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.Generators -{ - using System; - using System.Collections.Generic; - using TriangleNet.Geometry; - - /// - /// Generates a ring polygon. - /// - public class RingPolygon : BaseGenerator - { - public RingPolygon() - { - name = "Ring"; - description = ""; - parameter = 2; - - descriptions[0] = "Number of points:"; - descriptions[1] = "Variation:"; - - ranges[0] = new int[] { 50, 250 }; - ranges[1] = new int[] { 0, 1 }; - } - - public override string ParameterDescription(int paramIndex, double paramValue) - { - if (paramIndex == 0) - { - int numRays = GetParamValueInt(paramIndex, paramValue); - return numRays.ToString(); - } - - if (paramIndex == 1) - { - double variation = GetParamValueDouble(paramIndex, paramValue); - return variation.ToString("0.0", Util.Nfi); - } - - return ""; - } - - public override IPolygon Generate(double param0, double param1, double param2) - { - int n = GetParamValueInt(0, param0); - int m = n / 2; - - var input = new Polygon(n + 1); - - double ro, r = 10; - double step = 2 * Math.PI / m; - - var inner = new List(m); - - // Inner ring - for (int i = 0; i < m; i++) - { - inner.Add(new Vertex(r * Math.Cos(i * step), r * Math.Sin(i * step))); - } - - input.AddContour(inner, 1); - - r = 1.5 * r; - - var outer = new List(n); - - step = 2 * Math.PI / n; - double offset = step / 2; - - // Outer ring - for (int i = 0; i < n; i++) - { - ro = r; - - if (i % 2 == 0) - { - ro = r + r * Util.Random.NextDouble() * (param1 / 100); - } - - outer.Add(new Vertex(ro * Math.Cos(i * step + offset), ro * Math.Sin(i * step + offset))); - } - - input.AddContour(outer, 2); - - input.Holes.Add(new Point(0, 0)); - - return input; - } - - public override string ToString() - { - return this.Name; - } - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.Generators +{ + using System; + using System.Collections.Generic; + using TriangleNet.Geometry; + + /// + /// Generates a ring polygon. + /// + public class RingPolygon : BaseGenerator + { + public RingPolygon() + { + name = "Ring"; + description = ""; + parameter = 2; + + descriptions[0] = "Number of points:"; + descriptions[1] = "Variation:"; + + ranges[0] = new int[] { 50, 250 }; + ranges[1] = new int[] { 0, 1 }; + } + + public override string ParameterDescription(int paramIndex, double paramValue) + { + if (paramIndex == 0) + { + int numRays = GetParamValueInt(paramIndex, paramValue); + return numRays.ToString(); + } + + if (paramIndex == 1) + { + double variation = GetParamValueDouble(paramIndex, paramValue); + return variation.ToString("0.0", Util.Nfi); + } + + return ""; + } + + public override IPolygon Generate(double param0, double param1, double param2) + { + int n = GetParamValueInt(0, param0); + int m = n / 2; + + var input = new Polygon(n + 1); + + double ro, r = 10; + double step = 2 * Math.PI / m; + + var inner = new List(m); + + // Inner ring + for (int i = 0; i < m; i++) + { + inner.Add(new Vertex(r * Math.Cos(i * step), r * Math.Sin(i * step))); + } + + input.Add(new Contour(inner, 1)); + + r = 1.5 * r; + + var outer = new List(n); + + step = 2 * Math.PI / n; + double offset = step / 2; + + // Outer ring + for (int i = 0; i < n; i++) + { + ro = r; + + if (i % 2 == 0) + { + ro = r + r * Util.Random.NextDouble() * (param1 / 100); + } + + outer.Add(new Vertex(ro * Math.Cos(i * step + offset), ro * Math.Sin(i * step + offset))); + } + + input.Add(new Contour(outer, 2)); + + input.Holes.Add(new Point(0, 0)); + + return input; + } + + public override string ToString() + { + return this.Name; + } + } +} diff --git a/Triangle.NET/TestApp/Generators/StarInBox.cs b/src/Triangle.Viewer/Generators/StarInBox.cs similarity index 96% rename from Triangle.NET/TestApp/Generators/StarInBox.cs rename to src/Triangle.Viewer/Generators/StarInBox.cs index b35c68b..c95c833 100644 --- a/Triangle.NET/TestApp/Generators/StarInBox.cs +++ b/src/Triangle.Viewer/Generators/StarInBox.cs @@ -1,63 +1,63 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.Generators -{ - using System; - using TriangleNet.Geometry; - - /// - /// Generates a star contained in a box. - /// - public class StarInBox : BaseGenerator - { - public StarInBox() - { - name = "Star in Box"; - description = ""; - parameter = 1; - - descriptions[0] = "Number of rays:"; - - ranges[0] = new int[] { 3, 61 }; - } - - public override IPolygon Generate(double param0, double param1, double param2) - { - int numRays = GetParamValueInt(0, param0); - - var g = new Polygon(numRays + 4); - - g.Add(new Vertex(0, 0)); // Center - - double x, y, r, e, step = 2 * Math.PI / numRays; - - for (int i = 0; i < numRays; i++) - { - e = Util.Random.NextDouble() * step * 0.7; - r = (Util.Random.NextDouble() + 0.7) * 0.5; - x = r * Math.Cos(i * step + e); - y = r * Math.Sin(i * step + e); - - g.Add(new Vertex(x, y, 2)); - g.Add(new Segment(g.Points[0], g.Points[i + 1], 2)); - } - - g.Add(new Vertex(-1, -1, 1)); // Box - g.Add(new Vertex(1, -1, 1)); - g.Add(new Vertex(1, 1, 1)); - g.Add(new Vertex(-1, 1, 1)); - - numRays = g.Count; - g.Add(new Segment(g.Points[numRays - 1], g.Points[numRays - 2], 1)); - g.Add(new Segment(g.Points[numRays - 2], g.Points[numRays - 3], 1)); - g.Add(new Segment(g.Points[numRays - 3], g.Points[numRays - 4], 1)); - g.Add(new Segment(g.Points[numRays - 4], g.Points[numRays - 1], 1)); - - return g; - } - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.Generators +{ + using System; + using TriangleNet.Geometry; + + /// + /// Generates a star contained in a box. + /// + public class StarInBox : BaseGenerator + { + public StarInBox() + { + name = "Star in Box"; + description = ""; + parameter = 1; + + descriptions[0] = "Number of rays:"; + + ranges[0] = new int[] { 3, 61 }; + } + + public override IPolygon Generate(double param0, double param1, double param2) + { + int numRays = GetParamValueInt(0, param0); + + var g = new Polygon(numRays + 4); + + g.Add(new Vertex(0, 0)); // Center + + double x, y, r, e, step = 2 * Math.PI / numRays; + + for (int i = 0; i < numRays; i++) + { + e = Util.Random.NextDouble() * step * 0.7; + r = (Util.Random.NextDouble() + 0.7) * 0.5; + x = r * Math.Cos(i * step + e); + y = r * Math.Sin(i * step + e); + + g.Add(new Vertex(x, y, 2)); + g.Add(new Segment(g.Points[0], g.Points[i + 1], 2)); + } + + g.Add(new Vertex(-1, -1, 1)); // Box + g.Add(new Vertex(1, -1, 1)); + g.Add(new Vertex(1, 1, 1)); + g.Add(new Vertex(-1, 1, 1)); + + numRays = g.Count; + g.Add(new Segment(g.Points[numRays - 1], g.Points[numRays - 2], 1)); + g.Add(new Segment(g.Points[numRays - 2], g.Points[numRays - 3], 1)); + g.Add(new Segment(g.Points[numRays - 3], g.Points[numRays - 4], 1)); + g.Add(new Segment(g.Points[numRays - 4], g.Points[numRays - 1], 1)); + + return g; + } + } +} diff --git a/Triangle.NET/TestApp/GenericEventArgs.cs b/src/Triangle.Viewer/GenericEventArgs.cs similarity index 94% rename from Triangle.NET/TestApp/GenericEventArgs.cs rename to src/Triangle.Viewer/GenericEventArgs.cs index 9a68171..ea43c90 100644 --- a/Triangle.NET/TestApp/GenericEventArgs.cs +++ b/src/Triangle.Viewer/GenericEventArgs.cs @@ -1,19 +1,19 @@ -using System; - -namespace MeshExplorer -{ - public class GenericEventArgs : EventArgs - { - T argument; - - public T Argument - { - get { return argument; } - } - - public GenericEventArgs(T arg) - { - argument = arg; - } - } -} +using System; + +namespace MeshExplorer +{ + public class GenericEventArgs : EventArgs + { + T argument; + + public T Argument + { + get { return argument; } + } + + public GenericEventArgs(T arg) + { + argument = arg; + } + } +} diff --git a/Triangle.NET/TestApp/IO/FileProcessor.cs b/src/Triangle.Viewer/IO/FileProcessor.cs similarity index 95% rename from Triangle.NET/TestApp/IO/FileProcessor.cs rename to src/Triangle.Viewer/IO/FileProcessor.cs index 353a8f4..d676664 100644 --- a/Triangle.NET/TestApp/IO/FileProcessor.cs +++ b/src/Triangle.Viewer/IO/FileProcessor.cs @@ -1,126 +1,125 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.IO -{ - using System; - using System.Collections.Generic; - using System.IO; - using MeshExplorer.IO.Formats; - using TriangleNet.IO; - using TriangleNet.Geometry; - using TriangleNet; - - /// - /// Provides static methods to read and write mesh files. - /// - public static class FileProcessor - { - static Dictionary container = new Dictionary(); - - public static bool CanHandleFile(string path) - { - if (File.Exists(path)) - { - var provider = GetProviderInstance(path); - - if (provider != null) - { - return true; - } - } - - return false; - } - - /// - /// Returns true, if the given file contains mesh information. - /// - /// - /// - public static bool ContainsMeshData(string path) - { - IMeshFile provider = GetProviderInstance(path); - - return provider.ContainsMeshData(path); - } - - /// - /// Read an input geometry from given file. - /// - public static IPolygon Read(string path) - { - var provider = GetProviderInstance(path); - - return provider.Read(path); - } - - /// - /// Read a mesh from given file. - /// - /// - /// - public static Mesh Import(string path) - { - var provider = GetProviderInstance(path); - - return (Mesh)provider.Import(path); - } - - /// - /// Save the current mesh to given file. - /// - public static void Save(string path, Mesh mesh) - { - IMeshFile provider = GetProviderInstance(path); - - provider.Write(mesh, path); - } - - private static IMeshFile GetProviderInstance(string path) - { - string ext = Path.GetExtension(path); - - IMeshFile provider = null; - - if (container.ContainsKey(ext)) - { - provider = container[ext]; - } - else - { - provider = CreateProviderInstance(ext); - } - - return provider; - } - - private static IMeshFile CreateProviderInstance(string ext) - { - // TODO: automate by using IMeshFormat's Extensions property. - - IMeshFile provider = null; - - if (ext == ".node" || ext == ".poly" || ext == ".ele") - { - provider = new TriangleFile(); - } - else if (ext == ".json") - { - provider = new JsonFile(); - } - - if (provider == null) - { - throw new NotImplementedException("File format not implemented."); - } - - container.Add(ext, provider); - - return provider; - } - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.IO +{ + using MeshExplorer.IO.Formats; + using System; + using System.Collections.Generic; + using System.IO; + using TriangleNet; + using TriangleNet.Geometry; + + /// + /// Provides static methods to read and write mesh files. + /// + public static class FileProcessor + { + static Dictionary container = new Dictionary(); + + public static bool CanHandleFile(string path) + { + if (File.Exists(path)) + { + var provider = GetProviderInstance(path); + + if (provider != null) + { + return true; + } + } + + return false; + } + + /// + /// Returns true, if the given file contains mesh information. + /// + /// + /// + public static bool ContainsMeshData(string path) + { + IMeshFile provider = GetProviderInstance(path); + + return provider.ContainsMeshData(path); + } + + /// + /// Read an input geometry from given file. + /// + public static IPolygon Read(string path) + { + var provider = GetProviderInstance(path); + + return provider.Read(path); + } + + /// + /// Read a mesh from given file. + /// + /// + /// + public static Mesh Import(string path) + { + var provider = GetProviderInstance(path); + + return (Mesh)provider.Import(path); + } + + /// + /// Save the current mesh to given file. + /// + public static void Save(string path, Mesh mesh) + { + IMeshFile provider = GetProviderInstance(path); + + provider.Write(mesh, path); + } + + private static IMeshFile GetProviderInstance(string path) + { + string ext = Path.GetExtension(path); + + IMeshFile provider = null; + + if (container.ContainsKey(ext)) + { + provider = container[ext]; + } + else + { + provider = CreateProviderInstance(ext); + } + + return provider; + } + + private static IMeshFile CreateProviderInstance(string ext) + { + // TODO: automate by using IMeshFormat's Extensions property. + + IMeshFile provider = null; + + if (ext == ".node" || ext == ".poly" || ext == ".ele") + { + provider = new TriangleFile(); + } + else if (ext == ".json") + { + provider = new JsonFile(); + } + + if (provider == null) + { + throw new NotImplementedException("File format not implemented."); + } + + container.Add(ext, provider); + + return provider; + } + } +} diff --git a/Triangle.NET/TestApp/IO/Formats/JsonFile.cs b/src/Triangle.Viewer/IO/Formats/JsonFile.cs similarity index 96% rename from Triangle.NET/TestApp/IO/Formats/JsonFile.cs rename to src/Triangle.Viewer/IO/Formats/JsonFile.cs index dc37bdd..4dacc1c 100644 --- a/Triangle.NET/TestApp/IO/Formats/JsonFile.cs +++ b/src/Triangle.Viewer/IO/Formats/JsonFile.cs @@ -1,574 +1,574 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.IO.Formats -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.IO; - using System.Text; - using TriangleNet; - using TriangleNet.Topology; - using TriangleNet.Geometry; - using TriangleNet.IO; - using TriangleNet.Meshing; - - /// - /// Read and write JSON files. - /// - /// - /// The JSON format: - /// { - /// "config": { - /// "ver": 1, - /// "type": "mesh" | "poly" | "points", - /// "dim": 2 - /// }, - /// "points": { - /// "data": [ p0.x, p0.y ... pn.x, pn.y ], - /// "markers": [ ... ], - /// "attributes": [ ... ] - /// }, - /// "segments": { - /// "data": [ s0(1), s0(2) ... sn(1), sn(2) ], - /// "markers": [ ... ] - /// }, - /// "holes": [ h0.x, h0.y ... hn.x, hn.y ], - /// "triangles": { - /// "data": [ t0(1), t0(2), t0(3) ... tn(1), tn(2), tn(3) ], - /// "neighbors": [ t0.n1, t0.n2, t0.n3 ... tn.n1, tn.n2, tn.n3 ], - /// "attributes": [ ... ] - /// } - /// } - /// - public class JsonFile : IMeshFile - { - string file; - Dictionary json; - - /// - /// Gets the supported file extensions. - /// - public string[] Extensions - { - get { return new string[] { ".json" }; } - } - - public bool ContainsMeshData(string filename) - { - ParseJson(filename); - - if (this.json.ContainsKey("config")) - { - var config = this.json["config"] as Dictionary; - - if (config != null && config.ContainsKey("type")) - { - return config["type"].ToString() == "mesh"; - } - } - - return false; - } - - public bool IsSupported(string file) - { - throw new NotImplementedException(); - } - - public IMesh Import(string filename) - { - var geometry = (Polygon)this.Read(filename); - - List triangles = null; - - if (this.json.ContainsKey("triangles")) - { - var tri = this.json["triangles"] as Dictionary; - - if (tri != null) - { - triangles = ReadTriangles(tri, geometry.Points.Count); - } - } - - return Converter.ToMesh(geometry, triangles); - } - - public void Write(IMesh mesh, string filename) - { - using (StreamWriter writer = new StreamWriter(filename)) - { - int nv = mesh.Vertices.Count; - int ns = mesh.Segments.Count; - int nh = mesh.Holes.Count; - int ne = mesh.Triangles.Count; - - writer.Write("{"); - - // Config header - writer.Write("\"config\":{"); - writer.Write("\"ver\":1,"); - writer.Write("\"type\":\"{0}\",", ne > 0 ? "mesh" : (ns > 0 ? "poly" : "points")); - writer.Write("\"dim\":2"); - writer.Write("}"); - - if (((Mesh)mesh).CurrentNumbering == NodeNumbering.None) - { - ((Mesh)mesh).Renumber(NodeNumbering.Linear); - } - - // Write the coordinates - if (nv > 0) - { - writer.Write(","); - WritePoints((Mesh)mesh, writer, nv); - } - - // Write the segments - if (ns > 0) - { - writer.Write(","); - WriteSegments(mesh.Segments, writer, ns); - } - - // Write the holes - if (nh > 0) - { - writer.Write(","); - WriteHoles(mesh.Holes, writer, nh); - } - - // Write the elements - if (ne > 0) - { - writer.Write(","); - WriteTriangles(mesh.Triangles, writer, ne); - } - - writer.Write("}"); - } - } - - public void Write(IMesh mesh, Stream stream) - { - throw new NotImplementedException(); - } - - /// - /// - /// - /// - /// - public IPolygon Read(string filename) - { - ParseJson(filename); - - var data = new Polygon(); - - if (json == null) - { - // TODO: Exception? - return data; - } - - if (json.ContainsKey("config")) - { - - } - - int count = 0; - - if (json.ContainsKey("points")) - { - var points = json["points"] as Dictionary; - - if (points != null) - { - ReadPoints(data, points, ref count); - } - else - { - // TODO: Exception? - return data; - } - } - - if (json.ContainsKey("segments")) - { - var segments = json["segments"] as Dictionary; - - if (segments != null) - { - ReadSegments(data, segments, count); - } - } - - if (json.ContainsKey("holes")) - { - var holes = json["holes"] as ArrayList; - - if (holes != null) - { - ReadHoles(data, holes); - } - } - - return data; - } - - public void Write(IPolygon polygon, string filename) - { - throw new NotImplementedException(); - } - - public void Write(IPolygon polygon, Stream stream) - { - throw new NotImplementedException(); - } - - private void ParseJson(string filename) - { - if (this.json == null || this.file != filename) - { - this.file = filename; - - string content = File.ReadAllText(filename); - - JsonParser parser = new JsonParser(content); - this.json = parser.Decode() as Dictionary; - } - } - - #region Read helpers - - private void ReadPoints(Polygon geometry, Dictionary points, ref int count) - { - ArrayList data = points["data"] as ArrayList; - - ArrayList markers = null; - ArrayList attributes = null; - - if (points.ContainsKey("markers")) - { - markers = points["markers"] as ArrayList; - } - - if (points.ContainsKey("attributes")) - { - attributes = points["attributes"] as ArrayList; - } - - if (data != null) - { - int mark, n = data.Count; - - if (n % 2 != 0) - { - throw new Exception("JSON format error (points)."); - } - - // Number of points - count = n / 2; - - for (int i = 0; i < n; i += 2) - { - mark = 0; - - if (markers != null && markers.Count == count) - { - mark = int.Parse(markers[i / 2].ToString()); - } - - geometry.Add(new Vertex( - double.Parse(data[i].ToString(), Util.Nfi), - double.Parse(data[i + 1].ToString(), Util.Nfi), - mark - )); - } - } - } - - private void ReadSegments(Polygon geometry, Dictionary segments, int count) - { - ArrayList data = segments["data"] as ArrayList; - - ArrayList markers = null; - - if (segments.ContainsKey("markers")) - { - markers = segments["markers"] as ArrayList; - } - - if (data != null) - { - int mark, n = data.Count; - - if (n % 2 != 0) - { - throw new Exception("JSON format error (segments)."); - } - - int p0, p1; - - throw new NotImplementedException(); - // TODO: Fix JSON format - - for (int i = 0; i < n; i += 2) - { - mark = 0; - - if (markers != null && markers.Count == n) - { - mark = int.Parse(markers[i / 2].ToString()); - } - - p0 = int.Parse(data[i].ToString()); - p1 = int.Parse(data[i + 1].ToString()); - - if (p0 < 0 || p0 >= count || p1 < 0 || p1 >= count) - { - throw new Exception("JSON format error (segment index)."); - } - - //geometry.Add(new Edge(p0, p1, mark)); - } - } - } - - private void ReadHoles(Polygon geometry, ArrayList holes) - { - int n = holes.Count; - - if (n % 2 != 0) - { - throw new Exception("JSON format error (holes)."); - } - - for (int i = 0; i < n; i += 2) - { - geometry.Holes.Add(new Point( - double.Parse(holes[i].ToString(), Util.Nfi), - double.Parse(holes[i + 1].ToString(), Util.Nfi) - )); - } - } - - private List ReadTriangles(Dictionary triangles, int points) - { - ArrayList data = triangles["data"] as ArrayList; - - ArrayList neighbors = null; - ArrayList attributes = null; - - if (triangles.ContainsKey("neighbors")) - { - neighbors = triangles["neighbors"] as ArrayList; - } - - if (triangles.ContainsKey("attributes")) - { - attributes = triangles["attributes"] as ArrayList; - } - - List output = null; - - if (data != null) - { - int n = data.Count; - - if (n % 3 != 0) - { - throw new Exception("JSON format error (triangles)."); - } - - output = new List(n / 3); - - int p0, p1, p2, n0, n1, n2; - - for (int i = 0; i < n; i += 3) - { - p0 = int.Parse(data[i].ToString()); - p1 = int.Parse(data[i + 1].ToString()); - p2 = int.Parse(data[i + 2].ToString()); - - n0 = n1 = n2 = -1; - - if (p0 < 0 || p0 >= points || p1 < 0 || p1 >= points || p2 < 0 || p2 >= points) - { - throw new Exception("JSON format error (triangle index)."); - } - - if (neighbors.Count == n) - { - n0 = int.Parse(neighbors[i].ToString()); - n1 = int.Parse(neighbors[i + 1].ToString()); - n2 = int.Parse(neighbors[i + 2].ToString()); - } - - // TODO: Set neighbors - output.Add(new InputTriangle(p0, p1, p2)); - } - } - - return output; - } - - #endregion - - #region Write helpers - - private void WritePoints(Mesh mesh, StreamWriter writer, int nv) - { - bool useMarkers = false; - - StringBuilder markers; - - writer.Write("\"points\":{\"data\":["); - - if (mesh.CurrentNumbering == NodeNumbering.Linear) - { - markers = WritePoints(mesh.Vertices, writer, nv, useMarkers); - } - else - { - Vertex[] nodes = new Vertex[mesh.Vertices.Count]; - - foreach (var node in mesh.Vertices) - { - nodes[node.ID] = node; - } - - markers = WritePoints(nodes, writer, nv, useMarkers); - } - - writer.Write("]"); - if (useMarkers) - { - writer.Write(",\"markers\":[" + markers.ToString() + "]"); - } - - // TODO: writer.Write(",\"attributes\":[]"); - writer.Write("}"); - } - - private static StringBuilder WritePoints(IEnumerable data, StreamWriter writer, int nv, bool useMarkers) - { - StringBuilder markers = new StringBuilder(); - - int i = 0; - string seperator; - foreach (var item in data) - { - seperator = (i == nv - 1) ? String.Empty : ", "; - - writer.Write("{0},{1}{2}", - item.X.ToString(Util.Nfi), - item.Y.ToString(Util.Nfi), seperator); - - if (item.Label > 0) - { - useMarkers = true; - } - - markers.AppendFormat("{0}{1}", item.Label, seperator); - - i++; - } - - return markers; - } - - private void WriteHoles(IEnumerable data, StreamWriter writer, int nh) - { - int i = 0; - - writer.Write("\"holes\":["); - foreach (var item in data) - { - writer.Write("{0},{1}{2}", - item.X.ToString(Util.Nfi), - item.Y.ToString(Util.Nfi), (i == nh - 1) ? String.Empty : ", "); - - i++; - } - - writer.Write("]"); - } - - private void WriteSegments(IEnumerable data, StreamWriter writer, int ns) - { - int i = 0; - - StringBuilder markers = new StringBuilder(); - bool useMarkers = false; - - string seperator; - - writer.Write("\"segments\":{\"data\":["); - foreach (var item in data) - { - seperator = (i == ns - 1) ? String.Empty : ", "; - - writer.Write("{0},{1}{2}", - item.P0, item.P1, seperator); - - if (item.Label > 0) - { - useMarkers = true; - } - - markers.AppendFormat("{0}{1}", item.Label, seperator); - - i++; - } - - writer.Write("]"); - - if (useMarkers) - { - writer.Write(",\"markers\":[" + markers.ToString() + "]"); - } - - writer.Write("}"); - } - - private void WriteTriangles(IEnumerable data, StreamWriter writer, int ne) - { - int i = 0; - - StringBuilder neighbors = new StringBuilder(); - - string seperator; - - writer.Write("\"triangles\":{\"data\":["); - foreach (var item in data) - { - seperator = (i == ne - 1) ? String.Empty : ", "; - - writer.Write("{0},{1},{2}{3}", - item.GetVertexID(0), - item.GetVertexID(1), - item.GetVertexID(2), - seperator); - - neighbors.AppendFormat("{0},{1},{2}{3}", - item.GetNeighborID(0), - item.GetNeighborID(1), - item.GetNeighborID(2), - seperator); - - i++; - } - writer.Write("]"); - writer.Write(",\"neighbors\":[" + neighbors.ToString() + "]"); - writer.Write("}"); - } - - #endregion - } +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.IO.Formats +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.IO; + using System.Text; + using TriangleNet; + using TriangleNet.Topology; + using TriangleNet.Geometry; + using TriangleNet.IO; + using TriangleNet.Meshing; + + /// + /// Read and write JSON files. + /// + /// + /// The JSON format: + /// { + /// "config": { + /// "ver": 1, + /// "type": "mesh" | "poly" | "points", + /// "dim": 2 + /// }, + /// "points": { + /// "data": [ p0.x, p0.y ... pn.x, pn.y ], + /// "markers": [ ... ], + /// "attributes": [ ... ] + /// }, + /// "segments": { + /// "data": [ s0(1), s0(2) ... sn(1), sn(2) ], + /// "markers": [ ... ] + /// }, + /// "holes": [ h0.x, h0.y ... hn.x, hn.y ], + /// "triangles": { + /// "data": [ t0(1), t0(2), t0(3) ... tn(1), tn(2), tn(3) ], + /// "neighbors": [ t0.n1, t0.n2, t0.n3 ... tn.n1, tn.n2, tn.n3 ], + /// "attributes": [ ... ] + /// } + /// } + /// + public class JsonFile : IMeshFile + { + string file; + Dictionary json; + + /// + /// Gets the supported file extensions. + /// + public string[] Extensions + { + get { return new string[] { ".json" }; } + } + + public bool ContainsMeshData(string filename) + { + ParseJson(filename); + + if (this.json.ContainsKey("config")) + { + var config = this.json["config"] as Dictionary; + + if (config != null && config.ContainsKey("type")) + { + return config["type"].ToString() == "mesh"; + } + } + + return false; + } + + public bool IsSupported(string file) + { + throw new NotImplementedException(); + } + + public IMesh Import(string filename) + { + var geometry = (Polygon)this.Read(filename); + + List triangles = null; + + if (this.json.ContainsKey("triangles")) + { + var tri = this.json["triangles"] as Dictionary; + + if (tri != null) + { + triangles = ReadTriangles(tri, geometry.Points.Count); + } + } + + return Converter.Instance.ToMesh(geometry, triangles); + } + + public void Write(IMesh mesh, string filename) + { + using (StreamWriter writer = new StreamWriter(filename)) + { + int nv = mesh.Vertices.Count; + int ns = mesh.Segments.Count; + int nh = mesh.Holes.Count; + int ne = mesh.Triangles.Count; + + writer.Write("{"); + + // Config header + writer.Write("\"config\":{"); + writer.Write("\"ver\":1,"); + writer.Write("\"type\":\"{0}\",", ne > 0 ? "mesh" : (ns > 0 ? "poly" : "points")); + writer.Write("\"dim\":2"); + writer.Write("}"); + + if (((Mesh)mesh).CurrentNumbering == NodeNumbering.None) + { + ((Mesh)mesh).Renumber(NodeNumbering.Linear); + } + + // Write the coordinates + if (nv > 0) + { + writer.Write(","); + WritePoints((Mesh)mesh, writer, nv); + } + + // Write the segments + if (ns > 0) + { + writer.Write(","); + WriteSegments(mesh.Segments, writer, ns); + } + + // Write the holes + if (nh > 0) + { + writer.Write(","); + WriteHoles(mesh.Holes, writer, nh); + } + + // Write the elements + if (ne > 0) + { + writer.Write(","); + WriteTriangles(mesh.Triangles, writer, ne); + } + + writer.Write("}"); + } + } + + public void Write(IMesh mesh, Stream stream) + { + throw new NotImplementedException(); + } + + /// + /// + /// + /// + /// + public IPolygon Read(string filename) + { + ParseJson(filename); + + var data = new Polygon(); + + if (json == null) + { + // TODO: Exception? + return data; + } + + if (json.ContainsKey("config")) + { + + } + + int count = 0; + + if (json.ContainsKey("points")) + { + var points = json["points"] as Dictionary; + + if (points != null) + { + ReadPoints(data, points, ref count); + } + else + { + // TODO: Exception? + return data; + } + } + + if (json.ContainsKey("segments")) + { + var segments = json["segments"] as Dictionary; + + if (segments != null) + { + ReadSegments(data, segments, count); + } + } + + if (json.ContainsKey("holes")) + { + var holes = json["holes"] as ArrayList; + + if (holes != null) + { + ReadHoles(data, holes); + } + } + + return data; + } + + public void Write(IPolygon polygon, string filename) + { + throw new NotImplementedException(); + } + + public void Write(IPolygon polygon, Stream stream) + { + throw new NotImplementedException(); + } + + private void ParseJson(string filename) + { + if (this.json == null || this.file != filename) + { + this.file = filename; + + string content = File.ReadAllText(filename); + + JsonParser parser = new JsonParser(content); + this.json = parser.Decode() as Dictionary; + } + } + + #region Read helpers + + private void ReadPoints(Polygon geometry, Dictionary points, ref int count) + { + ArrayList data = points["data"] as ArrayList; + + ArrayList markers = null; + ArrayList attributes = null; + + if (points.ContainsKey("markers")) + { + markers = points["markers"] as ArrayList; + } + + if (points.ContainsKey("attributes")) + { + attributes = points["attributes"] as ArrayList; + } + + if (data != null) + { + int mark, n = data.Count; + + if (n % 2 != 0) + { + throw new Exception("JSON format error (points)."); + } + + // Number of points + count = n / 2; + + for (int i = 0; i < n; i += 2) + { + mark = 0; + + if (markers != null && markers.Count == count) + { + mark = int.Parse(markers[i / 2].ToString()); + } + + geometry.Add(new Vertex( + double.Parse(data[i].ToString(), Util.Nfi), + double.Parse(data[i + 1].ToString(), Util.Nfi), + mark + )); + } + } + } + + private void ReadSegments(Polygon geometry, Dictionary segments, int count) + { + ArrayList data = segments["data"] as ArrayList; + + ArrayList markers = null; + + if (segments.ContainsKey("markers")) + { + markers = segments["markers"] as ArrayList; + } + + if (data != null) + { + int mark, n = data.Count; + + if (n % 2 != 0) + { + throw new Exception("JSON format error (segments)."); + } + + int p0, p1; + + throw new NotImplementedException(); + // TODO: Fix JSON format + + for (int i = 0; i < n; i += 2) + { + mark = 0; + + if (markers != null && markers.Count == n) + { + mark = int.Parse(markers[i / 2].ToString()); + } + + p0 = int.Parse(data[i].ToString()); + p1 = int.Parse(data[i + 1].ToString()); + + if (p0 < 0 || p0 >= count || p1 < 0 || p1 >= count) + { + throw new Exception("JSON format error (segment index)."); + } + + //geometry.Add(new Edge(p0, p1, mark)); + } + } + } + + private void ReadHoles(Polygon geometry, ArrayList holes) + { + int n = holes.Count; + + if (n % 2 != 0) + { + throw new Exception("JSON format error (holes)."); + } + + for (int i = 0; i < n; i += 2) + { + geometry.Holes.Add(new Point( + double.Parse(holes[i].ToString(), Util.Nfi), + double.Parse(holes[i + 1].ToString(), Util.Nfi) + )); + } + } + + private List ReadTriangles(Dictionary triangles, int points) + { + ArrayList data = triangles["data"] as ArrayList; + + ArrayList neighbors = null; + ArrayList attributes = null; + + if (triangles.ContainsKey("neighbors")) + { + neighbors = triangles["neighbors"] as ArrayList; + } + + if (triangles.ContainsKey("attributes")) + { + attributes = triangles["attributes"] as ArrayList; + } + + List output = null; + + if (data != null) + { + int n = data.Count; + + if (n % 3 != 0) + { + throw new Exception("JSON format error (triangles)."); + } + + output = new List(n / 3); + + int p0, p1, p2, n0, n1, n2; + + for (int i = 0; i < n; i += 3) + { + p0 = int.Parse(data[i].ToString()); + p1 = int.Parse(data[i + 1].ToString()); + p2 = int.Parse(data[i + 2].ToString()); + + n0 = n1 = n2 = -1; + + if (p0 < 0 || p0 >= points || p1 < 0 || p1 >= points || p2 < 0 || p2 >= points) + { + throw new Exception("JSON format error (triangle index)."); + } + + if (neighbors.Count == n) + { + n0 = int.Parse(neighbors[i].ToString()); + n1 = int.Parse(neighbors[i + 1].ToString()); + n2 = int.Parse(neighbors[i + 2].ToString()); + } + + // TODO: Set neighbors + output.Add(new InputTriangle(p0, p1, p2)); + } + } + + return output; + } + + #endregion + + #region Write helpers + + private void WritePoints(Mesh mesh, StreamWriter writer, int nv) + { + bool useMarkers = false; + + StringBuilder markers; + + writer.Write("\"points\":{\"data\":["); + + if (mesh.CurrentNumbering == NodeNumbering.Linear) + { + markers = WritePoints(mesh.Vertices, writer, nv, useMarkers); + } + else + { + Vertex[] nodes = new Vertex[mesh.Vertices.Count]; + + foreach (var node in mesh.Vertices) + { + nodes[node.ID] = node; + } + + markers = WritePoints(nodes, writer, nv, useMarkers); + } + + writer.Write("]"); + if (useMarkers) + { + writer.Write(",\"markers\":[" + markers.ToString() + "]"); + } + + // TODO: writer.Write(",\"attributes\":[]"); + writer.Write("}"); + } + + private static StringBuilder WritePoints(IEnumerable data, StreamWriter writer, int nv, bool useMarkers) + { + StringBuilder markers = new StringBuilder(); + + int i = 0; + string seperator; + foreach (var item in data) + { + seperator = (i == nv - 1) ? String.Empty : ", "; + + writer.Write("{0},{1}{2}", + item.X.ToString(Util.Nfi), + item.Y.ToString(Util.Nfi), seperator); + + if (item.Label > 0) + { + useMarkers = true; + } + + markers.AppendFormat("{0}{1}", item.Label, seperator); + + i++; + } + + return markers; + } + + private void WriteHoles(IEnumerable data, StreamWriter writer, int nh) + { + int i = 0; + + writer.Write("\"holes\":["); + foreach (var item in data) + { + writer.Write("{0},{1}{2}", + item.X.ToString(Util.Nfi), + item.Y.ToString(Util.Nfi), (i == nh - 1) ? String.Empty : ", "); + + i++; + } + + writer.Write("]"); + } + + private void WriteSegments(IEnumerable data, StreamWriter writer, int ns) + { + int i = 0; + + StringBuilder markers = new StringBuilder(); + bool useMarkers = false; + + string seperator; + + writer.Write("\"segments\":{\"data\":["); + foreach (var item in data) + { + seperator = (i == ns - 1) ? String.Empty : ", "; + + writer.Write("{0},{1}{2}", + item.P0, item.P1, seperator); + + if (item.Label > 0) + { + useMarkers = true; + } + + markers.AppendFormat("{0}{1}", item.Label, seperator); + + i++; + } + + writer.Write("]"); + + if (useMarkers) + { + writer.Write(",\"markers\":[" + markers.ToString() + "]"); + } + + writer.Write("}"); + } + + private void WriteTriangles(IEnumerable data, StreamWriter writer, int ne) + { + int i = 0; + + StringBuilder neighbors = new StringBuilder(); + + string seperator; + + writer.Write("\"triangles\":{\"data\":["); + foreach (var item in data) + { + seperator = (i == ne - 1) ? String.Empty : ", "; + + writer.Write("{0},{1},{2}{3}", + item.GetVertexID(0), + item.GetVertexID(1), + item.GetVertexID(2), + seperator); + + neighbors.AppendFormat("{0},{1},{2}{3}", + item.GetNeighborID(0), + item.GetNeighborID(1), + item.GetNeighborID(2), + seperator); + + i++; + } + writer.Write("]"); + writer.Write(",\"neighbors\":[" + neighbors.ToString() + "]"); + writer.Write("}"); + } + + #endregion + } } \ No newline at end of file diff --git a/Triangle.NET/TestApp/IO/Formats/TriangleFile.cs b/src/Triangle.Viewer/IO/Formats/TriangleFile.cs similarity index 96% rename from Triangle.NET/TestApp/IO/Formats/TriangleFile.cs rename to src/Triangle.Viewer/IO/Formats/TriangleFile.cs index ae60afa..be6c56a 100644 --- a/Triangle.NET/TestApp/IO/Formats/TriangleFile.cs +++ b/src/Triangle.Viewer/IO/Formats/TriangleFile.cs @@ -1,83 +1,83 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.IO.Formats -{ - using System; - using System.IO; - using TriangleNet.Geometry; - using TriangleNet.IO; - using TriangleNet.Meshing; - - /// - /// Read and write files defined in classic Triangle format. - /// - public class TriangleFile : IMeshFile - { - TriangleFormat format = new TriangleFormat(); - - /// - /// Gets the supported file extensions. - /// - public string[] Extensions - { - get { return new string[] { ".node", ".poly", ".ele" }; } - } - - public bool ContainsMeshData(string filename) - { - string ext = Path.GetExtension(filename); - - if (ext == ".node" || ext == ".poly") - { - if (File.Exists(Path.ChangeExtension(filename, ".ele"))) - { - return true; - } - } - - return (ext == ".ele"); - } - - public bool IsSupported(string file) - { - throw new NotImplementedException(); - } - - public IPolygon Read(string filename) - { - return format.Read(filename); - } - - public void Write(IPolygon polygon, string filename) - { - format.Write(polygon, filename); - } - - public void Write(IPolygon polygon, Stream stream) - { - format.Write(polygon, stream); - } - - public IMesh Import(string filename) - { - return format.Import(filename); - } - - public void Write(IMesh mesh, string filename) - { - if (mesh.Vertices.Count > 0) - { - format.Write(mesh, filename); - } - } - - public void Write(IMesh mesh, Stream stream) - { - format.Write(mesh, stream); - } - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.IO.Formats +{ + using System; + using System.IO; + using TriangleNet.Geometry; + using TriangleNet.IO; + using TriangleNet.Meshing; + + /// + /// Read and write files defined in classic Triangle format. + /// + public class TriangleFile : IMeshFile + { + TriangleFormat format = new TriangleFormat(); + + /// + /// Gets the supported file extensions. + /// + public string[] Extensions + { + get { return new string[] { ".node", ".poly", ".ele" }; } + } + + public bool ContainsMeshData(string filename) + { + string ext = Path.GetExtension(filename); + + if (ext == ".node" || ext == ".poly") + { + if (File.Exists(Path.ChangeExtension(filename, ".ele"))) + { + return true; + } + } + + return (ext == ".ele"); + } + + public bool IsSupported(string file) + { + throw new NotImplementedException(); + } + + public IPolygon Read(string filename) + { + return format.Read(filename); + } + + public void Write(IPolygon polygon, string filename) + { + format.Write(polygon, filename); + } + + public void Write(IPolygon polygon, Stream stream) + { + format.Write(polygon, stream); + } + + public IMesh Import(string filename) + { + return format.Import(filename); + } + + public void Write(IMesh mesh, string filename) + { + if (mesh.Vertices.Count > 0) + { + format.Write(mesh, filename); + } + } + + public void Write(IMesh mesh, Stream stream) + { + format.Write(mesh, stream); + } + } +} diff --git a/Triangle.NET/TestApp/IO/IMeshFile.cs b/src/Triangle.Viewer/IO/IMeshFile.cs similarity index 85% rename from Triangle.NET/TestApp/IO/IMeshFile.cs rename to src/Triangle.Viewer/IO/IMeshFile.cs index 35e55cb..47f0092 100644 --- a/Triangle.NET/TestApp/IO/IMeshFile.cs +++ b/src/Triangle.Viewer/IO/IMeshFile.cs @@ -1,33 +1,28 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.IO -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using TriangleNet; - using TriangleNet.IO; - - /// - /// Defines an interface for mesh file formats. - /// - public interface IMeshFile : IPolygonFormat, IMeshFormat - { - /// - /// The supported file extensions. - /// - string[] Extensions { get; } - - /// - /// Return true, if a (previously saved) mesh is associated with the given file. - /// - /// The file name. - /// True, if a mesh is associated with the given file. - bool ContainsMeshData(string filename); - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.IO +{ + using TriangleNet.IO; + + /// + /// Defines an interface for mesh file formats. + /// + public interface IMeshFile : IPolygonFormat, IMeshFormat + { + /// + /// The supported file extensions. + /// + string[] Extensions { get; } + + /// + /// Return true, if a (previously saved) mesh is associated with the given file. + /// + /// The file name. + /// True, if a mesh is associated with the given file. + bool ContainsMeshData(string filename); + } +} diff --git a/Triangle.NET/TestApp/IO/ImageWriter.cs b/src/Triangle.Viewer/IO/ImageWriter.cs similarity index 96% rename from Triangle.NET/TestApp/IO/ImageWriter.cs rename to src/Triangle.Viewer/IO/ImageWriter.cs index e1e764d..b0a265d 100644 --- a/Triangle.NET/TestApp/IO/ImageWriter.cs +++ b/src/Triangle.Viewer/IO/ImageWriter.cs @@ -1,88 +1,88 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.IO -{ - using System.IO; - using System.IO.Compression; - using TriangleNet; - using TriangleNet.Rendering.GDI; - using TriangleNet.Rendering.Text; - - /// - /// Writes an image of the mesh to disk. - /// - public class ImageWriter - { - /// - /// Export the mesh to PNG format. - /// - /// The current mesh. - /// The PNG filename. - /// Image type (0 = png, 1 = eps, 2 = svg). - /// The desired width of the image. - /// Use GZip compression (only eps or svg). - public void Export(Mesh mesh, string filename, int type, int width, bool compress) - { - if (type == 1) - { - ExportEps(mesh, filename, width, compress); - } - else if (type == 2) - { - ExportSvg(mesh, filename, width, compress); - } - else - { - ImageRenderer.Save(mesh, filename, width); - } - } - - private void ExportEps(Mesh mesh, string filename, int width, bool compress) - { - var eps = new EpsImage(); - - eps.Export(mesh, filename, width); - - if (compress) - { - CompressFile(filename, true); - } - } - - private void ExportSvg(Mesh mesh, string filename, int width, bool compress) - { - var svg = new SvgImage(); - - svg.Export(mesh, filename, width); - - if (compress) - { - CompressFile(filename, true); - } - } - - private void CompressFile(string filename, bool cleanup) - { - if (!File.Exists(filename)) - { - return; - } - - using (var input = File.OpenRead(filename)) - using (var output = File.Create(filename + ".gz")) - using (var gzip = new GZipStream(output, CompressionMode.Compress)) - { - input.CopyTo(gzip); - } - - if (cleanup) - { - File.Delete(filename); - } - } - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.IO +{ + using System.IO; + using System.IO.Compression; + using TriangleNet; + using TriangleNet.Rendering.GDI; + using TriangleNet.Rendering.Text; + + /// + /// Writes an image of the mesh to disk. + /// + public class ImageWriter + { + /// + /// Export the mesh to PNG format. + /// + /// The current mesh. + /// The PNG filename. + /// Image type (0 = png, 1 = eps, 2 = svg). + /// The desired width of the image. + /// Use GZip compression (only eps or svg). + public void Export(Mesh mesh, string filename, int type, int width, bool compress) + { + if (type == 1) + { + ExportEps(mesh, filename, width, compress); + } + else if (type == 2) + { + ExportSvg(mesh, filename, width, compress); + } + else + { + ImageRenderer.Save(mesh, filename, width); + } + } + + private void ExportEps(Mesh mesh, string filename, int width, bool compress) + { + var eps = new EpsImage(); + + eps.Export(mesh, filename, width); + + if (compress) + { + CompressFile(filename, true); + } + } + + private void ExportSvg(Mesh mesh, string filename, int width, bool compress) + { + var svg = new SvgImage(); + + svg.Export(mesh, filename, width); + + if (compress) + { + CompressFile(filename, true); + } + } + + private void CompressFile(string filename, bool cleanup) + { + if (!File.Exists(filename)) + { + return; + } + + using (var input = File.OpenRead(filename)) + using (var output = File.Create(filename + ".gz")) + using (var gzip = new GZipStream(output, CompressionMode.Compress)) + { + input.CopyTo(gzip); + } + + if (cleanup) + { + File.Delete(filename); + } + } + } +} diff --git a/Triangle.NET/TestApp/IO/JsonParser.cs b/src/Triangle.Viewer/IO/JsonParser.cs similarity index 96% rename from Triangle.NET/TestApp/IO/JsonParser.cs rename to src/Triangle.Viewer/IO/JsonParser.cs index ced3436..99cfebd 100644 --- a/Triangle.NET/TestApp/IO/JsonParser.cs +++ b/src/Triangle.Viewer/IO/JsonParser.cs @@ -1,408 +1,408 @@ -// ----------------------------------------------------------------------- -// -// fastJSON - http://fastjson.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.IO -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Globalization; - using System.Text; - - /// - /// This class encodes and decodes JSON strings. - /// Spec. details, see http://www.json.org/ - /// - /// JSON uses Arrays and Objects. These correspond here to the datatypes ArrayList and Hashtable. - /// All numbers are parsed to doubles. - /// - internal class JsonParser - { - enum Token - { - None = -1, // Used to denote no Lookahead available - Curly_Open, - Curly_Close, - Squared_Open, - Squared_Close, - Colon, - Comma, - String, - Number, - True, - False, - Null - } - - readonly char[] json; - readonly StringBuilder s = new StringBuilder(); - Token lookAheadToken = Token.None; - int index; - - public JsonParser(string json) - { - this.json = json.ToCharArray(); - } - - public object Decode() - { - return ParseValue(); - } - - private Dictionary ParseObject() - { - Dictionary table = new Dictionary(); - - ConsumeToken(); // { - - while (true) - { - switch (LookAhead()) - { - - case Token.Comma: - ConsumeToken(); - break; - - case Token.Curly_Close: - ConsumeToken(); - return table; - - default: - { - - // name - string name = ParseString(); - - // : - if (NextToken() != Token.Colon) - { - throw new Exception("Expected colon at index " + index); - } - - // value - object value = ParseValue(); - - table[name] = value; - } - break; - } - } - } - - private ArrayList ParseArray() - { - ArrayList array = new ArrayList(); - - ConsumeToken(); // [ - - while (true) - { - switch (LookAhead()) - { - - case Token.Comma: - ConsumeToken(); - break; - - case Token.Squared_Close: - ConsumeToken(); - return array; - - default: - { - array.Add(ParseValue()); - } - break; - } - } - } - - private object ParseValue() - { - switch (LookAhead()) - { - case Token.Number: - return ParseNumber(); - - case Token.String: - return ParseString(); - - case Token.Curly_Open: - return ParseObject(); - - case Token.Squared_Open: - return ParseArray(); - - case Token.True: - ConsumeToken(); - return true; - - case Token.False: - ConsumeToken(); - return false; - - case Token.Null: - ConsumeToken(); - return null; - } - - throw new Exception("Unrecognized token at index" + index); - } - - private string ParseString() - { - ConsumeToken(); // " - - s.Length = 0; - - int runIndex = -1; - - while (index < json.Length) - { - var c = json[index++]; - - if (c == '"') - { - if (runIndex != -1) - { - if (s.Length == 0) - return new string(json, runIndex, index - runIndex - 1); - - s.Append(json, runIndex, index - runIndex - 1); - } - return s.ToString(); - } - - if (c != '\\') - { - if (runIndex == -1) - runIndex = index - 1; - - continue; - } - - if (index == json.Length) break; - - if (runIndex != -1) - { - s.Append(json, runIndex, index - runIndex - 1); - runIndex = -1; - } - - switch (json[index++]) - { - case '"': - s.Append('"'); - break; - - case '\\': - s.Append('\\'); - break; - - case '/': - s.Append('/'); - break; - - case 'b': - s.Append('\b'); - break; - - case 'f': - s.Append('\f'); - break; - - case 'n': - s.Append('\n'); - break; - - case 'r': - s.Append('\r'); - break; - - case 't': - s.Append('\t'); - break; - - case 'u': - { - int remainingLength = json.Length - index; - if (remainingLength < 4) break; - - // parse the 32 bit hex into an integer codepoint - uint codePoint = ParseUnicode(json[index], json[index + 1], json[index + 2], json[index + 3]); - s.Append((char)codePoint); - - // skip 4 chars - index += 4; - } - break; - } - } - - throw new Exception("Unexpectedly reached end of string"); - } - - private uint ParseSingleChar(char c1, uint multipliyer) - { - uint p1 = 0; - if (c1 >= '0' && c1 <= '9') - p1 = (uint)(c1 - '0') * multipliyer; - else if (c1 >= 'A' && c1 <= 'F') - p1 = (uint)((c1 - 'A') + 10) * multipliyer; - else if (c1 >= 'a' && c1 <= 'f') - p1 = (uint)((c1 - 'a') + 10) * multipliyer; - return p1; - } - - private uint ParseUnicode(char c1, char c2, char c3, char c4) - { - uint p1 = ParseSingleChar(c1, 0x1000); - uint p2 = ParseSingleChar(c2, 0x100); - uint p3 = ParseSingleChar(c3, 0x10); - uint p4 = ParseSingleChar(c4, 1); - - return p1 + p2 + p3 + p4; - } - - private string ParseNumber() - { - ConsumeToken(); - - // Need to start back one place because the first digit is also a token and would have been consumed - var startIndex = index - 1; - - do - { - var c = json[index]; - - if ((c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E') - { - if (++index == json.Length) throw new Exception("Unexpected end of string whilst parsing number"); - continue; - } - - break; - } while (true); - - return new string(json, startIndex, index - startIndex); - } - - private Token LookAhead() - { - if (lookAheadToken != Token.None) return lookAheadToken; - - return lookAheadToken = NextTokenCore(); - } - - private void ConsumeToken() - { - lookAheadToken = Token.None; - } - - private Token NextToken() - { - var result = lookAheadToken != Token.None ? lookAheadToken : NextTokenCore(); - - lookAheadToken = Token.None; - - return result; - } - - private Token NextTokenCore() - { - char c; - - // Skip past whitespace - do - { - c = json[index]; - - if (c > ' ') break; - if (c != ' ' && c != '\t' && c != '\n' && c != '\r') break; - - } while (++index < json.Length); - - if (index == json.Length) - { - throw new Exception("Reached end of string unexpectedly"); - } - - c = json[index]; - - index++; - - //if (c >= '0' && c <= '9') - // return Token.Number; - - switch (c) - { - case '{': - return Token.Curly_Open; - - case '}': - return Token.Curly_Close; - - case '[': - return Token.Squared_Open; - - case ']': - return Token.Squared_Close; - - case ',': - return Token.Comma; - - case '"': - return Token.String; - - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case '-': case '+': case '.': - return Token.Number; - - case ':': - return Token.Colon; - - case 'f': - if (json.Length - index >= 4 && - json[index + 0] == 'a' && - json[index + 1] == 'l' && - json[index + 2] == 's' && - json[index + 3] == 'e') - { - index += 4; - return Token.False; - } - break; - - case 't': - if (json.Length - index >= 3 && - json[index + 0] == 'r' && - json[index + 1] == 'u' && - json[index + 2] == 'e') - { - index += 3; - return Token.True; - } - break; - - case 'n': - if (json.Length - index >= 3 && - json[index + 0] == 'u' && - json[index + 1] == 'l' && - json[index + 2] == 'l') - { - index += 3; - return Token.Null; - } - break; - - } - - throw new Exception("Could not find token at index " + --index); - } - } -} +// ----------------------------------------------------------------------- +// +// fastJSON - http://fastjson.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.IO +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Globalization; + using System.Text; + + /// + /// This class encodes and decodes JSON strings. + /// Spec. details, see http://www.json.org/ + /// + /// JSON uses Arrays and Objects. These correspond here to the datatypes ArrayList and Hashtable. + /// All numbers are parsed to doubles. + /// + internal class JsonParser + { + enum Token + { + None = -1, // Used to denote no Lookahead available + Curly_Open, + Curly_Close, + Squared_Open, + Squared_Close, + Colon, + Comma, + String, + Number, + True, + False, + Null + } + + readonly char[] json; + readonly StringBuilder s = new StringBuilder(); + Token lookAheadToken = Token.None; + int index; + + public JsonParser(string json) + { + this.json = json.ToCharArray(); + } + + public object Decode() + { + return ParseValue(); + } + + private Dictionary ParseObject() + { + Dictionary table = new Dictionary(); + + ConsumeToken(); // { + + while (true) + { + switch (LookAhead()) + { + + case Token.Comma: + ConsumeToken(); + break; + + case Token.Curly_Close: + ConsumeToken(); + return table; + + default: + { + + // name + string name = ParseString(); + + // : + if (NextToken() != Token.Colon) + { + throw new Exception("Expected colon at index " + index); + } + + // value + object value = ParseValue(); + + table[name] = value; + } + break; + } + } + } + + private ArrayList ParseArray() + { + ArrayList array = new ArrayList(); + + ConsumeToken(); // [ + + while (true) + { + switch (LookAhead()) + { + + case Token.Comma: + ConsumeToken(); + break; + + case Token.Squared_Close: + ConsumeToken(); + return array; + + default: + { + array.Add(ParseValue()); + } + break; + } + } + } + + private object ParseValue() + { + switch (LookAhead()) + { + case Token.Number: + return ParseNumber(); + + case Token.String: + return ParseString(); + + case Token.Curly_Open: + return ParseObject(); + + case Token.Squared_Open: + return ParseArray(); + + case Token.True: + ConsumeToken(); + return true; + + case Token.False: + ConsumeToken(); + return false; + + case Token.Null: + ConsumeToken(); + return null; + } + + throw new Exception("Unrecognized token at index" + index); + } + + private string ParseString() + { + ConsumeToken(); // " + + s.Length = 0; + + int runIndex = -1; + + while (index < json.Length) + { + var c = json[index++]; + + if (c == '"') + { + if (runIndex != -1) + { + if (s.Length == 0) + return new string(json, runIndex, index - runIndex - 1); + + s.Append(json, runIndex, index - runIndex - 1); + } + return s.ToString(); + } + + if (c != '\\') + { + if (runIndex == -1) + runIndex = index - 1; + + continue; + } + + if (index == json.Length) break; + + if (runIndex != -1) + { + s.Append(json, runIndex, index - runIndex - 1); + runIndex = -1; + } + + switch (json[index++]) + { + case '"': + s.Append('"'); + break; + + case '\\': + s.Append('\\'); + break; + + case '/': + s.Append('/'); + break; + + case 'b': + s.Append('\b'); + break; + + case 'f': + s.Append('\f'); + break; + + case 'n': + s.Append('\n'); + break; + + case 'r': + s.Append('\r'); + break; + + case 't': + s.Append('\t'); + break; + + case 'u': + { + int remainingLength = json.Length - index; + if (remainingLength < 4) break; + + // parse the 32 bit hex into an integer codepoint + uint codePoint = ParseUnicode(json[index], json[index + 1], json[index + 2], json[index + 3]); + s.Append((char)codePoint); + + // skip 4 chars + index += 4; + } + break; + } + } + + throw new Exception("Unexpectedly reached end of string"); + } + + private uint ParseSingleChar(char c1, uint multipliyer) + { + uint p1 = 0; + if (c1 >= '0' && c1 <= '9') + p1 = (uint)(c1 - '0') * multipliyer; + else if (c1 >= 'A' && c1 <= 'F') + p1 = (uint)((c1 - 'A') + 10) * multipliyer; + else if (c1 >= 'a' && c1 <= 'f') + p1 = (uint)((c1 - 'a') + 10) * multipliyer; + return p1; + } + + private uint ParseUnicode(char c1, char c2, char c3, char c4) + { + uint p1 = ParseSingleChar(c1, 0x1000); + uint p2 = ParseSingleChar(c2, 0x100); + uint p3 = ParseSingleChar(c3, 0x10); + uint p4 = ParseSingleChar(c4, 1); + + return p1 + p2 + p3 + p4; + } + + private string ParseNumber() + { + ConsumeToken(); + + // Need to start back one place because the first digit is also a token and would have been consumed + var startIndex = index - 1; + + do + { + var c = json[index]; + + if ((c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E') + { + if (++index == json.Length) throw new Exception("Unexpected end of string whilst parsing number"); + continue; + } + + break; + } while (true); + + return new string(json, startIndex, index - startIndex); + } + + private Token LookAhead() + { + if (lookAheadToken != Token.None) return lookAheadToken; + + return lookAheadToken = NextTokenCore(); + } + + private void ConsumeToken() + { + lookAheadToken = Token.None; + } + + private Token NextToken() + { + var result = lookAheadToken != Token.None ? lookAheadToken : NextTokenCore(); + + lookAheadToken = Token.None; + + return result; + } + + private Token NextTokenCore() + { + char c; + + // Skip past whitespace + do + { + c = json[index]; + + if (c > ' ') break; + if (c != ' ' && c != '\t' && c != '\n' && c != '\r') break; + + } while (++index < json.Length); + + if (index == json.Length) + { + throw new Exception("Reached end of string unexpectedly"); + } + + c = json[index]; + + index++; + + //if (c >= '0' && c <= '9') + // return Token.Number; + + switch (c) + { + case '{': + return Token.Curly_Open; + + case '}': + return Token.Curly_Close; + + case '[': + return Token.Squared_Open; + + case ']': + return Token.Squared_Close; + + case ',': + return Token.Comma; + + case '"': + return Token.String; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case '-': case '+': case '.': + return Token.Number; + + case ':': + return Token.Colon; + + case 'f': + if (json.Length - index >= 4 && + json[index + 0] == 'a' && + json[index + 1] == 'l' && + json[index + 2] == 's' && + json[index + 3] == 'e') + { + index += 4; + return Token.False; + } + break; + + case 't': + if (json.Length - index >= 3 && + json[index + 0] == 'r' && + json[index + 1] == 'u' && + json[index + 2] == 'e') + { + index += 3; + return Token.True; + } + break; + + case 'n': + if (json.Length - index >= 3 && + json[index + 0] == 'u' && + json[index + 1] == 'l' && + json[index + 2] == 'l') + { + index += 3; + return Token.Null; + } + break; + + } + + throw new Exception("Could not find token at index " + --index); + } + } +} diff --git a/Triangle.NET/TestApp/Program.cs b/src/Triangle.Viewer/Program.cs similarity index 95% rename from Triangle.NET/TestApp/Program.cs rename to src/Triangle.Viewer/Program.cs index 928c59e..b01de2e 100644 --- a/Triangle.NET/TestApp/Program.cs +++ b/src/Triangle.Viewer/Program.cs @@ -1,21 +1,21 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows.Forms; - -namespace MeshExplorer -{ - static class Program - { - /// - /// The main entry point for the application. - /// - [STAThread] - static void Main() - { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new FormMain()); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Forms; + +namespace MeshExplorer +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new FormMain()); + } + } +} diff --git a/Triangle.NET/TestApp/Settings.cs b/src/Triangle.Viewer/Settings.cs similarity index 97% rename from Triangle.NET/TestApp/Settings.cs rename to src/Triangle.Viewer/Settings.cs index e21d9b0..04f59af 100644 --- a/Triangle.NET/TestApp/Settings.cs +++ b/src/Triangle.Viewer/Settings.cs @@ -1,78 +1,78 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.IO; - using System.Windows.Forms; - - /// - /// Stores some of the data used in the main application. - /// - public class Settings - { - // String resources - public static string ImportString = "The selected file has associated mesh information. " + - "You can choose to import the mesh or just read the geometry."; - - public static string VoronoiString = "Make sure you use the \"Confoming Delaunay\" option " + - "when building the Voronoi diagram from a constrained mesh."; - - // Open file dialog - public string OfdDirectory { get; set; } - public string OfdFilter { get; set; } - public int OfdFilterIndex{ get; set; } - - // Save file dialog - public string SfdDirectory { get; set; } - public string SfdFilter { get; set; } - public int SfdFilterIndex { get; set; } - - public string CurrentFile { get; set; } - - public bool RefineMode { get; set; } - public bool ExceptionThrown { get; set; } - - public Settings() - { - if (Directory.Exists(@"..\..\..\Data\")) - { - OfdDirectory = Path.GetFullPath(@"..\..\..\Data\"); - } - else if (Directory.Exists(@"Data\")) - { - OfdDirectory = Path.GetFullPath(@"Data\"); - } - else - { - //System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase; - OfdDirectory = Application.StartupPath; - } - - SfdDirectory = OfdDirectory; - SfdFilter = "Triangle file (*.node;*.poly)|*.node;*.poly"; - SfdFilter += "|Triangle.NET JSON (*.json)|*.json"; - SfdFilterIndex = 1; - - OfdFilter = SfdFilter; - //OfdFilter += "|Polygon data (*.dat)|*.dat"; - //OfdFilter += "|COMSOL mesh (*.mphtxt)|*.mphtxt"; - //OfdFilter += "|AVS UCD data (*.ucd)|*.ucd"; - //OfdFilter += "|VTK data (*.vtk)|*.vtk"; - - OfdFilterIndex = 0; - - CurrentFile = ""; - - RefineMode = false; - ExceptionThrown = false; - } - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.IO; + using System.Windows.Forms; + + /// + /// Stores some of the data used in the main application. + /// + public class Settings + { + // String resources + public static string ImportString = "The selected file has associated mesh information. " + + "You can choose to import the mesh or just read the geometry."; + + public static string VoronoiString = "Make sure you use the \"Confoming Delaunay\" option " + + "when building the Voronoi diagram from a constrained mesh."; + + // Open file dialog + public string OfdDirectory { get; set; } + public string OfdFilter { get; set; } + public int OfdFilterIndex{ get; set; } + + // Save file dialog + public string SfdDirectory { get; set; } + public string SfdFilter { get; set; } + public int SfdFilterIndex { get; set; } + + public string CurrentFile { get; set; } + + public bool RefineMode { get; set; } + public bool ExceptionThrown { get; set; } + + public Settings() + { + if (Directory.Exists(@"..\..\..\Data\")) + { + OfdDirectory = Path.GetFullPath(@"..\..\..\Data\"); + } + else if (Directory.Exists(@"Data\")) + { + OfdDirectory = Path.GetFullPath(@"Data\"); + } + else + { + //System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase; + OfdDirectory = Application.StartupPath; + } + + SfdDirectory = OfdDirectory; + SfdFilter = "Triangle file (*.node;*.poly)|*.node;*.poly"; + SfdFilter += "|Triangle.NET JSON (*.json)|*.json"; + SfdFilterIndex = 1; + + OfdFilter = SfdFilter; + //OfdFilter += "|Polygon data (*.dat)|*.dat"; + //OfdFilter += "|COMSOL mesh (*.mphtxt)|*.mphtxt"; + //OfdFilter += "|AVS UCD data (*.ucd)|*.ucd"; + //OfdFilter += "|VTK data (*.vtk)|*.vtk"; + + OfdFilterIndex = 0; + + CurrentFile = ""; + + RefineMode = false; + ExceptionThrown = false; + } + } +} diff --git a/Triangle.NET/TestApp/Topology/TopologyControlView.Designer.cs b/src/Triangle.Viewer/Topology/TopologyControlView.Designer.cs similarity index 97% rename from Triangle.NET/TestApp/Topology/TopologyControlView.Designer.cs rename to src/Triangle.Viewer/Topology/TopologyControlView.Designer.cs index bdb22e1..128d58b 100644 --- a/Triangle.NET/TestApp/Topology/TopologyControlView.Designer.cs +++ b/src/Triangle.Viewer/Topology/TopologyControlView.Designer.cs @@ -1,359 +1,359 @@ -namespace MeshExplorer.Topology -{ - partial class TopologyControlView - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Component Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.lbS2 = new System.Windows.Forms.Label(); - this.lbS1 = new System.Windows.Forms.Label(); - this.lbS0 = new System.Windows.Forms.Label(); - this.label6 = new System.Windows.Forms.Label(); - this.lbN2 = new System.Windows.Forms.Label(); - this.lbN1 = new System.Windows.Forms.Label(); - this.lbN0 = new System.Windows.Forms.Label(); - this.label5 = new System.Windows.Forms.Label(); - this.lbPosition = new System.Windows.Forms.Label(); - this.lbV2 = new System.Windows.Forms.Label(); - this.lbV1 = new System.Windows.Forms.Label(); - this.lbV0 = new System.Windows.Forms.Label(); - this.label4 = new System.Windows.Forms.Label(); - this.label1 = new System.Windows.Forms.Label(); - this.label2 = new System.Windows.Forms.Label(); - this.lbTriangle = new System.Windows.Forms.Label(); - this.darkButton9 = new MeshExplorer.Controls.DarkButton(); - this.darkButton7 = new MeshExplorer.Controls.DarkButton(); - this.darkButton5 = new MeshExplorer.Controls.DarkButton(); - this.darkButton3 = new MeshExplorer.Controls.DarkButton(); - this.darkButton8 = new MeshExplorer.Controls.DarkButton(); - this.darkButton6 = new MeshExplorer.Controls.DarkButton(); - this.darkButton4 = new MeshExplorer.Controls.DarkButton(); - this.darkButton2 = new MeshExplorer.Controls.DarkButton(); - this.darkButton1 = new MeshExplorer.Controls.DarkButton(); - this.SuspendLayout(); - // - // lbS2 - // - this.lbS2.Location = new System.Drawing.Point(137, 122); - this.lbS2.Name = "lbS2"; - this.lbS2.Size = new System.Drawing.Size(53, 13); - this.lbS2.TabIndex = 1; - this.lbS2.Text = "-"; - this.lbS2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbS1 - // - this.lbS1.Location = new System.Drawing.Point(137, 107); - this.lbS1.Name = "lbS1"; - this.lbS1.Size = new System.Drawing.Size(53, 13); - this.lbS1.TabIndex = 1; - this.lbS1.Text = "-"; - this.lbS1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbS0 - // - this.lbS0.Location = new System.Drawing.Point(137, 92); - this.lbS0.Name = "lbS0"; - this.lbS0.Size = new System.Drawing.Size(53, 13); - this.lbS0.TabIndex = 1; - this.lbS0.Text = "-"; - this.lbS0.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // label6 - // - this.label6.ImageAlign = System.Drawing.ContentAlignment.MiddleRight; - this.label6.Location = new System.Drawing.Point(134, 71); - this.label6.Name = "label6"; - this.label6.Size = new System.Drawing.Size(57, 13); - this.label6.TabIndex = 1; - this.label6.Text = "Segments"; - // - // lbN2 - // - this.lbN2.Location = new System.Drawing.Point(66, 122); - this.lbN2.Name = "lbN2"; - this.lbN2.Size = new System.Drawing.Size(57, 13); - this.lbN2.TabIndex = 1; - this.lbN2.Text = "-"; - this.lbN2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbN1 - // - this.lbN1.Location = new System.Drawing.Point(66, 107); - this.lbN1.Name = "lbN1"; - this.lbN1.Size = new System.Drawing.Size(57, 13); - this.lbN1.TabIndex = 1; - this.lbN1.Text = "-"; - this.lbN1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbN0 - // - this.lbN0.Location = new System.Drawing.Point(66, 92); - this.lbN0.Name = "lbN0"; - this.lbN0.Size = new System.Drawing.Size(57, 13); - this.lbN0.TabIndex = 1; - this.lbN0.Text = "-"; - this.lbN0.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // label5 - // - this.label5.ImageAlign = System.Drawing.ContentAlignment.MiddleRight; - this.label5.Location = new System.Drawing.Point(63, 71); - this.label5.Name = "label5"; - this.label5.Size = new System.Drawing.Size(61, 13); - this.label5.TabIndex = 1; - this.label5.Text = "Neighbors"; - // - // lbPosition - // - this.lbPosition.AutoSize = true; - this.lbPosition.Location = new System.Drawing.Point(70, 15); - this.lbPosition.Name = "lbPosition"; - this.lbPosition.Size = new System.Drawing.Size(11, 13); - this.lbPosition.TabIndex = 1; - this.lbPosition.Text = "-"; - // - // lbV2 - // - this.lbV2.Location = new System.Drawing.Point(16, 122); - this.lbV2.Name = "lbV2"; - this.lbV2.Size = new System.Drawing.Size(38, 13); - this.lbV2.TabIndex = 1; - this.lbV2.Text = "-"; - this.lbV2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbV1 - // - this.lbV1.Location = new System.Drawing.Point(16, 107); - this.lbV1.Name = "lbV1"; - this.lbV1.Size = new System.Drawing.Size(38, 13); - this.lbV1.TabIndex = 1; - this.lbV1.Text = "-"; - this.lbV1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbV0 - // - this.lbV0.Location = new System.Drawing.Point(16, 92); - this.lbV0.Name = "lbV0"; - this.lbV0.Size = new System.Drawing.Size(38, 13); - this.lbV0.TabIndex = 1; - this.lbV0.Text = "-"; - this.lbV0.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // label4 - // - this.label4.ImageAlign = System.Drawing.ContentAlignment.MiddleRight; - this.label4.Location = new System.Drawing.Point(8, 71); - this.label4.Name = "label4"; - this.label4.Size = new System.Drawing.Size(47, 13); - this.label4.TabIndex = 1; - this.label4.Text = "Vertices"; - // - // label1 - // - this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(6, 15); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(52, 13); - this.label1.TabIndex = 1; - this.label1.Text = "Position:"; - // - // label2 - // - this.label2.AutoSize = true; - this.label2.Location = new System.Drawing.Point(7, 37); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(51, 13); - this.label2.TabIndex = 1; - this.label2.Text = "Triangle:"; - // - // lbTriangle - // - this.lbTriangle.AutoSize = true; - this.lbTriangle.Location = new System.Drawing.Point(70, 37); - this.lbTriangle.Name = "lbTriangle"; - this.lbTriangle.Size = new System.Drawing.Size(11, 13); - this.lbTriangle.TabIndex = 1; - this.lbTriangle.Text = "-"; - // - // darkButton9 - // - this.darkButton9.Location = new System.Drawing.Point(99, 367); - this.darkButton9.Name = "darkButton9"; - this.darkButton9.Size = new System.Drawing.Size(78, 23); - this.darkButton9.TabIndex = 10; - this.darkButton9.Text = "Rprev"; - this.darkButton9.UseVisualStyleBackColor = true; - this.darkButton9.Click += new System.EventHandler(this.btnPrimitive_Click); - // - // darkButton7 - // - this.darkButton7.Location = new System.Drawing.Point(99, 338); - this.darkButton7.Name = "darkButton7"; - this.darkButton7.Size = new System.Drawing.Size(78, 23); - this.darkButton7.TabIndex = 8; - this.darkButton7.Text = "Dprev"; - this.darkButton7.UseVisualStyleBackColor = true; - this.darkButton7.Click += new System.EventHandler(this.btnPrimitive_Click); - // - // darkButton5 - // - this.darkButton5.Location = new System.Drawing.Point(99, 309); - this.darkButton5.Name = "darkButton5"; - this.darkButton5.Size = new System.Drawing.Size(78, 23); - this.darkButton5.TabIndex = 6; - this.darkButton5.Text = "Oprev"; - this.darkButton5.UseVisualStyleBackColor = true; - this.darkButton5.Click += new System.EventHandler(this.btnPrimitive_Click); - // - // darkButton3 - // - this.darkButton3.Location = new System.Drawing.Point(99, 280); - this.darkButton3.Name = "darkButton3"; - this.darkButton3.Size = new System.Drawing.Size(78, 23); - this.darkButton3.TabIndex = 4; - this.darkButton3.Text = "Lprev"; - this.darkButton3.UseVisualStyleBackColor = true; - this.darkButton3.Click += new System.EventHandler(this.btnPrimitive_Click); - // - // darkButton8 - // - this.darkButton8.Location = new System.Drawing.Point(11, 367); - this.darkButton8.Name = "darkButton8"; - this.darkButton8.Size = new System.Drawing.Size(78, 23); - this.darkButton8.TabIndex = 9; - this.darkButton8.Text = "Rnext"; - this.darkButton8.UseVisualStyleBackColor = true; - this.darkButton8.Click += new System.EventHandler(this.btnPrimitive_Click); - // - // darkButton6 - // - this.darkButton6.Location = new System.Drawing.Point(11, 338); - this.darkButton6.Name = "darkButton6"; - this.darkButton6.Size = new System.Drawing.Size(78, 23); - this.darkButton6.TabIndex = 7; - this.darkButton6.Text = "Dnext"; - this.darkButton6.UseVisualStyleBackColor = true; - this.darkButton6.Click += new System.EventHandler(this.btnPrimitive_Click); - // - // darkButton4 - // - this.darkButton4.Location = new System.Drawing.Point(11, 309); - this.darkButton4.Name = "darkButton4"; - this.darkButton4.Size = new System.Drawing.Size(78, 23); - this.darkButton4.TabIndex = 5; - this.darkButton4.Text = "Onext"; - this.darkButton4.UseVisualStyleBackColor = true; - this.darkButton4.Click += new System.EventHandler(this.btnPrimitive_Click); - // - // darkButton2 - // - this.darkButton2.Location = new System.Drawing.Point(11, 280); - this.darkButton2.Name = "darkButton2"; - this.darkButton2.Size = new System.Drawing.Size(78, 23); - this.darkButton2.TabIndex = 3; - this.darkButton2.Text = "Lnext"; - this.darkButton2.UseVisualStyleBackColor = true; - this.darkButton2.Click += new System.EventHandler(this.btnPrimitive_Click); - // - // darkButton1 - // - this.darkButton1.Location = new System.Drawing.Point(11, 251); - this.darkButton1.Name = "darkButton1"; - this.darkButton1.Size = new System.Drawing.Size(166, 23); - this.darkButton1.TabIndex = 2; - this.darkButton1.Text = "Sym"; - this.darkButton1.UseVisualStyleBackColor = true; - this.darkButton1.Click += new System.EventHandler(this.btnPrimitive_Click); - // - // TopologyControlView - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76))))); - this.Controls.Add(this.darkButton9); - this.Controls.Add(this.darkButton7); - this.Controls.Add(this.darkButton5); - this.Controls.Add(this.darkButton3); - this.Controls.Add(this.darkButton8); - this.Controls.Add(this.darkButton6); - this.Controls.Add(this.darkButton4); - this.Controls.Add(this.darkButton2); - this.Controls.Add(this.darkButton1); - this.Controls.Add(this.lbS2); - this.Controls.Add(this.lbS1); - this.Controls.Add(this.lbS0); - this.Controls.Add(this.label6); - this.Controls.Add(this.lbN2); - this.Controls.Add(this.lbN1); - this.Controls.Add(this.lbN0); - this.Controls.Add(this.label5); - this.Controls.Add(this.lbTriangle); - this.Controls.Add(this.lbPosition); - this.Controls.Add(this.lbV2); - this.Controls.Add(this.lbV1); - this.Controls.Add(this.lbV0); - this.Controls.Add(this.label4); - this.Controls.Add(this.label2); - this.Controls.Add(this.label1); - this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ForeColor = System.Drawing.Color.White; - this.Name = "TopologyControlView"; - this.Size = new System.Drawing.Size(195, 439); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.Label lbS2; - private System.Windows.Forms.Label lbS1; - private System.Windows.Forms.Label lbS0; - private System.Windows.Forms.Label label6; - private System.Windows.Forms.Label lbN2; - private System.Windows.Forms.Label lbN1; - private System.Windows.Forms.Label lbN0; - private System.Windows.Forms.Label label5; - private System.Windows.Forms.Label lbPosition; - private System.Windows.Forms.Label lbV2; - private System.Windows.Forms.Label lbV1; - private System.Windows.Forms.Label lbV0; - private System.Windows.Forms.Label label4; - private System.Windows.Forms.Label label1; - private System.Windows.Forms.Label label2; - private System.Windows.Forms.Label lbTriangle; - private Controls.DarkButton darkButton9; - private Controls.DarkButton darkButton7; - private Controls.DarkButton darkButton5; - private Controls.DarkButton darkButton3; - private Controls.DarkButton darkButton8; - private Controls.DarkButton darkButton6; - private Controls.DarkButton darkButton4; - private Controls.DarkButton darkButton2; - private Controls.DarkButton darkButton1; - } -} +namespace MeshExplorer.Topology +{ + partial class TopologyControlView + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.lbS2 = new System.Windows.Forms.Label(); + this.lbS1 = new System.Windows.Forms.Label(); + this.lbS0 = new System.Windows.Forms.Label(); + this.label6 = new System.Windows.Forms.Label(); + this.lbN2 = new System.Windows.Forms.Label(); + this.lbN1 = new System.Windows.Forms.Label(); + this.lbN0 = new System.Windows.Forms.Label(); + this.label5 = new System.Windows.Forms.Label(); + this.lbPosition = new System.Windows.Forms.Label(); + this.lbV2 = new System.Windows.Forms.Label(); + this.lbV1 = new System.Windows.Forms.Label(); + this.lbV0 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.lbTriangle = new System.Windows.Forms.Label(); + this.darkButton9 = new MeshExplorer.Controls.DarkButton(); + this.darkButton7 = new MeshExplorer.Controls.DarkButton(); + this.darkButton5 = new MeshExplorer.Controls.DarkButton(); + this.darkButton3 = new MeshExplorer.Controls.DarkButton(); + this.darkButton8 = new MeshExplorer.Controls.DarkButton(); + this.darkButton6 = new MeshExplorer.Controls.DarkButton(); + this.darkButton4 = new MeshExplorer.Controls.DarkButton(); + this.darkButton2 = new MeshExplorer.Controls.DarkButton(); + this.darkButton1 = new MeshExplorer.Controls.DarkButton(); + this.SuspendLayout(); + // + // lbS2 + // + this.lbS2.Location = new System.Drawing.Point(137, 122); + this.lbS2.Name = "lbS2"; + this.lbS2.Size = new System.Drawing.Size(53, 13); + this.lbS2.TabIndex = 1; + this.lbS2.Text = "-"; + this.lbS2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbS1 + // + this.lbS1.Location = new System.Drawing.Point(137, 107); + this.lbS1.Name = "lbS1"; + this.lbS1.Size = new System.Drawing.Size(53, 13); + this.lbS1.TabIndex = 1; + this.lbS1.Text = "-"; + this.lbS1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbS0 + // + this.lbS0.Location = new System.Drawing.Point(137, 92); + this.lbS0.Name = "lbS0"; + this.lbS0.Size = new System.Drawing.Size(53, 13); + this.lbS0.TabIndex = 1; + this.lbS0.Text = "-"; + this.lbS0.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // label6 + // + this.label6.ImageAlign = System.Drawing.ContentAlignment.MiddleRight; + this.label6.Location = new System.Drawing.Point(134, 71); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(57, 13); + this.label6.TabIndex = 1; + this.label6.Text = "Segments"; + // + // lbN2 + // + this.lbN2.Location = new System.Drawing.Point(66, 122); + this.lbN2.Name = "lbN2"; + this.lbN2.Size = new System.Drawing.Size(57, 13); + this.lbN2.TabIndex = 1; + this.lbN2.Text = "-"; + this.lbN2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbN1 + // + this.lbN1.Location = new System.Drawing.Point(66, 107); + this.lbN1.Name = "lbN1"; + this.lbN1.Size = new System.Drawing.Size(57, 13); + this.lbN1.TabIndex = 1; + this.lbN1.Text = "-"; + this.lbN1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbN0 + // + this.lbN0.Location = new System.Drawing.Point(66, 92); + this.lbN0.Name = "lbN0"; + this.lbN0.Size = new System.Drawing.Size(57, 13); + this.lbN0.TabIndex = 1; + this.lbN0.Text = "-"; + this.lbN0.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // label5 + // + this.label5.ImageAlign = System.Drawing.ContentAlignment.MiddleRight; + this.label5.Location = new System.Drawing.Point(63, 71); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(61, 13); + this.label5.TabIndex = 1; + this.label5.Text = "Neighbors"; + // + // lbPosition + // + this.lbPosition.AutoSize = true; + this.lbPosition.Location = new System.Drawing.Point(70, 15); + this.lbPosition.Name = "lbPosition"; + this.lbPosition.Size = new System.Drawing.Size(11, 13); + this.lbPosition.TabIndex = 1; + this.lbPosition.Text = "-"; + // + // lbV2 + // + this.lbV2.Location = new System.Drawing.Point(16, 122); + this.lbV2.Name = "lbV2"; + this.lbV2.Size = new System.Drawing.Size(38, 13); + this.lbV2.TabIndex = 1; + this.lbV2.Text = "-"; + this.lbV2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbV1 + // + this.lbV1.Location = new System.Drawing.Point(16, 107); + this.lbV1.Name = "lbV1"; + this.lbV1.Size = new System.Drawing.Size(38, 13); + this.lbV1.TabIndex = 1; + this.lbV1.Text = "-"; + this.lbV1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbV0 + // + this.lbV0.Location = new System.Drawing.Point(16, 92); + this.lbV0.Name = "lbV0"; + this.lbV0.Size = new System.Drawing.Size(38, 13); + this.lbV0.TabIndex = 1; + this.lbV0.Text = "-"; + this.lbV0.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // label4 + // + this.label4.ImageAlign = System.Drawing.ContentAlignment.MiddleRight; + this.label4.Location = new System.Drawing.Point(8, 71); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(47, 13); + this.label4.TabIndex = 1; + this.label4.Text = "Vertices"; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(6, 15); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(52, 13); + this.label1.TabIndex = 1; + this.label1.Text = "Position:"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(7, 37); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(51, 13); + this.label2.TabIndex = 1; + this.label2.Text = "Triangle:"; + // + // lbTriangle + // + this.lbTriangle.AutoSize = true; + this.lbTriangle.Location = new System.Drawing.Point(70, 37); + this.lbTriangle.Name = "lbTriangle"; + this.lbTriangle.Size = new System.Drawing.Size(11, 13); + this.lbTriangle.TabIndex = 1; + this.lbTriangle.Text = "-"; + // + // darkButton9 + // + this.darkButton9.Location = new System.Drawing.Point(99, 367); + this.darkButton9.Name = "darkButton9"; + this.darkButton9.Size = new System.Drawing.Size(78, 23); + this.darkButton9.TabIndex = 10; + this.darkButton9.Text = "Rprev"; + this.darkButton9.UseVisualStyleBackColor = true; + this.darkButton9.Click += new System.EventHandler(this.btnPrimitive_Click); + // + // darkButton7 + // + this.darkButton7.Location = new System.Drawing.Point(99, 338); + this.darkButton7.Name = "darkButton7"; + this.darkButton7.Size = new System.Drawing.Size(78, 23); + this.darkButton7.TabIndex = 8; + this.darkButton7.Text = "Dprev"; + this.darkButton7.UseVisualStyleBackColor = true; + this.darkButton7.Click += new System.EventHandler(this.btnPrimitive_Click); + // + // darkButton5 + // + this.darkButton5.Location = new System.Drawing.Point(99, 309); + this.darkButton5.Name = "darkButton5"; + this.darkButton5.Size = new System.Drawing.Size(78, 23); + this.darkButton5.TabIndex = 6; + this.darkButton5.Text = "Oprev"; + this.darkButton5.UseVisualStyleBackColor = true; + this.darkButton5.Click += new System.EventHandler(this.btnPrimitive_Click); + // + // darkButton3 + // + this.darkButton3.Location = new System.Drawing.Point(99, 280); + this.darkButton3.Name = "darkButton3"; + this.darkButton3.Size = new System.Drawing.Size(78, 23); + this.darkButton3.TabIndex = 4; + this.darkButton3.Text = "Lprev"; + this.darkButton3.UseVisualStyleBackColor = true; + this.darkButton3.Click += new System.EventHandler(this.btnPrimitive_Click); + // + // darkButton8 + // + this.darkButton8.Location = new System.Drawing.Point(11, 367); + this.darkButton8.Name = "darkButton8"; + this.darkButton8.Size = new System.Drawing.Size(78, 23); + this.darkButton8.TabIndex = 9; + this.darkButton8.Text = "Rnext"; + this.darkButton8.UseVisualStyleBackColor = true; + this.darkButton8.Click += new System.EventHandler(this.btnPrimitive_Click); + // + // darkButton6 + // + this.darkButton6.Location = new System.Drawing.Point(11, 338); + this.darkButton6.Name = "darkButton6"; + this.darkButton6.Size = new System.Drawing.Size(78, 23); + this.darkButton6.TabIndex = 7; + this.darkButton6.Text = "Dnext"; + this.darkButton6.UseVisualStyleBackColor = true; + this.darkButton6.Click += new System.EventHandler(this.btnPrimitive_Click); + // + // darkButton4 + // + this.darkButton4.Location = new System.Drawing.Point(11, 309); + this.darkButton4.Name = "darkButton4"; + this.darkButton4.Size = new System.Drawing.Size(78, 23); + this.darkButton4.TabIndex = 5; + this.darkButton4.Text = "Onext"; + this.darkButton4.UseVisualStyleBackColor = true; + this.darkButton4.Click += new System.EventHandler(this.btnPrimitive_Click); + // + // darkButton2 + // + this.darkButton2.Location = new System.Drawing.Point(11, 280); + this.darkButton2.Name = "darkButton2"; + this.darkButton2.Size = new System.Drawing.Size(78, 23); + this.darkButton2.TabIndex = 3; + this.darkButton2.Text = "Lnext"; + this.darkButton2.UseVisualStyleBackColor = true; + this.darkButton2.Click += new System.EventHandler(this.btnPrimitive_Click); + // + // darkButton1 + // + this.darkButton1.Location = new System.Drawing.Point(11, 251); + this.darkButton1.Name = "darkButton1"; + this.darkButton1.Size = new System.Drawing.Size(166, 23); + this.darkButton1.TabIndex = 2; + this.darkButton1.Text = "Sym"; + this.darkButton1.UseVisualStyleBackColor = true; + this.darkButton1.Click += new System.EventHandler(this.btnPrimitive_Click); + // + // TopologyControlView + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76))))); + this.Controls.Add(this.darkButton9); + this.Controls.Add(this.darkButton7); + this.Controls.Add(this.darkButton5); + this.Controls.Add(this.darkButton3); + this.Controls.Add(this.darkButton8); + this.Controls.Add(this.darkButton6); + this.Controls.Add(this.darkButton4); + this.Controls.Add(this.darkButton2); + this.Controls.Add(this.darkButton1); + this.Controls.Add(this.lbS2); + this.Controls.Add(this.lbS1); + this.Controls.Add(this.lbS0); + this.Controls.Add(this.label6); + this.Controls.Add(this.lbN2); + this.Controls.Add(this.lbN1); + this.Controls.Add(this.lbN0); + this.Controls.Add(this.label5); + this.Controls.Add(this.lbTriangle); + this.Controls.Add(this.lbPosition); + this.Controls.Add(this.lbV2); + this.Controls.Add(this.lbV1); + this.Controls.Add(this.lbV0); + this.Controls.Add(this.label4); + this.Controls.Add(this.label2); + this.Controls.Add(this.label1); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ForeColor = System.Drawing.Color.White; + this.Name = "TopologyControlView"; + this.Size = new System.Drawing.Size(195, 439); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label lbS2; + private System.Windows.Forms.Label lbS1; + private System.Windows.Forms.Label lbS0; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.Label lbN2; + private System.Windows.Forms.Label lbN1; + private System.Windows.Forms.Label lbN0; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.Label lbPosition; + private System.Windows.Forms.Label lbV2; + private System.Windows.Forms.Label lbV1; + private System.Windows.Forms.Label lbV0; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label lbTriangle; + private Controls.DarkButton darkButton9; + private Controls.DarkButton darkButton7; + private Controls.DarkButton darkButton5; + private Controls.DarkButton darkButton3; + private Controls.DarkButton darkButton8; + private Controls.DarkButton darkButton6; + private Controls.DarkButton darkButton4; + private Controls.DarkButton darkButton2; + private Controls.DarkButton darkButton1; + } +} diff --git a/Triangle.NET/TestApp/Topology/TopologyControlView.cs b/src/Triangle.Viewer/Topology/TopologyControlView.cs similarity index 91% rename from Triangle.NET/TestApp/Topology/TopologyControlView.cs rename to src/Triangle.Viewer/Topology/TopologyControlView.cs index 69563e3..351684e 100644 --- a/Triangle.NET/TestApp/Topology/TopologyControlView.cs +++ b/src/Triangle.Viewer/Topology/TopologyControlView.cs @@ -1,83 +1,83 @@ -using System; -using System.Drawing; -using System.Globalization; -using System.Windows.Forms; -using TriangleNet.Geometry; - -namespace MeshExplorer.Topology -{ - public partial class TopologyControlView : UserControl - { - public event EventHandler> PrimitiveCommandInvoked; - - public TopologyControlView() - { - InitializeComponent(); - } - - public void SetPosition(PointF p) - { - var nfi = NumberFormatInfo.InvariantInfo; - - lbPosition.Text = String.Format(nfi, "X: {0:0.0}, Y: {1:0.0}", p.X, p.Y); - } - - public void SetTriangle(ITriangle tri) - { - if (tri != null) - { - lbTriangle.Text = tri.ID.ToString(); - - lbV0.Text = tri.GetVertexID(0).ToString(); - lbV1.Text = tri.GetVertexID(1).ToString(); - lbV2.Text = tri.GetVertexID(2).ToString(); - - lbN0.Text = tri.GetNeighborID(0).ToString(); - lbN1.Text = tri.GetNeighborID(1).ToString(); - lbN2.Text = tri.GetNeighborID(2).ToString(); - - lbS0.Text = GetSegmentString(tri.GetSegment(0)); - lbS1.Text = GetSegmentString(tri.GetSegment(1)); - lbS2.Text = GetSegmentString(tri.GetSegment(2)); - } - else - { - lbTriangle.Text = "-"; - - lbV0.Text = "-"; - lbV1.Text = "-"; - lbV2.Text = "-"; - - lbN0.Text = "-"; - lbN1.Text = "-"; - lbN2.Text = "-"; - - lbS0.Text = "-"; - lbS1.Text = "-"; - lbS2.Text = "-"; - } - } - - private string GetSegmentString(ISegment seg) - { - return seg == null ? "-" : "[" + seg.P0 + " - " + seg.P1 + "]"; - } - - private void btnPrimitive_Click(object sender, EventArgs e) - { - var button = sender as Button; - - if (button != null) - { - var name = button.Text.ToLowerInvariant(); - - var handler = PrimitiveCommandInvoked; - - if (handler != null) - { - handler(this, new GenericEventArgs(name)); - } - } - } - } -} +using System; +using System.Drawing; +using System.Globalization; +using System.Windows.Forms; +using TriangleNet.Geometry; + +namespace MeshExplorer.Topology +{ + public partial class TopologyControlView : UserControl + { + public event EventHandler> PrimitiveCommandInvoked; + + public TopologyControlView() + { + InitializeComponent(); + } + + public void SetPosition(double x, double y) + { + var nfi = NumberFormatInfo.InvariantInfo; + + lbPosition.Text = string.Format(nfi, "X: {0:0.0}, Y: {1:0.0}", x, y); + } + + public void SetTriangle(ITriangle tri) + { + if (tri != null) + { + lbTriangle.Text = tri.ID.ToString(); + + lbV0.Text = tri.GetVertexID(0).ToString(); + lbV1.Text = tri.GetVertexID(1).ToString(); + lbV2.Text = tri.GetVertexID(2).ToString(); + + lbN0.Text = tri.GetNeighborID(0).ToString(); + lbN1.Text = tri.GetNeighborID(1).ToString(); + lbN2.Text = tri.GetNeighborID(2).ToString(); + + lbS0.Text = GetSegmentString(tri.GetSegment(0)); + lbS1.Text = GetSegmentString(tri.GetSegment(1)); + lbS2.Text = GetSegmentString(tri.GetSegment(2)); + } + else + { + lbTriangle.Text = "-"; + + lbV0.Text = "-"; + lbV1.Text = "-"; + lbV2.Text = "-"; + + lbN0.Text = "-"; + lbN1.Text = "-"; + lbN2.Text = "-"; + + lbS0.Text = "-"; + lbS1.Text = "-"; + lbS2.Text = "-"; + } + } + + private string GetSegmentString(ISegment seg) + { + return seg == null ? "-" : "[" + seg.P0 + " - " + seg.P1 + "]"; + } + + private void btnPrimitive_Click(object sender, EventArgs e) + { + var button = sender as Button; + + if (button != null) + { + var name = button.Text.ToLowerInvariant(); + + var handler = PrimitiveCommandInvoked; + + if (handler != null) + { + handler(this, new GenericEventArgs(name)); + } + } + } + } +} diff --git a/Triangle.NET/TestApp/Topology/TopologyControlView.resx b/src/Triangle.Viewer/Topology/TopologyControlView.resx similarity index 97% rename from Triangle.NET/TestApp/Topology/TopologyControlView.resx rename to src/Triangle.Viewer/Topology/TopologyControlView.resx index 29dcb1b..1af7de1 100644 --- a/Triangle.NET/TestApp/Topology/TopologyControlView.resx +++ b/src/Triangle.Viewer/Topology/TopologyControlView.resx @@ -1,120 +1,120 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + \ No newline at end of file diff --git a/Triangle.NET/TestApp/Topology/TopologyRenderControl.cs b/src/Triangle.Viewer/Topology/TopologyRenderControl.cs similarity index 88% rename from Triangle.NET/TestApp/Topology/TopologyRenderControl.cs rename to src/Triangle.Viewer/Topology/TopologyRenderControl.cs index 13dc147..d8090c9 100644 --- a/Triangle.NET/TestApp/Topology/TopologyRenderControl.cs +++ b/src/Triangle.Viewer/Topology/TopologyRenderControl.cs @@ -1,149 +1,145 @@ - -namespace MeshExplorer.Topology -{ - using System.Drawing; - using System.Drawing.Drawing2D; - using System.Drawing.Text; - using System.Windows.Forms; - using TriangleNet; - using TriangleNet.Rendering; - using TriangleNet.Topology; - - public class TopologyRenderControl : Control - { - // Rendering stuff - private BufferedGraphics buffer; - private BufferedGraphicsContext context; - - Projection zoom; - TopologyRenderer renderer; - - bool initialized = false; - - public Projection Zoom - { - get { return zoom; } - } - - /// - /// Initializes a new instance of the class. - /// - public TopologyRenderControl() - { - SetStyle(ControlStyles.ResizeRedraw, true); - - this.BackColor = Color.Black; - - context = new BufferedGraphicsContext(); - } - - /// - /// Initialize the graphics buffer (should be called in the forms load event). - /// - public void Initialize(Mesh mesh) - { - renderer = new TopologyRenderer(mesh); - - zoom = new Projection(this.ClientRectangle); - //zoom.ClipMargin = 10.0f; - - var b = mesh.Bounds; - zoom.Initialize(new BoundingBox((float)b.Left, (float)b.Right, - (float)b.Bottom, (float)b.Top)); - - InitializeBuffer(); - - initialized = true; - - this.Render(); - } - - public void Update(Otri otri) - { - if (otri.Triangle == null || otri.Triangle.ID < 0) - { - renderer.SelectTriangle(null, null, null); - } - else - { - renderer.SelectTriangle(otri.Triangle, otri.Org(), otri.Dest()); - } - - this.Render(); - } - - private void InitializeBuffer() - { - if (this.Width > 0 && this.Height > 0) - { - if (buffer != null) - { - if (this.ClientRectangle == buffer.Graphics.VisibleClipBounds) - { - this.Invalidate(); - - // Bounds didn't change. Probably we just restored the window - // from minimized state. - return; - } - - buffer.Dispose(); - } - - buffer = context.Allocate(Graphics.FromHwnd(this.Handle), this.ClientRectangle); - - if (initialized) - { - this.Render(); - } - } - } - - private void Render() - { - if (buffer == null) - { - return; - } - - Graphics g = buffer.Graphics; - g.Clear(this.BackColor); - - if (!initialized || renderer == null) - { - return; - } - - g.SmoothingMode = SmoothingMode.AntiAlias; - g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; - - renderer.Render(g, zoom); - - this.Invalidate(); - } - - #region Control overrides - - protected override void OnPaint(PaintEventArgs pe) - { - if (!initialized) - { - base.OnPaint(pe); - return; - } - - buffer.Render(); - } - - protected override void OnPaintBackground(PaintEventArgs pevent) - { - // Do nothing - if (!initialized) - { - base.OnPaintBackground(pevent); - } - } - - #endregion - } -} + +namespace MeshExplorer.Topology +{ + using System.Drawing; + using System.Drawing.Drawing2D; + using System.Drawing.Text; + using System.Windows.Forms; + using TriangleNet; + using TriangleNet.Rendering; + using TriangleNet.Topology; + + public class TopologyRenderControl : Control + { + // Rendering stuff + private BufferedGraphics buffer; + private BufferedGraphicsContext context; + + Projection zoom; + TopologyRenderer renderer; + + bool initialized = false; + + public Projection Zoom + { + get { return zoom; } + } + + /// + /// Initializes a new instance of the class. + /// + public TopologyRenderControl() + { + SetStyle(ControlStyles.ResizeRedraw, true); + + this.BackColor = Color.Black; + + context = new BufferedGraphicsContext(); + } + + /// + /// Initialize the graphics buffer (should be called in the forms load event). + /// + public void Initialize(Mesh mesh) + { + renderer = new TopologyRenderer(mesh); + + zoom = new Projection(ClientRectangle); + zoom.Initialize(mesh.Bounds); + + InitializeBuffer(); + + initialized = true; + + Render(); + } + + public void Update(Otri otri) + { + if (otri.Triangle == null || otri.Triangle.ID < 0) + { + renderer.SelectTriangle(null, null, null); + } + else + { + renderer.SelectTriangle(otri.Triangle, otri.Org(), otri.Dest()); + } + + this.Render(); + } + + private void InitializeBuffer() + { + if (this.Width > 0 && this.Height > 0) + { + if (buffer != null) + { + if (this.ClientRectangle == buffer.Graphics.VisibleClipBounds) + { + this.Invalidate(); + + // Bounds didn't change. Probably we just restored the window + // from minimized state. + return; + } + + buffer.Dispose(); + } + + buffer = context.Allocate(Graphics.FromHwnd(this.Handle), this.ClientRectangle); + + if (initialized) + { + this.Render(); + } + } + } + + private void Render() + { + if (buffer == null) + { + return; + } + + Graphics g = buffer.Graphics; + g.Clear(this.BackColor); + + if (!initialized || renderer == null) + { + return; + } + + g.SmoothingMode = SmoothingMode.AntiAlias; + g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; + + renderer.Render(g, zoom); + + this.Invalidate(); + } + + #region Control overrides + + protected override void OnPaint(PaintEventArgs pe) + { + if (!initialized) + { + base.OnPaint(pe); + return; + } + + buffer.Render(); + } + + protected override void OnPaintBackground(PaintEventArgs pevent) + { + // Do nothing + if (!initialized) + { + base.OnPaintBackground(pevent); + } + } + + #endregion + } +} diff --git a/Triangle.NET/TestApp/Topology/TopologyRenderer.cs b/src/Triangle.Viewer/Topology/TopologyRenderer.cs similarity index 86% rename from Triangle.NET/TestApp/Topology/TopologyRenderer.cs rename to src/Triangle.Viewer/Topology/TopologyRenderer.cs index ad153b7..cf815b9 100644 --- a/Triangle.NET/TestApp/Topology/TopologyRenderer.cs +++ b/src/Triangle.Viewer/Topology/TopologyRenderer.cs @@ -1,283 +1,290 @@ - -namespace MeshExplorer.Topology -{ - using System; - using System.Drawing; - using TriangleNet; - using TriangleNet.Geometry; - using TriangleNet.Rendering; - - public class TopologyRenderer - { - Projection zoom; - Mesh mesh; - PointF[] points; - - // Colors - Color Background = Color.FromArgb(0, 0, 0); - Brush Point = new SolidBrush(Color.FromArgb(0, 80, 0)); - Brush Triangle = new SolidBrush(Color.FromArgb(50, 50, 50)); - Pen Line = new Pen(Color.FromArgb(30, 30, 30)); - Pen Segment = new Pen(Color.DarkBlue); - - Brush SelectedTriangle = new SolidBrush(Color.FromArgb(50, 0, 0)); - Pen SelectedEdge = new Pen(Color.DarkRed, 2.0f); - - Font font, fontTri; - - ITriangle currentTri; - Vertex currentOrg, currentDest; - - /// - /// Initializes a new instance of the class. - /// - public TopologyRenderer(Mesh mesh) - { - this.mesh = mesh; - - points = new PointF[mesh.Vertices.Count]; - - int k = 0; - - foreach (var v in mesh.Vertices) - { - points[k++] = new PointF((float)v.X, (float)v.Y); - } - - font = new Font("Arial", 7.5f); - fontTri = new Font("Arial", 12f, FontStyle.Bold); - } - - /// - /// Renders the mesh. - /// - public void Render(Graphics g, Projection zoom) - { - this.zoom = zoom; - - if (mesh.Edges != null) - { - this.RenderSelectedTriangle(g); - this.RenderEdges(g); - this.RenderTriangleIds(g); - } - else if (mesh.Triangles != null) - { - this.RenderTriangles(g); - } - - if (mesh.Segments != null) - { - this.RenderSegments(g); - } - - RenderSelectedEdge(g); - - if (mesh.Vertices != null) - { - this.RenderPoints(g); - } - } - - public void SelectTriangle(ITriangle tri, Vertex org, Vertex dest) - { - currentTri = tri; - currentOrg = org; - currentDest = dest; - } - - #region Helpers - - private PointF GetIncenter(PointF p0, PointF p1, PointF p2) - { - double cx, cy, a, b, c, - dax = p1.X - p0.X, - dbx = p2.X - p1.X, - dcx = p0.X - p2.X, - day = p1.Y - p0.Y, - dby = p2.Y - p1.Y, - dcy = p0.Y - p2.Y; - - a = Math.Sqrt(dax * dax + day * day); - b = Math.Sqrt(dbx * dbx + dby * dby); - c = Math.Sqrt(dcx * dcx + dcy * dcy); - - cx = (a * p2.X + b * p0.X + c * p1.X) / (a + b + c); - cy = (a * p2.Y + b * p0.Y + c * p1.Y) / (a + b + c); - - return new PointF((float)cx, (float)cy); - } - - private PointF GetCentroid(PointF p0, PointF p1, PointF p2) - { - double cx, cy; - - cx = (p0.X + p1.X + p2.X) / 3; - cy = (p0.Y + p1.Y + p2.Y) / 3; - - return new PointF((float)cx, (float)cy); - } - - private PointF GetPoint(ITriangle tri, int index) - { - var v = tri.GetVertex(index); - - return new PointF((float)v.X, (float)v.Y); - } - - private PointF GetPoint(ISegment seg, int index) - { - var v = seg.GetVertex(index); - - return new PointF((float)v.X, (float)v.Y); - } - - #endregion - - private void RenderPoints(Graphics g) - { - int n = points.Length; - PointF pt; - - int id = currentOrg != null ? currentOrg.ID : -1; - - for (int i = 0; i < n; i++) - { - var brush = i == id ? Brushes.DarkRed : Point; - - pt = points[i]; - zoom.WorldToScreen(ref pt); - g.FillEllipse(brush, pt.X - 10f, pt.Y - 10f, 20, 20); - - pt.X -= i > 9 ? 7 : 4; - pt.Y -= 6; - g.DrawString(i.ToString(), font, Brushes.White, pt); - } - } - - private void RenderTriangles(Graphics g) - { - PointF p0, p1, p2, center; - - var triangles = mesh.Triangles; - - // Draw triangles - foreach (var tri in triangles) - { - p0 = points[tri.GetVertexID(0)]; - p1 = points[tri.GetVertexID(1)]; - p2 = points[tri.GetVertexID(2)]; - - zoom.WorldToScreen(ref p0); - zoom.WorldToScreen(ref p1); - zoom.WorldToScreen(ref p2); - - g.DrawLine(Line, p0, p1); - g.DrawLine(Line, p1, p2); - g.DrawLine(Line, p2, p0); - - center = GetIncenter(p0, p1, p2); - center.X -= 5; - center.Y -= 5; - - g.DrawString(tri.ID.ToString(), fontTri, Triangle, center); - } - } - - private void RenderTriangleIds(Graphics g) - { - PointF p0, p1, p2, center; - - var triangles = mesh.Triangles; - - // Draw triangles - foreach (var tri in triangles) - { - p0 = points[tri.GetVertexID(0)]; - p1 = points[tri.GetVertexID(1)]; - p2 = points[tri.GetVertexID(2)]; - - zoom.WorldToScreen(ref p0); - zoom.WorldToScreen(ref p1); - zoom.WorldToScreen(ref p2); - - center = GetIncenter(p0, p1, p2); - center.X -= 5; - center.Y -= 5; - - g.DrawString(tri.ID.ToString(), fontTri, Triangle, center); - } - } - - private void RenderEdges(Graphics g) - { - PointF p0, p1; - - var edges = mesh.Edges; - - // Draw edges - foreach (var edge in edges) - { - p0 = points[edge.P0]; - p1 = points[edge.P1]; - - zoom.WorldToScreen(ref p0); - zoom.WorldToScreen(ref p1); - - g.DrawLine(Line, p0, p1); - } - } - - private void RenderSegments(Graphics g) - { - PointF p0, p1; - - var segments = mesh.Segments; - - foreach (var seg in segments) - { - p0 = points[seg.P0]; - p1 = points[seg.P1]; - - zoom.WorldToScreen(ref p0); - zoom.WorldToScreen(ref p1); - - g.DrawLine(Segment, p0, p1); - } - } - - private void RenderSelectedEdge(Graphics g) - { - if (currentTri != null) - { - PointF p0, p1; - - p0 = points[currentOrg.ID]; - p1 = points[currentDest.ID]; - - zoom.WorldToScreen(ref p0); - zoom.WorldToScreen(ref p1); - - g.DrawLine(SelectedEdge, p0, p1); - } - } - - private void RenderSelectedTriangle(Graphics g) - { - if (currentTri != null) - { - var p = new PointF[3]; - - p[0] = points[currentTri.GetVertexID(0)]; - p[1] = points[currentTri.GetVertexID(1)]; - p[2] = points[currentTri.GetVertexID(2)]; - - zoom.WorldToScreen(ref p[0]); - zoom.WorldToScreen(ref p[1]); - zoom.WorldToScreen(ref p[2]); - - g.FillPolygon(SelectedTriangle, p); - } - } - } -} + +namespace MeshExplorer.Topology +{ + using System; + using System.Drawing; + using TriangleNet; + using TriangleNet.Geometry; + using TriangleNet.Rendering; + + public class TopologyRenderer + { + Projection zoom; + Mesh mesh; + PointF[] points; + + // Colors + Color Background = Color.FromArgb(0, 0, 0); + Brush Point = new SolidBrush(Color.FromArgb(0, 80, 0)); + Brush Triangle = new SolidBrush(Color.FromArgb(50, 50, 50)); + Pen Line = new Pen(Color.FromArgb(30, 30, 30)); + Pen Segment = new Pen(Color.DarkBlue); + + Brush SelectedTriangle = new SolidBrush(Color.FromArgb(50, 0, 0)); + Pen SelectedEdge = new Pen(Color.DarkRed, 2.0f); + + Font font, fontTri; + + ITriangle currentTri; + Vertex currentOrg, currentDest; + + /// + /// Initializes a new instance of the class. + /// + public TopologyRenderer(Mesh mesh) + { + this.mesh = mesh; + + points = new PointF[mesh.Vertices.Count]; + + int k = 0; + + var b = mesh.Bounds; + + double s = 1d / Math.Max(b.Width, b.Height); + double dx = b.X; + double dy = b.Y; + + foreach (var v in mesh.Vertices) + { + // Normalized device coordinates. + points[k++] = new PointF((float)((v.X - dx) * s), (float)((v.Y - dy) * s)); + } + + font = new Font("Arial", 7.5f); + fontTri = new Font("Arial", 12f, FontStyle.Bold); + } + + /// + /// Renders the mesh. + /// + public void Render(Graphics g, Projection zoom) + { + this.zoom = zoom; + + if (mesh.Edges != null) + { + this.RenderSelectedTriangle(g); + this.RenderEdges(g); + this.RenderTriangleIds(g); + } + else if (mesh.Triangles != null) + { + this.RenderTriangles(g); + } + + if (mesh.Segments != null) + { + this.RenderSegments(g); + } + + RenderSelectedEdge(g); + + if (mesh.Vertices != null) + { + this.RenderPoints(g); + } + } + + public void SelectTriangle(ITriangle tri, Vertex org, Vertex dest) + { + currentTri = tri; + currentOrg = org; + currentDest = dest; + } + + #region Helpers + + private PointF GetIncenter(PointF p0, PointF p1, PointF p2) + { + double cx, cy, a, b, c, + dax = p1.X - p0.X, + dbx = p2.X - p1.X, + dcx = p0.X - p2.X, + day = p1.Y - p0.Y, + dby = p2.Y - p1.Y, + dcy = p0.Y - p2.Y; + + a = Math.Sqrt(dax * dax + day * day); + b = Math.Sqrt(dbx * dbx + dby * dby); + c = Math.Sqrt(dcx * dcx + dcy * dcy); + + cx = (a * p2.X + b * p0.X + c * p1.X) / (a + b + c); + cy = (a * p2.Y + b * p0.Y + c * p1.Y) / (a + b + c); + + return new PointF((float)cx, (float)cy); + } + + private PointF GetCentroid(PointF p0, PointF p1, PointF p2) + { + double cx, cy; + + cx = (p0.X + p1.X + p2.X) / 3; + cy = (p0.Y + p1.Y + p2.Y) / 3; + + return new PointF((float)cx, (float)cy); + } + + private PointF GetPoint(ITriangle tri, int index) + { + var v = tri.GetVertex(index); + + return new PointF((float)v.X, (float)v.Y); + } + + private PointF GetPoint(ISegment seg, int index) + { + var v = seg.GetVertex(index); + + return new PointF((float)v.X, (float)v.Y); + } + + #endregion + + private void RenderPoints(Graphics g) + { + int n = points.Length; + PointF pt; + + int id = currentOrg != null ? currentOrg.ID : -1; + + for (int i = 0; i < n; i++) + { + var brush = i == id ? Brushes.DarkRed : Point; + + pt = points[i]; + zoom.NdcToScreen(ref pt); + g.FillEllipse(brush, pt.X - 10f, pt.Y - 10f, 20, 20); + + pt.X -= i > 9 ? 7 : 4; + pt.Y -= 6; + g.DrawString(i.ToString(), font, Brushes.White, pt); + } + } + + private void RenderTriangles(Graphics g) + { + PointF p0, p1, p2, center; + + var triangles = mesh.Triangles; + + // Draw triangles + foreach (var tri in triangles) + { + p0 = points[tri.GetVertexID(0)]; + p1 = points[tri.GetVertexID(1)]; + p2 = points[tri.GetVertexID(2)]; + + zoom.NdcToScreen(ref p0); + zoom.NdcToScreen(ref p1); + zoom.NdcToScreen(ref p2); + + g.DrawLine(Line, p0, p1); + g.DrawLine(Line, p1, p2); + g.DrawLine(Line, p2, p0); + + center = GetIncenter(p0, p1, p2); + center.X -= 5; + center.Y -= 5; + + g.DrawString(tri.ID.ToString(), fontTri, Triangle, center); + } + } + + private void RenderTriangleIds(Graphics g) + { + PointF p0, p1, p2, center; + + var triangles = mesh.Triangles; + + // Draw triangles + foreach (var tri in triangles) + { + p0 = points[tri.GetVertexID(0)]; + p1 = points[tri.GetVertexID(1)]; + p2 = points[tri.GetVertexID(2)]; + + zoom.NdcToScreen(ref p0); + zoom.NdcToScreen(ref p1); + zoom.NdcToScreen(ref p2); + + center = GetIncenter(p0, p1, p2); + center.X -= 5; + center.Y -= 5; + + g.DrawString(tri.ID.ToString(), fontTri, Triangle, center); + } + } + + private void RenderEdges(Graphics g) + { + PointF p0, p1; + + var edges = mesh.Edges; + + // Draw edges + foreach (var edge in edges) + { + p0 = points[edge.P0]; + p1 = points[edge.P1]; + + zoom.NdcToScreen(ref p0); + zoom.NdcToScreen(ref p1); + + g.DrawLine(Line, p0, p1); + } + } + + private void RenderSegments(Graphics g) + { + PointF p0, p1; + + var segments = mesh.Segments; + + foreach (var seg in segments) + { + p0 = points[seg.P0]; + p1 = points[seg.P1]; + + zoom.NdcToScreen(ref p0); + zoom.NdcToScreen(ref p1); + + g.DrawLine(Segment, p0, p1); + } + } + + private void RenderSelectedEdge(Graphics g) + { + if (currentTri != null) + { + PointF p0, p1; + + p0 = points[currentOrg.ID]; + p1 = points[currentDest.ID]; + + zoom.NdcToScreen(ref p0); + zoom.NdcToScreen(ref p1); + + g.DrawLine(SelectedEdge, p0, p1); + } + } + + private void RenderSelectedTriangle(Graphics g) + { + if (currentTri != null) + { + var p = new PointF[3]; + + p[0] = points[currentTri.GetVertexID(0)]; + p[1] = points[currentTri.GetVertexID(1)]; + p[2] = points[currentTri.GetVertexID(2)]; + + zoom.NdcToScreen(ref p[0]); + zoom.NdcToScreen(ref p[1]); + zoom.NdcToScreen(ref p[2]); + + g.FillPolygon(SelectedTriangle, p); + } + } + } +} diff --git a/src/Triangle.Viewer/Triangle.Viewer.csproj b/src/Triangle.Viewer/Triangle.Viewer.csproj new file mode 100644 index 0000000..a40279a --- /dev/null +++ b/src/Triangle.Viewer/Triangle.Viewer.csproj @@ -0,0 +1,16 @@ + + + WinExe + net6.0-windows + true + MeshExplorer + Triangle.Viewer + AnyCPU;x64 + + + + + + + + \ No newline at end of file diff --git a/Triangle.NET/TestApp/Util.cs b/src/Triangle.Viewer/Util.cs similarity index 96% rename from Triangle.NET/TestApp/Util.cs rename to src/Triangle.Viewer/Util.cs index 62a34da..79c1b79 100644 --- a/Triangle.NET/TestApp/Util.cs +++ b/src/Triangle.Viewer/Util.cs @@ -1,79 +1,79 @@ -// ----------------------------------------------------------------------- -// -// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer -{ - using System; - using System.Drawing; - using System.Globalization; - using System.IO; - - /// - /// Utility class. - /// - public static class Util - { - internal static NumberFormatInfo Nfi = CultureInfo.InvariantCulture.NumberFormat; - - internal static Random Random = new Random(DateTime.Now.Millisecond); - - internal static bool TryReadLine(StreamReader reader, out string[] token) - { - token = null; - - if (reader.EndOfStream) - { - return false; - } - - string line = reader.ReadLine().Trim(); - - while (String.IsNullOrWhiteSpace(line) || line.StartsWith("#")) - { - if (reader.EndOfStream) - { - return false; - } - - line = reader.ReadLine().Trim(); - } - - token = line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); - - return true; - } - - internal static string DoubleToString(double d) - { - double max = 999999; - double min = 0.00001; - - string spec = "0.00000"; - - if (d < min || d > max) - { - spec = "0.###e-000"; - } - - return d.ToString(spec, Util.Nfi); - } - - internal static string AngleToString(double d) - { - double max = 180 - 10E-14; - double min = 10E-14; - - string spec = "0.00000"; - - if (d < min || d > max) - { - spec = "0.#"; - } - - return d.ToString(spec, Util.Nfi); - } - } -} +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer +{ + using System; + using System.Drawing; + using System.Globalization; + using System.IO; + + /// + /// Utility class. + /// + public static class Util + { + internal static NumberFormatInfo Nfi = CultureInfo.InvariantCulture.NumberFormat; + + internal static Random Random = new Random(DateTime.Now.Millisecond); + + internal static bool TryReadLine(StreamReader reader, out string[] token) + { + token = null; + + if (reader.EndOfStream) + { + return false; + } + + string line = reader.ReadLine().Trim(); + + while (String.IsNullOrWhiteSpace(line) || line.StartsWith("#")) + { + if (reader.EndOfStream) + { + return false; + } + + line = reader.ReadLine().Trim(); + } + + token = line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + + return true; + } + + internal static string DoubleToString(double d) + { + double max = 999999; + double min = 0.00001; + + string spec = "0.00000"; + + if (d < min || d > max) + { + spec = "0.###e-000"; + } + + return d.ToString(spec, Util.Nfi); + } + + internal static string AngleToString(double d) + { + double max = 180 - 10E-14; + double min = 10E-14; + + string spec = "0.00000"; + + if (d < min || d > max) + { + spec = "0.#"; + } + + return d.ToString(spec, Util.Nfi); + } + } +} diff --git a/Triangle.NET/TestApp/Views/AboutView.Designer.cs b/src/Triangle.Viewer/Views/AboutView.Designer.cs similarity index 90% rename from Triangle.NET/TestApp/Views/AboutView.Designer.cs rename to src/Triangle.Viewer/Views/AboutView.Designer.cs index 1db81f5..cd4a335 100644 --- a/Triangle.NET/TestApp/Views/AboutView.Designer.cs +++ b/src/Triangle.Viewer/Views/AboutView.Designer.cs @@ -1,145 +1,145 @@ -namespace MeshExplorer.Views -{ - partial class AboutView - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Component Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.lbCodeplex = new System.Windows.Forms.Label(); - this.label15 = new System.Windows.Forms.Label(); - this.label1 = new System.Windows.Forms.Label(); - this.label19 = new System.Windows.Forms.Label(); - this.label18 = new System.Windows.Forms.Label(); - this.label7 = new System.Windows.Forms.Label(); - this.lbShortcuts = new System.Windows.Forms.Label(); - this.SuspendLayout(); - // - // lbCodeplex - // - this.lbCodeplex.AutoSize = true; - this.lbCodeplex.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Underline, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lbCodeplex.ForeColor = System.Drawing.Color.White; - this.lbCodeplex.Location = new System.Drawing.Point(72, 82); - this.lbCodeplex.Name = "lbCodeplex"; - this.lbCodeplex.Size = new System.Drawing.Size(153, 13); - this.lbCodeplex.TabIndex = 9; - this.lbCodeplex.Text = "http://triangle.codeplex.com"; - this.lbCodeplex.Click += new System.EventHandler(this.lbCodeplex_Clicked); - // - // label15 - // - this.label15.AutoSize = true; - this.label15.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label15.ForeColor = System.Drawing.Color.White; - this.label15.Location = new System.Drawing.Point(10, 17); - this.label15.Name = "label15"; - this.label15.Size = new System.Drawing.Size(73, 13); - this.label15.TabIndex = 7; - this.label15.Text = "Triangle.NET"; - // - // label1 - // - this.label1.AutoSize = true; - this.label1.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label1.ForeColor = System.Drawing.Color.White; - this.label1.Location = new System.Drawing.Point(10, 132); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(108, 13); - this.label1.TabIndex = 8; - this.label1.Text = "Keyboard shortcuts"; - // - // label19 - // - this.label19.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label19.ForeColor = System.Drawing.Color.White; - this.label19.Location = new System.Drawing.Point(72, 42); - this.label19.Name = "label19"; - this.label19.Size = new System.Drawing.Size(134, 40); - this.label19.TabIndex = 6; - this.label19.Text = "Beta 4 (2014-05-30)\r\nChristian Woltering\r\nMIT"; - // - // label18 - // - this.label18.ForeColor = System.Drawing.Color.White; - this.label18.Location = new System.Drawing.Point(15, 42); - this.label18.Name = "label18"; - this.label18.Size = new System.Drawing.Size(51, 40); - this.label18.TabIndex = 4; - this.label18.Text = "Version:\r\nAuthor:\r\nLicense:"; - this.label18.TextAlign = System.Drawing.ContentAlignment.TopRight; - // - // label7 - // - this.label7.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label7.ForeColor = System.Drawing.Color.White; - this.label7.Location = new System.Drawing.Point(72, 155); - this.label7.Name = "label7"; - this.label7.Size = new System.Drawing.Size(134, 108); - this.label7.TabIndex = 5; - this.label7.Text = "File Open\r\nFile Save\r\nReload Input\r\n\r\nTriangulate / Refine\r\nSmooth\r\n\r\nShow Log"; - // - // lbShortcuts - // - this.lbShortcuts.ForeColor = System.Drawing.Color.White; - this.lbShortcuts.Location = new System.Drawing.Point(24, 155); - this.lbShortcuts.Name = "lbShortcuts"; - this.lbShortcuts.Size = new System.Drawing.Size(36, 108); - this.lbShortcuts.TabIndex = 3; - this.lbShortcuts.Text = "F3\r\nF4\r\nF5\r\n\r\nF8\r\nF9\r\n\r\nF12"; - this.lbShortcuts.TextAlign = System.Drawing.ContentAlignment.TopRight; - // - // AboutView - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.DimGray; - this.Controls.Add(this.lbCodeplex); - this.Controls.Add(this.label15); - this.Controls.Add(this.label1); - this.Controls.Add(this.label19); - this.Controls.Add(this.label18); - this.Controls.Add(this.label7); - this.Controls.Add(this.lbShortcuts); - this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ForeColor = System.Drawing.Color.DarkGray; - this.Name = "AboutView"; - this.Size = new System.Drawing.Size(272, 509); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.Label lbCodeplex; - private System.Windows.Forms.Label label15; - private System.Windows.Forms.Label label1; - private System.Windows.Forms.Label label19; - private System.Windows.Forms.Label label18; - private System.Windows.Forms.Label label7; - private System.Windows.Forms.Label lbShortcuts; - } -} +namespace MeshExplorer.Views +{ + partial class AboutView + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.lbCodeplex = new System.Windows.Forms.Label(); + this.label15 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.label19 = new System.Windows.Forms.Label(); + this.label18 = new System.Windows.Forms.Label(); + this.label7 = new System.Windows.Forms.Label(); + this.lbShortcuts = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // lbCodeplex + // + this.lbCodeplex.AutoSize = true; + this.lbCodeplex.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Underline, System.Drawing.GraphicsUnit.Point); + this.lbCodeplex.ForeColor = System.Drawing.Color.White; + this.lbCodeplex.Location = new System.Drawing.Point(72, 82); + this.lbCodeplex.Name = "lbCodeplex"; + this.lbCodeplex.Size = new System.Drawing.Size(166, 13); + this.lbCodeplex.TabIndex = 9; + this.lbCodeplex.Text = "github.com/wo80/Triangle.NET"; + this.lbCodeplex.Click += new System.EventHandler(this.lbCodeplex_Clicked); + // + // label15 + // + this.label15.AutoSize = true; + this.label15.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point); + this.label15.ForeColor = System.Drawing.Color.White; + this.label15.Location = new System.Drawing.Point(10, 17); + this.label15.Name = "label15"; + this.label15.Size = new System.Drawing.Size(72, 13); + this.label15.TabIndex = 7; + this.label15.Text = "Triangle.NET"; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point); + this.label1.ForeColor = System.Drawing.Color.White; + this.label1.Location = new System.Drawing.Point(10, 132); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(108, 13); + this.label1.TabIndex = 8; + this.label1.Text = "Keyboard shortcuts"; + // + // label19 + // + this.label19.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.label19.ForeColor = System.Drawing.Color.White; + this.label19.Location = new System.Drawing.Point(72, 42); + this.label19.Name = "label19"; + this.label19.Size = new System.Drawing.Size(134, 40); + this.label19.TabIndex = 6; + this.label19.Text = "Beta 5 (2022-03-05)\r\nChristian Woltering"; + // + // label18 + // + this.label18.ForeColor = System.Drawing.Color.White; + this.label18.Location = new System.Drawing.Point(15, 42); + this.label18.Name = "label18"; + this.label18.Size = new System.Drawing.Size(51, 40); + this.label18.TabIndex = 4; + this.label18.Text = "Version:\r\nAuthor:"; + this.label18.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // label7 + // + this.label7.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.label7.ForeColor = System.Drawing.Color.White; + this.label7.Location = new System.Drawing.Point(72, 155); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(134, 108); + this.label7.TabIndex = 5; + this.label7.Text = "File Open\r\nFile Save\r\nReload Input\r\n\r\nTriangulate / Refine\r\nSmooth\r\n\r\nShow Log"; + // + // lbShortcuts + // + this.lbShortcuts.ForeColor = System.Drawing.Color.White; + this.lbShortcuts.Location = new System.Drawing.Point(24, 155); + this.lbShortcuts.Name = "lbShortcuts"; + this.lbShortcuts.Size = new System.Drawing.Size(36, 108); + this.lbShortcuts.TabIndex = 3; + this.lbShortcuts.Text = "F3\r\nF4\r\nF5\r\n\r\nF8\r\nF9\r\n\r\nF12"; + this.lbShortcuts.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // AboutView + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.DimGray; + this.Controls.Add(this.lbCodeplex); + this.Controls.Add(this.label15); + this.Controls.Add(this.label1); + this.Controls.Add(this.label19); + this.Controls.Add(this.label18); + this.Controls.Add(this.label7); + this.Controls.Add(this.lbShortcuts); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.ForeColor = System.Drawing.Color.DarkGray; + this.Name = "AboutView"; + this.Size = new System.Drawing.Size(272, 509); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label lbCodeplex; + private System.Windows.Forms.Label label15; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label19; + private System.Windows.Forms.Label label18; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.Label lbShortcuts; + } +} diff --git a/Triangle.NET/TestApp/Views/AboutView.cs b/src/Triangle.Viewer/Views/AboutView.cs similarity index 83% rename from Triangle.NET/TestApp/Views/AboutView.cs rename to src/Triangle.Viewer/Views/AboutView.cs index defddf2..7ac939d 100644 --- a/Triangle.NET/TestApp/Views/AboutView.cs +++ b/src/Triangle.Viewer/Views/AboutView.cs @@ -1,47 +1,51 @@ -using System; -using System.Diagnostics; -using System.Windows.Forms; -using TriangleNet; -using TriangleNet.Geometry; - -namespace MeshExplorer.Views -{ - public partial class AboutView : UserControl, IView - { - public AboutView() - { - InitializeComponent(); - } - - private void lbCodeplex_Clicked(object sender, EventArgs e) - { - try - { - ProcessStartInfo info = new ProcessStartInfo("http://triangle.codeplex.com/"); - Process.Start(info); - } - catch (Exception) - { } - } - - #region IView - - public void HandleNewInput(IPolygon geometry) - { - } - - public void HandleMeshImport(IPolygon geometry, Mesh mesh) - { - } - - public void HandleMeshUpdate(Mesh mesh) - { - } - - public void HandleMeshChange(Mesh mesh) - { - } - - #endregion - } -} +using System; +using System.Diagnostics; +using System.Windows.Forms; +using TriangleNet; +using TriangleNet.Geometry; + +namespace MeshExplorer.Views +{ + public partial class AboutView : UserControl, IView + { + public AboutView() + { + InitializeComponent(); + } + + private void lbCodeplex_Clicked(object sender, EventArgs e) + { + try + { + var info = new ProcessStartInfo("https://github.com/wo80/Triangle.NET") + { + UseShellExecute = true + }; + + Process.Start(info); + } + catch (Exception) + { } + } + + #region IView + + public void HandleNewInput(IPolygon geometry) + { + } + + public void HandleMeshImport(IPolygon geometry, Mesh mesh) + { + } + + public void HandleMeshUpdate(Mesh mesh) + { + } + + public void HandleMeshChange(Mesh mesh) + { + } + + #endregion + } +} diff --git a/src/Triangle.Viewer/Views/AboutView.resx b/src/Triangle.Viewer/Views/AboutView.resx new file mode 100644 index 0000000..f298a7b --- /dev/null +++ b/src/Triangle.Viewer/Views/AboutView.resx @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Triangle.NET/TestApp/Views/IView.cs b/src/Triangle.Viewer/Views/IView.cs similarity index 83% rename from Triangle.NET/TestApp/Views/IView.cs rename to src/Triangle.Viewer/Views/IView.cs index 42d5a50..1cccfbb 100644 --- a/Triangle.NET/TestApp/Views/IView.cs +++ b/src/Triangle.Viewer/Views/IView.cs @@ -1,26 +1,22 @@ -// ----------------------------------------------------------------------- -// -// TODO: Update copyright text. -// -// ----------------------------------------------------------------------- - -namespace MeshExplorer.Views -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using TriangleNet; - using TriangleNet.Geometry; - - /// - /// TODO: Update summary. - /// - public interface IView - { - void HandleNewInput(IPolygon geometry); - void HandleMeshImport(IPolygon geometry, Mesh mesh); - void HandleMeshUpdate(Mesh mesh); - void HandleMeshChange(Mesh mesh); - } -} +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.Views +{ + using TriangleNet; + using TriangleNet.Geometry; + + /// + /// TODO: Update summary. + /// + public interface IView + { + void HandleNewInput(IPolygon geometry); + void HandleMeshImport(IPolygon geometry, Mesh mesh); + void HandleMeshUpdate(Mesh mesh); + void HandleMeshChange(Mesh mesh); + } +} diff --git a/Triangle.NET/TestApp/Views/MeshControlView.Designer.cs b/src/Triangle.Viewer/Views/MeshControlView.Designer.cs similarity index 97% rename from Triangle.NET/TestApp/Views/MeshControlView.Designer.cs rename to src/Triangle.Viewer/Views/MeshControlView.Designer.cs index 089f253..1b35151 100644 --- a/Triangle.NET/TestApp/Views/MeshControlView.Designer.cs +++ b/src/Triangle.Viewer/Views/MeshControlView.Designer.cs @@ -1,289 +1,289 @@ -namespace MeshExplorer.Views -{ - partial class MeshControlView - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Component Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.lbMaxArea = new System.Windows.Forms.Label(); - this.label6 = new System.Windows.Forms.Label(); - this.lbMinAngle = new System.Windows.Forms.Label(); - this.label23 = new System.Windows.Forms.Label(); - this.label9 = new System.Windows.Forms.Label(); - this.label8 = new System.Windows.Forms.Label(); - this.label5 = new System.Windows.Forms.Label(); - this.label1 = new System.Windows.Forms.Label(); - this.label2 = new System.Windows.Forms.Label(); - this.lbMaxAngle = new System.Windows.Forms.Label(); - this.cbSweepline = new MeshExplorer.Controls.DarkCheckBox(); - this.slMaxAngle = new MeshExplorer.Controls.DarkSlider(); - this.slMaxArea = new MeshExplorer.Controls.DarkSlider(); - this.slMinAngle = new MeshExplorer.Controls.DarkSlider(); - this.cbConformDel = new MeshExplorer.Controls.DarkCheckBox(); - this.cbConvex = new MeshExplorer.Controls.DarkCheckBox(); - this.cbQuality = new MeshExplorer.Controls.DarkCheckBox(); - this.SuspendLayout(); - // - // lbMaxArea - // - this.lbMaxArea.AutoSize = true; - this.lbMaxArea.ForeColor = System.Drawing.Color.White; - this.lbMaxArea.Location = new System.Drawing.Point(227, 87); - this.lbMaxArea.Name = "lbMaxArea"; - this.lbMaxArea.Size = new System.Drawing.Size(13, 13); - this.lbMaxArea.TabIndex = 20; - this.lbMaxArea.Text = "0"; - // - // label6 - // - this.label6.AutoSize = true; - this.label6.ForeColor = System.Drawing.Color.White; - this.label6.Location = new System.Drawing.Point(8, 86); - this.label6.Name = "label6"; - this.label6.Size = new System.Drawing.Size(81, 13); - this.label6.TabIndex = 23; - this.label6.Text = "Maximum area"; - // - // lbMinAngle - // - this.lbMinAngle.AutoSize = true; - this.lbMinAngle.ForeColor = System.Drawing.Color.White; - this.lbMinAngle.Location = new System.Drawing.Point(227, 43); - this.lbMinAngle.Name = "lbMinAngle"; - this.lbMinAngle.Size = new System.Drawing.Size(19, 13); - this.lbMinAngle.TabIndex = 22; - this.lbMinAngle.Text = "20"; - // - // label23 - // - this.label23.BackColor = System.Drawing.Color.DimGray; - this.label23.ForeColor = System.Drawing.Color.DarkGray; - this.label23.Location = new System.Drawing.Point(8, 173); - this.label23.Name = "label23"; - this.label23.Size = new System.Drawing.Size(251, 33); - this.label23.TabIndex = 24; - this.label23.Text = "Ensure that all triangles in the mesh are truly Delaunay, and not just constraine" + - "d Delaunay."; - // - // label9 - // - this.label9.BackColor = System.Drawing.Color.DimGray; - this.label9.ForeColor = System.Drawing.Color.DarkGray; - this.label9.Location = new System.Drawing.Point(8, 237); - this.label9.Name = "label9"; - this.label9.Size = new System.Drawing.Size(258, 33); - this.label9.TabIndex = 26; - this.label9.Text = "Use the convex mesh option, if the convex hull should be included in the output."; - // - // label8 - // - this.label8.BackColor = System.Drawing.Color.DimGray; - this.label8.ForeColor = System.Drawing.Color.DarkGray; - this.label8.Location = new System.Drawing.Point(8, 110); - this.label8.Name = "label8"; - this.label8.Size = new System.Drawing.Size(258, 33); - this.label8.TabIndex = 25; - this.label8.Text = "Hint: maximum area values of 0 or 1 will be irgnored (no area constraints are set" + - ")."; - // - // label5 - // - this.label5.AutoSize = true; - this.label5.ForeColor = System.Drawing.Color.White; - this.label5.Location = new System.Drawing.Point(8, 42); - this.label5.Name = "label5"; - this.label5.Size = new System.Drawing.Size(87, 13); - this.label5.TabIndex = 21; - this.label5.Text = "Minimum angle"; - // - // label1 - // - this.label1.BackColor = System.Drawing.Color.DimGray; - this.label1.ForeColor = System.Drawing.Color.DarkGray; - this.label1.Location = new System.Drawing.Point(8, 304); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(258, 33); - this.label1.TabIndex = 26; - this.label1.Text = "Use Sweepline algorithm for triangulation instead of default Divide && Conquer."; - // - // label2 - // - this.label2.AutoSize = true; - this.label2.ForeColor = System.Drawing.Color.White; - this.label2.Location = new System.Drawing.Point(8, 64); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(88, 13); - this.label2.TabIndex = 21; - this.label2.Text = "Maximum angle"; - // - // lbMaxAngle - // - this.lbMaxAngle.AutoSize = true; - this.lbMaxAngle.ForeColor = System.Drawing.Color.White; - this.lbMaxAngle.Location = new System.Drawing.Point(227, 65); - this.lbMaxAngle.Name = "lbMaxAngle"; - this.lbMaxAngle.Size = new System.Drawing.Size(25, 13); - this.lbMaxAngle.TabIndex = 22; - this.lbMaxAngle.Text = "180"; - // - // cbSweepline - // - this.cbSweepline.BackColor = System.Drawing.Color.DimGray; - this.cbSweepline.Checked = false; - this.cbSweepline.Location = new System.Drawing.Point(11, 278); - this.cbSweepline.Name = "cbSweepline"; - this.cbSweepline.Size = new System.Drawing.Size(181, 23); - this.cbSweepline.TabIndex = 27; - this.cbSweepline.Text = "Use Sweepline algorithm"; - this.cbSweepline.UseVisualStyleBackColor = false; - // - // slMaxAngle - // - this.slMaxAngle.BackColor = System.Drawing.Color.Transparent; - this.slMaxAngle.CriticalPercent = ((uint)(89u)); - this.slMaxAngle.Location = new System.Drawing.Point(102, 61); - this.slMaxAngle.Maximum = 100; - this.slMaxAngle.Minimum = 0; - this.slMaxAngle.Name = "slMaxAngle"; - this.slMaxAngle.Size = new System.Drawing.Size(119, 18); - this.slMaxAngle.TabIndex = 18; - this.slMaxAngle.Text = "darkSlider1"; - this.slMaxAngle.Value = 0; - this.slMaxAngle.ValueChanging += new System.EventHandler(this.slMaxAngle_ValueChanging); - // - // slMaxArea - // - this.slMaxArea.BackColor = System.Drawing.Color.Transparent; - this.slMaxArea.CriticalPercent = ((uint)(0u)); - this.slMaxArea.Location = new System.Drawing.Point(102, 83); - this.slMaxArea.Maximum = 100; - this.slMaxArea.Minimum = 0; - this.slMaxArea.Name = "slMaxArea"; - this.slMaxArea.Size = new System.Drawing.Size(119, 18); - this.slMaxArea.TabIndex = 19; - this.slMaxArea.Text = "darkSlider1"; - this.slMaxArea.Value = 0; - this.slMaxArea.ValueChanging += new System.EventHandler(this.slMaxArea_ValueChanging); - // - // slMinAngle - // - this.slMinAngle.BackColor = System.Drawing.Color.Transparent; - this.slMinAngle.CriticalPercent = ((uint)(89u)); - this.slMinAngle.Location = new System.Drawing.Point(102, 39); - this.slMinAngle.Maximum = 100; - this.slMinAngle.Minimum = 0; - this.slMinAngle.Name = "slMinAngle"; - this.slMinAngle.Size = new System.Drawing.Size(119, 18); - this.slMinAngle.TabIndex = 18; - this.slMinAngle.Text = "darkSlider1"; - this.slMinAngle.Value = 50; - this.slMinAngle.ValueChanging += new System.EventHandler(this.slMinAngle_ValueChanging); - // - // cbConformDel - // - this.cbConformDel.BackColor = System.Drawing.Color.DimGray; - this.cbConformDel.Checked = false; - this.cbConformDel.Location = new System.Drawing.Point(11, 153); - this.cbConformDel.Name = "cbConformDel"; - this.cbConformDel.Size = new System.Drawing.Size(142, 17); - this.cbConformDel.TabIndex = 16; - this.cbConformDel.Text = "Conforming Delaunay"; - this.cbConformDel.UseVisualStyleBackColor = false; - // - // cbConvex - // - this.cbConvex.BackColor = System.Drawing.Color.DimGray; - this.cbConvex.Checked = false; - this.cbConvex.Location = new System.Drawing.Point(11, 217); - this.cbConvex.Name = "cbConvex"; - this.cbConvex.Size = new System.Drawing.Size(115, 17); - this.cbConvex.TabIndex = 15; - this.cbConvex.Text = "Convex mesh"; - this.cbConvex.UseVisualStyleBackColor = false; - // - // cbQuality - // - this.cbQuality.BackColor = System.Drawing.Color.DimGray; - this.cbQuality.Checked = false; - this.cbQuality.Location = new System.Drawing.Point(11, 16); - this.cbQuality.Name = "cbQuality"; - this.cbQuality.Size = new System.Drawing.Size(115, 17); - this.cbQuality.TabIndex = 17; - this.cbQuality.Text = "Quality mesh"; - this.cbQuality.UseVisualStyleBackColor = false; - // - // MeshControlView - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.DimGray; - this.Controls.Add(this.cbSweepline); - this.Controls.Add(this.lbMaxArea); - this.Controls.Add(this.label6); - this.Controls.Add(this.lbMaxAngle); - this.Controls.Add(this.lbMinAngle); - this.Controls.Add(this.label23); - this.Controls.Add(this.label1); - this.Controls.Add(this.label9); - this.Controls.Add(this.label8); - this.Controls.Add(this.label2); - this.Controls.Add(this.label5); - this.Controls.Add(this.slMaxAngle); - this.Controls.Add(this.slMaxArea); - this.Controls.Add(this.slMinAngle); - this.Controls.Add(this.cbConformDel); - this.Controls.Add(this.cbConvex); - this.Controls.Add(this.cbQuality); - this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ForeColor = System.Drawing.Color.DarkGray; - this.Name = "MeshControlView"; - this.Size = new System.Drawing.Size(272, 509); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.Label lbMaxArea; - private System.Windows.Forms.Label label6; - private System.Windows.Forms.Label lbMinAngle; - private System.Windows.Forms.Label label23; - private System.Windows.Forms.Label label9; - private System.Windows.Forms.Label label8; - private System.Windows.Forms.Label label5; - private Controls.DarkSlider slMaxArea; - private Controls.DarkSlider slMinAngle; - private Controls.DarkCheckBox cbConformDel; - private Controls.DarkCheckBox cbConvex; - private Controls.DarkCheckBox cbQuality; - private Controls.DarkCheckBox cbSweepline; - private System.Windows.Forms.Label label1; - private Controls.DarkSlider slMaxAngle; - private System.Windows.Forms.Label label2; - private System.Windows.Forms.Label lbMaxAngle; - } -} +namespace MeshExplorer.Views +{ + partial class MeshControlView + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.lbMaxArea = new System.Windows.Forms.Label(); + this.label6 = new System.Windows.Forms.Label(); + this.lbMinAngle = new System.Windows.Forms.Label(); + this.label23 = new System.Windows.Forms.Label(); + this.label9 = new System.Windows.Forms.Label(); + this.label8 = new System.Windows.Forms.Label(); + this.label5 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.lbMaxAngle = new System.Windows.Forms.Label(); + this.cbSweepline = new MeshExplorer.Controls.DarkCheckBox(); + this.slMaxAngle = new MeshExplorer.Controls.DarkSlider(); + this.slMaxArea = new MeshExplorer.Controls.DarkSlider(); + this.slMinAngle = new MeshExplorer.Controls.DarkSlider(); + this.cbConformDel = new MeshExplorer.Controls.DarkCheckBox(); + this.cbConvex = new MeshExplorer.Controls.DarkCheckBox(); + this.cbQuality = new MeshExplorer.Controls.DarkCheckBox(); + this.SuspendLayout(); + // + // lbMaxArea + // + this.lbMaxArea.AutoSize = true; + this.lbMaxArea.ForeColor = System.Drawing.Color.White; + this.lbMaxArea.Location = new System.Drawing.Point(227, 87); + this.lbMaxArea.Name = "lbMaxArea"; + this.lbMaxArea.Size = new System.Drawing.Size(13, 13); + this.lbMaxArea.TabIndex = 20; + this.lbMaxArea.Text = "0"; + // + // label6 + // + this.label6.AutoSize = true; + this.label6.ForeColor = System.Drawing.Color.White; + this.label6.Location = new System.Drawing.Point(8, 86); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(81, 13); + this.label6.TabIndex = 23; + this.label6.Text = "Maximum area"; + // + // lbMinAngle + // + this.lbMinAngle.AutoSize = true; + this.lbMinAngle.ForeColor = System.Drawing.Color.White; + this.lbMinAngle.Location = new System.Drawing.Point(227, 43); + this.lbMinAngle.Name = "lbMinAngle"; + this.lbMinAngle.Size = new System.Drawing.Size(19, 13); + this.lbMinAngle.TabIndex = 22; + this.lbMinAngle.Text = "20"; + // + // label23 + // + this.label23.BackColor = System.Drawing.Color.DimGray; + this.label23.ForeColor = System.Drawing.Color.DarkGray; + this.label23.Location = new System.Drawing.Point(8, 173); + this.label23.Name = "label23"; + this.label23.Size = new System.Drawing.Size(251, 33); + this.label23.TabIndex = 24; + this.label23.Text = "Ensure that all triangles in the mesh are truly Delaunay, and not just constraine" + + "d Delaunay."; + // + // label9 + // + this.label9.BackColor = System.Drawing.Color.DimGray; + this.label9.ForeColor = System.Drawing.Color.DarkGray; + this.label9.Location = new System.Drawing.Point(8, 237); + this.label9.Name = "label9"; + this.label9.Size = new System.Drawing.Size(258, 33); + this.label9.TabIndex = 26; + this.label9.Text = "Use the convex mesh option, if the convex hull should be included in the output."; + // + // label8 + // + this.label8.BackColor = System.Drawing.Color.DimGray; + this.label8.ForeColor = System.Drawing.Color.DarkGray; + this.label8.Location = new System.Drawing.Point(8, 110); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(258, 33); + this.label8.TabIndex = 25; + this.label8.Text = "Hint: maximum area values of 0 or 1 will be irgnored (no area constraints are set" + + ")."; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.ForeColor = System.Drawing.Color.White; + this.label5.Location = new System.Drawing.Point(8, 42); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(87, 13); + this.label5.TabIndex = 21; + this.label5.Text = "Minimum angle"; + // + // label1 + // + this.label1.BackColor = System.Drawing.Color.DimGray; + this.label1.ForeColor = System.Drawing.Color.DarkGray; + this.label1.Location = new System.Drawing.Point(8, 304); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(258, 33); + this.label1.TabIndex = 26; + this.label1.Text = "Use Sweepline algorithm for triangulation instead of default Divide && Conquer."; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.ForeColor = System.Drawing.Color.White; + this.label2.Location = new System.Drawing.Point(8, 64); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(88, 13); + this.label2.TabIndex = 21; + this.label2.Text = "Maximum angle"; + // + // lbMaxAngle + // + this.lbMaxAngle.AutoSize = true; + this.lbMaxAngle.ForeColor = System.Drawing.Color.White; + this.lbMaxAngle.Location = new System.Drawing.Point(227, 65); + this.lbMaxAngle.Name = "lbMaxAngle"; + this.lbMaxAngle.Size = new System.Drawing.Size(25, 13); + this.lbMaxAngle.TabIndex = 22; + this.lbMaxAngle.Text = "180"; + // + // cbSweepline + // + this.cbSweepline.BackColor = System.Drawing.Color.DimGray; + this.cbSweepline.Checked = false; + this.cbSweepline.Location = new System.Drawing.Point(11, 278); + this.cbSweepline.Name = "cbSweepline"; + this.cbSweepline.Size = new System.Drawing.Size(181, 23); + this.cbSweepline.TabIndex = 27; + this.cbSweepline.Text = "Use Sweepline algorithm"; + this.cbSweepline.UseVisualStyleBackColor = false; + // + // slMaxAngle + // + this.slMaxAngle.BackColor = System.Drawing.Color.Transparent; + this.slMaxAngle.CriticalPercent = ((uint)(89u)); + this.slMaxAngle.Location = new System.Drawing.Point(102, 61); + this.slMaxAngle.Maximum = 100; + this.slMaxAngle.Minimum = 0; + this.slMaxAngle.Name = "slMaxAngle"; + this.slMaxAngle.Size = new System.Drawing.Size(119, 18); + this.slMaxAngle.TabIndex = 18; + this.slMaxAngle.Text = "darkSlider1"; + this.slMaxAngle.Value = 0; + this.slMaxAngle.ValueChanging += new System.EventHandler(this.slMaxAngle_ValueChanging); + // + // slMaxArea + // + this.slMaxArea.BackColor = System.Drawing.Color.Transparent; + this.slMaxArea.CriticalPercent = ((uint)(0u)); + this.slMaxArea.Location = new System.Drawing.Point(102, 83); + this.slMaxArea.Maximum = 100; + this.slMaxArea.Minimum = 0; + this.slMaxArea.Name = "slMaxArea"; + this.slMaxArea.Size = new System.Drawing.Size(119, 18); + this.slMaxArea.TabIndex = 19; + this.slMaxArea.Text = "darkSlider1"; + this.slMaxArea.Value = 0; + this.slMaxArea.ValueChanging += new System.EventHandler(this.slMaxArea_ValueChanging); + // + // slMinAngle + // + this.slMinAngle.BackColor = System.Drawing.Color.Transparent; + this.slMinAngle.CriticalPercent = ((uint)(89u)); + this.slMinAngle.Location = new System.Drawing.Point(102, 39); + this.slMinAngle.Maximum = 100; + this.slMinAngle.Minimum = 0; + this.slMinAngle.Name = "slMinAngle"; + this.slMinAngle.Size = new System.Drawing.Size(119, 18); + this.slMinAngle.TabIndex = 18; + this.slMinAngle.Text = "darkSlider1"; + this.slMinAngle.Value = 50; + this.slMinAngle.ValueChanging += new System.EventHandler(this.slMinAngle_ValueChanging); + // + // cbConformDel + // + this.cbConformDel.BackColor = System.Drawing.Color.DimGray; + this.cbConformDel.Checked = false; + this.cbConformDel.Location = new System.Drawing.Point(11, 153); + this.cbConformDel.Name = "cbConformDel"; + this.cbConformDel.Size = new System.Drawing.Size(142, 17); + this.cbConformDel.TabIndex = 16; + this.cbConformDel.Text = "Conforming Delaunay"; + this.cbConformDel.UseVisualStyleBackColor = false; + // + // cbConvex + // + this.cbConvex.BackColor = System.Drawing.Color.DimGray; + this.cbConvex.Checked = false; + this.cbConvex.Location = new System.Drawing.Point(11, 217); + this.cbConvex.Name = "cbConvex"; + this.cbConvex.Size = new System.Drawing.Size(115, 17); + this.cbConvex.TabIndex = 15; + this.cbConvex.Text = "Convex mesh"; + this.cbConvex.UseVisualStyleBackColor = false; + // + // cbQuality + // + this.cbQuality.BackColor = System.Drawing.Color.DimGray; + this.cbQuality.Checked = false; + this.cbQuality.Location = new System.Drawing.Point(11, 16); + this.cbQuality.Name = "cbQuality"; + this.cbQuality.Size = new System.Drawing.Size(115, 17); + this.cbQuality.TabIndex = 17; + this.cbQuality.Text = "Quality mesh"; + this.cbQuality.UseVisualStyleBackColor = false; + // + // MeshControlView + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.DimGray; + this.Controls.Add(this.cbSweepline); + this.Controls.Add(this.lbMaxArea); + this.Controls.Add(this.label6); + this.Controls.Add(this.lbMaxAngle); + this.Controls.Add(this.lbMinAngle); + this.Controls.Add(this.label23); + this.Controls.Add(this.label1); + this.Controls.Add(this.label9); + this.Controls.Add(this.label8); + this.Controls.Add(this.label2); + this.Controls.Add(this.label5); + this.Controls.Add(this.slMaxAngle); + this.Controls.Add(this.slMaxArea); + this.Controls.Add(this.slMinAngle); + this.Controls.Add(this.cbConformDel); + this.Controls.Add(this.cbConvex); + this.Controls.Add(this.cbQuality); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ForeColor = System.Drawing.Color.DarkGray; + this.Name = "MeshControlView"; + this.Size = new System.Drawing.Size(272, 509); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label lbMaxArea; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.Label lbMinAngle; + private System.Windows.Forms.Label label23; + private System.Windows.Forms.Label label9; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.Label label5; + private Controls.DarkSlider slMaxArea; + private Controls.DarkSlider slMinAngle; + private Controls.DarkCheckBox cbConformDel; + private Controls.DarkCheckBox cbConvex; + private Controls.DarkCheckBox cbQuality; + private Controls.DarkCheckBox cbSweepline; + private System.Windows.Forms.Label label1; + private Controls.DarkSlider slMaxAngle; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label lbMaxAngle; + } +} diff --git a/Triangle.NET/TestApp/Views/MeshControlView.cs b/src/Triangle.Viewer/Views/MeshControlView.cs similarity index 95% rename from Triangle.NET/TestApp/Views/MeshControlView.cs rename to src/Triangle.Viewer/Views/MeshControlView.cs index c6107fe..bf231da 100644 --- a/Triangle.NET/TestApp/Views/MeshControlView.cs +++ b/src/Triangle.Viewer/Views/MeshControlView.cs @@ -1,91 +1,91 @@ -using System; -using System.Windows.Forms; -using TriangleNet; -using TriangleNet.Geometry; - -namespace MeshExplorer.Views -{ - public partial class MeshControlView : UserControl, IView - { - public MeshControlView() - { - InitializeComponent(); - } - - public bool ParamQualityChecked - { - get { return cbQuality.Checked; } - } - - public bool ParamConvexChecked - { - get { return cbConvex.Checked; } - } - - public bool ParamConformDelChecked - { - get { return cbConformDel.Checked; } - } - - public bool ParamSweeplineChecked - { - get { return cbSweepline.Checked; } - } - - public int ParamMinAngleValue - { - get { return (slMinAngle.Value * 40) / 100; } - } - - public int ParamMaxAngleValue - { - get { return 80 + (100 - slMaxAngle.Value); } - } - - public double ParamMaxAreaValue - { - get { return slMaxArea.Value * 0.01; } - } - - private void slMinAngle_ValueChanging(object sender, EventArgs e) - { - // Between 0 and 40 (step 1) - int angle = (slMinAngle.Value * 40) / 100; - lbMinAngle.Text = angle.ToString(); - } - - private void slMaxAngle_ValueChanging(object sender, EventArgs e) - { - // Between 180 and 80 (step 1) - int angle = 80 + (100 - slMaxAngle.Value); - lbMaxAngle.Text = angle.ToString(); - } - - private void slMaxArea_ValueChanging(object sender, EventArgs e) - { - // Between 0 and 1 (step 0.01) - double area = slMaxArea.Value * 0.01; - lbMaxArea.Text = area.ToString(Util.Nfi); - } - - #region IView - - public void HandleNewInput(IPolygon geometry) - { - } - - public void HandleMeshImport(IPolygon geometry, Mesh mesh) - { - } - - public void HandleMeshUpdate(Mesh mesh) - { - } - - public void HandleMeshChange(Mesh mesh) - { - } - - #endregion - } -} +using System; +using System.Windows.Forms; +using TriangleNet; +using TriangleNet.Geometry; + +namespace MeshExplorer.Views +{ + public partial class MeshControlView : UserControl, IView + { + public MeshControlView() + { + InitializeComponent(); + } + + public bool ParamQualityChecked + { + get { return cbQuality.Checked; } + } + + public bool ParamConvexChecked + { + get { return cbConvex.Checked; } + } + + public bool ParamConformDelChecked + { + get { return cbConformDel.Checked; } + } + + public bool ParamSweeplineChecked + { + get { return cbSweepline.Checked; } + } + + public int ParamMinAngleValue + { + get { return (slMinAngle.Value * 40) / 100; } + } + + public int ParamMaxAngleValue + { + get { return 80 + (100 - slMaxAngle.Value); } + } + + public double ParamMaxAreaValue + { + get { return slMaxArea.Value * 0.01; } + } + + private void slMinAngle_ValueChanging(object sender, EventArgs e) + { + // Between 0 and 40 (step 1) + int angle = (slMinAngle.Value * 40) / 100; + lbMinAngle.Text = angle.ToString(); + } + + private void slMaxAngle_ValueChanging(object sender, EventArgs e) + { + // Between 180 and 80 (step 1) + int angle = 80 + (100 - slMaxAngle.Value); + lbMaxAngle.Text = angle.ToString(); + } + + private void slMaxArea_ValueChanging(object sender, EventArgs e) + { + // Between 0 and 1 (step 0.01) + double area = slMaxArea.Value * 0.01; + lbMaxArea.Text = area.ToString(Util.Nfi); + } + + #region IView + + public void HandleNewInput(IPolygon geometry) + { + } + + public void HandleMeshImport(IPolygon geometry, Mesh mesh) + { + } + + public void HandleMeshUpdate(Mesh mesh) + { + } + + public void HandleMeshChange(Mesh mesh) + { + } + + #endregion + } +} diff --git a/Triangle.NET/TestApp/Views/MeshControlView.resx b/src/Triangle.Viewer/Views/MeshControlView.resx similarity index 97% rename from Triangle.NET/TestApp/Views/MeshControlView.resx rename to src/Triangle.Viewer/Views/MeshControlView.resx index 29dcb1b..1af7de1 100644 --- a/Triangle.NET/TestApp/Views/MeshControlView.resx +++ b/src/Triangle.Viewer/Views/MeshControlView.resx @@ -1,120 +1,120 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + \ No newline at end of file diff --git a/Triangle.NET/TestApp/Views/StatisticView.Designer.cs b/src/Triangle.Viewer/Views/StatisticView.Designer.cs similarity index 97% rename from Triangle.NET/TestApp/Views/StatisticView.Designer.cs rename to src/Triangle.Viewer/Views/StatisticView.Designer.cs index c0a418e..44507d3 100644 --- a/Triangle.NET/TestApp/Views/StatisticView.Designer.cs +++ b/src/Triangle.Viewer/Views/StatisticView.Designer.cs @@ -1,483 +1,483 @@ -namespace MeshExplorer.Views -{ - partial class StatisticView - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Component Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.label20 = new System.Windows.Forms.Label(); - this.label4 = new System.Windows.Forms.Label(); - this.label3 = new System.Windows.Forms.Label(); - this.label2 = new System.Windows.Forms.Label(); - this.lbNumSeg = new System.Windows.Forms.Label(); - this.lbNumSeg2 = new System.Windows.Forms.Label(); - this.lbNumTri = new System.Windows.Forms.Label(); - this.lbNumTri2 = new System.Windows.Forms.Label(); - this.lbNumVert = new System.Windows.Forms.Label(); - this.lbNumVert2 = new System.Windows.Forms.Label(); - this.label32 = new System.Windows.Forms.Label(); - this.label13 = new System.Windows.Forms.Label(); - this.label31 = new System.Windows.Forms.Label(); - this.label12 = new System.Windows.Forms.Label(); - this.label16 = new System.Windows.Forms.Label(); - this.label29 = new System.Windows.Forms.Label(); - this.label14 = new System.Windows.Forms.Label(); - this.lbAngleMax = new System.Windows.Forms.Label(); - this.lbQualAspectAve = new System.Windows.Forms.Label(); - this.lbEdgeMax = new System.Windows.Forms.Label(); - this.lbQualAlphaAve = new System.Windows.Forms.Label(); - this.lbAreaMax = new System.Windows.Forms.Label(); - this.lbAngleMin = new System.Windows.Forms.Label(); - this.lbQualAspectMin = new System.Windows.Forms.Label(); - this.lbEdgeMin = new System.Windows.Forms.Label(); - this.lbQualAlphaMin = new System.Windows.Forms.Label(); - this.lbAreaMin = new System.Windows.Forms.Label(); - this.label22 = new System.Windows.Forms.Label(); - this.label10 = new System.Windows.Forms.Label(); - this.label17 = new System.Windows.Forms.Label(); - this.label21 = new System.Windows.Forms.Label(); - this.label11 = new System.Windows.Forms.Label(); - this.angleHistogram1 = new MeshExplorer.Controls.AngleHistogram(); - this.SuspendLayout(); - // - // label20 - // - this.label20.AutoSize = true; - this.label20.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label20.ForeColor = System.Drawing.Color.White; - this.label20.Location = new System.Drawing.Point(8, 17); - this.label20.Name = "label20"; - this.label20.Size = new System.Drawing.Size(39, 13); - this.label20.TabIndex = 59; - this.label20.Text = "Mesh:"; - // - // label4 - // - this.label4.AutoSize = true; - this.label4.ForeColor = System.Drawing.Color.DarkGray; - this.label4.Location = new System.Drawing.Point(8, 57); - this.label4.Name = "label4"; - this.label4.Size = new System.Drawing.Size(60, 13); - this.label4.TabIndex = 56; - this.label4.Text = "Segments:"; - // - // label3 - // - this.label3.AutoSize = true; - this.label3.ForeColor = System.Drawing.Color.DarkGray; - this.label3.Location = new System.Drawing.Point(8, 76); - this.label3.Name = "label3"; - this.label3.Size = new System.Drawing.Size(56, 13); - this.label3.TabIndex = 57; - this.label3.Text = "Triangles:"; - // - // label2 - // - this.label2.AutoSize = true; - this.label2.ForeColor = System.Drawing.Color.DarkGray; - this.label2.Location = new System.Drawing.Point(8, 38); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(50, 13); - this.label2.TabIndex = 58; - this.label2.Text = "Vertices:"; - // - // lbNumSeg - // - this.lbNumSeg.ForeColor = System.Drawing.Color.White; - this.lbNumSeg.Location = new System.Drawing.Point(98, 57); - this.lbNumSeg.Name = "lbNumSeg"; - this.lbNumSeg.Size = new System.Drawing.Size(70, 13); - this.lbNumSeg.TabIndex = 53; - this.lbNumSeg.Text = "-"; - this.lbNumSeg.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbNumSeg2 - // - this.lbNumSeg2.ForeColor = System.Drawing.Color.DarkGray; - this.lbNumSeg2.Location = new System.Drawing.Point(188, 57); - this.lbNumSeg2.Name = "lbNumSeg2"; - this.lbNumSeg2.Size = new System.Drawing.Size(70, 13); - this.lbNumSeg2.TabIndex = 52; - this.lbNumSeg2.Text = "-"; - this.lbNumSeg2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbNumTri - // - this.lbNumTri.ForeColor = System.Drawing.Color.White; - this.lbNumTri.Location = new System.Drawing.Point(98, 76); - this.lbNumTri.Name = "lbNumTri"; - this.lbNumTri.Size = new System.Drawing.Size(70, 13); - this.lbNumTri.TabIndex = 50; - this.lbNumTri.Text = "-"; - this.lbNumTri.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbNumTri2 - // - this.lbNumTri2.ForeColor = System.Drawing.Color.DarkGray; - this.lbNumTri2.Location = new System.Drawing.Point(188, 76); - this.lbNumTri2.Name = "lbNumTri2"; - this.lbNumTri2.Size = new System.Drawing.Size(70, 13); - this.lbNumTri2.TabIndex = 51; - this.lbNumTri2.Text = "-"; - this.lbNumTri2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbNumVert - // - this.lbNumVert.ForeColor = System.Drawing.Color.White; - this.lbNumVert.Location = new System.Drawing.Point(98, 38); - this.lbNumVert.Name = "lbNumVert"; - this.lbNumVert.Size = new System.Drawing.Size(70, 13); - this.lbNumVert.TabIndex = 54; - this.lbNumVert.Text = "-"; - this.lbNumVert.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbNumVert2 - // - this.lbNumVert2.ForeColor = System.Drawing.Color.DarkGray; - this.lbNumVert2.Location = new System.Drawing.Point(188, 38); - this.lbNumVert2.Name = "lbNumVert2"; - this.lbNumVert2.Size = new System.Drawing.Size(70, 13); - this.lbNumVert2.TabIndex = 55; - this.lbNumVert2.Text = "-"; - this.lbNumVert2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // label32 - // - this.label32.AutoSize = true; - this.label32.ForeColor = System.Drawing.Color.DarkGray; - this.label32.Location = new System.Drawing.Point(210, 213); - this.label32.Name = "label32"; - this.label32.Size = new System.Drawing.Size(48, 13); - this.label32.TabIndex = 41; - this.label32.Text = "Average"; - // - // label13 - // - this.label13.AutoSize = true; - this.label13.ForeColor = System.Drawing.Color.DarkGray; - this.label13.Location = new System.Drawing.Point(202, 114); - this.label13.Name = "label13"; - this.label13.Size = new System.Drawing.Size(56, 13); - this.label13.TabIndex = 42; - this.label13.Text = "Maximum"; - // - // label31 - // - this.label31.AutoSize = true; - this.label31.ForeColor = System.Drawing.Color.DarkGray; - this.label31.Location = new System.Drawing.Point(112, 213); - this.label31.Name = "label31"; - this.label31.Size = new System.Drawing.Size(55, 13); - this.label31.TabIndex = 44; - this.label31.Text = "Minimum"; - // - // label12 - // - this.label12.AutoSize = true; - this.label12.ForeColor = System.Drawing.Color.DarkGray; - this.label12.Location = new System.Drawing.Point(113, 114); - this.label12.Name = "label12"; - this.label12.Size = new System.Drawing.Size(55, 13); - this.label12.TabIndex = 43; - this.label12.Text = "Minimum"; - // - // label16 - // - this.label16.AutoSize = true; - this.label16.ForeColor = System.Drawing.Color.DarkGray; - this.label16.Location = new System.Drawing.Point(8, 173); - this.label16.Name = "label16"; - this.label16.Size = new System.Drawing.Size(40, 13); - this.label16.TabIndex = 40; - this.label16.Text = "Angle:"; - // - // label29 - // - this.label29.AutoSize = true; - this.label29.ForeColor = System.Drawing.Color.DarkGray; - this.label29.Location = new System.Drawing.Point(8, 253); - this.label29.Name = "label29"; - this.label29.Size = new System.Drawing.Size(71, 13); - this.label29.TabIndex = 47; - this.label29.Text = "Aspect ratio:"; - // - // label14 - // - this.label14.AutoSize = true; - this.label14.ForeColor = System.Drawing.Color.DarkGray; - this.label14.Location = new System.Drawing.Point(8, 154); - this.label14.Name = "label14"; - this.label14.Size = new System.Drawing.Size(73, 13); - this.label14.TabIndex = 48; - this.label14.Text = "Edge length:"; - // - // lbAngleMax - // - this.lbAngleMax.ForeColor = System.Drawing.Color.White; - this.lbAngleMax.Location = new System.Drawing.Point(182, 173); - this.lbAngleMax.Name = "lbAngleMax"; - this.lbAngleMax.Size = new System.Drawing.Size(76, 13); - this.lbAngleMax.TabIndex = 49; - this.lbAngleMax.Text = "-"; - this.lbAngleMax.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbQualAspectAve - // - this.lbQualAspectAve.ForeColor = System.Drawing.Color.White; - this.lbQualAspectAve.Location = new System.Drawing.Point(182, 253); - this.lbQualAspectAve.Name = "lbQualAspectAve"; - this.lbQualAspectAve.Size = new System.Drawing.Size(76, 13); - this.lbQualAspectAve.TabIndex = 46; - this.lbQualAspectAve.Text = "-"; - this.lbQualAspectAve.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbEdgeMax - // - this.lbEdgeMax.ForeColor = System.Drawing.Color.White; - this.lbEdgeMax.Location = new System.Drawing.Point(182, 154); - this.lbEdgeMax.Name = "lbEdgeMax"; - this.lbEdgeMax.Size = new System.Drawing.Size(76, 13); - this.lbEdgeMax.TabIndex = 45; - this.lbEdgeMax.Text = "-"; - this.lbEdgeMax.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbQualAlphaAve - // - this.lbQualAlphaAve.ForeColor = System.Drawing.Color.White; - this.lbQualAlphaAve.Location = new System.Drawing.Point(182, 234); - this.lbQualAlphaAve.Name = "lbQualAlphaAve"; - this.lbQualAlphaAve.Size = new System.Drawing.Size(76, 13); - this.lbQualAlphaAve.TabIndex = 30; - this.lbQualAlphaAve.Text = "-"; - this.lbQualAlphaAve.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbAreaMax - // - this.lbAreaMax.ForeColor = System.Drawing.Color.White; - this.lbAreaMax.Location = new System.Drawing.Point(182, 135); - this.lbAreaMax.Name = "lbAreaMax"; - this.lbAreaMax.Size = new System.Drawing.Size(76, 13); - this.lbAreaMax.TabIndex = 31; - this.lbAreaMax.Text = "-"; - this.lbAreaMax.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbAngleMin - // - this.lbAngleMin.ForeColor = System.Drawing.Color.White; - this.lbAngleMin.Location = new System.Drawing.Point(100, 173); - this.lbAngleMin.Name = "lbAngleMin"; - this.lbAngleMin.Size = new System.Drawing.Size(68, 13); - this.lbAngleMin.TabIndex = 32; - this.lbAngleMin.Text = "-"; - this.lbAngleMin.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbQualAspectMin - // - this.lbQualAspectMin.ForeColor = System.Drawing.Color.White; - this.lbQualAspectMin.Location = new System.Drawing.Point(100, 253); - this.lbQualAspectMin.Name = "lbQualAspectMin"; - this.lbQualAspectMin.Size = new System.Drawing.Size(68, 13); - this.lbQualAspectMin.TabIndex = 29; - this.lbQualAspectMin.Text = "-"; - this.lbQualAspectMin.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbEdgeMin - // - this.lbEdgeMin.ForeColor = System.Drawing.Color.White; - this.lbEdgeMin.Location = new System.Drawing.Point(100, 154); - this.lbEdgeMin.Name = "lbEdgeMin"; - this.lbEdgeMin.Size = new System.Drawing.Size(68, 13); - this.lbEdgeMin.TabIndex = 28; - this.lbEdgeMin.Text = "-"; - this.lbEdgeMin.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbQualAlphaMin - // - this.lbQualAlphaMin.ForeColor = System.Drawing.Color.White; - this.lbQualAlphaMin.Location = new System.Drawing.Point(100, 234); - this.lbQualAlphaMin.Name = "lbQualAlphaMin"; - this.lbQualAlphaMin.Size = new System.Drawing.Size(68, 13); - this.lbQualAlphaMin.TabIndex = 37; - this.lbQualAlphaMin.Text = "-"; - this.lbQualAlphaMin.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // lbAreaMin - // - this.lbAreaMin.ForeColor = System.Drawing.Color.White; - this.lbAreaMin.Location = new System.Drawing.Point(100, 135); - this.lbAreaMin.Name = "lbAreaMin"; - this.lbAreaMin.Size = new System.Drawing.Size(68, 13); - this.lbAreaMin.TabIndex = 36; - this.lbAreaMin.Text = "-"; - this.lbAreaMin.TextAlign = System.Drawing.ContentAlignment.MiddleRight; - // - // label22 - // - this.label22.AutoSize = true; - this.label22.ForeColor = System.Drawing.Color.DarkGray; - this.label22.Location = new System.Drawing.Point(8, 234); - this.label22.Name = "label22"; - this.label22.Size = new System.Drawing.Size(65, 13); - this.label22.TabIndex = 39; - this.label22.Text = "Min. angle:"; - // - // label10 - // - this.label10.AutoSize = true; - this.label10.ForeColor = System.Drawing.Color.DarkGray; - this.label10.Location = new System.Drawing.Point(8, 135); - this.label10.Name = "label10"; - this.label10.Size = new System.Drawing.Size(76, 13); - this.label10.TabIndex = 38; - this.label10.Text = "Triangle area:"; - // - // label17 - // - this.label17.AutoSize = true; - this.label17.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label17.ForeColor = System.Drawing.Color.White; - this.label17.Location = new System.Drawing.Point(8, 290); - this.label17.Name = "label17"; - this.label17.Size = new System.Drawing.Size(97, 13); - this.label17.TabIndex = 33; - this.label17.Text = "Angle histogram:"; - // - // label21 - // - this.label21.AutoSize = true; - this.label21.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label21.ForeColor = System.Drawing.Color.White; - this.label21.Location = new System.Drawing.Point(8, 213); - this.label21.Name = "label21"; - this.label21.Size = new System.Drawing.Size(47, 13); - this.label21.TabIndex = 35; - this.label21.Text = "Quality:"; - // - // label11 - // - this.label11.AutoSize = true; - this.label11.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label11.ForeColor = System.Drawing.Color.White; - this.label11.Location = new System.Drawing.Point(8, 114); - this.label11.Name = "label11"; - this.label11.Size = new System.Drawing.Size(50, 13); - this.label11.TabIndex = 34; - this.label11.Text = "Statistic:"; - // - // angleHistogram1 - // - this.angleHistogram1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76))))); - this.angleHistogram1.Font = new System.Drawing.Font("Segoe UI", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.angleHistogram1.Location = new System.Drawing.Point(6, 308); - this.angleHistogram1.Name = "angleHistogram1"; - this.angleHistogram1.Size = new System.Drawing.Size(260, 195); - this.angleHistogram1.TabIndex = 27; - this.angleHistogram1.Text = "angleHistogram1"; - // - // StatisticView - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.DimGray; - this.Controls.Add(this.label20); - this.Controls.Add(this.label4); - this.Controls.Add(this.label3); - this.Controls.Add(this.label2); - this.Controls.Add(this.lbNumSeg); - this.Controls.Add(this.lbNumSeg2); - this.Controls.Add(this.lbNumTri); - this.Controls.Add(this.lbNumTri2); - this.Controls.Add(this.lbNumVert); - this.Controls.Add(this.lbNumVert2); - this.Controls.Add(this.label32); - this.Controls.Add(this.label13); - this.Controls.Add(this.label31); - this.Controls.Add(this.label12); - this.Controls.Add(this.label16); - this.Controls.Add(this.label29); - this.Controls.Add(this.label14); - this.Controls.Add(this.lbAngleMax); - this.Controls.Add(this.lbQualAspectAve); - this.Controls.Add(this.lbEdgeMax); - this.Controls.Add(this.lbQualAlphaAve); - this.Controls.Add(this.lbAreaMax); - this.Controls.Add(this.lbAngleMin); - this.Controls.Add(this.lbQualAspectMin); - this.Controls.Add(this.lbEdgeMin); - this.Controls.Add(this.lbQualAlphaMin); - this.Controls.Add(this.lbAreaMin); - this.Controls.Add(this.label22); - this.Controls.Add(this.label10); - this.Controls.Add(this.label17); - this.Controls.Add(this.label21); - this.Controls.Add(this.label11); - this.Controls.Add(this.angleHistogram1); - this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ForeColor = System.Drawing.Color.DarkGray; - this.Name = "StatisticView"; - this.Size = new System.Drawing.Size(272, 509); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.Label label20; - private System.Windows.Forms.Label label4; - private System.Windows.Forms.Label label3; - private System.Windows.Forms.Label label2; - private System.Windows.Forms.Label lbNumSeg; - private System.Windows.Forms.Label lbNumSeg2; - private System.Windows.Forms.Label lbNumTri; - private System.Windows.Forms.Label lbNumTri2; - private System.Windows.Forms.Label lbNumVert; - private System.Windows.Forms.Label lbNumVert2; - private System.Windows.Forms.Label label32; - private System.Windows.Forms.Label label13; - private System.Windows.Forms.Label label31; - private System.Windows.Forms.Label label12; - private System.Windows.Forms.Label label16; - private System.Windows.Forms.Label label29; - private System.Windows.Forms.Label label14; - private System.Windows.Forms.Label lbAngleMax; - private System.Windows.Forms.Label lbQualAspectAve; - private System.Windows.Forms.Label lbEdgeMax; - private System.Windows.Forms.Label lbQualAlphaAve; - private System.Windows.Forms.Label lbAreaMax; - private System.Windows.Forms.Label lbAngleMin; - private System.Windows.Forms.Label lbQualAspectMin; - private System.Windows.Forms.Label lbEdgeMin; - private System.Windows.Forms.Label lbQualAlphaMin; - private System.Windows.Forms.Label lbAreaMin; - private System.Windows.Forms.Label label22; - private System.Windows.Forms.Label label10; - private System.Windows.Forms.Label label17; - private System.Windows.Forms.Label label21; - private System.Windows.Forms.Label label11; - private Controls.AngleHistogram angleHistogram1; - } -} +namespace MeshExplorer.Views +{ + partial class StatisticView + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label20 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.lbNumSeg = new System.Windows.Forms.Label(); + this.lbNumSeg2 = new System.Windows.Forms.Label(); + this.lbNumTri = new System.Windows.Forms.Label(); + this.lbNumTri2 = new System.Windows.Forms.Label(); + this.lbNumVert = new System.Windows.Forms.Label(); + this.lbNumVert2 = new System.Windows.Forms.Label(); + this.label32 = new System.Windows.Forms.Label(); + this.label13 = new System.Windows.Forms.Label(); + this.label31 = new System.Windows.Forms.Label(); + this.label12 = new System.Windows.Forms.Label(); + this.label16 = new System.Windows.Forms.Label(); + this.label29 = new System.Windows.Forms.Label(); + this.label14 = new System.Windows.Forms.Label(); + this.lbAngleMax = new System.Windows.Forms.Label(); + this.lbQualAspectAve = new System.Windows.Forms.Label(); + this.lbEdgeMax = new System.Windows.Forms.Label(); + this.lbQualAlphaAve = new System.Windows.Forms.Label(); + this.lbAreaMax = new System.Windows.Forms.Label(); + this.lbAngleMin = new System.Windows.Forms.Label(); + this.lbQualAspectMin = new System.Windows.Forms.Label(); + this.lbEdgeMin = new System.Windows.Forms.Label(); + this.lbQualAlphaMin = new System.Windows.Forms.Label(); + this.lbAreaMin = new System.Windows.Forms.Label(); + this.label22 = new System.Windows.Forms.Label(); + this.label10 = new System.Windows.Forms.Label(); + this.label17 = new System.Windows.Forms.Label(); + this.label21 = new System.Windows.Forms.Label(); + this.label11 = new System.Windows.Forms.Label(); + this.angleHistogram1 = new MeshExplorer.Controls.AngleHistogram(); + this.SuspendLayout(); + // + // label20 + // + this.label20.AutoSize = true; + this.label20.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label20.ForeColor = System.Drawing.Color.White; + this.label20.Location = new System.Drawing.Point(8, 17); + this.label20.Name = "label20"; + this.label20.Size = new System.Drawing.Size(39, 13); + this.label20.TabIndex = 59; + this.label20.Text = "Mesh:"; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.ForeColor = System.Drawing.Color.DarkGray; + this.label4.Location = new System.Drawing.Point(8, 57); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(60, 13); + this.label4.TabIndex = 56; + this.label4.Text = "Segments:"; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.ForeColor = System.Drawing.Color.DarkGray; + this.label3.Location = new System.Drawing.Point(8, 76); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(56, 13); + this.label3.TabIndex = 57; + this.label3.Text = "Triangles:"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.ForeColor = System.Drawing.Color.DarkGray; + this.label2.Location = new System.Drawing.Point(8, 38); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(50, 13); + this.label2.TabIndex = 58; + this.label2.Text = "Vertices:"; + // + // lbNumSeg + // + this.lbNumSeg.ForeColor = System.Drawing.Color.White; + this.lbNumSeg.Location = new System.Drawing.Point(98, 57); + this.lbNumSeg.Name = "lbNumSeg"; + this.lbNumSeg.Size = new System.Drawing.Size(70, 13); + this.lbNumSeg.TabIndex = 53; + this.lbNumSeg.Text = "-"; + this.lbNumSeg.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbNumSeg2 + // + this.lbNumSeg2.ForeColor = System.Drawing.Color.DarkGray; + this.lbNumSeg2.Location = new System.Drawing.Point(188, 57); + this.lbNumSeg2.Name = "lbNumSeg2"; + this.lbNumSeg2.Size = new System.Drawing.Size(70, 13); + this.lbNumSeg2.TabIndex = 52; + this.lbNumSeg2.Text = "-"; + this.lbNumSeg2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbNumTri + // + this.lbNumTri.ForeColor = System.Drawing.Color.White; + this.lbNumTri.Location = new System.Drawing.Point(98, 76); + this.lbNumTri.Name = "lbNumTri"; + this.lbNumTri.Size = new System.Drawing.Size(70, 13); + this.lbNumTri.TabIndex = 50; + this.lbNumTri.Text = "-"; + this.lbNumTri.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbNumTri2 + // + this.lbNumTri2.ForeColor = System.Drawing.Color.DarkGray; + this.lbNumTri2.Location = new System.Drawing.Point(188, 76); + this.lbNumTri2.Name = "lbNumTri2"; + this.lbNumTri2.Size = new System.Drawing.Size(70, 13); + this.lbNumTri2.TabIndex = 51; + this.lbNumTri2.Text = "-"; + this.lbNumTri2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbNumVert + // + this.lbNumVert.ForeColor = System.Drawing.Color.White; + this.lbNumVert.Location = new System.Drawing.Point(98, 38); + this.lbNumVert.Name = "lbNumVert"; + this.lbNumVert.Size = new System.Drawing.Size(70, 13); + this.lbNumVert.TabIndex = 54; + this.lbNumVert.Text = "-"; + this.lbNumVert.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbNumVert2 + // + this.lbNumVert2.ForeColor = System.Drawing.Color.DarkGray; + this.lbNumVert2.Location = new System.Drawing.Point(188, 38); + this.lbNumVert2.Name = "lbNumVert2"; + this.lbNumVert2.Size = new System.Drawing.Size(70, 13); + this.lbNumVert2.TabIndex = 55; + this.lbNumVert2.Text = "-"; + this.lbNumVert2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // label32 + // + this.label32.AutoSize = true; + this.label32.ForeColor = System.Drawing.Color.DarkGray; + this.label32.Location = new System.Drawing.Point(210, 213); + this.label32.Name = "label32"; + this.label32.Size = new System.Drawing.Size(48, 13); + this.label32.TabIndex = 41; + this.label32.Text = "Average"; + // + // label13 + // + this.label13.AutoSize = true; + this.label13.ForeColor = System.Drawing.Color.DarkGray; + this.label13.Location = new System.Drawing.Point(202, 114); + this.label13.Name = "label13"; + this.label13.Size = new System.Drawing.Size(56, 13); + this.label13.TabIndex = 42; + this.label13.Text = "Maximum"; + // + // label31 + // + this.label31.AutoSize = true; + this.label31.ForeColor = System.Drawing.Color.DarkGray; + this.label31.Location = new System.Drawing.Point(112, 213); + this.label31.Name = "label31"; + this.label31.Size = new System.Drawing.Size(55, 13); + this.label31.TabIndex = 44; + this.label31.Text = "Minimum"; + // + // label12 + // + this.label12.AutoSize = true; + this.label12.ForeColor = System.Drawing.Color.DarkGray; + this.label12.Location = new System.Drawing.Point(113, 114); + this.label12.Name = "label12"; + this.label12.Size = new System.Drawing.Size(55, 13); + this.label12.TabIndex = 43; + this.label12.Text = "Minimum"; + // + // label16 + // + this.label16.AutoSize = true; + this.label16.ForeColor = System.Drawing.Color.DarkGray; + this.label16.Location = new System.Drawing.Point(8, 173); + this.label16.Name = "label16"; + this.label16.Size = new System.Drawing.Size(40, 13); + this.label16.TabIndex = 40; + this.label16.Text = "Angle:"; + // + // label29 + // + this.label29.AutoSize = true; + this.label29.ForeColor = System.Drawing.Color.DarkGray; + this.label29.Location = new System.Drawing.Point(8, 253); + this.label29.Name = "label29"; + this.label29.Size = new System.Drawing.Size(71, 13); + this.label29.TabIndex = 47; + this.label29.Text = "Aspect ratio:"; + // + // label14 + // + this.label14.AutoSize = true; + this.label14.ForeColor = System.Drawing.Color.DarkGray; + this.label14.Location = new System.Drawing.Point(8, 154); + this.label14.Name = "label14"; + this.label14.Size = new System.Drawing.Size(73, 13); + this.label14.TabIndex = 48; + this.label14.Text = "Edge length:"; + // + // lbAngleMax + // + this.lbAngleMax.ForeColor = System.Drawing.Color.White; + this.lbAngleMax.Location = new System.Drawing.Point(182, 173); + this.lbAngleMax.Name = "lbAngleMax"; + this.lbAngleMax.Size = new System.Drawing.Size(76, 13); + this.lbAngleMax.TabIndex = 49; + this.lbAngleMax.Text = "-"; + this.lbAngleMax.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbQualAspectAve + // + this.lbQualAspectAve.ForeColor = System.Drawing.Color.White; + this.lbQualAspectAve.Location = new System.Drawing.Point(182, 253); + this.lbQualAspectAve.Name = "lbQualAspectAve"; + this.lbQualAspectAve.Size = new System.Drawing.Size(76, 13); + this.lbQualAspectAve.TabIndex = 46; + this.lbQualAspectAve.Text = "-"; + this.lbQualAspectAve.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbEdgeMax + // + this.lbEdgeMax.ForeColor = System.Drawing.Color.White; + this.lbEdgeMax.Location = new System.Drawing.Point(182, 154); + this.lbEdgeMax.Name = "lbEdgeMax"; + this.lbEdgeMax.Size = new System.Drawing.Size(76, 13); + this.lbEdgeMax.TabIndex = 45; + this.lbEdgeMax.Text = "-"; + this.lbEdgeMax.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbQualAlphaAve + // + this.lbQualAlphaAve.ForeColor = System.Drawing.Color.White; + this.lbQualAlphaAve.Location = new System.Drawing.Point(182, 234); + this.lbQualAlphaAve.Name = "lbQualAlphaAve"; + this.lbQualAlphaAve.Size = new System.Drawing.Size(76, 13); + this.lbQualAlphaAve.TabIndex = 30; + this.lbQualAlphaAve.Text = "-"; + this.lbQualAlphaAve.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbAreaMax + // + this.lbAreaMax.ForeColor = System.Drawing.Color.White; + this.lbAreaMax.Location = new System.Drawing.Point(182, 135); + this.lbAreaMax.Name = "lbAreaMax"; + this.lbAreaMax.Size = new System.Drawing.Size(76, 13); + this.lbAreaMax.TabIndex = 31; + this.lbAreaMax.Text = "-"; + this.lbAreaMax.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbAngleMin + // + this.lbAngleMin.ForeColor = System.Drawing.Color.White; + this.lbAngleMin.Location = new System.Drawing.Point(100, 173); + this.lbAngleMin.Name = "lbAngleMin"; + this.lbAngleMin.Size = new System.Drawing.Size(68, 13); + this.lbAngleMin.TabIndex = 32; + this.lbAngleMin.Text = "-"; + this.lbAngleMin.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbQualAspectMin + // + this.lbQualAspectMin.ForeColor = System.Drawing.Color.White; + this.lbQualAspectMin.Location = new System.Drawing.Point(100, 253); + this.lbQualAspectMin.Name = "lbQualAspectMin"; + this.lbQualAspectMin.Size = new System.Drawing.Size(68, 13); + this.lbQualAspectMin.TabIndex = 29; + this.lbQualAspectMin.Text = "-"; + this.lbQualAspectMin.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbEdgeMin + // + this.lbEdgeMin.ForeColor = System.Drawing.Color.White; + this.lbEdgeMin.Location = new System.Drawing.Point(100, 154); + this.lbEdgeMin.Name = "lbEdgeMin"; + this.lbEdgeMin.Size = new System.Drawing.Size(68, 13); + this.lbEdgeMin.TabIndex = 28; + this.lbEdgeMin.Text = "-"; + this.lbEdgeMin.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbQualAlphaMin + // + this.lbQualAlphaMin.ForeColor = System.Drawing.Color.White; + this.lbQualAlphaMin.Location = new System.Drawing.Point(100, 234); + this.lbQualAlphaMin.Name = "lbQualAlphaMin"; + this.lbQualAlphaMin.Size = new System.Drawing.Size(68, 13); + this.lbQualAlphaMin.TabIndex = 37; + this.lbQualAlphaMin.Text = "-"; + this.lbQualAlphaMin.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbAreaMin + // + this.lbAreaMin.ForeColor = System.Drawing.Color.White; + this.lbAreaMin.Location = new System.Drawing.Point(100, 135); + this.lbAreaMin.Name = "lbAreaMin"; + this.lbAreaMin.Size = new System.Drawing.Size(68, 13); + this.lbAreaMin.TabIndex = 36; + this.lbAreaMin.Text = "-"; + this.lbAreaMin.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // label22 + // + this.label22.AutoSize = true; + this.label22.ForeColor = System.Drawing.Color.DarkGray; + this.label22.Location = new System.Drawing.Point(8, 234); + this.label22.Name = "label22"; + this.label22.Size = new System.Drawing.Size(65, 13); + this.label22.TabIndex = 39; + this.label22.Text = "Min. angle:"; + // + // label10 + // + this.label10.AutoSize = true; + this.label10.ForeColor = System.Drawing.Color.DarkGray; + this.label10.Location = new System.Drawing.Point(8, 135); + this.label10.Name = "label10"; + this.label10.Size = new System.Drawing.Size(76, 13); + this.label10.TabIndex = 38; + this.label10.Text = "Triangle area:"; + // + // label17 + // + this.label17.AutoSize = true; + this.label17.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label17.ForeColor = System.Drawing.Color.White; + this.label17.Location = new System.Drawing.Point(8, 290); + this.label17.Name = "label17"; + this.label17.Size = new System.Drawing.Size(97, 13); + this.label17.TabIndex = 33; + this.label17.Text = "Angle histogram:"; + // + // label21 + // + this.label21.AutoSize = true; + this.label21.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label21.ForeColor = System.Drawing.Color.White; + this.label21.Location = new System.Drawing.Point(8, 213); + this.label21.Name = "label21"; + this.label21.Size = new System.Drawing.Size(47, 13); + this.label21.TabIndex = 35; + this.label21.Text = "Quality:"; + // + // label11 + // + this.label11.AutoSize = true; + this.label11.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label11.ForeColor = System.Drawing.Color.White; + this.label11.Location = new System.Drawing.Point(8, 114); + this.label11.Name = "label11"; + this.label11.Size = new System.Drawing.Size(50, 13); + this.label11.TabIndex = 34; + this.label11.Text = "Statistic:"; + // + // angleHistogram1 + // + this.angleHistogram1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76))))); + this.angleHistogram1.Font = new System.Drawing.Font("Segoe UI", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.angleHistogram1.Location = new System.Drawing.Point(6, 308); + this.angleHistogram1.Name = "angleHistogram1"; + this.angleHistogram1.Size = new System.Drawing.Size(260, 195); + this.angleHistogram1.TabIndex = 27; + this.angleHistogram1.Text = "angleHistogram1"; + // + // StatisticView + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.DimGray; + this.Controls.Add(this.label20); + this.Controls.Add(this.label4); + this.Controls.Add(this.label3); + this.Controls.Add(this.label2); + this.Controls.Add(this.lbNumSeg); + this.Controls.Add(this.lbNumSeg2); + this.Controls.Add(this.lbNumTri); + this.Controls.Add(this.lbNumTri2); + this.Controls.Add(this.lbNumVert); + this.Controls.Add(this.lbNumVert2); + this.Controls.Add(this.label32); + this.Controls.Add(this.label13); + this.Controls.Add(this.label31); + this.Controls.Add(this.label12); + this.Controls.Add(this.label16); + this.Controls.Add(this.label29); + this.Controls.Add(this.label14); + this.Controls.Add(this.lbAngleMax); + this.Controls.Add(this.lbQualAspectAve); + this.Controls.Add(this.lbEdgeMax); + this.Controls.Add(this.lbQualAlphaAve); + this.Controls.Add(this.lbAreaMax); + this.Controls.Add(this.lbAngleMin); + this.Controls.Add(this.lbQualAspectMin); + this.Controls.Add(this.lbEdgeMin); + this.Controls.Add(this.lbQualAlphaMin); + this.Controls.Add(this.lbAreaMin); + this.Controls.Add(this.label22); + this.Controls.Add(this.label10); + this.Controls.Add(this.label17); + this.Controls.Add(this.label21); + this.Controls.Add(this.label11); + this.Controls.Add(this.angleHistogram1); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ForeColor = System.Drawing.Color.DarkGray; + this.Name = "StatisticView"; + this.Size = new System.Drawing.Size(272, 509); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label20; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label lbNumSeg; + private System.Windows.Forms.Label lbNumSeg2; + private System.Windows.Forms.Label lbNumTri; + private System.Windows.Forms.Label lbNumTri2; + private System.Windows.Forms.Label lbNumVert; + private System.Windows.Forms.Label lbNumVert2; + private System.Windows.Forms.Label label32; + private System.Windows.Forms.Label label13; + private System.Windows.Forms.Label label31; + private System.Windows.Forms.Label label12; + private System.Windows.Forms.Label label16; + private System.Windows.Forms.Label label29; + private System.Windows.Forms.Label label14; + private System.Windows.Forms.Label lbAngleMax; + private System.Windows.Forms.Label lbQualAspectAve; + private System.Windows.Forms.Label lbEdgeMax; + private System.Windows.Forms.Label lbQualAlphaAve; + private System.Windows.Forms.Label lbAreaMax; + private System.Windows.Forms.Label lbAngleMin; + private System.Windows.Forms.Label lbQualAspectMin; + private System.Windows.Forms.Label lbEdgeMin; + private System.Windows.Forms.Label lbQualAlphaMin; + private System.Windows.Forms.Label lbAreaMin; + private System.Windows.Forms.Label label22; + private System.Windows.Forms.Label label10; + private System.Windows.Forms.Label label17; + private System.Windows.Forms.Label label21; + private System.Windows.Forms.Label label11; + private Controls.AngleHistogram angleHistogram1; + } +} diff --git a/Triangle.NET/TestApp/Views/StatisticView.cs b/src/Triangle.Viewer/Views/StatisticView.cs similarity index 96% rename from Triangle.NET/TestApp/Views/StatisticView.cs rename to src/Triangle.Viewer/Views/StatisticView.cs index f48c9fc..68fe871 100644 --- a/Triangle.NET/TestApp/Views/StatisticView.cs +++ b/src/Triangle.Viewer/Views/StatisticView.cs @@ -1,109 +1,109 @@ -using System.Linq; -using System.Windows.Forms; -using TriangleNet; -using TriangleNet.Geometry; -using TriangleNet.Tools; - -namespace MeshExplorer.Views -{ - public partial class StatisticView : UserControl, IView - { - Statistic statistic = new Statistic(); - QualityMeasure quality; - - public Statistic Statistic - { - get { return statistic; } - } - - public StatisticView() - { - InitializeComponent(); - } - - public void UpdateStatistic(Mesh mesh) - { - statistic.Update(mesh, 10); - } - - #region IView - - public void HandleNewInput(IPolygon geometry) - { - // Reset labels - lbNumVert2.Text = "-"; - lbNumTri2.Text = "-"; - lbNumSeg2.Text = "-"; - - lbNumVert.Text = geometry.Points.Count.ToString(); - lbNumSeg.Text = geometry.Segments.Count().ToString(); - lbNumTri.Text = "0"; - - // Statistics labels - lbAreaMin.Text = "-"; - lbAreaMax.Text = "-"; - lbEdgeMin.Text = "-"; - lbEdgeMax.Text = "-"; - lbAngleMin.Text = "-"; - lbAngleMax.Text = "-"; - - // Quality labels - lbQualAlphaMin.Text = "-"; - lbQualAlphaAve.Text = "-"; - lbQualAspectMin.Text = "-"; - lbQualAspectAve.Text = "-"; - - angleHistogram1.SetData(null, null); - } - - public void HandleMeshImport(IPolygon geometry, Mesh mesh) - { - // Previous mesh stats - lbNumVert2.Text = "-"; - lbNumTri2.Text = "-"; - lbNumSeg2.Text = "-"; - } - - public void HandleMeshUpdate(Mesh mesh) - { - // Previous mesh stats - lbNumVert2.Text = lbNumVert.Text; - lbNumTri2.Text = lbNumTri.Text; - lbNumSeg2.Text = lbNumSeg.Text; - } - - public void HandleMeshChange(Mesh mesh) - { - // New mesh stats - lbNumVert.Text = mesh.Vertices.Count.ToString(); - lbNumSeg.Text = mesh.Segments.Count.ToString(); - lbNumTri.Text = mesh.Triangles.Count.ToString(); - - // Update statistics tab - angleHistogram1.SetData(statistic.MinAngleHistogram, statistic.MaxAngleHistogram); - - lbAreaMin.Text = Util.DoubleToString(statistic.SmallestArea); - lbAreaMax.Text = Util.DoubleToString(statistic.LargestArea); - lbEdgeMin.Text = Util.DoubleToString(statistic.ShortestEdge); - lbEdgeMax.Text = Util.DoubleToString(statistic.LongestEdge); - lbAngleMin.Text = Util.AngleToString(statistic.SmallestAngle); - lbAngleMax.Text = Util.AngleToString(statistic.LargestAngle); - - // Update quality - if (quality == null) - { - quality = new QualityMeasure(); - } - - quality.Update(mesh); - - lbQualAlphaMin.Text = Util.DoubleToString(quality.AlphaMinimum); - lbQualAlphaAve.Text = Util.DoubleToString(quality.AlphaAverage); - - lbQualAspectMin.Text = Util.DoubleToString(quality.Q_Minimum); - lbQualAspectAve.Text = Util.DoubleToString(quality.Q_Average); - } - - #endregion - } -} +using System.Linq; +using System.Windows.Forms; +using TriangleNet; +using TriangleNet.Geometry; +using TriangleNet.Tools; + +namespace MeshExplorer.Views +{ + public partial class StatisticView : UserControl, IView + { + Statistic statistic = new Statistic(); + QualityMeasure quality; + + public Statistic Statistic + { + get { return statistic; } + } + + public StatisticView() + { + InitializeComponent(); + } + + public void UpdateStatistic(Mesh mesh) + { + statistic.Update(mesh, 10); + } + + #region IView + + public void HandleNewInput(IPolygon geometry) + { + // Reset labels + lbNumVert2.Text = "-"; + lbNumTri2.Text = "-"; + lbNumSeg2.Text = "-"; + + lbNumVert.Text = geometry.Points.Count.ToString(); + lbNumSeg.Text = geometry.Segments.Count().ToString(); + lbNumTri.Text = "0"; + + // Statistics labels + lbAreaMin.Text = "-"; + lbAreaMax.Text = "-"; + lbEdgeMin.Text = "-"; + lbEdgeMax.Text = "-"; + lbAngleMin.Text = "-"; + lbAngleMax.Text = "-"; + + // Quality labels + lbQualAlphaMin.Text = "-"; + lbQualAlphaAve.Text = "-"; + lbQualAspectMin.Text = "-"; + lbQualAspectAve.Text = "-"; + + angleHistogram1.SetData(null, null); + } + + public void HandleMeshImport(IPolygon geometry, Mesh mesh) + { + // Previous mesh stats + lbNumVert2.Text = "-"; + lbNumTri2.Text = "-"; + lbNumSeg2.Text = "-"; + } + + public void HandleMeshUpdate(Mesh mesh) + { + // Previous mesh stats + lbNumVert2.Text = lbNumVert.Text; + lbNumTri2.Text = lbNumTri.Text; + lbNumSeg2.Text = lbNumSeg.Text; + } + + public void HandleMeshChange(Mesh mesh) + { + // New mesh stats + lbNumVert.Text = mesh.Vertices.Count.ToString(); + lbNumSeg.Text = mesh.Segments.Count.ToString(); + lbNumTri.Text = mesh.Triangles.Count.ToString(); + + // Update statistics tab + angleHistogram1.SetData(statistic.MinAngleHistogram, statistic.MaxAngleHistogram); + + lbAreaMin.Text = Util.DoubleToString(statistic.SmallestArea); + lbAreaMax.Text = Util.DoubleToString(statistic.LargestArea); + lbEdgeMin.Text = Util.DoubleToString(statistic.ShortestEdge); + lbEdgeMax.Text = Util.DoubleToString(statistic.LongestEdge); + lbAngleMin.Text = Util.AngleToString(statistic.SmallestAngle); + lbAngleMax.Text = Util.AngleToString(statistic.LargestAngle); + + // Update quality + if (quality == null) + { + quality = new QualityMeasure(); + } + + quality.Update(mesh); + + lbQualAlphaMin.Text = Util.DoubleToString(quality.AlphaMinimum); + lbQualAlphaAve.Text = Util.DoubleToString(quality.AlphaAverage); + + lbQualAspectMin.Text = Util.DoubleToString(quality.Q_Minimum); + lbQualAspectAve.Text = Util.DoubleToString(quality.Q_Average); + } + + #endregion + } +} diff --git a/Triangle.NET/TestApp/Views/StatisticView.resx b/src/Triangle.Viewer/Views/StatisticView.resx similarity index 97% rename from Triangle.NET/TestApp/Views/StatisticView.resx rename to src/Triangle.Viewer/Views/StatisticView.resx index 29dcb1b..1af7de1 100644 --- a/Triangle.NET/TestApp/Views/StatisticView.resx +++ b/src/Triangle.Viewer/Views/StatisticView.resx @@ -1,120 +1,120 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + \ No newline at end of file diff --git a/src/Triangle.sln b/src/Triangle.sln new file mode 100644 index 0000000..e69823c --- /dev/null +++ b/src/Triangle.sln @@ -0,0 +1,61 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32126.317 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Triangle", "Triangle\Triangle.csproj", "{F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Triangle.Tests", "Triangle.Tests\Triangle.Tests.csproj", "{67CCA496-DCD1-4D43-85D5-0A19A3948774}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Triangle.Examples", "Triangle.Examples\Triangle.Examples.csproj", "{70EC2A88-E7CC-4678-9936-ED539969B003}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Triangle.Rendering", "Triangle.Rendering\Triangle.Rendering.csproj", "{20D64FA8-EC38-4507-9D99-96F855ED62C0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Debug|x64.ActiveCfg = Debug|x64 + {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Debug|x64.Build.0 = Debug|x64 + {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Release|Any CPU.Build.0 = Release|Any CPU + {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Release|x64.ActiveCfg = Release|x64 + {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Release|x64.Build.0 = Release|x64 + {67CCA496-DCD1-4D43-85D5-0A19A3948774}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67CCA496-DCD1-4D43-85D5-0A19A3948774}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67CCA496-DCD1-4D43-85D5-0A19A3948774}.Debug|x64.ActiveCfg = Debug|Any CPU + {67CCA496-DCD1-4D43-85D5-0A19A3948774}.Debug|x64.Build.0 = Debug|Any CPU + {67CCA496-DCD1-4D43-85D5-0A19A3948774}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67CCA496-DCD1-4D43-85D5-0A19A3948774}.Release|Any CPU.Build.0 = Release|Any CPU + {67CCA496-DCD1-4D43-85D5-0A19A3948774}.Release|x64.ActiveCfg = Release|Any CPU + {67CCA496-DCD1-4D43-85D5-0A19A3948774}.Release|x64.Build.0 = Release|Any CPU + {70EC2A88-E7CC-4678-9936-ED539969B003}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {70EC2A88-E7CC-4678-9936-ED539969B003}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70EC2A88-E7CC-4678-9936-ED539969B003}.Debug|x64.ActiveCfg = Debug|x64 + {70EC2A88-E7CC-4678-9936-ED539969B003}.Debug|x64.Build.0 = Debug|x64 + {70EC2A88-E7CC-4678-9936-ED539969B003}.Release|Any CPU.ActiveCfg = Release|Any CPU + {70EC2A88-E7CC-4678-9936-ED539969B003}.Release|Any CPU.Build.0 = Release|Any CPU + {70EC2A88-E7CC-4678-9936-ED539969B003}.Release|x64.ActiveCfg = Release|x64 + {70EC2A88-E7CC-4678-9936-ED539969B003}.Release|x64.Build.0 = Release|x64 + {20D64FA8-EC38-4507-9D99-96F855ED62C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {20D64FA8-EC38-4507-9D99-96F855ED62C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {20D64FA8-EC38-4507-9D99-96F855ED62C0}.Debug|x64.ActiveCfg = Debug|x64 + {20D64FA8-EC38-4507-9D99-96F855ED62C0}.Debug|x64.Build.0 = Debug|x64 + {20D64FA8-EC38-4507-9D99-96F855ED62C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {20D64FA8-EC38-4507-9D99-96F855ED62C0}.Release|Any CPU.Build.0 = Release|Any CPU + {20D64FA8-EC38-4507-9D99-96F855ED62C0}.Release|x64.ActiveCfg = Release|x64 + {20D64FA8-EC38-4507-9D99-96F855ED62C0}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {90F4F9E9-43D6-4A3D-91D5-B2DF113E13AE} + EndGlobalSection +EndGlobal diff --git a/Triangle.NET/Triangle/Behavior.cs b/src/Triangle/Behavior.cs similarity index 93% rename from Triangle.NET/Triangle/Behavior.cs rename to src/Triangle/Behavior.cs index 15601a5..5226774 100644 --- a/Triangle.NET/Triangle/Behavior.cs +++ b/src/Triangle/Behavior.cs @@ -1,248 +1,258 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet -{ - using System; - using TriangleNet.Geometry; - - /// - /// Controls the behavior of the meshing software. - /// - class Behavior - { - bool poly = false; - bool quality = false; - bool varArea = false; - bool convex = false; - bool jettison = false; - bool boundaryMarkers = true; - bool noHoles = false; - bool conformDel = false; - - Func usertest; - - int noBisect = 0; - - double minAngle = 0.0; - double maxAngle = 0.0; - double maxArea = -1.0; - - internal bool fixedArea = false; - internal bool useSegments = true; - internal bool useRegions = false; - internal double goodAngle = 0.0; - internal double maxGoodAngle = 0.0; - internal double offconstant = 0.0; - - /// - /// Creates an instance of the Behavior class. - /// - public Behavior(bool quality = false, double minAngle = 20.0) - { - if (quality) - { - this.quality = true; - this.minAngle = minAngle; - - Update(); - } - } - - /// - /// Update quality options dependencies. - /// - private void Update() - { - this.quality = true; - - if (this.minAngle < 0 || this.minAngle > 60) - { - this.minAngle = 0; - this.quality = false; - - Log.Instance.Warning("Invalid quality option (minimum angle).", "Mesh.Behavior"); - } - - if ((this.maxAngle != 0.0) && (this.maxAngle < 60 || this.maxAngle > 180)) - { - this.maxAngle = 0; - this.quality = false; - - Log.Instance.Warning("Invalid quality option (maximum angle).", "Mesh.Behavior"); - } - - this.useSegments = this.Poly || this.Quality || this.Convex; - this.goodAngle = Math.Cos(this.MinAngle * Math.PI / 180.0); - this.maxGoodAngle = Math.Cos(this.MaxAngle * Math.PI / 180.0); - - if (this.goodAngle == 1.0) - { - this.offconstant = 0.0; - } - else - { - this.offconstant = 0.475 * Math.Sqrt((1.0 + this.goodAngle) / (1.0 - this.goodAngle)); - } - - this.goodAngle *= this.goodAngle; - } - - #region Static properties - - /// - /// No exact arithmetic. - /// - public static bool NoExact { get; set; } - - #endregion - - #region Public properties - - /// - /// Quality mesh generation. - /// - public bool Quality - { - get { return quality; } - set - { - quality = value; - if (quality) - { - Update(); - } - } - } - - /// - /// Minimum angle constraint. - /// - public double MinAngle - { - get { return minAngle; } - set { minAngle = value; Update(); } - } - - /// - /// Maximum angle constraint. - /// - public double MaxAngle - { - get { return maxAngle; } - set { maxAngle = value; Update(); } - } - - /// - /// Maximum area constraint. - /// - public double MaxArea - { - get { return maxArea; } - set - { - maxArea = value; - fixedArea = value > 0.0; - } - } - - /// - /// Apply a maximum triangle area constraint. - /// - public bool VarArea - { - get { return varArea; } - set { varArea = value; } - } - - /// - /// Input is a Planar Straight Line Graph. - /// - public bool Poly - { - get { return poly; } - set { poly = value; } - } - - /// - /// Apply a user-defined triangle constraint. - /// - public Func UserTest - { - get { return usertest; } - set { usertest = value; } - } - - /// - /// Enclose the convex hull with segments. - /// - public bool Convex - { - get { return convex; } - set { convex = value; } - } - - /// - /// Conforming Delaunay (all triangles are truly Delaunay). - /// - public bool ConformingDelaunay - { - get { return conformDel; } - set { conformDel = value; } - } - - /// - /// Suppresses boundary segment splitting. - /// - /// - /// 0 = split segments - /// 1 = no new vertices on the boundary - /// 2 = prevent all segment splitting, including internal boundaries - /// - public int NoBisect - { - get { return noBisect; } - set - { - noBisect = value; - if (noBisect < 0 || noBisect > 2) - { - noBisect = 0; - } - } - } - - /// - /// Compute boundary information. - /// - public bool UseBoundaryMarkers - { - get { return boundaryMarkers; } - set { boundaryMarkers = value; } - } - - /// - /// Ignores holes in polygons. - /// - public bool NoHoles - { - get { return noHoles; } - set { noHoles = value; } - } - - /// - /// Jettison unused vertices from output. - /// - public bool Jettison - { - get { return jettison; } - set { jettison = value; } - } - - #endregion - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using TriangleNet.Geometry; + + /// + /// Controls the behavior of the meshing software. + /// + class Behavior + { + bool poly = false; + bool quality = false; + bool varArea = false; + bool convex = false; + bool jettison = false; + bool boundaryMarkers = true; + bool noHoles = false; + bool conformDel = false; + + Func usertest; + Func exclude; + + int noBisect = 0; + + double minAngle = 0.0; + double maxAngle = 0.0; + double maxArea = -1.0; + + internal bool fixedArea = false; + internal bool useSegments = true; + internal bool useRegions = false; + internal double goodAngle = 0.0; + internal double maxGoodAngle = 0.0; + internal double offconstant = 0.0; + + /// + /// Creates an instance of the Behavior class. + /// + public Behavior(bool quality = false, double minAngle = 20.0) + { + if (quality) + { + this.quality = true; + this.minAngle = minAngle; + + Update(); + } + } + + /// + /// Update quality options dependencies. + /// + private void Update() + { + this.quality = true; + + if (this.minAngle < 0 || this.minAngle > 60) + { + this.minAngle = 0; + this.quality = false; + + Log.Instance.Warning("Invalid quality option (minimum angle).", "Mesh.Behavior"); + } + + if ((this.maxAngle != 0.0) && (this.maxAngle < 60 || this.maxAngle > 180)) + { + this.maxAngle = 0; + this.quality = false; + + Log.Instance.Warning("Invalid quality option (maximum angle).", "Mesh.Behavior"); + } + + this.useSegments = this.Poly || this.Quality || this.Convex; + this.goodAngle = Math.Cos(this.MinAngle * Math.PI / 180.0); + this.maxGoodAngle = Math.Cos(this.MaxAngle * Math.PI / 180.0); + + if (this.goodAngle == 1.0) + { + this.offconstant = 0.0; + } + else + { + this.offconstant = 0.475 * Math.Sqrt((1.0 + this.goodAngle) / (1.0 - this.goodAngle)); + } + + this.goodAngle *= this.goodAngle; + } + + #region Static properties + + /// + /// No exact arithmetic. + /// + public static bool NoExact { get; set; } + + #endregion + + #region Public properties + + /// + /// Quality mesh generation. + /// + public bool Quality + { + get { return quality; } + set + { + quality = value; + if (quality) + { + Update(); + } + } + } + + /// + /// Minimum angle constraint. + /// + public double MinAngle + { + get { return minAngle; } + set { minAngle = value; Update(); } + } + + /// + /// Maximum angle constraint. + /// + public double MaxAngle + { + get { return maxAngle; } + set { maxAngle = value; Update(); } + } + + /// + /// Maximum area constraint. + /// + public double MaxArea + { + get { return maxArea; } + set + { + maxArea = value; + fixedArea = value > 0.0; + } + } + + /// + /// Apply a maximum triangle area constraint. + /// + public bool VarArea + { + get { return varArea; } + set { varArea = value; } + } + + /// + /// Input is a Planar Straight Line Graph. + /// + public bool Poly + { + get { return poly; } + set { poly = value; } + } + + /// + /// Apply a user-defined triangle constraint. + /// + public Func UserTest + { + get { return usertest; } + set { usertest = value; } + } + + /// + /// Exclude triangles from being refined. + /// + public Func Exclude + { + get { return exclude; } + set { exclude = value; } + } + + /// + /// Enclose the convex hull with segments. + /// + public bool Convex + { + get { return convex; } + set { convex = value; } + } + + /// + /// Conforming Delaunay (all triangles are truly Delaunay). + /// + public bool ConformingDelaunay + { + get { return conformDel; } + set { conformDel = value; } + } + + /// + /// Suppresses boundary segment splitting. + /// + /// + /// 0 = split segments + /// 1 = no new vertices on the boundary + /// 2 = prevent all segment splitting, including internal boundaries + /// + public int NoBisect + { + get { return noBisect; } + set + { + noBisect = value; + if (noBisect < 0 || noBisect > 2) + { + noBisect = 0; + } + } + } + + /// + /// Compute boundary information. + /// + public bool UseBoundaryMarkers + { + get { return boundaryMarkers; } + set { boundaryMarkers = value; } + } + + /// + /// Ignores holes in polygons. + /// + public bool NoHoles + { + get { return noHoles; } + set { noHoles = value; } + } + + /// + /// Jettison unused vertices from output. + /// + public bool Jettison + { + get { return jettison; } + set { jettison = value; } + } + + #endregion + } +} diff --git a/Triangle.NET/Triangle/Configuration.cs b/src/Triangle/Configuration.cs similarity index 86% rename from Triangle.NET/Triangle/Configuration.cs rename to src/Triangle/Configuration.cs index 6dcb8e4..f723b35 100644 --- a/Triangle.NET/Triangle/Configuration.cs +++ b/src/Triangle/Configuration.cs @@ -1,44 +1,42 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet -{ - using System; - using TriangleNet.Meshing; - using TriangleNet.Meshing.Algorithm; - - /// - /// Configure advanced aspects of the library. - /// - public class Configuration - { - public Configuration() - : this(() => RobustPredicates.Default, () => new TrianglePool()) - { - } - - public Configuration(Func predicates) - : this(predicates, () => new TrianglePool()) - { - } - - public Configuration(Func predicates, Func trianglePool) - { - Predicates = predicates; - TrianglePool = trianglePool; - } - - /// - /// Gets or sets the factory method for the implementation. - /// - public Func Predicates { get; set; } - - /// - /// Gets or sets the factory method for the . - /// - public Func TrianglePool { get; set; } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + + /// + /// Configure advanced aspects of the library. + /// + public class Configuration + { + public Configuration() + : this(() => RobustPredicates.Default, () => new TrianglePool()) + { + } + + public Configuration(Func predicates) + : this(predicates, () => new TrianglePool()) + { + } + + public Configuration(Func predicates, Func trianglePool) + { + Predicates = predicates; + TrianglePool = trianglePool; + } + + /// + /// Gets or sets the factory method for the implementation. + /// + public Func Predicates { get; set; } + + /// + /// Gets or sets the factory method for the . + /// + public Func TrianglePool { get; set; } + } +} diff --git a/Triangle.NET/Triangle/Enums.cs b/src/Triangle/Enums.cs similarity index 89% rename from Triangle.NET/Triangle/Enums.cs rename to src/Triangle/Enums.cs index 3213cda..aa5d5fb 100644 --- a/Triangle.NET/Triangle/Enums.cs +++ b/src/Triangle/Enums.cs @@ -1,46 +1,46 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet -{ - /// - /// The type of the mesh vertex. - /// - public enum VertexType { InputVertex, SegmentVertex, FreeVertex, DeadVertex, UndeadVertex }; - - /// - /// Node renumbering algorithms. - /// - public enum NodeNumbering { None, Linear, CuthillMcKee }; - - /// - /// Labels that signify the result of point location. - /// - /// The result of a search indicates that the point falls in the - /// interior of a triangle, on an edge, on a vertex, or outside the mesh. - /// - public enum LocateResult { InTriangle, OnEdge, OnVertex, Outside }; - - /// - /// Labels that signify the result of vertex insertion. - /// - /// The result indicates that the vertex was inserted with complete - /// success, was inserted but encroaches upon a subsegment, was not inserted - /// because it lies on a segment, or was not inserted because another vertex - /// occupies the same location. - /// - enum InsertVertexResult { Successful, Encroaching, Violating, Duplicate }; - - /// - /// Labels that signify the result of direction finding. - /// - /// The result indicates that a segment connecting the two query - /// points falls within the direction triangle, along the left edge of the - /// direction triangle, or along the right edge of the direction triangle. - /// - enum FindDirectionResult { Within, Leftcollinear, Rightcollinear }; -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + /// + /// The type of the mesh vertex. + /// + public enum VertexType { InputVertex, SegmentVertex, FreeVertex, DeadVertex, UndeadVertex }; + + /// + /// Node renumbering algorithms. + /// + public enum NodeNumbering { None, Linear, CuthillMcKee }; + + /// + /// Labels that signify the result of point location. + /// + /// The result of a search indicates that the point falls in the + /// interior of a triangle, on an edge, on a vertex, or outside the mesh. + /// + public enum LocateResult { InTriangle, OnEdge, OnVertex, Outside }; + + /// + /// Labels that signify the result of vertex insertion. + /// + /// The result indicates that the vertex was inserted with complete + /// success, was inserted but encroaches upon a subsegment, was not inserted + /// because it lies on a segment, or was not inserted because another vertex + /// occupies the same location. + /// + enum InsertVertexResult { Successful, Encroaching, Violating, Duplicate }; + + /// + /// Labels that signify the result of direction finding. + /// + /// The result indicates that a segment connecting the two query + /// points falls within the direction triangle, along the left edge of the + /// direction triangle, or along the right edge of the direction triangle. + /// + enum FindDirectionResult { Within, Leftcollinear, Rightcollinear }; +} diff --git a/Triangle.NET/Triangle/Geometry/Contour.cs b/src/Triangle/Geometry/Contour.cs similarity index 87% rename from Triangle.NET/Triangle/Geometry/Contour.cs rename to src/Triangle/Geometry/Contour.cs index d4a9f6a..1fb2ea8 100644 --- a/Triangle.NET/Triangle/Geometry/Contour.cs +++ b/src/Triangle/Geometry/Contour.cs @@ -1,246 +1,272 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Geometry -{ - using System; - using System.Collections.Generic; - - public class Contour - { - int marker; - - bool convex; - - /// - /// Gets or sets the list of points making up the contour. - /// - public List Points { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// The points that make up the contour. - public Contour(IEnumerable points) - : this(points, 0, false) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The points that make up the contour. - /// Contour marker. - public Contour(IEnumerable points, int marker) - : this(points, marker, false) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The points that make up the contour. - /// Contour marker. - /// The hole is convex. - public Contour(IEnumerable points, int marker, bool convex) - { - AddPoints(points); - - this.marker = marker; - this.convex = convex; - } - - public List GetSegments() - { - var segments = new List(); - - var p = this.Points; - - int count = p.Count - 1; - - for (int i = 0; i < count; i++) - { - // Add segments to polygon. - segments.Add(new Segment(p[i], p[i + 1], marker)); - } - - // Close the contour. - segments.Add(new Segment(p[count], p[0], marker)); - - return segments; - } - - /// - /// Try to find a point inside the contour. - /// - /// The number of iterations on each segment (default = 5). - /// Threshold for co-linear points (default = 2e-5). - /// Point inside the contour - /// Throws if no point could be found. - /// - /// For each corner (index i) of the contour, the 3 points with indices i-1, i and i+1 - /// are considered and a search on the line through the corner vertex is started (either - /// on the bisecting line, or, if is less than - /// eps, on the perpendicular line. - /// A given number of points will be tested (limit), while the distance to the contour - /// boundary will be reduced in each iteration (with a factor 1 / 2^i, i = 1 ... limit). - /// - public Point FindInteriorPoint(int limit = 5, double eps = 2e-5) - { - if (convex) - { - int count = this.Points.Count; - - var point = new Point(0.0, 0.0); - - for (int i = 0; i < count; i++) - { - point.x += this.Points[i].x; - point.y += this.Points[i].y; - } - - // If the contour is convex, use its centroid. - point.x /= count; - point.y /= count; - - return point; - } - - return FindPointInPolygon(this.Points, limit, eps); - } - - private void AddPoints(IEnumerable points) - { - this.Points = new List(points); - - int count = Points.Count - 1; - - // Check if first vertex equals last vertex. - if (Points[0] == Points[count]) - { - Points.RemoveAt(count); - } - } - - #region Helper methods - - private static Point FindPointInPolygon(List contour, int limit, double eps) - { - var bounds = new Rectangle(); - bounds.Expand(contour); - - int length = contour.Count; - - var test = new Point(); - - Point a, b, c; // Current corner points. - - double bx, by; - double dx, dy; - double h; - - var predicates = new RobustPredicates(); - - a = contour[0]; - b = contour[1]; - - for (int i = 0; i < length; i++) - { - c = contour[(i + 2) % length]; - - // Corner point. - bx = b.x; - by = b.y; - - // NOTE: if we knew the contour points were in counterclockwise order, we - // could skip concave corners and search only in one direction. - - h = predicates.CounterClockwise(a, b, c); - - if (Math.Abs(h) < eps) - { - // Points are nearly co-linear. Use perpendicular direction. - dx = (c.y - a.y) / 2; - dy = (a.x - c.x) / 2; - } - else - { - // Direction [midpoint(a-c) -> corner point] - dx = (a.x + c.x) / 2 - bx; - dy = (a.y + c.y) / 2 - by; - } - - // Move around the contour. - a = b; - b = c; - - h = 1.0; - - for (int j = 0; j < limit; j++) - { - // Search in direction. - test.x = bx + dx * h; - test.y = by + dy * h; - - if (bounds.Contains(test) && IsPointInPolygon(test, contour)) - { - return test; - } - - // Search in opposite direction (see NOTE above). - test.x = bx - dx * h; - test.y = by - dy * h; - - if (bounds.Contains(test) && IsPointInPolygon(test, contour)) - { - return test; - } - - h = h / 2; - } - } - - throw new Exception(); - } - - /// - /// Return true if the given point is inside the polygon, or false if it is not. - /// - /// The point to check. - /// The polygon (list of contour points). - /// - /// - /// WARNING: If the point is exactly on the edge of the polygon, then the function - /// may return true or false. - /// - /// See http://alienryderflex.com/polygon/ - /// - private static bool IsPointInPolygon(Point point, List poly) - { - bool inside = false; - - double x = point.x; - double y = point.y; - - int count = poly.Count; - - for (int i = 0, j = count - 1; i < count; i++) - { - if (((poly[i].y < y && poly[j].y >= y) || (poly[j].y < y && poly[i].y >= y)) - && (poly[i].x <= x || poly[j].x <= x)) - { - inside ^= (poly[i].x + (y - poly[i].y) / (poly[j].y - poly[i].y) * (poly[j].x - poly[i].x) < x); - } - - j = i; - } - - return inside; - } - - #endregion - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + using System.Collections.Generic; + using TriangleNet.Tools; + + public class Contour + { + int marker; + + bool convex; + + /// + /// Gets or sets the list of points making up the contour. + /// + public List Points { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The points that make up the contour. + public Contour(IEnumerable points) + : this(points, 0, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The points that make up the contour. + /// Contour marker. + public Contour(IEnumerable points, int marker) + : this(points, marker, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The points that make up the contour. + /// Contour marker. + /// The hole is convex. + public Contour(IEnumerable points, int marker, bool convex) + { + AddPoints(points); + + this.marker = marker; + this.convex = convex; + } + + public List GetSegments() + { + var segments = new List(); + + var p = Points; + + int count = p.Count - 1; + + for (int i = 0; i < count; i++) + { + // Add segments to polygon. + segments.Add(new Segment(p[i], p[i + 1], marker)); + } + + // Close the contour. + segments.Add(new Segment(p[count], p[0], marker)); + + return segments; + } + + /// + /// Try to find a point inside the contour. + /// + /// The number of iterations on each segment (default = 5). + /// Threshold for co-linear points (default = 2e-5). + /// Point inside the contour + /// Throws if no point could be found. + /// + /// For each corner (index i) of the contour, the 3 points with indices i-1, i and i+1 + /// are considered and a search on the line through the corner vertex is started (either + /// on the bisecting line, or, if is less than + /// eps, on the perpendicular line. + /// A given number of points will be tested (limit), while the distance to the contour + /// boundary will be reduced in each iteration (with a factor 1 / 2^i, i = 1 ... limit). + /// + public Point FindInteriorPoint(int limit = 5, double eps = 2e-5) + { + if (convex) + { + var p = Points; + + int count = p.Count; + + var point = new Point(0.0, 0.0); + + for (int i = 0; i < count; i++) + { + point.x += p[i].x; + point.y += p[i].y; + } + + // If the contour is convex, use its centroid. + point.x /= count; + point.y /= count; + + return point; + } + + return FindPointInPolygon(this.Points, limit, eps); + } + + private void AddPoints(IEnumerable points) + { + Points = new List(points); + + int count = Points.Count - 1; + + // Check if first vertex equals last vertex. + if (Points[0] == Points[count]) + { + Points.RemoveAt(count); + } + } + + #region Helper methods + + private static Point FindPointInPolygon(List contour, int limit, double eps) + { + var bounds = new Rectangle(); + bounds.Expand(contour); + + int length = contour.Count; + + var test = new Point(); + + Point a, b, c; // Current corner points. + + double bx, by; + double dx, dy; + double h; + + var predicates = RobustPredicates.Default; + + a = contour[0]; + b = contour[1]; + + for (int i = 0; i < length; i++) + { + c = contour[(i + 2) % length]; + + // Corner point. + bx = b.x; + by = b.y; + + // NOTE: if we knew the contour points were in counterclockwise order, we + // could skip concave corners and search only in one direction. + + h = predicates.CounterClockwise(a, b, c); + + if (Math.Abs(h) < eps) + { + // Points are nearly co-linear. Use perpendicular direction. + dx = (c.y - a.y) / 2; + dy = (a.x - c.x) / 2; + } + else + { + // Direction [midpoint(a-c) -> corner point] + dx = (a.x + c.x) / 2 - bx; + dy = (a.y + c.y) / 2 - by; + } + + // Move around the contour. + a = b; + b = c; + + h = 1.0; + + for (int j = 0; j < limit; j++) + { + // Search in direction. + test.x = bx + dx * h; + test.y = by + dy * h; + + if (bounds.Contains(test) && IsPointInPolygon(test, contour) && !IsPointOnSegment(test, contour)) + { + return test; + } + + // Search in opposite direction (see NOTE above). + test.x = bx - dx * h; + test.y = by - dy * h; + + if (bounds.Contains(test) && IsPointInPolygon(test, contour) && !IsPointOnSegment(test, contour)) + { + return test; + } + + h = h / 2; + } + } + + throw new Exception(); + } + + /// + /// Return true if the given point is inside the polygon, or false if it is not. + /// + /// The point to check. + /// The polygon (list of contour points). + /// + /// + /// WARNING: If the point is exactly on the edge of the polygon, then the function + /// may return true or false. + /// + /// See http://alienryderflex.com/polygon/ + /// + private static bool IsPointInPolygon(Point point, List poly) + { + bool inside = false; + + double x = point.x; + double y = point.y; + + int count = poly.Count; + + for (int i = 0, j = count - 1; i < count; i++) + { + if (((poly[i].y < y && poly[j].y >= y) || (poly[j].y < y && poly[i].y >= y)) + && (poly[i].x <= x || poly[j].x <= x)) + { + inside ^= (poly[i].x + (y - poly[i].y) / (poly[j].y - poly[i].y) * (poly[j].x - poly[i].x) < x); + } + + j = i; + } + + return inside; + } + + + /// + /// Work around IsPointInPolygon() failing for points on segments. + /// + private static bool IsPointOnSegment(Point test, List contour, double eps = 1e-12) + { + int count = contour.Count; + + int i = count - 1; + + for (int j = 0; j < count; j++) + { + if (IntersectionHelper.IsPointOnSegment(contour[i], contour[j], test, eps)) + { + return true; + } + + i = j; + } + + return false; + } + + #endregion + } +} diff --git a/Triangle.NET/Triangle/Geometry/Edge.cs b/src/Triangle/Geometry/Edge.cs similarity index 68% rename from Triangle.NET/Triangle/Geometry/Edge.cs rename to src/Triangle/Geometry/Edge.cs index f8daeb6..f837101 100644 --- a/Triangle.NET/Triangle/Geometry/Edge.cs +++ b/src/Triangle/Geometry/Edge.cs @@ -1,58 +1,46 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Geometry -{ - /// - /// Represents a straight line segment in 2D space. - /// - public class Edge : IEdge - { - /// - /// Gets the first endpoints index. - /// - public int P0 - { - get; - private set; - } - - /// - /// Gets the second endpoints index. - /// - public int P1 - { - get; - private set; - } - - /// - /// Gets the segments boundary mark. - /// - public int Label - { - get; - private set; - } - - /// - /// Initializes a new instance of the class. - /// - public Edge(int p0, int p1) - : this(p0, p1, 0) - { } - - /// - /// Initializes a new instance of the class. - /// - public Edge(int p0, int p1, int label) - { - this.P0 = p0; - this.P1 = p1; - this.Label = label; - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + /// + /// Represents a straight line segment in 2D space. + /// + public class Edge : IEdge + { + /// + /// Gets the first endpoints index. + /// + public int P0 { get; private set; } + + /// + /// Gets the second endpoints index. + /// + public int P1 { get; private set; } + + /// + /// Gets the segments boundary mark. + /// + public int Label { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + public Edge(int p0, int p1) + : this(p0, p1, 0) + { } + + /// + /// Initializes a new instance of the class. + /// + public Edge(int p0, int p1, int label) + { + P0 = p0; + P1 = p1; + Label = label; + } + } +} diff --git a/Triangle.NET/Triangle/Geometry/ExtensionMethods.cs b/src/Triangle/Geometry/ExtensionMethods.cs similarity index 97% rename from Triangle.NET/Triangle/Geometry/ExtensionMethods.cs rename to src/Triangle/Geometry/ExtensionMethods.cs index 9087adb..d583738 100644 --- a/Triangle.NET/Triangle/Geometry/ExtensionMethods.cs +++ b/src/Triangle/Geometry/ExtensionMethods.cs @@ -1,143 +1,143 @@ - -namespace TriangleNet.Geometry -{ - using System; - using TriangleNet.Meshing; - - public static class ExtensionMethods - { - #region IPolygon extensions - - /// - /// Triangulates a polygon. - /// - public static IMesh Triangulate(this IPolygon polygon) - { - return (new GenericMesher()).Triangulate(polygon, null, null); - } - - /// - /// Triangulates a polygon, applying constraint options. - /// - /// Constraint options. - public static IMesh Triangulate(this IPolygon polygon, ConstraintOptions options) - { - return (new GenericMesher()).Triangulate(polygon, options, null); - } - - /// - /// Triangulates a polygon, applying quality options. - /// - /// Quality options. - public static IMesh Triangulate(this IPolygon polygon, QualityOptions quality) - { - return (new GenericMesher()).Triangulate(polygon, null, quality); - } - - /// - /// Triangulates a polygon, applying quality and constraint options. - /// - /// Constraint options. - /// Quality options. - public static IMesh Triangulate(this IPolygon polygon, ConstraintOptions options, QualityOptions quality) - { - return (new GenericMesher()).Triangulate(polygon, options, quality); - } - - /// - /// Triangulates a polygon, applying quality and constraint options. - /// - /// Constraint options. - /// Quality options. - /// The triangulation algorithm. - public static IMesh Triangulate(this IPolygon polygon, ConstraintOptions options, QualityOptions quality, - ITriangulator triangulator) - { - return (new GenericMesher(triangulator)).Triangulate(polygon, options, quality); - } - - #endregion - - #region Rectangle extensions - - #endregion - - #region ITriangle extensions - - /// - /// Test whether a given point lies inside a triangle or not. - /// - /// Point to locate. - /// True, if point is inside or on the edge of this triangle. - public static bool Contains(this ITriangle triangle, Point p) - { - return Contains(triangle, p.X, p.Y); - } - - /// - /// Test whether a given point lies inside a triangle or not. - /// - /// Point to locate. - /// Point to locate. - /// True, if point is inside or on the edge of this triangle. - public static bool Contains(this ITriangle triangle, double x, double y) - { - var t0 = triangle.GetVertex(0); - var t1 = triangle.GetVertex(1); - var t2 = triangle.GetVertex(2); - - // TODO: no need to create new Point instances here - Point d0 = new Point(t1.X - t0.X, t1.Y - t0.Y); - Point d1 = new Point(t2.X - t0.X, t2.Y - t0.Y); - Point d2 = new Point(x - t0.X, y - t0.Y); - - // crossproduct of (0, 0, 1) and d0 - Point c0 = new Point(-d0.Y, d0.X); - - // crossproduct of (0, 0, 1) and d1 - Point c1 = new Point(-d1.Y, d1.X); - - // Linear combination d2 = s * d0 + v * d1. - // - // Multiply both sides of the equation with c0 and c1 - // and solve for s and v respectively - // - // s = d2 * c1 / d0 * c1 - // v = d2 * c0 / d1 * c0 - - double s = DotProduct(d2, c1) / DotProduct(d0, c1); - double v = DotProduct(d2, c0) / DotProduct(d1, c0); - - if (s >= 0 && v >= 0 && ((s + v) <= 1)) - { - // Point is inside or on the edge of this triangle. - return true; - } - - return false; - } - - public static Rectangle Bounds(this ITriangle triangle) - { - var bounds = new Rectangle(); - - for (int i = 0; i < 3; i++) - { - bounds.Expand(triangle.GetVertex(i)); - } - - return bounds; - } - - #endregion - - #region Helper methods - - internal static double DotProduct(Point p, Point q) - { - return p.X * q.X + p.Y * q.Y; - } - - #endregion - } -} + +namespace TriangleNet.Geometry +{ + using System; + using TriangleNet.Meshing; + + public static class ExtensionMethods + { + #region IPolygon extensions + + /// + /// Triangulates a polygon. + /// + public static IMesh Triangulate(this IPolygon polygon) + { + return (new GenericMesher()).Triangulate(polygon, null, null); + } + + /// + /// Triangulates a polygon, applying constraint options. + /// + /// Constraint options. + public static IMesh Triangulate(this IPolygon polygon, ConstraintOptions options) + { + return (new GenericMesher()).Triangulate(polygon, options, null); + } + + /// + /// Triangulates a polygon, applying quality options. + /// + /// Quality options. + public static IMesh Triangulate(this IPolygon polygon, QualityOptions quality) + { + return (new GenericMesher()).Triangulate(polygon, null, quality); + } + + /// + /// Triangulates a polygon, applying quality and constraint options. + /// + /// Constraint options. + /// Quality options. + public static IMesh Triangulate(this IPolygon polygon, ConstraintOptions options, QualityOptions quality) + { + return (new GenericMesher()).Triangulate(polygon, options, quality); + } + + /// + /// Triangulates a polygon, applying quality and constraint options. + /// + /// Constraint options. + /// Quality options. + /// The triangulation algorithm. + public static IMesh Triangulate(this IPolygon polygon, ConstraintOptions options, QualityOptions quality, + ITriangulator triangulator) + { + return (new GenericMesher(triangulator)).Triangulate(polygon, options, quality); + } + + #endregion + + #region Rectangle extensions + + #endregion + + #region ITriangle extensions + + /// + /// Test whether a given point lies inside a triangle or not. + /// + /// Point to locate. + /// True, if point is inside or on the edge of this triangle. + public static bool Contains(this ITriangle triangle, Point p) + { + return Contains(triangle, p.X, p.Y); + } + + /// + /// Test whether a given point lies inside a triangle or not. + /// + /// Point to locate. + /// Point to locate. + /// True, if point is inside or on the edge of this triangle. + public static bool Contains(this ITriangle triangle, double x, double y) + { + var t0 = triangle.GetVertex(0); + var t1 = triangle.GetVertex(1); + var t2 = triangle.GetVertex(2); + + // TODO: no need to create new Point instances here + Point d0 = new Point(t1.X - t0.X, t1.Y - t0.Y); + Point d1 = new Point(t2.X - t0.X, t2.Y - t0.Y); + Point d2 = new Point(x - t0.X, y - t0.Y); + + // crossproduct of (0, 0, 1) and d0 + Point c0 = new Point(-d0.Y, d0.X); + + // crossproduct of (0, 0, 1) and d1 + Point c1 = new Point(-d1.Y, d1.X); + + // Linear combination d2 = s * d0 + v * d1. + // + // Multiply both sides of the equation with c0 and c1 + // and solve for s and v respectively + // + // s = d2 * c1 / d0 * c1 + // v = d2 * c0 / d1 * c0 + + double s = DotProduct(d2, c1) / DotProduct(d0, c1); + double v = DotProduct(d2, c0) / DotProduct(d1, c0); + + if (s >= 0 && v >= 0 && ((s + v) <= 1)) + { + // Point is inside or on the edge of this triangle. + return true; + } + + return false; + } + + public static Rectangle Bounds(this ITriangle triangle) + { + var bounds = new Rectangle(); + + for (int i = 0; i < 3; i++) + { + bounds.Expand(triangle.GetVertex(i)); + } + + return bounds; + } + + #endregion + + #region Helper methods + + internal static double DotProduct(Point p, Point q) + { + return p.X * q.X + p.Y * q.Y; + } + + #endregion + } +} diff --git a/Triangle.NET/Triangle/Geometry/IEdge.cs b/src/Triangle/Geometry/IEdge.cs similarity index 87% rename from Triangle.NET/Triangle/Geometry/IEdge.cs rename to src/Triangle/Geometry/IEdge.cs index a7edf5f..b34c16f 100644 --- a/Triangle.NET/Triangle/Geometry/IEdge.cs +++ b/src/Triangle/Geometry/IEdge.cs @@ -1,29 +1,29 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Geometry -{ - public interface IEdge - { - /// - /// Gets the first endpoints index. - /// - int P0 { get; } - - /// - /// Gets the second endpoints index. - /// - int P1 { get; } - - /// - /// Gets or sets a general-purpose label. - /// - /// - /// This is used for the segments boundary mark. - /// - int Label { get; } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + public interface IEdge + { + /// + /// Gets the first endpoints index. + /// + int P0 { get; } + + /// + /// Gets the second endpoints index. + /// + int P1 { get; } + + /// + /// Gets or sets a general-purpose label. + /// + /// + /// This is used for the segments boundary mark. + /// + int Label { get; } + } +} diff --git a/Triangle.NET/Triangle/Geometry/IPolygon.cs b/src/Triangle/Geometry/IPolygon.cs similarity index 85% rename from Triangle.NET/Triangle/Geometry/IPolygon.cs rename to src/Triangle/Geometry/IPolygon.cs index a627b78..0a4c834 100644 --- a/Triangle.NET/Triangle/Geometry/IPolygon.cs +++ b/src/Triangle/Geometry/IPolygon.cs @@ -1,93 +1,86 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Geometry -{ - using System; - using System.Collections.Generic; - - /// - /// Polygon interface. - /// - public interface IPolygon - { - /// - /// Gets the vertices of the polygon. - /// - List Points { get; } - - /// - /// Gets the segments of the polygon. - /// - List Segments { get; } - - /// - /// Gets a list of points defining the holes of the polygon. - /// - List Holes { get; } - - /// - /// Gets a list of pointers defining the regions of the polygon. - /// - List Regions { get; } - - /// - /// Gets or sets a value indicating whether the vertices have marks or not. - /// - bool HasPointMarkers { get; set; } - - /// - /// Gets or sets a value indicating whether the segments have marks or not. - /// - bool HasSegmentMarkers { get; set; } - - [Obsolete("Use polygon.Add(contour) method instead.")] - void AddContour(IEnumerable points, int marker, bool hole, bool convex); - - [Obsolete("Use polygon.Add(contour) method instead.")] - void AddContour(IEnumerable points, int marker, Point hole); - - /// - /// Compute the bounds of the polygon. - /// - /// Rectangle defining an axis-aligned bounding box. - Rectangle Bounds(); - - /// - /// Add a vertex to the polygon. - /// - /// The vertex to insert. - void Add(Vertex vertex); - - /// - /// Add a segment to the polygon. - /// - /// The segment to insert. - /// If true, both endpoints will be added to the points list. - void Add(ISegment segment, bool insert = false); - - /// - /// Add a segment to the polygon. - /// - /// The segment to insert. - /// The index of the segment endpoint to add to the points list (must be 0 or 1). - void Add(ISegment segment, int index); - - /// - /// Add a contour to the polygon. - /// - /// The contour to insert. - /// Treat contour as a hole. - void Add(Contour contour, bool hole = false); - - /// - /// Add a contour to the polygon. - /// - /// The contour to insert. - /// Point inside the contour, making it a hole. - void Add(Contour contour, Point hole); - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System.Collections.Generic; + + /// + /// Polygon interface. + /// + public interface IPolygon + { + /// + /// Gets the vertices of the polygon. + /// + List Points { get; } + + /// + /// Gets the segments of the polygon. + /// + List Segments { get; } + + /// + /// Gets a list of points defining the holes of the polygon. + /// + List Holes { get; } + + /// + /// Gets a list of pointers defining the regions of the polygon. + /// + List Regions { get; } + + /// + /// Gets or sets a value indicating whether the vertices have marks or not. + /// + bool HasPointMarkers { get; set; } + + /// + /// Gets or sets a value indicating whether the segments have marks or not. + /// + bool HasSegmentMarkers { get; set; } + + /// + /// Compute the bounds of the polygon. + /// + /// Rectangle defining an axis-aligned bounding box. + Rectangle Bounds(); + + /// + /// Add a vertex to the polygon. + /// + /// The vertex to insert. + void Add(Vertex vertex); + + /// + /// Add a segment to the polygon. + /// + /// The segment to insert. + /// If true, both endpoints will be added to the points list. + void Add(ISegment segment, bool insert = false); + + /// + /// Add a segment to the polygon. + /// + /// The segment to insert. + /// The index of the segment endpoint to add to the points list (must be 0 or 1). + void Add(ISegment segment, int index); + + /// + /// Add a contour to the polygon. + /// + /// The contour to insert. + /// Treat contour as a hole. + void Add(Contour contour, bool hole = false); + + /// + /// Add a contour to the polygon. + /// + /// The contour to insert. + /// Point inside the contour, making it a hole. + void Add(Contour contour, Point hole); + } +} diff --git a/Triangle.NET/Triangle/Geometry/ISegment.cs b/src/Triangle/Geometry/ISegment.cs similarity index 88% rename from Triangle.NET/Triangle/Geometry/ISegment.cs rename to src/Triangle/Geometry/ISegment.cs index 5a99983..ab8d439 100644 --- a/Triangle.NET/Triangle/Geometry/ISegment.cs +++ b/src/Triangle/Geometry/ISegment.cs @@ -1,26 +1,26 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Geometry -{ - /// - /// Interface for segment geometry. - /// - public interface ISegment : IEdge - { - /// - /// Gets the vertex at given index. - /// - /// The local index (0 or 1). - Vertex GetVertex(int index); - - /// - /// Gets an adjoining triangle. - /// - /// The triangle index (0 or 1). - ITriangle GetTriangle(int index); - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + /// + /// Interface for segment geometry. + /// + public interface ISegment : IEdge + { + /// + /// Gets the vertex at given index. + /// + /// The local index (0 or 1). + Vertex GetVertex(int index); + + /// + /// Gets an adjoining triangle. + /// + /// The triangle index (0 or 1). + ITriangle GetTriangle(int index); + } +} diff --git a/Triangle.NET/Triangle/Geometry/ITriangle.cs b/src/Triangle/Geometry/ITriangle.cs similarity index 92% rename from Triangle.NET/Triangle/Geometry/ITriangle.cs rename to src/Triangle/Geometry/ITriangle.cs index 603741a..91ee86d 100644 --- a/Triangle.NET/Triangle/Geometry/ITriangle.cs +++ b/src/Triangle/Geometry/ITriangle.cs @@ -1,69 +1,67 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Geometry -{ - using TriangleNet.Topology; - - /// - /// Triangle interface. - /// - public interface ITriangle - { - /// - /// Gets or sets the triangle ID. - /// - int ID { get; set; } - - /// - /// Gets or sets a general-purpose label. - /// - /// - /// This is used for region information. - /// - int Label { get; set; } - - /// - /// Gets or sets the triangle area constraint. - /// - double Area { get; set; } - - /// - /// Gets the vertex at given index. - /// - /// The local index (0, 1 or 2). - /// The vertex. - Vertex GetVertex(int index); - - /// - /// Gets the ID of the vertex at given index. - /// - /// The local index (0, 1 or 2). - /// The vertex ID. - int GetVertexID(int index); - - /// - /// Gets the neighbor triangle at given index. - /// - /// The local index (0, 1 or 2). - /// The neighbor triangle. - ITriangle GetNeighbor(int index); - - /// - /// Gets the ID of the neighbor triangle at given index. - /// - /// The local index (0, 1 or 2). - /// The neighbor triangle ID. - int GetNeighborID(int index); - - /// - /// Gets the segment at given index. - /// - /// The local index (0, 1 or 2). - /// The segment. - ISegment GetSegment(int index); - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + /// + /// Triangle interface. + /// + public interface ITriangle + { + /// + /// Gets or sets the triangle ID. + /// + int ID { get; set; } + + /// + /// Gets or sets a general-purpose label. + /// + /// + /// This is used for region information. + /// + int Label { get; set; } + + /// + /// Gets or sets the triangle area constraint. + /// + double Area { get; set; } + + /// + /// Gets the vertex at given index. + /// + /// The local index (0, 1 or 2). + /// The vertex. + Vertex GetVertex(int index); + + /// + /// Gets the ID of the vertex at given index. + /// + /// The local index (0, 1 or 2). + /// The vertex ID. + int GetVertexID(int index); + + /// + /// Gets the neighbor triangle at given index. + /// + /// The local index (0, 1 or 2). + /// The neighbor triangle. + ITriangle GetNeighbor(int index); + + /// + /// Gets the ID of the neighbor triangle at given index. + /// + /// The local index (0, 1 or 2). + /// The neighbor triangle ID. + int GetNeighborID(int index); + + /// + /// Gets the segment at given index. + /// + /// The local index (0, 1 or 2). + /// The segment. + ISegment GetSegment(int index); + } +} diff --git a/Triangle.NET/Triangle/Geometry/Point.cs b/src/Triangle/Geometry/Point.cs similarity index 63% rename from Triangle.NET/Triangle/Geometry/Point.cs rename to src/Triangle/Geometry/Point.cs index 3c1fb95..976c36e 100644 --- a/Triangle.NET/Triangle/Geometry/Point.cs +++ b/src/Triangle/Geometry/Point.cs @@ -1,180 +1,161 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Geometry -{ - using System; - using System.Diagnostics; - - /// - /// Represents a 2D point. - /// -#if USE_Z - [DebuggerDisplay("ID {ID} [{X}, {Y}, {Z}]")] -#else - [DebuggerDisplay("ID {ID} [{X}, {Y}]")] -#endif - public class Point : IComparable, IEquatable - { - internal int id; - internal int label; - - internal double x; - internal double y; -#if USE_Z - internal double z; -#endif - - public Point() - : this(0.0, 0.0, 0) - { - } - - public Point(double x, double y) - : this(x, y, 0) - { - } - - public Point(double x, double y, int label) - { - this.x = x; - this.y = y; - this.label = label; - } - - #region Public properties - - /// - /// Gets or sets the vertex id. - /// - public int ID - { - get { return this.id; } - set { this.id = value; } - } - - /// - /// Gets or sets the vertex x coordinate. - /// - public double X - { - get { return this.x; } - set { this.x = value; } - } - - /// - /// Gets or sets the vertex y coordinate. - /// - public double Y - { - get { return this.y; } - set { this.y = value; } - } - -#if USE_Z - /// - /// Gets or sets the vertex z coordinate. - /// - public double Z - { - get { return this.z; } - set { this.z = value; } - } -#endif - - /// - /// Gets or sets a general-purpose label. - /// - /// - /// This is used for the vertex boundary mark. - /// - public int Label - { - get { return this.label; } - set { this.label = value; } - } - - #endregion - - #region Operator overloading / overriding Equals - - // Compare "Guidelines for Overriding Equals() and Operator ==" - // http://msdn.microsoft.com/en-us/library/ms173147.aspx - - public static bool operator ==(Point a, Point b) - { - // If both are null, or both are same instance, return true. - if (Object.ReferenceEquals(a, b)) - { - return true; - } - - // If one is null, but not both, return false. - if (((object)a == null) || ((object)b == null)) - { - return false; - } - - return a.Equals(b); - } - - public static bool operator !=(Point a, Point b) - { - return !(a == b); - } - - public override bool Equals(object obj) - { - // If parameter is null return false. - if (obj == null) - { - return false; - } - - Point p = obj as Point; - - if ((object)p == null) - { - return false; - } - - return (x == p.x) && (y == p.y); - } - - public bool Equals(Point p) - { - // If vertex is null return false. - if ((object)p == null) - { - return false; - } - - // Return true if the fields match: - return (x == p.x) && (y == p.y); - } - - #endregion - - public int CompareTo(Point other) - { - if (x == other.x && y == other.y) - { - return 0; - } - - return (x < other.x || (x == other.x && y < other.y)) ? -1 : 1; - } - - public override int GetHashCode() - { - int hash = 19; - hash = hash * 31 + x.GetHashCode(); - hash = hash * 31 + y.GetHashCode(); - - return hash; - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + using System.Diagnostics; + + /// + /// Represents a 2D point. + /// +#if USE_Z + [DebuggerDisplay("ID {ID} [{X}, {Y}, {Z}]")] +#else + [DebuggerDisplay("ID {ID} [{X}, {Y}]")] +#endif + public class Point : IComparable, IEquatable + { + internal int id; + internal int label; + + internal double x; + internal double y; +#if USE_Z + internal double z; +#endif + + public Point() + : this(0.0, 0.0, 0) + { + } + + public Point(double x, double y) + : this(x, y, 0) + { + } + + public Point(double x, double y, int label) + { + this.x = x; + this.y = y; + this.label = label; + } + + #region Public properties + + /// + /// Gets or sets the vertex id. + /// + public int ID + { + get { return id; } + set { id = value; } + } + + /// + /// Gets or sets the vertex x coordinate. + /// + public double X + { + get { return x; } + set { x = value; } + } + + /// + /// Gets or sets the vertex y coordinate. + /// + public double Y + { + get { return y; } + set { y = value; } + } + +#if USE_Z + /// + /// Gets or sets the vertex z coordinate. + /// + public double Z + { + get { return z; } + set { z = value; } + } +#endif + + /// + /// Gets or sets a general-purpose label. + /// + /// + /// This is used for the vertex boundary mark. + /// + public int Label + { + get { return label; } + set { label = value; } + } + + #endregion + + #region Overriding Equals() and == Operator + + public static bool operator ==(Point a, Point b) + { + if (a is null) + { + // If one is null, but not both, return false. + return b is null; + } + + // If both are same instance, return true. + if (ReferenceEquals(a, b)) + { + return true; + } + + return a.Equals(b); + } + + public static bool operator !=(Point a, Point b) + { + return !(a == b); + } + + public override bool Equals(object obj) => Equals(obj as Point); + + public bool Equals(Point p) + { + // If object is null return false. + if (p is null) + { + return false; + } + + // Return true if the fields match. + return (x == p.x) && (y == p.y); + } + + #endregion + + public int CompareTo(Point other) + { + if (x == other.x && y == other.y) + { + return 0; + } + + return (x < other.x || (x == other.x && y < other.y)) ? -1 : 1; + } + + public override int GetHashCode() + { + int hash = 19; + hash = hash * 31 + x.GetHashCode(); + hash = hash * 31 + y.GetHashCode(); + + return hash; + } + } +} diff --git a/src/Triangle/Geometry/Polygon.cs b/src/Triangle/Geometry/Polygon.cs new file mode 100644 index 0000000..99a6c97 --- /dev/null +++ b/src/Triangle/Geometry/Polygon.cs @@ -0,0 +1,137 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System.Collections.Generic; + + /// + /// A polygon represented as a planar straight line graph. + /// + public class Polygon : IPolygon + { + private readonly List points; + private readonly List segments; + private readonly List holes; + private readonly List regions; + + /// + public List Points => points; + + /// + public List Holes => holes; + + /// + public List Regions => regions; + + /// + public List Segments => segments; + + /// + public bool HasPointMarkers { get; set; } + + /// + public bool HasSegmentMarkers { get; set; } + + /// + public int Count + { + get { return points.Count; } + } + + /// + /// Initializes a new instance of the class. + /// + public Polygon() + : this(3, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The default capacity for the points list. + public Polygon(int capacity) + : this(capacity, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The default capacity for the points list. + /// Use point and segment markers. + public Polygon(int capacity, bool markers) + { + points = new List(capacity); + holes = new List(); + regions = new List(); + + segments = new List(); + + HasPointMarkers = markers; + HasSegmentMarkers = markers; + } + + /// + public Rectangle Bounds() + { + var bounds = new Rectangle(); + bounds.Expand(points); + + return bounds; + } + + /// + public void Add(Vertex vertex) + { + points.Add(vertex); + } + + /// + public void Add(ISegment segment, bool insert = false) + { + segments.Add(segment); + + if (insert) + { + points.Add(segment.GetVertex(0)); + points.Add(segment.GetVertex(1)); + } + } + + /// + public void Add(ISegment segment, int index) + { + segments.Add(segment); + + points.Add(segment.GetVertex(index)); + } + + /// + public void Add(Contour contour, bool hole = false) + { + if (hole) + { + Add(contour, contour.FindInteriorPoint()); + } + else + { + points.AddRange(contour.Points); + segments.AddRange(contour.GetSegments()); + } + } + + /// + public void Add(Contour contour, Point hole) + { + points.AddRange(contour.Points); + segments.AddRange(contour.GetSegments()); + + holes.Add(hole); + } + } +} diff --git a/Triangle.NET/Triangle/Geometry/Rectangle.cs b/src/Triangle/Geometry/Rectangle.cs similarity index 77% rename from Triangle.NET/Triangle/Geometry/Rectangle.cs rename to src/Triangle/Geometry/Rectangle.cs index 44ea007..ce7e55d 100644 --- a/Triangle.NET/Triangle/Geometry/Rectangle.cs +++ b/src/Triangle/Geometry/Rectangle.cs @@ -1,189 +1,181 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Geometry -{ - using System; - using System.Collections.Generic; - - /// - /// A simple rectangle class. - /// - public class Rectangle - { - double xmin, ymin, xmax, ymax; - - /// - /// Initializes a new instance of the class. - /// - public Rectangle() - { - this.xmin = this.ymin = double.MaxValue; - this.xmax = this.ymax = -double.MaxValue; - } - - public Rectangle(Rectangle other) - : this(other.Left, other.Bottom, other.Right, other.Top) - { - } - - /// - /// Initializes a new instance of the class - /// with predefined bounds. - /// - /// Minimum x value (left). - /// Minimum y value (bottom). - /// Width of the rectangle. - /// Height of the rectangle. - public Rectangle(double x, double y, double width, double height) - { - this.xmin = x; - this.ymin = y; - this.xmax = x + width; - this.ymax = y + height; - } - - /// - /// Gets the minimum x value (left boundary). - /// - public double Left - { - get { return xmin; } - } - - /// - /// Gets the maximum x value (right boundary). - /// - public double Right - { - get { return xmax; } - } - - /// - /// Gets the minimum y value (bottom boundary). - /// - public double Bottom - { - get { return ymin; } - } - - /// - /// Gets the maximum y value (top boundary). - /// - public double Top - { - get { return ymax; } - } - - /// - /// Gets the width of the rectangle. - /// - public double Width - { - get { return xmax - xmin; } - } - - /// - /// Gets the height of the rectangle. - /// - public double Height - { - get { return ymax - ymin; } - } - - /// - /// Update bounds. - /// - /// Add dx to left and right bounds. - /// Add dy to top and bottom bounds. - public void Resize(double dx, double dy) - { - xmin -= dx; - xmax += dx; - ymin -= dy; - ymax += dy; - } - - /// - /// Expand rectangle to include given point. - /// - /// Point. - public void Expand(Point p) - { - xmin = Math.Min(xmin, p.x); - ymin = Math.Min(ymin, p.y); - xmax = Math.Max(xmax, p.x); - ymax = Math.Max(ymax, p.y); - } - - /// - /// Expand rectangle to include a list of points. - /// - public void Expand(IEnumerable points) - { - foreach (var p in points) - { - Expand(p); - } - } - - /// - /// Expand rectangle to include given rectangle. - /// - /// X coordinate. - /// Y coordinate. - public void Expand(Rectangle other) - { - xmin = Math.Min(xmin, other.xmin); - ymin = Math.Min(ymin, other.ymin); - xmax = Math.Max(xmax, other.xmax); - ymax = Math.Max(ymax, other.ymax); - } - - /// - /// Check if given point is inside rectangle. - /// - /// Point to check. - /// Point to check. - /// Return true, if rectangle contains given point. - public bool Contains(double x, double y) - { - return ((x >= xmin) && (x <= xmax) && (y >= ymin) && (y <= ymax)); - } - - /// - /// Check if given point is inside rectangle. - /// - /// Point to check. - /// Return true, if rectangle contains given point. - public bool Contains(Point pt) - { - return Contains(pt.x, pt.y); - } - - /// - /// Check if this rectangle contains other rectangle. - /// - /// Rectangle to check. - /// Return true, if this rectangle contains given rectangle. - public bool Contains(Rectangle other) - { - return (xmin <= other.Left && other.Right <= xmax - && ymin <= other.Bottom && other.Top <= ymax); - } - - /// - /// Check if this rectangle intersects other rectangle. - /// - /// Rectangle to check. - /// Return true, if given rectangle intersects this rectangle. - public bool Intersects(Rectangle other) - { - return (other.Left < xmax && xmin < other.Right - && other.Bottom < ymax && ymin < other.Top); - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + using System.Collections.Generic; + + /// + /// A simple rectangle class. + /// + public class Rectangle + { + double xmin, ymin, xmax, ymax; + + /// + /// Initializes a new instance of the class. + /// + public Rectangle() + { + xmin = ymin = double.MaxValue; + xmax = ymax = -double.MaxValue; + } + + public Rectangle(Rectangle other) + : this(other.Left, other.Bottom, other.Right, other.Top) + { + } + + /// + /// Initializes a new instance of the class + /// with predefined bounds. + /// + /// Minimum x value (left). + /// Minimum y value (bottom). + /// Width of the rectangle. + /// Height of the rectangle. + public Rectangle(double x, double y, double width, double height) + { + xmin = x; + ymin = y; + xmax = x + width; + ymax = y + height; + } + + /// + /// Gets the minimum x value (left boundary). + /// + public double Left => xmin; + + /// + /// Gets the maximum x value (right boundary). + /// + public double Right => xmax; + + /// + /// Gets the minimum y value (bottom boundary). + /// + public double Bottom => ymin; + + /// + /// Gets the maximum y value (top boundary). + /// + public double Top => ymax; + + /// + /// Gets the minimum x value (left boundary). + /// + public double X => xmin; + + /// + /// Gets the minimum y value (bottom boundary). + /// + public double Y => ymin; + + /// + /// Gets the width of the rectangle. + /// + public double Width => xmax - xmin; + + /// + /// Gets the height of the rectangle. + /// + public double Height => ymax - ymin; + + /// + /// Update bounds. + /// + /// Add dx to left and right bounds. + /// Add dy to top and bottom bounds. + public void Resize(double dx, double dy) + { + xmin -= dx; + xmax += dx; + ymin -= dy; + ymax += dy; + } + + /// + /// Expand rectangle to include given point. + /// + /// Point. + public void Expand(Point p) + { + xmin = Math.Min(xmin, p.x); + ymin = Math.Min(ymin, p.y); + xmax = Math.Max(xmax, p.x); + ymax = Math.Max(ymax, p.y); + } + + /// + /// Expand rectangle to include a list of points. + /// + public void Expand(IEnumerable points) + { + foreach (var p in points) + { + Expand(p); + } + } + + /// + /// Expand rectangle to include given rectangle. + /// + /// X coordinate. + /// Y coordinate. + public void Expand(Rectangle other) + { + xmin = Math.Min(xmin, other.xmin); + ymin = Math.Min(ymin, other.ymin); + xmax = Math.Max(xmax, other.xmax); + ymax = Math.Max(ymax, other.ymax); + } + + /// + /// Check if given point is inside rectangle. + /// + /// Point to check. + /// Point to check. + /// Return true, if rectangle contains given point. + public bool Contains(double x, double y) + { + return (x >= xmin) && (x <= xmax) && (y >= ymin) && (y <= ymax); + } + + /// + /// Check if given point is inside rectangle. + /// + /// Point to check. + /// Return true, if rectangle contains given point. + public bool Contains(Point pt) + { + return Contains(pt.x, pt.y); + } + + /// + /// Check if this rectangle contains other rectangle. + /// + /// Rectangle to check. + /// Return true, if this rectangle contains given rectangle. + public bool Contains(Rectangle other) + { + return xmin <= other.Left && other.Right <= xmax + && ymin <= other.Bottom && other.Top <= ymax; + } + + /// + /// Check if this rectangle intersects other rectangle. + /// + /// Rectangle to check. + /// Return true, if given rectangle intersects this rectangle. + public bool Intersects(Rectangle other) + { + return other.Left < xmax && xmin < other.Right + && other.Bottom < ymax && ymin < other.Top; + } + } +} diff --git a/Triangle.NET/Triangle/Geometry/RegionPointer.cs b/src/Triangle/Geometry/RegionPointer.cs similarity index 88% rename from Triangle.NET/Triangle/Geometry/RegionPointer.cs rename to src/Triangle/Geometry/RegionPointer.cs index 2db73a3..6eb93b3 100644 --- a/Triangle.NET/Triangle/Geometry/RegionPointer.cs +++ b/src/Triangle/Geometry/RegionPointer.cs @@ -1,63 +1,62 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Geometry -{ - using System; - using System.Collections.Generic; - - /// - /// Pointer to a region in the mesh geometry. A region is a well-defined - /// subset of the geomerty (enclosed by subsegments). - /// - public class RegionPointer - { - internal Point point; - internal int id; - internal double area; - - /// - /// Gets or sets a region area constraint. - /// - public double Area - { - get { return area; } - set - { - if (value < 0.0) - { - throw new ArgumentException("Area constraints must not be negative."); - } - area = value; - } - } - - /// - /// Initializes a new instance of the class. - /// - /// X coordinate of the region. - /// Y coordinate of the region. - /// Region id. - public RegionPointer(double x, double y, int id) - : this(x, y, id, 0.0) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// X coordinate of the region. - /// Y coordinate of the region. - /// Region id. - /// Area constraint. - public RegionPointer(double x, double y, int id, double area) - { - this.point = new Point(x, y); - this.id = id; - this.area = area; - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + + /// + /// Pointer to a region in the mesh geometry. A region is a well-defined + /// subset of the geometry (enclosed by subsegments). + /// + public class RegionPointer + { + internal Point point; + internal int id; + internal double area; + + /// + /// Gets or sets a region area constraint. + /// + public double Area + { + get { return area; } + set + { + if (value < 0.0) + { + throw new ArgumentException("Area constraints must not be negative."); + } + area = value; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// X coordinate of the region. + /// Y coordinate of the region. + /// Region id. + public RegionPointer(double x, double y, int id) + : this(x, y, id, 0.0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// X coordinate of the region. + /// Y coordinate of the region. + /// Region id. + /// Area constraint. + public RegionPointer(double x, double y, int id, double area) + { + this.point = new Point(x, y); + this.id = id; + this.area = area; + } + } +} diff --git a/Triangle.NET/Triangle/Geometry/Segment.cs b/src/Triangle/Geometry/Segment.cs similarity index 79% rename from Triangle.NET/Triangle/Geometry/Segment.cs rename to src/Triangle/Geometry/Segment.cs index c521709..463f169 100644 --- a/Triangle.NET/Triangle/Geometry/Segment.cs +++ b/src/Triangle/Geometry/Segment.cs @@ -1,92 +1,79 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Geometry -{ - using System; - - /// - /// Represents a straight line segment in 2D space. - /// - public class Segment : ISegment - { - Vertex v0; - Vertex v1; - - int label; - - /// - /// Gets or sets the segments boundary mark. - /// - public int Label - { - get { return label; } - set { label = value; } - } - /// - /// Gets the first endpoints index. - /// - public int P0 - { - get { return v0.id; } - } - - /// - /// Gets the second endpoints index. - /// - public int P1 - { - get { return v1.id; } - } - - /// - /// Initializes a new instance of the class. - /// - public Segment(Vertex v0, Vertex v1) - : this (v0, v1, 0) - { - } - - /// - /// Initializes a new instance of the class. - /// - public Segment(Vertex v0, Vertex v1, int label) - { - this.v0 = v0; - this.v1 = v1; - - this.label = label; - } - - /// - /// Gets the specified segment endpoint. - /// - /// The endpoint index (0 or 1). - /// - public Vertex GetVertex(int index) - { - if (index == 0) - { - return v0; - } - - if (index == 1) - { - return v1; - } - - throw new IndexOutOfRangeException(); - } - - /// - /// WARNING: not implemented. - /// - public ITriangle GetTriangle(int index) - { - throw new NotImplementedException(); - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + + /// + /// Represents a straight line segment in 2D space. + /// + public class Segment : ISegment + { + Vertex v0; + Vertex v1; + + int label; + + /// + /// Gets or sets the segments boundary mark. + /// + public int Label + { + get { return label; } + set { label = value; } + } + /// + /// Gets the first endpoints index. + /// + public int P0 => v0.id; + + /// + /// Gets the second endpoints index. + /// + public int P1 => v1.id; + + /// + /// Initializes a new instance of the class. + /// + public Segment(Vertex v0, Vertex v1) + : this (v0, v1, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + public Segment(Vertex v0, Vertex v1, int label) + { + this.v0 = v0; + this.v1 = v1; + + this.label = label; + } + + /// + /// Gets the specified segment endpoint. + /// + /// The endpoint index (0 or 1). + /// + public Vertex GetVertex(int index) + { + if (index == 0) return v0; + if (index == 1) return v1; + + throw new IndexOutOfRangeException(); + } + + /// + /// WARNING: not implemented. + /// + public ITriangle GetTriangle(int index) + { + throw new NotImplementedException(); + } + } +} diff --git a/Triangle.NET/Triangle/Geometry/Vertex.cs b/src/Triangle/Geometry/Vertex.cs similarity index 80% rename from Triangle.NET/Triangle/Geometry/Vertex.cs rename to src/Triangle/Geometry/Vertex.cs index 49f1b58..fdeb317 100644 --- a/Triangle.NET/Triangle/Geometry/Vertex.cs +++ b/src/Triangle/Geometry/Vertex.cs @@ -1,125 +1,111 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Geometry -{ - using System; - using TriangleNet.Topology; - - /// - /// The vertex data structure. - /// - public class Vertex : Point - { - // Hash for dictionary. Will be set by mesh instance. - internal int hash; - -#if USE_ATTRIBS - internal double[] attributes; -#endif - internal VertexType type; - internal Otri tri; - - /// - /// Initializes a new instance of the class. - /// - public Vertex() - : this(0, 0, 0) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The x coordinate of the vertex. - /// The y coordinate of the vertex. - public Vertex(double x, double y) - : this(x, y, 0) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The x coordinate of the vertex. - /// The y coordinate of the vertex. - /// The boundary mark. - public Vertex(double x, double y, int mark) - : base(x, y, mark) - { - this.type = VertexType.InputVertex; - } - -#if USE_ATTRIBS - /// - /// Initializes a new instance of the class. - /// - /// The x coordinate of the vertex. - /// The y coordinate of the vertex. - /// The boundary mark. - /// The number of point attributes. - public Vertex(double x, double y, int mark, int attribs) - : this(x, y, mark) - { - if (attribs > 0) - { - this.attributes = new double[attribs]; - } - } -#endif - - #region Public properties - -#if USE_ATTRIBS - /// - /// Gets the vertex attributes (may be null). - /// - public double[] Attributes - { - get { return this.attributes; } - } -#endif - - /// - /// Gets the vertex type. - /// - public VertexType Type - { - get { return this.type; } - } - - /// - /// Gets the specified coordinate of the vertex. - /// - /// Coordinate index. - /// X coordinate, if index is 0, Y coordinate, if index is 1. - public double this[int i] - { - get - { - if (i == 0) - { - return x; - } - - if (i == 1) - { - return y; - } - - throw new ArgumentOutOfRangeException("Index must be 0 or 1."); - } - } - - #endregion - - public override int GetHashCode() - { - return this.hash; - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Geometry +{ + using System; + using TriangleNet.Topology; + + /// + /// The vertex data structure. + /// + public class Vertex : Point + { + // Hash for dictionary. Will be set by mesh instance. + internal int hash; + +#if USE_ATTRIBS + internal double[] attributes; +#endif + internal VertexType type; + internal Otri tri; + + /// + /// Initializes a new instance of the class. + /// + public Vertex() + : this(0, 0, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The x coordinate of the vertex. + /// The y coordinate of the vertex. + public Vertex(double x, double y) + : this(x, y, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The x coordinate of the vertex. + /// The y coordinate of the vertex. + /// The boundary mark. + public Vertex(double x, double y, int mark) + : base(x, y, mark) + { + type = VertexType.InputVertex; + } + +#if USE_ATTRIBS + /// + /// Initializes a new instance of the class. + /// + /// The x coordinate of the vertex. + /// The y coordinate of the vertex. + /// The boundary mark. + /// The number of point attributes. + public Vertex(double x, double y, int mark, int attribs) + : this(x, y, mark) + { + if (attribs > 0) + { + this.attributes = new double[attribs]; + } + } +#endif + + #region Public properties + +#if USE_ATTRIBS + /// + /// Gets the vertex attributes (may be null). + /// + public double[] Attributes + { + get { return this.attributes; } + } +#endif + + /// + /// Gets the vertex type. + /// + public VertexType Type => type; + + /// + /// Gets the specified coordinate of the vertex. + /// + /// Coordinate index. + /// X coordinate, if index is 0, Y coordinate, if index is 1. + public double this[int i] + { + get + { + if (i == 0) return x; + if (i == 1) return y; + + throw new ArgumentOutOfRangeException("Index must be 0 or 1."); + } + } + + #endregion + + public override int GetHashCode() => hash; + } +} diff --git a/Triangle.NET/Triangle/IO/DebugWriter.cs b/src/Triangle/IO/DebugWriter.cs similarity index 95% rename from Triangle.NET/Triangle/IO/DebugWriter.cs rename to src/Triangle/IO/DebugWriter.cs index 717c5ad..b78b970 100644 --- a/Triangle.NET/Triangle/IO/DebugWriter.cs +++ b/src/Triangle/IO/DebugWriter.cs @@ -1,263 +1,263 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.IO -{ - using System; - using System.Globalization; - using System.IO; - using System.IO.Compression; - using System.Text; - using TriangleNet.Topology; - using TriangleNet.Geometry; - - /// - /// Writes a the current mesh into a text file. - /// - /// - /// File format: - /// - /// num_nodes - /// id_1 nx ny mark - /// ... - /// id_n nx ny mark - /// - /// num_segs - /// id_1 p1 p2 mark - /// ... - /// id_n p1 p2 mark - /// - /// num_tris - /// id_1 p1 p2 p3 n1 n2 n3 - /// ... - /// id_n p1 p2 p3 n1 n2 n3 - /// - class DebugWriter - { - static NumberFormatInfo nfi = CultureInfo.InvariantCulture.NumberFormat; - - int iteration; - string session; - StreamWriter stream; - string tmpFile; - int[] vertices; - int triangles; - - #region Singleton pattern - - private static readonly DebugWriter instance = new DebugWriter(); - - // Explicit static constructor to tell C# compiler - // not to mark type as beforefieldinit - static DebugWriter() { } - - private DebugWriter() { } - - public static DebugWriter Session - { - get - { - return instance; - } - } - - #endregion - - /// - /// Start a new session with given name. - /// - /// Name of the session (and output files). - public void Start(string session) - { - this.iteration = 0; - this.session = session; - - if (this.stream != null) - { - throw new Exception("A session is active. Finish before starting a new."); - } - - this.tmpFile = Path.GetTempFileName(); - this.stream = new StreamWriter(tmpFile); - } - - /// - /// Write complete mesh to file. - /// - public void Write(Mesh mesh, bool skip = false) - { - this.WriteMesh(mesh, skip); - - this.triangles = mesh.Triangles.Count; - } - - /// - /// Finish this session. - /// - public void Finish() - { - this.Finish(session + ".mshx"); - } - - private void Finish(string path) - { - if (stream != null) - { - stream.Flush(); - stream.Dispose(); - stream = null; - - string header = "#!N" + this.iteration + Environment.NewLine; - - using (var gzFile = new FileStream(path, FileMode.Create)) - { - using (var gzStream = new GZipStream(gzFile, CompressionMode.Compress, false)) - { - byte[] bytes = Encoding.UTF8.GetBytes(header); - gzStream.Write(bytes, 0, bytes.Length); - - // TODO: read with stream - bytes = File.ReadAllBytes(tmpFile); - gzStream.Write(bytes, 0, bytes.Length); - } - } - - File.Delete(this.tmpFile); - } - } - - private void WriteGeometry(IPolygon geometry) - { - stream.WriteLine("#!G{0}", this.iteration++); - } - - private void WriteMesh(Mesh mesh, bool skip) - { - // Mesh may have changed, but we choose to skip - if (triangles == mesh.triangles.Count && skip) - { - return; - } - - // Header line - stream.WriteLine("#!M{0}", this.iteration++); - - Vertex p1, p2, p3; - - if (VerticesChanged(mesh)) - { - HashVertices(mesh); - - // Number of vertices. - stream.WriteLine("{0}", mesh.vertices.Count); - - foreach (var v in mesh.vertices.Values) - { - // Vertex number, x and y coordinates and marker. - stream.WriteLine("{0} {1} {2} {3}", v.id, v.x.ToString(nfi), v.y.ToString(nfi), v.label); - } - } - else - { - stream.WriteLine("0"); - } - - // Number of segments. - stream.WriteLine("{0}", mesh.subsegs.Count); - - Osub subseg = default(Osub); - subseg.orient = 0; - - foreach (var item in mesh.subsegs.Values) - { - if (item.hash <= 0) - { - continue; - } - - subseg.seg = item; - - p1 = subseg.Org(); - p2 = subseg.Dest(); - - // Segment number, indices of its two endpoints, and marker. - stream.WriteLine("{0} {1} {2} {3}", subseg.seg.hash, p1.id, p2.id, subseg.seg.boundary); - } - - Otri tri = default(Otri), trisym = default(Otri); - tri.orient = 0; - - int n1, n2, n3, h1, h2, h3; - - // Number of triangles. - stream.WriteLine("{0}", mesh.triangles.Count); - - foreach (var item in mesh.triangles) - { - tri.tri = item; - - p1 = tri.Org(); - p2 = tri.Dest(); - p3 = tri.Apex(); - - h1 = (p1 == null) ? -1 : p1.id; - h2 = (p2 == null) ? -1 : p2.id; - h3 = (p3 == null) ? -1 : p3.id; - - // Triangle number, indices for three vertices. - stream.Write("{0} {1} {2} {3}", tri.tri.hash, h1, h2, h3); - - tri.orient = 1; - tri.Sym(ref trisym); - n1 = trisym.tri.hash; - - tri.orient = 2; - tri.Sym(ref trisym); - n2 = trisym.tri.hash; - - tri.orient = 0; - tri.Sym(ref trisym); - n3 = trisym.tri.hash; - - // Neighboring triangle numbers. - stream.WriteLine(" {0} {1} {2}", n1, n2, n3); - } - } - - private bool VerticesChanged(Mesh mesh) - { - if (vertices == null || mesh.Vertices.Count != vertices.Length) - { - return true; - } - - int i = 0; - foreach (var v in mesh.Vertices) - { - if (v.id != vertices[i++]) - { - return true; - } - } - - return false; - } - - private void HashVertices(Mesh mesh) - { - if (vertices == null || mesh.Vertices.Count != vertices.Length) - { - vertices = new int[mesh.Vertices.Count]; - } - - int i = 0; - foreach (var v in mesh.Vertices) - { - vertices[i++] = v.id; - } - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System; + using System.Globalization; + using System.IO; + using System.IO.Compression; + using System.Text; + using TriangleNet.Topology; + using TriangleNet.Geometry; + + /// + /// Writes a the current mesh into a text file. + /// + /// + /// File format: + /// + /// num_nodes + /// id_1 nx ny mark + /// ... + /// id_n nx ny mark + /// + /// num_segs + /// id_1 p1 p2 mark + /// ... + /// id_n p1 p2 mark + /// + /// num_tris + /// id_1 p1 p2 p3 n1 n2 n3 + /// ... + /// id_n p1 p2 p3 n1 n2 n3 + /// + class DebugWriter + { + static NumberFormatInfo nfi = CultureInfo.InvariantCulture.NumberFormat; + + int iteration; + string session; + StreamWriter stream; + string tmpFile; + int[] vertices; + int triangles; + + #region Singleton pattern + + private static readonly DebugWriter instance = new DebugWriter(); + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static DebugWriter() { } + + private DebugWriter() { } + + public static DebugWriter Session + { + get + { + return instance; + } + } + + #endregion + + /// + /// Start a new session with given name. + /// + /// Name of the session (and output files). + public void Start(string session) + { + this.iteration = 0; + this.session = session; + + if (this.stream != null) + { + throw new Exception("A session is active. Finish before starting a new."); + } + + this.tmpFile = Path.GetTempFileName(); + this.stream = new StreamWriter(tmpFile); + } + + /// + /// Write complete mesh to file. + /// + public void Write(Mesh mesh, bool skip = false) + { + this.WriteMesh(mesh, skip); + + this.triangles = mesh.Triangles.Count; + } + + /// + /// Finish this session. + /// + public void Finish() + { + this.Finish(session + ".mshx"); + } + + private void Finish(string path) + { + if (stream != null) + { + stream.Flush(); + stream.Dispose(); + stream = null; + + string header = "#!N" + this.iteration + Environment.NewLine; + + using (var gzFile = new FileStream(path, FileMode.Create)) + { + using (var gzStream = new GZipStream(gzFile, CompressionMode.Compress, false)) + { + byte[] bytes = Encoding.UTF8.GetBytes(header); + gzStream.Write(bytes, 0, bytes.Length); + + // TODO: read with stream + bytes = File.ReadAllBytes(tmpFile); + gzStream.Write(bytes, 0, bytes.Length); + } + } + + File.Delete(this.tmpFile); + } + } + + private void WriteGeometry(IPolygon geometry) + { + stream.WriteLine("#!G{0}", this.iteration++); + } + + private void WriteMesh(Mesh mesh, bool skip) + { + // Mesh may have changed, but we choose to skip + if (triangles == mesh.triangles.Count && skip) + { + return; + } + + // Header line + stream.WriteLine("#!M{0}", this.iteration++); + + Vertex p1, p2, p3; + + if (VerticesChanged(mesh)) + { + HashVertices(mesh); + + // Number of vertices. + stream.WriteLine("{0}", mesh.vertices.Count); + + foreach (var v in mesh.vertices.Values) + { + // Vertex number, x and y coordinates and marker. + stream.WriteLine("{0} {1} {2} {3}", v.id, v.x.ToString(nfi), v.y.ToString(nfi), v.label); + } + } + else + { + stream.WriteLine("0"); + } + + // Number of segments. + stream.WriteLine("{0}", mesh.subsegs.Count); + + Osub subseg = default(Osub); + subseg.orient = 0; + + foreach (var item in mesh.subsegs.Values) + { + if (item.hash <= 0) + { + continue; + } + + subseg.seg = item; + + p1 = subseg.Org(); + p2 = subseg.Dest(); + + // Segment number, indices of its two endpoints, and marker. + stream.WriteLine("{0} {1} {2} {3}", subseg.seg.hash, p1.id, p2.id, subseg.seg.boundary); + } + + Otri tri = default(Otri), trisym = default(Otri); + tri.orient = 0; + + int n1, n2, n3, h1, h2, h3; + + // Number of triangles. + stream.WriteLine("{0}", mesh.triangles.Count); + + foreach (var item in mesh.triangles) + { + tri.tri = item; + + p1 = tri.Org(); + p2 = tri.Dest(); + p3 = tri.Apex(); + + h1 = (p1 == null) ? -1 : p1.id; + h2 = (p2 == null) ? -1 : p2.id; + h3 = (p3 == null) ? -1 : p3.id; + + // Triangle number, indices for three vertices. + stream.Write("{0} {1} {2} {3}", tri.tri.hash, h1, h2, h3); + + tri.orient = 1; + tri.Sym(ref trisym); + n1 = trisym.tri.hash; + + tri.orient = 2; + tri.Sym(ref trisym); + n2 = trisym.tri.hash; + + tri.orient = 0; + tri.Sym(ref trisym); + n3 = trisym.tri.hash; + + // Neighboring triangle numbers. + stream.WriteLine(" {0} {1} {2}", n1, n2, n3); + } + } + + private bool VerticesChanged(Mesh mesh) + { + if (vertices == null || mesh.Vertices.Count != vertices.Length) + { + return true; + } + + int i = 0; + foreach (var v in mesh.Vertices) + { + if (v.id != vertices[i++]) + { + return true; + } + } + + return false; + } + + private void HashVertices(Mesh mesh) + { + if (vertices == null || mesh.Vertices.Count != vertices.Length) + { + vertices = new int[mesh.Vertices.Count]; + } + + int i = 0; + foreach (var v in mesh.Vertices) + { + vertices[i++] = v.id; + } + } + } +} diff --git a/Triangle.NET/Triangle/IO/FileProcessor.cs b/src/Triangle/IO/FileProcessor.cs similarity index 94% rename from Triangle.NET/Triangle/IO/FileProcessor.cs rename to src/Triangle/IO/FileProcessor.cs index d2f1b70..b4b9224 100644 --- a/Triangle.NET/Triangle/IO/FileProcessor.cs +++ b/src/Triangle/IO/FileProcessor.cs @@ -1,126 +1,126 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.IO -{ - using System; - using System.Collections.Generic; - using TriangleNet.Geometry; - using TriangleNet.Meshing; - - public static class FileProcessor - { - static List formats; - - static FileProcessor() - { - formats = new List(); - - // Add Triangle file format as default. - formats.Add(new TriangleFormat()); - } - - public static void Add(IFileFormat format) - { - formats.Add(format); - } - - public static bool IsSupported(string file) - { - foreach (var format in formats) - { - if (format.IsSupported(file)) - { - return true; - } - } - - return false; - } - - #region Polygon read/write - - /// - /// Read a file containing polygon geometry. - /// - /// The path of the file to read. - /// An instance of the class. - public static IPolygon Read(string filename) - { - foreach (IPolygonFormat format in formats) - { - if (format != null && format.IsSupported(filename)) - { - return format.Read(filename); - } - } - - throw new Exception("File format not supported."); - } - - /// - /// Save a polygon geometry to disk. - /// - /// An instance of the class. - /// The path of the file to save. - public static void Write(IPolygon polygon, string filename) - { - foreach (IPolygonFormat format in formats) - { - if (format != null && format.IsSupported(filename)) - { - format.Write(polygon, filename); - return; - } - } - - throw new Exception("File format not supported."); - } - - #endregion - - #region Mesh read/write - - /// - /// Read a file containing a mesh. - /// - /// The path of the file to read. - /// An instance of the interface. - public static IMesh Import(string filename) - { - foreach (IMeshFormat format in formats) - { - if (format != null && format.IsSupported(filename)) - { - return format.Import(filename); - } - } - - throw new Exception("File format not supported."); - } - - /// - /// Save a mesh to disk. - /// - /// An instance of the interface. - /// The path of the file to save. - public static void Write(IMesh mesh, string filename) - { - foreach (IMeshFormat format in formats) - { - if (format != null && format.IsSupported(filename)) - { - format.Write(mesh, filename); - return; - } - } - - throw new Exception("File format not supported."); - } - - #endregion - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System; + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + + public static class FileProcessor + { + static List formats; + + static FileProcessor() + { + formats = new List(); + + // Add Triangle file format as default. + formats.Add(new TriangleFormat()); + } + + public static void Add(IFileFormat format) + { + formats.Add(format); + } + + public static bool IsSupported(string file) + { + foreach (var format in formats) + { + if (format.IsSupported(file)) + { + return true; + } + } + + return false; + } + + #region Polygon read/write + + /// + /// Read a file containing polygon geometry. + /// + /// The path of the file to read. + /// An instance of the class. + public static IPolygon Read(string filename) + { + foreach (IPolygonFormat format in formats) + { + if (format != null && format.IsSupported(filename)) + { + return format.Read(filename); + } + } + + throw new Exception("File format not supported."); + } + + /// + /// Save a polygon geometry to disk. + /// + /// An instance of the class. + /// The path of the file to save. + public static void Write(IPolygon polygon, string filename) + { + foreach (IPolygonFormat format in formats) + { + if (format != null && format.IsSupported(filename)) + { + format.Write(polygon, filename); + return; + } + } + + throw new Exception("File format not supported."); + } + + #endregion + + #region Mesh read/write + + /// + /// Read a file containing a mesh. + /// + /// The path of the file to read. + /// An instance of the interface. + public static IMesh Import(string filename) + { + foreach (IMeshFormat format in formats) + { + if (format != null && format.IsSupported(filename)) + { + return format.Import(filename); + } + } + + throw new Exception("File format not supported."); + } + + /// + /// Save a mesh to disk. + /// + /// An instance of the interface. + /// The path of the file to save. + public static void Write(IMesh mesh, string filename) + { + foreach (IMeshFormat format in formats) + { + if (format != null && format.IsSupported(filename)) + { + format.Write(mesh, filename); + return; + } + } + + throw new Exception("File format not supported."); + } + + #endregion + } +} diff --git a/Triangle.NET/Triangle/IO/IFileFormat.cs b/src/Triangle/IO/IFileFormat.cs similarity index 78% rename from Triangle.NET/Triangle/IO/IFileFormat.cs rename to src/Triangle/IO/IFileFormat.cs index cd61998..490641a 100644 --- a/Triangle.NET/Triangle/IO/IFileFormat.cs +++ b/src/Triangle/IO/IFileFormat.cs @@ -1,13 +1,13 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.IO -{ - public interface IFileFormat - { - bool IsSupported(string file); - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + public interface IFileFormat + { + bool IsSupported(string file); + } +} diff --git a/Triangle.NET/Triangle/IO/IMeshFormat.cs b/src/Triangle/IO/IMeshFormat.cs similarity index 92% rename from Triangle.NET/Triangle/IO/IMeshFormat.cs rename to src/Triangle/IO/IMeshFormat.cs index 99a2a13..846716f 100644 --- a/Triangle.NET/Triangle/IO/IMeshFormat.cs +++ b/src/Triangle/IO/IMeshFormat.cs @@ -1,38 +1,38 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.IO -{ - using System.IO; - using TriangleNet.Meshing; - - /// - /// Interface for mesh I/O. - /// - public interface IMeshFormat : IFileFormat - { - /// - /// Read a file containing a mesh. - /// - /// The path of the file to read. - /// An instance of the interface. - IMesh Import(string filename); - - /// - /// Save a mesh to disk. - /// - /// An instance of the interface. - /// The path of the file to save. - void Write(IMesh mesh, string filename); - - /// - /// Save a mesh to a . - /// - /// An instance of the interface. - /// The stream to save to. - void Write(IMesh mesh, Stream stream); - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System.IO; + using TriangleNet.Meshing; + + /// + /// Interface for mesh I/O. + /// + public interface IMeshFormat : IFileFormat + { + /// + /// Read a file containing a mesh. + /// + /// The path of the file to read. + /// An instance of the interface. + IMesh Import(string filename); + + /// + /// Save a mesh to disk. + /// + /// An instance of the interface. + /// The path of the file to save. + void Write(IMesh mesh, string filename); + + /// + /// Save a mesh to a . + /// + /// An instance of the interface. + /// The stream to save to. + void Write(IMesh mesh, Stream stream); + } +} diff --git a/Triangle.NET/Triangle/IO/IPolygonFormat.cs b/src/Triangle/IO/IPolygonFormat.cs similarity index 92% rename from Triangle.NET/Triangle/IO/IPolygonFormat.cs rename to src/Triangle/IO/IPolygonFormat.cs index 7394243..ba2e9a7 100644 --- a/Triangle.NET/Triangle/IO/IPolygonFormat.cs +++ b/src/Triangle/IO/IPolygonFormat.cs @@ -1,38 +1,38 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.IO -{ - using System.IO; - using TriangleNet.Geometry; - - /// - /// Interface for geometry input. - /// - public interface IPolygonFormat : IFileFormat - { - /// - /// Read a file containing polygon geometry. - /// - /// The path of the file to read. - /// An instance of the class. - IPolygon Read(string filename); - - /// - /// Save a polygon geometry to disk. - /// - /// An instance of the class. - /// The path of the file to save. - void Write(IPolygon polygon, string filename); - - /// - /// Save a polygon geometry to a . - /// - /// An instance of the class. - /// The stream to save to. - void Write(IPolygon polygon, Stream stream); - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System.IO; + using TriangleNet.Geometry; + + /// + /// Interface for geometry input. + /// + public interface IPolygonFormat : IFileFormat + { + /// + /// Read a file containing polygon geometry. + /// + /// The path of the file to read. + /// An instance of the class. + IPolygon Read(string filename); + + /// + /// Save a polygon geometry to disk. + /// + /// An instance of the class. + /// The path of the file to save. + void Write(IPolygon polygon, string filename); + + /// + /// Save a polygon geometry to a . + /// + /// An instance of the class. + /// The stream to save to. + void Write(IPolygon polygon, Stream stream); + } +} diff --git a/Triangle.NET/Triangle/IO/InputTriangle.cs b/src/Triangle/IO/InputTriangle.cs similarity index 72% rename from Triangle.NET/Triangle/IO/InputTriangle.cs rename to src/Triangle/IO/InputTriangle.cs index 5f46353..16d428d 100644 --- a/Triangle.NET/Triangle/IO/InputTriangle.cs +++ b/src/Triangle/IO/InputTriangle.cs @@ -1,85 +1,89 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.IO -{ - using TriangleNet.Topology; - using TriangleNet.Geometry; - - /// - /// Simple triangle class for input. - /// - public class InputTriangle : ITriangle - { - internal int[] vertices; - internal int label; - internal double area; - - public InputTriangle(int p0, int p1, int p2) - { - this.vertices = new int[] { p0, p1, p2 }; - } - - #region Public properties - - /// - /// Gets the triangle id. - /// - public int ID - { - get { return 0; } - set { } - } - - /// - /// Region ID the triangle belongs to. - /// - public int Label - { - get { return label; } - set { label = value; } - } - - /// - /// Gets the triangle area constraint. - /// - public double Area - { - get { return area; } - set { area = value; } - } - - /// - /// Gets the specified corners vertex. - /// - public Vertex GetVertex(int index) - { - return null; // TODO: throw NotSupportedException? - } - - public int GetVertexID(int index) - { - return vertices[index]; - } - - public ITriangle GetNeighbor(int index) - { - return null; - } - - public int GetNeighborID(int index) - { - return -1; - } - - public ISegment GetSegment(int index) - { - return null; - } - - #endregion - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System; + using TriangleNet.Geometry; + + /// + /// Simple triangle class for input. + /// + public class InputTriangle : ITriangle + { + internal int[] vertices; + internal int label; + internal double area; + + public InputTriangle(int p0, int p1, int p2) + { + vertices = new int[] { p0, p1, p2 }; + } + + #region Public properties + + /// + public int ID + { + get { return 0; } + set { } + } + + /// + public int Label + { + get { return label; } + set { label = value; } + } + + /// + public double Area + { + get { return area; } + set { area = value; } + } + + /// + /// WARNING: not implemented. + /// + public Vertex GetVertex(int index) + { + throw new NotImplementedException(); + } + + /// + public int GetVertexID(int index) + { + return vertices[index]; + } + + /// + /// WARNING: not implemented. + /// + public ITriangle GetNeighbor(int index) + { + throw new NotImplementedException(); + } + + /// + /// WARNING: not implemented. + /// + public int GetNeighborID(int index) + { + throw new NotImplementedException(); + } + + /// + /// WARNING: not implemented. + /// + public ISegment GetSegment(int index) + { + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/Triangle.NET/Triangle/IO/TriangleFormat.cs b/src/Triangle/IO/TriangleFormat.cs similarity index 91% rename from Triangle.NET/Triangle/IO/TriangleFormat.cs rename to src/Triangle/IO/TriangleFormat.cs index 9fd7965..244280b 100644 --- a/Triangle.NET/Triangle/IO/TriangleFormat.cs +++ b/src/Triangle/IO/TriangleFormat.cs @@ -1,92 +1,92 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.IO -{ - using System; - using System.Collections.Generic; - using System.IO; - using TriangleNet.Geometry; - using TriangleNet.Meshing; - - /// - /// Implements geometry and mesh file formats of the the original Triangle code. - /// - public class TriangleFormat : IPolygonFormat, IMeshFormat - { - public bool IsSupported(string file) - { - string ext = Path.GetExtension(file).ToLower(); - - if (ext == ".node" || ext == ".poly" || ext == ".ele") - { - return true; - } - - return false; - } - - public IMesh Import(string filename) - { - string ext = Path.GetExtension(filename); - - if (ext == ".node" || ext == ".poly" || ext == ".ele") - { - List triangles; - Polygon geometry; - - (new TriangleReader()).Read(filename, out geometry, out triangles); - - if (geometry != null && triangles != null) - { - return Converter.ToMesh(geometry, triangles.ToArray()); - } - } - - throw new NotSupportedException("Could not load '" + filename + "' file."); - } - - public void Write(IMesh mesh, string filename) - { - var writer = new TriangleWriter(); - - writer.WritePoly((Mesh)mesh, Path.ChangeExtension(filename, ".poly")); - writer.WriteElements((Mesh)mesh, Path.ChangeExtension(filename, ".ele")); - } - - public void Write(IMesh mesh, Stream stream) - { - throw new NotImplementedException(); - } - - public IPolygon Read(string filename) - { - string ext = Path.GetExtension(filename); - - if (ext == ".node") - { - return (new TriangleReader()).ReadNodeFile(filename); - } - else if (ext == ".poly") - { - return (new TriangleReader()).ReadPolyFile(filename); - } - - throw new NotSupportedException("File format '" + ext + "' not supported."); - } - - - public void Write(IPolygon polygon, string filename) - { - (new TriangleWriter()).WritePoly(polygon, filename); - } - - public void Write(IPolygon polygon, Stream stream) - { - throw new NotImplementedException(); - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System; + using System.Collections.Generic; + using System.IO; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + + /// + /// Implements geometry and mesh file formats of the the original Triangle code. + /// + public class TriangleFormat : IPolygonFormat, IMeshFormat + { + public bool IsSupported(string file) + { + string ext = Path.GetExtension(file).ToLower(); + + if (ext == ".node" || ext == ".poly" || ext == ".ele") + { + return true; + } + + return false; + } + + public IMesh Import(string filename) + { + string ext = Path.GetExtension(filename); + + if (ext == ".node" || ext == ".poly" || ext == ".ele") + { + List triangles; + Polygon geometry; + + (new TriangleReader()).Read(filename, out geometry, out triangles); + + if (geometry != null && triangles != null) + { + return Converter.Instance.ToMesh(geometry, triangles.ToArray()); + } + } + + throw new NotSupportedException("Could not load '" + filename + "' file."); + } + + public void Write(IMesh mesh, string filename) + { + var writer = new TriangleWriter(); + + writer.WritePoly((Mesh)mesh, Path.ChangeExtension(filename, ".poly")); + writer.WriteElements((Mesh)mesh, Path.ChangeExtension(filename, ".ele")); + } + + public void Write(IMesh mesh, Stream stream) + { + throw new NotImplementedException(); + } + + public IPolygon Read(string filename) + { + string ext = Path.GetExtension(filename); + + if (ext == ".node") + { + return (new TriangleReader()).ReadNodeFile(filename); + } + else if (ext == ".poly") + { + return (new TriangleReader()).ReadPolyFile(filename); + } + + throw new NotSupportedException("File format '" + ext + "' not supported."); + } + + + public void Write(IPolygon polygon, string filename) + { + (new TriangleWriter()).WritePoly(polygon, filename); + } + + public void Write(IPolygon polygon, Stream stream) + { + throw new NotImplementedException(); + } + } +} diff --git a/Triangle.NET/Triangle/IO/TriangleReader.cs b/src/Triangle/IO/TriangleReader.cs similarity index 93% rename from Triangle.NET/Triangle/IO/TriangleReader.cs rename to src/Triangle/IO/TriangleReader.cs index 747a3bf..3b627b9 100644 --- a/Triangle.NET/Triangle/IO/TriangleReader.cs +++ b/src/Triangle/IO/TriangleReader.cs @@ -1,741 +1,737 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.IO -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using TriangleNet.Geometry; - - /// - /// Helper methods for reading Triangle file formats. - /// - public class TriangleReader - { - static NumberFormatInfo nfi = NumberFormatInfo.InvariantInfo; - - int startIndex = 0; - - #region Helper methods - - private bool TryReadLine(StreamReader reader, out string[] token) - { - token = null; - - if (reader.EndOfStream) - { - return false; - } - - string line = reader.ReadLine().Trim(); - - while (String.IsNullOrWhiteSpace(line) || line.StartsWith("#")) - { - if (reader.EndOfStream) - { - return false; - } - - line = reader.ReadLine().Trim(); - } - - token = line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); - - return true; - } - - /// - /// Read vertex information of the given line. - /// - /// The input geometry. - /// The current vertex index. - /// The current line. - /// Number of point attributes - /// Number of point markers (0 or 1) - private void ReadVertex(List data, int index, string[] line, int attributes, int marks) - { - double x = double.Parse(line[1], nfi); - double y = double.Parse(line[2], nfi); - - var v = new Vertex(x, y); - - // Read a vertex marker. - if (marks > 0 && line.Length > 3 + attributes) - { - v.Label = int.Parse(line[3 + attributes]); - } - - if (attributes > 0) - { -#if USE_ATTRIBS - var attribs = new double[attributes]; - - // Read the vertex attributes. - for (int j = 0; j < attributes; j++) - { - if (line.Length > 3 + j) - { - attribs[j] = double.Parse(line[3 + j], nfi); - } - } - - v.attributes = attribs; -#endif - } - - data.Add(v); - } - - #endregion - - #region Main I/O methods - - /// - /// Reads geometry information from .node or .poly files. - /// - public void Read(string filename, out Polygon polygon) - { - polygon = null; - - string path = Path.ChangeExtension(filename, ".poly"); - - if (File.Exists(path)) - { - polygon = ReadPolyFile(path); - } - else - { - path = Path.ChangeExtension(filename, ".node"); - polygon = ReadNodeFile(path); - } - } - - /// - /// Reads a mesh from .node, .poly or .ele files. - /// - public void Read(string filename, out Polygon geometry, out List triangles) - { - triangles = null; - - Read(filename, out geometry); - - string path = Path.ChangeExtension(filename, ".ele"); - - if (File.Exists(path) && geometry != null) - { - triangles = ReadEleFile(path); - } - } - - /// - /// Reads geometry information from .node or .poly files. - /// - public IPolygon Read(string filename) - { - Polygon geometry = null; - - Read(filename, out geometry); - - return geometry; - } - - #endregion - - /// - /// Read the vertices from a file, which may be a .node or .poly file. - /// - /// - /// Will NOT read associated .ele by default. - public Polygon ReadNodeFile(string nodefilename) - { - return ReadNodeFile(nodefilename, false); - } - - /// - /// Read the vertices from a file, which may be a .node or .poly file. - /// - /// - /// - public Polygon ReadNodeFile(string nodefilename, bool readElements) - { - Polygon data; - - startIndex = 0; - - string[] line; - int invertices = 0, attributes = 0, nodemarkers = 0; - - using (var reader = new StreamReader(nodefilename)) - { - if (!TryReadLine(reader, out line)) - { - throw new Exception("Can't read input file."); - } - - // Read number of vertices, number of dimensions, number of vertex - // attributes, and number of boundary markers. - invertices = int.Parse(line[0]); - - if (invertices < 3) - { - throw new Exception("Input must have at least three input vertices."); - } - - if (line.Length > 1) - { - if (int.Parse(line[1]) != 2) - { - throw new Exception("Triangle only works with two-dimensional meshes."); - } - } - - if (line.Length > 2) - { - attributes = int.Parse(line[2]); - } - - if (line.Length > 3) - { - nodemarkers = int.Parse(line[3]); - } - - data = new Polygon(invertices); - - // Read the vertices. - if (invertices > 0) - { - for (int i = 0; i < invertices; i++) - { - if (!TryReadLine(reader, out line)) - { - throw new Exception("Can't read input file (vertices)."); - } - - if (line.Length < 3) - { - throw new Exception("Invalid vertex."); - } - - if (i == 0) - { - startIndex = int.Parse(line[0], nfi); - } - - ReadVertex(data.Points, i, line, attributes, nodemarkers); - } - } - } - - if (readElements) - { - // Read area file - string elefile = Path.ChangeExtension(nodefilename, ".ele"); - if (File.Exists(elefile)) - { - ReadEleFile(elefile, true); - } - } - - return data; - } - - /// - /// Read the vertices and segments from a .poly file. - /// - /// - /// Will NOT read associated .ele by default. - public Polygon ReadPolyFile(string polyfilename) - { - return ReadPolyFile(polyfilename, false, false); - } - - /// - /// Read the vertices and segments from a .poly file. - /// - /// - /// If true, look for an associated .ele file. - /// Will NOT read associated .area by default. - public Polygon ReadPolyFile(string polyfilename, bool readElements) - { - return ReadPolyFile(polyfilename, readElements, false); - } - - /// - /// Read the vertices and segments from a .poly file. - /// - /// - /// If true, look for an associated .ele file. - /// If true, look for an associated .area file. - public Polygon ReadPolyFile(string polyfilename, bool readElements, bool readArea) - { - // Read poly file - Polygon data; - - startIndex = 0; - - string[] line; - int invertices = 0, attributes = 0, nodemarkers = 0; - - using (var reader = new StreamReader(polyfilename)) - { - if (!TryReadLine(reader, out line)) - { - throw new Exception("Can't read input file."); - } - - // Read number of vertices, number of dimensions, number of vertex - // attributes, and number of boundary markers. - invertices = int.Parse(line[0]); - - if (line.Length > 1) - { - if (int.Parse(line[1]) != 2) - { - throw new Exception("Triangle only works with two-dimensional meshes."); - } - } - - if (line.Length > 2) - { - attributes = int.Parse(line[2]); - } - - if (line.Length > 3) - { - nodemarkers = int.Parse(line[3]); - } - - // Read the vertices. - if (invertices > 0) - { - data = new Polygon(invertices); - - for (int i = 0; i < invertices; i++) - { - if (!TryReadLine(reader, out line)) - { - throw new Exception("Can't read input file (vertices)."); - } - - if (line.Length < 3) - { - throw new Exception("Invalid vertex."); - } - - if (i == 0) - { - // Set the start index! - startIndex = int.Parse(line[0], nfi); - } - - ReadVertex(data.Points, i, line, attributes, nodemarkers); - } - } - else - { - // If the .poly file claims there are zero vertices, that means that - // the vertices should be read from a separate .node file. - data = ReadNodeFile(Path.ChangeExtension(polyfilename, ".node")); - - invertices = data.Points.Count; - } - - var points = data.Points; - - if (points.Count == 0) - { - throw new Exception("No nodes available."); - } - - // Read the segments from a .poly file. - - // Read number of segments and number of boundary markers. - if (!TryReadLine(reader, out line)) - { - throw new Exception("Can't read input file (segments)."); - } - - int insegments = int.Parse(line[0]); - - int segmentmarkers = 0; - if (line.Length > 1) - { - segmentmarkers = int.Parse(line[1]); - } - - int end1, end2, mark; - // Read and insert the segments. - for (int i = 0; i < insegments; i++) - { - if (!TryReadLine(reader, out line)) - { - throw new Exception("Can't read input file (segments)."); - } - - if (line.Length < 3) - { - throw new Exception("Segment has no endpoints."); - } - - // TODO: startIndex ok? - end1 = int.Parse(line[1]) - startIndex; - end2 = int.Parse(line[2]) - startIndex; - mark = 0; - - if (segmentmarkers > 0 && line.Length > 3) - { - mark = int.Parse(line[3]); - } - - if ((end1 < 0) || (end1 >= invertices)) - { - if (Log.Verbose) - { - Log.Instance.Warning("Invalid first endpoint of segment.", - "MeshReader.ReadPolyfile()"); - } - } - else if ((end2 < 0) || (end2 >= invertices)) - { - if (Log.Verbose) - { - Log.Instance.Warning("Invalid second endpoint of segment.", - "MeshReader.ReadPolyfile()"); - } - } - else - { - data.Add(new Segment(points[end1], points[end2], mark)); - } - } - - // Read holes from a .poly file. - - // Read the holes. - if (!TryReadLine(reader, out line)) - { - throw new Exception("Can't read input file (holes)."); - } - - int holes = int.Parse(line[0]); - if (holes > 0) - { - for (int i = 0; i < holes; i++) - { - if (!TryReadLine(reader, out line)) - { - throw new Exception("Can't read input file (holes)."); - } - - if (line.Length < 3) - { - throw new Exception("Invalid hole."); - } - - data.Holes.Add(new Point(double.Parse(line[1], nfi), - double.Parse(line[2], nfi))); - } - } - - // Read area constraints (optional). - if (TryReadLine(reader, out line)) - { - int id, regions = int.Parse(line[0]); - - if (regions > 0) - { - for (int i = 0; i < regions; i++) - { - if (!TryReadLine(reader, out line)) - { - throw new Exception("Can't read input file (region)."); - } - - if (line.Length < 4) - { - throw new Exception("Invalid region attributes."); - } - - if (!int.TryParse(line[3], out id)) - { - id = i; - } - - double area = 0.0; - - if (line.Length > 4) - { - double.TryParse(line[4], NumberStyles.Number, nfi, out area); - } - - // Triangle's .poly file format allows region definitions with - // either 4 or 5 parameters, and different interpretations for - // them depending on the number of parameters. - // - // See http://www.cs.cmu.edu/~quake/triangle.poly.html - // - // The .NET version will interpret the fourth parameter always - // as an integer region id and the optional fifth parameter as - // an area constraint. - - data.Regions.Add(new RegionPointer( - double.Parse(line[1], nfi), // Region x - double.Parse(line[2], nfi), // Region y - id, area)); - } - } - } - } - - // Read ele file - if (readElements) - { - string elefile = Path.ChangeExtension(polyfilename, ".ele"); - if (File.Exists(elefile)) - { - ReadEleFile(elefile, readArea); - } - } - - return data; - } - - /// - /// Read elements from an .ele file. - /// - /// The file name. - /// A list of triangles. - public List ReadEleFile(string elefilename) - { - return ReadEleFile(elefilename, false); - } - - /// - /// Read the elements from an .ele file. - /// - /// - /// - /// - private List ReadEleFile(string elefilename, bool readArea) - { - int intriangles = 0, attributes = 0; - - List triangles; - - using (var reader = new StreamReader(elefilename)) - { - // Read number of elements and number of attributes. - string[] line; - bool validRegion = false; - - if (!TryReadLine(reader, out line)) - { - throw new Exception("Can't read input file (elements)."); - } - - intriangles = int.Parse(line[0]); - - // We irgnore index 1 (number of nodes per triangle) - attributes = 0; - if (line.Length > 2) - { - attributes = int.Parse(line[2]); - validRegion = true; - } - - if (attributes > 1) - { - Log.Instance.Warning("Triangle attributes not supported.", "FileReader.Read"); - } - - triangles = new List(intriangles); - - InputTriangle tri; - - // Read triangles. - for (int i = 0; i < intriangles; i++) - { - if (!TryReadLine(reader, out line)) - { - throw new Exception("Can't read input file (elements)."); - } - - if (line.Length < 4) - { - throw new Exception("Triangle has no nodes."); - } - - // TODO: startIndex ok? - tri = new InputTriangle( - int.Parse(line[1]) - startIndex, - int.Parse(line[2]) - startIndex, - int.Parse(line[3]) - startIndex); - - // Read triangle region - if (attributes > 0 && validRegion) - { - int region = 0; - validRegion = int.TryParse(line[4], out region); - tri.label = region; - } - - triangles.Add(tri); - } - } - - // Read area file - if (readArea) - { - string areafile = Path.ChangeExtension(elefilename, ".area"); - if (File.Exists(areafile)) - { - ReadAreaFile(areafile, intriangles); - } - } - - return triangles; - } - - /// - /// Read the area constraints from an .area file. - /// - /// - /// - /// - private double[] ReadAreaFile(string areafilename, int intriangles) - { - double[] data = null; - - using (var reader = new StreamReader(areafilename)) - { - string[] line; - - if (!TryReadLine(reader, out line)) - { - throw new Exception("Can't read input file (area)."); - } - - if (int.Parse(line[0]) != intriangles) - { - Log.Instance.Warning("Number of area constraints doesn't match number of triangles.", - "ReadAreaFile()"); - return null; - } - - data = new double[intriangles]; - - // Read area constraints. - for (int i = 0; i < intriangles; i++) - { - if (!TryReadLine(reader, out line)) - { - throw new Exception("Can't read input file (area)."); - } - - if (line.Length != 2) - { - throw new Exception("Triangle has no nodes."); - } - - data[i] = double.Parse(line[1], nfi); - } - } - - return data; - } - - /// - /// Read an .edge file. - /// - /// The file name. - /// The number of input vertices (read from a .node or .poly file). - /// A List of edges. - public List ReadEdgeFile(string edgeFile, int invertices) - { - // Read poly file - List data = null; - - startIndex = 0; - - string[] line; - - using (var reader = new StreamReader(edgeFile)) - { - // Read the edges from a .edge file. - - // Read number of segments and number of boundary markers. - if (!TryReadLine(reader, out line)) - { - throw new Exception("Can't read input file (segments)."); - } - - int inedges = int.Parse(line[0]); - - int edgemarkers = 0; - if (line.Length > 1) - { - edgemarkers = int.Parse(line[1]); - } - - if (inedges > 0) - { - data = new List(inedges); - } - - int end1, end2, mark; - // Read and insert the segments. - for (int i = 0; i < inedges; i++) - { - if (!TryReadLine(reader, out line)) - { - throw new Exception("Can't read input file (segments)."); - } - - if (line.Length < 3) - { - throw new Exception("Segment has no endpoints."); - } - - // TODO: startIndex ok? - end1 = int.Parse(line[1]) - startIndex; - end2 = int.Parse(line[2]) - startIndex; - mark = 0; - - if (edgemarkers > 0 && line.Length > 3) - { - mark = int.Parse(line[3]); - } - - if ((end1 < 0) || (end1 >= invertices)) - { - if (Log.Verbose) - { - Log.Instance.Warning("Invalid first endpoint of segment.", - "MeshReader.ReadPolyfile()"); - } - } - else if ((end2 < 0) || (end2 >= invertices)) - { - if (Log.Verbose) - { - Log.Instance.Warning("Invalid second endpoint of segment.", - "MeshReader.ReadPolyfile()"); - } - } - else - { - data.Add(new Edge(end1, end2, mark)); - } - } - } - - return data; - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using TriangleNet.Geometry; + + /// + /// Helper methods for reading Triangle file formats. + /// + public class TriangleReader + { + private static readonly NumberFormatInfo nfi = NumberFormatInfo.InvariantInfo; + + int startIndex = 0; + + #region Helper methods + + private bool TryReadLine(StreamReader reader, out string[] token) + { + token = null; + + if (reader.EndOfStream) + { + return false; + } + + string line = reader.ReadLine().Trim(); + + while (string.IsNullOrWhiteSpace(line) || line.StartsWith("#")) + { + if (reader.EndOfStream) + { + return false; + } + + line = reader.ReadLine().Trim(); + } + + token = line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + + return true; + } + + /// + /// Read vertex information of the given line. + /// + /// The input geometry. + /// The current vertex index. + /// The current line. + /// Number of point attributes + /// Number of point markers (0 or 1) + private void ReadVertex(List data, int index, string[] line, int attributes, int marks) + { + double x = double.Parse(line[1], nfi); + double y = double.Parse(line[2], nfi); + + var v = new Vertex(x, y); + + // Read a vertex marker. + if (marks > 0 && line.Length > 3 + attributes) + { + v.Label = int.Parse(line[3 + attributes]); + } + + if (attributes > 0) + { +#if USE_ATTRIBS + var attribs = new double[attributes]; + + // Read the vertex attributes. + for (int j = 0; j < attributes; j++) + { + if (line.Length > 3 + j) + { + attribs[j] = double.Parse(line[3 + j], nfi); + } + } + + v.attributes = attribs; +#endif + } + + data.Add(v); + } + + #endregion + + #region Main I/O methods + + /// + /// Reads geometry information from .node or .poly files. + /// + public void Read(string filename, out Polygon polygon) + { + string path = Path.ChangeExtension(filename, ".poly"); + + if (File.Exists(path)) + { + polygon = ReadPolyFile(path); + } + else + { + path = Path.ChangeExtension(filename, ".node"); + polygon = ReadNodeFile(path); + } + } + + /// + /// Reads a mesh from .node, .poly or .ele files. + /// + public void Read(string filename, out Polygon geometry, out List triangles) + { + triangles = null; + + Read(filename, out geometry); + + string path = Path.ChangeExtension(filename, ".ele"); + + if (File.Exists(path) && geometry != null) + { + triangles = ReadEleFile(path); + } + } + + /// + /// Reads geometry information from .node or .poly files. + /// + public IPolygon Read(string filename) + { + Read(filename, out Polygon geometry); + + return geometry; + } + + #endregion + + /// + /// Read the vertices from a file, which may be a .node or .poly file. + /// + /// + /// Will NOT read associated .ele by default. + public Polygon ReadNodeFile(string nodefilename) + { + return ReadNodeFile(nodefilename, false); + } + + /// + /// Read the vertices from a file, which may be a .node or .poly file. + /// + /// + /// + public Polygon ReadNodeFile(string nodefilename, bool readElements) + { + Polygon data; + + startIndex = 0; + + string[] line; + int invertices = 0, attributes = 0, nodemarkers = 0; + + using (var reader = new StreamReader(nodefilename)) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file."); + } + + // Read number of vertices, number of dimensions, number of vertex + // attributes, and number of boundary markers. + invertices = int.Parse(line[0]); + + if (invertices < 3) + { + throw new Exception("Input must have at least three input vertices."); + } + + if (line.Length > 1) + { + if (int.Parse(line[1]) != 2) + { + throw new Exception("Triangle only works with two-dimensional meshes."); + } + } + + if (line.Length > 2) + { + attributes = int.Parse(line[2]); + } + + if (line.Length > 3) + { + nodemarkers = int.Parse(line[3]); + } + + data = new Polygon(invertices); + + // Read the vertices. + if (invertices > 0) + { + for (int i = 0; i < invertices; i++) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (vertices)."); + } + + if (line.Length < 3) + { + throw new Exception("Invalid vertex."); + } + + if (i == 0) + { + startIndex = int.Parse(line[0], nfi); + } + + ReadVertex(data.Points, i, line, attributes, nodemarkers); + } + } + } + + if (readElements) + { + // Read area file + string elefile = Path.ChangeExtension(nodefilename, ".ele"); + if (File.Exists(elefile)) + { + ReadEleFile(elefile, true); + } + } + + return data; + } + + /// + /// Read the vertices and segments from a .poly file. + /// + /// + /// Will NOT read associated .ele by default. + public Polygon ReadPolyFile(string polyfilename) + { + return ReadPolyFile(polyfilename, false, false); + } + + /// + /// Read the vertices and segments from a .poly file. + /// + /// + /// If true, look for an associated .ele file. + /// Will NOT read associated .area by default. + public Polygon ReadPolyFile(string polyfilename, bool readElements) + { + return ReadPolyFile(polyfilename, readElements, false); + } + + /// + /// Read the vertices and segments from a .poly file. + /// + /// + /// If true, look for an associated .ele file. + /// If true, look for an associated .area file. + public Polygon ReadPolyFile(string polyfilename, bool readElements, bool readArea) + { + // Read poly file + Polygon data; + + startIndex = 0; + + string[] line; + int invertices, attributes = 0, nodemarkers = 0; + + using (var reader = new StreamReader(polyfilename)) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file."); + } + + // Read number of vertices, number of dimensions, number of vertex + // attributes, and number of boundary markers. + invertices = int.Parse(line[0]); + + if (line.Length > 1) + { + if (int.Parse(line[1]) != 2) + { + throw new Exception("Triangle only works with two-dimensional meshes."); + } + } + + if (line.Length > 2) + { + attributes = int.Parse(line[2]); + } + + if (line.Length > 3) + { + nodemarkers = int.Parse(line[3]); + } + + // Read the vertices. + if (invertices > 0) + { + data = new Polygon(invertices); + + for (int i = 0; i < invertices; i++) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (vertices)."); + } + + if (line.Length < 3) + { + throw new Exception("Invalid vertex."); + } + + if (i == 0) + { + // Set the start index! + startIndex = int.Parse(line[0], nfi); + } + + ReadVertex(data.Points, i, line, attributes, nodemarkers); + } + } + else + { + // If the .poly file claims there are zero vertices, that means that + // the vertices should be read from a separate .node file. + data = ReadNodeFile(Path.ChangeExtension(polyfilename, ".node")); + + invertices = data.Points.Count; + } + + var points = data.Points; + + if (points.Count == 0) + { + throw new Exception("No nodes available."); + } + + // Read the segments from a .poly file. + + // Read number of segments and number of boundary markers. + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (segments)."); + } + + int insegments = int.Parse(line[0]); + + int segmentmarkers = 0; + if (line.Length > 1) + { + segmentmarkers = int.Parse(line[1]); + } + + int end1, end2, mark; + // Read and insert the segments. + for (int i = 0; i < insegments; i++) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (segments)."); + } + + if (line.Length < 3) + { + throw new Exception("Segment has no endpoints."); + } + + // TODO: startIndex ok? + end1 = int.Parse(line[1]) - startIndex; + end2 = int.Parse(line[2]) - startIndex; + mark = 0; + + if (segmentmarkers > 0 && line.Length > 3) + { + mark = int.Parse(line[3]); + } + + if ((end1 < 0) || (end1 >= invertices)) + { + if (Log.Verbose) + { + Log.Instance.Warning("Invalid first endpoint of segment.", + "TriangleReader.ReadPolyfile()"); + } + } + else if ((end2 < 0) || (end2 >= invertices)) + { + if (Log.Verbose) + { + Log.Instance.Warning("Invalid second endpoint of segment.", + "TriangleReader.ReadPolyfile()"); + } + } + else + { + data.Add(new Segment(points[end1], points[end2], mark)); + } + } + + // Read holes from a .poly file. + + // Read the holes. + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (holes)."); + } + + int holes = int.Parse(line[0]); + if (holes > 0) + { + for (int i = 0; i < holes; i++) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (holes)."); + } + + if (line.Length < 3) + { + throw new Exception("Invalid hole."); + } + + data.Holes.Add(new Point(double.Parse(line[1], nfi), + double.Parse(line[2], nfi))); + } + } + + // Read area constraints (optional). + if (TryReadLine(reader, out line)) + { + int id, regions = int.Parse(line[0]); + + if (regions > 0) + { + for (int i = 0; i < regions; i++) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (region)."); + } + + if (line.Length < 4) + { + throw new Exception("Invalid region attributes."); + } + + if (!int.TryParse(line[3], out id)) + { + id = i; + } + + double area = 0.0; + + if (line.Length > 4) + { + double.TryParse(line[4], NumberStyles.Number, nfi, out area); + } + + // Triangle's .poly file format allows region definitions with + // either 4 or 5 parameters, and different interpretations for + // them depending on the number of parameters. + // + // See http://www.cs.cmu.edu/~quake/triangle.poly.html + // + // The .NET version will interpret the fourth parameter always + // as an integer region id and the optional fifth parameter as + // an area constraint. + + data.Regions.Add(new RegionPointer( + double.Parse(line[1], nfi), // Region x + double.Parse(line[2], nfi), // Region y + id, area)); + } + } + } + } + + // Read ele file + if (readElements) + { + string elefile = Path.ChangeExtension(polyfilename, ".ele"); + if (File.Exists(elefile)) + { + ReadEleFile(elefile, readArea); + } + } + + return data; + } + + /// + /// Read elements from an .ele file. + /// + /// The file name. + /// A list of triangles. + public List ReadEleFile(string elefilename) + { + return ReadEleFile(elefilename, false); + } + + /// + /// Read the elements from an .ele file. + /// + /// + /// + private List ReadEleFile(string elefilename, bool readArea) + { + int intriangles = 0, attributes = 0; + + List triangles; + + using (var reader = new StreamReader(elefilename)) + { + // Read number of elements and number of attributes. + string[] line; + bool validRegion = false; + + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (elements)."); + } + + intriangles = int.Parse(line[0]); + + // We ignore index 1 (number of nodes per triangle) + attributes = 0; + if (line.Length > 2) + { + attributes = int.Parse(line[2]); + validRegion = true; + } + + if (attributes > 1) + { + Log.Instance.Warning("Triangle attributes not supported.", + "TriangleReader.ReadEleFile()"); + } + + triangles = new List(intriangles); + + InputTriangle tri; + + // Read triangles. + for (int i = 0; i < intriangles; i++) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (elements)."); + } + + if (line.Length < 4) + { + throw new Exception("Triangle has no nodes."); + } + + // TODO: startIndex ok? + tri = new InputTriangle( + int.Parse(line[1]) - startIndex, + int.Parse(line[2]) - startIndex, + int.Parse(line[3]) - startIndex); + + // Read triangle region + if (attributes > 0 && validRegion) + { + int region = 0; + validRegion = int.TryParse(line[4], out region); + tri.label = region; + } + + triangles.Add(tri); + } + } + + // Read area file + if (readArea) + { + string areafile = Path.ChangeExtension(elefilename, ".area"); + if (File.Exists(areafile)) + { + ReadAreaFile(areafile, intriangles); + } + } + + return triangles; + } + + /// + /// Read the area constraints from an .area file. + /// + /// + /// + /// + private double[] ReadAreaFile(string areafilename, int intriangles) + { + double[] data = null; + + using (var reader = new StreamReader(areafilename)) + { + string[] line; + + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (area)."); + } + + if (int.Parse(line[0]) != intriangles) + { + Log.Instance.Warning("Number of area constraints doesn't match number of triangles.", + "TriangleReader.ReadAreaFile()"); + return null; + } + + data = new double[intriangles]; + + // Read area constraints. + for (int i = 0; i < intriangles; i++) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (area)."); + } + + if (line.Length != 2) + { + throw new Exception("Triangle has no nodes."); + } + + data[i] = double.Parse(line[1], nfi); + } + } + + return data; + } + + /// + /// Read an .edge file. + /// + /// The file name. + /// The number of input vertices (read from a .node or .poly file). + /// A List of edges. + public List ReadEdgeFile(string edgeFile, int invertices) + { + // Read poly file + List data = null; + + startIndex = 0; + + string[] line; + + using (var reader = new StreamReader(edgeFile)) + { + // Read the edges from a .edge file. + + // Read number of segments and number of boundary markers. + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (segments)."); + } + + int inedges = int.Parse(line[0]); + + int edgemarkers = 0; + if (line.Length > 1) + { + edgemarkers = int.Parse(line[1]); + } + + if (inedges > 0) + { + data = new List(inedges); + } + + int end1, end2, mark; + // Read and insert the segments. + for (int i = 0; i < inedges; i++) + { + if (!TryReadLine(reader, out line)) + { + throw new Exception("Can't read input file (segments)."); + } + + if (line.Length < 3) + { + throw new Exception("Segment has no endpoints."); + } + + // TODO: startIndex ok? + end1 = int.Parse(line[1]) - startIndex; + end2 = int.Parse(line[2]) - startIndex; + mark = 0; + + if (edgemarkers > 0 && line.Length > 3) + { + mark = int.Parse(line[3]); + } + + if ((end1 < 0) || (end1 >= invertices)) + { + if (Log.Verbose) + { + Log.Instance.Warning("Invalid first endpoint of segment.", + "TriangleReader.ReadEdgeFile()"); + } + } + else if ((end2 < 0) || (end2 >= invertices)) + { + if (Log.Verbose) + { + Log.Instance.Warning("Invalid second endpoint of segment.", + "TriangleReader.ReadEdgeFile()"); + } + } + else + { + data.Add(new Edge(end1, end2, mark)); + } + } + } + + return data; + } + } +} diff --git a/Triangle.NET/Triangle/IO/TriangleWriter.cs b/src/Triangle/IO/TriangleWriter.cs similarity index 95% rename from Triangle.NET/Triangle/IO/TriangleWriter.cs rename to src/Triangle/IO/TriangleWriter.cs index c0a0d3a..2fbd2d2 100644 --- a/Triangle.NET/Triangle/IO/TriangleWriter.cs +++ b/src/Triangle/IO/TriangleWriter.cs @@ -1,459 +1,458 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.IO -{ - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using TriangleNet.Geometry; - using TriangleNet.Topology; - - /// - /// Helper methods for writing Triangle file formats. - /// - public class TriangleWriter - { - static NumberFormatInfo nfi = NumberFormatInfo.InvariantInfo; - - /// - /// Number the vertices and write them to a .node file. - /// - /// - /// - public void Write(Mesh mesh, string filename) - { - WritePoly(mesh, Path.ChangeExtension(filename, ".poly")); - WriteElements(mesh, Path.ChangeExtension(filename, ".ele")); - } - - /// - /// Number the vertices and write them to a .node file. - /// - /// - /// - public void WriteNodes(Mesh mesh, string filename) - { - using (var writer = new StreamWriter(filename)) - { - WriteNodes(writer, mesh); - } - } - - /// - /// Number the vertices and write them to a .node file. - /// - private void WriteNodes(StreamWriter writer, Mesh mesh) - { - int outvertices = mesh.vertices.Count; - int nextras = mesh.nextras; - - Behavior behavior = mesh.behavior; - - if (behavior.Jettison) - { - outvertices = mesh.vertices.Count - mesh.undeads; - } - - if (writer != null) - { - // Number of vertices, number of dimensions, number of vertex attributes, - // and number of boundary markers (zero or one). - writer.WriteLine("{0} {1} {2} {3}", outvertices, mesh.mesh_dim, nextras, - behavior.UseBoundaryMarkers ? "1" : "0"); - - if (mesh.numbering == NodeNumbering.None) - { - // If the mesh isn't numbered yet, use linear node numbering. - mesh.Renumber(); - } - - if (mesh.numbering == NodeNumbering.Linear) - { - // If numbering is linear, just use the dictionary values. - WriteNodes(writer, mesh.vertices.Values, behavior.UseBoundaryMarkers, - nextras, behavior.Jettison); - } - else - { - // If numbering is not linear, a simple 'foreach' traversal of the dictionary - // values doesn't reflect the actual numbering. Use an array instead. - - // TODO: Could use a custom sorting function on dictionary values instead. - Vertex[] nodes = new Vertex[mesh.vertices.Count]; - - foreach (var node in mesh.vertices.Values) - { - nodes[node.id] = node; - } - - WriteNodes(writer, nodes, behavior.UseBoundaryMarkers, - nextras, behavior.Jettison); - } - } - } - - /// - /// Write the vertices to a stream. - /// - /// - /// - private void WriteNodes(StreamWriter writer, IEnumerable nodes, bool markers, - int attribs, bool jettison) - { - int index = 0; - - foreach (var vertex in nodes) - { - if (!jettison || vertex.type != VertexType.UndeadVertex) - { - // Vertex number, x and y coordinates. - writer.Write("{0} {1} {2}", index, vertex.x.ToString(nfi), vertex.y.ToString(nfi)); - -#if USE_ATTRIBS - // Write attributes. - for (int j = 0; j < attribs; j++) - { - writer.Write(" {0}", vertex.attributes[j].ToString(nfi)); - } -#endif - - if (markers) - { - // Write the boundary marker. - writer.Write(" {0}", vertex.label); - } - - writer.WriteLine(); - - index++; - } - } - } - - /// - /// Write the triangles to an .ele file. - /// - /// - /// - public void WriteElements(Mesh mesh, string filename) - { - Otri tri = default(Otri); - Vertex p1, p2, p3; - bool regions = mesh.behavior.useRegions; - - int j = 0; - - tri.orient = 0; - - using (var writer = new StreamWriter(filename)) - { - // Number of triangles, vertices per triangle, attributes per triangle. - writer.WriteLine("{0} 3 {1}", mesh.triangles.Count, regions ? 1 : 0); - - foreach (var item in mesh.triangles) - { - tri.tri = item; - - p1 = tri.Org(); - p2 = tri.Dest(); - p3 = tri.Apex(); - - // Triangle number, indices for three vertices. - writer.Write("{0} {1} {2} {3}", j, p1.id, p2.id, p3.id); - - if (regions) - { - writer.Write(" {0}", tri.tri.label); - } - - writer.WriteLine(); - - // Number elements - item.id = j++; - } - } - } - - /// - /// Write the segments and holes to a .poly file. - /// - /// Data source. - /// File name. - /// Write nodes into this file. - /// If the nodes should not be written into this file, - /// make sure a .node file was written before, so that the nodes - /// are numbered right. - public void WritePoly(IPolygon polygon, string filename) - { - bool hasMarkers = polygon.HasSegmentMarkers; - - using (var writer = new StreamWriter(filename)) - { - // TODO: write vertex attributes - - writer.WriteLine("{0} 2 0 {1}", polygon.Points.Count, polygon.HasPointMarkers ? "1" : "0"); - - // Write nodes to this file. - WriteNodes(writer, polygon.Points, polygon.HasPointMarkers, 0, false); - - // Number of segments, number of boundary markers (zero or one). - writer.WriteLine("{0} {1}", polygon.Segments.Count, hasMarkers ? "1" : "0"); - - Vertex p, q; - - int j = 0; - foreach (var seg in polygon.Segments) - { - p = seg.GetVertex(0); - q = seg.GetVertex(1); - - // Segment number, indices of its two endpoints, and possibly a marker. - if (hasMarkers) - { - writer.WriteLine("{0} {1} {2} {3}", j, p.ID, q.ID, seg.Label); - } - else - { - writer.WriteLine("{0} {1} {2}", j, p.ID, q.ID); - } - - j++; - } - - // Holes - j = 0; - writer.WriteLine("{0}", polygon.Holes.Count); - foreach (var hole in polygon.Holes) - { - writer.WriteLine("{0} {1} {2}", j++, hole.X.ToString(nfi), hole.Y.ToString(nfi)); - } - - // Regions - if (polygon.Regions.Count > 0) - { - j = 0; - writer.WriteLine("{0}", polygon.Regions.Count); - foreach (var region in polygon.Regions) - { - writer.WriteLine("{0} {1} {2} {3}", j, region.point.X.ToString(nfi), - region.point.Y.ToString(nfi), region.id); - - j++; - } - } - } - } - - /// - /// Write the segments and holes to a .poly file. - /// - /// - /// - public void WritePoly(Mesh mesh, string filename) - { - WritePoly(mesh, filename, true); - } - - /// - /// Write the segments and holes to a .poly file. - /// - /// Data source. - /// File name. - /// Write nodes into this file. - /// If the nodes should not be written into this file, - /// make sure a .node file was written before, so that the nodes - /// are numbered right. - public void WritePoly(Mesh mesh, string filename, bool writeNodes) - { - Osub subseg = default(Osub); - Vertex pt1, pt2; - - bool useBoundaryMarkers = mesh.behavior.UseBoundaryMarkers; - - using (var writer = new StreamWriter(filename)) - { - if (writeNodes) - { - // Write nodes to this file. - WriteNodes(writer, mesh); - } - else - { - // The zero indicates that the vertices are in a separate .node file. - // Followed by number of dimensions, number of vertex attributes, - // and number of boundary markers (zero or one). - writer.WriteLine("0 {0} {1} {2}", mesh.mesh_dim, mesh.nextras, - useBoundaryMarkers ? "1" : "0"); - } - - // Number of segments, number of boundary markers (zero or one). - writer.WriteLine("{0} {1}", mesh.subsegs.Count, - useBoundaryMarkers ? "1" : "0"); - - subseg.orient = 0; - - int j = 0; - foreach (var item in mesh.subsegs.Values) - { - subseg.seg = item; - - pt1 = subseg.Org(); - pt2 = subseg.Dest(); - - // Segment number, indices of its two endpoints, and possibly a marker. - if (useBoundaryMarkers) - { - writer.WriteLine("{0} {1} {2} {3}", j, pt1.id, pt2.id, subseg.seg.boundary); - } - else - { - writer.WriteLine("{0} {1} {2}", j, pt1.id, pt2.id); - } - - j++; - } - - // Holes - j = 0; - writer.WriteLine("{0}", mesh.holes.Count); - foreach (var hole in mesh.holes) - { - writer.WriteLine("{0} {1} {2}", j++, hole.X.ToString(nfi), hole.Y.ToString(nfi)); - } - - // Regions - if (mesh.regions.Count > 0) - { - j = 0; - writer.WriteLine("{0}", mesh.regions.Count); - foreach (var region in mesh.regions) - { - writer.WriteLine("{0} {1} {2} {3}", j, region.point.X.ToString(nfi), - region.point.Y.ToString(nfi), region.id); - - j++; - } - } - } - } - - /// - /// Write the edges to an .edge file. - /// - /// - /// - public void WriteEdges(Mesh mesh, string filename) - { - Otri tri = default(Otri), trisym = default(Otri); - Osub checkmark = default(Osub); - Vertex p1, p2; - - Behavior behavior = mesh.behavior; - - using (var writer = new StreamWriter(filename)) - { - // Number of edges, number of boundary markers (zero or one). - writer.WriteLine("{0} {1}", mesh.NumberOfEdges, behavior.UseBoundaryMarkers ? "1" : "0"); - - long index = 0; - // To loop over the set of edges, loop over all triangles, and look at - // the three edges of each triangle. If there isn't another triangle - // adjacent to the edge, operate on the edge. If there is another - // adjacent triangle, operate on the edge only if the current triangle - // has a smaller pointer than its neighbor. This way, each edge is - // considered only once. - foreach (var item in mesh.triangles) - { - tri.tri = item; - - for (tri.orient = 0; tri.orient < 3; tri.orient++) - { - tri.Sym(ref trisym); - if ((tri.tri.id < trisym.tri.id) || (trisym.tri.id == Mesh.DUMMY)) - { - p1 = tri.Org(); - p2 = tri.Dest(); - - if (behavior.UseBoundaryMarkers) - { - // Edge number, indices of two endpoints, and a boundary marker. - // If there's no subsegment, the boundary marker is zero. - if (behavior.useSegments) - { - tri.Pivot(ref checkmark); - - if (checkmark.seg.hash == Mesh.DUMMY) - { - writer.WriteLine("{0} {1} {2} {3}", index, p1.id, p2.id, 0); - } - else - { - writer.WriteLine("{0} {1} {2} {3}", index, p1.id, p2.id, - checkmark.seg.boundary); - } - } - else - { - writer.WriteLine("{0} {1} {2} {3}", index, p1.id, p2.id, - trisym.tri.id == Mesh.DUMMY ? "1" : "0"); - } - } - else - { - // Edge number, indices of two endpoints. - writer.WriteLine("{0} {1} {2}", index, p1.id, p2.id); - } - - index++; - } - } - } - } - } - - /// - /// Write the triangle neighbors to a .neigh file. - /// - /// - /// - /// WARNING: Be sure WriteElements has been called before, - /// so the elements are numbered right! - public void WriteNeighbors(Mesh mesh, string filename) - { - Otri tri = default(Otri), trisym = default(Otri); - int n1, n2, n3; - int i = 0; - - using (StreamWriter writer = new StreamWriter(filename)) - { - // Number of triangles, three neighbors per triangle. - writer.WriteLine("{0} 3", mesh.triangles.Count); - - foreach (var item in mesh.triangles) - { - tri.tri = item; - - tri.orient = 1; - tri.Sym(ref trisym); - n1 = trisym.tri.id; - - tri.orient = 2; - tri.Sym(ref trisym); - n2 = trisym.tri.id; - - tri.orient = 0; - tri.Sym(ref trisym); - n3 = trisym.tri.id; - - // Triangle number, neighboring triangle numbers. - writer.WriteLine("{0} {1} {2} {3}", i++, n1, n2, n3); - } - } - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.IO +{ + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using TriangleNet.Geometry; + using TriangleNet.Topology; + + /// + /// Helper methods for writing Triangle file formats. + /// + public class TriangleWriter + { + static NumberFormatInfo nfi = NumberFormatInfo.InvariantInfo; + + /// + /// Number the vertices and write them to a .node file. + /// + /// + /// + public void Write(Mesh mesh, string filename) + { + WritePoly(mesh, Path.ChangeExtension(filename, ".poly")); + WriteElements(mesh, Path.ChangeExtension(filename, ".ele")); + } + + /// + /// Number the vertices and write them to a .node file. + /// + /// + /// + public void WriteNodes(Mesh mesh, string filename) + { + using (var writer = new StreamWriter(filename)) + { + WriteNodes(writer, mesh); + } + } + + /// + /// Number the vertices and write them to a .node file. + /// + private void WriteNodes(StreamWriter writer, Mesh mesh) + { + int outvertices = mesh.vertices.Count; + int nextras = mesh.nextras; + + Behavior behavior = mesh.behavior; + + if (behavior.Jettison) + { + outvertices = mesh.vertices.Count - mesh.undeads; + } + + if (writer != null) + { + // Number of vertices, number of dimensions, number of vertex attributes, + // and number of boundary markers (zero or one). + writer.WriteLine("{0} {1} {2} {3}", outvertices, mesh.mesh_dim, nextras, + behavior.UseBoundaryMarkers ? "1" : "0"); + + if (mesh.numbering == NodeNumbering.None) + { + // If the mesh isn't numbered yet, use linear node numbering. + mesh.Renumber(); + } + + if (mesh.numbering == NodeNumbering.Linear) + { + // If numbering is linear, just use the dictionary values. + WriteNodes(writer, mesh.vertices.Values, behavior.UseBoundaryMarkers, + nextras, behavior.Jettison); + } + else + { + // If numbering is not linear, a simple 'foreach' traversal of the dictionary + // values doesn't reflect the actual numbering. Use an array instead. + + // TODO: Could use a custom sorting function on dictionary values instead. + Vertex[] nodes = new Vertex[mesh.vertices.Count]; + + foreach (var node in mesh.vertices.Values) + { + nodes[node.id] = node; + } + + WriteNodes(writer, nodes, behavior.UseBoundaryMarkers, + nextras, behavior.Jettison); + } + } + } + + /// + /// Write the vertices to a stream. + /// + /// + /// + private void WriteNodes(StreamWriter writer, IEnumerable nodes, bool markers, + int attribs, bool jettison) + { + int index = 0; + + foreach (var vertex in nodes) + { + if (!jettison || vertex.type != VertexType.UndeadVertex) + { + // Vertex number, x and y coordinates. + writer.Write("{0} {1} {2}", index, vertex.x.ToString(nfi), vertex.y.ToString(nfi)); + +#if USE_ATTRIBS + // Write attributes. + for (int j = 0; j < attribs; j++) + { + writer.Write(" {0}", vertex.attributes[j].ToString(nfi)); + } +#endif + + if (markers) + { + // Write the boundary marker. + writer.Write(" {0}", vertex.label); + } + + writer.WriteLine(); + + index++; + } + } + } + + /// + /// Write the triangles to an .ele file. + /// + /// + /// + public void WriteElements(Mesh mesh, string filename) + { + Otri tri = default(Otri); + Vertex p1, p2, p3; + bool regions = mesh.behavior.useRegions; + + int j = 0; + + tri.orient = 0; + + using (var writer = new StreamWriter(filename)) + { + // Number of triangles, vertices per triangle, attributes per triangle. + writer.WriteLine("{0} 3 {1}", mesh.triangles.Count, regions ? 1 : 0); + + foreach (var item in mesh.triangles) + { + tri.tri = item; + + p1 = tri.Org(); + p2 = tri.Dest(); + p3 = tri.Apex(); + + // Triangle number, indices for three vertices. + writer.Write("{0} {1} {2} {3}", j, p1.id, p2.id, p3.id); + + if (regions) + { + writer.Write(" {0}", tri.tri.label); + } + + writer.WriteLine(); + + // Number elements + item.id = j++; + } + } + } + + /// + /// Write the segments and holes to a .poly file. + /// + /// Data source. + /// File name. + /// If the nodes should not be written into this file, + /// make sure a .node file was written before, so that the nodes + /// are numbered right. + public void WritePoly(IPolygon polygon, string filename) + { + bool hasMarkers = polygon.HasSegmentMarkers; + + using (var writer = new StreamWriter(filename)) + { + // TODO: write vertex attributes + + writer.WriteLine("{0} 2 0 {1}", polygon.Points.Count, polygon.HasPointMarkers ? "1" : "0"); + + // Write nodes to this file. + WriteNodes(writer, polygon.Points, polygon.HasPointMarkers, 0, false); + + // Number of segments, number of boundary markers (zero or one). + writer.WriteLine("{0} {1}", polygon.Segments.Count, hasMarkers ? "1" : "0"); + + Vertex p, q; + + int j = 0; + foreach (var seg in polygon.Segments) + { + p = seg.GetVertex(0); + q = seg.GetVertex(1); + + // Segment number, indices of its two endpoints, and possibly a marker. + if (hasMarkers) + { + writer.WriteLine("{0} {1} {2} {3}", j, p.ID, q.ID, seg.Label); + } + else + { + writer.WriteLine("{0} {1} {2}", j, p.ID, q.ID); + } + + j++; + } + + // Holes + j = 0; + writer.WriteLine("{0}", polygon.Holes.Count); + foreach (var hole in polygon.Holes) + { + writer.WriteLine("{0} {1} {2}", j++, hole.X.ToString(nfi), hole.Y.ToString(nfi)); + } + + // Regions + if (polygon.Regions.Count > 0) + { + j = 0; + writer.WriteLine("{0}", polygon.Regions.Count); + foreach (var region in polygon.Regions) + { + writer.WriteLine("{0} {1} {2} {3}", j, region.point.X.ToString(nfi), + region.point.Y.ToString(nfi), region.id); + + j++; + } + } + } + } + + /// + /// Write the segments and holes to a .poly file. + /// + /// + /// + public void WritePoly(Mesh mesh, string filename) + { + WritePoly(mesh, filename, true); + } + + /// + /// Write the segments and holes to a .poly file. + /// + /// Data source. + /// File name. + /// Write nodes into this file. + /// If the nodes should not be written into this file, + /// make sure a .node file was written before, so that the nodes + /// are numbered right. + public void WritePoly(Mesh mesh, string filename, bool writeNodes) + { + Osub subseg = default(Osub); + Vertex pt1, pt2; + + bool useBoundaryMarkers = mesh.behavior.UseBoundaryMarkers; + + using (var writer = new StreamWriter(filename)) + { + if (writeNodes) + { + // Write nodes to this file. + WriteNodes(writer, mesh); + } + else + { + // The zero indicates that the vertices are in a separate .node file. + // Followed by number of dimensions, number of vertex attributes, + // and number of boundary markers (zero or one). + writer.WriteLine("0 {0} {1} {2}", mesh.mesh_dim, mesh.nextras, + useBoundaryMarkers ? "1" : "0"); + } + + // Number of segments, number of boundary markers (zero or one). + writer.WriteLine("{0} {1}", mesh.subsegs.Count, + useBoundaryMarkers ? "1" : "0"); + + subseg.orient = 0; + + int j = 0; + foreach (var item in mesh.subsegs.Values) + { + subseg.seg = item; + + pt1 = subseg.Org(); + pt2 = subseg.Dest(); + + // Segment number, indices of its two endpoints, and possibly a marker. + if (useBoundaryMarkers) + { + writer.WriteLine("{0} {1} {2} {3}", j, pt1.id, pt2.id, subseg.seg.boundary); + } + else + { + writer.WriteLine("{0} {1} {2}", j, pt1.id, pt2.id); + } + + j++; + } + + // Holes + j = 0; + writer.WriteLine("{0}", mesh.holes.Count); + foreach (var hole in mesh.holes) + { + writer.WriteLine("{0} {1} {2}", j++, hole.X.ToString(nfi), hole.Y.ToString(nfi)); + } + + // Regions + if (mesh.regions.Count > 0) + { + j = 0; + writer.WriteLine("{0}", mesh.regions.Count); + foreach (var region in mesh.regions) + { + writer.WriteLine("{0} {1} {2} {3}", j, region.point.X.ToString(nfi), + region.point.Y.ToString(nfi), region.id); + + j++; + } + } + } + } + + /// + /// Write the edges to an .edge file. + /// + /// + /// + public void WriteEdges(Mesh mesh, string filename) + { + Otri tri = default(Otri), trisym = default(Otri); + Osub checkmark = default(Osub); + Vertex p1, p2; + + Behavior behavior = mesh.behavior; + + using (var writer = new StreamWriter(filename)) + { + // Number of edges, number of boundary markers (zero or one). + writer.WriteLine("{0} {1}", mesh.NumberOfEdges, behavior.UseBoundaryMarkers ? "1" : "0"); + + long index = 0; + // To loop over the set of edges, loop over all triangles, and look at + // the three edges of each triangle. If there isn't another triangle + // adjacent to the edge, operate on the edge. If there is another + // adjacent triangle, operate on the edge only if the current triangle + // has a smaller pointer than its neighbor. This way, each edge is + // considered only once. + foreach (var item in mesh.triangles) + { + tri.tri = item; + + for (tri.orient = 0; tri.orient < 3; tri.orient++) + { + tri.Sym(ref trisym); + if ((tri.tri.id < trisym.tri.id) || (trisym.tri.id == Mesh.DUMMY)) + { + p1 = tri.Org(); + p2 = tri.Dest(); + + if (behavior.UseBoundaryMarkers) + { + // Edge number, indices of two endpoints, and a boundary marker. + // If there's no subsegment, the boundary marker is zero. + if (behavior.useSegments) + { + tri.Pivot(ref checkmark); + + if (checkmark.seg.hash == Mesh.DUMMY) + { + writer.WriteLine("{0} {1} {2} {3}", index, p1.id, p2.id, 0); + } + else + { + writer.WriteLine("{0} {1} {2} {3}", index, p1.id, p2.id, + checkmark.seg.boundary); + } + } + else + { + writer.WriteLine("{0} {1} {2} {3}", index, p1.id, p2.id, + trisym.tri.id == Mesh.DUMMY ? "1" : "0"); + } + } + else + { + // Edge number, indices of two endpoints. + writer.WriteLine("{0} {1} {2}", index, p1.id, p2.id); + } + + index++; + } + } + } + } + } + + /// + /// Write the triangle neighbors to a .neigh file. + /// + /// + /// + /// WARNING: Be sure WriteElements has been called before, + /// so the elements are numbered right! + public void WriteNeighbors(Mesh mesh, string filename) + { + Otri tri = default(Otri), trisym = default(Otri); + int n1, n2, n3; + int i = 0; + + using (StreamWriter writer = new StreamWriter(filename)) + { + // Number of triangles, three neighbors per triangle. + writer.WriteLine("{0} 3", mesh.triangles.Count); + + foreach (var item in mesh.triangles) + { + tri.tri = item; + + tri.orient = 1; + tri.Sym(ref trisym); + n1 = trisym.tri.id; + + tri.orient = 2; + tri.Sym(ref trisym); + n2 = trisym.tri.id; + + tri.orient = 0; + tri.Sym(ref trisym); + n3 = trisym.tri.id; + + // Triangle number, neighboring triangle numbers. + writer.WriteLine("{0} {1} {2} {3}", i++, n1, n2, n3); + } + } + } + } +} diff --git a/Triangle.NET/Triangle/IPredicates.cs b/src/Triangle/IPredicates.cs similarity index 60% rename from Triangle.NET/Triangle/IPredicates.cs rename to src/Triangle/IPredicates.cs index 895690d..27e5e94 100644 --- a/Triangle.NET/Triangle/IPredicates.cs +++ b/src/Triangle/IPredicates.cs @@ -1,22 +1,17 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet -{ - using TriangleNet.Geometry; - - public interface IPredicates - { - double CounterClockwise(Point a, Point b, Point c); - - double InCircle(Point a, Point b, Point c, Point p); - - Point FindCircumcenter(Point org, Point dest, Point apex, ref double xi, ref double eta); - - Point FindCircumcenter(Point org, Point dest, Point apex, ref double xi, ref double eta, - double offconstant); - } -} + +namespace TriangleNet +{ + using TriangleNet.Geometry; + + public interface IPredicates + { + double CounterClockwise(Point a, Point b, Point c); + + double InCircle(Point a, Point b, Point c, Point p); + + Point FindCircumcenter(Point org, Point dest, Point apex, ref double xi, ref double eta); + + Point FindCircumcenter(Point org, Point dest, Point apex, ref double xi, ref double eta, + double offconstant); + } +} diff --git a/src/Triangle/Log.cs b/src/Triangle/Log.cs new file mode 100644 index 0000000..369a328 --- /dev/null +++ b/src/Triangle/Log.cs @@ -0,0 +1,154 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using System.Collections.Generic; + + public enum LogLevel { Info, Warning, Error } + + /// + /// Represents an item stored in the log. + /// + public class LogItem + { + private readonly DateTime time; + private readonly LogLevel level; + private readonly string message; + private readonly string details; + + /// + /// Gets the the item was logged. + /// + public DateTime Time => time; + + /// + /// Gets the . + /// + public LogLevel Level => level; + + /// + /// Gets the log message. + /// + public string Message => message; + + /// + /// Gets further details of the log message. + /// + public string Details => details; + + /// + /// Creates a new instance of the class. + /// + /// The log level. + /// The log message. + public LogItem(LogLevel level, string message) + : this(level, message, "") + { } + + /// + /// Creates a new instance of the class. + /// + /// The log level. + /// The log message. + /// The message details. + public LogItem(LogLevel level, string message, string details) + { + time = DateTime.Now; + + this.level = level; + this.message = message; + this.details = details; + } + } + + /// + /// A simple logger, which logs messages to a List. + public sealed class Log + { + /// + /// Log detailed information. + /// + public static bool Verbose { get; set; } + + /// + /// Gets all log messages. + /// + public IList Data => data; + + private readonly List data = new List(); + + #region Singleton pattern + + // Singleton pattern as proposed by Jon Skeet: + // https://csharpindepth.com/Articles/Singleton + + private static readonly Log instance = new Log(); + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static Log() { } + + private Log() { } + + public static Log Instance + { + get + { + return instance; + } + } + + #endregion + + /// + /// Adds a to the log. + /// + /// + public void Add(LogItem item) + { + data.Add(item); + } + + /// + /// Clear all messages from the log. + /// + public void Clear() + { + data.Clear(); + } + + /// + /// Log info message. + /// + /// The message. + public void Info(string message) + { + data.Add(new LogItem(LogLevel.Info, message)); + } + + /// + /// Log warning message. + /// + /// The message. + /// Message details, for example the code location where the error occurred (class, method). + public void Warning(string message, string details) + { + data.Add(new LogItem(LogLevel.Warning, message, details)); + } + + /// + /// Log error message. + /// + /// The message. + /// Message details, for example the code location where the error occurred (class, method). + public void Error(string message, string details) + { + data.Add(new LogItem(LogLevel.Error, message, details)); + } + } +} diff --git a/Triangle.NET/Triangle/Mesh.cs b/src/Triangle/Mesh.cs similarity index 94% rename from Triangle.NET/Triangle/Mesh.cs rename to src/Triangle/Mesh.cs index ad2deb2..6b15dcb 100644 --- a/Triangle.NET/Triangle/Mesh.cs +++ b/src/Triangle/Mesh.cs @@ -1,1768 +1,1730 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet -{ - using System; - using System.Collections.Generic; - using TriangleNet.Geometry; - using TriangleNet.Logging; - using TriangleNet.Meshing; - using TriangleNet.Meshing.Data; - using TriangleNet.Meshing.Iterators; - using TriangleNet.Tools; - using TriangleNet.Topology; - - /// - /// Mesh data structure. - /// - public class Mesh : IMesh - { - #region Variables - - IPredicates predicates; - - ILog logger; - - QualityMesher qualityMesher; - - // Stack that maintains a list of recently flipped triangles. - Stack flipstack; - - // TODO: Check if custom hashmap implementation could be faster. - - // Using hashsets for memory management should quite fast. - internal TrianglePool triangles; - internal Dictionary subsegs; - internal Dictionary vertices; - - // Hash seeds (should belong to mesh instance) - internal int hash_vtx = 0; - internal int hash_seg = 0; - internal int hash_tri = 0; - - internal List holes; - internal List regions; - - // TODO: remove mesh_dim, invertices and insegments - - // Other variables. - internal Rectangle bounds; // x and y bounds. - internal int invertices; // Number of input vertices. - internal int insegments; // Number of input segments. - internal int undeads; // Number of input vertices that don't appear in the mesh. - internal int mesh_dim; // Dimension (ought to be 2). - internal int nextras; // Number of attributes per vertex. - //internal int eextras; // Number of attributes per triangle. - internal int hullsize; // Number of edges in convex hull. - internal int steinerleft; // Number of Steiner points not yet used. - internal bool checksegments; // Are there segments in the triangulation yet? - internal bool checkquality; // Has quality triangulation begun yet? - - // Triangular bounding box vertices. - internal Vertex infvertex1, infvertex2, infvertex3; - - internal TriangleLocator locator; - - // Controls the behavior of the mesh instance. - internal Behavior behavior; - - // The current node numbering - internal NodeNumbering numbering; - - #endregion - - #region Public properties - - /// - /// Gets the mesh bounding box. - /// - public Rectangle Bounds - { - get { return this.bounds; } - } - - /// - /// Gets the mesh vertices. - /// - public ICollection Vertices - { - get { return this.vertices.Values; } - } - - /// - /// Gets the mesh holes. - /// - public IList Holes - { - get { return this.holes; } - } - - /// - /// Gets the mesh triangles. - /// - public ICollection Triangles - { - get { return this.triangles; } - } - - /// - /// Gets the mesh segments. - /// - public ICollection Segments - { - get { return this.subsegs.Values; } - } - - /// - /// Gets the mesh edges. - /// - public IEnumerable Edges - { - get - { - var e = new EdgeIterator(this); - while (e.MoveNext()) - { - yield return e.Current; - } - } - } - - /// - /// Gets the number of input vertices. - /// - public int NumberOfInputPoints - { - get { return invertices; } - } - - /// - /// Gets the number of mesh edges. - /// - public int NumberOfEdges - { - get { return (3 * triangles.Count + hullsize) / 2; } - } - - /// - /// Indicates whether the input is a PSLG or a point set. - /// - public bool IsPolygon - { - get { return this.insegments > 0; } - } - - /// - /// Gets the current node numbering. - /// - public NodeNumbering CurrentNumbering - { - get { return numbering; } - } - - #endregion - - #region "Outer space" variables - - internal const int DUMMY = -1; - - // The triangle that fills "outer space," called 'dummytri', is pointed to - // by every triangle and subsegment on a boundary (be it outer or inner) of - // the triangulation. Also, 'dummytri' points to one of the triangles on - // the convex hull (until the holes and concavities are carved), making it - // possible to find a starting triangle for point location. - - // 'dummytri' and 'dummysub' are generally required to fulfill only a few - // invariants: their vertices must remain NULL and 'dummytri' must always - // be bonded (at offset zero) to some triangle on the convex hull of the - // mesh, via a boundary edge. Otherwise, the connections of 'dummytri' and - // 'dummysub' may change willy-nilly. This makes it possible to avoid - // writing a good deal of special-case code (in the edge flip, for example) - // for dealing with the boundary of the mesh, places where no subsegment is - // present, and so forth. Other entities are frequently bonded to - // 'dummytri' and 'dummysub' as if they were real mesh entities, with no - // harm done. - - internal Triangle dummytri; - - // Set up 'dummysub', the omnipresent subsegment pointed to by any - // triangle side or subsegment end that isn't attached to a real - // subsegment. - - internal SubSegment dummysub; - - private void Initialize() - { - dummysub = new SubSegment(); - dummysub.hash = DUMMY; - - // Initialize the two adjoining subsegments to be the omnipresent - // subsegment. These will eventually be changed by various bonding - // operations, but their values don't really matter, as long as they - // can legally be dereferenced. - dummysub.subsegs[0].seg = dummysub; - dummysub.subsegs[1].seg = dummysub; - - // Set up 'dummytri', the 'triangle' that occupies "outer space." - dummytri = new Triangle(); - dummytri.hash = dummytri.id = DUMMY; - - // Initialize the three adjoining triangles to be "outer space." These - // will eventually be changed by various bonding operations, but their - // values don't really matter, as long as they can legally be - // dereferenced. - dummytri.neighbors[0].tri = dummytri; - dummytri.neighbors[1].tri = dummytri; - dummytri.neighbors[2].tri = dummytri; - - // Initialize the three adjoining subsegments of 'dummytri' to be - // the omnipresent subsegment. - dummytri.subsegs[0].seg = dummysub; - dummytri.subsegs[1].seg = dummysub; - dummytri.subsegs[2].seg = dummysub; - } - - #endregion - - /// - /// Initializes a new instance of the class. - /// - public Mesh(Configuration config) - { - Initialize(); - - logger = Log.Instance; - - behavior = new Behavior(); - - vertices = new Dictionary(); - subsegs = new Dictionary(); - - triangles = config.TrianglePool(); - - flipstack = new Stack(); - - holes = new List(); - regions = new List(); - - steinerleft = -1; - - this.predicates = config.Predicates(); - - this.locator = new TriangleLocator(this, predicates); - } - - public void Refine(QualityOptions quality, bool delaunay = false) - { - invertices = vertices.Count; - - if (behavior.Poly) - { - insegments = behavior.useSegments ? subsegs.Count : hullsize; - } - - Reset(); - - if (qualityMesher == null) - { - qualityMesher = new QualityMesher(this, new Configuration()); - } - - // Enforce angle and area constraints. - qualityMesher.Apply(quality, delaunay); - } - - /// - /// Renumber vertex and triangle id's. - /// - public void Renumber() - { - this.Renumber(NodeNumbering.Linear); - } - - /// - /// Renumber vertex and triangle id's. - /// - public void Renumber(NodeNumbering num) - { - // Don't need to do anything if the nodes are already numbered. - if (num == this.numbering) - { - return; - } - - int id; - - if (num == NodeNumbering.Linear) - { - id = 0; - foreach (var node in this.vertices.Values) - { - node.id = id++; - } - } - else if (num == NodeNumbering.CuthillMcKee) - { - var rcm = new CuthillMcKee(); - var iperm = rcm.Renumber(this); - - // Permute the node indices. - foreach (var node in this.vertices.Values) - { - node.id = iperm[node.id]; - } - } - - // Remember the current numbering. - numbering = num; - - // Triangles will always be numbered from 0 to n-1 - id = 0; - foreach (var item in this.triangles) - { - item.id = id++; - } - } - - #region Misc - - /// - /// Set QualityMesher for mesh refinement. - /// - /// - internal void SetQualityMesher(QualityMesher qmesher) - { - qualityMesher = qmesher; - } - - internal void CopyTo(Mesh target) - { - target.vertices = this.vertices; - target.triangles = this.triangles; - target.subsegs = this.subsegs; - - target.holes = this.holes; - target.regions = this.regions; - - target.hash_vtx = this.hash_vtx; - target.hash_seg = this.hash_seg; - target.hash_tri = this.hash_tri; - - target.numbering = this.numbering; - target.hullsize = this.hullsize; - } - - /// - /// Reset all the mesh data. This method will also wipe - /// out all mesh data. - /// - private void ResetData() - { - vertices.Clear(); - triangles.Restart(); - subsegs.Clear(); - - holes.Clear(); - regions.Clear(); - - this.hash_vtx = 0; - this.hash_seg = 0; - this.hash_tri = 0; - - flipstack.Clear(); - - hullsize = 0; - - Reset(); - - locator.Reset(); - } - - /// - /// Reset the mesh triangulation state. - /// - private void Reset() - { - numbering = NodeNumbering.None; - - undeads = 0; // No eliminated input vertices yet. - checksegments = false; // There are no segments in the triangulation yet. - checkquality = false; // The quality triangulation stage has not begun. - - Statistic.InCircleCount = 0; - Statistic.CounterClockwiseCount = 0; - Statistic.InCircleAdaptCount = 0; - Statistic.CounterClockwiseAdaptCount = 0; - Statistic.Orient3dCount = 0; - Statistic.HyperbolaCount = 0; - Statistic.CircleTopCount = 0; - Statistic.CircumcenterCount = 0; - } - - /// - /// Read the vertices from memory. - /// - /// The input data. - internal void TransferNodes(IList points) - { - this.invertices = points.Count; - this.mesh_dim = 2; - this.bounds = new Rectangle(); - - if (this.invertices < 3) - { - logger.Error("Input must have at least three input vertices.", "Mesh.TransferNodes()"); - throw new Exception("Input must have at least three input vertices."); - } - - var v = points[0]; - -#if USE_ATTRIBS - // Check attributes. - this.nextras = v.attributes == null ? 0 : v.attributes.Length; -#endif - - // Simple heuristic to check if ids are already set. We assume that if the - // first two vertex ids are distinct, then all input vertices have pairwise - // distinct ids. - bool userId = (v.id != points[1].id); - - foreach (var p in points) - { - if (userId) - { - p.hash = p.id; - - // Make sure the hash counter gets updated. - hash_vtx = Math.Max(p.hash + 1, hash_vtx); - } - else - { - p.hash = p.id = hash_vtx++; - } - - this.vertices.Add(p.hash, p); - this.bounds.Expand(p); - } - } - - /// - /// Construct a mapping from vertices to triangles to improve the speed of - /// point location for segment insertion. - /// - /// - /// Traverses all the triangles, and provides each corner of each triangle - /// with a pointer to that triangle. Of course, pointers will be overwritten - /// by other pointers because (almost) each vertex is a corner of several - /// triangles, but in the end every vertex will point to some triangle - /// that contains it. - /// - internal void MakeVertexMap() - { - Otri tri = default(Otri); - Vertex triorg; - - foreach (var t in this.triangles) - { - tri.tri = t; - // Check all three vertices of the triangle. - for (tri.orient = 0; tri.orient < 3; tri.orient++) - { - triorg = tri.Org(); - triorg.tri = tri; - } - } - } - - #endregion - - #region Factory - - /// - /// Create a new triangle with orientation zero. - /// - /// Reference to the new triangle. - internal void MakeTriangle(ref Otri newotri) - { - Triangle tri = triangles.Get(); - - //tri.id = tri.hash; - - tri.subsegs[0].seg = dummysub; - tri.subsegs[1].seg = dummysub; - tri.subsegs[2].seg = dummysub; - - tri.neighbors[0].tri = dummytri; - tri.neighbors[1].tri = dummytri; - tri.neighbors[2].tri = dummytri; - - newotri.tri = tri; - newotri.orient = 0; - } - - /// - /// Create a new subsegment with orientation zero. - /// - /// Reference to the new subseg. - internal void MakeSegment(ref Osub newsubseg) - { - var seg = new SubSegment(); - - seg.hash = this.hash_seg++; - - seg.subsegs[0].seg = dummysub; - seg.subsegs[1].seg = dummysub; - - seg.triangles[0].tri = dummytri; - seg.triangles[1].tri = dummytri; - - newsubseg.seg = seg; - newsubseg.orient = 0; - - subsegs.Add(seg.hash, seg); - } - - #endregion - - #region Manipulation - - /// - /// Insert a vertex into a Delaunay triangulation, performing flips as necessary - /// to maintain the Delaunay property. - /// - /// The point to be inserted. - /// The triangle to start the search. - /// Segment to split. - /// Check for creation of encroached subsegments. - /// Check for creation of bad quality triangles. - /// If a duplicate vertex or violated segment does not prevent the - /// vertex from being inserted, the return value will be ENCROACHINGVERTEX if - /// the vertex encroaches upon a subsegment (and checking is enabled), or - /// SUCCESSFULVERTEX otherwise. In either case, 'searchtri' is set to a handle - /// whose origin is the newly inserted vertex. - /// - /// The point 'newvertex' is located. If 'searchtri.triangle' is not NULL, - /// the search for the containing triangle begins from 'searchtri'. If - /// 'searchtri.triangle' is NULL, a full point location procedure is called. - /// If 'insertvertex' is found inside a triangle, the triangle is split into - /// three; if 'insertvertex' lies on an edge, the edge is split in two, - /// thereby splitting the two adjacent triangles into four. Edge flips are - /// used to restore the Delaunay property. If 'insertvertex' lies on an - /// existing vertex, no action is taken, and the value DUPLICATEVERTEX is - /// returned. On return, 'searchtri' is set to a handle whose origin is the - /// existing vertex. - /// - /// InsertVertex() does not use flip() for reasons of speed; some - /// information can be reused from edge flip to edge flip, like the - /// locations of subsegments. - /// - /// Param 'splitseg': Normally, the parameter 'splitseg' is set to NULL, - /// implying that no subsegment should be split. In this case, if 'insertvertex' - /// is found to lie on a segment, no action is taken, and the value VIOLATINGVERTEX - /// is returned. On return, 'searchtri' is set to a handle whose primary edge is the - /// violated subsegment. - /// If the calling routine wishes to split a subsegment by inserting a vertex in it, - /// the parameter 'splitseg' should be that subsegment. In this case, 'searchtri' - /// MUST be the triangle handle reached by pivoting from that subsegment; no point - /// location is done. - /// - /// Param 'segmentflaws': Flags that indicate whether or not there should - /// be checks for the creation of encroached subsegments. If a newly inserted - /// vertex encroaches upon subsegments, these subsegments are added to the list - /// of subsegments to be split if 'segmentflaws' is set. - /// - /// Param 'triflaws': Flags that indicate whether or not there should be - /// checks for the creation of bad quality triangles. If bad triangles are - /// created, these are added to the queue if 'triflaws' is set. - /// - internal InsertVertexResult InsertVertex(Vertex newvertex, ref Otri searchtri, - ref Osub splitseg, bool segmentflaws, bool triflaws) - { - Otri horiz = default(Otri); - Otri top = default(Otri); - Otri botleft = default(Otri), botright = default(Otri); - Otri topleft = default(Otri), topright = default(Otri); - Otri newbotleft = default(Otri), newbotright = default(Otri); - Otri newtopright = default(Otri); - Otri botlcasing = default(Otri), botrcasing = default(Otri); - Otri toplcasing = default(Otri), toprcasing = default(Otri); - Otri testtri = default(Otri); - Osub botlsubseg = default(Osub), botrsubseg = default(Osub); - Osub toplsubseg = default(Osub), toprsubseg = default(Osub); - Osub brokensubseg = default(Osub); - Osub checksubseg = default(Osub); - Osub rightsubseg = default(Osub); - Osub newsubseg = default(Osub); - BadSubseg encroached; - //FlipStacker newflip; - Vertex first; - Vertex leftvertex, rightvertex, botvertex, topvertex, farvertex; - Vertex segmentorg, segmentdest; - int region; - double area; - InsertVertexResult success; - LocateResult intersect; - bool doflip; - bool mirrorflag; - bool enq; - - if (splitseg.seg == null) - { - // Find the location of the vertex to be inserted. Check if a good - // starting triangle has already been provided by the caller. - if (searchtri.tri.id == DUMMY) - { - // Find a boundary triangle. - horiz.tri = dummytri; - horiz.orient = 0; - horiz.Sym(); - - // Search for a triangle containing 'newvertex'. - intersect = locator.Locate(newvertex, ref horiz); - } - else - { - // Start searching from the triangle provided by the caller. - searchtri.Copy(ref horiz); - intersect = locator.PreciseLocate(newvertex, ref horiz, true); - } - } - else - { - // The calling routine provides the subsegment in which - // the vertex is inserted. - searchtri.Copy(ref horiz); - intersect = LocateResult.OnEdge; - } - - if (intersect == LocateResult.OnVertex) - { - // There's already a vertex there. Return in 'searchtri' a triangle - // whose origin is the existing vertex. - horiz.Copy(ref searchtri); - locator.Update(ref horiz); - return InsertVertexResult.Duplicate; - } - if ((intersect == LocateResult.OnEdge) || (intersect == LocateResult.Outside)) - { - // The vertex falls on an edge or boundary. - if (checksegments && (splitseg.seg == null)) - { - // Check whether the vertex falls on a subsegment. - horiz.Pivot(ref brokensubseg); - if (brokensubseg.seg.hash != DUMMY) - { - // The vertex falls on a subsegment, and hence will not be inserted. - if (segmentflaws) - { - enq = behavior.NoBisect != 2; - if (enq && (behavior.NoBisect == 1)) - { - // This subsegment may be split only if it is an - // internal boundary. - horiz.Sym(ref testtri); - enq = testtri.tri.id != DUMMY; - } - if (enq) - { - // Add the subsegment to the list of encroached subsegments. - encroached = new BadSubseg(); - encroached.subseg = brokensubseg; - encroached.org = brokensubseg.Org(); - encroached.dest = brokensubseg.Dest(); - - qualityMesher.AddBadSubseg(encroached); - } - } - // Return a handle whose primary edge contains the vertex, - // which has not been inserted. - horiz.Copy(ref searchtri); - locator.Update(ref horiz); - return InsertVertexResult.Violating; - } - } - - // Insert the vertex on an edge, dividing one triangle into two (if - // the edge lies on a boundary) or two triangles into four. - horiz.Lprev(ref botright); - botright.Sym(ref botrcasing); - horiz.Sym(ref topright); - // Is there a second triangle? (Or does this edge lie on a boundary?) - mirrorflag = topright.tri.id != DUMMY; - if (mirrorflag) - { - topright.Lnext(); - topright.Sym(ref toprcasing); - MakeTriangle(ref newtopright); - } - else - { - // Splitting a boundary edge increases the number of boundary edges. - hullsize++; - } - MakeTriangle(ref newbotright); - - // Set the vertices of changed and new triangles. - rightvertex = horiz.Org(); - leftvertex = horiz.Dest(); - botvertex = horiz.Apex(); - newbotright.SetOrg(botvertex); - newbotright.SetDest(rightvertex); - newbotright.SetApex(newvertex); - horiz.SetOrg(newvertex); - - // Set the region of a new triangle. - newbotright.tri.label = botright.tri.label; - - if (behavior.VarArea) - { - // Set the area constraint of a new triangle. - newbotright.tri.area = botright.tri.area; - } - - if (mirrorflag) - { - topvertex = topright.Dest(); - newtopright.SetOrg(rightvertex); - newtopright.SetDest(topvertex); - newtopright.SetApex(newvertex); - topright.SetOrg(newvertex); - - // Set the region of another new triangle. - newtopright.tri.label = topright.tri.label; - - if (behavior.VarArea) - { - // Set the area constraint of another new triangle. - newtopright.tri.area = topright.tri.area; - } - } - - // There may be subsegments that need to be bonded - // to the new triangle(s). - if (checksegments) - { - botright.Pivot(ref botrsubseg); - - if (botrsubseg.seg.hash != DUMMY) - { - botright.SegDissolve(dummysub); - newbotright.SegBond(ref botrsubseg); - } - - if (mirrorflag) - { - topright.Pivot(ref toprsubseg); - if (toprsubseg.seg.hash != DUMMY) - { - topright.SegDissolve(dummysub); - newtopright.SegBond(ref toprsubseg); - } - } - } - - // Bond the new triangle(s) to the surrounding triangles. - newbotright.Bond(ref botrcasing); - newbotright.Lprev(); - newbotright.Bond(ref botright); - newbotright.Lprev(); - - if (mirrorflag) - { - newtopright.Bond(ref toprcasing); - newtopright.Lnext(); - newtopright.Bond(ref topright); - newtopright.Lnext(); - newtopright.Bond(ref newbotright); - } - - if (splitseg.seg != null) - { - // Split the subsegment into two. - splitseg.SetDest(newvertex); - segmentorg = splitseg.SegOrg(); - segmentdest = splitseg.SegDest(); - splitseg.Sym(); - splitseg.Pivot(ref rightsubseg); - InsertSubseg(ref newbotright, splitseg.seg.boundary); - newbotright.Pivot(ref newsubseg); - newsubseg.SetSegOrg(segmentorg); - newsubseg.SetSegDest(segmentdest); - splitseg.Bond(ref newsubseg); - newsubseg.Sym(); - newsubseg.Bond(ref rightsubseg); - splitseg.Sym(); - - // Transfer the subsegment's boundary marker to the vertex if required. - if (newvertex.label == 0) - { - newvertex.label = splitseg.seg.boundary; - } - } - - if (checkquality) - { - flipstack.Clear(); - - flipstack.Push(default(Otri)); // Dummy flip (see UndoVertex) - flipstack.Push(horiz); - } - - // Position 'horiz' on the first edge to check for - // the Delaunay property. - horiz.Lnext(); - } - else - { - // Insert the vertex in a triangle, splitting it into three. - horiz.Lnext(ref botleft); - horiz.Lprev(ref botright); - botleft.Sym(ref botlcasing); - botright.Sym(ref botrcasing); - MakeTriangle(ref newbotleft); - MakeTriangle(ref newbotright); - - // Set the vertices of changed and new triangles. - rightvertex = horiz.Org(); - leftvertex = horiz.Dest(); - botvertex = horiz.Apex(); - newbotleft.SetOrg(leftvertex); - newbotleft.SetDest(botvertex); - newbotleft.SetApex(newvertex); - newbotright.SetOrg(botvertex); - newbotright.SetDest(rightvertex); - newbotright.SetApex(newvertex); - horiz.SetApex(newvertex); - - // Set the region of the new triangles. - newbotleft.tri.label = horiz.tri.label; - newbotright.tri.label = horiz.tri.label; - - if (behavior.VarArea) - { - // Set the area constraint of the new triangles. - area = horiz.tri.area; - newbotleft.tri.area = area; - newbotright.tri.area = area; - } - - // There may be subsegments that need to be bonded - // to the new triangles. - if (checksegments) - { - botleft.Pivot(ref botlsubseg); - if (botlsubseg.seg.hash != DUMMY) - { - botleft.SegDissolve(dummysub); - newbotleft.SegBond(ref botlsubseg); - } - botright.Pivot(ref botrsubseg); - if (botrsubseg.seg.hash != DUMMY) - { - botright.SegDissolve(dummysub); - newbotright.SegBond(ref botrsubseg); - } - } - - // Bond the new triangles to the surrounding triangles. - newbotleft.Bond(ref botlcasing); - newbotright.Bond(ref botrcasing); - newbotleft.Lnext(); - newbotright.Lprev(); - newbotleft.Bond(ref newbotright); - newbotleft.Lnext(); - botleft.Bond(ref newbotleft); - newbotright.Lprev(); - botright.Bond(ref newbotright); - - if (checkquality) - { - flipstack.Clear(); - flipstack.Push(horiz); - } - } - - // The insertion is successful by default, unless an encroached - // subsegment is found. - success = InsertVertexResult.Successful; - - if (newvertex.tri.tri != null) - { - // Store the coordinates of the triangle that contains newvertex. - newvertex.tri.SetOrg(rightvertex); - newvertex.tri.SetDest(leftvertex); - newvertex.tri.SetApex(botvertex); - } - - // Circle around the newly inserted vertex, checking each edge opposite it - // for the Delaunay property. Non-Delaunay edges are flipped. 'horiz' is - // always the edge being checked. 'first' marks where to stop circling. - first = horiz.Org(); - rightvertex = first; - leftvertex = horiz.Dest(); - // Circle until finished. - while (true) - { - // By default, the edge will be flipped. - doflip = true; - - if (checksegments) - { - // Check for a subsegment, which cannot be flipped. - horiz.Pivot(ref checksubseg); - if (checksubseg.seg.hash != DUMMY) - { - // The edge is a subsegment and cannot be flipped. - doflip = false; - - if (segmentflaws) - { - // Does the new vertex encroach upon this subsegment? - if (qualityMesher.CheckSeg4Encroach(ref checksubseg) > 0) - { - success = InsertVertexResult.Encroaching; - } - } - } - } - - if (doflip) - { - // Check if the edge is a boundary edge. - horiz.Sym(ref top); - if (top.tri.id == DUMMY) - { - // The edge is a boundary edge and cannot be flipped. - doflip = false; - } - else - { - // Find the vertex on the other side of the edge. - farvertex = top.Apex(); - // In the incremental Delaunay triangulation algorithm, any of - // 'leftvertex', 'rightvertex', and 'farvertex' could be vertices - // of the triangular bounding box. These vertices must be - // treated as if they are infinitely distant, even though their - // "coordinates" are not. - if ((leftvertex == infvertex1) || (leftvertex == infvertex2) || - (leftvertex == infvertex3)) - { - // 'leftvertex' is infinitely distant. Check the convexity of - // the boundary of the triangulation. 'farvertex' might be - // infinite as well, but trust me, this same condition should - // be applied. - doflip = predicates.CounterClockwise(newvertex, rightvertex, farvertex) > 0.0; - } - else if ((rightvertex == infvertex1) || - (rightvertex == infvertex2) || - (rightvertex == infvertex3)) - { - // 'rightvertex' is infinitely distant. Check the convexity of - // the boundary of the triangulation. 'farvertex' might be - // infinite as well, but trust me, this same condition should - // be applied. - doflip = predicates.CounterClockwise(farvertex, leftvertex, newvertex) > 0.0; - } - else if ((farvertex == infvertex1) || - (farvertex == infvertex2) || - (farvertex == infvertex3)) - { - // 'farvertex' is infinitely distant and cannot be inside - // the circumcircle of the triangle 'horiz'. - doflip = false; - } - else - { - // Test whether the edge is locally Delaunay. - doflip = predicates.InCircle(leftvertex, newvertex, rightvertex, farvertex) > 0.0; - } - if (doflip) - { - // We made it! Flip the edge 'horiz' by rotating its containing - // quadrilateral (the two triangles adjacent to 'horiz'). - // Identify the casing of the quadrilateral. - top.Lprev(ref topleft); - topleft.Sym(ref toplcasing); - top.Lnext(ref topright); - topright.Sym(ref toprcasing); - horiz.Lnext(ref botleft); - botleft.Sym(ref botlcasing); - horiz.Lprev(ref botright); - botright.Sym(ref botrcasing); - // Rotate the quadrilateral one-quarter turn counterclockwise. - topleft.Bond(ref botlcasing); - botleft.Bond(ref botrcasing); - botright.Bond(ref toprcasing); - topright.Bond(ref toplcasing); - if (checksegments) - { - // Check for subsegments and rebond them to the quadrilateral. - topleft.Pivot(ref toplsubseg); - botleft.Pivot(ref botlsubseg); - botright.Pivot(ref botrsubseg); - topright.Pivot(ref toprsubseg); - if (toplsubseg.seg.hash == DUMMY) - { - topright.SegDissolve(dummysub); - } - else - { - topright.SegBond(ref toplsubseg); - } - if (botlsubseg.seg.hash == DUMMY) - { - topleft.SegDissolve(dummysub); - } - else - { - topleft.SegBond(ref botlsubseg); - } - if (botrsubseg.seg.hash == DUMMY) - { - botleft.SegDissolve(dummysub); - } - else - { - botleft.SegBond(ref botrsubseg); - } - if (toprsubseg.seg.hash == DUMMY) - { - botright.SegDissolve(dummysub); - } - else - { - botright.SegBond(ref toprsubseg); - } - } - // New vertex assignments for the rotated quadrilateral. - horiz.SetOrg(farvertex); - horiz.SetDest(newvertex); - horiz.SetApex(rightvertex); - top.SetOrg(newvertex); - top.SetDest(farvertex); - top.SetApex(leftvertex); - - // Assign region. - // TODO: check region ok (no Math.Min necessary) - region = Math.Min(top.tri.label, horiz.tri.label); - top.tri.label = region; - horiz.tri.label = region; - - if (behavior.VarArea) - { - if ((top.tri.area <= 0.0) || (horiz.tri.area <= 0.0)) - { - area = -1.0; - } - else - { - // Take the average of the two triangles' area constraints. - // This prevents small area constraints from migrating a - // long, long way from their original location due to flips. - area = 0.5 * (top.tri.area + horiz.tri.area); - } - - top.tri.area = area; - horiz.tri.area = area; - } - - if (checkquality) - { - flipstack.Push(horiz); - } - - // On the next iterations, consider the two edges that were exposed (this - // is, are now visible to the newly inserted vertex) by the edge flip. - horiz.Lprev(); - leftvertex = farvertex; - } - } - } - if (!doflip) - { - // The handle 'horiz' is accepted as locally Delaunay. - if (triflaws) - { - // Check the triangle 'horiz' for quality. - qualityMesher.TestTriangle(ref horiz); - } - - // Look for the next edge around the newly inserted vertex. - horiz.Lnext(); - horiz.Sym(ref testtri); - // Check for finishing a complete revolution about the new vertex, or - // falling outside of the triangulation. The latter will happen when - // a vertex is inserted at a boundary. - if ((leftvertex == first) || (testtri.tri.id == DUMMY)) - { - // We're done. Return a triangle whose origin is the new vertex. - horiz.Lnext(ref searchtri); - - Otri recenttri = default(Otri); - horiz.Lnext(ref recenttri); - locator.Update(ref recenttri); - - return success; - } - // Finish finding the next edge around the newly inserted vertex. - testtri.Lnext(ref horiz); - rightvertex = leftvertex; - leftvertex = horiz.Dest(); - } - } - } - - /// - /// Create a new subsegment and inserts it between two triangles. Its - /// vertices are properly initialized. - /// - /// The new subsegment is inserted at the edge - /// described by this handle. - /// The marker 'subsegmark' is applied to the - /// subsegment and, if appropriate, its vertices. - internal void InsertSubseg(ref Otri tri, int subsegmark) - { - Otri oppotri = default(Otri); - Osub newsubseg = default(Osub); - Vertex triorg, tridest; - - triorg = tri.Org(); - tridest = tri.Dest(); - // Mark vertices if possible. - if (triorg.label == 0) - { - triorg.label = subsegmark; - } - if (tridest.label == 0) - { - tridest.label = subsegmark; - } - // Check if there's already a subsegment here. - tri.Pivot(ref newsubseg); - if (newsubseg.seg.hash == DUMMY) - { - // Make new subsegment and initialize its vertices. - MakeSegment(ref newsubseg); - newsubseg.SetOrg(tridest); - newsubseg.SetDest(triorg); - newsubseg.SetSegOrg(tridest); - newsubseg.SetSegDest(triorg); - // Bond new subsegment to the two triangles it is sandwiched between. - // Note that the facing triangle 'oppotri' might be equal to 'dummytri' - // (outer space), but the new subsegment is bonded to it all the same. - tri.SegBond(ref newsubseg); - tri.Sym(ref oppotri); - newsubseg.Sym(); - oppotri.SegBond(ref newsubseg); - newsubseg.seg.boundary = subsegmark; - } - else if (newsubseg.seg.boundary == 0) - { - newsubseg.seg.boundary = subsegmark; - } - } - - /// - /// Transform two triangles to two different triangles by flipping an edge - /// counterclockwise within a quadrilateral. - /// - /// Handle to the edge that will be flipped. - /// Imagine the original triangles, abc and bad, oriented so that the - /// shared edge ab lies in a horizontal plane, with the vertex b on the left - /// and the vertex a on the right. The vertex c lies below the edge, and - /// the vertex d lies above the edge. The 'flipedge' handle holds the edge - /// ab of triangle abc, and is directed left, from vertex a to vertex b. - /// - /// The triangles abc and bad are deleted and replaced by the triangles cdb - /// and dca. The triangles that represent abc and bad are NOT deallocated; - /// they are reused for dca and cdb, respectively. Hence, any handles that - /// may have held the original triangles are still valid, although not - /// directed as they were before. - /// - /// Upon completion of this routine, the 'flipedge' handle holds the edge - /// dc of triangle dca, and is directed down, from vertex d to vertex c. - /// (Hence, the two triangles have rotated counterclockwise.) - /// - /// WARNING: This transformation is geometrically valid only if the - /// quadrilateral adbc is convex. Furthermore, this transformation is - /// valid only if there is not a subsegment between the triangles abc and - /// bad. This routine does not check either of these preconditions, and - /// it is the responsibility of the calling routine to ensure that they are - /// met. If they are not, the streets shall be filled with wailing and - /// gnashing of teeth. - /// - /// Terminology - /// - /// A "local transformation" replaces a small set of triangles with another - /// set of triangles. This may or may not involve inserting or deleting a - /// vertex. - /// - /// The term "casing" is used to describe the set of triangles that are - /// attached to the triangles being transformed, but are not transformed - /// themselves. Think of the casing as a fixed hollow structure inside - /// which all the action happens. A "casing" is only defined relative to - /// a single transformation; each occurrence of a transformation will - /// involve a different casing. - /// - internal void Flip(ref Otri flipedge) - { - Otri botleft = default(Otri), botright = default(Otri); - Otri topleft = default(Otri), topright = default(Otri); - Otri top = default(Otri); - Otri botlcasing = default(Otri), botrcasing = default(Otri); - Otri toplcasing = default(Otri), toprcasing = default(Otri); - Osub botlsubseg = default(Osub), botrsubseg = default(Osub); - Osub toplsubseg = default(Osub), toprsubseg = default(Osub); - Vertex leftvertex, rightvertex, botvertex; - Vertex farvertex; - - // Identify the vertices of the quadrilateral. - rightvertex = flipedge.Org(); - leftvertex = flipedge.Dest(); - botvertex = flipedge.Apex(); - flipedge.Sym(ref top); - - // SELF CHECK - - //if (top.triangle.id == DUMMY) - //{ - // logger.Error("Attempt to flip on boundary.", "Mesh.Flip()"); - // flipedge.LnextSelf(); - // return; - //} - - //if (checksegments) - //{ - // flipedge.SegPivot(ref toplsubseg); - // if (toplsubseg.ss != Segment.Empty) - // { - // logger.Error("Attempt to flip a segment.", "Mesh.Flip()"); - // flipedge.LnextSelf(); - // return; - // } - //} - - farvertex = top.Apex(); - - // Identify the casing of the quadrilateral. - top.Lprev(ref topleft); - topleft.Sym(ref toplcasing); - top.Lnext(ref topright); - topright.Sym(ref toprcasing); - flipedge.Lnext(ref botleft); - botleft.Sym(ref botlcasing); - flipedge.Lprev(ref botright); - botright.Sym(ref botrcasing); - // Rotate the quadrilateral one-quarter turn counterclockwise. - topleft.Bond(ref botlcasing); - botleft.Bond(ref botrcasing); - botright.Bond(ref toprcasing); - topright.Bond(ref toplcasing); - - if (checksegments) - { - // Check for subsegments and rebond them to the quadrilateral. - topleft.Pivot(ref toplsubseg); - botleft.Pivot(ref botlsubseg); - botright.Pivot(ref botrsubseg); - topright.Pivot(ref toprsubseg); - - if (toplsubseg.seg.hash == DUMMY) - { - topright.SegDissolve(dummysub); - } - else - { - topright.SegBond(ref toplsubseg); - } - - if (botlsubseg.seg.hash == DUMMY) - { - topleft.SegDissolve(dummysub); - } - else - { - topleft.SegBond(ref botlsubseg); - } - - if (botrsubseg.seg.hash == DUMMY) - { - botleft.SegDissolve(dummysub); - } - else - { - botleft.SegBond(ref botrsubseg); - } - - if (toprsubseg.seg.hash == DUMMY) - { - botright.SegDissolve(dummysub); - } - else - { - botright.SegBond(ref toprsubseg); - } - } - - // New vertex assignments for the rotated quadrilateral. - flipedge.SetOrg(farvertex); - flipedge.SetDest(botvertex); - flipedge.SetApex(rightvertex); - top.SetOrg(botvertex); - top.SetDest(farvertex); - top.SetApex(leftvertex); - } - - /// - /// Transform two triangles to two different triangles by flipping an edge - /// clockwise within a quadrilateral. Reverses the flip() operation so that - /// the data structures representing the triangles are back where they were - /// before the flip(). - /// - /// - /// - /// See above Flip() remarks for more information. - /// - /// Upon completion of this routine, the 'flipedge' handle holds the edge - /// cd of triangle cdb, and is directed up, from vertex c to vertex d. - /// (Hence, the two triangles have rotated clockwise.) - /// - internal void Unflip(ref Otri flipedge) - { - Otri botleft = default(Otri), botright = default(Otri); - Otri topleft = default(Otri), topright = default(Otri); - Otri top = default(Otri); - Otri botlcasing = default(Otri), botrcasing = default(Otri); - Otri toplcasing = default(Otri), toprcasing = default(Otri); - Osub botlsubseg = default(Osub), botrsubseg = default(Osub); - Osub toplsubseg = default(Osub), toprsubseg = default(Osub); - Vertex leftvertex, rightvertex, botvertex; - Vertex farvertex; - - // Identify the vertices of the quadrilateral. - rightvertex = flipedge.Org(); - leftvertex = flipedge.Dest(); - botvertex = flipedge.Apex(); - flipedge.Sym(ref top); - - farvertex = top.Apex(); - - // Identify the casing of the quadrilateral. - top.Lprev(ref topleft); - topleft.Sym(ref toplcasing); - top.Lnext(ref topright); - topright.Sym(ref toprcasing); - flipedge.Lnext(ref botleft); - botleft.Sym(ref botlcasing); - flipedge.Lprev(ref botright); - botright.Sym(ref botrcasing); - // Rotate the quadrilateral one-quarter turn clockwise. - topleft.Bond(ref toprcasing); - botleft.Bond(ref toplcasing); - botright.Bond(ref botlcasing); - topright.Bond(ref botrcasing); - - if (checksegments) - { - // Check for subsegments and rebond them to the quadrilateral. - topleft.Pivot(ref toplsubseg); - botleft.Pivot(ref botlsubseg); - botright.Pivot(ref botrsubseg); - topright.Pivot(ref toprsubseg); - if (toplsubseg.seg.hash == DUMMY) - { - botleft.SegDissolve(dummysub); - } - else - { - botleft.SegBond(ref toplsubseg); - } - if (botlsubseg.seg.hash == DUMMY) - { - botright.SegDissolve(dummysub); - } - else - { - botright.SegBond(ref botlsubseg); - } - if (botrsubseg.seg.hash == DUMMY) - { - topright.SegDissolve(dummysub); - } - else - { - topright.SegBond(ref botrsubseg); - } - if (toprsubseg.seg.hash == DUMMY) - { - topleft.SegDissolve(dummysub); - } - else - { - topleft.SegBond(ref toprsubseg); - } - } - - // New vertex assignments for the rotated quadrilateral. - flipedge.SetOrg(botvertex); - flipedge.SetDest(farvertex); - flipedge.SetApex(leftvertex); - top.SetOrg(farvertex); - top.SetDest(botvertex); - top.SetApex(rightvertex); - } - - /// - /// Find the Delaunay triangulation of a polygon that has a certain "nice" shape. - /// This includes the polygons that result from deletion of a vertex or insertion - /// of a segment. - /// - /// The primary edge of the first triangle. - /// The primary edge of the last triangle. - /// The number of sides of the polygon, including its - /// base. - /// A flag, wether to perform the last flip. - /// A flag that determines whether the new triangles should - /// be tested for quality, and enqueued if they are bad. - /// - // This is a conceptually difficult routine. The starting assumption is - // that we have a polygon with n sides. n - 1 of these sides are currently - // represented as edges in the mesh. One side, called the "base", need not - // be. - // - // Inside the polygon is a structure I call a "fan", consisting of n - 1 - // triangles that share a common origin. For each of these triangles, the - // edge opposite the origin is one of the sides of the polygon. The - // primary edge of each triangle is the edge directed from the origin to - // the destination; note that this is not the same edge that is a side of - // the polygon. 'firstedge' is the primary edge of the first triangle. - // From there, the triangles follow in counterclockwise order about the - // polygon, until 'lastedge', the primary edge of the last triangle. - // 'firstedge' and 'lastedge' are probably connected to other triangles - // beyond the extremes of the fan, but their identity is not important, as - // long as the fan remains connected to them. - // - // Imagine the polygon oriented so that its base is at the bottom. This - // puts 'firstedge' on the far right, and 'lastedge' on the far left. - // The right vertex of the base is the destination of 'firstedge', and the - // left vertex of the base is the apex of 'lastedge'. - // - // The challenge now is to find the right sequence of edge flips to - // transform the fan into a Delaunay triangulation of the polygon. Each - // edge flip effectively removes one triangle from the fan, committing it - // to the polygon. The resulting polygon has one fewer edge. If 'doflip' - // is set, the final flip will be performed, resulting in a fan of one - // (useless?) triangle. If 'doflip' is not set, the final flip is not - // performed, resulting in a fan of two triangles, and an unfinished - // triangular polygon that is not yet filled out with a single triangle. - // On completion of the routine, 'lastedge' is the last remaining triangle, - // or the leftmost of the last two. - // - // Although the flips are performed in the order described above, the - // decisions about what flips to perform are made in precisely the reverse - // order. The recursive triangulatepolygon() procedure makes a decision, - // uses up to two recursive calls to triangulate the "subproblems" - // (polygons with fewer edges), and then performs an edge flip. - // - // The "decision" it makes is which vertex of the polygon should be - // connected to the base. This decision is made by testing every possible - // vertex. Once the best vertex is found, the two edges that connect this - // vertex to the base become the bases for two smaller polygons. These - // are triangulated recursively. Unfortunately, this approach can take - // O(n^2) time not only in the worst case, but in many common cases. It's - // rarely a big deal for vertex deletion, where n is rarely larger than - // ten, but it could be a big deal for segment insertion, especially if - // there's a lot of long segments that each cut many triangles. I ought to - // code a faster algorithm some day. - /// - private void TriangulatePolygon(Otri firstedge, Otri lastedge, - int edgecount, bool doflip, bool triflaws) - { - Otri testtri = default(Otri); - Otri besttri = default(Otri); - Otri tempedge = default(Otri); - Vertex leftbasevertex, rightbasevertex; - Vertex testvertex; - Vertex bestvertex; - - int bestnumber = 1; - - // Identify the base vertices. - leftbasevertex = lastedge.Apex(); - rightbasevertex = firstedge.Dest(); - - // Find the best vertex to connect the base to. - firstedge.Onext(ref besttri); - bestvertex = besttri.Dest(); - besttri.Copy(ref testtri); - - for (int i = 2; i <= edgecount - 2; i++) - { - testtri.Onext(); - testvertex = testtri.Dest(); - // Is this a better vertex? - if (predicates.InCircle(leftbasevertex, rightbasevertex, bestvertex, testvertex) > 0.0) - { - testtri.Copy(ref besttri); - bestvertex = testvertex; - bestnumber = i; - } - } - - if (bestnumber > 1) - { - // Recursively triangulate the smaller polygon on the right. - besttri.Oprev(ref tempedge); - TriangulatePolygon(firstedge, tempedge, bestnumber + 1, true, triflaws); - } - - if (bestnumber < edgecount - 2) - { - // Recursively triangulate the smaller polygon on the left. - besttri.Sym(ref tempedge); - TriangulatePolygon(besttri, lastedge, edgecount - bestnumber, true, triflaws); - // Find 'besttri' again; it may have been lost to edge flips. - tempedge.Sym(ref besttri); - } - - if (doflip) - { - // Do one final edge flip. - Flip(ref besttri); - if (triflaws) - { - // Check the quality of the newly committed triangle. - besttri.Sym(ref testtri); - qualityMesher.TestTriangle(ref testtri); - } - } - // Return the base triangle. - besttri.Copy(ref lastedge); - } - - /// - /// Delete a vertex from a Delaunay triangulation, ensuring that the - /// triangulation remains Delaunay. - /// - /// - /// The origin of 'deltri' is deleted. The union of the triangles - /// adjacent to this vertex is a polygon, for which the Delaunay triangulation - /// is found. Two triangles are removed from the mesh. - /// - /// Only interior vertices that do not lie on segments or boundaries - /// may be deleted. - /// - internal void DeleteVertex(ref Otri deltri) - { - Otri countingtri = default(Otri); - Otri firstedge = default(Otri), lastedge = default(Otri); - Otri deltriright = default(Otri); - Otri lefttri = default(Otri), righttri = default(Otri); - Otri leftcasing = default(Otri), rightcasing = default(Otri); - Osub leftsubseg = default(Osub), rightsubseg = default(Osub); - Vertex delvertex; - Vertex neworg; - int edgecount; - - delvertex = deltri.Org(); - - VertexDealloc(delvertex); - - // Count the degree of the vertex being deleted. - deltri.Onext(ref countingtri); - edgecount = 1; - while (!deltri.Equals(countingtri)) - { - edgecount++; - countingtri.Onext(); - } - - if (edgecount > 3) - { - // Triangulate the polygon defined by the union of all triangles - // adjacent to the vertex being deleted. Check the quality of - // the resulting triangles. - deltri.Onext(ref firstedge); - deltri.Oprev(ref lastedge); - TriangulatePolygon(firstedge, lastedge, edgecount, false, behavior.NoBisect == 0); - } - // Splice out two triangles. - deltri.Lprev(ref deltriright); - deltri.Dnext(ref lefttri); - lefttri.Sym(ref leftcasing); - deltriright.Oprev(ref righttri); - righttri.Sym(ref rightcasing); - deltri.Bond(ref leftcasing); - deltriright.Bond(ref rightcasing); - lefttri.Pivot(ref leftsubseg); - if (leftsubseg.seg.hash != DUMMY) - { - deltri.SegBond(ref leftsubseg); - } - righttri.Pivot(ref rightsubseg); - if (rightsubseg.seg.hash != DUMMY) - { - deltriright.SegBond(ref rightsubseg); - } - - // Set the new origin of 'deltri' and check its quality. - neworg = lefttri.Org(); - deltri.SetOrg(neworg); - if (behavior.NoBisect == 0) - { - qualityMesher.TestTriangle(ref deltri); - } - - // Delete the two spliced-out triangles. - TriangleDealloc(lefttri.tri); - TriangleDealloc(righttri.tri); - } - - /// - /// Undo the most recent vertex insertion. - /// - /// - /// Walks through the list of transformations (flips and a vertex insertion) - /// in the reverse of the order in which they were done, and undoes them. - /// The inserted vertex is removed from the triangulation and deallocated. - /// Two triangles (possibly just one) are also deallocated. - /// - internal void UndoVertex() - { - Otri fliptri; - - Otri botleft = default(Otri), botright = default(Otri), topright = default(Otri); - Otri botlcasing = default(Otri), botrcasing = default(Otri), toprcasing = default(Otri); - Otri gluetri = default(Otri); - Osub botlsubseg = default(Osub), botrsubseg = default(Osub), toprsubseg = default(Osub); - Vertex botvertex, rightvertex; - - // Walk through the list of transformations (flips and a vertex insertion) - // in the reverse of the order in which they were done, and undo them. - while (flipstack.Count > 0) - { - // Find a triangle involved in the last unreversed transformation. - fliptri = flipstack.Pop(); - - // We are reversing one of three transformations: a trisection of one - // triangle into three (by inserting a vertex in the triangle), a - // bisection of two triangles into four (by inserting a vertex in an - // edge), or an edge flip. - if (flipstack.Count == 0) - { - // Restore a triangle that was split into three triangles, - // so it is again one triangle. - fliptri.Dprev(ref botleft); - botleft.Lnext(); - fliptri.Onext(ref botright); - botright.Lprev(); - botleft.Sym(ref botlcasing); - botright.Sym(ref botrcasing); - botvertex = botleft.Dest(); - - fliptri.SetApex(botvertex); - fliptri.Lnext(); - fliptri.Bond(ref botlcasing); - botleft.Pivot(ref botlsubseg); - fliptri.SegBond(ref botlsubseg); - fliptri.Lnext(); - fliptri.Bond(ref botrcasing); - botright.Pivot(ref botrsubseg); - fliptri.SegBond(ref botrsubseg); - - // Delete the two spliced-out triangles. - TriangleDealloc(botleft.tri); - TriangleDealloc(botright.tri); - } - else if (flipstack.Peek().tri == null) // Dummy flip - { - // Restore two triangles that were split into four triangles, - // so they are again two triangles. - fliptri.Lprev(ref gluetri); - gluetri.Sym(ref botright); - botright.Lnext(); - botright.Sym(ref botrcasing); - rightvertex = botright.Dest(); - - fliptri.SetOrg(rightvertex); - gluetri.Bond(ref botrcasing); - botright.Pivot(ref botrsubseg); - gluetri.SegBond(ref botrsubseg); - - // Delete the spliced-out triangle. - TriangleDealloc(botright.tri); - - fliptri.Sym(ref gluetri); - if (gluetri.tri.id != DUMMY) - { - gluetri.Lnext(); - gluetri.Dnext(ref topright); - topright.Sym(ref toprcasing); - - gluetri.SetOrg(rightvertex); - gluetri.Bond(ref toprcasing); - topright.Pivot(ref toprsubseg); - gluetri.SegBond(ref toprsubseg); - - // Delete the spliced-out triangle. - TriangleDealloc(topright.tri); - } - - flipstack.Clear(); - } - else - { - // Undo an edge flip. - Unflip(ref fliptri); - } - } - } - - #endregion - - #region Dealloc - - /// - /// Deallocate space for a triangle, marking it dead. - /// - /// - internal void TriangleDealloc(Triangle dyingtriangle) - { - // Mark the triangle as dead. This makes it possible to detect dead - // triangles when traversing the list of all triangles. - Otri.Kill(dyingtriangle); - triangles.Release(dyingtriangle); - } - - /// - /// Deallocate space for a vertex, marking it dead. - /// - /// - internal void VertexDealloc(Vertex dyingvertex) - { - // Mark the vertex as dead. This makes it possible to detect dead - // vertices when traversing the list of all vertices. - dyingvertex.type = VertexType.DeadVertex; - vertices.Remove(dyingvertex.hash); - } - - /// - /// Deallocate space for a subsegment, marking it dead. - /// - /// - internal void SubsegDealloc(SubSegment dyingsubseg) - { - // Mark the subsegment as dead. This makes it possible to detect dead - // subsegments when traversing the list of all subsegments. - Osub.Kill(dyingsubseg); - subsegs.Remove(dyingsubseg.hash); - } - - #endregion - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + using TriangleNet.Meshing.Data; + using TriangleNet.Meshing.Iterators; + using TriangleNet.Tools; + using TriangleNet.Topology; + + /// + /// Mesh data structure. + /// + public class Mesh : IMesh + { + #region Variables + + IPredicates predicates; + + Log logger = Log.Instance; + + QualityMesher qualityMesher; + + // Stack that maintains a list of recently flipped triangles. + Stack flipstack; + + // TODO: Check if custom hashmap implementation could be faster. + + // Using hashsets for memory management should quite fast. + internal TrianglePool triangles; + internal Dictionary subsegs; + internal Dictionary vertices; + + // Hash seeds (should belong to mesh instance) + internal int hash_vtx = 0; + internal int hash_seg = 0; + internal int hash_tri = 0; + + internal List holes; + internal List regions; + + // TODO: remove mesh_dim, invertices and insegments + + // Other variables. + internal Rectangle bounds; // x and y bounds. + internal int invertices; // Number of input vertices. + internal int insegments; // Number of input segments. + internal int undeads; // Number of input vertices that don't appear in the mesh. + internal int mesh_dim; // Dimension (ought to be 2). + internal int nextras; // Number of attributes per vertex. + //internal int eextras; // Number of attributes per triangle. + internal int hullsize; // Number of edges in convex hull. + internal int steinerleft; // Number of Steiner points not yet used. + internal bool checksegments; // Are there segments in the triangulation yet? + internal bool checkquality; // Has quality triangulation begun yet? + + // Triangular bounding box vertices. + internal Vertex infvertex1, infvertex2, infvertex3; + + internal TriangleLocator locator; + + // Controls the behavior of the mesh instance. + internal Behavior behavior; + + // The current node numbering + internal NodeNumbering numbering; + + #endregion + + #region Public properties + + /// + /// Gets the mesh bounding box. + /// + public Rectangle Bounds => bounds; + + /// + /// Gets the mesh vertices. + /// + public ICollection Vertices => vertices.Values; + + /// + /// Gets the mesh holes. + /// + public IList Holes => holes; + + /// + /// Gets the mesh triangles. + /// + public ICollection Triangles => triangles; + + /// + /// Gets the mesh segments. + /// + public ICollection Segments => subsegs.Values; + + /// + /// Gets the mesh edges. + /// + public IEnumerable Edges => new EdgeIterator().EnumerateEdges(this); + + /// + /// Gets the number of input vertices. + /// + public int NumberOfInputPoints => invertices; + + /// + /// Gets the number of mesh edges. + /// + public int NumberOfEdges => (3 * triangles.Count + hullsize) / 2; + + /// + /// Indicates whether the input is a PSLG or a point set. + /// + public bool IsPolygon => insegments > 0; + + /// + /// Gets the current node numbering. + /// + public NodeNumbering CurrentNumbering => numbering; + + #endregion + + #region "Outer space" variables + + internal const int DUMMY = -1; + + // The triangle that fills "outer space," called 'dummytri', is pointed to + // by every triangle and subsegment on a boundary (be it outer or inner) of + // the triangulation. Also, 'dummytri' points to one of the triangles on + // the convex hull (until the holes and concavities are carved), making it + // possible to find a starting triangle for point location. + + // 'dummytri' and 'dummysub' are generally required to fulfill only a few + // invariants: their vertices must remain NULL and 'dummytri' must always + // be bonded (at offset zero) to some triangle on the convex hull of the + // mesh, via a boundary edge. Otherwise, the connections of 'dummytri' and + // 'dummysub' may change willy-nilly. This makes it possible to avoid + // writing a good deal of special-case code (in the edge flip, for example) + // for dealing with the boundary of the mesh, places where no subsegment is + // present, and so forth. Other entities are frequently bonded to + // 'dummytri' and 'dummysub' as if they were real mesh entities, with no + // harm done. + + internal Triangle dummytri; + + // Set up 'dummysub', the omnipresent subsegment pointed to by any + // triangle side or subsegment end that isn't attached to a real + // subsegment. + + internal SubSegment dummysub; + + private void Initialize() + { + dummysub = new SubSegment(); + dummysub.hash = DUMMY; + + // Initialize the two adjoining subsegments to be the omnipresent + // subsegment. These will eventually be changed by various bonding + // operations, but their values don't really matter, as long as they + // can legally be dereferenced. + dummysub.subsegs[0].seg = dummysub; + dummysub.subsegs[1].seg = dummysub; + + // Set up 'dummytri', the 'triangle' that occupies "outer space." + dummytri = new Triangle(); + dummytri.hash = dummytri.id = DUMMY; + + // Initialize the three adjoining triangles to be "outer space." These + // will eventually be changed by various bonding operations, but their + // values don't really matter, as long as they can legally be + // dereferenced. + dummytri.neighbors[0].tri = dummytri; + dummytri.neighbors[1].tri = dummytri; + dummytri.neighbors[2].tri = dummytri; + + // Initialize the three adjoining subsegments of 'dummytri' to be + // the omnipresent subsegment. + dummytri.subsegs[0].seg = dummysub; + dummytri.subsegs[1].seg = dummysub; + dummytri.subsegs[2].seg = dummysub; + } + + #endregion + + /// + /// Initializes a new instance of the class. + /// + public Mesh(Configuration config, IList points) + { + Initialize(); + + behavior = new Behavior(); + + vertices = new Dictionary(points.Count); + subsegs = new Dictionary(); + + triangles = config.TrianglePool(); + + flipstack = new Stack(); + + holes = new List(); + regions = new List(); + + steinerleft = -1; + + predicates = config.Predicates(); + + locator = new TriangleLocator(this, predicates); + + TransferNodes(points); + } + + public void Refine(QualityOptions quality, bool delaunay = false) + { + invertices = vertices.Count; + + if (behavior.Poly) + { + insegments = behavior.useSegments ? subsegs.Count : hullsize; + } + + Reset(); + + if (qualityMesher == null) + { + qualityMesher = new QualityMesher(this, new Configuration()); + } + + // Enforce angle and area constraints. + qualityMesher.Apply(quality, delaunay); + } + + /// + /// Renumber vertex and triangle id's. + /// + public void Renumber() + { + this.Renumber(NodeNumbering.Linear); + } + + /// + /// Renumber vertex and triangle id's. + /// + public void Renumber(NodeNumbering num) + { + // Don't need to do anything if the nodes are already numbered. + if (num == this.numbering) + { + return; + } + + int id; + + if (num == NodeNumbering.Linear) + { + id = 0; + foreach (var node in this.vertices.Values) + { + node.id = id++; + } + } + else if (num == NodeNumbering.CuthillMcKee) + { + var rcm = new CuthillMcKee(); + var iperm = rcm.Renumber(this); + + // Permute the node indices. + foreach (var node in this.vertices.Values) + { + node.id = iperm[node.id]; + } + } + + // Remember the current numbering. + numbering = num; + + // Triangles will always be numbered from 0 to n-1 + id = 0; + foreach (var item in this.triangles) + { + item.id = id++; + } + } + + #region Misc + + /// + /// Set QualityMesher for mesh refinement. + /// + /// + internal void SetQualityMesher(QualityMesher qmesher) + { + qualityMesher = qmesher; + } + + internal void CopyTo(Mesh target) + { + target.vertices = this.vertices; + target.triangles = this.triangles; + target.subsegs = this.subsegs; + + target.holes = this.holes; + target.regions = this.regions; + + target.hash_vtx = this.hash_vtx; + target.hash_seg = this.hash_seg; + target.hash_tri = this.hash_tri; + + target.numbering = this.numbering; + target.hullsize = this.hullsize; + } + + /// + /// Reset all the mesh data. This method will also wipe + /// out all mesh data. + /// + private void ResetData() + { + vertices.Clear(); + triangles.Restart(); + subsegs.Clear(); + + holes.Clear(); + regions.Clear(); + + this.hash_vtx = 0; + this.hash_seg = 0; + this.hash_tri = 0; + + flipstack.Clear(); + + hullsize = 0; + + Reset(); + + locator.Reset(); + } + + /// + /// Reset the mesh triangulation state. + /// + private void Reset() + { + numbering = NodeNumbering.None; + + //undeads = 0; // No eliminated input vertices yet. + checksegments = false; // There are no segments in the triangulation yet. + checkquality = false; // The quality triangulation stage has not begun. + + Statistic.InCircleCount = 0; + Statistic.CounterClockwiseCount = 0; + Statistic.InCircleAdaptCount = 0; + Statistic.CounterClockwiseAdaptCount = 0; + Statistic.Orient3dCount = 0; + Statistic.HyperbolaCount = 0; + Statistic.CircleTopCount = 0; + Statistic.CircumcenterCount = 0; + } + + /// + /// Read the vertices from memory. + /// + /// The input data. + private void TransferNodes(IList points) + { + invertices = points.Count; + mesh_dim = 2; + bounds = new Rectangle(); + + if (invertices < 3) + { + logger.Error("Input must have at least three input vertices.", "Mesh.TransferNodes()"); + throw new Exception("Input must have at least three input vertices."); + } + + var v = points[0]; + +#if USE_ATTRIBS + // Check attributes. + this.nextras = v.attributes == null ? 0 : v.attributes.Length; +#endif + + // Simple heuristic to check if ids are already set. We assume that if the + // first two vertex ids are distinct, then all input vertices have pairwise + // distinct ids. + bool userId = (v.id != points[1].id); + + foreach (var p in points) + { + if (userId) + { + p.hash = p.id; + + // Make sure the hash counter gets updated. + hash_vtx = Math.Max(p.hash + 1, hash_vtx); + } + else + { + p.hash = p.id = hash_vtx++; + } + + vertices.Add(p.hash, p); + bounds.Expand(p); + } + } + + /// + /// Construct a mapping from vertices to triangles to improve the speed of + /// point location for segment insertion. + /// + /// + /// Traverses all the triangles, and provides each corner of each triangle + /// with a pointer to that triangle. Of course, pointers will be overwritten + /// by other pointers because (almost) each vertex is a corner of several + /// triangles, but in the end every vertex will point to some triangle + /// that contains it. + /// + internal void MakeVertexMap() + { + Otri tri = default(Otri); + Vertex triorg; + + foreach (var t in this.triangles) + { + tri.tri = t; + // Check all three vertices of the triangle. + for (tri.orient = 0; tri.orient < 3; tri.orient++) + { + triorg = tri.Org(); + triorg.tri = tri; + } + } + } + + #endregion + + #region Factory + + /// + /// Create a new triangle with orientation zero. + /// + /// Reference to the new triangle. + internal void MakeTriangle(ref Otri newotri) + { + Triangle tri = triangles.Get(); + + //tri.id = tri.hash; + + tri.subsegs[0].seg = dummysub; + tri.subsegs[1].seg = dummysub; + tri.subsegs[2].seg = dummysub; + + tri.neighbors[0].tri = dummytri; + tri.neighbors[1].tri = dummytri; + tri.neighbors[2].tri = dummytri; + + newotri.tri = tri; + newotri.orient = 0; + } + + /// + /// Create a new subsegment with orientation zero. + /// + /// Reference to the new subseg. + internal void MakeSegment(ref Osub newsubseg) + { + var seg = new SubSegment(); + + seg.hash = this.hash_seg++; + + seg.subsegs[0].seg = dummysub; + seg.subsegs[1].seg = dummysub; + + seg.triangles[0].tri = dummytri; + seg.triangles[1].tri = dummytri; + + newsubseg.seg = seg; + newsubseg.orient = 0; + + subsegs.Add(seg.hash, seg); + } + + #endregion + + #region Manipulation + + /// + /// Insert a vertex into a Delaunay triangulation, performing flips as necessary + /// to maintain the Delaunay property. + /// + /// The point to be inserted. + /// The triangle to start the search. + /// Segment to split. + /// Check for creation of encroached subsegments. + /// Check for creation of bad quality triangles. + /// If a duplicate vertex or violated segment does not prevent the + /// vertex from being inserted, the return value will be ENCROACHINGVERTEX if + /// the vertex encroaches upon a subsegment (and checking is enabled), or + /// SUCCESSFULVERTEX otherwise. In either case, 'searchtri' is set to a handle + /// whose origin is the newly inserted vertex. + /// + /// The point 'newvertex' is located. If 'searchtri.triangle' is not NULL, + /// the search for the containing triangle begins from 'searchtri'. If + /// 'searchtri.triangle' is NULL, a full point location procedure is called. + /// If 'insertvertex' is found inside a triangle, the triangle is split into + /// three; if 'insertvertex' lies on an edge, the edge is split in two, + /// thereby splitting the two adjacent triangles into four. Edge flips are + /// used to restore the Delaunay property. If 'insertvertex' lies on an + /// existing vertex, no action is taken, and the value DUPLICATEVERTEX is + /// returned. On return, 'searchtri' is set to a handle whose origin is the + /// existing vertex. + /// + /// InsertVertex() does not use flip() for reasons of speed; some + /// information can be reused from edge flip to edge flip, like the + /// locations of subsegments. + /// + /// Param 'splitseg': Normally, the parameter 'splitseg' is set to NULL, + /// implying that no subsegment should be split. In this case, if 'insertvertex' + /// is found to lie on a segment, no action is taken, and the value VIOLATINGVERTEX + /// is returned. On return, 'searchtri' is set to a handle whose primary edge is the + /// violated subsegment. + /// If the calling routine wishes to split a subsegment by inserting a vertex in it, + /// the parameter 'splitseg' should be that subsegment. In this case, 'searchtri' + /// MUST be the triangle handle reached by pivoting from that subsegment; no point + /// location is done. + /// + /// Param 'segmentflaws': Flags that indicate whether or not there should + /// be checks for the creation of encroached subsegments. If a newly inserted + /// vertex encroaches upon subsegments, these subsegments are added to the list + /// of subsegments to be split if 'segmentflaws' is set. + /// + /// Param 'triflaws': Flags that indicate whether or not there should be + /// checks for the creation of bad quality triangles. If bad triangles are + /// created, these are added to the queue if 'triflaws' is set. + /// + internal InsertVertexResult InsertVertex(Vertex newvertex, ref Otri searchtri, + ref Osub splitseg, bool segmentflaws, bool triflaws) + { + Otri horiz = default(Otri); + Otri top = default(Otri); + Otri botleft = default(Otri), botright = default(Otri); + Otri topleft = default(Otri), topright = default(Otri); + Otri newbotleft = default(Otri), newbotright = default(Otri); + Otri newtopright = default(Otri); + Otri botlcasing = default(Otri), botrcasing = default(Otri); + Otri toplcasing = default(Otri), toprcasing = default(Otri); + Otri testtri = default(Otri); + Osub botlsubseg = default(Osub), botrsubseg = default(Osub); + Osub toplsubseg = default(Osub), toprsubseg = default(Osub); + Osub brokensubseg = default(Osub); + Osub checksubseg = default(Osub); + Osub rightsubseg = default(Osub); + Osub newsubseg = default(Osub); + BadSubseg encroached; + //FlipStacker newflip; + Vertex first; + Vertex leftvertex, rightvertex, botvertex, topvertex, farvertex; + Vertex segmentorg, segmentdest; + int region; + double area; + InsertVertexResult success; + LocateResult intersect; + bool doflip; + bool mirrorflag; + bool enq; + + if (splitseg.seg == null) + { + // Find the location of the vertex to be inserted. Check if a good + // starting triangle has already been provided by the caller. + if (searchtri.tri.id == DUMMY) + { + // Find a boundary triangle. + horiz.tri = dummytri; + horiz.orient = 0; + horiz.Sym(); + + // Search for a triangle containing 'newvertex'. + intersect = locator.Locate(newvertex, ref horiz); + } + else + { + // Start searching from the triangle provided by the caller. + searchtri.Copy(ref horiz); + intersect = locator.PreciseLocate(newvertex, ref horiz, true); + } + } + else + { + // The calling routine provides the subsegment in which + // the vertex is inserted. + searchtri.Copy(ref horiz); + intersect = LocateResult.OnEdge; + } + + if (intersect == LocateResult.OnVertex) + { + // There's already a vertex there. Return in 'searchtri' a triangle + // whose origin is the existing vertex. + horiz.Copy(ref searchtri); + locator.Update(ref horiz); + return InsertVertexResult.Duplicate; + } + if ((intersect == LocateResult.OnEdge) || (intersect == LocateResult.Outside)) + { + // The vertex falls on an edge or boundary. + if (checksegments && (splitseg.seg == null)) + { + // Check whether the vertex falls on a subsegment. + horiz.Pivot(ref brokensubseg); + if (brokensubseg.seg.hash != DUMMY) + { + // The vertex falls on a subsegment, and hence will not be inserted. + if (segmentflaws) + { + enq = behavior.NoBisect != 2; + if (enq && (behavior.NoBisect == 1)) + { + // This subsegment may be split only if it is an + // internal boundary. + horiz.Sym(ref testtri); + enq = testtri.tri.id != DUMMY; + } + if (enq) + { + // Add the subsegment to the list of encroached subsegments. + encroached = new BadSubseg(); + encroached.subseg = brokensubseg; + encroached.org = brokensubseg.Org(); + encroached.dest = brokensubseg.Dest(); + + qualityMesher.AddBadSubseg(encroached); + } + } + // Return a handle whose primary edge contains the vertex, + // which has not been inserted. + horiz.Copy(ref searchtri); + locator.Update(ref horiz); + return InsertVertexResult.Violating; + } + } + + // Insert the vertex on an edge, dividing one triangle into two (if + // the edge lies on a boundary) or two triangles into four. + horiz.Lprev(ref botright); + botright.Sym(ref botrcasing); + horiz.Sym(ref topright); + // Is there a second triangle? (Or does this edge lie on a boundary?) + mirrorflag = topright.tri.id != DUMMY; + if (mirrorflag) + { + topright.Lnext(); + topright.Sym(ref toprcasing); + MakeTriangle(ref newtopright); + } + else + { + // Splitting a boundary edge increases the number of boundary edges. + hullsize++; + } + MakeTriangle(ref newbotright); + + // Set the vertices of changed and new triangles. + rightvertex = horiz.Org(); + leftvertex = horiz.Dest(); + botvertex = horiz.Apex(); + newbotright.SetOrg(botvertex); + newbotright.SetDest(rightvertex); + newbotright.SetApex(newvertex); + horiz.SetOrg(newvertex); + + // Set the region of a new triangle. + newbotright.tri.label = botright.tri.label; + + if (behavior.VarArea) + { + // Set the area constraint of a new triangle. + newbotright.tri.area = botright.tri.area; + } + + if (mirrorflag) + { + topvertex = topright.Dest(); + newtopright.SetOrg(rightvertex); + newtopright.SetDest(topvertex); + newtopright.SetApex(newvertex); + topright.SetOrg(newvertex); + + // Set the region of another new triangle. + newtopright.tri.label = topright.tri.label; + + if (behavior.VarArea) + { + // Set the area constraint of another new triangle. + newtopright.tri.area = topright.tri.area; + } + } + + // There may be subsegments that need to be bonded + // to the new triangle(s). + if (checksegments) + { + botright.Pivot(ref botrsubseg); + + if (botrsubseg.seg.hash != DUMMY) + { + botright.SegDissolve(dummysub); + newbotright.SegBond(ref botrsubseg); + } + + if (mirrorflag) + { + topright.Pivot(ref toprsubseg); + if (toprsubseg.seg.hash != DUMMY) + { + topright.SegDissolve(dummysub); + newtopright.SegBond(ref toprsubseg); + } + } + } + + // Bond the new triangle(s) to the surrounding triangles. + newbotright.Bond(ref botrcasing); + newbotright.Lprev(); + newbotright.Bond(ref botright); + newbotright.Lprev(); + + if (mirrorflag) + { + newtopright.Bond(ref toprcasing); + newtopright.Lnext(); + newtopright.Bond(ref topright); + newtopright.Lnext(); + newtopright.Bond(ref newbotright); + } + + if (splitseg.seg != null) + { + // Split the subsegment into two. + splitseg.SetDest(newvertex); + segmentorg = splitseg.SegOrg(); + segmentdest = splitseg.SegDest(); + splitseg.Sym(); + splitseg.Pivot(ref rightsubseg); + InsertSubseg(ref newbotright, splitseg.seg.boundary); + newbotright.Pivot(ref newsubseg); + newsubseg.SetSegOrg(segmentorg); + newsubseg.SetSegDest(segmentdest); + splitseg.Bond(ref newsubseg); + newsubseg.Sym(); + newsubseg.Bond(ref rightsubseg); + splitseg.Sym(); + + // Transfer the subsegment's boundary marker to the vertex if required. + if (newvertex.label == 0) + { + newvertex.label = splitseg.seg.boundary; + } + } + + if (checkquality) + { + flipstack.Clear(); + + flipstack.Push(default(Otri)); // Dummy flip (see UndoVertex) + flipstack.Push(horiz); + } + + // Position 'horiz' on the first edge to check for + // the Delaunay property. + horiz.Lnext(); + } + else + { + // Insert the vertex in a triangle, splitting it into three. + horiz.Lnext(ref botleft); + horiz.Lprev(ref botright); + botleft.Sym(ref botlcasing); + botright.Sym(ref botrcasing); + MakeTriangle(ref newbotleft); + MakeTriangle(ref newbotright); + + // Set the vertices of changed and new triangles. + rightvertex = horiz.Org(); + leftvertex = horiz.Dest(); + botvertex = horiz.Apex(); + newbotleft.SetOrg(leftvertex); + newbotleft.SetDest(botvertex); + newbotleft.SetApex(newvertex); + newbotright.SetOrg(botvertex); + newbotright.SetDest(rightvertex); + newbotright.SetApex(newvertex); + horiz.SetApex(newvertex); + + // Set the region of the new triangles. + newbotleft.tri.label = horiz.tri.label; + newbotright.tri.label = horiz.tri.label; + + if (behavior.VarArea) + { + // Set the area constraint of the new triangles. + area = horiz.tri.area; + newbotleft.tri.area = area; + newbotright.tri.area = area; + } + + // There may be subsegments that need to be bonded + // to the new triangles. + if (checksegments) + { + botleft.Pivot(ref botlsubseg); + if (botlsubseg.seg.hash != DUMMY) + { + botleft.SegDissolve(dummysub); + newbotleft.SegBond(ref botlsubseg); + } + botright.Pivot(ref botrsubseg); + if (botrsubseg.seg.hash != DUMMY) + { + botright.SegDissolve(dummysub); + newbotright.SegBond(ref botrsubseg); + } + } + + // Bond the new triangles to the surrounding triangles. + newbotleft.Bond(ref botlcasing); + newbotright.Bond(ref botrcasing); + newbotleft.Lnext(); + newbotright.Lprev(); + newbotleft.Bond(ref newbotright); + newbotleft.Lnext(); + botleft.Bond(ref newbotleft); + newbotright.Lprev(); + botright.Bond(ref newbotright); + + if (checkquality) + { + flipstack.Clear(); + flipstack.Push(horiz); + } + } + + // The insertion is successful by default, unless an encroached + // subsegment is found. + success = InsertVertexResult.Successful; + + if (newvertex.tri.tri != null) + { + // Store the coordinates of the triangle that contains newvertex. + newvertex.tri.SetOrg(rightvertex); + newvertex.tri.SetDest(leftvertex); + newvertex.tri.SetApex(botvertex); + } + + // Circle around the newly inserted vertex, checking each edge opposite it + // for the Delaunay property. Non-Delaunay edges are flipped. 'horiz' is + // always the edge being checked. 'first' marks where to stop circling. + first = horiz.Org(); + rightvertex = first; + leftvertex = horiz.Dest(); + // Circle until finished. + while (true) + { + // By default, the edge will be flipped. + doflip = true; + + if (checksegments) + { + // Check for a subsegment, which cannot be flipped. + horiz.Pivot(ref checksubseg); + if (checksubseg.seg.hash != DUMMY) + { + // The edge is a subsegment and cannot be flipped. + doflip = false; + + if (segmentflaws) + { + // Does the new vertex encroach upon this subsegment? + if (qualityMesher.CheckSeg4Encroach(ref checksubseg) > 0) + { + success = InsertVertexResult.Encroaching; + } + } + } + } + + if (doflip) + { + // Check if the edge is a boundary edge. + horiz.Sym(ref top); + if (top.tri.id == DUMMY) + { + // The edge is a boundary edge and cannot be flipped. + doflip = false; + } + else + { + // Find the vertex on the other side of the edge. + farvertex = top.Apex(); + // In the incremental Delaunay triangulation algorithm, any of + // 'leftvertex', 'rightvertex', and 'farvertex' could be vertices + // of the triangular bounding box. These vertices must be + // treated as if they are infinitely distant, even though their + // "coordinates" are not. + if ((leftvertex == infvertex1) || (leftvertex == infvertex2) || + (leftvertex == infvertex3)) + { + // 'leftvertex' is infinitely distant. Check the convexity of + // the boundary of the triangulation. 'farvertex' might be + // infinite as well, but trust me, this same condition should + // be applied. + doflip = predicates.CounterClockwise(newvertex, rightvertex, farvertex) > 0.0; + } + else if ((rightvertex == infvertex1) || + (rightvertex == infvertex2) || + (rightvertex == infvertex3)) + { + // 'rightvertex' is infinitely distant. Check the convexity of + // the boundary of the triangulation. 'farvertex' might be + // infinite as well, but trust me, this same condition should + // be applied. + doflip = predicates.CounterClockwise(farvertex, leftvertex, newvertex) > 0.0; + } + else if ((farvertex == infvertex1) || + (farvertex == infvertex2) || + (farvertex == infvertex3)) + { + // 'farvertex' is infinitely distant and cannot be inside + // the circumcircle of the triangle 'horiz'. + doflip = false; + } + else + { + // Test whether the edge is locally Delaunay. + doflip = predicates.InCircle(leftvertex, newvertex, rightvertex, farvertex) > 0.0; + } + if (doflip) + { + // We made it! Flip the edge 'horiz' by rotating its containing + // quadrilateral (the two triangles adjacent to 'horiz'). + // Identify the casing of the quadrilateral. + top.Lprev(ref topleft); + topleft.Sym(ref toplcasing); + top.Lnext(ref topright); + topright.Sym(ref toprcasing); + horiz.Lnext(ref botleft); + botleft.Sym(ref botlcasing); + horiz.Lprev(ref botright); + botright.Sym(ref botrcasing); + // Rotate the quadrilateral one-quarter turn counterclockwise. + topleft.Bond(ref botlcasing); + botleft.Bond(ref botrcasing); + botright.Bond(ref toprcasing); + topright.Bond(ref toplcasing); + if (checksegments) + { + // Check for subsegments and rebond them to the quadrilateral. + topleft.Pivot(ref toplsubseg); + botleft.Pivot(ref botlsubseg); + botright.Pivot(ref botrsubseg); + topright.Pivot(ref toprsubseg); + if (toplsubseg.seg.hash == DUMMY) + { + topright.SegDissolve(dummysub); + } + else + { + topright.SegBond(ref toplsubseg); + } + if (botlsubseg.seg.hash == DUMMY) + { + topleft.SegDissolve(dummysub); + } + else + { + topleft.SegBond(ref botlsubseg); + } + if (botrsubseg.seg.hash == DUMMY) + { + botleft.SegDissolve(dummysub); + } + else + { + botleft.SegBond(ref botrsubseg); + } + if (toprsubseg.seg.hash == DUMMY) + { + botright.SegDissolve(dummysub); + } + else + { + botright.SegBond(ref toprsubseg); + } + } + // New vertex assignments for the rotated quadrilateral. + horiz.SetOrg(farvertex); + horiz.SetDest(newvertex); + horiz.SetApex(rightvertex); + top.SetOrg(newvertex); + top.SetDest(farvertex); + top.SetApex(leftvertex); + + // Assign region. + // TODO: check region ok (no Math.Min necessary) + region = Math.Min(top.tri.label, horiz.tri.label); + top.tri.label = region; + horiz.tri.label = region; + + if (behavior.VarArea) + { + if ((top.tri.area <= 0.0) || (horiz.tri.area <= 0.0)) + { + area = -1.0; + } + else + { + // Take the average of the two triangles' area constraints. + // This prevents small area constraints from migrating a + // long, long way from their original location due to flips. + area = 0.5 * (top.tri.area + horiz.tri.area); + } + + top.tri.area = area; + horiz.tri.area = area; + } + + if (checkquality) + { + flipstack.Push(horiz); + } + + // On the next iterations, consider the two edges that were exposed (this + // is, are now visible to the newly inserted vertex) by the edge flip. + horiz.Lprev(); + leftvertex = farvertex; + } + } + } + if (!doflip) + { + // The handle 'horiz' is accepted as locally Delaunay. + if (triflaws) + { + // Check the triangle 'horiz' for quality. + qualityMesher.TestTriangle(ref horiz); + } + + // Look for the next edge around the newly inserted vertex. + horiz.Lnext(); + horiz.Sym(ref testtri); + // Check for finishing a complete revolution about the new vertex, or + // falling outside of the triangulation. The latter will happen when + // a vertex is inserted at a boundary. + if ((leftvertex == first) || (testtri.tri.id == DUMMY)) + { + // We're done. Return a triangle whose origin is the new vertex. + horiz.Lnext(ref searchtri); + + Otri recenttri = default(Otri); + horiz.Lnext(ref recenttri); + locator.Update(ref recenttri); + + return success; + } + // Finish finding the next edge around the newly inserted vertex. + testtri.Lnext(ref horiz); + rightvertex = leftvertex; + leftvertex = horiz.Dest(); + } + } + } + + /// + /// Create a new subsegment and inserts it between two triangles. Its + /// vertices are properly initialized. + /// + /// The new subsegment is inserted at the edge + /// described by this handle. + /// The marker 'subsegmark' is applied to the + /// subsegment and, if appropriate, its vertices. + internal void InsertSubseg(ref Otri tri, int subsegmark) + { + Otri oppotri = default(Otri); + Osub newsubseg = default(Osub); + Vertex triorg, tridest; + + triorg = tri.Org(); + tridest = tri.Dest(); + // Mark vertices if possible. + if (triorg.label == 0) + { + triorg.label = subsegmark; + } + if (tridest.label == 0) + { + tridest.label = subsegmark; + } + // Check if there's already a subsegment here. + tri.Pivot(ref newsubseg); + if (newsubseg.seg.hash == DUMMY) + { + // Make new subsegment and initialize its vertices. + MakeSegment(ref newsubseg); + newsubseg.SetOrg(tridest); + newsubseg.SetDest(triorg); + newsubseg.SetSegOrg(tridest); + newsubseg.SetSegDest(triorg); + // Bond new subsegment to the two triangles it is sandwiched between. + // Note that the facing triangle 'oppotri' might be equal to 'dummytri' + // (outer space), but the new subsegment is bonded to it all the same. + tri.SegBond(ref newsubseg); + tri.Sym(ref oppotri); + newsubseg.Sym(); + oppotri.SegBond(ref newsubseg); + newsubseg.seg.boundary = subsegmark; + } + else if (newsubseg.seg.boundary == 0) + { + newsubseg.seg.boundary = subsegmark; + } + } + + /// + /// Transform two triangles to two different triangles by flipping an edge + /// counterclockwise within a quadrilateral. + /// + /// Handle to the edge that will be flipped. + /// Imagine the original triangles, abc and bad, oriented so that the + /// shared edge ab lies in a horizontal plane, with the vertex b on the left + /// and the vertex a on the right. The vertex c lies below the edge, and + /// the vertex d lies above the edge. The 'flipedge' handle holds the edge + /// ab of triangle abc, and is directed left, from vertex a to vertex b. + /// + /// The triangles abc and bad are deleted and replaced by the triangles cdb + /// and dca. The triangles that represent abc and bad are NOT deallocated; + /// they are reused for dca and cdb, respectively. Hence, any handles that + /// may have held the original triangles are still valid, although not + /// directed as they were before. + /// + /// Upon completion of this routine, the 'flipedge' handle holds the edge + /// dc of triangle dca, and is directed down, from vertex d to vertex c. + /// (Hence, the two triangles have rotated counterclockwise.) + /// + /// WARNING: This transformation is geometrically valid only if the + /// quadrilateral adbc is convex. Furthermore, this transformation is + /// valid only if there is not a subsegment between the triangles abc and + /// bad. This routine does not check either of these preconditions, and + /// it is the responsibility of the calling routine to ensure that they are + /// met. If they are not, the streets shall be filled with wailing and + /// gnashing of teeth. + /// + /// Terminology + /// + /// A "local transformation" replaces a small set of triangles with another + /// set of triangles. This may or may not involve inserting or deleting a + /// vertex. + /// + /// The term "casing" is used to describe the set of triangles that are + /// attached to the triangles being transformed, but are not transformed + /// themselves. Think of the casing as a fixed hollow structure inside + /// which all the action happens. A "casing" is only defined relative to + /// a single transformation; each occurrence of a transformation will + /// involve a different casing. + /// + internal void Flip(ref Otri flipedge) + { + Otri botleft = default(Otri), botright = default(Otri); + Otri topleft = default(Otri), topright = default(Otri); + Otri top = default(Otri); + Otri botlcasing = default(Otri), botrcasing = default(Otri); + Otri toplcasing = default(Otri), toprcasing = default(Otri); + Osub botlsubseg = default(Osub), botrsubseg = default(Osub); + Osub toplsubseg = default(Osub), toprsubseg = default(Osub); + Vertex leftvertex, rightvertex, botvertex; + Vertex farvertex; + + // Identify the vertices of the quadrilateral. + rightvertex = flipedge.Org(); + leftvertex = flipedge.Dest(); + botvertex = flipedge.Apex(); + flipedge.Sym(ref top); + + // SELF CHECK + + //if (top.triangle.id == DUMMY) + //{ + // logger.Error("Attempt to flip on boundary.", "Mesh.Flip()"); + // flipedge.LnextSelf(); + // return; + //} + + //if (checksegments) + //{ + // flipedge.SegPivot(ref toplsubseg); + // if (toplsubseg.ss != Segment.Empty) + // { + // logger.Error("Attempt to flip a segment.", "Mesh.Flip()"); + // flipedge.LnextSelf(); + // return; + // } + //} + + farvertex = top.Apex(); + + // Identify the casing of the quadrilateral. + top.Lprev(ref topleft); + topleft.Sym(ref toplcasing); + top.Lnext(ref topright); + topright.Sym(ref toprcasing); + flipedge.Lnext(ref botleft); + botleft.Sym(ref botlcasing); + flipedge.Lprev(ref botright); + botright.Sym(ref botrcasing); + // Rotate the quadrilateral one-quarter turn counterclockwise. + topleft.Bond(ref botlcasing); + botleft.Bond(ref botrcasing); + botright.Bond(ref toprcasing); + topright.Bond(ref toplcasing); + + if (checksegments) + { + // Check for subsegments and rebond them to the quadrilateral. + topleft.Pivot(ref toplsubseg); + botleft.Pivot(ref botlsubseg); + botright.Pivot(ref botrsubseg); + topright.Pivot(ref toprsubseg); + + if (toplsubseg.seg.hash == DUMMY) + { + topright.SegDissolve(dummysub); + } + else + { + topright.SegBond(ref toplsubseg); + } + + if (botlsubseg.seg.hash == DUMMY) + { + topleft.SegDissolve(dummysub); + } + else + { + topleft.SegBond(ref botlsubseg); + } + + if (botrsubseg.seg.hash == DUMMY) + { + botleft.SegDissolve(dummysub); + } + else + { + botleft.SegBond(ref botrsubseg); + } + + if (toprsubseg.seg.hash == DUMMY) + { + botright.SegDissolve(dummysub); + } + else + { + botright.SegBond(ref toprsubseg); + } + } + + // New vertex assignments for the rotated quadrilateral. + flipedge.SetOrg(farvertex); + flipedge.SetDest(botvertex); + flipedge.SetApex(rightvertex); + top.SetOrg(botvertex); + top.SetDest(farvertex); + top.SetApex(leftvertex); + } + + /// + /// Transform two triangles to two different triangles by flipping an edge + /// clockwise within a quadrilateral. Reverses the flip() operation so that + /// the data structures representing the triangles are back where they were + /// before the flip(). + /// + /// + /// + /// See above Flip() remarks for more information. + /// + /// Upon completion of this routine, the 'flipedge' handle holds the edge + /// cd of triangle cdb, and is directed up, from vertex c to vertex d. + /// (Hence, the two triangles have rotated clockwise.) + /// + internal void Unflip(ref Otri flipedge) + { + Otri botleft = default(Otri), botright = default(Otri); + Otri topleft = default(Otri), topright = default(Otri); + Otri top = default(Otri); + Otri botlcasing = default(Otri), botrcasing = default(Otri); + Otri toplcasing = default(Otri), toprcasing = default(Otri); + Osub botlsubseg = default(Osub), botrsubseg = default(Osub); + Osub toplsubseg = default(Osub), toprsubseg = default(Osub); + Vertex leftvertex, rightvertex, botvertex; + Vertex farvertex; + + // Identify the vertices of the quadrilateral. + rightvertex = flipedge.Org(); + leftvertex = flipedge.Dest(); + botvertex = flipedge.Apex(); + flipedge.Sym(ref top); + + farvertex = top.Apex(); + + // Identify the casing of the quadrilateral. + top.Lprev(ref topleft); + topleft.Sym(ref toplcasing); + top.Lnext(ref topright); + topright.Sym(ref toprcasing); + flipedge.Lnext(ref botleft); + botleft.Sym(ref botlcasing); + flipedge.Lprev(ref botright); + botright.Sym(ref botrcasing); + // Rotate the quadrilateral one-quarter turn clockwise. + topleft.Bond(ref toprcasing); + botleft.Bond(ref toplcasing); + botright.Bond(ref botlcasing); + topright.Bond(ref botrcasing); + + if (checksegments) + { + // Check for subsegments and rebond them to the quadrilateral. + topleft.Pivot(ref toplsubseg); + botleft.Pivot(ref botlsubseg); + botright.Pivot(ref botrsubseg); + topright.Pivot(ref toprsubseg); + if (toplsubseg.seg.hash == DUMMY) + { + botleft.SegDissolve(dummysub); + } + else + { + botleft.SegBond(ref toplsubseg); + } + if (botlsubseg.seg.hash == DUMMY) + { + botright.SegDissolve(dummysub); + } + else + { + botright.SegBond(ref botlsubseg); + } + if (botrsubseg.seg.hash == DUMMY) + { + topright.SegDissolve(dummysub); + } + else + { + topright.SegBond(ref botrsubseg); + } + if (toprsubseg.seg.hash == DUMMY) + { + topleft.SegDissolve(dummysub); + } + else + { + topleft.SegBond(ref toprsubseg); + } + } + + // New vertex assignments for the rotated quadrilateral. + flipedge.SetOrg(botvertex); + flipedge.SetDest(farvertex); + flipedge.SetApex(leftvertex); + top.SetOrg(farvertex); + top.SetDest(botvertex); + top.SetApex(rightvertex); + } + + /// + /// Find the Delaunay triangulation of a polygon that has a certain "nice" shape. + /// This includes the polygons that result from deletion of a vertex or insertion + /// of a segment. + /// + /// The primary edge of the first triangle. + /// The primary edge of the last triangle. + /// The number of sides of the polygon, including its + /// base. + /// A flag, wether to perform the last flip. + /// A flag that determines whether the new triangles should + /// be tested for quality, and enqueued if they are bad. + /// + // This is a conceptually difficult routine. The starting assumption is + // that we have a polygon with n sides. n - 1 of these sides are currently + // represented as edges in the mesh. One side, called the "base", need not + // be. + // + // Inside the polygon is a structure I call a "fan", consisting of n - 1 + // triangles that share a common origin. For each of these triangles, the + // edge opposite the origin is one of the sides of the polygon. The + // primary edge of each triangle is the edge directed from the origin to + // the destination; note that this is not the same edge that is a side of + // the polygon. 'firstedge' is the primary edge of the first triangle. + // From there, the triangles follow in counterclockwise order about the + // polygon, until 'lastedge', the primary edge of the last triangle. + // 'firstedge' and 'lastedge' are probably connected to other triangles + // beyond the extremes of the fan, but their identity is not important, as + // long as the fan remains connected to them. + // + // Imagine the polygon oriented so that its base is at the bottom. This + // puts 'firstedge' on the far right, and 'lastedge' on the far left. + // The right vertex of the base is the destination of 'firstedge', and the + // left vertex of the base is the apex of 'lastedge'. + // + // The challenge now is to find the right sequence of edge flips to + // transform the fan into a Delaunay triangulation of the polygon. Each + // edge flip effectively removes one triangle from the fan, committing it + // to the polygon. The resulting polygon has one fewer edge. If 'doflip' + // is set, the final flip will be performed, resulting in a fan of one + // (useless?) triangle. If 'doflip' is not set, the final flip is not + // performed, resulting in a fan of two triangles, and an unfinished + // triangular polygon that is not yet filled out with a single triangle. + // On completion of the routine, 'lastedge' is the last remaining triangle, + // or the leftmost of the last two. + // + // Although the flips are performed in the order described above, the + // decisions about what flips to perform are made in precisely the reverse + // order. The recursive triangulatepolygon() procedure makes a decision, + // uses up to two recursive calls to triangulate the "subproblems" + // (polygons with fewer edges), and then performs an edge flip. + // + // The "decision" it makes is which vertex of the polygon should be + // connected to the base. This decision is made by testing every possible + // vertex. Once the best vertex is found, the two edges that connect this + // vertex to the base become the bases for two smaller polygons. These + // are triangulated recursively. Unfortunately, this approach can take + // O(n^2) time not only in the worst case, but in many common cases. It's + // rarely a big deal for vertex deletion, where n is rarely larger than + // ten, but it could be a big deal for segment insertion, especially if + // there's a lot of long segments that each cut many triangles. I ought to + // code a faster algorithm some day. + /// + private void TriangulatePolygon(Otri firstedge, Otri lastedge, + int edgecount, bool doflip, bool triflaws) + { + Otri testtri = default(Otri); + Otri besttri = default(Otri); + Otri tempedge = default(Otri); + Vertex leftbasevertex, rightbasevertex; + Vertex testvertex; + Vertex bestvertex; + + int bestnumber = 1; + + // Identify the base vertices. + leftbasevertex = lastedge.Apex(); + rightbasevertex = firstedge.Dest(); + + // Find the best vertex to connect the base to. + firstedge.Onext(ref besttri); + bestvertex = besttri.Dest(); + besttri.Copy(ref testtri); + + for (int i = 2; i <= edgecount - 2; i++) + { + testtri.Onext(); + testvertex = testtri.Dest(); + // Is this a better vertex? + if (predicates.InCircle(leftbasevertex, rightbasevertex, bestvertex, testvertex) > 0.0) + { + testtri.Copy(ref besttri); + bestvertex = testvertex; + bestnumber = i; + } + } + + if (bestnumber > 1) + { + // Recursively triangulate the smaller polygon on the right. + besttri.Oprev(ref tempedge); + TriangulatePolygon(firstedge, tempedge, bestnumber + 1, true, triflaws); + } + + if (bestnumber < edgecount - 2) + { + // Recursively triangulate the smaller polygon on the left. + besttri.Sym(ref tempedge); + TriangulatePolygon(besttri, lastedge, edgecount - bestnumber, true, triflaws); + // Find 'besttri' again; it may have been lost to edge flips. + tempedge.Sym(ref besttri); + } + + if (doflip) + { + // Do one final edge flip. + Flip(ref besttri); + if (triflaws) + { + // Check the quality of the newly committed triangle. + besttri.Sym(ref testtri); + qualityMesher.TestTriangle(ref testtri); + } + } + // Return the base triangle. + besttri.Copy(ref lastedge); + } + + /// + /// Delete a vertex from a Delaunay triangulation, ensuring that the + /// triangulation remains Delaunay. + /// + /// + /// The origin of 'deltri' is deleted. The union of the triangles + /// adjacent to this vertex is a polygon, for which the Delaunay triangulation + /// is found. Two triangles are removed from the mesh. + /// + /// Only interior vertices that do not lie on segments or boundaries + /// may be deleted. + /// + internal void DeleteVertex(ref Otri deltri) + { + Otri countingtri = default(Otri); + Otri firstedge = default(Otri), lastedge = default(Otri); + Otri deltriright = default(Otri); + Otri lefttri = default(Otri), righttri = default(Otri); + Otri leftcasing = default(Otri), rightcasing = default(Otri); + Osub leftsubseg = default(Osub), rightsubseg = default(Osub); + Vertex delvertex; + Vertex neworg; + int edgecount; + + delvertex = deltri.Org(); + + VertexDealloc(delvertex); + + // Count the degree of the vertex being deleted. + deltri.Onext(ref countingtri); + edgecount = 1; + while (!deltri.Equals(countingtri)) + { + edgecount++; + countingtri.Onext(); + } + + if (edgecount > 3) + { + // Triangulate the polygon defined by the union of all triangles + // adjacent to the vertex being deleted. Check the quality of + // the resulting triangles. + deltri.Onext(ref firstedge); + deltri.Oprev(ref lastedge); + TriangulatePolygon(firstedge, lastedge, edgecount, false, behavior.NoBisect == 0); + } + // Splice out two triangles. + deltri.Lprev(ref deltriright); + deltri.Dnext(ref lefttri); + lefttri.Sym(ref leftcasing); + deltriright.Oprev(ref righttri); + righttri.Sym(ref rightcasing); + deltri.Bond(ref leftcasing); + deltriright.Bond(ref rightcasing); + lefttri.Pivot(ref leftsubseg); + if (leftsubseg.seg.hash != DUMMY) + { + deltri.SegBond(ref leftsubseg); + } + righttri.Pivot(ref rightsubseg); + if (rightsubseg.seg.hash != DUMMY) + { + deltriright.SegBond(ref rightsubseg); + } + + // Set the new origin of 'deltri' and check its quality. + neworg = lefttri.Org(); + deltri.SetOrg(neworg); + if (behavior.NoBisect == 0) + { + qualityMesher.TestTriangle(ref deltri); + } + + // Delete the two spliced-out triangles. + TriangleDealloc(lefttri.tri); + TriangleDealloc(righttri.tri); + } + + /// + /// Undo the most recent vertex insertion. + /// + /// + /// Walks through the list of transformations (flips and a vertex insertion) + /// in the reverse of the order in which they were done, and undoes them. + /// The inserted vertex is removed from the triangulation and deallocated. + /// Two triangles (possibly just one) are also deallocated. + /// + internal void UndoVertex() + { + Otri fliptri; + + Otri botleft = default(Otri), botright = default(Otri), topright = default(Otri); + Otri botlcasing = default(Otri), botrcasing = default(Otri), toprcasing = default(Otri); + Otri gluetri = default(Otri); + Osub botlsubseg = default(Osub), botrsubseg = default(Osub), toprsubseg = default(Osub); + Vertex botvertex, rightvertex; + + // Walk through the list of transformations (flips and a vertex insertion) + // in the reverse of the order in which they were done, and undo them. + while (flipstack.Count > 0) + { + // Find a triangle involved in the last unreversed transformation. + fliptri = flipstack.Pop(); + + // We are reversing one of three transformations: a trisection of one + // triangle into three (by inserting a vertex in the triangle), a + // bisection of two triangles into four (by inserting a vertex in an + // edge), or an edge flip. + if (flipstack.Count == 0) + { + // Restore a triangle that was split into three triangles, + // so it is again one triangle. + fliptri.Dprev(ref botleft); + botleft.Lnext(); + fliptri.Onext(ref botright); + botright.Lprev(); + botleft.Sym(ref botlcasing); + botright.Sym(ref botrcasing); + botvertex = botleft.Dest(); + + fliptri.SetApex(botvertex); + fliptri.Lnext(); + fliptri.Bond(ref botlcasing); + botleft.Pivot(ref botlsubseg); + fliptri.SegBond(ref botlsubseg); + fliptri.Lnext(); + fliptri.Bond(ref botrcasing); + botright.Pivot(ref botrsubseg); + fliptri.SegBond(ref botrsubseg); + + // Delete the two spliced-out triangles. + TriangleDealloc(botleft.tri); + TriangleDealloc(botright.tri); + } + else if (flipstack.Peek().tri == null) // Dummy flip + { + // Restore two triangles that were split into four triangles, + // so they are again two triangles. + fliptri.Lprev(ref gluetri); + gluetri.Sym(ref botright); + botright.Lnext(); + botright.Sym(ref botrcasing); + rightvertex = botright.Dest(); + + fliptri.SetOrg(rightvertex); + gluetri.Bond(ref botrcasing); + botright.Pivot(ref botrsubseg); + gluetri.SegBond(ref botrsubseg); + + // Delete the spliced-out triangle. + TriangleDealloc(botright.tri); + + fliptri.Sym(ref gluetri); + if (gluetri.tri.id != DUMMY) + { + gluetri.Lnext(); + gluetri.Dnext(ref topright); + topright.Sym(ref toprcasing); + + gluetri.SetOrg(rightvertex); + gluetri.Bond(ref toprcasing); + topright.Pivot(ref toprsubseg); + gluetri.SegBond(ref toprsubseg); + + // Delete the spliced-out triangle. + TriangleDealloc(topright.tri); + } + + flipstack.Clear(); + } + else + { + // Undo an edge flip. + Unflip(ref fliptri); + } + } + } + + #endregion + + #region Dealloc + + /// + /// Deallocate space for a triangle, marking it dead. + /// + /// + internal void TriangleDealloc(Triangle dyingtriangle) + { + // Mark the triangle as dead. This makes it possible to detect dead + // triangles when traversing the list of all triangles. + Otri.Kill(dyingtriangle); + triangles.Release(dyingtriangle); + } + + /// + /// Deallocate space for a vertex, marking it dead. + /// + /// + internal void VertexDealloc(Vertex dyingvertex) + { + // Mark the vertex as dead. This makes it possible to detect dead + // vertices when traversing the list of all vertices. + dyingvertex.type = VertexType.DeadVertex; + vertices.Remove(dyingvertex.hash); + } + + /// + /// Deallocate space for a subsegment, marking it dead. + /// + /// + internal void SubsegDealloc(SubSegment dyingsubseg) + { + // Mark the subsegment as dead. This makes it possible to detect dead + // subsegments when traversing the list of all subsegments. + Osub.Kill(dyingsubseg); + subsegs.Remove(dyingsubseg.hash); + } + + #endregion + } +} diff --git a/Triangle.NET/Triangle/MeshValidator.cs b/src/Triangle/MeshValidator.cs similarity index 76% rename from Triangle.NET/Triangle/MeshValidator.cs rename to src/Triangle/MeshValidator.cs index d6e4982..cacec5e 100644 --- a/Triangle.NET/Triangle/MeshValidator.cs +++ b/src/Triangle/MeshValidator.cs @@ -1,215 +1,262 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet -{ - using System; - using TriangleNet.Topology; - using TriangleNet.Geometry; - - public static class MeshValidator - { - private static RobustPredicates predicates = RobustPredicates.Default; - - /// - /// Test the mesh for topological consistency. - /// - public static bool IsConsistent(Mesh mesh) - { - Otri tri = default(Otri); - Otri oppotri = default(Otri), oppooppotri = default(Otri); - Vertex org, dest, apex; - Vertex oppoorg, oppodest; - - var logger = Log.Instance; - - // Temporarily turn on exact arithmetic if it's off. - bool saveexact = Behavior.NoExact; - Behavior.NoExact = false; - - int horrors = 0; - - // Run through the list of triangles, checking each one. - foreach (var t in mesh.triangles) - { - tri.tri = t; - - // Check all three edges of the triangle. - for (tri.orient = 0; tri.orient < 3; tri.orient++) - { - org = tri.Org(); - dest = tri.Dest(); - if (tri.orient == 0) - { - // Only test for inversion once. - // Test if the triangle is flat or inverted. - apex = tri.Apex(); - if (predicates.CounterClockwise(org, dest, apex) <= 0.0) - { - if (Log.Verbose) - { - logger.Warning(String.Format("Triangle is flat or inverted (ID {0}).", t.id), - "MeshValidator.IsConsistent()"); - } - - horrors++; - } - } - - // Find the neighboring triangle on this edge. - tri.Sym(ref oppotri); - if (oppotri.tri.id != Mesh.DUMMY) - { - // Check that the triangle's neighbor knows it's a neighbor. - oppotri.Sym(ref oppooppotri); - if ((tri.tri != oppooppotri.tri) || (tri.orient != oppooppotri.orient)) - { - if (tri.tri == oppooppotri.tri && Log.Verbose) - { - logger.Warning("Asymmetric triangle-triangle bond: (Right triangle, wrong orientation)", - "MeshValidator.IsConsistent()"); - } - - horrors++; - } - // Check that both triangles agree on the identities - // of their shared vertices. - oppoorg = oppotri.Org(); - oppodest = oppotri.Dest(); - if ((org != oppodest) || (dest != oppoorg)) - { - if (Log.Verbose) - { - logger.Warning("Mismatched edge coordinates between two triangles.", - "MeshValidator.IsConsistent()"); - } - - horrors++; - } - } - } - } - - // Check for unconnected vertices - mesh.MakeVertexMap(); - foreach (var v in mesh.vertices.Values) - { - if (v.tri.tri == null && Log.Verbose) - { - logger.Warning("Vertex (ID " + v.id + ") not connected to mesh (duplicate input vertex?)", - "MeshValidator.IsConsistent()"); - } - } - - // Restore the status of exact arithmetic. - Behavior.NoExact = saveexact; - - return (horrors == 0); - } - - /// - /// Check if the mesh is (conforming) Delaunay. - /// - public static bool IsDelaunay(Mesh mesh) - { - return IsDelaunay(mesh, false); - } - - /// - /// Check if that the mesh is (constrained) Delaunay. - /// - public static bool IsConstrainedDelaunay(Mesh mesh) - { - return IsDelaunay(mesh, true); - } - - /// - /// Ensure that the mesh is (constrained) Delaunay. - /// - private static bool IsDelaunay(Mesh mesh, bool constrained) - { - Otri loop = default(Otri); - Otri oppotri = default(Otri); - Osub opposubseg = default(Osub); - Vertex org, dest, apex; - Vertex oppoapex; - - bool shouldbedelaunay; - - var logger = Log.Instance; - - // Temporarily turn on exact arithmetic if it's off. - bool saveexact = Behavior.NoExact; - Behavior.NoExact = false; - - int horrors = 0; - - var inf1 = mesh.infvertex1; - var inf2 = mesh.infvertex2; - var inf3 = mesh.infvertex3; - - // Run through the list of triangles, checking each one. - foreach (var tri in mesh.triangles) - { - loop.tri = tri; - - // Check all three edges of the triangle. - for (loop.orient = 0; loop.orient < 3; loop.orient++) - { - org = loop.Org(); - dest = loop.Dest(); - apex = loop.Apex(); - - loop.Sym(ref oppotri); - oppoapex = oppotri.Apex(); - - // Only test that the edge is locally Delaunay if there is an - // adjoining triangle whose pointer is larger (to ensure that - // each pair isn't tested twice). - shouldbedelaunay = (loop.tri.id < oppotri.tri.id) && - !Otri.IsDead(oppotri.tri) && (oppotri.tri.id != Mesh.DUMMY) && - (org != inf1) && (org != inf2) && (org != inf3) && - (dest != inf1) && (dest != inf2) && (dest != inf3) && - (apex != inf1) && (apex != inf2) && (apex != inf3) && - (oppoapex != inf1) && (oppoapex != inf2) && (oppoapex != inf3); - - if (constrained && mesh.checksegments && shouldbedelaunay) - { - // If a subsegment separates the triangles, then the edge is - // constrained, so no local Delaunay test should be done. - loop.Pivot(ref opposubseg); - - if (opposubseg.seg.hash != Mesh.DUMMY) - { - shouldbedelaunay = false; - } - } - - if (shouldbedelaunay) - { - if (predicates.NonRegular(org, dest, apex, oppoapex) > 0.0) - { - if (Log.Verbose) - { - logger.Warning(String.Format("Non-regular pair of triangles found (IDs {0}/{1}).", - loop.tri.id, oppotri.tri.id), "MeshValidator.IsDelaunay()"); - } - - horrors++; - } - } - } - - } - - // Restore the status of exact arithmetic. - Behavior.NoExact = saveexact; - - return (horrors == 0); - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + using TriangleNet.Tools; + using TriangleNet.Topology; + + public static class MeshValidator + { + private static RobustPredicates predicates = RobustPredicates.Default; + + /// + /// Test the mesh for topological consistency. + /// + /// The mesh. + /// True, if mesh is topologically consistent. + public static bool IsConsistent(Mesh mesh) + { + Otri tri = default(Otri); + Otri oppotri = default(Otri), oppooppotri = default(Otri); + Vertex org, dest, apex; + Vertex oppoorg, oppodest; + + var logger = Log.Instance; + + // Temporarily turn on exact arithmetic if it's off. + bool saveexact = Behavior.NoExact; + Behavior.NoExact = false; + + int horrors = 0; + + // Run through the list of triangles, checking each one. + foreach (var t in mesh.triangles) + { + tri.tri = t; + + // Check all three edges of the triangle. + for (tri.orient = 0; tri.orient < 3; tri.orient++) + { + org = tri.Org(); + dest = tri.Dest(); + if (tri.orient == 0) + { + // Only test for inversion once. + // Test if the triangle is flat or inverted. + apex = tri.Apex(); + if (predicates.CounterClockwise(org, dest, apex) <= 0.0) + { + if (Log.Verbose) + { + logger.Warning(string.Format("Triangle is flat or inverted (ID {0}).", t.id), + "MeshValidator.IsConsistent()"); + } + + horrors++; + } + } + + // Find the neighboring triangle on this edge. + tri.Sym(ref oppotri); + if (oppotri.tri.id != Mesh.DUMMY) + { + // Check that the triangle's neighbor knows it's a neighbor. + oppotri.Sym(ref oppooppotri); + if ((tri.tri != oppooppotri.tri) || (tri.orient != oppooppotri.orient)) + { + if (tri.tri == oppooppotri.tri && Log.Verbose) + { + logger.Warning("Asymmetric triangle-triangle bond: (Right triangle, wrong orientation)", + "MeshValidator.IsConsistent()"); + } + + horrors++; + } + // Check that both triangles agree on the identities + // of their shared vertices. + oppoorg = oppotri.Org(); + oppodest = oppotri.Dest(); + if ((org != oppodest) || (dest != oppoorg)) + { + if (Log.Verbose) + { + logger.Warning("Mismatched edge coordinates between two triangles.", + "MeshValidator.IsConsistent()"); + } + + horrors++; + } + } + } + } + + // Check for unconnected vertices + mesh.MakeVertexMap(); + foreach (var v in mesh.vertices.Values) + { + if (v.tri.tri == null && Log.Verbose) + { + logger.Warning("Vertex (ID " + v.id + ") not connected to mesh (duplicate input vertex?)", + "MeshValidator.IsConsistent()"); + } + } + + // Restore the status of exact arithmetic. + Behavior.NoExact = saveexact; + + return (horrors == 0); + } + + /// + /// Check whether the mesh is (conforming) Delaunay. + /// + /// The mesh. + /// True, if mesh is (conforming) Delaunay. + public static bool IsDelaunay(Mesh mesh) + { + return IsDelaunay(mesh, false); + } + + /// + /// Check whether the mesh is (constrained) Delaunay. + /// + /// The mesh. + /// True, if mesh is (constrained) Delaunay. + public static bool IsConstrainedDelaunay(Mesh mesh) + { + return IsDelaunay(mesh, true); + } + + /// + /// Ensure that the mesh is (constrained) Delaunay. + /// + private static bool IsDelaunay(Mesh mesh, bool constrained) + { + Otri loop = default(Otri); + Otri oppotri = default(Otri); + Osub opposubseg = default(Osub); + Vertex org, dest, apex; + Vertex oppoapex; + + bool shouldbedelaunay; + + var logger = Log.Instance; + + // Temporarily turn on exact arithmetic if it's off. + bool saveexact = Behavior.NoExact; + Behavior.NoExact = false; + + int horrors = 0; + + var inf1 = mesh.infvertex1; + var inf2 = mesh.infvertex2; + var inf3 = mesh.infvertex3; + + // Run through the list of triangles, checking each one. + foreach (var tri in mesh.triangles) + { + loop.tri = tri; + + // Check all three edges of the triangle. + for (loop.orient = 0; loop.orient < 3; loop.orient++) + { + org = loop.Org(); + dest = loop.Dest(); + apex = loop.Apex(); + + loop.Sym(ref oppotri); + oppoapex = oppotri.Apex(); + + // Only test that the edge is locally Delaunay if there is an + // adjoining triangle whose pointer is larger (to ensure that + // each pair isn't tested twice). + shouldbedelaunay = (loop.tri.id < oppotri.tri.id) && + !Otri.IsDead(oppotri.tri) && (oppotri.tri.id != Mesh.DUMMY) && + (org != inf1) && (org != inf2) && (org != inf3) && + (dest != inf1) && (dest != inf2) && (dest != inf3) && + (apex != inf1) && (apex != inf2) && (apex != inf3) && + (oppoapex != inf1) && (oppoapex != inf2) && (oppoapex != inf3); + + if (constrained && mesh.checksegments && shouldbedelaunay) + { + // If a subsegment separates the triangles, then the edge is + // constrained, so no local Delaunay test should be done. + loop.Pivot(ref opposubseg); + + if (opposubseg.seg.hash != Mesh.DUMMY) + { + shouldbedelaunay = false; + } + } + + if (shouldbedelaunay) + { + if (predicates.NonRegular(org, dest, apex, oppoapex) > 0.0) + { + if (Log.Verbose) + { + logger.Warning(string.Format("Non-regular pair of triangles found (IDs {0}/{1}).", + loop.tri.id, oppotri.tri.id), "MeshValidator.IsDelaunay()"); + } + + horrors++; + } + } + } + + } + + // Restore the status of exact arithmetic. + Behavior.NoExact = saveexact; + + return (horrors == 0); + } + + /// + /// Check whether the mesh has degenerate boundary triangles. + /// + /// The mesh. + /// Threshold for what angle is considered invalid (too small). + /// + public static IEnumerable GetDegenerateBoundaryTriangles(IMesh mesh, double threshold = 1e-8) + { + // We will compare against the squared cosine of the maximum angle. + threshold = Math.Sqrt(threshold); + + var data = new double[6]; + + foreach (var triangle in mesh.Triangles) + { + for (int i = 0; i < 3; i++) + { + var neighbor = triangle.GetNeighbor(i); + + // Triangle lies on mesh boundary. + if (neighbor == null) + { + Statistic.ComputeAngles(triangle, data); + + // The squared cosine of the maximum angle will be near 1.0 only + // if the maximum angle is near 180 degrees. + if (Math.Abs(1.0 - data[1]) < threshold) + { + yield return triangle; + } + + // Next triangle. + break; + } + } + } + } + } +} diff --git a/Triangle.NET/Triangle/Meshing/Algorithm/Dwyer.cs b/src/Triangle/Meshing/Algorithm/Dwyer.cs similarity index 95% rename from Triangle.NET/Triangle/Meshing/Algorithm/Dwyer.cs rename to src/Triangle/Meshing/Algorithm/Dwyer.cs index a4c834d..d161b22 100644 --- a/Triangle.NET/Triangle/Meshing/Algorithm/Dwyer.cs +++ b/src/Triangle/Meshing/Algorithm/Dwyer.cs @@ -1,694 +1,693 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Meshing.Algorithm -{ - using System; - using System.Collections.Generic; - using TriangleNet.Geometry; - using TriangleNet.Tools; - using TriangleNet.Topology; - - /// - /// Builds a delaunay triangulation using the divide-and-conquer algorithm. - /// - /// - /// The divide-and-conquer bounding box - /// - /// I originally implemented the divide-and-conquer and incremental Delaunay - /// triangulations using the edge-based data structure presented by Guibas - /// and Stolfi. Switching to a triangle-based data structure doubled the - /// speed. However, I had to think of a few extra tricks to maintain the - /// elegance of the original algorithms. - /// - /// The "bounding box" used by my variant of the divide-and-conquer - /// algorithm uses one triangle for each edge of the convex hull of the - /// triangulation. These bounding triangles all share a common apical - /// vertex, which is represented by NULL and which represents nothing. - /// The bounding triangles are linked in a circular fan about this NULL - /// vertex, and the edges on the convex hull of the triangulation appear - /// opposite the NULL vertex. You might find it easiest to imagine that - /// the NULL vertex is a point in 3D space behind the center of the - /// triangulation, and that the bounding triangles form a sort of cone. - /// - /// This bounding box makes it easy to represent degenerate cases. For - /// instance, the triangulation of two vertices is a single edge. This edge - /// is represented by two bounding box triangles, one on each "side" of the - /// edge. These triangles are also linked together in a fan about the NULL - /// vertex. - /// - /// The bounding box also makes it easy to traverse the convex hull, as the - /// divide-and-conquer algorithm needs to do. - /// - public class Dwyer : ITriangulator - { - // Random is not threadsafe, so don't make this static. - Random rand = new Random(DateTime.Now.Millisecond); - - IPredicates predicates; - - public bool UseDwyer = true; - - Vertex[] sortarray; - Mesh mesh; - - /// - /// Form a Delaunay triangulation by the divide-and-conquer method. - /// - /// - /// - /// Sorts the vertices, calls a recursive procedure to triangulate them, and - /// removes the bounding box, setting boundary markers as appropriate. - /// - public IMesh Triangulate(IList points, Configuration config) - { - this.predicates = config.Predicates(); - - this.mesh = new Mesh(config); - this.mesh.TransferNodes(points); - - Otri hullleft = default(Otri), hullright = default(Otri); - int i, j, n = points.Count; - - // Allocate an array of pointers to vertices for sorting. - this.sortarray = new Vertex[n]; - i = 0; - foreach (var v in points) - { - sortarray[i++] = v; - } - - // Sort the vertices. - VertexSorter.Sort(sortarray); - - // Discard duplicate vertices, which can really mess up the algorithm. - i = 0; - for (j = 1; j < n; j++) - { - if ((sortarray[i].x == sortarray[j].x) && (sortarray[i].y == sortarray[j].y)) - { - if (Log.Verbose) - { - Log.Instance.Warning( - String.Format("A duplicate vertex appeared and was ignored (ID {0}).", sortarray[j].id), - "Dwyer.Triangulate()"); - } - sortarray[j].type = VertexType.UndeadVertex; - mesh.undeads++; - } - else - { - i++; - sortarray[i] = sortarray[j]; - } - } - i++; - if (UseDwyer) - { - // Re-sort the array of vertices to accommodate alternating cuts. - VertexSorter.Alternate(sortarray, i); - } - - // Form the Delaunay triangulation. - DivconqRecurse(0, i - 1, 0, ref hullleft, ref hullright); - - this.mesh.hullsize = RemoveGhosts(ref hullleft); - - return this.mesh; - } - - /// - /// Merge two adjacent Delaunay triangulations into a single Delaunay triangulation. - /// - /// Bounding triangles of the left triangulation. - /// Bounding triangles of the left triangulation. - /// Bounding triangles of the right triangulation. - /// Bounding triangles of the right triangulation. - /// - /// - /// This is similar to the algorithm given by Guibas and Stolfi, but uses - /// a triangle-based, rather than edge-based, data structure. - /// - /// The algorithm walks up the gap between the two triangulations, knitting - /// them together. As they are merged, some of their bounding triangles - /// are converted into real triangles of the triangulation. The procedure - /// pulls each hull's bounding triangles apart, then knits them together - /// like the teeth of two gears. The Delaunay property determines, at each - /// step, whether the next "tooth" is a bounding triangle of the left hull - /// or the right. When a bounding triangle becomes real, its apex is - /// changed from NULL to a real vertex. - /// - /// Only two new triangles need to be allocated. These become new bounding - /// triangles at the top and bottom of the seam. They are used to connect - /// the remaining bounding triangles (those that have not been converted - /// into real triangles) into a single fan. - /// - /// On entry, 'farleft' and 'innerleft' are bounding triangles of the left - /// triangulation. The origin of 'farleft' is the leftmost vertex, and - /// the destination of 'innerleft' is the rightmost vertex of the - /// triangulation. Similarly, 'innerright' and 'farright' are bounding - /// triangles of the right triangulation. The origin of 'innerright' and - /// destination of 'farright' are the leftmost and rightmost vertices. - /// - /// On completion, the origin of 'farleft' is the leftmost vertex of the - /// merged triangulation, and the destination of 'farright' is the rightmost - /// vertex. - /// - void MergeHulls(ref Otri farleft, ref Otri innerleft, ref Otri innerright, - ref Otri farright, int axis) - { - Otri leftcand = default(Otri), rightcand = default(Otri); - Otri nextedge = default(Otri); - Otri sidecasing = default(Otri), topcasing = default(Otri), outercasing = default(Otri); - Otri checkedge = default(Otri); - Otri baseedge = default(Otri); - Vertex innerleftdest; - Vertex innerrightorg; - Vertex innerleftapex, innerrightapex; - Vertex farleftpt, farrightpt; - Vertex farleftapex, farrightapex; - Vertex lowerleft, lowerright; - Vertex upperleft, upperright; - Vertex nextapex; - Vertex checkvertex; - bool changemade; - bool badedge; - bool leftfinished, rightfinished; - - innerleftdest = innerleft.Dest(); - innerleftapex = innerleft.Apex(); - innerrightorg = innerright.Org(); - innerrightapex = innerright.Apex(); - // Special treatment for horizontal cuts. - if (UseDwyer && (axis == 1)) - { - farleftpt = farleft.Org(); - farleftapex = farleft.Apex(); - farrightpt = farright.Dest(); - farrightapex = farright.Apex(); - // The pointers to the extremal vertices are shifted to point to the - // topmost and bottommost vertex of each hull, rather than the - // leftmost and rightmost vertices. - while (farleftapex.y < farleftpt.y) - { - farleft.Lnext(); - farleft.Sym(); - farleftpt = farleftapex; - farleftapex = farleft.Apex(); - } - innerleft.Sym(ref checkedge); - checkvertex = checkedge.Apex(); - while (checkvertex.y > innerleftdest.y) - { - checkedge.Lnext(ref innerleft); - innerleftapex = innerleftdest; - innerleftdest = checkvertex; - innerleft.Sym(ref checkedge); - checkvertex = checkedge.Apex(); - } - while (innerrightapex.y < innerrightorg.y) - { - innerright.Lnext(); - innerright.Sym(); - innerrightorg = innerrightapex; - innerrightapex = innerright.Apex(); - } - farright.Sym(ref checkedge); - checkvertex = checkedge.Apex(); - while (checkvertex.y > farrightpt.y) - { - checkedge.Lnext(ref farright); - farrightapex = farrightpt; - farrightpt = checkvertex; - farright.Sym(ref checkedge); - checkvertex = checkedge.Apex(); - } - } - // Find a line tangent to and below both hulls. - do - { - changemade = false; - // Make innerleftdest the "bottommost" vertex of the left hull. - if (predicates.CounterClockwise(innerleftdest, innerleftapex, innerrightorg) > 0.0) - { - innerleft.Lprev(); - innerleft.Sym(); - innerleftdest = innerleftapex; - innerleftapex = innerleft.Apex(); - changemade = true; - } - // Make innerrightorg the "bottommost" vertex of the right hull. - if (predicates.CounterClockwise(innerrightapex, innerrightorg, innerleftdest) > 0.0) - { - innerright.Lnext(); - innerright.Sym(); - innerrightorg = innerrightapex; - innerrightapex = innerright.Apex(); - changemade = true; - } - } while (changemade); - - // Find the two candidates to be the next "gear tooth." - innerleft.Sym(ref leftcand); - innerright.Sym(ref rightcand); - // Create the bottom new bounding triangle. - mesh.MakeTriangle(ref baseedge); - // Connect it to the bounding boxes of the left and right triangulations. - baseedge.Bond(ref innerleft); - baseedge.Lnext(); - baseedge.Bond(ref innerright); - baseedge.Lnext(); - baseedge.SetOrg(innerrightorg); - baseedge.SetDest(innerleftdest); - // Apex is intentionally left NULL. - - // Fix the extreme triangles if necessary. - farleftpt = farleft.Org(); - if (innerleftdest == farleftpt) - { - baseedge.Lnext(ref farleft); - } - farrightpt = farright.Dest(); - if (innerrightorg == farrightpt) - { - baseedge.Lprev(ref farright); - } - // The vertices of the current knitting edge. - lowerleft = innerleftdest; - lowerright = innerrightorg; - // The candidate vertices for knitting. - upperleft = leftcand.Apex(); - upperright = rightcand.Apex(); - // Walk up the gap between the two triangulations, knitting them together. - while (true) - { - // Have we reached the top? (This isn't quite the right question, - // because even though the left triangulation might seem finished now, - // moving up on the right triangulation might reveal a new vertex of - // the left triangulation. And vice-versa.) - leftfinished = predicates.CounterClockwise(upperleft, lowerleft, lowerright) <= 0.0; - rightfinished = predicates.CounterClockwise(upperright, lowerleft, lowerright) <= 0.0; - if (leftfinished && rightfinished) - { - // Create the top new bounding triangle. - mesh.MakeTriangle(ref nextedge); - nextedge.SetOrg(lowerleft); - nextedge.SetDest(lowerright); - // Apex is intentionally left NULL. - // Connect it to the bounding boxes of the two triangulations. - nextedge.Bond(ref baseedge); - nextedge.Lnext(); - nextedge.Bond(ref rightcand); - nextedge.Lnext(); - nextedge.Bond(ref leftcand); - - // Special treatment for horizontal cuts. - if (UseDwyer && (axis == 1)) - { - farleftpt = farleft.Org(); - farleftapex = farleft.Apex(); - farrightpt = farright.Dest(); - farrightapex = farright.Apex(); - farleft.Sym(ref checkedge); - checkvertex = checkedge.Apex(); - // The pointers to the extremal vertices are restored to the - // leftmost and rightmost vertices (rather than topmost and - // bottommost). - while (checkvertex.x < farleftpt.x) - { - checkedge.Lprev(ref farleft); - farleftapex = farleftpt; - farleftpt = checkvertex; - farleft.Sym(ref checkedge); - checkvertex = checkedge.Apex(); - } - while (farrightapex.x > farrightpt.x) - { - farright.Lprev(); - farright.Sym(); - farrightpt = farrightapex; - farrightapex = farright.Apex(); - } - } - return; - } - // Consider eliminating edges from the left triangulation. - if (!leftfinished) - { - // What vertex would be exposed if an edge were deleted? - leftcand.Lprev(ref nextedge); - nextedge.Sym(); - nextapex = nextedge.Apex(); - // If nextapex is NULL, then no vertex would be exposed; the - // triangulation would have been eaten right through. - if (nextapex != null) - { - // Check whether the edge is Delaunay. - badedge = predicates.InCircle(lowerleft, lowerright, upperleft, nextapex) > 0.0; - while (badedge) - { - // Eliminate the edge with an edge flip. As a result, the - // left triangulation will have one more boundary triangle. - nextedge.Lnext(); - nextedge.Sym(ref topcasing); - nextedge.Lnext(); - nextedge.Sym(ref sidecasing); - nextedge.Bond(ref topcasing); - leftcand.Bond(ref sidecasing); - leftcand.Lnext(); - leftcand.Sym(ref outercasing); - nextedge.Lprev(); - nextedge.Bond(ref outercasing); - // Correct the vertices to reflect the edge flip. - leftcand.SetOrg(lowerleft); - leftcand.SetDest(null); - leftcand.SetApex(nextapex); - nextedge.SetOrg(null); - nextedge.SetDest(upperleft); - nextedge.SetApex(nextapex); - // Consider the newly exposed vertex. - upperleft = nextapex; - // What vertex would be exposed if another edge were deleted? - sidecasing.Copy(ref nextedge); - nextapex = nextedge.Apex(); - if (nextapex != null) - { - // Check whether the edge is Delaunay. - badedge = predicates.InCircle(lowerleft, lowerright, upperleft, nextapex) > 0.0; - } - else - { - // Avoid eating right through the triangulation. - badedge = false; - } - } - } - } - // Consider eliminating edges from the right triangulation. - if (!rightfinished) - { - // What vertex would be exposed if an edge were deleted? - rightcand.Lnext(ref nextedge); - nextedge.Sym(); - nextapex = nextedge.Apex(); - // If nextapex is NULL, then no vertex would be exposed; the - // triangulation would have been eaten right through. - if (nextapex != null) - { - // Check whether the edge is Delaunay. - badedge = predicates.InCircle(lowerleft, lowerright, upperright, nextapex) > 0.0; - while (badedge) - { - // Eliminate the edge with an edge flip. As a result, the - // right triangulation will have one more boundary triangle. - nextedge.Lprev(); - nextedge.Sym(ref topcasing); - nextedge.Lprev(); - nextedge.Sym(ref sidecasing); - nextedge.Bond(ref topcasing); - rightcand.Bond(ref sidecasing); - rightcand.Lprev(); - rightcand.Sym(ref outercasing); - nextedge.Lnext(); - nextedge.Bond(ref outercasing); - // Correct the vertices to reflect the edge flip. - rightcand.SetOrg(null); - rightcand.SetDest(lowerright); - rightcand.SetApex(nextapex); - nextedge.SetOrg(upperright); - nextedge.SetDest(null); - nextedge.SetApex(nextapex); - // Consider the newly exposed vertex. - upperright = nextapex; - // What vertex would be exposed if another edge were deleted? - sidecasing.Copy(ref nextedge); - nextapex = nextedge.Apex(); - if (nextapex != null) - { - // Check whether the edge is Delaunay. - badedge = predicates.InCircle(lowerleft, lowerright, upperright, nextapex) > 0.0; - } - else - { - // Avoid eating right through the triangulation. - badedge = false; - } - } - } - } - if (leftfinished || (!rightfinished && - (predicates.InCircle(upperleft, lowerleft, lowerright, upperright) > 0.0))) - { - // Knit the triangulations, adding an edge from 'lowerleft' - // to 'upperright'. - baseedge.Bond(ref rightcand); - rightcand.Lprev(ref baseedge); - baseedge.SetDest(lowerleft); - lowerright = upperright; - baseedge.Sym(ref rightcand); - upperright = rightcand.Apex(); - } - else - { - // Knit the triangulations, adding an edge from 'upperleft' - // to 'lowerright'. - baseedge.Bond(ref leftcand); - leftcand.Lnext(ref baseedge); - baseedge.SetOrg(lowerright); - lowerleft = upperleft; - baseedge.Sym(ref leftcand); - upperleft = leftcand.Apex(); - } - } - } - - /// - /// Recursively form a Delaunay triangulation by the divide-and-conquer method. - /// - /// - /// - /// - /// - /// - /// - /// Recursively breaks down the problem into smaller pieces, which are - /// knitted together by mergehulls(). The base cases (problems of two or - /// three vertices) are handled specially here. - /// - /// On completion, 'farleft' and 'farright' are bounding triangles such that - /// the origin of 'farleft' is the leftmost vertex (breaking ties by - /// choosing the highest leftmost vertex), and the destination of - /// 'farright' is the rightmost vertex (breaking ties by choosing the - /// lowest rightmost vertex). - /// - void DivconqRecurse(int left, int right, int axis, - ref Otri farleft, ref Otri farright) - { - Otri midtri = default(Otri); - Otri tri1 = default(Otri); - Otri tri2 = default(Otri); - Otri tri3 = default(Otri); - Otri innerleft = default(Otri), innerright = default(Otri); - double area; - int vertices = right - left + 1; - int divider; - - if (vertices == 2) - { - // The triangulation of two vertices is an edge. An edge is - // represented by two bounding triangles. - mesh.MakeTriangle(ref farleft); - farleft.SetOrg(sortarray[left]); - farleft.SetDest(sortarray[left + 1]); - // The apex is intentionally left NULL. - mesh.MakeTriangle(ref farright); - farright.SetOrg(sortarray[left + 1]); - farright.SetDest(sortarray[left]); - // The apex is intentionally left NULL. - farleft.Bond(ref farright); - farleft.Lprev(); - farright.Lnext(); - farleft.Bond(ref farright); - farleft.Lprev(); - farright.Lnext(); - farleft.Bond(ref farright); - - // Ensure that the origin of 'farleft' is sortarray[0]. - farright.Lprev(ref farleft); - return; - } - else if (vertices == 3) - { - // The triangulation of three vertices is either a triangle (with - // three bounding triangles) or two edges (with four bounding - // triangles). In either case, four triangles are created. - mesh.MakeTriangle(ref midtri); - mesh.MakeTriangle(ref tri1); - mesh.MakeTriangle(ref tri2); - mesh.MakeTriangle(ref tri3); - area = predicates.CounterClockwise(sortarray[left], sortarray[left + 1], sortarray[left + 2]); - if (area == 0.0) - { - // Three collinear vertices; the triangulation is two edges. - midtri.SetOrg(sortarray[left]); - midtri.SetDest(sortarray[left + 1]); - tri1.SetOrg(sortarray[left + 1]); - tri1.SetDest(sortarray[left]); - tri2.SetOrg(sortarray[left + 2]); - tri2.SetDest(sortarray[left + 1]); - tri3.SetOrg(sortarray[left + 1]); - tri3.SetDest(sortarray[left + 2]); - // All apices are intentionally left NULL. - midtri.Bond(ref tri1); - tri2.Bond(ref tri3); - midtri.Lnext(); - tri1.Lprev(); - tri2.Lnext(); - tri3.Lprev(); - midtri.Bond(ref tri3); - tri1.Bond(ref tri2); - midtri.Lnext(); - tri1.Lprev(); - tri2.Lnext(); - tri3.Lprev(); - midtri.Bond(ref tri1); - tri2.Bond(ref tri3); - // Ensure that the origin of 'farleft' is sortarray[0]. - tri1.Copy(ref farleft); - // Ensure that the destination of 'farright' is sortarray[2]. - tri2.Copy(ref farright); - } - else - { - // The three vertices are not collinear; the triangulation is one - // triangle, namely 'midtri'. - midtri.SetOrg(sortarray[left]); - tri1.SetDest(sortarray[left]); - tri3.SetOrg(sortarray[left]); - // Apices of tri1, tri2, and tri3 are left NULL. - if (area > 0.0) - { - // The vertices are in counterclockwise order. - midtri.SetDest(sortarray[left + 1]); - tri1.SetOrg(sortarray[left + 1]); - tri2.SetDest(sortarray[left + 1]); - midtri.SetApex(sortarray[left + 2]); - tri2.SetOrg(sortarray[left + 2]); - tri3.SetDest(sortarray[left + 2]); - } - else - { - // The vertices are in clockwise order. - midtri.SetDest(sortarray[left + 2]); - tri1.SetOrg(sortarray[left + 2]); - tri2.SetDest(sortarray[left + 2]); - midtri.SetApex(sortarray[left + 1]); - tri2.SetOrg(sortarray[left + 1]); - tri3.SetDest(sortarray[left + 1]); - } - // The topology does not depend on how the vertices are ordered. - midtri.Bond(ref tri1); - midtri.Lnext(); - midtri.Bond(ref tri2); - midtri.Lnext(); - midtri.Bond(ref tri3); - tri1.Lprev(); - tri2.Lnext(); - tri1.Bond(ref tri2); - tri1.Lprev(); - tri3.Lprev(); - tri1.Bond(ref tri3); - tri2.Lnext(); - tri3.Lprev(); - tri2.Bond(ref tri3); - // Ensure that the origin of 'farleft' is sortarray[0]. - tri1.Copy(ref farleft); - // Ensure that the destination of 'farright' is sortarray[2]. - if (area > 0.0) - { - tri2.Copy(ref farright); - } - else - { - farleft.Lnext(ref farright); - } - } - - return; - } - else - { - // Split the vertices in half. - divider = vertices >> 1; - - // Recursively triangulate each half. - DivconqRecurse(left, left + divider - 1, 1 - axis, ref farleft, ref innerleft); - DivconqRecurse(left + divider, right, 1 - axis, ref innerright, ref farright); - - // Merge the two triangulations into one. - MergeHulls(ref farleft, ref innerleft, ref innerright, ref farright, axis); - } - } - - /// - /// Removes ghost triangles. - /// - /// - /// Number of vertices on the hull. - int RemoveGhosts(ref Otri startghost) - { - Otri searchedge = default(Otri); - Otri dissolveedge = default(Otri); - Otri deadtriangle = default(Otri); - Vertex markorg; - - int hullsize; - - bool noPoly = !mesh.behavior.Poly; - - // Find an edge on the convex hull to start point location from. - startghost.Lprev(ref searchedge); - searchedge.Sym(); - mesh.dummytri.neighbors[0] = searchedge; - - // Remove the bounding box and count the convex hull edges. - startghost.Copy(ref dissolveedge); - hullsize = 0; - do - { - hullsize++; - dissolveedge.Lnext(ref deadtriangle); - dissolveedge.Lprev(); - dissolveedge.Sym(); - - // If no PSLG is involved, set the boundary markers of all the vertices - // on the convex hull. If a PSLG is used, this step is done later. - if (noPoly) - { - // Watch out for the case where all the input vertices are collinear. - if (dissolveedge.tri.id != Mesh.DUMMY) - { - markorg = dissolveedge.Org(); - if (markorg.label == 0) - { - markorg.label = 1; - } - } - } - // Remove a bounding triangle from a convex hull triangle. - dissolveedge.Dissolve(mesh.dummytri); - // Find the next bounding triangle. - deadtriangle.Sym(ref dissolveedge); - - // Delete the bounding triangle. - mesh.TriangleDealloc(deadtriangle.tri); - } while (!dissolveedge.Equals(startghost)); - - return hullsize; - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing.Algorithm +{ + using System; + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Tools; + using TriangleNet.Topology; + + /// + /// Builds a delaunay triangulation using the divide-and-conquer algorithm. + /// + /// + /// The divide-and-conquer bounding box + /// + /// I originally implemented the divide-and-conquer and incremental Delaunay + /// triangulations using the edge-based data structure presented by Guibas + /// and Stolfi. Switching to a triangle-based data structure doubled the + /// speed. However, I had to think of a few extra tricks to maintain the + /// elegance of the original algorithms. + /// + /// The "bounding box" used by my variant of the divide-and-conquer + /// algorithm uses one triangle for each edge of the convex hull of the + /// triangulation. These bounding triangles all share a common apical + /// vertex, which is represented by NULL and which represents nothing. + /// The bounding triangles are linked in a circular fan about this NULL + /// vertex, and the edges on the convex hull of the triangulation appear + /// opposite the NULL vertex. You might find it easiest to imagine that + /// the NULL vertex is a point in 3D space behind the center of the + /// triangulation, and that the bounding triangles form a sort of cone. + /// + /// This bounding box makes it easy to represent degenerate cases. For + /// instance, the triangulation of two vertices is a single edge. This edge + /// is represented by two bounding box triangles, one on each "side" of the + /// edge. These triangles are also linked together in a fan about the NULL + /// vertex. + /// + /// The bounding box also makes it easy to traverse the convex hull, as the + /// divide-and-conquer algorithm needs to do. + /// + public class Dwyer : ITriangulator + { + IPredicates predicates; + + /// + /// Gets or sets a value indicating whether to use alternating cuts (default = true). + /// + public bool UseDwyer = true; + + Vertex[] sortarray; + Mesh mesh; + + /// + /// Form a Delaunay triangulation by the divide-and-conquer method. + /// + /// + /// + /// Sorts the vertices, calls a recursive procedure to triangulate them, and + /// removes the bounding box, setting boundary markers as appropriate. + /// + public IMesh Triangulate(IList points, Configuration config) + { + predicates = config.Predicates(); + + mesh = new Mesh(config, points); + + Otri hullleft = default(Otri), hullright = default(Otri); + int i, j, n = points.Count; + + // Allocate an array of pointers to vertices for sorting. + sortarray = new Vertex[n]; + i = 0; + foreach (var v in points) + { + sortarray[i++] = v; + } + + // Sort the vertices. + VertexSorter.Sort(sortarray); + + // Discard duplicate vertices, which can really mess up the algorithm. + i = 0; + for (j = 1; j < n; j++) + { + if ((sortarray[i].x == sortarray[j].x) && (sortarray[i].y == sortarray[j].y)) + { + if (Log.Verbose) + { + Log.Instance.Warning( + string.Format("A duplicate vertex appeared and was ignored (ID {0}).", sortarray[j].id), + "Dwyer.Triangulate()"); + } + sortarray[j].type = VertexType.UndeadVertex; + mesh.undeads++; + } + else + { + i++; + sortarray[i] = sortarray[j]; + } + } + i++; + if (UseDwyer) + { + // Re-sort the array of vertices to accommodate alternating cuts. + VertexSorter.Alternate(sortarray, i); + } + + // Form the Delaunay triangulation. + DivconqRecurse(0, i - 1, 0, ref hullleft, ref hullright); + + mesh.hullsize = RemoveGhosts(ref hullleft); + + return mesh; + } + + /// + /// Merge two adjacent Delaunay triangulations into a single Delaunay triangulation. + /// + /// Bounding triangles of the left triangulation. + /// Bounding triangles of the left triangulation. + /// Bounding triangles of the right triangulation. + /// Bounding triangles of the right triangulation. + /// + /// + /// This is similar to the algorithm given by Guibas and Stolfi, but uses + /// a triangle-based, rather than edge-based, data structure. + /// + /// The algorithm walks up the gap between the two triangulations, knitting + /// them together. As they are merged, some of their bounding triangles + /// are converted into real triangles of the triangulation. The procedure + /// pulls each hull's bounding triangles apart, then knits them together + /// like the teeth of two gears. The Delaunay property determines, at each + /// step, whether the next "tooth" is a bounding triangle of the left hull + /// or the right. When a bounding triangle becomes real, its apex is + /// changed from NULL to a real vertex. + /// + /// Only two new triangles need to be allocated. These become new bounding + /// triangles at the top and bottom of the seam. They are used to connect + /// the remaining bounding triangles (those that have not been converted + /// into real triangles) into a single fan. + /// + /// On entry, 'farleft' and 'innerleft' are bounding triangles of the left + /// triangulation. The origin of 'farleft' is the leftmost vertex, and + /// the destination of 'innerleft' is the rightmost vertex of the + /// triangulation. Similarly, 'innerright' and 'farright' are bounding + /// triangles of the right triangulation. The origin of 'innerright' and + /// destination of 'farright' are the leftmost and rightmost vertices. + /// + /// On completion, the origin of 'farleft' is the leftmost vertex of the + /// merged triangulation, and the destination of 'farright' is the rightmost + /// vertex. + /// + void MergeHulls(ref Otri farleft, ref Otri innerleft, ref Otri innerright, + ref Otri farright, int axis) + { + Otri leftcand = default(Otri), rightcand = default(Otri); + Otri nextedge = default(Otri); + Otri sidecasing = default(Otri), topcasing = default(Otri), outercasing = default(Otri); + Otri checkedge = default(Otri); + Otri baseedge = default(Otri); + Vertex innerleftdest; + Vertex innerrightorg; + Vertex innerleftapex, innerrightapex; + Vertex farleftpt, farrightpt; + Vertex farleftapex, farrightapex; + Vertex lowerleft, lowerright; + Vertex upperleft, upperright; + Vertex nextapex; + Vertex checkvertex; + bool changemade; + bool badedge; + bool leftfinished, rightfinished; + + innerleftdest = innerleft.Dest(); + innerleftapex = innerleft.Apex(); + innerrightorg = innerright.Org(); + innerrightapex = innerright.Apex(); + // Special treatment for horizontal cuts. + if (UseDwyer && (axis == 1)) + { + farleftpt = farleft.Org(); + farleftapex = farleft.Apex(); + farrightpt = farright.Dest(); + farrightapex = farright.Apex(); + // The pointers to the extremal vertices are shifted to point to the + // topmost and bottommost vertex of each hull, rather than the + // leftmost and rightmost vertices. + while (farleftapex.y < farleftpt.y) + { + farleft.Lnext(); + farleft.Sym(); + farleftpt = farleftapex; + farleftapex = farleft.Apex(); + } + innerleft.Sym(ref checkedge); + checkvertex = checkedge.Apex(); + while (checkvertex.y > innerleftdest.y) + { + checkedge.Lnext(ref innerleft); + innerleftapex = innerleftdest; + innerleftdest = checkvertex; + innerleft.Sym(ref checkedge); + checkvertex = checkedge.Apex(); + } + while (innerrightapex.y < innerrightorg.y) + { + innerright.Lnext(); + innerright.Sym(); + innerrightorg = innerrightapex; + innerrightapex = innerright.Apex(); + } + farright.Sym(ref checkedge); + checkvertex = checkedge.Apex(); + while (checkvertex.y > farrightpt.y) + { + checkedge.Lnext(ref farright); + farrightapex = farrightpt; + farrightpt = checkvertex; + farright.Sym(ref checkedge); + checkvertex = checkedge.Apex(); + } + } + // Find a line tangent to and below both hulls. + do + { + changemade = false; + // Make innerleftdest the "bottommost" vertex of the left hull. + if (predicates.CounterClockwise(innerleftdest, innerleftapex, innerrightorg) > 0.0) + { + innerleft.Lprev(); + innerleft.Sym(); + innerleftdest = innerleftapex; + innerleftapex = innerleft.Apex(); + changemade = true; + } + // Make innerrightorg the "bottommost" vertex of the right hull. + if (predicates.CounterClockwise(innerrightapex, innerrightorg, innerleftdest) > 0.0) + { + innerright.Lnext(); + innerright.Sym(); + innerrightorg = innerrightapex; + innerrightapex = innerright.Apex(); + changemade = true; + } + } while (changemade); + + // Find the two candidates to be the next "gear tooth." + innerleft.Sym(ref leftcand); + innerright.Sym(ref rightcand); + // Create the bottom new bounding triangle. + mesh.MakeTriangle(ref baseedge); + // Connect it to the bounding boxes of the left and right triangulations. + baseedge.Bond(ref innerleft); + baseedge.Lnext(); + baseedge.Bond(ref innerright); + baseedge.Lnext(); + baseedge.SetOrg(innerrightorg); + baseedge.SetDest(innerleftdest); + // Apex is intentionally left NULL. + + // Fix the extreme triangles if necessary. + farleftpt = farleft.Org(); + if (innerleftdest == farleftpt) + { + baseedge.Lnext(ref farleft); + } + farrightpt = farright.Dest(); + if (innerrightorg == farrightpt) + { + baseedge.Lprev(ref farright); + } + // The vertices of the current knitting edge. + lowerleft = innerleftdest; + lowerright = innerrightorg; + // The candidate vertices for knitting. + upperleft = leftcand.Apex(); + upperright = rightcand.Apex(); + // Walk up the gap between the two triangulations, knitting them together. + while (true) + { + // Have we reached the top? (This isn't quite the right question, + // because even though the left triangulation might seem finished now, + // moving up on the right triangulation might reveal a new vertex of + // the left triangulation. And vice-versa.) + leftfinished = predicates.CounterClockwise(upperleft, lowerleft, lowerright) <= 0.0; + rightfinished = predicates.CounterClockwise(upperright, lowerleft, lowerright) <= 0.0; + if (leftfinished && rightfinished) + { + // Create the top new bounding triangle. + mesh.MakeTriangle(ref nextedge); + nextedge.SetOrg(lowerleft); + nextedge.SetDest(lowerright); + // Apex is intentionally left NULL. + // Connect it to the bounding boxes of the two triangulations. + nextedge.Bond(ref baseedge); + nextedge.Lnext(); + nextedge.Bond(ref rightcand); + nextedge.Lnext(); + nextedge.Bond(ref leftcand); + + // Special treatment for horizontal cuts. + if (UseDwyer && (axis == 1)) + { + farleftpt = farleft.Org(); + farleftapex = farleft.Apex(); + farrightpt = farright.Dest(); + farrightapex = farright.Apex(); + farleft.Sym(ref checkedge); + checkvertex = checkedge.Apex(); + // The pointers to the extremal vertices are restored to the + // leftmost and rightmost vertices (rather than topmost and + // bottommost). + while (checkvertex.x < farleftpt.x) + { + checkedge.Lprev(ref farleft); + farleftapex = farleftpt; + farleftpt = checkvertex; + farleft.Sym(ref checkedge); + checkvertex = checkedge.Apex(); + } + while (farrightapex.x > farrightpt.x) + { + farright.Lprev(); + farright.Sym(); + farrightpt = farrightapex; + farrightapex = farright.Apex(); + } + } + return; + } + // Consider eliminating edges from the left triangulation. + if (!leftfinished) + { + // What vertex would be exposed if an edge were deleted? + leftcand.Lprev(ref nextedge); + nextedge.Sym(); + nextapex = nextedge.Apex(); + // If nextapex is NULL, then no vertex would be exposed; the + // triangulation would have been eaten right through. + if (nextapex != null) + { + // Check whether the edge is Delaunay. + badedge = predicates.InCircle(lowerleft, lowerright, upperleft, nextapex) > 0.0; + while (badedge) + { + // Eliminate the edge with an edge flip. As a result, the + // left triangulation will have one more boundary triangle. + nextedge.Lnext(); + nextedge.Sym(ref topcasing); + nextedge.Lnext(); + nextedge.Sym(ref sidecasing); + nextedge.Bond(ref topcasing); + leftcand.Bond(ref sidecasing); + leftcand.Lnext(); + leftcand.Sym(ref outercasing); + nextedge.Lprev(); + nextedge.Bond(ref outercasing); + // Correct the vertices to reflect the edge flip. + leftcand.SetOrg(lowerleft); + leftcand.SetDest(null); + leftcand.SetApex(nextapex); + nextedge.SetOrg(null); + nextedge.SetDest(upperleft); + nextedge.SetApex(nextapex); + // Consider the newly exposed vertex. + upperleft = nextapex; + // What vertex would be exposed if another edge were deleted? + sidecasing.Copy(ref nextedge); + nextapex = nextedge.Apex(); + if (nextapex != null) + { + // Check whether the edge is Delaunay. + badedge = predicates.InCircle(lowerleft, lowerright, upperleft, nextapex) > 0.0; + } + else + { + // Avoid eating right through the triangulation. + badedge = false; + } + } + } + } + // Consider eliminating edges from the right triangulation. + if (!rightfinished) + { + // What vertex would be exposed if an edge were deleted? + rightcand.Lnext(ref nextedge); + nextedge.Sym(); + nextapex = nextedge.Apex(); + // If nextapex is NULL, then no vertex would be exposed; the + // triangulation would have been eaten right through. + if (nextapex != null) + { + // Check whether the edge is Delaunay. + badedge = predicates.InCircle(lowerleft, lowerright, upperright, nextapex) > 0.0; + while (badedge) + { + // Eliminate the edge with an edge flip. As a result, the + // right triangulation will have one more boundary triangle. + nextedge.Lprev(); + nextedge.Sym(ref topcasing); + nextedge.Lprev(); + nextedge.Sym(ref sidecasing); + nextedge.Bond(ref topcasing); + rightcand.Bond(ref sidecasing); + rightcand.Lprev(); + rightcand.Sym(ref outercasing); + nextedge.Lnext(); + nextedge.Bond(ref outercasing); + // Correct the vertices to reflect the edge flip. + rightcand.SetOrg(null); + rightcand.SetDest(lowerright); + rightcand.SetApex(nextapex); + nextedge.SetOrg(upperright); + nextedge.SetDest(null); + nextedge.SetApex(nextapex); + // Consider the newly exposed vertex. + upperright = nextapex; + // What vertex would be exposed if another edge were deleted? + sidecasing.Copy(ref nextedge); + nextapex = nextedge.Apex(); + if (nextapex != null) + { + // Check whether the edge is Delaunay. + badedge = predicates.InCircle(lowerleft, lowerright, upperright, nextapex) > 0.0; + } + else + { + // Avoid eating right through the triangulation. + badedge = false; + } + } + } + } + if (leftfinished || (!rightfinished && + (predicates.InCircle(upperleft, lowerleft, lowerright, upperright) > 0.0))) + { + // Knit the triangulations, adding an edge from 'lowerleft' + // to 'upperright'. + baseedge.Bond(ref rightcand); + rightcand.Lprev(ref baseedge); + baseedge.SetDest(lowerleft); + lowerright = upperright; + baseedge.Sym(ref rightcand); + upperright = rightcand.Apex(); + } + else + { + // Knit the triangulations, adding an edge from 'upperleft' + // to 'lowerright'. + baseedge.Bond(ref leftcand); + leftcand.Lnext(ref baseedge); + baseedge.SetOrg(lowerright); + lowerleft = upperleft; + baseedge.Sym(ref leftcand); + upperleft = leftcand.Apex(); + } + } + } + + /// + /// Recursively form a Delaunay triangulation by the divide-and-conquer method. + /// + /// + /// + /// + /// + /// + /// + /// Recursively breaks down the problem into smaller pieces, which are + /// knitted together by mergehulls(). The base cases (problems of two or + /// three vertices) are handled specially here. + /// + /// On completion, 'farleft' and 'farright' are bounding triangles such that + /// the origin of 'farleft' is the leftmost vertex (breaking ties by + /// choosing the highest leftmost vertex), and the destination of + /// 'farright' is the rightmost vertex (breaking ties by choosing the + /// lowest rightmost vertex). + /// + void DivconqRecurse(int left, int right, int axis, + ref Otri farleft, ref Otri farright) + { + Otri midtri = default(Otri); + Otri tri1 = default(Otri); + Otri tri2 = default(Otri); + Otri tri3 = default(Otri); + Otri innerleft = default(Otri), innerright = default(Otri); + double area; + int vertices = right - left + 1; + int divider; + + if (vertices == 2) + { + // The triangulation of two vertices is an edge. An edge is + // represented by two bounding triangles. + mesh.MakeTriangle(ref farleft); + farleft.SetOrg(sortarray[left]); + farleft.SetDest(sortarray[left + 1]); + // The apex is intentionally left NULL. + mesh.MakeTriangle(ref farright); + farright.SetOrg(sortarray[left + 1]); + farright.SetDest(sortarray[left]); + // The apex is intentionally left NULL. + farleft.Bond(ref farright); + farleft.Lprev(); + farright.Lnext(); + farleft.Bond(ref farright); + farleft.Lprev(); + farright.Lnext(); + farleft.Bond(ref farright); + + // Ensure that the origin of 'farleft' is sortarray[0]. + farright.Lprev(ref farleft); + return; + } + else if (vertices == 3) + { + // The triangulation of three vertices is either a triangle (with + // three bounding triangles) or two edges (with four bounding + // triangles). In either case, four triangles are created. + mesh.MakeTriangle(ref midtri); + mesh.MakeTriangle(ref tri1); + mesh.MakeTriangle(ref tri2); + mesh.MakeTriangle(ref tri3); + area = predicates.CounterClockwise(sortarray[left], sortarray[left + 1], sortarray[left + 2]); + if (area == 0.0) + { + // Three collinear vertices; the triangulation is two edges. + midtri.SetOrg(sortarray[left]); + midtri.SetDest(sortarray[left + 1]); + tri1.SetOrg(sortarray[left + 1]); + tri1.SetDest(sortarray[left]); + tri2.SetOrg(sortarray[left + 2]); + tri2.SetDest(sortarray[left + 1]); + tri3.SetOrg(sortarray[left + 1]); + tri3.SetDest(sortarray[left + 2]); + // All apices are intentionally left NULL. + midtri.Bond(ref tri1); + tri2.Bond(ref tri3); + midtri.Lnext(); + tri1.Lprev(); + tri2.Lnext(); + tri3.Lprev(); + midtri.Bond(ref tri3); + tri1.Bond(ref tri2); + midtri.Lnext(); + tri1.Lprev(); + tri2.Lnext(); + tri3.Lprev(); + midtri.Bond(ref tri1); + tri2.Bond(ref tri3); + // Ensure that the origin of 'farleft' is sortarray[0]. + tri1.Copy(ref farleft); + // Ensure that the destination of 'farright' is sortarray[2]. + tri2.Copy(ref farright); + } + else + { + // The three vertices are not collinear; the triangulation is one + // triangle, namely 'midtri'. + midtri.SetOrg(sortarray[left]); + tri1.SetDest(sortarray[left]); + tri3.SetOrg(sortarray[left]); + // Apices of tri1, tri2, and tri3 are left NULL. + if (area > 0.0) + { + // The vertices are in counterclockwise order. + midtri.SetDest(sortarray[left + 1]); + tri1.SetOrg(sortarray[left + 1]); + tri2.SetDest(sortarray[left + 1]); + midtri.SetApex(sortarray[left + 2]); + tri2.SetOrg(sortarray[left + 2]); + tri3.SetDest(sortarray[left + 2]); + } + else + { + // The vertices are in clockwise order. + midtri.SetDest(sortarray[left + 2]); + tri1.SetOrg(sortarray[left + 2]); + tri2.SetDest(sortarray[left + 2]); + midtri.SetApex(sortarray[left + 1]); + tri2.SetOrg(sortarray[left + 1]); + tri3.SetDest(sortarray[left + 1]); + } + // The topology does not depend on how the vertices are ordered. + midtri.Bond(ref tri1); + midtri.Lnext(); + midtri.Bond(ref tri2); + midtri.Lnext(); + midtri.Bond(ref tri3); + tri1.Lprev(); + tri2.Lnext(); + tri1.Bond(ref tri2); + tri1.Lprev(); + tri3.Lprev(); + tri1.Bond(ref tri3); + tri2.Lnext(); + tri3.Lprev(); + tri2.Bond(ref tri3); + // Ensure that the origin of 'farleft' is sortarray[0]. + tri1.Copy(ref farleft); + // Ensure that the destination of 'farright' is sortarray[2]. + if (area > 0.0) + { + tri2.Copy(ref farright); + } + else + { + farleft.Lnext(ref farright); + } + } + + return; + } + else + { + // Split the vertices in half. + divider = vertices >> 1; + + // Recursively triangulate each half. + DivconqRecurse(left, left + divider - 1, 1 - axis, ref farleft, ref innerleft); + DivconqRecurse(left + divider, right, 1 - axis, ref innerright, ref farright); + + // Merge the two triangulations into one. + MergeHulls(ref farleft, ref innerleft, ref innerright, ref farright, axis); + } + } + + /// + /// Removes ghost triangles. + /// + /// + /// Number of vertices on the hull. + int RemoveGhosts(ref Otri startghost) + { + Otri searchedge = default(Otri); + Otri dissolveedge = default(Otri); + Otri deadtriangle = default(Otri); + Vertex markorg; + + int hullsize; + + bool noPoly = !mesh.behavior.Poly; + + // Find an edge on the convex hull to start point location from. + startghost.Lprev(ref searchedge); + searchedge.Sym(); + mesh.dummytri.neighbors[0] = searchedge; + + // Remove the bounding box and count the convex hull edges. + startghost.Copy(ref dissolveedge); + hullsize = 0; + do + { + hullsize++; + dissolveedge.Lnext(ref deadtriangle); + dissolveedge.Lprev(); + dissolveedge.Sym(); + + // If no PSLG is involved, set the boundary markers of all the vertices + // on the convex hull. If a PSLG is used, this step is done later. + if (noPoly) + { + // Watch out for the case where all the input vertices are collinear. + if (dissolveedge.tri.id != Mesh.DUMMY) + { + markorg = dissolveedge.Org(); + if (markorg.label == 0) + { + markorg.label = 1; + } + } + } + // Remove a bounding triangle from a convex hull triangle. + dissolveedge.Dissolve(mesh.dummytri); + // Find the next bounding triangle. + deadtriangle.Sym(ref dissolveedge); + + // Delete the bounding triangle. + mesh.TriangleDealloc(deadtriangle.tri); + } while (!dissolveedge.Equals(startghost)); + + return hullsize; + } + } +} diff --git a/Triangle.NET/Triangle/Meshing/Algorithm/Incremental.cs b/src/Triangle/Meshing/Algorithm/Incremental.cs similarity index 93% rename from Triangle.NET/Triangle/Meshing/Algorithm/Incremental.cs rename to src/Triangle/Meshing/Algorithm/Incremental.cs index 3686fa4..0badf61 100644 --- a/Triangle.NET/Triangle/Meshing/Algorithm/Incremental.cs +++ b/src/Triangle/Meshing/Algorithm/Incremental.cs @@ -1,191 +1,190 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Meshing.Algorithm -{ - using System.Collections.Generic; - using TriangleNet.Topology; - using TriangleNet.Geometry; - - /// - /// Builds a delaunay triangulation using the incremental algorithm. - /// - public class Incremental : ITriangulator - { - Mesh mesh; - - /// - /// Form a Delaunay triangulation by incrementally inserting vertices. - /// - /// Returns the number of edges on the convex hull of the - /// triangulation. - public IMesh Triangulate(IList points, Configuration config) - { - this.mesh = new Mesh(config); - this.mesh.TransferNodes(points); - - Otri starttri = new Otri(); - - // Create a triangular bounding box. - GetBoundingBox(); - - foreach (var v in mesh.vertices.Values) - { - starttri.tri = mesh.dummytri; - Osub tmp = default(Osub); - if (mesh.InsertVertex(v, ref starttri, ref tmp, false, false) == InsertVertexResult.Duplicate) - { - if (Log.Verbose) - { - Log.Instance.Warning("A duplicate vertex appeared and was ignored.", - "Incremental.Triangulate()"); - } - v.type = VertexType.UndeadVertex; - mesh.undeads++; - } - } - - // Remove the bounding box. - this.mesh.hullsize = RemoveBox(); - - return this.mesh; - } - - /// - /// Form an "infinite" bounding triangle to insert vertices into. - /// - /// - /// The vertices at "infinity" are assigned finite coordinates, which are - /// used by the point location routines, but (mostly) ignored by the - /// Delaunay edge flip routines. - /// - void GetBoundingBox() - { - Otri inftri = default(Otri); // Handle for the triangular bounding box. - Rectangle box = mesh.bounds; - - // Find the width (or height, whichever is larger) of the triangulation. - double width = box.Width; - if (box.Height > width) - { - width = box.Height; - } - if (width == 0.0) - { - width = 1.0; - } - // Create the vertices of the bounding box. - mesh.infvertex1 = new Vertex(box.Left - 50.0 * width, box.Bottom - 40.0 * width); - mesh.infvertex2 = new Vertex(box.Right + 50.0 * width, box.Bottom - 40.0 * width); - mesh.infvertex3 = new Vertex(0.5 * (box.Left + box.Right), box.Top + 60.0 * width); - - // Create the bounding box. - mesh.MakeTriangle(ref inftri); - - inftri.SetOrg(mesh.infvertex1); - inftri.SetDest(mesh.infvertex2); - inftri.SetApex(mesh.infvertex3); - - // Link dummytri to the bounding box so we can always find an - // edge to begin searching (point location) from. - mesh.dummytri.neighbors[0] = inftri; - } - - /// - /// Remove the "infinite" bounding triangle, setting boundary markers as appropriate. - /// - /// Returns the number of edges on the convex hull of the triangulation. - /// - /// The triangular bounding box has three boundary triangles (one for each - /// side of the bounding box), and a bunch of triangles fanning out from - /// the three bounding box vertices (one triangle for each edge of the - /// convex hull of the inner mesh). This routine removes these triangles. - /// - int RemoveBox() - { - Otri deadtriangle = default(Otri); - Otri searchedge = default(Otri); - Otri checkedge = default(Otri); - Otri nextedge = default(Otri), finaledge = default(Otri), dissolveedge = default(Otri); - Vertex markorg; - int hullsize; - - bool noPoly = !mesh.behavior.Poly; - - // Find a boundary triangle. - nextedge.tri = mesh.dummytri; - nextedge.orient = 0; - nextedge.Sym(); - - // Mark a place to stop. - nextedge.Lprev(ref finaledge); - nextedge.Lnext(); - nextedge.Sym(); - // Find a triangle (on the boundary of the vertex set) that isn't - // a bounding box triangle. - nextedge.Lprev(ref searchedge); - searchedge.Sym(); - // Check whether nextedge is another boundary triangle - // adjacent to the first one. - nextedge.Lnext(ref checkedge); - checkedge.Sym(); - if (checkedge.tri.id == Mesh.DUMMY) - { - // Go on to the next triangle. There are only three boundary - // triangles, and this next triangle cannot be the third one, - // so it's safe to stop here. - searchedge.Lprev(); - searchedge.Sym(); - } - - // Find a new boundary edge to search from, as the current search - // edge lies on a bounding box triangle and will be deleted. - mesh.dummytri.neighbors[0] = searchedge; - - hullsize = -2; - while (!nextedge.Equals(finaledge)) - { - hullsize++; - nextedge.Lprev(ref dissolveedge); - dissolveedge.Sym(); - // If not using a PSLG, the vertices should be marked now. - // (If using a PSLG, markhull() will do the job.) - if (noPoly) - { - // Be careful! One must check for the case where all the input - // vertices are collinear, and thus all the triangles are part of - // the bounding box. Otherwise, the setvertexmark() call below - // will cause a bad pointer reference. - if (dissolveedge.tri.id != Mesh.DUMMY) - { - markorg = dissolveedge.Org(); - if (markorg.label == 0) - { - markorg.label = 1; - } - } - } - // Disconnect the bounding box triangle from the mesh triangle. - dissolveedge.Dissolve(mesh.dummytri); - nextedge.Lnext(ref deadtriangle); - deadtriangle.Sym(ref nextedge); - // Get rid of the bounding box triangle. - mesh.TriangleDealloc(deadtriangle.tri); - // Do we need to turn the corner? - if (nextedge.tri.id == Mesh.DUMMY) - { - // Turn the corner. - dissolveedge.Copy(ref nextedge); - } - } - - mesh.TriangleDealloc(finaledge.tri); - - return hullsize; - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing.Algorithm +{ + using System.Collections.Generic; + using TriangleNet.Topology; + using TriangleNet.Geometry; + + /// + /// Builds a delaunay triangulation using the incremental algorithm. + /// + public class Incremental : ITriangulator + { + Mesh mesh; + + /// + /// Form a Delaunay triangulation by incrementally inserting vertices. + /// + /// Returns the number of edges on the convex hull of the + /// triangulation. + public IMesh Triangulate(IList points, Configuration config) + { + mesh = new Mesh(config, points); + + Otri starttri = new Otri(); + + // Create a triangular bounding box. + GetBoundingBox(); + + foreach (var v in mesh.vertices.Values) + { + starttri.tri = mesh.dummytri; + Osub tmp = default(Osub); + if (mesh.InsertVertex(v, ref starttri, ref tmp, false, false) == InsertVertexResult.Duplicate) + { + if (Log.Verbose) + { + Log.Instance.Warning("A duplicate vertex appeared and was ignored.", + "Incremental.Triangulate()"); + } + v.type = VertexType.UndeadVertex; + mesh.undeads++; + } + } + + // Remove the bounding box. + mesh.hullsize = RemoveBox(); + + return mesh; + } + + /// + /// Form an "infinite" bounding triangle to insert vertices into. + /// + /// + /// The vertices at "infinity" are assigned finite coordinates, which are + /// used by the point location routines, but (mostly) ignored by the + /// Delaunay edge flip routines. + /// + void GetBoundingBox() + { + Otri inftri = default(Otri); // Handle for the triangular bounding box. + Rectangle box = mesh.bounds; + + // Find the width (or height, whichever is larger) of the triangulation. + double width = box.Width; + if (box.Height > width) + { + width = box.Height; + } + if (width == 0.0) + { + width = 1.0; + } + // Create the vertices of the bounding box. + mesh.infvertex1 = new Vertex(box.Left - 50.0 * width, box.Bottom - 40.0 * width); + mesh.infvertex2 = new Vertex(box.Right + 50.0 * width, box.Bottom - 40.0 * width); + mesh.infvertex3 = new Vertex(0.5 * (box.Left + box.Right), box.Top + 60.0 * width); + + // Create the bounding box. + mesh.MakeTriangle(ref inftri); + + inftri.SetOrg(mesh.infvertex1); + inftri.SetDest(mesh.infvertex2); + inftri.SetApex(mesh.infvertex3); + + // Link dummytri to the bounding box so we can always find an + // edge to begin searching (point location) from. + mesh.dummytri.neighbors[0] = inftri; + } + + /// + /// Remove the "infinite" bounding triangle, setting boundary markers as appropriate. + /// + /// Returns the number of edges on the convex hull of the triangulation. + /// + /// The triangular bounding box has three boundary triangles (one for each + /// side of the bounding box), and a bunch of triangles fanning out from + /// the three bounding box vertices (one triangle for each edge of the + /// convex hull of the inner mesh). This routine removes these triangles. + /// + int RemoveBox() + { + Otri deadtriangle = default(Otri); + Otri searchedge = default(Otri); + Otri checkedge = default(Otri); + Otri nextedge = default(Otri), finaledge = default(Otri), dissolveedge = default(Otri); + Vertex markorg; + int hullsize; + + bool noPoly = !mesh.behavior.Poly; + + // Find a boundary triangle. + nextedge.tri = mesh.dummytri; + nextedge.orient = 0; + nextedge.Sym(); + + // Mark a place to stop. + nextedge.Lprev(ref finaledge); + nextedge.Lnext(); + nextedge.Sym(); + // Find a triangle (on the boundary of the vertex set) that isn't + // a bounding box triangle. + nextedge.Lprev(ref searchedge); + searchedge.Sym(); + // Check whether nextedge is another boundary triangle + // adjacent to the first one. + nextedge.Lnext(ref checkedge); + checkedge.Sym(); + if (checkedge.tri.id == Mesh.DUMMY) + { + // Go on to the next triangle. There are only three boundary + // triangles, and this next triangle cannot be the third one, + // so it's safe to stop here. + searchedge.Lprev(); + searchedge.Sym(); + } + + // Find a new boundary edge to search from, as the current search + // edge lies on a bounding box triangle and will be deleted. + mesh.dummytri.neighbors[0] = searchedge; + + hullsize = -2; + while (!nextedge.Equals(finaledge)) + { + hullsize++; + nextedge.Lprev(ref dissolveedge); + dissolveedge.Sym(); + // If not using a PSLG, the vertices should be marked now. + // (If using a PSLG, markhull() will do the job.) + if (noPoly) + { + // Be careful! One must check for the case where all the input + // vertices are collinear, and thus all the triangles are part of + // the bounding box. Otherwise, the setvertexmark() call below + // will cause a bad pointer reference. + if (dissolveedge.tri.id != Mesh.DUMMY) + { + markorg = dissolveedge.Org(); + if (markorg.label == 0) + { + markorg.label = 1; + } + } + } + // Disconnect the bounding box triangle from the mesh triangle. + dissolveedge.Dissolve(mesh.dummytri); + nextedge.Lnext(ref deadtriangle); + deadtriangle.Sym(ref nextedge); + // Get rid of the bounding box triangle. + mesh.TriangleDealloc(deadtriangle.tri); + // Do we need to turn the corner? + if (nextedge.tri.id == Mesh.DUMMY) + { + // Turn the corner. + dissolveedge.Copy(ref nextedge); + } + } + + mesh.TriangleDealloc(finaledge.tri); + + return hullsize; + } + } +} diff --git a/Triangle.NET/Triangle/Meshing/Algorithm/SweepLine.cs b/src/Triangle/Meshing/Algorithm/SweepLine.cs similarity index 96% rename from Triangle.NET/Triangle/Meshing/Algorithm/SweepLine.cs rename to src/Triangle/Meshing/Algorithm/SweepLine.cs index bce274d..7074e52 100644 --- a/Triangle.NET/Triangle/Meshing/Algorithm/SweepLine.cs +++ b/src/Triangle/Meshing/Algorithm/SweepLine.cs @@ -1,808 +1,807 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Meshing.Algorithm -{ - using System; - using System.Collections.Generic; - using TriangleNet.Topology; - using TriangleNet.Geometry; - using TriangleNet.Tools; - - /// - /// Builds a delaunay triangulation using the sweepline algorithm. - /// - public class SweepLine : ITriangulator - { - static int randomseed = 1; - static int SAMPLERATE = 10; - - static int randomnation(int choices) - { - randomseed = (randomseed * 1366 + 150889) % 714025; - return randomseed / (714025 / choices + 1); - } - - IPredicates predicates; - - Mesh mesh; - double xminextreme; // Nonexistent x value used as a flag in sweepline. - List splaynodes; - - public IMesh Triangulate(IList points, Configuration config) - { - this.predicates = config.Predicates(); - - this.mesh = new Mesh(config); - this.mesh.TransferNodes(points); - - // Nonexistent x value used as a flag to mark circle events in sweepline - // Delaunay algorithm. - xminextreme = 10 * mesh.bounds.Left - 9 * mesh.bounds.Right; - - SweepEvent[] eventheap; - - SweepEvent nextevent; - SweepEvent newevent; - SplayNode splayroot; - Otri bottommost = default(Otri); - Otri searchtri = default(Otri); - Otri fliptri; - Otri lefttri = default(Otri); - Otri righttri = default(Otri); - Otri farlefttri = default(Otri); - Otri farrighttri = default(Otri); - Otri inserttri = default(Otri); - Vertex firstvertex, secondvertex; - Vertex nextvertex, lastvertex; - Vertex connectvertex; - Vertex leftvertex, midvertex, rightvertex; - double lefttest, righttest; - int heapsize; - bool check4events, farrightflag = false; - - splaynodes = new List(); - splayroot = null; - - heapsize = points.Count; - CreateHeap(out eventheap, heapsize);//, out events, out freeevents); - - mesh.MakeTriangle(ref lefttri); - mesh.MakeTriangle(ref righttri); - lefttri.Bond(ref righttri); - lefttri.Lnext(); - righttri.Lprev(); - lefttri.Bond(ref righttri); - lefttri.Lnext(); - righttri.Lprev(); - lefttri.Bond(ref righttri); - firstvertex = eventheap[0].vertexEvent; - - HeapDelete(eventheap, heapsize, 0); - heapsize--; - do - { - if (heapsize == 0) - { - Log.Instance.Error("Input vertices are all identical.", "SweepLine.Triangulate()"); - throw new Exception("Input vertices are all identical."); - } - secondvertex = eventheap[0].vertexEvent; - HeapDelete(eventheap, heapsize, 0); - heapsize--; - if ((firstvertex.x == secondvertex.x) && - (firstvertex.y == secondvertex.y)) - { - if (Log.Verbose) - { - Log.Instance.Warning("A duplicate vertex appeared and was ignored (ID " + secondvertex.id + ").", - "SweepLine.Triangulate().1"); - } - secondvertex.type = VertexType.UndeadVertex; - mesh.undeads++; - } - } while ((firstvertex.x == secondvertex.x) && - (firstvertex.y == secondvertex.y)); - lefttri.SetOrg(firstvertex); - lefttri.SetDest(secondvertex); - righttri.SetOrg(secondvertex); - righttri.SetDest(firstvertex); - lefttri.Lprev(ref bottommost); - lastvertex = secondvertex; - - while (heapsize > 0) - { - nextevent = eventheap[0]; - HeapDelete(eventheap, heapsize, 0); - heapsize--; - check4events = true; - if (nextevent.xkey < mesh.bounds.Left) - { - fliptri = nextevent.otriEvent; - fliptri.Oprev(ref farlefttri); - Check4DeadEvent(ref farlefttri, eventheap, ref heapsize); - fliptri.Onext(ref farrighttri); - Check4DeadEvent(ref farrighttri, eventheap, ref heapsize); - - if (farlefttri.Equals(bottommost)) - { - fliptri.Lprev(ref bottommost); - } - mesh.Flip(ref fliptri); - fliptri.SetApex(null); - fliptri.Lprev(ref lefttri); - fliptri.Lnext(ref righttri); - lefttri.Sym(ref farlefttri); - - if (randomnation(SAMPLERATE) == 0) - { - fliptri.Sym(); - leftvertex = fliptri.Dest(); - midvertex = fliptri.Apex(); - rightvertex = fliptri.Org(); - splayroot = CircleTopInsert(splayroot, lefttri, leftvertex, midvertex, rightvertex, nextevent.ykey); - } - } - else - { - nextvertex = nextevent.vertexEvent; - if ((nextvertex.x == lastvertex.x) && - (nextvertex.y == lastvertex.y)) - { - if (Log.Verbose) - { - Log.Instance.Warning("A duplicate vertex appeared and was ignored (ID " + nextvertex.id + ").", - "SweepLine.Triangulate().2"); - } - nextvertex.type = VertexType.UndeadVertex; - mesh.undeads++; - check4events = false; - } - else - { - lastvertex = nextvertex; - - splayroot = FrontLocate(splayroot, bottommost, nextvertex, ref searchtri, ref farrightflag); - - //bottommost.Copy(ref searchtri); - //farrightflag = false; - //while (!farrightflag && RightOfHyperbola(ref searchtri, nextvertex)) - //{ - // searchtri.OnextSelf(); - // farrightflag = searchtri.Equal(bottommost); - //} - - Check4DeadEvent(ref searchtri, eventheap, ref heapsize); - - searchtri.Copy(ref farrighttri); - searchtri.Sym(ref farlefttri); - mesh.MakeTriangle(ref lefttri); - mesh.MakeTriangle(ref righttri); - connectvertex = farrighttri.Dest(); - lefttri.SetOrg(connectvertex); - lefttri.SetDest(nextvertex); - righttri.SetOrg(nextvertex); - righttri.SetDest(connectvertex); - lefttri.Bond(ref righttri); - lefttri.Lnext(); - righttri.Lprev(); - lefttri.Bond(ref righttri); - lefttri.Lnext(); - righttri.Lprev(); - lefttri.Bond(ref farlefttri); - righttri.Bond(ref farrighttri); - if (!farrightflag && farrighttri.Equals(bottommost)) - { - lefttri.Copy(ref bottommost); - } - - if (randomnation(SAMPLERATE) == 0) - { - splayroot = SplayInsert(splayroot, lefttri, nextvertex); - } - else if (randomnation(SAMPLERATE) == 0) - { - righttri.Lnext(ref inserttri); - splayroot = SplayInsert(splayroot, inserttri, nextvertex); - } - } - } - - if (check4events) - { - leftvertex = farlefttri.Apex(); - midvertex = lefttri.Dest(); - rightvertex = lefttri.Apex(); - lefttest = predicates.CounterClockwise(leftvertex, midvertex, rightvertex); - if (lefttest > 0.0) - { - newevent = new SweepEvent(); - - newevent.xkey = xminextreme; - newevent.ykey = CircleTop(leftvertex, midvertex, rightvertex, lefttest); - newevent.otriEvent = lefttri; - HeapInsert(eventheap, heapsize, newevent); - heapsize++; - lefttri.SetOrg(new SweepEventVertex(newevent)); - } - leftvertex = righttri.Apex(); - midvertex = righttri.Org(); - rightvertex = farrighttri.Apex(); - righttest = predicates.CounterClockwise(leftvertex, midvertex, rightvertex); - if (righttest > 0.0) - { - newevent = new SweepEvent(); - - newevent.xkey = xminextreme; - newevent.ykey = CircleTop(leftvertex, midvertex, rightvertex, righttest); - newevent.otriEvent = farrighttri; - HeapInsert(eventheap, heapsize, newevent); - heapsize++; - farrighttri.SetOrg(new SweepEventVertex(newevent)); - } - } - } - - splaynodes.Clear(); - bottommost.Lprev(); - - this.mesh.hullsize = RemoveGhosts(ref bottommost); - - return this.mesh; - } - - #region Heap - - void HeapInsert(SweepEvent[] heap, int heapsize, SweepEvent newevent) - { - double eventx, eventy; - int eventnum; - int parent; - bool notdone; - - eventx = newevent.xkey; - eventy = newevent.ykey; - eventnum = heapsize; - notdone = eventnum > 0; - while (notdone) - { - parent = (eventnum - 1) >> 1; - if ((heap[parent].ykey < eventy) || - ((heap[parent].ykey == eventy) - && (heap[parent].xkey <= eventx))) - { - notdone = false; - } - else - { - heap[eventnum] = heap[parent]; - heap[eventnum].heapposition = eventnum; - - eventnum = parent; - notdone = eventnum > 0; - } - } - heap[eventnum] = newevent; - newevent.heapposition = eventnum; - } - - void Heapify(SweepEvent[] heap, int heapsize, int eventnum) - { - SweepEvent thisevent; - double eventx, eventy; - int leftchild, rightchild; - int smallest; - bool notdone; - - thisevent = heap[eventnum]; - eventx = thisevent.xkey; - eventy = thisevent.ykey; - leftchild = 2 * eventnum + 1; - notdone = leftchild < heapsize; - while (notdone) - { - if ((heap[leftchild].ykey < eventy) || - ((heap[leftchild].ykey == eventy) - && (heap[leftchild].xkey < eventx))) - { - smallest = leftchild; - } - else - { - smallest = eventnum; - } - rightchild = leftchild + 1; - if (rightchild < heapsize) - { - if ((heap[rightchild].ykey < heap[smallest].ykey) || - ((heap[rightchild].ykey == heap[smallest].ykey) - && (heap[rightchild].xkey < heap[smallest].xkey))) - { - smallest = rightchild; - } - } - if (smallest == eventnum) - { - notdone = false; - } - else - { - heap[eventnum] = heap[smallest]; - heap[eventnum].heapposition = eventnum; - heap[smallest] = thisevent; - thisevent.heapposition = smallest; - - eventnum = smallest; - leftchild = 2 * eventnum + 1; - notdone = leftchild < heapsize; - } - } - } - - void HeapDelete(SweepEvent[] heap, int heapsize, int eventnum) - { - SweepEvent moveevent; - double eventx, eventy; - int parent; - bool notdone; - - moveevent = heap[heapsize - 1]; - if (eventnum > 0) - { - eventx = moveevent.xkey; - eventy = moveevent.ykey; - do - { - parent = (eventnum - 1) >> 1; - if ((heap[parent].ykey < eventy) || - ((heap[parent].ykey == eventy) - && (heap[parent].xkey <= eventx))) - { - notdone = false; - } - else - { - heap[eventnum] = heap[parent]; - heap[eventnum].heapposition = eventnum; - - eventnum = parent; - notdone = eventnum > 0; - } - } while (notdone); - } - heap[eventnum] = moveevent; - moveevent.heapposition = eventnum; - Heapify(heap, heapsize - 1, eventnum); - } - - void CreateHeap(out SweepEvent[] eventheap, int size) - { - Vertex thisvertex; - int maxevents; - int i; - SweepEvent evt; - - maxevents = (3 * size) / 2; - eventheap = new SweepEvent[maxevents]; - - i = 0; - foreach (var v in mesh.vertices.Values) - { - thisvertex = v; - evt = new SweepEvent(); - evt.vertexEvent = thisvertex; - evt.xkey = thisvertex.x; - evt.ykey = thisvertex.y; - HeapInsert(eventheap, i++, evt); - } - } - - #endregion - - #region Splaytree - - SplayNode Splay(SplayNode splaytree, Point searchpoint, ref Otri searchtri) - { - SplayNode child, grandchild; - SplayNode lefttree, righttree; - SplayNode leftright; - Vertex checkvertex; - bool rightofroot, rightofchild; - - if (splaytree == null) - { - return null; - } - checkvertex = splaytree.keyedge.Dest(); - if (checkvertex == splaytree.keydest) - { - rightofroot = RightOfHyperbola(ref splaytree.keyedge, searchpoint); - if (rightofroot) - { - splaytree.keyedge.Copy(ref searchtri); - child = splaytree.rchild; - } - else - { - child = splaytree.lchild; - } - if (child == null) - { - return splaytree; - } - checkvertex = child.keyedge.Dest(); - if (checkvertex != child.keydest) - { - child = Splay(child, searchpoint, ref searchtri); - if (child == null) - { - if (rightofroot) - { - splaytree.rchild = null; - } - else - { - splaytree.lchild = null; - } - return splaytree; - } - } - rightofchild = RightOfHyperbola(ref child.keyedge, searchpoint); - if (rightofchild) - { - child.keyedge.Copy(ref searchtri); - grandchild = Splay(child.rchild, searchpoint, ref searchtri); - child.rchild = grandchild; - } - else - { - grandchild = Splay(child.lchild, searchpoint, ref searchtri); - child.lchild = grandchild; - } - if (grandchild == null) - { - if (rightofroot) - { - splaytree.rchild = child.lchild; - child.lchild = splaytree; - } - else - { - splaytree.lchild = child.rchild; - child.rchild = splaytree; - } - return child; - } - if (rightofchild) - { - if (rightofroot) - { - splaytree.rchild = child.lchild; - child.lchild = splaytree; - } - else - { - splaytree.lchild = grandchild.rchild; - grandchild.rchild = splaytree; - } - child.rchild = grandchild.lchild; - grandchild.lchild = child; - } - else - { - if (rightofroot) - { - splaytree.rchild = grandchild.lchild; - grandchild.lchild = splaytree; - } - else - { - splaytree.lchild = child.rchild; - child.rchild = splaytree; - } - child.lchild = grandchild.rchild; - grandchild.rchild = child; - } - return grandchild; - } - else - { - lefttree = Splay(splaytree.lchild, searchpoint, ref searchtri); - righttree = Splay(splaytree.rchild, searchpoint, ref searchtri); - - splaynodes.Remove(splaytree); - if (lefttree == null) - { - return righttree; - } - else if (righttree == null) - { - return lefttree; - } - else if (lefttree.rchild == null) - { - lefttree.rchild = righttree.lchild; - righttree.lchild = lefttree; - return righttree; - } - else if (righttree.lchild == null) - { - righttree.lchild = lefttree.rchild; - lefttree.rchild = righttree; - return lefttree; - } - else - { - // printf("Holy Toledo!!!\n"); - leftright = lefttree.rchild; - while (leftright.rchild != null) - { - leftright = leftright.rchild; - } - leftright.rchild = righttree; - return lefttree; - } - } - } - - SplayNode SplayInsert(SplayNode splayroot, Otri newkey, Point searchpoint) - { - SplayNode newsplaynode; - - newsplaynode = new SplayNode(); //poolalloc(m.splaynodes); - splaynodes.Add(newsplaynode); - newkey.Copy(ref newsplaynode.keyedge); - newsplaynode.keydest = newkey.Dest(); - if (splayroot == null) - { - newsplaynode.lchild = null; - newsplaynode.rchild = null; - } - else if (RightOfHyperbola(ref splayroot.keyedge, searchpoint)) - { - newsplaynode.lchild = splayroot; - newsplaynode.rchild = splayroot.rchild; - splayroot.rchild = null; - } - else - { - newsplaynode.lchild = splayroot.lchild; - newsplaynode.rchild = splayroot; - splayroot.lchild = null; - } - return newsplaynode; - } - - SplayNode FrontLocate(SplayNode splayroot, Otri bottommost, Vertex searchvertex, - ref Otri searchtri, ref bool farright) - { - bool farrightflag; - - bottommost.Copy(ref searchtri); - splayroot = Splay(splayroot, searchvertex, ref searchtri); - - farrightflag = false; - while (!farrightflag && RightOfHyperbola(ref searchtri, searchvertex)) - { - searchtri.Onext(); - farrightflag = searchtri.Equals(bottommost); - } - farright = farrightflag; - return splayroot; - } - - SplayNode CircleTopInsert(SplayNode splayroot, Otri newkey, - Vertex pa, Vertex pb, Vertex pc, double topy) - { - double ccwabc; - double xac, yac, xbc, ybc; - double aclen2, bclen2; - Point searchpoint = new Point(); // TODO: mesh.nextras - Otri dummytri = default(Otri); - - ccwabc = predicates.CounterClockwise(pa, pb, pc); - xac = pa.x - pc.x; - yac = pa.y - pc.y; - xbc = pb.x - pc.x; - ybc = pb.y - pc.y; - aclen2 = xac * xac + yac * yac; - bclen2 = xbc * xbc + ybc * ybc; - searchpoint.x = pc.x - (yac * bclen2 - ybc * aclen2) / (2.0 * ccwabc); - searchpoint.y = topy; - return SplayInsert(Splay(splayroot, searchpoint, ref dummytri), newkey, searchpoint); - } - - #endregion - - bool RightOfHyperbola(ref Otri fronttri, Point newsite) - { - Vertex leftvertex, rightvertex; - double dxa, dya, dxb, dyb; - - Statistic.HyperbolaCount++; - - leftvertex = fronttri.Dest(); - rightvertex = fronttri.Apex(); - if ((leftvertex.y < rightvertex.y) || - ((leftvertex.y == rightvertex.y) && - (leftvertex.x < rightvertex.x))) - { - if (newsite.x >= rightvertex.x) - { - return true; - } - } - else - { - if (newsite.x <= leftvertex.x) - { - return false; - } - } - dxa = leftvertex.x - newsite.x; - dya = leftvertex.y - newsite.y; - dxb = rightvertex.x - newsite.x; - dyb = rightvertex.y - newsite.y; - return dya * (dxb * dxb + dyb * dyb) > dyb * (dxa * dxa + dya * dya); - } - - double CircleTop(Vertex pa, Vertex pb, Vertex pc, double ccwabc) - { - double xac, yac, xbc, ybc, xab, yab; - double aclen2, bclen2, ablen2; - - Statistic.CircleTopCount++; - - xac = pa.x - pc.x; - yac = pa.y - pc.y; - xbc = pb.x - pc.x; - ybc = pb.y - pc.y; - xab = pa.x - pb.x; - yab = pa.y - pb.y; - aclen2 = xac * xac + yac * yac; - bclen2 = xbc * xbc + ybc * ybc; - ablen2 = xab * xab + yab * yab; - return pc.y + (xac * bclen2 - xbc * aclen2 + Math.Sqrt(aclen2 * bclen2 * ablen2)) / (2.0 * ccwabc); - } - - void Check4DeadEvent(ref Otri checktri, SweepEvent[] eventheap, ref int heapsize) - { - SweepEvent deadevent; - SweepEventVertex eventvertex; - int eventnum = -1; - - eventvertex = checktri.Org() as SweepEventVertex; - if (eventvertex != null) - { - deadevent = eventvertex.evt; - eventnum = deadevent.heapposition; - - HeapDelete(eventheap, heapsize, eventnum); - heapsize--; - checktri.SetOrg(null); - } - } - - /// - /// Removes ghost triangles. - /// - /// - /// Number of vertices on the hull. - int RemoveGhosts(ref Otri startghost) - { - Otri searchedge = default(Otri); - Otri dissolveedge = default(Otri); - Otri deadtriangle = default(Otri); - Vertex markorg; - int hullsize; - - bool noPoly = !mesh.behavior.Poly; - - var dummytri = mesh.dummytri; - - // Find an edge on the convex hull to start point location from. - startghost.Lprev(ref searchedge); - searchedge.Sym(); - dummytri.neighbors[0] = searchedge; - // Remove the bounding box and count the convex hull edges. - startghost.Copy(ref dissolveedge); - hullsize = 0; - do - { - hullsize++; - dissolveedge.Lnext(ref deadtriangle); - dissolveedge.Lprev(); - dissolveedge.Sym(); - - // If no PSLG is involved, set the boundary markers of all the vertices - // on the convex hull. If a PSLG is used, this step is done later. - if (noPoly) - { - // Watch out for the case where all the input vertices are collinear. - if (dissolveedge.tri.id != Mesh.DUMMY) - { - markorg = dissolveedge.Org(); - if (markorg.label == 0) - { - markorg.label = 1; - } - } - } - // Remove a bounding triangle from a convex hull triangle. - dissolveedge.Dissolve(dummytri); - // Find the next bounding triangle. - deadtriangle.Sym(ref dissolveedge); - - // Delete the bounding triangle. - mesh.TriangleDealloc(deadtriangle.tri); - } while (!dissolveedge.Equals(startghost)); - - return hullsize; - } - - #region Internal classes - - /// - /// A node in a heap used to store events for the sweepline Delaunay algorithm. - /// - /// - /// Only used in the sweepline algorithm. - /// - /// Nodes do not point directly to their parents or children in the heap. Instead, each - /// node knows its position in the heap, and can look up its parent and children in a - /// separate array. To distinguish site events from circle events, all circle events are - /// given an invalid (smaller than 'xmin') x-coordinate 'xkey'. - /// - class SweepEvent - { - public double xkey, ykey; // Coordinates of the event. - public Vertex vertexEvent; // Vertex event. - public Otri otriEvent; // Circle event. - public int heapposition; // Marks this event's position in the heap. - } - - /// - /// Introducing a new class which aggregates a sweep event is the easiest way - /// to handle the pointer magic of the original code (casting a sweep event - /// to vertex etc.). - /// - class SweepEventVertex : Vertex - { - public SweepEvent evt; - - public SweepEventVertex(SweepEvent e) - { - evt = e; - } - } - - /// - /// A node in the splay tree. - /// - /// - /// Only used in the sweepline algorithm. - /// - /// Each node holds an oriented ghost triangle that represents a boundary edge - /// of the growing triangulation. When a circle event covers two boundary edges - /// with a triangle, so that they are no longer boundary edges, those edges are - /// not immediately deleted from the tree; rather, they are lazily deleted when - /// they are next encountered. (Since only a random sample of boundary edges are - /// kept in the tree, lazy deletion is faster.) 'keydest' is used to verify that - /// a triangle is still the same as when it entered the splay tree; if it has - /// been rotated (due to a circle event), it no longer represents a boundary - /// edge and should be deleted. - /// - class SplayNode - { - public Otri keyedge; // Lprev of an edge on the front. - public Vertex keydest; // Used to verify that splay node is still live. - public SplayNode lchild, rchild; // Children in splay tree. - } - - #endregion - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing.Algorithm +{ + using System; + using System.Collections.Generic; + using TriangleNet.Topology; + using TriangleNet.Geometry; + using TriangleNet.Tools; + + /// + /// Builds a delaunay triangulation using the sweepline algorithm. + /// + public class SweepLine : ITriangulator + { + static int randomseed = 1; + static int SAMPLERATE = 10; + + static int randomnation(int choices) + { + randomseed = (randomseed * 1366 + 150889) % 714025; + return randomseed / (714025 / choices + 1); + } + + IPredicates predicates; + + Mesh mesh; + double xminextreme; // Nonexistent x value used as a flag in sweepline. + List splaynodes; + + public IMesh Triangulate(IList points, Configuration config) + { + predicates = config.Predicates(); + + mesh = new Mesh(config, points); + + // Nonexistent x value used as a flag to mark circle events in sweepline + // Delaunay algorithm. + xminextreme = 10 * mesh.bounds.Left - 9 * mesh.bounds.Right; + + SweepEvent[] eventheap; + + SweepEvent nextevent; + SweepEvent newevent; + SplayNode splayroot; + Otri bottommost = default(Otri); + Otri searchtri = default(Otri); + Otri fliptri; + Otri lefttri = default(Otri); + Otri righttri = default(Otri); + Otri farlefttri = default(Otri); + Otri farrighttri = default(Otri); + Otri inserttri = default(Otri); + Vertex firstvertex, secondvertex; + Vertex nextvertex, lastvertex; + Vertex connectvertex; + Vertex leftvertex, midvertex, rightvertex; + double lefttest, righttest; + int heapsize; + bool check4events, farrightflag = false; + + splaynodes = new List(); + splayroot = null; + + heapsize = points.Count; + CreateHeap(out eventheap, heapsize);//, out events, out freeevents); + + mesh.MakeTriangle(ref lefttri); + mesh.MakeTriangle(ref righttri); + lefttri.Bond(ref righttri); + lefttri.Lnext(); + righttri.Lprev(); + lefttri.Bond(ref righttri); + lefttri.Lnext(); + righttri.Lprev(); + lefttri.Bond(ref righttri); + firstvertex = eventheap[0].vertexEvent; + + HeapDelete(eventheap, heapsize, 0); + heapsize--; + do + { + if (heapsize == 0) + { + Log.Instance.Error("Input vertices are all identical.", "SweepLine.Triangulate()"); + throw new Exception("Input vertices are all identical."); + } + secondvertex = eventheap[0].vertexEvent; + HeapDelete(eventheap, heapsize, 0); + heapsize--; + if ((firstvertex.x == secondvertex.x) && + (firstvertex.y == secondvertex.y)) + { + if (Log.Verbose) + { + Log.Instance.Warning("A duplicate vertex appeared and was ignored (ID " + secondvertex.id + ").", + "SweepLine.Triangulate().1"); + } + secondvertex.type = VertexType.UndeadVertex; + mesh.undeads++; + } + } while ((firstvertex.x == secondvertex.x) && + (firstvertex.y == secondvertex.y)); + lefttri.SetOrg(firstvertex); + lefttri.SetDest(secondvertex); + righttri.SetOrg(secondvertex); + righttri.SetDest(firstvertex); + lefttri.Lprev(ref bottommost); + lastvertex = secondvertex; + + while (heapsize > 0) + { + nextevent = eventheap[0]; + HeapDelete(eventheap, heapsize, 0); + heapsize--; + check4events = true; + if (nextevent.xkey < mesh.bounds.Left) + { + fliptri = nextevent.otriEvent; + fliptri.Oprev(ref farlefttri); + Check4DeadEvent(ref farlefttri, eventheap, ref heapsize); + fliptri.Onext(ref farrighttri); + Check4DeadEvent(ref farrighttri, eventheap, ref heapsize); + + if (farlefttri.Equals(bottommost)) + { + fliptri.Lprev(ref bottommost); + } + mesh.Flip(ref fliptri); + fliptri.SetApex(null); + fliptri.Lprev(ref lefttri); + fliptri.Lnext(ref righttri); + lefttri.Sym(ref farlefttri); + + if (randomnation(SAMPLERATE) == 0) + { + fliptri.Sym(); + leftvertex = fliptri.Dest(); + midvertex = fliptri.Apex(); + rightvertex = fliptri.Org(); + splayroot = CircleTopInsert(splayroot, lefttri, leftvertex, midvertex, rightvertex, nextevent.ykey); + } + } + else + { + nextvertex = nextevent.vertexEvent; + if ((nextvertex.x == lastvertex.x) && + (nextvertex.y == lastvertex.y)) + { + if (Log.Verbose) + { + Log.Instance.Warning("A duplicate vertex appeared and was ignored (ID " + nextvertex.id + ").", + "SweepLine.Triangulate().2"); + } + nextvertex.type = VertexType.UndeadVertex; + mesh.undeads++; + check4events = false; + } + else + { + lastvertex = nextvertex; + + splayroot = FrontLocate(splayroot, bottommost, nextvertex, ref searchtri, ref farrightflag); + + //bottommost.Copy(ref searchtri); + //farrightflag = false; + //while (!farrightflag && RightOfHyperbola(ref searchtri, nextvertex)) + //{ + // searchtri.OnextSelf(); + // farrightflag = searchtri.Equal(bottommost); + //} + + Check4DeadEvent(ref searchtri, eventheap, ref heapsize); + + searchtri.Copy(ref farrighttri); + searchtri.Sym(ref farlefttri); + mesh.MakeTriangle(ref lefttri); + mesh.MakeTriangle(ref righttri); + connectvertex = farrighttri.Dest(); + lefttri.SetOrg(connectvertex); + lefttri.SetDest(nextvertex); + righttri.SetOrg(nextvertex); + righttri.SetDest(connectvertex); + lefttri.Bond(ref righttri); + lefttri.Lnext(); + righttri.Lprev(); + lefttri.Bond(ref righttri); + lefttri.Lnext(); + righttri.Lprev(); + lefttri.Bond(ref farlefttri); + righttri.Bond(ref farrighttri); + if (!farrightflag && farrighttri.Equals(bottommost)) + { + lefttri.Copy(ref bottommost); + } + + if (randomnation(SAMPLERATE) == 0) + { + splayroot = SplayInsert(splayroot, lefttri, nextvertex); + } + else if (randomnation(SAMPLERATE) == 0) + { + righttri.Lnext(ref inserttri); + splayroot = SplayInsert(splayroot, inserttri, nextvertex); + } + } + } + + if (check4events) + { + leftvertex = farlefttri.Apex(); + midvertex = lefttri.Dest(); + rightvertex = lefttri.Apex(); + lefttest = predicates.CounterClockwise(leftvertex, midvertex, rightvertex); + if (lefttest > 0.0) + { + newevent = new SweepEvent(); + + newevent.xkey = xminextreme; + newevent.ykey = CircleTop(leftvertex, midvertex, rightvertex, lefttest); + newevent.otriEvent = lefttri; + HeapInsert(eventheap, heapsize, newevent); + heapsize++; + lefttri.SetOrg(new SweepEventVertex(newevent)); + } + leftvertex = righttri.Apex(); + midvertex = righttri.Org(); + rightvertex = farrighttri.Apex(); + righttest = predicates.CounterClockwise(leftvertex, midvertex, rightvertex); + if (righttest > 0.0) + { + newevent = new SweepEvent(); + + newevent.xkey = xminextreme; + newevent.ykey = CircleTop(leftvertex, midvertex, rightvertex, righttest); + newevent.otriEvent = farrighttri; + HeapInsert(eventheap, heapsize, newevent); + heapsize++; + farrighttri.SetOrg(new SweepEventVertex(newevent)); + } + } + } + + splaynodes.Clear(); + bottommost.Lprev(); + + this.mesh.hullsize = RemoveGhosts(ref bottommost); + + return this.mesh; + } + + #region Heap + + void HeapInsert(SweepEvent[] heap, int heapsize, SweepEvent newevent) + { + double eventx, eventy; + int eventnum; + int parent; + bool notdone; + + eventx = newevent.xkey; + eventy = newevent.ykey; + eventnum = heapsize; + notdone = eventnum > 0; + while (notdone) + { + parent = (eventnum - 1) >> 1; + if ((heap[parent].ykey < eventy) || + ((heap[parent].ykey == eventy) + && (heap[parent].xkey <= eventx))) + { + notdone = false; + } + else + { + heap[eventnum] = heap[parent]; + heap[eventnum].heapposition = eventnum; + + eventnum = parent; + notdone = eventnum > 0; + } + } + heap[eventnum] = newevent; + newevent.heapposition = eventnum; + } + + void Heapify(SweepEvent[] heap, int heapsize, int eventnum) + { + SweepEvent thisevent; + double eventx, eventy; + int leftchild, rightchild; + int smallest; + bool notdone; + + thisevent = heap[eventnum]; + eventx = thisevent.xkey; + eventy = thisevent.ykey; + leftchild = 2 * eventnum + 1; + notdone = leftchild < heapsize; + while (notdone) + { + if ((heap[leftchild].ykey < eventy) || + ((heap[leftchild].ykey == eventy) + && (heap[leftchild].xkey < eventx))) + { + smallest = leftchild; + } + else + { + smallest = eventnum; + } + rightchild = leftchild + 1; + if (rightchild < heapsize) + { + if ((heap[rightchild].ykey < heap[smallest].ykey) || + ((heap[rightchild].ykey == heap[smallest].ykey) + && (heap[rightchild].xkey < heap[smallest].xkey))) + { + smallest = rightchild; + } + } + if (smallest == eventnum) + { + notdone = false; + } + else + { + heap[eventnum] = heap[smallest]; + heap[eventnum].heapposition = eventnum; + heap[smallest] = thisevent; + thisevent.heapposition = smallest; + + eventnum = smallest; + leftchild = 2 * eventnum + 1; + notdone = leftchild < heapsize; + } + } + } + + void HeapDelete(SweepEvent[] heap, int heapsize, int eventnum) + { + SweepEvent moveevent; + double eventx, eventy; + int parent; + bool notdone; + + moveevent = heap[heapsize - 1]; + if (eventnum > 0) + { + eventx = moveevent.xkey; + eventy = moveevent.ykey; + do + { + parent = (eventnum - 1) >> 1; + if ((heap[parent].ykey < eventy) || + ((heap[parent].ykey == eventy) + && (heap[parent].xkey <= eventx))) + { + notdone = false; + } + else + { + heap[eventnum] = heap[parent]; + heap[eventnum].heapposition = eventnum; + + eventnum = parent; + notdone = eventnum > 0; + } + } while (notdone); + } + heap[eventnum] = moveevent; + moveevent.heapposition = eventnum; + Heapify(heap, heapsize - 1, eventnum); + } + + void CreateHeap(out SweepEvent[] eventheap, int size) + { + Vertex thisvertex; + int maxevents; + int i; + SweepEvent evt; + + maxevents = (3 * size) / 2; + eventheap = new SweepEvent[maxevents]; + + i = 0; + foreach (var v in mesh.vertices.Values) + { + thisvertex = v; + evt = new SweepEvent(); + evt.vertexEvent = thisvertex; + evt.xkey = thisvertex.x; + evt.ykey = thisvertex.y; + HeapInsert(eventheap, i++, evt); + } + } + + #endregion + + #region Splaytree + + SplayNode Splay(SplayNode splaytree, Point searchpoint, ref Otri searchtri) + { + SplayNode child, grandchild; + SplayNode lefttree, righttree; + SplayNode leftright; + Vertex checkvertex; + bool rightofroot, rightofchild; + + if (splaytree == null) + { + return null; + } + checkvertex = splaytree.keyedge.Dest(); + if (checkvertex == splaytree.keydest) + { + rightofroot = RightOfHyperbola(ref splaytree.keyedge, searchpoint); + if (rightofroot) + { + splaytree.keyedge.Copy(ref searchtri); + child = splaytree.rchild; + } + else + { + child = splaytree.lchild; + } + if (child == null) + { + return splaytree; + } + checkvertex = child.keyedge.Dest(); + if (checkvertex != child.keydest) + { + child = Splay(child, searchpoint, ref searchtri); + if (child == null) + { + if (rightofroot) + { + splaytree.rchild = null; + } + else + { + splaytree.lchild = null; + } + return splaytree; + } + } + rightofchild = RightOfHyperbola(ref child.keyedge, searchpoint); + if (rightofchild) + { + child.keyedge.Copy(ref searchtri); + grandchild = Splay(child.rchild, searchpoint, ref searchtri); + child.rchild = grandchild; + } + else + { + grandchild = Splay(child.lchild, searchpoint, ref searchtri); + child.lchild = grandchild; + } + if (grandchild == null) + { + if (rightofroot) + { + splaytree.rchild = child.lchild; + child.lchild = splaytree; + } + else + { + splaytree.lchild = child.rchild; + child.rchild = splaytree; + } + return child; + } + if (rightofchild) + { + if (rightofroot) + { + splaytree.rchild = child.lchild; + child.lchild = splaytree; + } + else + { + splaytree.lchild = grandchild.rchild; + grandchild.rchild = splaytree; + } + child.rchild = grandchild.lchild; + grandchild.lchild = child; + } + else + { + if (rightofroot) + { + splaytree.rchild = grandchild.lchild; + grandchild.lchild = splaytree; + } + else + { + splaytree.lchild = child.rchild; + child.rchild = splaytree; + } + child.lchild = grandchild.rchild; + grandchild.rchild = child; + } + return grandchild; + } + else + { + lefttree = Splay(splaytree.lchild, searchpoint, ref searchtri); + righttree = Splay(splaytree.rchild, searchpoint, ref searchtri); + + splaynodes.Remove(splaytree); + if (lefttree == null) + { + return righttree; + } + else if (righttree == null) + { + return lefttree; + } + else if (lefttree.rchild == null) + { + lefttree.rchild = righttree.lchild; + righttree.lchild = lefttree; + return righttree; + } + else if (righttree.lchild == null) + { + righttree.lchild = lefttree.rchild; + lefttree.rchild = righttree; + return lefttree; + } + else + { + // printf("Holy Toledo!!!\n"); + leftright = lefttree.rchild; + while (leftright.rchild != null) + { + leftright = leftright.rchild; + } + leftright.rchild = righttree; + return lefttree; + } + } + } + + SplayNode SplayInsert(SplayNode splayroot, Otri newkey, Point searchpoint) + { + SplayNode newsplaynode; + + newsplaynode = new SplayNode(); //poolalloc(m.splaynodes); + splaynodes.Add(newsplaynode); + newkey.Copy(ref newsplaynode.keyedge); + newsplaynode.keydest = newkey.Dest(); + if (splayroot == null) + { + newsplaynode.lchild = null; + newsplaynode.rchild = null; + } + else if (RightOfHyperbola(ref splayroot.keyedge, searchpoint)) + { + newsplaynode.lchild = splayroot; + newsplaynode.rchild = splayroot.rchild; + splayroot.rchild = null; + } + else + { + newsplaynode.lchild = splayroot.lchild; + newsplaynode.rchild = splayroot; + splayroot.lchild = null; + } + return newsplaynode; + } + + SplayNode FrontLocate(SplayNode splayroot, Otri bottommost, Vertex searchvertex, + ref Otri searchtri, ref bool farright) + { + bool farrightflag; + + bottommost.Copy(ref searchtri); + splayroot = Splay(splayroot, searchvertex, ref searchtri); + + farrightflag = false; + while (!farrightflag && RightOfHyperbola(ref searchtri, searchvertex)) + { + searchtri.Onext(); + farrightflag = searchtri.Equals(bottommost); + } + farright = farrightflag; + return splayroot; + } + + SplayNode CircleTopInsert(SplayNode splayroot, Otri newkey, + Vertex pa, Vertex pb, Vertex pc, double topy) + { + double ccwabc; + double xac, yac, xbc, ybc; + double aclen2, bclen2; + Point searchpoint = new Point(); // TODO: mesh.nextras + Otri dummytri = default(Otri); + + ccwabc = predicates.CounterClockwise(pa, pb, pc); + xac = pa.x - pc.x; + yac = pa.y - pc.y; + xbc = pb.x - pc.x; + ybc = pb.y - pc.y; + aclen2 = xac * xac + yac * yac; + bclen2 = xbc * xbc + ybc * ybc; + searchpoint.x = pc.x - (yac * bclen2 - ybc * aclen2) / (2.0 * ccwabc); + searchpoint.y = topy; + return SplayInsert(Splay(splayroot, searchpoint, ref dummytri), newkey, searchpoint); + } + + #endregion + + bool RightOfHyperbola(ref Otri fronttri, Point newsite) + { + Vertex leftvertex, rightvertex; + double dxa, dya, dxb, dyb; + + Statistic.HyperbolaCount++; + + leftvertex = fronttri.Dest(); + rightvertex = fronttri.Apex(); + if ((leftvertex.y < rightvertex.y) || + ((leftvertex.y == rightvertex.y) && + (leftvertex.x < rightvertex.x))) + { + if (newsite.x >= rightvertex.x) + { + return true; + } + } + else + { + if (newsite.x <= leftvertex.x) + { + return false; + } + } + dxa = leftvertex.x - newsite.x; + dya = leftvertex.y - newsite.y; + dxb = rightvertex.x - newsite.x; + dyb = rightvertex.y - newsite.y; + return dya * (dxb * dxb + dyb * dyb) > dyb * (dxa * dxa + dya * dya); + } + + double CircleTop(Vertex pa, Vertex pb, Vertex pc, double ccwabc) + { + double xac, yac, xbc, ybc, xab, yab; + double aclen2, bclen2, ablen2; + + Statistic.CircleTopCount++; + + xac = pa.x - pc.x; + yac = pa.y - pc.y; + xbc = pb.x - pc.x; + ybc = pb.y - pc.y; + xab = pa.x - pb.x; + yab = pa.y - pb.y; + aclen2 = xac * xac + yac * yac; + bclen2 = xbc * xbc + ybc * ybc; + ablen2 = xab * xab + yab * yab; + return pc.y + (xac * bclen2 - xbc * aclen2 + Math.Sqrt(aclen2 * bclen2 * ablen2)) / (2.0 * ccwabc); + } + + void Check4DeadEvent(ref Otri checktri, SweepEvent[] eventheap, ref int heapsize) + { + SweepEvent deadevent; + SweepEventVertex eventvertex; + int eventnum = -1; + + eventvertex = checktri.Org() as SweepEventVertex; + if (eventvertex != null) + { + deadevent = eventvertex.evt; + eventnum = deadevent.heapposition; + + HeapDelete(eventheap, heapsize, eventnum); + heapsize--; + checktri.SetOrg(null); + } + } + + /// + /// Removes ghost triangles. + /// + /// + /// Number of vertices on the hull. + int RemoveGhosts(ref Otri startghost) + { + Otri searchedge = default(Otri); + Otri dissolveedge = default(Otri); + Otri deadtriangle = default(Otri); + Vertex markorg; + int hullsize; + + bool noPoly = !mesh.behavior.Poly; + + var dummytri = mesh.dummytri; + + // Find an edge on the convex hull to start point location from. + startghost.Lprev(ref searchedge); + searchedge.Sym(); + dummytri.neighbors[0] = searchedge; + // Remove the bounding box and count the convex hull edges. + startghost.Copy(ref dissolveedge); + hullsize = 0; + do + { + hullsize++; + dissolveedge.Lnext(ref deadtriangle); + dissolveedge.Lprev(); + dissolveedge.Sym(); + + // If no PSLG is involved, set the boundary markers of all the vertices + // on the convex hull. If a PSLG is used, this step is done later. + if (noPoly) + { + // Watch out for the case where all the input vertices are collinear. + if (dissolveedge.tri.id != Mesh.DUMMY) + { + markorg = dissolveedge.Org(); + if (markorg.label == 0) + { + markorg.label = 1; + } + } + } + // Remove a bounding triangle from a convex hull triangle. + dissolveedge.Dissolve(dummytri); + // Find the next bounding triangle. + deadtriangle.Sym(ref dissolveedge); + + // Delete the bounding triangle. + mesh.TriangleDealloc(deadtriangle.tri); + } while (!dissolveedge.Equals(startghost)); + + return hullsize; + } + + #region Internal classes + + /// + /// A node in a heap used to store events for the sweepline Delaunay algorithm. + /// + /// + /// Only used in the sweepline algorithm. + /// + /// Nodes do not point directly to their parents or children in the heap. Instead, each + /// node knows its position in the heap, and can look up its parent and children in a + /// separate array. To distinguish site events from circle events, all circle events are + /// given an invalid (smaller than 'xmin') x-coordinate 'xkey'. + /// + class SweepEvent + { + public double xkey, ykey; // Coordinates of the event. + public Vertex vertexEvent; // Vertex event. + public Otri otriEvent; // Circle event. + public int heapposition; // Marks this event's position in the heap. + } + + /// + /// Introducing a new class which aggregates a sweep event is the easiest way + /// to handle the pointer magic of the original code (casting a sweep event + /// to vertex etc.). + /// + class SweepEventVertex : Vertex + { + public SweepEvent evt; + + public SweepEventVertex(SweepEvent e) + { + evt = e; + } + } + + /// + /// A node in the splay tree. + /// + /// + /// Only used in the sweepline algorithm. + /// + /// Each node holds an oriented ghost triangle that represents a boundary edge + /// of the growing triangulation. When a circle event covers two boundary edges + /// with a triangle, so that they are no longer boundary edges, those edges are + /// not immediately deleted from the tree; rather, they are lazily deleted when + /// they are next encountered. (Since only a random sample of boundary edges are + /// kept in the tree, lazy deletion is faster.) 'keydest' is used to verify that + /// a triangle is still the same as when it entered the splay tree; if it has + /// been rotated (due to a circle event), it no longer represents a boundary + /// edge and should be deleted. + /// + class SplayNode + { + public Otri keyedge; // Lprev of an edge on the front. + public Vertex keydest; // Used to verify that splay node is still live. + public SplayNode lchild, rchild; // Children in splay tree. + } + + #endregion + } +} diff --git a/Triangle.NET/Triangle/Meshing/ConstraintMesher.cs b/src/Triangle/Meshing/ConstraintMesher.cs similarity index 97% rename from Triangle.NET/Triangle/Meshing/ConstraintMesher.cs rename to src/Triangle/Meshing/ConstraintMesher.cs index ee42aaf..211e543 100644 --- a/Triangle.NET/Triangle/Meshing/ConstraintMesher.cs +++ b/src/Triangle/Meshing/ConstraintMesher.cs @@ -1,1228 +1,1225 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Meshing -{ - using System; - using System.Collections.Generic; - using TriangleNet.Geometry; - using TriangleNet.Logging; - using TriangleNet.Meshing.Iterators; - using TriangleNet.Topology; - - internal class ConstraintMesher - { - IPredicates predicates; - - Mesh mesh; - Behavior behavior; - TriangleLocator locator; - - List viri; - - ILog logger; - - public ConstraintMesher(Mesh mesh, Configuration config) - { - this.mesh = mesh; - this.predicates = config.Predicates(); - - this.behavior = mesh.behavior; - this.locator = mesh.locator; - - this.viri = new List(); - - logger = Log.Instance; - } - - - /// - /// Insert segments into the mesh. - /// - /// The polygon. - /// Constraint options. - public void Apply(IPolygon input, ConstraintOptions options) - { - behavior.Poly = input.Segments.Count > 0; - - // Copy constraint options - if (options != null) - { - behavior.ConformingDelaunay = options.ConformingDelaunay; - behavior.Convex = options.Convex; - behavior.NoBisect = options.SegmentSplitting; - - if (behavior.ConformingDelaunay) - { - behavior.Quality = true; - } - } - - //if (input.EdgeMarkers != null) - //{ - // behavior.UseBoundaryMarkers = true; - //} - - behavior.useRegions = input.Regions.Count > 0; - - // Ensure that no vertex can be mistaken for a triangular bounding - // box vertex in insertvertex(). - mesh.infvertex1 = null; - mesh.infvertex2 = null; - mesh.infvertex3 = null; - - if (behavior.useSegments) - { - // Segments will be introduced next. - mesh.checksegments = true; - - // Insert PSLG segments and/or convex hull segments. - FormSkeleton(input); - } - - if (behavior.Poly && (mesh.triangles.Count > 0)) - { - // Copy holes and regions - mesh.holes.AddRange(input.Holes); - mesh.regions.AddRange(input.Regions); - - // Carve out holes and concavities. - CarveHoles(); - } - } - - /// - /// Find the holes and infect them. Find the area constraints and infect - /// them. Infect the convex hull. Spread the infection and kill triangles. - /// Spread the area constraints. - /// - private void CarveHoles() - { - Otri searchtri = default(Otri); - Vertex searchorg, searchdest; - LocateResult intersect; - - Triangle[] regionTris = null; - - var dummytri = mesh.dummytri; - - if (!mesh.behavior.Convex) - { - // Mark as infected any unprotected triangles on the boundary. - // This is one way by which concavities are created. - InfectHull(); - } - - if (!mesh.behavior.NoHoles) - { - // Infect each triangle in which a hole lies. - foreach (var hole in mesh.holes) - { - // Ignore holes that aren't within the bounds of the mesh. - if (mesh.bounds.Contains(hole)) - { - // Start searching from some triangle on the outer boundary. - searchtri.tri = dummytri; - searchtri.orient = 0; - searchtri.Sym(); - // Ensure that the hole is to the left of this boundary edge; - // otherwise, locate() will falsely report that the hole - // falls within the starting triangle. - searchorg = searchtri.Org(); - searchdest = searchtri.Dest(); - if (predicates.CounterClockwise(searchorg, searchdest, hole) > 0.0) - { - // Find a triangle that contains the hole. - intersect = mesh.locator.Locate(hole, ref searchtri); - if ((intersect != LocateResult.Outside) && (!searchtri.IsInfected())) - { - // Infect the triangle. This is done by marking the triangle - // as infected and including the triangle in the virus pool. - searchtri.Infect(); - viri.Add(searchtri.tri); - } - } - } - } - } - - // Now, we have to find all the regions BEFORE we carve the holes, because locate() won't - // work when the triangulation is no longer convex. (Incidentally, this is the reason why - // regional attributes and area constraints can't be used when refining a preexisting mesh, - // which might not be convex; they can only be used with a freshly triangulated PSLG.) - if (mesh.regions.Count > 0) - { - int i = 0; - - regionTris = new Triangle[mesh.regions.Count]; - - // Find the starting triangle for each region. - foreach (var region in mesh.regions) - { - regionTris[i] = dummytri; - // Ignore region points that aren't within the bounds of the mesh. - if (mesh.bounds.Contains(region.point)) - { - // Start searching from some triangle on the outer boundary. - searchtri.tri = dummytri; - searchtri.orient = 0; - searchtri.Sym(); - // Ensure that the region point is to the left of this boundary - // edge; otherwise, locate() will falsely report that the - // region point falls within the starting triangle. - searchorg = searchtri.Org(); - searchdest = searchtri.Dest(); - if (predicates.CounterClockwise(searchorg, searchdest, region.point) > 0.0) - { - // Find a triangle that contains the region point. - intersect = mesh.locator.Locate(region.point, ref searchtri); - if ((intersect != LocateResult.Outside) && (!searchtri.IsInfected())) - { - // Record the triangle for processing after the - // holes have been carved. - regionTris[i] = searchtri.tri; - regionTris[i].label = region.id; - regionTris[i].area = region.area; - } - } - } - - i++; - } - } - - if (viri.Count > 0) - { - // Carve the holes and concavities. - Plague(); - } - - if (regionTris != null) - { - var iterator = new RegionIterator(mesh); - - for (int i = 0; i < regionTris.Length; i++) - { - if (regionTris[i].id != Mesh.DUMMY) - { - // Make sure the triangle under consideration still exists. - // It may have been eaten by the virus. - if (!Otri.IsDead(regionTris[i])) - { - // Apply one region's attribute and/or area constraint. - iterator.Process(regionTris[i]); - } - } - } - } - - // Free up memory (virus pool should be empty anyway). - viri.Clear(); - } - - /// - /// Create the segments of a triangulation, including PSLG segments and edges - /// on the convex hull. - /// - private void FormSkeleton(IPolygon input) - { - // The segment endpoints. - Vertex p, q; - - mesh.insegments = 0; - - if (behavior.Poly) - { - // If the input vertices are collinear, there is no triangulation, - // so don't try to insert segments. - if (mesh.triangles.Count == 0) - { - return; - } - - // If segments are to be inserted, compute a mapping - // from vertices to triangles. - if (input.Segments.Count > 0) - { - mesh.MakeVertexMap(); - } - - // Read and insert the segments. - foreach (var seg in input.Segments) - { - mesh.insegments++; - - p = seg.GetVertex(0); - q = seg.GetVertex(1); - - if ((p.x == q.x) && (p.y == q.y)) - { - if (Log.Verbose) - { - logger.Warning("Endpoints of segment (IDs " + p.id + "/" + q.id + ") are coincident.", - "Mesh.FormSkeleton()"); - } - } - else - { - InsertSegment(p, q, seg.Label); - } - } - } - - if (behavior.Convex || !behavior.Poly) - { - // Enclose the convex hull with subsegments. - MarkHull(); - } - } - - #region Carving holes - - /// - /// Virally infect all of the triangles of the convex hull that are not - /// protected by subsegments. Where there are subsegments, set boundary - /// markers as appropriate. - /// - private void InfectHull() - { - Otri hulltri = default(Otri); - Otri nexttri = default(Otri); - Otri starttri = default(Otri); - Osub hullsubseg = default(Osub); - Vertex horg, hdest; - - var dummytri = mesh.dummytri; - - // Find a triangle handle on the hull. - hulltri.tri = dummytri; - hulltri.orient = 0; - hulltri.Sym(); - - // Remember where we started so we know when to stop. - hulltri.Copy(ref starttri); - // Go once counterclockwise around the convex hull. - do - { - // Ignore triangles that are already infected. - if (!hulltri.IsInfected()) - { - // Is the triangle protected by a subsegment? - hulltri.Pivot(ref hullsubseg); - if (hullsubseg.seg.hash == Mesh.DUMMY) - { - // The triangle is not protected; infect it. - if (!hulltri.IsInfected()) - { - hulltri.Infect(); - viri.Add(hulltri.tri); - } - } - else - { - // The triangle is protected; set boundary markers if appropriate. - if (hullsubseg.seg.boundary == 0) - { - hullsubseg.seg.boundary = 1; - horg = hulltri.Org(); - hdest = hulltri.Dest(); - if (horg.label == 0) - { - horg.label = 1; - } - if (hdest.label == 0) - { - hdest.label = 1; - } - } - } - } - // To find the next hull edge, go clockwise around the next vertex. - hulltri.Lnext(); - hulltri.Oprev(ref nexttri); - while (nexttri.tri.id != Mesh.DUMMY) - { - nexttri.Copy(ref hulltri); - hulltri.Oprev(ref nexttri); - } - - } while (!hulltri.Equals(starttri)); - } - - /// - /// Spread the virus from all infected triangles to any neighbors not - /// protected by subsegments. Delete all infected triangles. - /// - /// - /// This is the procedure that actually creates holes and concavities. - /// - /// This procedure operates in two phases. The first phase identifies all - /// the triangles that will die, and marks them as infected. They are - /// marked to ensure that each triangle is added to the virus pool only - /// once, so the procedure will terminate. - /// - /// The second phase actually eliminates the infected triangles. It also - /// eliminates orphaned vertices. - /// - void Plague() - { - Otri testtri = default(Otri); - Otri neighbor = default(Otri); - Osub neighborsubseg = default(Osub); - Vertex testvertex; - Vertex norg, ndest; - - var dummysub = mesh.dummysub; - var dummytri = mesh.dummytri; - - bool killorg; - - // Loop through all the infected triangles, spreading the virus to - // their neighbors, then to their neighbors' neighbors. - for (int i = 0; i < viri.Count; i++) - { - // WARNING: Don't use foreach, mesh.viri list may get modified. - - testtri.tri = viri[i]; - // A triangle is marked as infected by messing with one of its pointers - // to subsegments, setting it to an illegal value. Hence, we have to - // temporarily uninfect this triangle so that we can examine its - // adjacent subsegments. - // TODO: Not true in the C# version (so we could skip this). - testtri.Uninfect(); - - // Check each of the triangle's three neighbors. - for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) - { - // Find the neighbor. - testtri.Sym(ref neighbor); - // Check for a subsegment between the triangle and its neighbor. - testtri.Pivot(ref neighborsubseg); - // Check if the neighbor is nonexistent or already infected. - if ((neighbor.tri.id == Mesh.DUMMY) || neighbor.IsInfected()) - { - if (neighborsubseg.seg.hash != Mesh.DUMMY) - { - // There is a subsegment separating the triangle from its - // neighbor, but both triangles are dying, so the subsegment - // dies too. - mesh.SubsegDealloc(neighborsubseg.seg); - if (neighbor.tri.id != Mesh.DUMMY) - { - // Make sure the subsegment doesn't get deallocated again - // later when the infected neighbor is visited. - neighbor.Uninfect(); - neighbor.SegDissolve(dummysub); - neighbor.Infect(); - } - } - } - else - { // The neighbor exists and is not infected. - if (neighborsubseg.seg.hash == Mesh.DUMMY) - { - // There is no subsegment protecting the neighbor, so - // the neighbor becomes infected. - neighbor.Infect(); - // Ensure that the neighbor's neighbors will be infected. - viri.Add(neighbor.tri); - } - else - { - // The neighbor is protected by a subsegment. - // Remove this triangle from the subsegment. - neighborsubseg.TriDissolve(dummytri); - // The subsegment becomes a boundary. Set markers accordingly. - if (neighborsubseg.seg.boundary == 0) - { - neighborsubseg.seg.boundary = 1; - } - norg = neighbor.Org(); - ndest = neighbor.Dest(); - if (norg.label == 0) - { - norg.label = 1; - } - if (ndest.label == 0) - { - ndest.label = 1; - } - } - } - } - // Remark the triangle as infected, so it doesn't get added to the - // virus pool again. - testtri.Infect(); - } - - foreach (var virus in viri) - { - testtri.tri = virus; - - // Check each of the three corners of the triangle for elimination. - // This is done by walking around each vertex, checking if it is - // still connected to at least one live triangle. - for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) - { - testvertex = testtri.Org(); - // Check if the vertex has already been tested. - if (testvertex != null) - { - killorg = true; - // Mark the corner of the triangle as having been tested. - testtri.SetOrg(null); - // Walk counterclockwise about the vertex. - testtri.Onext(ref neighbor); - // Stop upon reaching a boundary or the starting triangle. - while ((neighbor.tri.id != Mesh.DUMMY) && - (!neighbor.Equals(testtri))) - { - if (neighbor.IsInfected()) - { - // Mark the corner of this triangle as having been tested. - neighbor.SetOrg(null); - } - else - { - // A live triangle. The vertex survives. - killorg = false; - } - // Walk counterclockwise about the vertex. - neighbor.Onext(); - } - // If we reached a boundary, we must walk clockwise as well. - if (neighbor.tri.id == Mesh.DUMMY) - { - // Walk clockwise about the vertex. - testtri.Oprev(ref neighbor); - // Stop upon reaching a boundary. - while (neighbor.tri.id != Mesh.DUMMY) - { - if (neighbor.IsInfected()) - { - // Mark the corner of this triangle as having been tested. - neighbor.SetOrg(null); - } - else - { - // A live triangle. The vertex survives. - killorg = false; - } - // Walk clockwise about the vertex. - neighbor.Oprev(); - } - } - if (killorg) - { - // Deleting vertex - testvertex.type = VertexType.UndeadVertex; - mesh.undeads++; - } - } - } - - // Record changes in the number of boundary edges, and disconnect - // dead triangles from their neighbors. - for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) - { - testtri.Sym(ref neighbor); - if (neighbor.tri.id == Mesh.DUMMY) - { - // There is no neighboring triangle on this edge, so this edge - // is a boundary edge. This triangle is being deleted, so this - // boundary edge is deleted. - mesh.hullsize--; - } - else - { - // Disconnect the triangle from its neighbor. - neighbor.Dissolve(dummytri); - // There is a neighboring triangle on this edge, so this edge - // becomes a boundary edge when this triangle is deleted. - mesh.hullsize++; - } - } - // Return the dead triangle to the pool of triangles. - mesh.TriangleDealloc(testtri.tri); - } - - // Empty the virus pool. - viri.Clear(); - } - - #endregion - - #region Segment insertion - - /// - /// Find the first triangle on the path from one point to another. - /// - /// - /// - /// - /// The return value notes whether the destination or apex of the found - /// triangle is collinear with the two points in question. - /// - /// Finds the triangle that intersects a line segment drawn from the - /// origin of 'searchtri' to the point 'searchpoint', and returns the result - /// in 'searchtri'. The origin of 'searchtri' does not change, even though - /// the triangle returned may differ from the one passed in. This routine - /// is used to find the direction to move in to get from one point to - /// another. - /// - private FindDirectionResult FindDirection(ref Otri searchtri, Vertex searchpoint) - { - Otri checktri = default(Otri); - Vertex startvertex; - Vertex leftvertex, rightvertex; - double leftccw, rightccw; - bool leftflag, rightflag; - - startvertex = searchtri.Org(); - rightvertex = searchtri.Dest(); - leftvertex = searchtri.Apex(); - // Is 'searchpoint' to the left? - leftccw = predicates.CounterClockwise(searchpoint, startvertex, leftvertex); - leftflag = leftccw > 0.0; - // Is 'searchpoint' to the right? - rightccw = predicates.CounterClockwise(startvertex, searchpoint, rightvertex); - rightflag = rightccw > 0.0; - if (leftflag && rightflag) - { - // 'searchtri' faces directly away from 'searchpoint'. We could go left - // or right. Ask whether it's a triangle or a boundary on the left. - searchtri.Onext(ref checktri); - if (checktri.tri.id == Mesh.DUMMY) - { - leftflag = false; - } - else - { - rightflag = false; - } - } - while (leftflag) - { - // Turn left until satisfied. - searchtri.Onext(); - if (searchtri.tri.id == Mesh.DUMMY) - { - logger.Error("Unable to find a triangle on path.", "Mesh.FindDirection().1"); - throw new Exception("Unable to find a triangle on path."); - } - leftvertex = searchtri.Apex(); - rightccw = leftccw; - leftccw = predicates.CounterClockwise(searchpoint, startvertex, leftvertex); - leftflag = leftccw > 0.0; - } - while (rightflag) - { - // Turn right until satisfied. - searchtri.Oprev(); - if (searchtri.tri.id == Mesh.DUMMY) - { - logger.Error("Unable to find a triangle on path.", "Mesh.FindDirection().2"); - throw new Exception("Unable to find a triangle on path."); - } - rightvertex = searchtri.Dest(); - leftccw = rightccw; - rightccw = predicates.CounterClockwise(startvertex, searchpoint, rightvertex); - rightflag = rightccw > 0.0; - } - if (leftccw == 0.0) - { - return FindDirectionResult.Leftcollinear; - } - else if (rightccw == 0.0) - { - return FindDirectionResult.Rightcollinear; - } - else - { - return FindDirectionResult.Within; - } - } - - /// - /// Find the intersection of an existing segment and a segment that is being - /// inserted. Insert a vertex at the intersection, splitting an existing subsegment. - /// - /// - /// - /// - /// - /// The segment being inserted connects the apex of splittri to endpoint2. - /// splitsubseg is the subsegment being split, and MUST adjoin splittri. - /// Hence, endpoints of the subsegment being split are the origin and - /// destination of splittri. - /// - /// On completion, splittri is a handle having the newly inserted - /// intersection point as its origin, and endpoint1 as its destination. - /// - private void SegmentIntersection(ref Otri splittri, ref Osub splitsubseg, Vertex endpoint2) - { - Osub opposubseg = default(Osub); - Vertex endpoint1; - Vertex torg, tdest; - Vertex leftvertex, rightvertex; - Vertex newvertex; - InsertVertexResult success; - - var dummysub = mesh.dummysub; - - double ex, ey; - double tx, ty; - double etx, ety; - double split, denom; - - // Find the other three segment endpoints. - endpoint1 = splittri.Apex(); - torg = splittri.Org(); - tdest = splittri.Dest(); - // Segment intersection formulae; see the Antonio reference. - tx = tdest.x - torg.x; - ty = tdest.y - torg.y; - ex = endpoint2.x - endpoint1.x; - ey = endpoint2.y - endpoint1.y; - etx = torg.x - endpoint2.x; - ety = torg.y - endpoint2.y; - denom = ty * ex - tx * ey; - if (denom == 0.0) - { - logger.Error("Attempt to find intersection of parallel segments.", - "Mesh.SegmentIntersection()"); - throw new Exception("Attempt to find intersection of parallel segments."); - } - split = (ey * etx - ex * ety) / denom; - - // Create the new vertex. - newvertex = new Vertex( - torg.x + split * (tdest.x - torg.x), - torg.y + split * (tdest.y - torg.y), - splitsubseg.seg.boundary -#if USE_ATTRIBS - , mesh.nextras -#endif - ); - - newvertex.hash = mesh.hash_vtx++; - newvertex.id = newvertex.hash; - -#if USE_ATTRIBS - // Interpolate its attributes. - for (int i = 0; i < mesh.nextras; i++) - { - newvertex.attributes[i] = torg.attributes[i] + split * (tdest.attributes[i] - torg.attributes[i]); - } -#endif -#if USE_Z - newvertex.z = torg.z + split * (tdest.z - torg.z); -#endif - - mesh.vertices.Add(newvertex.hash, newvertex); - - // Insert the intersection vertex. This should always succeed. - success = mesh.InsertVertex(newvertex, ref splittri, ref splitsubseg, false, false); - if (success != InsertVertexResult.Successful) - { - logger.Error("Failure to split a segment.", "Mesh.SegmentIntersection()"); - throw new Exception("Failure to split a segment."); - } - // Record a triangle whose origin is the new vertex. - newvertex.tri = splittri; - if (mesh.steinerleft > 0) - { - mesh.steinerleft--; - } - - // Divide the segment into two, and correct the segment endpoints. - splitsubseg.Sym(); - splitsubseg.Pivot(ref opposubseg); - splitsubseg.Dissolve(dummysub); - opposubseg.Dissolve(dummysub); - do - { - splitsubseg.SetSegOrg(newvertex); - splitsubseg.Next(); - } while (splitsubseg.seg.hash != Mesh.DUMMY); - do - { - opposubseg.SetSegOrg(newvertex); - opposubseg.Next(); - } while (opposubseg.seg.hash != Mesh.DUMMY); - - // Inserting the vertex may have caused edge flips. We wish to rediscover - // the edge connecting endpoint1 to the new intersection vertex. - FindDirection(ref splittri, endpoint1); - - rightvertex = splittri.Dest(); - leftvertex = splittri.Apex(); - if ((leftvertex.x == endpoint1.x) && (leftvertex.y == endpoint1.y)) - { - splittri.Onext(); - } - else if ((rightvertex.x != endpoint1.x) || (rightvertex.y != endpoint1.y)) - { - logger.Error("Topological inconsistency after splitting a segment.", "Mesh.SegmentIntersection()"); - throw new Exception("Topological inconsistency after splitting a segment."); - } - // 'splittri' should have destination endpoint1. - } - - /// - /// Scout the first triangle on the path from one endpoint to another, and check - /// for completion (reaching the second endpoint), a collinear vertex, or the - /// intersection of two segments. - /// - /// - /// - /// - /// Returns true if the entire segment is successfully inserted, and false - /// if the job must be finished by ConstrainedEdge(). - /// - /// If the first triangle on the path has the second endpoint as its - /// destination or apex, a subsegment is inserted and the job is done. - /// - /// If the first triangle on the path has a destination or apex that lies on - /// the segment, a subsegment is inserted connecting the first endpoint to - /// the collinear vertex, and the search is continued from the collinear - /// vertex. - /// - /// If the first triangle on the path has a subsegment opposite its origin, - /// then there is a segment that intersects the segment being inserted. - /// Their intersection vertex is inserted, splitting the subsegment. - /// - private bool ScoutSegment(ref Otri searchtri, Vertex endpoint2, int newmark) - { - Otri crosstri = default(Otri); - Osub crosssubseg = default(Osub); - Vertex leftvertex, rightvertex; - FindDirectionResult collinear; - - collinear = FindDirection(ref searchtri, endpoint2); - rightvertex = searchtri.Dest(); - leftvertex = searchtri.Apex(); - if (((leftvertex.x == endpoint2.x) && (leftvertex.y == endpoint2.y)) || - ((rightvertex.x == endpoint2.x) && (rightvertex.y == endpoint2.y))) - { - // The segment is already an edge in the mesh. - if ((leftvertex.x == endpoint2.x) && (leftvertex.y == endpoint2.y)) - { - searchtri.Lprev(); - } - // Insert a subsegment, if there isn't already one there. - mesh.InsertSubseg(ref searchtri, newmark); - return true; - } - else if (collinear == FindDirectionResult.Leftcollinear) - { - // We've collided with a vertex between the segment's endpoints. - // Make the collinear vertex be the triangle's origin. - searchtri.Lprev(); - mesh.InsertSubseg(ref searchtri, newmark); - // Insert the remainder of the segment. - return ScoutSegment(ref searchtri, endpoint2, newmark); - } - else if (collinear == FindDirectionResult.Rightcollinear) - { - // We've collided with a vertex between the segment's endpoints. - mesh.InsertSubseg(ref searchtri, newmark); - // Make the collinear vertex be the triangle's origin. - searchtri.Lnext(); - // Insert the remainder of the segment. - return ScoutSegment(ref searchtri, endpoint2, newmark); - } - else - { - searchtri.Lnext(ref crosstri); - crosstri.Pivot(ref crosssubseg); - // Check for a crossing segment. - if (crosssubseg.seg.hash == Mesh.DUMMY) - { - return false; - } - else - { - // Insert a vertex at the intersection. - SegmentIntersection(ref crosstri, ref crosssubseg, endpoint2); - crosstri.Copy(ref searchtri); - mesh.InsertSubseg(ref searchtri, newmark); - // Insert the remainder of the segment. - return ScoutSegment(ref searchtri, endpoint2, newmark); - } - } - } - - /// - /// Enforce the Delaunay condition at an edge, fanning out recursively from - /// an existing vertex. Pay special attention to stacking inverted triangles. - /// - /// - /// Indicates whether or not fixuptri is to the left of - /// the segment being inserted. (Imagine that the segment is pointing up from - /// endpoint1 to endpoint2.) - /// - /// This is a support routine for inserting segments into a constrained - /// Delaunay triangulation. - /// - /// The origin of fixuptri is treated as if it has just been inserted, and - /// the local Delaunay condition needs to be enforced. It is only enforced - /// in one sector, however, that being the angular range defined by - /// fixuptri. - /// - /// This routine also needs to make decisions regarding the "stacking" of - /// triangles. (Read the description of ConstrainedEdge() below before - /// reading on here, so you understand the algorithm.) If the position of - /// the new vertex (the origin of fixuptri) indicates that the vertex before - /// it on the polygon is a reflex vertex, then "stack" the triangle by - /// doing nothing. (fixuptri is an inverted triangle, which is how stacked - /// triangles are identified.) - /// - /// Otherwise, check whether the vertex before that was a reflex vertex. - /// If so, perform an edge flip, thereby eliminating an inverted triangle - /// (popping it off the stack). The edge flip may result in the creation - /// of a new inverted triangle, depending on whether or not the new vertex - /// is visible to the vertex three edges behind on the polygon. - /// - /// If neither of the two vertices behind the new vertex are reflex - /// vertices, fixuptri and fartri, the triangle opposite it, are not - /// inverted; hence, ensure that the edge between them is locally Delaunay. - /// - private void DelaunayFixup(ref Otri fixuptri, bool leftside) - { - Otri neartri = default(Otri); - Otri fartri = default(Otri); - Osub faredge = default(Osub); - Vertex nearvertex, leftvertex, rightvertex, farvertex; - - fixuptri.Lnext(ref neartri); - neartri.Sym(ref fartri); - // Check if the edge opposite the origin of fixuptri can be flipped. - if (fartri.tri.id == Mesh.DUMMY) - { - return; - } - neartri.Pivot(ref faredge); - if (faredge.seg.hash != Mesh.DUMMY) - { - return; - } - // Find all the relevant vertices. - nearvertex = neartri.Apex(); - leftvertex = neartri.Org(); - rightvertex = neartri.Dest(); - farvertex = fartri.Apex(); - // Check whether the previous polygon vertex is a reflex vertex. - if (leftside) - { - if (predicates.CounterClockwise(nearvertex, leftvertex, farvertex) <= 0.0) - { - // leftvertex is a reflex vertex too. Nothing can - // be done until a convex section is found. - return; - } - } - else - { - if (predicates.CounterClockwise(farvertex, rightvertex, nearvertex) <= 0.0) - { - // rightvertex is a reflex vertex too. Nothing can - // be done until a convex section is found. - return; - } - } - if (predicates.CounterClockwise(rightvertex, leftvertex, farvertex) > 0.0) - { - // fartri is not an inverted triangle, and farvertex is not a reflex - // vertex. As there are no reflex vertices, fixuptri isn't an - // inverted triangle, either. Hence, test the edge between the - // triangles to ensure it is locally Delaunay. - if (predicates.InCircle(leftvertex, farvertex, rightvertex, nearvertex) <= 0.0) - { - return; - } - // Not locally Delaunay; go on to an edge flip. - } - // else fartri is inverted; remove it from the stack by flipping. - mesh.Flip(ref neartri); - fixuptri.Lprev(); // Restore the origin of fixuptri after the flip. - // Recursively process the two triangles that result from the flip. - DelaunayFixup(ref fixuptri, leftside); - DelaunayFixup(ref fartri, leftside); - } - - /// - /// Force a segment into a constrained Delaunay triangulation by deleting the - /// triangles it intersects, and triangulating the polygons that form on each - /// side of it. - /// - /// - /// - /// - /// - /// Generates a single subsegment connecting 'endpoint1' to 'endpoint2'. - /// The triangle 'starttri' has 'endpoint1' as its origin. 'newmark' is the - /// boundary marker of the segment. - /// - /// To insert a segment, every triangle whose interior intersects the - /// segment is deleted. The union of these deleted triangles is a polygon - /// (which is not necessarily monotone, but is close enough), which is - /// divided into two polygons by the new segment. This routine's task is - /// to generate the Delaunay triangulation of these two polygons. - /// - /// You might think of this routine's behavior as a two-step process. The - /// first step is to walk from endpoint1 to endpoint2, flipping each edge - /// encountered. This step creates a fan of edges connected to endpoint1, - /// including the desired edge to endpoint2. The second step enforces the - /// Delaunay condition on each side of the segment in an incremental manner: - /// proceeding along the polygon from endpoint1 to endpoint2 (this is done - /// independently on each side of the segment), each vertex is "enforced" - /// as if it had just been inserted, but affecting only the previous - /// vertices. The result is the same as if the vertices had been inserted - /// in the order they appear on the polygon, so the result is Delaunay. - /// - /// In truth, ConstrainedEdge() interleaves these two steps. The procedure - /// walks from endpoint1 to endpoint2, and each time an edge is encountered - /// and flipped, the newly exposed vertex (at the far end of the flipped - /// edge) is "enforced" upon the previously flipped edges, usually affecting - /// only one side of the polygon (depending upon which side of the segment - /// the vertex falls on). - /// - /// The algorithm is complicated by the need to handle polygons that are not - /// convex. Although the polygon is not necessarily monotone, it can be - /// triangulated in a manner similar to the stack-based algorithms for - /// monotone polygons. For each reflex vertex (local concavity) of the - /// polygon, there will be an inverted triangle formed by one of the edge - /// flips. (An inverted triangle is one with negative area - that is, its - /// vertices are arranged in clockwise order - and is best thought of as a - /// wrinkle in the fabric of the mesh.) Each inverted triangle can be - /// thought of as a reflex vertex pushed on the stack, waiting to be fixed - /// later. - /// - /// A reflex vertex is popped from the stack when a vertex is inserted that - /// is visible to the reflex vertex. (However, if the vertex behind the - /// reflex vertex is not visible to the reflex vertex, a new inverted - /// triangle will take its place on the stack.) These details are handled - /// by the DelaunayFixup() routine above. - /// - private void ConstrainedEdge(ref Otri starttri, Vertex endpoint2, int newmark) - { - Otri fixuptri = default(Otri), fixuptri2 = default(Otri); - Osub crosssubseg = default(Osub); - Vertex endpoint1; - Vertex farvertex; - double area; - bool collision; - bool done; - - endpoint1 = starttri.Org(); - starttri.Lnext(ref fixuptri); - mesh.Flip(ref fixuptri); - // 'collision' indicates whether we have found a vertex directly - // between endpoint1 and endpoint2. - collision = false; - done = false; - do - { - farvertex = fixuptri.Org(); - // 'farvertex' is the extreme point of the polygon we are "digging" - // to get from endpoint1 to endpoint2. - if ((farvertex.x == endpoint2.x) && (farvertex.y == endpoint2.y)) - { - fixuptri.Oprev(ref fixuptri2); - // Enforce the Delaunay condition around endpoint2. - DelaunayFixup(ref fixuptri, false); - DelaunayFixup(ref fixuptri2, true); - done = true; - } - else - { - // Check whether farvertex is to the left or right of the segment being - // inserted, to decide which edge of fixuptri to dig through next. - area = predicates.CounterClockwise(endpoint1, endpoint2, farvertex); - if (area == 0.0) - { - // We've collided with a vertex between endpoint1 and endpoint2. - collision = true; - fixuptri.Oprev(ref fixuptri2); - // Enforce the Delaunay condition around farvertex. - DelaunayFixup(ref fixuptri, false); - DelaunayFixup(ref fixuptri2, true); - done = true; - } - else - { - if (area > 0.0) - { - // farvertex is to the left of the segment. - fixuptri.Oprev(ref fixuptri2); - // Enforce the Delaunay condition around farvertex, on the - // left side of the segment only. - DelaunayFixup(ref fixuptri2, true); - // Flip the edge that crosses the segment. After the edge is - // flipped, one of its endpoints is the fan vertex, and the - // destination of fixuptri is the fan vertex. - fixuptri.Lprev(); - } - else - { - // farvertex is to the right of the segment. - DelaunayFixup(ref fixuptri, false); - // Flip the edge that crosses the segment. After the edge is - // flipped, one of its endpoints is the fan vertex, and the - // destination of fixuptri is the fan vertex. - fixuptri.Oprev(); - } - // Check for two intersecting segments. - fixuptri.Pivot(ref crosssubseg); - if (crosssubseg.seg.hash == Mesh.DUMMY) - { - mesh.Flip(ref fixuptri); // May create inverted triangle at left. - } - else - { - // We've collided with a segment between endpoint1 and endpoint2. - collision = true; - // Insert a vertex at the intersection. - SegmentIntersection(ref fixuptri, ref crosssubseg, endpoint2); - done = true; - } - } - } - } while (!done); - // Insert a subsegment to make the segment permanent. - mesh.InsertSubseg(ref fixuptri, newmark); - // If there was a collision with an interceding vertex, install another - // segment connecting that vertex with endpoint2. - if (collision) - { - // Insert the remainder of the segment. - if (!ScoutSegment(ref fixuptri, endpoint2, newmark)) - { - ConstrainedEdge(ref fixuptri, endpoint2, newmark); - } - } - } - - /// - /// Insert a PSLG segment into a triangulation. - /// - /// - /// - /// - private void InsertSegment(Vertex endpoint1, Vertex endpoint2, int newmark) - { - Otri searchtri1 = default(Otri), searchtri2 = default(Otri); - Vertex checkvertex = null; - - var dummytri = mesh.dummytri; - - // Find a triangle whose origin is the segment's first endpoint. - searchtri1 = endpoint1.tri; - if (searchtri1.tri != null) - { - checkvertex = searchtri1.Org(); - } - - if (checkvertex != endpoint1) - { - // Find a boundary triangle to search from. - searchtri1.tri = dummytri; - searchtri1.orient = 0; - searchtri1.Sym(); - // Search for the segment's first endpoint by point location. - if (locator.Locate(endpoint1, ref searchtri1) != LocateResult.OnVertex) - { - logger.Error("Unable to locate PSLG vertex in triangulation.", "Mesh.InsertSegment().1"); - throw new Exception("Unable to locate PSLG vertex in triangulation."); - } - } - // Remember this triangle to improve subsequent point location. - locator.Update(ref searchtri1); - - // Scout the beginnings of a path from the first endpoint - // toward the second. - if (ScoutSegment(ref searchtri1, endpoint2, newmark)) - { - // The segment was easily inserted. - return; - } - // The first endpoint may have changed if a collision with an intervening - // vertex on the segment occurred. - endpoint1 = searchtri1.Org(); - - // Find a triangle whose origin is the segment's second endpoint. - checkvertex = null; - searchtri2 = endpoint2.tri; - if (searchtri2.tri != null) - { - checkvertex = searchtri2.Org(); - } - if (checkvertex != endpoint2) - { - // Find a boundary triangle to search from. - searchtri2.tri = dummytri; - searchtri2.orient = 0; - searchtri2.Sym(); - // Search for the segment's second endpoint by point location. - if (locator.Locate(endpoint2, ref searchtri2) != LocateResult.OnVertex) - { - logger.Error("Unable to locate PSLG vertex in triangulation.", "Mesh.InsertSegment().2"); - throw new Exception("Unable to locate PSLG vertex in triangulation."); - } - } - // Remember this triangle to improve subsequent point location. - locator.Update(ref searchtri2); - // Scout the beginnings of a path from the second endpoint - // toward the first. - if (ScoutSegment(ref searchtri2, endpoint1, newmark)) - { - // The segment was easily inserted. - return; - } - // The second endpoint may have changed if a collision with an intervening - // vertex on the segment occurred. - endpoint2 = searchtri2.Org(); - - // Insert the segment directly into the triangulation. - ConstrainedEdge(ref searchtri1, endpoint2, newmark); - } - - /// - /// Cover the convex hull of a triangulation with subsegments. - /// - private void MarkHull() - { - Otri hulltri = default(Otri); - Otri nexttri = default(Otri); - Otri starttri = default(Otri); - - // Find a triangle handle on the hull. - hulltri.tri = mesh.dummytri; - hulltri.orient = 0; - hulltri.Sym(); - // Remember where we started so we know when to stop. - hulltri.Copy(ref starttri); - // Go once counterclockwise around the convex hull. - do - { - // Create a subsegment if there isn't already one here. - mesh.InsertSubseg(ref hulltri, 1); - // To find the next hull edge, go clockwise around the next vertex. - hulltri.Lnext(); - hulltri.Oprev(ref nexttri); - while (nexttri.tri.id != Mesh.DUMMY) - { - nexttri.Copy(ref hulltri); - hulltri.Oprev(ref nexttri); - } - } while (!hulltri.Equals(starttri)); - } - - #endregion - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing +{ + using System; + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Meshing.Iterators; + using TriangleNet.Topology; + + internal class ConstraintMesher + { + IPredicates predicates; + + Mesh mesh; + Behavior behavior; + TriangleLocator locator; + + List viri; + + Log logger = Log.Instance; + + public ConstraintMesher(Mesh mesh, Configuration config) + { + this.mesh = mesh; + this.predicates = config.Predicates(); + + this.behavior = mesh.behavior; + this.locator = mesh.locator; + + this.viri = new List(); + } + + + /// + /// Insert segments into the mesh. + /// + /// The polygon. + /// Constraint options. + public void Apply(IPolygon input, ConstraintOptions options) + { + behavior.Poly = input.Segments.Count > 0; + + // Copy constraint options + if (options != null) + { + behavior.ConformingDelaunay = options.ConformingDelaunay; + behavior.Convex = options.Convex; + behavior.NoBisect = options.SegmentSplitting; + + if (behavior.ConformingDelaunay) + { + behavior.Quality = true; + } + } + + //if (input.EdgeMarkers != null) + //{ + // behavior.UseBoundaryMarkers = true; + //} + + behavior.useRegions = input.Regions.Count > 0; + + // Ensure that no vertex can be mistaken for a triangular bounding + // box vertex in insertvertex(). + mesh.infvertex1 = null; + mesh.infvertex2 = null; + mesh.infvertex3 = null; + + if (behavior.useSegments) + { + // Segments will be introduced next. + mesh.checksegments = true; + + // Insert PSLG segments and/or convex hull segments. + FormSkeleton(input); + } + + if (behavior.Poly && (mesh.triangles.Count > 0)) + { + // Copy holes and regions + mesh.holes.AddRange(input.Holes); + mesh.regions.AddRange(input.Regions); + + // Carve out holes and concavities. + CarveHoles(); + } + } + + /// + /// Find the holes and infect them. Find the area constraints and infect + /// them. Infect the convex hull. Spread the infection and kill triangles. + /// Spread the area constraints. + /// + private void CarveHoles() + { + Otri searchtri = default(Otri); + Vertex searchorg, searchdest; + LocateResult intersect; + + Triangle[] regionTris = null; + + var dummytri = mesh.dummytri; + + if (!mesh.behavior.Convex) + { + // Mark as infected any unprotected triangles on the boundary. + // This is one way by which concavities are created. + InfectHull(); + } + + if (!mesh.behavior.NoHoles) + { + // Infect each triangle in which a hole lies. + foreach (var hole in mesh.holes) + { + // Ignore holes that aren't within the bounds of the mesh. + if (mesh.bounds.Contains(hole)) + { + // Start searching from some triangle on the outer boundary. + searchtri.tri = dummytri; + searchtri.orient = 0; + searchtri.Sym(); + // Ensure that the hole is to the left of this boundary edge; + // otherwise, locate() will falsely report that the hole + // falls within the starting triangle. + searchorg = searchtri.Org(); + searchdest = searchtri.Dest(); + if (predicates.CounterClockwise(searchorg, searchdest, hole) > 0.0) + { + // Find a triangle that contains the hole. + intersect = mesh.locator.Locate(hole, ref searchtri); + if ((intersect != LocateResult.Outside) && (!searchtri.IsInfected())) + { + // Infect the triangle. This is done by marking the triangle + // as infected and including the triangle in the virus pool. + searchtri.Infect(); + viri.Add(searchtri.tri); + } + } + } + } + } + + // Now, we have to find all the regions BEFORE we carve the holes, because locate() won't + // work when the triangulation is no longer convex. (Incidentally, this is the reason why + // regional attributes and area constraints can't be used when refining a preexisting mesh, + // which might not be convex; they can only be used with a freshly triangulated PSLG.) + if (mesh.regions.Count > 0) + { + int i = 0; + + regionTris = new Triangle[mesh.regions.Count]; + + // Find the starting triangle for each region. + foreach (var region in mesh.regions) + { + regionTris[i] = dummytri; + // Ignore region points that aren't within the bounds of the mesh. + if (mesh.bounds.Contains(region.point)) + { + // Start searching from some triangle on the outer boundary. + searchtri.tri = dummytri; + searchtri.orient = 0; + searchtri.Sym(); + // Ensure that the region point is to the left of this boundary + // edge; otherwise, locate() will falsely report that the + // region point falls within the starting triangle. + searchorg = searchtri.Org(); + searchdest = searchtri.Dest(); + if (predicates.CounterClockwise(searchorg, searchdest, region.point) > 0.0) + { + // Find a triangle that contains the region point. + intersect = mesh.locator.Locate(region.point, ref searchtri); + if ((intersect != LocateResult.Outside) && (!searchtri.IsInfected())) + { + // Record the triangle for processing after the + // holes have been carved. + regionTris[i] = searchtri.tri; + regionTris[i].label = region.id; + regionTris[i].area = region.area; + } + } + } + + i++; + } + } + + if (viri.Count > 0) + { + // Carve the holes and concavities. + Plague(); + } + + if (regionTris != null) + { + var iterator = new RegionIterator(mesh); + + for (int i = 0; i < regionTris.Length; i++) + { + if (regionTris[i].id != Mesh.DUMMY) + { + // Make sure the triangle under consideration still exists. + // It may have been eaten by the virus. + if (!Otri.IsDead(regionTris[i])) + { + // Apply one region's attribute and/or area constraint. + iterator.Process(regionTris[i]); + } + } + } + } + + // Free up memory (virus pool should be empty anyway). + viri.Clear(); + } + + /// + /// Create the segments of a triangulation, including PSLG segments and edges + /// on the convex hull. + /// + private void FormSkeleton(IPolygon input) + { + // The segment endpoints. + Vertex p, q; + + mesh.insegments = 0; + + if (behavior.Poly) + { + // If the input vertices are collinear, there is no triangulation, + // so don't try to insert segments. + if (mesh.triangles.Count == 0) + { + return; + } + + // If segments are to be inserted, compute a mapping + // from vertices to triangles. + if (input.Segments.Count > 0) + { + mesh.MakeVertexMap(); + } + + // Read and insert the segments. + foreach (var seg in input.Segments) + { + mesh.insegments++; + + p = seg.GetVertex(0); + q = seg.GetVertex(1); + + if ((p.x == q.x) && (p.y == q.y)) + { + if (Log.Verbose) + { + logger.Warning("Endpoints of segment (IDs " + p.id + "/" + q.id + ") are coincident.", + "Mesh.FormSkeleton()"); + } + } + else + { + InsertSegment(p, q, seg.Label); + } + } + } + + if (behavior.Convex || !behavior.Poly) + { + // Enclose the convex hull with subsegments. + MarkHull(); + } + } + + #region Carving holes + + /// + /// Virally infect all of the triangles of the convex hull that are not + /// protected by subsegments. Where there are subsegments, set boundary + /// markers as appropriate. + /// + private void InfectHull() + { + Otri hulltri = default(Otri); + Otri nexttri = default(Otri); + Otri starttri = default(Otri); + Osub hullsubseg = default(Osub); + Vertex horg, hdest; + + var dummytri = mesh.dummytri; + + // Find a triangle handle on the hull. + hulltri.tri = dummytri; + hulltri.orient = 0; + hulltri.Sym(); + + // Remember where we started so we know when to stop. + hulltri.Copy(ref starttri); + // Go once counterclockwise around the convex hull. + do + { + // Ignore triangles that are already infected. + if (!hulltri.IsInfected()) + { + // Is the triangle protected by a subsegment? + hulltri.Pivot(ref hullsubseg); + if (hullsubseg.seg.hash == Mesh.DUMMY) + { + // The triangle is not protected; infect it. + if (!hulltri.IsInfected()) + { + hulltri.Infect(); + viri.Add(hulltri.tri); + } + } + else + { + // The triangle is protected; set boundary markers if appropriate. + if (hullsubseg.seg.boundary == 0) + { + hullsubseg.seg.boundary = 1; + horg = hulltri.Org(); + hdest = hulltri.Dest(); + if (horg.label == 0) + { + horg.label = 1; + } + if (hdest.label == 0) + { + hdest.label = 1; + } + } + } + } + // To find the next hull edge, go clockwise around the next vertex. + hulltri.Lnext(); + hulltri.Oprev(ref nexttri); + while (nexttri.tri.id != Mesh.DUMMY) + { + nexttri.Copy(ref hulltri); + hulltri.Oprev(ref nexttri); + } + + } while (!hulltri.Equals(starttri)); + } + + /// + /// Spread the virus from all infected triangles to any neighbors not + /// protected by subsegments. Delete all infected triangles. + /// + /// + /// This is the procedure that actually creates holes and concavities. + /// + /// This procedure operates in two phases. The first phase identifies all + /// the triangles that will die, and marks them as infected. They are + /// marked to ensure that each triangle is added to the virus pool only + /// once, so the procedure will terminate. + /// + /// The second phase actually eliminates the infected triangles. It also + /// eliminates orphaned vertices. + /// + void Plague() + { + Otri testtri = default(Otri); + Otri neighbor = default(Otri); + Osub neighborsubseg = default(Osub); + Vertex testvertex; + Vertex norg, ndest; + + var dummysub = mesh.dummysub; + var dummytri = mesh.dummytri; + + bool killorg; + + // Loop through all the infected triangles, spreading the virus to + // their neighbors, then to their neighbors' neighbors. + for (int i = 0; i < viri.Count; i++) + { + // WARNING: Don't use foreach, mesh.viri list may get modified. + + testtri.tri = viri[i]; + // A triangle is marked as infected by messing with one of its pointers + // to subsegments, setting it to an illegal value. Hence, we have to + // temporarily uninfect this triangle so that we can examine its + // adjacent subsegments. + // TODO: Not true in the C# version (so we could skip this). + testtri.Uninfect(); + + // Check each of the triangle's three neighbors. + for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) + { + // Find the neighbor. + testtri.Sym(ref neighbor); + // Check for a subsegment between the triangle and its neighbor. + testtri.Pivot(ref neighborsubseg); + // Check if the neighbor is nonexistent or already infected. + if ((neighbor.tri.id == Mesh.DUMMY) || neighbor.IsInfected()) + { + if (neighborsubseg.seg.hash != Mesh.DUMMY) + { + // There is a subsegment separating the triangle from its + // neighbor, but both triangles are dying, so the subsegment + // dies too. + mesh.SubsegDealloc(neighborsubseg.seg); + if (neighbor.tri.id != Mesh.DUMMY) + { + // Make sure the subsegment doesn't get deallocated again + // later when the infected neighbor is visited. + neighbor.Uninfect(); + neighbor.SegDissolve(dummysub); + neighbor.Infect(); + } + } + } + else + { // The neighbor exists and is not infected. + if (neighborsubseg.seg.hash == Mesh.DUMMY) + { + // There is no subsegment protecting the neighbor, so + // the neighbor becomes infected. + neighbor.Infect(); + // Ensure that the neighbor's neighbors will be infected. + viri.Add(neighbor.tri); + } + else + { + // The neighbor is protected by a subsegment. + // Remove this triangle from the subsegment. + neighborsubseg.TriDissolve(dummytri); + // The subsegment becomes a boundary. Set markers accordingly. + if (neighborsubseg.seg.boundary == 0) + { + neighborsubseg.seg.boundary = 1; + } + norg = neighbor.Org(); + ndest = neighbor.Dest(); + if (norg.label == 0) + { + norg.label = 1; + } + if (ndest.label == 0) + { + ndest.label = 1; + } + } + } + } + // Remark the triangle as infected, so it doesn't get added to the + // virus pool again. + testtri.Infect(); + } + + foreach (var virus in viri) + { + testtri.tri = virus; + + // Check each of the three corners of the triangle for elimination. + // This is done by walking around each vertex, checking if it is + // still connected to at least one live triangle. + for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) + { + testvertex = testtri.Org(); + // Check if the vertex has already been tested. + if (testvertex != null) + { + killorg = true; + // Mark the corner of the triangle as having been tested. + testtri.SetOrg(null); + // Walk counterclockwise about the vertex. + testtri.Onext(ref neighbor); + // Stop upon reaching a boundary or the starting triangle. + while ((neighbor.tri.id != Mesh.DUMMY) && + (!neighbor.Equals(testtri))) + { + if (neighbor.IsInfected()) + { + // Mark the corner of this triangle as having been tested. + neighbor.SetOrg(null); + } + else + { + // A live triangle. The vertex survives. + killorg = false; + } + // Walk counterclockwise about the vertex. + neighbor.Onext(); + } + // If we reached a boundary, we must walk clockwise as well. + if (neighbor.tri.id == Mesh.DUMMY) + { + // Walk clockwise about the vertex. + testtri.Oprev(ref neighbor); + // Stop upon reaching a boundary. + while (neighbor.tri.id != Mesh.DUMMY) + { + if (neighbor.IsInfected()) + { + // Mark the corner of this triangle as having been tested. + neighbor.SetOrg(null); + } + else + { + // A live triangle. The vertex survives. + killorg = false; + } + // Walk clockwise about the vertex. + neighbor.Oprev(); + } + } + if (killorg) + { + // Deleting vertex + testvertex.type = VertexType.UndeadVertex; + mesh.undeads++; + } + } + } + + // Record changes in the number of boundary edges, and disconnect + // dead triangles from their neighbors. + for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) + { + testtri.Sym(ref neighbor); + if (neighbor.tri.id == Mesh.DUMMY) + { + // There is no neighboring triangle on this edge, so this edge + // is a boundary edge. This triangle is being deleted, so this + // boundary edge is deleted. + mesh.hullsize--; + } + else + { + // Disconnect the triangle from its neighbor. + neighbor.Dissolve(dummytri); + // There is a neighboring triangle on this edge, so this edge + // becomes a boundary edge when this triangle is deleted. + mesh.hullsize++; + } + } + // Return the dead triangle to the pool of triangles. + mesh.TriangleDealloc(testtri.tri); + } + + // Empty the virus pool. + viri.Clear(); + } + + #endregion + + #region Segment insertion + + /// + /// Find the first triangle on the path from one point to another. + /// + /// + /// + /// + /// The return value notes whether the destination or apex of the found + /// triangle is collinear with the two points in question. + /// + /// Finds the triangle that intersects a line segment drawn from the + /// origin of 'searchtri' to the point 'searchpoint', and returns the result + /// in 'searchtri'. The origin of 'searchtri' does not change, even though + /// the triangle returned may differ from the one passed in. This routine + /// is used to find the direction to move in to get from one point to + /// another. + /// + private FindDirectionResult FindDirection(ref Otri searchtri, Vertex searchpoint) + { + Otri checktri = default(Otri); + Vertex startvertex; + Vertex leftvertex, rightvertex; + double leftccw, rightccw; + bool leftflag, rightflag; + + startvertex = searchtri.Org(); + rightvertex = searchtri.Dest(); + leftvertex = searchtri.Apex(); + // Is 'searchpoint' to the left? + leftccw = predicates.CounterClockwise(searchpoint, startvertex, leftvertex); + leftflag = leftccw > 0.0; + // Is 'searchpoint' to the right? + rightccw = predicates.CounterClockwise(startvertex, searchpoint, rightvertex); + rightflag = rightccw > 0.0; + if (leftflag && rightflag) + { + // 'searchtri' faces directly away from 'searchpoint'. We could go left + // or right. Ask whether it's a triangle or a boundary on the left. + searchtri.Onext(ref checktri); + if (checktri.tri.id == Mesh.DUMMY) + { + leftflag = false; + } + else + { + rightflag = false; + } + } + while (leftflag) + { + // Turn left until satisfied. + searchtri.Onext(); + if (searchtri.tri.id == Mesh.DUMMY) + { + logger.Error("Unable to find a triangle on path.", "Mesh.FindDirection().1"); + throw new Exception("Unable to find a triangle on path."); + } + leftvertex = searchtri.Apex(); + rightccw = leftccw; + leftccw = predicates.CounterClockwise(searchpoint, startvertex, leftvertex); + leftflag = leftccw > 0.0; + } + while (rightflag) + { + // Turn right until satisfied. + searchtri.Oprev(); + if (searchtri.tri.id == Mesh.DUMMY) + { + logger.Error("Unable to find a triangle on path.", "Mesh.FindDirection().2"); + throw new Exception("Unable to find a triangle on path."); + } + rightvertex = searchtri.Dest(); + leftccw = rightccw; + rightccw = predicates.CounterClockwise(startvertex, searchpoint, rightvertex); + rightflag = rightccw > 0.0; + } + if (leftccw == 0.0) + { + return FindDirectionResult.Leftcollinear; + } + else if (rightccw == 0.0) + { + return FindDirectionResult.Rightcollinear; + } + else + { + return FindDirectionResult.Within; + } + } + + /// + /// Find the intersection of an existing segment and a segment that is being + /// inserted. Insert a vertex at the intersection, splitting an existing subsegment. + /// + /// + /// + /// + /// + /// The segment being inserted connects the apex of splittri to endpoint2. + /// splitsubseg is the subsegment being split, and MUST adjoin splittri. + /// Hence, endpoints of the subsegment being split are the origin and + /// destination of splittri. + /// + /// On completion, splittri is a handle having the newly inserted + /// intersection point as its origin, and endpoint1 as its destination. + /// + private void SegmentIntersection(ref Otri splittri, ref Osub splitsubseg, Vertex endpoint2) + { + Osub opposubseg = default(Osub); + Vertex endpoint1; + Vertex torg, tdest; + Vertex leftvertex, rightvertex; + Vertex newvertex; + InsertVertexResult success; + + var dummysub = mesh.dummysub; + + double ex, ey; + double tx, ty; + double etx, ety; + double split, denom; + + // Find the other three segment endpoints. + endpoint1 = splittri.Apex(); + torg = splittri.Org(); + tdest = splittri.Dest(); + // Segment intersection formulae; see the Antonio reference. + tx = tdest.x - torg.x; + ty = tdest.y - torg.y; + ex = endpoint2.x - endpoint1.x; + ey = endpoint2.y - endpoint1.y; + etx = torg.x - endpoint2.x; + ety = torg.y - endpoint2.y; + denom = ty * ex - tx * ey; + if (denom == 0.0) + { + logger.Error("Attempt to find intersection of parallel segments.", + "Mesh.SegmentIntersection()"); + throw new Exception("Attempt to find intersection of parallel segments."); + } + split = (ey * etx - ex * ety) / denom; + + // Create the new vertex. + newvertex = new Vertex( + torg.x + split * (tdest.x - torg.x), + torg.y + split * (tdest.y - torg.y), + splitsubseg.seg.boundary +#if USE_ATTRIBS + , mesh.nextras +#endif + ); + + newvertex.hash = mesh.hash_vtx++; + newvertex.id = newvertex.hash; + +#if USE_ATTRIBS + // Interpolate its attributes. + for (int i = 0; i < mesh.nextras; i++) + { + newvertex.attributes[i] = torg.attributes[i] + split * (tdest.attributes[i] - torg.attributes[i]); + } +#endif +#if USE_Z + newvertex.z = torg.z + split * (tdest.z - torg.z); +#endif + + mesh.vertices.Add(newvertex.hash, newvertex); + + // Insert the intersection vertex. This should always succeed. + success = mesh.InsertVertex(newvertex, ref splittri, ref splitsubseg, false, false); + if (success != InsertVertexResult.Successful) + { + logger.Error("Failure to split a segment.", "Mesh.SegmentIntersection()"); + throw new Exception("Failure to split a segment."); + } + // Record a triangle whose origin is the new vertex. + newvertex.tri = splittri; + if (mesh.steinerleft > 0) + { + mesh.steinerleft--; + } + + // Divide the segment into two, and correct the segment endpoints. + splitsubseg.Sym(); + splitsubseg.Pivot(ref opposubseg); + splitsubseg.Dissolve(dummysub); + opposubseg.Dissolve(dummysub); + do + { + splitsubseg.SetSegOrg(newvertex); + splitsubseg.Next(); + } while (splitsubseg.seg.hash != Mesh.DUMMY); + do + { + opposubseg.SetSegOrg(newvertex); + opposubseg.Next(); + } while (opposubseg.seg.hash != Mesh.DUMMY); + + // Inserting the vertex may have caused edge flips. We wish to rediscover + // the edge connecting endpoint1 to the new intersection vertex. + FindDirection(ref splittri, endpoint1); + + rightvertex = splittri.Dest(); + leftvertex = splittri.Apex(); + if ((leftvertex.x == endpoint1.x) && (leftvertex.y == endpoint1.y)) + { + splittri.Onext(); + } + else if ((rightvertex.x != endpoint1.x) || (rightvertex.y != endpoint1.y)) + { + logger.Error("Topological inconsistency after splitting a segment.", "Mesh.SegmentIntersection()"); + throw new Exception("Topological inconsistency after splitting a segment."); + } + // 'splittri' should have destination endpoint1. + } + + /// + /// Scout the first triangle on the path from one endpoint to another, and check + /// for completion (reaching the second endpoint), a collinear vertex, or the + /// intersection of two segments. + /// + /// + /// + /// + /// Returns true if the entire segment is successfully inserted, and false + /// if the job must be finished by ConstrainedEdge(). + /// + /// If the first triangle on the path has the second endpoint as its + /// destination or apex, a subsegment is inserted and the job is done. + /// + /// If the first triangle on the path has a destination or apex that lies on + /// the segment, a subsegment is inserted connecting the first endpoint to + /// the collinear vertex, and the search is continued from the collinear + /// vertex. + /// + /// If the first triangle on the path has a subsegment opposite its origin, + /// then there is a segment that intersects the segment being inserted. + /// Their intersection vertex is inserted, splitting the subsegment. + /// + private bool ScoutSegment(ref Otri searchtri, Vertex endpoint2, int newmark) + { + Otri crosstri = default(Otri); + Osub crosssubseg = default(Osub); + Vertex leftvertex, rightvertex; + FindDirectionResult collinear; + + collinear = FindDirection(ref searchtri, endpoint2); + rightvertex = searchtri.Dest(); + leftvertex = searchtri.Apex(); + if (((leftvertex.x == endpoint2.x) && (leftvertex.y == endpoint2.y)) || + ((rightvertex.x == endpoint2.x) && (rightvertex.y == endpoint2.y))) + { + // The segment is already an edge in the mesh. + if ((leftvertex.x == endpoint2.x) && (leftvertex.y == endpoint2.y)) + { + searchtri.Lprev(); + } + // Insert a subsegment, if there isn't already one there. + mesh.InsertSubseg(ref searchtri, newmark); + return true; + } + else if (collinear == FindDirectionResult.Leftcollinear) + { + // We've collided with a vertex between the segment's endpoints. + // Make the collinear vertex be the triangle's origin. + searchtri.Lprev(); + mesh.InsertSubseg(ref searchtri, newmark); + // Insert the remainder of the segment. + return ScoutSegment(ref searchtri, endpoint2, newmark); + } + else if (collinear == FindDirectionResult.Rightcollinear) + { + // We've collided with a vertex between the segment's endpoints. + mesh.InsertSubseg(ref searchtri, newmark); + // Make the collinear vertex be the triangle's origin. + searchtri.Lnext(); + // Insert the remainder of the segment. + return ScoutSegment(ref searchtri, endpoint2, newmark); + } + else + { + searchtri.Lnext(ref crosstri); + crosstri.Pivot(ref crosssubseg); + // Check for a crossing segment. + if (crosssubseg.seg.hash == Mesh.DUMMY) + { + return false; + } + else + { + // Insert a vertex at the intersection. + SegmentIntersection(ref crosstri, ref crosssubseg, endpoint2); + crosstri.Copy(ref searchtri); + mesh.InsertSubseg(ref searchtri, newmark); + // Insert the remainder of the segment. + return ScoutSegment(ref searchtri, endpoint2, newmark); + } + } + } + + /// + /// Enforce the Delaunay condition at an edge, fanning out recursively from + /// an existing vertex. Pay special attention to stacking inverted triangles. + /// + /// + /// Indicates whether or not fixuptri is to the left of + /// the segment being inserted. (Imagine that the segment is pointing up from + /// endpoint1 to endpoint2.) + /// + /// This is a support routine for inserting segments into a constrained + /// Delaunay triangulation. + /// + /// The origin of fixuptri is treated as if it has just been inserted, and + /// the local Delaunay condition needs to be enforced. It is only enforced + /// in one sector, however, that being the angular range defined by + /// fixuptri. + /// + /// This routine also needs to make decisions regarding the "stacking" of + /// triangles. (Read the description of ConstrainedEdge() below before + /// reading on here, so you understand the algorithm.) If the position of + /// the new vertex (the origin of fixuptri) indicates that the vertex before + /// it on the polygon is a reflex vertex, then "stack" the triangle by + /// doing nothing. (fixuptri is an inverted triangle, which is how stacked + /// triangles are identified.) + /// + /// Otherwise, check whether the vertex before that was a reflex vertex. + /// If so, perform an edge flip, thereby eliminating an inverted triangle + /// (popping it off the stack). The edge flip may result in the creation + /// of a new inverted triangle, depending on whether or not the new vertex + /// is visible to the vertex three edges behind on the polygon. + /// + /// If neither of the two vertices behind the new vertex are reflex + /// vertices, fixuptri and fartri, the triangle opposite it, are not + /// inverted; hence, ensure that the edge between them is locally Delaunay. + /// + private void DelaunayFixup(ref Otri fixuptri, bool leftside) + { + Otri neartri = default(Otri); + Otri fartri = default(Otri); + Osub faredge = default(Osub); + Vertex nearvertex, leftvertex, rightvertex, farvertex; + + fixuptri.Lnext(ref neartri); + neartri.Sym(ref fartri); + // Check if the edge opposite the origin of fixuptri can be flipped. + if (fartri.tri.id == Mesh.DUMMY) + { + return; + } + neartri.Pivot(ref faredge); + if (faredge.seg.hash != Mesh.DUMMY) + { + return; + } + // Find all the relevant vertices. + nearvertex = neartri.Apex(); + leftvertex = neartri.Org(); + rightvertex = neartri.Dest(); + farvertex = fartri.Apex(); + // Check whether the previous polygon vertex is a reflex vertex. + if (leftside) + { + if (predicates.CounterClockwise(nearvertex, leftvertex, farvertex) <= 0.0) + { + // leftvertex is a reflex vertex too. Nothing can + // be done until a convex section is found. + return; + } + } + else + { + if (predicates.CounterClockwise(farvertex, rightvertex, nearvertex) <= 0.0) + { + // rightvertex is a reflex vertex too. Nothing can + // be done until a convex section is found. + return; + } + } + if (predicates.CounterClockwise(rightvertex, leftvertex, farvertex) > 0.0) + { + // fartri is not an inverted triangle, and farvertex is not a reflex + // vertex. As there are no reflex vertices, fixuptri isn't an + // inverted triangle, either. Hence, test the edge between the + // triangles to ensure it is locally Delaunay. + if (predicates.InCircle(leftvertex, farvertex, rightvertex, nearvertex) <= 0.0) + { + return; + } + // Not locally Delaunay; go on to an edge flip. + } + // else fartri is inverted; remove it from the stack by flipping. + mesh.Flip(ref neartri); + fixuptri.Lprev(); // Restore the origin of fixuptri after the flip. + // Recursively process the two triangles that result from the flip. + DelaunayFixup(ref fixuptri, leftside); + DelaunayFixup(ref fartri, leftside); + } + + /// + /// Force a segment into a constrained Delaunay triangulation by deleting the + /// triangles it intersects, and triangulating the polygons that form on each + /// side of it. + /// + /// + /// + /// + /// + /// Generates a single subsegment connecting 'endpoint1' to 'endpoint2'. + /// The triangle 'starttri' has 'endpoint1' as its origin. 'newmark' is the + /// boundary marker of the segment. + /// + /// To insert a segment, every triangle whose interior intersects the + /// segment is deleted. The union of these deleted triangles is a polygon + /// (which is not necessarily monotone, but is close enough), which is + /// divided into two polygons by the new segment. This routine's task is + /// to generate the Delaunay triangulation of these two polygons. + /// + /// You might think of this routine's behavior as a two-step process. The + /// first step is to walk from endpoint1 to endpoint2, flipping each edge + /// encountered. This step creates a fan of edges connected to endpoint1, + /// including the desired edge to endpoint2. The second step enforces the + /// Delaunay condition on each side of the segment in an incremental manner: + /// proceeding along the polygon from endpoint1 to endpoint2 (this is done + /// independently on each side of the segment), each vertex is "enforced" + /// as if it had just been inserted, but affecting only the previous + /// vertices. The result is the same as if the vertices had been inserted + /// in the order they appear on the polygon, so the result is Delaunay. + /// + /// In truth, ConstrainedEdge() interleaves these two steps. The procedure + /// walks from endpoint1 to endpoint2, and each time an edge is encountered + /// and flipped, the newly exposed vertex (at the far end of the flipped + /// edge) is "enforced" upon the previously flipped edges, usually affecting + /// only one side of the polygon (depending upon which side of the segment + /// the vertex falls on). + /// + /// The algorithm is complicated by the need to handle polygons that are not + /// convex. Although the polygon is not necessarily monotone, it can be + /// triangulated in a manner similar to the stack-based algorithms for + /// monotone polygons. For each reflex vertex (local concavity) of the + /// polygon, there will be an inverted triangle formed by one of the edge + /// flips. (An inverted triangle is one with negative area - that is, its + /// vertices are arranged in clockwise order - and is best thought of as a + /// wrinkle in the fabric of the mesh.) Each inverted triangle can be + /// thought of as a reflex vertex pushed on the stack, waiting to be fixed + /// later. + /// + /// A reflex vertex is popped from the stack when a vertex is inserted that + /// is visible to the reflex vertex. (However, if the vertex behind the + /// reflex vertex is not visible to the reflex vertex, a new inverted + /// triangle will take its place on the stack.) These details are handled + /// by the DelaunayFixup() routine above. + /// + private void ConstrainedEdge(ref Otri starttri, Vertex endpoint2, int newmark) + { + Otri fixuptri = default(Otri), fixuptri2 = default(Otri); + Osub crosssubseg = default(Osub); + Vertex endpoint1; + Vertex farvertex; + double area; + bool collision; + bool done; + + endpoint1 = starttri.Org(); + starttri.Lnext(ref fixuptri); + mesh.Flip(ref fixuptri); + // 'collision' indicates whether we have found a vertex directly + // between endpoint1 and endpoint2. + collision = false; + done = false; + do + { + farvertex = fixuptri.Org(); + // 'farvertex' is the extreme point of the polygon we are "digging" + // to get from endpoint1 to endpoint2. + if ((farvertex.x == endpoint2.x) && (farvertex.y == endpoint2.y)) + { + fixuptri.Oprev(ref fixuptri2); + // Enforce the Delaunay condition around endpoint2. + DelaunayFixup(ref fixuptri, false); + DelaunayFixup(ref fixuptri2, true); + done = true; + } + else + { + // Check whether farvertex is to the left or right of the segment being + // inserted, to decide which edge of fixuptri to dig through next. + area = predicates.CounterClockwise(endpoint1, endpoint2, farvertex); + if (area == 0.0) + { + // We've collided with a vertex between endpoint1 and endpoint2. + collision = true; + fixuptri.Oprev(ref fixuptri2); + // Enforce the Delaunay condition around farvertex. + DelaunayFixup(ref fixuptri, false); + DelaunayFixup(ref fixuptri2, true); + done = true; + } + else + { + if (area > 0.0) + { + // farvertex is to the left of the segment. + fixuptri.Oprev(ref fixuptri2); + // Enforce the Delaunay condition around farvertex, on the + // left side of the segment only. + DelaunayFixup(ref fixuptri2, true); + // Flip the edge that crosses the segment. After the edge is + // flipped, one of its endpoints is the fan vertex, and the + // destination of fixuptri is the fan vertex. + fixuptri.Lprev(); + } + else + { + // farvertex is to the right of the segment. + DelaunayFixup(ref fixuptri, false); + // Flip the edge that crosses the segment. After the edge is + // flipped, one of its endpoints is the fan vertex, and the + // destination of fixuptri is the fan vertex. + fixuptri.Oprev(); + } + // Check for two intersecting segments. + fixuptri.Pivot(ref crosssubseg); + if (crosssubseg.seg.hash == Mesh.DUMMY) + { + mesh.Flip(ref fixuptri); // May create inverted triangle at left. + } + else + { + // We've collided with a segment between endpoint1 and endpoint2. + collision = true; + // Insert a vertex at the intersection. + SegmentIntersection(ref fixuptri, ref crosssubseg, endpoint2); + done = true; + } + } + } + } while (!done); + // Insert a subsegment to make the segment permanent. + mesh.InsertSubseg(ref fixuptri, newmark); + // If there was a collision with an interceding vertex, install another + // segment connecting that vertex with endpoint2. + if (collision) + { + // Insert the remainder of the segment. + if (!ScoutSegment(ref fixuptri, endpoint2, newmark)) + { + ConstrainedEdge(ref fixuptri, endpoint2, newmark); + } + } + } + + /// + /// Insert a PSLG segment into a triangulation. + /// + /// + /// + /// + private void InsertSegment(Vertex endpoint1, Vertex endpoint2, int newmark) + { + Otri searchtri1 = default(Otri), searchtri2 = default(Otri); + Vertex checkvertex = null; + + var dummytri = mesh.dummytri; + + // Find a triangle whose origin is the segment's first endpoint. + searchtri1 = endpoint1.tri; + if (searchtri1.tri != null) + { + checkvertex = searchtri1.Org(); + } + + if (checkvertex != endpoint1) + { + // Find a boundary triangle to search from. + searchtri1.tri = dummytri; + searchtri1.orient = 0; + searchtri1.Sym(); + // Search for the segment's first endpoint by point location. + if (locator.Locate(endpoint1, ref searchtri1) != LocateResult.OnVertex) + { + logger.Error("Unable to locate PSLG vertex in triangulation.", "Mesh.InsertSegment().1"); + throw new Exception("Unable to locate PSLG vertex in triangulation."); + } + } + // Remember this triangle to improve subsequent point location. + locator.Update(ref searchtri1); + + // Scout the beginnings of a path from the first endpoint + // toward the second. + if (ScoutSegment(ref searchtri1, endpoint2, newmark)) + { + // The segment was easily inserted. + return; + } + // The first endpoint may have changed if a collision with an intervening + // vertex on the segment occurred. + endpoint1 = searchtri1.Org(); + + // Find a triangle whose origin is the segment's second endpoint. + checkvertex = null; + searchtri2 = endpoint2.tri; + if (searchtri2.tri != null) + { + checkvertex = searchtri2.Org(); + } + if (checkvertex != endpoint2) + { + // Find a boundary triangle to search from. + searchtri2.tri = dummytri; + searchtri2.orient = 0; + searchtri2.Sym(); + // Search for the segment's second endpoint by point location. + if (locator.Locate(endpoint2, ref searchtri2) != LocateResult.OnVertex) + { + logger.Error("Unable to locate PSLG vertex in triangulation.", "Mesh.InsertSegment().2"); + throw new Exception("Unable to locate PSLG vertex in triangulation."); + } + } + // Remember this triangle to improve subsequent point location. + locator.Update(ref searchtri2); + // Scout the beginnings of a path from the second endpoint + // toward the first. + if (ScoutSegment(ref searchtri2, endpoint1, newmark)) + { + // The segment was easily inserted. + return; + } + // The second endpoint may have changed if a collision with an intervening + // vertex on the segment occurred. + endpoint2 = searchtri2.Org(); + + // Insert the segment directly into the triangulation. + ConstrainedEdge(ref searchtri1, endpoint2, newmark); + } + + /// + /// Cover the convex hull of a triangulation with subsegments. + /// + private void MarkHull() + { + Otri hulltri = default(Otri); + Otri nexttri = default(Otri); + Otri starttri = default(Otri); + + // Find a triangle handle on the hull. + hulltri.tri = mesh.dummytri; + hulltri.orient = 0; + hulltri.Sym(); + // Remember where we started so we know when to stop. + hulltri.Copy(ref starttri); + // Go once counterclockwise around the convex hull. + do + { + // Create a subsegment if there isn't already one here. + mesh.InsertSubseg(ref hulltri, 1); + // To find the next hull edge, go clockwise around the next vertex. + hulltri.Lnext(); + hulltri.Oprev(ref nexttri); + while (nexttri.tri.id != Mesh.DUMMY) + { + nexttri.Copy(ref hulltri); + hulltri.Oprev(ref nexttri); + } + } while (!hulltri.Equals(starttri)); + } + + #endregion + } +} diff --git a/Triangle.NET/Triangle/Meshing/ConstraintOptions.cs b/src/Triangle/Meshing/ConstraintOptions.cs similarity index 96% rename from Triangle.NET/Triangle/Meshing/ConstraintOptions.cs rename to src/Triangle/Meshing/ConstraintOptions.cs index 4325b3a..684273e 100644 --- a/Triangle.NET/Triangle/Meshing/ConstraintOptions.cs +++ b/src/Triangle/Meshing/ConstraintOptions.cs @@ -1,40 +1,40 @@ - -namespace TriangleNet.Meshing -{ - /// - /// Mesh constraint options for polygon triangulation. - /// - public class ConstraintOptions - { - // TODO: remove ConstraintOptions.UseRegions - - /// - /// Gets or sets a value indicating whether to use regions. - /// - [System.Obsolete("Not used anywhere, will be removed in beta 4.")] - public bool UseRegions { get; set; } - - /// - /// Gets or sets a value indicating whether to create a Conforming - /// Delaunay triangulation. - /// - public bool ConformingDelaunay { get; set; } - - /// - /// Gets or sets a value indicating whether to enclose the convex - /// hull with segments. - /// - public bool Convex { get; set; } - - /// - /// Gets or sets a flag indicating whether to suppress boundary - /// segment splitting. - /// - /// - /// 0 = split segments (default) - /// 1 = no new vertices on the boundary - /// 2 = prevent all segment splitting, including internal boundaries - /// - public int SegmentSplitting { get; set; } - } -} + +namespace TriangleNet.Meshing +{ + /// + /// Mesh constraint options for polygon triangulation. + /// + public class ConstraintOptions + { + // TODO: remove ConstraintOptions.UseRegions + + /// + /// Gets or sets a value indicating whether to use regions. + /// + [System.Obsolete("Not used anywhere, will be removed in beta 4.")] + public bool UseRegions { get; set; } + + /// + /// Gets or sets a value indicating whether to create a Conforming + /// Delaunay triangulation. + /// + public bool ConformingDelaunay { get; set; } + + /// + /// Gets or sets a value indicating whether to enclose the convex + /// hull with segments. + /// + public bool Convex { get; set; } + + /// + /// Gets or sets a flag indicating whether to suppress boundary + /// segment splitting. + /// + /// + /// 0 = split segments (default) + /// 1 = no new vertices on the boundary + /// 2 = prevent all segment splitting, including internal boundaries + /// + public int SegmentSplitting { get; set; } + } +} diff --git a/Triangle.NET/Triangle/Meshing/Converter.cs b/src/Triangle/Meshing/Converter.cs similarity index 91% rename from Triangle.NET/Triangle/Meshing/Converter.cs rename to src/Triangle/Meshing/Converter.cs index e8cba91..bd8392a 100644 --- a/Triangle.NET/Triangle/Meshing/Converter.cs +++ b/src/Triangle/Meshing/Converter.cs @@ -1,487 +1,493 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Meshing -{ - using System; - using System.Collections.Generic; - using System.Linq; - using TriangleNet.Geometry; - using TriangleNet.Topology; - using TriangleNet.Topology.DCEL; - - using HVertex = TriangleNet.Topology.DCEL.Vertex; - using TVertex = TriangleNet.Geometry.Vertex; - - /// - /// The Converter class provides methods for mesh reconstruction and conversion. - /// - public static class Converter - { - #region Triangle mesh conversion - - /// - /// Reconstruct a triangulation from its raw data representation. - /// - public static Mesh ToMesh(Polygon polygon, IList triangles) - { - return ToMesh(polygon, triangles.ToArray()); - } - - /// - /// Reconstruct a triangulation from its raw data representation. - /// - public static Mesh ToMesh(Polygon polygon, ITriangle[] triangles) - { - Otri tri = default(Otri); - Osub subseg = default(Osub); - int i = 0; - - int elements = triangles == null ? 0 : triangles.Length; - int segments = polygon.Segments.Count; - - // TODO: Configuration should be a function argument. - var mesh = new Mesh(new Configuration()); - - mesh.TransferNodes(polygon.Points); - - mesh.regions.AddRange(polygon.Regions); - mesh.behavior.useRegions = polygon.Regions.Count > 0; - - if (polygon.Segments.Count > 0) - { - mesh.behavior.Poly = true; - mesh.holes.AddRange(polygon.Holes); - } - - // Create the triangles. - for (i = 0; i < elements; i++) - { - mesh.MakeTriangle(ref tri); - } - - if (mesh.behavior.Poly) - { - mesh.insegments = segments; - - // Create the subsegments. - for (i = 0; i < segments; i++) - { - mesh.MakeSegment(ref subseg); - } - } - - var vertexarray = SetNeighbors(mesh, triangles); - - SetSegments(mesh, polygon, vertexarray); - - return mesh; - } - - /// - /// Finds the adjacencies between triangles by forming a stack of triangles for - /// each vertex. Each triangle is on three different stacks simultaneously. - /// - private static List[] SetNeighbors(Mesh mesh, ITriangle[] triangles) - { - Otri tri = default(Otri); - Otri triangleleft = default(Otri); - Otri checktri = default(Otri); - Otri checkleft = default(Otri); - Otri nexttri; - TVertex tdest, tapex; - TVertex checkdest, checkapex; - int[] corner = new int[3]; - int aroundvertex; - int i; - - // Allocate a temporary array that maps each vertex to some adjacent triangle. - var vertexarray = new List[mesh.vertices.Count]; - - // Each vertex is initially unrepresented. - for (i = 0; i < mesh.vertices.Count; i++) - { - Otri tmp = default(Otri); - tmp.tri = mesh.dummytri; - vertexarray[i] = new List(3); - vertexarray[i].Add(tmp); - } - - i = 0; - - // Read the triangles from the .ele file, and link - // together those that share an edge. - foreach (var item in mesh.triangles) - { - tri.tri = item; - - // Copy the triangle's three corners. - for (int j = 0; j < 3; j++) - { - corner[j] = triangles[i].GetVertexID(j); - - if ((corner[j] < 0) || (corner[j] >= mesh.invertices)) - { - Log.Instance.Error("Triangle has an invalid vertex index.", "MeshReader.Reconstruct()"); - throw new Exception("Triangle has an invalid vertex index."); - } - } - - // Read the triangle's attributes. - tri.tri.label = triangles[i].Label; - - // TODO: VarArea - if (mesh.behavior.VarArea) - { - tri.tri.area = triangles[i].Area; - } - - // Set the triangle's vertices. - tri.orient = 0; - tri.SetOrg(mesh.vertices[corner[0]]); - tri.SetDest(mesh.vertices[corner[1]]); - tri.SetApex(mesh.vertices[corner[2]]); - - // Try linking the triangle to others that share these vertices. - for (tri.orient = 0; tri.orient < 3; tri.orient++) - { - // Take the number for the origin of triangleloop. - aroundvertex = corner[tri.orient]; - - int index = vertexarray[aroundvertex].Count - 1; - - // Look for other triangles having this vertex. - nexttri = vertexarray[aroundvertex][index]; - - // Push the current triangle onto the stack. - vertexarray[aroundvertex].Add(tri); - - checktri = nexttri; - - if (checktri.tri.id != Mesh.DUMMY) - { - tdest = tri.Dest(); - tapex = tri.Apex(); - - // Look for other triangles that share an edge. - do - { - checkdest = checktri.Dest(); - checkapex = checktri.Apex(); - - if (tapex == checkdest) - { - // The two triangles share an edge; bond them together. - tri.Lprev(ref triangleleft); - triangleleft.Bond(ref checktri); - } - if (tdest == checkapex) - { - // The two triangles share an edge; bond them together. - checktri.Lprev(ref checkleft); - tri.Bond(ref checkleft); - } - // Find the next triangle in the stack. - index--; - nexttri = vertexarray[aroundvertex][index]; - - checktri = nexttri; - } while (checktri.tri.id != Mesh.DUMMY); - } - } - - i++; - } - - return vertexarray; - } - - /// - /// Finds the adjacencies between triangles and subsegments. - /// - private static void SetSegments(Mesh mesh, Polygon polygon, List[] vertexarray) - { - Otri checktri = default(Otri); - Otri nexttri; // Triangle - TVertex checkdest; - Otri checkneighbor = default(Otri); - Osub subseg = default(Osub); - Otri prevlink; // Triangle - - TVertex tmp; - TVertex sorg, sdest; - - bool notfound; - - //bool segmentmarkers = false; - int boundmarker; - int aroundvertex; - int i; - - int hullsize = 0; - - // Prepare to count the boundary edges. - if (mesh.behavior.Poly) - { - // Link the segments to their neighboring triangles. - boundmarker = 0; - i = 0; - foreach (var item in mesh.subsegs.Values) - { - subseg.seg = item; - - sorg = polygon.Segments[i].GetVertex(0); - sdest = polygon.Segments[i].GetVertex(1); - - boundmarker = polygon.Segments[i].Label; - - if ((sorg.id < 0 || sorg.id >= mesh.invertices) || (sdest.id < 0 || sdest.id >= mesh.invertices)) - { - Log.Instance.Error("Segment has an invalid vertex index.", "MeshReader.Reconstruct()"); - throw new Exception("Segment has an invalid vertex index."); - } - - // set the subsegment's vertices. - subseg.orient = 0; - subseg.SetOrg(sorg); - subseg.SetDest(sdest); - subseg.SetSegOrg(sorg); - subseg.SetSegDest(sdest); - subseg.seg.boundary = boundmarker; - // Try linking the subsegment to triangles that share these vertices. - for (subseg.orient = 0; subseg.orient < 2; subseg.orient++) - { - // Take the number for the destination of subsegloop. - aroundvertex = subseg.orient == 1 ? sorg.id : sdest.id; - - int index = vertexarray[aroundvertex].Count - 1; - - // Look for triangles having this vertex. - prevlink = vertexarray[aroundvertex][index]; - nexttri = vertexarray[aroundvertex][index]; - - checktri = nexttri; - tmp = subseg.Org(); - notfound = true; - // Look for triangles having this edge. Note that I'm only - // comparing each triangle's destination with the subsegment; - // each triangle's apex is handled through a different vertex. - // Because each triangle appears on three vertices' lists, each - // occurrence of a triangle on a list can (and does) represent - // an edge. In this way, most edges are represented twice, and - // every triangle-subsegment bond is represented once. - while (notfound && (checktri.tri.id != Mesh.DUMMY)) - { - checkdest = checktri.Dest(); - - if (tmp == checkdest) - { - // We have a match. Remove this triangle from the list. - //prevlink = vertexarray[aroundvertex][index]; - vertexarray[aroundvertex].Remove(prevlink); - // Bond the subsegment to the triangle. - checktri.SegBond(ref subseg); - // Check if this is a boundary edge. - checktri.Sym(ref checkneighbor); - if (checkneighbor.tri.id == Mesh.DUMMY) - { - // The next line doesn't insert a subsegment (because there's - // already one there), but it sets the boundary markers of - // the existing subsegment and its vertices. - mesh.InsertSubseg(ref checktri, 1); - hullsize++; - } - notfound = false; - } - index--; - // Find the next triangle in the stack. - prevlink = vertexarray[aroundvertex][index]; - nexttri = vertexarray[aroundvertex][index]; - - checktri = nexttri; - } - } - - i++; - } - } - - // Mark the remaining edges as not being attached to any subsegment. - // Also, count the (yet uncounted) boundary edges. - for (i = 0; i < mesh.vertices.Count; i++) - { - // Search the stack of triangles adjacent to a vertex. - int index = vertexarray[i].Count - 1; - nexttri = vertexarray[i][index]; - checktri = nexttri; - - while (checktri.tri.id != Mesh.DUMMY) - { - // Find the next triangle in the stack before this - // information gets overwritten. - index--; - nexttri = vertexarray[i][index]; - // No adjacent subsegment. (This overwrites the stack info.) - checktri.SegDissolve(mesh.dummysub); - checktri.Sym(ref checkneighbor); - if (checkneighbor.tri.id == Mesh.DUMMY) - { - mesh.InsertSubseg(ref checktri, 1); - hullsize++; - } - - checktri = nexttri; - } - } - - mesh.hullsize = hullsize; - } - - #endregion - - #region DCEL conversion - - public static DcelMesh ToDCEL(Mesh mesh) - { - var dcel = new DcelMesh(); - - var vertices = new HVertex[mesh.vertices.Count]; - var faces = new Face[mesh.triangles.Count]; - - dcel.HalfEdges.Capacity = 2 * mesh.NumberOfEdges; - - mesh.Renumber(); - - HVertex vertex; - - foreach (var v in mesh.vertices.Values) - { - vertex = new HVertex(v.x, v.y); - vertex.id = v.id; - vertex.label = v.label; - - vertices[v.id] = vertex; - } - - // Maps a triangle to its 3 edges (used to set next pointers). - var map = new List[mesh.triangles.Count]; - - Face face; - - foreach (var t in mesh.triangles) - { - face = new Face(null); - face.id = t.id; - - faces[t.id] = face; - - map[t.id] = new List(3); - } - - Otri tri = default(Otri), neighbor = default(Otri); - TriangleNet.Geometry.Vertex org, dest; - - int id, nid, count = mesh.triangles.Count; - - HalfEdge edge, twin, next; - - var edges = dcel.HalfEdges; - - // Count half-edges (edge ids). - int k = 0; - - // Maps a vertex to its leaving boundary edge. - var boundary = new Dictionary(); - - foreach (var t in mesh.triangles) - { - id = t.id; - - tri.tri = t; - - for (int i = 0; i < 3; i++) - { - tri.orient = i; - tri.Sym(ref neighbor); - - nid = neighbor.tri.id; - - if (id < nid || nid < 0) - { - face = faces[id]; - - // Get the endpoints of the current triangle edge. - org = tri.Org(); - dest = tri.Dest(); - - // Create half-edges. - edge = new HalfEdge(vertices[org.id], face); - twin = new HalfEdge(vertices[dest.id], nid < 0 ? Face.Empty : faces[nid]); - - map[id].Add(edge); - - if (nid >= 0) - { - map[nid].Add(twin); - } - else - { - boundary.Add(dest.id, twin); - } - - // Set leaving edges. - edge.origin.leaving = edge; - twin.origin.leaving = twin; - - // Set twin edges. - edge.twin = twin; - twin.twin = edge; - - edge.id = k++; - twin.id = k++; - - edges.Add(edge); - edges.Add(twin); - } - } - } - - // Set next pointers for each triangle face. - foreach (var t in map) - { - edge = t[0]; - next = t[1]; - - if (edge.twin.origin.id == next.origin.id) - { - edge.next = next; - next.next = t[2]; - t[2].next = edge; - } - else - { - edge.next = t[2]; - next.next = edge; - t[2].next = next; - } - } - - // Resolve boundary edges. - foreach (var e in boundary.Values) - { - e.next = boundary[e.twin.origin.id]; - } - - dcel.Vertices.AddRange(vertices); - dcel.Faces.AddRange(faces); - - return dcel; - } - - #endregion - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing +{ + using System; + using System.Collections.Generic; + using System.Linq; + using TriangleNet.Geometry; + using TriangleNet.Topology; + using TriangleNet.Topology.DCEL; + + using HVertex = TriangleNet.Topology.DCEL.Vertex; + using TVertex = TriangleNet.Geometry.Vertex; + + /// + /// The Converter class provides methods for mesh reconstruction and conversion. + /// + public class Converter + { + private static readonly Lazy lazy = new Lazy(() => new Converter()); + + /// + /// Gets the instance. + /// + public static Converter Instance { get { return lazy.Value; } } + + private Converter() + { + } + + #region Triangle mesh conversion + + /// + /// Reconstruct a triangulation from its raw data representation. + /// + public Mesh ToMesh(Polygon polygon, ICollection triangles) + { + return ToMesh(polygon, triangles.ToArray()); + } + + /// + /// Reconstruct a triangulation from its raw data representation. + /// + public Mesh ToMesh(Polygon polygon, ITriangle[] triangles, Configuration config = null) + { + Otri tri = default(Otri); + Osub subseg = default(Osub); + + int elements = triangles == null ? 0 : triangles.Length; + int segments = polygon.Segments.Count; + + var mesh = new Mesh(config ?? new Configuration(), polygon.Points); + + mesh.regions.AddRange(polygon.Regions); + mesh.behavior.useRegions = polygon.Regions.Count > 0; + + if (polygon.Segments.Count > 0) + { + mesh.behavior.Poly = true; + mesh.holes.AddRange(polygon.Holes); + } + + // Create the triangles. + for (int i = 0; i < elements; i++) + { + mesh.MakeTriangle(ref tri); + } + + if (mesh.behavior.Poly) + { + mesh.insegments = segments; + + // Create the subsegments. + for (int i = 0; i < segments; i++) + { + mesh.MakeSegment(ref subseg); + } + } + + var vertexarray = SetNeighbors(mesh, triangles); + + SetSegments(mesh, polygon, vertexarray); + + return mesh; + } + + /// + /// Finds the adjacencies between triangles by forming a stack of triangles for + /// each vertex. Each triangle is on three different stacks simultaneously. + /// + private List[] SetNeighbors(Mesh mesh, ITriangle[] triangles) + { + Otri tri = default(Otri); + Otri triangleleft = default(Otri); + Otri checktri = default(Otri); + Otri checkleft = default(Otri); + Otri nexttri; + TVertex tdest, tapex; + TVertex checkdest, checkapex; + int[] corner = new int[3]; + int aroundvertex; + int i; + + // Allocate a temporary array that maps each vertex to some adjacent triangle. + var vertexarray = new List[mesh.vertices.Count]; + + // Each vertex is initially unrepresented. + for (i = 0; i < mesh.vertices.Count; i++) + { + Otri tmp = default(Otri); + tmp.tri = mesh.dummytri; + vertexarray[i] = new List(3); + vertexarray[i].Add(tmp); + } + + i = 0; + + // Read the triangles from the .ele file, and link + // together those that share an edge. + foreach (var item in mesh.triangles) + { + tri.tri = item; + + // Copy the triangle's three corners. + for (int j = 0; j < 3; j++) + { + corner[j] = triangles[i].GetVertexID(j); + + if ((corner[j] < 0) || (corner[j] >= mesh.invertices)) + { + Log.Instance.Error("Triangle has an invalid vertex index.", "MeshReader.Reconstruct()"); + throw new Exception("Triangle has an invalid vertex index."); + } + } + + // Read the triangle's attributes. + tri.tri.label = triangles[i].Label; + + // TODO: VarArea + if (mesh.behavior.VarArea) + { + tri.tri.area = triangles[i].Area; + } + + // Set the triangle's vertices. + tri.orient = 0; + tri.SetOrg(mesh.vertices[corner[0]]); + tri.SetDest(mesh.vertices[corner[1]]); + tri.SetApex(mesh.vertices[corner[2]]); + + // Try linking the triangle to others that share these vertices. + for (tri.orient = 0; tri.orient < 3; tri.orient++) + { + // Take the number for the origin of triangleloop. + aroundvertex = corner[tri.orient]; + + int index = vertexarray[aroundvertex].Count - 1; + + // Look for other triangles having this vertex. + nexttri = vertexarray[aroundvertex][index]; + + // Push the current triangle onto the stack. + vertexarray[aroundvertex].Add(tri); + + checktri = nexttri; + + if (checktri.tri.id != Mesh.DUMMY) + { + tdest = tri.Dest(); + tapex = tri.Apex(); + + // Look for other triangles that share an edge. + do + { + checkdest = checktri.Dest(); + checkapex = checktri.Apex(); + + if (tapex == checkdest) + { + // The two triangles share an edge; bond them together. + tri.Lprev(ref triangleleft); + triangleleft.Bond(ref checktri); + } + if (tdest == checkapex) + { + // The two triangles share an edge; bond them together. + checktri.Lprev(ref checkleft); + tri.Bond(ref checkleft); + } + // Find the next triangle in the stack. + index--; + nexttri = vertexarray[aroundvertex][index]; + + checktri = nexttri; + } while (checktri.tri.id != Mesh.DUMMY); + } + } + + i++; + } + + return vertexarray; + } + + /// + /// Finds the adjacencies between triangles and subsegments. + /// + private void SetSegments(Mesh mesh, Polygon polygon, List[] vertexarray) + { + Otri checktri = default(Otri); + Otri nexttri; // Triangle + TVertex checkdest; + Otri checkneighbor = default(Otri); + Osub subseg = default(Osub); + Otri prevlink; // Triangle + + TVertex tmp; + TVertex sorg, sdest; + + bool notfound; + + //bool segmentmarkers = false; + int boundmarker; + int aroundvertex; + int i; + + int hullsize = 0; + + // Prepare to count the boundary edges. + if (mesh.behavior.Poly) + { + // Link the segments to their neighboring triangles. + i = 0; + foreach (var item in mesh.subsegs.Values) + { + subseg.seg = item; + + sorg = polygon.Segments[i].GetVertex(0); + sdest = polygon.Segments[i].GetVertex(1); + + boundmarker = polygon.Segments[i].Label; + + if ((sorg.id < 0 || sorg.id >= mesh.invertices) || (sdest.id < 0 || sdest.id >= mesh.invertices)) + { + Log.Instance.Error("Segment has an invalid vertex index.", "MeshReader.Reconstruct()"); + throw new Exception("Segment has an invalid vertex index."); + } + + // set the subsegment's vertices. + subseg.orient = 0; + subseg.SetOrg(sorg); + subseg.SetDest(sdest); + subseg.SetSegOrg(sorg); + subseg.SetSegDest(sdest); + subseg.seg.boundary = boundmarker; + // Try linking the subsegment to triangles that share these vertices. + for (subseg.orient = 0; subseg.orient < 2; subseg.orient++) + { + // Take the number for the destination of subsegloop. + aroundvertex = subseg.orient == 1 ? sorg.id : sdest.id; + + int index = vertexarray[aroundvertex].Count - 1; + + // Look for triangles having this vertex. + prevlink = vertexarray[aroundvertex][index]; + nexttri = vertexarray[aroundvertex][index]; + + checktri = nexttri; + tmp = subseg.Org(); + notfound = true; + // Look for triangles having this edge. Note that I'm only + // comparing each triangle's destination with the subsegment; + // each triangle's apex is handled through a different vertex. + // Because each triangle appears on three vertices' lists, each + // occurrence of a triangle on a list can (and does) represent + // an edge. In this way, most edges are represented twice, and + // every triangle-subsegment bond is represented once. + while (notfound && (checktri.tri.id != Mesh.DUMMY)) + { + checkdest = checktri.Dest(); + + if (tmp == checkdest) + { + // We have a match. Remove this triangle from the list. + //prevlink = vertexarray[aroundvertex][index]; + vertexarray[aroundvertex].Remove(prevlink); + // Bond the subsegment to the triangle. + checktri.SegBond(ref subseg); + // Check if this is a boundary edge. + checktri.Sym(ref checkneighbor); + if (checkneighbor.tri.id == Mesh.DUMMY) + { + // The next line doesn't insert a subsegment (because there's + // already one there), but it sets the boundary markers of + // the existing subsegment and its vertices. + mesh.InsertSubseg(ref checktri, 1); + hullsize++; + } + notfound = false; + } + index--; + // Find the next triangle in the stack. + prevlink = vertexarray[aroundvertex][index]; + nexttri = vertexarray[aroundvertex][index]; + + checktri = nexttri; + } + } + + i++; + } + } + + // Mark the remaining edges as not being attached to any subsegment. + // Also, count the (yet uncounted) boundary edges. + for (i = 0; i < mesh.vertices.Count; i++) + { + // Search the stack of triangles adjacent to a vertex. + int index = vertexarray[i].Count - 1; + nexttri = vertexarray[i][index]; + checktri = nexttri; + + while (checktri.tri.id != Mesh.DUMMY) + { + // Find the next triangle in the stack before this + // information gets overwritten. + index--; + nexttri = vertexarray[i][index]; + // No adjacent subsegment. (This overwrites the stack info.) + checktri.SegDissolve(mesh.dummysub); + checktri.Sym(ref checkneighbor); + if (checkneighbor.tri.id == Mesh.DUMMY) + { + mesh.InsertSubseg(ref checktri, 1); + hullsize++; + } + + checktri = nexttri; + } + } + + mesh.hullsize = hullsize; + } + + #endregion + + #region DCEL conversion + + public DcelMesh ToDCEL(Mesh mesh) + { + var dcel = new DcelMesh(); + + var vertices = new HVertex[mesh.vertices.Count]; + var faces = new Face[mesh.triangles.Count]; + + dcel.HalfEdges.Capacity = 2 * mesh.NumberOfEdges; + + mesh.Renumber(); + + HVertex vertex; + + foreach (var v in mesh.vertices.Values) + { + vertex = new HVertex(v.x, v.y); + vertex.id = v.id; + vertex.label = v.label; + + vertices[v.id] = vertex; + } + + // Maps a triangle to its 3 edges (used to set next pointers). + var map = new List[mesh.triangles.Count]; + + Face face; + + foreach (var t in mesh.triangles) + { + face = new Face(null); + face.id = t.id; + + faces[t.id] = face; + + map[t.id] = new List(3); + } + + Otri tri = default(Otri), neighbor = default(Otri); + TVertex org, dest; + + int id, nid, count = mesh.triangles.Count; + + HalfEdge edge, twin, next; + + var edges = dcel.HalfEdges; + + // Count half-edges (edge ids). + int k = 0; + + // Maps a vertex to its leaving boundary edge. + var boundary = new Dictionary(); + + foreach (var t in mesh.triangles) + { + id = t.id; + + tri.tri = t; + + for (int i = 0; i < 3; i++) + { + tri.orient = i; + tri.Sym(ref neighbor); + + nid = neighbor.tri.id; + + if (id < nid || nid < 0) + { + face = faces[id]; + + // Get the endpoints of the current triangle edge. + org = tri.Org(); + dest = tri.Dest(); + + // Create half-edges. + edge = new HalfEdge(vertices[org.id], face); + twin = new HalfEdge(vertices[dest.id], nid < 0 ? Face.Empty : faces[nid]); + + map[id].Add(edge); + + if (nid >= 0) + { + map[nid].Add(twin); + } + else + { + boundary.Add(dest.id, twin); + } + + // Set leaving edges. + edge.origin.leaving = edge; + twin.origin.leaving = twin; + + // Set twin edges. + edge.twin = twin; + twin.twin = edge; + + edge.id = k++; + twin.id = k++; + + edges.Add(edge); + edges.Add(twin); + } + } + } + + // Set next pointers for each triangle face. + foreach (var t in map) + { + edge = t[0]; + next = t[1]; + + if (edge.twin.origin.id == next.origin.id) + { + edge.next = next; + next.next = t[2]; + t[2].next = edge; + } + else + { + edge.next = t[2]; + next.next = edge; + t[2].next = next; + } + } + + // Resolve boundary edges. + foreach (var e in boundary.Values) + { + e.next = boundary[e.twin.origin.id]; + } + + dcel.Vertices.AddRange(vertices); + dcel.Faces.AddRange(faces); + + return dcel; + } + + #endregion + } +} diff --git a/Triangle.NET/Triangle/Meshing/Data/BadSubseg.cs b/src/Triangle/Meshing/Data/BadSubseg.cs similarity index 81% rename from Triangle.NET/Triangle/Meshing/Data/BadSubseg.cs rename to src/Triangle/Meshing/Data/BadSubseg.cs index 85f23a2..3d7c13f 100644 --- a/Triangle.NET/Triangle/Meshing/Data/BadSubseg.cs +++ b/src/Triangle/Meshing/Data/BadSubseg.cs @@ -1,36 +1,36 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Meshing.Data -{ - using System; - using TriangleNet.Geometry; - using TriangleNet.Topology; - - /// - /// A queue used to store encroached subsegments. - /// - /// - /// Each subsegment's vertices are stored so that we can check whether a - /// subsegment is still the same. - /// - class BadSubseg - { - public Osub subseg; // An encroached subsegment. - public Vertex org, dest; // Its two vertices. - - public override int GetHashCode() - { - return subseg.seg.hash; - } - - public override string ToString() - { - return String.Format("B-SID {0}", subseg.seg.hash); - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing.Data +{ + using System; + using TriangleNet.Geometry; + using TriangleNet.Topology; + + /// + /// A queue used to store encroached subsegments. + /// + /// + /// Each subsegment's vertices are stored so that we can check whether a + /// subsegment is still the same. + /// + class BadSubseg + { + public Osub subseg; // An encroached subsegment. + public Vertex org, dest; // Its two vertices. + + public override int GetHashCode() + { + return subseg.seg.hash; + } + + public override string ToString() + { + return String.Format("B-SID {0}", subseg.seg.hash); + } + } +} diff --git a/Triangle.NET/Triangle/Meshing/Data/BadTriQueue.cs b/src/Triangle/Meshing/Data/BadTriQueue.cs similarity index 94% rename from Triangle.NET/Triangle/Meshing/Data/BadTriQueue.cs rename to src/Triangle/Meshing/Data/BadTriQueue.cs index 87d5d18..c4ab1ef 100644 --- a/Triangle.NET/Triangle/Meshing/Data/BadTriQueue.cs +++ b/src/Triangle/Meshing/Data/BadTriQueue.cs @@ -1,194 +1,194 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Meshing.Data -{ - using TriangleNet.Geometry; - using TriangleNet.Topology; - - /// - /// A (priority) queue for bad triangles. - /// - /// - // The queue is actually a set of 4096 queues. I use multiple queues to - // give priority to smaller angles. I originally implemented a heap, but - // the queues are faster by a larger margin than I'd suspected. - /// - class BadTriQueue - { - const double SQRT2 = 1.4142135623730950488016887242096980785696718753769480732; - - public int Count { get { return this.count; } } - - // Variables that maintain the bad triangle queues. The queues are - // ordered from 4095 (highest priority) to 0 (lowest priority). - BadTriangle[] queuefront; - BadTriangle[] queuetail; - int[] nextnonemptyq; - int firstnonemptyq; - - int count; - - public BadTriQueue() - { - queuefront = new BadTriangle[4096]; - queuetail = new BadTriangle[4096]; - nextnonemptyq = new int[4096]; - - firstnonemptyq = -1; - - count = 0; - } - - /// - /// Add a bad triangle data structure to the end of a queue. - /// - /// The bad triangle to enqueue. - public void Enqueue(BadTriangle badtri) - { - double length, multiplier; - int exponent, expincrement; - int queuenumber; - int posexponent; - int i; - - this.count++; - - // Determine the appropriate queue to put the bad triangle into. - // Recall that the key is the square of its shortest edge length. - if (badtri.key >= 1.0) - { - length = badtri.key; - posexponent = 1; - } - else - { - // 'badtri.key' is 2.0 to a negative exponent, so we'll record that - // fact and use the reciprocal of 'badtri.key', which is > 1.0. - length = 1.0 / badtri.key; - posexponent = 0; - } - // 'length' is approximately 2.0 to what exponent? The following code - // determines the answer in time logarithmic in the exponent. - exponent = 0; - while (length > 2.0) - { - // Find an approximation by repeated squaring of two. - expincrement = 1; - multiplier = 0.5; - while (length * multiplier * multiplier > 1.0) - { - expincrement *= 2; - multiplier *= multiplier; - } - // Reduce the value of 'length', then iterate if necessary. - exponent += expincrement; - length *= multiplier; - } - // 'length' is approximately squareroot(2.0) to what exponent? - exponent = 2 * exponent + (length > SQRT2 ? 1 : 0); - // 'exponent' is now in the range 0...2047 for IEEE double precision. - // Choose a queue in the range 0...4095. The shortest edges have the - // highest priority (queue 4095). - if (posexponent > 0) - { - queuenumber = 2047 - exponent; - } - else - { - queuenumber = 2048 + exponent; - } - - // Are we inserting into an empty queue? - if (queuefront[queuenumber] == null) - { - // Yes, we are inserting into an empty queue. - // Will this become the highest-priority queue? - if (queuenumber > firstnonemptyq) - { - // Yes, this is the highest-priority queue. - nextnonemptyq[queuenumber] = firstnonemptyq; - firstnonemptyq = queuenumber; - } - else - { - // No, this is not the highest-priority queue. - // Find the queue with next higher priority. - i = queuenumber + 1; - while (queuefront[i] == null) - { - i++; - } - // Mark the newly nonempty queue as following a higher-priority queue. - nextnonemptyq[queuenumber] = nextnonemptyq[i]; - nextnonemptyq[i] = queuenumber; - } - // Put the bad triangle at the beginning of the (empty) queue. - queuefront[queuenumber] = badtri; - } - else - { - // Add the bad triangle to the end of an already nonempty queue. - queuetail[queuenumber].next = badtri; - } - // Maintain a pointer to the last triangle of the queue. - queuetail[queuenumber] = badtri; - // Newly enqueued bad triangle has no successor in the queue. - badtri.next = null; - } - - /// - /// Add a bad triangle to the end of a queue. - /// - /// - /// - /// - /// - /// - public void Enqueue(ref Otri enqtri, double minedge, Vertex apex, Vertex org, Vertex dest) - { - // Allocate space for the bad triangle. - BadTriangle newbad = new BadTriangle(); - - newbad.poortri = enqtri; - newbad.key = minedge; - newbad.apex = apex; - newbad.org = org; - newbad.dest = dest; - - Enqueue(newbad); - } - - /// - /// Remove a triangle from the front of the queue. - /// - /// - public BadTriangle Dequeue() - { - // If no queues are nonempty, return NULL. - if (firstnonemptyq < 0) - { - return null; - } - - this.count--; - - // Find the first triangle of the highest-priority queue. - BadTriangle result = queuefront[firstnonemptyq]; - // Remove the triangle from the queue. - queuefront[firstnonemptyq] = result.next; - // If this queue is now empty, note the new highest-priority - // nonempty queue. - if (result == queuetail[firstnonemptyq]) - { - firstnonemptyq = nextnonemptyq[firstnonemptyq]; - } - - return result; - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing.Data +{ + using TriangleNet.Geometry; + using TriangleNet.Topology; + + /// + /// A (priority) queue for bad triangles. + /// + /// + // The queue is actually a set of 4096 queues. I use multiple queues to + // give priority to smaller angles. I originally implemented a heap, but + // the queues are faster by a larger margin than I'd suspected. + /// + class BadTriQueue + { + const double SQRT2 = 1.4142135623730950488016887242096980785696718753769480732; + + public int Count { get { return this.count; } } + + // Variables that maintain the bad triangle queues. The queues are + // ordered from 4095 (highest priority) to 0 (lowest priority). + BadTriangle[] queuefront; + BadTriangle[] queuetail; + int[] nextnonemptyq; + int firstnonemptyq; + + int count; + + public BadTriQueue() + { + queuefront = new BadTriangle[4096]; + queuetail = new BadTriangle[4096]; + nextnonemptyq = new int[4096]; + + firstnonemptyq = -1; + + count = 0; + } + + /// + /// Add a bad triangle data structure to the end of a queue. + /// + /// The bad triangle to enqueue. + public void Enqueue(BadTriangle badtri) + { + double length, multiplier; + int exponent, expincrement; + int queuenumber; + int posexponent; + int i; + + this.count++; + + // Determine the appropriate queue to put the bad triangle into. + // Recall that the key is the square of its shortest edge length. + if (badtri.key >= 1.0) + { + length = badtri.key; + posexponent = 1; + } + else + { + // 'badtri.key' is 2.0 to a negative exponent, so we'll record that + // fact and use the reciprocal of 'badtri.key', which is > 1.0. + length = 1.0 / badtri.key; + posexponent = 0; + } + // 'length' is approximately 2.0 to what exponent? The following code + // determines the answer in time logarithmic in the exponent. + exponent = 0; + while (length > 2.0) + { + // Find an approximation by repeated squaring of two. + expincrement = 1; + multiplier = 0.5; + while (length * multiplier * multiplier > 1.0) + { + expincrement *= 2; + multiplier *= multiplier; + } + // Reduce the value of 'length', then iterate if necessary. + exponent += expincrement; + length *= multiplier; + } + // 'length' is approximately squareroot(2.0) to what exponent? + exponent = 2 * exponent + (length > SQRT2 ? 1 : 0); + // 'exponent' is now in the range 0...2047 for IEEE double precision. + // Choose a queue in the range 0...4095. The shortest edges have the + // highest priority (queue 4095). + if (posexponent > 0) + { + queuenumber = 2047 - exponent; + } + else + { + queuenumber = 2048 + exponent; + } + + // Are we inserting into an empty queue? + if (queuefront[queuenumber] == null) + { + // Yes, we are inserting into an empty queue. + // Will this become the highest-priority queue? + if (queuenumber > firstnonemptyq) + { + // Yes, this is the highest-priority queue. + nextnonemptyq[queuenumber] = firstnonemptyq; + firstnonemptyq = queuenumber; + } + else + { + // No, this is not the highest-priority queue. + // Find the queue with next higher priority. + i = queuenumber + 1; + while (queuefront[i] == null) + { + i++; + } + // Mark the newly nonempty queue as following a higher-priority queue. + nextnonemptyq[queuenumber] = nextnonemptyq[i]; + nextnonemptyq[i] = queuenumber; + } + // Put the bad triangle at the beginning of the (empty) queue. + queuefront[queuenumber] = badtri; + } + else + { + // Add the bad triangle to the end of an already nonempty queue. + queuetail[queuenumber].next = badtri; + } + // Maintain a pointer to the last triangle of the queue. + queuetail[queuenumber] = badtri; + // Newly enqueued bad triangle has no successor in the queue. + badtri.next = null; + } + + /// + /// Add a bad triangle to the end of a queue. + /// + /// + /// + /// + /// + /// + public void Enqueue(ref Otri enqtri, double minedge, Vertex apex, Vertex org, Vertex dest) + { + // Allocate space for the bad triangle. + BadTriangle newbad = new BadTriangle(); + + newbad.poortri = enqtri; + newbad.key = minedge; + newbad.apex = apex; + newbad.org = org; + newbad.dest = dest; + + Enqueue(newbad); + } + + /// + /// Remove a triangle from the front of the queue. + /// + /// + public BadTriangle Dequeue() + { + // If no queues are nonempty, return NULL. + if (firstnonemptyq < 0) + { + return null; + } + + this.count--; + + // Find the first triangle of the highest-priority queue. + BadTriangle result = queuefront[firstnonemptyq]; + // Remove the triangle from the queue. + queuefront[firstnonemptyq] = result.next; + // If this queue is now empty, note the new highest-priority + // nonempty queue. + if (result == queuetail[firstnonemptyq]) + { + firstnonemptyq = nextnonemptyq[firstnonemptyq]; + } + + return result; + } + } +} diff --git a/Triangle.NET/Triangle/Meshing/Data/BadTriangle.cs b/src/Triangle/Meshing/Data/BadTriangle.cs similarity index 83% rename from Triangle.NET/Triangle/Meshing/Data/BadTriangle.cs rename to src/Triangle/Meshing/Data/BadTriangle.cs index 40cc398..7337e07 100644 --- a/Triangle.NET/Triangle/Meshing/Data/BadTriangle.cs +++ b/src/Triangle/Meshing/Data/BadTriangle.cs @@ -1,34 +1,34 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Meshing.Data -{ - using System; - using TriangleNet.Geometry; - using TriangleNet.Topology; - - /// - /// A queue used to store bad triangles. - /// - /// - /// The key is the square of the cosine of the smallest angle of the triangle. - /// Each triangle's vertices are stored so that one can check whether a - /// triangle is still the same. - /// - class BadTriangle - { - public Otri poortri; // A skinny or too-large triangle. - public double key; // cos^2 of smallest (apical) angle. - public Vertex org, dest, apex; // Its three vertices. - public BadTriangle next; // Pointer to next bad triangle. - - public override string ToString() - { - return String.Format("B-TID {0}", poortri.tri.hash); - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing.Data +{ + using System; + using TriangleNet.Geometry; + using TriangleNet.Topology; + + /// + /// A queue used to store bad triangles. + /// + /// + /// The key is the square of the cosine of the smallest angle of the triangle. + /// Each triangle's vertices are stored so that one can check whether a + /// triangle is still the same. + /// + class BadTriangle + { + public Otri poortri; // A skinny or too-large triangle. + public double key; // cos^2 of smallest (apical) angle. + public Vertex org, dest, apex; // Its three vertices. + public BadTriangle next; // Pointer to next bad triangle. + + public override string ToString() + { + return String.Format("B-TID {0}", poortri.tri.hash); + } + } +} diff --git a/Triangle.NET/Triangle/Meshing/GenericMesher.cs b/src/Triangle/Meshing/GenericMesher.cs similarity index 94% rename from Triangle.NET/Triangle/Meshing/GenericMesher.cs rename to src/Triangle/Meshing/GenericMesher.cs index 75c6dfc..b83a889 100644 --- a/Triangle.NET/Triangle/Meshing/GenericMesher.cs +++ b/src/Triangle/Meshing/GenericMesher.cs @@ -1,233 +1,233 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Meshing -{ - using System; - using System.Collections.Generic; - using TriangleNet.Geometry; - using TriangleNet.IO; - using TriangleNet.Meshing.Algorithm; - - /// - /// Create meshes of point sets or polygons. - /// - public class GenericMesher - { - Configuration config; - ITriangulator triangulator; - - public GenericMesher() - : this(new Dwyer(), new Configuration()) - { - } - - public GenericMesher(ITriangulator triangulator) - : this(triangulator, new Configuration()) - { - } - - public GenericMesher(Configuration config) - : this(new Dwyer(), config) - { - } - - public GenericMesher(ITriangulator triangulator, Configuration config) - { - this.config = config; - this.triangulator = triangulator; - } - - /// - public IMesh Triangulate(IList points) - { - return triangulator.Triangulate(points, config); - } - - /// - public IMesh Triangulate(IPolygon polygon) - { - return Triangulate(polygon, null, null); - } - - /// - public IMesh Triangulate(IPolygon polygon, ConstraintOptions options) - { - return Triangulate(polygon, options, null); - } - - /// - public IMesh Triangulate(IPolygon polygon, QualityOptions quality) - { - return Triangulate(polygon, null, quality); - } - - /// - public IMesh Triangulate(IPolygon polygon, ConstraintOptions options, QualityOptions quality) - { - var mesh = (Mesh)triangulator.Triangulate(polygon.Points, config); - - var cmesher = new ConstraintMesher(mesh, config); - var qmesher = new QualityMesher(mesh, config); - - mesh.SetQualityMesher(qmesher); - - // Insert segments. - cmesher.Apply(polygon, options); - - // Refine mesh. - qmesher.Apply(quality); - - return mesh; - } - - /// - /// Generates a structured mesh with bounds [0, 0, width, height]. - /// - /// Width of the mesh (must be > 0). - /// Height of the mesh (must be > 0). - /// Number of segments in x direction. - /// Number of segments in y direction. - /// Mesh - public static IMesh StructuredMesh(double width, double height, int nx, int ny) - { - if (width <= 0.0) - { - throw new ArgumentException("width"); - } - - if (height <= 0.0) - { - throw new ArgumentException("height"); - } - - return StructuredMesh(new Rectangle(0.0, 0.0, width, height), nx, ny); - } - - /// - /// Generates a structured mesh. - /// - /// Bounds of the mesh. - /// Number of segments in x direction. - /// Number of segments in y direction. - /// Mesh - public static IMesh StructuredMesh(Rectangle bounds, int nx, int ny) - { - var polygon = new Polygon((nx + 1) * (ny + 1)); - - double x, y, dx, dy, left, bottom; - - dx = bounds.Width / nx; - dy = bounds.Height / ny; - - left = bounds.Left; - bottom = bounds.Bottom; - - int i, j, k, l, n = 0; - - // Add vertices. - var points = new Vertex[(nx + 1) * (ny + 1)]; - - for (i = 0; i <= nx; i++) - { - x = left + i * dx; - - for (j = 0; j <= ny; j++) - { - y = bottom + j * dy; - - points[n++] = new Vertex(x, y); - } - } - - polygon.Points.AddRange(points); - - n = 0; - - // Set vertex hash and id. - foreach (var v in points) - { - v.hash = v.id = n++; - } - - // Add boundary segments. - var segments = polygon.Segments; - - segments.Capacity = 2 * (nx + ny); - - Vertex a, b; - - for (j = 0; j < ny; j++) - { - // Left - a = points[j]; - b = points[j + 1]; - - segments.Add(new Segment(a, b, 1)); - - a.Label = b.Label = 1; - - // Right - a = points[nx * (ny + 1) + j]; - b = points[nx * (ny + 1) + (j + 1)]; - - segments.Add(new Segment(a, b, 1)); - - a.Label = b.Label = 1; - } - - for (i = 0; i < nx; i++) - { - // Bottom - a = points[(ny + 1) * i]; - b = points[(ny + 1) * (i + 1)]; - - segments.Add(new Segment(a, b, 1)); - - a.Label = b.Label = 1; - - // Top - a = points[ny + (ny + 1) * i]; - b = points[ny + (ny + 1) * (i + 1)]; - - segments.Add(new Segment(a, b, 1)); - - a.Label = b.Label = 1; - } - - // Add triangles. - var triangles = new InputTriangle[2 * nx * ny]; - - n = 0; - - for (i = 0; i < nx; i++) - { - for (j = 0; j < ny; j++) - { - k = j + (ny + 1) * i; - l = j + (ny + 1) * (i + 1); - - // Create 2 triangles in rectangle [k, l, l + 1, k + 1]. - - if ((i + j) % 2 == 0) - { - // Diagonal from bottom left to top right. - triangles[n++] = new InputTriangle(k, l, l + 1); - triangles[n++] = new InputTriangle(k, l + 1, k + 1); - } - else - { - // Diagonal from top left to bottom right. - triangles[n++] = new InputTriangle(k, l, k + 1); - triangles[n++] = new InputTriangle(l, l + 1, k + 1); - } - } - } - - return Converter.ToMesh(polygon, triangles); - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing +{ + using System; + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.IO; + using TriangleNet.Meshing.Algorithm; + + /// + /// Create meshes of point sets or polygons. + /// + public class GenericMesher + { + Configuration config; + ITriangulator triangulator; + + public GenericMesher() + : this(new Dwyer(), new Configuration()) + { + } + + public GenericMesher(ITriangulator triangulator) + : this(triangulator, new Configuration()) + { + } + + public GenericMesher(Configuration config) + : this(new Dwyer(), config) + { + } + + public GenericMesher(ITriangulator triangulator, Configuration config) + { + this.config = config; + this.triangulator = triangulator; + } + + /// + public IMesh Triangulate(IList points) + { + return triangulator.Triangulate(points, config); + } + + /// + public IMesh Triangulate(IPolygon polygon) + { + return Triangulate(polygon, null, null); + } + + /// + public IMesh Triangulate(IPolygon polygon, ConstraintOptions options) + { + return Triangulate(polygon, options, null); + } + + /// + public IMesh Triangulate(IPolygon polygon, QualityOptions quality) + { + return Triangulate(polygon, null, quality); + } + + /// + public IMesh Triangulate(IPolygon polygon, ConstraintOptions options, QualityOptions quality) + { + var mesh = (Mesh)triangulator.Triangulate(polygon.Points, config); + + var cmesher = new ConstraintMesher(mesh, config); + var qmesher = new QualityMesher(mesh, config); + + mesh.SetQualityMesher(qmesher); + + // Insert segments. + cmesher.Apply(polygon, options); + + // Refine mesh. + qmesher.Apply(quality); + + return mesh; + } + + /// + /// Generates a structured mesh with bounds [0, 0, width, height]. + /// + /// Width of the mesh (must be > 0). + /// Height of the mesh (must be > 0). + /// Number of segments in x direction. + /// Number of segments in y direction. + /// Mesh + public static IMesh StructuredMesh(double width, double height, int nx, int ny) + { + if (width <= 0.0) + { + throw new ArgumentException("width"); + } + + if (height <= 0.0) + { + throw new ArgumentException("height"); + } + + return StructuredMesh(new Rectangle(0.0, 0.0, width, height), nx, ny); + } + + /// + /// Generates a structured mesh. + /// + /// Bounds of the mesh. + /// Number of segments in x direction. + /// Number of segments in y direction. + /// Mesh + public static IMesh StructuredMesh(Rectangle bounds, int nx, int ny) + { + var polygon = new Polygon((nx + 1) * (ny + 1)); + + double x, y, dx, dy, left, bottom; + + dx = bounds.Width / nx; + dy = bounds.Height / ny; + + left = bounds.Left; + bottom = bounds.Bottom; + + int i, j, k, l, n = 0; + + // Add vertices. + var points = new Vertex[(nx + 1) * (ny + 1)]; + + for (i = 0; i <= nx; i++) + { + x = left + i * dx; + + for (j = 0; j <= ny; j++) + { + y = bottom + j * dy; + + points[n++] = new Vertex(x, y); + } + } + + polygon.Points.AddRange(points); + + n = 0; + + // Set vertex hash and id. + foreach (var v in points) + { + v.hash = v.id = n++; + } + + // Add boundary segments. + var segments = polygon.Segments; + + segments.Capacity = 2 * (nx + ny); + + Vertex a, b; + + for (j = 0; j < ny; j++) + { + // Left + a = points[j]; + b = points[j + 1]; + + segments.Add(new Segment(a, b, 1)); + + a.Label = b.Label = 1; + + // Right + a = points[nx * (ny + 1) + j]; + b = points[nx * (ny + 1) + (j + 1)]; + + segments.Add(new Segment(a, b, 1)); + + a.Label = b.Label = 1; + } + + for (i = 0; i < nx; i++) + { + // Bottom + a = points[(ny + 1) * i]; + b = points[(ny + 1) * (i + 1)]; + + segments.Add(new Segment(a, b, 1)); + + a.Label = b.Label = 1; + + // Top + a = points[ny + (ny + 1) * i]; + b = points[ny + (ny + 1) * (i + 1)]; + + segments.Add(new Segment(a, b, 1)); + + a.Label = b.Label = 1; + } + + // Add triangles. + var triangles = new InputTriangle[2 * nx * ny]; + + n = 0; + + for (i = 0; i < nx; i++) + { + for (j = 0; j < ny; j++) + { + k = j + (ny + 1) * i; + l = j + (ny + 1) * (i + 1); + + // Create 2 triangles in rectangle [k, l, l + 1, k + 1]. + + if ((i + j) % 2 == 0) + { + // Diagonal from bottom left to top right. + triangles[n++] = new InputTriangle(k, l, l + 1); + triangles[n++] = new InputTriangle(k, l + 1, k + 1); + } + else + { + // Diagonal from top left to bottom right. + triangles[n++] = new InputTriangle(k, l, k + 1); + triangles[n++] = new InputTriangle(l, l + 1, k + 1); + } + } + } + + return Converter.Instance.ToMesh(polygon, triangles); + } + } +} diff --git a/Triangle.NET/Triangle/Meshing/IConstraintMesher.cs b/src/Triangle/Meshing/IConstraintMesher.cs similarity index 96% rename from Triangle.NET/Triangle/Meshing/IConstraintMesher.cs rename to src/Triangle/Meshing/IConstraintMesher.cs index 1336417..d54ed32 100644 --- a/Triangle.NET/Triangle/Meshing/IConstraintMesher.cs +++ b/src/Triangle/Meshing/IConstraintMesher.cs @@ -1,26 +1,26 @@ - -namespace TriangleNet.Meshing -{ - using TriangleNet.Geometry; - - /// - /// Interface for polygon triangulation. - /// - public interface IConstraintMesher - { - /// - /// Triangulates a polygon. - /// - /// The polygon. - /// Mesh - IMesh Triangulate(IPolygon polygon); - - /// - /// Triangulates a polygon, applying constraint options. - /// - /// The polygon. - /// Constraint options. - /// Mesh - IMesh Triangulate(IPolygon polygon, ConstraintOptions options); - } -} + +namespace TriangleNet.Meshing +{ + using TriangleNet.Geometry; + + /// + /// Interface for polygon triangulation. + /// + public interface IConstraintMesher + { + /// + /// Triangulates a polygon. + /// + /// The polygon. + /// Mesh + IMesh Triangulate(IPolygon polygon); + + /// + /// Triangulates a polygon, applying constraint options. + /// + /// The polygon. + /// Constraint options. + /// Mesh + IMesh Triangulate(IPolygon polygon, ConstraintOptions options); + } +} diff --git a/Triangle.NET/Triangle/Meshing/IMesh.cs b/src/Triangle/Meshing/IMesh.cs similarity index 96% rename from Triangle.NET/Triangle/Meshing/IMesh.cs rename to src/Triangle/Meshing/IMesh.cs index f5fb484..1203c31 100644 --- a/Triangle.NET/Triangle/Meshing/IMesh.cs +++ b/src/Triangle/Meshing/IMesh.cs @@ -1,57 +1,57 @@ - -namespace TriangleNet.Meshing -{ - using System.Collections.Generic; - using TriangleNet.Topology; - using TriangleNet.Geometry; - - /// - /// Mesh interface. - /// - public interface IMesh - { - /// - /// Gets the vertices of the mesh. - /// - ICollection Vertices { get; } - - /// - /// Gets the edges of the mesh. - /// - IEnumerable Edges { get; } - - /// - /// Gets the segments (constraint edges) of the mesh. - /// - ICollection Segments { get; } - - /// - /// Gets the triangles of the mesh. - /// - ICollection Triangles { get; } - - /// - /// Gets the holes of the mesh. - /// - IList Holes { get; } - - /// - /// Gets the bounds of the mesh. - /// - Rectangle Bounds { get; } - - /// - /// Renumber mesh vertices and triangles. - /// - void Renumber(); - - /// - /// Refine the mesh. - /// - /// The quality constraints. - /// - /// A value indicating, if the refined mesh should be Conforming Delaunay. - /// - void Refine(QualityOptions quality, bool delaunay); - } -} + +namespace TriangleNet.Meshing +{ + using System.Collections.Generic; + using TriangleNet.Topology; + using TriangleNet.Geometry; + + /// + /// Mesh interface. + /// + public interface IMesh + { + /// + /// Gets the vertices of the mesh. + /// + ICollection Vertices { get; } + + /// + /// Gets the edges of the mesh. + /// + IEnumerable Edges { get; } + + /// + /// Gets the segments (constraint edges) of the mesh. + /// + ICollection Segments { get; } + + /// + /// Gets the triangles of the mesh. + /// + ICollection Triangles { get; } + + /// + /// Gets the holes of the mesh. + /// + IList Holes { get; } + + /// + /// Gets the bounds of the mesh. + /// + Rectangle Bounds { get; } + + /// + /// Renumber mesh vertices and triangles. + /// + void Renumber(); + + /// + /// Refine the mesh. + /// + /// The quality constraints. + /// + /// A value indicating, if the refined mesh should be Conforming Delaunay. + /// + void Refine(QualityOptions quality, bool delaunay); + } +} diff --git a/Triangle.NET/Triangle/Meshing/IQualityMesher.cs b/src/Triangle/Meshing/IQualityMesher.cs similarity index 97% rename from Triangle.NET/Triangle/Meshing/IQualityMesher.cs rename to src/Triangle/Meshing/IQualityMesher.cs index 8d8a1ff..8f97960 100644 --- a/Triangle.NET/Triangle/Meshing/IQualityMesher.cs +++ b/src/Triangle/Meshing/IQualityMesher.cs @@ -1,28 +1,28 @@ - -namespace TriangleNet.Meshing -{ - using TriangleNet.Geometry; - - /// - /// Interface for polygon triangulation with quality constraints. - /// - public interface IQualityMesher - { - /// - /// Triangulates a polygon, applying quality options. - /// - /// The polygon. - /// Quality options. - /// Mesh - IMesh Triangulate(IPolygon polygon, QualityOptions quality); - - /// - /// Triangulates a polygon, applying quality and constraint options. - /// - /// The polygon. - /// Constraint options. - /// Quality options. - /// Mesh - IMesh Triangulate(IPolygon polygon, ConstraintOptions options, QualityOptions quality); - } -} + +namespace TriangleNet.Meshing +{ + using TriangleNet.Geometry; + + /// + /// Interface for polygon triangulation with quality constraints. + /// + public interface IQualityMesher + { + /// + /// Triangulates a polygon, applying quality options. + /// + /// The polygon. + /// Quality options. + /// Mesh + IMesh Triangulate(IPolygon polygon, QualityOptions quality); + + /// + /// Triangulates a polygon, applying quality and constraint options. + /// + /// The polygon. + /// Constraint options. + /// Quality options. + /// Mesh + IMesh Triangulate(IPolygon polygon, ConstraintOptions options, QualityOptions quality); + } +} diff --git a/Triangle.NET/Triangle/Meshing/ITriangulator.cs b/src/Triangle/Meshing/ITriangulator.cs similarity index 62% rename from Triangle.NET/Triangle/Meshing/ITriangulator.cs rename to src/Triangle/Meshing/ITriangulator.cs index 5bc1541..c091599 100644 --- a/Triangle.NET/Triangle/Meshing/ITriangulator.cs +++ b/src/Triangle/Meshing/ITriangulator.cs @@ -1,25 +1,20 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Meshing -{ - using System.Collections.Generic; - using TriangleNet.Geometry; - - /// - /// Interface for point set triangulation. - /// - public interface ITriangulator - { - /// - /// Triangulates a point set. - /// - /// Collection of points. - /// - /// Mesh - IMesh Triangulate(IList points, Configuration config); - } -} + +namespace TriangleNet.Meshing +{ + using System.Collections.Generic; + using TriangleNet.Geometry; + + /// + /// Interface for point set triangulation. + /// + public interface ITriangulator + { + /// + /// Triangulates a point set. + /// + /// Collection of points. + /// + /// Mesh + IMesh Triangulate(IList points, Configuration config); + } +} diff --git a/src/Triangle/Meshing/Iterators/EdgeIterator.cs b/src/Triangle/Meshing/Iterators/EdgeIterator.cs new file mode 100644 index 0000000..b823481 --- /dev/null +++ b/src/Triangle/Meshing/Iterators/EdgeIterator.cs @@ -0,0 +1,108 @@ +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing.Iterators +{ + using System.Collections.Generic; + using TriangleNet.Topology; + using TriangleNet.Geometry; + + /// + /// Enumerates the edges of a triangulation. + /// + public class EdgeIterator + { + public IEnumerable EnumerateEdges(IMesh mesh) + { + Otri tri = default; + Otri neighbor = default; + Osub sub = default; + + Vertex p1, p2; + + foreach (var t in mesh.Triangles) + { + tri.tri = t; + tri.orient = 0; + + for (int i = 0; i < 3; i++) + { + tri.Sym(ref neighbor); + + int nid = neighbor.tri.id; + + if ((tri.tri.id < nid) || (nid == Mesh.DUMMY)) + { + p1 = tri.Org(); + p2 = tri.Dest(); + + tri.Pivot(ref sub); + + // Boundary mark of dummysub is 0, so we don't need to worry about that. + yield return new Edge(p1.id, p2.id, sub.seg.boundary); + } + + tri.orient++; + } + } + } + + /// + /// Enumerate the edges of the mesh. + /// + /// + /// + /// + /// + /// In contrast to this method will return + /// objects that include the vertex information (and not only the indices). + /// + public static IEnumerable EnumerateEdges(IMesh mesh, bool skipSegments = true) + { + Otri tri = default; + Otri neighbor = default; + Osub sub = default; + + Vertex p1, p2; + + bool segments = !skipSegments; + + foreach (var t in mesh.Triangles) + { + tri.tri = t; + tri.orient = 0; + + for (int i = 0; i < 3; i++) + { + tri.Sym(ref neighbor); + + int nid = neighbor.tri.id; + + if ((tri.tri.id < nid) || (nid == Mesh.DUMMY)) + { + p1 = tri.Org(); + p2 = tri.Dest(); + + tri.Pivot(ref sub); + + if (sub.seg.hash == Mesh.DUMMY) + { + yield return new Segment(p1, p2); + } + else if (segments) + { + // Segments might be processed separately, so only + // include them if requested. + yield return sub.seg; + } + } + + tri.orient++; + } + } + } + } +} diff --git a/Triangle.NET/Triangle/Meshing/Iterators/RegionIterator.cs b/src/Triangle/Meshing/Iterators/RegionIterator.cs similarity index 94% rename from Triangle.NET/Triangle/Meshing/Iterators/RegionIterator.cs rename to src/Triangle/Meshing/Iterators/RegionIterator.cs index 0ae494b..93d355a 100644 --- a/Triangle.NET/Triangle/Meshing/Iterators/RegionIterator.cs +++ b/src/Triangle/Meshing/Iterators/RegionIterator.cs @@ -1,135 +1,135 @@ -// ----------------------------------------------------------------------- -// -// Original Matlab code by John Burkardt, Florida State University -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Meshing.Iterators -{ - using System; - using System.Collections.Generic; - using TriangleNet.Topology; - - /// - /// Iterates the region a given triangle belongs to and applies an action - /// to each connected trianlge in that region. - /// - /// - /// The default action is to set the region id and area constraint. - /// - public class RegionIterator - { - List region; - - public RegionIterator(Mesh mesh) - { - this.region = new List(); - } - - /// - /// Set the region attribute of all trianlges connected to given triangle. - /// - /// The triangle seed. - /// If non-zero, process all triangles of the - /// region that is enclosed by segments with given boundary label. - public void Process(Triangle triangle, int boundary = 0) - { - this.Process(triangle, (tri) => - { - // Set the region id and area constraint. - tri.label = triangle.label; - tri.area = triangle.area; - }, boundary); - } - - /// - /// Process all trianlges connected to given triangle and apply given action. - /// - /// The seeding triangle. - /// The action to apply to each triangle. - /// If non-zero, process all triangles of the - /// region that is enclosed by segments with given boundary label. - public void Process(Triangle triangle, Action action, int boundary = 0) - { - // Make sure the triangle under consideration still exists. - // It may have been eaten by the virus. - if (triangle.id == Mesh.DUMMY || Otri.IsDead(triangle)) - { - return; - } - - // Add the seeding triangle to the region. - region.Add(triangle); - - triangle.infected = true; - - if (boundary == 0) - { - // Stop at any subsegment. - ProcessRegion(action, seg => seg.hash == Mesh.DUMMY); - } - else - { - // Stop at segments that have the given boundary label. - ProcessRegion(action, seg => seg.boundary != boundary); - } - - // Free up memory (virus pool should be empty anyway). - region.Clear(); - } - - /// - /// Apply given action to each triangle of selected region. - /// - /// - /// - void ProcessRegion(Action action, Func protector) - { - Otri testtri = default(Otri); - Otri neighbor = default(Otri); - Osub neighborsubseg = default(Osub); - - // Loop through all the infected triangles, spreading the attribute - // and/or area constraint to their neighbors, then to their neighbors' - // neighbors. - for (int i = 0; i < region.Count; i++) - { - // WARNING: Don't use foreach, viri list gets modified. - - testtri.tri = region[i]; - - // Apply function. - action(testtri.tri); - - // Check each of the triangle's three neighbors. - for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) - { - // Find the neighbor. - testtri.Sym(ref neighbor); - // Check for a subsegment between the triangle and its neighbor. - testtri.Pivot(ref neighborsubseg); - // Make sure the neighbor exists, is not already infected, and - // isn't protected by a subsegment. - if ((neighbor.tri.id != Mesh.DUMMY) && !neighbor.IsInfected() - && protector(neighborsubseg.seg)) - { - // Infect the neighbor. - neighbor.Infect(); - // Ensure that the neighbor's neighbors will be infected. - region.Add(neighbor.tri); - } - } - } - - // Uninfect all triangles. - foreach (var virus in region) - { - virus.infected = false; - } - - // Empty the virus pool. - region.Clear(); - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing.Iterators +{ + using System; + using System.Collections.Generic; + using TriangleNet.Topology; + + /// + /// Iterates the region a given triangle belongs to and applies an action + /// to each connected trianlge in that region. + /// + /// + /// The default action is to set the region id and area constraint. + /// + public class RegionIterator + { + List region; + + public RegionIterator(Mesh mesh) + { + this.region = new List(); + } + + /// + /// Set the region attribute of all trianlges connected to given triangle. + /// + /// The triangle seed. + /// If non-zero, process all triangles of the + /// region that is enclosed by segments with given boundary label. + public void Process(Triangle triangle, int boundary = 0) + { + this.Process(triangle, (tri) => + { + // Set the region id and area constraint. + tri.label = triangle.label; + tri.area = triangle.area; + }, boundary); + } + + /// + /// Process all trianlges connected to given triangle and apply given action. + /// + /// The seeding triangle. + /// The action to apply to each triangle. + /// If non-zero, process all triangles of the + /// region that is enclosed by segments with given boundary label. + public void Process(Triangle triangle, Action action, int boundary = 0) + { + // Make sure the triangle under consideration still exists. + // It may have been eaten by the virus. + if (triangle.id == Mesh.DUMMY || Otri.IsDead(triangle)) + { + return; + } + + // Add the seeding triangle to the region. + region.Add(triangle); + + triangle.infected = true; + + if (boundary == 0) + { + // Stop at any subsegment. + ProcessRegion(action, seg => seg.hash == Mesh.DUMMY); + } + else + { + // Stop at segments that have the given boundary label. + ProcessRegion(action, seg => seg.boundary != boundary); + } + + // Free up memory (virus pool should be empty anyway). + region.Clear(); + } + + /// + /// Apply given action to each triangle of selected region. + /// + /// + /// + void ProcessRegion(Action action, Func protector) + { + Otri testtri = default(Otri); + Otri neighbor = default(Otri); + Osub neighborsubseg = default(Osub); + + // Loop through all the infected triangles, spreading the attribute + // and/or area constraint to their neighbors, then to their neighbors' + // neighbors. + for (int i = 0; i < region.Count; i++) + { + // WARNING: Don't use foreach, viri list gets modified. + + testtri.tri = region[i]; + + // Apply function. + action(testtri.tri); + + // Check each of the triangle's three neighbors. + for (testtri.orient = 0; testtri.orient < 3; testtri.orient++) + { + // Find the neighbor. + testtri.Sym(ref neighbor); + // Check for a subsegment between the triangle and its neighbor. + testtri.Pivot(ref neighborsubseg); + // Make sure the neighbor exists, is not already infected, and + // isn't protected by a subsegment. + if ((neighbor.tri.id != Mesh.DUMMY) && !neighbor.IsInfected() + && protector(neighborsubseg.seg)) + { + // Infect the neighbor. + neighbor.Infect(); + // Ensure that the neighbor's neighbors will be infected. + region.Add(neighbor.tri); + } + } + } + + // Uninfect all triangles. + foreach (var virus in region) + { + virus.infected = false; + } + + // Empty the virus pool. + region.Clear(); + } + } +} diff --git a/Triangle.NET/Triangle/Meshing/Iterators/VertexCirculator.cs b/src/Triangle/Meshing/Iterators/VertexCirculator.cs similarity index 90% rename from Triangle.NET/Triangle/Meshing/Iterators/VertexCirculator.cs rename to src/Triangle/Meshing/Iterators/VertexCirculator.cs index 44821a6..69191d2 100644 --- a/Triangle.NET/Triangle/Meshing/Iterators/VertexCirculator.cs +++ b/src/Triangle/Meshing/Iterators/VertexCirculator.cs @@ -1,100 +1,105 @@ - -namespace TriangleNet.Meshing.Iterators -{ - using System.Collections.Generic; - using TriangleNet.Geometry; - using TriangleNet.Topology; - - public class VertexCirculator - { - List cache = new List(); - - public VertexCirculator(Mesh mesh) - { - mesh.MakeVertexMap(); - } - - /// - /// Enumerate all vertices adjacent to given vertex. - /// - /// The center vertex. - /// - public IEnumerable EnumerateVertices(Vertex vertex) - { - BuildCache(vertex, true); - - foreach (var item in cache) - { - yield return item.Dest(); - } - } - - /// - /// Enumerate all triangles adjacent to given vertex. - /// - /// The center vertex. - /// - public IEnumerable EnumerateTriangles(Vertex vertex) - { - BuildCache(vertex, false); - - foreach (var item in cache) - { - yield return item.tri; - } - } - - private void BuildCache(Vertex vertex, bool vertices) - { - cache.Clear(); - - Otri init = vertex.tri; - Otri next = default(Otri); - Otri prev = default(Otri); - - init.Copy(ref next); - - // Move counter-clockwise around the vertex. - while (next.tri.id != Mesh.DUMMY) - { - cache.Add(next); - - next.Copy(ref prev); - next.Onext(); - - if (next.Equals(init)) - { - break; - } - } - - if (next.tri.id == Mesh.DUMMY) - { - // We reached the boundary. To get all adjacent triangles, start - // again at init triangle and now move clockwise. - init.Copy(ref next); - - if (vertices) - { - // Don't forget to add the vertex lying on the boundary. - prev.Lnext(); - cache.Add(prev); - } - - next.Oprev(); - - while (next.tri.id != Mesh.DUMMY) - { - cache.Insert(0, next); - - next.Oprev(); - - if (next.Equals(init)) - { - break; - } - } - } - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing.Iterators +{ + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Topology; + + public class VertexCirculator + { + List cache = new List(); + + public VertexCirculator(Mesh mesh) + { + mesh.MakeVertexMap(); + } + + /// + /// Enumerate all vertices adjacent to given vertex. + /// + /// The center vertex. + /// + public IEnumerable EnumerateVertices(Vertex vertex) + { + BuildCache(vertex, true); + + foreach (var item in cache) + { + yield return item.Dest(); + } + } + + /// + /// Enumerate all triangles adjacent to given vertex. + /// + /// The center vertex. + /// + public IEnumerable EnumerateTriangles(Vertex vertex) + { + BuildCache(vertex, false); + + foreach (var item in cache) + { + yield return item.tri; + } + } + + private void BuildCache(Vertex vertex, bool vertices) + { + cache.Clear(); + + Otri init = vertex.tri; + Otri next = default(Otri); + Otri prev = default(Otri); + + init.Copy(ref next); + + // Move counter-clockwise around the vertex. + while (next.tri.id != Mesh.DUMMY) + { + cache.Add(next); + + next.Copy(ref prev); + next.Onext(); + + if (next.Equals(init)) + { + break; + } + } + + if (next.tri.id == Mesh.DUMMY) + { + // We reached the boundary. To get all adjacent triangles, start + // again at init triangle and now move clockwise. + init.Copy(ref next); + + if (vertices) + { + // Don't forget to add the vertex lying on the boundary. + prev.Lnext(); + cache.Add(prev); + } + + next.Oprev(); + + while (next.tri.id != Mesh.DUMMY) + { + cache.Insert(0, next); + + next.Oprev(); + + if (next.Equals(init)) + { + break; + } + } + } + } + } +} diff --git a/Triangle.NET/Triangle/Meshing/QualityMesher.cs b/src/Triangle/Meshing/QualityMesher.cs similarity index 97% rename from Triangle.NET/Triangle/Meshing/QualityMesher.cs rename to src/Triangle/Meshing/QualityMesher.cs index edeef48..a54416d 100644 --- a/Triangle.NET/Triangle/Meshing/QualityMesher.cs +++ b/src/Triangle/Meshing/QualityMesher.cs @@ -1,898 +1,900 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Meshing -{ - using System; - using System.Collections.Generic; - using TriangleNet.Geometry; - using TriangleNet.Logging; - using TriangleNet.Meshing.Data; - using TriangleNet.Tools; - using TriangleNet.Topology; - - /// - /// Provides methods for mesh quality enforcement and testing. - /// - class QualityMesher - { - IPredicates predicates; - - Queue badsubsegs; - BadTriQueue queue; - Mesh mesh; - Behavior behavior; - - NewLocation newLocation; - - ILog logger; - - // Stores the vertices of the triangle that contains newvertex - // in SplitTriangle method. - Triangle newvertex_tri; - - public QualityMesher(Mesh mesh, Configuration config) - { - logger = Log.Instance; - - badsubsegs = new Queue(); - queue = new BadTriQueue(); - - this.mesh = mesh; - this.predicates = config.Predicates(); - - this.behavior = mesh.behavior; - - newLocation = new NewLocation(mesh, predicates); - - newvertex_tri = new Triangle(); - } - - /// - /// Apply quality constraints to a mesh. - /// - /// The quality constraints. - /// A value indicating, if the refined mesh should be Conforming Delaunay. - public void Apply(QualityOptions quality, bool delaunay = false) - { - // Copy quality options - if (quality != null) - { - behavior.Quality = true; - - behavior.MinAngle = quality.MinimumAngle; - behavior.MaxAngle = quality.MaximumAngle; - behavior.MaxArea = quality.MaximumArea; - behavior.UserTest = quality.UserTest; - behavior.VarArea = quality.VariableArea; - - behavior.ConformingDelaunay = behavior.ConformingDelaunay || delaunay; - - mesh.steinerleft = quality.SteinerPoints == 0 ? -1 : quality.SteinerPoints; - } - - // TODO: remove - if (!behavior.Poly) - { - // Be careful not to allocate space for element area constraints that - // will never be assigned any value (other than the default -1.0). - behavior.VarArea = false; - } - - // Ensure that no vertex can be mistaken for a triangular bounding - // box vertex in insertvertex(). - mesh.infvertex1 = null; - mesh.infvertex2 = null; - mesh.infvertex3 = null; - - if (behavior.useSegments) - { - mesh.checksegments = true; - } - - if (behavior.Quality && mesh.triangles.Count > 0) - { - // Enforce angle and area constraints. - EnforceQuality(); - } - } - - /// - /// Add a bad subsegment to the queue. - /// - /// Bad subsegment. - public void AddBadSubseg(BadSubseg badseg) - { - badsubsegs.Enqueue(badseg); - } - - #region Check - - /// - /// Check a subsegment to see if it is encroached; add it to the list if it is. - /// - /// The subsegment to check. - /// Returns a nonzero value if the subsegment is encroached. - /// - /// A subsegment is encroached if there is a vertex in its diametral lens. - /// For Ruppert's algorithm (-D switch), the "diametral lens" is the - /// diametral circle. For Chew's algorithm (default), the diametral lens is - /// just big enough to enclose two isosceles triangles whose bases are the - /// subsegment. Each of the two isosceles triangles has two angles equal - /// to 'b.minangle'. - /// - /// Chew's algorithm does not require diametral lenses at all--but they save - /// time. Any vertex inside a subsegment's diametral lens implies that the - /// triangle adjoining the subsegment will be too skinny, so it's only a - /// matter of time before the encroaching vertex is deleted by Chew's - /// algorithm. It's faster to simply not insert the doomed vertex in the - /// first place, which is why I use diametral lenses with Chew's algorithm. - /// - public int CheckSeg4Encroach(ref Osub testsubseg) - { - Otri neighbortri = default(Otri); - Osub testsym = default(Osub); - BadSubseg encroachedseg; - double dotproduct; - int encroached; - int sides; - Vertex eorg, edest, eapex; - - encroached = 0; - sides = 0; - - eorg = testsubseg.Org(); - edest = testsubseg.Dest(); - // Check one neighbor of the subsegment. - testsubseg.Pivot(ref neighbortri); - // Does the neighbor exist, or is this a boundary edge? - if (neighbortri.tri.id != Mesh.DUMMY) - { - sides++; - // Find a vertex opposite this subsegment. - eapex = neighbortri.Apex(); - // Check whether the apex is in the diametral lens of the subsegment - // (the diametral circle if 'conformdel' is set). A dot product - // of two sides of the triangle is used to check whether the angle - // at the apex is greater than (180 - 2 'minangle') degrees (for - // lenses; 90 degrees for diametral circles). - dotproduct = (eorg.x - eapex.x) * (edest.x - eapex.x) + - (eorg.y - eapex.y) * (edest.y - eapex.y); - if (dotproduct < 0.0) - { - if (behavior.ConformingDelaunay || - (dotproduct * dotproduct >= - (2.0 * behavior.goodAngle - 1.0) * (2.0 * behavior.goodAngle - 1.0) * - ((eorg.x - eapex.x) * (eorg.x - eapex.x) + - (eorg.y - eapex.y) * (eorg.y - eapex.y)) * - ((edest.x - eapex.x) * (edest.x - eapex.x) + - (edest.y - eapex.y) * (edest.y - eapex.y)))) - { - encroached = 1; - } - } - } - // Check the other neighbor of the subsegment. - testsubseg.Sym(ref testsym); - testsym.Pivot(ref neighbortri); - // Does the neighbor exist, or is this a boundary edge? - if (neighbortri.tri.id != Mesh.DUMMY) - { - sides++; - // Find the other vertex opposite this subsegment. - eapex = neighbortri.Apex(); - // Check whether the apex is in the diametral lens of the subsegment - // (or the diametral circle, if 'conformdel' is set). - dotproduct = (eorg.x - eapex.x) * (edest.x - eapex.x) + - (eorg.y - eapex.y) * (edest.y - eapex.y); - if (dotproduct < 0.0) - { - if (behavior.ConformingDelaunay || - (dotproduct * dotproduct >= - (2.0 * behavior.goodAngle - 1.0) * (2.0 * behavior.goodAngle - 1.0) * - ((eorg.x - eapex.x) * (eorg.x - eapex.x) + - (eorg.y - eapex.y) * (eorg.y - eapex.y)) * - ((edest.x - eapex.x) * (edest.x - eapex.x) + - (edest.y - eapex.y) * (edest.y - eapex.y)))) - { - encroached += 2; - } - } - } - - if (encroached > 0 && (behavior.NoBisect == 0 || ((behavior.NoBisect == 1) && (sides == 2)))) - { - // Add the subsegment to the list of encroached subsegments. - // Be sure to get the orientation right. - encroachedseg = new BadSubseg(); - if (encroached == 1) - { - encroachedseg.subseg = testsubseg; - encroachedseg.org = eorg; - encroachedseg.dest = edest; - } - else - { - encroachedseg.subseg = testsym; - encroachedseg.org = edest; - encroachedseg.dest = eorg; - } - - badsubsegs.Enqueue(encroachedseg); - } - - return encroached; - } - - /// - /// Test a triangle for quality and size. - /// - /// Triangle to check. - /// - /// Tests a triangle to see if it satisfies the minimum angle condition and - /// the maximum area condition. Triangles that aren't up to spec are added - /// to the bad triangle queue. - /// - public void TestTriangle(ref Otri testtri) - { - Otri tri1 = default(Otri), tri2 = default(Otri); - Osub testsub = default(Osub); - Vertex torg, tdest, tapex; - Vertex base1, base2; - Vertex org1, dest1, org2, dest2; - Vertex joinvertex; - double dxod, dyod, dxda, dyda, dxao, dyao; - double dxod2, dyod2, dxda2, dyda2, dxao2, dyao2; - double apexlen, orglen, destlen, minedge; - double angle; - double area; - double dist1, dist2; - - double maxangle; - - torg = testtri.Org(); - tdest = testtri.Dest(); - tapex = testtri.Apex(); - dxod = torg.x - tdest.x; - dyod = torg.y - tdest.y; - dxda = tdest.x - tapex.x; - dyda = tdest.y - tapex.y; - dxao = tapex.x - torg.x; - dyao = tapex.y - torg.y; - dxod2 = dxod * dxod; - dyod2 = dyod * dyod; - dxda2 = dxda * dxda; - dyda2 = dyda * dyda; - dxao2 = dxao * dxao; - dyao2 = dyao * dyao; - // Find the lengths of the triangle's three edges. - apexlen = dxod2 + dyod2; - orglen = dxda2 + dyda2; - destlen = dxao2 + dyao2; - - if ((apexlen < orglen) && (apexlen < destlen)) - { - // The edge opposite the apex is shortest. - minedge = apexlen; - // Find the square of the cosine of the angle at the apex. - angle = dxda * dxao + dyda * dyao; - angle = angle * angle / (orglen * destlen); - base1 = torg; - base2 = tdest; - testtri.Copy(ref tri1); - } - else if (orglen < destlen) - { - // The edge opposite the origin is shortest. - minedge = orglen; - // Find the square of the cosine of the angle at the origin. - angle = dxod * dxao + dyod * dyao; - angle = angle * angle / (apexlen * destlen); - base1 = tdest; - base2 = tapex; - testtri.Lnext(ref tri1); - } - else - { - // The edge opposite the destination is shortest. - minedge = destlen; - // Find the square of the cosine of the angle at the destination. - angle = dxod * dxda + dyod * dyda; - angle = angle * angle / (apexlen * orglen); - base1 = tapex; - base2 = torg; - testtri.Lprev(ref tri1); - } - - if (behavior.VarArea || behavior.fixedArea || (behavior.UserTest != null)) - { - // Check whether the area is larger than permitted. - area = 0.5 * (dxod * dyda - dyod * dxda); - if (behavior.fixedArea && (area > behavior.MaxArea)) - { - // Add this triangle to the list of bad triangles. - queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); - return; - } - - // Nonpositive area constraints are treated as unconstrained. - if ((behavior.VarArea) && (area > testtri.tri.area) && (testtri.tri.area > 0.0)) - { - // Add this triangle to the list of bad triangles. - queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); - return; - } - - // Check whether the user thinks this triangle is too large. - if ((behavior.UserTest != null) && behavior.UserTest(testtri.tri, area)) - { - queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); - return; - } - } - - // find the maximum edge and accordingly the pqr orientation - if ((apexlen > orglen) && (apexlen > destlen)) - { - // The edge opposite the apex is longest. - // maxedge = apexlen; - // Find the cosine of the angle at the apex. - maxangle = (orglen + destlen - apexlen) / (2 * Math.Sqrt(orglen * destlen)); - } - else if (orglen > destlen) - { - // The edge opposite the origin is longest. - // maxedge = orglen; - // Find the cosine of the angle at the origin. - maxangle = (apexlen + destlen - orglen) / (2 * Math.Sqrt(apexlen * destlen)); - } - else - { - // The edge opposite the destination is longest. - // maxedge = destlen; - // Find the cosine of the angle at the destination. - maxangle = (apexlen + orglen - destlen) / (2 * Math.Sqrt(apexlen * orglen)); - } - - // Check whether the angle is smaller than permitted. - if ((angle > behavior.goodAngle) || (maxangle < behavior.maxGoodAngle && behavior.MaxAngle != 0.0)) - { - // Use the rules of Miller, Pav, and Walkington to decide that certain - // triangles should not be split, even if they have bad angles. - // A skinny triangle is not split if its shortest edge subtends a - // small input angle, and both endpoints of the edge lie on a - // concentric circular shell. For convenience, I make a small - // adjustment to that rule: I check if the endpoints of the edge - // both lie in segment interiors, equidistant from the apex where - // the two segments meet. - // First, check if both points lie in segment interiors. - if ((base1.type == VertexType.SegmentVertex) && - (base2.type == VertexType.SegmentVertex)) - { - // Check if both points lie in a common segment. If they do, the - // skinny triangle is enqueued to be split as usual. - tri1.Pivot(ref testsub); - if (testsub.seg.hash == Mesh.DUMMY) - { - // No common segment. Find a subsegment that contains 'torg'. - tri1.Copy(ref tri2); - do - { - tri1.Oprev(); - tri1.Pivot(ref testsub); - } while (testsub.seg.hash == Mesh.DUMMY); - // Find the endpoints of the containing segment. - org1 = testsub.SegOrg(); - dest1 = testsub.SegDest(); - // Find a subsegment that contains 'tdest'. - do - { - tri2.Dnext(); - tri2.Pivot(ref testsub); - } while (testsub.seg.hash == Mesh.DUMMY); - // Find the endpoints of the containing segment. - org2 = testsub.SegOrg(); - dest2 = testsub.SegDest(); - // Check if the two containing segments have an endpoint in common. - joinvertex = null; - if ((dest1.x == org2.x) && (dest1.y == org2.y)) - { - joinvertex = dest1; - } - else if ((org1.x == dest2.x) && (org1.y == dest2.y)) - { - joinvertex = org1; - } - if (joinvertex != null) - { - // Compute the distance from the common endpoint (of the two - // segments) to each of the endpoints of the shortest edge. - dist1 = ((base1.x - joinvertex.x) * (base1.x - joinvertex.x) + - (base1.y - joinvertex.y) * (base1.y - joinvertex.y)); - dist2 = ((base2.x - joinvertex.x) * (base2.x - joinvertex.x) + - (base2.y - joinvertex.y) * (base2.y - joinvertex.y)); - // If the two distances are equal, don't split the triangle. - if ((dist1 < 1.001 * dist2) && (dist1 > 0.999 * dist2)) - { - // Return now to avoid enqueueing the bad triangle. - return; - } - } - } - } - - // Add this triangle to the list of bad triangles. - queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); - } - } - - #endregion - - #region Maintanance - - /// - /// Traverse the entire list of subsegments, and check each to see if it - /// is encroached. If so, add it to the list. - /// - private void TallyEncs() - { - Osub subsegloop = default(Osub); - subsegloop.orient = 0; - - foreach (var seg in mesh.subsegs.Values) - { - subsegloop.seg = seg; - // If the segment is encroached, add it to the list. - CheckSeg4Encroach(ref subsegloop); - } - } - - /// - /// Split all the encroached subsegments. - /// - /// A flag that specifies whether one should take - /// note of new bad triangles that result from inserting vertices to repair - /// encroached subsegments. - /// - /// Each encroached subsegment is repaired by splitting it - inserting a - /// vertex at or near its midpoint. Newly inserted vertices may encroach - /// upon other subsegments; these are also repaired. - /// - private void SplitEncSegs(bool triflaws) - { - Otri enctri = default(Otri); - Otri testtri = default(Otri); - Osub testsh = default(Osub); - Osub currentenc = default(Osub); - BadSubseg seg; - Vertex eorg, edest, eapex; - Vertex newvertex; - InsertVertexResult success; - double segmentlength, nearestpoweroftwo; - double split; - double multiplier, divisor; - bool acuteorg, acuteorg2, acutedest, acutedest2; - - // Note that steinerleft == -1 if an unlimited number - // of Steiner points is allowed. - while (badsubsegs.Count > 0) - { - if (mesh.steinerleft == 0) - { - break; - } - - seg = badsubsegs.Dequeue(); - - currentenc = seg.subseg; - eorg = currentenc.Org(); - edest = currentenc.Dest(); - // Make sure that this segment is still the same segment it was - // when it was determined to be encroached. If the segment was - // enqueued multiple times (because several newly inserted - // vertices encroached it), it may have already been split. - if (!Osub.IsDead(currentenc.seg) && (eorg == seg.org) && (edest == seg.dest)) - { - // To decide where to split a segment, we need to know if the - // segment shares an endpoint with an adjacent segment. - // The concern is that, if we simply split every encroached - // segment in its center, two adjacent segments with a small - // angle between them might lead to an infinite loop; each - // vertex added to split one segment will encroach upon the - // other segment, which must then be split with a vertex that - // will encroach upon the first segment, and so on forever. - // To avoid this, imagine a set of concentric circles, whose - // radii are powers of two, about each segment endpoint. - // These concentric circles determine where the segment is - // split. (If both endpoints are shared with adjacent - // segments, split the segment in the middle, and apply the - // concentric circles for later splittings.) - - // Is the origin shared with another segment? - currentenc.Pivot(ref enctri); - enctri.Lnext(ref testtri); - testtri.Pivot(ref testsh); - acuteorg = testsh.seg.hash != Mesh.DUMMY; - // Is the destination shared with another segment? - testtri.Lnext(); - testtri.Pivot(ref testsh); - acutedest = testsh.seg.hash != Mesh.DUMMY; - - // If we're using Chew's algorithm (rather than Ruppert's) - // to define encroachment, delete free vertices from the - // subsegment's diametral circle. - if (!behavior.ConformingDelaunay && !acuteorg && !acutedest) - { - eapex = enctri.Apex(); - while ((eapex.type == VertexType.FreeVertex) && - ((eorg.x - eapex.x) * (edest.x - eapex.x) + - (eorg.y - eapex.y) * (edest.y - eapex.y) < 0.0)) - { - mesh.DeleteVertex(ref testtri); - currentenc.Pivot(ref enctri); - eapex = enctri.Apex(); - enctri.Lprev(ref testtri); - } - } - - // Now, check the other side of the segment, if there's a triangle there. - enctri.Sym(ref testtri); - if (testtri.tri.id != Mesh.DUMMY) - { - // Is the destination shared with another segment? - testtri.Lnext(); - testtri.Pivot(ref testsh); - acutedest2 = testsh.seg.hash != Mesh.DUMMY; - acutedest = acutedest || acutedest2; - // Is the origin shared with another segment? - testtri.Lnext(); - testtri.Pivot(ref testsh); - acuteorg2 = testsh.seg.hash != Mesh.DUMMY; - acuteorg = acuteorg || acuteorg2; - - // Delete free vertices from the subsegment's diametral circle. - if (!behavior.ConformingDelaunay && !acuteorg2 && !acutedest2) - { - eapex = testtri.Org(); - while ((eapex.type == VertexType.FreeVertex) && - ((eorg.x - eapex.x) * (edest.x - eapex.x) + - (eorg.y - eapex.y) * (edest.y - eapex.y) < 0.0)) - { - mesh.DeleteVertex(ref testtri); - enctri.Sym(ref testtri); - eapex = testtri.Apex(); - testtri.Lprev(); - } - } - } - - // Use the concentric circles if exactly one endpoint is shared - // with another adjacent segment. - if (acuteorg || acutedest) - { - segmentlength = Math.Sqrt((edest.x - eorg.x) * (edest.x - eorg.x) + - (edest.y - eorg.y) * (edest.y - eorg.y)); - // Find the power of two that most evenly splits the segment. - // The worst case is a 2:1 ratio between subsegment lengths. - nearestpoweroftwo = 1.0; - while (segmentlength > 3.0 * nearestpoweroftwo) - { - nearestpoweroftwo *= 2.0; - } - while (segmentlength < 1.5 * nearestpoweroftwo) - { - nearestpoweroftwo *= 0.5; - } - // Where do we split the segment? - split = nearestpoweroftwo / segmentlength; - if (acutedest) - { - split = 1.0 - split; - } - } - else - { - // If we're not worried about adjacent segments, split - // this segment in the middle. - split = 0.5; - } - - // Create the new vertex (interpolate coordinates). - newvertex = new Vertex( - eorg.x + split * (edest.x - eorg.x), - eorg.y + split * (edest.y - eorg.y), - currentenc.seg.boundary -#if USE_ATTRIBS - , mesh.nextras -#endif - ); - - newvertex.type = VertexType.SegmentVertex; - - newvertex.hash = mesh.hash_vtx++; - newvertex.id = newvertex.hash; - - mesh.vertices.Add(newvertex.hash, newvertex); -#if USE_ATTRIBS - // Interpolate attributes. - for (int i = 0; i < mesh.nextras; i++) - { - newvertex.attributes[i] = eorg.attributes[i] - + split * (edest.attributes[i] - eorg.attributes[i]); - } -#endif -#if USE_Z - newvertex.z = eorg.z + split * (edest.z - eorg.z); -#endif - if (!Behavior.NoExact) - { - // Roundoff in the above calculation may yield a 'newvertex' - // that is not precisely collinear with 'eorg' and 'edest'. - // Improve collinearity by one step of iterative refinement. - multiplier = predicates.CounterClockwise(eorg, edest, newvertex); - divisor = ((eorg.x - edest.x) * (eorg.x - edest.x) + - (eorg.y - edest.y) * (eorg.y - edest.y)); - if ((multiplier != 0.0) && (divisor != 0.0)) - { - multiplier = multiplier / divisor; - // Watch out for NANs. - if (!double.IsNaN(multiplier)) - { - newvertex.x += multiplier * (edest.y - eorg.y); - newvertex.y += multiplier * (eorg.x - edest.x); - } - } - } - - // Check whether the new vertex lies on an endpoint. - if (((newvertex.x == eorg.x) && (newvertex.y == eorg.y)) || - ((newvertex.x == edest.x) && (newvertex.y == edest.y))) - { - - logger.Error("Ran out of precision: I attempted to split a" - + " segment to a smaller size than can be accommodated by" - + " the finite precision of floating point arithmetic.", - "Quality.SplitEncSegs()"); - - throw new Exception("Ran out of precision"); - } - // Insert the splitting vertex. This should always succeed. - success = mesh.InsertVertex(newvertex, ref enctri, ref currentenc, true, triflaws); - if ((success != InsertVertexResult.Successful) && (success != InsertVertexResult.Encroaching)) - { - logger.Error("Failure to split a segment.", "Quality.SplitEncSegs()"); - throw new Exception("Failure to split a segment."); - } - if (mesh.steinerleft > 0) - { - mesh.steinerleft--; - } - // Check the two new subsegments to see if they're encroached. - CheckSeg4Encroach(ref currentenc); - currentenc.Next(); - CheckSeg4Encroach(ref currentenc); - } - - // Set subsegment's origin to NULL. This makes it possible to detect dead - // badsubsegs when traversing the list of all badsubsegs. - seg.org = null; - } - } - - /// - /// Test every triangle in the mesh for quality measures. - /// - private void TallyFaces() - { - Otri triangleloop = default(Otri); - triangleloop.orient = 0; - - foreach (var tri in mesh.triangles) - { - triangleloop.tri = tri; - - // If the triangle is bad, enqueue it. - TestTriangle(ref triangleloop); - } - } - - /// - /// Inserts a vertex at the circumcenter of a triangle. Deletes - /// the newly inserted vertex if it encroaches upon a segment. - /// - /// - private void SplitTriangle(BadTriangle badtri) - { - Otri badotri = default(Otri); - Vertex borg, bdest, bapex; - Point newloc; // Location of the new vertex - double xi = 0, eta = 0; - InsertVertexResult success; - bool errorflag; - - badotri = badtri.poortri; - borg = badotri.Org(); - bdest = badotri.Dest(); - bapex = badotri.Apex(); - - // Make sure that this triangle is still the same triangle it was - // when it was tested and determined to be of bad quality. - // Subsequent transformations may have made it a different triangle. - if (!Otri.IsDead(badotri.tri) && (borg == badtri.org) && - (bdest == badtri.dest) && (bapex == badtri.apex)) - { - errorflag = false; - // Create a new vertex at the triangle's circumcenter. - - // Using the original (simpler) Steiner point location method - // for mesh refinement. - // TODO: NewLocation doesn't work for refinement. Why? Maybe - // reset VertexType? - if (behavior.fixedArea || behavior.VarArea) - { - newloc = predicates.FindCircumcenter(borg, bdest, bapex, ref xi, ref eta, behavior.offconstant); - } - else - { - newloc = newLocation.FindLocation(borg, bdest, bapex, ref xi, ref eta, true, badotri); - } - - // Check whether the new vertex lies on a triangle vertex. - if (((newloc.x == borg.x) && (newloc.y == borg.y)) || - ((newloc.x == bdest.x) && (newloc.y == bdest.y)) || - ((newloc.x == bapex.x) && (newloc.y == bapex.y))) - { - if (Log.Verbose) - { - logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()"); - errorflag = true; - } - } - else - { - // The new vertex must be in the interior, and therefore is a - // free vertex with a marker of zero. - Vertex newvertex = new Vertex(newloc.x, newloc.y, 0 -#if USE_ATTRIBS - , mesh.nextras -#endif - ); - - newvertex.type = VertexType.FreeVertex; - - // Ensure that the handle 'badotri' does not represent the longest - // edge of the triangle. This ensures that the circumcenter must - // fall to the left of this edge, so point location will work. - // (If the angle org-apex-dest exceeds 90 degrees, then the - // circumcenter lies outside the org-dest edge, and eta is - // negative. Roundoff error might prevent eta from being - // negative when it should be, so I test eta against xi.) - if (eta < xi) - { - badotri.Lprev(); - } - - // Assign triangle for attributes interpolation. - newvertex.tri.tri = newvertex_tri; - - // Insert the circumcenter, searching from the edge of the triangle, - // and maintain the Delaunay property of the triangulation. - Osub tmp = default(Osub); - success = mesh.InsertVertex(newvertex, ref badotri, ref tmp, true, true); - - if (success == InsertVertexResult.Successful) - { - newvertex.hash = mesh.hash_vtx++; - newvertex.id = newvertex.hash; -#if USE_ATTRIBS - if (mesh.nextras > 0) - { - Interpolation.InterpolateAttributes(newvertex, newvertex.tri.tri, mesh.nextras); - } -#endif -#if USE_Z - Interpolation.InterpolateZ(newvertex, newvertex.tri.tri); -#endif - mesh.vertices.Add(newvertex.hash, newvertex); - - if (mesh.steinerleft > 0) - { - mesh.steinerleft--; - } - } - else if (success == InsertVertexResult.Encroaching) - { - // If the newly inserted vertex encroaches upon a subsegment, - // delete the new vertex. - mesh.UndoVertex(); - } - else if (success == InsertVertexResult.Violating) - { - // Failed to insert the new vertex, but some subsegment was - // marked as being encroached. - } - else - { // success == DUPLICATEVERTEX - // Couldn't insert the new vertex because a vertex is already there. - if (Log.Verbose) - { - logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()"); - errorflag = true; - } - } - } - if (errorflag) - { - logger.Error("The new vertex is at the circumcenter of triangle: This probably " - + "means that I am trying to refine triangles to a smaller size than can be " - + "accommodated by the finite precision of floating point arithmetic.", - "Quality.SplitTriangle()"); - - throw new Exception("The new vertex is at the circumcenter of triangle."); - } - } - } - - /// - /// Remove all the encroached subsegments and bad triangles from the triangulation. - /// - private void EnforceQuality() - { - BadTriangle badtri; - - // Test all segments to see if they're encroached. - TallyEncs(); - - // Fix encroached subsegments without noting bad triangles. - SplitEncSegs(false); - // At this point, if we haven't run out of Steiner points, the - // triangulation should be (conforming) Delaunay. - - // Next, we worry about enforcing triangle quality. - if ((behavior.MinAngle > 0.0) || behavior.VarArea || behavior.fixedArea || behavior.UserTest != null) - { - // TODO: Reset queue? (Or is it always empty at this point) - - // Test all triangles to see if they're bad. - TallyFaces(); - - mesh.checkquality = true; - while ((queue.Count > 0) && (mesh.steinerleft != 0)) - { - // Fix one bad triangle by inserting a vertex at its circumcenter. - badtri = queue.Dequeue(); - SplitTriangle(badtri); - - if (badsubsegs.Count > 0) - { - // Put bad triangle back in queue for another try later. - queue.Enqueue(badtri); - // Fix any encroached subsegments that resulted. - // Record any new bad triangles that result. - SplitEncSegs(true); - } - } - } - - // At this point, if the "-D" switch was selected and we haven't run out - // of Steiner points, the triangulation should be (conforming) Delaunay - // and have no low-quality triangles. - - // Might we have run out of Steiner points too soon? - if (Log.Verbose && behavior.ConformingDelaunay && (badsubsegs.Count > 0) && (mesh.steinerleft == 0)) - { - - logger.Warning("I ran out of Steiner points, but the mesh has encroached subsegments, " - + "and therefore might not be truly Delaunay. If the Delaunay property is important " - + "to you, try increasing the number of Steiner points.", - "Quality.EnforceQuality()"); - } - } - - #endregion - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Meshing +{ + using System; + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Meshing.Data; + using TriangleNet.Topology; + + /// + /// Provides methods for mesh quality enforcement and testing. + /// + class QualityMesher + { + IPredicates predicates; + + Queue badsubsegs; + BadTriQueue queue; + Mesh mesh; + Behavior behavior; + + NewLocation newLocation; + + Log logger = Log.Instance; + + // Stores the vertices of the triangle that contains newvertex + // in SplitTriangle method. + Triangle newvertex_tri; + + public QualityMesher(Mesh mesh, Configuration config) + { + badsubsegs = new Queue(); + queue = new BadTriQueue(); + + this.mesh = mesh; + this.predicates = config.Predicates(); + + this.behavior = mesh.behavior; + + newLocation = new NewLocation(mesh, predicates); + + newvertex_tri = new Triangle(); + } + + /// + /// Apply quality constraints to a mesh. + /// + /// The quality constraints. + /// A value indicating, if the refined mesh should be Conforming Delaunay. + public void Apply(QualityOptions quality, bool delaunay = false) + { + // Copy quality options + if (quality != null) + { + behavior.Quality = true; + + behavior.MinAngle = quality.MinimumAngle; + behavior.MaxAngle = quality.MaximumAngle; + behavior.MaxArea = quality.MaximumArea; + behavior.UserTest = quality.UserTest; + behavior.Exclude = quality.Exclude; + behavior.VarArea = quality.VariableArea; + + behavior.ConformingDelaunay = behavior.ConformingDelaunay || delaunay; + + mesh.steinerleft = quality.SteinerPoints == 0 ? -1 : quality.SteinerPoints; + } + + // TODO: remove + if (!behavior.Poly) + { + // Be careful not to allocate space for element area constraints that + // will never be assigned any value (other than the default -1.0). + behavior.VarArea = false; + } + + // Ensure that no vertex can be mistaken for a triangular bounding + // box vertex in insertvertex(). + mesh.infvertex1 = null; + mesh.infvertex2 = null; + mesh.infvertex3 = null; + + if (behavior.useSegments) + { + mesh.checksegments = true; + } + + if (behavior.Quality && mesh.triangles.Count > 0) + { + // Enforce angle and area constraints. + EnforceQuality(); + } + } + + /// + /// Add a bad subsegment to the queue. + /// + /// Bad subsegment. + public void AddBadSubseg(BadSubseg badseg) + { + badsubsegs.Enqueue(badseg); + } + + #region Check + + /// + /// Check a subsegment to see if it is encroached; add it to the list if it is. + /// + /// The subsegment to check. + /// Returns a nonzero value if the subsegment is encroached. + /// + /// A subsegment is encroached if there is a vertex in its diametral lens. + /// For Ruppert's algorithm (-D switch), the "diametral lens" is the + /// diametral circle. For Chew's algorithm (default), the diametral lens is + /// just big enough to enclose two isosceles triangles whose bases are the + /// subsegment. Each of the two isosceles triangles has two angles equal + /// to 'b.minangle'. + /// + /// Chew's algorithm does not require diametral lenses at all--but they save + /// time. Any vertex inside a subsegment's diametral lens implies that the + /// triangle adjoining the subsegment will be too skinny, so it's only a + /// matter of time before the encroaching vertex is deleted by Chew's + /// algorithm. It's faster to simply not insert the doomed vertex in the + /// first place, which is why I use diametral lenses with Chew's algorithm. + /// + public int CheckSeg4Encroach(ref Osub testsubseg) + { + Otri neighbortri = default(Otri); + Osub testsym = default(Osub); + BadSubseg encroachedseg; + double dotproduct; + int encroached; + int sides; + Vertex eorg, edest, eapex; + + encroached = 0; + sides = 0; + + eorg = testsubseg.Org(); + edest = testsubseg.Dest(); + // Check one neighbor of the subsegment. + testsubseg.Pivot(ref neighbortri); + // Does the neighbor exist, or is this a boundary edge? + if (neighbortri.tri.id != Mesh.DUMMY) + { + sides++; + // Find a vertex opposite this subsegment. + eapex = neighbortri.Apex(); + // Check whether the apex is in the diametral lens of the subsegment + // (the diametral circle if 'conformdel' is set). A dot product + // of two sides of the triangle is used to check whether the angle + // at the apex is greater than (180 - 2 'minangle') degrees (for + // lenses; 90 degrees for diametral circles). + dotproduct = (eorg.x - eapex.x) * (edest.x - eapex.x) + + (eorg.y - eapex.y) * (edest.y - eapex.y); + if (dotproduct < 0.0) + { + if (behavior.ConformingDelaunay || + (dotproduct * dotproduct >= + (2.0 * behavior.goodAngle - 1.0) * (2.0 * behavior.goodAngle - 1.0) * + ((eorg.x - eapex.x) * (eorg.x - eapex.x) + + (eorg.y - eapex.y) * (eorg.y - eapex.y)) * + ((edest.x - eapex.x) * (edest.x - eapex.x) + + (edest.y - eapex.y) * (edest.y - eapex.y)))) + { + encroached = 1; + } + } + } + // Check the other neighbor of the subsegment. + testsubseg.Sym(ref testsym); + testsym.Pivot(ref neighbortri); + // Does the neighbor exist, or is this a boundary edge? + if (neighbortri.tri.id != Mesh.DUMMY) + { + sides++; + // Find the other vertex opposite this subsegment. + eapex = neighbortri.Apex(); + // Check whether the apex is in the diametral lens of the subsegment + // (or the diametral circle, if 'conformdel' is set). + dotproduct = (eorg.x - eapex.x) * (edest.x - eapex.x) + + (eorg.y - eapex.y) * (edest.y - eapex.y); + if (dotproduct < 0.0) + { + if (behavior.ConformingDelaunay || + (dotproduct * dotproduct >= + (2.0 * behavior.goodAngle - 1.0) * (2.0 * behavior.goodAngle - 1.0) * + ((eorg.x - eapex.x) * (eorg.x - eapex.x) + + (eorg.y - eapex.y) * (eorg.y - eapex.y)) * + ((edest.x - eapex.x) * (edest.x - eapex.x) + + (edest.y - eapex.y) * (edest.y - eapex.y)))) + { + encroached += 2; + } + } + } + + if (encroached > 0 && (behavior.NoBisect == 0 || ((behavior.NoBisect == 1) && (sides == 2)))) + { + // Add the subsegment to the list of encroached subsegments. + // Be sure to get the orientation right. + encroachedseg = new BadSubseg(); + if (encroached == 1) + { + encroachedseg.subseg = testsubseg; + encroachedseg.org = eorg; + encroachedseg.dest = edest; + } + else + { + encroachedseg.subseg = testsym; + encroachedseg.org = edest; + encroachedseg.dest = eorg; + } + + badsubsegs.Enqueue(encroachedseg); + } + + return encroached; + } + + /// + /// Test a triangle for quality and size. + /// + /// Triangle to check. + /// + /// Tests a triangle to see if it satisfies the minimum angle condition and + /// the maximum area condition. Triangles that aren't up to spec are added + /// to the bad triangle queue. + /// + public void TestTriangle(ref Otri testtri) + { + Otri tri1 = default(Otri), tri2 = default(Otri); + Osub testsub = default(Osub); + Vertex torg, tdest, tapex; + Vertex base1, base2; + Vertex org1, dest1, org2, dest2; + Vertex joinvertex; + double dxod, dyod, dxda, dyda, dxao, dyao; + double dxod2, dyod2, dxda2, dyda2, dxao2, dyao2; + double apexlen, orglen, destlen, minedge; + double angle; + double area; + double dist1, dist2; + + double maxangle; + + if (behavior.Exclude != null && behavior.Exclude(testtri.tri)) + { + return; + } + + torg = testtri.Org(); + tdest = testtri.Dest(); + tapex = testtri.Apex(); + dxod = torg.x - tdest.x; + dyod = torg.y - tdest.y; + dxda = tdest.x - tapex.x; + dyda = tdest.y - tapex.y; + dxao = tapex.x - torg.x; + dyao = tapex.y - torg.y; + dxod2 = dxod * dxod; + dyod2 = dyod * dyod; + dxda2 = dxda * dxda; + dyda2 = dyda * dyda; + dxao2 = dxao * dxao; + dyao2 = dyao * dyao; + // Find the lengths of the triangle's three edges. + apexlen = dxod2 + dyod2; + orglen = dxda2 + dyda2; + destlen = dxao2 + dyao2; + + if ((apexlen < orglen) && (apexlen < destlen)) + { + // The edge opposite the apex is shortest. + minedge = apexlen; + // Find the square of the cosine of the angle at the apex. + angle = dxda * dxao + dyda * dyao; + angle = angle * angle / (orglen * destlen); + base1 = torg; + base2 = tdest; + testtri.Copy(ref tri1); + } + else if (orglen < destlen) + { + // The edge opposite the origin is shortest. + minedge = orglen; + // Find the square of the cosine of the angle at the origin. + angle = dxod * dxao + dyod * dyao; + angle = angle * angle / (apexlen * destlen); + base1 = tdest; + base2 = tapex; + testtri.Lnext(ref tri1); + } + else + { + // The edge opposite the destination is shortest. + minedge = destlen; + // Find the square of the cosine of the angle at the destination. + angle = dxod * dxda + dyod * dyda; + angle = angle * angle / (apexlen * orglen); + base1 = tapex; + base2 = torg; + testtri.Lprev(ref tri1); + } + + if (behavior.VarArea || behavior.fixedArea || (behavior.UserTest != null)) + { + // Check whether the area is larger than permitted. + area = 0.5 * (dxod * dyda - dyod * dxda); + if (behavior.fixedArea && (area > behavior.MaxArea)) + { + // Add this triangle to the list of bad triangles. + queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); + return; + } + + // Nonpositive area constraints are treated as unconstrained. + if ((behavior.VarArea) && (area > testtri.tri.area) && (testtri.tri.area > 0.0)) + { + // Add this triangle to the list of bad triangles. + queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); + return; + } + + // Check whether the user thinks this triangle is too large. + if ((behavior.UserTest != null) && behavior.UserTest(testtri.tri, area)) + { + queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); + return; + } + } + + // find the maximum edge and accordingly the pqr orientation + if ((apexlen > orglen) && (apexlen > destlen)) + { + // The edge opposite the apex is longest. + // maxedge = apexlen; + // Find the cosine of the angle at the apex. + maxangle = (orglen + destlen - apexlen) / (2 * Math.Sqrt(orglen * destlen)); + } + else if (orglen > destlen) + { + // The edge opposite the origin is longest. + // maxedge = orglen; + // Find the cosine of the angle at the origin. + maxangle = (apexlen + destlen - orglen) / (2 * Math.Sqrt(apexlen * destlen)); + } + else + { + // The edge opposite the destination is longest. + // maxedge = destlen; + // Find the cosine of the angle at the destination. + maxangle = (apexlen + orglen - destlen) / (2 * Math.Sqrt(apexlen * orglen)); + } + + // Check whether the angle is smaller than permitted. + if ((angle > behavior.goodAngle) || (maxangle < behavior.maxGoodAngle && behavior.MaxAngle != 0.0)) + { + // Use the rules of Miller, Pav, and Walkington to decide that certain + // triangles should not be split, even if they have bad angles. + // A skinny triangle is not split if its shortest edge subtends a + // small input angle, and both endpoints of the edge lie on a + // concentric circular shell. For convenience, I make a small + // adjustment to that rule: I check if the endpoints of the edge + // both lie in segment interiors, equidistant from the apex where + // the two segments meet. + // First, check if both points lie in segment interiors. + if ((base1.type == VertexType.SegmentVertex) && + (base2.type == VertexType.SegmentVertex)) + { + // Check if both points lie in a common segment. If they do, the + // skinny triangle is enqueued to be split as usual. + tri1.Pivot(ref testsub); + if (testsub.seg.hash == Mesh.DUMMY) + { + // No common segment. Find a subsegment that contains 'torg'. + tri1.Copy(ref tri2); + do + { + tri1.Oprev(); + tri1.Pivot(ref testsub); + } while (testsub.seg.hash == Mesh.DUMMY); + // Find the endpoints of the containing segment. + org1 = testsub.SegOrg(); + dest1 = testsub.SegDest(); + // Find a subsegment that contains 'tdest'. + do + { + tri2.Dnext(); + tri2.Pivot(ref testsub); + } while (testsub.seg.hash == Mesh.DUMMY); + // Find the endpoints of the containing segment. + org2 = testsub.SegOrg(); + dest2 = testsub.SegDest(); + // Check if the two containing segments have an endpoint in common. + joinvertex = null; + if ((dest1.x == org2.x) && (dest1.y == org2.y)) + { + joinvertex = dest1; + } + else if ((org1.x == dest2.x) && (org1.y == dest2.y)) + { + joinvertex = org1; + } + if (joinvertex != null) + { + // Compute the distance from the common endpoint (of the two + // segments) to each of the endpoints of the shortest edge. + dist1 = ((base1.x - joinvertex.x) * (base1.x - joinvertex.x) + + (base1.y - joinvertex.y) * (base1.y - joinvertex.y)); + dist2 = ((base2.x - joinvertex.x) * (base2.x - joinvertex.x) + + (base2.y - joinvertex.y) * (base2.y - joinvertex.y)); + // If the two distances are equal, don't split the triangle. + if ((dist1 < 1.001 * dist2) && (dist1 > 0.999 * dist2)) + { + // Return now to avoid enqueueing the bad triangle. + return; + } + } + } + } + + // Add this triangle to the list of bad triangles. + queue.Enqueue(ref testtri, minedge, tapex, torg, tdest); + } + } + + #endregion + + #region Maintanance + + /// + /// Traverse the entire list of subsegments, and check each to see if it + /// is encroached. If so, add it to the list. + /// + private void TallyEncs() + { + Osub subsegloop = default(Osub); + subsegloop.orient = 0; + + foreach (var seg in mesh.subsegs.Values) + { + subsegloop.seg = seg; + // If the segment is encroached, add it to the list. + CheckSeg4Encroach(ref subsegloop); + } + } + + /// + /// Split all the encroached subsegments. + /// + /// A flag that specifies whether one should take + /// note of new bad triangles that result from inserting vertices to repair + /// encroached subsegments. + /// + /// Each encroached subsegment is repaired by splitting it - inserting a + /// vertex at or near its midpoint. Newly inserted vertices may encroach + /// upon other subsegments; these are also repaired. + /// + private void SplitEncSegs(bool triflaws) + { + Otri enctri = default(Otri); + Otri testtri = default(Otri); + Osub testsh = default(Osub); + Osub currentenc = default(Osub); + BadSubseg seg; + Vertex eorg, edest, eapex; + Vertex newvertex; + InsertVertexResult success; + double segmentlength, nearestpoweroftwo; + double split; + double multiplier, divisor; + bool acuteorg, acuteorg2, acutedest, acutedest2; + + // Note that steinerleft == -1 if an unlimited number + // of Steiner points is allowed. + while (badsubsegs.Count > 0) + { + if (mesh.steinerleft == 0) + { + break; + } + + seg = badsubsegs.Dequeue(); + + currentenc = seg.subseg; + eorg = currentenc.Org(); + edest = currentenc.Dest(); + // Make sure that this segment is still the same segment it was + // when it was determined to be encroached. If the segment was + // enqueued multiple times (because several newly inserted + // vertices encroached it), it may have already been split. + if (!Osub.IsDead(currentenc.seg) && (eorg == seg.org) && (edest == seg.dest)) + { + // To decide where to split a segment, we need to know if the + // segment shares an endpoint with an adjacent segment. + // The concern is that, if we simply split every encroached + // segment in its center, two adjacent segments with a small + // angle between them might lead to an infinite loop; each + // vertex added to split one segment will encroach upon the + // other segment, which must then be split with a vertex that + // will encroach upon the first segment, and so on forever. + // To avoid this, imagine a set of concentric circles, whose + // radii are powers of two, about each segment endpoint. + // These concentric circles determine where the segment is + // split. (If both endpoints are shared with adjacent + // segments, split the segment in the middle, and apply the + // concentric circles for later splittings.) + + // Is the origin shared with another segment? + currentenc.Pivot(ref enctri); + enctri.Lnext(ref testtri); + testtri.Pivot(ref testsh); + acuteorg = testsh.seg.hash != Mesh.DUMMY; + // Is the destination shared with another segment? + testtri.Lnext(); + testtri.Pivot(ref testsh); + acutedest = testsh.seg.hash != Mesh.DUMMY; + + // If we're using Chew's algorithm (rather than Ruppert's) + // to define encroachment, delete free vertices from the + // subsegment's diametral circle. + if (!behavior.ConformingDelaunay && !acuteorg && !acutedest) + { + eapex = enctri.Apex(); + while ((eapex.type == VertexType.FreeVertex) && + ((eorg.x - eapex.x) * (edest.x - eapex.x) + + (eorg.y - eapex.y) * (edest.y - eapex.y) < 0.0)) + { + mesh.DeleteVertex(ref testtri); + currentenc.Pivot(ref enctri); + eapex = enctri.Apex(); + enctri.Lprev(ref testtri); + } + } + + // Now, check the other side of the segment, if there's a triangle there. + enctri.Sym(ref testtri); + if (testtri.tri.id != Mesh.DUMMY) + { + // Is the destination shared with another segment? + testtri.Lnext(); + testtri.Pivot(ref testsh); + acutedest2 = testsh.seg.hash != Mesh.DUMMY; + acutedest = acutedest || acutedest2; + // Is the origin shared with another segment? + testtri.Lnext(); + testtri.Pivot(ref testsh); + acuteorg2 = testsh.seg.hash != Mesh.DUMMY; + acuteorg = acuteorg || acuteorg2; + + // Delete free vertices from the subsegment's diametral circle. + if (!behavior.ConformingDelaunay && !acuteorg2 && !acutedest2) + { + eapex = testtri.Org(); + while ((eapex.type == VertexType.FreeVertex) && + ((eorg.x - eapex.x) * (edest.x - eapex.x) + + (eorg.y - eapex.y) * (edest.y - eapex.y) < 0.0)) + { + mesh.DeleteVertex(ref testtri); + enctri.Sym(ref testtri); + eapex = testtri.Apex(); + testtri.Lprev(); + } + } + } + + // Use the concentric circles if exactly one endpoint is shared + // with another adjacent segment. + if (acuteorg || acutedest) + { + segmentlength = Math.Sqrt((edest.x - eorg.x) * (edest.x - eorg.x) + + (edest.y - eorg.y) * (edest.y - eorg.y)); + // Find the power of two that most evenly splits the segment. + // The worst case is a 2:1 ratio between subsegment lengths. + nearestpoweroftwo = 1.0; + while (segmentlength > 3.0 * nearestpoweroftwo) + { + nearestpoweroftwo *= 2.0; + } + while (segmentlength < 1.5 * nearestpoweroftwo) + { + nearestpoweroftwo *= 0.5; + } + // Where do we split the segment? + split = nearestpoweroftwo / segmentlength; + if (acutedest) + { + split = 1.0 - split; + } + } + else + { + // If we're not worried about adjacent segments, split + // this segment in the middle. + split = 0.5; + } + + // Create the new vertex (interpolate coordinates). + newvertex = new Vertex( + eorg.x + split * (edest.x - eorg.x), + eorg.y + split * (edest.y - eorg.y), + currentenc.seg.boundary +#if USE_ATTRIBS + , mesh.nextras +#endif + ); + + newvertex.type = VertexType.SegmentVertex; + + newvertex.hash = mesh.hash_vtx++; + newvertex.id = newvertex.hash; + + mesh.vertices.Add(newvertex.hash, newvertex); +#if USE_ATTRIBS + // Interpolate attributes. + for (int i = 0; i < mesh.nextras; i++) + { + newvertex.attributes[i] = eorg.attributes[i] + + split * (edest.attributes[i] - eorg.attributes[i]); + } +#endif +#if USE_Z + newvertex.z = eorg.z + split * (edest.z - eorg.z); +#endif + if (!Behavior.NoExact) + { + // Roundoff in the above calculation may yield a 'newvertex' + // that is not precisely collinear with 'eorg' and 'edest'. + // Improve collinearity by one step of iterative refinement. + multiplier = predicates.CounterClockwise(eorg, edest, newvertex); + divisor = ((eorg.x - edest.x) * (eorg.x - edest.x) + + (eorg.y - edest.y) * (eorg.y - edest.y)); + if ((multiplier != 0.0) && (divisor != 0.0)) + { + multiplier = multiplier / divisor; + // Watch out for NANs. + if (!double.IsNaN(multiplier)) + { + newvertex.x += multiplier * (edest.y - eorg.y); + newvertex.y += multiplier * (eorg.x - edest.x); + } + } + } + + // Check whether the new vertex lies on an endpoint. + if (((newvertex.x == eorg.x) && (newvertex.y == eorg.y)) || + ((newvertex.x == edest.x) && (newvertex.y == edest.y))) + { + + logger.Error("Ran out of precision: I attempted to split a" + + " segment to a smaller size than can be accommodated by" + + " the finite precision of floating point arithmetic.", + "Quality.SplitEncSegs()"); + + throw new Exception("Ran out of precision"); + } + // Insert the splitting vertex. This should always succeed. + success = mesh.InsertVertex(newvertex, ref enctri, ref currentenc, true, triflaws); + if ((success != InsertVertexResult.Successful) && (success != InsertVertexResult.Encroaching)) + { + logger.Error("Failure to split a segment.", "Quality.SplitEncSegs()"); + throw new Exception("Failure to split a segment."); + } + if (mesh.steinerleft > 0) + { + mesh.steinerleft--; + } + // Check the two new subsegments to see if they're encroached. + CheckSeg4Encroach(ref currentenc); + currentenc.Next(); + CheckSeg4Encroach(ref currentenc); + } + + // Set subsegment's origin to NULL. This makes it possible to detect dead + // badsubsegs when traversing the list of all badsubsegs. + seg.org = null; + } + } + + /// + /// Test every triangle in the mesh for quality measures. + /// + private void TallyFaces() + { + Otri triangleloop = default(Otri); + triangleloop.orient = 0; + + foreach (var tri in mesh.triangles) + { + triangleloop.tri = tri; + + // If the triangle is bad, enqueue it. + TestTriangle(ref triangleloop); + } + } + + /// + /// Inserts a vertex at the circumcenter of a triangle. Deletes + /// the newly inserted vertex if it encroaches upon a segment. + /// + /// + private void SplitTriangle(BadTriangle badtri) + { + Otri badotri = default(Otri); + Vertex borg, bdest, bapex; + Point newloc; // Location of the new vertex + double xi = 0, eta = 0; + InsertVertexResult success; + bool errorflag; + + badotri = badtri.poortri; + borg = badotri.Org(); + bdest = badotri.Dest(); + bapex = badotri.Apex(); + + // Make sure that this triangle is still the same triangle it was + // when it was tested and determined to be of bad quality. + // Subsequent transformations may have made it a different triangle. + if (!Otri.IsDead(badotri.tri) && (borg == badtri.org) && + (bdest == badtri.dest) && (bapex == badtri.apex)) + { + errorflag = false; + // Create a new vertex at the triangle's circumcenter. + + // Using the original (simpler) Steiner point location method + // for mesh refinement. + // TODO: NewLocation doesn't work for refinement. Why? Maybe + // reset VertexType? + if (behavior.fixedArea || behavior.VarArea) + { + newloc = predicates.FindCircumcenter(borg, bdest, bapex, ref xi, ref eta, behavior.offconstant); + } + else + { + newloc = newLocation.FindLocation(borg, bdest, bapex, ref xi, ref eta, true, badotri); + } + + // Check whether the new vertex lies on a triangle vertex. + if (((newloc.x == borg.x) && (newloc.y == borg.y)) || + ((newloc.x == bdest.x) && (newloc.y == bdest.y)) || + ((newloc.x == bapex.x) && (newloc.y == bapex.y))) + { + if (Log.Verbose) + { + logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()"); + errorflag = true; + } + } + else + { + // The new vertex must be in the interior, and therefore is a + // free vertex with a marker of zero. + Vertex newvertex = new Vertex(newloc.x, newloc.y, 0 +#if USE_ATTRIBS + , mesh.nextras +#endif + ); + + newvertex.type = VertexType.FreeVertex; + + // Ensure that the handle 'badotri' does not represent the longest + // edge of the triangle. This ensures that the circumcenter must + // fall to the left of this edge, so point location will work. + // (If the angle org-apex-dest exceeds 90 degrees, then the + // circumcenter lies outside the org-dest edge, and eta is + // negative. Roundoff error might prevent eta from being + // negative when it should be, so I test eta against xi.) + if (eta < xi) + { + badotri.Lprev(); + } + + // Assign triangle for attributes interpolation. + newvertex.tri.tri = newvertex_tri; + + // Insert the circumcenter, searching from the edge of the triangle, + // and maintain the Delaunay property of the triangulation. + Osub tmp = default(Osub); + success = mesh.InsertVertex(newvertex, ref badotri, ref tmp, true, true); + + if (success == InsertVertexResult.Successful) + { + newvertex.hash = mesh.hash_vtx++; + newvertex.id = newvertex.hash; +#if USE_ATTRIBS + if (mesh.nextras > 0) + { + Interpolation.InterpolateAttributes(newvertex, newvertex.tri.tri, mesh.nextras); + } +#endif +#if USE_Z + Interpolation.InterpolateZ(newvertex, newvertex.tri.tri); +#endif + mesh.vertices.Add(newvertex.hash, newvertex); + + if (mesh.steinerleft > 0) + { + mesh.steinerleft--; + } + } + else if (success == InsertVertexResult.Encroaching) + { + // If the newly inserted vertex encroaches upon a subsegment, + // delete the new vertex. + mesh.UndoVertex(); + } + else if (success == InsertVertexResult.Violating) + { + // Failed to insert the new vertex, but some subsegment was + // marked as being encroached. + } + else + { // success == DUPLICATEVERTEX + // Couldn't insert the new vertex because a vertex is already there. + if (Log.Verbose) + { + logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()"); + errorflag = true; + } + } + } + if (errorflag) + { + logger.Error("The new vertex is at the circumcenter of triangle: This probably " + + "means that I am trying to refine triangles to a smaller size than can be " + + "accommodated by the finite precision of floating point arithmetic.", + "Quality.SplitTriangle()"); + + throw new Exception("The new vertex is at the circumcenter of triangle."); + } + } + } + + /// + /// Remove all the encroached subsegments and bad triangles from the triangulation. + /// + private void EnforceQuality() + { + BadTriangle badtri; + + // Test all segments to see if they're encroached. + TallyEncs(); + + // Fix encroached subsegments without noting bad triangles. + SplitEncSegs(false); + // At this point, if we haven't run out of Steiner points, the + // triangulation should be (conforming) Delaunay. + + // Next, we worry about enforcing triangle quality. + if ((behavior.MinAngle > 0.0) || behavior.VarArea || behavior.fixedArea || behavior.UserTest != null) + { + // TODO: Reset queue? (Or is it always empty at this point) + + // Test all triangles to see if they're bad. + TallyFaces(); + + mesh.checkquality = true; + while ((queue.Count > 0) && (mesh.steinerleft != 0)) + { + // Fix one bad triangle by inserting a vertex at its circumcenter. + badtri = queue.Dequeue(); + SplitTriangle(badtri); + + if (badsubsegs.Count > 0) + { + // Put bad triangle back in queue for another try later. + queue.Enqueue(badtri); + // Fix any encroached subsegments that resulted. + // Record any new bad triangles that result. + SplitEncSegs(true); + } + } + } + + // At this point, if the "-D" switch was selected and we haven't run out + // of Steiner points, the triangulation should be (conforming) Delaunay + // and have no low-quality triangles. + + // Might we have run out of Steiner points too soon? + if (Log.Verbose && behavior.ConformingDelaunay && (badsubsegs.Count > 0) && (mesh.steinerleft == 0)) + { + + logger.Warning("I ran out of Steiner points, but the mesh has encroached subsegments, " + + "and therefore might not be truly Delaunay. If the Delaunay property is important " + + "to you, try increasing the number of Steiner points.", + "Quality.EnforceQuality()"); + } + } + + #endregion + } +} diff --git a/Triangle.NET/Triangle/Meshing/QualityOptions.cs b/src/Triangle/Meshing/QualityOptions.cs similarity index 82% rename from Triangle.NET/Triangle/Meshing/QualityOptions.cs rename to src/Triangle/Meshing/QualityOptions.cs index bf9f8b8..393fca5 100644 --- a/Triangle.NET/Triangle/Meshing/QualityOptions.cs +++ b/src/Triangle/Meshing/QualityOptions.cs @@ -1,55 +1,64 @@ - -namespace TriangleNet.Meshing -{ - using System; - using TriangleNet.Geometry; - - /// - /// Mesh constraint options for quality triangulation. - /// - public class QualityOptions - { - /// - /// Gets or sets a maximum angle constraint. - /// - public double MaximumAngle { get; set; } - - /// - /// Gets or sets a minimum angle constraint. - /// - public double MinimumAngle { get; set; } - - /// - /// Gets or sets a maximum triangle area constraint. - /// - public double MaximumArea { get; set; } - - /// - /// Gets or sets a user-defined triangle constraint. - /// - /// - /// The test function will be called for each triangle in the mesh. The - /// second argument is the area of the triangle tested. If the function - /// returns true, the triangle is considered bad and will be refined. - /// - public Func UserTest { get; set; } - - /// - /// Gets or sets an area constraint per triangle. - /// - /// - /// If this flag is set to true, the value will - /// be used to check if a triangle needs refinement. - /// - public bool VariableArea { get; set; } - - /// - /// Gets or sets the maximum number of Steiner points to be inserted into the mesh. - /// - /// - /// If the value is 0 (default), an unknown number of Steiner points may be inserted - /// to meet the other quality constraints. - /// - public int SteinerPoints { get; set; } - } -} + +namespace TriangleNet.Meshing +{ + using System; + using TriangleNet.Geometry; + + /// + /// Mesh constraint options for quality triangulation. + /// + public class QualityOptions + { + /// + /// Gets or sets a maximum angle constraint. + /// + public double MaximumAngle { get; set; } + + /// + /// Gets or sets a minimum angle constraint. + /// + public double MinimumAngle { get; set; } + + /// + /// Gets or sets a maximum triangle area constraint. + /// + public double MaximumArea { get; set; } + + /// + /// Gets or sets a user-defined triangle constraint. + /// + /// + /// The test function will be called for each triangle in the mesh. The + /// second argument is the area of the triangle tested. If the function + /// returns true, the triangle is considered bad and will be refined. + /// + public Func UserTest { get; set; } + + /// + /// Gets or sets a test function for excluding triangles from refinement. + /// + /// + /// The exclude test takes higher priority than all other test, i.e. if a + /// triangle passes the exclude test, no other test will be executed. + /// + public Func Exclude { get; set; } + + /// + /// Gets or sets an area constraint per triangle. + /// + /// + /// If this flag is set to true, the value will + /// be used to check if a triangle needs refinement. + /// + public bool VariableArea { get; set; } + + /// + /// Gets or sets the maximum number of Steiner points to be inserted into the mesh. + /// + /// + /// If the value is 0 (default), an unknown number of Steiner points may be inserted + /// to meet the other quality constraints. + /// + public int SteinerPoints { get; set; } + } +} diff --git a/Triangle.NET/Triangle/NewLocation.cs b/src/Triangle/NewLocation.cs similarity index 98% rename from Triangle.NET/Triangle/NewLocation.cs rename to src/Triangle/NewLocation.cs index 4755b21..cadc324 100644 --- a/Triangle.NET/Triangle/NewLocation.cs +++ b/src/Triangle/NewLocation.cs @@ -1,4115 +1,4121 @@ -// ----------------------------------------------------------------------- -// -// Original code by Hale Erten and Alper Üngör, http://www.cise.ufl.edu/~ungor/aCute/index.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet -{ - using System; - using TriangleNet.Topology; - using TriangleNet.Geometry; - using TriangleNet.Tools; - - /// - /// Find new Steiner point locations. - /// - /// - /// http://www.cise.ufl.edu/~ungor/aCute/index.html - /// - class NewLocation - { - const double EPS = 1e-50; - - IPredicates predicates; - - Mesh mesh; - Behavior behavior; - - // Work arrays for wegde intersection - double[] petalx = new double[20]; - double[] petaly = new double[20]; - double[] petalr = new double[20]; - double[] wedges = new double[500]; - double[] initialConvexPoly = new double[500]; - - // Work arrays for smoothing - double[] points_p = new double[500]; - double[] points_q = new double[500]; - double[] points_r = new double[500]; - - // Work arrays for convex polygon split - double[] poly1 = new double[100]; - double[] poly2 = new double[100]; - double[][] polys = new double[3][]; - - public NewLocation(Mesh mesh, IPredicates predicates) - { - this.mesh = mesh; - this.predicates = predicates; - - this.behavior = mesh.behavior; - } - - /// - /// Find a new location for a Steiner point. - /// - /// - /// - /// - /// - /// - /// - /// - /// - public Point FindLocation(Vertex org, Vertex dest, Vertex apex, - ref double xi, ref double eta, bool offcenter, Otri badotri) - { - // Based on using -U switch, call the corresponding function - if (behavior.MaxAngle == 0.0) - { - // Disable the "no max angle" code. It may return weired vertex locations. - return FindNewLocationWithoutMaxAngle(org, dest, apex, ref xi, ref eta, true, badotri); - } - - // With max angle - return FindNewLocation(org, dest, apex, ref xi, ref eta, true, badotri); - } - - /// - /// Find a new location for a Steiner point. - /// - /// - /// - /// - /// - /// - /// - /// - /// - private Point FindNewLocationWithoutMaxAngle(Vertex torg, Vertex tdest, Vertex tapex, - ref double xi, ref double eta, bool offcenter, Otri badotri) - { - double offconstant = behavior.offconstant; - - // for calculating the distances of the edges - double xdo, ydo, xao, yao, xda, yda; - double dodist, aodist, dadist; - // for exact calculation - double denominator; - double dx, dy, dxoff, dyoff; - - ////////////////////////////// HALE'S VARIABLES ////////////////////////////// - // keeps the difference of coordinates edge - double xShortestEdge = 0, yShortestEdge = 0; - - // keeps the square of edge lengths - double shortestEdgeDist = 0, middleEdgeDist = 0, longestEdgeDist = 0; - - // keeps the vertices according to the angle incident to that vertex in a triangle - Point smallestAngleCorner, middleAngleCorner, largestAngleCorner; - - // keeps the type of orientation if the triangle - int orientation = 0; - // keeps the coordinates of circumcenter of itself and neighbor triangle circumcenter - Point myCircumcenter, neighborCircumcenter; - - // keeps if bad triangle is almost good or not - int almostGood = 0; - // keeps the cosine of the largest angle - double cosMaxAngle; - bool isObtuse; // 1: obtuse 0: nonobtuse - // keeps the radius of petal - double petalRadius; - // for calculating petal center - double xPetalCtr_1, yPetalCtr_1, xPetalCtr_2, yPetalCtr_2, xPetalCtr, yPetalCtr, xMidOfShortestEdge, yMidOfShortestEdge; - double dxcenter1, dycenter1, dxcenter2, dycenter2; - // for finding neighbor - Otri neighborotri = default(Otri); - double[] thirdPoint = new double[2]; - //int neighborNotFound = -1; - bool neighborNotFound; - // for keeping the vertices of the neighbor triangle - Vertex neighborvertex_1; - Vertex neighborvertex_2; - Vertex neighborvertex_3; - // dummy variables - double xi_tmp = 0, eta_tmp = 0; - //vertex thirdVertex; - // for petal intersection - double vector_x, vector_y, xMidOfLongestEdge, yMidOfLongestEdge, inter_x, inter_y; - double[] p = new double[5], voronoiOrInter = new double[4]; - bool isCorrect; - - // for vector calculations in perturbation - double ax, ay, d; - double pertConst = 0.06; // perturbation constant - - double lengthConst = 1; // used at comparing circumcenter's distance to proposed point's distance - double justAcute = 1; // used for making the program working for one direction only - // for smoothing - int relocated = 0;// used to differentiate between calling the deletevertex and just proposing a steiner point - double[] newloc = new double[2]; // new location suggested by smoothing - double origin_x = 0, origin_y = 0; // for keeping torg safe - Otri delotri; // keeping the original orientation for relocation process - // keeps the first and second direction suggested points - double dxFirstSuggestion, dyFirstSuggestion, dxSecondSuggestion, dySecondSuggestion; - // second direction variables - double xMidOfMiddleEdge, yMidOfMiddleEdge; - ////////////////////////////// END OF HALE'S VARIABLES ////////////////////////////// - - Statistic.CircumcenterCount++; - - // Compute the circumcenter of the triangle. - xdo = tdest.x - torg.x; - ydo = tdest.y - torg.y; - xao = tapex.x - torg.x; - yao = tapex.y - torg.y; - xda = tapex.x - tdest.x; - yda = tapex.y - tdest.y; - // keeps the square of the distances - dodist = xdo * xdo + ydo * ydo; - aodist = xao * xao + yao * yao; - dadist = (tdest.x - tapex.x) * (tdest.x - tapex.x) + - (tdest.y - tapex.y) * (tdest.y - tapex.y); - // checking if the user wanted exact arithmetic or not - if (Behavior.NoExact) - { - denominator = 0.5 / (xdo * yao - xao * ydo); - } - else - { - // Use the counterclockwise() routine to ensure a positive (and - // reasonably accurate) result, avoiding any possibility of - // division by zero. - denominator = 0.5 / predicates.CounterClockwise(tdest, tapex, torg); - // Don't count the above as an orientation test. - Statistic.CounterClockwiseCount--; - } - // calculate the circumcenter in terms of distance to origin point - dx = (yao * dodist - ydo * aodist) * denominator; - dy = (xdo * aodist - xao * dodist) * denominator; - // for debugging and for keeping circumcenter to use later - // coordinate value of the circumcenter - myCircumcenter = new Point(torg.x + dx, torg.y + dy); - - delotri = badotri; // save for later - ///////////////// FINDING THE ORIENTATION OF TRIANGLE ////////////////// - // Find the (squared) length of the triangle's shortest edge. This - // serves as a conservative estimate of the insertion radius of the - // circumcenter's parent. The estimate is used to ensure that - // the algorithm terminates even if very small angles appear in - // the input PSLG. - // find the orientation of the triangle, basically shortest and longest edges - orientation = LongestShortestEdge(aodist, dadist, dodist); - //printf("org: (%f,%f), dest: (%f,%f), apex: (%f,%f)\n",torg[0],torg[1],tdest[0],tdest[1],tapex[0],tapex[1]); - ///////////////////////////////////////////////////////////////////////////////////////////// - // 123: shortest: aodist // 213: shortest: dadist // 312: shortest: dodist // - // middle: dadist // middle: aodist // middle: aodist // - // longest: dodist // longest: dodist // longest: dadist // - // 132: shortest: aodist // 231: shortest: dadist // 321: shortest: dodist // - // middle: dodist // middle: dodist // middle: dadist // - // longest: dadist // longest: aodist // longest: aodist // - ///////////////////////////////////////////////////////////////////////////////////////////// - - switch (orientation) - { - case 123: // assign necessary information - /// smallest angle corner: dest - /// largest angle corner: apex - xShortestEdge = xao; yShortestEdge = yao; - - shortestEdgeDist = aodist; - middleEdgeDist = dadist; - longestEdgeDist = dodist; - - smallestAngleCorner = tdest; - middleAngleCorner = torg; - largestAngleCorner = tapex; - break; - - case 132: // assign necessary information - /// smallest angle corner: dest - /// largest angle corner: org - xShortestEdge = xao; yShortestEdge = yao; - - shortestEdgeDist = aodist; - middleEdgeDist = dodist; - longestEdgeDist = dadist; - - smallestAngleCorner = tdest; - middleAngleCorner = tapex; - largestAngleCorner = torg; - - break; - case 213: // assign necessary information - /// smallest angle corner: org - /// largest angle corner: apex - xShortestEdge = xda; yShortestEdge = yda; - - shortestEdgeDist = dadist; - middleEdgeDist = aodist; - longestEdgeDist = dodist; - - smallestAngleCorner = torg; - middleAngleCorner = tdest; - largestAngleCorner = tapex; - break; - case 231: // assign necessary information - /// smallest angle corner: org - /// largest angle corner: dest - xShortestEdge = xda; yShortestEdge = yda; - - shortestEdgeDist = dadist; - middleEdgeDist = dodist; - longestEdgeDist = aodist; - - smallestAngleCorner = torg; - middleAngleCorner = tapex; - largestAngleCorner = tdest; - break; - case 312: // assign necessary information - /// smallest angle corner: apex - /// largest angle corner: org - xShortestEdge = xdo; yShortestEdge = ydo; - - shortestEdgeDist = dodist; - middleEdgeDist = aodist; - longestEdgeDist = dadist; - - smallestAngleCorner = tapex; - middleAngleCorner = tdest; - largestAngleCorner = torg; - break; - case 321: // assign necessary information - default: // TODO: is this safe? - /// smallest angle corner: apex - /// largest angle corner: dest - xShortestEdge = xdo; yShortestEdge = ydo; - - shortestEdgeDist = dodist; - middleEdgeDist = dadist; - longestEdgeDist = aodist; - - smallestAngleCorner = tapex; - middleAngleCorner = torg; - largestAngleCorner = tdest; - break; - - }// end of switch - // check for offcenter condition - if (offcenter && (offconstant > 0.0)) - { - // origin has the smallest angle - if (orientation == 213 || orientation == 231) - { - // Find the position of the off-center, as described by Alper Ungor. - dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; - dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; - // If the off-center is closer to destination than the - // circumcenter, use the off-center instead. - /// doubleLY BAD CASE /// - if (dxoff * dxoff + dyoff * dyoff < - (dx - xdo) * (dx - xdo) + (dy - ydo) * (dy - ydo)) - { - dx = xdo + dxoff; - dy = ydo + dyoff; - } - /// ALMOST GOOD CASE /// - else - { - almostGood = 1; - } - // destination has the smallest angle - } - else if (orientation == 123 || orientation == 132) - { - // Find the position of the off-center, as described by Alper Ungor. - dxoff = 0.5 * xShortestEdge + offconstant * yShortestEdge; - dyoff = 0.5 * yShortestEdge - offconstant * xShortestEdge; - // If the off-center is closer to the origin than the - // circumcenter, use the off-center instead. - /// doubleLY BAD CASE /// - if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) - { - dx = dxoff; - dy = dyoff; - } - /// ALMOST GOOD CASE /// - else - { - almostGood = 1; - } - // apex has the smallest angle - } - else - {//orientation == 312 || orientation == 321 - // Find the position of the off-center, as described by Alper Ungor. - dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; - dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; - // If the off-center is closer to the origin than the - // circumcenter, use the off-center instead. - /// doubleLY BAD CASE /// - if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) - { - dx = dxoff; - dy = dyoff; - } - /// ALMOST GOOD CASE /// - else - { - almostGood = 1; - } - } - } - // if the bad triangle is almost good, apply our approach - if (almostGood == 1) - { - - /// calculate cosine of largest angle /// - cosMaxAngle = (middleEdgeDist + shortestEdgeDist - longestEdgeDist) / (2 * Math.Sqrt(middleEdgeDist) * Math.Sqrt(shortestEdgeDist)); - if (cosMaxAngle < 0.0) - { - // obtuse - isObtuse = true; - } - else if (Math.Abs(cosMaxAngle - 0.0) <= EPS) - { - // right triangle (largest angle is 90 degrees) - isObtuse = true; - } - else - { - // nonobtuse - isObtuse = false; - } - /// RELOCATION (LOCAL SMOOTHING) /// - /// check for possible relocation of one of triangle's points /// - relocated = DoSmoothing(delotri, torg, tdest, tapex, ref newloc); - /// if relocation is possible, delete that vertex and insert a vertex at the new location /// - if (relocated > 0) - { - Statistic.RelocationCount++; - - dx = newloc[0] - torg.x; - dy = newloc[1] - torg.y; - origin_x = torg.x; // keep for later use - origin_y = torg.y; - switch (relocated) - { - case 1: - //printf("Relocate: (%f,%f)\n", torg[0],torg[1]); - mesh.DeleteVertex(ref delotri); - break; - case 2: - //printf("Relocate: (%f,%f)\n", tdest[0],tdest[1]); - delotri.Lnext(); - mesh.DeleteVertex(ref delotri); - break; - case 3: - //printf("Relocate: (%f,%f)\n", tapex[0],tapex[1]); - delotri.Lprev(); - mesh.DeleteVertex(ref delotri); - break; - - } - } - else - { - // calculate radius of the petal according to angle constraint - // first find the visible region, PETAL - // find the center of the circle and radius - petalRadius = Math.Sqrt(shortestEdgeDist) / (2 * Math.Sin(behavior.MinAngle * Math.PI / 180.0)); - /// compute two possible centers of the petal /// - // finding the center - // first find the middle point of smallest edge - xMidOfShortestEdge = (middleAngleCorner.x + largestAngleCorner.x) / 2.0; - yMidOfShortestEdge = (middleAngleCorner.y + largestAngleCorner.y) / 2.0; - // two possible centers - xPetalCtr_1 = xMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - - largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); - yPetalCtr_1 = yMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - - middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); - - xPetalCtr_2 = xMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - - largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); - yPetalCtr_2 = yMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - - middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); - // find the correct circle since there will be two possible circles - // calculate the distance to smallest angle corner - dxcenter1 = (xPetalCtr_1 - smallestAngleCorner.x) * (xPetalCtr_1 - smallestAngleCorner.x); - dycenter1 = (yPetalCtr_1 - smallestAngleCorner.y) * (yPetalCtr_1 - smallestAngleCorner.y); - dxcenter2 = (xPetalCtr_2 - smallestAngleCorner.x) * (xPetalCtr_2 - smallestAngleCorner.x); - dycenter2 = (yPetalCtr_2 - smallestAngleCorner.y) * (yPetalCtr_2 - smallestAngleCorner.y); - - // whichever is closer to smallest angle corner, it must be the center - if (dxcenter1 + dycenter1 <= dxcenter2 + dycenter2) - { - xPetalCtr = xPetalCtr_1; yPetalCtr = yPetalCtr_1; - } - else - { - xPetalCtr = xPetalCtr_2; yPetalCtr = yPetalCtr_2; - } - - /// find the third point of the neighbor triangle /// - neighborNotFound = GetNeighborsVertex(badotri, middleAngleCorner.x, middleAngleCorner.y, - smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); - /// find the circumcenter of the neighbor triangle /// - dxFirstSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter - dyFirstSuggestion = dy; - // if there is a neighbor triangle - if (!neighborNotFound) - { - neighborvertex_1 = neighborotri.Org(); - neighborvertex_2 = neighborotri.Dest(); - neighborvertex_3 = neighborotri.Apex(); - // now calculate neighbor's circumcenter which is the voronoi site - neighborCircumcenter = predicates.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, - ref xi_tmp, ref eta_tmp); - - /// compute petal and Voronoi edge intersection /// - // in order to avoid degenerate cases, we need to do a vector based calculation for line - vector_x = (middleAngleCorner.y - smallestAngleCorner.y);//(-y, x) - vector_y = smallestAngleCorner.x - middleAngleCorner.x; - vector_x = myCircumcenter.x + vector_x; - vector_y = myCircumcenter.y + vector_y; - - - // by intersecting bisectors you will end up with the one you want to walk on - // then this line and circle should be intersected - CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, - xPetalCtr, yPetalCtr, petalRadius, ref p); - /// choose the correct intersection point /// - // calculate middle point of the longest edge(bisector) - xMidOfLongestEdge = (middleAngleCorner.x + smallestAngleCorner.x) / 2.0; - yMidOfLongestEdge = (middleAngleCorner.y + smallestAngleCorner.y) / 2.0; - // we need to find correct intersection point, since line intersects circle twice - isCorrect = ChooseCorrectPoint(xMidOfLongestEdge, yMidOfLongestEdge, p[3], p[4], - myCircumcenter.x, myCircumcenter.y, isObtuse); - // make sure which point is the correct one to be considered - if (isCorrect) - { - inter_x = p[3]; - inter_y = p[4]; - } - else - { - inter_x = p[1]; - inter_y = p[2]; - } - /// check if there is a Voronoi vertex between before intersection /// - // check if the voronoi vertex is between the intersection and circumcenter - PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, - neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); - - /// determine the point to be suggested /// - if (p[0] > 0.0) - { // there is at least one intersection point - // if it is between circumcenter and intersection - // if it returns 1.0 this means we have a voronoi vertex within feasible region - if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) - { - if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) - { - // go back to circumcenter - dxFirstSuggestion = dx; - dyFirstSuggestion = dy; - - } - else - { // we are not creating a bad triangle - // neighbor's circumcenter is suggested - dxFirstSuggestion = voronoiOrInter[2] - torg.x; - dyFirstSuggestion = voronoiOrInter[3] - torg.y; - } - - } - else - { // there is no voronoi vertex between intersection point and circumcenter - if (IsBadTriangleAngle(largestAngleCorner.x, largestAngleCorner.y, middleAngleCorner.x, middleAngleCorner.y, inter_x, inter_y)) - { - // if it is inside feasible region, then insert v2 - // apply perturbation - // find the distance between circumcenter and intersection point - d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + - (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); - // then find the vector going from intersection point to circumcenter - ax = myCircumcenter.x - inter_x; - ay = myCircumcenter.y - inter_y; - - ax = ax / d; - ay = ay / d; - // now calculate the new intersection point which is perturbated towards the circumcenter - inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); - inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); - if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) - { - // go back to circumcenter - dxFirstSuggestion = dx; - dyFirstSuggestion = dy; - - } - else - { - // intersection point is suggested - dxFirstSuggestion = inter_x - torg.x; - dyFirstSuggestion = inter_y - torg.y; - - } - } - else - { - // intersection point is suggested - dxFirstSuggestion = inter_x - torg.x; - dyFirstSuggestion = inter_y - torg.y; - } - } - /// if it is an acute triangle, check if it is a good enough location /// - // for acute triangle case, we need to check if it is ok to use either of them - if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + - (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > - lengthConst * ((smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * - (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + - (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * - (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)))) - { - // use circumcenter - dxFirstSuggestion = dx; - dyFirstSuggestion = dy; - }// else we stick to what we have found - }// intersection point - - }// if it is on the boundary, meaning no neighbor triangle in this direction, try other direction - - /// DO THE SAME THING FOR THE OTHER DIRECTION /// - /// find the third point of the neighbor triangle /// - neighborNotFound = GetNeighborsVertex(badotri, largestAngleCorner.x, largestAngleCorner.y, - smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); - /// find the circumcenter of the neighbor triangle /// - dxSecondSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter - dySecondSuggestion = dy; - // if there is a neighbor triangle - if (!neighborNotFound) - { - neighborvertex_1 = neighborotri.Org(); - neighborvertex_2 = neighborotri.Dest(); - neighborvertex_3 = neighborotri.Apex(); - // now calculate neighbor's circumcenter which is the voronoi site - neighborCircumcenter = predicates.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, - ref xi_tmp, ref eta_tmp); - - /// compute petal and Voronoi edge intersection /// - // in order to avoid degenerate cases, we need to do a vector based calculation for line - vector_x = (largestAngleCorner.y - smallestAngleCorner.y);//(-y, x) - vector_y = smallestAngleCorner.x - largestAngleCorner.x; - vector_x = myCircumcenter.x + vector_x; - vector_y = myCircumcenter.y + vector_y; - - - // by intersecting bisectors you will end up with the one you want to walk on - // then this line and circle should be intersected - CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, - xPetalCtr, yPetalCtr, petalRadius, ref p); - - /// choose the correct intersection point /// - // calcuwedgeslate middle point of the longest edge(bisector) - xMidOfMiddleEdge = (largestAngleCorner.x + smallestAngleCorner.x) / 2.0; - yMidOfMiddleEdge = (largestAngleCorner.y + smallestAngleCorner.y) / 2.0; - // we need to find correct intersection point, since line intersects circle twice - // this direction is always ACUTE - isCorrect = ChooseCorrectPoint(xMidOfMiddleEdge, yMidOfMiddleEdge, p[3], p[4], - myCircumcenter.x, myCircumcenter.y, false/*(isObtuse+1)%2*/); - // make sure which point is the correct one to be considered - if (isCorrect) - { - inter_x = p[3]; - inter_y = p[4]; - } - else - { - inter_x = p[1]; - inter_y = p[2]; - } - - /// check if there is a Voronoi vertex between before intersection /// - // check if the voronoi vertex is between the intersection and circumcenter - PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, - neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); - - /// determine the point to be suggested /// - if (p[0] > 0.0) - { // there is at least one intersection point - // if it is between circumcenter and intersection - // if it returns 1.0 this means we have a voronoi vertex within feasible region - if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) - { - if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) - { - // go back to circumcenter - dxSecondSuggestion = dx; - dySecondSuggestion = dy; - - } - else - { // we are not creating a bad triangle - // neighbor's circumcenter is suggested - dxSecondSuggestion = voronoiOrInter[2] - torg.x; - dySecondSuggestion = voronoiOrInter[3] - torg.y; - - } - - } - else - { // there is no voronoi vertex between intersection point and circumcenter - if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) - { - // if it is inside feasible region, then insert v2 - // apply perturbation - // find the distance between circumcenter and intersection point - d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + - (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); - // then find the vector going from intersection point to circumcenter - ax = myCircumcenter.x - inter_x; - ay = myCircumcenter.y - inter_y; - - ax = ax / d; - ay = ay / d; - // now calculate the new intersection point which is perturbated towards the circumcenter - inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); - inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); - if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) - { - // go back to circumcenter - dxSecondSuggestion = dx; - dySecondSuggestion = dy; - - } - else - { - // intersection point is suggested - dxSecondSuggestion = inter_x - torg.x; - dySecondSuggestion = inter_y - torg.y; - } - } - else - { - - // intersection point is suggested - dxSecondSuggestion = inter_x - torg.x; - dySecondSuggestion = inter_y - torg.y; - } - } - /// if it is an acute triangle, check if it is a good enough location /// - // for acute triangle case, we need to check if it is ok to use either of them - if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + - (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > - lengthConst * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * - (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + - (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * - (smallestAngleCorner.y - (dySecondSuggestion + torg.y)))) - { - // use circumcenter - dxSecondSuggestion = dx; - dySecondSuggestion = dy; - }// else we stick on what we have found - } - }// if it is on the boundary, meaning no neighbor triangle in this direction, the other direction might be ok - if (isObtuse) - { - //obtuse: do nothing - dx = dxFirstSuggestion; - dy = dyFirstSuggestion; - } - else - { // acute : consider other direction - if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * - (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + - (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * - (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > - (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * - (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + - (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * - (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) - { - dx = dxSecondSuggestion; - dy = dySecondSuggestion; - } - else - { - dx = dxFirstSuggestion; - dy = dyFirstSuggestion; - } - - }// end if obtuse - }// end of relocation - }// end of almostGood - - Point circumcenter = new Point(); - - if (relocated <= 0) - { - circumcenter.x = torg.x + dx; - circumcenter.y = torg.y + dy; - } - else - { - circumcenter.x = origin_x + dx; - circumcenter.y = origin_y + dy; - } - - xi = (yao * dx - xao * dy) * (2.0 * denominator); - eta = (xdo * dy - ydo * dx) * (2.0 * denominator); - - return circumcenter; - } - - /// - /// Find a new location for a Steiner point. - /// - /// - /// - /// - /// - /// - /// - /// - /// - private Point FindNewLocation(Vertex torg, Vertex tdest, Vertex tapex, - ref double xi, ref double eta, bool offcenter, Otri badotri) - { - double offconstant = behavior.offconstant; - - // for calculating the distances of the edges - double xdo, ydo, xao, yao, xda, yda; - double dodist, aodist, dadist; - // for exact calculation - double denominator; - double dx, dy, dxoff, dyoff; - - ////////////////////////////// HALE'S VARIABLES ////////////////////////////// - // keeps the difference of coordinates edge - double xShortestEdge = 0, yShortestEdge = 0; - - // keeps the square of edge lengths - double shortestEdgeDist = 0, middleEdgeDist = 0, longestEdgeDist = 0; - - // keeps the vertices according to the angle incident to that vertex in a triangle - Point smallestAngleCorner, middleAngleCorner, largestAngleCorner; - - // keeps the type of orientation if the triangle - int orientation = 0; - // keeps the coordinates of circumcenter of itself and neighbor triangle circumcenter - Point myCircumcenter, neighborCircumcenter; - - // keeps if bad triangle is almost good or not - int almostGood = 0; - // keeps the cosine of the largest angle - double cosMaxAngle; - bool isObtuse; // 1: obtuse 0: nonobtuse - // keeps the radius of petal - double petalRadius; - // for calculating petal center - double xPetalCtr_1, yPetalCtr_1, xPetalCtr_2, yPetalCtr_2, xPetalCtr, yPetalCtr, xMidOfShortestEdge, yMidOfShortestEdge; - double dxcenter1, dycenter1, dxcenter2, dycenter2; - // for finding neighbor - Otri neighborotri = default(Otri); - double[] thirdPoint = new double[2]; - //int neighborNotFound = -1; - // for keeping the vertices of the neighbor triangle - Vertex neighborvertex_1; - Vertex neighborvertex_2; - Vertex neighborvertex_3; - // dummy variables - double xi_tmp = 0, eta_tmp = 0; - //vertex thirdVertex; - // for petal intersection - double vector_x, vector_y, xMidOfLongestEdge, yMidOfLongestEdge, inter_x, inter_y; - double[] p = new double[5], voronoiOrInter = new double[4]; - bool isCorrect; - - // for vector calculations in perturbation - double ax, ay, d; - double pertConst = 0.06; // perturbation constant - - double lengthConst = 1; // used at comparing circumcenter's distance to proposed point's distance - double justAcute = 1; // used for making the program working for one direction only - // for smoothing - int relocated = 0;// used to differentiate between calling the deletevertex and just proposing a steiner point - double[] newloc = new double[2]; // new location suggested by smoothing - double origin_x = 0, origin_y = 0; // for keeping torg safe - Otri delotri; // keeping the original orientation for relocation process - // keeps the first and second direction suggested points - double dxFirstSuggestion, dyFirstSuggestion, dxSecondSuggestion, dySecondSuggestion; - // second direction variables - double xMidOfMiddleEdge, yMidOfMiddleEdge; - - double minangle; // in order to make sure that the circumcircle of the bad triangle is greater than petal - // for calculating the slab - double linepnt1_x, linepnt1_y, linepnt2_x, linepnt2_y; // two points of the line - double line_inter_x = 0, line_inter_y = 0; - double line_vector_x, line_vector_y; - double[] line_p = new double[3]; // used for getting the return values of functions related to line intersection - double[] line_result = new double[4]; - // intersection of slab and the petal - double petal_slab_inter_x_first, petal_slab_inter_y_first, petal_slab_inter_x_second, petal_slab_inter_y_second, x_1, y_1, x_2, y_2; - double petal_bisector_x, petal_bisector_y, dist; - double alpha; - bool neighborNotFound_first; - bool neighborNotFound_second; - ////////////////////////////// END OF HALE'S VARIABLES ////////////////////////////// - - Statistic.CircumcenterCount++; - - // Compute the circumcenter of the triangle. - xdo = tdest.x - torg.x; - ydo = tdest.y - torg.y; - xao = tapex.x - torg.x; - yao = tapex.y - torg.y; - xda = tapex.x - tdest.x; - yda = tapex.y - tdest.y; - // keeps the square of the distances - dodist = xdo * xdo + ydo * ydo; - aodist = xao * xao + yao * yao; - dadist = (tdest.x - tapex.x) * (tdest.x - tapex.x) + - (tdest.y - tapex.y) * (tdest.y - tapex.y); - // checking if the user wanted exact arithmetic or not - if (Behavior.NoExact) - { - denominator = 0.5 / (xdo * yao - xao * ydo); - } - else - { - // Use the counterclockwise() routine to ensure a positive (and - // reasonably accurate) result, avoiding any possibility of - // division by zero. - denominator = 0.5 / predicates.CounterClockwise(tdest, tapex, torg); - // Don't count the above as an orientation test. - Statistic.CounterClockwiseCount--; - } - // calculate the circumcenter in terms of distance to origin point - dx = (yao * dodist - ydo * aodist) * denominator; - dy = (xdo * aodist - xao * dodist) * denominator; - // for debugging and for keeping circumcenter to use later - // coordinate value of the circumcenter - myCircumcenter = new Point(torg.x + dx, torg.y + dy); - - delotri = badotri; // save for later - ///////////////// FINDING THE ORIENTATION OF TRIANGLE ////////////////// - // Find the (squared) length of the triangle's shortest edge. This - // serves as a conservative estimate of the insertion radius of the - // circumcenter's parent. The estimate is used to ensure that - // the algorithm terminates even if very small angles appear in - // the input PSLG. - // find the orientation of the triangle, basically shortest and longest edges - orientation = LongestShortestEdge(aodist, dadist, dodist); - //printf("org: (%f,%f), dest: (%f,%f), apex: (%f,%f)\n",torg[0],torg[1],tdest[0],tdest[1],tapex[0],tapex[1]); - ///////////////////////////////////////////////////////////////////////////////////////////// - // 123: shortest: aodist // 213: shortest: dadist // 312: shortest: dodist // - // middle: dadist // middle: aodist // middle: aodist // - // longest: dodist // longest: dodist // longest: dadist // - // 132: shortest: aodist // 231: shortest: dadist // 321: shortest: dodist // - // middle: dodist // middle: dodist // middle: dadist // - // longest: dadist // longest: aodist // longest: aodist // - ///////////////////////////////////////////////////////////////////////////////////////////// - - switch (orientation) - { - case 123: // assign necessary information - /// smallest angle corner: dest - /// largest angle corner: apex - xShortestEdge = xao; yShortestEdge = yao; - - shortestEdgeDist = aodist; - middleEdgeDist = dadist; - longestEdgeDist = dodist; - - smallestAngleCorner = tdest; - middleAngleCorner = torg; - largestAngleCorner = tapex; - break; - - case 132: // assign necessary information - /// smallest angle corner: dest - /// largest angle corner: org - xShortestEdge = xao; yShortestEdge = yao; - - shortestEdgeDist = aodist; - middleEdgeDist = dodist; - longestEdgeDist = dadist; - - smallestAngleCorner = tdest; - middleAngleCorner = tapex; - largestAngleCorner = torg; - - break; - case 213: // assign necessary information - /// smallest angle corner: org - /// largest angle corner: apex - xShortestEdge = xda; yShortestEdge = yda; - - shortestEdgeDist = dadist; - middleEdgeDist = aodist; - longestEdgeDist = dodist; - - smallestAngleCorner = torg; - middleAngleCorner = tdest; - largestAngleCorner = tapex; - break; - case 231: // assign necessary information - /// smallest angle corner: org - /// largest angle corner: dest - xShortestEdge = xda; yShortestEdge = yda; - - shortestEdgeDist = dadist; - middleEdgeDist = dodist; - longestEdgeDist = aodist; - - smallestAngleCorner = torg; - middleAngleCorner = tapex; - largestAngleCorner = tdest; - break; - case 312: // assign necessary information - /// smallest angle corner: apex - /// largest angle corner: org - xShortestEdge = xdo; yShortestEdge = ydo; - - shortestEdgeDist = dodist; - middleEdgeDist = aodist; - longestEdgeDist = dadist; - - smallestAngleCorner = tapex; - middleAngleCorner = tdest; - largestAngleCorner = torg; - break; - case 321: // assign necessary information - default: // TODO: is this safe? - /// smallest angle corner: apex - /// largest angle corner: dest - xShortestEdge = xdo; yShortestEdge = ydo; - - shortestEdgeDist = dodist; - middleEdgeDist = dadist; - longestEdgeDist = aodist; - - smallestAngleCorner = tapex; - middleAngleCorner = torg; - largestAngleCorner = tdest; - break; - - }// end of switch - // check for offcenter condition - if (offcenter && (offconstant > 0.0)) - { - // origin has the smallest angle - if (orientation == 213 || orientation == 231) - { - // Find the position of the off-center, as described by Alper Ungor. - dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; - dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; - // If the off-center is closer to destination than the - // circumcenter, use the off-center instead. - /// doubleLY BAD CASE /// - if (dxoff * dxoff + dyoff * dyoff < - (dx - xdo) * (dx - xdo) + (dy - ydo) * (dy - ydo)) - { - dx = xdo + dxoff; - dy = ydo + dyoff; - } - /// ALMOST GOOD CASE /// - else - { - almostGood = 1; - } - // destination has the smallest angle - } - else if (orientation == 123 || orientation == 132) - { - // Find the position of the off-center, as described by Alper Ungor. - dxoff = 0.5 * xShortestEdge + offconstant * yShortestEdge; - dyoff = 0.5 * yShortestEdge - offconstant * xShortestEdge; - // If the off-center is closer to the origin than the - // circumcenter, use the off-center instead. - /// doubleLY BAD CASE /// - if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) - { - dx = dxoff; - dy = dyoff; - } - /// ALMOST GOOD CASE /// - else - { - almostGood = 1; - } - // apex has the smallest angle - } - else - {//orientation == 312 || orientation == 321 - // Find the position of the off-center, as described by Alper Ungor. - dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; - dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; - // If the off-center is closer to the origin than the - // circumcenter, use the off-center instead. - /// doubleLY BAD CASE /// - if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) - { - dx = dxoff; - dy = dyoff; - } - /// ALMOST GOOD CASE /// - else - { - almostGood = 1; - } - } - } - // if the bad triangle is almost good, apply our approach - if (almostGood == 1) - { - - /// calculate cosine of largest angle /// - cosMaxAngle = (middleEdgeDist + shortestEdgeDist - longestEdgeDist) / (2 * Math.Sqrt(middleEdgeDist) * Math.Sqrt(shortestEdgeDist)); - if (cosMaxAngle < 0.0) - { - // obtuse - isObtuse = true; - } - else if (Math.Abs(cosMaxAngle - 0.0) <= EPS) - { - // right triangle (largest angle is 90 degrees) - isObtuse = true; - } - else - { - // nonobtuse - isObtuse = false; - } - /// RELOCATION (LOCAL SMOOTHING) /// - /// check for possible relocation of one of triangle's points /// - relocated = DoSmoothing(delotri, torg, tdest, tapex, ref newloc); - /// if relocation is possible, delete that vertex and insert a vertex at the new location /// - if (relocated > 0) - { - Statistic.RelocationCount++; - - dx = newloc[0] - torg.x; - dy = newloc[1] - torg.y; - origin_x = torg.x; // keep for later use - origin_y = torg.y; - switch (relocated) - { - case 1: - //printf("Relocate: (%f,%f)\n", torg[0],torg[1]); - mesh.DeleteVertex(ref delotri); - break; - case 2: - //printf("Relocate: (%f,%f)\n", tdest[0],tdest[1]); - delotri.Lnext(); - mesh.DeleteVertex(ref delotri); - break; - case 3: - //printf("Relocate: (%f,%f)\n", tapex[0],tapex[1]); - delotri.Lprev(); - mesh.DeleteVertex(ref delotri); - break; - } - } - else - { - // calculate radius of the petal according to angle constraint - // first find the visible region, PETAL - // find the center of the circle and radius - // choose minimum angle as the maximum of quality angle and the minimum angle of the bad triangle - minangle = Math.Acos((middleEdgeDist + longestEdgeDist - shortestEdgeDist) / (2 * Math.Sqrt(middleEdgeDist) * Math.Sqrt(longestEdgeDist))) * 180.0 / Math.PI; - if (behavior.MinAngle > minangle) - { - minangle = behavior.MinAngle; - } - else - { - minangle = minangle + 0.5; - } - petalRadius = Math.Sqrt(shortestEdgeDist) / (2 * Math.Sin(minangle * Math.PI / 180.0)); - /// compute two possible centers of the petal /// - // finding the center - // first find the middle point of smallest edge - xMidOfShortestEdge = (middleAngleCorner.x + largestAngleCorner.x) / 2.0; - yMidOfShortestEdge = (middleAngleCorner.y + largestAngleCorner.y) / 2.0; - // two possible centers - xPetalCtr_1 = xMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - - largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); - yPetalCtr_1 = yMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - - middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); - - xPetalCtr_2 = xMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - - largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); - yPetalCtr_2 = yMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - - middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); - // find the correct circle since there will be two possible circles - // calculate the distance to smallest angle corner - dxcenter1 = (xPetalCtr_1 - smallestAngleCorner.x) * (xPetalCtr_1 - smallestAngleCorner.x); - dycenter1 = (yPetalCtr_1 - smallestAngleCorner.y) * (yPetalCtr_1 - smallestAngleCorner.y); - dxcenter2 = (xPetalCtr_2 - smallestAngleCorner.x) * (xPetalCtr_2 - smallestAngleCorner.x); - dycenter2 = (yPetalCtr_2 - smallestAngleCorner.y) * (yPetalCtr_2 - smallestAngleCorner.y); - - // whichever is closer to smallest angle corner, it must be the center - if (dxcenter1 + dycenter1 <= dxcenter2 + dycenter2) - { - xPetalCtr = xPetalCtr_1; yPetalCtr = yPetalCtr_1; - } - else - { - xPetalCtr = xPetalCtr_2; yPetalCtr = yPetalCtr_2; - } - /// find the third point of the neighbor triangle /// - neighborNotFound_first = GetNeighborsVertex(badotri, middleAngleCorner.x, middleAngleCorner.y, - smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); - /// find the circumcenter of the neighbor triangle /// - dxFirstSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter - dyFirstSuggestion = dy; - /// before checking the neighbor, find the petal and slab intersections /// - // calculate the intersection point of the petal and the slab lines - // first find the vector - // distance between xmid and petal center - dist = Math.Sqrt((xPetalCtr - xMidOfShortestEdge) * (xPetalCtr - xMidOfShortestEdge) + (yPetalCtr - yMidOfShortestEdge) * (yPetalCtr - yMidOfShortestEdge)); - // find the unit vector goes from mid point to petal center - line_vector_x = (xPetalCtr - xMidOfShortestEdge) / dist; - line_vector_y = (yPetalCtr - yMidOfShortestEdge) / dist; - // find the third point other than p and q - petal_bisector_x = xPetalCtr + line_vector_x * petalRadius; - petal_bisector_y = yPetalCtr + line_vector_y * petalRadius; - alpha = (2.0 * behavior.MaxAngle + minangle - 180.0) * Math.PI / 180.0; - // rotate the vector cw around the petal center - x_1 = petal_bisector_x * Math.Cos(alpha) + petal_bisector_y * Math.Sin(alpha) + xPetalCtr - xPetalCtr * Math.Cos(alpha) - yPetalCtr * Math.Sin(alpha); - y_1 = -petal_bisector_x * Math.Sin(alpha) + petal_bisector_y * Math.Cos(alpha) + yPetalCtr + xPetalCtr * Math.Sin(alpha) - yPetalCtr * Math.Cos(alpha); - // rotate the vector ccw around the petal center - x_2 = petal_bisector_x * Math.Cos(alpha) - petal_bisector_y * Math.Sin(alpha) + xPetalCtr - xPetalCtr * Math.Cos(alpha) + yPetalCtr * Math.Sin(alpha); - y_2 = petal_bisector_x * Math.Sin(alpha) + petal_bisector_y * Math.Cos(alpha) + yPetalCtr - xPetalCtr * Math.Sin(alpha) - yPetalCtr * Math.Cos(alpha); - // we need to find correct intersection point, since there are two possibilities - // weather it is obtuse/acute the one closer to the minimum angle corner is the first direction - isCorrect = ChooseCorrectPoint(x_2, y_2, middleAngleCorner.x, middleAngleCorner.y, x_1, y_1, true); - // make sure which point is the correct one to be considered - if (isCorrect) - { - petal_slab_inter_x_first = x_1; - petal_slab_inter_y_first = y_1; - petal_slab_inter_x_second = x_2; - petal_slab_inter_y_second = y_2; - } - else - { - petal_slab_inter_x_first = x_2; - petal_slab_inter_y_first = y_2; - petal_slab_inter_x_second = x_1; - petal_slab_inter_y_second = y_1; - } - /// choose the correct intersection point /// - // calculate middle point of the longest edge(bisector) - xMidOfLongestEdge = (middleAngleCorner.x + smallestAngleCorner.x) / 2.0; - yMidOfLongestEdge = (middleAngleCorner.y + smallestAngleCorner.y) / 2.0; - // if there is a neighbor triangle - if (!neighborNotFound_first) - { - neighborvertex_1 = neighborotri.Org(); - neighborvertex_2 = neighborotri.Dest(); - neighborvertex_3 = neighborotri.Apex(); - // now calculate neighbor's circumcenter which is the voronoi site - neighborCircumcenter = predicates.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, - ref xi_tmp, ref eta_tmp); - - /// compute petal and Voronoi edge intersection /// - // in order to avoid degenerate cases, we need to do a vector based calculation for line - vector_x = (middleAngleCorner.y - smallestAngleCorner.y);//(-y, x) - vector_y = smallestAngleCorner.x - middleAngleCorner.x; - vector_x = myCircumcenter.x + vector_x; - vector_y = myCircumcenter.y + vector_y; - // by intersecting bisectors you will end up with the one you want to walk on - // then this line and circle should be intersected - CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, - xPetalCtr, yPetalCtr, petalRadius, ref p); - // we need to find correct intersection point, since line intersects circle twice - isCorrect = ChooseCorrectPoint(xMidOfLongestEdge, yMidOfLongestEdge, p[3], p[4], - myCircumcenter.x, myCircumcenter.y, isObtuse); - // make sure which point is the correct one to be considered - if (isCorrect) - { - inter_x = p[3]; - inter_y = p[4]; - } - else - { - inter_x = p[1]; - inter_y = p[2]; - } - //----------------------hale new first direction: for slab calculation---------------// - // calculate the intersection of angle lines and Voronoi - linepnt1_x = middleAngleCorner.x; - linepnt1_y = middleAngleCorner.y; - // vector from middleAngleCorner to largestAngleCorner - line_vector_x = largestAngleCorner.x - middleAngleCorner.x; - line_vector_y = largestAngleCorner.y - middleAngleCorner.y; - // rotate the vector around middleAngleCorner in cw by maxangle degrees - linepnt2_x = petal_slab_inter_x_first; - linepnt2_y = petal_slab_inter_y_first; - // now calculate the intersection of two lines - LineLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, linepnt1_x, linepnt1_y, linepnt2_x, linepnt2_y, ref line_p); - // check if there is a suitable intersection - if (line_p[0] > 0.0) - { - line_inter_x = line_p[1]; - line_inter_y = line_p[2]; - } - else - { - // for debugging (to make sure) - //printf("1) No intersection between two lines!!!\n"); - //printf("(%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f)\n",myCircumcenter.x,myCircumcenter.y,vector_x,vector_y,linepnt1_x,linepnt1_y,linepnt2_x,linepnt2_y); - } - - //---------------------------------------------------------------------// - /// check if there is a Voronoi vertex between before intersection /// - // check if the voronoi vertex is between the intersection and circumcenter - PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, - neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); - - /// determine the point to be suggested /// - if (p[0] > 0.0) - { // there is at least one intersection point - // if it is between circumcenter and intersection - // if it returns 1.0 this means we have a voronoi vertex within feasible region - if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) - { - //-----------------hale new continues 1------------------// - // now check if the line intersection is between cc and voronoi - PointBetweenPoints(voronoiOrInter[2], voronoiOrInter[3], myCircumcenter.x, myCircumcenter.y, line_inter_x, line_inter_y, ref line_result); - if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0) - { - // check if we can go further by picking the slab line and petal intersection - // calculate the distance to the smallest angle corner - // check if we create a bad triangle or not - if (((smallestAngleCorner.x - petal_slab_inter_x_first) * (smallestAngleCorner.x - petal_slab_inter_x_first) + - (smallestAngleCorner.y - petal_slab_inter_y_first) * (smallestAngleCorner.y - petal_slab_inter_y_first) > - lengthConst * ((smallestAngleCorner.x - line_inter_x) * - (smallestAngleCorner.x - line_inter_x) + - (smallestAngleCorner.y - line_inter_y) * - (smallestAngleCorner.y - line_inter_y))) - && (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, petal_slab_inter_x_first, petal_slab_inter_y_first)) - && MinDistanceToNeighbor(petal_slab_inter_x_first, petal_slab_inter_y_first, ref neighborotri) > MinDistanceToNeighbor(line_inter_x, line_inter_y, ref neighborotri)) - { - // check the neighbor's vertices also, which one if better - //slab and petal intersection is advised - dxFirstSuggestion = petal_slab_inter_x_first - torg.x; - dyFirstSuggestion = petal_slab_inter_y_first - torg.y; - } - else - { // slab intersection point is further away - if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) - { - // apply perturbation - // find the distance between circumcenter and intersection point - d = Math.Sqrt((line_inter_x - myCircumcenter.x) * (line_inter_x - myCircumcenter.x) + - (line_inter_y - myCircumcenter.y) * (line_inter_y - myCircumcenter.y)); - // then find the vector going from intersection point to circumcenter - ax = myCircumcenter.x - line_inter_x; - ay = myCircumcenter.y - line_inter_y; - - ax = ax / d; - ay = ay / d; - // now calculate the new intersection point which is perturbated towards the circumcenter - line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); - line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); - if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) - { - // go back to circumcenter - dxFirstSuggestion = dx; - dyFirstSuggestion = dy; - } - else - { - // intersection point is suggested - dxFirstSuggestion = line_inter_x - torg.x; - dyFirstSuggestion = line_inter_y - torg.y; - } - } - else - {// we are not creating a bad triangle - // slab intersection is advised - dxFirstSuggestion = line_result[2] - torg.x; - dyFirstSuggestion = line_result[3] - torg.y; - } - } - //------------------------------------------------------// - } - else - { - /// NOW APPLY A BREADTH-FIRST SEARCH ON THE VORONOI - if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) - { - // go back to circumcenter - dxFirstSuggestion = dx; - dyFirstSuggestion = dy; - } - else - { - // we are not creating a bad triangle - // neighbor's circumcenter is suggested - dxFirstSuggestion = voronoiOrInter[2] - torg.x; - dyFirstSuggestion = voronoiOrInter[3] - torg.y; - } - } - } - else - { // there is no voronoi vertex between intersection point and circumcenter - //-----------------hale new continues 2-----------------// - // now check if the line intersection is between cc and intersection point - PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, line_inter_x, line_inter_y, ref line_result); - if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0) - { - // check if we can go further by picking the slab line and petal intersection - // calculate the distance to the smallest angle corner - if (((smallestAngleCorner.x - petal_slab_inter_x_first) * (smallestAngleCorner.x - petal_slab_inter_x_first) + - (smallestAngleCorner.y - petal_slab_inter_y_first) * (smallestAngleCorner.y - petal_slab_inter_y_first) > - lengthConst * ((smallestAngleCorner.x - line_inter_x) * - (smallestAngleCorner.x - line_inter_x) + - (smallestAngleCorner.y - line_inter_y) * - (smallestAngleCorner.y - line_inter_y))) - && (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, petal_slab_inter_x_first, petal_slab_inter_y_first)) - && MinDistanceToNeighbor(petal_slab_inter_x_first, petal_slab_inter_y_first, ref neighborotri) > MinDistanceToNeighbor(line_inter_x, line_inter_y, ref neighborotri)) - { - //slab and petal intersection is advised - dxFirstSuggestion = petal_slab_inter_x_first - torg.x; - dyFirstSuggestion = petal_slab_inter_y_first - torg.y; - } - else - { // slab intersection point is further away - if (IsBadTriangleAngle(largestAngleCorner.x, largestAngleCorner.y, middleAngleCorner.x, middleAngleCorner.y, line_inter_x, line_inter_y)) - { - // apply perturbation - // find the distance between circumcenter and intersection point - d = Math.Sqrt((line_inter_x - myCircumcenter.x) * (line_inter_x - myCircumcenter.x) + - (line_inter_y - myCircumcenter.y) * (line_inter_y - myCircumcenter.y)); - // then find the vector going from intersection point to circumcenter - ax = myCircumcenter.x - line_inter_x; - ay = myCircumcenter.y - line_inter_y; - - ax = ax / d; - ay = ay / d; - // now calculate the new intersection point which is perturbated towards the circumcenter - line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); - line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); - if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) - { - // go back to circumcenter - dxFirstSuggestion = dx; - dyFirstSuggestion = dy; - } - else - { - // intersection point is suggested - dxFirstSuggestion = line_inter_x - torg.x; - dyFirstSuggestion = line_inter_y - torg.y; - } - } - else - {// we are not creating a bad triangle - // slab intersection is advised - dxFirstSuggestion = line_result[2] - torg.x; - dyFirstSuggestion = line_result[3] - torg.y; - } - } - //------------------------------------------------------// - } - else - { - if (IsBadTriangleAngle(largestAngleCorner.x, largestAngleCorner.y, middleAngleCorner.x, middleAngleCorner.y, inter_x, inter_y)) - { - //printf("testtriangle returned false! bad triangle\n"); - // if it is inside feasible region, then insert v2 - // apply perturbation - // find the distance between circumcenter and intersection point - d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + - (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); - // then find the vector going from intersection point to circumcenter - ax = myCircumcenter.x - inter_x; - ay = myCircumcenter.y - inter_y; - - ax = ax / d; - ay = ay / d; - // now calculate the new intersection point which is perturbated towards the circumcenter - inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); - inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); - if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) - { - // go back to circumcenter - dxFirstSuggestion = dx; - dyFirstSuggestion = dy; - } - else - { - // intersection point is suggested - dxFirstSuggestion = inter_x - torg.x; - dyFirstSuggestion = inter_y - torg.y; - } - } - else - { - // intersection point is suggested - dxFirstSuggestion = inter_x - torg.x; - dyFirstSuggestion = inter_y - torg.y; - } - } - } - /// if it is an acute triangle, check if it is a good enough location /// - // for acute triangle case, we need to check if it is ok to use either of them - if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + - (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > - lengthConst * ((smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * - (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + - (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * - (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)))) - { - // use circumcenter - dxFirstSuggestion = dx; - dyFirstSuggestion = dy; - - }// else we stick to what we have found - }// intersection point - - }// if it is on the boundary, meaning no neighbor triangle in this direction, try other direction - - /// DO THE SAME THING FOR THE OTHER DIRECTION /// - /// find the third point of the neighbor triangle /// - neighborNotFound_second = GetNeighborsVertex(badotri, largestAngleCorner.x, largestAngleCorner.y, - smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); - /// find the circumcenter of the neighbor triangle /// - dxSecondSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter - dySecondSuggestion = dy; - - /// choose the correct intersection point /// - // calculate middle point of the longest edge(bisector) - xMidOfMiddleEdge = (largestAngleCorner.x + smallestAngleCorner.x) / 2.0; - yMidOfMiddleEdge = (largestAngleCorner.y + smallestAngleCorner.y) / 2.0; - // if there is a neighbor triangle - if (!neighborNotFound_second) - { - neighborvertex_1 = neighborotri.Org(); - neighborvertex_2 = neighborotri.Dest(); - neighborvertex_3 = neighborotri.Apex(); - // now calculate neighbor's circumcenter which is the voronoi site - neighborCircumcenter = predicates.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, - ref xi_tmp, ref eta_tmp); - - /// compute petal and Voronoi edge intersection /// - // in order to avoid degenerate cases, we need to do a vector based calculation for line - vector_x = (largestAngleCorner.y - smallestAngleCorner.y);//(-y, x) - vector_y = smallestAngleCorner.x - largestAngleCorner.x; - vector_x = myCircumcenter.x + vector_x; - vector_y = myCircumcenter.y + vector_y; - - - // by intersecting bisectors you will end up with the one you want to walk on - // then this line and circle should be intersected - CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, - xPetalCtr, yPetalCtr, petalRadius, ref p); - - // we need to find correct intersection point, since line intersects circle twice - // this direction is always ACUTE - isCorrect = ChooseCorrectPoint(xMidOfMiddleEdge, yMidOfMiddleEdge, p[3], p[4], - myCircumcenter.x, myCircumcenter.y, false/*(isObtuse+1)%2*/); - // make sure which point is the correct one to be considered - if (isCorrect) - { - inter_x = p[3]; - inter_y = p[4]; - } - else - { - inter_x = p[1]; - inter_y = p[2]; - } - //----------------------hale new second direction:for slab calculation---------------// - // calculate the intersection of angle lines and Voronoi - linepnt1_x = largestAngleCorner.x; - linepnt1_y = largestAngleCorner.y; - // vector from largestAngleCorner to middleAngleCorner - line_vector_x = middleAngleCorner.x - largestAngleCorner.x; - line_vector_y = middleAngleCorner.y - largestAngleCorner.y; - // rotate the vector around largestAngleCorner in ccw by maxangle degrees - linepnt2_x = petal_slab_inter_x_second; - linepnt2_y = petal_slab_inter_y_second; - // now calculate the intersection of two lines - LineLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, linepnt1_x, linepnt1_y, linepnt2_x, linepnt2_y, ref line_p); - // check if there is a suitable intersection - if (line_p[0] > 0.0) - { - line_inter_x = line_p[1]; - line_inter_y = line_p[2]; - } - else - { - // for debugging (to make sure) - //printf("1) No intersection between two lines!!!\n"); - //printf("(%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f)\n",myCircumcenter.x,myCircumcenter.y,vector_x,vector_y,linepnt1_x,linepnt1_y,linepnt2_x,linepnt2_y); - } - //---------------------------------------------------------------------// - /// check if there is a Voronoi vertex between before intersection /// - // check if the voronoi vertex is between the intersection and circumcenter - PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, - neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); - /// determine the point to be suggested /// - if (p[0] > 0.0) - { // there is at least one intersection point - // if it is between circumcenter and intersection - // if it returns 1.0 this means we have a voronoi vertex within feasible region - if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) - { - //-----------------hale new continues 1------------------// - // now check if the line intersection is between cc and voronoi - PointBetweenPoints(voronoiOrInter[2], voronoiOrInter[3], myCircumcenter.x, myCircumcenter.y, line_inter_x, line_inter_y, ref line_result); - if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0) - { - // check if we can go further by picking the slab line and petal intersection - // calculate the distance to the smallest angle corner - // - if (((smallestAngleCorner.x - petal_slab_inter_x_second) * (smallestAngleCorner.x - petal_slab_inter_x_second) + - (smallestAngleCorner.y - petal_slab_inter_y_second) * (smallestAngleCorner.y - petal_slab_inter_y_second) > - lengthConst * ((smallestAngleCorner.x - line_inter_x) * - (smallestAngleCorner.x - line_inter_x) + - (smallestAngleCorner.y - line_inter_y) * - (smallestAngleCorner.y - line_inter_y))) - && (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, petal_slab_inter_x_second, petal_slab_inter_y_second)) - && MinDistanceToNeighbor(petal_slab_inter_x_second, petal_slab_inter_y_second, ref neighborotri) > MinDistanceToNeighbor(line_inter_x, line_inter_y, ref neighborotri)) - { - // slab and petal intersection is advised - dxSecondSuggestion = petal_slab_inter_x_second - torg.x; - dySecondSuggestion = petal_slab_inter_y_second - torg.y; - } - else - { // slab intersection point is further away - if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) - { - // apply perturbation - // find the distance between circumcenter and intersection point - d = Math.Sqrt((line_inter_x - myCircumcenter.x) * (line_inter_x - myCircumcenter.x) + - (line_inter_y - myCircumcenter.y) * (line_inter_y - myCircumcenter.y)); - // then find the vector going from intersection point to circumcenter - ax = myCircumcenter.x - line_inter_x; - ay = myCircumcenter.y - line_inter_y; - - ax = ax / d; - ay = ay / d; - // now calculate the new intersection point which is perturbated towards the circumcenter - line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); - line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); - if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) - { - // go back to circumcenter - dxSecondSuggestion = dx; - dySecondSuggestion = dy; - } - else - { - // intersection point is suggested - dxSecondSuggestion = line_inter_x - torg.x; - dySecondSuggestion = line_inter_y - torg.y; - - } - } - else - {// we are not creating a bad triangle - // slab intersection is advised - dxSecondSuggestion = line_result[2] - torg.x; - dySecondSuggestion = line_result[3] - torg.y; - } - } - //------------------------------------------------------// - } - else - { - if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) - { - // go back to circumcenter - dxSecondSuggestion = dx; - dySecondSuggestion = dy; - } - else - { // we are not creating a bad triangle - // neighbor's circumcenter is suggested - dxSecondSuggestion = voronoiOrInter[2] - torg.x; - dySecondSuggestion = voronoiOrInter[3] - torg.y; - } - } - } - else - { // there is no voronoi vertex between intersection point and circumcenter - //-----------------hale new continues 2-----------------// - // now check if the line intersection is between cc and intersection point - PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, line_inter_x, line_inter_y, ref line_result); - if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0) - { - // check if we can go further by picking the slab line and petal intersection - // calculate the distance to the smallest angle corner - if (((smallestAngleCorner.x - petal_slab_inter_x_second) * (smallestAngleCorner.x - petal_slab_inter_x_second) + - (smallestAngleCorner.y - petal_slab_inter_y_second) * (smallestAngleCorner.y - petal_slab_inter_y_second) > - lengthConst * ((smallestAngleCorner.x - line_inter_x) * - (smallestAngleCorner.x - line_inter_x) + - (smallestAngleCorner.y - line_inter_y) * - (smallestAngleCorner.y - line_inter_y))) - && (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, petal_slab_inter_x_second, petal_slab_inter_y_second)) - && MinDistanceToNeighbor(petal_slab_inter_x_second, petal_slab_inter_y_second, ref neighborotri) > MinDistanceToNeighbor(line_inter_x, line_inter_y, ref neighborotri)) - { - // slab and petal intersection is advised - dxSecondSuggestion = petal_slab_inter_x_second - torg.x; - dySecondSuggestion = petal_slab_inter_y_second - torg.y; - } - else - { // slab intersection point is further away ; - if (IsBadTriangleAngle(largestAngleCorner.x, largestAngleCorner.y, middleAngleCorner.x, middleAngleCorner.y, line_inter_x, line_inter_y)) - { - // apply perturbation - // find the distance between circumcenter and intersection point - d = Math.Sqrt((line_inter_x - myCircumcenter.x) * (line_inter_x - myCircumcenter.x) + - (line_inter_y - myCircumcenter.y) * (line_inter_y - myCircumcenter.y)); - // then find the vector going from intersection point to circumcenter - ax = myCircumcenter.x - line_inter_x; - ay = myCircumcenter.y - line_inter_y; - - ax = ax / d; - ay = ay / d; - // now calculate the new intersection point which is perturbated towards the circumcenter - line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); - line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); - if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) - { - // go back to circumcenter - dxSecondSuggestion = dx; - dySecondSuggestion = dy; - } - else - { - // intersection point is suggested - dxSecondSuggestion = line_inter_x - torg.x; - dySecondSuggestion = line_inter_y - torg.y; - } - } - else - { - // we are not creating a bad triangle - // slab intersection is advised - dxSecondSuggestion = line_result[2] - torg.x; - dySecondSuggestion = line_result[3] - torg.y; - } - } - //------------------------------------------------------// - } - else - { - if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) - { - // if it is inside feasible region, then insert v2 - // apply perturbation - // find the distance between circumcenter and intersection point - d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + - (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); - // then find the vector going from intersection point to circumcenter - ax = myCircumcenter.x - inter_x; - ay = myCircumcenter.y - inter_y; - - ax = ax / d; - ay = ay / d; - // now calculate the new intersection point which is perturbated towards the circumcenter - inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); - inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); - if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) - { - // go back to circumcenter - dxSecondSuggestion = dx; - dySecondSuggestion = dy; - } - else - { - // intersection point is suggested - dxSecondSuggestion = inter_x - torg.x; - dySecondSuggestion = inter_y - torg.y; - } - } - else - { - // intersection point is suggested - dxSecondSuggestion = inter_x - torg.x; - dySecondSuggestion = inter_y - torg.y; - } - } - } - - /// if it is an acute triangle, check if it is a good enough location /// - // for acute triangle case, we need to check if it is ok to use either of them - if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + - (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > - lengthConst * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * - (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + - (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * - (smallestAngleCorner.y - (dySecondSuggestion + torg.y)))) - { - // use circumcenter - dxSecondSuggestion = dx; - dySecondSuggestion = dy; - - }// else we stick on what we have found - } - }// if it is on the boundary, meaning no neighbor triangle in this direction, the other direction might be ok - if (isObtuse) - { - if (neighborNotFound_first && neighborNotFound_second) - { - //obtuse: check if the other direction works - if (justAcute * ((smallestAngleCorner.x - (xMidOfMiddleEdge)) * - (smallestAngleCorner.x - (xMidOfMiddleEdge)) + - (smallestAngleCorner.y - (yMidOfMiddleEdge)) * - (smallestAngleCorner.y - (yMidOfMiddleEdge))) > - (smallestAngleCorner.x - (xMidOfLongestEdge)) * - (smallestAngleCorner.x - (xMidOfLongestEdge)) + - (smallestAngleCorner.y - (yMidOfLongestEdge)) * - (smallestAngleCorner.y - (yMidOfLongestEdge))) - { - dx = dxSecondSuggestion; - dy = dySecondSuggestion; - } - else - { - dx = dxFirstSuggestion; - dy = dyFirstSuggestion; - } - } - else if (neighborNotFound_first) - { - //obtuse: check if the other direction works - if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * - (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + - (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * - (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > - (smallestAngleCorner.x - (xMidOfLongestEdge)) * - (smallestAngleCorner.x - (xMidOfLongestEdge)) + - (smallestAngleCorner.y - (yMidOfLongestEdge)) * - (smallestAngleCorner.y - (yMidOfLongestEdge))) - { - dx = dxSecondSuggestion; - dy = dySecondSuggestion; - } - else - { - dx = dxFirstSuggestion; - dy = dyFirstSuggestion; - } - } - else if (neighborNotFound_second) - { - //obtuse: check if the other direction works - if (justAcute * ((smallestAngleCorner.x - (xMidOfMiddleEdge)) * - (smallestAngleCorner.x - (xMidOfMiddleEdge)) + - (smallestAngleCorner.y - (yMidOfMiddleEdge)) * - (smallestAngleCorner.y - (yMidOfMiddleEdge))) > - (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * - (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + - (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * - (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) - { - dx = dxSecondSuggestion; - dy = dySecondSuggestion; - } - else - { - dx = dxFirstSuggestion; - dy = dyFirstSuggestion; - } - } - else - { - //obtuse: check if the other direction works - if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * - (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + - (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * - (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > - (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * - (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + - (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * - (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) - { - dx = dxSecondSuggestion; - dy = dySecondSuggestion; - } - else - { - dx = dxFirstSuggestion; - dy = dyFirstSuggestion; - } - } - } - else - { // acute : consider other direction - if (neighborNotFound_first && neighborNotFound_second) - { - //obtuse: check if the other direction works - if (justAcute * ((smallestAngleCorner.x - (xMidOfMiddleEdge)) * - (smallestAngleCorner.x - (xMidOfMiddleEdge)) + - (smallestAngleCorner.y - (yMidOfMiddleEdge)) * - (smallestAngleCorner.y - (yMidOfMiddleEdge))) > - (smallestAngleCorner.x - (xMidOfLongestEdge)) * - (smallestAngleCorner.x - (xMidOfLongestEdge)) + - (smallestAngleCorner.y - (yMidOfLongestEdge)) * - (smallestAngleCorner.y - (yMidOfLongestEdge))) - { - dx = dxSecondSuggestion; - dy = dySecondSuggestion; - } - else - { - dx = dxFirstSuggestion; - dy = dyFirstSuggestion; - } - } - else if (neighborNotFound_first) - { - //obtuse: check if the other direction works - if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * - (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + - (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * - (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > - (smallestAngleCorner.x - (xMidOfLongestEdge)) * - (smallestAngleCorner.x - (xMidOfLongestEdge)) + - (smallestAngleCorner.y - (yMidOfLongestEdge)) * - (smallestAngleCorner.y - (yMidOfLongestEdge))) - { - dx = dxSecondSuggestion; - dy = dySecondSuggestion; - } - else - { - dx = dxFirstSuggestion; - dy = dyFirstSuggestion; - } - } - else if (neighborNotFound_second) - { - //obtuse: check if the other direction works - if (justAcute * ((smallestAngleCorner.x - (xMidOfMiddleEdge)) * - (smallestAngleCorner.x - (xMidOfMiddleEdge)) + - (smallestAngleCorner.y - (yMidOfMiddleEdge)) * - (smallestAngleCorner.y - (yMidOfMiddleEdge))) > - (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * - (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + - (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * - (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) - { - dx = dxSecondSuggestion; - dy = dySecondSuggestion; - } - else - { - dx = dxFirstSuggestion; - dy = dyFirstSuggestion; - } - } - else - { - //obtuse: check if the other direction works - if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * - (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + - (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * - (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > - (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * - (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + - (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * - (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) - { - dx = dxSecondSuggestion; - dy = dySecondSuggestion; - } - else - { - dx = dxFirstSuggestion; - dy = dyFirstSuggestion; - } - } - - }// end if obtuse - }// end of relocation - }// end of almostGood - - Point circumcenter = new Point(); - - if (relocated <= 0) - { - circumcenter.x = torg.x + dx; - circumcenter.y = torg.y + dy; - } - else - { - circumcenter.x = origin_x + dx; - circumcenter.y = origin_y + dy; - } - xi = (yao * dx - xao * dy) * (2.0 * denominator); - eta = (xdo * dy - ydo * dx) * (2.0 * denominator); - - return circumcenter; - } - - /// - /// Given square of edge lengths of a triangle, - // determine its orientation - /// - /// - /// - /// - /// Returns a number indicating an orientation. - private int LongestShortestEdge(double aodist, double dadist, double dodist) - { - // 123: shortest: aodist // 213: shortest: dadist // 312: shortest: dodist - // middle: dadist // middle: aodist // middle: aodist - // longest: dodist // longest: dodist // longest: dadist - // 132: shortest: aodist // 231: shortest: dadist // 321: shortest: dodist - // middle: dodist // middle: dodist // middle: dadist - // longest: dadist // longest: aodist // longest: aodist - - int max = 0, min = 0, mid = 0, minMidMax; - if (dodist < aodist && dodist < dadist) - { - min = 3; // apex is the smallest angle, dodist is the longest edge - if (aodist < dadist) - { - max = 2; // dadist is the longest edge - mid = 1; // aodist is the middle longest edge - } - else - { - max = 1; // aodist is the longest edge - mid = 2; // dadist is the middle longest edge - } - } - else if (aodist < dadist) - { - min = 1; // dest is the smallest angle, aodist is the biggest edge - if (dodist < dadist) - { - max = 2; // dadist is the longest edge - mid = 3; // dodist is the middle longest edge - } - else - { - max = 3; // dodist is the longest edge - mid = 2; // dadist is the middle longest edge - } - } - else - { - min = 2; // origin is the smallest angle, dadist is the biggest edge - if (aodist < dodist) - { - max = 3; // dodist is the longest edge - mid = 1; // aodist is the middle longest edge - } - else - { - max = 1; // aodist is the longest edge - mid = 3; // dodist is the middle longest edge - } - } - minMidMax = min * 100 + mid * 10 + max; - // HANDLE ISOSCELES TRIANGLE CASE - return minMidMax; - } - - /// - /// Checks if smothing is possible for a given bad triangle. - /// - /// - /// - /// - /// - /// The new location for the point, if somothing is possible. - /// Returns 1, 2 or 3 if smoothing will work, 0 otherwise. - private int DoSmoothing(Otri badotri, Vertex torg, Vertex tdest, Vertex tapex, - ref double[] newloc) - { - - int numpoints_p = 0;// keeps the number of points in a star of point p, q, r - int numpoints_q = 0; - int numpoints_r = 0; - //int i; - double[] possibilities = new double[6];//there can be more than one possibilities - int num_pos = 0; // number of possibilities - int flag1 = 0, flag2 = 0, flag3 = 0; - bool newLocFound = false; - - //vertex v1, v2, v3; // for ccw test - //double p1[2], p2[2], p3[2]; - //double temp[2]; - - //********************* TRY TO RELOCATE POINT "p" *************** - - // get the surrounding points of p, so this gives us the triangles - numpoints_p = GetStarPoints(badotri, torg, tdest, tapex, 1, ref points_p); - // check if the points in counterclockwise order - // p1[0] = points_p[0]; p1[1] = points_p[1]; - // p2[0] = points_p[2]; p2[1] = points_p[3]; - // p3[0] = points_p[4]; p3[1] = points_p[5]; - // v1 = (vertex)p1; v2 = (vertex)p2; v3 = (vertex)p3; - // if(counterclockwise(m,b,v1,v2,v3) < 0){ - // // reverse the order to ccw - // for(i = 0; i < numpoints_p/2; i++){ - // temp[0] = points_p[2*i]; - // temp[1] = points_p[2*i+1]; - // points_p[2*i] = points_p[2*(numpoints_p-1)-2*i]; - // points_p[2*i+1] = points_p[2*(numpoints_p-1)+1-2*i]; - // points_p[2*(numpoints_p-1)-2*i] = temp[0]; - // points_p[2*(numpoints_p-1)+1-2*i] = temp[1]; - // } - // } - // m.counterclockcount--; - // INTERSECTION OF PETALS - // first check whether the star angles are appropriate for relocation - if (torg.type == VertexType.FreeVertex && numpoints_p != 0 && ValidPolygonAngles(numpoints_p, points_p)) - { - //newLocFound = getPetalIntersection(m, b, numpoints_p, points_p, newloc); - //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_p, points_p, newloc,torg[0],torg[1]); - if (behavior.MaxAngle == 0.0) - { - newLocFound = GetWedgeIntersectionWithoutMaxAngle(numpoints_p, points_p, ref newloc); - } - else - { - newLocFound = GetWedgeIntersection(numpoints_p, points_p, ref newloc); - } - //printf("call petal intersection for p\n"); - // make sure the relocated point is a free vertex - if (newLocFound) - { - possibilities[0] = newloc[0];// something found - possibilities[1] = newloc[1]; - num_pos++;// increase the number of possibilities - flag1 = 1; - } - } - - //********************* TRY TO RELOCATE POINT "q" *************** - - // get the surrounding points of q, so this gives us the triangles - numpoints_q = GetStarPoints(badotri, torg, tdest, tapex, 2, ref points_q); - // // check if the points in counterclockwise order - // v1[0] = points_q[0]; v1[1] = points_q[1]; - // v2[0] = points_q[2]; v2[1] = points_q[3]; - // v3[0] = points_q[4]; v3[1] = points_q[5]; - // if(counterclockwise(m,b,v1,v2,v3) < 0){ - // // reverse the order to ccw - // for(i = 0; i < numpoints_q/2; i++){ - // temp[0] = points_q[2*i]; - // temp[1] = points_q[2*i+1]; - // points_q[2*i] = points_q[2*(numpoints_q-1)-2*i]; - // points_q[2*i+1] = points_q[2*(numpoints_q-1)+1-2*i]; - // points_q[2*(numpoints_q-1)-2*i] = temp[0]; - // points_q[2*(numpoints_q-1)+1-2*i] = temp[1]; - // } - // } - // m.counterclockcount--; - // INTERSECTION OF PETALS - // first check whether the star angles are appropriate for relocation - if (tdest.type == VertexType.FreeVertex && numpoints_q != 0 && ValidPolygonAngles(numpoints_q, points_q)) - { - //newLocFound = getPetalIntersection(m, b,numpoints_q, points_q, newloc); - //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_q, points_q, newloc,tapex[0],tapex[1]); - if (behavior.MaxAngle == 0.0) - { - newLocFound = GetWedgeIntersectionWithoutMaxAngle(numpoints_q, points_q, ref newloc); - } - else - { - newLocFound = GetWedgeIntersection(numpoints_q, points_q, ref newloc); - } - //printf("call petal intersection for q\n"); - - // make sure the relocated point is a free vertex - if (newLocFound) - { - possibilities[2] = newloc[0];// something found - possibilities[3] = newloc[1]; - num_pos++;// increase the number of possibilities - flag2 = 2; - } - } - - - //********************* TRY TO RELOCATE POINT "q" *************** - // get the surrounding points of r, so this gives us the triangles - numpoints_r = GetStarPoints(badotri, torg, tdest, tapex, 3, ref points_r); - // check if the points in counterclockwise order - // v1[0] = points_r[0]; v1[1] = points_r[1]; - // v2[0] = points_r[2]; v2[1] = points_r[3]; - // v3[0] = points_r[4]; v3[1] = points_r[5]; - // if(counterclockwise(m,b,v1,v2,v3) < 0){ - // // reverse the order to ccw - // for(i = 0; i < numpoints_r/2; i++){ - // temp[0] = points_r[2*i]; - // temp[1] = points_r[2*i+1]; - // points_r[2*i] = points_r[2*(numpoints_r-1)-2*i]; - // points_r[2*i+1] = points_r[2*(numpoints_r-1)+1-2*i]; - // points_r[2*(numpoints_r-1)-2*i] = temp[0]; - // points_r[2*(numpoints_r-1)+1-2*i] = temp[1]; - // } - // } - // m.counterclockcount--; - // INTERSECTION OF PETALS - // first check whether the star angles are appropriate for relocation - if (tapex.type == VertexType.FreeVertex && numpoints_r != 0 && ValidPolygonAngles(numpoints_r, points_r)) - { - //newLocFound = getPetalIntersection(m, b,numpoints_r, points_r, newloc); - //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_r, points_r, newloc,tdest[0],tdest[1]); - if (behavior.MaxAngle == 0.0) - { - newLocFound = GetWedgeIntersectionWithoutMaxAngle(numpoints_r, points_r, ref newloc); - } - else - { - newLocFound = GetWedgeIntersection(numpoints_r, points_r, ref newloc); - } - - //printf("call petal intersection for r\n"); - - - // make sure the relocated point is a free vertex - if (newLocFound) - { - possibilities[4] = newloc[0];// something found - possibilities[5] = newloc[1]; - num_pos++;// increase the number of possibilities - flag3 = 3; - } - } - //printf("numpossibilities %d\n",num_pos); - //////////// AFTER FINISH CHECKING EVERY POSSIBILITY, CHOOSE ANY OF THE AVAILABLE ONE ////////////////////// - if (num_pos > 0) - { - if (flag1 > 0) - { // suggest to relocate origin - newloc[0] = possibilities[0]; - newloc[1] = possibilities[1]; - return flag1; - - } - else - { - if (flag2 > 0) - { // suggest to relocate apex - newloc[0] = possibilities[2]; - newloc[1] = possibilities[3]; - return flag2; - - } - else - {// suggest to relocate destination - if (flag3 > 0) - { - newloc[0] = possibilities[4]; - newloc[1] = possibilities[5]; - return flag3; - - } - } - } - } - - return 0;// could not find any good relocation - } - - /// - /// Finds the star of a given point. - /// - /// - /// - /// - /// - /// - /// List of points on the star of the given point. - /// Number of points on the star of the given point. - private int GetStarPoints(Otri badotri, Vertex p, Vertex q, Vertex r, - int whichPoint, ref double[] points) - { - - Otri neighotri = default(Otri); // for return value of the function - Otri tempotri; // for temporary usage - double first_x = 0, first_y = 0; // keeps the first point to be considered - double second_x = 0, second_y = 0; // for determining the edge we will begin - double third_x = 0, third_y = 0; // termination - double[] returnPoint = new double[2]; // for keeping the returned point - int numvertices = 0; // for keeping number of surrounding vertices - - // first determine which point to be used to find its neighbor triangles - switch (whichPoint) - { - case 1: - first_x = p.x; // point at the center - first_y = p.y; - second_x = r.x; // second vertex of first edge to consider - second_y = r.y; - third_x = q.x; // for terminating the search - third_y = q.y; - break; - case 2: - first_x = q.x; // point at the center - first_y = q.y; - second_x = p.x; // second vertex of first edge to consider - second_y = p.y; - third_x = r.x; // for terminating the search - third_y = r.y; - break; - case 3: - first_x = r.x; // point at the center - first_y = r.y; - second_x = q.x; // second vertex of first edge to consider - second_y = q.y; - third_x = p.x; // for terminating the search - third_y = p.y; - break; - } - tempotri = badotri; - // add first point as the end of first edge - points[numvertices] = second_x; - numvertices++; - points[numvertices] = second_y; - numvertices++; - // assign as dummy value - returnPoint[0] = second_x; returnPoint[1] = second_y; - // until we reach the third point of the beginning triangle - do - { - // find the neighbor's third point where it is incident to given edge - if (!GetNeighborsVertex(tempotri, first_x, first_y, second_x, second_y, ref returnPoint, ref neighotri)) - { - // go to next triangle - tempotri = neighotri; - // now the second point is the neighbor's third vertex - second_x = returnPoint[0]; - second_y = returnPoint[1]; - // add a new point to the list of surrounding points - points[numvertices] = returnPoint[0]; - numvertices++; - points[numvertices] = returnPoint[1]; - numvertices++; - } - else - { - numvertices = 0; - break; - } - - } while (!((Math.Abs(returnPoint[0] - third_x) <= EPS) && - (Math.Abs(returnPoint[1] - third_y) <= EPS))); - return numvertices / 2; - - } - - /// - /// Gets a neighbours vertex. - /// - /// - /// - /// - /// - /// - /// Neighbor's third vertex incident to given edge. - /// Pointer for the neighbor triangle. - /// Returns true if vertex was found. - private bool GetNeighborsVertex(Otri badotri, - double first_x, double first_y, - double second_x, double second_y, - ref double[] thirdpoint, ref Otri neighotri) - { - - Otri neighbor = default(Otri); // keeps the neighbor triangles - bool notFound = false; // boolean variable if we can find that neighbor or not - - // for keeping the vertices of the neighbor triangle - Vertex neighborvertex_1 = null; - Vertex neighborvertex_2 = null; - Vertex neighborvertex_3 = null; - - // used for finding neighbor triangle - int firstVertexMatched = 0, secondVertexMatched = 0; // to find the correct neighbor - //triangle ptr; // Temporary variable used by sym() - //int i; // index variable - // find neighbors - // Check each of the triangle's three neighbors to find the correct one - for (badotri.orient = 0; badotri.orient < 3; badotri.orient++) - { - // Find the neighbor. - badotri.Sym(ref neighbor); - // check if it is the one we are looking for by checking the corners - // first check if the neighbor is nonexistent, since it can be on the border - if (neighbor.tri.id != Mesh.DUMMY) - { - // then check if two wanted corners are also in this triangle - // take the vertices of the candidate neighbor - neighborvertex_1 = neighbor.Org(); - neighborvertex_2 = neighbor.Dest(); - neighborvertex_3 = neighbor.Apex(); - - // check if it is really a triangle - if ((neighborvertex_1.x == neighborvertex_2.x && neighborvertex_1.y == neighborvertex_2.y) - || (neighborvertex_2.x == neighborvertex_3.x && neighborvertex_2.y == neighborvertex_3.y) - || (neighborvertex_1.x == neighborvertex_3.x && neighborvertex_1.y == neighborvertex_3.y)) - { - //printf("Two vertices are the same!!!!!!!\n"); - } - else - { - // begin searching for the correct neighbor triangle - firstVertexMatched = 0; - if ((Math.Abs(first_x - neighborvertex_1.x) < EPS) && - (Math.Abs(first_y - neighborvertex_1.y) < EPS)) - { - firstVertexMatched = 11; // neighbor's 1st vertex is matched to first vertex - - } - else if ((Math.Abs(first_x - neighborvertex_2.x) < EPS) && - (Math.Abs(first_y - neighborvertex_2.y) < EPS)) - { - firstVertexMatched = 12; // neighbor's 2nd vertex is matched to first vertex - - } - else if ((Math.Abs(first_x - neighborvertex_3.x) < EPS) && - (Math.Abs(first_y - neighborvertex_3.y) < EPS)) - { - firstVertexMatched = 13; // neighbor's 3rd vertex is matched to first vertex - - }/*else{ - // none of them matched - } // end of first vertex matching */ - - secondVertexMatched = 0; - if ((Math.Abs(second_x - neighborvertex_1.x) < EPS) && - (Math.Abs(second_y - neighborvertex_1.y) < EPS)) - { - secondVertexMatched = 21; // neighbor's 1st vertex is matched to second vertex - } - else if ((Math.Abs(second_x - neighborvertex_2.x) < EPS) && - (Math.Abs(second_y - neighborvertex_2.y) < EPS)) - { - secondVertexMatched = 22; // neighbor's 2nd vertex is matched to second vertex - } - else if ((Math.Abs(second_x - neighborvertex_3.x) < EPS) && - (Math.Abs(second_y - neighborvertex_3.y) < EPS)) - { - secondVertexMatched = 23; // neighbor's 3rd vertex is matched to second vertex - }/*else{ - // none of them matched - } // end of second vertex matching*/ - - } - - }// if neighbor exists or not - - if (((firstVertexMatched == 11) && (secondVertexMatched == 22 || secondVertexMatched == 23)) - || ((firstVertexMatched == 12) && (secondVertexMatched == 21 || secondVertexMatched == 23)) - || ((firstVertexMatched == 13) && (secondVertexMatched == 21 || secondVertexMatched == 22))) - break; - }// end of for loop over all orientations - - switch (firstVertexMatched) - { - case 0: - notFound = true; - break; - case 11: - if (secondVertexMatched == 22) - { - thirdpoint[0] = neighborvertex_3.x; - thirdpoint[1] = neighborvertex_3.y; - } - else if (secondVertexMatched == 23) - { - thirdpoint[0] = neighborvertex_2.x; - thirdpoint[1] = neighborvertex_2.y; - } - else { notFound = true; } - break; - case 12: - if (secondVertexMatched == 21) - { - thirdpoint[0] = neighborvertex_3.x; - thirdpoint[1] = neighborvertex_3.y; - } - else if (secondVertexMatched == 23) - { - thirdpoint[0] = neighborvertex_1.x; - thirdpoint[1] = neighborvertex_1.y; - } - else { notFound = true; } - break; - case 13: - if (secondVertexMatched == 21) - { - thirdpoint[0] = neighborvertex_2.x; - thirdpoint[1] = neighborvertex_2.y; - } - else if (secondVertexMatched == 22) - { - thirdpoint[0] = neighborvertex_1.x; - thirdpoint[1] = neighborvertex_1.y; - } - else { notFound = true; } - break; - default: - if (secondVertexMatched == 0) { notFound = true; } - break; - } - // pointer of the neighbor triangle - neighotri = neighbor; - return notFound; - - } - - /// - /// Find a new point location by wedge intersection. - /// - /// - /// - /// A new location for the point according to surrounding points. - /// Returns true if new location found - private bool GetWedgeIntersectionWithoutMaxAngle(int numpoints, - double[] points, ref double[] newloc) - { - //double total_x = 0; - //double total_y = 0; - double x0, y0, x1, y1, x2, y2; - //double compConst = 0.01; // for comparing real numbers - - double x01, y01; - //double x12, y12; - - //double ax, ay, bx, by; //two intersections of two petals disks - - double d01;//, d12 - - //double petalx0, petaly0, petalr0, petalx1, petaly1, petalr1; - - //double p[5]; - - // Resize work arrays - if (2 * numpoints > petalx.Length) - { - petalx = new double[2 * numpoints]; - petaly = new double[2 * numpoints]; - petalr = new double[2 * numpoints]; - wedges = new double[2 * numpoints * 16 + 36]; - } - - double xmid, ymid, dist, x3, y3; - double x_1, y_1, x_2, y_2, x_3, y_3, x_4, y_4, tempx, tempy; - double ux, uy; - double alpha; - double[] p1 = new double[3]; - - //double poly_points; - int numpolypoints = 0; - - //int numBadTriangle; - - int i, j; - - int s, flag, count, num; - - double petalcenterconstant, petalradiusconstant; - - x0 = points[2 * numpoints - 4]; - y0 = points[2 * numpoints - 3]; - x1 = points[2 * numpoints - 2]; - y1 = points[2 * numpoints - 1]; - - // minimum angle - alpha = behavior.MinAngle * Math.PI / 180.0; - // initialize the constants - if (behavior.goodAngle == 1.0) - { - petalcenterconstant = 0; - petalradiusconstant = 0; - } - else - { - petalcenterconstant = 0.5 / Math.Tan(alpha); - petalradiusconstant = 0.5 / Math.Sin(alpha); - } - - for (i = 0; i < numpoints * 2; i = i + 2) - { - x2 = points[i]; - y2 = points[i + 1]; - - //printf("POLYGON POINTS (p,q) #%d (%.12f, %.12f) (%.12f, %.12f)\n", i/2, x0, y0,x1, y1); - - x01 = x1 - x0; - y01 = y1 - y0; - d01 = Math.Sqrt(x01 * x01 + y01 * y01); - // find the petal of each edge 01; - - // printf("PETAL CONSTANT (%.12f, %.12f)\n", - // b.petalcenterconstant, b.petalradiusconstant ); - // printf("PETAL DIFFS (%.6f, %.6f, %.4f)\n", x01, y01, d01); - - petalx[i / 2] = x0 + 0.5 * x01 - petalcenterconstant * y01; - petaly[i / 2] = y0 + 0.5 * y01 + petalcenterconstant * x01; - petalr[i / 2] = petalradiusconstant * d01; - petalx[numpoints + i / 2] = petalx[i / 2]; - petaly[numpoints + i / 2] = petaly[i / 2]; - petalr[numpoints + i / 2] = petalr[i / 2]; - //printf("PETAL POINTS #%d (%.12f, %.12f) R= %.12f\n", i/2, petalx[i/2],petaly[i/2], petalr[i/2]); - - /// FIRST FIND THE HALF-PLANE POINTS FOR EACH PETAL - xmid = (x0 + x1) / 2.0; // mid point of pq - ymid = (y0 + y1) / 2.0; - - // distance between xmid and petal center - dist = Math.Sqrt((petalx[i / 2] - xmid) * (petalx[i / 2] - xmid) + (petaly[i / 2] - ymid) * (petaly[i / 2] - ymid)); - // find the unit vector goes from mid point to petal center - ux = (petalx[i / 2] - xmid) / dist; - uy = (petaly[i / 2] - ymid) / dist; - // find the third point other than p and q - x3 = petalx[i / 2] + ux * petalr[i / 2]; - y3 = petaly[i / 2] + uy * petalr[i / 2]; - /// FIND THE LINE POINTS BY THE ROTATION MATRIX - // cw rotation matrix [cosX sinX; -sinX cosX] - // cw rotation about (x,y) [ux*cosX + uy*sinX + x - x*cosX - y*sinX; -ux*sinX + uy*cosX + y + x*sinX - y*cosX] - // ccw rotation matrix [cosX -sinX; sinX cosX] - // ccw rotation about (x,y) [ux*cosX - uy*sinX + x - x*cosX + y*sinX; ux*sinX + uy*cosX + y - x*sinX - y*cosX] - /// LINE #1: (x1,y1) & (x_1,y_1) - // vector from p to q - ux = x1 - x0; - uy = y1 - y0; - // rotate the vector around p = (x0,y0) in ccw by alpha degrees - x_1 = x1 * Math.Cos(alpha) - y1 * Math.Sin(alpha) + x0 - x0 * Math.Cos(alpha) + y0 * Math.Sin(alpha); - y_1 = x1 * Math.Sin(alpha) + y1 * Math.Cos(alpha) + y0 - x0 * Math.Sin(alpha) - y0 * Math.Cos(alpha); - // add these to wedges list as lines in order - wedges[i * 16] = x0; wedges[i * 16 + 1] = y0; - wedges[i * 16 + 2] = x_1; wedges[i * 16 + 3] = y_1; - //printf("LINE #1 (%.12f, %.12f) (%.12f, %.12f)\n", x0,y0,x_1,y_1); - /// LINE #2: (x2,y2) & (x_2,y_2) - // vector from p to q - ux = x0 - x1; - uy = y0 - y1; - // rotate the vector around q = (x1,y1) in cw by alpha degrees - x_2 = x0 * Math.Cos(alpha) + y0 * Math.Sin(alpha) + x1 - x1 * Math.Cos(alpha) - y1 * Math.Sin(alpha); - y_2 = -x0 * Math.Sin(alpha) + y0 * Math.Cos(alpha) + y1 + x1 * Math.Sin(alpha) - y1 * Math.Cos(alpha); - // add these to wedges list as lines in order - wedges[i * 16 + 4] = x_2; wedges[i * 16 + 5] = y_2; - wedges[i * 16 + 6] = x1; wedges[i * 16 + 7] = y1; - //printf("LINE #2 (%.12f, %.12f) (%.12f, %.12f)\n", x_2,y_2,x1,y1); - // vector from (petalx, petaly) to (x3,y3) - ux = x3 - petalx[i / 2]; - uy = y3 - petaly[i / 2]; - tempx = x3; tempy = y3; - /// LINE #3, #4, #5: (x3,y3) & (x_3,y_3) - for (j = 1; j < 4; j++) - { - // rotate the vector around (petalx,petaly) in cw by (60 - alpha)*j degrees - x_3 = x3 * Math.Cos((Math.PI / 3.0 - alpha) * j) + y3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + petalx[i / 2] - petalx[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j) - petaly[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j); - y_3 = -x3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + y3 * Math.Cos((Math.PI / 3.0 - alpha) * j) + petaly[i / 2] + petalx[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j) - petaly[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j); - // add these to wedges list as lines in order - wedges[i * 16 + 8 + 4 * (j - 1)] = x_3; wedges[i * 16 + 9 + 4 * (j - 1)] = y_3; - wedges[i * 16 + 10 + 4 * (j - 1)] = tempx; wedges[i * 16 + 11 + 4 * (j - 1)] = tempy; - tempx = x_3; tempy = y_3; - } - tempx = x3; tempy = y3; - /// LINE #6, #7, #8: (x3,y3) & (x_4,y_4) - for (j = 1; j < 4; j++) - { - // rotate the vector around (petalx,petaly) in ccw by (60 - alpha)*j degrees - x_4 = x3 * Math.Cos((Math.PI / 3.0 - alpha) * j) - y3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + petalx[i / 2] - petalx[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j) + petaly[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j); - y_4 = x3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + y3 * Math.Cos((Math.PI / 3.0 - alpha) * j) + petaly[i / 2] - petalx[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j) - petaly[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j); - - // add these to wedges list as lines in order - wedges[i * 16 + 20 + 4 * (j - 1)] = tempx; wedges[i * 16 + 21 + 4 * (j - 1)] = tempy; - wedges[i * 16 + 22 + 4 * (j - 1)] = x_4; wedges[i * 16 + 23 + 4 * (j - 1)] = y_4; - tempx = x_4; tempy = y_4; - } - //printf("LINE #3 (%.12f, %.12f) (%.12f, %.12f)\n", x_3,y_3,x3,y3); - //printf("LINE #4 (%.12f, %.12f) (%.12f, %.12f)\n", x3,y3,x_4,y_4); - - /// IF IT IS THE FIRST ONE, FIND THE CONVEX POLYGON - if (i == 0) - { - // line1 & line2: p1 - LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); - if ((p1[0] == 1.0)) - { - // #0 - initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; - // #1 - initialConvexPoly[2] = wedges[i * 16 + 16]; initialConvexPoly[3] = wedges[i * 16 + 17]; - // #2 - initialConvexPoly[4] = wedges[i * 16 + 12]; initialConvexPoly[5] = wedges[i * 16 + 13]; - // #3 - initialConvexPoly[6] = wedges[i * 16 + 8]; initialConvexPoly[7] = wedges[i * 16 + 9]; - // #4 - initialConvexPoly[8] = x3; initialConvexPoly[9] = y3; - // #5 - initialConvexPoly[10] = wedges[i * 16 + 22]; initialConvexPoly[11] = wedges[i * 16 + 23]; - // #6 - initialConvexPoly[12] = wedges[i * 16 + 26]; initialConvexPoly[13] = wedges[i * 16 + 27]; - // #7 - initialConvexPoly[14] = wedges[i * 16 + 30]; initialConvexPoly[15] = wedges[i * 16 + 31]; - //printf("INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f]\n", initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15]); - } - } - - x0 = x1; y0 = y1; - x1 = x2; y1 = y2; - } - - /// HALF PLANE INTERSECTION: START SPLITTING THE INITIAL POLYGON TO FIND FEASIBLE REGION - if (numpoints != 0) - { - // first intersect the opposite located ones - s = (numpoints - 1) / 2 + 1; - flag = 0; - count = 0; - i = 1; - num = 8; - for (j = 0; j < 32; j = j + 4) - { - numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[32 * s + j], wedges[32 * s + 1 + j], wedges[32 * s + 2 + j], wedges[32 * s + 3 + j]); - if (numpolypoints == 0) - return false; - else - num = numpolypoints; - } - count++; - while (count < numpoints - 1) - { - for (j = 0; j < 32; j = j + 4) - { - numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[32 * (i + s * flag) + j], wedges[32 * (i + s * flag) + 1 + j], wedges[32 * (i + s * flag) + 2 + j], wedges[32 * (i + s * flag) + 3 + j]); - if (numpolypoints == 0) - return false; - else - num = numpolypoints; - } - i = i + flag; - flag = (flag + 1) % 2; - count++; - } - /// IF THERE IS A FEASIBLE INTERSECTION POLYGON, FIND ITS CENTROID AS THE NEW LOCATION - FindPolyCentroid(numpolypoints, initialConvexPoly, ref newloc); - - if (behavior.fixedArea) - { - // numBadTriangle = 0; - // for(j= 0; j < numpoints *2-2; j = j+2){ - // if(testTriangleAngleArea(m,b,&newloc[0],&newloc[1], &points[j], &points[j+1], &points[j+2], &points[j+3] )){ - // numBadTriangle++; - // } - // } - // if(testTriangleAngleArea(m,b, &newloc[0],&newloc[1], &points[0], &points[1], &points[numpoints*2-2], &points[numpoints*2-1] )){ - // numBadTriangle++; - // } - // - // if (numBadTriangle == 0) { - // - // return 1; - // } - } - else - { - //printf("yes, we found a feasible region num: %d newloc (%.12f,%.12f)\n", numpolypoints, newloc[0], newloc[1]); - // for(i = 0; i < 2*numpolypoints; i = i+2){ - // printf("point %d) (%.12f,%.12f)\n", i/2, initialConvexPoly[i], initialConvexPoly[i+1]); - // } - // printf("numpoints %d\n",numpoints); - return true; - } - } - - - return false; - } - - /// - /// Find a new point location by wedge intersection. - /// - /// - /// - /// A new location for the point according to surrounding points. - /// Returns true if new location found - private bool GetWedgeIntersection(int numpoints, double[] points, ref double[] newloc) - { - //double total_x = 0; - //double total_y = 0; - double x0, y0, x1, y1, x2, y2; - //double compConst = 0.01; // for comparing real numbers - - double x01, y01; - //double x12, y12; - - //double ax, ay, bx, by; //two intersections of two petals disks - - double d01;//, d12 - - //double petalx0, petaly1, petaly0, petalr0, petalx1, petalr1; - - //double p[5]; - - // Resize work arrays - if (2 * numpoints > petalx.Length) - { - petalx = new double[2 * numpoints]; - petaly = new double[2 * numpoints]; - petalr = new double[2 * numpoints]; - wedges = new double[2 * numpoints * 20 + 40]; - } - - double xmid, ymid, dist, x3, y3; - double x_1, y_1, x_2, y_2, x_3, y_3, x_4, y_4, tempx, tempy, x_5, y_5, x_6, y_6; - double ux, uy; - - double[] p1 = new double[3]; - double[] p2 = new double[3]; - double[] p3 = new double[3]; - double[] p4 = new double[3]; - - //double poly_points; - int numpolypoints = 0; - int howManyPoints = 0; // keeps the number of points used for representing the wedge - double line345 = 4.0, line789 = 4.0; // flag keeping which line to skip or construct - - int numBadTriangle; - - int i, j, k; - - int s, flag, count, num; - - int n, e; - - double weight; - - double petalcenterconstant, petalradiusconstant; - - x0 = points[2 * numpoints - 4]; - y0 = points[2 * numpoints - 3]; - x1 = points[2 * numpoints - 2]; - y1 = points[2 * numpoints - 1]; - - // minimum / maximum angle - double alpha, sinAlpha, cosAlpha, beta, sinBeta, cosBeta; - alpha = behavior.MinAngle * Math.PI / 180.0; - sinAlpha = Math.Sin(alpha); - cosAlpha = Math.Cos(alpha); - beta = behavior.MaxAngle * Math.PI / 180.0; - sinBeta = Math.Sin(beta); - cosBeta = Math.Cos(beta); - - // initialize the constants - if (behavior.goodAngle == 1.0) - { - petalcenterconstant = 0; - petalradiusconstant = 0; - } - else - { - petalcenterconstant = 0.5 / Math.Tan(alpha); - petalradiusconstant = 0.5 / Math.Sin(alpha); - } - - for (i = 0; i < numpoints * 2; i = i + 2) - { - // go to the next point - x2 = points[i]; - y2 = points[i + 1]; - - // printf("POLYGON POINTS (p,q) #%d (%.12f, %.12f) (%.12f, %.12f)\n", i/2, x0, y0,x1, y1); - - x01 = x1 - x0; - y01 = y1 - y0; - d01 = Math.Sqrt(x01 * x01 + y01 * y01); - // find the petal of each edge 01; - - // printf("PETAL CONSTANT (%.12f, %.12f)\n", - // b.petalcenterconstant, b.petalradiusconstant ); - // printf("PETAL DIFFS (%.6f, %.6f, %.4f)\n", x01, y01, d01); - //printf("i:%d numpoints:%d\n", i, numpoints); - petalx[i / 2] = x0 + 0.5 * x01 - petalcenterconstant * y01; - petaly[i / 2] = y0 + 0.5 * y01 + petalcenterconstant * x01; - petalr[i / 2] = petalradiusconstant * d01; - petalx[numpoints + i / 2] = petalx[i / 2]; - petaly[numpoints + i / 2] = petaly[i / 2]; - petalr[numpoints + i / 2] = petalr[i / 2]; - //printf("PETAL POINTS #%d (%.12f, %.12f) R= %.12f\n", i/2, petalx[i/2],petaly[i/2], petalr[i/2]); - - /// FIRST FIND THE HALF-PLANE POINTS FOR EACH PETAL - xmid = (x0 + x1) / 2.0; // mid point of pq - ymid = (y0 + y1) / 2.0; - - // distance between xmid and petal center - dist = Math.Sqrt((petalx[i / 2] - xmid) * (petalx[i / 2] - xmid) + (petaly[i / 2] - ymid) * (petaly[i / 2] - ymid)); - // find the unit vector goes from mid point to petal center - ux = (petalx[i / 2] - xmid) / dist; - uy = (petaly[i / 2] - ymid) / dist; - // find the third point other than p and q - x3 = petalx[i / 2] + ux * petalr[i / 2]; - y3 = petaly[i / 2] + uy * petalr[i / 2]; - /// FIND THE LINE POINTS BY THE ROTATION MATRIX - // cw rotation matrix [cosX sinX; -sinX cosX] - // cw rotation about (x,y) [ux*cosX + uy*sinX + x - x*cosX - y*sinX; -ux*sinX + uy*cosX + y + x*sinX - y*cosX] - // ccw rotation matrix [cosX -sinX; sinX cosX] - // ccw rotation about (x,y) [ux*cosX - uy*sinX + x - x*cosX + y*sinX; ux*sinX + uy*cosX + y - x*sinX - y*cosX] - /// LINE #1: (x1,y1) & (x_1,y_1) - // vector from p to q - ux = x1 - x0; - uy = y1 - y0; - // rotate the vector around p = (x0,y0) in ccw by alpha degrees - x_1 = x1 * cosAlpha - y1 * sinAlpha + x0 - x0 * cosAlpha + y0 * sinAlpha; - y_1 = x1 * sinAlpha + y1 * cosAlpha + y0 - x0 * sinAlpha - y0 * cosAlpha; - // add these to wedges list as lines in order - wedges[i * 20] = x0; wedges[i * 20 + 1] = y0; - wedges[i * 20 + 2] = x_1; wedges[i * 20 + 3] = y_1; - //printf("LINE #1 (%.12f, %.12f) (%.12f, %.12f)\n", x0,y0,x_1,y_1); - /// LINE #2: (x2,y2) & (x_2,y_2) - // vector from q to p - ux = x0 - x1; - uy = y0 - y1; - // rotate the vector around q = (x1,y1) in cw by alpha degrees - x_2 = x0 * cosAlpha + y0 * sinAlpha + x1 - x1 * cosAlpha - y1 * sinAlpha; - y_2 = -x0 * sinAlpha + y0 * cosAlpha + y1 + x1 * sinAlpha - y1 * cosAlpha; - // add these to wedges list as lines in order - wedges[i * 20 + 4] = x_2; wedges[i * 20 + 5] = y_2; - wedges[i * 20 + 6] = x1; wedges[i * 20 + 7] = y1; - //printf("LINE #2 (%.12f, %.12f) (%.12f, %.12f)\n", x_2,y_2,x1,y1); - // vector from (petalx, petaly) to (x3,y3) - ux = x3 - petalx[i / 2]; - uy = y3 - petaly[i / 2]; - tempx = x3; tempy = y3; - - /// DETERMINE HOW MANY POINTS TO USE ACCORDING TO THE MINANGLE-MAXANGLE COMBINATION - // petal center angle - alpha = (2.0 * behavior.MaxAngle + behavior.MinAngle - 180.0); - if (alpha <= 0.0) - {// when only angle lines needed - // 4 point case - howManyPoints = 4; - //printf("4 point case\n"); - line345 = 1.0; - line789 = 1.0; - } - else if (alpha <= 5.0) - {// when only angle lines plus two other lines are needed - // 6 point case - howManyPoints = 6; - //printf("6 point case\n"); - line345 = 2.0; - line789 = 2.0; - } - else if (alpha <= 10.0) - {// when we need more lines - // 8 point case - howManyPoints = 8; - line345 = 3.0; - line789 = 3.0; - //printf("8 point case\n"); - } - else - {// when we have a big wedge - // 10 point case - howManyPoints = 10; - //printf("10 point case\n"); - line345 = 4.0; - line789 = 4.0; - } - alpha = alpha * Math.PI / 180.0; - /// LINE #3, #4, #5: (x3,y3) & (x_3,y_3) - for (j = 1; j < line345; j++) - { - if (line345 == 1) - continue; - // rotate the vector around (petalx,petaly) in cw by (alpha/3.0)*j degrees - x_3 = x3 * Math.Cos((alpha / (line345 - 1.0)) * j) + y3 * Math.Sin(((alpha / (line345 - 1.0)) * j)) + petalx[i / 2] - petalx[i / 2] * Math.Cos(((alpha / (line345 - 1.0)) * j)) - petaly[i / 2] * Math.Sin(((alpha / (line345 - 1.0)) * j)); - y_3 = -x3 * Math.Sin(((alpha / (line345 - 1.0)) * j)) + y3 * Math.Cos(((alpha / (line345 - 1.0)) * j)) + petaly[i / 2] + petalx[i / 2] * Math.Sin(((alpha / (line345 - 1.0)) * j)) - petaly[i / 2] * Math.Cos(((alpha / (line345 - 1.0)) * j)); - // add these to wedges list as lines in order - wedges[i * 20 + 8 + 4 * (j - 1)] = x_3; wedges[i * 20 + 9 + 4 * (j - 1)] = y_3; - wedges[i * 20 + 10 + 4 * (j - 1)] = tempx; wedges[i * 20 + 11 + 4 * (j - 1)] = tempy; - tempx = x_3; tempy = y_3; - } - /// LINE #6: (x2,y2) & (x_3,y_3) - // vector from q to p - ux = x0 - x1; - uy = y0 - y1; - // rotate the vector around q = (x1,y1) in cw by alpha degrees - x_5 = x0 * cosBeta + y0 * sinBeta + x1 - x1 * cosBeta - y1 * sinBeta; - y_5 = -x0 * sinBeta + y0 * cosBeta + y1 + x1 * sinBeta - y1 * cosBeta; - wedges[i * 20 + 20] = x1; wedges[i * 20 + 21] = y1; - wedges[i * 20 + 22] = x_5; wedges[i * 20 + 23] = y_5; - - tempx = x3; tempy = y3; - /// LINE #7, #8, #9: (x3,y3) & (x_4,y_4) - for (j = 1; j < line789; j++) - { - if (line789 == 1) - continue; - // rotate the vector around (petalx,petaly) in ccw by (alpha/3.0)*j degrees - x_4 = x3 * Math.Cos((alpha / (line789 - 1.0)) * j) - y3 * Math.Sin((alpha / (line789 - 1.0)) * j) + petalx[i / 2] - petalx[i / 2] * Math.Cos((alpha / (line789 - 1.0)) * j) + petaly[i / 2] * Math.Sin((alpha / (line789 - 1.0)) * j); - y_4 = x3 * Math.Sin((alpha / (line789 - 1.0)) * j) + y3 * Math.Cos((alpha / (line789 - 1.0)) * j) + petaly[i / 2] - petalx[i / 2] * Math.Sin((alpha / (line789 - 1.0)) * j) - petaly[i / 2] * Math.Cos((alpha / (line789 - 1.0)) * j); - - // add these to wedges list as lines in order - wedges[i * 20 + 24 + 4 * (j - 1)] = tempx; wedges[i * 20 + 25 + 4 * (j - 1)] = tempy; - wedges[i * 20 + 26 + 4 * (j - 1)] = x_4; wedges[i * 20 + 27 + 4 * (j - 1)] = y_4; - tempx = x_4; tempy = y_4; - } - /// LINE #10: (x1,y1) & (x_3,y_3) - // vector from p to q - ux = x1 - x0; - uy = y1 - y0; - // rotate the vector around p = (x0,y0) in ccw by alpha degrees - x_6 = x1 * cosBeta - y1 * sinBeta + x0 - x0 * cosBeta + y0 * sinBeta; - y_6 = x1 * sinBeta + y1 * cosBeta + y0 - x0 * sinBeta - y0 * cosBeta; - wedges[i * 20 + 36] = x_6; wedges[i * 20 + 37] = y_6; - wedges[i * 20 + 38] = x0; wedges[i * 20 + 39] = y0; - - //printf("LINE #1 (%.12f, %.12f) (%.12f, %.12f)\n", x0,y0,x_1,y_1); - /// IF IT IS THE FIRST ONE, FIND THE CONVEX POLYGON - if (i == 0) - { - switch (howManyPoints) - { - case 4: - // line1 & line2 & line3 & line4 - LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); - LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2); - LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_5, y_5, ref p3); - LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p4); - if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0) && (p4[0] == 1.0)) - { - // #0 - initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; - // #1 - initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2]; - // #2 - initialConvexPoly[4] = p3[1]; initialConvexPoly[5] = p3[2]; - // #3 - initialConvexPoly[6] = p4[1]; initialConvexPoly[7] = p4[2]; - } - break; - case 6: - // line1 & line2 & line3 - LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); - LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2); - LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p3); - if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0)) - { - // #0 - initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; - // #1 - initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2]; - // #2 - initialConvexPoly[4] = wedges[i * 20 + 8]; initialConvexPoly[5] = wedges[i * 20 + 9]; - // #3 - initialConvexPoly[6] = x3; initialConvexPoly[7] = y3; - // #4 - initialConvexPoly[8] = wedges[i * 20 + 26]; initialConvexPoly[9] = wedges[i * 20 + 27]; - // #5 - initialConvexPoly[10] = p3[1]; initialConvexPoly[11] = p3[2]; - } - break; - case 8: - // line1 & line2: p1 - LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); - LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2); - LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p3); - if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0)) - { - // #0 - initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; - // #1 - initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2]; - // #2 - initialConvexPoly[4] = wedges[i * 20 + 12]; initialConvexPoly[5] = wedges[i * 20 + 13]; - // #3 - initialConvexPoly[6] = wedges[i * 20 + 8]; initialConvexPoly[7] = wedges[i * 20 + 9]; - // #4 - initialConvexPoly[8] = x3; initialConvexPoly[9] = y3; - // #5 - initialConvexPoly[10] = wedges[i * 20 + 26]; initialConvexPoly[11] = wedges[i * 20 + 27]; - // #6 - initialConvexPoly[12] = wedges[i * 20 + 30]; initialConvexPoly[13] = wedges[i * 20 + 31]; - // #7 - initialConvexPoly[14] = p3[1]; initialConvexPoly[15] = p3[2]; - } - break; - case 10: - // line1 & line2: p1 - LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); - LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2); - LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p3); - //printf("p3 %f %f %f (%f %f) (%f %f) (%f %f) (%f %f)\n",p3[0],p3[1],p3[2], x0, y0, x_6, x_6, x1, y1, x_2, y_2); - if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0)) - { - // #0 - initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; - // #1 - initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2]; - // #2 - initialConvexPoly[4] = wedges[i * 20 + 16]; initialConvexPoly[5] = wedges[i * 20 + 17]; - // #3 - initialConvexPoly[6] = wedges[i * 20 + 12]; initialConvexPoly[7] = wedges[i * 20 + 13]; - // #4 - initialConvexPoly[8] = wedges[i * 20 + 8]; initialConvexPoly[9] = wedges[i * 20 + 9]; - // #5 - initialConvexPoly[10] = x3; initialConvexPoly[11] = y3; - // #6 - initialConvexPoly[12] = wedges[i * 20 + 28]; initialConvexPoly[13] = wedges[i * 20 + 29]; - // #7 - initialConvexPoly[14] = wedges[i * 20 + 32]; initialConvexPoly[15] = wedges[i * 20 + 33]; - // #8 - initialConvexPoly[16] = wedges[i * 20 + 34]; initialConvexPoly[17] = wedges[i * 20 + 35]; - // #9 - initialConvexPoly[18] = p3[1]; initialConvexPoly[19] = p3[2]; - } - break; - } - // printf("smallest edge (%f,%f) (%f,%f)\n", x0,y0, x1,y1); - // printf("real INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;]\n", initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15],initialConvexPoly[16],initialConvexPoly[17],initialConvexPoly[18],initialConvexPoly[19]); - } - - x0 = x1; y0 = y1; - x1 = x2; y1 = y2; - } - /// HALF PLANE INTERSECTION: START SPLITTING THE INITIAL POLYGON TO FIND FEASIBLE REGION - if (numpoints != 0) - { - // first intersect the opposite located ones - s = (numpoints - 1) / 2 + 1; - flag = 0; - count = 0; - i = 1; - num = howManyPoints; - for (j = 0; j < 40; j = j + 4) - { - // in order to skip non-existent lines - if (howManyPoints == 4 && (j == 8 || j == 12 || j == 16 || j == 24 || j == 28 || j == 32)) - { - continue; - } - else if (howManyPoints == 6 && (j == 12 || j == 16 || j == 28 || j == 32)) - { - continue; - } - else if (howManyPoints == 8 && (j == 16 || j == 32)) - { - continue; - } - // printf("%d 1 INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;]\n",num, initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15],initialConvexPoly[16],initialConvexPoly[17],initialConvexPoly[18],initialConvexPoly[19]); - // printf("line (%f, %f) (%f, %f)\n",wedges[40*s+j],wedges[40*s+1+j], wedges[40*s+2+j], wedges[40*s+3+j]); - numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[40 * s + j], wedges[40 * s + 1 + j], wedges[40 * s + 2 + j], wedges[40 * s + 3 + j]); - - if (numpolypoints == 0) - return false; - else - num = numpolypoints; - } - count++; - //printf("yes here\n"); - while (count < numpoints - 1) - { - for (j = 0; j < 40; j = j + 4) - { - // in order to skip non-existent lines - if (howManyPoints == 4 && (j == 8 || j == 12 || j == 16 || j == 24 || j == 28 || j == 32)) - { - continue; - } - else if (howManyPoints == 6 && (j == 12 || j == 16 || j == 28 || j == 32)) - { - continue; - } - else if (howManyPoints == 8 && (j == 16 || j == 32)) - { - continue; - } - ////printf("%d 2 INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;]\n",numpolypoints, initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15],initialConvexPoly[16],initialConvexPoly[17],initialConvexPoly[18],initialConvexPoly[19]); - //printf("line (%.20f, %.20f) (%.20f, %.20f)\n", wedges[40 * (i + s * flag) + j], wedges[40 * (i + s * flag) + 1 + j], wedges[40 * (i + s * flag) + 2 + j], wedges[40 * (i + s * flag) + 3 + j]); - numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[40 * (i + s * flag) + j], wedges[40 * (i + s * flag) + 1 + j], wedges[40 * (i + s * flag) + 2 + j], wedges[40 * (i + s * flag) + 3 + j]); - - if (numpolypoints == 0) - return false; - else - num = numpolypoints; - } - i = i + flag; - flag = (flag + 1) % 2; - count++; - } - /// IF THERE IS A FEASIBLE INTERSECTION POLYGON, FIND ITS CENTROID AS THE NEW LOCATION - FindPolyCentroid(numpolypoints, initialConvexPoly, ref newloc); - - if (behavior.MaxAngle != 0.0) - { - numBadTriangle = 0; - for (j = 0; j < numpoints * 2 - 2; j = j + 2) - { - if (IsBadTriangleAngle(newloc[0], newloc[1], points[j], points[j + 1], points[j + 2], points[j + 3])) - { - numBadTriangle++; - } - } - if (IsBadTriangleAngle(newloc[0], newloc[1], points[0], points[1], points[numpoints * 2 - 2], points[numpoints * 2 - 1])) - { - numBadTriangle++; - } - - if (numBadTriangle == 0) - { - - return true; - } - n = (numpoints <= 2) ? 20 : 30; - // try points other than centroid - for (k = 0; k < 2 * numpoints; k = k + 2) - { - for (e = 1; e < n; e = e + 1) - { - newloc[0] = 0.0; newloc[1] = 0.0; - for (i = 0; i < 2 * numpoints; i = i + 2) - { - weight = 1.0 / numpoints; - if (i == k) - { - newloc[0] = newloc[0] + 0.1 * e * weight * points[i]; - newloc[1] = newloc[1] + 0.1 * e * weight * points[i + 1]; - } - else - { - weight = (1.0 - 0.1 * e * weight) / (double)(numpoints - 1.0); - newloc[0] = newloc[0] + weight * points[i]; - newloc[1] = newloc[1] + weight * points[i + 1]; - } - - } - numBadTriangle = 0; - for (j = 0; j < numpoints * 2 - 2; j = j + 2) - { - if (IsBadTriangleAngle(newloc[0], newloc[1], points[j], points[j + 1], points[j + 2], points[j + 3])) - { - numBadTriangle++; - } - } - if (IsBadTriangleAngle(newloc[0], newloc[1], points[0], points[1], points[numpoints * 2 - 2], points[numpoints * 2 - 1])) - { - numBadTriangle++; - } - - if (numBadTriangle == 0) - { - - return true; - } - } - } - } - else - { - //printf("yes, we found a feasible region num: %d newloc (%.12f,%.12f)\n", numpolypoints, newloc[0], newloc[1]); - // for(i = 0; i < 2*numpolypoints; i = i+2){ - // printf("point %d) (%.12f,%.12f)\n", i/2, initialConvexPoly[i], initialConvexPoly[i+1]); - // } - // printf("numpoints %d\n",numpoints); - return true; - } - } - - - return false; - } - - /// - /// Check polygon for min angle. - /// - /// - /// - /// Returns true if the polygon has angles greater than 2*minangle. - private bool ValidPolygonAngles(int numpoints, double[] points) - { - int i;//,j - for (i = 0; i < numpoints; i++) - { - if (i == numpoints - 1) - { - if (IsBadPolygonAngle(points[i * 2], points[i * 2 + 1], points[0], points[1], points[2], points[3])) - { - return false; // one of the inner angles is less than required - } - } - else if (i == numpoints - 2) - { - if (IsBadPolygonAngle(points[i * 2], points[i * 2 + 1], points[(i + 1) * 2], points[(i + 1) * 2 + 1], points[0], points[1])) - { - return false; // one of the inner angles is less than required - } - } - else - { - if (IsBadPolygonAngle(points[i * 2], points[i * 2 + 1], points[(i + 1) * 2], points[(i + 1) * 2 + 1], points[(i + 2) * 2], points[(i + 2) * 2 + 1])) - { - return false; // one of the inner angles is less than required - } - } - } - return true; // all angles are valid - } - - /// - /// Given three coordinates of a polygon, tests to see if it satisfies the minimum - /// angle condition for relocation. - /// - /// - /// - /// - /// - /// - /// - /// Returns true, if it is a BAD polygon corner, returns false if it is a GOOD - /// polygon corner - private bool IsBadPolygonAngle(double x1, double y1, - double x2, double y2, double x3, double y3) - { - // variables keeping the distance values for the edges - double dx12, dy12, dx23, dy23, dx31, dy31; - double dist12, dist23, dist31; - - double cosAngle; // in order to check minimum angle condition - - // calculate the side lengths - - dx12 = x1 - x2; - dy12 = y1 - y2; - dx23 = x2 - x3; - dy23 = y2 - y3; - dx31 = x3 - x1; - dy31 = y3 - y1; - // calculate the squares of the side lentghs - dist12 = dx12 * dx12 + dy12 * dy12; - dist23 = dx23 * dx23 + dy23 * dy23; - dist31 = dx31 * dx31 + dy31 * dy31; - - /// calculate cosine of largest angle /// - cosAngle = (dist12 + dist23 - dist31) / (2 * Math.Sqrt(dist12) * Math.Sqrt(dist23)); - // Check whether the angle is smaller than permitted which is 2*minangle!!! - //printf("angle: %f 2*minangle = %f\n",acos(cosAngle)*180/PI, 2*acos(Math.Sqrt(b.goodangle))*180/PI); - if (Math.Acos(cosAngle) < 2 * Math.Acos(Math.Sqrt(behavior.goodAngle))) - { - return true;// it is a BAD triangle - } - return false;// it is a GOOD triangle - - } - - /// - /// Given four points representing two lines, returns the intersection point. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// The intersection point. - /// - // referenced to: http://local.wasp.uwa.edu.au/~pbourke/geometry/ - /// - private void LineLineIntersection( - double x1, double y1, - double x2, double y2, - double x3, double y3, - double x4, double y4, ref double[] p) - { - // x1,y1 P1 coordinates (point of line 1) - // x2,y2 P2 coordinates (point of line 1) - // x3,y3 P3 coordinates (point of line 2) - // x4,y4 P4 coordinates (point of line 2) - // p[1],p[2] intersection coordinates - // - // This function returns a pointer array which first index indicates - // weather they intersect on one point or not, followed by coordinate pairs. - - double u_a, u_b, denom; - - // calculate denominator first - denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); - u_a = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3); - u_b = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3); - // if denominator and numerator equal to zero, lines are coincident - if (Math.Abs(denom - 0.0) < EPS && (Math.Abs(u_b - 0.0) < EPS && Math.Abs(u_a - 0.0) < EPS)) - { - p[0] = 0.0; - } - // if denominator equals to zero, lines are parallel - else if (Math.Abs(denom - 0.0) < EPS) - { - p[0] = 0.0; - } - else - { - p[0] = 1.0; - u_a = u_a / denom; - u_b = u_b / denom; - p[1] = x1 + u_a * (x2 - x1); // not the intersection point - p[2] = y1 + u_a * (y2 - y1); - } - } - - /// - /// Returns the convex polygon which is the intersection of the given convex - /// polygon with the halfplane on the left side (regarding the directional vector) - /// of the given line. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// http://www.mathematik.uni-ulm.de/stochastik/lehre/ws03_04/rt/Geometry2D.ps - /// - private int HalfPlaneIntersection(int numvertices, ref double[] convexPoly, double x1, double y1, double x2, double y2) - { - double dx, dy; // direction of the line - double z, min, max; - int i, j; - - int numpolys; - double[] res = null; - int count = 0; - int intFound = 0; - dx = x2 - x1; - dy = y2 - y1; - numpolys = SplitConvexPolygon(numvertices, convexPoly, x1, y1, x2, y2, polys); - - if (numpolys == 3) - { - count = numvertices; - } - else - { - for (i = 0; i < numpolys; i++) - { - min = double.MaxValue; - max = double.MinValue; - // compute the minimum and maximum of the - // third coordinate of the cross product - for (j = 1; j <= 2 * polys[i][0] - 1; j = j + 2) - { - z = dx * (polys[i][j + 1] - y1) - dy * (polys[i][j] - x1); - min = (z < min ? z : min); - max = (z > max ? z : max); - } - // ... and choose the (absolute) greater of both - z = (Math.Abs(min) > Math.Abs(max) ? min : max); - // and if it is positive, the polygon polys[i] - // is on the left side of line - if (z > 0.0) - { - res = polys[i]; - intFound = 1; - break; - } - } - if (intFound == 1) - { - while (count < res[0]) - { - convexPoly[2 * count] = res[2 * count + 1]; - convexPoly[2 * count + 1] = res[2 * count + 2]; - count++; - - } - } - } - // update convexPoly - return count; - } - - /// - /// Splits a convex polygons into one or two polygons through the intersection - /// with the given line (regarding the directional vector of the given line). - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// http://www.mathematik.uni-ulm.de/stochastik/lehre/ws03_04/rt/Geometry2D.ps - /// - private int SplitConvexPolygon(int numvertices, double[] convexPoly, double x1, double y1, double x2, double y2, double[][] polys) - { - // state = 0: before the first intersection (with the line) - // state = 1: after the first intersection (with the line) - // state = 2: after the second intersection (with the line) - - int state = 0; - double[] p = new double[3]; - int poly1counter = 0; - int poly2counter = 0; - int numpolys; - int i; - double compConst = 0.000000000001; - // for debugging - int case1 = 0, case2 = 0, case3 = 0, case31 = 0, case32 = 0, case33 = 0, case311 = 0, case3111 = 0; - // intersect all edges of poly with line - for (i = 0; i < 2 * numvertices; i = i + 2) - { - int j = (i + 2 >= 2 * numvertices) ? 0 : i + 2; - LineLineSegmentIntersection(x1, y1, x2, y2, convexPoly[i], convexPoly[i + 1], convexPoly[j], convexPoly[j + 1], ref p); - // if this edge does not intersect with line - if (Math.Abs(p[0] - 0.0) <= compConst) - { - //System.out.println("null"); - // add p[j] to the proper polygon - if (state == 1) - { - poly2counter++; - poly2[2 * poly2counter - 1] = convexPoly[j]; - poly2[2 * poly2counter] = convexPoly[j + 1]; - } - else - { - poly1counter++; - poly1[2 * poly1counter - 1] = convexPoly[j]; - poly1[2 * poly1counter] = convexPoly[j + 1]; - } - // debug - case1++; - } - // ... or if the intersection is the whole edge - else if (Math.Abs(p[0] - 2.0) <= compConst) - { - //System.out.println(o); - // then we can not reach state 1 and 2 - poly1counter++; - poly1[2 * poly1counter - 1] = convexPoly[j]; - poly1[2 * poly1counter] = convexPoly[j + 1]; - // debug - case2++; - } - // ... or if the intersection is a point - else - { - // debug - case3++; - // if the point is the second vertex of the edge - if (Math.Abs(p[1] - convexPoly[j]) <= compConst && Math.Abs(p[2] - convexPoly[j + 1]) <= compConst) - { - // debug - case31++; - if (state == 1) - { - poly2counter++; - poly2[2 * poly2counter - 1] = convexPoly[j]; - poly2[2 * poly2counter] = convexPoly[j + 1]; - poly1counter++; - poly1[2 * poly1counter - 1] = convexPoly[j]; - poly1[2 * poly1counter] = convexPoly[j + 1]; - state++; - } - else if (state == 0) - { - // debug - case311++; - poly1counter++; - poly1[2 * poly1counter - 1] = convexPoly[j]; - poly1[2 * poly1counter] = convexPoly[j + 1]; - // test whether the polygon is splitted - // or the line only touches the polygon - if (i + 4 < 2 * numvertices) - { - int s1 = LinePointLocation(x1, y1, x2, y2, convexPoly[i], convexPoly[i + 1]); - int s2 = LinePointLocation(x1, y1, x2, y2, convexPoly[i + 4], convexPoly[i + 5]); - // the line only splits the polygon - // when the previous and next vertex lie - // on different sides of the line - if (s1 != s2 && s1 != 0 && s2 != 0) - { - // debug - case3111++; - poly2counter++; - poly2[2 * poly2counter - 1] = convexPoly[j]; - poly2[2 * poly2counter] = convexPoly[j + 1]; - state++; - } - } - } - } - // ... if the point is not the other vertex of the edge - else if (!(Math.Abs(p[1] - convexPoly[i]) <= compConst && Math.Abs(p[2] - convexPoly[i + 1]) <= compConst)) - { - // debug - case32++; - poly1counter++; - poly1[2 * poly1counter - 1] = p[1]; - poly1[2 * poly1counter] = p[2]; - poly2counter++; - poly2[2 * poly2counter - 1] = p[1]; - poly2[2 * poly2counter] = p[2]; - if (state == 1) - { - poly1counter++; - poly1[2 * poly1counter - 1] = convexPoly[j]; - poly1[2 * poly1counter] = convexPoly[j + 1]; - } - else if (state == 0) - { - poly2counter++; - poly2[2 * poly2counter - 1] = convexPoly[j]; - poly2[2 * poly2counter] = convexPoly[j + 1]; - } - state++; - } - // ... else if the point is the second vertex of the edge - else - { - // debug - case33++; - if (state == 1) - { - poly2counter++; - poly2[2 * poly2counter - 1] = convexPoly[j]; - poly2[2 * poly2counter] = convexPoly[j + 1]; - } - else - { - poly1counter++; - poly1[2 * poly1counter - 1] = convexPoly[j]; - poly1[2 * poly1counter] = convexPoly[j + 1]; - } - } - } - } - // after splitting the state must be 0 or 2 - // (depending whether the polygon was splitted or not) - if (state != 0 && state != 2) - { - // printf("there is something wrong state: %d\n", state); - // printf("polygon might not be convex!!\n"); - // printf("case1: %d\ncase2: %d\ncase3: %d\ncase31: %d case311: %d case3111: %d\ncase32: %d\ncase33: %d\n", case1, case2, case3, case31, case311, case3111, case32, case33); - // printf("numvertices %d\n=============\n", numvertices); - - // if there is something wrong with the intersection, just ignore this one - numpolys = 3; - } - else - { - // finally convert the vertex lists into convex polygons - numpolys = (state == 0) ? 1 : 2; - poly1[0] = poly1counter; - poly2[0] = poly2counter; - // convert the first convex polygon - polys[0] = poly1; - // convert the second convex polygon - if (state == 2) - { - polys[1] = poly2; - } - } - return numpolys; - } - - /// - /// Determines on which side (relative to the direction) of the given line and the - /// point lies (regarding the directional vector) of the given line. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// http://www.mathematik.uni-ulm.de/stochastik/lehre/ws03_04/rt/Geometry2D.ps - /// - private int LinePointLocation(double x1, double y1, double x2, double y2, double x, double y) - { - double z; - if (Math.Atan((y2 - y1) / (x2 - x1)) * 180.0 / Math.PI == 90.0) - { - if (Math.Abs(x1 - x) <= 0.00000000001) - return 0; - } - else - { - if (Math.Abs(y1 + (((y2 - y1) * (x - x1)) / (x2 - x1)) - y) <= EPS) - return 0; - } - // third component of the 3 dimensional product - z = (x2 - x1) * (y - y1) - (y2 - y1) * (x - x1); - if (Math.Abs(z - 0.0) <= 0.00000000001) - { - return 0; - } - else if (z > 0) - { - return 1; - } - else - { - return 2; - } - } - - /// - /// Given four points representing one line and a line segment, returns the intersection point - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// referenced to: http://local.wasp.uwa.edu.au/~pbourke/geometry/ - /// - private void LineLineSegmentIntersection( - double x1, double y1, - double x2, double y2, - double x3, double y3, - double x4, double y4, ref double[] p) - { - // x1,y1 P1 coordinates (point of line) - // x2,y2 P2 coordinates (point of line) - // x3,y3 P3 coordinates (point of line segment) - // x4,y4 P4 coordinates (point of line segment) - // p[1],p[2] intersection coordinates - // - // This function returns a pointer array which first index indicates - // weather they intersect on one point or not, followed by coordinate pairs. - - double u_a, u_b, denom; - double compConst = 0.0000000000001; - // calculate denominator first - denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); - u_a = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3); - u_b = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3); - - - //if(fabs(denom-0.0) < compConst && (fabs(u_b-0.0) < compConst && fabs(u_a-0.0) < compConst)){ - //printf("denom %.20f u_b %.20f u_a %.20f\n",denom, u_b, u_a); - if (Math.Abs(denom - 0.0) < compConst) - { - if (Math.Abs(u_b - 0.0) < compConst && Math.Abs(u_a - 0.0) < compConst) - { - p[0] = 2.0; // if denominator and numerator equal to zero, lines are coincident - } - else - { - p[0] = 0.0;// if denominator equals to zero, lines are parallel - } - - } - else - { - u_b = u_b / denom; - u_a = u_a / denom; - // printf("u_b %.20f\n", u_b); - if (u_b < -compConst || u_b > 1.0 + compConst) - { // check if it is on the line segment - // printf("line (%.20f, %.20f) (%.20f, %.20f) line seg (%.20f, %.20f) (%.20f, %.20f) \n",x1, y1 ,x2, y2 ,x3, y3 , x4, y4); - p[0] = 0.0; - } - else - { - p[0] = 1.0; - p[1] = x1 + u_a * (x2 - x1); // intersection point - p[2] = y1 + u_a * (y2 - y1); - } - } - - } - - /// - /// Returns the centroid of a given polygon - /// - /// - /// - /// Centroid of a given polygon - private void FindPolyCentroid(int numpoints, double[] points, ref double[] centroid) - { - int i; - //double area = 0.0;//, temp - centroid[0] = 0.0; centroid[1] = 0.0; - - for (i = 0; i < 2 * numpoints; i = i + 2) - { - - centroid[0] = centroid[0] + points[i]; - centroid[1] = centroid[1] + points[i + 1]; - - } - centroid[0] = centroid[0] / numpoints; - centroid[1] = centroid[1] / numpoints; - } - - /// - /// Given two points representing a line and a radius together with a center point - /// representing a circle, returns the intersection points. - /// - /// - /// - /// - /// - /// - /// - /// - /// Pointer to list of intersection points - /// - /// referenced to: http://local.wasp.uwa.edu.au/~pbourke/geometry/sphereline/ - /// - private void CircleLineIntersection( - double x1, double y1, - double x2, double y2, - double x3, double y3, double r, ref double[] p) - { - // x1,y1 P1 coordinates [point of line] - // x2,y2 P2 coordinates [point of line] - // x3,y3, r P3 coordinates(circle center) and radius [circle] - // p[1],p[2]; p[3],p[4] intersection coordinates - // - // This function returns a pointer array which first index indicates - // the number of intersection points, followed by coordinate pairs. - - //double x , y ; - double a, b, c, mu, i; - - a = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); - b = 2 * ((x2 - x1) * (x1 - x3) + (y2 - y1) * (y1 - y3)); - c = x3 * x3 + y3 * y3 + x1 * x1 + y1 * y1 - 2 * (x3 * x1 + y3 * y1) - r * r; - i = b * b - 4 * a * c; - - if (i < 0.0) - { - // no intersection - p[0] = 0.0; - } - else if (Math.Abs(i - 0.0) < EPS) - { - // one intersection - p[0] = 1.0; - - mu = -b / (2 * a); - p[1] = x1 + mu * (x2 - x1); - p[2] = y1 + mu * (y2 - y1); - - } - else if (i > 0.0 && !(Math.Abs(a - 0.0) < EPS)) - { - // two intersections - p[0] = 2.0; - // first intersection - mu = (-b + Math.Sqrt(i)) / (2 * a); - p[1] = x1 + mu * (x2 - x1); - p[2] = y1 + mu * (y2 - y1); - // second intersection - mu = (-b - Math.Sqrt(i)) / (2 * a); - p[3] = x1 + mu * (x2 - x1); - p[4] = y1 + mu * (y2 - y1); - - - } - else - { - p[0] = 0.0; - } - } - - /// - /// Given three points, check if the point is the correct point that we are looking for. - /// - /// P1 coordinates (bisector point of dual edge on triangle) - /// P1 coordinates (bisector point of dual edge on triangle) - /// P2 coordinates (intersection point) - /// P2 coordinates (intersection point) - /// P3 coordinates (circumcenter point) - /// P3 coordinates (circumcenter point) - /// - /// Returns true, if given point is the correct one otherwise return false. - private bool ChooseCorrectPoint( - double x1, double y1, - double x2, double y2, - double x3, double y3, bool isObtuse) - { - double d1, d2; - bool p; - - // squared distance between circumcenter and intersection point - d1 = (x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3); - // squared distance between bisector point and intersection point - d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); - - if (isObtuse) - { - // obtuse case - if (d2 >= d1) - { - p = true; // means we have found the right point - } - else - { - p = false; // means take the other point - } - } - else - { - // non-obtuse case - if (d2 < d1) - { - p = true; // means we have found the right point - } - else - { - p = false; // means take the other point - } - } - /// HANDLE RIGHT TRIANGLE CASE!!!!!!!!!!!!!!!!!!!!!!!!!!!! - return p; - - } - - /// - /// This function returns a pointer array which first index indicates the whether - /// the point is in between the other points, followed by coordinate pairs. - /// - /// P1 coordinates [point of line] (point on Voronoi edge - intersection) - /// P1 coordinates [point of line] (point on Voronoi edge - intersection) - /// P2 coordinates [point of line] (circumcenter) - /// P2 coordinates [point of line] (circumcenter) - /// P3 coordinates [point to be compared] (neighbor's circumcenter) - /// P3 coordinates [point to be compared] (neighbor's circumcenter) - /// - private void PointBetweenPoints(double x1, double y1, double x2, double y2, double x, double y, ref double[] p) - { - // now check whether the point is close to circumcenter than intersection point - // BETWEEN THE POINTS - if ((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y) < (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) - { - p[0] = 1.0; - // calculate the squared distance to circumcenter - p[1] = (x - x2) * (x - x2) + (y - y2) * (y - y2); - p[2] = x; - p[3] = y; - }// *NOT* BETWEEN THE POINTS - else - { - p[0] = 0.0; - p[1] = 0.0; - p[2] = 0.0; - p[3] = 0.0; - } - } - - /// - /// Given three coordinates of a triangle, tests a triangle to see if it satisfies - /// the minimum and/or maximum angle condition. - /// - /// - /// - /// - /// - /// - /// - /// Returns true, if it is a BAD triangle, returns false if it is a GOOD triangle. - private bool IsBadTriangleAngle(double x1, double y1, double x2, double y2, double x3, double y3) - { - // variables keeping the distance values for the edges - double dxod, dyod, dxda, dyda, dxao, dyao; - double dxod2, dyod2, dxda2, dyda2, dxao2, dyao2; - - double apexlen, orglen, destlen; - double angle; // in order to check minimum angle condition - - double maxangle; // in order to check minimum angle condition - // calculate the side lengths - - dxod = x1 - x2; - dyod = y1 - y2; - dxda = x2 - x3; - dyda = y2 - y3; - dxao = x3 - x1; - dyao = y3 - y1; - // calculate the squares of the side lentghs - dxod2 = dxod * dxod; - dyod2 = dyod * dyod; - dxda2 = dxda * dxda; - dyda2 = dyda * dyda; - dxao2 = dxao * dxao; - dyao2 = dyao * dyao; - - // Find the lengths of the triangle's three edges. - apexlen = dxod2 + dyod2; - orglen = dxda2 + dyda2; - destlen = dxao2 + dyao2; - - // try to find the minimum edge and accordingly the pqr orientation - if ((apexlen < orglen) && (apexlen < destlen)) - { - // Find the square of the cosine of the angle at the apex. - angle = dxda * dxao + dyda * dyao; - angle = angle * angle / (orglen * destlen); - } - else if (orglen < destlen) - { - // Find the square of the cosine of the angle at the origin. - angle = dxod * dxao + dyod * dyao; - angle = angle * angle / (apexlen * destlen); - } - else - { - // Find the square of the cosine of the angle at the destination. - angle = dxod * dxda + dyod * dyda; - angle = angle * angle / (apexlen * orglen); - - } - - // try to find the maximum edge and accordingly the pqr orientation - if ((apexlen > orglen) && (apexlen > destlen)) - { - // Find the cosine of the angle at the apex. - maxangle = (orglen + destlen - apexlen) / (2 * Math.Sqrt(orglen * destlen)); - } - else if (orglen > destlen) - { - // Find the cosine of the angle at the origin. - maxangle = (apexlen + destlen - orglen) / (2 * Math.Sqrt(apexlen * destlen)); - } - else - { - // Find the cosine of the angle at the destination. - maxangle = (apexlen + orglen - destlen) / (2 * Math.Sqrt(apexlen * orglen)); - } - - // Check whether the angle is smaller than permitted. - if ((angle > behavior.goodAngle) || (behavior.MaxAngle != 0.00 && maxangle < behavior.maxGoodAngle)) - { - return true;// it is a bad triangle - } - - return false;// it is a good triangle - } - - /// - /// Given the triangulation, and a vertex returns the minimum distance to the - /// vertices of the triangle where the given vertex located. - /// - /// - /// - /// - /// - private double MinDistanceToNeighbor(double newlocX, double newlocY, ref Otri searchtri) - { - Otri horiz = default(Otri); // for search operation - LocateResult intersect = LocateResult.Outside; - Vertex v1, v2, v3, torg, tdest; - double d1, d2, d3, ahead; - //triangle ptr; // Temporary variable used by sym(). - - Point newvertex = new Point(newlocX, newlocY); - - // printf("newvertex %f,%f\n", newvertex[0], newvertex[1]); - // Find the location of the vertex to be inserted. Check if a good - // starting triangle has already been provided by the caller. - // Find a boundary triangle. - //horiz.tri = m.dummytri; - //horiz.orient = 0; - //horiz.symself(); - // Search for a triangle containing 'newvertex'. - // Start searching from the triangle provided by the caller. - // Where are we? - torg = searchtri.Org(); - tdest = searchtri.Dest(); - // Check the starting triangle's vertices. - if ((torg.x == newvertex.x) && (torg.y == newvertex.y)) - { - intersect = LocateResult.OnVertex; - searchtri.Copy(ref horiz); - - } - else if ((tdest.x == newvertex.x) && (tdest.y == newvertex.y)) - { - searchtri.Lnext(); - intersect = LocateResult.OnVertex; - searchtri.Copy(ref horiz); - } - else - { - // Orient 'searchtri' to fit the preconditions of calling preciselocate(). - ahead = predicates.CounterClockwise(torg, tdest, newvertex); - if (ahead < 0.0) - { - // Turn around so that 'searchpoint' is to the left of the - // edge specified by 'searchtri'. - searchtri.Sym(); - searchtri.Copy(ref horiz); - intersect = mesh.locator.PreciseLocate(newvertex, ref horiz, false); - } - else if (ahead == 0.0) - { - // Check if 'searchpoint' is between 'torg' and 'tdest'. - if (((torg.x < newvertex.x) == (newvertex.x < tdest.x)) && - ((torg.y < newvertex.y) == (newvertex.y < tdest.y))) - { - intersect = LocateResult.OnEdge; - searchtri.Copy(ref horiz); - - } - } - else - { - searchtri.Copy(ref horiz); - intersect = mesh.locator.PreciseLocate(newvertex, ref horiz, false); - } - } - if (intersect == LocateResult.OnVertex || intersect == LocateResult.Outside) - { - // set distance to 0 - //m.VertexDealloc(newvertex); - return 0.0; - } - else - { // intersect == ONEDGE || intersect == INTRIANGLE - // find the triangle vertices - v1 = horiz.Org(); - v2 = horiz.Dest(); - v3 = horiz.Apex(); - d1 = (v1.x - newvertex.x) * (v1.x - newvertex.x) + (v1.y - newvertex.y) * (v1.y - newvertex.y); - d2 = (v2.x - newvertex.x) * (v2.x - newvertex.x) + (v2.y - newvertex.y) * (v2.y - newvertex.y); - d3 = (v3.x - newvertex.x) * (v3.x - newvertex.x) + (v3.y - newvertex.y) * (v3.y - newvertex.y); - //m.VertexDealloc(newvertex); - // find minimum of the distance - if (d1 <= d2 && d1 <= d3) - { - return d1; - } - else if (d2 <= d3) - { - return d2; - } - else - { - return d3; - } - } - } - } +// ----------------------------------------------------------------------- +// +// Original code by Hale Erten and Alper Üngör, https://www.cise.ufl.edu/~ungor/aCute/index.html +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using TriangleNet.Topology; + using TriangleNet.Geometry; + using TriangleNet.Tools; + + /// + /// Find new Steiner point locations. + /// + /// + /// See https://www.cise.ufl.edu/~ungor/aCute/index.html + /// + class NewLocation + { + const double EPS = 1e-50; + + IPredicates predicates; + + Mesh mesh; + Behavior behavior; + + // Work arrays for wegde intersection + double[] petalx = new double[20]; + double[] petaly = new double[20]; + double[] petalr = new double[20]; + double[] wedges = new double[500]; + double[] initialConvexPoly = new double[500]; + + // Work arrays for smoothing + double[] points_p = new double[500]; + double[] points_q = new double[500]; + double[] points_r = new double[500]; + + // Work arrays for convex polygon split + double[] poly1 = new double[100]; + double[] poly2 = new double[100]; + double[][] polys = new double[3][]; + + public NewLocation(Mesh mesh, IPredicates predicates) + { + this.mesh = mesh; + this.predicates = predicates; + + this.behavior = mesh.behavior; + } + + /// + /// Find a new location for a Steiner point. + /// + /// + /// + /// + /// + /// + /// + /// + /// + public Point FindLocation(Vertex org, Vertex dest, Vertex apex, + ref double xi, ref double eta, bool offcenter, Otri badotri) + { + // Based on using -U switch, call the corresponding function + if (behavior.MaxAngle == 0.0) + { + // Disable the "no max angle" code. It may return weired vertex locations. + return FindNewLocationWithoutMaxAngle(org, dest, apex, ref xi, ref eta, true, badotri); + } + + // With max angle + return FindNewLocation(org, dest, apex, ref xi, ref eta, true, badotri); + } + + /// + /// Find a new location for a Steiner point. + /// + /// + /// + /// + /// + /// + /// + /// + /// + private Point FindNewLocationWithoutMaxAngle(Vertex torg, Vertex tdest, Vertex tapex, + ref double xi, ref double eta, bool offcenter, Otri badotri) + { + double offconstant = behavior.offconstant; + + // for calculating the distances of the edges + double xdo, ydo, xao, yao, xda, yda; + double dodist, aodist, dadist; + // for exact calculation + double denominator; + double dx, dy, dxoff, dyoff; + + ////////////////////////////// HALE'S VARIABLES ////////////////////////////// + // keeps the difference of coordinates edge + double xShortestEdge = 0, yShortestEdge = 0; + + // keeps the square of edge lengths + double shortestEdgeDist = 0, middleEdgeDist = 0, longestEdgeDist = 0; + + // keeps the vertices according to the angle incident to that vertex in a triangle + Point smallestAngleCorner, middleAngleCorner, largestAngleCorner; + + // keeps the type of orientation if the triangle + int orientation = 0; + // keeps the coordinates of circumcenter of itself and neighbor triangle circumcenter + Point myCircumcenter, neighborCircumcenter; + + // keeps if bad triangle is almost good or not + int almostGood = 0; + // keeps the cosine of the largest angle + double cosMaxAngle; + bool isObtuse; // 1: obtuse 0: nonobtuse + // keeps the radius of petal + double petalRadius; + // for calculating petal center + double xPetalCtr_1, yPetalCtr_1, xPetalCtr_2, yPetalCtr_2, xPetalCtr, yPetalCtr, xMidOfShortestEdge, yMidOfShortestEdge; + double dxcenter1, dycenter1, dxcenter2, dycenter2; + // for finding neighbor + Otri neighborotri = default(Otri); + double[] thirdPoint = new double[2]; + //int neighborNotFound = -1; + bool neighborNotFound; + // for keeping the vertices of the neighbor triangle + Vertex neighborvertex_1; + Vertex neighborvertex_2; + Vertex neighborvertex_3; + // dummy variables + double xi_tmp = 0, eta_tmp = 0; + //vertex thirdVertex; + // for petal intersection + double vector_x, vector_y, xMidOfLongestEdge, yMidOfLongestEdge, inter_x, inter_y; + double[] p = new double[5], voronoiOrInter = new double[4]; + bool isCorrect; + + // for vector calculations in perturbation + double ax, ay, d; + double pertConst = 0.06; // perturbation constant + + double lengthConst = 1; // used at comparing circumcenter's distance to proposed point's distance + double justAcute = 1; // used for making the program working for one direction only + // for smoothing + int relocated = 0;// used to differentiate between calling the deletevertex and just proposing a steiner point + double[] newloc = new double[2]; // new location suggested by smoothing + double origin_x = 0, origin_y = 0; // for keeping torg safe + Otri delotri; // keeping the original orientation for relocation process + // keeps the first and second direction suggested points + double dxFirstSuggestion, dyFirstSuggestion, dxSecondSuggestion, dySecondSuggestion; + // second direction variables + double xMidOfMiddleEdge, yMidOfMiddleEdge; + ////////////////////////////// END OF HALE'S VARIABLES ////////////////////////////// + + Statistic.CircumcenterCount++; + + // Compute the circumcenter of the triangle. + xdo = tdest.x - torg.x; + ydo = tdest.y - torg.y; + xao = tapex.x - torg.x; + yao = tapex.y - torg.y; + xda = tapex.x - tdest.x; + yda = tapex.y - tdest.y; + // keeps the square of the distances + dodist = xdo * xdo + ydo * ydo; + aodist = xao * xao + yao * yao; + dadist = (tdest.x - tapex.x) * (tdest.x - tapex.x) + + (tdest.y - tapex.y) * (tdest.y - tapex.y); + // checking if the user wanted exact arithmetic or not + if (Behavior.NoExact) + { + denominator = 0.5 / (xdo * yao - xao * ydo); + } + else + { + // Use the counterclockwise() routine to ensure a positive (and + // reasonably accurate) result, avoiding any possibility of + // division by zero. + denominator = 0.5 / predicates.CounterClockwise(tdest, tapex, torg); + // Don't count the above as an orientation test. + Statistic.CounterClockwiseCount--; + } + // calculate the circumcenter in terms of distance to origin point + dx = (yao * dodist - ydo * aodist) * denominator; + dy = (xdo * aodist - xao * dodist) * denominator; + // for debugging and for keeping circumcenter to use later + // coordinate value of the circumcenter + myCircumcenter = new Point(torg.x + dx, torg.y + dy); + + delotri = badotri; // save for later + ///////////////// FINDING THE ORIENTATION OF TRIANGLE ////////////////// + // Find the (squared) length of the triangle's shortest edge. This + // serves as a conservative estimate of the insertion radius of the + // circumcenter's parent. The estimate is used to ensure that + // the algorithm terminates even if very small angles appear in + // the input PSLG. + // find the orientation of the triangle, basically shortest and longest edges + orientation = LongestShortestEdge(aodist, dadist, dodist); + //printf("org: (%f,%f), dest: (%f,%f), apex: (%f,%f)\n",torg[0],torg[1],tdest[0],tdest[1],tapex[0],tapex[1]); + ///////////////////////////////////////////////////////////////////////////////////////////// + // 123: shortest: aodist // 213: shortest: dadist // 312: shortest: dodist // + // middle: dadist // middle: aodist // middle: aodist // + // longest: dodist // longest: dodist // longest: dadist // + // 132: shortest: aodist // 231: shortest: dadist // 321: shortest: dodist // + // middle: dodist // middle: dodist // middle: dadist // + // longest: dadist // longest: aodist // longest: aodist // + ///////////////////////////////////////////////////////////////////////////////////////////// + + switch (orientation) + { + case 123: // assign necessary information + /// smallest angle corner: dest + /// largest angle corner: apex + xShortestEdge = xao; yShortestEdge = yao; + + shortestEdgeDist = aodist; + middleEdgeDist = dadist; + longestEdgeDist = dodist; + + smallestAngleCorner = tdest; + middleAngleCorner = torg; + largestAngleCorner = tapex; + break; + + case 132: // assign necessary information + /// smallest angle corner: dest + /// largest angle corner: org + xShortestEdge = xao; yShortestEdge = yao; + + shortestEdgeDist = aodist; + middleEdgeDist = dodist; + longestEdgeDist = dadist; + + smallestAngleCorner = tdest; + middleAngleCorner = tapex; + largestAngleCorner = torg; + + break; + case 213: // assign necessary information + /// smallest angle corner: org + /// largest angle corner: apex + xShortestEdge = xda; yShortestEdge = yda; + + shortestEdgeDist = dadist; + middleEdgeDist = aodist; + longestEdgeDist = dodist; + + smallestAngleCorner = torg; + middleAngleCorner = tdest; + largestAngleCorner = tapex; + break; + case 231: // assign necessary information + /// smallest angle corner: org + /// largest angle corner: dest + xShortestEdge = xda; yShortestEdge = yda; + + shortestEdgeDist = dadist; + middleEdgeDist = dodist; + longestEdgeDist = aodist; + + smallestAngleCorner = torg; + middleAngleCorner = tapex; + largestAngleCorner = tdest; + break; + case 312: // assign necessary information + /// smallest angle corner: apex + /// largest angle corner: org + xShortestEdge = xdo; yShortestEdge = ydo; + + shortestEdgeDist = dodist; + middleEdgeDist = aodist; + longestEdgeDist = dadist; + + smallestAngleCorner = tapex; + middleAngleCorner = tdest; + largestAngleCorner = torg; + break; + case 321: // assign necessary information + default: // TODO: is this safe? + /// smallest angle corner: apex + /// largest angle corner: dest + xShortestEdge = xdo; yShortestEdge = ydo; + + shortestEdgeDist = dodist; + middleEdgeDist = dadist; + longestEdgeDist = aodist; + + smallestAngleCorner = tapex; + middleAngleCorner = torg; + largestAngleCorner = tdest; + break; + + }// end of switch + // check for offcenter condition + if (offcenter && (offconstant > 0.0)) + { + // origin has the smallest angle + if (orientation == 213 || orientation == 231) + { + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; + dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; + // If the off-center is closer to destination than the + // circumcenter, use the off-center instead. + /// doubleLY BAD CASE /// + if (dxoff * dxoff + dyoff * dyoff < + (dx - xdo) * (dx - xdo) + (dy - ydo) * (dy - ydo)) + { + dx = xdo + dxoff; + dy = ydo + dyoff; + } + /// ALMOST GOOD CASE /// + else + { + almostGood = 1; + } + // destination has the smallest angle + } + else if (orientation == 123 || orientation == 132) + { + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xShortestEdge + offconstant * yShortestEdge; + dyoff = 0.5 * yShortestEdge - offconstant * xShortestEdge; + // If the off-center is closer to the origin than the + // circumcenter, use the off-center instead. + /// doubleLY BAD CASE /// + if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) + { + dx = dxoff; + dy = dyoff; + } + /// ALMOST GOOD CASE /// + else + { + almostGood = 1; + } + // apex has the smallest angle + } + else + {//orientation == 312 || orientation == 321 + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; + dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; + // If the off-center is closer to the origin than the + // circumcenter, use the off-center instead. + /// doubleLY BAD CASE /// + if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) + { + dx = dxoff; + dy = dyoff; + } + /// ALMOST GOOD CASE /// + else + { + almostGood = 1; + } + } + } + // if the bad triangle is almost good, apply our approach + if (almostGood == 1) + { + + /// calculate cosine of largest angle /// + cosMaxAngle = (middleEdgeDist + shortestEdgeDist - longestEdgeDist) / (2 * Math.Sqrt(middleEdgeDist) * Math.Sqrt(shortestEdgeDist)); + if (cosMaxAngle < 0.0) + { + // obtuse + isObtuse = true; + } + else if (Math.Abs(cosMaxAngle - 0.0) <= EPS) + { + // right triangle (largest angle is 90 degrees) + isObtuse = true; + } + else + { + // nonobtuse + isObtuse = false; + } + /// RELOCATION (LOCAL SMOOTHING) /// + /// check for possible relocation of one of triangle's points /// + relocated = DoSmoothing(delotri, torg, tdest, tapex, ref newloc); + /// if relocation is possible, delete that vertex and insert a vertex at the new location /// + if (relocated > 0) + { + Statistic.RelocationCount++; + + dx = newloc[0] - torg.x; + dy = newloc[1] - torg.y; + origin_x = torg.x; // keep for later use + origin_y = torg.y; + switch (relocated) + { + case 1: + //printf("Relocate: (%f,%f)\n", torg[0],torg[1]); + mesh.DeleteVertex(ref delotri); + break; + case 2: + //printf("Relocate: (%f,%f)\n", tdest[0],tdest[1]); + delotri.Lnext(); + mesh.DeleteVertex(ref delotri); + break; + case 3: + //printf("Relocate: (%f,%f)\n", tapex[0],tapex[1]); + delotri.Lprev(); + mesh.DeleteVertex(ref delotri); + break; + + } + } + else + { + // calculate radius of the petal according to angle constraint + // first find the visible region, PETAL + // find the center of the circle and radius + petalRadius = Math.Sqrt(shortestEdgeDist) / (2 * Math.Sin(behavior.MinAngle * Math.PI / 180.0)); + /// compute two possible centers of the petal /// + // finding the center + // first find the middle point of smallest edge + xMidOfShortestEdge = (middleAngleCorner.x + largestAngleCorner.x) / 2.0; + yMidOfShortestEdge = (middleAngleCorner.y + largestAngleCorner.y) / 2.0; + // two possible centers + xPetalCtr_1 = xMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - + largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); + yPetalCtr_1 = yMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - + middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); + + xPetalCtr_2 = xMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - + largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); + yPetalCtr_2 = yMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - + middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); + // find the correct circle since there will be two possible circles + // calculate the distance to smallest angle corner + dxcenter1 = (xPetalCtr_1 - smallestAngleCorner.x) * (xPetalCtr_1 - smallestAngleCorner.x); + dycenter1 = (yPetalCtr_1 - smallestAngleCorner.y) * (yPetalCtr_1 - smallestAngleCorner.y); + dxcenter2 = (xPetalCtr_2 - smallestAngleCorner.x) * (xPetalCtr_2 - smallestAngleCorner.x); + dycenter2 = (yPetalCtr_2 - smallestAngleCorner.y) * (yPetalCtr_2 - smallestAngleCorner.y); + + // whichever is closer to smallest angle corner, it must be the center + if (dxcenter1 + dycenter1 <= dxcenter2 + dycenter2) + { + xPetalCtr = xPetalCtr_1; yPetalCtr = yPetalCtr_1; + } + else + { + xPetalCtr = xPetalCtr_2; yPetalCtr = yPetalCtr_2; + } + + /// find the third point of the neighbor triangle /// + neighborNotFound = GetNeighborsVertex(badotri, middleAngleCorner.x, middleAngleCorner.y, + smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); + /// find the circumcenter of the neighbor triangle /// + dxFirstSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter + dyFirstSuggestion = dy; + // if there is a neighbor triangle + if (!neighborNotFound) + { + neighborvertex_1 = neighborotri.Org(); + neighborvertex_2 = neighborotri.Dest(); + neighborvertex_3 = neighborotri.Apex(); + // now calculate neighbor's circumcenter which is the voronoi site + neighborCircumcenter = predicates.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, + ref xi_tmp, ref eta_tmp); + + /// compute petal and Voronoi edge intersection /// + // in order to avoid degenerate cases, we need to do a vector based calculation for line + vector_x = (middleAngleCorner.y - smallestAngleCorner.y);//(-y, x) + vector_y = smallestAngleCorner.x - middleAngleCorner.x; + vector_x = myCircumcenter.x + vector_x; + vector_y = myCircumcenter.y + vector_y; + + + // by intersecting bisectors you will end up with the one you want to walk on + // then this line and circle should be intersected + CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, + xPetalCtr, yPetalCtr, petalRadius, ref p); + /// choose the correct intersection point /// + // calculate middle point of the longest edge(bisector) + xMidOfLongestEdge = (middleAngleCorner.x + smallestAngleCorner.x) / 2.0; + yMidOfLongestEdge = (middleAngleCorner.y + smallestAngleCorner.y) / 2.0; + // we need to find correct intersection point, since line intersects circle twice + isCorrect = ChooseCorrectPoint(xMidOfLongestEdge, yMidOfLongestEdge, p[3], p[4], + myCircumcenter.x, myCircumcenter.y, isObtuse); + // make sure which point is the correct one to be considered + if (isCorrect) + { + inter_x = p[3]; + inter_y = p[4]; + } + else + { + inter_x = p[1]; + inter_y = p[2]; + } + /// check if there is a Voronoi vertex between before intersection /// + // check if the voronoi vertex is between the intersection and circumcenter + PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, + neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); + + /// determine the point to be suggested /// + if (p[0] > 0.0) + { // there is at least one intersection point + // if it is between circumcenter and intersection + // if it returns 1.0 this means we have a voronoi vertex within feasible region + if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) + { + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) + { + // go back to circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + + } + else + { // we are not creating a bad triangle + // neighbor's circumcenter is suggested + dxFirstSuggestion = voronoiOrInter[2] - torg.x; + dyFirstSuggestion = voronoiOrInter[3] - torg.y; + } + + } + else + { // there is no voronoi vertex between intersection point and circumcenter + if (IsBadTriangleAngle(largestAngleCorner.x, largestAngleCorner.y, middleAngleCorner.x, middleAngleCorner.y, inter_x, inter_y)) + { + // if it is inside feasible region, then insert v2 + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + + (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - inter_x; + ay = myCircumcenter.y - inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) + { + // go back to circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + + } + else + { + // intersection point is suggested + dxFirstSuggestion = inter_x - torg.x; + dyFirstSuggestion = inter_y - torg.y; + + } + } + else + { + // intersection point is suggested + dxFirstSuggestion = inter_x - torg.x; + dyFirstSuggestion = inter_y - torg.y; + } + } + /// if it is an acute triangle, check if it is a good enough location /// + // for acute triangle case, we need to check if it is ok to use either of them + if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + + (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > + lengthConst * ((smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)))) + { + // use circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + }// else we stick to what we have found + }// intersection point + + }// if it is on the boundary, meaning no neighbor triangle in this direction, try other direction + + /// DO THE SAME THING FOR THE OTHER DIRECTION /// + /// find the third point of the neighbor triangle /// + neighborNotFound = GetNeighborsVertex(badotri, largestAngleCorner.x, largestAngleCorner.y, + smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); + /// find the circumcenter of the neighbor triangle /// + dxSecondSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter + dySecondSuggestion = dy; + // if there is a neighbor triangle + if (!neighborNotFound) + { + neighborvertex_1 = neighborotri.Org(); + neighborvertex_2 = neighborotri.Dest(); + neighborvertex_3 = neighborotri.Apex(); + // now calculate neighbor's circumcenter which is the voronoi site + neighborCircumcenter = predicates.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, + ref xi_tmp, ref eta_tmp); + + /// compute petal and Voronoi edge intersection /// + // in order to avoid degenerate cases, we need to do a vector based calculation for line + vector_x = (largestAngleCorner.y - smallestAngleCorner.y);//(-y, x) + vector_y = smallestAngleCorner.x - largestAngleCorner.x; + vector_x = myCircumcenter.x + vector_x; + vector_y = myCircumcenter.y + vector_y; + + + // by intersecting bisectors you will end up with the one you want to walk on + // then this line and circle should be intersected + CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, + xPetalCtr, yPetalCtr, petalRadius, ref p); + + /// choose the correct intersection point /// + // calcuwedgeslate middle point of the longest edge(bisector) + xMidOfMiddleEdge = (largestAngleCorner.x + smallestAngleCorner.x) / 2.0; + yMidOfMiddleEdge = (largestAngleCorner.y + smallestAngleCorner.y) / 2.0; + // we need to find correct intersection point, since line intersects circle twice + // this direction is always ACUTE + isCorrect = ChooseCorrectPoint(xMidOfMiddleEdge, yMidOfMiddleEdge, p[3], p[4], + myCircumcenter.x, myCircumcenter.y, false/*(isObtuse+1)%2*/); + // make sure which point is the correct one to be considered + if (isCorrect) + { + inter_x = p[3]; + inter_y = p[4]; + } + else + { + inter_x = p[1]; + inter_y = p[2]; + } + + /// check if there is a Voronoi vertex between before intersection /// + // check if the voronoi vertex is between the intersection and circumcenter + PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, + neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); + + /// determine the point to be suggested /// + if (p[0] > 0.0) + { // there is at least one intersection point + // if it is between circumcenter and intersection + // if it returns 1.0 this means we have a voronoi vertex within feasible region + if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) + { + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) + { + // go back to circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + + } + else + { // we are not creating a bad triangle + // neighbor's circumcenter is suggested + dxSecondSuggestion = voronoiOrInter[2] - torg.x; + dySecondSuggestion = voronoiOrInter[3] - torg.y; + + } + + } + else + { // there is no voronoi vertex between intersection point and circumcenter + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) + { + // if it is inside feasible region, then insert v2 + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + + (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - inter_x; + ay = myCircumcenter.y - inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) + { + // go back to circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + + } + else + { + // intersection point is suggested + dxSecondSuggestion = inter_x - torg.x; + dySecondSuggestion = inter_y - torg.y; + } + } + else + { + + // intersection point is suggested + dxSecondSuggestion = inter_x - torg.x; + dySecondSuggestion = inter_y - torg.y; + } + } + /// if it is an acute triangle, check if it is a good enough location /// + // for acute triangle case, we need to check if it is ok to use either of them + if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + + (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > + lengthConst * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)))) + { + // use circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + }// else we stick on what we have found + } + }// if it is on the boundary, meaning no neighbor triangle in this direction, the other direction might be ok + if (isObtuse) + { + //obtuse: do nothing + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + else + { // acute : consider other direction + if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + + }// end if obtuse + }// end of relocation + }// end of almostGood + + Point circumcenter = new Point(); + + if (relocated <= 0) + { + circumcenter.x = torg.x + dx; + circumcenter.y = torg.y + dy; + } + else + { + circumcenter.x = origin_x + dx; + circumcenter.y = origin_y + dy; + } + + xi = (yao * dx - xao * dy) * (2.0 * denominator); + eta = (xdo * dy - ydo * dx) * (2.0 * denominator); + + return circumcenter; + } + + /// + /// Find a new location for a Steiner point. + /// + /// + /// + /// + /// + /// + /// + /// + /// + private Point FindNewLocation(Vertex torg, Vertex tdest, Vertex tapex, + ref double xi, ref double eta, bool offcenter, Otri badotri) + { + double offconstant = behavior.offconstant; + + // for calculating the distances of the edges + double xdo, ydo, xao, yao, xda, yda; + double dodist, aodist, dadist; + // for exact calculation + double denominator; + double dx, dy, dxoff, dyoff; + + ////////////////////////////// HALE'S VARIABLES ////////////////////////////// + // keeps the difference of coordinates edge + double xShortestEdge = 0, yShortestEdge = 0; + + // keeps the square of edge lengths + double shortestEdgeDist = 0, middleEdgeDist = 0, longestEdgeDist = 0; + + // keeps the vertices according to the angle incident to that vertex in a triangle + Point smallestAngleCorner, middleAngleCorner, largestAngleCorner; + + // keeps the type of orientation if the triangle + int orientation = 0; + // keeps the coordinates of circumcenter of itself and neighbor triangle circumcenter + Point myCircumcenter, neighborCircumcenter; + + // keeps if bad triangle is almost good or not + int almostGood = 0; + // keeps the cosine of the largest angle + double cosMaxAngle; + bool isObtuse; // 1: obtuse 0: nonobtuse + // keeps the radius of petal + double petalRadius; + // for calculating petal center + double xPetalCtr_1, yPetalCtr_1, xPetalCtr_2, yPetalCtr_2, xPetalCtr, yPetalCtr, xMidOfShortestEdge, yMidOfShortestEdge; + double dxcenter1, dycenter1, dxcenter2, dycenter2; + // for finding neighbor + Otri neighborotri = default(Otri); + double[] thirdPoint = new double[2]; + //int neighborNotFound = -1; + // for keeping the vertices of the neighbor triangle + Vertex neighborvertex_1; + Vertex neighborvertex_2; + Vertex neighborvertex_3; + // dummy variables + double xi_tmp = 0, eta_tmp = 0; + //vertex thirdVertex; + // for petal intersection + double vector_x, vector_y, xMidOfLongestEdge, yMidOfLongestEdge, inter_x, inter_y; + double[] p = new double[5], voronoiOrInter = new double[4]; + bool isCorrect; + + // for vector calculations in perturbation + double ax, ay, d; + double pertConst = 0.06; // perturbation constant + + double lengthConst = 1; // used at comparing circumcenter's distance to proposed point's distance + double justAcute = 1; // used for making the program working for one direction only + // for smoothing + int relocated = 0;// used to differentiate between calling the deletevertex and just proposing a steiner point + double[] newloc = new double[2]; // new location suggested by smoothing + double origin_x = 0, origin_y = 0; // for keeping torg safe + Otri delotri; // keeping the original orientation for relocation process + // keeps the first and second direction suggested points + double dxFirstSuggestion, dyFirstSuggestion, dxSecondSuggestion, dySecondSuggestion; + // second direction variables + double xMidOfMiddleEdge, yMidOfMiddleEdge; + + double minangle; // in order to make sure that the circumcircle of the bad triangle is greater than petal + // for calculating the slab + double linepnt1_x, linepnt1_y, linepnt2_x, linepnt2_y; // two points of the line + double line_inter_x = 0, line_inter_y = 0; + double line_vector_x, line_vector_y; + double[] line_p = new double[3]; // used for getting the return values of functions related to line intersection + double[] line_result = new double[4]; + // intersection of slab and the petal + double petal_slab_inter_x_first, petal_slab_inter_y_first, petal_slab_inter_x_second, petal_slab_inter_y_second, x_1, y_1, x_2, y_2; + double petal_bisector_x, petal_bisector_y, dist; + double alpha; + bool neighborNotFound_first; + bool neighborNotFound_second; + ////////////////////////////// END OF HALE'S VARIABLES ////////////////////////////// + + Statistic.CircumcenterCount++; + + // Compute the circumcenter of the triangle. + xdo = tdest.x - torg.x; + ydo = tdest.y - torg.y; + xao = tapex.x - torg.x; + yao = tapex.y - torg.y; + xda = tapex.x - tdest.x; + yda = tapex.y - tdest.y; + // keeps the square of the distances + dodist = xdo * xdo + ydo * ydo; + aodist = xao * xao + yao * yao; + dadist = (tdest.x - tapex.x) * (tdest.x - tapex.x) + + (tdest.y - tapex.y) * (tdest.y - tapex.y); + // checking if the user wanted exact arithmetic or not + if (Behavior.NoExact) + { + denominator = 0.5 / (xdo * yao - xao * ydo); + } + else + { + // Use the counterclockwise() routine to ensure a positive (and + // reasonably accurate) result, avoiding any possibility of + // division by zero. + denominator = 0.5 / predicates.CounterClockwise(tdest, tapex, torg); + // Don't count the above as an orientation test. + Statistic.CounterClockwiseCount--; + } + // calculate the circumcenter in terms of distance to origin point + dx = (yao * dodist - ydo * aodist) * denominator; + dy = (xdo * aodist - xao * dodist) * denominator; + // for debugging and for keeping circumcenter to use later + // coordinate value of the circumcenter + myCircumcenter = new Point(torg.x + dx, torg.y + dy); + + delotri = badotri; // save for later + ///////////////// FINDING THE ORIENTATION OF TRIANGLE ////////////////// + // Find the (squared) length of the triangle's shortest edge. This + // serves as a conservative estimate of the insertion radius of the + // circumcenter's parent. The estimate is used to ensure that + // the algorithm terminates even if very small angles appear in + // the input PSLG. + // find the orientation of the triangle, basically shortest and longest edges + orientation = LongestShortestEdge(aodist, dadist, dodist); + //printf("org: (%f,%f), dest: (%f,%f), apex: (%f,%f)\n",torg[0],torg[1],tdest[0],tdest[1],tapex[0],tapex[1]); + ///////////////////////////////////////////////////////////////////////////////////////////// + // 123: shortest: aodist // 213: shortest: dadist // 312: shortest: dodist // + // middle: dadist // middle: aodist // middle: aodist // + // longest: dodist // longest: dodist // longest: dadist // + // 132: shortest: aodist // 231: shortest: dadist // 321: shortest: dodist // + // middle: dodist // middle: dodist // middle: dadist // + // longest: dadist // longest: aodist // longest: aodist // + ///////////////////////////////////////////////////////////////////////////////////////////// + + switch (orientation) + { + case 123: // assign necessary information + /// smallest angle corner: dest + /// largest angle corner: apex + xShortestEdge = xao; yShortestEdge = yao; + + shortestEdgeDist = aodist; + middleEdgeDist = dadist; + longestEdgeDist = dodist; + + smallestAngleCorner = tdest; + middleAngleCorner = torg; + largestAngleCorner = tapex; + break; + + case 132: // assign necessary information + /// smallest angle corner: dest + /// largest angle corner: org + xShortestEdge = xao; yShortestEdge = yao; + + shortestEdgeDist = aodist; + middleEdgeDist = dodist; + longestEdgeDist = dadist; + + smallestAngleCorner = tdest; + middleAngleCorner = tapex; + largestAngleCorner = torg; + + break; + case 213: // assign necessary information + /// smallest angle corner: org + /// largest angle corner: apex + xShortestEdge = xda; yShortestEdge = yda; + + shortestEdgeDist = dadist; + middleEdgeDist = aodist; + longestEdgeDist = dodist; + + smallestAngleCorner = torg; + middleAngleCorner = tdest; + largestAngleCorner = tapex; + break; + case 231: // assign necessary information + /// smallest angle corner: org + /// largest angle corner: dest + xShortestEdge = xda; yShortestEdge = yda; + + shortestEdgeDist = dadist; + middleEdgeDist = dodist; + longestEdgeDist = aodist; + + smallestAngleCorner = torg; + middleAngleCorner = tapex; + largestAngleCorner = tdest; + break; + case 312: // assign necessary information + /// smallest angle corner: apex + /// largest angle corner: org + xShortestEdge = xdo; yShortestEdge = ydo; + + shortestEdgeDist = dodist; + middleEdgeDist = aodist; + longestEdgeDist = dadist; + + smallestAngleCorner = tapex; + middleAngleCorner = tdest; + largestAngleCorner = torg; + break; + case 321: // assign necessary information + default: // TODO: is this safe? + /// smallest angle corner: apex + /// largest angle corner: dest + xShortestEdge = xdo; yShortestEdge = ydo; + + shortestEdgeDist = dodist; + middleEdgeDist = dadist; + longestEdgeDist = aodist; + + smallestAngleCorner = tapex; + middleAngleCorner = torg; + largestAngleCorner = tdest; + break; + + }// end of switch + // check for offcenter condition + if (offcenter && (offconstant > 0.0)) + { + // origin has the smallest angle + if (orientation == 213 || orientation == 231) + { + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; + dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; + // If the off-center is closer to destination than the + // circumcenter, use the off-center instead. + /// doubleLY BAD CASE /// + if (dxoff * dxoff + dyoff * dyoff < + (dx - xdo) * (dx - xdo) + (dy - ydo) * (dy - ydo)) + { + dx = xdo + dxoff; + dy = ydo + dyoff; + } + /// ALMOST GOOD CASE /// + else + { + almostGood = 1; + } + // destination has the smallest angle + } + else if (orientation == 123 || orientation == 132) + { + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xShortestEdge + offconstant * yShortestEdge; + dyoff = 0.5 * yShortestEdge - offconstant * xShortestEdge; + // If the off-center is closer to the origin than the + // circumcenter, use the off-center instead. + /// doubleLY BAD CASE /// + if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) + { + dx = dxoff; + dy = dyoff; + } + /// ALMOST GOOD CASE /// + else + { + almostGood = 1; + } + // apex has the smallest angle + } + else + {//orientation == 312 || orientation == 321 + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; + dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; + // If the off-center is closer to the origin than the + // circumcenter, use the off-center instead. + /// doubleLY BAD CASE /// + if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) + { + dx = dxoff; + dy = dyoff; + } + /// ALMOST GOOD CASE /// + else + { + almostGood = 1; + } + } + } + // if the bad triangle is almost good, apply our approach + if (almostGood == 1) + { + + /// calculate cosine of largest angle /// + cosMaxAngle = (middleEdgeDist + shortestEdgeDist - longestEdgeDist) / (2 * Math.Sqrt(middleEdgeDist) * Math.Sqrt(shortestEdgeDist)); + if (cosMaxAngle < 0.0) + { + // obtuse + isObtuse = true; + } + else if (Math.Abs(cosMaxAngle - 0.0) <= EPS) + { + // right triangle (largest angle is 90 degrees) + isObtuse = true; + } + else + { + // nonobtuse + isObtuse = false; + } + /// RELOCATION (LOCAL SMOOTHING) /// + /// check for possible relocation of one of triangle's points /// + relocated = DoSmoothing(delotri, torg, tdest, tapex, ref newloc); + /// if relocation is possible, delete that vertex and insert a vertex at the new location /// + if (relocated > 0) + { + Statistic.RelocationCount++; + + dx = newloc[0] - torg.x; + dy = newloc[1] - torg.y; + origin_x = torg.x; // keep for later use + origin_y = torg.y; + switch (relocated) + { + case 1: + //printf("Relocate: (%f,%f)\n", torg[0],torg[1]); + mesh.DeleteVertex(ref delotri); + break; + case 2: + //printf("Relocate: (%f,%f)\n", tdest[0],tdest[1]); + delotri.Lnext(); + mesh.DeleteVertex(ref delotri); + break; + case 3: + //printf("Relocate: (%f,%f)\n", tapex[0],tapex[1]); + delotri.Lprev(); + mesh.DeleteVertex(ref delotri); + break; + } + } + else + { + // calculate radius of the petal according to angle constraint + // first find the visible region, PETAL + // find the center of the circle and radius + // choose minimum angle as the maximum of quality angle and the minimum angle of the bad triangle + minangle = Math.Acos((middleEdgeDist + longestEdgeDist - shortestEdgeDist) / (2 * Math.Sqrt(middleEdgeDist) * Math.Sqrt(longestEdgeDist))) * 180.0 / Math.PI; + if (behavior.MinAngle > minangle) + { + minangle = behavior.MinAngle; + } + else + { + minangle = minangle + 0.5; + } + petalRadius = Math.Sqrt(shortestEdgeDist) / (2 * Math.Sin(minangle * Math.PI / 180.0)); + /// compute two possible centers of the petal /// + // finding the center + // first find the middle point of smallest edge + xMidOfShortestEdge = (middleAngleCorner.x + largestAngleCorner.x) / 2.0; + yMidOfShortestEdge = (middleAngleCorner.y + largestAngleCorner.y) / 2.0; + // two possible centers + xPetalCtr_1 = xMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - + largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); + yPetalCtr_1 = yMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - + middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); + + xPetalCtr_2 = xMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - + largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); + yPetalCtr_2 = yMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - + middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); + // find the correct circle since there will be two possible circles + // calculate the distance to smallest angle corner + dxcenter1 = (xPetalCtr_1 - smallestAngleCorner.x) * (xPetalCtr_1 - smallestAngleCorner.x); + dycenter1 = (yPetalCtr_1 - smallestAngleCorner.y) * (yPetalCtr_1 - smallestAngleCorner.y); + dxcenter2 = (xPetalCtr_2 - smallestAngleCorner.x) * (xPetalCtr_2 - smallestAngleCorner.x); + dycenter2 = (yPetalCtr_2 - smallestAngleCorner.y) * (yPetalCtr_2 - smallestAngleCorner.y); + + // whichever is closer to smallest angle corner, it must be the center + if (dxcenter1 + dycenter1 <= dxcenter2 + dycenter2) + { + xPetalCtr = xPetalCtr_1; yPetalCtr = yPetalCtr_1; + } + else + { + xPetalCtr = xPetalCtr_2; yPetalCtr = yPetalCtr_2; + } + /// find the third point of the neighbor triangle /// + neighborNotFound_first = GetNeighborsVertex(badotri, middleAngleCorner.x, middleAngleCorner.y, + smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); + /// find the circumcenter of the neighbor triangle /// + dxFirstSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter + dyFirstSuggestion = dy; + /// before checking the neighbor, find the petal and slab intersections /// + // calculate the intersection point of the petal and the slab lines + // first find the vector + // distance between xmid and petal center + dist = Math.Sqrt((xPetalCtr - xMidOfShortestEdge) * (xPetalCtr - xMidOfShortestEdge) + (yPetalCtr - yMidOfShortestEdge) * (yPetalCtr - yMidOfShortestEdge)); + // find the unit vector goes from mid point to petal center + line_vector_x = (xPetalCtr - xMidOfShortestEdge) / dist; + line_vector_y = (yPetalCtr - yMidOfShortestEdge) / dist; + // find the third point other than p and q + petal_bisector_x = xPetalCtr + line_vector_x * petalRadius; + petal_bisector_y = yPetalCtr + line_vector_y * petalRadius; + alpha = (2.0 * behavior.MaxAngle + minangle - 180.0) * Math.PI / 180.0; + // rotate the vector cw around the petal center + x_1 = petal_bisector_x * Math.Cos(alpha) + petal_bisector_y * Math.Sin(alpha) + xPetalCtr - xPetalCtr * Math.Cos(alpha) - yPetalCtr * Math.Sin(alpha); + y_1 = -petal_bisector_x * Math.Sin(alpha) + petal_bisector_y * Math.Cos(alpha) + yPetalCtr + xPetalCtr * Math.Sin(alpha) - yPetalCtr * Math.Cos(alpha); + // rotate the vector ccw around the petal center + x_2 = petal_bisector_x * Math.Cos(alpha) - petal_bisector_y * Math.Sin(alpha) + xPetalCtr - xPetalCtr * Math.Cos(alpha) + yPetalCtr * Math.Sin(alpha); + y_2 = petal_bisector_x * Math.Sin(alpha) + petal_bisector_y * Math.Cos(alpha) + yPetalCtr - xPetalCtr * Math.Sin(alpha) - yPetalCtr * Math.Cos(alpha); + // we need to find correct intersection point, since there are two possibilities + // weather it is obtuse/acute the one closer to the minimum angle corner is the first direction + isCorrect = ChooseCorrectPoint(x_2, y_2, middleAngleCorner.x, middleAngleCorner.y, x_1, y_1, true); + // make sure which point is the correct one to be considered + if (isCorrect) + { + petal_slab_inter_x_first = x_1; + petal_slab_inter_y_first = y_1; + petal_slab_inter_x_second = x_2; + petal_slab_inter_y_second = y_2; + } + else + { + petal_slab_inter_x_first = x_2; + petal_slab_inter_y_first = y_2; + petal_slab_inter_x_second = x_1; + petal_slab_inter_y_second = y_1; + } + /// choose the correct intersection point /// + // calculate middle point of the longest edge(bisector) + xMidOfLongestEdge = (middleAngleCorner.x + smallestAngleCorner.x) / 2.0; + yMidOfLongestEdge = (middleAngleCorner.y + smallestAngleCorner.y) / 2.0; + // if there is a neighbor triangle + if (!neighborNotFound_first) + { + neighborvertex_1 = neighborotri.Org(); + neighborvertex_2 = neighborotri.Dest(); + neighborvertex_3 = neighborotri.Apex(); + // now calculate neighbor's circumcenter which is the voronoi site + neighborCircumcenter = predicates.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, + ref xi_tmp, ref eta_tmp); + + /// compute petal and Voronoi edge intersection /// + // in order to avoid degenerate cases, we need to do a vector based calculation for line + vector_x = (middleAngleCorner.y - smallestAngleCorner.y);//(-y, x) + vector_y = smallestAngleCorner.x - middleAngleCorner.x; + vector_x = myCircumcenter.x + vector_x; + vector_y = myCircumcenter.y + vector_y; + // by intersecting bisectors you will end up with the one you want to walk on + // then this line and circle should be intersected + CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, + xPetalCtr, yPetalCtr, petalRadius, ref p); + // we need to find correct intersection point, since line intersects circle twice + isCorrect = ChooseCorrectPoint(xMidOfLongestEdge, yMidOfLongestEdge, p[3], p[4], + myCircumcenter.x, myCircumcenter.y, isObtuse); + // make sure which point is the correct one to be considered + if (isCorrect) + { + inter_x = p[3]; + inter_y = p[4]; + } + else + { + inter_x = p[1]; + inter_y = p[2]; + } + //----------------------hale new first direction: for slab calculation---------------// + // calculate the intersection of angle lines and Voronoi + linepnt1_x = middleAngleCorner.x; + linepnt1_y = middleAngleCorner.y; + // vector from middleAngleCorner to largestAngleCorner + line_vector_x = largestAngleCorner.x - middleAngleCorner.x; + line_vector_y = largestAngleCorner.y - middleAngleCorner.y; + // rotate the vector around middleAngleCorner in cw by maxangle degrees + linepnt2_x = petal_slab_inter_x_first; + linepnt2_y = petal_slab_inter_y_first; + // now calculate the intersection of two lines + LineLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, linepnt1_x, linepnt1_y, linepnt2_x, linepnt2_y, ref line_p); + // check if there is a suitable intersection + if (line_p[0] > 0.0) + { + line_inter_x = line_p[1]; + line_inter_y = line_p[2]; + } + else + { + // for debugging (to make sure) + //printf("1) No intersection between two lines!!!\n"); + //printf("(%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f)\n",myCircumcenter.x,myCircumcenter.y,vector_x,vector_y,linepnt1_x,linepnt1_y,linepnt2_x,linepnt2_y); + } + + //---------------------------------------------------------------------// + /// check if there is a Voronoi vertex between before intersection /// + // check if the voronoi vertex is between the intersection and circumcenter + PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, + neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); + + /// determine the point to be suggested /// + if (p[0] > 0.0) + { // there is at least one intersection point + // if it is between circumcenter and intersection + // if it returns 1.0 this means we have a voronoi vertex within feasible region + if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) + { + //-----------------hale new continues 1------------------// + // now check if the line intersection is between cc and voronoi + PointBetweenPoints(voronoiOrInter[2], voronoiOrInter[3], myCircumcenter.x, myCircumcenter.y, line_inter_x, line_inter_y, ref line_result); + if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0) + { + // check if we can go further by picking the slab line and petal intersection + // calculate the distance to the smallest angle corner + // check if we create a bad triangle or not + if (((smallestAngleCorner.x - petal_slab_inter_x_first) * (smallestAngleCorner.x - petal_slab_inter_x_first) + + (smallestAngleCorner.y - petal_slab_inter_y_first) * (smallestAngleCorner.y - petal_slab_inter_y_first) > + lengthConst * ((smallestAngleCorner.x - line_inter_x) * + (smallestAngleCorner.x - line_inter_x) + + (smallestAngleCorner.y - line_inter_y) * + (smallestAngleCorner.y - line_inter_y))) + && (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, petal_slab_inter_x_first, petal_slab_inter_y_first)) + && MinDistanceToNeighbor(petal_slab_inter_x_first, petal_slab_inter_y_first, ref neighborotri) > MinDistanceToNeighbor(line_inter_x, line_inter_y, ref neighborotri)) + { + // check the neighbor's vertices also, which one if better + //slab and petal intersection is advised + dxFirstSuggestion = petal_slab_inter_x_first - torg.x; + dyFirstSuggestion = petal_slab_inter_y_first - torg.y; + } + else + { // slab intersection point is further away + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) + { + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((line_inter_x - myCircumcenter.x) * (line_inter_x - myCircumcenter.x) + + (line_inter_y - myCircumcenter.y) * (line_inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - line_inter_x; + ay = myCircumcenter.y - line_inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) + { + // go back to circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + } + else + { + // intersection point is suggested + dxFirstSuggestion = line_inter_x - torg.x; + dyFirstSuggestion = line_inter_y - torg.y; + } + } + else + {// we are not creating a bad triangle + // slab intersection is advised + dxFirstSuggestion = line_result[2] - torg.x; + dyFirstSuggestion = line_result[3] - torg.y; + } + } + //------------------------------------------------------// + } + else + { + /// NOW APPLY A BREADTH-FIRST SEARCH ON THE VORONOI + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) + { + // go back to circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + } + else + { + // we are not creating a bad triangle + // neighbor's circumcenter is suggested + dxFirstSuggestion = voronoiOrInter[2] - torg.x; + dyFirstSuggestion = voronoiOrInter[3] - torg.y; + } + } + } + else + { // there is no voronoi vertex between intersection point and circumcenter + //-----------------hale new continues 2-----------------// + // now check if the line intersection is between cc and intersection point + PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, line_inter_x, line_inter_y, ref line_result); + if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0) + { + // check if we can go further by picking the slab line and petal intersection + // calculate the distance to the smallest angle corner + if (((smallestAngleCorner.x - petal_slab_inter_x_first) * (smallestAngleCorner.x - petal_slab_inter_x_first) + + (smallestAngleCorner.y - petal_slab_inter_y_first) * (smallestAngleCorner.y - petal_slab_inter_y_first) > + lengthConst * ((smallestAngleCorner.x - line_inter_x) * + (smallestAngleCorner.x - line_inter_x) + + (smallestAngleCorner.y - line_inter_y) * + (smallestAngleCorner.y - line_inter_y))) + && (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, petal_slab_inter_x_first, petal_slab_inter_y_first)) + && MinDistanceToNeighbor(petal_slab_inter_x_first, petal_slab_inter_y_first, ref neighborotri) > MinDistanceToNeighbor(line_inter_x, line_inter_y, ref neighborotri)) + { + //slab and petal intersection is advised + dxFirstSuggestion = petal_slab_inter_x_first - torg.x; + dyFirstSuggestion = petal_slab_inter_y_first - torg.y; + } + else + { // slab intersection point is further away + if (IsBadTriangleAngle(largestAngleCorner.x, largestAngleCorner.y, middleAngleCorner.x, middleAngleCorner.y, line_inter_x, line_inter_y)) + { + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((line_inter_x - myCircumcenter.x) * (line_inter_x - myCircumcenter.x) + + (line_inter_y - myCircumcenter.y) * (line_inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - line_inter_x; + ay = myCircumcenter.y - line_inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) + { + // go back to circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + } + else + { + // intersection point is suggested + dxFirstSuggestion = line_inter_x - torg.x; + dyFirstSuggestion = line_inter_y - torg.y; + } + } + else + {// we are not creating a bad triangle + // slab intersection is advised + dxFirstSuggestion = line_result[2] - torg.x; + dyFirstSuggestion = line_result[3] - torg.y; + } + } + //------------------------------------------------------// + } + else + { + if (IsBadTriangleAngle(largestAngleCorner.x, largestAngleCorner.y, middleAngleCorner.x, middleAngleCorner.y, inter_x, inter_y)) + { + //printf("testtriangle returned false! bad triangle\n"); + // if it is inside feasible region, then insert v2 + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + + (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - inter_x; + ay = myCircumcenter.y - inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) + { + // go back to circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + } + else + { + // intersection point is suggested + dxFirstSuggestion = inter_x - torg.x; + dyFirstSuggestion = inter_y - torg.y; + } + } + else + { + // intersection point is suggested + dxFirstSuggestion = inter_x - torg.x; + dyFirstSuggestion = inter_y - torg.y; + } + } + } + /// if it is an acute triangle, check if it is a good enough location /// + // for acute triangle case, we need to check if it is ok to use either of them + if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + + (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > + lengthConst * ((smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)))) + { + // use circumcenter + dxFirstSuggestion = dx; + dyFirstSuggestion = dy; + + }// else we stick to what we have found + }// intersection point + + }// if it is on the boundary, meaning no neighbor triangle in this direction, try other direction + + /// DO THE SAME THING FOR THE OTHER DIRECTION /// + /// find the third point of the neighbor triangle /// + neighborNotFound_second = GetNeighborsVertex(badotri, largestAngleCorner.x, largestAngleCorner.y, + smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); + /// find the circumcenter of the neighbor triangle /// + dxSecondSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter + dySecondSuggestion = dy; + + /// choose the correct intersection point /// + // calculate middle point of the longest edge(bisector) + xMidOfMiddleEdge = (largestAngleCorner.x + smallestAngleCorner.x) / 2.0; + yMidOfMiddleEdge = (largestAngleCorner.y + smallestAngleCorner.y) / 2.0; + // if there is a neighbor triangle + if (!neighborNotFound_second) + { + neighborvertex_1 = neighborotri.Org(); + neighborvertex_2 = neighborotri.Dest(); + neighborvertex_3 = neighborotri.Apex(); + // now calculate neighbor's circumcenter which is the voronoi site + neighborCircumcenter = predicates.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, + ref xi_tmp, ref eta_tmp); + + /// compute petal and Voronoi edge intersection /// + // in order to avoid degenerate cases, we need to do a vector based calculation for line + vector_x = (largestAngleCorner.y - smallestAngleCorner.y);//(-y, x) + vector_y = smallestAngleCorner.x - largestAngleCorner.x; + vector_x = myCircumcenter.x + vector_x; + vector_y = myCircumcenter.y + vector_y; + + + // by intersecting bisectors you will end up with the one you want to walk on + // then this line and circle should be intersected + CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, + xPetalCtr, yPetalCtr, petalRadius, ref p); + + // we need to find correct intersection point, since line intersects circle twice + // this direction is always ACUTE + isCorrect = ChooseCorrectPoint(xMidOfMiddleEdge, yMidOfMiddleEdge, p[3], p[4], + myCircumcenter.x, myCircumcenter.y, false/*(isObtuse+1)%2*/); + // make sure which point is the correct one to be considered + if (isCorrect) + { + inter_x = p[3]; + inter_y = p[4]; + } + else + { + inter_x = p[1]; + inter_y = p[2]; + } + //----------------------hale new second direction:for slab calculation---------------// + // calculate the intersection of angle lines and Voronoi + linepnt1_x = largestAngleCorner.x; + linepnt1_y = largestAngleCorner.y; + // vector from largestAngleCorner to middleAngleCorner + line_vector_x = middleAngleCorner.x - largestAngleCorner.x; + line_vector_y = middleAngleCorner.y - largestAngleCorner.y; + // rotate the vector around largestAngleCorner in ccw by maxangle degrees + linepnt2_x = petal_slab_inter_x_second; + linepnt2_y = petal_slab_inter_y_second; + // now calculate the intersection of two lines + LineLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, linepnt1_x, linepnt1_y, linepnt2_x, linepnt2_y, ref line_p); + // check if there is a suitable intersection + if (line_p[0] > 0.0) + { + line_inter_x = line_p[1]; + line_inter_y = line_p[2]; + } + else + { + // for debugging (to make sure) + //printf("1) No intersection between two lines!!!\n"); + //printf("(%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f)\n",myCircumcenter.x,myCircumcenter.y,vector_x,vector_y,linepnt1_x,linepnt1_y,linepnt2_x,linepnt2_y); + } + //---------------------------------------------------------------------// + /// check if there is a Voronoi vertex between before intersection /// + // check if the voronoi vertex is between the intersection and circumcenter + PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, + neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); + /// determine the point to be suggested /// + if (p[0] > 0.0) + { // there is at least one intersection point + // if it is between circumcenter and intersection + // if it returns 1.0 this means we have a voronoi vertex within feasible region + if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) + { + //-----------------hale new continues 1------------------// + // now check if the line intersection is between cc and voronoi + PointBetweenPoints(voronoiOrInter[2], voronoiOrInter[3], myCircumcenter.x, myCircumcenter.y, line_inter_x, line_inter_y, ref line_result); + if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0) + { + // check if we can go further by picking the slab line and petal intersection + // calculate the distance to the smallest angle corner + // + if (((smallestAngleCorner.x - petal_slab_inter_x_second) * (smallestAngleCorner.x - petal_slab_inter_x_second) + + (smallestAngleCorner.y - petal_slab_inter_y_second) * (smallestAngleCorner.y - petal_slab_inter_y_second) > + lengthConst * ((smallestAngleCorner.x - line_inter_x) * + (smallestAngleCorner.x - line_inter_x) + + (smallestAngleCorner.y - line_inter_y) * + (smallestAngleCorner.y - line_inter_y))) + && (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, petal_slab_inter_x_second, petal_slab_inter_y_second)) + && MinDistanceToNeighbor(petal_slab_inter_x_second, petal_slab_inter_y_second, ref neighborotri) > MinDistanceToNeighbor(line_inter_x, line_inter_y, ref neighborotri)) + { + // slab and petal intersection is advised + dxSecondSuggestion = petal_slab_inter_x_second - torg.x; + dySecondSuggestion = petal_slab_inter_y_second - torg.y; + } + else + { // slab intersection point is further away + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) + { + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((line_inter_x - myCircumcenter.x) * (line_inter_x - myCircumcenter.x) + + (line_inter_y - myCircumcenter.y) * (line_inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - line_inter_x; + ay = myCircumcenter.y - line_inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) + { + // go back to circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + } + else + { + // intersection point is suggested + dxSecondSuggestion = line_inter_x - torg.x; + dySecondSuggestion = line_inter_y - torg.y; + + } + } + else + {// we are not creating a bad triangle + // slab intersection is advised + dxSecondSuggestion = line_result[2] - torg.x; + dySecondSuggestion = line_result[3] - torg.y; + } + } + //------------------------------------------------------// + } + else + { + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) + { + // go back to circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + } + else + { // we are not creating a bad triangle + // neighbor's circumcenter is suggested + dxSecondSuggestion = voronoiOrInter[2] - torg.x; + dySecondSuggestion = voronoiOrInter[3] - torg.y; + } + } + } + else + { // there is no voronoi vertex between intersection point and circumcenter + //-----------------hale new continues 2-----------------// + // now check if the line intersection is between cc and intersection point + PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, line_inter_x, line_inter_y, ref line_result); + if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0) + { + // check if we can go further by picking the slab line and petal intersection + // calculate the distance to the smallest angle corner + if (((smallestAngleCorner.x - petal_slab_inter_x_second) * (smallestAngleCorner.x - petal_slab_inter_x_second) + + (smallestAngleCorner.y - petal_slab_inter_y_second) * (smallestAngleCorner.y - petal_slab_inter_y_second) > + lengthConst * ((smallestAngleCorner.x - line_inter_x) * + (smallestAngleCorner.x - line_inter_x) + + (smallestAngleCorner.y - line_inter_y) * + (smallestAngleCorner.y - line_inter_y))) + && (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, petal_slab_inter_x_second, petal_slab_inter_y_second)) + && MinDistanceToNeighbor(petal_slab_inter_x_second, petal_slab_inter_y_second, ref neighborotri) > MinDistanceToNeighbor(line_inter_x, line_inter_y, ref neighborotri)) + { + // slab and petal intersection is advised + dxSecondSuggestion = petal_slab_inter_x_second - torg.x; + dySecondSuggestion = petal_slab_inter_y_second - torg.y; + } + else + { // slab intersection point is further away ; + if (IsBadTriangleAngle(largestAngleCorner.x, largestAngleCorner.y, middleAngleCorner.x, middleAngleCorner.y, line_inter_x, line_inter_y)) + { + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((line_inter_x - myCircumcenter.x) * (line_inter_x - myCircumcenter.x) + + (line_inter_y - myCircumcenter.y) * (line_inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - line_inter_x; + ay = myCircumcenter.y - line_inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, line_inter_x, line_inter_y)) + { + // go back to circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + } + else + { + // intersection point is suggested + dxSecondSuggestion = line_inter_x - torg.x; + dySecondSuggestion = line_inter_y - torg.y; + } + } + else + { + // we are not creating a bad triangle + // slab intersection is advised + dxSecondSuggestion = line_result[2] - torg.x; + dySecondSuggestion = line_result[3] - torg.y; + } + } + //------------------------------------------------------// + } + else + { + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) + { + // if it is inside feasible region, then insert v2 + // apply perturbation + // find the distance between circumcenter and intersection point + d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + + (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); + // then find the vector going from intersection point to circumcenter + ax = myCircumcenter.x - inter_x; + ay = myCircumcenter.y - inter_y; + + ax = ax / d; + ay = ay / d; + // now calculate the new intersection point which is perturbated towards the circumcenter + inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); + inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); + if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) + { + // go back to circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + } + else + { + // intersection point is suggested + dxSecondSuggestion = inter_x - torg.x; + dySecondSuggestion = inter_y - torg.y; + } + } + else + { + // intersection point is suggested + dxSecondSuggestion = inter_x - torg.x; + dySecondSuggestion = inter_y - torg.y; + } + } + } + + /// if it is an acute triangle, check if it is a good enough location /// + // for acute triangle case, we need to check if it is ok to use either of them + if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + + (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > + lengthConst * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)))) + { + // use circumcenter + dxSecondSuggestion = dx; + dySecondSuggestion = dy; + + }// else we stick on what we have found + } + }// if it is on the boundary, meaning no neighbor triangle in this direction, the other direction might be ok + if (isObtuse) + { + if (neighborNotFound_first && neighborNotFound_second) + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (xMidOfMiddleEdge)) * + (smallestAngleCorner.x - (xMidOfMiddleEdge)) + + (smallestAngleCorner.y - (yMidOfMiddleEdge)) * + (smallestAngleCorner.y - (yMidOfMiddleEdge))) > + (smallestAngleCorner.x - (xMidOfLongestEdge)) * + (smallestAngleCorner.x - (xMidOfLongestEdge)) + + (smallestAngleCorner.y - (yMidOfLongestEdge)) * + (smallestAngleCorner.y - (yMidOfLongestEdge))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + else if (neighborNotFound_first) + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > + (smallestAngleCorner.x - (xMidOfLongestEdge)) * + (smallestAngleCorner.x - (xMidOfLongestEdge)) + + (smallestAngleCorner.y - (yMidOfLongestEdge)) * + (smallestAngleCorner.y - (yMidOfLongestEdge))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + else if (neighborNotFound_second) + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (xMidOfMiddleEdge)) * + (smallestAngleCorner.x - (xMidOfMiddleEdge)) + + (smallestAngleCorner.y - (yMidOfMiddleEdge)) * + (smallestAngleCorner.y - (yMidOfMiddleEdge))) > + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + else + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + } + else + { // acute : consider other direction + if (neighborNotFound_first && neighborNotFound_second) + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (xMidOfMiddleEdge)) * + (smallestAngleCorner.x - (xMidOfMiddleEdge)) + + (smallestAngleCorner.y - (yMidOfMiddleEdge)) * + (smallestAngleCorner.y - (yMidOfMiddleEdge))) > + (smallestAngleCorner.x - (xMidOfLongestEdge)) * + (smallestAngleCorner.x - (xMidOfLongestEdge)) + + (smallestAngleCorner.y - (yMidOfLongestEdge)) * + (smallestAngleCorner.y - (yMidOfLongestEdge))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + else if (neighborNotFound_first) + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > + (smallestAngleCorner.x - (xMidOfLongestEdge)) * + (smallestAngleCorner.x - (xMidOfLongestEdge)) + + (smallestAngleCorner.y - (yMidOfLongestEdge)) * + (smallestAngleCorner.y - (yMidOfLongestEdge))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + else if (neighborNotFound_second) + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (xMidOfMiddleEdge)) * + (smallestAngleCorner.x - (xMidOfMiddleEdge)) + + (smallestAngleCorner.y - (yMidOfMiddleEdge)) * + (smallestAngleCorner.y - (yMidOfMiddleEdge))) > + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + else + { + //obtuse: check if the other direction works + if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * + (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * + (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) + { + dx = dxSecondSuggestion; + dy = dySecondSuggestion; + } + else + { + dx = dxFirstSuggestion; + dy = dyFirstSuggestion; + } + } + + }// end if obtuse + }// end of relocation + }// end of almostGood + + Point circumcenter = new Point(); + + if (relocated <= 0) + { + circumcenter.x = torg.x + dx; + circumcenter.y = torg.y + dy; + } + else + { + circumcenter.x = origin_x + dx; + circumcenter.y = origin_y + dy; + } + xi = (yao * dx - xao * dy) * (2.0 * denominator); + eta = (xdo * dy - ydo * dx) * (2.0 * denominator); + + return circumcenter; + } + + /// + /// Given square of edge lengths of a triangle, + // determine its orientation + /// + /// + /// + /// + /// Returns a number indicating an orientation. + private int LongestShortestEdge(double aodist, double dadist, double dodist) + { + // 123: shortest: aodist // 213: shortest: dadist // 312: shortest: dodist + // middle: dadist // middle: aodist // middle: aodist + // longest: dodist // longest: dodist // longest: dadist + // 132: shortest: aodist // 231: shortest: dadist // 321: shortest: dodist + // middle: dodist // middle: dodist // middle: dadist + // longest: dadist // longest: aodist // longest: aodist + + int max = 0, min = 0, mid = 0, minMidMax; + if (dodist < aodist && dodist < dadist) + { + min = 3; // apex is the smallest angle, dodist is the longest edge + if (aodist < dadist) + { + max = 2; // dadist is the longest edge + mid = 1; // aodist is the middle longest edge + } + else + { + max = 1; // aodist is the longest edge + mid = 2; // dadist is the middle longest edge + } + } + else if (aodist < dadist) + { + min = 1; // dest is the smallest angle, aodist is the biggest edge + if (dodist < dadist) + { + max = 2; // dadist is the longest edge + mid = 3; // dodist is the middle longest edge + } + else + { + max = 3; // dodist is the longest edge + mid = 2; // dadist is the middle longest edge + } + } + else + { + min = 2; // origin is the smallest angle, dadist is the biggest edge + if (aodist < dodist) + { + max = 3; // dodist is the longest edge + mid = 1; // aodist is the middle longest edge + } + else + { + max = 1; // aodist is the longest edge + mid = 3; // dodist is the middle longest edge + } + } + minMidMax = min * 100 + mid * 10 + max; + // HANDLE ISOSCELES TRIANGLE CASE + return minMidMax; + } + + /// + /// Checks if smothing is possible for a given bad triangle. + /// + /// + /// + /// + /// + /// The new location for the point, if somothing is possible. + /// Returns 1, 2 or 3 if smoothing will work, 0 otherwise. + private int DoSmoothing(Otri badotri, Vertex torg, Vertex tdest, Vertex tapex, + ref double[] newloc) + { + + int numpoints_p = 0;// keeps the number of points in a star of point p, q, r + int numpoints_q = 0; + int numpoints_r = 0; + //int i; + double[] possibilities = new double[6];//there can be more than one possibilities + int num_pos = 0; // number of possibilities + int flag1 = 0, flag2 = 0, flag3 = 0; + bool newLocFound = false; + + //vertex v1, v2, v3; // for ccw test + //double p1[2], p2[2], p3[2]; + //double temp[2]; + + //********************* TRY TO RELOCATE POINT "p" *************** + + // get the surrounding points of p, so this gives us the triangles + numpoints_p = GetStarPoints(badotri, torg, tdest, tapex, 1, ref points_p); + // check if the points in counterclockwise order + // p1[0] = points_p[0]; p1[1] = points_p[1]; + // p2[0] = points_p[2]; p2[1] = points_p[3]; + // p3[0] = points_p[4]; p3[1] = points_p[5]; + // v1 = (vertex)p1; v2 = (vertex)p2; v3 = (vertex)p3; + // if(counterclockwise(m,b,v1,v2,v3) < 0){ + // // reverse the order to ccw + // for(i = 0; i < numpoints_p/2; i++){ + // temp[0] = points_p[2*i]; + // temp[1] = points_p[2*i+1]; + // points_p[2*i] = points_p[2*(numpoints_p-1)-2*i]; + // points_p[2*i+1] = points_p[2*(numpoints_p-1)+1-2*i]; + // points_p[2*(numpoints_p-1)-2*i] = temp[0]; + // points_p[2*(numpoints_p-1)+1-2*i] = temp[1]; + // } + // } + // m.counterclockcount--; + // INTERSECTION OF PETALS + // first check whether the star angles are appropriate for relocation + if (torg.type == VertexType.FreeVertex && numpoints_p != 0 && ValidPolygonAngles(numpoints_p, points_p)) + { + //newLocFound = getPetalIntersection(m, b, numpoints_p, points_p, newloc); + //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_p, points_p, newloc,torg[0],torg[1]); + if (behavior.MaxAngle == 0.0) + { + newLocFound = GetWedgeIntersectionWithoutMaxAngle(numpoints_p, points_p, ref newloc); + } + else + { + newLocFound = GetWedgeIntersection(numpoints_p, points_p, ref newloc); + } + //printf("call petal intersection for p\n"); + // make sure the relocated point is a free vertex + if (newLocFound) + { + possibilities[0] = newloc[0];// something found + possibilities[1] = newloc[1]; + num_pos++;// increase the number of possibilities + flag1 = 1; + } + } + + //********************* TRY TO RELOCATE POINT "q" *************** + + // get the surrounding points of q, so this gives us the triangles + numpoints_q = GetStarPoints(badotri, torg, tdest, tapex, 2, ref points_q); + // // check if the points in counterclockwise order + // v1[0] = points_q[0]; v1[1] = points_q[1]; + // v2[0] = points_q[2]; v2[1] = points_q[3]; + // v3[0] = points_q[4]; v3[1] = points_q[5]; + // if(counterclockwise(m,b,v1,v2,v3) < 0){ + // // reverse the order to ccw + // for(i = 0; i < numpoints_q/2; i++){ + // temp[0] = points_q[2*i]; + // temp[1] = points_q[2*i+1]; + // points_q[2*i] = points_q[2*(numpoints_q-1)-2*i]; + // points_q[2*i+1] = points_q[2*(numpoints_q-1)+1-2*i]; + // points_q[2*(numpoints_q-1)-2*i] = temp[0]; + // points_q[2*(numpoints_q-1)+1-2*i] = temp[1]; + // } + // } + // m.counterclockcount--; + // INTERSECTION OF PETALS + // first check whether the star angles are appropriate for relocation + if (tdest.type == VertexType.FreeVertex && numpoints_q != 0 && ValidPolygonAngles(numpoints_q, points_q)) + { + //newLocFound = getPetalIntersection(m, b,numpoints_q, points_q, newloc); + //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_q, points_q, newloc,tapex[0],tapex[1]); + if (behavior.MaxAngle == 0.0) + { + newLocFound = GetWedgeIntersectionWithoutMaxAngle(numpoints_q, points_q, ref newloc); + } + else + { + newLocFound = GetWedgeIntersection(numpoints_q, points_q, ref newloc); + } + //printf("call petal intersection for q\n"); + + // make sure the relocated point is a free vertex + if (newLocFound) + { + possibilities[2] = newloc[0];// something found + possibilities[3] = newloc[1]; + num_pos++;// increase the number of possibilities + flag2 = 2; + } + } + + + //********************* TRY TO RELOCATE POINT "q" *************** + // get the surrounding points of r, so this gives us the triangles + numpoints_r = GetStarPoints(badotri, torg, tdest, tapex, 3, ref points_r); + // check if the points in counterclockwise order + // v1[0] = points_r[0]; v1[1] = points_r[1]; + // v2[0] = points_r[2]; v2[1] = points_r[3]; + // v3[0] = points_r[4]; v3[1] = points_r[5]; + // if(counterclockwise(m,b,v1,v2,v3) < 0){ + // // reverse the order to ccw + // for(i = 0; i < numpoints_r/2; i++){ + // temp[0] = points_r[2*i]; + // temp[1] = points_r[2*i+1]; + // points_r[2*i] = points_r[2*(numpoints_r-1)-2*i]; + // points_r[2*i+1] = points_r[2*(numpoints_r-1)+1-2*i]; + // points_r[2*(numpoints_r-1)-2*i] = temp[0]; + // points_r[2*(numpoints_r-1)+1-2*i] = temp[1]; + // } + // } + // m.counterclockcount--; + // INTERSECTION OF PETALS + // first check whether the star angles are appropriate for relocation + if (tapex.type == VertexType.FreeVertex && numpoints_r != 0 && ValidPolygonAngles(numpoints_r, points_r)) + { + //newLocFound = getPetalIntersection(m, b,numpoints_r, points_r, newloc); + //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_r, points_r, newloc,tdest[0],tdest[1]); + if (behavior.MaxAngle == 0.0) + { + newLocFound = GetWedgeIntersectionWithoutMaxAngle(numpoints_r, points_r, ref newloc); + } + else + { + newLocFound = GetWedgeIntersection(numpoints_r, points_r, ref newloc); + } + + //printf("call petal intersection for r\n"); + + + // make sure the relocated point is a free vertex + if (newLocFound) + { + possibilities[4] = newloc[0];// something found + possibilities[5] = newloc[1]; + num_pos++;// increase the number of possibilities + flag3 = 3; + } + } + //printf("numpossibilities %d\n",num_pos); + //////////// AFTER FINISH CHECKING EVERY POSSIBILITY, CHOOSE ANY OF THE AVAILABLE ONE ////////////////////// + if (num_pos > 0) + { + if (flag1 > 0) + { // suggest to relocate origin + newloc[0] = possibilities[0]; + newloc[1] = possibilities[1]; + return flag1; + + } + else + { + if (flag2 > 0) + { // suggest to relocate apex + newloc[0] = possibilities[2]; + newloc[1] = possibilities[3]; + return flag2; + + } + else + {// suggest to relocate destination + if (flag3 > 0) + { + newloc[0] = possibilities[4]; + newloc[1] = possibilities[5]; + return flag3; + + } + } + } + } + + return 0;// could not find any good relocation + } + + /// + /// Finds the star of a given point. + /// + /// + /// + /// + /// + /// + /// List of points on the star of the given point. + /// Number of points on the star of the given point. + private int GetStarPoints(Otri badotri, Vertex p, Vertex q, Vertex r, + int whichPoint, ref double[] points) + { + + Otri neighotri = default(Otri); // for return value of the function + Otri tempotri; // for temporary usage + double first_x = 0, first_y = 0; // keeps the first point to be considered + double second_x = 0, second_y = 0; // for determining the edge we will begin + double third_x = 0, third_y = 0; // termination + double[] returnPoint = new double[2]; // for keeping the returned point + int numvertices = 0; // for keeping number of surrounding vertices + + // first determine which point to be used to find its neighbor triangles + switch (whichPoint) + { + case 1: + first_x = p.x; // point at the center + first_y = p.y; + second_x = r.x; // second vertex of first edge to consider + second_y = r.y; + third_x = q.x; // for terminating the search + third_y = q.y; + break; + case 2: + first_x = q.x; // point at the center + first_y = q.y; + second_x = p.x; // second vertex of first edge to consider + second_y = p.y; + third_x = r.x; // for terminating the search + third_y = r.y; + break; + case 3: + first_x = r.x; // point at the center + first_y = r.y; + second_x = q.x; // second vertex of first edge to consider + second_y = q.y; + third_x = p.x; // for terminating the search + third_y = p.y; + break; + } + tempotri = badotri; + // add first point as the end of first edge + points[numvertices] = second_x; + numvertices++; + points[numvertices] = second_y; + numvertices++; + // assign as dummy value + returnPoint[0] = second_x; returnPoint[1] = second_y; + // until we reach the third point of the beginning triangle + do + { + // find the neighbor's third point where it is incident to given edge + if (!GetNeighborsVertex(tempotri, first_x, first_y, second_x, second_y, ref returnPoint, ref neighotri)) + { + // go to next triangle + tempotri = neighotri; + // now the second point is the neighbor's third vertex + second_x = returnPoint[0]; + second_y = returnPoint[1]; + + if (numvertices == points.Length) + { + Array.Resize(ref points, numvertices * 2); + } + + // add a new point to the list of surrounding points + points[numvertices] = returnPoint[0]; + numvertices++; + points[numvertices] = returnPoint[1]; + numvertices++; + } + else + { + numvertices = 0; + break; + } + + } while (!((Math.Abs(returnPoint[0] - third_x) <= EPS) && + (Math.Abs(returnPoint[1] - third_y) <= EPS))); + return numvertices / 2; + + } + + /// + /// Gets a neighbours vertex. + /// + /// + /// + /// + /// + /// + /// Neighbor's third vertex incident to given edge. + /// Pointer for the neighbor triangle. + /// Returns true if vertex was found. + private bool GetNeighborsVertex(Otri badotri, + double first_x, double first_y, + double second_x, double second_y, + ref double[] thirdpoint, ref Otri neighotri) + { + + Otri neighbor = default(Otri); // keeps the neighbor triangles + bool notFound = false; // boolean variable if we can find that neighbor or not + + // for keeping the vertices of the neighbor triangle + Vertex neighborvertex_1 = null; + Vertex neighborvertex_2 = null; + Vertex neighborvertex_3 = null; + + // used for finding neighbor triangle + int firstVertexMatched = 0, secondVertexMatched = 0; // to find the correct neighbor + //triangle ptr; // Temporary variable used by sym() + //int i; // index variable + // find neighbors + // Check each of the triangle's three neighbors to find the correct one + for (badotri.orient = 0; badotri.orient < 3; badotri.orient++) + { + // Find the neighbor. + badotri.Sym(ref neighbor); + // check if it is the one we are looking for by checking the corners + // first check if the neighbor is nonexistent, since it can be on the border + if (neighbor.tri.id != Mesh.DUMMY) + { + // then check if two wanted corners are also in this triangle + // take the vertices of the candidate neighbor + neighborvertex_1 = neighbor.Org(); + neighborvertex_2 = neighbor.Dest(); + neighborvertex_3 = neighbor.Apex(); + + // check if it is really a triangle + if ((neighborvertex_1.x == neighborvertex_2.x && neighborvertex_1.y == neighborvertex_2.y) + || (neighborvertex_2.x == neighborvertex_3.x && neighborvertex_2.y == neighborvertex_3.y) + || (neighborvertex_1.x == neighborvertex_3.x && neighborvertex_1.y == neighborvertex_3.y)) + { + //printf("Two vertices are the same!!!!!!!\n"); + } + else + { + // begin searching for the correct neighbor triangle + firstVertexMatched = 0; + if ((Math.Abs(first_x - neighborvertex_1.x) < EPS) && + (Math.Abs(first_y - neighborvertex_1.y) < EPS)) + { + firstVertexMatched = 11; // neighbor's 1st vertex is matched to first vertex + + } + else if ((Math.Abs(first_x - neighborvertex_2.x) < EPS) && + (Math.Abs(first_y - neighborvertex_2.y) < EPS)) + { + firstVertexMatched = 12; // neighbor's 2nd vertex is matched to first vertex + + } + else if ((Math.Abs(first_x - neighborvertex_3.x) < EPS) && + (Math.Abs(first_y - neighborvertex_3.y) < EPS)) + { + firstVertexMatched = 13; // neighbor's 3rd vertex is matched to first vertex + + }/*else{ + // none of them matched + } // end of first vertex matching */ + + secondVertexMatched = 0; + if ((Math.Abs(second_x - neighborvertex_1.x) < EPS) && + (Math.Abs(second_y - neighborvertex_1.y) < EPS)) + { + secondVertexMatched = 21; // neighbor's 1st vertex is matched to second vertex + } + else if ((Math.Abs(second_x - neighborvertex_2.x) < EPS) && + (Math.Abs(second_y - neighborvertex_2.y) < EPS)) + { + secondVertexMatched = 22; // neighbor's 2nd vertex is matched to second vertex + } + else if ((Math.Abs(second_x - neighborvertex_3.x) < EPS) && + (Math.Abs(second_y - neighborvertex_3.y) < EPS)) + { + secondVertexMatched = 23; // neighbor's 3rd vertex is matched to second vertex + }/*else{ + // none of them matched + } // end of second vertex matching*/ + + } + + }// if neighbor exists or not + + if (((firstVertexMatched == 11) && (secondVertexMatched == 22 || secondVertexMatched == 23)) + || ((firstVertexMatched == 12) && (secondVertexMatched == 21 || secondVertexMatched == 23)) + || ((firstVertexMatched == 13) && (secondVertexMatched == 21 || secondVertexMatched == 22))) + break; + }// end of for loop over all orientations + + switch (firstVertexMatched) + { + case 0: + notFound = true; + break; + case 11: + if (secondVertexMatched == 22) + { + thirdpoint[0] = neighborvertex_3.x; + thirdpoint[1] = neighborvertex_3.y; + } + else if (secondVertexMatched == 23) + { + thirdpoint[0] = neighborvertex_2.x; + thirdpoint[1] = neighborvertex_2.y; + } + else { notFound = true; } + break; + case 12: + if (secondVertexMatched == 21) + { + thirdpoint[0] = neighborvertex_3.x; + thirdpoint[1] = neighborvertex_3.y; + } + else if (secondVertexMatched == 23) + { + thirdpoint[0] = neighborvertex_1.x; + thirdpoint[1] = neighborvertex_1.y; + } + else { notFound = true; } + break; + case 13: + if (secondVertexMatched == 21) + { + thirdpoint[0] = neighborvertex_2.x; + thirdpoint[1] = neighborvertex_2.y; + } + else if (secondVertexMatched == 22) + { + thirdpoint[0] = neighborvertex_1.x; + thirdpoint[1] = neighborvertex_1.y; + } + else { notFound = true; } + break; + default: + if (secondVertexMatched == 0) { notFound = true; } + break; + } + // pointer of the neighbor triangle + neighotri = neighbor; + return notFound; + + } + + /// + /// Find a new point location by wedge intersection. + /// + /// + /// + /// A new location for the point according to surrounding points. + /// Returns true if new location found + private bool GetWedgeIntersectionWithoutMaxAngle(int numpoints, + double[] points, ref double[] newloc) + { + //double total_x = 0; + //double total_y = 0; + double x0, y0, x1, y1, x2, y2; + //double compConst = 0.01; // for comparing real numbers + + double x01, y01; + //double x12, y12; + + //double ax, ay, bx, by; //two intersections of two petals disks + + double d01;//, d12 + + //double petalx0, petaly0, petalr0, petalx1, petaly1, petalr1; + + //double p[5]; + + // Resize work arrays + if (2 * numpoints > petalx.Length) + { + petalx = new double[2 * numpoints]; + petaly = new double[2 * numpoints]; + petalr = new double[2 * numpoints]; + wedges = new double[2 * numpoints * 16 + 36]; + } + + double xmid, ymid, dist, x3, y3; + double x_1, y_1, x_2, y_2, x_3, y_3, x_4, y_4, tempx, tempy; + double ux, uy; + double alpha; + double[] p1 = new double[3]; + + //double poly_points; + int numpolypoints = 0; + + //int numBadTriangle; + + int i, j; + + int s, flag, count, num; + + double petalcenterconstant, petalradiusconstant; + + x0 = points[2 * numpoints - 4]; + y0 = points[2 * numpoints - 3]; + x1 = points[2 * numpoints - 2]; + y1 = points[2 * numpoints - 1]; + + // minimum angle + alpha = behavior.MinAngle * Math.PI / 180.0; + // initialize the constants + if (behavior.goodAngle == 1.0) + { + petalcenterconstant = 0; + petalradiusconstant = 0; + } + else + { + petalcenterconstant = 0.5 / Math.Tan(alpha); + petalradiusconstant = 0.5 / Math.Sin(alpha); + } + + for (i = 0; i < numpoints * 2; i = i + 2) + { + x2 = points[i]; + y2 = points[i + 1]; + + //printf("POLYGON POINTS (p,q) #%d (%.12f, %.12f) (%.12f, %.12f)\n", i/2, x0, y0,x1, y1); + + x01 = x1 - x0; + y01 = y1 - y0; + d01 = Math.Sqrt(x01 * x01 + y01 * y01); + // find the petal of each edge 01; + + // printf("PETAL CONSTANT (%.12f, %.12f)\n", + // b.petalcenterconstant, b.petalradiusconstant ); + // printf("PETAL DIFFS (%.6f, %.6f, %.4f)\n", x01, y01, d01); + + petalx[i / 2] = x0 + 0.5 * x01 - petalcenterconstant * y01; + petaly[i / 2] = y0 + 0.5 * y01 + petalcenterconstant * x01; + petalr[i / 2] = petalradiusconstant * d01; + petalx[numpoints + i / 2] = petalx[i / 2]; + petaly[numpoints + i / 2] = petaly[i / 2]; + petalr[numpoints + i / 2] = petalr[i / 2]; + //printf("PETAL POINTS #%d (%.12f, %.12f) R= %.12f\n", i/2, petalx[i/2],petaly[i/2], petalr[i/2]); + + /// FIRST FIND THE HALF-PLANE POINTS FOR EACH PETAL + xmid = (x0 + x1) / 2.0; // mid point of pq + ymid = (y0 + y1) / 2.0; + + // distance between xmid and petal center + dist = Math.Sqrt((petalx[i / 2] - xmid) * (petalx[i / 2] - xmid) + (petaly[i / 2] - ymid) * (petaly[i / 2] - ymid)); + // find the unit vector goes from mid point to petal center + ux = (petalx[i / 2] - xmid) / dist; + uy = (petaly[i / 2] - ymid) / dist; + // find the third point other than p and q + x3 = petalx[i / 2] + ux * petalr[i / 2]; + y3 = petaly[i / 2] + uy * petalr[i / 2]; + /// FIND THE LINE POINTS BY THE ROTATION MATRIX + // cw rotation matrix [cosX sinX; -sinX cosX] + // cw rotation about (x,y) [ux*cosX + uy*sinX + x - x*cosX - y*sinX; -ux*sinX + uy*cosX + y + x*sinX - y*cosX] + // ccw rotation matrix [cosX -sinX; sinX cosX] + // ccw rotation about (x,y) [ux*cosX - uy*sinX + x - x*cosX + y*sinX; ux*sinX + uy*cosX + y - x*sinX - y*cosX] + /// LINE #1: (x1,y1) & (x_1,y_1) + // vector from p to q + ux = x1 - x0; + uy = y1 - y0; + // rotate the vector around p = (x0,y0) in ccw by alpha degrees + x_1 = x1 * Math.Cos(alpha) - y1 * Math.Sin(alpha) + x0 - x0 * Math.Cos(alpha) + y0 * Math.Sin(alpha); + y_1 = x1 * Math.Sin(alpha) + y1 * Math.Cos(alpha) + y0 - x0 * Math.Sin(alpha) - y0 * Math.Cos(alpha); + // add these to wedges list as lines in order + wedges[i * 16] = x0; wedges[i * 16 + 1] = y0; + wedges[i * 16 + 2] = x_1; wedges[i * 16 + 3] = y_1; + //printf("LINE #1 (%.12f, %.12f) (%.12f, %.12f)\n", x0,y0,x_1,y_1); + /// LINE #2: (x2,y2) & (x_2,y_2) + // vector from p to q + ux = x0 - x1; + uy = y0 - y1; + // rotate the vector around q = (x1,y1) in cw by alpha degrees + x_2 = x0 * Math.Cos(alpha) + y0 * Math.Sin(alpha) + x1 - x1 * Math.Cos(alpha) - y1 * Math.Sin(alpha); + y_2 = -x0 * Math.Sin(alpha) + y0 * Math.Cos(alpha) + y1 + x1 * Math.Sin(alpha) - y1 * Math.Cos(alpha); + // add these to wedges list as lines in order + wedges[i * 16 + 4] = x_2; wedges[i * 16 + 5] = y_2; + wedges[i * 16 + 6] = x1; wedges[i * 16 + 7] = y1; + //printf("LINE #2 (%.12f, %.12f) (%.12f, %.12f)\n", x_2,y_2,x1,y1); + // vector from (petalx, petaly) to (x3,y3) + ux = x3 - petalx[i / 2]; + uy = y3 - petaly[i / 2]; + tempx = x3; tempy = y3; + /// LINE #3, #4, #5: (x3,y3) & (x_3,y_3) + for (j = 1; j < 4; j++) + { + // rotate the vector around (petalx,petaly) in cw by (60 - alpha)*j degrees + x_3 = x3 * Math.Cos((Math.PI / 3.0 - alpha) * j) + y3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + petalx[i / 2] - petalx[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j) - petaly[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j); + y_3 = -x3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + y3 * Math.Cos((Math.PI / 3.0 - alpha) * j) + petaly[i / 2] + petalx[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j) - petaly[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j); + // add these to wedges list as lines in order + wedges[i * 16 + 8 + 4 * (j - 1)] = x_3; wedges[i * 16 + 9 + 4 * (j - 1)] = y_3; + wedges[i * 16 + 10 + 4 * (j - 1)] = tempx; wedges[i * 16 + 11 + 4 * (j - 1)] = tempy; + tempx = x_3; tempy = y_3; + } + tempx = x3; tempy = y3; + /// LINE #6, #7, #8: (x3,y3) & (x_4,y_4) + for (j = 1; j < 4; j++) + { + // rotate the vector around (petalx,petaly) in ccw by (60 - alpha)*j degrees + x_4 = x3 * Math.Cos((Math.PI / 3.0 - alpha) * j) - y3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + petalx[i / 2] - petalx[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j) + petaly[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j); + y_4 = x3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + y3 * Math.Cos((Math.PI / 3.0 - alpha) * j) + petaly[i / 2] - petalx[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j) - petaly[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j); + + // add these to wedges list as lines in order + wedges[i * 16 + 20 + 4 * (j - 1)] = tempx; wedges[i * 16 + 21 + 4 * (j - 1)] = tempy; + wedges[i * 16 + 22 + 4 * (j - 1)] = x_4; wedges[i * 16 + 23 + 4 * (j - 1)] = y_4; + tempx = x_4; tempy = y_4; + } + //printf("LINE #3 (%.12f, %.12f) (%.12f, %.12f)\n", x_3,y_3,x3,y3); + //printf("LINE #4 (%.12f, %.12f) (%.12f, %.12f)\n", x3,y3,x_4,y_4); + + /// IF IT IS THE FIRST ONE, FIND THE CONVEX POLYGON + if (i == 0) + { + // line1 & line2: p1 + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); + if ((p1[0] == 1.0)) + { + // #0 + initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; + // #1 + initialConvexPoly[2] = wedges[i * 16 + 16]; initialConvexPoly[3] = wedges[i * 16 + 17]; + // #2 + initialConvexPoly[4] = wedges[i * 16 + 12]; initialConvexPoly[5] = wedges[i * 16 + 13]; + // #3 + initialConvexPoly[6] = wedges[i * 16 + 8]; initialConvexPoly[7] = wedges[i * 16 + 9]; + // #4 + initialConvexPoly[8] = x3; initialConvexPoly[9] = y3; + // #5 + initialConvexPoly[10] = wedges[i * 16 + 22]; initialConvexPoly[11] = wedges[i * 16 + 23]; + // #6 + initialConvexPoly[12] = wedges[i * 16 + 26]; initialConvexPoly[13] = wedges[i * 16 + 27]; + // #7 + initialConvexPoly[14] = wedges[i * 16 + 30]; initialConvexPoly[15] = wedges[i * 16 + 31]; + //printf("INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f]\n", initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15]); + } + } + + x0 = x1; y0 = y1; + x1 = x2; y1 = y2; + } + + /// HALF PLANE INTERSECTION: START SPLITTING THE INITIAL POLYGON TO FIND FEASIBLE REGION + if (numpoints != 0) + { + // first intersect the opposite located ones + s = (numpoints - 1) / 2 + 1; + flag = 0; + count = 0; + i = 1; + num = 8; + for (j = 0; j < 32; j = j + 4) + { + numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[32 * s + j], wedges[32 * s + 1 + j], wedges[32 * s + 2 + j], wedges[32 * s + 3 + j]); + if (numpolypoints == 0) + return false; + else + num = numpolypoints; + } + count++; + while (count < numpoints - 1) + { + for (j = 0; j < 32; j = j + 4) + { + numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[32 * (i + s * flag) + j], wedges[32 * (i + s * flag) + 1 + j], wedges[32 * (i + s * flag) + 2 + j], wedges[32 * (i + s * flag) + 3 + j]); + if (numpolypoints == 0) + return false; + else + num = numpolypoints; + } + i = i + flag; + flag = (flag + 1) % 2; + count++; + } + /// IF THERE IS A FEASIBLE INTERSECTION POLYGON, FIND ITS CENTROID AS THE NEW LOCATION + FindPolyCentroid(numpolypoints, initialConvexPoly, ref newloc); + + if (behavior.fixedArea) + { + // numBadTriangle = 0; + // for(j= 0; j < numpoints *2-2; j = j+2){ + // if(testTriangleAngleArea(m,b,&newloc[0],&newloc[1], &points[j], &points[j+1], &points[j+2], &points[j+3] )){ + // numBadTriangle++; + // } + // } + // if(testTriangleAngleArea(m,b, &newloc[0],&newloc[1], &points[0], &points[1], &points[numpoints*2-2], &points[numpoints*2-1] )){ + // numBadTriangle++; + // } + // + // if (numBadTriangle == 0) { + // + // return 1; + // } + } + else + { + //printf("yes, we found a feasible region num: %d newloc (%.12f,%.12f)\n", numpolypoints, newloc[0], newloc[1]); + // for(i = 0; i < 2*numpolypoints; i = i+2){ + // printf("point %d) (%.12f,%.12f)\n", i/2, initialConvexPoly[i], initialConvexPoly[i+1]); + // } + // printf("numpoints %d\n",numpoints); + return true; + } + } + + + return false; + } + + /// + /// Find a new point location by wedge intersection. + /// + /// + /// + /// A new location for the point according to surrounding points. + /// Returns true if new location found + private bool GetWedgeIntersection(int numpoints, double[] points, ref double[] newloc) + { + //double total_x = 0; + //double total_y = 0; + double x0, y0, x1, y1, x2, y2; + //double compConst = 0.01; // for comparing real numbers + + double x01, y01; + //double x12, y12; + + //double ax, ay, bx, by; //two intersections of two petals disks + + double d01;//, d12 + + //double petalx0, petaly1, petaly0, petalr0, petalx1, petalr1; + + //double p[5]; + + // Resize work arrays + if (2 * numpoints > petalx.Length) + { + petalx = new double[2 * numpoints]; + petaly = new double[2 * numpoints]; + petalr = new double[2 * numpoints]; + wedges = new double[2 * numpoints * 20 + 40]; + } + + double xmid, ymid, dist, x3, y3; + double x_1, y_1, x_2, y_2, x_3, y_3, x_4, y_4, tempx, tempy, x_5, y_5, x_6, y_6; + double ux, uy; + + double[] p1 = new double[3]; + double[] p2 = new double[3]; + double[] p3 = new double[3]; + double[] p4 = new double[3]; + + //double poly_points; + int numpolypoints = 0; + int howManyPoints = 0; // keeps the number of points used for representing the wedge + double line345 = 4.0, line789 = 4.0; // flag keeping which line to skip or construct + + int numBadTriangle; + + int i, j, k; + + int s, flag, count, num; + + int n, e; + + double weight; + + double petalcenterconstant, petalradiusconstant; + + x0 = points[2 * numpoints - 4]; + y0 = points[2 * numpoints - 3]; + x1 = points[2 * numpoints - 2]; + y1 = points[2 * numpoints - 1]; + + // minimum / maximum angle + double alpha, sinAlpha, cosAlpha, beta, sinBeta, cosBeta; + alpha = behavior.MinAngle * Math.PI / 180.0; + sinAlpha = Math.Sin(alpha); + cosAlpha = Math.Cos(alpha); + beta = behavior.MaxAngle * Math.PI / 180.0; + sinBeta = Math.Sin(beta); + cosBeta = Math.Cos(beta); + + // initialize the constants + if (behavior.goodAngle == 1.0) + { + petalcenterconstant = 0; + petalradiusconstant = 0; + } + else + { + petalcenterconstant = 0.5 / Math.Tan(alpha); + petalradiusconstant = 0.5 / Math.Sin(alpha); + } + + for (i = 0; i < numpoints * 2; i = i + 2) + { + // go to the next point + x2 = points[i]; + y2 = points[i + 1]; + + // printf("POLYGON POINTS (p,q) #%d (%.12f, %.12f) (%.12f, %.12f)\n", i/2, x0, y0,x1, y1); + + x01 = x1 - x0; + y01 = y1 - y0; + d01 = Math.Sqrt(x01 * x01 + y01 * y01); + // find the petal of each edge 01; + + // printf("PETAL CONSTANT (%.12f, %.12f)\n", + // b.petalcenterconstant, b.petalradiusconstant ); + // printf("PETAL DIFFS (%.6f, %.6f, %.4f)\n", x01, y01, d01); + //printf("i:%d numpoints:%d\n", i, numpoints); + petalx[i / 2] = x0 + 0.5 * x01 - petalcenterconstant * y01; + petaly[i / 2] = y0 + 0.5 * y01 + petalcenterconstant * x01; + petalr[i / 2] = petalradiusconstant * d01; + petalx[numpoints + i / 2] = petalx[i / 2]; + petaly[numpoints + i / 2] = petaly[i / 2]; + petalr[numpoints + i / 2] = petalr[i / 2]; + //printf("PETAL POINTS #%d (%.12f, %.12f) R= %.12f\n", i/2, petalx[i/2],petaly[i/2], petalr[i/2]); + + /// FIRST FIND THE HALF-PLANE POINTS FOR EACH PETAL + xmid = (x0 + x1) / 2.0; // mid point of pq + ymid = (y0 + y1) / 2.0; + + // distance between xmid and petal center + dist = Math.Sqrt((petalx[i / 2] - xmid) * (petalx[i / 2] - xmid) + (petaly[i / 2] - ymid) * (petaly[i / 2] - ymid)); + // find the unit vector goes from mid point to petal center + ux = (petalx[i / 2] - xmid) / dist; + uy = (petaly[i / 2] - ymid) / dist; + // find the third point other than p and q + x3 = petalx[i / 2] + ux * petalr[i / 2]; + y3 = petaly[i / 2] + uy * petalr[i / 2]; + /// FIND THE LINE POINTS BY THE ROTATION MATRIX + // cw rotation matrix [cosX sinX; -sinX cosX] + // cw rotation about (x,y) [ux*cosX + uy*sinX + x - x*cosX - y*sinX; -ux*sinX + uy*cosX + y + x*sinX - y*cosX] + // ccw rotation matrix [cosX -sinX; sinX cosX] + // ccw rotation about (x,y) [ux*cosX - uy*sinX + x - x*cosX + y*sinX; ux*sinX + uy*cosX + y - x*sinX - y*cosX] + /// LINE #1: (x1,y1) & (x_1,y_1) + // vector from p to q + ux = x1 - x0; + uy = y1 - y0; + // rotate the vector around p = (x0,y0) in ccw by alpha degrees + x_1 = x1 * cosAlpha - y1 * sinAlpha + x0 - x0 * cosAlpha + y0 * sinAlpha; + y_1 = x1 * sinAlpha + y1 * cosAlpha + y0 - x0 * sinAlpha - y0 * cosAlpha; + // add these to wedges list as lines in order + wedges[i * 20] = x0; wedges[i * 20 + 1] = y0; + wedges[i * 20 + 2] = x_1; wedges[i * 20 + 3] = y_1; + //printf("LINE #1 (%.12f, %.12f) (%.12f, %.12f)\n", x0,y0,x_1,y_1); + /// LINE #2: (x2,y2) & (x_2,y_2) + // vector from q to p + ux = x0 - x1; + uy = y0 - y1; + // rotate the vector around q = (x1,y1) in cw by alpha degrees + x_2 = x0 * cosAlpha + y0 * sinAlpha + x1 - x1 * cosAlpha - y1 * sinAlpha; + y_2 = -x0 * sinAlpha + y0 * cosAlpha + y1 + x1 * sinAlpha - y1 * cosAlpha; + // add these to wedges list as lines in order + wedges[i * 20 + 4] = x_2; wedges[i * 20 + 5] = y_2; + wedges[i * 20 + 6] = x1; wedges[i * 20 + 7] = y1; + //printf("LINE #2 (%.12f, %.12f) (%.12f, %.12f)\n", x_2,y_2,x1,y1); + // vector from (petalx, petaly) to (x3,y3) + ux = x3 - petalx[i / 2]; + uy = y3 - petaly[i / 2]; + tempx = x3; tempy = y3; + + /// DETERMINE HOW MANY POINTS TO USE ACCORDING TO THE MINANGLE-MAXANGLE COMBINATION + // petal center angle + alpha = (2.0 * behavior.MaxAngle + behavior.MinAngle - 180.0); + if (alpha <= 0.0) + {// when only angle lines needed + // 4 point case + howManyPoints = 4; + //printf("4 point case\n"); + line345 = 1.0; + line789 = 1.0; + } + else if (alpha <= 5.0) + {// when only angle lines plus two other lines are needed + // 6 point case + howManyPoints = 6; + //printf("6 point case\n"); + line345 = 2.0; + line789 = 2.0; + } + else if (alpha <= 10.0) + {// when we need more lines + // 8 point case + howManyPoints = 8; + line345 = 3.0; + line789 = 3.0; + //printf("8 point case\n"); + } + else + {// when we have a big wedge + // 10 point case + howManyPoints = 10; + //printf("10 point case\n"); + line345 = 4.0; + line789 = 4.0; + } + alpha = alpha * Math.PI / 180.0; + /// LINE #3, #4, #5: (x3,y3) & (x_3,y_3) + for (j = 1; j < line345; j++) + { + if (line345 == 1) + continue; + // rotate the vector around (petalx,petaly) in cw by (alpha/3.0)*j degrees + x_3 = x3 * Math.Cos((alpha / (line345 - 1.0)) * j) + y3 * Math.Sin(((alpha / (line345 - 1.0)) * j)) + petalx[i / 2] - petalx[i / 2] * Math.Cos(((alpha / (line345 - 1.0)) * j)) - petaly[i / 2] * Math.Sin(((alpha / (line345 - 1.0)) * j)); + y_3 = -x3 * Math.Sin(((alpha / (line345 - 1.0)) * j)) + y3 * Math.Cos(((alpha / (line345 - 1.0)) * j)) + petaly[i / 2] + petalx[i / 2] * Math.Sin(((alpha / (line345 - 1.0)) * j)) - petaly[i / 2] * Math.Cos(((alpha / (line345 - 1.0)) * j)); + // add these to wedges list as lines in order + wedges[i * 20 + 8 + 4 * (j - 1)] = x_3; wedges[i * 20 + 9 + 4 * (j - 1)] = y_3; + wedges[i * 20 + 10 + 4 * (j - 1)] = tempx; wedges[i * 20 + 11 + 4 * (j - 1)] = tempy; + tempx = x_3; tempy = y_3; + } + /// LINE #6: (x2,y2) & (x_3,y_3) + // vector from q to p + ux = x0 - x1; + uy = y0 - y1; + // rotate the vector around q = (x1,y1) in cw by alpha degrees + x_5 = x0 * cosBeta + y0 * sinBeta + x1 - x1 * cosBeta - y1 * sinBeta; + y_5 = -x0 * sinBeta + y0 * cosBeta + y1 + x1 * sinBeta - y1 * cosBeta; + wedges[i * 20 + 20] = x1; wedges[i * 20 + 21] = y1; + wedges[i * 20 + 22] = x_5; wedges[i * 20 + 23] = y_5; + + tempx = x3; tempy = y3; + /// LINE #7, #8, #9: (x3,y3) & (x_4,y_4) + for (j = 1; j < line789; j++) + { + if (line789 == 1) + continue; + // rotate the vector around (petalx,petaly) in ccw by (alpha/3.0)*j degrees + x_4 = x3 * Math.Cos((alpha / (line789 - 1.0)) * j) - y3 * Math.Sin((alpha / (line789 - 1.0)) * j) + petalx[i / 2] - petalx[i / 2] * Math.Cos((alpha / (line789 - 1.0)) * j) + petaly[i / 2] * Math.Sin((alpha / (line789 - 1.0)) * j); + y_4 = x3 * Math.Sin((alpha / (line789 - 1.0)) * j) + y3 * Math.Cos((alpha / (line789 - 1.0)) * j) + petaly[i / 2] - petalx[i / 2] * Math.Sin((alpha / (line789 - 1.0)) * j) - petaly[i / 2] * Math.Cos((alpha / (line789 - 1.0)) * j); + + // add these to wedges list as lines in order + wedges[i * 20 + 24 + 4 * (j - 1)] = tempx; wedges[i * 20 + 25 + 4 * (j - 1)] = tempy; + wedges[i * 20 + 26 + 4 * (j - 1)] = x_4; wedges[i * 20 + 27 + 4 * (j - 1)] = y_4; + tempx = x_4; tempy = y_4; + } + /// LINE #10: (x1,y1) & (x_3,y_3) + // vector from p to q + ux = x1 - x0; + uy = y1 - y0; + // rotate the vector around p = (x0,y0) in ccw by alpha degrees + x_6 = x1 * cosBeta - y1 * sinBeta + x0 - x0 * cosBeta + y0 * sinBeta; + y_6 = x1 * sinBeta + y1 * cosBeta + y0 - x0 * sinBeta - y0 * cosBeta; + wedges[i * 20 + 36] = x_6; wedges[i * 20 + 37] = y_6; + wedges[i * 20 + 38] = x0; wedges[i * 20 + 39] = y0; + + //printf("LINE #1 (%.12f, %.12f) (%.12f, %.12f)\n", x0,y0,x_1,y_1); + /// IF IT IS THE FIRST ONE, FIND THE CONVEX POLYGON + if (i == 0) + { + switch (howManyPoints) + { + case 4: + // line1 & line2 & line3 & line4 + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2); + LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_5, y_5, ref p3); + LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p4); + if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0) && (p4[0] == 1.0)) + { + // #0 + initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; + // #1 + initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2]; + // #2 + initialConvexPoly[4] = p3[1]; initialConvexPoly[5] = p3[2]; + // #3 + initialConvexPoly[6] = p4[1]; initialConvexPoly[7] = p4[2]; + } + break; + case 6: + // line1 & line2 & line3 + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2); + LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p3); + if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0)) + { + // #0 + initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; + // #1 + initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2]; + // #2 + initialConvexPoly[4] = wedges[i * 20 + 8]; initialConvexPoly[5] = wedges[i * 20 + 9]; + // #3 + initialConvexPoly[6] = x3; initialConvexPoly[7] = y3; + // #4 + initialConvexPoly[8] = wedges[i * 20 + 26]; initialConvexPoly[9] = wedges[i * 20 + 27]; + // #5 + initialConvexPoly[10] = p3[1]; initialConvexPoly[11] = p3[2]; + } + break; + case 8: + // line1 & line2: p1 + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2); + LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p3); + if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0)) + { + // #0 + initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; + // #1 + initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2]; + // #2 + initialConvexPoly[4] = wedges[i * 20 + 12]; initialConvexPoly[5] = wedges[i * 20 + 13]; + // #3 + initialConvexPoly[6] = wedges[i * 20 + 8]; initialConvexPoly[7] = wedges[i * 20 + 9]; + // #4 + initialConvexPoly[8] = x3; initialConvexPoly[9] = y3; + // #5 + initialConvexPoly[10] = wedges[i * 20 + 26]; initialConvexPoly[11] = wedges[i * 20 + 27]; + // #6 + initialConvexPoly[12] = wedges[i * 20 + 30]; initialConvexPoly[13] = wedges[i * 20 + 31]; + // #7 + initialConvexPoly[14] = p3[1]; initialConvexPoly[15] = p3[2]; + } + break; + case 10: + // line1 & line2: p1 + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1); + LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2); + LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p3); + //printf("p3 %f %f %f (%f %f) (%f %f) (%f %f) (%f %f)\n",p3[0],p3[1],p3[2], x0, y0, x_6, x_6, x1, y1, x_2, y_2); + if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0)) + { + // #0 + initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2]; + // #1 + initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2]; + // #2 + initialConvexPoly[4] = wedges[i * 20 + 16]; initialConvexPoly[5] = wedges[i * 20 + 17]; + // #3 + initialConvexPoly[6] = wedges[i * 20 + 12]; initialConvexPoly[7] = wedges[i * 20 + 13]; + // #4 + initialConvexPoly[8] = wedges[i * 20 + 8]; initialConvexPoly[9] = wedges[i * 20 + 9]; + // #5 + initialConvexPoly[10] = x3; initialConvexPoly[11] = y3; + // #6 + initialConvexPoly[12] = wedges[i * 20 + 28]; initialConvexPoly[13] = wedges[i * 20 + 29]; + // #7 + initialConvexPoly[14] = wedges[i * 20 + 32]; initialConvexPoly[15] = wedges[i * 20 + 33]; + // #8 + initialConvexPoly[16] = wedges[i * 20 + 34]; initialConvexPoly[17] = wedges[i * 20 + 35]; + // #9 + initialConvexPoly[18] = p3[1]; initialConvexPoly[19] = p3[2]; + } + break; + } + // printf("smallest edge (%f,%f) (%f,%f)\n", x0,y0, x1,y1); + // printf("real INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;]\n", initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15],initialConvexPoly[16],initialConvexPoly[17],initialConvexPoly[18],initialConvexPoly[19]); + } + + x0 = x1; y0 = y1; + x1 = x2; y1 = y2; + } + /// HALF PLANE INTERSECTION: START SPLITTING THE INITIAL POLYGON TO FIND FEASIBLE REGION + if (numpoints != 0) + { + // first intersect the opposite located ones + s = (numpoints - 1) / 2 + 1; + flag = 0; + count = 0; + i = 1; + num = howManyPoints; + for (j = 0; j < 40; j = j + 4) + { + // in order to skip non-existent lines + if (howManyPoints == 4 && (j == 8 || j == 12 || j == 16 || j == 24 || j == 28 || j == 32)) + { + continue; + } + else if (howManyPoints == 6 && (j == 12 || j == 16 || j == 28 || j == 32)) + { + continue; + } + else if (howManyPoints == 8 && (j == 16 || j == 32)) + { + continue; + } + // printf("%d 1 INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;]\n",num, initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15],initialConvexPoly[16],initialConvexPoly[17],initialConvexPoly[18],initialConvexPoly[19]); + // printf("line (%f, %f) (%f, %f)\n",wedges[40*s+j],wedges[40*s+1+j], wedges[40*s+2+j], wedges[40*s+3+j]); + numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[40 * s + j], wedges[40 * s + 1 + j], wedges[40 * s + 2 + j], wedges[40 * s + 3 + j]); + + if (numpolypoints == 0) + return false; + else + num = numpolypoints; + } + count++; + //printf("yes here\n"); + while (count < numpoints - 1) + { + for (j = 0; j < 40; j = j + 4) + { + // in order to skip non-existent lines + if (howManyPoints == 4 && (j == 8 || j == 12 || j == 16 || j == 24 || j == 28 || j == 32)) + { + continue; + } + else if (howManyPoints == 6 && (j == 12 || j == 16 || j == 28 || j == 32)) + { + continue; + } + else if (howManyPoints == 8 && (j == 16 || j == 32)) + { + continue; + } + ////printf("%d 2 INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;]\n",numpolypoints, initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15],initialConvexPoly[16],initialConvexPoly[17],initialConvexPoly[18],initialConvexPoly[19]); + //printf("line (%.20f, %.20f) (%.20f, %.20f)\n", wedges[40 * (i + s * flag) + j], wedges[40 * (i + s * flag) + 1 + j], wedges[40 * (i + s * flag) + 2 + j], wedges[40 * (i + s * flag) + 3 + j]); + numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[40 * (i + s * flag) + j], wedges[40 * (i + s * flag) + 1 + j], wedges[40 * (i + s * flag) + 2 + j], wedges[40 * (i + s * flag) + 3 + j]); + + if (numpolypoints == 0) + return false; + else + num = numpolypoints; + } + i = i + flag; + flag = (flag + 1) % 2; + count++; + } + /// IF THERE IS A FEASIBLE INTERSECTION POLYGON, FIND ITS CENTROID AS THE NEW LOCATION + FindPolyCentroid(numpolypoints, initialConvexPoly, ref newloc); + + if (behavior.MaxAngle != 0.0) + { + numBadTriangle = 0; + for (j = 0; j < numpoints * 2 - 2; j = j + 2) + { + if (IsBadTriangleAngle(newloc[0], newloc[1], points[j], points[j + 1], points[j + 2], points[j + 3])) + { + numBadTriangle++; + } + } + if (IsBadTriangleAngle(newloc[0], newloc[1], points[0], points[1], points[numpoints * 2 - 2], points[numpoints * 2 - 1])) + { + numBadTriangle++; + } + + if (numBadTriangle == 0) + { + + return true; + } + n = (numpoints <= 2) ? 20 : 30; + // try points other than centroid + for (k = 0; k < 2 * numpoints; k = k + 2) + { + for (e = 1; e < n; e = e + 1) + { + newloc[0] = 0.0; newloc[1] = 0.0; + for (i = 0; i < 2 * numpoints; i = i + 2) + { + weight = 1.0 / numpoints; + if (i == k) + { + newloc[0] = newloc[0] + 0.1 * e * weight * points[i]; + newloc[1] = newloc[1] + 0.1 * e * weight * points[i + 1]; + } + else + { + weight = (1.0 - 0.1 * e * weight) / (double)(numpoints - 1.0); + newloc[0] = newloc[0] + weight * points[i]; + newloc[1] = newloc[1] + weight * points[i + 1]; + } + + } + numBadTriangle = 0; + for (j = 0; j < numpoints * 2 - 2; j = j + 2) + { + if (IsBadTriangleAngle(newloc[0], newloc[1], points[j], points[j + 1], points[j + 2], points[j + 3])) + { + numBadTriangle++; + } + } + if (IsBadTriangleAngle(newloc[0], newloc[1], points[0], points[1], points[numpoints * 2 - 2], points[numpoints * 2 - 1])) + { + numBadTriangle++; + } + + if (numBadTriangle == 0) + { + + return true; + } + } + } + } + else + { + //printf("yes, we found a feasible region num: %d newloc (%.12f,%.12f)\n", numpolypoints, newloc[0], newloc[1]); + // for(i = 0; i < 2*numpolypoints; i = i+2){ + // printf("point %d) (%.12f,%.12f)\n", i/2, initialConvexPoly[i], initialConvexPoly[i+1]); + // } + // printf("numpoints %d\n",numpoints); + return true; + } + } + + + return false; + } + + /// + /// Check polygon for min angle. + /// + /// + /// + /// Returns true if the polygon has angles greater than 2*minangle. + private bool ValidPolygonAngles(int numpoints, double[] points) + { + int i;//,j + for (i = 0; i < numpoints; i++) + { + if (i == numpoints - 1) + { + if (IsBadPolygonAngle(points[i * 2], points[i * 2 + 1], points[0], points[1], points[2], points[3])) + { + return false; // one of the inner angles is less than required + } + } + else if (i == numpoints - 2) + { + if (IsBadPolygonAngle(points[i * 2], points[i * 2 + 1], points[(i + 1) * 2], points[(i + 1) * 2 + 1], points[0], points[1])) + { + return false; // one of the inner angles is less than required + } + } + else + { + if (IsBadPolygonAngle(points[i * 2], points[i * 2 + 1], points[(i + 1) * 2], points[(i + 1) * 2 + 1], points[(i + 2) * 2], points[(i + 2) * 2 + 1])) + { + return false; // one of the inner angles is less than required + } + } + } + return true; // all angles are valid + } + + /// + /// Given three coordinates of a polygon, tests to see if it satisfies the minimum + /// angle condition for relocation. + /// + /// + /// + /// + /// + /// + /// + /// Returns true, if it is a BAD polygon corner, returns false if it is a GOOD + /// polygon corner + private bool IsBadPolygonAngle(double x1, double y1, + double x2, double y2, double x3, double y3) + { + // variables keeping the distance values for the edges + double dx12, dy12, dx23, dy23, dx31, dy31; + double dist12, dist23, dist31; + + double cosAngle; // in order to check minimum angle condition + + // calculate the side lengths + + dx12 = x1 - x2; + dy12 = y1 - y2; + dx23 = x2 - x3; + dy23 = y2 - y3; + dx31 = x3 - x1; + dy31 = y3 - y1; + // calculate the squares of the side lentghs + dist12 = dx12 * dx12 + dy12 * dy12; + dist23 = dx23 * dx23 + dy23 * dy23; + dist31 = dx31 * dx31 + dy31 * dy31; + + /// calculate cosine of largest angle /// + cosAngle = (dist12 + dist23 - dist31) / (2 * Math.Sqrt(dist12) * Math.Sqrt(dist23)); + // Check whether the angle is smaller than permitted which is 2*minangle!!! + //printf("angle: %f 2*minangle = %f\n",acos(cosAngle)*180/PI, 2*acos(Math.Sqrt(b.goodangle))*180/PI); + if (Math.Acos(cosAngle) < 2 * Math.Acos(Math.Sqrt(behavior.goodAngle))) + { + return true;// it is a BAD triangle + } + return false;// it is a GOOD triangle + + } + + /// + /// Given four points representing two lines, returns the intersection point. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// The intersection point. + /// + // referenced to: http://local.wasp.uwa.edu.au/~pbourke/geometry/ + /// + private void LineLineIntersection( + double x1, double y1, + double x2, double y2, + double x3, double y3, + double x4, double y4, ref double[] p) + { + // x1,y1 P1 coordinates (point of line 1) + // x2,y2 P2 coordinates (point of line 1) + // x3,y3 P3 coordinates (point of line 2) + // x4,y4 P4 coordinates (point of line 2) + // p[1],p[2] intersection coordinates + // + // This function returns a pointer array which first index indicates + // weather they intersect on one point or not, followed by coordinate pairs. + + double u_a, u_b, denom; + + // calculate denominator first + denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); + u_a = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3); + u_b = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3); + // if denominator and numerator equal to zero, lines are coincident + if (Math.Abs(denom - 0.0) < EPS && (Math.Abs(u_b - 0.0) < EPS && Math.Abs(u_a - 0.0) < EPS)) + { + p[0] = 0.0; + } + // if denominator equals to zero, lines are parallel + else if (Math.Abs(denom - 0.0) < EPS) + { + p[0] = 0.0; + } + else + { + p[0] = 1.0; + u_a = u_a / denom; + u_b = u_b / denom; + p[1] = x1 + u_a * (x2 - x1); // not the intersection point + p[2] = y1 + u_a * (y2 - y1); + } + } + + /// + /// Returns the convex polygon which is the intersection of the given convex + /// polygon with the halfplane on the left side (regarding the directional vector) + /// of the given line. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// http://www.mathematik.uni-ulm.de/stochastik/lehre/ws03_04/rt/Geometry2D.ps + /// + private int HalfPlaneIntersection(int numvertices, ref double[] convexPoly, double x1, double y1, double x2, double y2) + { + double dx, dy; // direction of the line + double z, min, max; + int i, j; + + int numpolys; + double[] res = null; + int count = 0; + int intFound = 0; + dx = x2 - x1; + dy = y2 - y1; + numpolys = SplitConvexPolygon(numvertices, convexPoly, x1, y1, x2, y2, polys); + + if (numpolys == 3) + { + count = numvertices; + } + else + { + for (i = 0; i < numpolys; i++) + { + min = double.MaxValue; + max = double.MinValue; + // compute the minimum and maximum of the + // third coordinate of the cross product + for (j = 1; j <= 2 * polys[i][0] - 1; j = j + 2) + { + z = dx * (polys[i][j + 1] - y1) - dy * (polys[i][j] - x1); + min = (z < min ? z : min); + max = (z > max ? z : max); + } + // ... and choose the (absolute) greater of both + z = (Math.Abs(min) > Math.Abs(max) ? min : max); + // and if it is positive, the polygon polys[i] + // is on the left side of line + if (z > 0.0) + { + res = polys[i]; + intFound = 1; + break; + } + } + if (intFound == 1) + { + while (count < res[0]) + { + convexPoly[2 * count] = res[2 * count + 1]; + convexPoly[2 * count + 1] = res[2 * count + 2]; + count++; + + } + } + } + // update convexPoly + return count; + } + + /// + /// Splits a convex polygons into one or two polygons through the intersection + /// with the given line (regarding the directional vector of the given line). + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// http://www.mathematik.uni-ulm.de/stochastik/lehre/ws03_04/rt/Geometry2D.ps + /// + private int SplitConvexPolygon(int numvertices, double[] convexPoly, double x1, double y1, double x2, double y2, double[][] polys) + { + // state = 0: before the first intersection (with the line) + // state = 1: after the first intersection (with the line) + // state = 2: after the second intersection (with the line) + + int state = 0; + double[] p = new double[3]; + int poly1counter = 0; + int poly2counter = 0; + int numpolys; + int i; + double compConst = 0.000000000001; + // for debugging + int case1 = 0, case2 = 0, case3 = 0, case31 = 0, case32 = 0, case33 = 0, case311 = 0, case3111 = 0; + // intersect all edges of poly with line + for (i = 0; i < 2 * numvertices; i = i + 2) + { + int j = (i + 2 >= 2 * numvertices) ? 0 : i + 2; + LineLineSegmentIntersection(x1, y1, x2, y2, convexPoly[i], convexPoly[i + 1], convexPoly[j], convexPoly[j + 1], ref p); + // if this edge does not intersect with line + if (Math.Abs(p[0] - 0.0) <= compConst) + { + //System.out.println("null"); + // add p[j] to the proper polygon + if (state == 1) + { + poly2counter++; + poly2[2 * poly2counter - 1] = convexPoly[j]; + poly2[2 * poly2counter] = convexPoly[j + 1]; + } + else + { + poly1counter++; + poly1[2 * poly1counter - 1] = convexPoly[j]; + poly1[2 * poly1counter] = convexPoly[j + 1]; + } + // debug + case1++; + } + // ... or if the intersection is the whole edge + else if (Math.Abs(p[0] - 2.0) <= compConst) + { + //System.out.println(o); + // then we can not reach state 1 and 2 + poly1counter++; + poly1[2 * poly1counter - 1] = convexPoly[j]; + poly1[2 * poly1counter] = convexPoly[j + 1]; + // debug + case2++; + } + // ... or if the intersection is a point + else + { + // debug + case3++; + // if the point is the second vertex of the edge + if (Math.Abs(p[1] - convexPoly[j]) <= compConst && Math.Abs(p[2] - convexPoly[j + 1]) <= compConst) + { + // debug + case31++; + if (state == 1) + { + poly2counter++; + poly2[2 * poly2counter - 1] = convexPoly[j]; + poly2[2 * poly2counter] = convexPoly[j + 1]; + poly1counter++; + poly1[2 * poly1counter - 1] = convexPoly[j]; + poly1[2 * poly1counter] = convexPoly[j + 1]; + state++; + } + else if (state == 0) + { + // debug + case311++; + poly1counter++; + poly1[2 * poly1counter - 1] = convexPoly[j]; + poly1[2 * poly1counter] = convexPoly[j + 1]; + // test whether the polygon is splitted + // or the line only touches the polygon + if (i + 4 < 2 * numvertices) + { + int s1 = LinePointLocation(x1, y1, x2, y2, convexPoly[i], convexPoly[i + 1]); + int s2 = LinePointLocation(x1, y1, x2, y2, convexPoly[i + 4], convexPoly[i + 5]); + // the line only splits the polygon + // when the previous and next vertex lie + // on different sides of the line + if (s1 != s2 && s1 != 0 && s2 != 0) + { + // debug + case3111++; + poly2counter++; + poly2[2 * poly2counter - 1] = convexPoly[j]; + poly2[2 * poly2counter] = convexPoly[j + 1]; + state++; + } + } + } + } + // ... if the point is not the other vertex of the edge + else if (!(Math.Abs(p[1] - convexPoly[i]) <= compConst && Math.Abs(p[2] - convexPoly[i + 1]) <= compConst)) + { + // debug + case32++; + poly1counter++; + poly1[2 * poly1counter - 1] = p[1]; + poly1[2 * poly1counter] = p[2]; + poly2counter++; + poly2[2 * poly2counter - 1] = p[1]; + poly2[2 * poly2counter] = p[2]; + if (state == 1) + { + poly1counter++; + poly1[2 * poly1counter - 1] = convexPoly[j]; + poly1[2 * poly1counter] = convexPoly[j + 1]; + } + else if (state == 0) + { + poly2counter++; + poly2[2 * poly2counter - 1] = convexPoly[j]; + poly2[2 * poly2counter] = convexPoly[j + 1]; + } + state++; + } + // ... else if the point is the second vertex of the edge + else + { + // debug + case33++; + if (state == 1) + { + poly2counter++; + poly2[2 * poly2counter - 1] = convexPoly[j]; + poly2[2 * poly2counter] = convexPoly[j + 1]; + } + else + { + poly1counter++; + poly1[2 * poly1counter - 1] = convexPoly[j]; + poly1[2 * poly1counter] = convexPoly[j + 1]; + } + } + } + } + // after splitting the state must be 0 or 2 + // (depending whether the polygon was splitted or not) + if (state != 0 && state != 2) + { + // printf("there is something wrong state: %d\n", state); + // printf("polygon might not be convex!!\n"); + // printf("case1: %d\ncase2: %d\ncase3: %d\ncase31: %d case311: %d case3111: %d\ncase32: %d\ncase33: %d\n", case1, case2, case3, case31, case311, case3111, case32, case33); + // printf("numvertices %d\n=============\n", numvertices); + + // if there is something wrong with the intersection, just ignore this one + numpolys = 3; + } + else + { + // finally convert the vertex lists into convex polygons + numpolys = (state == 0) ? 1 : 2; + poly1[0] = poly1counter; + poly2[0] = poly2counter; + // convert the first convex polygon + polys[0] = poly1; + // convert the second convex polygon + if (state == 2) + { + polys[1] = poly2; + } + } + return numpolys; + } + + /// + /// Determines on which side (relative to the direction) of the given line and the + /// point lies (regarding the directional vector) of the given line. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// http://www.mathematik.uni-ulm.de/stochastik/lehre/ws03_04/rt/Geometry2D.ps + /// + private int LinePointLocation(double x1, double y1, double x2, double y2, double x, double y) + { + double z; + if (Math.Atan((y2 - y1) / (x2 - x1)) * 180.0 / Math.PI == 90.0) + { + if (Math.Abs(x1 - x) <= 0.00000000001) + return 0; + } + else + { + if (Math.Abs(y1 + (((y2 - y1) * (x - x1)) / (x2 - x1)) - y) <= EPS) + return 0; + } + // third component of the 3 dimensional product + z = (x2 - x1) * (y - y1) - (y2 - y1) * (x - x1); + if (Math.Abs(z - 0.0) <= 0.00000000001) + { + return 0; + } + else if (z > 0) + { + return 1; + } + else + { + return 2; + } + } + + /// + /// Given four points representing one line and a line segment, returns the intersection point + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// referenced to: http://local.wasp.uwa.edu.au/~pbourke/geometry/ + /// + private void LineLineSegmentIntersection( + double x1, double y1, + double x2, double y2, + double x3, double y3, + double x4, double y4, ref double[] p) + { + // x1,y1 P1 coordinates (point of line) + // x2,y2 P2 coordinates (point of line) + // x3,y3 P3 coordinates (point of line segment) + // x4,y4 P4 coordinates (point of line segment) + // p[1],p[2] intersection coordinates + // + // This function returns a pointer array which first index indicates + // weather they intersect on one point or not, followed by coordinate pairs. + + double u_a, u_b, denom; + double compConst = 0.0000000000001; + // calculate denominator first + denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); + u_a = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3); + u_b = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3); + + + //if(fabs(denom-0.0) < compConst && (fabs(u_b-0.0) < compConst && fabs(u_a-0.0) < compConst)){ + //printf("denom %.20f u_b %.20f u_a %.20f\n",denom, u_b, u_a); + if (Math.Abs(denom - 0.0) < compConst) + { + if (Math.Abs(u_b - 0.0) < compConst && Math.Abs(u_a - 0.0) < compConst) + { + p[0] = 2.0; // if denominator and numerator equal to zero, lines are coincident + } + else + { + p[0] = 0.0;// if denominator equals to zero, lines are parallel + } + + } + else + { + u_b = u_b / denom; + u_a = u_a / denom; + // printf("u_b %.20f\n", u_b); + if (u_b < -compConst || u_b > 1.0 + compConst) + { // check if it is on the line segment + // printf("line (%.20f, %.20f) (%.20f, %.20f) line seg (%.20f, %.20f) (%.20f, %.20f) \n",x1, y1 ,x2, y2 ,x3, y3 , x4, y4); + p[0] = 0.0; + } + else + { + p[0] = 1.0; + p[1] = x1 + u_a * (x2 - x1); // intersection point + p[2] = y1 + u_a * (y2 - y1); + } + } + + } + + /// + /// Returns the centroid of a given polygon + /// + /// + /// + /// Centroid of a given polygon + private void FindPolyCentroid(int numpoints, double[] points, ref double[] centroid) + { + int i; + //double area = 0.0;//, temp + centroid[0] = 0.0; centroid[1] = 0.0; + + for (i = 0; i < 2 * numpoints; i = i + 2) + { + + centroid[0] = centroid[0] + points[i]; + centroid[1] = centroid[1] + points[i + 1]; + + } + centroid[0] = centroid[0] / numpoints; + centroid[1] = centroid[1] / numpoints; + } + + /// + /// Given two points representing a line and a radius together with a center point + /// representing a circle, returns the intersection points. + /// + /// + /// + /// + /// + /// + /// + /// + /// Pointer to list of intersection points + /// + /// referenced to: http://local.wasp.uwa.edu.au/~pbourke/geometry/sphereline/ + /// + private void CircleLineIntersection( + double x1, double y1, + double x2, double y2, + double x3, double y3, double r, ref double[] p) + { + // x1,y1 P1 coordinates [point of line] + // x2,y2 P2 coordinates [point of line] + // x3,y3, r P3 coordinates(circle center) and radius [circle] + // p[1],p[2]; p[3],p[4] intersection coordinates + // + // This function returns a pointer array which first index indicates + // the number of intersection points, followed by coordinate pairs. + + //double x , y ; + double a, b, c, mu, i; + + a = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); + b = 2 * ((x2 - x1) * (x1 - x3) + (y2 - y1) * (y1 - y3)); + c = x3 * x3 + y3 * y3 + x1 * x1 + y1 * y1 - 2 * (x3 * x1 + y3 * y1) - r * r; + i = b * b - 4 * a * c; + + if (i < 0.0) + { + // no intersection + p[0] = 0.0; + } + else if (Math.Abs(i - 0.0) < EPS) + { + // one intersection + p[0] = 1.0; + + mu = -b / (2 * a); + p[1] = x1 + mu * (x2 - x1); + p[2] = y1 + mu * (y2 - y1); + + } + else if (i > 0.0 && !(Math.Abs(a - 0.0) < EPS)) + { + // two intersections + p[0] = 2.0; + // first intersection + mu = (-b + Math.Sqrt(i)) / (2 * a); + p[1] = x1 + mu * (x2 - x1); + p[2] = y1 + mu * (y2 - y1); + // second intersection + mu = (-b - Math.Sqrt(i)) / (2 * a); + p[3] = x1 + mu * (x2 - x1); + p[4] = y1 + mu * (y2 - y1); + + + } + else + { + p[0] = 0.0; + } + } + + /// + /// Given three points, check if the point is the correct point that we are looking for. + /// + /// P1 coordinates (bisector point of dual edge on triangle) + /// P1 coordinates (bisector point of dual edge on triangle) + /// P2 coordinates (intersection point) + /// P2 coordinates (intersection point) + /// P3 coordinates (circumcenter point) + /// P3 coordinates (circumcenter point) + /// + /// Returns true, if given point is the correct one otherwise return false. + private bool ChooseCorrectPoint( + double x1, double y1, + double x2, double y2, + double x3, double y3, bool isObtuse) + { + double d1, d2; + bool p; + + // squared distance between circumcenter and intersection point + d1 = (x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3); + // squared distance between bisector point and intersection point + d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); + + if (isObtuse) + { + // obtuse case + if (d2 >= d1) + { + p = true; // means we have found the right point + } + else + { + p = false; // means take the other point + } + } + else + { + // non-obtuse case + if (d2 < d1) + { + p = true; // means we have found the right point + } + else + { + p = false; // means take the other point + } + } + /// HANDLE RIGHT TRIANGLE CASE!!!!!!!!!!!!!!!!!!!!!!!!!!!! + return p; + + } + + /// + /// This function returns a pointer array which first index indicates the whether + /// the point is in between the other points, followed by coordinate pairs. + /// + /// P1 coordinates [point of line] (point on Voronoi edge - intersection) + /// P1 coordinates [point of line] (point on Voronoi edge - intersection) + /// P2 coordinates [point of line] (circumcenter) + /// P2 coordinates [point of line] (circumcenter) + /// P3 coordinates [point to be compared] (neighbor's circumcenter) + /// P3 coordinates [point to be compared] (neighbor's circumcenter) + /// + private void PointBetweenPoints(double x1, double y1, double x2, double y2, double x, double y, ref double[] p) + { + // now check whether the point is close to circumcenter than intersection point + // BETWEEN THE POINTS + if ((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y) < (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) + { + p[0] = 1.0; + // calculate the squared distance to circumcenter + p[1] = (x - x2) * (x - x2) + (y - y2) * (y - y2); + p[2] = x; + p[3] = y; + }// *NOT* BETWEEN THE POINTS + else + { + p[0] = 0.0; + p[1] = 0.0; + p[2] = 0.0; + p[3] = 0.0; + } + } + + /// + /// Given three coordinates of a triangle, tests a triangle to see if it satisfies + /// the minimum and/or maximum angle condition. + /// + /// + /// + /// + /// + /// + /// + /// Returns true, if it is a BAD triangle, returns false if it is a GOOD triangle. + private bool IsBadTriangleAngle(double x1, double y1, double x2, double y2, double x3, double y3) + { + // variables keeping the distance values for the edges + double dxod, dyod, dxda, dyda, dxao, dyao; + double dxod2, dyod2, dxda2, dyda2, dxao2, dyao2; + + double apexlen, orglen, destlen; + double angle; // in order to check minimum angle condition + + double maxangle; // in order to check minimum angle condition + // calculate the side lengths + + dxod = x1 - x2; + dyod = y1 - y2; + dxda = x2 - x3; + dyda = y2 - y3; + dxao = x3 - x1; + dyao = y3 - y1; + // calculate the squares of the side lentghs + dxod2 = dxod * dxod; + dyod2 = dyod * dyod; + dxda2 = dxda * dxda; + dyda2 = dyda * dyda; + dxao2 = dxao * dxao; + dyao2 = dyao * dyao; + + // Find the lengths of the triangle's three edges. + apexlen = dxod2 + dyod2; + orglen = dxda2 + dyda2; + destlen = dxao2 + dyao2; + + // try to find the minimum edge and accordingly the pqr orientation + if ((apexlen < orglen) && (apexlen < destlen)) + { + // Find the square of the cosine of the angle at the apex. + angle = dxda * dxao + dyda * dyao; + angle = angle * angle / (orglen * destlen); + } + else if (orglen < destlen) + { + // Find the square of the cosine of the angle at the origin. + angle = dxod * dxao + dyod * dyao; + angle = angle * angle / (apexlen * destlen); + } + else + { + // Find the square of the cosine of the angle at the destination. + angle = dxod * dxda + dyod * dyda; + angle = angle * angle / (apexlen * orglen); + + } + + // try to find the maximum edge and accordingly the pqr orientation + if ((apexlen > orglen) && (apexlen > destlen)) + { + // Find the cosine of the angle at the apex. + maxangle = (orglen + destlen - apexlen) / (2 * Math.Sqrt(orglen * destlen)); + } + else if (orglen > destlen) + { + // Find the cosine of the angle at the origin. + maxangle = (apexlen + destlen - orglen) / (2 * Math.Sqrt(apexlen * destlen)); + } + else + { + // Find the cosine of the angle at the destination. + maxangle = (apexlen + orglen - destlen) / (2 * Math.Sqrt(apexlen * orglen)); + } + + // Check whether the angle is smaller than permitted. + if ((angle > behavior.goodAngle) || (behavior.MaxAngle != 0.00 && maxangle < behavior.maxGoodAngle)) + { + return true;// it is a bad triangle + } + + return false;// it is a good triangle + } + + /// + /// Given the triangulation, and a vertex returns the minimum distance to the + /// vertices of the triangle where the given vertex located. + /// + /// + /// + /// + /// + private double MinDistanceToNeighbor(double newlocX, double newlocY, ref Otri searchtri) + { + Otri horiz = default(Otri); // for search operation + LocateResult intersect = LocateResult.Outside; + Vertex v1, v2, v3, torg, tdest; + double d1, d2, d3, ahead; + //triangle ptr; // Temporary variable used by sym(). + + Point newvertex = new Point(newlocX, newlocY); + + // printf("newvertex %f,%f\n", newvertex[0], newvertex[1]); + // Find the location of the vertex to be inserted. Check if a good + // starting triangle has already been provided by the caller. + // Find a boundary triangle. + //horiz.tri = m.dummytri; + //horiz.orient = 0; + //horiz.symself(); + // Search for a triangle containing 'newvertex'. + // Start searching from the triangle provided by the caller. + // Where are we? + torg = searchtri.Org(); + tdest = searchtri.Dest(); + // Check the starting triangle's vertices. + if ((torg.x == newvertex.x) && (torg.y == newvertex.y)) + { + intersect = LocateResult.OnVertex; + searchtri.Copy(ref horiz); + + } + else if ((tdest.x == newvertex.x) && (tdest.y == newvertex.y)) + { + searchtri.Lnext(); + intersect = LocateResult.OnVertex; + searchtri.Copy(ref horiz); + } + else + { + // Orient 'searchtri' to fit the preconditions of calling preciselocate(). + ahead = predicates.CounterClockwise(torg, tdest, newvertex); + if (ahead < 0.0) + { + // Turn around so that 'searchpoint' is to the left of the + // edge specified by 'searchtri'. + searchtri.Sym(); + searchtri.Copy(ref horiz); + intersect = mesh.locator.PreciseLocate(newvertex, ref horiz, false); + } + else if (ahead == 0.0) + { + // Check if 'searchpoint' is between 'torg' and 'tdest'. + if (((torg.x < newvertex.x) == (newvertex.x < tdest.x)) && + ((torg.y < newvertex.y) == (newvertex.y < tdest.y))) + { + intersect = LocateResult.OnEdge; + searchtri.Copy(ref horiz); + + } + } + else + { + searchtri.Copy(ref horiz); + intersect = mesh.locator.PreciseLocate(newvertex, ref horiz, false); + } + } + if (intersect == LocateResult.OnVertex || intersect == LocateResult.Outside) + { + // set distance to 0 + //m.VertexDealloc(newvertex); + return 0.0; + } + else + { // intersect == ONEDGE || intersect == INTRIANGLE + // find the triangle vertices + v1 = horiz.Org(); + v2 = horiz.Dest(); + v3 = horiz.Apex(); + d1 = (v1.x - newvertex.x) * (v1.x - newvertex.x) + (v1.y - newvertex.y) * (v1.y - newvertex.y); + d2 = (v2.x - newvertex.x) * (v2.x - newvertex.x) + (v2.y - newvertex.y) * (v2.y - newvertex.y); + d3 = (v3.x - newvertex.x) * (v3.x - newvertex.x) + (v3.y - newvertex.y) * (v3.y - newvertex.y); + //m.VertexDealloc(newvertex); + // find minimum of the distance + if (d1 <= d2 && d1 <= d3) + { + return d1; + } + else if (d2 <= d3) + { + return d2; + } + else + { + return d3; + } + } + } + } } \ No newline at end of file diff --git a/Triangle.NET/Triangle/RobustPredicates.cs b/src/Triangle/RobustPredicates.cs similarity index 98% rename from Triangle.NET/Triangle/RobustPredicates.cs rename to src/Triangle/RobustPredicates.cs index fd47d07..16e0d73 100644 --- a/Triangle.NET/Triangle/RobustPredicates.cs +++ b/src/Triangle/RobustPredicates.cs @@ -1,1346 +1,1346 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet -{ - using System; - using TriangleNet.Geometry; - using TriangleNet.Tools; - - /// - /// Adaptive exact arithmetic geometric predicates. - /// - /// - /// The adaptive exact arithmetic geometric predicates implemented herein are described in - /// detail in the paper "Adaptive Precision Floating-Point Arithmetic and Fast Robust - /// Geometric Predicates." by Jonathan Richard Shewchuk, see - /// http://www.cs.cmu.edu/~quake/robust.html - /// - /// The macros of the original C code were automatically expanded using the Visual Studio - /// command prompt with the command "CL /P /C EXACT.C", see - /// http://msdn.microsoft.com/en-us/library/8z9z0bx6.aspx - /// - public class RobustPredicates : IPredicates - { - #region Default predicates instance (Singleton) - - private static readonly object creationLock = new object(); - private static RobustPredicates _default; - - /// - /// Gets the default configuration instance. - /// - public static RobustPredicates Default - { - get - { - if (_default == null) - { - lock (creationLock) - { - if (_default == null) - { - _default = new RobustPredicates(); - } - } - } - - return _default; - } - } - - #endregion - - #region Static initialization - - private static double epsilon, splitter, resulterrbound; - private static double ccwerrboundA, ccwerrboundB, ccwerrboundC; - private static double iccerrboundA, iccerrboundB, iccerrboundC; - //private static double o3derrboundA, o3derrboundB, o3derrboundC; - - /// - /// Initialize the variables used for exact arithmetic. - /// - /// - /// 'epsilon' is the largest power of two such that 1.0 + epsilon = 1.0 in - /// floating-point arithmetic. 'epsilon' bounds the relative roundoff - /// error. It is used for floating-point error analysis. - /// - /// 'splitter' is used to split floating-point numbers into two half- - /// length significands for exact multiplication. - /// - /// I imagine that a highly optimizing compiler might be too smart for its - /// own good, and somehow cause this routine to fail, if it pretends that - /// floating-point arithmetic is too much like double arithmetic. - /// - /// Don't change this routine unless you fully understand it. - /// - static RobustPredicates() - { - double half; - double check, lastcheck; - bool every_other; - - every_other = true; - half = 0.5; - epsilon = 1.0; - splitter = 1.0; - check = 1.0; - // Repeatedly divide 'epsilon' by two until it is too small to add to - // one without causing roundoff. (Also check if the sum is equal to - // the previous sum, for machines that round up instead of using exact - // rounding. Not that these routines will work on such machines.) - do - { - lastcheck = check; - epsilon *= half; - if (every_other) - { - splitter *= 2.0; - } - every_other = !every_other; - check = 1.0 + epsilon; - } while ((check != 1.0) && (check != lastcheck)); - splitter += 1.0; - // Error bounds for orientation and incircle tests. - resulterrbound = (3.0 + 8.0 * epsilon) * epsilon; - ccwerrboundA = (3.0 + 16.0 * epsilon) * epsilon; - ccwerrboundB = (2.0 + 12.0 * epsilon) * epsilon; - ccwerrboundC = (9.0 + 64.0 * epsilon) * epsilon * epsilon; - iccerrboundA = (10.0 + 96.0 * epsilon) * epsilon; - iccerrboundB = (4.0 + 48.0 * epsilon) * epsilon; - iccerrboundC = (44.0 + 576.0 * epsilon) * epsilon * epsilon; - //o3derrboundA = (7.0 + 56.0 * epsilon) * epsilon; - //o3derrboundB = (3.0 + 28.0 * epsilon) * epsilon; - //o3derrboundC = (26.0 + 288.0 * epsilon) * epsilon * epsilon; - } - - #endregion - - public RobustPredicates() - { - AllocateWorkspace(); - } - - /// - /// Check, if the three points appear in counterclockwise order. The result is - /// also a rough approximation of twice the signed area of the triangle defined - /// by the three points. - /// - /// Point a. - /// Point b. - /// Point c. - /// Return a positive value if the points pa, pb, and pc occur in - /// counterclockwise order; a negative value if they occur in clockwise order; - /// and zero if they are collinear. - public double CounterClockwise(Point pa, Point pb, Point pc) - { - double detleft, detright, det; - double detsum, errbound; - - Statistic.CounterClockwiseCount++; - - detleft = (pa.x - pc.x) * (pb.y - pc.y); - detright = (pa.y - pc.y) * (pb.x - pc.x); - det = detleft - detright; - - if (Behavior.NoExact) - { - return det; - } - - if (detleft > 0.0) - { - if (detright <= 0.0) - { - return det; - } - else - { - detsum = detleft + detright; - } - } - else if (detleft < 0.0) - { - if (detright >= 0.0) - { - return det; - } - else - { - detsum = -detleft - detright; - } - } - else - { - return det; - } - - errbound = ccwerrboundA * detsum; - if ((det >= errbound) || (-det >= errbound)) - { - return det; - } - - Statistic.CounterClockwiseAdaptCount++; - return CounterClockwiseAdapt(pa, pb, pc, detsum); - } - - /// - /// Check if the point pd lies inside the circle passing through pa, pb, and pc. The - /// points pa, pb, and pc must be in counterclockwise order, or the sign of the result - /// will be reversed. - /// - /// Point a. - /// Point b. - /// Point c. - /// Point d. - /// Return a positive value if the point pd lies inside the circle passing through - /// pa, pb, and pc; a negative value if it lies outside; and zero if the four points - /// are cocircular. - public double InCircle(Point pa, Point pb, Point pc, Point pd) - { - double adx, bdx, cdx, ady, bdy, cdy; - double bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady; - double alift, blift, clift; - double det; - double permanent, errbound; - - Statistic.InCircleCount++; - - adx = pa.x - pd.x; - bdx = pb.x - pd.x; - cdx = pc.x - pd.x; - ady = pa.y - pd.y; - bdy = pb.y - pd.y; - cdy = pc.y - pd.y; - - bdxcdy = bdx * cdy; - cdxbdy = cdx * bdy; - alift = adx * adx + ady * ady; - - cdxady = cdx * ady; - adxcdy = adx * cdy; - blift = bdx * bdx + bdy * bdy; - - adxbdy = adx * bdy; - bdxady = bdx * ady; - clift = cdx * cdx + cdy * cdy; - - det = alift * (bdxcdy - cdxbdy) - + blift * (cdxady - adxcdy) - + clift * (adxbdy - bdxady); - - if (Behavior.NoExact) - { - return det; - } - - permanent = (Math.Abs(bdxcdy) + Math.Abs(cdxbdy)) * alift - + (Math.Abs(cdxady) + Math.Abs(adxcdy)) * blift - + (Math.Abs(adxbdy) + Math.Abs(bdxady)) * clift; - errbound = iccerrboundA * permanent; - if ((det > errbound) || (-det > errbound)) - { - return det; - } - - Statistic.InCircleAdaptCount++; - return InCircleAdapt(pa, pb, pc, pd, permanent); - } - - /// - /// Return a positive value if the point pd is incompatible with the circle - /// or plane passing through pa, pb, and pc (meaning that pd is inside the - /// circle or below the plane); a negative value if it is compatible; and - /// zero if the four points are cocircular/coplanar. The points pa, pb, and - /// pc must be in counterclockwise order, or the sign of the result will be - /// reversed. - /// - /// Point a. - /// Point b. - /// Point c. - /// Point d. - /// Return a positive value if the point pd lies inside the circle passing through - /// pa, pb, and pc; a negative value if it lies outside; and zero if the four points - /// are cocircular. - public double NonRegular(Point pa, Point pb, Point pc, Point pd) - { - return InCircle(pa, pb, pc, pd); - } - - /// - /// Find the circumcenter of a triangle. - /// - /// Triangle point. - /// Triangle point. - /// Triangle point. - /// Relative coordinate of new location. - /// Relative coordinate of new location. - /// Off-center constant. - /// Coordinates of the circumcenter (or off-center) - public Point FindCircumcenter(Point org, Point dest, Point apex, - ref double xi, ref double eta, double offconstant) - { - double xdo, ydo, xao, yao; - double dodist, aodist, dadist; - double denominator; - double dx, dy, dxoff, dyoff; - - Statistic.CircumcenterCount++; - - // Compute the circumcenter of the triangle. - xdo = dest.x - org.x; - ydo = dest.y - org.y; - xao = apex.x - org.x; - yao = apex.y - org.y; - dodist = xdo * xdo + ydo * ydo; - aodist = xao * xao + yao * yao; - dadist = (dest.x - apex.x) * (dest.x - apex.x) + - (dest.y - apex.y) * (dest.y - apex.y); - - if (Behavior.NoExact) - { - denominator = 0.5 / (xdo * yao - xao * ydo); - } - else - { - // Use the counterclockwise() routine to ensure a positive (and - // reasonably accurate) result, avoiding any possibility of - // division by zero. - denominator = 0.5 / CounterClockwise(dest, apex, org); - // Don't count the above as an orientation test. - Statistic.CounterClockwiseCount--; - } - - dx = (yao * dodist - ydo * aodist) * denominator; - dy = (xdo * aodist - xao * dodist) * denominator; - - // Find the (squared) length of the triangle's shortest edge. This - // serves as a conservative estimate of the insertion radius of the - // circumcenter's parent. The estimate is used to ensure that - // the algorithm terminates even if very small angles appear in - // the input PSLG. - if ((dodist < aodist) && (dodist < dadist)) - { - if (offconstant > 0.0) - { - // Find the position of the off-center, as described by Alper Ungor. - dxoff = 0.5 * xdo - offconstant * ydo; - dyoff = 0.5 * ydo + offconstant * xdo; - // If the off-center is closer to the origin than the - // circumcenter, use the off-center instead. - if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) - { - dx = dxoff; - dy = dyoff; - } - } - } - else if (aodist < dadist) - { - if (offconstant > 0.0) - { - dxoff = 0.5 * xao + offconstant * yao; - dyoff = 0.5 * yao - offconstant * xao; - // If the off-center is closer to the origin than the - // circumcenter, use the off-center instead. - if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) - { - dx = dxoff; - dy = dyoff; - } - } - } - else - { - if (offconstant > 0.0) - { - dxoff = 0.5 * (apex.x - dest.x) - offconstant * (apex.y - dest.y); - dyoff = 0.5 * (apex.y - dest.y) + offconstant * (apex.x - dest.x); - // If the off-center is closer to the destination than the - // circumcenter, use the off-center instead. - if (dxoff * dxoff + dyoff * dyoff < - (dx - xdo) * (dx - xdo) + (dy - ydo) * (dy - ydo)) - { - dx = xdo + dxoff; - dy = ydo + dyoff; - } - } - } - - // To interpolate vertex attributes for the new vertex inserted at - // the circumcenter, define a coordinate system with a xi-axis, - // directed from the triangle's origin to its destination, and - // an eta-axis, directed from its origin to its apex. - // Calculate the xi and eta coordinates of the circumcenter. - xi = (yao * dx - xao * dy) * (2.0 * denominator); - eta = (xdo * dy - ydo * dx) * (2.0 * denominator); - - return new Point(org.x + dx, org.y + dy); - } - - /// - /// Find the circumcenter of a triangle. - /// - /// Triangle point. - /// Triangle point. - /// Triangle point. - /// Relative coordinate of new location. - /// Relative coordinate of new location. - /// Coordinates of the circumcenter - /// - /// The result is returned both in terms of x-y coordinates and xi-eta - /// (barycentric) coordinates. The xi-eta coordinate system is defined in - /// terms of the triangle: the origin of the triangle is the origin of the - /// coordinate system; the destination of the triangle is one unit along the - /// xi axis; and the apex of the triangle is one unit along the eta axis. - /// This procedure also returns the square of the length of the triangle's - /// shortest edge. - /// - public Point FindCircumcenter(Point org, Point dest, Point apex, - ref double xi, ref double eta) - { - double xdo, ydo, xao, yao; - double dodist, aodist; - double denominator; - double dx, dy; - - Statistic.CircumcenterCount++; - - // Compute the circumcenter of the triangle. - xdo = dest.x - org.x; - ydo = dest.y - org.y; - xao = apex.x - org.x; - yao = apex.y - org.y; - dodist = xdo * xdo + ydo * ydo; - aodist = xao * xao + yao * yao; - - if (Behavior.NoExact) - { - denominator = 0.5 / (xdo * yao - xao * ydo); - } - else - { - // Use the counterclockwise() routine to ensure a positive (and - // reasonably accurate) result, avoiding any possibility of - // division by zero. - denominator = 0.5 / CounterClockwise(dest, apex, org); - // Don't count the above as an orientation test. - Statistic.CounterClockwiseCount--; - } - - dx = (yao * dodist - ydo * aodist) * denominator; - dy = (xdo * aodist - xao * dodist) * denominator; - - // To interpolate vertex attributes for the new vertex inserted at - // the circumcenter, define a coordinate system with a xi-axis, - // directed from the triangle's origin to its destination, and - // an eta-axis, directed from its origin to its apex. - // Calculate the xi and eta coordinates of the circumcenter. - xi = (yao * dx - xao * dy) * (2.0 * denominator); - eta = (xdo * dy - ydo * dx) * (2.0 * denominator); - - return new Point(org.x + dx, org.y + dy); - } - - #region Exact arithmetics - - /// - /// Sum two expansions, eliminating zero components from the output expansion. - /// - /// - /// - /// - /// - /// - /// - /// - /// Sets h = e + f. See the Robust Predicates paper for details. - /// - /// If round-to-even is used (as with IEEE 754), maintains the strongly nonoverlapping - /// property. (That is, if e is strongly nonoverlapping, h will be also.) Does NOT - /// maintain the nonoverlapping or nonadjacent properties. - /// - private int FastExpansionSumZeroElim(int elen, double[] e, int flen, double[] f, double[] h) - { - double Q; - double Qnew; - double hh; - double bvirt; - double avirt, bround, around; - int eindex, findex, hindex; - double enow, fnow; - - enow = e[0]; - fnow = f[0]; - eindex = findex = 0; - if ((fnow > enow) == (fnow > -enow)) - { - Q = enow; - enow = e[++eindex]; - } - else - { - Q = fnow; - fnow = f[++findex]; - } - hindex = 0; - if ((eindex < elen) && (findex < flen)) - { - if ((fnow > enow) == (fnow > -enow)) - { - Qnew = (double)(enow + Q); bvirt = Qnew - enow; hh = Q - bvirt; - enow = e[++eindex]; - } - else - { - Qnew = (double)(fnow + Q); bvirt = Qnew - fnow; hh = Q - bvirt; - fnow = f[++findex]; - } - Q = Qnew; - if (hh != 0.0) - { - h[hindex++] = hh; - } - while ((eindex < elen) && (findex < flen)) - { - if ((fnow > enow) == (fnow > -enow)) - { - Qnew = (double)(Q + enow); - bvirt = (double)(Qnew - Q); - avirt = Qnew - bvirt; - bround = enow - bvirt; - around = Q - avirt; - hh = around + bround; - - enow = e[++eindex]; - } - else - { - Qnew = (double)(Q + fnow); - bvirt = (double)(Qnew - Q); - avirt = Qnew - bvirt; - bround = fnow - bvirt; - around = Q - avirt; - hh = around + bround; - - fnow = f[++findex]; - } - Q = Qnew; - if (hh != 0.0) - { - h[hindex++] = hh; - } - } - } - while (eindex < elen) - { - Qnew = (double)(Q + enow); - bvirt = (double)(Qnew - Q); - avirt = Qnew - bvirt; - bround = enow - bvirt; - around = Q - avirt; - hh = around + bround; - - enow = e[++eindex]; - Q = Qnew; - if (hh != 0.0) - { - h[hindex++] = hh; - } - } - while (findex < flen) - { - Qnew = (double)(Q + fnow); - bvirt = (double)(Qnew - Q); - avirt = Qnew - bvirt; - bround = fnow - bvirt; - around = Q - avirt; - hh = around + bround; - - fnow = f[++findex]; - Q = Qnew; - if (hh != 0.0) - { - h[hindex++] = hh; - } - } - if ((Q != 0.0) || (hindex == 0)) - { - h[hindex++] = Q; - } - return hindex; - } - - /// - /// Multiply an expansion by a scalar, eliminating zero components from the output expansion. - /// - /// - /// - /// - /// - /// - /// - /// Sets h = be. See my Robust Predicates paper for details. - /// - /// Maintains the nonoverlapping property. If round-to-even is used (as with IEEE 754), - /// maintains the strongly nonoverlapping and nonadjacent properties as well. (That is, - /// if e has one of these properties, so will h.) - /// - private int ScaleExpansionZeroElim(int elen, double[] e, double b, double[] h) - { - double Q, sum; - double hh; - double product1; - double product0; - int eindex, hindex; - double enow; - double bvirt; - double avirt, bround, around; - double c; - double abig; - double ahi, alo, bhi, blo; - double err1, err2, err3; - - c = (double)(splitter * b); abig = (double)(c - b); bhi = c - abig; blo = b - bhi; - Q = (double)(e[0] * b); c = (double)(splitter * e[0]); abig = (double)(c - e[0]); ahi = c - abig; alo = e[0] - ahi; err1 = Q - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); hh = (alo * blo) - err3; - hindex = 0; - if (hh != 0) - { - h[hindex++] = hh; - } - for (eindex = 1; eindex < elen; eindex++) - { - enow = e[eindex]; - product1 = (double)(enow * b); c = (double)(splitter * enow); abig = (double)(c - enow); ahi = c - abig; alo = enow - ahi; err1 = product1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); product0 = (alo * blo) - err3; - sum = (double)(Q + product0); bvirt = (double)(sum - Q); avirt = sum - bvirt; bround = product0 - bvirt; around = Q - avirt; hh = around + bround; - if (hh != 0) - { - h[hindex++] = hh; - } - Q = (double)(product1 + sum); bvirt = Q - product1; hh = sum - bvirt; - if (hh != 0) - { - h[hindex++] = hh; - } - } - if ((Q != 0.0) || (hindex == 0)) - { - h[hindex++] = Q; - } - return hindex; - } - - /// - /// Produce a one-word estimate of an expansion's value. - /// - /// - /// - /// - private double Estimate(int elen, double[] e) - { - double Q; - int eindex; - - Q = e[0]; - for (eindex = 1; eindex < elen; eindex++) - { - Q += e[eindex]; - } - return Q; - } - - /// - /// Return a positive value if the points pa, pb, and pc occur in counterclockwise - /// order; a negative value if they occur in clockwise order; and zero if they are - /// collinear. The result is also a rough approximation of twice the signed area of - /// the triangle defined by the three points. - /// - /// - /// - /// - /// - /// - /// - /// Uses exact arithmetic if necessary to ensure a correct answer. The result returned - /// is the determinant of a matrix. This determinant is computed adaptively, in the - /// sense that exact arithmetic is used only to the degree it is needed to ensure that - /// the returned value has the correct sign. Hence, this function is usually quite fast, - /// but will run more slowly when the input points are collinear or nearly so. - /// - private double CounterClockwiseAdapt(Point pa, Point pb, Point pc, double detsum) - { - double acx, acy, bcx, bcy; - double acxtail, acytail, bcxtail, bcytail; - double detleft, detright; - double detlefttail, detrighttail; - double det, errbound; - // Edited to work around index out of range exceptions (changed array length from 4 to 5). - // See unsafe indexing in FastExpansionSumZeroElim. - double[] B = new double[5], u = new double[5]; - double[] C1 = new double[8], C2 = new double[12], D = new double[16]; - double B3; - int C1length, C2length, Dlength; - - double u3; - double s1, t1; - double s0, t0; - - double bvirt; - double avirt, bround, around; - double c; - double abig; - double ahi, alo, bhi, blo; - double err1, err2, err3; - double _i, _j; - double _0; - - acx = (double)(pa.x - pc.x); - bcx = (double)(pb.x - pc.x); - acy = (double)(pa.y - pc.y); - bcy = (double)(pb.y - pc.y); - - detleft = (double)(acx * bcy); c = (double)(splitter * acx); abig = (double)(c - acx); ahi = c - abig; alo = acx - ahi; c = (double)(splitter * bcy); abig = (double)(c - bcy); bhi = c - abig; blo = bcy - bhi; err1 = detleft - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); detlefttail = (alo * blo) - err3; - detright = (double)(acy * bcx); c = (double)(splitter * acy); abig = (double)(c - acy); ahi = c - abig; alo = acy - ahi; c = (double)(splitter * bcx); abig = (double)(c - bcx); bhi = c - abig; blo = bcx - bhi; err1 = detright - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); detrighttail = (alo * blo) - err3; - - _i = (double)(detlefttail - detrighttail); bvirt = (double)(detlefttail - _i); avirt = _i + bvirt; bround = bvirt - detrighttail; around = detlefttail - avirt; B[0] = around + bround; _j = (double)(detleft + _i); bvirt = (double)(_j - detleft); avirt = _j - bvirt; bround = _i - bvirt; around = detleft - avirt; _0 = around + bround; _i = (double)(_0 - detright); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - detright; around = _0 - avirt; B[1] = around + bround; B3 = (double)(_j + _i); bvirt = (double)(B3 - _j); avirt = B3 - bvirt; bround = _i - bvirt; around = _j - avirt; B[2] = around + bround; - - B[3] = B3; - - det = Estimate(4, B); - errbound = ccwerrboundB * detsum; - if ((det >= errbound) || (-det >= errbound)) - { - return det; - } - - bvirt = (double)(pa.x - acx); avirt = acx + bvirt; bround = bvirt - pc.x; around = pa.x - avirt; acxtail = around + bround; - bvirt = (double)(pb.x - bcx); avirt = bcx + bvirt; bround = bvirt - pc.x; around = pb.x - avirt; bcxtail = around + bround; - bvirt = (double)(pa.y - acy); avirt = acy + bvirt; bround = bvirt - pc.y; around = pa.y - avirt; acytail = around + bround; - bvirt = (double)(pb.y - bcy); avirt = bcy + bvirt; bround = bvirt - pc.y; around = pb.y - avirt; bcytail = around + bround; - - if ((acxtail == 0.0) && (acytail == 0.0) - && (bcxtail == 0.0) && (bcytail == 0.0)) - { - return det; - } - - errbound = ccwerrboundC * detsum + resulterrbound * ((det) >= 0.0 ? (det) : -(det)); - det += (acx * bcytail + bcy * acxtail) - - (acy * bcxtail + bcx * acytail); - if ((det >= errbound) || (-det >= errbound)) - { - return det; - } - - s1 = (double)(acxtail * bcy); c = (double)(splitter * acxtail); abig = (double)(c - acxtail); ahi = c - abig; alo = acxtail - ahi; c = (double)(splitter * bcy); abig = (double)(c - bcy); bhi = c - abig; blo = bcy - bhi; err1 = s1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); s0 = (alo * blo) - err3; - t1 = (double)(acytail * bcx); c = (double)(splitter * acytail); abig = (double)(c - acytail); ahi = c - abig; alo = acytail - ahi; c = (double)(splitter * bcx); abig = (double)(c - bcx); bhi = c - abig; blo = bcx - bhi; err1 = t1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); t0 = (alo * blo) - err3; - _i = (double)(s0 - t0); bvirt = (double)(s0 - _i); avirt = _i + bvirt; bround = bvirt - t0; around = s0 - avirt; u[0] = around + bround; _j = (double)(s1 + _i); bvirt = (double)(_j - s1); avirt = _j - bvirt; bround = _i - bvirt; around = s1 - avirt; _0 = around + bround; _i = (double)(_0 - t1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - t1; around = _0 - avirt; u[1] = around + bround; u3 = (double)(_j + _i); bvirt = (double)(u3 - _j); avirt = u3 - bvirt; bround = _i - bvirt; around = _j - avirt; u[2] = around + bround; - u[3] = u3; - C1length = FastExpansionSumZeroElim(4, B, 4, u, C1); - - s1 = (double)(acx * bcytail); c = (double)(splitter * acx); abig = (double)(c - acx); ahi = c - abig; alo = acx - ahi; c = (double)(splitter * bcytail); abig = (double)(c - bcytail); bhi = c - abig; blo = bcytail - bhi; err1 = s1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); s0 = (alo * blo) - err3; - t1 = (double)(acy * bcxtail); c = (double)(splitter * acy); abig = (double)(c - acy); ahi = c - abig; alo = acy - ahi; c = (double)(splitter * bcxtail); abig = (double)(c - bcxtail); bhi = c - abig; blo = bcxtail - bhi; err1 = t1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); t0 = (alo * blo) - err3; - _i = (double)(s0 - t0); bvirt = (double)(s0 - _i); avirt = _i + bvirt; bround = bvirt - t0; around = s0 - avirt; u[0] = around + bround; _j = (double)(s1 + _i); bvirt = (double)(_j - s1); avirt = _j - bvirt; bround = _i - bvirt; around = s1 - avirt; _0 = around + bround; _i = (double)(_0 - t1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - t1; around = _0 - avirt; u[1] = around + bround; u3 = (double)(_j + _i); bvirt = (double)(u3 - _j); avirt = u3 - bvirt; bround = _i - bvirt; around = _j - avirt; u[2] = around + bround; - u[3] = u3; - C2length = FastExpansionSumZeroElim(C1length, C1, 4, u, C2); - - s1 = (double)(acxtail * bcytail); c = (double)(splitter * acxtail); abig = (double)(c - acxtail); ahi = c - abig; alo = acxtail - ahi; c = (double)(splitter * bcytail); abig = (double)(c - bcytail); bhi = c - abig; blo = bcytail - bhi; err1 = s1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); s0 = (alo * blo) - err3; - t1 = (double)(acytail * bcxtail); c = (double)(splitter * acytail); abig = (double)(c - acytail); ahi = c - abig; alo = acytail - ahi; c = (double)(splitter * bcxtail); abig = (double)(c - bcxtail); bhi = c - abig; blo = bcxtail - bhi; err1 = t1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); t0 = (alo * blo) - err3; - _i = (double)(s0 - t0); bvirt = (double)(s0 - _i); avirt = _i + bvirt; bround = bvirt - t0; around = s0 - avirt; u[0] = around + bround; _j = (double)(s1 + _i); bvirt = (double)(_j - s1); avirt = _j - bvirt; bround = _i - bvirt; around = s1 - avirt; _0 = around + bround; _i = (double)(_0 - t1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - t1; around = _0 - avirt; u[1] = around + bround; u3 = (double)(_j + _i); bvirt = (double)(u3 - _j); avirt = u3 - bvirt; bround = _i - bvirt; around = _j - avirt; u[2] = around + bround; - u[3] = u3; - Dlength = FastExpansionSumZeroElim(C2length, C2, 4, u, D); - - return (D[Dlength - 1]); - } - - /// - /// Return a positive value if the point pd lies inside the circle passing through - /// pa, pb, and pc; a negative value if it lies outside; and zero if the four points - /// are cocircular. The points pa, pb, and pc must be in counterclockwise order, or - /// the sign of the result will be reversed. - /// - /// - /// - /// - /// - /// - /// - /// - /// Uses exact arithmetic if necessary to ensure a correct answer. The result returned - /// is the determinant of a matrix. This determinant is computed adaptively, in the - /// sense that exact arithmetic is used only to the degree it is needed to ensure that - /// the returned value has the correct sign. Hence, this function is usually quite fast, - /// but will run more slowly when the input points are cocircular or nearly so. - /// - private double InCircleAdapt(Point pa, Point pb, Point pc, Point pd, double permanent) - { - double adx, bdx, cdx, ady, bdy, cdy; - double det, errbound; - - double bdxcdy1, cdxbdy1, cdxady1, adxcdy1, adxbdy1, bdxady1; - double bdxcdy0, cdxbdy0, cdxady0, adxcdy0, adxbdy0, bdxady0; - double[] bc = new double[4], ca = new double[4], ab = new double[4]; - double bc3, ca3, ab3; - int axbclen, axxbclen, aybclen, ayybclen, alen; - int bxcalen, bxxcalen, bycalen, byycalen, blen; - int cxablen, cxxablen, cyablen, cyyablen, clen; - int ablen; - double[] finnow, finother, finswap; - int finlength; - - double adxtail, bdxtail, cdxtail, adytail, bdytail, cdytail; - double adxadx1, adyady1, bdxbdx1, bdybdy1, cdxcdx1, cdycdy1; - double adxadx0, adyady0, bdxbdx0, bdybdy0, cdxcdx0, cdycdy0; - double[] aa = new double[4], bb = new double[4], cc = new double[4]; - double aa3, bb3, cc3; - double ti1, tj1; - double ti0, tj0; - // Edited to work around index out of range exceptions (changed array length from 4 to 5). - // See unsafe indexing in FastExpansionSumZeroElim. - double[] u = new double[5], v = new double[5]; - double u3, v3; - int temp8len, temp16alen, temp16blen, temp16clen; - int temp32alen, temp32blen, temp48len, temp64len; - double[] axtbb = new double[8], axtcc = new double[8], aytbb = new double[8], aytcc = new double[8]; - int axtbblen, axtcclen, aytbblen, aytcclen; - double[] bxtaa = new double[8], bxtcc = new double[8], bytaa = new double[8], bytcc = new double[8]; - int bxtaalen, bxtcclen, bytaalen, bytcclen; - double[] cxtaa = new double[8], cxtbb = new double[8], cytaa = new double[8], cytbb = new double[8]; - int cxtaalen, cxtbblen, cytaalen, cytbblen; - double[] axtbc = new double[8], aytbc = new double[8], bxtca = new double[8], bytca = new double[8], cxtab = new double[8], cytab = new double[8]; - int axtbclen = 0, aytbclen = 0, bxtcalen = 0, bytcalen = 0, cxtablen = 0, cytablen = 0; - double[] axtbct = new double[16], aytbct = new double[16], bxtcat = new double[16], bytcat = new double[16], cxtabt = new double[16], cytabt = new double[16]; - int axtbctlen, aytbctlen, bxtcatlen, bytcatlen, cxtabtlen, cytabtlen; - double[] axtbctt = new double[8], aytbctt = new double[8], bxtcatt = new double[8]; - double[] bytcatt = new double[8], cxtabtt = new double[8], cytabtt = new double[8]; - int axtbcttlen, aytbcttlen, bxtcattlen, bytcattlen, cxtabttlen, cytabttlen; - double[] abt = new double[8], bct = new double[8], cat = new double[8]; - int abtlen, bctlen, catlen; - double[] abtt = new double[4], bctt = new double[4], catt = new double[4]; - int abttlen, bcttlen, cattlen; - double abtt3, bctt3, catt3; - double negate; - - double bvirt; - double avirt, bround, around; - double c; - double abig; - double ahi, alo, bhi, blo; - double err1, err2, err3; - double _i, _j; - double _0; - - adx = (double)(pa.x - pd.x); - bdx = (double)(pb.x - pd.x); - cdx = (double)(pc.x - pd.x); - ady = (double)(pa.y - pd.y); - bdy = (double)(pb.y - pd.y); - cdy = (double)(pc.y - pd.y); - - adx = (double)(pa.x - pd.x); - bdx = (double)(pb.x - pd.x); - cdx = (double)(pc.x - pd.x); - ady = (double)(pa.y - pd.y); - bdy = (double)(pb.y - pd.y); - cdy = (double)(pc.y - pd.y); - - bdxcdy1 = (double)(bdx * cdy); c = (double)(splitter * bdx); abig = (double)(c - bdx); ahi = c - abig; alo = bdx - ahi; c = (double)(splitter * cdy); abig = (double)(c - cdy); bhi = c - abig; blo = cdy - bhi; err1 = bdxcdy1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); bdxcdy0 = (alo * blo) - err3; - cdxbdy1 = (double)(cdx * bdy); c = (double)(splitter * cdx); abig = (double)(c - cdx); ahi = c - abig; alo = cdx - ahi; c = (double)(splitter * bdy); abig = (double)(c - bdy); bhi = c - abig; blo = bdy - bhi; err1 = cdxbdy1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); cdxbdy0 = (alo * blo) - err3; - _i = (double)(bdxcdy0 - cdxbdy0); bvirt = (double)(bdxcdy0 - _i); avirt = _i + bvirt; bround = bvirt - cdxbdy0; around = bdxcdy0 - avirt; bc[0] = around + bround; _j = (double)(bdxcdy1 + _i); bvirt = (double)(_j - bdxcdy1); avirt = _j - bvirt; bround = _i - bvirt; around = bdxcdy1 - avirt; _0 = around + bround; _i = (double)(_0 - cdxbdy1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - cdxbdy1; around = _0 - avirt; bc[1] = around + bround; bc3 = (double)(_j + _i); bvirt = (double)(bc3 - _j); avirt = bc3 - bvirt; bround = _i - bvirt; around = _j - avirt; bc[2] = around + bround; - bc[3] = bc3; - axbclen = ScaleExpansionZeroElim(4, bc, adx, axbc); - axxbclen = ScaleExpansionZeroElim(axbclen, axbc, adx, axxbc); - aybclen = ScaleExpansionZeroElim(4, bc, ady, aybc); - ayybclen = ScaleExpansionZeroElim(aybclen, aybc, ady, ayybc); - alen = FastExpansionSumZeroElim(axxbclen, axxbc, ayybclen, ayybc, adet); - - cdxady1 = (double)(cdx * ady); c = (double)(splitter * cdx); abig = (double)(c - cdx); ahi = c - abig; alo = cdx - ahi; c = (double)(splitter * ady); abig = (double)(c - ady); bhi = c - abig; blo = ady - bhi; err1 = cdxady1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); cdxady0 = (alo * blo) - err3; - adxcdy1 = (double)(adx * cdy); c = (double)(splitter * adx); abig = (double)(c - adx); ahi = c - abig; alo = adx - ahi; c = (double)(splitter * cdy); abig = (double)(c - cdy); bhi = c - abig; blo = cdy - bhi; err1 = adxcdy1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); adxcdy0 = (alo * blo) - err3; - _i = (double)(cdxady0 - adxcdy0); bvirt = (double)(cdxady0 - _i); avirt = _i + bvirt; bround = bvirt - adxcdy0; around = cdxady0 - avirt; ca[0] = around + bround; _j = (double)(cdxady1 + _i); bvirt = (double)(_j - cdxady1); avirt = _j - bvirt; bround = _i - bvirt; around = cdxady1 - avirt; _0 = around + bround; _i = (double)(_0 - adxcdy1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - adxcdy1; around = _0 - avirt; ca[1] = around + bround; ca3 = (double)(_j + _i); bvirt = (double)(ca3 - _j); avirt = ca3 - bvirt; bround = _i - bvirt; around = _j - avirt; ca[2] = around + bround; - ca[3] = ca3; - bxcalen = ScaleExpansionZeroElim(4, ca, bdx, bxca); - bxxcalen = ScaleExpansionZeroElim(bxcalen, bxca, bdx, bxxca); - bycalen = ScaleExpansionZeroElim(4, ca, bdy, byca); - byycalen = ScaleExpansionZeroElim(bycalen, byca, bdy, byyca); - blen = FastExpansionSumZeroElim(bxxcalen, bxxca, byycalen, byyca, bdet); - - adxbdy1 = (double)(adx * bdy); c = (double)(splitter * adx); abig = (double)(c - adx); ahi = c - abig; alo = adx - ahi; c = (double)(splitter * bdy); abig = (double)(c - bdy); bhi = c - abig; blo = bdy - bhi; err1 = adxbdy1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); adxbdy0 = (alo * blo) - err3; - bdxady1 = (double)(bdx * ady); c = (double)(splitter * bdx); abig = (double)(c - bdx); ahi = c - abig; alo = bdx - ahi; c = (double)(splitter * ady); abig = (double)(c - ady); bhi = c - abig; blo = ady - bhi; err1 = bdxady1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); bdxady0 = (alo * blo) - err3; - _i = (double)(adxbdy0 - bdxady0); bvirt = (double)(adxbdy0 - _i); avirt = _i + bvirt; bround = bvirt - bdxady0; around = adxbdy0 - avirt; ab[0] = around + bround; _j = (double)(adxbdy1 + _i); bvirt = (double)(_j - adxbdy1); avirt = _j - bvirt; bround = _i - bvirt; around = adxbdy1 - avirt; _0 = around + bround; _i = (double)(_0 - bdxady1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - bdxady1; around = _0 - avirt; ab[1] = around + bround; ab3 = (double)(_j + _i); bvirt = (double)(ab3 - _j); avirt = ab3 - bvirt; bround = _i - bvirt; around = _j - avirt; ab[2] = around + bround; - ab[3] = ab3; - cxablen = ScaleExpansionZeroElim(4, ab, cdx, cxab); - cxxablen = ScaleExpansionZeroElim(cxablen, cxab, cdx, cxxab); - cyablen = ScaleExpansionZeroElim(4, ab, cdy, cyab); - cyyablen = ScaleExpansionZeroElim(cyablen, cyab, cdy, cyyab); - clen = FastExpansionSumZeroElim(cxxablen, cxxab, cyyablen, cyyab, cdet); - - ablen = FastExpansionSumZeroElim(alen, adet, blen, bdet, abdet); - finlength = FastExpansionSumZeroElim(ablen, abdet, clen, cdet, fin1); - - det = Estimate(finlength, fin1); - errbound = iccerrboundB * permanent; - if ((det >= errbound) || (-det >= errbound)) - { - return det; - } - - bvirt = (double)(pa.x - adx); avirt = adx + bvirt; bround = bvirt - pd.x; around = pa.x - avirt; adxtail = around + bround; - bvirt = (double)(pa.y - ady); avirt = ady + bvirt; bround = bvirt - pd.y; around = pa.y - avirt; adytail = around + bround; - bvirt = (double)(pb.x - bdx); avirt = bdx + bvirt; bround = bvirt - pd.x; around = pb.x - avirt; bdxtail = around + bround; - bvirt = (double)(pb.y - bdy); avirt = bdy + bvirt; bround = bvirt - pd.y; around = pb.y - avirt; bdytail = around + bround; - bvirt = (double)(pc.x - cdx); avirt = cdx + bvirt; bround = bvirt - pd.x; around = pc.x - avirt; cdxtail = around + bround; - bvirt = (double)(pc.y - cdy); avirt = cdy + bvirt; bround = bvirt - pd.y; around = pc.y - avirt; cdytail = around + bround; - if ((adxtail == 0.0) && (bdxtail == 0.0) && (cdxtail == 0.0) - && (adytail == 0.0) && (bdytail == 0.0) && (cdytail == 0.0)) - { - return det; - } - - errbound = iccerrboundC * permanent + resulterrbound * ((det) >= 0.0 ? (det) : -(det)); - det += ((adx * adx + ady * ady) * ((bdx * cdytail + cdy * bdxtail) - (bdy * cdxtail + cdx * bdytail)) - + 2.0 * (adx * adxtail + ady * adytail) * (bdx * cdy - bdy * cdx)) - + ((bdx * bdx + bdy * bdy) * ((cdx * adytail + ady * cdxtail) - (cdy * adxtail + adx * cdytail)) - + 2.0 * (bdx * bdxtail + bdy * bdytail) * (cdx * ady - cdy * adx)) - + ((cdx * cdx + cdy * cdy) * ((adx * bdytail + bdy * adxtail) - (ady * bdxtail + bdx * adytail)) - + 2.0 * (cdx * cdxtail + cdy * cdytail) * (adx * bdy - ady * bdx)); - if ((det >= errbound) || (-det >= errbound)) - { - return det; - } - - finnow = fin1; - finother = fin2; - - if ((bdxtail != 0.0) || (bdytail != 0.0) || (cdxtail != 0.0) || (cdytail != 0.0)) - { - adxadx1 = (double)(adx * adx); c = (double)(splitter * adx); abig = (double)(c - adx); ahi = c - abig; alo = adx - ahi; err1 = adxadx1 - (ahi * ahi); err3 = err1 - ((ahi + ahi) * alo); adxadx0 = (alo * alo) - err3; - adyady1 = (double)(ady * ady); c = (double)(splitter * ady); abig = (double)(c - ady); ahi = c - abig; alo = ady - ahi; err1 = adyady1 - (ahi * ahi); err3 = err1 - ((ahi + ahi) * alo); adyady0 = (alo * alo) - err3; - _i = (double)(adxadx0 + adyady0); bvirt = (double)(_i - adxadx0); avirt = _i - bvirt; bround = adyady0 - bvirt; around = adxadx0 - avirt; aa[0] = around + bround; _j = (double)(adxadx1 + _i); bvirt = (double)(_j - adxadx1); avirt = _j - bvirt; bround = _i - bvirt; around = adxadx1 - avirt; _0 = around + bround; _i = (double)(_0 + adyady1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = adyady1 - bvirt; around = _0 - avirt; aa[1] = around + bround; aa3 = (double)(_j + _i); bvirt = (double)(aa3 - _j); avirt = aa3 - bvirt; bround = _i - bvirt; around = _j - avirt; aa[2] = around + bround; - aa[3] = aa3; - } - if ((cdxtail != 0.0) || (cdytail != 0.0) || (adxtail != 0.0) || (adytail != 0.0)) - { - bdxbdx1 = (double)(bdx * bdx); c = (double)(splitter * bdx); abig = (double)(c - bdx); ahi = c - abig; alo = bdx - ahi; err1 = bdxbdx1 - (ahi * ahi); err3 = err1 - ((ahi + ahi) * alo); bdxbdx0 = (alo * alo) - err3; - bdybdy1 = (double)(bdy * bdy); c = (double)(splitter * bdy); abig = (double)(c - bdy); ahi = c - abig; alo = bdy - ahi; err1 = bdybdy1 - (ahi * ahi); err3 = err1 - ((ahi + ahi) * alo); bdybdy0 = (alo * alo) - err3; - _i = (double)(bdxbdx0 + bdybdy0); bvirt = (double)(_i - bdxbdx0); avirt = _i - bvirt; bround = bdybdy0 - bvirt; around = bdxbdx0 - avirt; bb[0] = around + bround; _j = (double)(bdxbdx1 + _i); bvirt = (double)(_j - bdxbdx1); avirt = _j - bvirt; bround = _i - bvirt; around = bdxbdx1 - avirt; _0 = around + bround; _i = (double)(_0 + bdybdy1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = bdybdy1 - bvirt; around = _0 - avirt; bb[1] = around + bround; bb3 = (double)(_j + _i); bvirt = (double)(bb3 - _j); avirt = bb3 - bvirt; bround = _i - bvirt; around = _j - avirt; bb[2] = around + bround; - bb[3] = bb3; - } - if ((adxtail != 0.0) || (adytail != 0.0) || (bdxtail != 0.0) || (bdytail != 0.0)) - { - cdxcdx1 = (double)(cdx * cdx); c = (double)(splitter * cdx); abig = (double)(c - cdx); ahi = c - abig; alo = cdx - ahi; err1 = cdxcdx1 - (ahi * ahi); err3 = err1 - ((ahi + ahi) * alo); cdxcdx0 = (alo * alo) - err3; - cdycdy1 = (double)(cdy * cdy); c = (double)(splitter * cdy); abig = (double)(c - cdy); ahi = c - abig; alo = cdy - ahi; err1 = cdycdy1 - (ahi * ahi); err3 = err1 - ((ahi + ahi) * alo); cdycdy0 = (alo * alo) - err3; - _i = (double)(cdxcdx0 + cdycdy0); bvirt = (double)(_i - cdxcdx0); avirt = _i - bvirt; bround = cdycdy0 - bvirt; around = cdxcdx0 - avirt; cc[0] = around + bround; _j = (double)(cdxcdx1 + _i); bvirt = (double)(_j - cdxcdx1); avirt = _j - bvirt; bround = _i - bvirt; around = cdxcdx1 - avirt; _0 = around + bround; _i = (double)(_0 + cdycdy1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = cdycdy1 - bvirt; around = _0 - avirt; cc[1] = around + bround; cc3 = (double)(_j + _i); bvirt = (double)(cc3 - _j); avirt = cc3 - bvirt; bround = _i - bvirt; around = _j - avirt; cc[2] = around + bround; - cc[3] = cc3; - } - - if (adxtail != 0.0) - { - axtbclen = ScaleExpansionZeroElim(4, bc, adxtail, axtbc); - temp16alen = ScaleExpansionZeroElim(axtbclen, axtbc, 2.0 * adx, temp16a); - - axtcclen = ScaleExpansionZeroElim(4, cc, adxtail, axtcc); - temp16blen = ScaleExpansionZeroElim(axtcclen, axtcc, bdy, temp16b); - - axtbblen = ScaleExpansionZeroElim(4, bb, adxtail, axtbb); - temp16clen = ScaleExpansionZeroElim(axtbblen, axtbb, -cdy, temp16c); - - temp32alen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32a); - temp48len = FastExpansionSumZeroElim(temp16clen, temp16c, temp32alen, temp32a, temp48); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); - finswap = finnow; finnow = finother; finother = finswap; - } - if (adytail != 0.0) - { - aytbclen = ScaleExpansionZeroElim(4, bc, adytail, aytbc); - temp16alen = ScaleExpansionZeroElim(aytbclen, aytbc, 2.0 * ady, temp16a); - - aytbblen = ScaleExpansionZeroElim(4, bb, adytail, aytbb); - temp16blen = ScaleExpansionZeroElim(aytbblen, aytbb, cdx, temp16b); - - aytcclen = ScaleExpansionZeroElim(4, cc, adytail, aytcc); - temp16clen = ScaleExpansionZeroElim(aytcclen, aytcc, -bdx, temp16c); - - temp32alen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32a); - temp48len = FastExpansionSumZeroElim(temp16clen, temp16c, temp32alen, temp32a, temp48); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); - finswap = finnow; finnow = finother; finother = finswap; - } - if (bdxtail != 0.0) - { - bxtcalen = ScaleExpansionZeroElim(4, ca, bdxtail, bxtca); - temp16alen = ScaleExpansionZeroElim(bxtcalen, bxtca, 2.0 * bdx, temp16a); - - bxtaalen = ScaleExpansionZeroElim(4, aa, bdxtail, bxtaa); - temp16blen = ScaleExpansionZeroElim(bxtaalen, bxtaa, cdy, temp16b); - - bxtcclen = ScaleExpansionZeroElim(4, cc, bdxtail, bxtcc); - temp16clen = ScaleExpansionZeroElim(bxtcclen, bxtcc, -ady, temp16c); - - temp32alen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32a); - temp48len = FastExpansionSumZeroElim(temp16clen, temp16c, temp32alen, temp32a, temp48); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); - finswap = finnow; finnow = finother; finother = finswap; - } - if (bdytail != 0.0) - { - bytcalen = ScaleExpansionZeroElim(4, ca, bdytail, bytca); - temp16alen = ScaleExpansionZeroElim(bytcalen, bytca, 2.0 * bdy, temp16a); - - bytcclen = ScaleExpansionZeroElim(4, cc, bdytail, bytcc); - temp16blen = ScaleExpansionZeroElim(bytcclen, bytcc, adx, temp16b); - - bytaalen = ScaleExpansionZeroElim(4, aa, bdytail, bytaa); - temp16clen = ScaleExpansionZeroElim(bytaalen, bytaa, -cdx, temp16c); - - temp32alen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32a); - temp48len = FastExpansionSumZeroElim(temp16clen, temp16c, temp32alen, temp32a, temp48); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); - finswap = finnow; finnow = finother; finother = finswap; - } - if (cdxtail != 0.0) - { - cxtablen = ScaleExpansionZeroElim(4, ab, cdxtail, cxtab); - temp16alen = ScaleExpansionZeroElim(cxtablen, cxtab, 2.0 * cdx, temp16a); - - cxtbblen = ScaleExpansionZeroElim(4, bb, cdxtail, cxtbb); - temp16blen = ScaleExpansionZeroElim(cxtbblen, cxtbb, ady, temp16b); - - cxtaalen = ScaleExpansionZeroElim(4, aa, cdxtail, cxtaa); - temp16clen = ScaleExpansionZeroElim(cxtaalen, cxtaa, -bdy, temp16c); - - temp32alen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32a); - temp48len = FastExpansionSumZeroElim(temp16clen, temp16c, temp32alen, temp32a, temp48); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); - finswap = finnow; finnow = finother; finother = finswap; - } - if (cdytail != 0.0) - { - cytablen = ScaleExpansionZeroElim(4, ab, cdytail, cytab); - temp16alen = ScaleExpansionZeroElim(cytablen, cytab, 2.0 * cdy, temp16a); - - cytaalen = ScaleExpansionZeroElim(4, aa, cdytail, cytaa); - temp16blen = ScaleExpansionZeroElim(cytaalen, cytaa, bdx, temp16b); - - cytbblen = ScaleExpansionZeroElim(4, bb, cdytail, cytbb); - temp16clen = ScaleExpansionZeroElim(cytbblen, cytbb, -adx, temp16c); - - temp32alen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32a); - temp48len = FastExpansionSumZeroElim(temp16clen, temp16c, temp32alen, temp32a, temp48); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); - finswap = finnow; finnow = finother; finother = finswap; - } - - if ((adxtail != 0.0) || (adytail != 0.0)) - { - if ((bdxtail != 0.0) || (bdytail != 0.0) - || (cdxtail != 0.0) || (cdytail != 0.0)) - { - ti1 = (double)(bdxtail * cdy); c = (double)(splitter * bdxtail); abig = (double)(c - bdxtail); ahi = c - abig; alo = bdxtail - ahi; c = (double)(splitter * cdy); abig = (double)(c - cdy); bhi = c - abig; blo = cdy - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; - tj1 = (double)(bdx * cdytail); c = (double)(splitter * bdx); abig = (double)(c - bdx); ahi = c - abig; alo = bdx - ahi; c = (double)(splitter * cdytail); abig = (double)(c - cdytail); bhi = c - abig; blo = cdytail - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; - _i = (double)(ti0 + tj0); bvirt = (double)(_i - ti0); avirt = _i - bvirt; bround = tj0 - bvirt; around = ti0 - avirt; u[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 + tj1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = tj1 - bvirt; around = _0 - avirt; u[1] = around + bround; u3 = (double)(_j + _i); bvirt = (double)(u3 - _j); avirt = u3 - bvirt; bround = _i - bvirt; around = _j - avirt; u[2] = around + bround; - u[3] = u3; - negate = -bdy; - ti1 = (double)(cdxtail * negate); c = (double)(splitter * cdxtail); abig = (double)(c - cdxtail); ahi = c - abig; alo = cdxtail - ahi; c = (double)(splitter * negate); abig = (double)(c - negate); bhi = c - abig; blo = negate - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; - negate = -bdytail; - tj1 = (double)(cdx * negate); c = (double)(splitter * cdx); abig = (double)(c - cdx); ahi = c - abig; alo = cdx - ahi; c = (double)(splitter * negate); abig = (double)(c - negate); bhi = c - abig; blo = negate - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; - _i = (double)(ti0 + tj0); bvirt = (double)(_i - ti0); avirt = _i - bvirt; bround = tj0 - bvirt; around = ti0 - avirt; v[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 + tj1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = tj1 - bvirt; around = _0 - avirt; v[1] = around + bround; v3 = (double)(_j + _i); bvirt = (double)(v3 - _j); avirt = v3 - bvirt; bround = _i - bvirt; around = _j - avirt; v[2] = around + bround; - v[3] = v3; - bctlen = FastExpansionSumZeroElim(4, u, 4, v, bct); - - ti1 = (double)(bdxtail * cdytail); c = (double)(splitter * bdxtail); abig = (double)(c - bdxtail); ahi = c - abig; alo = bdxtail - ahi; c = (double)(splitter * cdytail); abig = (double)(c - cdytail); bhi = c - abig; blo = cdytail - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; - tj1 = (double)(cdxtail * bdytail); c = (double)(splitter * cdxtail); abig = (double)(c - cdxtail); ahi = c - abig; alo = cdxtail - ahi; c = (double)(splitter * bdytail); abig = (double)(c - bdytail); bhi = c - abig; blo = bdytail - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; - _i = (double)(ti0 - tj0); bvirt = (double)(ti0 - _i); avirt = _i + bvirt; bround = bvirt - tj0; around = ti0 - avirt; bctt[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 - tj1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - tj1; around = _0 - avirt; bctt[1] = around + bround; bctt3 = (double)(_j + _i); bvirt = (double)(bctt3 - _j); avirt = bctt3 - bvirt; bround = _i - bvirt; around = _j - avirt; bctt[2] = around + bround; - bctt[3] = bctt3; - bcttlen = 4; - } - else - { - bct[0] = 0.0; - bctlen = 1; - bctt[0] = 0.0; - bcttlen = 1; - } - - if (adxtail != 0.0) - { - temp16alen = ScaleExpansionZeroElim(axtbclen, axtbc, adxtail, temp16a); - axtbctlen = ScaleExpansionZeroElim(bctlen, bct, adxtail, axtbct); - temp32alen = ScaleExpansionZeroElim(axtbctlen, axtbct, 2.0 * adx, temp32a); - temp48len = FastExpansionSumZeroElim(temp16alen, temp16a, temp32alen, temp32a, temp48); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); - finswap = finnow; finnow = finother; finother = finswap; - if (bdytail != 0.0) - { - temp8len = ScaleExpansionZeroElim(4, cc, adxtail, temp8); - temp16alen = ScaleExpansionZeroElim(temp8len, temp8, bdytail, temp16a); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp16alen, temp16a, finother); - finswap = finnow; finnow = finother; finother = finswap; - } - if (cdytail != 0.0) - { - temp8len = ScaleExpansionZeroElim(4, bb, -adxtail, temp8); - temp16alen = ScaleExpansionZeroElim(temp8len, temp8, cdytail, temp16a); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp16alen, temp16a, finother); - finswap = finnow; finnow = finother; finother = finswap; - } - - temp32alen = ScaleExpansionZeroElim(axtbctlen, axtbct, adxtail, temp32a); - axtbcttlen = ScaleExpansionZeroElim(bcttlen, bctt, adxtail, axtbctt); - temp16alen = ScaleExpansionZeroElim(axtbcttlen, axtbctt, 2.0 * adx, temp16a); - temp16blen = ScaleExpansionZeroElim(axtbcttlen, axtbctt, adxtail, temp16b); - temp32blen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32b); - temp64len = FastExpansionSumZeroElim(temp32alen, temp32a, temp32blen, temp32b, temp64); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp64len, temp64, finother); - finswap = finnow; finnow = finother; finother = finswap; - } - if (adytail != 0.0) - { - temp16alen = ScaleExpansionZeroElim(aytbclen, aytbc, adytail, temp16a); - aytbctlen = ScaleExpansionZeroElim(bctlen, bct, adytail, aytbct); - temp32alen = ScaleExpansionZeroElim(aytbctlen, aytbct, 2.0 * ady, temp32a); - temp48len = FastExpansionSumZeroElim(temp16alen, temp16a, temp32alen, temp32a, temp48); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); - finswap = finnow; finnow = finother; finother = finswap; - - - temp32alen = ScaleExpansionZeroElim(aytbctlen, aytbct, adytail, temp32a); - aytbcttlen = ScaleExpansionZeroElim(bcttlen, bctt, adytail, aytbctt); - temp16alen = ScaleExpansionZeroElim(aytbcttlen, aytbctt, 2.0 * ady, temp16a); - temp16blen = ScaleExpansionZeroElim(aytbcttlen, aytbctt, adytail, temp16b); - temp32blen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32b); - temp64len = FastExpansionSumZeroElim(temp32alen, temp32a, temp32blen, temp32b, temp64); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp64len, temp64, finother); - finswap = finnow; finnow = finother; finother = finswap; - } - } - if ((bdxtail != 0.0) || (bdytail != 0.0)) - { - if ((cdxtail != 0.0) || (cdytail != 0.0) - || (adxtail != 0.0) || (adytail != 0.0)) - { - ti1 = (double)(cdxtail * ady); c = (double)(splitter * cdxtail); abig = (double)(c - cdxtail); ahi = c - abig; alo = cdxtail - ahi; c = (double)(splitter * ady); abig = (double)(c - ady); bhi = c - abig; blo = ady - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; - tj1 = (double)(cdx * adytail); c = (double)(splitter * cdx); abig = (double)(c - cdx); ahi = c - abig; alo = cdx - ahi; c = (double)(splitter * adytail); abig = (double)(c - adytail); bhi = c - abig; blo = adytail - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; - _i = (double)(ti0 + tj0); bvirt = (double)(_i - ti0); avirt = _i - bvirt; bround = tj0 - bvirt; around = ti0 - avirt; u[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 + tj1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = tj1 - bvirt; around = _0 - avirt; u[1] = around + bround; u3 = (double)(_j + _i); bvirt = (double)(u3 - _j); avirt = u3 - bvirt; bround = _i - bvirt; around = _j - avirt; u[2] = around + bround; - u[3] = u3; - negate = -cdy; - ti1 = (double)(adxtail * negate); c = (double)(splitter * adxtail); abig = (double)(c - adxtail); ahi = c - abig; alo = adxtail - ahi; c = (double)(splitter * negate); abig = (double)(c - negate); bhi = c - abig; blo = negate - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; - negate = -cdytail; - tj1 = (double)(adx * negate); c = (double)(splitter * adx); abig = (double)(c - adx); ahi = c - abig; alo = adx - ahi; c = (double)(splitter * negate); abig = (double)(c - negate); bhi = c - abig; blo = negate - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; - _i = (double)(ti0 + tj0); bvirt = (double)(_i - ti0); avirt = _i - bvirt; bround = tj0 - bvirt; around = ti0 - avirt; v[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 + tj1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = tj1 - bvirt; around = _0 - avirt; v[1] = around + bround; v3 = (double)(_j + _i); bvirt = (double)(v3 - _j); avirt = v3 - bvirt; bround = _i - bvirt; around = _j - avirt; v[2] = around + bround; - v[3] = v3; - catlen = FastExpansionSumZeroElim(4, u, 4, v, cat); - - ti1 = (double)(cdxtail * adytail); c = (double)(splitter * cdxtail); abig = (double)(c - cdxtail); ahi = c - abig; alo = cdxtail - ahi; c = (double)(splitter * adytail); abig = (double)(c - adytail); bhi = c - abig; blo = adytail - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; - tj1 = (double)(adxtail * cdytail); c = (double)(splitter * adxtail); abig = (double)(c - adxtail); ahi = c - abig; alo = adxtail - ahi; c = (double)(splitter * cdytail); abig = (double)(c - cdytail); bhi = c - abig; blo = cdytail - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; - _i = (double)(ti0 - tj0); bvirt = (double)(ti0 - _i); avirt = _i + bvirt; bround = bvirt - tj0; around = ti0 - avirt; catt[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 - tj1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - tj1; around = _0 - avirt; catt[1] = around + bround; catt3 = (double)(_j + _i); bvirt = (double)(catt3 - _j); avirt = catt3 - bvirt; bround = _i - bvirt; around = _j - avirt; catt[2] = around + bround; - catt[3] = catt3; - cattlen = 4; - } - else - { - cat[0] = 0.0; - catlen = 1; - catt[0] = 0.0; - cattlen = 1; - } - - if (bdxtail != 0.0) - { - temp16alen = ScaleExpansionZeroElim(bxtcalen, bxtca, bdxtail, temp16a); - bxtcatlen = ScaleExpansionZeroElim(catlen, cat, bdxtail, bxtcat); - temp32alen = ScaleExpansionZeroElim(bxtcatlen, bxtcat, 2.0 * bdx, temp32a); - temp48len = FastExpansionSumZeroElim(temp16alen, temp16a, temp32alen, temp32a, temp48); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); - finswap = finnow; finnow = finother; finother = finswap; - if (cdytail != 0.0) - { - temp8len = ScaleExpansionZeroElim(4, aa, bdxtail, temp8); - temp16alen = ScaleExpansionZeroElim(temp8len, temp8, cdytail, temp16a); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp16alen, temp16a, finother); - finswap = finnow; finnow = finother; finother = finswap; - } - if (adytail != 0.0) - { - temp8len = ScaleExpansionZeroElim(4, cc, -bdxtail, temp8); - temp16alen = ScaleExpansionZeroElim(temp8len, temp8, adytail, temp16a); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp16alen, temp16a, finother); - finswap = finnow; finnow = finother; finother = finswap; - } - - temp32alen = ScaleExpansionZeroElim(bxtcatlen, bxtcat, bdxtail, temp32a); - bxtcattlen = ScaleExpansionZeroElim(cattlen, catt, bdxtail, bxtcatt); - temp16alen = ScaleExpansionZeroElim(bxtcattlen, bxtcatt, 2.0 * bdx, temp16a); - temp16blen = ScaleExpansionZeroElim(bxtcattlen, bxtcatt, bdxtail, temp16b); - temp32blen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32b); - temp64len = FastExpansionSumZeroElim(temp32alen, temp32a, temp32blen, temp32b, temp64); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp64len, temp64, finother); - finswap = finnow; finnow = finother; finother = finswap; - } - if (bdytail != 0.0) - { - temp16alen = ScaleExpansionZeroElim(bytcalen, bytca, bdytail, temp16a); - bytcatlen = ScaleExpansionZeroElim(catlen, cat, bdytail, bytcat); - temp32alen = ScaleExpansionZeroElim(bytcatlen, bytcat, 2.0 * bdy, temp32a); - temp48len = FastExpansionSumZeroElim(temp16alen, temp16a, temp32alen, temp32a, temp48); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); - finswap = finnow; finnow = finother; finother = finswap; - - temp32alen = ScaleExpansionZeroElim(bytcatlen, bytcat, bdytail, temp32a); - bytcattlen = ScaleExpansionZeroElim(cattlen, catt, bdytail, bytcatt); - temp16alen = ScaleExpansionZeroElim(bytcattlen, bytcatt, 2.0 * bdy, temp16a); - temp16blen = ScaleExpansionZeroElim(bytcattlen, bytcatt, bdytail, temp16b); - temp32blen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32b); - temp64len = FastExpansionSumZeroElim(temp32alen, temp32a, temp32blen, temp32b, temp64); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp64len, temp64, finother); - finswap = finnow; finnow = finother; finother = finswap; - } - } - if ((cdxtail != 0.0) || (cdytail != 0.0)) - { - if ((adxtail != 0.0) || (adytail != 0.0) - || (bdxtail != 0.0) || (bdytail != 0.0)) - { - ti1 = (double)(adxtail * bdy); c = (double)(splitter * adxtail); abig = (double)(c - adxtail); ahi = c - abig; alo = adxtail - ahi; c = (double)(splitter * bdy); abig = (double)(c - bdy); bhi = c - abig; blo = bdy - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; - tj1 = (double)(adx * bdytail); c = (double)(splitter * adx); abig = (double)(c - adx); ahi = c - abig; alo = adx - ahi; c = (double)(splitter * bdytail); abig = (double)(c - bdytail); bhi = c - abig; blo = bdytail - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; - _i = (double)(ti0 + tj0); bvirt = (double)(_i - ti0); avirt = _i - bvirt; bround = tj0 - bvirt; around = ti0 - avirt; u[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 + tj1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = tj1 - bvirt; around = _0 - avirt; u[1] = around + bround; u3 = (double)(_j + _i); bvirt = (double)(u3 - _j); avirt = u3 - bvirt; bround = _i - bvirt; around = _j - avirt; u[2] = around + bround; - u[3] = u3; - negate = -ady; - ti1 = (double)(bdxtail * negate); c = (double)(splitter * bdxtail); abig = (double)(c - bdxtail); ahi = c - abig; alo = bdxtail - ahi; c = (double)(splitter * negate); abig = (double)(c - negate); bhi = c - abig; blo = negate - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; - negate = -adytail; - tj1 = (double)(bdx * negate); c = (double)(splitter * bdx); abig = (double)(c - bdx); ahi = c - abig; alo = bdx - ahi; c = (double)(splitter * negate); abig = (double)(c - negate); bhi = c - abig; blo = negate - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; - _i = (double)(ti0 + tj0); bvirt = (double)(_i - ti0); avirt = _i - bvirt; bround = tj0 - bvirt; around = ti0 - avirt; v[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 + tj1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = tj1 - bvirt; around = _0 - avirt; v[1] = around + bround; v3 = (double)(_j + _i); bvirt = (double)(v3 - _j); avirt = v3 - bvirt; bround = _i - bvirt; around = _j - avirt; v[2] = around + bround; - v[3] = v3; - abtlen = FastExpansionSumZeroElim(4, u, 4, v, abt); - - ti1 = (double)(adxtail * bdytail); c = (double)(splitter * adxtail); abig = (double)(c - adxtail); ahi = c - abig; alo = adxtail - ahi; c = (double)(splitter * bdytail); abig = (double)(c - bdytail); bhi = c - abig; blo = bdytail - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; - tj1 = (double)(bdxtail * adytail); c = (double)(splitter * bdxtail); abig = (double)(c - bdxtail); ahi = c - abig; alo = bdxtail - ahi; c = (double)(splitter * adytail); abig = (double)(c - adytail); bhi = c - abig; blo = adytail - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; - _i = (double)(ti0 - tj0); bvirt = (double)(ti0 - _i); avirt = _i + bvirt; bround = bvirt - tj0; around = ti0 - avirt; abtt[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 - tj1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - tj1; around = _0 - avirt; abtt[1] = around + bround; abtt3 = (double)(_j + _i); bvirt = (double)(abtt3 - _j); avirt = abtt3 - bvirt; bround = _i - bvirt; around = _j - avirt; abtt[2] = around + bround; - abtt[3] = abtt3; - abttlen = 4; - } - else - { - abt[0] = 0.0; - abtlen = 1; - abtt[0] = 0.0; - abttlen = 1; - } - - if (cdxtail != 0.0) - { - temp16alen = ScaleExpansionZeroElim(cxtablen, cxtab, cdxtail, temp16a); - cxtabtlen = ScaleExpansionZeroElim(abtlen, abt, cdxtail, cxtabt); - temp32alen = ScaleExpansionZeroElim(cxtabtlen, cxtabt, 2.0 * cdx, temp32a); - temp48len = FastExpansionSumZeroElim(temp16alen, temp16a, temp32alen, temp32a, temp48); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); - finswap = finnow; finnow = finother; finother = finswap; - if (adytail != 0.0) - { - temp8len = ScaleExpansionZeroElim(4, bb, cdxtail, temp8); - temp16alen = ScaleExpansionZeroElim(temp8len, temp8, adytail, temp16a); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp16alen, temp16a, finother); - finswap = finnow; finnow = finother; finother = finswap; - } - if (bdytail != 0.0) - { - temp8len = ScaleExpansionZeroElim(4, aa, -cdxtail, temp8); - temp16alen = ScaleExpansionZeroElim(temp8len, temp8, bdytail, temp16a); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp16alen, temp16a, finother); - finswap = finnow; finnow = finother; finother = finswap; - } - - temp32alen = ScaleExpansionZeroElim(cxtabtlen, cxtabt, cdxtail, temp32a); - cxtabttlen = ScaleExpansionZeroElim(abttlen, abtt, cdxtail, cxtabtt); - temp16alen = ScaleExpansionZeroElim(cxtabttlen, cxtabtt, 2.0 * cdx, temp16a); - temp16blen = ScaleExpansionZeroElim(cxtabttlen, cxtabtt, cdxtail, temp16b); - temp32blen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32b); - temp64len = FastExpansionSumZeroElim(temp32alen, temp32a, temp32blen, temp32b, temp64); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp64len, temp64, finother); - finswap = finnow; finnow = finother; finother = finswap; - } - if (cdytail != 0.0) - { - temp16alen = ScaleExpansionZeroElim(cytablen, cytab, cdytail, temp16a); - cytabtlen = ScaleExpansionZeroElim(abtlen, abt, cdytail, cytabt); - temp32alen = ScaleExpansionZeroElim(cytabtlen, cytabt, 2.0 * cdy, temp32a); - temp48len = FastExpansionSumZeroElim(temp16alen, temp16a, temp32alen, temp32a, temp48); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); - finswap = finnow; finnow = finother; finother = finswap; - - - temp32alen = ScaleExpansionZeroElim(cytabtlen, cytabt, cdytail, temp32a); - cytabttlen = ScaleExpansionZeroElim(abttlen, abtt, cdytail, cytabtt); - temp16alen = ScaleExpansionZeroElim(cytabttlen, cytabtt, 2.0 * cdy, temp16a); - temp16blen = ScaleExpansionZeroElim(cytabttlen, cytabtt, cdytail, temp16b); - temp32blen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32b); - temp64len = FastExpansionSumZeroElim(temp32alen, temp32a, temp32blen, temp32b, temp64); - finlength = FastExpansionSumZeroElim(finlength, finnow, temp64len, temp64, finother); - finswap = finnow; finnow = finother; finother = finswap; - } - } - - return finnow[finlength - 1]; - } - - #region Workspace - - // InCircleAdapt workspace: - double[] fin1, fin2, abdet; - - double[] axbc, axxbc, aybc, ayybc, adet; - double[] bxca, bxxca, byca, byyca, bdet; - double[] cxab, cxxab, cyab, cyyab, cdet; - - double[] temp8, temp16a, temp16b, temp16c; - double[] temp32a, temp32b, temp48, temp64; - - private void AllocateWorkspace() - { - fin1 = new double[1152]; - fin2 = new double[1152]; - abdet = new double[64]; - - axbc = new double[8]; - axxbc = new double[16]; - aybc = new double[8]; - ayybc = new double[16]; - adet = new double[32]; - - bxca = new double[8]; - bxxca = new double[16]; - byca = new double[8]; - byyca = new double[16]; - bdet = new double[32]; - - cxab = new double[8]; - cxxab = new double[16]; - cyab = new double[8]; - cyyab = new double[16]; - cdet = new double[32]; - - temp8 = new double[8]; - temp16a = new double[16]; - temp16b = new double[16]; - temp16c = new double[16]; - - temp32a = new double[32]; - temp32b = new double[32]; - temp48 = new double[48]; - temp64 = new double[64]; - } - - private void ClearWorkspace() - { - } - - #endregion - - #endregion - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using TriangleNet.Geometry; + using TriangleNet.Tools; + + /// + /// Adaptive exact arithmetic geometric predicates. + /// + /// + /// The adaptive exact arithmetic geometric predicates implemented herein are described in + /// detail in the paper "Adaptive Precision Floating-Point Arithmetic and Fast Robust + /// Geometric Predicates." by Jonathan Richard Shewchuk, see + /// http://www.cs.cmu.edu/~quake/robust.html + /// + /// The macros of the original C code were automatically expanded using the Visual Studio + /// command prompt with the command "CL /P /C EXACT.C", see + /// http://msdn.microsoft.com/en-us/library/8z9z0bx6.aspx + /// + public class RobustPredicates : IPredicates + { + #region Default predicates instance (Singleton) + + private static readonly object creationLock = new object(); + private static RobustPredicates _default; + + /// + /// Gets the default configuration instance. + /// + public static RobustPredicates Default + { + get + { + if (_default == null) + { + lock (creationLock) + { + if (_default == null) + { + _default = new RobustPredicates(); + } + } + } + + return _default; + } + } + + #endregion + + #region Static initialization + + private static double epsilon, splitter, resulterrbound; + private static double ccwerrboundA, ccwerrboundB, ccwerrboundC; + private static double iccerrboundA, iccerrboundB, iccerrboundC; + //private static double o3derrboundA, o3derrboundB, o3derrboundC; + + /// + /// Initialize the variables used for exact arithmetic. + /// + /// + /// 'epsilon' is the largest power of two such that 1.0 + epsilon = 1.0 in + /// floating-point arithmetic. 'epsilon' bounds the relative roundoff + /// error. It is used for floating-point error analysis. + /// + /// 'splitter' is used to split floating-point numbers into two half- + /// length significands for exact multiplication. + /// + /// I imagine that a highly optimizing compiler might be too smart for its + /// own good, and somehow cause this routine to fail, if it pretends that + /// floating-point arithmetic is too much like double arithmetic. + /// + /// Don't change this routine unless you fully understand it. + /// + static RobustPredicates() + { + double half; + double check, lastcheck; + bool every_other; + + every_other = true; + half = 0.5; + epsilon = 1.0; + splitter = 1.0; + check = 1.0; + // Repeatedly divide 'epsilon' by two until it is too small to add to + // one without causing roundoff. (Also check if the sum is equal to + // the previous sum, for machines that round up instead of using exact + // rounding. Not that these routines will work on such machines.) + do + { + lastcheck = check; + epsilon *= half; + if (every_other) + { + splitter *= 2.0; + } + every_other = !every_other; + check = 1.0 + epsilon; + } while ((check != 1.0) && (check != lastcheck)); + splitter += 1.0; + // Error bounds for orientation and incircle tests. + resulterrbound = (3.0 + 8.0 * epsilon) * epsilon; + ccwerrboundA = (3.0 + 16.0 * epsilon) * epsilon; + ccwerrboundB = (2.0 + 12.0 * epsilon) * epsilon; + ccwerrboundC = (9.0 + 64.0 * epsilon) * epsilon * epsilon; + iccerrboundA = (10.0 + 96.0 * epsilon) * epsilon; + iccerrboundB = (4.0 + 48.0 * epsilon) * epsilon; + iccerrboundC = (44.0 + 576.0 * epsilon) * epsilon * epsilon; + //o3derrboundA = (7.0 + 56.0 * epsilon) * epsilon; + //o3derrboundB = (3.0 + 28.0 * epsilon) * epsilon; + //o3derrboundC = (26.0 + 288.0 * epsilon) * epsilon * epsilon; + } + + #endregion + + public RobustPredicates() + { + AllocateWorkspace(); + } + + /// + /// Check, if the three points appear in counterclockwise order. The result is + /// also a rough approximation of twice the signed area of the triangle defined + /// by the three points. + /// + /// Point a. + /// Point b. + /// Point c. + /// Return a positive value if the points pa, pb, and pc occur in + /// counterclockwise order; a negative value if they occur in clockwise order; + /// and zero if they are collinear. + public double CounterClockwise(Point pa, Point pb, Point pc) + { + double detleft, detright, det; + double detsum, errbound; + + Statistic.CounterClockwiseCount++; + + detleft = (pa.x - pc.x) * (pb.y - pc.y); + detright = (pa.y - pc.y) * (pb.x - pc.x); + det = detleft - detright; + + if (Behavior.NoExact) + { + return det; + } + + if (detleft > 0.0) + { + if (detright <= 0.0) + { + return det; + } + else + { + detsum = detleft + detright; + } + } + else if (detleft < 0.0) + { + if (detright >= 0.0) + { + return det; + } + else + { + detsum = -detleft - detright; + } + } + else + { + return det; + } + + errbound = ccwerrboundA * detsum; + if ((det >= errbound) || (-det >= errbound)) + { + return det; + } + + Statistic.CounterClockwiseAdaptCount++; + return CounterClockwiseAdapt(pa, pb, pc, detsum); + } + + /// + /// Check if the point pd lies inside the circle passing through pa, pb, and pc. The + /// points pa, pb, and pc must be in counterclockwise order, or the sign of the result + /// will be reversed. + /// + /// Point a. + /// Point b. + /// Point c. + /// Point d. + /// Return a positive value if the point pd lies inside the circle passing through + /// pa, pb, and pc; a negative value if it lies outside; and zero if the four points + /// are cocircular. + public double InCircle(Point pa, Point pb, Point pc, Point pd) + { + double adx, bdx, cdx, ady, bdy, cdy; + double bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady; + double alift, blift, clift; + double det; + double permanent, errbound; + + Statistic.InCircleCount++; + + adx = pa.x - pd.x; + bdx = pb.x - pd.x; + cdx = pc.x - pd.x; + ady = pa.y - pd.y; + bdy = pb.y - pd.y; + cdy = pc.y - pd.y; + + bdxcdy = bdx * cdy; + cdxbdy = cdx * bdy; + alift = adx * adx + ady * ady; + + cdxady = cdx * ady; + adxcdy = adx * cdy; + blift = bdx * bdx + bdy * bdy; + + adxbdy = adx * bdy; + bdxady = bdx * ady; + clift = cdx * cdx + cdy * cdy; + + det = alift * (bdxcdy - cdxbdy) + + blift * (cdxady - adxcdy) + + clift * (adxbdy - bdxady); + + if (Behavior.NoExact) + { + return det; + } + + permanent = (Math.Abs(bdxcdy) + Math.Abs(cdxbdy)) * alift + + (Math.Abs(cdxady) + Math.Abs(adxcdy)) * blift + + (Math.Abs(adxbdy) + Math.Abs(bdxady)) * clift; + errbound = iccerrboundA * permanent; + if ((det > errbound) || (-det > errbound)) + { + return det; + } + + Statistic.InCircleAdaptCount++; + return InCircleAdapt(pa, pb, pc, pd, permanent); + } + + /// + /// Return a positive value if the point pd is incompatible with the circle + /// or plane passing through pa, pb, and pc (meaning that pd is inside the + /// circle or below the plane); a negative value if it is compatible; and + /// zero if the four points are cocircular/coplanar. The points pa, pb, and + /// pc must be in counterclockwise order, or the sign of the result will be + /// reversed. + /// + /// Point a. + /// Point b. + /// Point c. + /// Point d. + /// Return a positive value if the point pd lies inside the circle passing through + /// pa, pb, and pc; a negative value if it lies outside; and zero if the four points + /// are cocircular. + public double NonRegular(Point pa, Point pb, Point pc, Point pd) + { + return InCircle(pa, pb, pc, pd); + } + + /// + /// Find the circumcenter of a triangle. + /// + /// Triangle point. + /// Triangle point. + /// Triangle point. + /// Relative coordinate of new location. + /// Relative coordinate of new location. + /// Off-center constant. + /// Coordinates of the circumcenter (or off-center) + public Point FindCircumcenter(Point org, Point dest, Point apex, + ref double xi, ref double eta, double offconstant) + { + double xdo, ydo, xao, yao; + double dodist, aodist, dadist; + double denominator; + double dx, dy, dxoff, dyoff; + + Statistic.CircumcenterCount++; + + // Compute the circumcenter of the triangle. + xdo = dest.x - org.x; + ydo = dest.y - org.y; + xao = apex.x - org.x; + yao = apex.y - org.y; + dodist = xdo * xdo + ydo * ydo; + aodist = xao * xao + yao * yao; + dadist = (dest.x - apex.x) * (dest.x - apex.x) + + (dest.y - apex.y) * (dest.y - apex.y); + + if (Behavior.NoExact) + { + denominator = 0.5 / (xdo * yao - xao * ydo); + } + else + { + // Use the counterclockwise() routine to ensure a positive (and + // reasonably accurate) result, avoiding any possibility of + // division by zero. + denominator = 0.5 / CounterClockwise(dest, apex, org); + // Don't count the above as an orientation test. + Statistic.CounterClockwiseCount--; + } + + dx = (yao * dodist - ydo * aodist) * denominator; + dy = (xdo * aodist - xao * dodist) * denominator; + + // Find the (squared) length of the triangle's shortest edge. This + // serves as a conservative estimate of the insertion radius of the + // circumcenter's parent. The estimate is used to ensure that + // the algorithm terminates even if very small angles appear in + // the input PSLG. + if ((dodist < aodist) && (dodist < dadist)) + { + if (offconstant > 0.0) + { + // Find the position of the off-center, as described by Alper Ungor. + dxoff = 0.5 * xdo - offconstant * ydo; + dyoff = 0.5 * ydo + offconstant * xdo; + // If the off-center is closer to the origin than the + // circumcenter, use the off-center instead. + if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) + { + dx = dxoff; + dy = dyoff; + } + } + } + else if (aodist < dadist) + { + if (offconstant > 0.0) + { + dxoff = 0.5 * xao + offconstant * yao; + dyoff = 0.5 * yao - offconstant * xao; + // If the off-center is closer to the origin than the + // circumcenter, use the off-center instead. + if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) + { + dx = dxoff; + dy = dyoff; + } + } + } + else + { + if (offconstant > 0.0) + { + dxoff = 0.5 * (apex.x - dest.x) - offconstant * (apex.y - dest.y); + dyoff = 0.5 * (apex.y - dest.y) + offconstant * (apex.x - dest.x); + // If the off-center is closer to the destination than the + // circumcenter, use the off-center instead. + if (dxoff * dxoff + dyoff * dyoff < + (dx - xdo) * (dx - xdo) + (dy - ydo) * (dy - ydo)) + { + dx = xdo + dxoff; + dy = ydo + dyoff; + } + } + } + + // To interpolate vertex attributes for the new vertex inserted at + // the circumcenter, define a coordinate system with a xi-axis, + // directed from the triangle's origin to its destination, and + // an eta-axis, directed from its origin to its apex. + // Calculate the xi and eta coordinates of the circumcenter. + xi = (yao * dx - xao * dy) * (2.0 * denominator); + eta = (xdo * dy - ydo * dx) * (2.0 * denominator); + + return new Point(org.x + dx, org.y + dy); + } + + /// + /// Find the circumcenter of a triangle. + /// + /// Triangle point. + /// Triangle point. + /// Triangle point. + /// Relative coordinate of new location. + /// Relative coordinate of new location. + /// Coordinates of the circumcenter + /// + /// The result is returned both in terms of x-y coordinates and xi-eta + /// (barycentric) coordinates. The xi-eta coordinate system is defined in + /// terms of the triangle: the origin of the triangle is the origin of the + /// coordinate system; the destination of the triangle is one unit along the + /// xi axis; and the apex of the triangle is one unit along the eta axis. + /// This procedure also returns the square of the length of the triangle's + /// shortest edge. + /// + public Point FindCircumcenter(Point org, Point dest, Point apex, + ref double xi, ref double eta) + { + double xdo, ydo, xao, yao; + double dodist, aodist; + double denominator; + double dx, dy; + + Statistic.CircumcenterCount++; + + // Compute the circumcenter of the triangle. + xdo = dest.x - org.x; + ydo = dest.y - org.y; + xao = apex.x - org.x; + yao = apex.y - org.y; + dodist = xdo * xdo + ydo * ydo; + aodist = xao * xao + yao * yao; + + if (Behavior.NoExact) + { + denominator = 0.5 / (xdo * yao - xao * ydo); + } + else + { + // Use the counterclockwise() routine to ensure a positive (and + // reasonably accurate) result, avoiding any possibility of + // division by zero. + denominator = 0.5 / CounterClockwise(dest, apex, org); + // Don't count the above as an orientation test. + Statistic.CounterClockwiseCount--; + } + + dx = (yao * dodist - ydo * aodist) * denominator; + dy = (xdo * aodist - xao * dodist) * denominator; + + // To interpolate vertex attributes for the new vertex inserted at + // the circumcenter, define a coordinate system with a xi-axis, + // directed from the triangle's origin to its destination, and + // an eta-axis, directed from its origin to its apex. + // Calculate the xi and eta coordinates of the circumcenter. + xi = (yao * dx - xao * dy) * (2.0 * denominator); + eta = (xdo * dy - ydo * dx) * (2.0 * denominator); + + return new Point(org.x + dx, org.y + dy); + } + + #region Exact arithmetics + + /// + /// Sum two expansions, eliminating zero components from the output expansion. + /// + /// + /// + /// + /// + /// + /// + /// + /// Sets h = e + f. See the Robust Predicates paper for details. + /// + /// If round-to-even is used (as with IEEE 754), maintains the strongly nonoverlapping + /// property. (That is, if e is strongly nonoverlapping, h will be also.) Does NOT + /// maintain the nonoverlapping or nonadjacent properties. + /// + private int FastExpansionSumZeroElim(int elen, double[] e, int flen, double[] f, double[] h) + { + double Q; + double Qnew; + double hh; + double bvirt; + double avirt, bround, around; + int eindex, findex, hindex; + double enow, fnow; + + enow = e[0]; + fnow = f[0]; + eindex = findex = 0; + if ((fnow > enow) == (fnow > -enow)) + { + Q = enow; + enow = e[++eindex]; + } + else + { + Q = fnow; + fnow = f[++findex]; + } + hindex = 0; + if ((eindex < elen) && (findex < flen)) + { + if ((fnow > enow) == (fnow > -enow)) + { + Qnew = (double)(enow + Q); bvirt = Qnew - enow; hh = Q - bvirt; + enow = e[++eindex]; + } + else + { + Qnew = (double)(fnow + Q); bvirt = Qnew - fnow; hh = Q - bvirt; + fnow = f[++findex]; + } + Q = Qnew; + if (hh != 0.0) + { + h[hindex++] = hh; + } + while ((eindex < elen) && (findex < flen)) + { + if ((fnow > enow) == (fnow > -enow)) + { + Qnew = (double)(Q + enow); + bvirt = (double)(Qnew - Q); + avirt = Qnew - bvirt; + bround = enow - bvirt; + around = Q - avirt; + hh = around + bround; + + enow = e[++eindex]; + } + else + { + Qnew = (double)(Q + fnow); + bvirt = (double)(Qnew - Q); + avirt = Qnew - bvirt; + bround = fnow - bvirt; + around = Q - avirt; + hh = around + bround; + + fnow = f[++findex]; + } + Q = Qnew; + if (hh != 0.0) + { + h[hindex++] = hh; + } + } + } + while (eindex < elen) + { + Qnew = (double)(Q + enow); + bvirt = (double)(Qnew - Q); + avirt = Qnew - bvirt; + bround = enow - bvirt; + around = Q - avirt; + hh = around + bround; + + enow = e[++eindex]; + Q = Qnew; + if (hh != 0.0) + { + h[hindex++] = hh; + } + } + while (findex < flen) + { + Qnew = (double)(Q + fnow); + bvirt = (double)(Qnew - Q); + avirt = Qnew - bvirt; + bround = fnow - bvirt; + around = Q - avirt; + hh = around + bround; + + fnow = f[++findex]; + Q = Qnew; + if (hh != 0.0) + { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) + { + h[hindex++] = Q; + } + return hindex; + } + + /// + /// Multiply an expansion by a scalar, eliminating zero components from the output expansion. + /// + /// + /// + /// + /// + /// + /// + /// Sets h = be. See my Robust Predicates paper for details. + /// + /// Maintains the nonoverlapping property. If round-to-even is used (as with IEEE 754), + /// maintains the strongly nonoverlapping and nonadjacent properties as well. (That is, + /// if e has one of these properties, so will h.) + /// + private int ScaleExpansionZeroElim(int elen, double[] e, double b, double[] h) + { + double Q, sum; + double hh; + double product1; + double product0; + int eindex, hindex; + double enow; + double bvirt; + double avirt, bround, around; + double c; + double abig; + double ahi, alo, bhi, blo; + double err1, err2, err3; + + c = (double)(splitter * b); abig = (double)(c - b); bhi = c - abig; blo = b - bhi; + Q = (double)(e[0] * b); c = (double)(splitter * e[0]); abig = (double)(c - e[0]); ahi = c - abig; alo = e[0] - ahi; err1 = Q - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); hh = (alo * blo) - err3; + hindex = 0; + if (hh != 0) + { + h[hindex++] = hh; + } + for (eindex = 1; eindex < elen; eindex++) + { + enow = e[eindex]; + product1 = (double)(enow * b); c = (double)(splitter * enow); abig = (double)(c - enow); ahi = c - abig; alo = enow - ahi; err1 = product1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); product0 = (alo * blo) - err3; + sum = (double)(Q + product0); bvirt = (double)(sum - Q); avirt = sum - bvirt; bround = product0 - bvirt; around = Q - avirt; hh = around + bround; + if (hh != 0) + { + h[hindex++] = hh; + } + Q = (double)(product1 + sum); bvirt = Q - product1; hh = sum - bvirt; + if (hh != 0) + { + h[hindex++] = hh; + } + } + if ((Q != 0.0) || (hindex == 0)) + { + h[hindex++] = Q; + } + return hindex; + } + + /// + /// Produce a one-word estimate of an expansion's value. + /// + /// + /// + /// + private double Estimate(int elen, double[] e) + { + double Q; + int eindex; + + Q = e[0]; + for (eindex = 1; eindex < elen; eindex++) + { + Q += e[eindex]; + } + return Q; + } + + /// + /// Return a positive value if the points pa, pb, and pc occur in counterclockwise + /// order; a negative value if they occur in clockwise order; and zero if they are + /// collinear. The result is also a rough approximation of twice the signed area of + /// the triangle defined by the three points. + /// + /// + /// + /// + /// + /// + /// + /// Uses exact arithmetic if necessary to ensure a correct answer. The result returned + /// is the determinant of a matrix. This determinant is computed adaptively, in the + /// sense that exact arithmetic is used only to the degree it is needed to ensure that + /// the returned value has the correct sign. Hence, this function is usually quite fast, + /// but will run more slowly when the input points are collinear or nearly so. + /// + private double CounterClockwiseAdapt(Point pa, Point pb, Point pc, double detsum) + { + double acx, acy, bcx, bcy; + double acxtail, acytail, bcxtail, bcytail; + double detleft, detright; + double detlefttail, detrighttail; + double det, errbound; + // Edited to work around index out of range exceptions (changed array length from 4 to 5). + // See unsafe indexing in FastExpansionSumZeroElim. + double[] B = new double[5], u = new double[5]; + double[] C1 = new double[8], C2 = new double[12], D = new double[16]; + double B3; + int C1length, C2length, Dlength; + + double u3; + double s1, t1; + double s0, t0; + + double bvirt; + double avirt, bround, around; + double c; + double abig; + double ahi, alo, bhi, blo; + double err1, err2, err3; + double _i, _j; + double _0; + + acx = (double)(pa.x - pc.x); + bcx = (double)(pb.x - pc.x); + acy = (double)(pa.y - pc.y); + bcy = (double)(pb.y - pc.y); + + detleft = (double)(acx * bcy); c = (double)(splitter * acx); abig = (double)(c - acx); ahi = c - abig; alo = acx - ahi; c = (double)(splitter * bcy); abig = (double)(c - bcy); bhi = c - abig; blo = bcy - bhi; err1 = detleft - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); detlefttail = (alo * blo) - err3; + detright = (double)(acy * bcx); c = (double)(splitter * acy); abig = (double)(c - acy); ahi = c - abig; alo = acy - ahi; c = (double)(splitter * bcx); abig = (double)(c - bcx); bhi = c - abig; blo = bcx - bhi; err1 = detright - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); detrighttail = (alo * blo) - err3; + + _i = (double)(detlefttail - detrighttail); bvirt = (double)(detlefttail - _i); avirt = _i + bvirt; bround = bvirt - detrighttail; around = detlefttail - avirt; B[0] = around + bround; _j = (double)(detleft + _i); bvirt = (double)(_j - detleft); avirt = _j - bvirt; bround = _i - bvirt; around = detleft - avirt; _0 = around + bround; _i = (double)(_0 - detright); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - detright; around = _0 - avirt; B[1] = around + bround; B3 = (double)(_j + _i); bvirt = (double)(B3 - _j); avirt = B3 - bvirt; bround = _i - bvirt; around = _j - avirt; B[2] = around + bround; + + B[3] = B3; + + det = Estimate(4, B); + errbound = ccwerrboundB * detsum; + if ((det >= errbound) || (-det >= errbound)) + { + return det; + } + + bvirt = (double)(pa.x - acx); avirt = acx + bvirt; bround = bvirt - pc.x; around = pa.x - avirt; acxtail = around + bround; + bvirt = (double)(pb.x - bcx); avirt = bcx + bvirt; bround = bvirt - pc.x; around = pb.x - avirt; bcxtail = around + bround; + bvirt = (double)(pa.y - acy); avirt = acy + bvirt; bround = bvirt - pc.y; around = pa.y - avirt; acytail = around + bround; + bvirt = (double)(pb.y - bcy); avirt = bcy + bvirt; bround = bvirt - pc.y; around = pb.y - avirt; bcytail = around + bround; + + if ((acxtail == 0.0) && (acytail == 0.0) + && (bcxtail == 0.0) && (bcytail == 0.0)) + { + return det; + } + + errbound = ccwerrboundC * detsum + resulterrbound * ((det) >= 0.0 ? (det) : -(det)); + det += (acx * bcytail + bcy * acxtail) + - (acy * bcxtail + bcx * acytail); + if ((det >= errbound) || (-det >= errbound)) + { + return det; + } + + s1 = (double)(acxtail * bcy); c = (double)(splitter * acxtail); abig = (double)(c - acxtail); ahi = c - abig; alo = acxtail - ahi; c = (double)(splitter * bcy); abig = (double)(c - bcy); bhi = c - abig; blo = bcy - bhi; err1 = s1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); s0 = (alo * blo) - err3; + t1 = (double)(acytail * bcx); c = (double)(splitter * acytail); abig = (double)(c - acytail); ahi = c - abig; alo = acytail - ahi; c = (double)(splitter * bcx); abig = (double)(c - bcx); bhi = c - abig; blo = bcx - bhi; err1 = t1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); t0 = (alo * blo) - err3; + _i = (double)(s0 - t0); bvirt = (double)(s0 - _i); avirt = _i + bvirt; bround = bvirt - t0; around = s0 - avirt; u[0] = around + bround; _j = (double)(s1 + _i); bvirt = (double)(_j - s1); avirt = _j - bvirt; bround = _i - bvirt; around = s1 - avirt; _0 = around + bround; _i = (double)(_0 - t1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - t1; around = _0 - avirt; u[1] = around + bround; u3 = (double)(_j + _i); bvirt = (double)(u3 - _j); avirt = u3 - bvirt; bround = _i - bvirt; around = _j - avirt; u[2] = around + bround; + u[3] = u3; + C1length = FastExpansionSumZeroElim(4, B, 4, u, C1); + + s1 = (double)(acx * bcytail); c = (double)(splitter * acx); abig = (double)(c - acx); ahi = c - abig; alo = acx - ahi; c = (double)(splitter * bcytail); abig = (double)(c - bcytail); bhi = c - abig; blo = bcytail - bhi; err1 = s1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); s0 = (alo * blo) - err3; + t1 = (double)(acy * bcxtail); c = (double)(splitter * acy); abig = (double)(c - acy); ahi = c - abig; alo = acy - ahi; c = (double)(splitter * bcxtail); abig = (double)(c - bcxtail); bhi = c - abig; blo = bcxtail - bhi; err1 = t1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); t0 = (alo * blo) - err3; + _i = (double)(s0 - t0); bvirt = (double)(s0 - _i); avirt = _i + bvirt; bround = bvirt - t0; around = s0 - avirt; u[0] = around + bround; _j = (double)(s1 + _i); bvirt = (double)(_j - s1); avirt = _j - bvirt; bround = _i - bvirt; around = s1 - avirt; _0 = around + bround; _i = (double)(_0 - t1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - t1; around = _0 - avirt; u[1] = around + bround; u3 = (double)(_j + _i); bvirt = (double)(u3 - _j); avirt = u3 - bvirt; bround = _i - bvirt; around = _j - avirt; u[2] = around + bround; + u[3] = u3; + C2length = FastExpansionSumZeroElim(C1length, C1, 4, u, C2); + + s1 = (double)(acxtail * bcytail); c = (double)(splitter * acxtail); abig = (double)(c - acxtail); ahi = c - abig; alo = acxtail - ahi; c = (double)(splitter * bcytail); abig = (double)(c - bcytail); bhi = c - abig; blo = bcytail - bhi; err1 = s1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); s0 = (alo * blo) - err3; + t1 = (double)(acytail * bcxtail); c = (double)(splitter * acytail); abig = (double)(c - acytail); ahi = c - abig; alo = acytail - ahi; c = (double)(splitter * bcxtail); abig = (double)(c - bcxtail); bhi = c - abig; blo = bcxtail - bhi; err1 = t1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); t0 = (alo * blo) - err3; + _i = (double)(s0 - t0); bvirt = (double)(s0 - _i); avirt = _i + bvirt; bround = bvirt - t0; around = s0 - avirt; u[0] = around + bround; _j = (double)(s1 + _i); bvirt = (double)(_j - s1); avirt = _j - bvirt; bround = _i - bvirt; around = s1 - avirt; _0 = around + bround; _i = (double)(_0 - t1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - t1; around = _0 - avirt; u[1] = around + bround; u3 = (double)(_j + _i); bvirt = (double)(u3 - _j); avirt = u3 - bvirt; bround = _i - bvirt; around = _j - avirt; u[2] = around + bround; + u[3] = u3; + Dlength = FastExpansionSumZeroElim(C2length, C2, 4, u, D); + + return (D[Dlength - 1]); + } + + /// + /// Return a positive value if the point pd lies inside the circle passing through + /// pa, pb, and pc; a negative value if it lies outside; and zero if the four points + /// are cocircular. The points pa, pb, and pc must be in counterclockwise order, or + /// the sign of the result will be reversed. + /// + /// + /// + /// + /// + /// + /// + /// + /// Uses exact arithmetic if necessary to ensure a correct answer. The result returned + /// is the determinant of a matrix. This determinant is computed adaptively, in the + /// sense that exact arithmetic is used only to the degree it is needed to ensure that + /// the returned value has the correct sign. Hence, this function is usually quite fast, + /// but will run more slowly when the input points are cocircular or nearly so. + /// + private double InCircleAdapt(Point pa, Point pb, Point pc, Point pd, double permanent) + { + double adx, bdx, cdx, ady, bdy, cdy; + double det, errbound; + + double bdxcdy1, cdxbdy1, cdxady1, adxcdy1, adxbdy1, bdxady1; + double bdxcdy0, cdxbdy0, cdxady0, adxcdy0, adxbdy0, bdxady0; + double[] bc = new double[4], ca = new double[4], ab = new double[4]; + double bc3, ca3, ab3; + int axbclen, axxbclen, aybclen, ayybclen, alen; + int bxcalen, bxxcalen, bycalen, byycalen, blen; + int cxablen, cxxablen, cyablen, cyyablen, clen; + int ablen; + double[] finnow, finother, finswap; + int finlength; + + double adxtail, bdxtail, cdxtail, adytail, bdytail, cdytail; + double adxadx1, adyady1, bdxbdx1, bdybdy1, cdxcdx1, cdycdy1; + double adxadx0, adyady0, bdxbdx0, bdybdy0, cdxcdx0, cdycdy0; + double[] aa = new double[4], bb = new double[4], cc = new double[4]; + double aa3, bb3, cc3; + double ti1, tj1; + double ti0, tj0; + // Edited to work around index out of range exceptions (changed array length from 4 to 5). + // See unsafe indexing in FastExpansionSumZeroElim. + double[] u = new double[5], v = new double[5]; + double u3, v3; + int temp8len, temp16alen, temp16blen, temp16clen; + int temp32alen, temp32blen, temp48len, temp64len; + double[] axtbb = new double[8], axtcc = new double[8], aytbb = new double[8], aytcc = new double[8]; + int axtbblen, axtcclen, aytbblen, aytcclen; + double[] bxtaa = new double[8], bxtcc = new double[8], bytaa = new double[8], bytcc = new double[8]; + int bxtaalen, bxtcclen, bytaalen, bytcclen; + double[] cxtaa = new double[8], cxtbb = new double[8], cytaa = new double[8], cytbb = new double[8]; + int cxtaalen, cxtbblen, cytaalen, cytbblen; + double[] axtbc = new double[8], aytbc = new double[8], bxtca = new double[8], bytca = new double[8], cxtab = new double[8], cytab = new double[8]; + int axtbclen = 0, aytbclen = 0, bxtcalen = 0, bytcalen = 0, cxtablen = 0, cytablen = 0; + double[] axtbct = new double[16], aytbct = new double[16], bxtcat = new double[16], bytcat = new double[16], cxtabt = new double[16], cytabt = new double[16]; + int axtbctlen, aytbctlen, bxtcatlen, bytcatlen, cxtabtlen, cytabtlen; + double[] axtbctt = new double[8], aytbctt = new double[8], bxtcatt = new double[8]; + double[] bytcatt = new double[8], cxtabtt = new double[8], cytabtt = new double[8]; + int axtbcttlen, aytbcttlen, bxtcattlen, bytcattlen, cxtabttlen, cytabttlen; + double[] abt = new double[8], bct = new double[8], cat = new double[8]; + int abtlen, bctlen, catlen; + double[] abtt = new double[4], bctt = new double[4], catt = new double[4]; + int abttlen, bcttlen, cattlen; + double abtt3, bctt3, catt3; + double negate; + + double bvirt; + double avirt, bround, around; + double c; + double abig; + double ahi, alo, bhi, blo; + double err1, err2, err3; + double _i, _j; + double _0; + + adx = (double)(pa.x - pd.x); + bdx = (double)(pb.x - pd.x); + cdx = (double)(pc.x - pd.x); + ady = (double)(pa.y - pd.y); + bdy = (double)(pb.y - pd.y); + cdy = (double)(pc.y - pd.y); + + adx = (double)(pa.x - pd.x); + bdx = (double)(pb.x - pd.x); + cdx = (double)(pc.x - pd.x); + ady = (double)(pa.y - pd.y); + bdy = (double)(pb.y - pd.y); + cdy = (double)(pc.y - pd.y); + + bdxcdy1 = (double)(bdx * cdy); c = (double)(splitter * bdx); abig = (double)(c - bdx); ahi = c - abig; alo = bdx - ahi; c = (double)(splitter * cdy); abig = (double)(c - cdy); bhi = c - abig; blo = cdy - bhi; err1 = bdxcdy1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); bdxcdy0 = (alo * blo) - err3; + cdxbdy1 = (double)(cdx * bdy); c = (double)(splitter * cdx); abig = (double)(c - cdx); ahi = c - abig; alo = cdx - ahi; c = (double)(splitter * bdy); abig = (double)(c - bdy); bhi = c - abig; blo = bdy - bhi; err1 = cdxbdy1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); cdxbdy0 = (alo * blo) - err3; + _i = (double)(bdxcdy0 - cdxbdy0); bvirt = (double)(bdxcdy0 - _i); avirt = _i + bvirt; bround = bvirt - cdxbdy0; around = bdxcdy0 - avirt; bc[0] = around + bround; _j = (double)(bdxcdy1 + _i); bvirt = (double)(_j - bdxcdy1); avirt = _j - bvirt; bround = _i - bvirt; around = bdxcdy1 - avirt; _0 = around + bround; _i = (double)(_0 - cdxbdy1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - cdxbdy1; around = _0 - avirt; bc[1] = around + bround; bc3 = (double)(_j + _i); bvirt = (double)(bc3 - _j); avirt = bc3 - bvirt; bround = _i - bvirt; around = _j - avirt; bc[2] = around + bround; + bc[3] = bc3; + axbclen = ScaleExpansionZeroElim(4, bc, adx, axbc); + axxbclen = ScaleExpansionZeroElim(axbclen, axbc, adx, axxbc); + aybclen = ScaleExpansionZeroElim(4, bc, ady, aybc); + ayybclen = ScaleExpansionZeroElim(aybclen, aybc, ady, ayybc); + alen = FastExpansionSumZeroElim(axxbclen, axxbc, ayybclen, ayybc, adet); + + cdxady1 = (double)(cdx * ady); c = (double)(splitter * cdx); abig = (double)(c - cdx); ahi = c - abig; alo = cdx - ahi; c = (double)(splitter * ady); abig = (double)(c - ady); bhi = c - abig; blo = ady - bhi; err1 = cdxady1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); cdxady0 = (alo * blo) - err3; + adxcdy1 = (double)(adx * cdy); c = (double)(splitter * adx); abig = (double)(c - adx); ahi = c - abig; alo = adx - ahi; c = (double)(splitter * cdy); abig = (double)(c - cdy); bhi = c - abig; blo = cdy - bhi; err1 = adxcdy1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); adxcdy0 = (alo * blo) - err3; + _i = (double)(cdxady0 - adxcdy0); bvirt = (double)(cdxady0 - _i); avirt = _i + bvirt; bround = bvirt - adxcdy0; around = cdxady0 - avirt; ca[0] = around + bround; _j = (double)(cdxady1 + _i); bvirt = (double)(_j - cdxady1); avirt = _j - bvirt; bround = _i - bvirt; around = cdxady1 - avirt; _0 = around + bround; _i = (double)(_0 - adxcdy1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - adxcdy1; around = _0 - avirt; ca[1] = around + bround; ca3 = (double)(_j + _i); bvirt = (double)(ca3 - _j); avirt = ca3 - bvirt; bround = _i - bvirt; around = _j - avirt; ca[2] = around + bround; + ca[3] = ca3; + bxcalen = ScaleExpansionZeroElim(4, ca, bdx, bxca); + bxxcalen = ScaleExpansionZeroElim(bxcalen, bxca, bdx, bxxca); + bycalen = ScaleExpansionZeroElim(4, ca, bdy, byca); + byycalen = ScaleExpansionZeroElim(bycalen, byca, bdy, byyca); + blen = FastExpansionSumZeroElim(bxxcalen, bxxca, byycalen, byyca, bdet); + + adxbdy1 = (double)(adx * bdy); c = (double)(splitter * adx); abig = (double)(c - adx); ahi = c - abig; alo = adx - ahi; c = (double)(splitter * bdy); abig = (double)(c - bdy); bhi = c - abig; blo = bdy - bhi; err1 = adxbdy1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); adxbdy0 = (alo * blo) - err3; + bdxady1 = (double)(bdx * ady); c = (double)(splitter * bdx); abig = (double)(c - bdx); ahi = c - abig; alo = bdx - ahi; c = (double)(splitter * ady); abig = (double)(c - ady); bhi = c - abig; blo = ady - bhi; err1 = bdxady1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); bdxady0 = (alo * blo) - err3; + _i = (double)(adxbdy0 - bdxady0); bvirt = (double)(adxbdy0 - _i); avirt = _i + bvirt; bround = bvirt - bdxady0; around = adxbdy0 - avirt; ab[0] = around + bround; _j = (double)(adxbdy1 + _i); bvirt = (double)(_j - adxbdy1); avirt = _j - bvirt; bround = _i - bvirt; around = adxbdy1 - avirt; _0 = around + bround; _i = (double)(_0 - bdxady1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - bdxady1; around = _0 - avirt; ab[1] = around + bround; ab3 = (double)(_j + _i); bvirt = (double)(ab3 - _j); avirt = ab3 - bvirt; bround = _i - bvirt; around = _j - avirt; ab[2] = around + bround; + ab[3] = ab3; + cxablen = ScaleExpansionZeroElim(4, ab, cdx, cxab); + cxxablen = ScaleExpansionZeroElim(cxablen, cxab, cdx, cxxab); + cyablen = ScaleExpansionZeroElim(4, ab, cdy, cyab); + cyyablen = ScaleExpansionZeroElim(cyablen, cyab, cdy, cyyab); + clen = FastExpansionSumZeroElim(cxxablen, cxxab, cyyablen, cyyab, cdet); + + ablen = FastExpansionSumZeroElim(alen, adet, blen, bdet, abdet); + finlength = FastExpansionSumZeroElim(ablen, abdet, clen, cdet, fin1); + + det = Estimate(finlength, fin1); + errbound = iccerrboundB * permanent; + if ((det >= errbound) || (-det >= errbound)) + { + return det; + } + + bvirt = (double)(pa.x - adx); avirt = adx + bvirt; bround = bvirt - pd.x; around = pa.x - avirt; adxtail = around + bround; + bvirt = (double)(pa.y - ady); avirt = ady + bvirt; bround = bvirt - pd.y; around = pa.y - avirt; adytail = around + bround; + bvirt = (double)(pb.x - bdx); avirt = bdx + bvirt; bround = bvirt - pd.x; around = pb.x - avirt; bdxtail = around + bround; + bvirt = (double)(pb.y - bdy); avirt = bdy + bvirt; bround = bvirt - pd.y; around = pb.y - avirt; bdytail = around + bround; + bvirt = (double)(pc.x - cdx); avirt = cdx + bvirt; bround = bvirt - pd.x; around = pc.x - avirt; cdxtail = around + bround; + bvirt = (double)(pc.y - cdy); avirt = cdy + bvirt; bround = bvirt - pd.y; around = pc.y - avirt; cdytail = around + bround; + if ((adxtail == 0.0) && (bdxtail == 0.0) && (cdxtail == 0.0) + && (adytail == 0.0) && (bdytail == 0.0) && (cdytail == 0.0)) + { + return det; + } + + errbound = iccerrboundC * permanent + resulterrbound * ((det) >= 0.0 ? (det) : -(det)); + det += ((adx * adx + ady * ady) * ((bdx * cdytail + cdy * bdxtail) - (bdy * cdxtail + cdx * bdytail)) + + 2.0 * (adx * adxtail + ady * adytail) * (bdx * cdy - bdy * cdx)) + + ((bdx * bdx + bdy * bdy) * ((cdx * adytail + ady * cdxtail) - (cdy * adxtail + adx * cdytail)) + + 2.0 * (bdx * bdxtail + bdy * bdytail) * (cdx * ady - cdy * adx)) + + ((cdx * cdx + cdy * cdy) * ((adx * bdytail + bdy * adxtail) - (ady * bdxtail + bdx * adytail)) + + 2.0 * (cdx * cdxtail + cdy * cdytail) * (adx * bdy - ady * bdx)); + if ((det >= errbound) || (-det >= errbound)) + { + return det; + } + + finnow = fin1; + finother = fin2; + + if ((bdxtail != 0.0) || (bdytail != 0.0) || (cdxtail != 0.0) || (cdytail != 0.0)) + { + adxadx1 = (double)(adx * adx); c = (double)(splitter * adx); abig = (double)(c - adx); ahi = c - abig; alo = adx - ahi; err1 = adxadx1 - (ahi * ahi); err3 = err1 - ((ahi + ahi) * alo); adxadx0 = (alo * alo) - err3; + adyady1 = (double)(ady * ady); c = (double)(splitter * ady); abig = (double)(c - ady); ahi = c - abig; alo = ady - ahi; err1 = adyady1 - (ahi * ahi); err3 = err1 - ((ahi + ahi) * alo); adyady0 = (alo * alo) - err3; + _i = (double)(adxadx0 + adyady0); bvirt = (double)(_i - adxadx0); avirt = _i - bvirt; bround = adyady0 - bvirt; around = adxadx0 - avirt; aa[0] = around + bround; _j = (double)(adxadx1 + _i); bvirt = (double)(_j - adxadx1); avirt = _j - bvirt; bround = _i - bvirt; around = adxadx1 - avirt; _0 = around + bround; _i = (double)(_0 + adyady1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = adyady1 - bvirt; around = _0 - avirt; aa[1] = around + bround; aa3 = (double)(_j + _i); bvirt = (double)(aa3 - _j); avirt = aa3 - bvirt; bround = _i - bvirt; around = _j - avirt; aa[2] = around + bround; + aa[3] = aa3; + } + if ((cdxtail != 0.0) || (cdytail != 0.0) || (adxtail != 0.0) || (adytail != 0.0)) + { + bdxbdx1 = (double)(bdx * bdx); c = (double)(splitter * bdx); abig = (double)(c - bdx); ahi = c - abig; alo = bdx - ahi; err1 = bdxbdx1 - (ahi * ahi); err3 = err1 - ((ahi + ahi) * alo); bdxbdx0 = (alo * alo) - err3; + bdybdy1 = (double)(bdy * bdy); c = (double)(splitter * bdy); abig = (double)(c - bdy); ahi = c - abig; alo = bdy - ahi; err1 = bdybdy1 - (ahi * ahi); err3 = err1 - ((ahi + ahi) * alo); bdybdy0 = (alo * alo) - err3; + _i = (double)(bdxbdx0 + bdybdy0); bvirt = (double)(_i - bdxbdx0); avirt = _i - bvirt; bround = bdybdy0 - bvirt; around = bdxbdx0 - avirt; bb[0] = around + bround; _j = (double)(bdxbdx1 + _i); bvirt = (double)(_j - bdxbdx1); avirt = _j - bvirt; bround = _i - bvirt; around = bdxbdx1 - avirt; _0 = around + bround; _i = (double)(_0 + bdybdy1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = bdybdy1 - bvirt; around = _0 - avirt; bb[1] = around + bround; bb3 = (double)(_j + _i); bvirt = (double)(bb3 - _j); avirt = bb3 - bvirt; bround = _i - bvirt; around = _j - avirt; bb[2] = around + bround; + bb[3] = bb3; + } + if ((adxtail != 0.0) || (adytail != 0.0) || (bdxtail != 0.0) || (bdytail != 0.0)) + { + cdxcdx1 = (double)(cdx * cdx); c = (double)(splitter * cdx); abig = (double)(c - cdx); ahi = c - abig; alo = cdx - ahi; err1 = cdxcdx1 - (ahi * ahi); err3 = err1 - ((ahi + ahi) * alo); cdxcdx0 = (alo * alo) - err3; + cdycdy1 = (double)(cdy * cdy); c = (double)(splitter * cdy); abig = (double)(c - cdy); ahi = c - abig; alo = cdy - ahi; err1 = cdycdy1 - (ahi * ahi); err3 = err1 - ((ahi + ahi) * alo); cdycdy0 = (alo * alo) - err3; + _i = (double)(cdxcdx0 + cdycdy0); bvirt = (double)(_i - cdxcdx0); avirt = _i - bvirt; bround = cdycdy0 - bvirt; around = cdxcdx0 - avirt; cc[0] = around + bround; _j = (double)(cdxcdx1 + _i); bvirt = (double)(_j - cdxcdx1); avirt = _j - bvirt; bround = _i - bvirt; around = cdxcdx1 - avirt; _0 = around + bround; _i = (double)(_0 + cdycdy1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = cdycdy1 - bvirt; around = _0 - avirt; cc[1] = around + bround; cc3 = (double)(_j + _i); bvirt = (double)(cc3 - _j); avirt = cc3 - bvirt; bround = _i - bvirt; around = _j - avirt; cc[2] = around + bround; + cc[3] = cc3; + } + + if (adxtail != 0.0) + { + axtbclen = ScaleExpansionZeroElim(4, bc, adxtail, axtbc); + temp16alen = ScaleExpansionZeroElim(axtbclen, axtbc, 2.0 * adx, temp16a); + + axtcclen = ScaleExpansionZeroElim(4, cc, adxtail, axtcc); + temp16blen = ScaleExpansionZeroElim(axtcclen, axtcc, bdy, temp16b); + + axtbblen = ScaleExpansionZeroElim(4, bb, adxtail, axtbb); + temp16clen = ScaleExpansionZeroElim(axtbblen, axtbb, -cdy, temp16c); + + temp32alen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32a); + temp48len = FastExpansionSumZeroElim(temp16clen, temp16c, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) + { + aytbclen = ScaleExpansionZeroElim(4, bc, adytail, aytbc); + temp16alen = ScaleExpansionZeroElim(aytbclen, aytbc, 2.0 * ady, temp16a); + + aytbblen = ScaleExpansionZeroElim(4, bb, adytail, aytbb); + temp16blen = ScaleExpansionZeroElim(aytbblen, aytbb, cdx, temp16b); + + aytcclen = ScaleExpansionZeroElim(4, cc, adytail, aytcc); + temp16clen = ScaleExpansionZeroElim(aytcclen, aytcc, -bdx, temp16c); + + temp32alen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32a); + temp48len = FastExpansionSumZeroElim(temp16clen, temp16c, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdxtail != 0.0) + { + bxtcalen = ScaleExpansionZeroElim(4, ca, bdxtail, bxtca); + temp16alen = ScaleExpansionZeroElim(bxtcalen, bxtca, 2.0 * bdx, temp16a); + + bxtaalen = ScaleExpansionZeroElim(4, aa, bdxtail, bxtaa); + temp16blen = ScaleExpansionZeroElim(bxtaalen, bxtaa, cdy, temp16b); + + bxtcclen = ScaleExpansionZeroElim(4, cc, bdxtail, bxtcc); + temp16clen = ScaleExpansionZeroElim(bxtcclen, bxtcc, -ady, temp16c); + + temp32alen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32a); + temp48len = FastExpansionSumZeroElim(temp16clen, temp16c, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) + { + bytcalen = ScaleExpansionZeroElim(4, ca, bdytail, bytca); + temp16alen = ScaleExpansionZeroElim(bytcalen, bytca, 2.0 * bdy, temp16a); + + bytcclen = ScaleExpansionZeroElim(4, cc, bdytail, bytcc); + temp16blen = ScaleExpansionZeroElim(bytcclen, bytcc, adx, temp16b); + + bytaalen = ScaleExpansionZeroElim(4, aa, bdytail, bytaa); + temp16clen = ScaleExpansionZeroElim(bytaalen, bytaa, -cdx, temp16c); + + temp32alen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32a); + temp48len = FastExpansionSumZeroElim(temp16clen, temp16c, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdxtail != 0.0) + { + cxtablen = ScaleExpansionZeroElim(4, ab, cdxtail, cxtab); + temp16alen = ScaleExpansionZeroElim(cxtablen, cxtab, 2.0 * cdx, temp16a); + + cxtbblen = ScaleExpansionZeroElim(4, bb, cdxtail, cxtbb); + temp16blen = ScaleExpansionZeroElim(cxtbblen, cxtbb, ady, temp16b); + + cxtaalen = ScaleExpansionZeroElim(4, aa, cdxtail, cxtaa); + temp16clen = ScaleExpansionZeroElim(cxtaalen, cxtaa, -bdy, temp16c); + + temp32alen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32a); + temp48len = FastExpansionSumZeroElim(temp16clen, temp16c, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) + { + cytablen = ScaleExpansionZeroElim(4, ab, cdytail, cytab); + temp16alen = ScaleExpansionZeroElim(cytablen, cytab, 2.0 * cdy, temp16a); + + cytaalen = ScaleExpansionZeroElim(4, aa, cdytail, cytaa); + temp16blen = ScaleExpansionZeroElim(cytaalen, cytaa, bdx, temp16b); + + cytbblen = ScaleExpansionZeroElim(4, bb, cdytail, cytbb); + temp16clen = ScaleExpansionZeroElim(cytbblen, cytbb, -adx, temp16c); + + temp32alen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32a); + temp48len = FastExpansionSumZeroElim(temp16clen, temp16c, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + if ((adxtail != 0.0) || (adytail != 0.0)) + { + if ((bdxtail != 0.0) || (bdytail != 0.0) + || (cdxtail != 0.0) || (cdytail != 0.0)) + { + ti1 = (double)(bdxtail * cdy); c = (double)(splitter * bdxtail); abig = (double)(c - bdxtail); ahi = c - abig; alo = bdxtail - ahi; c = (double)(splitter * cdy); abig = (double)(c - cdy); bhi = c - abig; blo = cdy - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; + tj1 = (double)(bdx * cdytail); c = (double)(splitter * bdx); abig = (double)(c - bdx); ahi = c - abig; alo = bdx - ahi; c = (double)(splitter * cdytail); abig = (double)(c - cdytail); bhi = c - abig; blo = cdytail - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; + _i = (double)(ti0 + tj0); bvirt = (double)(_i - ti0); avirt = _i - bvirt; bround = tj0 - bvirt; around = ti0 - avirt; u[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 + tj1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = tj1 - bvirt; around = _0 - avirt; u[1] = around + bround; u3 = (double)(_j + _i); bvirt = (double)(u3 - _j); avirt = u3 - bvirt; bround = _i - bvirt; around = _j - avirt; u[2] = around + bround; + u[3] = u3; + negate = -bdy; + ti1 = (double)(cdxtail * negate); c = (double)(splitter * cdxtail); abig = (double)(c - cdxtail); ahi = c - abig; alo = cdxtail - ahi; c = (double)(splitter * negate); abig = (double)(c - negate); bhi = c - abig; blo = negate - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; + negate = -bdytail; + tj1 = (double)(cdx * negate); c = (double)(splitter * cdx); abig = (double)(c - cdx); ahi = c - abig; alo = cdx - ahi; c = (double)(splitter * negate); abig = (double)(c - negate); bhi = c - abig; blo = negate - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; + _i = (double)(ti0 + tj0); bvirt = (double)(_i - ti0); avirt = _i - bvirt; bround = tj0 - bvirt; around = ti0 - avirt; v[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 + tj1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = tj1 - bvirt; around = _0 - avirt; v[1] = around + bround; v3 = (double)(_j + _i); bvirt = (double)(v3 - _j); avirt = v3 - bvirt; bround = _i - bvirt; around = _j - avirt; v[2] = around + bround; + v[3] = v3; + bctlen = FastExpansionSumZeroElim(4, u, 4, v, bct); + + ti1 = (double)(bdxtail * cdytail); c = (double)(splitter * bdxtail); abig = (double)(c - bdxtail); ahi = c - abig; alo = bdxtail - ahi; c = (double)(splitter * cdytail); abig = (double)(c - cdytail); bhi = c - abig; blo = cdytail - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; + tj1 = (double)(cdxtail * bdytail); c = (double)(splitter * cdxtail); abig = (double)(c - cdxtail); ahi = c - abig; alo = cdxtail - ahi; c = (double)(splitter * bdytail); abig = (double)(c - bdytail); bhi = c - abig; blo = bdytail - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; + _i = (double)(ti0 - tj0); bvirt = (double)(ti0 - _i); avirt = _i + bvirt; bround = bvirt - tj0; around = ti0 - avirt; bctt[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 - tj1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - tj1; around = _0 - avirt; bctt[1] = around + bround; bctt3 = (double)(_j + _i); bvirt = (double)(bctt3 - _j); avirt = bctt3 - bvirt; bround = _i - bvirt; around = _j - avirt; bctt[2] = around + bround; + bctt[3] = bctt3; + bcttlen = 4; + } + else + { + bct[0] = 0.0; + bctlen = 1; + bctt[0] = 0.0; + bcttlen = 1; + } + + if (adxtail != 0.0) + { + temp16alen = ScaleExpansionZeroElim(axtbclen, axtbc, adxtail, temp16a); + axtbctlen = ScaleExpansionZeroElim(bctlen, bct, adxtail, axtbct); + temp32alen = ScaleExpansionZeroElim(axtbctlen, axtbct, 2.0 * adx, temp32a); + temp48len = FastExpansionSumZeroElim(temp16alen, temp16a, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (bdytail != 0.0) + { + temp8len = ScaleExpansionZeroElim(4, cc, adxtail, temp8); + temp16alen = ScaleExpansionZeroElim(temp8len, temp8, bdytail, temp16a); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp16alen, temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) + { + temp8len = ScaleExpansionZeroElim(4, bb, -adxtail, temp8); + temp16alen = ScaleExpansionZeroElim(temp8len, temp8, cdytail, temp16a); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp16alen, temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = ScaleExpansionZeroElim(axtbctlen, axtbct, adxtail, temp32a); + axtbcttlen = ScaleExpansionZeroElim(bcttlen, bctt, adxtail, axtbctt); + temp16alen = ScaleExpansionZeroElim(axtbcttlen, axtbctt, 2.0 * adx, temp16a); + temp16blen = ScaleExpansionZeroElim(axtbcttlen, axtbctt, adxtail, temp16b); + temp32blen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32b); + temp64len = FastExpansionSumZeroElim(temp32alen, temp32a, temp32blen, temp32b, temp64); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp64len, temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) + { + temp16alen = ScaleExpansionZeroElim(aytbclen, aytbc, adytail, temp16a); + aytbctlen = ScaleExpansionZeroElim(bctlen, bct, adytail, aytbct); + temp32alen = ScaleExpansionZeroElim(aytbctlen, aytbct, 2.0 * ady, temp32a); + temp48len = FastExpansionSumZeroElim(temp16alen, temp16a, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + + temp32alen = ScaleExpansionZeroElim(aytbctlen, aytbct, adytail, temp32a); + aytbcttlen = ScaleExpansionZeroElim(bcttlen, bctt, adytail, aytbctt); + temp16alen = ScaleExpansionZeroElim(aytbcttlen, aytbctt, 2.0 * ady, temp16a); + temp16blen = ScaleExpansionZeroElim(aytbcttlen, aytbctt, adytail, temp16b); + temp32blen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32b); + temp64len = FastExpansionSumZeroElim(temp32alen, temp32a, temp32blen, temp32b, temp64); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp64len, temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if ((bdxtail != 0.0) || (bdytail != 0.0)) + { + if ((cdxtail != 0.0) || (cdytail != 0.0) + || (adxtail != 0.0) || (adytail != 0.0)) + { + ti1 = (double)(cdxtail * ady); c = (double)(splitter * cdxtail); abig = (double)(c - cdxtail); ahi = c - abig; alo = cdxtail - ahi; c = (double)(splitter * ady); abig = (double)(c - ady); bhi = c - abig; blo = ady - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; + tj1 = (double)(cdx * adytail); c = (double)(splitter * cdx); abig = (double)(c - cdx); ahi = c - abig; alo = cdx - ahi; c = (double)(splitter * adytail); abig = (double)(c - adytail); bhi = c - abig; blo = adytail - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; + _i = (double)(ti0 + tj0); bvirt = (double)(_i - ti0); avirt = _i - bvirt; bround = tj0 - bvirt; around = ti0 - avirt; u[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 + tj1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = tj1 - bvirt; around = _0 - avirt; u[1] = around + bround; u3 = (double)(_j + _i); bvirt = (double)(u3 - _j); avirt = u3 - bvirt; bround = _i - bvirt; around = _j - avirt; u[2] = around + bround; + u[3] = u3; + negate = -cdy; + ti1 = (double)(adxtail * negate); c = (double)(splitter * adxtail); abig = (double)(c - adxtail); ahi = c - abig; alo = adxtail - ahi; c = (double)(splitter * negate); abig = (double)(c - negate); bhi = c - abig; blo = negate - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; + negate = -cdytail; + tj1 = (double)(adx * negate); c = (double)(splitter * adx); abig = (double)(c - adx); ahi = c - abig; alo = adx - ahi; c = (double)(splitter * negate); abig = (double)(c - negate); bhi = c - abig; blo = negate - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; + _i = (double)(ti0 + tj0); bvirt = (double)(_i - ti0); avirt = _i - bvirt; bround = tj0 - bvirt; around = ti0 - avirt; v[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 + tj1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = tj1 - bvirt; around = _0 - avirt; v[1] = around + bround; v3 = (double)(_j + _i); bvirt = (double)(v3 - _j); avirt = v3 - bvirt; bround = _i - bvirt; around = _j - avirt; v[2] = around + bround; + v[3] = v3; + catlen = FastExpansionSumZeroElim(4, u, 4, v, cat); + + ti1 = (double)(cdxtail * adytail); c = (double)(splitter * cdxtail); abig = (double)(c - cdxtail); ahi = c - abig; alo = cdxtail - ahi; c = (double)(splitter * adytail); abig = (double)(c - adytail); bhi = c - abig; blo = adytail - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; + tj1 = (double)(adxtail * cdytail); c = (double)(splitter * adxtail); abig = (double)(c - adxtail); ahi = c - abig; alo = adxtail - ahi; c = (double)(splitter * cdytail); abig = (double)(c - cdytail); bhi = c - abig; blo = cdytail - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; + _i = (double)(ti0 - tj0); bvirt = (double)(ti0 - _i); avirt = _i + bvirt; bround = bvirt - tj0; around = ti0 - avirt; catt[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 - tj1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - tj1; around = _0 - avirt; catt[1] = around + bround; catt3 = (double)(_j + _i); bvirt = (double)(catt3 - _j); avirt = catt3 - bvirt; bround = _i - bvirt; around = _j - avirt; catt[2] = around + bround; + catt[3] = catt3; + cattlen = 4; + } + else + { + cat[0] = 0.0; + catlen = 1; + catt[0] = 0.0; + cattlen = 1; + } + + if (bdxtail != 0.0) + { + temp16alen = ScaleExpansionZeroElim(bxtcalen, bxtca, bdxtail, temp16a); + bxtcatlen = ScaleExpansionZeroElim(catlen, cat, bdxtail, bxtcat); + temp32alen = ScaleExpansionZeroElim(bxtcatlen, bxtcat, 2.0 * bdx, temp32a); + temp48len = FastExpansionSumZeroElim(temp16alen, temp16a, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (cdytail != 0.0) + { + temp8len = ScaleExpansionZeroElim(4, aa, bdxtail, temp8); + temp16alen = ScaleExpansionZeroElim(temp8len, temp8, cdytail, temp16a); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp16alen, temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (adytail != 0.0) + { + temp8len = ScaleExpansionZeroElim(4, cc, -bdxtail, temp8); + temp16alen = ScaleExpansionZeroElim(temp8len, temp8, adytail, temp16a); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp16alen, temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = ScaleExpansionZeroElim(bxtcatlen, bxtcat, bdxtail, temp32a); + bxtcattlen = ScaleExpansionZeroElim(cattlen, catt, bdxtail, bxtcatt); + temp16alen = ScaleExpansionZeroElim(bxtcattlen, bxtcatt, 2.0 * bdx, temp16a); + temp16blen = ScaleExpansionZeroElim(bxtcattlen, bxtcatt, bdxtail, temp16b); + temp32blen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32b); + temp64len = FastExpansionSumZeroElim(temp32alen, temp32a, temp32blen, temp32b, temp64); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp64len, temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) + { + temp16alen = ScaleExpansionZeroElim(bytcalen, bytca, bdytail, temp16a); + bytcatlen = ScaleExpansionZeroElim(catlen, cat, bdytail, bytcat); + temp32alen = ScaleExpansionZeroElim(bytcatlen, bytcat, 2.0 * bdy, temp32a); + temp48len = FastExpansionSumZeroElim(temp16alen, temp16a, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + temp32alen = ScaleExpansionZeroElim(bytcatlen, bytcat, bdytail, temp32a); + bytcattlen = ScaleExpansionZeroElim(cattlen, catt, bdytail, bytcatt); + temp16alen = ScaleExpansionZeroElim(bytcattlen, bytcatt, 2.0 * bdy, temp16a); + temp16blen = ScaleExpansionZeroElim(bytcattlen, bytcatt, bdytail, temp16b); + temp32blen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32b); + temp64len = FastExpansionSumZeroElim(temp32alen, temp32a, temp32blen, temp32b, temp64); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp64len, temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + if ((cdxtail != 0.0) || (cdytail != 0.0)) + { + if ((adxtail != 0.0) || (adytail != 0.0) + || (bdxtail != 0.0) || (bdytail != 0.0)) + { + ti1 = (double)(adxtail * bdy); c = (double)(splitter * adxtail); abig = (double)(c - adxtail); ahi = c - abig; alo = adxtail - ahi; c = (double)(splitter * bdy); abig = (double)(c - bdy); bhi = c - abig; blo = bdy - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; + tj1 = (double)(adx * bdytail); c = (double)(splitter * adx); abig = (double)(c - adx); ahi = c - abig; alo = adx - ahi; c = (double)(splitter * bdytail); abig = (double)(c - bdytail); bhi = c - abig; blo = bdytail - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; + _i = (double)(ti0 + tj0); bvirt = (double)(_i - ti0); avirt = _i - bvirt; bround = tj0 - bvirt; around = ti0 - avirt; u[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 + tj1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = tj1 - bvirt; around = _0 - avirt; u[1] = around + bround; u3 = (double)(_j + _i); bvirt = (double)(u3 - _j); avirt = u3 - bvirt; bround = _i - bvirt; around = _j - avirt; u[2] = around + bround; + u[3] = u3; + negate = -ady; + ti1 = (double)(bdxtail * negate); c = (double)(splitter * bdxtail); abig = (double)(c - bdxtail); ahi = c - abig; alo = bdxtail - ahi; c = (double)(splitter * negate); abig = (double)(c - negate); bhi = c - abig; blo = negate - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; + negate = -adytail; + tj1 = (double)(bdx * negate); c = (double)(splitter * bdx); abig = (double)(c - bdx); ahi = c - abig; alo = bdx - ahi; c = (double)(splitter * negate); abig = (double)(c - negate); bhi = c - abig; blo = negate - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; + _i = (double)(ti0 + tj0); bvirt = (double)(_i - ti0); avirt = _i - bvirt; bround = tj0 - bvirt; around = ti0 - avirt; v[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 + tj1); bvirt = (double)(_i - _0); avirt = _i - bvirt; bround = tj1 - bvirt; around = _0 - avirt; v[1] = around + bround; v3 = (double)(_j + _i); bvirt = (double)(v3 - _j); avirt = v3 - bvirt; bround = _i - bvirt; around = _j - avirt; v[2] = around + bround; + v[3] = v3; + abtlen = FastExpansionSumZeroElim(4, u, 4, v, abt); + + ti1 = (double)(adxtail * bdytail); c = (double)(splitter * adxtail); abig = (double)(c - adxtail); ahi = c - abig; alo = adxtail - ahi; c = (double)(splitter * bdytail); abig = (double)(c - bdytail); bhi = c - abig; blo = bdytail - bhi; err1 = ti1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); ti0 = (alo * blo) - err3; + tj1 = (double)(bdxtail * adytail); c = (double)(splitter * bdxtail); abig = (double)(c - bdxtail); ahi = c - abig; alo = bdxtail - ahi; c = (double)(splitter * adytail); abig = (double)(c - adytail); bhi = c - abig; blo = adytail - bhi; err1 = tj1 - (ahi * bhi); err2 = err1 - (alo * bhi); err3 = err2 - (ahi * blo); tj0 = (alo * blo) - err3; + _i = (double)(ti0 - tj0); bvirt = (double)(ti0 - _i); avirt = _i + bvirt; bround = bvirt - tj0; around = ti0 - avirt; abtt[0] = around + bround; _j = (double)(ti1 + _i); bvirt = (double)(_j - ti1); avirt = _j - bvirt; bround = _i - bvirt; around = ti1 - avirt; _0 = around + bround; _i = (double)(_0 - tj1); bvirt = (double)(_0 - _i); avirt = _i + bvirt; bround = bvirt - tj1; around = _0 - avirt; abtt[1] = around + bround; abtt3 = (double)(_j + _i); bvirt = (double)(abtt3 - _j); avirt = abtt3 - bvirt; bround = _i - bvirt; around = _j - avirt; abtt[2] = around + bround; + abtt[3] = abtt3; + abttlen = 4; + } + else + { + abt[0] = 0.0; + abtlen = 1; + abtt[0] = 0.0; + abttlen = 1; + } + + if (cdxtail != 0.0) + { + temp16alen = ScaleExpansionZeroElim(cxtablen, cxtab, cdxtail, temp16a); + cxtabtlen = ScaleExpansionZeroElim(abtlen, abt, cdxtail, cxtabt); + temp32alen = ScaleExpansionZeroElim(cxtabtlen, cxtabt, 2.0 * cdx, temp32a); + temp48len = FastExpansionSumZeroElim(temp16alen, temp16a, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + if (adytail != 0.0) + { + temp8len = ScaleExpansionZeroElim(4, bb, cdxtail, temp8); + temp16alen = ScaleExpansionZeroElim(temp8len, temp8, adytail, temp16a); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp16alen, temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (bdytail != 0.0) + { + temp8len = ScaleExpansionZeroElim(4, aa, -cdxtail, temp8); + temp16alen = ScaleExpansionZeroElim(temp8len, temp8, bdytail, temp16a); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp16alen, temp16a, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + + temp32alen = ScaleExpansionZeroElim(cxtabtlen, cxtabt, cdxtail, temp32a); + cxtabttlen = ScaleExpansionZeroElim(abttlen, abtt, cdxtail, cxtabtt); + temp16alen = ScaleExpansionZeroElim(cxtabttlen, cxtabtt, 2.0 * cdx, temp16a); + temp16blen = ScaleExpansionZeroElim(cxtabttlen, cxtabtt, cdxtail, temp16b); + temp32blen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32b); + temp64len = FastExpansionSumZeroElim(temp32alen, temp32a, temp32blen, temp32b, temp64); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp64len, temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + if (cdytail != 0.0) + { + temp16alen = ScaleExpansionZeroElim(cytablen, cytab, cdytail, temp16a); + cytabtlen = ScaleExpansionZeroElim(abtlen, abt, cdytail, cytabt); + temp32alen = ScaleExpansionZeroElim(cytabtlen, cytabt, 2.0 * cdy, temp32a); + temp48len = FastExpansionSumZeroElim(temp16alen, temp16a, temp32alen, temp32a, temp48); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp48len, temp48, finother); + finswap = finnow; finnow = finother; finother = finswap; + + + temp32alen = ScaleExpansionZeroElim(cytabtlen, cytabt, cdytail, temp32a); + cytabttlen = ScaleExpansionZeroElim(abttlen, abtt, cdytail, cytabtt); + temp16alen = ScaleExpansionZeroElim(cytabttlen, cytabtt, 2.0 * cdy, temp16a); + temp16blen = ScaleExpansionZeroElim(cytabttlen, cytabtt, cdytail, temp16b); + temp32blen = FastExpansionSumZeroElim(temp16alen, temp16a, temp16blen, temp16b, temp32b); + temp64len = FastExpansionSumZeroElim(temp32alen, temp32a, temp32blen, temp32b, temp64); + finlength = FastExpansionSumZeroElim(finlength, finnow, temp64len, temp64, finother); + finswap = finnow; finnow = finother; finother = finswap; + } + } + + return finnow[finlength - 1]; + } + + #region Workspace + + // InCircleAdapt workspace: + double[] fin1, fin2, abdet; + + double[] axbc, axxbc, aybc, ayybc, adet; + double[] bxca, bxxca, byca, byyca, bdet; + double[] cxab, cxxab, cyab, cyyab, cdet; + + double[] temp8, temp16a, temp16b, temp16c; + double[] temp32a, temp32b, temp48, temp64; + + private void AllocateWorkspace() + { + fin1 = new double[1152]; + fin2 = new double[1152]; + abdet = new double[64]; + + axbc = new double[8]; + axxbc = new double[16]; + aybc = new double[8]; + ayybc = new double[16]; + adet = new double[32]; + + bxca = new double[8]; + bxxca = new double[16]; + byca = new double[8]; + byyca = new double[16]; + bdet = new double[32]; + + cxab = new double[8]; + cxxab = new double[16]; + cyab = new double[8]; + cyyab = new double[16]; + cdet = new double[32]; + + temp8 = new double[8]; + temp16a = new double[16]; + temp16b = new double[16]; + temp16c = new double[16]; + + temp32a = new double[32]; + temp32b = new double[32]; + temp48 = new double[48]; + temp64 = new double[64]; + } + + private void ClearWorkspace() + { + } + + #endregion + + #endregion + } +} diff --git a/src/Triangle/Smoothing/ISmoother.cs b/src/Triangle/Smoothing/ISmoother.cs new file mode 100644 index 0000000..623af88 --- /dev/null +++ b/src/Triangle/Smoothing/ISmoother.cs @@ -0,0 +1,24 @@ + +namespace TriangleNet.Smoothing +{ + using TriangleNet.Meshing; + + /// + /// Interface for mesh smoothers. + /// + public interface ISmoother + { + /// + /// Smooth mesh with 10 rounds of Voronoi iteration. + /// + /// The mesh. + void Smooth(IMesh mesh); + + /// + /// Smooth mesh with 10 rounds of Voronoi iteration. + /// + /// The mesh. + /// The number of iterations. + void Smooth(IMesh mesh, int limit); + } +} \ No newline at end of file diff --git a/Triangle.NET/Triangle/Smoothing/SimpleSmoother.cs b/src/Triangle/Smoothing/SimpleSmoother.cs similarity index 92% rename from Triangle.NET/Triangle/Smoothing/SimpleSmoother.cs rename to src/Triangle/Smoothing/SimpleSmoother.cs index 718a380..946aab6 100644 --- a/Triangle.NET/Triangle/Smoothing/SimpleSmoother.cs +++ b/src/Triangle/Smoothing/SimpleSmoother.cs @@ -1,170 +1,172 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Smoothing -{ - using TriangleNet.Geometry; - using TriangleNet.Meshing; - using TriangleNet.Topology.DCEL; - using TriangleNet.Voronoi; - - /// - /// Simple mesh smoother implementation. - /// - /// - /// Vertices wich should not move (e.g. segment vertices) MUST have a - /// boundary mark greater than 0. - /// - public class SimpleSmoother : ISmoother - { - TrianglePool pool; - Configuration config; - - IVoronoiFactory factory; - - ConstraintOptions options; - - /// - /// Initializes a new instance of the class. - /// - public SimpleSmoother() - : this(new VoronoiFactory()) - { - } - - /// - /// Initializes a new instance of the class. - /// - public SimpleSmoother(IVoronoiFactory factory) - { - this.factory = factory; - this.pool = new TrianglePool(); - - this.config = new Configuration( - () => RobustPredicates.Default, - () => pool.Restart()); - - this.options = new ConstraintOptions() { ConformingDelaunay = true }; - } - - /// - /// Initializes a new instance of the class. - /// - /// Voronoi object factory. - /// Configuration. - public SimpleSmoother(IVoronoiFactory factory, Configuration config) - { - this.factory = factory; - this.config = config; - - this.options = new ConstraintOptions() { ConformingDelaunay = true }; - } - - public void Smooth(IMesh mesh) - { - Smooth(mesh, 10); - } - - public void Smooth(IMesh mesh, int limit) - { - var smoothedMesh = (Mesh)mesh; - - var mesher = new GenericMesher(config); - var predicates = config.Predicates(); - - // The smoother should respect the mesh segment splitting behavior. - this.options.SegmentSplitting = smoothedMesh.behavior.NoBisect; - - // Take a few smoothing rounds (Lloyd's algorithm). - for (int i = 0; i < limit; i++) - { - Step(smoothedMesh, factory, predicates); - - // Actually, we only want to rebuild, if the mesh is no longer - // Delaunay. Flipping edges could be the right choice instead - // of re-triangulating... - smoothedMesh = (Mesh)mesher.Triangulate(Rebuild(smoothedMesh), options); - - factory.Reset(); - } - - smoothedMesh.CopyTo((Mesh)mesh); - } - - private void Step(Mesh mesh, IVoronoiFactory factory, IPredicates predicates) - { - var voronoi = new BoundedVoronoi(mesh, factory, predicates); - - double x, y; - - foreach (var face in voronoi.Faces) - { - if (face.generator.label == 0) - { - Centroid(face, out x, out y); - - face.generator.x = x; - face.generator.y = y; - } - } - } - - /// - /// Calculate the centroid of a polygon. - /// - private void Centroid(Face face, out double x, out double y) - { - double ai, atmp = 0, xtmp = 0, ytmp = 0; - - var edge = face.Edge; - var first = edge.Next.ID; - - Point p, q; - - do - { - p = edge.Origin; - q = edge.Twin.Origin; - - ai = p.x * q.y - q.x * p.y; - atmp += ai; - xtmp += (q.x + p.x) * ai; - ytmp += (q.y + p.y) * ai; - - edge = edge.Next; - - } while (edge.Next.ID != first); - - x = xtmp / (3 * atmp); - y = ytmp / (3 * atmp); - - //area = atmp / 2; - } - - /// - /// Rebuild the input geometry. - /// - private Polygon Rebuild(Mesh mesh) - { - var data = new Polygon(mesh.vertices.Count); - - foreach (var v in mesh.vertices.Values) - { - // Reset to input vertex. - v.type = VertexType.InputVertex; - - data.Points.Add(v); - } - - data.Segments.AddRange(mesh.subsegs.Values); - - data.Holes.AddRange(mesh.holes); - data.Regions.AddRange(mesh.regions); - - return data; - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Smoothing +{ + using TriangleNet.Geometry; + using TriangleNet.Meshing; + using TriangleNet.Topology.DCEL; + using TriangleNet.Voronoi; + + /// + /// Simple mesh smoother implementation (Lloyd's relaxation algorithm). + /// + /// + /// Vertices which should not move (e.g. segment vertices) MUST have a + /// boundary mark greater than 0. + /// + public class SimpleSmoother : ISmoother + { + TrianglePool pool; + Configuration config; + + IVoronoiFactory factory; + + ConstraintOptions options; + + /// + /// Initializes a new instance of the class. + /// + public SimpleSmoother() + : this(new VoronoiFactory()) + { + } + + /// + /// Initializes a new instance of the class. + /// + public SimpleSmoother(IVoronoiFactory factory) + { + this.factory = factory; + this.pool = new TrianglePool(); + + this.config = new Configuration( + () => RobustPredicates.Default, + () => pool.Restart()); + + this.options = new ConstraintOptions() { ConformingDelaunay = true }; + } + + /// + /// Initializes a new instance of the class. + /// + /// Voronoi object factory. + /// Configuration. + public SimpleSmoother(IVoronoiFactory factory, Configuration config) + { + this.factory = factory; + this.config = config; + + this.options = new ConstraintOptions() { ConformingDelaunay = true }; + } + + /// + public void Smooth(IMesh mesh) + { + Smooth(mesh, 10); + } + + /// + public void Smooth(IMesh mesh, int limit) + { + var smoothedMesh = (Mesh)mesh; + + var mesher = new GenericMesher(config); + var predicates = config.Predicates(); + + // The smoother should respect the mesh segment splitting behavior. + this.options.SegmentSplitting = smoothedMesh.behavior.NoBisect; + + // Take a few smoothing rounds (Lloyd's algorithm). + for (int i = 0; i < limit; i++) + { + Step(smoothedMesh, factory, predicates); + + // Actually, we only want to rebuild, if the mesh is no longer + // Delaunay. Flipping edges could be the right choice instead + // of re-triangulating... + smoothedMesh = (Mesh)mesher.Triangulate(Rebuild(smoothedMesh), options); + + factory.Reset(); + } + + smoothedMesh.CopyTo((Mesh)mesh); + } + + private void Step(Mesh mesh, IVoronoiFactory factory, IPredicates predicates) + { + var voronoi = new BoundedVoronoi(mesh, factory, predicates); + + double x, y; + + foreach (var face in voronoi.Faces) + { + if (face.generator.label == 0) + { + Centroid(face, out x, out y); + + face.generator.x = x; + face.generator.y = y; + } + } + } + + /// + /// Calculate the centroid of a polygon. + /// + private void Centroid(Face face, out double x, out double y) + { + double ai, atmp = 0, xtmp = 0, ytmp = 0; + + var edge = face.Edge; + var first = edge.Next.ID; + + Point p, q; + + do + { + p = edge.Origin; + q = edge.Twin.Origin; + + ai = p.x * q.y - q.x * p.y; + atmp += ai; + xtmp += (q.x + p.x) * ai; + ytmp += (q.y + p.y) * ai; + + edge = edge.Next; + + } while (edge.Next.ID != first); + + x = xtmp / (3 * atmp); + y = ytmp / (3 * atmp); + + //area = atmp / 2; + } + + /// + /// Rebuild the input geometry. + /// + private Polygon Rebuild(Mesh mesh) + { + var data = new Polygon(mesh.vertices.Count); + + foreach (var v in mesh.vertices.Values) + { + // Reset to input vertex. + v.type = VertexType.InputVertex; + + data.Points.Add(v); + } + + data.Segments.AddRange(mesh.subsegs.Values); + + data.Holes.AddRange(mesh.holes); + data.Regions.AddRange(mesh.regions); + + return data; + } + } +} diff --git a/Triangle.NET/Triangle/Smoothing/VoronoiFactory.cs b/src/Triangle/Smoothing/VoronoiFactory.cs similarity index 95% rename from Triangle.NET/Triangle/Smoothing/VoronoiFactory.cs rename to src/Triangle/Smoothing/VoronoiFactory.cs index 8be31aa..c310693 100644 --- a/Triangle.NET/Triangle/Smoothing/VoronoiFactory.cs +++ b/src/Triangle/Smoothing/VoronoiFactory.cs @@ -1,201 +1,201 @@ - -namespace TriangleNet.Smoothing -{ - using System; - using TriangleNet.Topology.DCEL; - using TriangleNet.Voronoi; - - /// - /// Factory which re-uses objects in the smoothing loop to enhance performance. - /// - /// - /// See . - /// - class VoronoiFactory : IVoronoiFactory - { - ObjectPool vertices; - ObjectPool edges; - ObjectPool faces; - - public VoronoiFactory() - { - vertices = new ObjectPool(); - edges = new ObjectPool(); - faces = new ObjectPool(); - } - - public void Initialize(int vertexCount, int edgeCount, int faceCount) - { - vertices.Capacity = vertexCount; - edges.Capacity = edgeCount; - faces.Capacity = faceCount; - - for (int i = vertices.Count; i < vertexCount; i++) - { - vertices.Put(new Vertex(0, 0)); - } - - - for (int i = edges.Count; i < edgeCount; i++) - { - edges.Put(new HalfEdge(null)); - } - - for (int i = faces.Count; i < faceCount; i++) - { - faces.Put(new Face(null)); - } - - Reset(); - } - - public void Reset() - { - vertices.Release(); - edges.Release(); - faces.Release(); - } - - public Vertex CreateVertex(double x, double y) - { - Vertex vertex; - - if (vertices.TryGet(out vertex)) - { - vertex.x = x; - vertex.y = y; - vertex.leaving = null; - - return vertex; - } - - vertex = new Vertex(x, y); - - vertices.Put(vertex); - - return vertex; - } - - public HalfEdge CreateHalfEdge(Vertex origin, Face face) - { - HalfEdge edge; - - if (edges.TryGet(out edge)) - { - edge.origin = origin; - edge.face = face; - edge.next = null; - edge.twin = null; - - if (face != null && face.edge == null) - { - face.edge = edge; - } - - return edge; - } - - edge = new HalfEdge(origin, face); - - edges.Put(edge); - - return edge; - } - - public Face CreateFace(Geometry.Vertex vertex) - { - Face face; - - if (faces.TryGet(out face)) - { - face.id = vertex.id; - face.generator = vertex; - face.edge = null; - - return face; - } - - face = new Face(vertex); - - faces.Put(face); - - return face; - } - - class ObjectPool where T : class - { - int index, count; - - T[] pool; - - public int Count - { - get { return count; } - } - - - public int Capacity - { - get { return this.pool.Length; } - set { Resize(value); } - } - - public ObjectPool(int capacity = 3) - { - this.index = 0; - this.count = 0; - - this.pool = new T[capacity]; - } - - public ObjectPool(T[] pool) - { - this.index = 0; - this.count = 0; - - this.pool = pool; - } - - public bool TryGet(out T obj) - { - if (this.index < this.count) - { - obj = this.pool[this.index++]; - - return true; - } - - obj = null; - - return false; - } - - public void Put(T obj) - { - var capacity = this.pool.Length; - - if (capacity <= this.count) - { - Resize(2 * capacity); - } - - this.pool[this.count++] = obj; - - this.index++; - } - - public void Release() - { - this.index = 0; - } - - private void Resize(int size) - { - if (size > this.count) - { - Array.Resize(ref this.pool, size); - } - } - } - } -} + +namespace TriangleNet.Smoothing +{ + using System; + using TriangleNet.Topology.DCEL; + using TriangleNet.Voronoi; + + /// + /// Factory which re-uses objects in the smoothing loop to enhance performance. + /// + /// + /// See . + /// + class VoronoiFactory : IVoronoiFactory + { + ObjectPool vertices; + ObjectPool edges; + ObjectPool faces; + + public VoronoiFactory() + { + vertices = new ObjectPool(); + edges = new ObjectPool(); + faces = new ObjectPool(); + } + + public void Initialize(int vertexCount, int edgeCount, int faceCount) + { + vertices.Capacity = vertexCount; + edges.Capacity = edgeCount; + faces.Capacity = faceCount; + + for (int i = vertices.Count; i < vertexCount; i++) + { + vertices.Put(new Vertex(0, 0)); + } + + + for (int i = edges.Count; i < edgeCount; i++) + { + edges.Put(new HalfEdge(null)); + } + + for (int i = faces.Count; i < faceCount; i++) + { + faces.Put(new Face(null)); + } + + Reset(); + } + + public void Reset() + { + vertices.Release(); + edges.Release(); + faces.Release(); + } + + public Vertex CreateVertex(double x, double y) + { + Vertex vertex; + + if (vertices.TryGet(out vertex)) + { + vertex.x = x; + vertex.y = y; + vertex.leaving = null; + + return vertex; + } + + vertex = new Vertex(x, y); + + vertices.Put(vertex); + + return vertex; + } + + public HalfEdge CreateHalfEdge(Vertex origin, Face face) + { + HalfEdge edge; + + if (edges.TryGet(out edge)) + { + edge.origin = origin; + edge.face = face; + edge.next = null; + edge.twin = null; + + if (face != null && face.edge == null) + { + face.edge = edge; + } + + return edge; + } + + edge = new HalfEdge(origin, face); + + edges.Put(edge); + + return edge; + } + + public Face CreateFace(Geometry.Vertex vertex) + { + Face face; + + if (faces.TryGet(out face)) + { + face.id = vertex.id; + face.generator = vertex; + face.edge = null; + + return face; + } + + face = new Face(vertex); + + faces.Put(face); + + return face; + } + + class ObjectPool where T : class + { + int index, count; + + T[] pool; + + public int Count + { + get { return count; } + } + + + public int Capacity + { + get { return this.pool.Length; } + set { Resize(value); } + } + + public ObjectPool(int capacity = 3) + { + this.index = 0; + this.count = 0; + + this.pool = new T[capacity]; + } + + public ObjectPool(T[] pool) + { + this.index = 0; + this.count = 0; + + this.pool = pool; + } + + public bool TryGet(out T obj) + { + if (this.index < this.count) + { + obj = this.pool[this.index++]; + + return true; + } + + obj = null; + + return false; + } + + public void Put(T obj) + { + var capacity = this.pool.Length; + + if (capacity <= this.count) + { + Resize(2 * capacity); + } + + this.pool[this.count++] = obj; + + this.index++; + } + + public void Release() + { + this.index = 0; + } + + private void Resize(int size) + { + if (size > this.count) + { + Array.Resize(ref this.pool, size); + } + } + } + } +} diff --git a/Triangle.NET/Triangle/Tools/AdjacencyMatrix.cs b/src/Triangle/Tools/AdjacencyMatrix.cs similarity index 95% rename from Triangle.NET/Triangle/Tools/AdjacencyMatrix.cs rename to src/Triangle/Tools/AdjacencyMatrix.cs index 6cabe33..8900ffc 100644 --- a/Triangle.NET/Triangle/Tools/AdjacencyMatrix.cs +++ b/src/Triangle/Tools/AdjacencyMatrix.cs @@ -1,285 +1,285 @@ -// ----------------------------------------------------------------------- -// -// Original Matlab code by John Burkardt, Florida State University -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Tools -{ - using System; - - /// - /// The adjacency matrix of the mesh. - /// - public class AdjacencyMatrix - { - // Number of adjacency entries. - int nnz; - - // Pointers into the actual adjacency structure adj. Information about row k is - // stored in entries pcol(k) through pcol(k+1)-1 of adj. Size: N + 1 - int[] pcol; - - // The adjacency structure. For each row, it contains the column indices - // of the nonzero entries. Size: nnz - int[] irow; - - /// - /// Gets the number of columns (nodes of the mesh). - /// - public readonly int N; - - /// - /// Gets the column pointers. - /// - public int[] ColumnPointers - { - get { return pcol; } - } - - /// - /// Gets the row indices. - /// - public int[] RowIndices - { - get { return irow; } - } - - public AdjacencyMatrix(Mesh mesh) - { - this.N = mesh.vertices.Count; - - // Set up the adj_row adjacency pointer array. - this.pcol = AdjacencyCount(mesh); - this.nnz = pcol[N]; - - // Set up the adj adjacency array. - this.irow = AdjacencySet(mesh, this.pcol); - - SortIndices(); - } - - public AdjacencyMatrix(int[] pcol, int[] irow) - { - this.N = pcol.Length - 1; - - this.nnz = pcol[N]; - - this.pcol = pcol; - this.irow = irow; - - if (pcol[0] != 0) - { - throw new ArgumentException("Expected 0-based indexing.", "pcol"); - } - - if (irow.Length < nnz) - { - throw new ArgumentException(); - } - } - - /// - /// Computes the bandwidth of an adjacency matrix. - /// - /// Bandwidth of the adjacency matrix. - public int Bandwidth() - { - int band_hi; - int band_lo; - int col; - int i, j; - - band_lo = 0; - band_hi = 0; - - for (i = 0; i < N; i++) - { - for (j = pcol[i]; j < pcol[i + 1]; j++) - { - col = irow[j]; - band_lo = Math.Max(band_lo, i - col); - band_hi = Math.Max(band_hi, col - i); - } - } - - return band_lo + 1 + band_hi; - } - - #region Adjacency matrix - - /// - /// Counts adjacencies in a triangulation. - /// - /// - /// This routine is called to count the adjacencies, so that the - /// appropriate amount of memory can be set aside for storage when - /// the adjacency structure is created. - /// - /// The triangulation is assumed to involve 3-node triangles. - /// - /// Two nodes are "adjacent" if they are both nodes in some triangle. - /// Also, a node is considered to be adjacent to itself. - /// - int[] AdjacencyCount(Mesh mesh) - { - int n = N; - int n1, n2, n3; - int tid, nid; - - int[] pcol = new int[n + 1]; - - // Set every node to be adjacent to itself. - for (int i = 0; i < n; i++) - { - pcol[i] = 1; - } - - // Examine each triangle. - foreach (var tri in mesh.triangles) - { - tid = tri.id; - - n1 = tri.vertices[0].id; - n2 = tri.vertices[1].id; - n3 = tri.vertices[2].id; - - // Add edge (1,2) if this is the first occurrence, that is, if - // the edge (1,2) is on a boundary (nid <= 0) or if this triangle - // is the first of the pair in which the edge occurs (tid < nid). - nid = tri.neighbors[2].tri.id; - - if (nid < 0 || tid < nid) - { - pcol[n1] += 1; - pcol[n2] += 1; - } - - // Add edge (2,3). - nid = tri.neighbors[0].tri.id; - - if (nid < 0 || tid < nid) - { - pcol[n2] += 1; - pcol[n3] += 1; - } - - // Add edge (3,1). - nid = tri.neighbors[1].tri.id; - - if (nid < 0 || tid < nid) - { - pcol[n3] += 1; - pcol[n1] += 1; - } - } - - // We used PCOL to count the number of entries in each column. - // Convert it to pointers into the ADJ array. - for (int i = n; i > 0; i--) - { - pcol[i] = pcol[i - 1]; - } - - pcol[0] = 0; - for (int i = 1; i <= n; i++) - { - pcol[i] = pcol[i - 1] + pcol[i]; - } - - return pcol; - } - - /// - /// Sets adjacencies in a triangulation. - /// - /// - /// This routine can be used to create the compressed column storage - /// for a linear triangle finite element discretization of Poisson's - /// equation in two dimensions. - /// - int[] AdjacencySet(Mesh mesh, int[] pcol) - { - int n = this.N; - - int[] col = new int[n]; - - // Copy of the adjacency rows input. - Array.Copy(pcol, col, n); - - int i, nnz = pcol[n]; - - // Output list, stores the actual adjacency information. - int[] list = new int[nnz]; - - // Set every node to be adjacent to itself. - for (i = 0; i < n; i++) - { - list[col[i]] = i; - col[i] += 1; - } - - int n1, n2, n3; // Vertex numbers. - int tid, nid; // Triangle and neighbor id. - - // Examine each triangle. - foreach (var tri in mesh.triangles) - { - tid = tri.id; - - n1 = tri.vertices[0].id; - n2 = tri.vertices[1].id; - n3 = tri.vertices[2].id; - - // Add edge (1,2) if this is the first occurrence, that is, if - // the edge (1,2) is on a boundary (nid <= 0) or if this triangle - // is the first of the pair in which the edge occurs (tid < nid). - nid = tri.neighbors[2].tri.id; - - if (nid < 0 || tid < nid) - { - list[col[n1]++] = n2; - list[col[n2]++] = n1; - } - - // Add edge (2,3). - nid = tri.neighbors[0].tri.id; - - if (nid < 0 || tid < nid) - { - list[col[n2]++] = n3; - list[col[n3]++] = n2; - } - - // Add edge (3,1). - nid = tri.neighbors[1].tri.id; - - if (nid < 0 || tid < nid) - { - list[col[n1]++] = n3; - list[col[n3]++] = n1; - } - } - - return list; - } - - public void SortIndices() - { - int k1, k2, n = N; - - int[] list = this.irow; - - // Ascending sort the entries for each column. - for (int i = 0; i < n; i++) - { - k1 = pcol[i]; - k2 = pcol[i + 1]; - Array.Sort(list, k1, k2 - k1); - } - } - - #endregion - } -} +// ----------------------------------------------------------------------- +// +// Original Matlab code by John Burkardt, Florida State University +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + + /// + /// The adjacency matrix of the mesh. + /// + public class AdjacencyMatrix + { + // Number of adjacency entries. + int nnz; + + // Pointers into the actual adjacency structure adj. Information about row k is + // stored in entries pcol(k) through pcol(k+1)-1 of adj. Size: N + 1 + int[] pcol; + + // The adjacency structure. For each row, it contains the column indices + // of the nonzero entries. Size: nnz + int[] irow; + + /// + /// Gets the number of columns (nodes of the mesh). + /// + public readonly int N; + + /// + /// Gets the column pointers. + /// + public int[] ColumnPointers + { + get { return pcol; } + } + + /// + /// Gets the row indices. + /// + public int[] RowIndices + { + get { return irow; } + } + + public AdjacencyMatrix(Mesh mesh) + { + this.N = mesh.vertices.Count; + + // Set up the adj_row adjacency pointer array. + this.pcol = AdjacencyCount(mesh); + this.nnz = pcol[N]; + + // Set up the adj adjacency array. + this.irow = AdjacencySet(mesh, this.pcol); + + SortIndices(); + } + + public AdjacencyMatrix(int[] pcol, int[] irow) + { + this.N = pcol.Length - 1; + + this.nnz = pcol[N]; + + this.pcol = pcol; + this.irow = irow; + + if (pcol[0] != 0) + { + throw new ArgumentException("Expected 0-based indexing.", "pcol"); + } + + if (irow.Length < nnz) + { + throw new ArgumentException(); + } + } + + /// + /// Computes the bandwidth of an adjacency matrix. + /// + /// Bandwidth of the adjacency matrix. + public int Bandwidth() + { + int band_hi; + int band_lo; + int col; + int i, j; + + band_lo = 0; + band_hi = 0; + + for (i = 0; i < N; i++) + { + for (j = pcol[i]; j < pcol[i + 1]; j++) + { + col = irow[j]; + band_lo = Math.Max(band_lo, i - col); + band_hi = Math.Max(band_hi, col - i); + } + } + + return band_lo + 1 + band_hi; + } + + #region Adjacency matrix + + /// + /// Counts adjacencies in a triangulation. + /// + /// + /// This routine is called to count the adjacencies, so that the + /// appropriate amount of memory can be set aside for storage when + /// the adjacency structure is created. + /// + /// The triangulation is assumed to involve 3-node triangles. + /// + /// Two nodes are "adjacent" if they are both nodes in some triangle. + /// Also, a node is considered to be adjacent to itself. + /// + int[] AdjacencyCount(Mesh mesh) + { + int n = N; + int n1, n2, n3; + int tid, nid; + + int[] pcol = new int[n + 1]; + + // Set every node to be adjacent to itself. + for (int i = 0; i < n; i++) + { + pcol[i] = 1; + } + + // Examine each triangle. + foreach (var tri in mesh.triangles) + { + tid = tri.id; + + n1 = tri.vertices[0].id; + n2 = tri.vertices[1].id; + n3 = tri.vertices[2].id; + + // Add edge (1,2) if this is the first occurrence, that is, if + // the edge (1,2) is on a boundary (nid <= 0) or if this triangle + // is the first of the pair in which the edge occurs (tid < nid). + nid = tri.neighbors[2].tri.id; + + if (nid < 0 || tid < nid) + { + pcol[n1] += 1; + pcol[n2] += 1; + } + + // Add edge (2,3). + nid = tri.neighbors[0].tri.id; + + if (nid < 0 || tid < nid) + { + pcol[n2] += 1; + pcol[n3] += 1; + } + + // Add edge (3,1). + nid = tri.neighbors[1].tri.id; + + if (nid < 0 || tid < nid) + { + pcol[n3] += 1; + pcol[n1] += 1; + } + } + + // We used PCOL to count the number of entries in each column. + // Convert it to pointers into the ADJ array. + for (int i = n; i > 0; i--) + { + pcol[i] = pcol[i - 1]; + } + + pcol[0] = 0; + for (int i = 1; i <= n; i++) + { + pcol[i] = pcol[i - 1] + pcol[i]; + } + + return pcol; + } + + /// + /// Sets adjacencies in a triangulation. + /// + /// + /// This routine can be used to create the compressed column storage + /// for a linear triangle finite element discretization of Poisson's + /// equation in two dimensions. + /// + int[] AdjacencySet(Mesh mesh, int[] pcol) + { + int n = this.N; + + int[] col = new int[n]; + + // Copy of the adjacency rows input. + Array.Copy(pcol, col, n); + + int i, nnz = pcol[n]; + + // Output list, stores the actual adjacency information. + int[] list = new int[nnz]; + + // Set every node to be adjacent to itself. + for (i = 0; i < n; i++) + { + list[col[i]] = i; + col[i] += 1; + } + + int n1, n2, n3; // Vertex numbers. + int tid, nid; // Triangle and neighbor id. + + // Examine each triangle. + foreach (var tri in mesh.triangles) + { + tid = tri.id; + + n1 = tri.vertices[0].id; + n2 = tri.vertices[1].id; + n3 = tri.vertices[2].id; + + // Add edge (1,2) if this is the first occurrence, that is, if + // the edge (1,2) is on a boundary (nid <= 0) or if this triangle + // is the first of the pair in which the edge occurs (tid < nid). + nid = tri.neighbors[2].tri.id; + + if (nid < 0 || tid < nid) + { + list[col[n1]++] = n2; + list[col[n2]++] = n1; + } + + // Add edge (2,3). + nid = tri.neighbors[0].tri.id; + + if (nid < 0 || tid < nid) + { + list[col[n2]++] = n3; + list[col[n3]++] = n2; + } + + // Add edge (3,1). + nid = tri.neighbors[1].tri.id; + + if (nid < 0 || tid < nid) + { + list[col[n1]++] = n3; + list[col[n3]++] = n1; + } + } + + return list; + } + + public void SortIndices() + { + int k1, k2, n = N; + + int[] list = this.irow; + + // Ascending sort the entries for each column. + for (int i = 0; i < n; i++) + { + k1 = pcol[i]; + k2 = pcol[i + 1]; + Array.Sort(list, k1, k2 - k1); + } + } + + #endregion + } +} diff --git a/Triangle.NET/Triangle/Tools/CuthillMcKee.cs b/src/Triangle/Tools/CuthillMcKee.cs similarity index 97% rename from Triangle.NET/Triangle/Tools/CuthillMcKee.cs rename to src/Triangle/Tools/CuthillMcKee.cs index 722ef49..9e5cd96 100644 --- a/Triangle.NET/Triangle/Tools/CuthillMcKee.cs +++ b/src/Triangle/Tools/CuthillMcKee.cs @@ -1,685 +1,685 @@ -// ----------------------------------------------------------------------- -// -// Original Matlab code by John Burkardt, Florida State University -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Tools -{ - using System; - - /// - /// Applies the Cuthill and McKee renumbering algorithm to reduce the bandwidth of - /// the adjacency matrix associated with the mesh. - /// - public class CuthillMcKee - { - // The adjacency matrix of the mesh. - AdjacencyMatrix matrix; - - /// - /// Gets the permutation vector for the Reverse Cuthill-McKee numbering. - /// - /// The mesh. - /// Permutation vector. - public int[] Renumber(Mesh mesh) - { - // Algorithm needs linear numbering of the nodes. - mesh.Renumber(NodeNumbering.Linear); - - return Renumber(new AdjacencyMatrix(mesh)); - } - - /// - /// Gets the permutation vector for the Reverse Cuthill-McKee numbering. - /// - /// The mesh. - /// Permutation vector. - public int[] Renumber(AdjacencyMatrix matrix) - { - this.matrix = matrix; - - int bandwidth1 = matrix.Bandwidth(); - - var pcol = matrix.ColumnPointers; - - // Adjust column pointers (1-based indexing). - Shift(pcol, true); - - // TODO: Make RCM work with 0-based matrix. - - // Compute the RCM permutation. - int[] perm = GenerateRcm(); - - int[] perm_inv = PermInverse(perm); - - int bandwidth2 = PermBandwidth(perm, perm_inv); - - if (Log.Verbose) - { - Log.Instance.Info(String.Format("Reverse Cuthill-McKee (Bandwidth: {0} > {1})", - bandwidth1, bandwidth2)); - } - - // Adjust column pointers (0-based indexing). - Shift(pcol, false); - - return perm_inv; - } - - #region RCM - - /// - /// Finds the reverse Cuthill-Mckee ordering for a general graph. - /// - /// The RCM ordering. - /// - /// For each connected component in the graph, the routine obtains - /// an ordering by calling RCM. - /// - int[] GenerateRcm() - { - // Number of nodes in the mesh. - int n = matrix.N; - - int[] perm = new int[n]; - - int i, num, root; - int iccsze = 0; - int level_num = 0; - - /// Index vector for a level structure. The level structure is stored in the - /// currently unused spaces in the permutation vector PERM. - int[] level_row = new int[n + 1]; - - /// Marks variables that have been numbered. - int[] mask = new int[n]; - - for (i = 0; i < n; i++) - { - mask[i] = 1; - } - - num = 1; - - for (i = 0; i < n; i++) - { - // For each masked connected component... - if (mask[i] != 0) - { - root = i; - - // Find a pseudo-peripheral node ROOT. The level structure found by - // ROOT_FIND is stored starting at PERM(NUM). - FindRoot(ref root, mask, ref level_num, level_row, perm, num - 1); - - // RCM orders the component using ROOT as the starting node. - Rcm(root, mask, perm, num - 1, ref iccsze); - - num += iccsze; - - // We can stop once every node is in one of the connected components. - if (n < num) - { - return perm; - } - } - } - - return perm; - } - - /// - /// RCM renumbers a connected component by the reverse Cuthill McKee algorithm. - /// - /// the node that defines the connected component. It is used as the starting - /// point for the RCM ordering. - /// Input/output, int MASK(NODE_NUM), a mask for the nodes. Only those nodes with - /// nonzero input mask values are considered by the routine. The nodes numbered by RCM will have - /// their mask values set to zero. - /// Output, int PERM(NODE_NUM), the RCM ordering. - /// Output, int ICCSZE, the size of the connected component that has been numbered. - /// the number of nodes. - /// - /// The connected component is specified by a node ROOT and a mask. - /// The numbering starts at the root node. - /// - /// An outline of the algorithm is as follows: - /// - /// X(1) = ROOT. - /// - /// for ( I = 1 to N-1) - /// Find all unlabeled neighbors of X(I), - /// assign them the next available labels, in order of increasing degree. - /// - /// When done, reverse the ordering. - /// - void Rcm(int root, int[] mask, int[] perm, int offset, ref int iccsze) - { - int[] pcol = matrix.ColumnPointers; - int[] irow = matrix.RowIndices; - - int fnbr; - int i, j, k, l; - int jstop, jstrt; - int lbegin, lnbr, lperm, lvlend; - int nbr, node; - - // Number of nodes in the mesh. - int n = matrix.N; - - /// Workspace, int DEG[NODE_NUM], a temporary vector used to hold - /// the degree of the nodes in the section graph specified by mask and root. - int[] deg = new int[n]; - - // Find the degrees of the nodes in the component specified by MASK and ROOT. - Degree(root, mask, deg, ref iccsze, perm, offset); - - mask[root] = 0; - - if (iccsze <= 1) - { - return; - } - - lvlend = 0; - lnbr = 1; - - // LBEGIN and LVLEND point to the beginning and - // the end of the current level respectively. - while (lvlend < lnbr) - { - lbegin = lvlend + 1; - lvlend = lnbr; - - for (i = lbegin; i <= lvlend; i++) - { - // For each node in the current level... - node = perm[offset + i - 1]; - jstrt = pcol[node]; - jstop = pcol[node + 1] - 1; - - // Find the unnumbered neighbors of NODE. - - // FNBR and LNBR point to the first and last neighbors - // of the current node in PERM. - fnbr = lnbr + 1; - - for (j = jstrt; j <= jstop; j++) - { - nbr = irow[j - 1]; - - if (mask[nbr] != 0) - { - lnbr += 1; - mask[nbr] = 0; - perm[offset + lnbr - 1] = nbr; - } - } - - // Node has neighbors - if (lnbr > fnbr) - { - // Sort the neighbors of NODE in increasing order by degree. - // Linear insertion is used. - k = fnbr; - - while (k < lnbr) - { - l = k; - k = k + 1; - nbr = perm[offset + k - 1]; - - while (fnbr < l) - { - lperm = perm[offset + l - 1]; - - if (deg[lperm - 1] <= deg[nbr - 1]) - { - break; - } - - perm[offset + l] = lperm; - l = l - 1; - } - perm[offset + l] = nbr; - } - } - } - } - - // We now have the Cuthill-McKee ordering. Reverse it. - ReverseVector(perm, offset, iccsze); - - return; - } - - /// - /// Finds a pseudo-peripheral node. - /// - /// On input, ROOT is a node in the the component of the graph for - /// which a pseudo-peripheral node is sought. On output, ROOT is the pseudo-peripheral - /// node obtained. - /// MASK[NODE_NUM], specifies a section subgraph. Nodes for which MASK - /// is zero are ignored by FNROOT. - /// Output, int LEVEL_NUM, is the number of levels in the level - /// structure rooted at the node ROOT. - /// Output, int LEVEL_ROW(NODE_NUM+1), the level structure array pair - /// containing the level structure found. - /// Output, int LEVEL(NODE_NUM), the level structure array pair - /// containing the level structure found. - /// the number of nodes. - /// - /// The diameter of a graph is the maximum distance (number of edges) - /// between any two nodes of the graph. - /// - /// The eccentricity of a node is the maximum distance between that - /// node and any other node of the graph. - /// - /// A peripheral node is a node whose eccentricity equals the - /// diameter of the graph. - /// - /// A pseudo-peripheral node is an approximation to a peripheral node; - /// it may be a peripheral node, but all we know is that we tried our - /// best. - /// - /// The routine is given a graph, and seeks pseudo-peripheral nodes, - /// using a modified version of the scheme of Gibbs, Poole and - /// Stockmeyer. It determines such a node for the section subgraph - /// specified by MASK and ROOT. - /// - /// The routine also determines the level structure associated with - /// the given pseudo-peripheral node; that is, how far each node - /// is from the pseudo-peripheral node. The level structure is - /// returned as a list of nodes LS, and pointers to the beginning - /// of the list of nodes that are at a distance of 0, 1, 2, ..., - /// NODE_NUM-1 from the pseudo-peripheral node. - /// - /// Reference: - /// Alan George, Joseph Liu, - /// Computer Solution of Large Sparse Positive Definite Systems, - /// Prentice Hall, 1981. - /// - /// Norman Gibbs, William Poole, Paul Stockmeyer, - /// An Algorithm for Reducing the Bandwidth and Profile of a Sparse Matrix, - /// SIAM Journal on Numerical Analysis, - /// Volume 13, pages 236-250, 1976. - /// - /// Norman Gibbs, - /// Algorithm 509: A Hybrid Profile Reduction Algorithm, - /// ACM Transactions on Mathematical Software, - /// Volume 2, pages 378-387, 1976. - /// - void FindRoot(ref int root, int[] mask, ref int level_num, int[] level_row, - int[] level, int offset) - { - int[] pcol = matrix.ColumnPointers; - int[] irow = matrix.RowIndices; - - int iccsze; - int j, jstrt; - int k, kstop, kstrt; - int mindeg; - int nghbor, ndeg; - int node; - int level_num2 = 0; - - // Determine the level structure rooted at ROOT. - GetLevelSet(ref root, mask, ref level_num, level_row, level, offset); - - // Count the number of nodes in this level structure. - iccsze = level_row[level_num] - 1; - - // Extreme cases: - // A complete graph has a level set of only a single level. - // Every node is equally good (or bad). - // or - // A "line graph" 0--0--0--0--0 has every node in its only level. - // By chance, we've stumbled on the ideal root. - if (level_num == 1 || level_num == iccsze) - { - return; - } - - // Pick any node from the last level that has minimum degree - // as the starting point to generate a new level set. - for (; ; ) - { - mindeg = iccsze; - - jstrt = level_row[level_num - 1]; - root = level[offset + jstrt - 1]; - - if (jstrt < iccsze) - { - for (j = jstrt; j <= iccsze; j++) - { - node = level[offset + j - 1]; - ndeg = 0; - kstrt = pcol[node - 1]; - kstop = pcol[node] - 1; - - for (k = kstrt; k <= kstop; k++) - { - nghbor = irow[k - 1]; - if (mask[nghbor] > 0) - { - ndeg += 1; - } - } - - if (ndeg < mindeg) - { - root = node; - mindeg = ndeg; - } - } - } - - // Generate the rooted level structure associated with this node. - GetLevelSet(ref root, mask, ref level_num2, level_row, level, offset); - - // If the number of levels did not increase, accept the new ROOT. - if (level_num2 <= level_num) - { - break; - } - - level_num = level_num2; - - // In the unlikely case that ROOT is one endpoint of a line graph, - // we can exit now. - if (iccsze <= level_num) - { - break; - } - } - - return; - } - - /// - /// Generates the connected level structure rooted at a given node. - /// - /// the node at which the level structure is to be rooted. - /// MASK[NODE_NUM]. On input, only nodes with nonzero MASK are to be processed. - /// On output, those nodes which were included in the level set have MASK set to 1. - /// Output, int LEVEL_NUM, the number of levels in the level structure. ROOT is - /// in level 1. The neighbors of ROOT are in level 2, and so on. - /// Output, int LEVEL_ROW[NODE_NUM+1], the rooted level structure. - /// Output, int LEVEL[NODE_NUM], the rooted level structure. - /// the number of nodes. - /// - /// Only nodes for which MASK is nonzero will be considered. - /// - /// The root node chosen by the user is assigned level 1, and masked. - /// All (unmasked) nodes reachable from a node in level 1 are - /// assigned level 2 and masked. The process continues until there - /// are no unmasked nodes adjacent to any node in the current level. - /// The number of levels may vary between 2 and NODE_NUM. - /// - /// Reference: - /// Alan George, Joseph Liu, - /// Computer Solution of Large Sparse Positive Definite Systems, - /// Prentice Hall, 1981. - /// - void GetLevelSet(ref int root, int[] mask, ref int level_num, int[] level_row, - int[] level, int offset) - { - int[] pcol = matrix.ColumnPointers; - int[] irow = matrix.RowIndices; - - int i, iccsze; - int j, jstop, jstrt; - int lbegin, lvlend, lvsize; - int nbr; - int node; - - mask[root] = 0; - level[offset] = root; - level_num = 0; - lvlend = 0; - iccsze = 1; - - // LBEGIN is the pointer to the beginning of the current level, and - // LVLEND points to the end of this level. - for (; ; ) - { - lbegin = lvlend + 1; - lvlend = iccsze; - level_num += 1; - level_row[level_num - 1] = lbegin; - - // Generate the next level by finding all the masked neighbors of nodes - // in the current level. - for (i = lbegin; i <= lvlend; i++) - { - node = level[offset + i - 1]; - jstrt = pcol[node]; - jstop = pcol[node + 1] - 1; - - for (j = jstrt; j <= jstop; j++) - { - nbr = irow[j - 1]; - - if (mask[nbr] != 0) - { - iccsze += 1; - level[offset + iccsze - 1] = nbr; - mask[nbr] = 0; - } - } - } - - // Compute the current level width (the number of nodes encountered.) - // If it is positive, generate the next level. - lvsize = iccsze - lvlend; - - if (lvsize <= 0) - { - break; - } - } - - level_row[level_num] = lvlend + 1; - - // Reset MASK to 1 for the nodes in the level structure. - for (i = 0; i < iccsze; i++) - { - mask[level[offset + i]] = 1; - } - - return; - } - - /// - /// Computes the degrees of the nodes in the connected component. - /// - /// the node that defines the connected component. - /// MASK[NODE_NUM], is nonzero for those nodes which are to be considered. - /// Output, int DEG[NODE_NUM], contains, for each node in the connected component, its degree. - /// Output, int ICCSIZE, the number of nodes in the connected component. - /// Output, int LS[NODE_NUM], stores in entries 1 through ICCSIZE the nodes in the - /// connected component, starting with ROOT, and proceeding by levels. - /// the number of nodes. - /// - /// The connected component is specified by MASK and ROOT. - /// Nodes for which MASK is zero are ignored. - /// - /// Reference: - /// Alan George, Joseph Liu, - /// Computer Solution of Large Sparse Positive Definite Systems, - /// Prentice Hall, 1981. - /// - void Degree(int root, int[] mask, int[] deg, ref int iccsze, int[] ls, int offset) - { - int[] pcol = matrix.ColumnPointers; - int[] irow = matrix.RowIndices; - - int i, ideg; - int j, jstop, jstrt; - int lbegin, lvlend; - int lvsize = 1; - int nbr, node; - - // The sign of ADJ_ROW(I) is used to indicate if node I has been considered. - ls[offset] = root; - pcol[root] = -pcol[root]; - lvlend = 0; - iccsze = 1; - - // If the current level width is nonzero, generate another level. - while (lvsize > 0) - { - // LBEGIN is the pointer to the beginning of the current level, and - // LVLEND points to the end of this level. - lbegin = lvlend + 1; - lvlend = iccsze; - - // Find the degrees of nodes in the current level, - // and at the same time, generate the next level. - for (i = lbegin; i <= lvlend; i++) - { - node = ls[offset + i - 1]; - jstrt = -pcol[node]; - jstop = Math.Abs(pcol[node + 1]) - 1; - ideg = 0; - - for (j = jstrt; j <= jstop; j++) - { - nbr = irow[j - 1]; - - if (mask[nbr] != 0) // EDIT: [nbr - 1] - { - ideg = ideg + 1; - - if (0 <= pcol[nbr]) // EDIT: [nbr - 1] - { - pcol[nbr] = -pcol[nbr]; // EDIT: [nbr - 1] - iccsze = iccsze + 1; - ls[offset + iccsze - 1] = nbr; - } - } - } - deg[node] = ideg; - } - - // Compute the current level width. - lvsize = iccsze - lvlend; - } - - // Reset ADJ_ROW to its correct sign and return. - for (i = 0; i < iccsze; i++) - { - node = ls[offset + i]; - pcol[node] = -pcol[node]; - } - - return; - } - - #endregion - - #region Tools - - /// - /// Computes the bandwidth of a permuted adjacency matrix. - /// - /// The permutation. - /// The inverse permutation. - /// Bandwidth of the permuted adjacency matrix. - /// - /// The matrix is defined by the adjacency information and a permutation. - /// The routine also computes the bandwidth and the size of the envelope. - /// - int PermBandwidth(int[] perm, int[] perm_inv) - { - int[] pcol = matrix.ColumnPointers; - int[] irow = matrix.RowIndices; - - int col, i, j; - - int band_lo = 0; - int band_hi = 0; - - int n = matrix.N; - - for (i = 0; i < n; i++) - { - for (j = pcol[perm[i]]; j < pcol[perm[i] + 1]; j++) - { - col = perm_inv[irow[j - 1]]; - band_lo = Math.Max(band_lo, i - col); - band_hi = Math.Max(band_hi, col - i); - } - } - - return band_lo + 1 + band_hi; - } - - /// - /// Produces the inverse of a given permutation. - /// - /// Number of items permuted. - /// PERM[N], a permutation. - /// The inverse permutation. - int[] PermInverse(int[] perm) - { - int n = matrix.N; - - int[] perm_inv = new int[n]; - - for (int i = 0; i < n; i++) - { - perm_inv[perm[i]] = i; - } - - return perm_inv; - } - - /// - /// Reverses the elements of an integer vector. - /// - /// number of entries in the array. - /// the array to be reversed. - /// - /// Input: - /// N = 5, - /// A = ( 11, 12, 13, 14, 15 ). - /// - /// Output: - /// A = ( 15, 14, 13, 12, 11 ). - /// - void ReverseVector(int[] a, int offset, int size) - { - int i; - int j; - - for (i = 0; i < size / 2; i++) - { - j = a[offset + i]; - a[offset + i] = a[offset + size - 1 - i]; - a[offset + size - 1 - i] = j; - } - - return; - } - - void Shift(int[] a, bool up) - { - int length = a.Length; - - if (up) - { - for (int i = 0; i < length; a[i]++, i++) ; - } - else - { - for (int i = 0; i < length; a[i]--, i++) ; - } - } - - #endregion - } -} +// ----------------------------------------------------------------------- +// +// Original Matlab code by John Burkardt, Florida State University +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + + /// + /// Applies the Cuthill and McKee renumbering algorithm to reduce the bandwidth of + /// the adjacency matrix associated with the mesh. + /// + public class CuthillMcKee + { + // The adjacency matrix of the mesh. + AdjacencyMatrix matrix; + + /// + /// Gets the permutation vector for the Reverse Cuthill-McKee numbering. + /// + /// The mesh. + /// Permutation vector. + public int[] Renumber(Mesh mesh) + { + // Algorithm needs linear numbering of the nodes. + mesh.Renumber(NodeNumbering.Linear); + + return Renumber(new AdjacencyMatrix(mesh)); + } + + /// + /// Gets the permutation vector for the Reverse Cuthill-McKee numbering. + /// + /// The mesh. + /// Permutation vector. + public int[] Renumber(AdjacencyMatrix matrix) + { + this.matrix = matrix; + + int bandwidth1 = matrix.Bandwidth(); + + var pcol = matrix.ColumnPointers; + + // Adjust column pointers (1-based indexing). + Shift(pcol, true); + + // TODO: Make RCM work with 0-based matrix. + + // Compute the RCM permutation. + int[] perm = GenerateRcm(); + + int[] perm_inv = PermInverse(perm); + + int bandwidth2 = PermBandwidth(perm, perm_inv); + + if (Log.Verbose) + { + Log.Instance.Info(String.Format("Reverse Cuthill-McKee (Bandwidth: {0} > {1})", + bandwidth1, bandwidth2)); + } + + // Adjust column pointers (0-based indexing). + Shift(pcol, false); + + return perm_inv; + } + + #region RCM + + /// + /// Finds the reverse Cuthill-Mckee ordering for a general graph. + /// + /// The RCM ordering. + /// + /// For each connected component in the graph, the routine obtains + /// an ordering by calling RCM. + /// + int[] GenerateRcm() + { + // Number of nodes in the mesh. + int n = matrix.N; + + int[] perm = new int[n]; + + int i, num, root; + int iccsze = 0; + int level_num = 0; + + /// Index vector for a level structure. The level structure is stored in the + /// currently unused spaces in the permutation vector PERM. + int[] level_row = new int[n + 1]; + + /// Marks variables that have been numbered. + int[] mask = new int[n]; + + for (i = 0; i < n; i++) + { + mask[i] = 1; + } + + num = 1; + + for (i = 0; i < n; i++) + { + // For each masked connected component... + if (mask[i] != 0) + { + root = i; + + // Find a pseudo-peripheral node ROOT. The level structure found by + // ROOT_FIND is stored starting at PERM(NUM). + FindRoot(ref root, mask, ref level_num, level_row, perm, num - 1); + + // RCM orders the component using ROOT as the starting node. + Rcm(root, mask, perm, num - 1, ref iccsze); + + num += iccsze; + + // We can stop once every node is in one of the connected components. + if (n < num) + { + return perm; + } + } + } + + return perm; + } + + /// + /// RCM renumbers a connected component by the reverse Cuthill McKee algorithm. + /// + /// the node that defines the connected component. It is used as the starting + /// point for the RCM ordering. + /// Input/output, int MASK(NODE_NUM), a mask for the nodes. Only those nodes with + /// nonzero input mask values are considered by the routine. The nodes numbered by RCM will have + /// their mask values set to zero. + /// Output, int PERM(NODE_NUM), the RCM ordering. + /// Output, int ICCSZE, the size of the connected component that has been numbered. + /// the number of nodes. + /// + /// The connected component is specified by a node ROOT and a mask. + /// The numbering starts at the root node. + /// + /// An outline of the algorithm is as follows: + /// + /// X(1) = ROOT. + /// + /// for ( I = 1 to N-1) + /// Find all unlabeled neighbors of X(I), + /// assign them the next available labels, in order of increasing degree. + /// + /// When done, reverse the ordering. + /// + void Rcm(int root, int[] mask, int[] perm, int offset, ref int iccsze) + { + int[] pcol = matrix.ColumnPointers; + int[] irow = matrix.RowIndices; + + int fnbr; + int i, j, k, l; + int jstop, jstrt; + int lbegin, lnbr, lperm, lvlend; + int nbr, node; + + // Number of nodes in the mesh. + int n = matrix.N; + + /// Workspace, int DEG[NODE_NUM], a temporary vector used to hold + /// the degree of the nodes in the section graph specified by mask and root. + int[] deg = new int[n]; + + // Find the degrees of the nodes in the component specified by MASK and ROOT. + Degree(root, mask, deg, ref iccsze, perm, offset); + + mask[root] = 0; + + if (iccsze <= 1) + { + return; + } + + lvlend = 0; + lnbr = 1; + + // LBEGIN and LVLEND point to the beginning and + // the end of the current level respectively. + while (lvlend < lnbr) + { + lbegin = lvlend + 1; + lvlend = lnbr; + + for (i = lbegin; i <= lvlend; i++) + { + // For each node in the current level... + node = perm[offset + i - 1]; + jstrt = pcol[node]; + jstop = pcol[node + 1] - 1; + + // Find the unnumbered neighbors of NODE. + + // FNBR and LNBR point to the first and last neighbors + // of the current node in PERM. + fnbr = lnbr + 1; + + for (j = jstrt; j <= jstop; j++) + { + nbr = irow[j - 1]; + + if (mask[nbr] != 0) + { + lnbr += 1; + mask[nbr] = 0; + perm[offset + lnbr - 1] = nbr; + } + } + + // Node has neighbors + if (lnbr > fnbr) + { + // Sort the neighbors of NODE in increasing order by degree. + // Linear insertion is used. + k = fnbr; + + while (k < lnbr) + { + l = k; + k = k + 1; + nbr = perm[offset + k - 1]; + + while (fnbr < l) + { + lperm = perm[offset + l - 1]; + + if (deg[lperm - 1] <= deg[nbr - 1]) + { + break; + } + + perm[offset + l] = lperm; + l = l - 1; + } + perm[offset + l] = nbr; + } + } + } + } + + // We now have the Cuthill-McKee ordering. Reverse it. + ReverseVector(perm, offset, iccsze); + + return; + } + + /// + /// Finds a pseudo-peripheral node. + /// + /// On input, ROOT is a node in the the component of the graph for + /// which a pseudo-peripheral node is sought. On output, ROOT is the pseudo-peripheral + /// node obtained. + /// MASK[NODE_NUM], specifies a section subgraph. Nodes for which MASK + /// is zero are ignored by FNROOT. + /// Output, int LEVEL_NUM, is the number of levels in the level + /// structure rooted at the node ROOT. + /// Output, int LEVEL_ROW(NODE_NUM+1), the level structure array pair + /// containing the level structure found. + /// Output, int LEVEL(NODE_NUM), the level structure array pair + /// containing the level structure found. + /// the number of nodes. + /// + /// The diameter of a graph is the maximum distance (number of edges) + /// between any two nodes of the graph. + /// + /// The eccentricity of a node is the maximum distance between that + /// node and any other node of the graph. + /// + /// A peripheral node is a node whose eccentricity equals the + /// diameter of the graph. + /// + /// A pseudo-peripheral node is an approximation to a peripheral node; + /// it may be a peripheral node, but all we know is that we tried our + /// best. + /// + /// The routine is given a graph, and seeks pseudo-peripheral nodes, + /// using a modified version of the scheme of Gibbs, Poole and + /// Stockmeyer. It determines such a node for the section subgraph + /// specified by MASK and ROOT. + /// + /// The routine also determines the level structure associated with + /// the given pseudo-peripheral node; that is, how far each node + /// is from the pseudo-peripheral node. The level structure is + /// returned as a list of nodes LS, and pointers to the beginning + /// of the list of nodes that are at a distance of 0, 1, 2, ..., + /// NODE_NUM-1 from the pseudo-peripheral node. + /// + /// Reference: + /// Alan George, Joseph Liu, + /// Computer Solution of Large Sparse Positive Definite Systems, + /// Prentice Hall, 1981. + /// + /// Norman Gibbs, William Poole, Paul Stockmeyer, + /// An Algorithm for Reducing the Bandwidth and Profile of a Sparse Matrix, + /// SIAM Journal on Numerical Analysis, + /// Volume 13, pages 236-250, 1976. + /// + /// Norman Gibbs, + /// Algorithm 509: A Hybrid Profile Reduction Algorithm, + /// ACM Transactions on Mathematical Software, + /// Volume 2, pages 378-387, 1976. + /// + void FindRoot(ref int root, int[] mask, ref int level_num, int[] level_row, + int[] level, int offset) + { + int[] pcol = matrix.ColumnPointers; + int[] irow = matrix.RowIndices; + + int iccsze; + int j, jstrt; + int k, kstop, kstrt; + int mindeg; + int nghbor, ndeg; + int node; + int level_num2 = 0; + + // Determine the level structure rooted at ROOT. + GetLevelSet(ref root, mask, ref level_num, level_row, level, offset); + + // Count the number of nodes in this level structure. + iccsze = level_row[level_num] - 1; + + // Extreme cases: + // A complete graph has a level set of only a single level. + // Every node is equally good (or bad). + // or + // A "line graph" 0--0--0--0--0 has every node in its only level. + // By chance, we've stumbled on the ideal root. + if (level_num == 1 || level_num == iccsze) + { + return; + } + + // Pick any node from the last level that has minimum degree + // as the starting point to generate a new level set. + for (; ; ) + { + mindeg = iccsze; + + jstrt = level_row[level_num - 1]; + root = level[offset + jstrt - 1]; + + if (jstrt < iccsze) + { + for (j = jstrt; j <= iccsze; j++) + { + node = level[offset + j - 1]; + ndeg = 0; + kstrt = pcol[node - 1]; + kstop = pcol[node] - 1; + + for (k = kstrt; k <= kstop; k++) + { + nghbor = irow[k - 1]; + if (mask[nghbor] > 0) + { + ndeg += 1; + } + } + + if (ndeg < mindeg) + { + root = node; + mindeg = ndeg; + } + } + } + + // Generate the rooted level structure associated with this node. + GetLevelSet(ref root, mask, ref level_num2, level_row, level, offset); + + // If the number of levels did not increase, accept the new ROOT. + if (level_num2 <= level_num) + { + break; + } + + level_num = level_num2; + + // In the unlikely case that ROOT is one endpoint of a line graph, + // we can exit now. + if (iccsze <= level_num) + { + break; + } + } + + return; + } + + /// + /// Generates the connected level structure rooted at a given node. + /// + /// the node at which the level structure is to be rooted. + /// MASK[NODE_NUM]. On input, only nodes with nonzero MASK are to be processed. + /// On output, those nodes which were included in the level set have MASK set to 1. + /// Output, int LEVEL_NUM, the number of levels in the level structure. ROOT is + /// in level 1. The neighbors of ROOT are in level 2, and so on. + /// Output, int LEVEL_ROW[NODE_NUM+1], the rooted level structure. + /// Output, int LEVEL[NODE_NUM], the rooted level structure. + /// the number of nodes. + /// + /// Only nodes for which MASK is nonzero will be considered. + /// + /// The root node chosen by the user is assigned level 1, and masked. + /// All (unmasked) nodes reachable from a node in level 1 are + /// assigned level 2 and masked. The process continues until there + /// are no unmasked nodes adjacent to any node in the current level. + /// The number of levels may vary between 2 and NODE_NUM. + /// + /// Reference: + /// Alan George, Joseph Liu, + /// Computer Solution of Large Sparse Positive Definite Systems, + /// Prentice Hall, 1981. + /// + void GetLevelSet(ref int root, int[] mask, ref int level_num, int[] level_row, + int[] level, int offset) + { + int[] pcol = matrix.ColumnPointers; + int[] irow = matrix.RowIndices; + + int i, iccsze; + int j, jstop, jstrt; + int lbegin, lvlend, lvsize; + int nbr; + int node; + + mask[root] = 0; + level[offset] = root; + level_num = 0; + lvlend = 0; + iccsze = 1; + + // LBEGIN is the pointer to the beginning of the current level, and + // LVLEND points to the end of this level. + for (; ; ) + { + lbegin = lvlend + 1; + lvlend = iccsze; + level_num += 1; + level_row[level_num - 1] = lbegin; + + // Generate the next level by finding all the masked neighbors of nodes + // in the current level. + for (i = lbegin; i <= lvlend; i++) + { + node = level[offset + i - 1]; + jstrt = pcol[node]; + jstop = pcol[node + 1] - 1; + + for (j = jstrt; j <= jstop; j++) + { + nbr = irow[j - 1]; + + if (mask[nbr] != 0) + { + iccsze += 1; + level[offset + iccsze - 1] = nbr; + mask[nbr] = 0; + } + } + } + + // Compute the current level width (the number of nodes encountered.) + // If it is positive, generate the next level. + lvsize = iccsze - lvlend; + + if (lvsize <= 0) + { + break; + } + } + + level_row[level_num] = lvlend + 1; + + // Reset MASK to 1 for the nodes in the level structure. + for (i = 0; i < iccsze; i++) + { + mask[level[offset + i]] = 1; + } + + return; + } + + /// + /// Computes the degrees of the nodes in the connected component. + /// + /// the node that defines the connected component. + /// MASK[NODE_NUM], is nonzero for those nodes which are to be considered. + /// Output, int DEG[NODE_NUM], contains, for each node in the connected component, its degree. + /// Output, int ICCSIZE, the number of nodes in the connected component. + /// Output, int LS[NODE_NUM], stores in entries 1 through ICCSIZE the nodes in the + /// connected component, starting with ROOT, and proceeding by levels. + /// the number of nodes. + /// + /// The connected component is specified by MASK and ROOT. + /// Nodes for which MASK is zero are ignored. + /// + /// Reference: + /// Alan George, Joseph Liu, + /// Computer Solution of Large Sparse Positive Definite Systems, + /// Prentice Hall, 1981. + /// + void Degree(int root, int[] mask, int[] deg, ref int iccsze, int[] ls, int offset) + { + int[] pcol = matrix.ColumnPointers; + int[] irow = matrix.RowIndices; + + int i, ideg; + int j, jstop, jstrt; + int lbegin, lvlend; + int lvsize = 1; + int nbr, node; + + // The sign of ADJ_ROW(I) is used to indicate if node I has been considered. + ls[offset] = root; + pcol[root] = -pcol[root]; + lvlend = 0; + iccsze = 1; + + // If the current level width is nonzero, generate another level. + while (lvsize > 0) + { + // LBEGIN is the pointer to the beginning of the current level, and + // LVLEND points to the end of this level. + lbegin = lvlend + 1; + lvlend = iccsze; + + // Find the degrees of nodes in the current level, + // and at the same time, generate the next level. + for (i = lbegin; i <= lvlend; i++) + { + node = ls[offset + i - 1]; + jstrt = -pcol[node]; + jstop = Math.Abs(pcol[node + 1]) - 1; + ideg = 0; + + for (j = jstrt; j <= jstop; j++) + { + nbr = irow[j - 1]; + + if (mask[nbr] != 0) // EDIT: [nbr - 1] + { + ideg = ideg + 1; + + if (0 <= pcol[nbr]) // EDIT: [nbr - 1] + { + pcol[nbr] = -pcol[nbr]; // EDIT: [nbr - 1] + iccsze = iccsze + 1; + ls[offset + iccsze - 1] = nbr; + } + } + } + deg[node] = ideg; + } + + // Compute the current level width. + lvsize = iccsze - lvlend; + } + + // Reset ADJ_ROW to its correct sign and return. + for (i = 0; i < iccsze; i++) + { + node = ls[offset + i]; + pcol[node] = -pcol[node]; + } + + return; + } + + #endregion + + #region Tools + + /// + /// Computes the bandwidth of a permuted adjacency matrix. + /// + /// The permutation. + /// The inverse permutation. + /// Bandwidth of the permuted adjacency matrix. + /// + /// The matrix is defined by the adjacency information and a permutation. + /// The routine also computes the bandwidth and the size of the envelope. + /// + int PermBandwidth(int[] perm, int[] perm_inv) + { + int[] pcol = matrix.ColumnPointers; + int[] irow = matrix.RowIndices; + + int col, i, j; + + int band_lo = 0; + int band_hi = 0; + + int n = matrix.N; + + for (i = 0; i < n; i++) + { + for (j = pcol[perm[i]]; j < pcol[perm[i] + 1]; j++) + { + col = perm_inv[irow[j - 1]]; + band_lo = Math.Max(band_lo, i - col); + band_hi = Math.Max(band_hi, col - i); + } + } + + return band_lo + 1 + band_hi; + } + + /// + /// Produces the inverse of a given permutation. + /// + /// Number of items permuted. + /// PERM[N], a permutation. + /// The inverse permutation. + int[] PermInverse(int[] perm) + { + int n = matrix.N; + + int[] perm_inv = new int[n]; + + for (int i = 0; i < n; i++) + { + perm_inv[perm[i]] = i; + } + + return perm_inv; + } + + /// + /// Reverses the elements of an integer vector. + /// + /// number of entries in the array. + /// the array to be reversed. + /// + /// Input: + /// N = 5, + /// A = ( 11, 12, 13, 14, 15 ). + /// + /// Output: + /// A = ( 15, 14, 13, 12, 11 ). + /// + void ReverseVector(int[] a, int offset, int size) + { + int i; + int j; + + for (i = 0; i < size / 2; i++) + { + j = a[offset + i]; + a[offset + i] = a[offset + size - 1 - i]; + a[offset + size - 1 - i] = j; + } + + return; + } + + void Shift(int[] a, bool up) + { + int length = a.Length; + + if (up) + { + for (int i = 0; i < length; a[i]++, i++) ; + } + else + { + for (int i = 0; i < length; a[i]--, i++) ; + } + } + + #endregion + } +} diff --git a/src/Triangle/Tools/Interpolation.cs b/src/Triangle/Tools/Interpolation.cs new file mode 100644 index 0000000..227e020 --- /dev/null +++ b/src/Triangle/Tools/Interpolation.cs @@ -0,0 +1,132 @@ +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using TriangleNet.Geometry; + + public static class Interpolation + { + /// + /// Linear interpolation of a point. + /// + /// The triangle containing the point + /// The point to interpolate. + /// The vertex data (z values). + /// The linear interpolation value. + /// + /// IMPORTANT: this method assumes the mesh vertex ids correspond to the data array indices. + /// + public static double InterpolatePoint(ITriangle tri, Point p, double[] data) + { + var org = tri.GetVertex(0); + var dest = tri.GetVertex(1); + var apex = tri.GetVertex(2); + + double xdo = dest.x - org.x; + double ydo = dest.y - org.y; + double xao = apex.x - org.x; + double yao = apex.y - org.y; + + double denominator = 0.5 / (xdo * yao - xao * ydo); + + double dx = p.x - org.x; + double dy = p.y - org.y; + + // To interpolate z value for the given point inserted, define a + // coordinate system with a xi-axis, directed from the triangle's + // origin to its destination, and an eta-axis, directed from its + // origin to its apex. + double xi = (yao * dx - xao * dy) * (2.0 * denominator); + double eta = (xdo * dy - ydo * dx) * (2.0 * denominator); + + double orgz = data[org.id]; + + return orgz + xi * (data[dest.id] - orgz) + eta * (data[apex.id] - orgz); + } + +#if USE_ATTRIBS + /// + /// Linear interpolation of vertex attributes. + /// + /// The interpolation vertex. + /// The triangle containing the vertex. + /// The number of vertex attributes. + /// + /// The vertex is expected to lie inside the triangle. + /// + public static void InterpolateAttributes(Vertex vertex, ITriangle triangle, int n) + { + var org = triangle.GetVertex(0); + var dest = triangle.GetVertex(1); + var apex = triangle.GetVertex(2); + + double xdo = dest.x - org.x; + double ydo = dest.y - org.y; + double xao = apex.x - org.x; + double yao = apex.y - org.y; + + double denominator = 0.5 / (xdo * yao - xao * ydo); + + double dx = vertex.x - org.x; + double dy = vertex.y - org.y; + + // To interpolate vertex attributes for the new vertex, define a + // coordinate system with a xi-axis directed from the triangle's + // origin to its destination, and an eta-axis, directed from its + // origin to its apex. + double xi = (yao * dx - xao * dy) * (2.0 * denominator); + double eta = (xdo * dy - ydo * dx) * (2.0 * denominator); + + for (int i = 0; i < n; i++) + { + // Interpolate the vertex attributes. + vertex.attributes[i] = org.attributes[i] + + xi * (dest.attributes[i] - org.attributes[i]) + + eta * (apex.attributes[i] - org.attributes[i]); + } + } +#endif + +#if USE_Z + /// + /// Linear interpolation of a scalar value. + /// + /// The interpolation point. + /// The triangle containing the point. + /// + /// The point is expected to lie inside the triangle. + /// + public static void InterpolateZ(Point p, ITriangle triangle) + { + var org = triangle.GetVertex(0); + var dest = triangle.GetVertex(1); + var apex = triangle.GetVertex(2); + + // Compute the circumcenter of the triangle. + double xdo = dest.x - org.x; + double ydo = dest.y - org.y; + double xao = apex.x - org.x; + double yao = apex.y - org.y; + + double denominator = 0.5 / (xdo * yao - xao * ydo); + + double dx = p.x - org.x; + double dy = p.y - org.y; + + // To interpolate z value for the given point inserted, define a + // coordinate system with a xi-axis, directed from the triangle's + // origin to its destination, and an eta-axis, directed from its + // origin to its apex. + double xi = (yao * dx - xao * dy) * (2.0 * denominator); + double eta = (xdo * dy - ydo * dx) * (2.0 * denominator); + + p.z = org.z + xi * (dest.z - org.z) + eta * (apex.z - org.z); + } +#endif + } +} diff --git a/Triangle.NET/Triangle/Tools/IntersectionHelper.cs b/src/Triangle/Tools/IntersectionHelper.cs similarity index 81% rename from Triangle.NET/Triangle/Tools/IntersectionHelper.cs rename to src/Triangle/Tools/IntersectionHelper.cs index fa70936..2257005 100644 --- a/Triangle.NET/Triangle/Tools/IntersectionHelper.cs +++ b/src/Triangle/Tools/IntersectionHelper.cs @@ -1,225 +1,260 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Tools -{ - using TriangleNet.Geometry; - - public static class IntersectionHelper - { - /// - /// Compute intersection of two segments. - /// - /// Segment 1 start point. - /// Segment 1 end point. - /// Segment 2 start point. - /// Segment 2 end point. - /// The intersection point. - /// - /// This is a special case of segment intersection. Since the calling algorithm assures - /// that a valid intersection exists, there's no need to check for any special cases. - /// - public static void IntersectSegments(Point p0, Point p1, Point q0, Point q1, ref Point c0) - { - double ux = p1.x - p0.x; - double uy = p1.y - p0.y; - double vx = q1.x - q0.x; - double vy = q1.y - q0.y; - double wx = p0.x - q0.x; - double wy = p0.y - q0.y; - - double d = (ux * vy - uy * vx); - double s = (vx * wy - vy * wx) / d; - - // Intersection point - c0.x = p0.X + s * ux; - c0.y = p0.Y + s * uy; - } - - /// - /// Intersect segment with a bounding box. - /// - /// The clip rectangle. - /// Segment endpoint. - /// Segment endpoint. - /// The new location of p0. - /// The new location of p1. - /// Returns true, if segment is clipped. - /// - /// Based on Liang-Barsky function by Daniel White: - /// http://www.skytopia.com/project/articles/compsci/clipping.html - /// - public static bool LiangBarsky(Rectangle rect, Point p0, Point p1, ref Point c0, ref Point c1) - { - // Define the x/y clipping values for the border. - double xmin = rect.Left; - double xmax = rect.Right; - double ymin = rect.Bottom; - double ymax = rect.Top; - - // Define the start and end points of the line. - double x0 = p0.X; - double y0 = p0.Y; - double x1 = p1.X; - double y1 = p1.Y; - - double t0 = 0.0; - double t1 = 1.0; - - double dx = x1 - x0; - double dy = y1 - y0; - - double p = 0.0, q = 0.0, r; - - for (int edge = 0; edge < 4; edge++) - { - // Traverse through left, right, bottom, top edges. - if (edge == 0) { p = -dx; q = -(xmin - x0); } - if (edge == 1) { p = dx; q = (xmax - x0); } - if (edge == 2) { p = -dy; q = -(ymin - y0); } - if (edge == 3) { p = dy; q = (ymax - y0); } - r = q / p; - if (p == 0 && q < 0) return false; // Don't draw line at all. (parallel line outside) - if (p < 0) - { - if (r > t1) return false; // Don't draw line at all. - else if (r > t0) t0 = r; // Line is clipped! - } - else if (p > 0) - { - if (r < t0) return false; // Don't draw line at all. - else if (r < t1) t1 = r; // Line is clipped! - } - } - - c0.X = x0 + t0 * dx; - c0.Y = y0 + t0 * dy; - c1.X = x0 + t1 * dx; - c1.Y = y0 + t1 * dy; - - return true; // (clipped) line is drawn - } - - /// - /// Intersect a ray with a bounding box. - /// - /// The clip rectangle. - /// The ray startpoint (inside the box). - /// Any point in ray direction (NOT the direction vector). - /// The intersection point. - /// Returns false, if startpoint is outside the box. - public static bool BoxRayIntersection(Rectangle rect, Point p0, Point p1, ref Point c1) - { - return BoxRayIntersection(rect, p0, p1.x - p0.x, p1.y - p0.y, ref c1); - } - - /// - /// Intersect a ray with a bounding box. - /// - /// The clip rectangle. - /// The ray startpoint (inside the box). - /// X direction. - /// Y direction. - /// Returns false, if startpoint is outside the box. - public static Point BoxRayIntersection(Rectangle rect, Point p, double dx, double dy) - { - var intersection = new Point(); - - if (BoxRayIntersection(rect, p, dx, dy, ref intersection)) - { - return intersection; - } - - return null; - } - - /// - /// Intersect a ray with a bounding box. - /// - /// The clip rectangle. - /// The ray startpoint (inside the box). - /// X direction. - /// Y direction. - /// The intersection point. - /// Returns false, if startpoint is outside the box. - public static bool BoxRayIntersection(Rectangle rect, Point p, double dx, double dy, ref Point c) - { - double x = p.X; - double y = p.Y; - - double t1, x1, y1, t2, x2, y2; - - // Bounding box - double xmin = rect.Left; - double xmax = rect.Right; - double ymin = rect.Bottom; - double ymax = rect.Top; - - // Check if point is inside the bounds - if (x < xmin || x > xmax || y < ymin || y > ymax) - { - return false; - } - - // Calculate the cut through the vertical boundaries - if (dx < 0) - { - // Line going to the left: intersect with x = minX - t1 = (xmin - x) / dx; - x1 = xmin; - y1 = y + t1 * dy; - } - else if (dx > 0) - { - // Line going to the right: intersect with x = maxX - t1 = (xmax - x) / dx; - x1 = xmax; - y1 = y + t1 * dy; - } - else - { - // Line going straight up or down: no intersection possible - t1 = double.MaxValue; - x1 = y1 = 0; - } - - // Calculate the cut through upper and lower boundaries - if (dy < 0) - { - // Line going downwards: intersect with y = minY - t2 = (ymin - y) / dy; - x2 = x + t2 * dx; - y2 = ymin; - } - else if (dy > 0) - { - // Line going upwards: intersect with y = maxY - t2 = (ymax - y) / dy; - x2 = x + t2 * dx; - y2 = ymax; - } - else - { - // Horizontal line: no intersection possible - t2 = double.MaxValue; - x2 = y2 = 0; - } - - if (t1 < t2) - { - c.x = x1; - c.y = y1; - } - else - { - c.x = x2; - c.y = y2; - } - - return true; - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + using TriangleNet.Geometry; + + public static class IntersectionHelper + { + /// + /// Check if a given test point lies on a segment. + /// + /// The segment start point. + /// The segment end point. + /// The point to test. + /// Threshold to test collinearity (default = 1e-12). + /// + public static bool IsPointOnSegment(Point a, Point b, Point test, double eps = 1e-12) + { + // The cross product. + double cross = (test.Y - a.Y) * (b.X - a.X) - (test.X - a.X) * (b.Y - a.Y); + + // Check if points are collinear. + if (Math.Abs(cross) > eps) return false; + + // The dot product (projection of test point onto segment). + double dot = (test.X - a.X) * (b.X - a.X) + (test.Y - a.Y) * (b.Y - a.Y); + + // Check if test point is actually between a and b (left of a). + if (dot < 0) return false; + + // Length of the segment. + double ab = (b.X - a.X) * (b.X - a.X) + (b.Y - a.Y) * (b.Y - a.Y); + + // Ignore duplicate input points. + if (ab == 0) return false; + + // Check if test point is actually between a and b (right of b). + if (dot > ab) return false; + + return true; + } + + /// + /// Compute intersection of two segments. + /// + /// Segment 1 start point. + /// Segment 1 end point. + /// Segment 2 start point. + /// Segment 2 end point. + /// The intersection point. + /// + /// This is a special case of segment intersection. Since the calling algorithm assures + /// that a valid intersection exists, there's no need to check for any special cases. + /// + public static void IntersectSegments(Point p0, Point p1, Point q0, Point q1, ref Point c0) + { + double ux = p1.x - p0.x; + double uy = p1.y - p0.y; + double vx = q1.x - q0.x; + double vy = q1.y - q0.y; + double wx = p0.x - q0.x; + double wy = p0.y - q0.y; + + double d = (ux * vy - uy * vx); + double s = (vx * wy - vy * wx) / d; + + // Intersection point + c0.x = p0.X + s * ux; + c0.y = p0.Y + s * uy; + } + + /// + /// Intersect segment with a bounding box. + /// + /// The clip rectangle. + /// Segment endpoint. + /// Segment endpoint. + /// The new location of p0. + /// The new location of p1. + /// Returns true, if segment is clipped. + /// + /// Based on Liang-Barsky function by Daniel White: + /// http://www.skytopia.com/project/articles/compsci/clipping.html + /// + public static bool LiangBarsky(Rectangle rect, Point p0, Point p1, ref Point c0, ref Point c1) + { + // Define the x/y clipping values for the border. + double xmin = rect.Left; + double xmax = rect.Right; + double ymin = rect.Bottom; + double ymax = rect.Top; + + // Define the start and end points of the line. + double x0 = p0.X; + double y0 = p0.Y; + double x1 = p1.X; + double y1 = p1.Y; + + double t0 = 0.0; + double t1 = 1.0; + + double dx = x1 - x0; + double dy = y1 - y0; + + double p = 0.0, q = 0.0, r; + + for (int edge = 0; edge < 4; edge++) + { + // Traverse through left, right, bottom, top edges. + if (edge == 0) { p = -dx; q = -(xmin - x0); } + if (edge == 1) { p = dx; q = (xmax - x0); } + if (edge == 2) { p = -dy; q = -(ymin - y0); } + if (edge == 3) { p = dy; q = (ymax - y0); } + r = q / p; + if (p == 0 && q < 0) return false; // Don't draw line at all. (parallel line outside) + if (p < 0) + { + if (r > t1) return false; // Don't draw line at all. + else if (r > t0) t0 = r; // Line is clipped! + } + else if (p > 0) + { + if (r < t0) return false; // Don't draw line at all. + else if (r < t1) t1 = r; // Line is clipped! + } + } + + c0.X = x0 + t0 * dx; + c0.Y = y0 + t0 * dy; + c1.X = x0 + t1 * dx; + c1.Y = y0 + t1 * dy; + + return true; // (clipped) line is drawn + } + + /// + /// Intersect a ray with a bounding box. + /// + /// The clip rectangle. + /// The ray start point (inside the box). + /// Any point in ray direction (NOT the direction vector). + /// The intersection point. + /// Returns false, if start point is outside the box. + public static bool BoxRayIntersection(Rectangle rect, Point p0, Point p1, ref Point c1) + { + return BoxRayIntersection(rect, p0, p1.x - p0.x, p1.y - p0.y, ref c1); + } + + /// + /// Intersect a ray with a bounding box. + /// + /// The clip rectangle. + /// The ray start point (inside the box). + /// X direction. + /// Y direction. + /// Returns false, if start point is outside the box. + public static Point BoxRayIntersection(Rectangle rect, Point p, double dx, double dy) + { + var intersection = new Point(); + + if (BoxRayIntersection(rect, p, dx, dy, ref intersection)) + { + return intersection; + } + + return null; + } + + /// + /// Intersect a ray with a bounding box. + /// + /// The clip rectangle. + /// The ray startpoint (inside the box). + /// X direction. + /// Y direction. + /// The intersection point. + /// Returns false, if startpoint is outside the box. + public static bool BoxRayIntersection(Rectangle rect, Point p, double dx, double dy, ref Point c) + { + double x = p.X; + double y = p.Y; + + double t1, x1, y1, t2, x2, y2; + + // Bounding box + double xmin = rect.Left; + double xmax = rect.Right; + double ymin = rect.Bottom; + double ymax = rect.Top; + + // Check if point is inside the bounds + if (x < xmin || x > xmax || y < ymin || y > ymax) + { + return false; + } + + // Calculate the cut through the vertical boundaries + if (dx < 0) + { + // Line going to the left: intersect with x = minX + t1 = (xmin - x) / dx; + x1 = xmin; + y1 = y + t1 * dy; + } + else if (dx > 0) + { + // Line going to the right: intersect with x = maxX + t1 = (xmax - x) / dx; + x1 = xmax; + y1 = y + t1 * dy; + } + else + { + // Line going straight up or down: no intersection possible + t1 = double.MaxValue; + x1 = y1 = 0; + } + + // Calculate the cut through upper and lower boundaries + if (dy < 0) + { + // Line going downwards: intersect with y = minY + t2 = (ymin - y) / dy; + x2 = x + t2 * dx; + y2 = ymin; + } + else if (dy > 0) + { + // Line going upwards: intersect with y = maxY + t2 = (ymax - y) / dy; + x2 = x + t2 * dx; + y2 = ymax; + } + else + { + // Horizontal line: no intersection possible + t2 = double.MaxValue; + x2 = y2 = 0; + } + + if (t1 < t2) + { + c.x = x1; + c.y = y1; + } + else + { + c.x = x2; + c.y = y2; + } + + return true; + } + } +} diff --git a/Triangle.NET/Triangle/Tools/PolygonValidator.cs b/src/Triangle/Tools/PolygonValidator.cs similarity index 72% rename from Triangle.NET/Triangle/Tools/PolygonValidator.cs rename to src/Triangle/Tools/PolygonValidator.cs index 8ec9f59..765c68d 100644 --- a/Triangle.NET/Triangle/Tools/PolygonValidator.cs +++ b/src/Triangle/Tools/PolygonValidator.cs @@ -1,245 +1,295 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Tools -{ - using System; - using System.Collections.Generic; - using TriangleNet.Geometry; - - public static class PolygonValidator - { - /// - /// Test the polygon for consistency. - /// - public static bool IsConsistent(IPolygon poly) - { - var logger = Log.Instance; - - var points = poly.Points; - - int horrors = 0; - - int i = 0; - int count = points.Count; - - if (count < 3) - { - logger.Warning("Polygon must have at least 3 vertices.", "PolygonValidator.IsConsistent()"); - return false; - } - - foreach (var p in points) - { - if (p == null) - { - horrors++; - logger.Warning(String.Format("Point {0} is null.", i), "PolygonValidator.IsConsistent()"); - } - else if (double.IsNaN(p.x) || double.IsNaN(p.y)) - { - horrors++; - logger.Warning(String.Format("Point {0} has invalid coordinates.", i), "PolygonValidator.IsConsistent()"); - } - else if (double.IsInfinity(p.x) || double.IsInfinity(p.y)) - { - horrors++; - logger.Warning(String.Format("Point {0} has invalid coordinates.", i), "PolygonValidator.IsConsistent()"); - } - - i++; - } - - i = 0; - - foreach (var seg in poly.Segments) - { - if (seg == null) - { - horrors++; - logger.Warning(String.Format("Segment {0} is null.", i), "PolygonValidator.IsConsistent()"); - - // Always abort if a NULL-segment is found. - return false; - } - - var p = seg.GetVertex(0); - var q = seg.GetVertex(1); - - if ((p.x == q.x) && (p.y == q.y)) - { - horrors++; - logger.Warning(String.Format("Endpoints of segment {0} are coincident (IDs {1} / {2}).", i, p.id, q.id), - "PolygonValidator.IsConsistent()"); - } - - i++; - } - - if (points[0].id == points[1].id) - { - horrors += CheckVertexIDs(poly, count); - } - else - { - horrors += CheckDuplicateIDs(poly); - } - - return horrors == 0; - } - - /// - /// Test the polygon for duplicate vertices. - /// - public static bool HasDuplicateVertices(IPolygon poly) - { - var logger = Log.Instance; - - int horrors = 0; - - var points = poly.Points.ToArray(); - - VertexSorter.Sort(points); - - for (int i = 1; i < points.Length; i++) - { - if (points[i - 1] == points[i]) - { - horrors++; - logger.Warning(String.Format("Found duplicate point {0}.", points[i]), - "PolygonValidator.HasDuplicateVertices()"); - } - } - - return horrors > 0; - } - - /// - /// Test the polygon for 360 degree angles. - /// - /// The polygon. - /// The angle threshold. - public static bool HasBadAngles(IPolygon poly, double threshold = 2e-12) - { - var logger = Log.Instance; - - int horrors = 0; - int i = 0; - - Point p0 = null, p1 = null; - Point q0, q1; - - int count = poly.Points.Count; - - foreach (var seg in poly.Segments) - { - q0 = p0; // Previous segment start point. - q1 = p1; // Previous segment end point. - - p0 = seg.GetVertex(0); // Current segment start point. - p1 = seg.GetVertex(1); // Current segment end point. - - if (p0 == p1 || q0 == q1) - { - // Ignore zero-length segments. - continue; - } - - if (q0 != null && q1 != null) - { - // The two segments are connected. - if (p0 == q1 && p1 != null) - { - if (IsBadAngle(q0, p0, p1,threshold)) - { - horrors++; - logger.Warning(String.Format("Bad segment angle found at index {0}.", i), - "PolygonValidator.HasBadAngles()"); - } - } - } - - i++; - } - - return horrors > 0; - } - - private static bool IsBadAngle(Point a, Point b, Point c, double threshold = 0.0) - { - double x = DotProduct(a, b, c); - double y = CrossProductLength(a, b, c); - - return Math.Abs(Math.Atan2(y, x)) <= threshold; - } - - // Returns the dot product . - private static double DotProduct(Point a, Point b, Point c) - { - // Calculate the dot product. - return (a.x - b.x) * (c.x - b.x) + (a.y - b.y) * (c.y - b.y); - } - - // Returns the length of cross product AB x BC. - private static double CrossProductLength(Point a, Point b, Point c) - { - // Calculate the Z coordinate of the cross product. - return (a.x - b.x) * (c.y - b.y) - (a.y - b.y) * (c.x - b.x); - } - - private static int CheckVertexIDs(IPolygon poly, int count) - { - var logger = Log.Instance; - - int horrors = 0; - - int i = 0; - - Vertex p, q; - - foreach (var seg in poly.Segments) - { - p = seg.GetVertex(0); - q = seg.GetVertex(1); - - if (p.id < 0 || p.id >= count) - { - horrors++; - logger.Warning(String.Format("Segment {0} has invalid startpoint.", i), - "PolygonValidator.IsConsistent()"); - } - - if (q.id < 0 || q.id >= count) - { - horrors++; - logger.Warning(String.Format("Segment {0} has invalid endpoint.", i), - "PolygonValidator.IsConsistent()"); - } - - i++; - } - - return horrors; - } - - private static int CheckDuplicateIDs(IPolygon poly) - { - var ids = new HashSet(); - - // Check for duplicate ids. - foreach (var p in poly.Points) - { - if (!ids.Add(p.id)) - { - Log.Instance.Warning("Found duplicate vertex ids.", "PolygonValidator.IsConsistent()"); - return 1; - } - } - - return 0; - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + using System.Collections.Generic; + using TriangleNet.Geometry; + + public static class PolygonValidator + { + /// + /// Test the polygon for consistency. + /// + public static bool IsConsistent(IPolygon poly) + { + var logger = Log.Instance; + + var points = poly.Points; + + int horrors = 0; + + int i = 0; + int count = points.Count; + + if (count < 3) + { + logger.Warning("Polygon must have at least 3 vertices.", "PolygonValidator.IsConsistent()"); + return false; + } + + foreach (var p in points) + { + if (p == null) + { + horrors++; + logger.Warning(string.Format("Point {0} is null.", i), "PolygonValidator.IsConsistent()"); + } + else if (double.IsNaN(p.x) || double.IsNaN(p.y)) + { + horrors++; + logger.Warning(string.Format("Point {0} has invalid coordinates.", i), "PolygonValidator.IsConsistent()"); + } + else if (double.IsInfinity(p.x) || double.IsInfinity(p.y)) + { + horrors++; + logger.Warning(string.Format("Point {0} has invalid coordinates.", i), "PolygonValidator.IsConsistent()"); + } + + i++; + } + + i = 0; + + foreach (var seg in poly.Segments) + { + if (seg == null) + { + horrors++; + logger.Warning(string.Format("Segment {0} is null.", i), "PolygonValidator.IsConsistent()"); + + // Always abort if a NULL-segment is found. + return false; + } + + var p = seg.GetVertex(0); + var q = seg.GetVertex(1); + + if ((p.x == q.x) && (p.y == q.y)) + { + horrors++; + logger.Warning(string.Format("Endpoints of segment {0} are coincident (IDs {1} / {2}).", i, p.id, q.id), + "PolygonValidator.IsConsistent()"); + } + + i++; + } + + if (points[0].id == points[1].id) + { + horrors += CheckVertexIDs(poly, count); + } + else + { + horrors += CheckDuplicateIDs(poly); + } + + return horrors == 0; + } + + /// + /// Test the polygon for duplicate vertices. + /// + public static bool HasDuplicateVertices(IPolygon poly) + { + var logger = Log.Instance; + + int horrors = 0; + + var points = poly.Points.ToArray(); + + VertexSorter.Sort(points); + + for (int i = 1; i < points.Length; i++) + { + if (points[i - 1] == points[i]) + { + horrors++; + logger.Warning(string.Format("Found duplicate point ({0}, {1}).", points[i].x, points[i].y), + "PolygonValidator.HasDuplicateVertices()"); + } + } + + return horrors > 0; + } + + /// + /// Get the ratio of the largest and smallest segment length. + /// + /// The polygon. + /// The ratio threshold. + /// + /// This method will also report zero-length segments. + /// + public static double GetSegmentRatio(IPolygon poly, double threshold = 2e12) + { + var logger = Log.Instance; + + double min = double.MaxValue; + double max = 0.0; + + foreach (var seg in poly.Segments) + { + var p = seg.GetVertex(0); + var q = seg.GetVertex(1); + + var dx = p.X - q.X; + var dy = p.Y - q.Y; + + var length = Math.Sqrt(dx * dx + dy * dy); + + if (length == 0.0) + { + logger.Warning(string.Format("Found zero-length segment (vertex IDs {0} / {1}).", p.id, q.id), + "PolygonValidator.GetSegmentRatio()"); + continue; + } + + min = Math.Min(min, length); + max = Math.Max(max, length); + } + + double ratio = max / min; + + if (ratio > threshold) + { + logger.Warning(string.Format("Polygon has large segment ratio {0:G2}.", ratio), + "PolygonValidator.GetSegmentRatio()"); + } + + return ratio; + } + + /// + /// Test the polygon for 360 degree angles. + /// + /// The polygon. + /// The angle threshold. + /// + /// This method assumes that segments are stored in order. There may be different, + /// unconnected contours in the polygon, but the segments of each contour have to + /// be in order to get a meaningful result. + /// + public static bool HasBadAngles(IPolygon poly, double threshold = 2e-12) + { + var logger = Log.Instance; + + int horrors = 0; + int i = 0; + + Point p0 = null, p1 = null; + Point q0, q1; + + foreach (var seg in poly.Segments) + { + q0 = p0; // Previous segment start point. + q1 = p1; // Previous segment end point. + + p0 = seg.GetVertex(0); // Current segment start point. + p1 = seg.GetVertex(1); // Current segment end point. + + if (p0 == p1 || q0 == q1) + { + // Ignore zero-length segments. + continue; + } + + if (q0 != null && q1 != null) + { + // The two segments are connected. + if (p0 == q1 && p1 != null) + { + if (IsBadAngle(q0, p0, p1,threshold)) + { + horrors++; + logger.Warning(string.Format("Bad segment angle found at index {0}.", i), + "PolygonValidator.HasBadAngles()"); + } + } + } + + i++; + } + + return horrors > 0; + } + + private static bool IsBadAngle(Point a, Point b, Point c, double threshold = 0.0) + { + double x = DotProduct(a, b, c); + double y = CrossProductLength(a, b, c); + + return Math.Abs(Math.Atan2(y, x)) <= threshold; + } + + // Returns the dot product . + private static double DotProduct(Point a, Point b, Point c) + { + // Calculate the dot product. + return (a.x - b.x) * (c.x - b.x) + (a.y - b.y) * (c.y - b.y); + } + + // Returns the length of cross product AB x BC. + private static double CrossProductLength(Point a, Point b, Point c) + { + // Calculate the Z coordinate of the cross product. + return (a.x - b.x) * (c.y - b.y) - (a.y - b.y) * (c.x - b.x); + } + + private static int CheckVertexIDs(IPolygon poly, int count) + { + var logger = Log.Instance; + + int horrors = 0; + + int i = 0; + + Vertex p, q; + + foreach (var seg in poly.Segments) + { + p = seg.GetVertex(0); + q = seg.GetVertex(1); + + if (p.id < 0 || p.id >= count) + { + horrors++; + logger.Warning(string.Format("Segment {0} has invalid start point.", i), + "PolygonValidator.IsConsistent()"); + } + + if (q.id < 0 || q.id >= count) + { + horrors++; + logger.Warning(string.Format("Segment {0} has invalid endpoint.", i), + "PolygonValidator.IsConsistent()"); + } + + i++; + } + + return horrors; + } + + private static int CheckDuplicateIDs(IPolygon poly) + { + var ids = new HashSet(); + + // Check for duplicate ids. + foreach (var p in poly.Points) + { + if (!ids.Add(p.id)) + { + Log.Instance.Warning("Found duplicate vertex ids.", "PolygonValidator.IsConsistent()"); + return 1; + } + } + + return 0; + } + } +} diff --git a/Triangle.NET/Triangle/Tools/QualityMeasure.cs b/src/Triangle/Tools/QualityMeasure.cs similarity index 96% rename from Triangle.NET/Triangle/Tools/QualityMeasure.cs rename to src/Triangle/Tools/QualityMeasure.cs index fc7bdc6..e98a901 100644 --- a/Triangle.NET/Triangle/Tools/QualityMeasure.cs +++ b/src/Triangle/Tools/QualityMeasure.cs @@ -1,541 +1,541 @@ -// ----------------------------------------------------------------------- -// -// Original Matlab code by John Burkardt, Florida State University -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Tools -{ - using System; - using TriangleNet.Geometry; - - /// - /// Provides mesh quality information. - /// - /// - /// Given a triangle abc with points A (ax, ay), B (bx, by), C (cx, cy). - /// - /// The side lengths are given as - /// a = sqrt((cx - bx)^2 + (cy - by)^2) -- side BC opposite of A - /// b = sqrt((cx - ax)^2 + (cy - ay)^2) -- side CA opposite of B - /// c = sqrt((ax - bx)^2 + (ay - by)^2) -- side AB opposite of C - /// - /// The angles are given as - /// ang_a = acos((b^2 + c^2 - a^2) / (2 * b * c)) -- angle at A - /// ang_b = acos((c^2 + a^2 - b^2) / (2 * c * a)) -- angle at B - /// ang_c = acos((a^2 + b^2 - c^2) / (2 * a * b)) -- angle at C - /// - /// The semiperimeter is given as - /// s = (a + b + c) / 2 - /// - /// The area is given as - /// D = abs(ax * (by - cy) + bx * (cy - ay) + cx * (ay - by)) / 2 - /// = sqrt(s * (s - a) * (s - b) * (s - c)) - /// - /// The inradius is given as - /// r = D / s - /// - /// The circumradius is given as - /// R = a * b * c / (4 * D) - /// - /// The altitudes are given as - /// alt_a = 2 * D / a -- altitude above side a - /// alt_b = 2 * D / b -- altitude above side b - /// alt_c = 2 * D / c -- altitude above side c - /// - /// The aspect ratio may be given as the ratio of the longest to the - /// shortest edge or, more commonly as the ratio of the circumradius - /// to twice the inradius - /// ar = R / (2 * r) - /// = a * b * c / (8 * (s - a) * (s - b) * (s - c)) - /// = a * b * c / ((b + c - a) * (c + a - b) * (a + b - c)) - /// - public class QualityMeasure - { - AreaMeasure areaMeasure; - AlphaMeasure alphaMeasure; - Q_Measure qMeasure; - - Mesh mesh; - - public QualityMeasure() - { - areaMeasure = new AreaMeasure(); - alphaMeasure = new AlphaMeasure(); - qMeasure = new Q_Measure(); - } - - #region Public properties - - /// - /// Minimum triangle area. - /// - public double AreaMinimum - { - get { return areaMeasure.area_min; } - } - - /// - /// Maximum triangle area. - /// - public double AreaMaximum - { - get { return areaMeasure.area_max; } - } - - /// - /// Ratio of maximum and minimum triangle area. - /// - public double AreaRatio - { - get { return areaMeasure.area_max / areaMeasure.area_min; } - } - - /// - /// Smallest angle. - /// - public double AlphaMinimum - { - get { return alphaMeasure.alpha_min; } - } - - /// - /// Maximum smallest angle. - /// - public double AlphaMaximum - { - get { return alphaMeasure.alpha_max; } - } - - /// - /// Average angle. - /// - public double AlphaAverage - { - get { return alphaMeasure.alpha_ave; } - } - - /// - /// Average angle weighted by area. - /// - public double AlphaArea - { - get { return alphaMeasure.alpha_area; } - } - - /// - /// Smallest aspect ratio. - /// - public double Q_Minimum - { - get { return qMeasure.q_min; } - } - - /// - /// Largest aspect ratio. - /// - public double Q_Maximum - { - get { return qMeasure.q_max; } - } - - /// - /// Average aspect ratio. - /// - public double Q_Average - { - get { return qMeasure.q_ave; } - } - - /// - /// Average aspect ratio weighted by area. - /// - public double Q_Area - { - get { return qMeasure.q_area; } - } - - #endregion - - public void Update(Mesh mesh) - { - this.mesh = mesh; - - // Reset all measures. - areaMeasure.Reset(); - alphaMeasure.Reset(); - qMeasure.Reset(); - - Compute(); - } - - private void Compute() - { - Point a, b, c; - double ab, bc, ca; - double lx, ly; - double area; - - int n = 0; - - foreach (var tri in mesh.triangles) - { - n++; - - a = tri.vertices[0]; - b = tri.vertices[1]; - c = tri.vertices[2]; - - lx = a.x - b.x; - ly = a.y - b.y; - ab = Math.Sqrt(lx * lx + ly * ly); - lx = b.x - c.x; - ly = b.y - c.y; - bc = Math.Sqrt(lx * lx + ly * ly); - lx = c.x - a.x; - ly = c.y - a.y; - ca = Math.Sqrt(lx * lx + ly * ly); - - area = areaMeasure.Measure(a, b, c); - alphaMeasure.Measure(ab, bc, ca, area); - qMeasure.Measure(ab, bc, ca, area); - } - - // Normalize measures - alphaMeasure.Normalize(n, areaMeasure.area_total); - qMeasure.Normalize(n, areaMeasure.area_total); - } - - /// - /// Determines the bandwidth of the coefficient matrix. - /// - /// Bandwidth of the coefficient matrix. - /// - /// The quantity computed here is the "geometric" bandwidth determined - /// by the finite element mesh alone. - /// - /// If a single finite element variable is associated with each node - /// of the mesh, and if the nodes and variables are numbered in the - /// same way, then the geometric bandwidth is the same as the bandwidth - /// of a typical finite element matrix. - /// - /// The bandwidth M is defined in terms of the lower and upper bandwidths: - /// - /// M = ML + 1 + MU - /// - /// where - /// - /// ML = maximum distance from any diagonal entry to a nonzero - /// entry in the same row, but earlier column, - /// - /// MU = maximum distance from any diagonal entry to a nonzero - /// entry in the same row, but later column. - /// - /// Because the finite element node adjacency relationship is symmetric, - /// we are guaranteed that ML = MU. - /// - public int Bandwidth() - { - if (mesh == null) return 0; - - // Lower and upper bandwidth of the matrix - int ml = 0, mu = 0; - - int gi, gj; - - foreach (var tri in mesh.triangles) - { - for (int j = 0; j < 3; j++) - { - gi = tri.GetVertex(j).id; - - for (int k = 0; k < 3; k++) - { - gj = tri.GetVertex(k).id; - - mu = Math.Max(mu, gj - gi); - ml = Math.Max(ml, gi - gj); - } - } - } - - return ml + 1 + mu; - } - - class AreaMeasure - { - // Minimum area - public double area_min = double.MaxValue; - // Maximum area - public double area_max = -double.MaxValue; - // Total area of geometry - public double area_total = 0; - // Nmber of triangles with zero area - public int area_zero = 0; - - /// - /// Reset all values. - /// - public void Reset() - { - area_min = double.MaxValue; - area_max = -double.MaxValue; - area_total = 0; - area_zero = 0; - } - - /// - /// Compute the area of given triangle. - /// - /// Triangle corner a. - /// Triangle corner b. - /// Triangle corner c. - /// Triangle area. - public double Measure(Point a, Point b, Point c) - { - double area = 0.5 * Math.Abs(a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)); - - area_min = Math.Min(area_min, area); - area_max = Math.Max(area_max, area); - area_total += area; - - if (area == 0.0) - { - area_zero = area_zero + 1; - } - - return area; - } - } - - /// - /// The alpha measure determines the triangulated pointset quality. - /// - /// - /// The alpha measure evaluates the uniformity of the shapes of the triangles - /// defined by a triangulated pointset. - /// - /// We compute the minimum angle among all the triangles in the triangulated - /// dataset and divide by the maximum possible value (which, in degrees, - /// is 60). The best possible value is 1, and the worst 0. A good - /// triangulation should have an alpha score close to 1. - /// - class AlphaMeasure - { - // Minimum value over all triangles - public double alpha_min; - // Maximum value over all triangles - public double alpha_max; - // Value averaged over all triangles - public double alpha_ave; - // Value averaged over all triangles and weighted by area - public double alpha_area; - - /// - /// Reset all values. - /// - public void Reset() - { - alpha_min = double.MaxValue; - alpha_max = -double.MaxValue; - alpha_ave = 0; - alpha_area = 0; - } - - double acos(double c) - { - if (c <= -1.0) - { - return Math.PI; - } - else if (1.0 <= c) - { - return 0.0; - } - else - { - return Math.Acos(c); - } - } - - /// - /// Compute q value of given triangle. - /// - /// Side length ab. - /// Side length bc. - /// Side length ca. - /// Triangle area. - /// - public double Measure(double ab, double bc, double ca, double area) - { - double alpha = double.MaxValue; - - double ab2 = ab * ab; - double bc2 = bc * bc; - double ca2 = ca * ca; - - double a_angle; - double b_angle; - double c_angle; - - // Take care of a ridiculous special case. - if (ab == 0.0 && bc == 0.0 && ca == 0.0) - { - a_angle = 2.0 * Math.PI / 3.0; - b_angle = 2.0 * Math.PI / 3.0; - c_angle = 2.0 * Math.PI / 3.0; - } - else - { - if (ca == 0.0 || ab == 0.0) - { - a_angle = Math.PI; - } - else - { - a_angle = acos((ca2 + ab2 - bc2) / (2.0 * ca * ab)); - } - - if (ab == 0.0 || bc == 0.0) - { - b_angle = Math.PI; - } - else - { - b_angle = acos((ab2 + bc2 - ca2) / (2.0 * ab * bc)); - } - - if (bc == 0.0 || ca == 0.0) - { - c_angle = Math.PI; - } - else - { - c_angle = acos((bc2 + ca2 - ab2) / (2.0 * bc * ca)); - } - } - - alpha = Math.Min(alpha, a_angle); - alpha = Math.Min(alpha, b_angle); - alpha = Math.Min(alpha, c_angle); - - // Normalize angle from [0,pi/3] radians into qualities in [0,1]. - alpha = alpha * 3.0 / Math.PI; - - alpha_ave += alpha; - alpha_area += area * alpha; - - alpha_min = Math.Min(alpha, alpha_min); - alpha_max = Math.Max(alpha, alpha_max); - - return alpha; - } - - /// - /// Normalize values. - /// - public void Normalize(int n, double area_total) - { - if (n > 0) - { - alpha_ave /= n; - } - else - { - alpha_ave = 0.0; - } - - if (0.0 < area_total) - { - alpha_area /= area_total; - } - else - { - alpha_area = 0.0; - } - } - } - - /// - /// The Q measure determines the triangulated pointset quality. - /// - /// - /// The Q measure evaluates the uniformity of the shapes of the triangles - /// defined by a triangulated pointset. It uses the aspect ratio - /// - /// 2 * (incircle radius) / (circumcircle radius) - /// - /// In an ideally regular mesh, all triangles would have the same - /// equilateral shape, for which Q = 1. A good mesh would have - /// 0.5 < Q. - /// - class Q_Measure - { - // Minimum value over all triangles - public double q_min; - // Maximum value over all triangles - public double q_max; - // Average value - public double q_ave; - // Average value weighted by the area of each triangle - public double q_area; - - /// - /// Reset all values. - /// - public void Reset() - { - q_min = double.MaxValue; - q_max = -double.MaxValue; - q_ave = 0; - q_area = 0; - } - - /// - /// Compute q value of given triangle. - /// - /// Side length ab. - /// Side length bc. - /// Side length ca. - /// Triangle area. - /// - public double Measure(double ab, double bc, double ca, double area) - { - double q = (bc + ca - ab) * (ca + ab - bc) * (ab + bc - ca) / (ab * bc * ca); - - q_min = Math.Min(q_min, q); - q_max = Math.Max(q_max, q); - - q_ave += q; - q_area += q * area; - - return q; - } - - /// - /// Normalize values. - /// - public void Normalize(int n, double area_total) - { - if (n > 0) - { - q_ave /= n; - } - else - { - q_ave = 0.0; - } - - if (area_total > 0.0) - { - q_area /= area_total; - } - else - { - q_area = 0.0; - } - } - } - } -} +// ----------------------------------------------------------------------- +// +// Original Matlab code by John Burkardt, Florida State University +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + using TriangleNet.Geometry; + + /// + /// Provides mesh quality information. + /// + /// + /// Given a triangle abc with points A (ax, ay), B (bx, by), C (cx, cy). + /// + /// The side lengths are given as + /// a = sqrt((cx - bx)^2 + (cy - by)^2) -- side BC opposite of A + /// b = sqrt((cx - ax)^2 + (cy - ay)^2) -- side CA opposite of B + /// c = sqrt((ax - bx)^2 + (ay - by)^2) -- side AB opposite of C + /// + /// The angles are given as + /// ang_a = acos((b^2 + c^2 - a^2) / (2 * b * c)) -- angle at A + /// ang_b = acos((c^2 + a^2 - b^2) / (2 * c * a)) -- angle at B + /// ang_c = acos((a^2 + b^2 - c^2) / (2 * a * b)) -- angle at C + /// + /// The semiperimeter is given as + /// s = (a + b + c) / 2 + /// + /// The area is given as + /// D = abs(ax * (by - cy) + bx * (cy - ay) + cx * (ay - by)) / 2 + /// = sqrt(s * (s - a) * (s - b) * (s - c)) + /// + /// The inradius is given as + /// r = D / s + /// + /// The circumradius is given as + /// R = a * b * c / (4 * D) + /// + /// The altitudes are given as + /// alt_a = 2 * D / a -- altitude above side a + /// alt_b = 2 * D / b -- altitude above side b + /// alt_c = 2 * D / c -- altitude above side c + /// + /// The aspect ratio may be given as the ratio of the longest to the + /// shortest edge or, more commonly as the ratio of the circumradius + /// to twice the inradius + /// ar = R / (2 * r) + /// = a * b * c / (8 * (s - a) * (s - b) * (s - c)) + /// = a * b * c / ((b + c - a) * (c + a - b) * (a + b - c)) + /// + public class QualityMeasure + { + AreaMeasure areaMeasure; + AlphaMeasure alphaMeasure; + Q_Measure qMeasure; + + Mesh mesh; + + public QualityMeasure() + { + areaMeasure = new AreaMeasure(); + alphaMeasure = new AlphaMeasure(); + qMeasure = new Q_Measure(); + } + + #region Public properties + + /// + /// Minimum triangle area. + /// + public double AreaMinimum + { + get { return areaMeasure.area_min; } + } + + /// + /// Maximum triangle area. + /// + public double AreaMaximum + { + get { return areaMeasure.area_max; } + } + + /// + /// Ratio of maximum and minimum triangle area. + /// + public double AreaRatio + { + get { return areaMeasure.area_max / areaMeasure.area_min; } + } + + /// + /// Smallest angle. + /// + public double AlphaMinimum + { + get { return alphaMeasure.alpha_min; } + } + + /// + /// Maximum smallest angle. + /// + public double AlphaMaximum + { + get { return alphaMeasure.alpha_max; } + } + + /// + /// Average angle. + /// + public double AlphaAverage + { + get { return alphaMeasure.alpha_ave; } + } + + /// + /// Average angle weighted by area. + /// + public double AlphaArea + { + get { return alphaMeasure.alpha_area; } + } + + /// + /// Smallest aspect ratio. + /// + public double Q_Minimum + { + get { return qMeasure.q_min; } + } + + /// + /// Largest aspect ratio. + /// + public double Q_Maximum + { + get { return qMeasure.q_max; } + } + + /// + /// Average aspect ratio. + /// + public double Q_Average + { + get { return qMeasure.q_ave; } + } + + /// + /// Average aspect ratio weighted by area. + /// + public double Q_Area + { + get { return qMeasure.q_area; } + } + + #endregion + + public void Update(Mesh mesh) + { + this.mesh = mesh; + + // Reset all measures. + areaMeasure.Reset(); + alphaMeasure.Reset(); + qMeasure.Reset(); + + Compute(); + } + + private void Compute() + { + Point a, b, c; + double ab, bc, ca; + double lx, ly; + double area; + + int n = 0; + + foreach (var tri in mesh.triangles) + { + n++; + + a = tri.vertices[0]; + b = tri.vertices[1]; + c = tri.vertices[2]; + + lx = a.x - b.x; + ly = a.y - b.y; + ab = Math.Sqrt(lx * lx + ly * ly); + lx = b.x - c.x; + ly = b.y - c.y; + bc = Math.Sqrt(lx * lx + ly * ly); + lx = c.x - a.x; + ly = c.y - a.y; + ca = Math.Sqrt(lx * lx + ly * ly); + + area = areaMeasure.Measure(a, b, c); + alphaMeasure.Measure(ab, bc, ca, area); + qMeasure.Measure(ab, bc, ca, area); + } + + // Normalize measures + alphaMeasure.Normalize(n, areaMeasure.area_total); + qMeasure.Normalize(n, areaMeasure.area_total); + } + + /// + /// Determines the bandwidth of the coefficient matrix. + /// + /// Bandwidth of the coefficient matrix. + /// + /// The quantity computed here is the "geometric" bandwidth determined + /// by the finite element mesh alone. + /// + /// If a single finite element variable is associated with each node + /// of the mesh, and if the nodes and variables are numbered in the + /// same way, then the geometric bandwidth is the same as the bandwidth + /// of a typical finite element matrix. + /// + /// The bandwidth M is defined in terms of the lower and upper bandwidths: + /// + /// M = ML + 1 + MU + /// + /// where + /// + /// ML = maximum distance from any diagonal entry to a nonzero + /// entry in the same row, but earlier column, + /// + /// MU = maximum distance from any diagonal entry to a nonzero + /// entry in the same row, but later column. + /// + /// Because the finite element node adjacency relationship is symmetric, + /// we are guaranteed that ML = MU. + /// + public int Bandwidth() + { + if (mesh == null) return 0; + + // Lower and upper bandwidth of the matrix + int ml = 0, mu = 0; + + int gi, gj; + + foreach (var tri in mesh.triangles) + { + for (int j = 0; j < 3; j++) + { + gi = tri.GetVertex(j).id; + + for (int k = 0; k < 3; k++) + { + gj = tri.GetVertex(k).id; + + mu = Math.Max(mu, gj - gi); + ml = Math.Max(ml, gi - gj); + } + } + } + + return ml + 1 + mu; + } + + class AreaMeasure + { + // Minimum area + public double area_min = double.MaxValue; + // Maximum area + public double area_max = -double.MaxValue; + // Total area of geometry + public double area_total = 0; + // Number of triangles with zero area + public int area_zero = 0; + + /// + /// Reset all values. + /// + public void Reset() + { + area_min = double.MaxValue; + area_max = -double.MaxValue; + area_total = 0; + area_zero = 0; + } + + /// + /// Compute the area of given triangle. + /// + /// Triangle corner a. + /// Triangle corner b. + /// Triangle corner c. + /// Triangle area. + public double Measure(Point a, Point b, Point c) + { + double area = 0.5 * Math.Abs(a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)); + + area_min = Math.Min(area_min, area); + area_max = Math.Max(area_max, area); + area_total += area; + + if (area == 0.0) + { + area_zero = area_zero + 1; + } + + return area; + } + } + + /// + /// The alpha measure determines the triangulated pointset quality. + /// + /// + /// The alpha measure evaluates the uniformity of the shapes of the triangles + /// defined by a triangulated pointset. + /// + /// We compute the minimum angle among all the triangles in the triangulated + /// dataset and divide by the maximum possible value (which, in degrees, + /// is 60). The best possible value is 1, and the worst 0. A good + /// triangulation should have an alpha score close to 1. + /// + class AlphaMeasure + { + // Minimum value over all triangles + public double alpha_min; + // Maximum value over all triangles + public double alpha_max; + // Value averaged over all triangles + public double alpha_ave; + // Value averaged over all triangles and weighted by area + public double alpha_area; + + /// + /// Reset all values. + /// + public void Reset() + { + alpha_min = double.MaxValue; + alpha_max = -double.MaxValue; + alpha_ave = 0; + alpha_area = 0; + } + + double acos(double c) + { + if (c <= -1.0) + { + return Math.PI; + } + else if (1.0 <= c) + { + return 0.0; + } + else + { + return Math.Acos(c); + } + } + + /// + /// Compute q value of given triangle. + /// + /// Side length ab. + /// Side length bc. + /// Side length ca. + /// Triangle area. + /// + public double Measure(double ab, double bc, double ca, double area) + { + double alpha = double.MaxValue; + + double ab2 = ab * ab; + double bc2 = bc * bc; + double ca2 = ca * ca; + + double a_angle; + double b_angle; + double c_angle; + + // Take care of a ridiculous special case. + if (ab == 0.0 && bc == 0.0 && ca == 0.0) + { + a_angle = 2.0 * Math.PI / 3.0; + b_angle = 2.0 * Math.PI / 3.0; + c_angle = 2.0 * Math.PI / 3.0; + } + else + { + if (ca == 0.0 || ab == 0.0) + { + a_angle = Math.PI; + } + else + { + a_angle = acos((ca2 + ab2 - bc2) / (2.0 * ca * ab)); + } + + if (ab == 0.0 || bc == 0.0) + { + b_angle = Math.PI; + } + else + { + b_angle = acos((ab2 + bc2 - ca2) / (2.0 * ab * bc)); + } + + if (bc == 0.0 || ca == 0.0) + { + c_angle = Math.PI; + } + else + { + c_angle = acos((bc2 + ca2 - ab2) / (2.0 * bc * ca)); + } + } + + alpha = Math.Min(alpha, a_angle); + alpha = Math.Min(alpha, b_angle); + alpha = Math.Min(alpha, c_angle); + + // Normalize angle from [0,pi/3] radians into qualities in [0,1]. + alpha = alpha * 3.0 / Math.PI; + + alpha_ave += alpha; + alpha_area += area * alpha; + + alpha_min = Math.Min(alpha, alpha_min); + alpha_max = Math.Max(alpha, alpha_max); + + return alpha; + } + + /// + /// Normalize values. + /// + public void Normalize(int n, double area_total) + { + if (n > 0) + { + alpha_ave /= n; + } + else + { + alpha_ave = 0.0; + } + + if (0.0 < area_total) + { + alpha_area /= area_total; + } + else + { + alpha_area = 0.0; + } + } + } + + /// + /// The Q measure determines the triangulated pointset quality. + /// + /// + /// The Q measure evaluates the uniformity of the shapes of the triangles + /// defined by a triangulated pointset. It uses the aspect ratio + /// + /// 2 * (incircle radius) / (circumcircle radius) + /// + /// In an ideally regular mesh, all triangles would have the same + /// equilateral shape, for which Q = 1. A good mesh would have + /// 0.5 < Q. + /// + class Q_Measure + { + // Minimum value over all triangles + public double q_min; + // Maximum value over all triangles + public double q_max; + // Average value + public double q_ave; + // Average value weighted by the area of each triangle + public double q_area; + + /// + /// Reset all values. + /// + public void Reset() + { + q_min = double.MaxValue; + q_max = -double.MaxValue; + q_ave = 0; + q_area = 0; + } + + /// + /// Compute q value of given triangle. + /// + /// Side length ab. + /// Side length bc. + /// Side length ca. + /// Triangle area. + /// + public double Measure(double ab, double bc, double ca, double area) + { + double q = (bc + ca - ab) * (ca + ab - bc) * (ab + bc - ca) / (ab * bc * ca); + + q_min = Math.Min(q_min, q); + q_max = Math.Max(q_max, q); + + q_ave += q; + q_area += q * area; + + return q; + } + + /// + /// Normalize values. + /// + public void Normalize(int n, double area_total) + { + if (n > 0) + { + q_ave /= n; + } + else + { + q_ave = 0.0; + } + + if (area_total > 0.0) + { + q_area /= area_total; + } + else + { + q_area = 0.0; + } + } + } + } +} diff --git a/Triangle.NET/Triangle/Tools/Statistic.cs b/src/Triangle/Tools/Statistic.cs similarity index 91% rename from Triangle.NET/Triangle/Tools/Statistic.cs rename to src/Triangle/Tools/Statistic.cs index a530f8c..77497d1 100644 --- a/Triangle.NET/Triangle/Tools/Statistic.cs +++ b/src/Triangle/Tools/Statistic.cs @@ -1,528 +1,528 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Tools -{ - using System; - using TriangleNet.Topology; - using TriangleNet.Geometry; - - /// - /// Gather mesh statistics. - /// - public class Statistic - { - #region Static members - - /// - /// Number of incircle tests performed. - /// - public static long InCircleCount = 0; - public static long InCircleAdaptCount = 0; - - /// - /// Number of counterclockwise tests performed. - /// - public static long CounterClockwiseCount = 0; - public static long CounterClockwiseAdaptCount = 0; - - /// - /// Number of 3D orientation tests performed. - /// - public static long Orient3dCount = 0; - - /// - /// Number of right-of-hyperbola tests performed. - /// - public static long HyperbolaCount = 0; - - /// - /// // Number of circumcenter calculations performed. - /// - public static long CircumcenterCount = 0; - - /// - /// Number of circle top calculations performed. - /// - public static long CircleTopCount = 0; - - /// - /// Number of vertex relocations. - /// - public static long RelocationCount = 0; - - #endregion - - #region Properties - - double minEdge = 0; - /// - /// Gets the shortest edge. - /// - public double ShortestEdge { get { return minEdge; } } - - double maxEdge = 0; - /// - /// Gets the longest edge. - /// - public double LongestEdge { get { return maxEdge; } } - - // - double minAspect = 0; - /// - /// Gets the shortest altitude. - /// - public double ShortestAltitude { get { return minAspect; } } - - double maxAspect = 0; - /// - /// Gets the largest aspect ratio. - /// - public double LargestAspectRatio { get { return maxAspect; } } - - double minArea = 0; - /// - /// Gets the smallest area. - /// - public double SmallestArea { get { return minArea; } } - - double maxArea = 0; - /// - /// Gets the largest area. - /// - public double LargestArea { get { return maxArea; } } - - double minAngle = 0; - /// - /// Gets the smallest angle. - /// - public double SmallestAngle { get { return minAngle; } } - - double maxAngle = 0; - /// - /// Gets the largest angle. - /// - public double LargestAngle { get { return maxAngle; } } - - int[] angleTable; - /// - /// Gets the angle histogram. - /// - public int[] AngleHistogram { get { return angleTable; } } - - int[] minAngles; - /// - /// Gets the min angles histogram. - /// - public int[] MinAngleHistogram { get { return minAngles; } } - - int[] maxAngles; - /// - /// Gets the max angles histogram. - /// - public int[] MaxAngleHistogram { get { return maxAngles; } } - - #endregion - - #region Private methods - - private void GetAspectHistogram(Mesh mesh) - { - int[] aspecttable; - double[] ratiotable; - - aspecttable = new int[16]; - ratiotable = new double[] { - 1.5, 2.0, 2.5, 3.0, 4.0, 6.0, 10.0, 15.0, 25.0, 50.0, - 100.0, 300.0, 1000.0, 10000.0, 100000.0, 0.0 }; - - - Otri tri = default(Otri); - Vertex[] p = new Vertex[3]; - double[] dx = new double[3], dy = new double[3]; - double[] edgelength = new double[3]; - double triarea; - double trilongest2; - double triminaltitude2; - double triaspect2; - - int aspectindex; - int i, j, k; - - tri.orient = 0; - foreach (var t in mesh.triangles) - { - tri.tri = t; - p[0] = tri.Org(); - p[1] = tri.Dest(); - p[2] = tri.Apex(); - trilongest2 = 0.0; - - for (i = 0; i < 3; i++) - { - j = plus1Mod3[i]; - k = minus1Mod3[i]; - dx[i] = p[j].x - p[k].x; - dy[i] = p[j].y - p[k].y; - edgelength[i] = dx[i] * dx[i] + dy[i] * dy[i]; - if (edgelength[i] > trilongest2) - { - trilongest2 = edgelength[i]; - } - } - - //triarea = Primitives.CounterClockwise(p[0], p[1], p[2]); - triarea = Math.Abs((p[2].x - p[0].x) * (p[1].y - p[0].y) - - (p[1].x - p[0].x) * (p[2].y - p[0].y)) / 2.0; - - triminaltitude2 = triarea * triarea / trilongest2; - - triaspect2 = trilongest2 / triminaltitude2; - - aspectindex = 0; - while ((triaspect2 > ratiotable[aspectindex] * ratiotable[aspectindex]) && (aspectindex < 15)) - { - aspectindex++; - } - aspecttable[aspectindex]++; - } - } - - #endregion - - static readonly int[] plus1Mod3 = { 1, 2, 0 }; - static readonly int[] minus1Mod3 = { 2, 0, 1 }; - - /// - /// Update statistics about the quality of the mesh. - /// - /// - public void Update(Mesh mesh, int sampleDegrees) - { - Point[] p = new Point[3]; - - int k1, k2; - int degreeStep; - - //sampleDegrees = 36; // sample every 5 degrees - //sampleDegrees = 45; // sample every 4 degrees - sampleDegrees = 60; // sample every 3 degrees - - double[] cosSquareTable = new double[sampleDegrees / 2 - 1]; - double[] dx = new double[3]; - double[] dy = new double[3]; - double[] edgeLength = new double[3]; - double dotProduct; - double cosSquare; - double triArea; - double triLongest2; - double triMinAltitude2; - double triAspect2; - - double radconst = Math.PI / sampleDegrees; - double degconst = 180.0 / Math.PI; - - // New angle table - angleTable = new int[sampleDegrees]; - minAngles = new int[sampleDegrees]; - maxAngles = new int[sampleDegrees]; - - for (int i = 0; i < sampleDegrees / 2 - 1; i++) - { - cosSquareTable[i] = Math.Cos(radconst * (i + 1)); - cosSquareTable[i] = cosSquareTable[i] * cosSquareTable[i]; - } - for (int i = 0; i < sampleDegrees; i++) - { - angleTable[i] = 0; - } - - minAspect = mesh.bounds.Width + mesh.bounds.Height; - minAspect = minAspect * minAspect; - maxAspect = 0.0; - minEdge = minAspect; - maxEdge = 0.0; - minArea = minAspect; - maxArea = 0.0; - minAngle = 0.0; - maxAngle = 2.0; - - bool acuteBiggest = true; - bool acuteBiggestTri = true; - - double triMinAngle, triMaxAngle = 1; - - foreach (var tri in mesh.triangles) - { - triMinAngle = 0; // Min angle: 0 < a < 60 degress - triMaxAngle = 1; // Max angle: 60 < a < 180 degress - - p[0] = tri.vertices[0]; - p[1] = tri.vertices[1]; - p[2] = tri.vertices[2]; - - triLongest2 = 0.0; - - for (int i = 0; i < 3; i++) - { - k1 = plus1Mod3[i]; - k2 = minus1Mod3[i]; - - dx[i] = p[k1].x - p[k2].x; - dy[i] = p[k1].y - p[k2].y; - - edgeLength[i] = dx[i] * dx[i] + dy[i] * dy[i]; - - if (edgeLength[i] > triLongest2) - { - triLongest2 = edgeLength[i]; - } - - if (edgeLength[i] > maxEdge) - { - maxEdge = edgeLength[i]; - } - - if (edgeLength[i] < minEdge) - { - minEdge = edgeLength[i]; - } - } - - //triarea = Primitives.CounterClockwise(p[0], p[1], p[2]); - triArea = Math.Abs((p[2].x - p[0].x) * (p[1].y - p[0].y) - - (p[1].x - p[0].x) * (p[2].y - p[0].y)); - - if (triArea < minArea) - { - minArea = triArea; - } - - if (triArea > maxArea) - { - maxArea = triArea; - } - - triMinAltitude2 = triArea * triArea / triLongest2; - if (triMinAltitude2 < minAspect) - { - minAspect = triMinAltitude2; - } - - triAspect2 = triLongest2 / triMinAltitude2; - if (triAspect2 > maxAspect) - { - maxAspect = triAspect2; - } - - for (int i = 0; i < 3; i++) - { - k1 = plus1Mod3[i]; - k2 = minus1Mod3[i]; - - dotProduct = dx[k1] * dx[k2] + dy[k1] * dy[k2]; - cosSquare = dotProduct * dotProduct / (edgeLength[k1] * edgeLength[k2]); - degreeStep = sampleDegrees / 2 - 1; - - for (int j = degreeStep - 1; j >= 0; j--) - { - if (cosSquare > cosSquareTable[j]) - { - degreeStep = j; - } - } - - if (dotProduct <= 0.0) - { - angleTable[degreeStep]++; - if (cosSquare > minAngle) - { - minAngle = cosSquare; - } - if (acuteBiggest && (cosSquare < maxAngle)) - { - maxAngle = cosSquare; - } - - // Update min/max angle per triangle - if (cosSquare > triMinAngle) - { - triMinAngle = cosSquare; - } - if (acuteBiggestTri && (cosSquare < triMaxAngle)) - { - triMaxAngle = cosSquare; - } - } - else - { - angleTable[sampleDegrees - degreeStep - 1]++; - if (acuteBiggest || (cosSquare > maxAngle)) - { - maxAngle = cosSquare; - acuteBiggest = false; - } - - // Update max angle for (possibly non-acute) triangle - if (acuteBiggestTri || (cosSquare > triMaxAngle)) - { - triMaxAngle = cosSquare; - acuteBiggestTri = false; - } - } - } - - // Update min angle histogram - degreeStep = sampleDegrees / 2 - 1; - - for (int j = degreeStep - 1; j >= 0; j--) - { - if (triMinAngle > cosSquareTable[j]) - { - degreeStep = j; - } - } - minAngles[degreeStep]++; - - // Update max angle histogram - degreeStep = sampleDegrees / 2 - 1; - - for (int j = degreeStep - 1; j >= 0; j--) - { - if (triMaxAngle > cosSquareTable[j]) - { - degreeStep = j; - } - } - - if (acuteBiggestTri) - { - maxAngles[degreeStep]++; - } - else - { - maxAngles[sampleDegrees - degreeStep - 1]++; - } - - acuteBiggestTri = true; - } - - minEdge = Math.Sqrt(minEdge); - maxEdge = Math.Sqrt(maxEdge); - minAspect = Math.Sqrt(minAspect); - maxAspect = Math.Sqrt(maxAspect); - minArea *= 0.5; - maxArea *= 0.5; - if (minAngle >= 1.0) - { - minAngle = 0.0; - } - else - { - minAngle = degconst * Math.Acos(Math.Sqrt(minAngle)); - } - - if (maxAngle >= 1.0) - { - maxAngle = 180.0; - } - else - { - if (acuteBiggest) - { - maxAngle = degconst * Math.Acos(Math.Sqrt(maxAngle)); - } - else - { - maxAngle = 180.0 - degconst * Math.Acos(Math.Sqrt(maxAngle)); - } - } - } - - /// - /// Compute angle information for given triangle. - /// - /// The triangle to check. - /// Array of doubles (length 6). - /// - /// On return, the squared cosines of the minimum and maximum angle will - /// be stored at position data[0] and data[1] respectively. - /// If the triangle was obtuse, data[2] will be set to -1 and maximum angle - /// is computed as (pi - acos(sqrt(data[1]))). - /// - public static void ComputeAngles(ITriangle triangle, double[] data) - { - double min = 0.0; - double max = 1.0; - - var va = triangle.GetVertex(0); - var vb = triangle.GetVertex(1); - var vc = triangle.GetVertex(2); - - double dxa = vb.x - vc.x; - double dya = vb.y - vc.y; - double lena = dxa * dxa + dya * dya; - - double dxb = vc.x - va.x; - double dyb = vc.y - va.y; - double lenb = dxb * dxb + dyb * dyb; - - double dxc = va.x - vb.x; - double dyc = va.y - vb.y; - double lenc = dxc * dxc + dyc * dyc; - - // Dot products. - double dota = data[0] = dxb * dxc + dyb * dyc; - double dotb = data[1] = dxc * dxa + dyc * dya; - double dotc = data[2] = dxa * dxb + dya * dyb; - - // Squared cosines. - data[3] = (dota * dota) / (lenb * lenc); - data[4] = (dotb * dotb) / (lenc * lena); - data[5] = (dotc * dotc) / (lena * lenb); - - // The sign of the dot product will tell us, if the angle is - // acute (value < 0) or obtuse (value > 0). - - bool acute = true; - - double cos, dot; - - for (int i = 0; i < 3; i++) - { - dot = data[i]; - cos = data[3 + i]; - - if (dot <= 0.0) - { - if (cos > min) - { - min = cos; - } - - if (acute && (cos < max)) - { - max = cos; - } - } - else - { - // Update max angle for (possibly non-acute) triangle - if (acute || (cos > max)) - { - max = cos; - acute = false; - } - } - } - - data[0] = min; - data[1] = max; - data[2] = acute ? 1.0 : -1.0; - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + using TriangleNet.Topology; + using TriangleNet.Geometry; + + /// + /// Gather mesh statistics. + /// + public class Statistic + { + #region Static members + + /// + /// Number of incircle tests performed. + /// + public static long InCircleCount = 0; + public static long InCircleAdaptCount = 0; + + /// + /// Number of counterclockwise tests performed. + /// + public static long CounterClockwiseCount = 0; + public static long CounterClockwiseAdaptCount = 0; + + /// + /// Number of 3D orientation tests performed. + /// + public static long Orient3dCount = 0; + + /// + /// Number of right-of-hyperbola tests performed. + /// + public static long HyperbolaCount = 0; + + /// + /// // Number of circumcenter calculations performed. + /// + public static long CircumcenterCount = 0; + + /// + /// Number of circle top calculations performed. + /// + public static long CircleTopCount = 0; + + /// + /// Number of vertex relocations. + /// + public static long RelocationCount = 0; + + #endregion + + #region Properties + + double minEdge = 0; + /// + /// Gets the shortest edge. + /// + public double ShortestEdge => minEdge; + + double maxEdge = 0; + /// + /// Gets the longest edge. + /// + public double LongestEdge => maxEdge; + + // + double minAspect = 0; + /// + /// Gets the shortest altitude. + /// + public double ShortestAltitude => minAspect; + + double maxAspect = 0; + /// + /// Gets the largest aspect ratio. + /// + public double LargestAspectRatio => maxAspect; + + double minArea = 0; + /// + /// Gets the smallest area. + /// + public double SmallestArea => minArea; + + double maxArea = 0; + /// + /// Gets the largest area. + /// + public double LargestArea => maxArea; + + double minAngle = 0; + /// + /// Gets the smallest angle. + /// + public double SmallestAngle => minAngle; + + double maxAngle = 0; + /// + /// Gets the largest angle. + /// + public double LargestAngle => maxAngle; + + int[] angleTable; + /// + /// Gets the angle histogram. + /// + public int[] AngleHistogram => angleTable; + + int[] minAngles; + /// + /// Gets the min angles histogram. + /// + public int[] MinAngleHistogram => minAngles; + + int[] maxAngles; + /// + /// Gets the max angles histogram. + /// + public int[] MaxAngleHistogram => maxAngles; + + #endregion + + #region Private methods + + private void GetAspectHistogram(Mesh mesh) + { + int[] aspecttable; + double[] ratiotable; + + aspecttable = new int[16]; + ratiotable = new double[] { + 1.5, 2.0, 2.5, 3.0, 4.0, 6.0, 10.0, 15.0, 25.0, 50.0, + 100.0, 300.0, 1000.0, 10000.0, 100000.0, 0.0 }; + + + Otri tri = default(Otri); + Vertex[] p = new Vertex[3]; + double[] dx = new double[3], dy = new double[3]; + double[] edgelength = new double[3]; + double triarea; + double trilongest2; + double triminaltitude2; + double triaspect2; + + int aspectindex; + int i, j, k; + + tri.orient = 0; + foreach (var t in mesh.triangles) + { + tri.tri = t; + p[0] = tri.Org(); + p[1] = tri.Dest(); + p[2] = tri.Apex(); + trilongest2 = 0.0; + + for (i = 0; i < 3; i++) + { + j = plus1Mod3[i]; + k = minus1Mod3[i]; + dx[i] = p[j].x - p[k].x; + dy[i] = p[j].y - p[k].y; + edgelength[i] = dx[i] * dx[i] + dy[i] * dy[i]; + if (edgelength[i] > trilongest2) + { + trilongest2 = edgelength[i]; + } + } + + //triarea = Primitives.CounterClockwise(p[0], p[1], p[2]); + triarea = Math.Abs((p[2].x - p[0].x) * (p[1].y - p[0].y) - + (p[1].x - p[0].x) * (p[2].y - p[0].y)) / 2.0; + + triminaltitude2 = triarea * triarea / trilongest2; + + triaspect2 = trilongest2 / triminaltitude2; + + aspectindex = 0; + while ((triaspect2 > ratiotable[aspectindex] * ratiotable[aspectindex]) && (aspectindex < 15)) + { + aspectindex++; + } + aspecttable[aspectindex]++; + } + } + + #endregion + + static readonly int[] plus1Mod3 = { 1, 2, 0 }; + static readonly int[] minus1Mod3 = { 2, 0, 1 }; + + /// + /// Update statistics about the quality of the mesh. + /// + /// + public void Update(Mesh mesh, int sampleDegrees) + { + Point[] p = new Point[3]; + + int k1, k2; + int degreeStep; + + //sampleDegrees = 36; // sample every 5 degrees + //sampleDegrees = 45; // sample every 4 degrees + sampleDegrees = 60; // sample every 3 degrees + + double[] cosSquareTable = new double[sampleDegrees / 2 - 1]; + double[] dx = new double[3]; + double[] dy = new double[3]; + double[] edgeLength = new double[3]; + double dotProduct; + double cosSquare; + double triArea; + double triLongest2; + double triMinAltitude2; + double triAspect2; + + double radconst = Math.PI / sampleDegrees; + double degconst = 180.0 / Math.PI; + + // New angle table + angleTable = new int[sampleDegrees]; + minAngles = new int[sampleDegrees]; + maxAngles = new int[sampleDegrees]; + + for (int i = 0; i < sampleDegrees / 2 - 1; i++) + { + cosSquareTable[i] = Math.Cos(radconst * (i + 1)); + cosSquareTable[i] = cosSquareTable[i] * cosSquareTable[i]; + } + for (int i = 0; i < sampleDegrees; i++) + { + angleTable[i] = 0; + } + + minAspect = mesh.bounds.Width + mesh.bounds.Height; + minAspect = minAspect * minAspect; + maxAspect = 0.0; + minEdge = minAspect; + maxEdge = 0.0; + minArea = minAspect; + maxArea = 0.0; + minAngle = 0.0; + maxAngle = 2.0; + + bool acuteBiggest = true; + bool acuteBiggestTri = true; + + double triMinAngle, triMaxAngle = 1; + + foreach (var tri in mesh.triangles) + { + triMinAngle = 0; // Min angle: 0 < a < 60 degrees + triMaxAngle = 1; // Max angle: 60 < a < 180 degrees + + p[0] = tri.vertices[0]; + p[1] = tri.vertices[1]; + p[2] = tri.vertices[2]; + + triLongest2 = 0.0; + + for (int i = 0; i < 3; i++) + { + k1 = plus1Mod3[i]; + k2 = minus1Mod3[i]; + + dx[i] = p[k1].x - p[k2].x; + dy[i] = p[k1].y - p[k2].y; + + edgeLength[i] = dx[i] * dx[i] + dy[i] * dy[i]; + + if (edgeLength[i] > triLongest2) + { + triLongest2 = edgeLength[i]; + } + + if (edgeLength[i] > maxEdge) + { + maxEdge = edgeLength[i]; + } + + if (edgeLength[i] < minEdge) + { + minEdge = edgeLength[i]; + } + } + + //triarea = Primitives.CounterClockwise(p[0], p[1], p[2]); + triArea = Math.Abs((p[2].x - p[0].x) * (p[1].y - p[0].y) - + (p[1].x - p[0].x) * (p[2].y - p[0].y)); + + if (triArea < minArea) + { + minArea = triArea; + } + + if (triArea > maxArea) + { + maxArea = triArea; + } + + triMinAltitude2 = triArea * triArea / triLongest2; + if (triMinAltitude2 < minAspect) + { + minAspect = triMinAltitude2; + } + + triAspect2 = triLongest2 / triMinAltitude2; + if (triAspect2 > maxAspect) + { + maxAspect = triAspect2; + } + + for (int i = 0; i < 3; i++) + { + k1 = plus1Mod3[i]; + k2 = minus1Mod3[i]; + + dotProduct = dx[k1] * dx[k2] + dy[k1] * dy[k2]; + cosSquare = dotProduct * dotProduct / (edgeLength[k1] * edgeLength[k2]); + degreeStep = sampleDegrees / 2 - 1; + + for (int j = degreeStep - 1; j >= 0; j--) + { + if (cosSquare > cosSquareTable[j]) + { + degreeStep = j; + } + } + + if (dotProduct <= 0.0) + { + angleTable[degreeStep]++; + if (cosSquare > minAngle) + { + minAngle = cosSquare; + } + if (acuteBiggest && (cosSquare < maxAngle)) + { + maxAngle = cosSquare; + } + + // Update min/max angle per triangle + if (cosSquare > triMinAngle) + { + triMinAngle = cosSquare; + } + if (acuteBiggestTri && (cosSquare < triMaxAngle)) + { + triMaxAngle = cosSquare; + } + } + else + { + angleTable[sampleDegrees - degreeStep - 1]++; + if (acuteBiggest || (cosSquare > maxAngle)) + { + maxAngle = cosSquare; + acuteBiggest = false; + } + + // Update max angle for (possibly non-acute) triangle + if (acuteBiggestTri || (cosSquare > triMaxAngle)) + { + triMaxAngle = cosSquare; + acuteBiggestTri = false; + } + } + } + + // Update min angle histogram + degreeStep = sampleDegrees / 2 - 1; + + for (int j = degreeStep - 1; j >= 0; j--) + { + if (triMinAngle > cosSquareTable[j]) + { + degreeStep = j; + } + } + minAngles[degreeStep]++; + + // Update max angle histogram + degreeStep = sampleDegrees / 2 - 1; + + for (int j = degreeStep - 1; j >= 0; j--) + { + if (triMaxAngle > cosSquareTable[j]) + { + degreeStep = j; + } + } + + if (acuteBiggestTri) + { + maxAngles[degreeStep]++; + } + else + { + maxAngles[sampleDegrees - degreeStep - 1]++; + } + + acuteBiggestTri = true; + } + + minEdge = Math.Sqrt(minEdge); + maxEdge = Math.Sqrt(maxEdge); + minAspect = Math.Sqrt(minAspect); + maxAspect = Math.Sqrt(maxAspect); + minArea *= 0.5; + maxArea *= 0.5; + if (minAngle >= 1.0) + { + minAngle = 0.0; + } + else + { + minAngle = degconst * Math.Acos(Math.Sqrt(minAngle)); + } + + if (maxAngle >= 1.0) + { + maxAngle = 180.0; + } + else + { + if (acuteBiggest) + { + maxAngle = degconst * Math.Acos(Math.Sqrt(maxAngle)); + } + else + { + maxAngle = 180.0 - degconst * Math.Acos(Math.Sqrt(maxAngle)); + } + } + } + + /// + /// Compute angle information for given triangle. + /// + /// The triangle to check. + /// Array of doubles (length 6). + /// + /// On return, the squared cosines of the minimum and maximum angle will + /// be stored at position data[0] and data[1] respectively. + /// If the triangle was obtuse, data[2] will be set to -1 and maximum angle + /// is computed as (pi - acos(sqrt(data[1]))). + /// + public static void ComputeAngles(ITriangle triangle, double[] data) + { + double min = 0.0; + double max = 1.0; + + var va = triangle.GetVertex(0); + var vb = triangle.GetVertex(1); + var vc = triangle.GetVertex(2); + + double dxa = vb.x - vc.x; + double dya = vb.y - vc.y; + double lena = dxa * dxa + dya * dya; + + double dxb = vc.x - va.x; + double dyb = vc.y - va.y; + double lenb = dxb * dxb + dyb * dyb; + + double dxc = va.x - vb.x; + double dyc = va.y - vb.y; + double lenc = dxc * dxc + dyc * dyc; + + // Dot products. + double dota = data[0] = dxb * dxc + dyb * dyc; + double dotb = data[1] = dxc * dxa + dyc * dya; + double dotc = data[2] = dxa * dxb + dya * dyb; + + // Squared cosines. + data[3] = (dota * dota) / (lenb * lenc); + data[4] = (dotb * dotb) / (lenc * lena); + data[5] = (dotc * dotc) / (lena * lenb); + + // The sign of the dot product will tell us, if the angle is + // acute (value < 0) or obtuse (value > 0). + + bool acute = true; + + double cos, dot; + + for (int i = 0; i < 3; i++) + { + dot = data[i]; + cos = data[3 + i]; + + if (dot <= 0.0) + { + if (cos > min) + { + min = cos; + } + + if (acute && (cos < max)) + { + max = cos; + } + } + else + { + // Update max angle for (possibly non-acute) triangle + if (acute || (cos > max)) + { + max = cos; + acute = false; + } + } + } + + data[0] = min; + data[1] = max; + data[2] = acute ? 1.0 : -1.0; + } + } +} diff --git a/Triangle.NET/Triangle/Tools/TriangleQuadTree.cs b/src/Triangle/Tools/TriangleQuadTree.cs similarity index 96% rename from Triangle.NET/Triangle/Tools/TriangleQuadTree.cs rename to src/Triangle/Tools/TriangleQuadTree.cs index 0b7203a..9956f24 100644 --- a/Triangle.NET/Triangle/Tools/TriangleQuadTree.cs +++ b/src/Triangle/Tools/TriangleQuadTree.cs @@ -1,426 +1,426 @@ -// ----------------------------------------------------------------------- -// -// Original code by Frank Dockhorn, [not available anymore: http://sourceforge.net/projects/quadtreesim/] -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Tools -{ - using System.Collections.Generic; - using System.Linq; - using TriangleNet.Geometry; - - /// - /// A Quadtree implementation optimized for triangles. - /// - public class TriangleQuadTree - { - QuadNode root; - - internal ITriangle[] triangles; - - internal int sizeBound; - internal int maxDepth; - - /// - /// Initializes a new instance of the class. - /// - /// Mesh containing triangles. - /// The maximum depth of the tree. - /// The maximum number of triangles contained in a leaf. - /// - /// The quadtree does not track changes of the mesh. If a mesh is refined or - /// changed in any other way, a new quadtree has to be built to make the point - /// location work. - /// - /// A node of the tree will be split, if its level if less than the max depth parameter - /// AND the number of triangles in the node is greater than the size bound. - /// - public TriangleQuadTree(Mesh mesh, int maxDepth = 10, int sizeBound = 10) - { - this.maxDepth = maxDepth; - this.sizeBound = sizeBound; - - triangles = mesh.Triangles.ToArray(); - - int currentDepth = 0; - - root = new QuadNode(mesh.Bounds, this, true); - root.CreateSubRegion(++currentDepth); - } - - public ITriangle Query(double x, double y) - { - var point = new Point(x, y); - var indices = root.FindTriangles(point); - - foreach (var i in indices) - { - var tri = this.triangles[i]; - - if (IsPointInTriangle(point, tri.GetVertex(0), tri.GetVertex(1), tri.GetVertex(2))) - { - return tri; - } - } - - return null; - } - - /// - /// Test, if a given point lies inside a triangle. - /// - /// Point to locate. - /// Corner point of triangle. - /// Corner point of triangle. - /// Corner point of triangle. - /// True, if point is inside or on the edge of this triangle. - internal static bool IsPointInTriangle(Point p, Point t0, Point t1, Point t2) - { - // TODO: no need to create new Point instances here - Point d0 = new Point(t1.x - t0.x, t1.y - t0.y); - Point d1 = new Point(t2.x - t0.x, t2.y - t0.y); - Point d2 = new Point(p.x - t0.x, p.y - t0.y); - - // crossproduct of (0, 0, 1) and d0 - Point c0 = new Point(-d0.y, d0.x); - - // crossproduct of (0, 0, 1) and d1 - Point c1 = new Point(-d1.y, d1.x); - - // Linear combination d2 = s * d0 + v * d1. - // - // Multiply both sides of the equation with c0 and c1 - // and solve for s and v respectively - // - // s = d2 * c1 / d0 * c1 - // v = d2 * c0 / d1 * c0 - - double s = DotProduct(d2, c1) / DotProduct(d0, c1); - double v = DotProduct(d2, c0) / DotProduct(d1, c0); - - if (s >= 0 && v >= 0 && ((s + v) <= 1)) - { - // Point is inside or on the edge of this triangle. - return true; - } - - return false; - } - - internal static double DotProduct(Point p, Point q) - { - return p.x * q.x + p.y * q.y; - } - - /// - /// A node of the quadtree. - /// - class QuadNode - { - const int SW = 0; - const int SE = 1; - const int NW = 2; - const int NE = 3; - - const double EPS = 1e-6; - - static readonly byte[] BITVECTOR = { 0x1, 0x2, 0x4, 0x8 }; - - Rectangle bounds; - Point pivot; - TriangleQuadTree tree; - QuadNode[] regions; - List triangles; - - byte bitRegions; - - public QuadNode(Rectangle box, TriangleQuadTree tree) - : this(box, tree, false) - { - } - - public QuadNode(Rectangle box, TriangleQuadTree tree, bool init) - { - this.tree = tree; - - this.bounds = new Rectangle(box.Left, box.Bottom, box.Width, box.Height); - this.pivot = new Point((box.Left + box.Right) / 2, (box.Bottom + box.Top) / 2); - - this.bitRegions = 0; - - this.regions = new QuadNode[4]; - this.triangles = new List(); - - if (init) - { - int count = tree.triangles.Length; - - // Allocate memory upfront - triangles.Capacity = count; - - for (int i = 0; i < count; i++) - { - triangles.Add(i); - } - } - } - - public List FindTriangles(Point searchPoint) - { - int region = FindRegion(searchPoint); - if (regions[region] == null) - { - return triangles; - } - return regions[region].FindTriangles(searchPoint); - } - - public void CreateSubRegion(int currentDepth) - { - // The four sub regions of the quad tree - // +--------------+ - // | nw 2 | ne 3 | - // |------+pivot--| - // | sw 0 | se 1 | - // +--------------+ - Rectangle box; - - var width = bounds.Right - pivot.x; - var height = bounds.Top - pivot.y; - - // 1. region south west - box = new Rectangle(bounds.Left, bounds.Bottom, width, height); - regions[0] = new QuadNode(box, tree); - - // 2. region south east - box = new Rectangle(pivot.x, bounds.Bottom, width, height); - regions[1] = new QuadNode(box, tree); - - // 3. region north west - box = new Rectangle(bounds.Left, pivot.y, width, height); - regions[2] = new QuadNode(box, tree); - - // 4. region north east - box = new Rectangle(pivot.x, pivot.y, width, height); - regions[3] = new QuadNode(box, tree); - - Point[] triangle = new Point[3]; - - // Find region for every triangle vertex - foreach (var index in triangles) - { - ITriangle tri = tree.triangles[index]; - - triangle[0] = tri.GetVertex(0); - triangle[1] = tri.GetVertex(1); - triangle[2] = tri.GetVertex(2); - - AddTriangleToRegion(triangle, index); - } - - for (int i = 0; i < 4; i++) - { - if (regions[i].triangles.Count > tree.sizeBound && currentDepth < tree.maxDepth) - { - regions[i].CreateSubRegion(currentDepth + 1); - } - } - } - - void AddTriangleToRegion(Point[] triangle, int index) - { - bitRegions = 0; - if (TriangleQuadTree.IsPointInTriangle(pivot, triangle[0], triangle[1], triangle[2])) - { - AddToRegion(index, SW); - AddToRegion(index, SE); - AddToRegion(index, NW); - AddToRegion(index, NE); - return; - } - - FindTriangleIntersections(triangle, index); - - if (bitRegions == 0) - { - // we didn't find any intersection so we add this triangle to a point's region - int region = FindRegion(triangle[0]); - regions[region].triangles.Add(index); - } - } - - void FindTriangleIntersections(Point[] triangle, int index) - { - // PLEASE NOTE: - // Handling of component comparison is tightly associated with the implementation - // of the findRegion() function. That means when the point to be compared equals - // the pivot point the triangle must be put at least into region 2. - // - // Linear equations are in parametric form. - // pivot.x = triangle[0].x + t * (triangle[1].x - triangle[0].x) - // pivot.y = triangle[0].y + t * (triangle[1].y - triangle[0].y) - - int k = 2; - - double dx, dy; - // Iterate through all triangle laterals and find bounding box intersections - for (int i = 0; i < 3; k = i++) - { - dx = triangle[i].x - triangle[k].x; - dy = triangle[i].y - triangle[k].y; - - if (dx != 0.0) - { - FindIntersectionsWithX(dx, dy, triangle, index, k); - } - if (dy != 0.0) - { - FindIntersectionsWithY(dx, dy, triangle, index, k); - } - } - } - - void FindIntersectionsWithX(double dx, double dy, Point[] triangle, int index, int k) - { - double t; - - // find intersection with plane x = m_pivot.dX - t = (pivot.x - triangle[k].x) / dx; - if (t < (1 + EPS) && t > -EPS) - { - // we have an intersection - double yComponent = triangle[k].y + t * dy; - - if (yComponent < pivot.y && yComponent >= bounds.Bottom) - { - AddToRegion(index, SW); - AddToRegion(index, SE); - } - else if (yComponent <= bounds.Top) - { - AddToRegion(index, NW); - AddToRegion(index, NE); - } - } - - // find intersection with plane x = m_boundingBox[0].dX - t = (bounds.Left - triangle[k].x) / dx; - if (t < (1 + EPS) && t > -EPS) - { - // we have an intersection - double yComponent = triangle[k].y + t * dy; - - if (yComponent < pivot.y && yComponent >= bounds.Bottom) - { - AddToRegion(index, SW); - } - else if (yComponent <= bounds.Top) // TODO: check && yComponent >= pivot.Y - { - AddToRegion(index, NW); - } - } - - // find intersection with plane x = m_boundingBox[1].dX - t = (bounds.Right - triangle[k].x) / dx; - if (t < (1 + EPS) && t > -EPS) - { - // we have an intersection - double yComponent = triangle[k].y + t * dy; - - if (yComponent < pivot.y && yComponent >= bounds.Bottom) - { - AddToRegion(index, SE); - } - else if (yComponent <= bounds.Top) - { - AddToRegion(index, NE); - } - } - } - - void FindIntersectionsWithY(double dx, double dy, Point[] triangle, int index, int k) - { - double t, xComponent; - - // find intersection with plane y = m_pivot.dY - t = (pivot.y - triangle[k].y) / dy; - if (t < (1 + EPS) && t > -EPS) - { - // we have an intersection - xComponent = triangle[k].x + t * dx; - - if (xComponent > pivot.x && xComponent <= bounds.Right) - { - AddToRegion(index, SE); - AddToRegion(index, NE); - } - else if (xComponent >= bounds.Left) - { - AddToRegion(index, SW); - AddToRegion(index, NW); - } - } - - // find intersection with plane y = m_boundingBox[0].dY - t = (bounds.Bottom - triangle[k].y) / dy; - if (t < (1 + EPS) && t > -EPS) - { - // we have an intersection - xComponent = triangle[k].x + t * dx; - - if (xComponent > pivot.x && xComponent <= bounds.Right) - { - AddToRegion(index, SE); - } - else if (xComponent >= bounds.Left) - { - AddToRegion(index, SW); - } - } - - // find intersection with plane y = m_boundingBox[1].dY - t = (bounds.Top - triangle[k].y) / dy; - if (t < (1 + EPS) && t > -EPS) - { - // we have an intersection - xComponent = triangle[k].x + t * dx; - - if (xComponent > pivot.x && xComponent <= bounds.Right) - { - AddToRegion(index, NE); - } - else if (xComponent >= bounds.Left) - { - AddToRegion(index, NW); - } - } - } - - int FindRegion(Point point) - { - int b = 2; - if (point.y < pivot.y) - { - b = 0; - } - if (point.x > pivot.x) - { - b++; - } - return b; - } - - void AddToRegion(int index, int region) - { - //if (!(m_bitRegions & BITVECTOR[region])) - if ((bitRegions & BITVECTOR[region]) == 0) - { - regions[region].triangles.Add(index); - bitRegions |= BITVECTOR[region]; - } - } - } - } -} +// ----------------------------------------------------------------------- +// +// Copyright(c) 2018 Frank Dockhorn, MIT license, https://sourceforge.net/projects/quadtreedemo/ +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System.Collections.Generic; + using System.Linq; + using TriangleNet.Geometry; + + /// + /// A Quadtree implementation optimized for triangles. + /// + public class TriangleQuadTree + { + QuadNode root; + + internal ITriangle[] triangles; + + internal int sizeBound; + internal int maxDepth; + + /// + /// Initializes a new instance of the class. + /// + /// Mesh containing triangles. + /// The maximum depth of the tree. + /// The maximum number of triangles contained in a leaf. + /// + /// The quadtree does not track changes of the mesh. If a mesh is refined or + /// changed in any other way, a new quadtree has to be built to make the point + /// location work. + /// + /// A node of the tree will be split, if its level if less than the max depth parameter + /// AND the number of triangles in the node is greater than the size bound. + /// + public TriangleQuadTree(Mesh mesh, int maxDepth = 10, int sizeBound = 10) + { + this.maxDepth = maxDepth; + this.sizeBound = sizeBound; + + triangles = mesh.Triangles.ToArray(); + + int currentDepth = 0; + + root = new QuadNode(mesh.Bounds, this, true); + root.CreateSubRegion(++currentDepth); + } + + public ITriangle Query(double x, double y) + { + var point = new Point(x, y); + var indices = root.FindTriangles(point); + + foreach (var i in indices) + { + var tri = this.triangles[i]; + + if (IsPointInTriangle(point, tri.GetVertex(0), tri.GetVertex(1), tri.GetVertex(2))) + { + return tri; + } + } + + return null; + } + + /// + /// Test, if a given point lies inside a triangle. + /// + /// Point to locate. + /// Corner point of triangle. + /// Corner point of triangle. + /// Corner point of triangle. + /// True, if point is inside or on the edge of this triangle. + internal static bool IsPointInTriangle(Point p, Point t0, Point t1, Point t2) + { + // TODO: no need to create new Point instances here + Point d0 = new Point(t1.x - t0.x, t1.y - t0.y); + Point d1 = new Point(t2.x - t0.x, t2.y - t0.y); + Point d2 = new Point(p.x - t0.x, p.y - t0.y); + + // crossproduct of (0, 0, 1) and d0 + Point c0 = new Point(-d0.y, d0.x); + + // crossproduct of (0, 0, 1) and d1 + Point c1 = new Point(-d1.y, d1.x); + + // Linear combination d2 = s * d0 + v * d1. + // + // Multiply both sides of the equation with c0 and c1 + // and solve for s and v respectively + // + // s = d2 * c1 / d0 * c1 + // v = d2 * c0 / d1 * c0 + + double s = DotProduct(d2, c1) / DotProduct(d0, c1); + double v = DotProduct(d2, c0) / DotProduct(d1, c0); + + if (s >= 0 && v >= 0 && ((s + v) <= 1)) + { + // Point is inside or on the edge of this triangle. + return true; + } + + return false; + } + + internal static double DotProduct(Point p, Point q) + { + return p.x * q.x + p.y * q.y; + } + + /// + /// A node of the quadtree. + /// + class QuadNode + { + const int SW = 0; + const int SE = 1; + const int NW = 2; + const int NE = 3; + + const double EPS = 1e-6; + + static readonly byte[] BITVECTOR = { 0x1, 0x2, 0x4, 0x8 }; + + Rectangle bounds; + Point pivot; + TriangleQuadTree tree; + QuadNode[] regions; + List triangles; + + byte bitRegions; + + public QuadNode(Rectangle box, TriangleQuadTree tree) + : this(box, tree, false) + { + } + + public QuadNode(Rectangle box, TriangleQuadTree tree, bool init) + { + this.tree = tree; + + this.bounds = new Rectangle(box.Left, box.Bottom, box.Width, box.Height); + this.pivot = new Point((box.Left + box.Right) / 2, (box.Bottom + box.Top) / 2); + + this.bitRegions = 0; + + this.regions = new QuadNode[4]; + this.triangles = new List(); + + if (init) + { + int count = tree.triangles.Length; + + // Allocate memory upfront + triangles.Capacity = count; + + for (int i = 0; i < count; i++) + { + triangles.Add(i); + } + } + } + + public List FindTriangles(Point searchPoint) + { + int region = FindRegion(searchPoint); + if (regions[region] == null) + { + return triangles; + } + return regions[region].FindTriangles(searchPoint); + } + + public void CreateSubRegion(int currentDepth) + { + // The four sub regions of the quad tree + // +--------------+ + // | nw 2 | ne 3 | + // |------+pivot--| + // | sw 0 | se 1 | + // +--------------+ + Rectangle box; + + var width = bounds.Right - pivot.x; + var height = bounds.Top - pivot.y; + + // 1. region south west + box = new Rectangle(bounds.Left, bounds.Bottom, width, height); + regions[0] = new QuadNode(box, tree); + + // 2. region south east + box = new Rectangle(pivot.x, bounds.Bottom, width, height); + regions[1] = new QuadNode(box, tree); + + // 3. region north west + box = new Rectangle(bounds.Left, pivot.y, width, height); + regions[2] = new QuadNode(box, tree); + + // 4. region north east + box = new Rectangle(pivot.x, pivot.y, width, height); + regions[3] = new QuadNode(box, tree); + + Point[] triangle = new Point[3]; + + // Find region for every triangle vertex + foreach (var index in triangles) + { + ITriangle tri = tree.triangles[index]; + + triangle[0] = tri.GetVertex(0); + triangle[1] = tri.GetVertex(1); + triangle[2] = tri.GetVertex(2); + + AddTriangleToRegion(triangle, index); + } + + for (int i = 0; i < 4; i++) + { + if (regions[i].triangles.Count > tree.sizeBound && currentDepth < tree.maxDepth) + { + regions[i].CreateSubRegion(currentDepth + 1); + } + } + } + + void AddTriangleToRegion(Point[] triangle, int index) + { + bitRegions = 0; + if (TriangleQuadTree.IsPointInTriangle(pivot, triangle[0], triangle[1], triangle[2])) + { + AddToRegion(index, SW); + AddToRegion(index, SE); + AddToRegion(index, NW); + AddToRegion(index, NE); + return; + } + + FindTriangleIntersections(triangle, index); + + if (bitRegions == 0) + { + // we didn't find any intersection so we add this triangle to a point's region + int region = FindRegion(triangle[0]); + regions[region].triangles.Add(index); + } + } + + void FindTriangleIntersections(Point[] triangle, int index) + { + // PLEASE NOTE: + // Handling of component comparison is tightly associated with the implementation + // of the findRegion() function. That means when the point to be compared equals + // the pivot point the triangle must be put at least into region 2. + // + // Linear equations are in parametric form. + // pivot.x = triangle[0].x + t * (triangle[1].x - triangle[0].x) + // pivot.y = triangle[0].y + t * (triangle[1].y - triangle[0].y) + + int k = 2; + + double dx, dy; + // Iterate through all triangle laterals and find bounding box intersections + for (int i = 0; i < 3; k = i++) + { + dx = triangle[i].x - triangle[k].x; + dy = triangle[i].y - triangle[k].y; + + if (dx != 0.0) + { + FindIntersectionsWithX(dx, dy, triangle, index, k); + } + if (dy != 0.0) + { + FindIntersectionsWithY(dx, dy, triangle, index, k); + } + } + } + + void FindIntersectionsWithX(double dx, double dy, Point[] triangle, int index, int k) + { + double t; + + // find intersection with plane x = m_pivot.dX + t = (pivot.x - triangle[k].x) / dx; + if (t < (1 + EPS) && t > -EPS) + { + // we have an intersection + double yComponent = triangle[k].y + t * dy; + + if (yComponent < pivot.y && yComponent >= bounds.Bottom) + { + AddToRegion(index, SW); + AddToRegion(index, SE); + } + else if (yComponent <= bounds.Top) + { + AddToRegion(index, NW); + AddToRegion(index, NE); + } + } + + // find intersection with plane x = m_boundingBox[0].dX + t = (bounds.Left - triangle[k].x) / dx; + if (t < (1 + EPS) && t > -EPS) + { + // we have an intersection + double yComponent = triangle[k].y + t * dy; + + if (yComponent < pivot.y && yComponent >= bounds.Bottom) + { + AddToRegion(index, SW); + } + else if (yComponent <= bounds.Top) // TODO: check && yComponent >= pivot.Y + { + AddToRegion(index, NW); + } + } + + // find intersection with plane x = m_boundingBox[1].dX + t = (bounds.Right - triangle[k].x) / dx; + if (t < (1 + EPS) && t > -EPS) + { + // we have an intersection + double yComponent = triangle[k].y + t * dy; + + if (yComponent < pivot.y && yComponent >= bounds.Bottom) + { + AddToRegion(index, SE); + } + else if (yComponent <= bounds.Top) + { + AddToRegion(index, NE); + } + } + } + + void FindIntersectionsWithY(double dx, double dy, Point[] triangle, int index, int k) + { + double t, xComponent; + + // find intersection with plane y = m_pivot.dY + t = (pivot.y - triangle[k].y) / dy; + if (t < (1 + EPS) && t > -EPS) + { + // we have an intersection + xComponent = triangle[k].x + t * dx; + + if (xComponent > pivot.x && xComponent <= bounds.Right) + { + AddToRegion(index, SE); + AddToRegion(index, NE); + } + else if (xComponent >= bounds.Left) + { + AddToRegion(index, SW); + AddToRegion(index, NW); + } + } + + // find intersection with plane y = m_boundingBox[0].dY + t = (bounds.Bottom - triangle[k].y) / dy; + if (t < (1 + EPS) && t > -EPS) + { + // we have an intersection + xComponent = triangle[k].x + t * dx; + + if (xComponent > pivot.x && xComponent <= bounds.Right) + { + AddToRegion(index, SE); + } + else if (xComponent >= bounds.Left) + { + AddToRegion(index, SW); + } + } + + // find intersection with plane y = m_boundingBox[1].dY + t = (bounds.Top - triangle[k].y) / dy; + if (t < (1 + EPS) && t > -EPS) + { + // we have an intersection + xComponent = triangle[k].x + t * dx; + + if (xComponent > pivot.x && xComponent <= bounds.Right) + { + AddToRegion(index, NE); + } + else if (xComponent >= bounds.Left) + { + AddToRegion(index, NW); + } + } + } + + int FindRegion(Point point) + { + int b = 2; + if (point.y < pivot.y) + { + b = 0; + } + if (point.x > pivot.x) + { + b++; + } + return b; + } + + void AddToRegion(int index, int region) + { + //if (!(m_bitRegions & BITVECTOR[region])) + if ((bitRegions & BITVECTOR[region]) == 0) + { + regions[region].triangles.Add(index); + bitRegions |= BITVECTOR[region]; + } + } + } + } +} diff --git a/Triangle.NET/Triangle/Tools/VertexSorter.cs b/src/Triangle/Tools/VertexSorter.cs similarity index 88% rename from Triangle.NET/Triangle/Tools/VertexSorter.cs rename to src/Triangle/Tools/VertexSorter.cs index 72e662e..87092f5 100644 --- a/Triangle.NET/Triangle/Tools/VertexSorter.cs +++ b/src/Triangle/Tools/VertexSorter.cs @@ -1,371 +1,371 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Tools -{ - using System; - using TriangleNet.Geometry; - - /// - /// Sort an array of points using quicksort. - /// - public class VertexSorter - { - private const int RANDOM_SEED = 57113; - - Random rand; - - Vertex[] points; - - VertexSorter(Vertex[] points, int seed) - { - this.points = points; - this.rand = new Random(seed); - } - - /// - /// Sorts the given vertex array by x-coordinate. - /// - /// The vertex array. - /// Random seed used for pivoting. - public static void Sort(Vertex[] array, int seed = RANDOM_SEED) - { - var qs = new VertexSorter(array, seed); - - qs.QuickSort(0, array.Length - 1); - } - - /// - /// Impose alternating cuts on given vertex array. - /// - /// The vertex array. - /// The number of vertices to sort. - /// Random seed used for pivoting. - public static void Alternate(Vertex[] array, int length, int seed = RANDOM_SEED) - { - var qs = new VertexSorter(array, seed); - - int divider = length >> 1; - - // Re-sort the array of vertices to accommodate alternating cuts. - if (length - divider >= 2) - { - if (divider >= 2) - { - qs.AlternateAxes(0, divider - 1, 1); - } - - qs.AlternateAxes(divider, length - 1, 1); - } - } - - #region Quicksort - - /// - /// Sort an array of vertices by x-coordinate, using the y-coordinate as a secondary key. - /// - /// - /// - /// - /// Uses quicksort. Randomized O(n log n) time. No, I did not make any of - /// the usual quicksort mistakes. - /// - private void QuickSort(int left, int right) - { - int oleft = left; - int oright = right; - int arraysize = right - left + 1; - int pivot; - double pivotx, pivoty; - Vertex temp; - - var array = this.points; - - if (arraysize < 32) - { - // Insertion sort - for (int i = left + 1; i <= right; i++) - { - var a = array[i]; - int j = i - 1; - while (j >= left && (array[j].x > a.x || (array[j].x == a.x && array[j].y > a.y))) - { - array[j + 1] = array[j]; - j--; - } - array[j + 1] = a; - } - - return; - } - - // Choose a random pivot to split the array. - pivot = rand.Next(left, right); - pivotx = array[pivot].x; - pivoty = array[pivot].y; - // Split the array. - left--; - right++; - while (left < right) - { - // Search for a vertex whose x-coordinate is too large for the left. - do - { - left++; - } - while ((left <= right) && ((array[left].x < pivotx) || - ((array[left].x == pivotx) && (array[left].y < pivoty)))); - - // Search for a vertex whose x-coordinate is too small for the right. - do - { - right--; - } - while ((left <= right) && ((array[right].x > pivotx) || - ((array[right].x == pivotx) && (array[right].y > pivoty)))); - - if (left < right) - { - // Swap the left and right vertices. - temp = array[left]; - array[left] = array[right]; - array[right] = temp; - } - } - - if (left > oleft) - { - // Recursively sort the left subset. - QuickSort(oleft, left); - } - - if (oright > right + 1) - { - // Recursively sort the right subset. - QuickSort(right + 1, oright); - } - } - - #endregion - - #region Alternate axes - - /// - /// Sorts the vertices as appropriate for the divide-and-conquer algorithm with - /// alternating cuts. - /// - /// - /// - /// - /// - /// Partitions by x-coordinate if axis == 0; by y-coordinate if axis == 1. - /// For the base case, subsets containing only two or three vertices are - /// always sorted by x-coordinate. - /// - private void AlternateAxes(int left, int right, int axis) - { - int size = right - left + 1; - int divider = size >> 1; - - if (size <= 3) - { - // Recursive base case: subsets of two or three vertices will be - // handled specially, and should always be sorted by x-coordinate. - axis = 0; - } - - // Partition with a horizontal or vertical cut. - if (axis == 0) - { - VertexMedianX(left, right, left + divider); - } - else - { - VertexMedianY(left, right, left + divider); - } - - // Recursively partition the subsets with a cross cut. - if (size - divider >= 2) - { - if (divider >= 2) - { - AlternateAxes(left, left + divider - 1, 1 - axis); - } - - AlternateAxes(left + divider, right, 1 - axis); - } - } - - /// - /// An order statistic algorithm, almost. Shuffles an array of vertices so that the - /// first 'median' vertices occur lexicographically before the remaining vertices. - /// - /// - /// - /// - /// - /// Uses the x-coordinate as the primary key. Very similar to the QuickSort() - /// procedure, but runs in randomized linear time. - /// - private void VertexMedianX(int left, int right, int median) - { - int arraysize = right - left + 1; - int oleft = left, oright = right; - int pivot; - double pivot1, pivot2; - Vertex temp; - - var array = this.points; - - if (arraysize == 2) - { - // Recursive base case. - if ((array[left].x > array[right].x) || - ((array[left].x == array[right].x) && - (array[left].y > array[right].y))) - { - temp = array[right]; - array[right] = array[left]; - array[left] = temp; - } - return; - } - - // Choose a random pivot to split the array. - pivot = rand.Next(left, right); - pivot1 = array[pivot].x; - pivot2 = array[pivot].y; - - left--; - right++; - while (left < right) - { - // Search for a vertex whose x-coordinate is too large for the left. - do - { - left++; - } - while ((left <= right) && ((array[left].x < pivot1) || - ((array[left].x == pivot1) && (array[left].y < pivot2)))); - - // Search for a vertex whose x-coordinate is too small for the right. - do - { - right--; - } - while ((left <= right) && ((array[right].x > pivot1) || - ((array[right].x == pivot1) && (array[right].y > pivot2)))); - - if (left < right) - { - // Swap the left and right vertices. - temp = array[left]; - array[left] = array[right]; - array[right] = temp; - } - } - - // Unlike in vertexsort(), at most one of the following conditionals is true. - if (left > median) - { - // Recursively shuffle the left subset. - VertexMedianX(oleft, left - 1, median); - } - - if (right < median - 1) - { - // Recursively shuffle the right subset. - VertexMedianX(right + 1, oright, median); - } - } - - /// - /// An order statistic algorithm, almost. Shuffles an array of vertices so that - /// the first 'median' vertices occur lexicographically before the remaining vertices. - /// - /// - /// - /// - /// - /// Uses the y-coordinate as the primary key. Very similar to the QuickSort() - /// procedure, but runs in randomized linear time. - /// - private void VertexMedianY(int left, int right, int median) - { - int arraysize = right - left + 1; - int oleft = left, oright = right; - int pivot; - double pivot1, pivot2; - Vertex temp; - - var array = this.points; - - if (arraysize == 2) - { - // Recursive base case. - if ((array[left].y > array[right].y) || - ((array[left].y == array[right].y) && - (array[left].x > array[right].x))) - { - temp = array[right]; - array[right] = array[left]; - array[left] = temp; - } - return; - } - - // Choose a random pivot to split the array. - pivot = rand.Next(left, right); - pivot1 = array[pivot].y; - pivot2 = array[pivot].x; - - left--; - right++; - while (left < right) - { - // Search for a vertex whose x-coordinate is too large for the left. - do - { - left++; - } - while ((left <= right) && ((array[left].y < pivot1) || - ((array[left].y == pivot1) && (array[left].x < pivot2)))); - - // Search for a vertex whose x-coordinate is too small for the right. - do - { - right--; - } - while ((left <= right) && ((array[right].y > pivot1) || - ((array[right].y == pivot1) && (array[right].x > pivot2)))); - - if (left < right) - { - // Swap the left and right vertices. - temp = array[left]; - array[left] = array[right]; - array[right] = temp; - } - } - - // Unlike in QuickSort(), at most one of the following conditionals is true. - if (left > median) - { - // Recursively shuffle the left subset. - VertexMedianY(oleft, left - 1, median); - } - - if (right < median - 1) - { - // Recursively shuffle the right subset. - VertexMedianY(right + 1, oright, median); - } - } - - #endregion - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Tools +{ + using System; + using TriangleNet.Geometry; + + /// + /// Sort an array of points using quicksort. + /// + public class VertexSorter + { + private const int RANDOM_SEED = 57113; + + Random rand; + + Vertex[] points; + + VertexSorter(Vertex[] points, int seed) + { + this.points = points; + this.rand = new Random(seed); + } + + /// + /// Sorts the given vertex array by x-coordinate. + /// + /// The vertex array. + /// Random seed used for pivoting. + public static void Sort(Vertex[] array, int seed = RANDOM_SEED) + { + var qs = new VertexSorter(array, seed); + + qs.QuickSort(0, array.Length - 1); + } + + /// + /// Impose alternating cuts on given vertex array. + /// + /// The vertex array. + /// The number of vertices to sort. + /// Random seed used for pivoting. + public static void Alternate(Vertex[] array, int length, int seed = RANDOM_SEED) + { + var qs = new VertexSorter(array, seed); + + int divider = length >> 1; + + // Re-sort the array of vertices to accommodate alternating cuts. + if (length - divider >= 2) + { + if (divider >= 2) + { + qs.AlternateAxes(0, divider - 1, 1); + } + + qs.AlternateAxes(divider, length - 1, 1); + } + } + + #region Quicksort + + /// + /// Sort an array of vertices by x-coordinate, using the y-coordinate as a secondary key. + /// + /// + /// + /// + /// Uses quicksort. Randomized O(n log n) time. No, I did not make any of + /// the usual quicksort mistakes. + /// + private void QuickSort(int left, int right) + { + int oleft = left; + int oright = right; + int arraysize = right - left + 1; + int pivot; + double pivotx, pivoty; + Vertex temp; + + var array = this.points; + + if (arraysize < 32) + { + // Insertion sort + for (int i = left + 1; i <= right; i++) + { + var a = array[i]; + int j = i - 1; + while (j >= left && (array[j].x > a.x || (array[j].x == a.x && array[j].y > a.y))) + { + array[j + 1] = array[j]; + j--; + } + array[j + 1] = a; + } + + return; + } + + // Choose a random pivot to split the array. + pivot = rand.Next(left, right); + pivotx = array[pivot].x; + pivoty = array[pivot].y; + // Split the array. + left--; + right++; + while (left < right) + { + // Search for a vertex whose x-coordinate is too large for the left. + do + { + left++; + } + while ((left <= right) && ((array[left].x < pivotx) || + ((array[left].x == pivotx) && (array[left].y < pivoty)))); + + // Search for a vertex whose x-coordinate is too small for the right. + do + { + right--; + } + while ((left <= right) && ((array[right].x > pivotx) || + ((array[right].x == pivotx) && (array[right].y > pivoty)))); + + if (left < right) + { + // Swap the left and right vertices. + temp = array[left]; + array[left] = array[right]; + array[right] = temp; + } + } + + if (left > oleft) + { + // Recursively sort the left subset. + QuickSort(oleft, left); + } + + if (oright > right + 1) + { + // Recursively sort the right subset. + QuickSort(right + 1, oright); + } + } + + #endregion + + #region Alternate axes + + /// + /// Sorts the vertices as appropriate for the divide-and-conquer algorithm with + /// alternating cuts. + /// + /// + /// + /// + /// + /// Partitions by x-coordinate if axis == 0; by y-coordinate if axis == 1. + /// For the base case, subsets containing only two or three vertices are + /// always sorted by x-coordinate. + /// + private void AlternateAxes(int left, int right, int axis) + { + int size = right - left + 1; + int divider = size >> 1; + + if (size <= 3) + { + // Recursive base case: subsets of two or three vertices will be + // handled specially, and should always be sorted by x-coordinate. + axis = 0; + } + + // Partition with a horizontal or vertical cut. + if (axis == 0) + { + VertexMedianX(left, right, left + divider); + } + else + { + VertexMedianY(left, right, left + divider); + } + + // Recursively partition the subsets with a cross cut. + if (size - divider >= 2) + { + if (divider >= 2) + { + AlternateAxes(left, left + divider - 1, 1 - axis); + } + + AlternateAxes(left + divider, right, 1 - axis); + } + } + + /// + /// An order statistic algorithm, almost. Shuffles an array of vertices so that the + /// first 'median' vertices occur lexicographically before the remaining vertices. + /// + /// + /// + /// + /// + /// Uses the x-coordinate as the primary key. Very similar to the QuickSort() + /// procedure, but runs in randomized linear time. + /// + private void VertexMedianX(int left, int right, int median) + { + int arraysize = right - left + 1; + int oleft = left, oright = right; + int pivot; + double px, py; // pivot x and y coordinatex + Vertex temp; + + var array = this.points; + + if (arraysize == 2) + { + // Recursive base case. + if ((array[left].x > array[right].x) || + ((array[left].x == array[right].x) && + (array[left].y > array[right].y))) + { + temp = array[right]; + array[right] = array[left]; + array[left] = temp; + } + return; + } + + // Choose a random pivot to split the array. + pivot = rand.Next(left, right); + px = array[pivot].x; + py = array[pivot].y; + + left--; + right++; + while (left < right) + { + // Search for a vertex whose x-coordinate is too large for the left. + do + { + left++; + } + while ((left <= right) && ((array[left].x < px) || + ((array[left].x == px) && (array[left].y < py)))); + + // Search for a vertex whose x-coordinate is too small for the right. + do + { + right--; + } + while ((left <= right) && ((array[right].x > px) || + ((array[right].x == px) && (array[right].y > py)))); + + if (left < right) + { + // Swap the left and right vertices. + temp = array[left]; + array[left] = array[right]; + array[right] = temp; + } + } + + // Unlike in QuickSort(), at most one of the following conditionals is true. + if (left > median) + { + // Recursively shuffle the left subset. + VertexMedianX(oleft, left - 1, median); + } + + if (right < median - 1) + { + // Recursively shuffle the right subset. + VertexMedianX(right + 1, oright, median); + } + } + + /// + /// An order statistic algorithm, almost. Shuffles an array of vertices so that + /// the first 'median' vertices occur lexicographically before the remaining vertices. + /// + /// + /// + /// + /// + /// Uses the y-coordinate as the primary key. Very similar to the QuickSort() + /// procedure, but runs in randomized linear time. + /// + private void VertexMedianY(int left, int right, int median) + { + int arraysize = right - left + 1; + int oleft = left, oright = right; + int pivot; + double px, py; // pivot x and y coordinatex + Vertex temp; + + var array = this.points; + + if (arraysize == 2) + { + // Recursive base case. + if ((array[left].y > array[right].y) || + ((array[left].y == array[right].y) && + (array[left].x > array[right].x))) + { + temp = array[right]; + array[right] = array[left]; + array[left] = temp; + } + return; + } + + // Choose a random pivot to split the array. + pivot = rand.Next(left, right); + px = array[pivot].y; + py = array[pivot].x; + + left--; + right++; + while (left < right) + { + // Search for a vertex whose x-coordinate is too large for the left. + do + { + left++; + } + while ((left <= right) && ((array[left].y < px) || + ((array[left].y == px) && (array[left].x < py)))); + + // Search for a vertex whose x-coordinate is too small for the right. + do + { + right--; + } + while ((left <= right) && ((array[right].y > px) || + ((array[right].y == px) && (array[right].x > py)))); + + if (left < right) + { + // Swap the left and right vertices. + temp = array[left]; + array[left] = array[right]; + array[right] = temp; + } + } + + // Unlike in QuickSort(), at most one of the following conditionals is true. + if (left > median) + { + // Recursively shuffle the left subset. + VertexMedianY(oleft, left - 1, median); + } + + if (right < median - 1) + { + // Recursively shuffle the right subset. + VertexMedianY(right + 1, oright, median); + } + } + + #endregion + } +} diff --git a/Triangle.NET/Triangle/Topology/DCEL/DcelMesh.cs b/src/Triangle/Topology/DCEL/DcelMesh.cs similarity index 94% rename from Triangle.NET/Triangle/Topology/DCEL/DcelMesh.cs rename to src/Triangle/Topology/DCEL/DcelMesh.cs index cf301db..c0359bd 100644 --- a/Triangle.NET/Triangle/Topology/DCEL/DcelMesh.cs +++ b/src/Triangle/Topology/DCEL/DcelMesh.cs @@ -1,269 +1,269 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Topology.DCEL -{ - using System.Collections.Generic; - using TriangleNet.Geometry; - - public class DcelMesh - { - protected List vertices; - protected List edges; - protected List faces; - - /// - /// Initializes a new instance of the class. - /// - public DcelMesh() - : this(true) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// If false, lists will not be initialized. - protected DcelMesh(bool initialize) - { - if (initialize) - { - vertices = new List(); - edges = new List(); - faces = new List(); - } - } - - /// - /// Gets the vertices of the Voronoi diagram. - /// - public List Vertices - { - get { return vertices; } - } - - /// - /// Gets the list of half-edges specify the Voronoi diagram topology. - /// - public List HalfEdges - { - get { return edges; } - } - - /// - /// Gets the faces of the Voronoi diagram. - /// - public List Faces - { - get { return faces; } - } - - /// - /// Gets the collection of edges of the Voronoi diagram. - /// - public IEnumerable Edges - { - get { return EnumerateEdges(); } - } - - /// - /// Check if the DCEL is consistend. - /// - /// If true, faces are assumed to be closed (i.e. all edges must have - /// a valid next pointer). - /// Maximum edge count of faces (default = 0 means skip check). - /// - public virtual bool IsConsistent(bool closed = true, int depth = 0) - { - // Check vertices for null pointers. - foreach (var vertex in vertices) - { - if (vertex.id < 0) - { - continue; - } - - if (vertex.leaving == null) - { - return false; - } - - if (vertex.Leaving.Origin.id != vertex.id) - { - return false; - } - } - - // Check faces for null pointers. - foreach (var face in faces) - { - if (face.ID < 0) - { - continue; - } - - if (face.edge == null) - { - return false; - } - - if (face.id != face.edge.face.id) - { - return false; - } - } - - // Check half-edges for null pointers. - foreach (var edge in edges) - { - if (edge.id < 0) - { - continue; - } - - if (edge.twin == null) - { - return false; - } - - if (edge.origin == null) - { - return false; - } - - if (edge.face == null) - { - return false; - } - - if (closed && edge.next == null) - { - return false; - } - } - - // Check half-edges (topology). - foreach (var edge in edges) - { - if (edge.id < 0) - { - continue; - } - - var twin = edge.twin; - var next = edge.next; - - if (edge.id != twin.twin.id) - { - return false; - } - - if (closed) - { - if (next.origin.id != twin.origin.id) - { - return false; - } - - if (next.twin.next.origin.id != edge.twin.origin.id) - { - return false; - } - } - } - - if (closed && depth > 0) - { - // Check if faces are closed. - foreach (var face in faces) - { - if (face.id < 0) - { - continue; - } - - var edge = face.edge; - var next = edge.next; - - int id = edge.id; - int k = 0; - - while (next.id != id && k < depth) - { - next = next.next; - k++; - } - - if (next.id != id) - { - return false; - } - } - } - - return true; - } - - /// - /// Search for half-edge without twin and add a twin. Connect twins to form connected - /// boundary contours. - /// - /// - /// This method assumes that all faces are closed (i.e. no edge.next pointers are null). - /// - public void ResolveBoundaryEdges() - { - // Maps vertices to leaving boundary edge. - var map = new Dictionary(); - - // TODO: parallel? - foreach (var edge in this.edges) - { - if (edge.twin == null) - { - var twin = edge.twin = new HalfEdge(edge.next.origin, Face.Empty); - twin.twin = edge; - - map.Add(twin.origin.id, twin); - } - } - - int j = edges.Count; - - foreach (var edge in map.Values) - { - edge.id = j++; - edge.next = map[edge.twin.origin.id]; - - this.edges.Add(edge); - } - } - - /// - /// Enumerates all edges of the DCEL. - /// - /// - /// This method assumes that each half-edge has a twin (i.e. NOT null). - /// - protected virtual IEnumerable EnumerateEdges() - { - var edges = new List(this.edges.Count / 2); - - foreach (var edge in this.edges) - { - var twin = edge.twin; - - // Report edge only once. - if (edge.id < twin.id) - { - edges.Add(new Edge(edge.origin.id, twin.origin.id)); - } - } - - return edges; - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Topology.DCEL +{ + using System.Collections.Generic; + using TriangleNet.Geometry; + + public class DcelMesh + { + protected List vertices; + protected List edges; + protected List faces; + + /// + /// Initializes a new instance of the class. + /// + public DcelMesh() + : this(true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// If false, lists will not be initialized. + protected DcelMesh(bool initialize) + { + if (initialize) + { + vertices = new List(); + edges = new List(); + faces = new List(); + } + } + + /// + /// Gets the vertices of the Voronoi diagram. + /// + public List Vertices + { + get { return vertices; } + } + + /// + /// Gets the list of half-edges specify the Voronoi diagram topology. + /// + public List HalfEdges + { + get { return edges; } + } + + /// + /// Gets the faces of the Voronoi diagram. + /// + public List Faces + { + get { return faces; } + } + + /// + /// Gets the collection of edges of the Voronoi diagram. + /// + public IEnumerable Edges + { + get { return EnumerateEdges(); } + } + + /// + /// Check if the DCEL is consistent. + /// + /// If true, faces are assumed to be closed (i.e. all edges must have + /// a valid next pointer). + /// Maximum edge count of faces (default = 0 means skip check). + /// + public virtual bool IsConsistent(bool closed = true, int depth = 0) + { + // Check vertices for null pointers. + foreach (var vertex in vertices) + { + if (vertex.id < 0) + { + continue; + } + + if (vertex.leaving == null) + { + return false; + } + + if (vertex.Leaving.Origin.id != vertex.id) + { + return false; + } + } + + // Check faces for null pointers. + foreach (var face in faces) + { + if (face.ID < 0) + { + continue; + } + + if (face.edge == null) + { + return false; + } + + if (face.id != face.edge.face.id) + { + return false; + } + } + + // Check half-edges for null pointers. + foreach (var edge in edges) + { + if (edge.id < 0) + { + continue; + } + + if (edge.twin == null) + { + return false; + } + + if (edge.origin == null) + { + return false; + } + + if (edge.face == null) + { + return false; + } + + if (closed && edge.next == null) + { + return false; + } + } + + // Check half-edges (topology). + foreach (var edge in edges) + { + if (edge.id < 0) + { + continue; + } + + var twin = edge.twin; + var next = edge.next; + + if (edge.id != twin.twin.id) + { + return false; + } + + if (closed) + { + if (next.origin.id != twin.origin.id) + { + return false; + } + + if (next.twin.next.origin.id != edge.twin.origin.id) + { + return false; + } + } + } + + if (closed && depth > 0) + { + // Check if faces are closed. + foreach (var face in faces) + { + if (face.id < 0) + { + continue; + } + + var edge = face.edge; + var next = edge.next; + + int id = edge.id; + int k = 0; + + while (next.id != id && k < depth) + { + next = next.next; + k++; + } + + if (next.id != id) + { + return false; + } + } + } + + return true; + } + + /// + /// Search for half-edge without twin and add a twin. Connect twins to form connected + /// boundary contours. + /// + /// + /// This method assumes that all faces are closed (i.e. no edge.next pointers are null). + /// + public void ResolveBoundaryEdges() + { + // Maps vertices to leaving boundary edge. + var map = new Dictionary(); + + // TODO: parallel? + foreach (var edge in this.edges) + { + if (edge.twin == null) + { + var twin = edge.twin = new HalfEdge(edge.next.origin, Face.Empty); + twin.twin = edge; + + map.Add(twin.origin.id, twin); + } + } + + int j = edges.Count; + + foreach (var edge in map.Values) + { + edge.id = j++; + edge.next = map[edge.twin.origin.id]; + + this.edges.Add(edge); + } + } + + /// + /// Enumerates all edges of the DCEL. + /// + /// + /// This method assumes that each half-edge has a twin (i.e. NOT null). + /// + protected virtual IEnumerable EnumerateEdges() + { + var edges = new List(this.edges.Count / 2); + + foreach (var edge in this.edges) + { + var twin = edge.twin; + + // Report edge only once. + if (edge.id < twin.id) + { + edges.Add(new Edge(edge.origin.id, twin.origin.id)); + } + } + + return edges; + } + } +} diff --git a/Triangle.NET/Triangle/Topology/DCEL/Face.cs b/src/Triangle/Topology/DCEL/Face.cs similarity index 93% rename from Triangle.NET/Triangle/Topology/DCEL/Face.cs rename to src/Triangle/Topology/DCEL/Face.cs index 7073920..df6c87a 100644 --- a/Triangle.NET/Triangle/Topology/DCEL/Face.cs +++ b/src/Triangle/Topology/DCEL/Face.cs @@ -1,112 +1,113 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Topology.DCEL -{ - using System.Collections.Generic; - using TriangleNet.Geometry; - - /// - /// A face of DCEL mesh. - /// - public class Face - { - #region Static initialization of "Outer Space" face - - public static readonly Face Empty; - - static Face() - { - Empty = new Face(null); - Empty.id = -1; - } - - #endregion - - internal int id; - internal int mark; - - internal Point generator; - - internal HalfEdge edge; - internal bool bounded; - - /// - /// Gets or sets the face id. - /// - public int ID - { - get { return id; } - set { id = value; } - } - - /// - /// Gets or sets a half-edge connected to the face. - /// - public HalfEdge Edge - { - get { return edge; } - set { edge = value; } - } - - /// - /// Gets or sets a value, indicating if the face is bounded (for Voronoi diagram). - /// - public bool Bounded - { - get { return bounded; } - set { bounded = value; } - } - - /// - /// Initializes a new instance of the class. - /// - /// The generator of this face (for Voronoi diagram) - public Face(Point generator) - : this(generator, null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The generator of this face (for Voronoi diagram) - /// The half-edge connected to this face. - public Face(Point generator, HalfEdge edge) - { - this.generator = generator; - this.edge = edge; - this.bounded = true; - - if (generator != null) - { - this.id = generator.ID; - } - } - - /// - /// Enumerates all half-edges of the face boundary. - /// - /// - public IEnumerable EnumerateEdges() - { - var edge = this.Edge; - int first = edge.ID; - - do - { - yield return edge; - - edge = edge.Next; - } while (edge.ID != first); - } - - public override string ToString() - { - return string.Format("F-ID {0}", id); - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Topology.DCEL +{ + using System.Collections.Generic; + using TriangleNet.Geometry; + + /// + /// A face of DCEL mesh. + /// + public class Face + { + #region Static initialization of "Outer Space" face + + public static readonly Face Empty; + + static Face() + { + Empty = new Face(null); + Empty.id = -1; + } + + #endregion + + internal int id; + internal int mark; + + // If the face is a Voronoi cell, this is the point that generates the cell. + internal Point generator; + + internal HalfEdge edge; + internal bool bounded; + + /// + /// Gets or sets the face id. + /// + public int ID + { + get { return id; } + set { id = value; } + } + + /// + /// Gets or sets a half-edge connected to the face. + /// + public HalfEdge Edge + { + get { return edge; } + set { edge = value; } + } + + /// + /// Gets or sets a value, indicating if the face is bounded (for Voronoi diagram). + /// + public bool Bounded + { + get { return bounded; } + set { bounded = value; } + } + + /// + /// Initializes a new instance of the class. + /// + /// The generator of this face (for Voronoi diagram) + public Face(Point generator) + : this(generator, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The generator of this face (for Voronoi diagram) + /// The half-edge connected to this face. + public Face(Point generator, HalfEdge edge) + { + this.generator = generator; + this.edge = edge; + this.bounded = true; + + if (generator != null) + { + this.id = generator.ID; + } + } + + /// + /// Enumerates all half-edges of the face boundary. + /// + /// + public IEnumerable EnumerateEdges() + { + var edge = this.Edge; + int first = edge.ID; + + do + { + yield return edge; + + edge = edge.Next; + } while (edge.ID != first); + } + + public override string ToString() + { + return string.Format("F-ID {0}", id); + } + } +} diff --git a/Triangle.NET/Triangle/Topology/DCEL/HalfEdge.cs b/src/Triangle/Topology/DCEL/HalfEdge.cs similarity index 93% rename from Triangle.NET/Triangle/Topology/DCEL/HalfEdge.cs rename to src/Triangle/Topology/DCEL/HalfEdge.cs index bff3dcc..e77863c 100644 --- a/Triangle.NET/Triangle/Topology/DCEL/HalfEdge.cs +++ b/src/Triangle/Topology/DCEL/HalfEdge.cs @@ -1,101 +1,101 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Topology.DCEL -{ - public class HalfEdge - { - internal int id; - internal int mark; - - internal Vertex origin; - internal Face face; - internal HalfEdge twin; - internal HalfEdge next; - - /// - /// Gets or sets the half-edge id. - /// - public int ID - { - get { return id; } - set { id = value; } - } - - public int Boundary - { - get { return mark; } - set { mark = value; } - } - - /// - /// Gets or sets the origin of the half-edge. - /// - public Vertex Origin - { - get { return origin; } - set { origin = value; } - } - - /// - /// Gets or sets the face connected to the half-edge. - /// - public Face Face - { - get { return face; } - set { face = value; } - } - - /// - /// Gets or sets the twin of the half-edge. - /// - public HalfEdge Twin - { - get { return twin; } - set { twin = value; } - } - - /// - /// Gets or sets the next pointer of the half-edge. - /// - public HalfEdge Next - { - get { return next; } - set { next = value; } - } - - /// - /// Initializes a new instance of the class. - /// - /// The origin of this half-edge. - public HalfEdge(Vertex origin) - { - this.origin = origin; - } - - /// - /// Initializes a new instance of the class. - /// - /// The origin of this half-edge. - /// The face connected to this half-edge. - public HalfEdge(Vertex origin, Face face) - { - this.origin = origin; - this.face = face; - - // IMPORTANT: do not remove the (face.edge == null) check! - if (face != null && face.edge == null) - { - face.edge = this; - } - } - - public override string ToString() - { - return string.Format("HE-ID {0} (Origin = VID-{1})", id, origin.id); - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Topology.DCEL +{ + public class HalfEdge + { + internal int id; + internal int mark; + + internal Vertex origin; + internal Face face; + internal HalfEdge twin; + internal HalfEdge next; + + /// + /// Gets or sets the half-edge id. + /// + public int ID + { + get { return id; } + set { id = value; } + } + + public int Boundary + { + get { return mark; } + set { mark = value; } + } + + /// + /// Gets or sets the origin of the half-edge. + /// + public Vertex Origin + { + get { return origin; } + set { origin = value; } + } + + /// + /// Gets or sets the face connected to the half-edge. + /// + public Face Face + { + get { return face; } + set { face = value; } + } + + /// + /// Gets or sets the twin of the half-edge. + /// + public HalfEdge Twin + { + get { return twin; } + set { twin = value; } + } + + /// + /// Gets or sets the next pointer of the half-edge. + /// + public HalfEdge Next + { + get { return next; } + set { next = value; } + } + + /// + /// Initializes a new instance of the class. + /// + /// The origin of this half-edge. + public HalfEdge(Vertex origin) + { + this.origin = origin; + } + + /// + /// Initializes a new instance of the class. + /// + /// The origin of this half-edge. + /// The face connected to this half-edge. + public HalfEdge(Vertex origin, Face face) + { + this.origin = origin; + this.face = face; + + // IMPORTANT: do not remove the (face.edge == null) check! + if (face != null && face.edge == null) + { + face.edge = this; + } + } + + public override string ToString() + { + return string.Format("HE-ID {0} (Origin = VID-{1})", id, origin.id); + } + } +} diff --git a/Triangle.NET/Triangle/Topology/DCEL/Vertex.cs b/src/Triangle/Topology/DCEL/Vertex.cs similarity index 93% rename from Triangle.NET/Triangle/Topology/DCEL/Vertex.cs rename to src/Triangle/Topology/DCEL/Vertex.cs index 2929e09..f66b628 100644 --- a/Triangle.NET/Triangle/Topology/DCEL/Vertex.cs +++ b/src/Triangle/Topology/DCEL/Vertex.cs @@ -1,68 +1,68 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Topology.DCEL -{ - using System.Collections.Generic; - - public class Vertex : TriangleNet.Geometry.Point - { - internal HalfEdge leaving; - - /// - /// Gets or sets a half-edge leaving the vertex. - /// - public HalfEdge Leaving - { - get { return leaving; } - set { leaving = value; } - } - - /// - /// Initializes a new instance of the class. - /// - /// The x coordinate. - /// The y coordinate. - public Vertex(double x, double y) - : base(x, y) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The x coordinate. - /// The y coordinate. - /// A half-edge leaving this vertex. - public Vertex(double x, double y, HalfEdge leaving) - : base(x, y) - { - this.leaving = leaving; - } - - /// - /// Enumerates all half-edges leaving this vertex. - /// - /// - public IEnumerable EnumerateEdges() - { - var edge = this.Leaving; - int first = edge.ID; - - do - { - yield return edge; - - edge = edge.Twin.Next; - } while (edge.ID != first); - } - - public override string ToString() - { - return string.Format("V-ID {0}", base.id); - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Topology.DCEL +{ + using System.Collections.Generic; + + public class Vertex : TriangleNet.Geometry.Point + { + internal HalfEdge leaving; + + /// + /// Gets or sets a half-edge leaving the vertex. + /// + public HalfEdge Leaving + { + get { return leaving; } + set { leaving = value; } + } + + /// + /// Initializes a new instance of the class. + /// + /// The x coordinate. + /// The y coordinate. + public Vertex(double x, double y) + : base(x, y) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The x coordinate. + /// The y coordinate. + /// A half-edge leaving this vertex. + public Vertex(double x, double y, HalfEdge leaving) + : base(x, y) + { + this.leaving = leaving; + } + + /// + /// Enumerates all half-edges leaving this vertex. + /// + /// + public IEnumerable EnumerateEdges() + { + var edge = this.Leaving; + int first = edge.ID; + + do + { + yield return edge; + + edge = edge.Twin.Next; + } while (edge.ID != first); + } + + public override string ToString() + { + return string.Format("V-ID {0}", base.id); + } + } +} diff --git a/Triangle.NET/Triangle/Topology/Osub.cs b/src/Triangle/Topology/Osub.cs similarity index 93% rename from Triangle.NET/Triangle/Topology/Osub.cs rename to src/Triangle/Topology/Osub.cs index f60281b..d3a2aef 100644 --- a/Triangle.NET/Triangle/Topology/Osub.cs +++ b/src/Triangle/Topology/Osub.cs @@ -1,256 +1,256 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Topology -{ - using System; - using TriangleNet.Geometry; - - /// - /// An oriented subsegment. - /// - /// - /// Includes a pointer to a subsegment and an orientation. The orientation denotes a - /// side of the edge. Hence, there are two possible orientations. By convention, the - /// edge is always directed so that the "side" denoted is the right side of the edge. - /// - public struct Osub - { - internal SubSegment seg; - internal int orient; // Ranges from 0 to 1. - - public SubSegment Segment - { - get { return seg; } - } - - public override string ToString() - { - if (seg == null) - { - return "O-TID [null]"; - } - return String.Format("O-SID {0}", seg.hash); - } - - #region Osub primitives - - /// - /// Reverse the orientation of a subsegment. [sym(ab) -> ba] - /// - public void Sym(ref Osub os) - { - os.seg = seg; - os.orient = 1 - orient; - } - - /// - /// Reverse the orientation of a subsegment. [sym(ab) -> ba] - /// - public void Sym() - { - orient = 1 - orient; - } - - /// - /// Find adjoining subsegment with the same origin. [pivot(ab) -> a*] - /// - /// spivot() finds the other subsegment (from the same segment) - /// that shares the same origin. - /// - public void Pivot(ref Osub os) - { - os = seg.subsegs[orient]; - } - - /// - /// Finds a triangle abutting a subsegment. - /// - internal void Pivot(ref Otri ot) - { - ot = seg.triangles[orient]; - } - - /// - /// Find next subsegment in sequence. [next(ab) -> b*] - /// - public void Next(ref Osub ot) - { - ot = seg.subsegs[1 - orient]; - } - - /// - /// Find next subsegment in sequence. [next(ab) -> b*] - /// - public void Next() - { - this = seg.subsegs[1 - orient]; - } - - /// - /// Get the origin of a subsegment - /// - public Vertex Org() - { - return seg.vertices[orient]; - } - - /// - /// Get the destination of a subsegment - /// - public Vertex Dest() - { - return seg.vertices[1 - orient]; - } - - - #endregion - - #region Osub primitives (internal) - - /// - /// Set the origin or destination of a subsegment. - /// - internal void SetOrg(Vertex vertex) - { - seg.vertices[orient] = vertex; - } - - /// - /// Set destination of a subsegment. - /// - internal void SetDest(Vertex vertex) - { - seg.vertices[1 - orient] = vertex; - } - - /// - /// Get the origin of the segment that includes the subsegment. - /// - internal Vertex SegOrg() - { - return seg.vertices[2 + orient]; - } - - /// - /// Get the destination of the segment that includes the subsegment. - /// - internal Vertex SegDest() - { - return seg.vertices[3 - orient]; - } - - /// - /// Set the origin of the segment that includes the subsegment. - /// - internal void SetSegOrg(Vertex vertex) - { - seg.vertices[2 + orient] = vertex; - } - - /// - /// Set the destination of the segment that includes the subsegment. - /// - internal void SetSegDest(Vertex vertex) - { - seg.vertices[3 - orient] = vertex; - } - - /* Unused primitives. - - /// - /// Find adjoining subsegment with the same origin. [pivot(ab) -> a*] - /// - public void PivotSelf() - { - this = seg.subsegs[orient]; - } - - /// - /// Read a boundary marker. - /// - /// Boundary markers are used to hold user-defined tags for - /// setting boundary conditions in finite element solvers. - public int Mark() - { - return seg.boundary; - } - - /// - /// Set a boundary marker. - /// - public void SetMark(int value) - { - seg.boundary = value; - } - - /// - /// Copy a subsegment. - /// - public void Copy(ref Osub o2) - { - o2.seg = seg; - o2.orient = orient; - } - - //*/ - - /// - /// Bond two subsegments together. [bond(abc, ba)] - /// - internal void Bond(ref Osub os) - { - seg.subsegs[orient] = os; - os.seg.subsegs[os.orient] = this; - } - - /// - /// Dissolve a subsegment bond (from one side). - /// - /// Note that the other subsegment will still think it's - /// connected to this subsegment. - internal void Dissolve(SubSegment dummy) - { - seg.subsegs[orient].seg = dummy; - } - - /// - /// Test for equality of subsegments. - /// - internal bool Equal(Osub os) - { - return ((seg == os.seg) && (orient == os.orient)); - } - - /// - /// Dissolve a bond (from the subsegment side). - /// - internal void TriDissolve(Triangle dummy) - { - seg.triangles[orient].tri = dummy; - } - - /// - /// Check a subsegment's deallocation. - /// - internal static bool IsDead(SubSegment sub) - { - return sub.subsegs[0].seg == null; - } - - /// - /// Set a subsegment's deallocation. - /// - internal static void Kill(SubSegment sub) - { - sub.subsegs[0].seg = null; - sub.subsegs[1].seg = null; - } - - #endregion - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Topology +{ + using System; + using TriangleNet.Geometry; + + /// + /// An oriented subsegment. + /// + /// + /// Includes a pointer to a subsegment and an orientation. The orientation denotes a + /// side of the edge. Hence, there are two possible orientations. By convention, the + /// edge is always directed so that the "side" denoted is the right side of the edge. + /// + public struct Osub + { + internal SubSegment seg; + internal int orient; // Ranges from 0 to 1. + + public SubSegment Segment + { + get { return seg; } + } + + public override string ToString() + { + if (seg == null) + { + return "O-TID [null]"; + } + return String.Format("O-SID {0}", seg.hash); + } + + #region Osub primitives + + /// + /// Reverse the orientation of a subsegment. [sym(ab) -> ba] + /// + public void Sym(ref Osub os) + { + os.seg = seg; + os.orient = 1 - orient; + } + + /// + /// Reverse the orientation of a subsegment. [sym(ab) -> ba] + /// + public void Sym() + { + orient = 1 - orient; + } + + /// + /// Find adjoining subsegment with the same origin. [pivot(ab) -> a*] + /// + /// spivot() finds the other subsegment (from the same segment) + /// that shares the same origin. + /// + public void Pivot(ref Osub os) + { + os = seg.subsegs[orient]; + } + + /// + /// Finds a triangle abutting a subsegment. + /// + internal void Pivot(ref Otri ot) + { + ot = seg.triangles[orient]; + } + + /// + /// Find next subsegment in sequence. [next(ab) -> b*] + /// + public void Next(ref Osub ot) + { + ot = seg.subsegs[1 - orient]; + } + + /// + /// Find next subsegment in sequence. [next(ab) -> b*] + /// + public void Next() + { + this = seg.subsegs[1 - orient]; + } + + /// + /// Get the origin of a subsegment + /// + public Vertex Org() + { + return seg.vertices[orient]; + } + + /// + /// Get the destination of a subsegment + /// + public Vertex Dest() + { + return seg.vertices[1 - orient]; + } + + + #endregion + + #region Osub primitives (internal) + + /// + /// Set the origin or destination of a subsegment. + /// + internal void SetOrg(Vertex vertex) + { + seg.vertices[orient] = vertex; + } + + /// + /// Set destination of a subsegment. + /// + internal void SetDest(Vertex vertex) + { + seg.vertices[1 - orient] = vertex; + } + + /// + /// Get the origin of the segment that includes the subsegment. + /// + internal Vertex SegOrg() + { + return seg.vertices[2 + orient]; + } + + /// + /// Get the destination of the segment that includes the subsegment. + /// + internal Vertex SegDest() + { + return seg.vertices[3 - orient]; + } + + /// + /// Set the origin of the segment that includes the subsegment. + /// + internal void SetSegOrg(Vertex vertex) + { + seg.vertices[2 + orient] = vertex; + } + + /// + /// Set the destination of the segment that includes the subsegment. + /// + internal void SetSegDest(Vertex vertex) + { + seg.vertices[3 - orient] = vertex; + } + + /* Unused primitives. + + /// + /// Find adjoining subsegment with the same origin. [pivot(ab) -> a*] + /// + public void PivotSelf() + { + this = seg.subsegs[orient]; + } + + /// + /// Read a boundary marker. + /// + /// Boundary markers are used to hold user-defined tags for + /// setting boundary conditions in finite element solvers. + public int Mark() + { + return seg.boundary; + } + + /// + /// Set a boundary marker. + /// + public void SetMark(int value) + { + seg.boundary = value; + } + + /// + /// Copy a subsegment. + /// + public void Copy(ref Osub o2) + { + o2.seg = seg; + o2.orient = orient; + } + + //*/ + + /// + /// Bond two subsegments together. [bond(abc, ba)] + /// + internal void Bond(ref Osub os) + { + seg.subsegs[orient] = os; + os.seg.subsegs[os.orient] = this; + } + + /// + /// Dissolve a subsegment bond (from one side). + /// + /// Note that the other subsegment will still think it's + /// connected to this subsegment. + internal void Dissolve(SubSegment dummy) + { + seg.subsegs[orient].seg = dummy; + } + + /// + /// Test for equality of subsegments. + /// + internal bool Equal(Osub os) + { + return ((seg == os.seg) && (orient == os.orient)); + } + + /// + /// Dissolve a bond (from the subsegment side). + /// + internal void TriDissolve(Triangle dummy) + { + seg.triangles[orient].tri = dummy; + } + + /// + /// Check a subsegment's deallocation. + /// + internal static bool IsDead(SubSegment sub) + { + return sub.subsegs[0].seg == null; + } + + /// + /// Set a subsegment's deallocation. + /// + internal static void Kill(SubSegment sub) + { + sub.subsegs[0].seg = null; + sub.subsegs[1].seg = null; + } + + #endregion + } +} diff --git a/Triangle.NET/Triangle/Topology/Otri.cs b/src/Triangle/Topology/Otri.cs similarity index 95% rename from Triangle.NET/Triangle/Topology/Otri.cs rename to src/Triangle/Topology/Otri.cs index ba9efee..d3feeab 100644 --- a/Triangle.NET/Triangle/Topology/Otri.cs +++ b/src/Triangle/Topology/Otri.cs @@ -1,481 +1,481 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Topology -{ - using System; - using TriangleNet.Geometry; - - /// - /// An oriented triangle. - /// - /// - /// Includes a pointer to a triangle and orientation. The orientation denotes an edge - /// of the triangle. Hence, there are three possible orientations. By convention, each - /// edge always points counterclockwise about the corresponding triangle. - /// - public struct Otri - { - internal Triangle tri; - internal int orient; // Ranges from 0 to 2. - - public Triangle Triangle - { - get { return tri; } - set { tri = value; } - } - - public override string ToString() - { - if (tri == null) - { - return "O-TID [null]"; - } - return String.Format("O-TID {0}", tri.hash); - } - - #region Otri primitives (public) - - // For fast access - static readonly int[] plus1Mod3 = { 1, 2, 0 }; - static readonly int[] minus1Mod3 = { 2, 0, 1 }; - - // The following primitives are all described by Guibas and Stolfi. - // However, Guibas and Stolfi use an edge-based data structure, - // whereas I use a triangle-based data structure. - // - // lnext: finds the next edge (counterclockwise) of a triangle. - // - // onext: spins counterclockwise around a vertex; that is, it finds - // the next edge with the same origin in the counterclockwise direction. This - // edge is part of a different triangle. - // - // oprev: spins clockwise around a vertex; that is, it finds the - // next edge with the same origin in the clockwise direction. This edge is - // part of a different triangle. - // - // dnext: spins counterclockwise around a vertex; that is, it finds - // the next edge with the same destination in the counterclockwise direction. - // This edge is part of a different triangle. - // - // dprev: spins clockwise around a vertex; that is, it finds the - // next edge with the same destination in the clockwise direction. This edge - // is part of a different triangle. - // - // rnext: moves one edge counterclockwise about the adjacent - // triangle. (It's best understood by reading Guibas and Stolfi. It - // involves changing triangles twice.) - // - // rprev: moves one edge clockwise about the adjacent triangle. - // (It's best understood by reading Guibas and Stolfi. It involves - // changing triangles twice.) - - /// - /// Find the abutting triangle; same edge. [sym(abc) -> ba*] - /// - /// Note that the edge direction is necessarily reversed, because the handle specified - /// by an oriented triangle is directed counterclockwise around the triangle. - /// - public void Sym(ref Otri ot) - { - ot.tri = tri.neighbors[orient].tri; - ot.orient = tri.neighbors[orient].orient; - } - - /// - /// Find the abutting triangle; same edge. [sym(abc) -> ba*] - /// - public void Sym() - { - int tmp = orient; - orient = tri.neighbors[tmp].orient; - tri = tri.neighbors[tmp].tri; - } - - /// - /// Find the next edge (counterclockwise) of a triangle. [lnext(abc) -> bca] - /// - public void Lnext(ref Otri ot) - { - ot.tri = tri; - ot.orient = plus1Mod3[orient]; - } - - /// - /// Find the next edge (counterclockwise) of a triangle. [lnext(abc) -> bca] - /// - public void Lnext() - { - orient = plus1Mod3[orient]; - } - - /// - /// Find the previous edge (clockwise) of a triangle. [lprev(abc) -> cab] - /// - public void Lprev(ref Otri ot) - { - ot.tri = tri; - ot.orient = minus1Mod3[orient]; - } - - /// - /// Find the previous edge (clockwise) of a triangle. [lprev(abc) -> cab] - /// - public void Lprev() - { - orient = minus1Mod3[orient]; - } - - /// - /// Find the next edge counterclockwise with the same origin. [onext(abc) -> ac*] - /// - public void Onext(ref Otri ot) - { - //Lprev(ref ot); - ot.tri = tri; - ot.orient = minus1Mod3[orient]; - - //ot.SymSelf(); - int tmp = ot.orient; - ot.orient = ot.tri.neighbors[tmp].orient; - ot.tri = ot.tri.neighbors[tmp].tri; - } - - /// - /// Find the next edge counterclockwise with the same origin. [onext(abc) -> ac*] - /// - public void Onext() - { - //LprevSelf(); - orient = minus1Mod3[orient]; - - //SymSelf(); - int tmp = orient; - orient = tri.neighbors[tmp].orient; - tri = tri.neighbors[tmp].tri; - } - - /// - /// Find the next edge clockwise with the same origin. [oprev(abc) -> a*b] - /// - public void Oprev(ref Otri ot) - { - //Sym(ref ot); - ot.tri = tri.neighbors[orient].tri; - ot.orient = tri.neighbors[orient].orient; - - //ot.LnextSelf(); - ot.orient = plus1Mod3[ot.orient]; - } - - /// - /// Find the next edge clockwise with the same origin. [oprev(abc) -> a*b] - /// - public void Oprev() - { - //SymSelf(); - int tmp = orient; - orient = tri.neighbors[tmp].orient; - tri = tri.neighbors[tmp].tri; - - //LnextSelf(); - orient = plus1Mod3[orient]; - } - - /// - /// Find the next edge counterclockwise with the same destination. [dnext(abc) -> *ba] - /// - public void Dnext(ref Otri ot) - { - //Sym(ref ot); - ot.tri = tri.neighbors[orient].tri; - ot.orient = tri.neighbors[orient].orient; - - //ot.LprevSelf(); - ot.orient = minus1Mod3[ot.orient]; - } - - /// - /// Find the next edge counterclockwise with the same destination. [dnext(abc) -> *ba] - /// - public void Dnext() - { - //SymSelf(); - int tmp = orient; - orient = tri.neighbors[tmp].orient; - tri = tri.neighbors[tmp].tri; - - //LprevSelf(); - orient = minus1Mod3[orient]; - } - - /// - /// Find the next edge clockwise with the same destination. [dprev(abc) -> cb*] - /// - public void Dprev(ref Otri ot) - { - //Lnext(ref ot); - ot.tri = tri; - ot.orient = plus1Mod3[orient]; - - //ot.SymSelf(); - int tmp = ot.orient; - ot.orient = ot.tri.neighbors[tmp].orient; - ot.tri = ot.tri.neighbors[tmp].tri; - } - - /// - /// Find the next edge clockwise with the same destination. [dprev(abc) -> cb*] - /// - public void Dprev() - { - //LnextSelf(); - orient = plus1Mod3[orient]; - - //SymSelf(); - int tmp = orient; - orient = tri.neighbors[tmp].orient; - tri = tri.neighbors[tmp].tri; - } - - /// - /// Find the next edge (counterclockwise) of the adjacent triangle. [rnext(abc) -> *a*] - /// - public void Rnext(ref Otri ot) - { - //Sym(ref ot); - ot.tri = tri.neighbors[orient].tri; - ot.orient = tri.neighbors[orient].orient; - - //ot.LnextSelf(); - ot.orient = plus1Mod3[ot.orient]; - - //ot.SymSelf(); - int tmp = ot.orient; - ot.orient = ot.tri.neighbors[tmp].orient; - ot.tri = ot.tri.neighbors[tmp].tri; - } - - /// - /// Find the next edge (counterclockwise) of the adjacent triangle. [rnext(abc) -> *a*] - /// - public void Rnext() - { - //SymSelf(); - int tmp = orient; - orient = tri.neighbors[tmp].orient; - tri = tri.neighbors[tmp].tri; - - //LnextSelf(); - orient = plus1Mod3[orient]; - - //SymSelf(); - tmp = orient; - orient = tri.neighbors[tmp].orient; - tri = tri.neighbors[tmp].tri; - } - - /// - /// Find the previous edge (clockwise) of the adjacent triangle. [rprev(abc) -> b**] - /// - public void Rprev(ref Otri ot) - { - //Sym(ref ot); - ot.tri = tri.neighbors[orient].tri; - ot.orient = tri.neighbors[orient].orient; - - //ot.LprevSelf(); - ot.orient = minus1Mod3[ot.orient]; - - //ot.SymSelf(); - int tmp = ot.orient; - ot.orient = ot.tri.neighbors[tmp].orient; - ot.tri = ot.tri.neighbors[tmp].tri; - } - - /// - /// Find the previous edge (clockwise) of the adjacent triangle. [rprev(abc) -> b**] - /// - public void Rprev() - { - //SymSelf(); - int tmp = orient; - orient = tri.neighbors[tmp].orient; - tri = tri.neighbors[tmp].tri; - - //LprevSelf(); - orient = minus1Mod3[orient]; - - //SymSelf(); - tmp = orient; - orient = tri.neighbors[tmp].orient; - tri = tri.neighbors[tmp].tri; - } - - /// - /// Origin [org(abc) -> a] - /// - public Vertex Org() - { - return tri.vertices[plus1Mod3[orient]]; - } - - /// - /// Destination [dest(abc) -> b] - /// - public Vertex Dest() - { - return tri.vertices[minus1Mod3[orient]]; - } - - /// - /// Apex [apex(abc) -> c] - /// - public Vertex Apex() - { - return tri.vertices[orient]; - } - - /// - /// Copy an oriented triangle. - /// - public void Copy(ref Otri ot) - { - ot.tri = tri; - ot.orient = orient; - } - - /// - /// Test for equality of oriented triangles. - /// - public bool Equals(Otri ot) - { - return ((tri == ot.tri) && (orient == ot.orient)); - } - - #endregion - - #region Otri primitives (internal) - - /// - /// Set Origin - /// - internal void SetOrg(Vertex v) - { - tri.vertices[plus1Mod3[orient]] = v; - } - - /// - /// Set Destination - /// - internal void SetDest(Vertex v) - { - tri.vertices[minus1Mod3[orient]] = v; - } - - /// - /// Set Apex - /// - internal void SetApex(Vertex v) - { - tri.vertices[orient] = v; - } - - /// - /// Bond two triangles together at the resepective handles. [bond(abc, bad)] - /// - internal void Bond(ref Otri ot) - { - tri.neighbors[orient].tri = ot.tri; - tri.neighbors[orient].orient = ot.orient; - - ot.tri.neighbors[ot.orient].tri = this.tri; - ot.tri.neighbors[ot.orient].orient = this.orient; - } - - /// - /// Dissolve a bond (from one side). - /// - /// Note that the other triangle will still think it's connected to - /// this triangle. Usually, however, the other triangle is being deleted - /// entirely, or bonded to another triangle, so it doesn't matter. - /// - internal void Dissolve(Triangle dummy) - { - tri.neighbors[orient].tri = dummy; - tri.neighbors[orient].orient = 0; - } - - /// - /// Infect a triangle with the virus. - /// - internal void Infect() - { - tri.infected = true; - } - - /// - /// Cure a triangle from the virus. - /// - internal void Uninfect() - { - tri.infected = false; - } - - /// - /// Test a triangle for viral infection. - /// - internal bool IsInfected() - { - return tri.infected; - } - - /// - /// Finds a subsegment abutting a triangle. - /// - internal void Pivot(ref Osub os) - { - os = tri.subsegs[orient]; - } - - /// - /// Bond a triangle to a subsegment. - /// - internal void SegBond(ref Osub os) - { - tri.subsegs[orient] = os; - os.seg.triangles[os.orient] = this; - } - - /// - /// Dissolve a bond (from the triangle side). - /// - internal void SegDissolve(SubSegment dummy) - { - tri.subsegs[orient].seg = dummy; - } - - /// - /// Check a triangle's deallocation. - /// - internal static bool IsDead(Triangle tria) - { - return tria.neighbors[0].tri == null; - } - - /// - /// Set a triangle's deallocation. - /// - internal static void Kill(Triangle tri) - { - tri.neighbors[0].tri = null; - tri.neighbors[2].tri = null; - } - - #endregion - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Topology +{ + using System; + using TriangleNet.Geometry; + + /// + /// An oriented triangle. + /// + /// + /// Includes a pointer to a triangle and orientation. The orientation denotes an edge + /// of the triangle. Hence, there are three possible orientations. By convention, each + /// edge always points counterclockwise about the corresponding triangle. + /// + public struct Otri + { + internal Triangle tri; + internal int orient; // Ranges from 0 to 2. + + public Triangle Triangle + { + get { return tri; } + set { tri = value; } + } + + public override string ToString() + { + if (tri == null) + { + return "O-TID [null]"; + } + return String.Format("O-TID {0}", tri.hash); + } + + #region Otri primitives (public) + + // For fast access + static readonly int[] plus1Mod3 = { 1, 2, 0 }; + static readonly int[] minus1Mod3 = { 2, 0, 1 }; + + // The following primitives are all described by Guibas and Stolfi. + // However, Guibas and Stolfi use an edge-based data structure, + // whereas I use a triangle-based data structure. + // + // lnext: finds the next edge (counterclockwise) of a triangle. + // + // onext: spins counterclockwise around a vertex; that is, it finds + // the next edge with the same origin in the counterclockwise direction. This + // edge is part of a different triangle. + // + // oprev: spins clockwise around a vertex; that is, it finds the + // next edge with the same origin in the clockwise direction. This edge is + // part of a different triangle. + // + // dnext: spins counterclockwise around a vertex; that is, it finds + // the next edge with the same destination in the counterclockwise direction. + // This edge is part of a different triangle. + // + // dprev: spins clockwise around a vertex; that is, it finds the + // next edge with the same destination in the clockwise direction. This edge + // is part of a different triangle. + // + // rnext: moves one edge counterclockwise about the adjacent + // triangle. (It's best understood by reading Guibas and Stolfi. It + // involves changing triangles twice.) + // + // rprev: moves one edge clockwise about the adjacent triangle. + // (It's best understood by reading Guibas and Stolfi. It involves + // changing triangles twice.) + + /// + /// Find the abutting triangle; same edge. [sym(abc) -> ba*] + /// + /// Note that the edge direction is necessarily reversed, because the handle specified + /// by an oriented triangle is directed counterclockwise around the triangle. + /// + public void Sym(ref Otri ot) + { + ot.tri = tri.neighbors[orient].tri; + ot.orient = tri.neighbors[orient].orient; + } + + /// + /// Find the abutting triangle; same edge. [sym(abc) -> ba*] + /// + public void Sym() + { + int tmp = orient; + orient = tri.neighbors[tmp].orient; + tri = tri.neighbors[tmp].tri; + } + + /// + /// Find the next edge (counterclockwise) of a triangle. [lnext(abc) -> bca] + /// + public void Lnext(ref Otri ot) + { + ot.tri = tri; + ot.orient = plus1Mod3[orient]; + } + + /// + /// Find the next edge (counterclockwise) of a triangle. [lnext(abc) -> bca] + /// + public void Lnext() + { + orient = plus1Mod3[orient]; + } + + /// + /// Find the previous edge (clockwise) of a triangle. [lprev(abc) -> cab] + /// + public void Lprev(ref Otri ot) + { + ot.tri = tri; + ot.orient = minus1Mod3[orient]; + } + + /// + /// Find the previous edge (clockwise) of a triangle. [lprev(abc) -> cab] + /// + public void Lprev() + { + orient = minus1Mod3[orient]; + } + + /// + /// Find the next edge counterclockwise with the same origin. [onext(abc) -> ac*] + /// + public void Onext(ref Otri ot) + { + //Lprev(ref ot); + ot.tri = tri; + ot.orient = minus1Mod3[orient]; + + //ot.SymSelf(); + int tmp = ot.orient; + ot.orient = ot.tri.neighbors[tmp].orient; + ot.tri = ot.tri.neighbors[tmp].tri; + } + + /// + /// Find the next edge counterclockwise with the same origin. [onext(abc) -> ac*] + /// + public void Onext() + { + //LprevSelf(); + orient = minus1Mod3[orient]; + + //SymSelf(); + int tmp = orient; + orient = tri.neighbors[tmp].orient; + tri = tri.neighbors[tmp].tri; + } + + /// + /// Find the next edge clockwise with the same origin. [oprev(abc) -> a*b] + /// + public void Oprev(ref Otri ot) + { + //Sym(ref ot); + ot.tri = tri.neighbors[orient].tri; + ot.orient = tri.neighbors[orient].orient; + + //ot.LnextSelf(); + ot.orient = plus1Mod3[ot.orient]; + } + + /// + /// Find the next edge clockwise with the same origin. [oprev(abc) -> a*b] + /// + public void Oprev() + { + //SymSelf(); + int tmp = orient; + orient = tri.neighbors[tmp].orient; + tri = tri.neighbors[tmp].tri; + + //LnextSelf(); + orient = plus1Mod3[orient]; + } + + /// + /// Find the next edge counterclockwise with the same destination. [dnext(abc) -> *ba] + /// + public void Dnext(ref Otri ot) + { + //Sym(ref ot); + ot.tri = tri.neighbors[orient].tri; + ot.orient = tri.neighbors[orient].orient; + + //ot.LprevSelf(); + ot.orient = minus1Mod3[ot.orient]; + } + + /// + /// Find the next edge counterclockwise with the same destination. [dnext(abc) -> *ba] + /// + public void Dnext() + { + //SymSelf(); + int tmp = orient; + orient = tri.neighbors[tmp].orient; + tri = tri.neighbors[tmp].tri; + + //LprevSelf(); + orient = minus1Mod3[orient]; + } + + /// + /// Find the next edge clockwise with the same destination. [dprev(abc) -> cb*] + /// + public void Dprev(ref Otri ot) + { + //Lnext(ref ot); + ot.tri = tri; + ot.orient = plus1Mod3[orient]; + + //ot.SymSelf(); + int tmp = ot.orient; + ot.orient = ot.tri.neighbors[tmp].orient; + ot.tri = ot.tri.neighbors[tmp].tri; + } + + /// + /// Find the next edge clockwise with the same destination. [dprev(abc) -> cb*] + /// + public void Dprev() + { + //LnextSelf(); + orient = plus1Mod3[orient]; + + //SymSelf(); + int tmp = orient; + orient = tri.neighbors[tmp].orient; + tri = tri.neighbors[tmp].tri; + } + + /// + /// Find the next edge (counterclockwise) of the adjacent triangle. [rnext(abc) -> *a*] + /// + public void Rnext(ref Otri ot) + { + //Sym(ref ot); + ot.tri = tri.neighbors[orient].tri; + ot.orient = tri.neighbors[orient].orient; + + //ot.LnextSelf(); + ot.orient = plus1Mod3[ot.orient]; + + //ot.SymSelf(); + int tmp = ot.orient; + ot.orient = ot.tri.neighbors[tmp].orient; + ot.tri = ot.tri.neighbors[tmp].tri; + } + + /// + /// Find the next edge (counterclockwise) of the adjacent triangle. [rnext(abc) -> *a*] + /// + public void Rnext() + { + //SymSelf(); + int tmp = orient; + orient = tri.neighbors[tmp].orient; + tri = tri.neighbors[tmp].tri; + + //LnextSelf(); + orient = plus1Mod3[orient]; + + //SymSelf(); + tmp = orient; + orient = tri.neighbors[tmp].orient; + tri = tri.neighbors[tmp].tri; + } + + /// + /// Find the previous edge (clockwise) of the adjacent triangle. [rprev(abc) -> b**] + /// + public void Rprev(ref Otri ot) + { + //Sym(ref ot); + ot.tri = tri.neighbors[orient].tri; + ot.orient = tri.neighbors[orient].orient; + + //ot.LprevSelf(); + ot.orient = minus1Mod3[ot.orient]; + + //ot.SymSelf(); + int tmp = ot.orient; + ot.orient = ot.tri.neighbors[tmp].orient; + ot.tri = ot.tri.neighbors[tmp].tri; + } + + /// + /// Find the previous edge (clockwise) of the adjacent triangle. [rprev(abc) -> b**] + /// + public void Rprev() + { + //SymSelf(); + int tmp = orient; + orient = tri.neighbors[tmp].orient; + tri = tri.neighbors[tmp].tri; + + //LprevSelf(); + orient = minus1Mod3[orient]; + + //SymSelf(); + tmp = orient; + orient = tri.neighbors[tmp].orient; + tri = tri.neighbors[tmp].tri; + } + + /// + /// Origin [org(abc) -> a] + /// + public Vertex Org() + { + return tri.vertices[plus1Mod3[orient]]; + } + + /// + /// Destination [dest(abc) -> b] + /// + public Vertex Dest() + { + return tri.vertices[minus1Mod3[orient]]; + } + + /// + /// Apex [apex(abc) -> c] + /// + public Vertex Apex() + { + return tri.vertices[orient]; + } + + /// + /// Copy an oriented triangle. + /// + public void Copy(ref Otri ot) + { + ot.tri = tri; + ot.orient = orient; + } + + /// + /// Test for equality of oriented triangles. + /// + public bool Equals(Otri ot) + { + return ((tri == ot.tri) && (orient == ot.orient)); + } + + #endregion + + #region Otri primitives (internal) + + /// + /// Set Origin + /// + internal void SetOrg(Vertex v) + { + tri.vertices[plus1Mod3[orient]] = v; + } + + /// + /// Set Destination + /// + internal void SetDest(Vertex v) + { + tri.vertices[minus1Mod3[orient]] = v; + } + + /// + /// Set Apex + /// + internal void SetApex(Vertex v) + { + tri.vertices[orient] = v; + } + + /// + /// Bond two triangles together at the resepective handles. [bond(abc, bad)] + /// + internal void Bond(ref Otri ot) + { + tri.neighbors[orient].tri = ot.tri; + tri.neighbors[orient].orient = ot.orient; + + ot.tri.neighbors[ot.orient].tri = this.tri; + ot.tri.neighbors[ot.orient].orient = this.orient; + } + + /// + /// Dissolve a bond (from one side). + /// + /// Note that the other triangle will still think it's connected to + /// this triangle. Usually, however, the other triangle is being deleted + /// entirely, or bonded to another triangle, so it doesn't matter. + /// + internal void Dissolve(Triangle dummy) + { + tri.neighbors[orient].tri = dummy; + tri.neighbors[orient].orient = 0; + } + + /// + /// Infect a triangle with the virus. + /// + internal void Infect() + { + tri.infected = true; + } + + /// + /// Cure a triangle from the virus. + /// + internal void Uninfect() + { + tri.infected = false; + } + + /// + /// Test a triangle for viral infection. + /// + internal bool IsInfected() + { + return tri.infected; + } + + /// + /// Finds a subsegment abutting a triangle. + /// + internal void Pivot(ref Osub os) + { + os = tri.subsegs[orient]; + } + + /// + /// Bond a triangle to a subsegment. + /// + internal void SegBond(ref Osub os) + { + tri.subsegs[orient] = os; + os.seg.triangles[os.orient] = this; + } + + /// + /// Dissolve a bond (from the triangle side). + /// + internal void SegDissolve(SubSegment dummy) + { + tri.subsegs[orient].seg = dummy; + } + + /// + /// Check a triangle's deallocation. + /// + internal static bool IsDead(Triangle tria) + { + return tria.neighbors[0].tri == null; + } + + /// + /// Set a triangle's deallocation. + /// + internal static void Kill(Triangle tri) + { + tri.neighbors[0].tri = null; + tri.neighbors[2].tri = null; + } + + #endregion + } +} diff --git a/Triangle.NET/Triangle/Topology/SubSegment.cs b/src/Triangle/Topology/SubSegment.cs similarity index 88% rename from Triangle.NET/Triangle/Topology/SubSegment.cs rename to src/Triangle/Topology/SubSegment.cs index b757644..4c936c7 100644 --- a/Triangle.NET/Triangle/Topology/SubSegment.cs +++ b/src/Triangle/Topology/SubSegment.cs @@ -1,96 +1,96 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Topology -{ - using System; - using TriangleNet.Geometry; - - /// - /// The subsegment data structure. - /// - public class SubSegment : ISegment - { - // Hash for dictionary. Will be set by mesh instance. - internal int hash; - - internal Osub[] subsegs; - internal Vertex[] vertices; - internal Otri[] triangles; - internal int boundary; - - public SubSegment() - { - // Four NULL vertices. - vertices = new Vertex[4]; - - // Set the boundary marker to zero. - boundary = 0; - - // Initialize the two adjoining subsegments to be the omnipresent - // subsegment. - subsegs = new Osub[2]; - - // Initialize the two adjoining triangles to be "outer space." - triangles = new Otri[2]; - } - - #region Public properties - - /// - /// Gets the first endpoints vertex id. - /// - public int P0 - { - get { return this.vertices[0].id; } - } - - /// - /// Gets the seconds endpoints vertex id. - /// - public int P1 - { - get { return this.vertices[1].id; } - } - - /// - /// Gets the segment boundary mark. - /// - public int Label - { - get { return this.boundary; } - } - - #endregion - - /// - /// Gets the segments endpoint. - /// - public Vertex GetVertex(int index) - { - return this.vertices[index]; // TODO: Check range? - } - - /// - /// Gets an adjoining triangle. - /// - public ITriangle GetTriangle(int index) - { - return triangles[index].tri.hash == Mesh.DUMMY ? null : triangles[index].tri; - } - - public override int GetHashCode() - { - return this.hash; - } - - public override string ToString() - { - return String.Format("SID {0}", hash); - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Topology +{ + using System; + using TriangleNet.Geometry; + + /// + /// The subsegment data structure. + /// + public class SubSegment : ISegment + { + // Hash for dictionary. Will be set by mesh instance. + internal int hash; + + internal Osub[] subsegs; + internal Vertex[] vertices; + internal Otri[] triangles; + internal int boundary; + + public SubSegment() + { + // Four NULL vertices. + vertices = new Vertex[4]; + + // Set the boundary marker to zero. + boundary = 0; + + // Initialize the two adjoining subsegments to be the omnipresent + // subsegment. + subsegs = new Osub[2]; + + // Initialize the two adjoining triangles to be "outer space." + triangles = new Otri[2]; + } + + #region Public properties + + /// + /// Gets the first endpoints vertex id. + /// + public int P0 + { + get { return this.vertices[0].id; } + } + + /// + /// Gets the seconds endpoints vertex id. + /// + public int P1 + { + get { return this.vertices[1].id; } + } + + /// + /// Gets the segment boundary mark. + /// + public int Label + { + get { return this.boundary; } + } + + #endregion + + /// + /// Gets the segments endpoint. + /// + public Vertex GetVertex(int index) + { + return this.vertices[index]; // TODO: Check range? + } + + /// + /// Gets an adjoining triangle. + /// + public ITriangle GetTriangle(int index) + { + return triangles[index].tri.hash == Mesh.DUMMY ? null : triangles[index].tri; + } + + public override int GetHashCode() + { + return this.hash; + } + + public override string ToString() + { + return String.Format("SID {0}", hash); + } + } +} diff --git a/Triangle.NET/Triangle/Topology/Triangle.cs b/src/Triangle/Topology/Triangle.cs similarity index 92% rename from Triangle.NET/Triangle/Topology/Triangle.cs rename to src/Triangle/Topology/Triangle.cs index d31dff6..5981663 100644 --- a/Triangle.NET/Triangle/Topology/Triangle.cs +++ b/src/Triangle/Topology/Triangle.cs @@ -1,128 +1,128 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Topology -{ - using System; - using TriangleNet.Geometry; - - /// - /// The triangle data structure. - /// - public class Triangle : ITriangle - { - // Hash for dictionary. Will be set by mesh instance. - internal int hash; - - // The ID is only used for mesh output. - internal int id; - - internal Otri[] neighbors; - internal Vertex[] vertices; - internal Osub[] subsegs; - internal int label; - internal double area; - internal bool infected; - - /// - /// Initializes a new instance of the class. - /// - public Triangle() - { - // Three NULL vertices. - vertices = new Vertex[3]; - - // Initialize the three adjoining subsegments to be the omnipresent subsegment. - subsegs = new Osub[3]; - - // Initialize the three adjoining triangles to be "outer space". - neighbors = new Otri[3]; - - // area = -1.0; - } - - #region Public properties - - /// - /// Gets or sets the triangle id. - /// - public int ID - { - get { return this.id; } - set { this.id = value; } - } - - /// - /// Region ID the triangle belongs to. - /// - public int Label - { - get { return this.label; } - set { this.label = value; } - } - - /// - /// Gets the triangle area constraint. - /// - public double Area - { - get { return this.area; } - set { this.area = value; } - } - - /// - /// Gets the specified corners vertex. - /// - public Vertex GetVertex(int index) - { - return this.vertices[index]; // TODO: Check range? - } - - public int GetVertexID(int index) - { - return this.vertices[index].id; - } - - /// - /// Gets a triangles' neighbor. - /// - /// The neighbor index (0, 1 or 2). - /// The neigbbor opposite of vertex with given index. - public ITriangle GetNeighbor(int index) - { - return neighbors[index].tri.hash == Mesh.DUMMY ? null : neighbors[index].tri; - } - - /// - public int GetNeighborID(int index) - { - return neighbors[index].tri.hash == Mesh.DUMMY ? -1 : neighbors[index].tri.id; - } - - /// - /// Gets a triangles segment. - /// - /// The vertex index (0, 1 or 2). - /// The segment opposite of vertex with given index. - public ISegment GetSegment(int index) - { - return subsegs[index].seg.hash == Mesh.DUMMY ? null : subsegs[index].seg; - } - - #endregion - - public override int GetHashCode() - { - return this.hash; - } - - public override string ToString() - { - return String.Format("TID {0}", hash); - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Topology +{ + using System; + using TriangleNet.Geometry; + + /// + /// The triangle data structure. + /// + public class Triangle : ITriangle + { + // Hash for dictionary. Will be set by mesh instance. + internal int hash; + + // The ID is only used for mesh output. + internal int id; + + internal Otri[] neighbors; + internal Vertex[] vertices; + internal Osub[] subsegs; + internal int label; + internal double area; + internal bool infected; + + /// + /// Initializes a new instance of the class. + /// + public Triangle() + { + // Three NULL vertices. + vertices = new Vertex[3]; + + // Initialize the three adjoining subsegments to be the omnipresent subsegment. + subsegs = new Osub[3]; + + // Initialize the three adjoining triangles to be "outer space". + neighbors = new Otri[3]; + + // area = -1.0; + } + + #region Public properties + + /// + /// Gets or sets the triangle id. + /// + public int ID + { + get { return this.id; } + set { this.id = value; } + } + + /// + /// Region ID the triangle belongs to. + /// + public int Label + { + get { return this.label; } + set { this.label = value; } + } + + /// + /// Gets the triangle area constraint. + /// + public double Area + { + get { return this.area; } + set { this.area = value; } + } + + /// + /// Gets the specified corners vertex. + /// + public Vertex GetVertex(int index) + { + return this.vertices[index]; // TODO: Check range? + } + + public int GetVertexID(int index) + { + return this.vertices[index].id; + } + + /// + /// Gets a triangles' neighbor. + /// + /// The neighbor index (0, 1 or 2). + /// The neigbbor opposite of vertex with given index. + public ITriangle GetNeighbor(int index) + { + return neighbors[index].tri.hash == Mesh.DUMMY ? null : neighbors[index].tri; + } + + /// + public int GetNeighborID(int index) + { + return neighbors[index].tri.hash == Mesh.DUMMY ? -1 : neighbors[index].tri.id; + } + + /// + /// Gets a triangles segment. + /// + /// The vertex index (0, 1 or 2). + /// The segment opposite of vertex with given index. + public ISegment GetSegment(int index) + { + return subsegs[index].seg.hash == Mesh.DUMMY ? null : subsegs[index].seg; + } + + #endregion + + public override int GetHashCode() + { + return this.hash; + } + + public override string ToString() + { + return String.Format("TID {0}", hash); + } + } +} diff --git a/src/Triangle/Triangle.csproj b/src/Triangle/Triangle.csproj new file mode 100644 index 0000000..662a324 --- /dev/null +++ b/src/Triangle/Triangle.csproj @@ -0,0 +1,18 @@ + + + + netstandard2.0 + TriangleNet + Triangle + AnyCPU;x64 + true + Christian Woltering + 1.0.0 + 1.0.0-beta5 + + + + + + + diff --git a/Triangle.NET/Triangle/TriangleLocator.cs b/src/Triangle/TriangleLocator.cs similarity index 96% rename from Triangle.NET/Triangle/TriangleLocator.cs rename to src/Triangle/TriangleLocator.cs index e0f3e5e..b59a936 100644 --- a/Triangle.NET/Triangle/TriangleLocator.cs +++ b/src/Triangle/TriangleLocator.cs @@ -1,363 +1,363 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet -{ - using TriangleNet.Geometry; - using TriangleNet.Topology; - - /// - /// Locate triangles in a mesh. - /// - /// - /// WARNING: This routine is designed for convex triangulations, and will - /// not generally work after the holes and concavities have been carved. - /// - /// Based on a paper by Ernst P. Mucke, Isaac Saias, and Binhai Zhu, "Fast - /// Randomized Point Location Without Preprocessing in Two- and Three-Dimensional - /// Delaunay Triangulations," Proceedings of the Twelfth Annual Symposium on - /// Computational Geometry, ACM, May 1996. - /// - public class TriangleLocator - { - TriangleSampler sampler; - Mesh mesh; - - IPredicates predicates; - - // Pointer to a recently visited triangle. Improves point location if - // proximate vertices are inserted sequentially. - internal Otri recenttri; - - public TriangleLocator(Mesh mesh) - : this(mesh, RobustPredicates.Default) - { - } - - public TriangleLocator(Mesh mesh, IPredicates predicates) - { - this.mesh = mesh; - this.predicates = predicates; - - sampler = new TriangleSampler(mesh); - } - - /// - /// Suggest the given triangle as a starting triangle for point location. - /// - /// - public void Update(ref Otri otri) - { - otri.Copy(ref recenttri); - } - - public void Reset() - { - sampler.Reset(); - recenttri.tri = null; // No triangle has been visited yet. - } - - /// - /// Find a triangle or edge containing a given point. - /// - /// The point to locate. - /// The triangle to start the search at. - /// If 'stopatsubsegment' is set, the search - /// will stop if it tries to walk through a subsegment, and will return OUTSIDE. - /// Location information. - /// - /// Begins its search from 'searchtri'. It is important that 'searchtri' - /// be a handle with the property that 'searchpoint' is strictly to the left - /// of the edge denoted by 'searchtri', or is collinear with that edge and - /// does not intersect that edge. (In particular, 'searchpoint' should not - /// be the origin or destination of that edge.) - /// - /// These conditions are imposed because preciselocate() is normally used in - /// one of two situations: - /// - /// (1) To try to find the location to insert a new point. Normally, we - /// know an edge that the point is strictly to the left of. In the - /// incremental Delaunay algorithm, that edge is a bounding box edge. - /// In Ruppert's Delaunay refinement algorithm for quality meshing, - /// that edge is the shortest edge of the triangle whose circumcenter - /// is being inserted. - /// - /// (2) To try to find an existing point. In this case, any edge on the - /// convex hull is a good starting edge. You must screen out the - /// possibility that the vertex sought is an endpoint of the starting - /// edge before you call preciselocate(). - /// - /// On completion, 'searchtri' is a triangle that contains 'searchpoint'. - /// - /// This implementation differs from that given by Guibas and Stolfi. It - /// walks from triangle to triangle, crossing an edge only if 'searchpoint' - /// is on the other side of the line containing that edge. After entering - /// a triangle, there are two edges by which one can leave that triangle. - /// If both edges are valid ('searchpoint' is on the other side of both - /// edges), one of the two is chosen by drawing a line perpendicular to - /// the entry edge (whose endpoints are 'forg' and 'fdest') passing through - /// 'fapex'. Depending on which side of this perpendicular 'searchpoint' - /// falls on, an exit edge is chosen. - /// - /// This implementation is empirically faster than the Guibas and Stolfi - /// point location routine (which I originally used), which tends to spiral - /// in toward its target. - /// - /// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri' - /// is a handle whose origin is the existing vertex. - /// - /// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a - /// handle whose primary edge is the edge on which the point lies. - /// - /// Returns INTRIANGLE if the point lies strictly within a triangle. - /// 'searchtri' is a handle on the triangle that contains the point. - /// - /// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a - /// handle whose primary edge the point is to the right of. This might - /// occur when the circumcenter of a triangle falls just slightly outside - /// the mesh due to floating-point roundoff error. It also occurs when - /// seeking a hole or region point that a foolish user has placed outside - /// the mesh. - /// - /// WARNING: This routine is designed for convex triangulations, and will - /// not generally work after the holes and concavities have been carved. - /// However, it can still be used to find the circumcenter of a triangle, as - /// long as the search is begun from the triangle in question. - public LocateResult PreciseLocate(Point searchpoint, ref Otri searchtri, - bool stopatsubsegment) - { - Otri backtracktri = default(Otri); - Osub checkedge = default(Osub); - Vertex forg, fdest, fapex; - double orgorient, destorient; - bool moveleft; - - // Where are we? - forg = searchtri.Org(); - fdest = searchtri.Dest(); - fapex = searchtri.Apex(); - while (true) - { - // Check whether the apex is the point we seek. - if ((fapex.x == searchpoint.x) && (fapex.y == searchpoint.y)) - { - searchtri.Lprev(); - return LocateResult.OnVertex; - } - // Does the point lie on the other side of the line defined by the - // triangle edge opposite the triangle's destination? - destorient = predicates.CounterClockwise(forg, fapex, searchpoint); - // Does the point lie on the other side of the line defined by the - // triangle edge opposite the triangle's origin? - orgorient = predicates.CounterClockwise(fapex, fdest, searchpoint); - if (destorient > 0.0) - { - if (orgorient > 0.0) - { - // Move left if the inner product of (fapex - searchpoint) and - // (fdest - forg) is positive. This is equivalent to drawing - // a line perpendicular to the line (forg, fdest) and passing - // through 'fapex', and determining which side of this line - // 'searchpoint' falls on. - moveleft = (fapex.x - searchpoint.x) * (fdest.x - forg.x) + - (fapex.y - searchpoint.y) * (fdest.y - forg.y) > 0.0; - } - else - { - moveleft = true; - } - } - else - { - if (orgorient > 0.0) - { - moveleft = false; - } - else - { - // The point we seek must be on the boundary of or inside this - // triangle. - if (destorient == 0.0) - { - searchtri.Lprev(); - return LocateResult.OnEdge; - } - if (orgorient == 0.0) - { - searchtri.Lnext(); - return LocateResult.OnEdge; - } - return LocateResult.InTriangle; - } - } - - // Move to another triangle. Leave a trace 'backtracktri' in case - // floating-point roundoff or some such bogey causes us to walk - // off a boundary of the triangulation. - if (moveleft) - { - searchtri.Lprev(ref backtracktri); - fdest = fapex; - } - else - { - searchtri.Lnext(ref backtracktri); - forg = fapex; - } - backtracktri.Sym(ref searchtri); - - if (mesh.checksegments && stopatsubsegment) - { - // Check for walking through a subsegment. - backtracktri.Pivot(ref checkedge); - if (checkedge.seg.hash != Mesh.DUMMY) - { - // Go back to the last triangle. - backtracktri.Copy(ref searchtri); - return LocateResult.Outside; - } - } - // Check for walking right out of the triangulation. - if (searchtri.tri.id == Mesh.DUMMY) - { - // Go back to the last triangle. - backtracktri.Copy(ref searchtri); - return LocateResult.Outside; - } - - fapex = searchtri.Apex(); - } - } - - /// - /// Find a triangle or edge containing a given point. - /// - /// The point to locate. - /// The triangle to start the search at. - /// Location information. - /// - /// Searching begins from one of: the input 'searchtri', a recently - /// encountered triangle 'recenttri', or from a triangle chosen from a - /// random sample. The choice is made by determining which triangle's - /// origin is closest to the point we are searching for. Normally, - /// 'searchtri' should be a handle on the convex hull of the triangulation. - /// - /// Details on the random sampling method can be found in the Mucke, Saias, - /// and Zhu paper cited in the header of this code. - /// - /// On completion, 'searchtri' is a triangle that contains 'searchpoint'. - /// - /// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri' - /// is a handle whose origin is the existing vertex. - /// - /// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a - /// handle whose primary edge is the edge on which the point lies. - /// - /// Returns INTRIANGLE if the point lies strictly within a triangle. - /// 'searchtri' is a handle on the triangle that contains the point. - /// - /// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a - /// handle whose primary edge the point is to the right of. This might - /// occur when the circumcenter of a triangle falls just slightly outside - /// the mesh due to floating-point roundoff error. It also occurs when - /// seeking a hole or region point that a foolish user has placed outside - /// the mesh. - /// - /// WARNING: This routine is designed for convex triangulations, and will - /// not generally work after the holes and concavities have been carved. - /// - public LocateResult Locate(Point searchpoint, ref Otri searchtri) - { - Otri sampletri = default(Otri); - Vertex torg, tdest; - double searchdist, dist; - double ahead; - - // Record the distance from the suggested starting triangle to the - // point we seek. - torg = searchtri.Org(); - searchdist = (searchpoint.x - torg.x) * (searchpoint.x - torg.x) + - (searchpoint.y - torg.y) * (searchpoint.y - torg.y); - - // If a recently encountered triangle has been recorded and has not been - // deallocated, test it as a good starting point. - if (recenttri.tri != null) - { - if (!Otri.IsDead(recenttri.tri)) - { - torg = recenttri.Org(); - if ((torg.x == searchpoint.x) && (torg.y == searchpoint.y)) - { - recenttri.Copy(ref searchtri); - return LocateResult.OnVertex; - } - dist = (searchpoint.x - torg.x) * (searchpoint.x - torg.x) + - (searchpoint.y - torg.y) * (searchpoint.y - torg.y); - if (dist < searchdist) - { - recenttri.Copy(ref searchtri); - searchdist = dist; - } - } - } - - // TODO: Improve sampling. - sampler.Update(); - - foreach (var t in sampler) - { - sampletri.tri = t; - if (!Otri.IsDead(sampletri.tri)) - { - torg = sampletri.Org(); - dist = (searchpoint.x - torg.x) * (searchpoint.x - torg.x) + - (searchpoint.y - torg.y) * (searchpoint.y - torg.y); - if (dist < searchdist) - { - sampletri.Copy(ref searchtri); - searchdist = dist; - } - } - } - - // Where are we? - torg = searchtri.Org(); - tdest = searchtri.Dest(); - - // Check the starting triangle's vertices. - if ((torg.x == searchpoint.x) && (torg.y == searchpoint.y)) - { - return LocateResult.OnVertex; - } - if ((tdest.x == searchpoint.x) && (tdest.y == searchpoint.y)) - { - searchtri.Lnext(); - return LocateResult.OnVertex; - } - - // Orient 'searchtri' to fit the preconditions of calling preciselocate(). - ahead = predicates.CounterClockwise(torg, tdest, searchpoint); - if (ahead < 0.0) - { - // Turn around so that 'searchpoint' is to the left of the - // edge specified by 'searchtri'. - searchtri.Sym(); - } - else if (ahead == 0.0) - { - // Check if 'searchpoint' is between 'torg' and 'tdest'. - if (((torg.x < searchpoint.x) == (searchpoint.x < tdest.x)) && - ((torg.y < searchpoint.y) == (searchpoint.y < tdest.y))) - { - return LocateResult.OnEdge; - } - } - - return PreciseLocate(searchpoint, ref searchtri, false); - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using TriangleNet.Geometry; + using TriangleNet.Topology; + + /// + /// Locate triangles in a mesh. + /// + /// + /// WARNING: This routine is designed for convex triangulations, and will + /// not generally work after the holes and concavities have been carved. + /// + /// Based on a paper by Ernst P. Mucke, Isaac Saias, and Binhai Zhu, "Fast + /// Randomized Point Location Without Preprocessing in Two- and Three-Dimensional + /// Delaunay Triangulations," Proceedings of the Twelfth Annual Symposium on + /// Computational Geometry, ACM, May 1996. + /// + public class TriangleLocator + { + TriangleSampler sampler; + Mesh mesh; + + IPredicates predicates; + + // Pointer to a recently visited triangle. Improves point location if + // proximate vertices are inserted sequentially. + internal Otri recenttri; + + public TriangleLocator(Mesh mesh) + : this(mesh, RobustPredicates.Default) + { + } + + public TriangleLocator(Mesh mesh, IPredicates predicates) + { + this.mesh = mesh; + this.predicates = predicates; + + sampler = new TriangleSampler(mesh); + } + + /// + /// Suggest the given triangle as a starting triangle for point location. + /// + /// + public void Update(ref Otri otri) + { + otri.Copy(ref recenttri); + } + + public void Reset() + { + sampler.Reset(); + recenttri.tri = null; // No triangle has been visited yet. + } + + /// + /// Find a triangle or edge containing a given point. + /// + /// The point to locate. + /// The triangle to start the search at. + /// If 'stopatsubsegment' is set, the search + /// will stop if it tries to walk through a subsegment, and will return OUTSIDE. + /// Location information. + /// + /// Begins its search from 'searchtri'. It is important that 'searchtri' + /// be a handle with the property that 'searchpoint' is strictly to the left + /// of the edge denoted by 'searchtri', or is collinear with that edge and + /// does not intersect that edge. (In particular, 'searchpoint' should not + /// be the origin or destination of that edge.) + /// + /// These conditions are imposed because preciselocate() is normally used in + /// one of two situations: + /// + /// (1) To try to find the location to insert a new point. Normally, we + /// know an edge that the point is strictly to the left of. In the + /// incremental Delaunay algorithm, that edge is a bounding box edge. + /// In Ruppert's Delaunay refinement algorithm for quality meshing, + /// that edge is the shortest edge of the triangle whose circumcenter + /// is being inserted. + /// + /// (2) To try to find an existing point. In this case, any edge on the + /// convex hull is a good starting edge. You must screen out the + /// possibility that the vertex sought is an endpoint of the starting + /// edge before you call preciselocate(). + /// + /// On completion, 'searchtri' is a triangle that contains 'searchpoint'. + /// + /// This implementation differs from that given by Guibas and Stolfi. It + /// walks from triangle to triangle, crossing an edge only if 'searchpoint' + /// is on the other side of the line containing that edge. After entering + /// a triangle, there are two edges by which one can leave that triangle. + /// If both edges are valid ('searchpoint' is on the other side of both + /// edges), one of the two is chosen by drawing a line perpendicular to + /// the entry edge (whose endpoints are 'forg' and 'fdest') passing through + /// 'fapex'. Depending on which side of this perpendicular 'searchpoint' + /// falls on, an exit edge is chosen. + /// + /// This implementation is empirically faster than the Guibas and Stolfi + /// point location routine (which I originally used), which tends to spiral + /// in toward its target. + /// + /// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri' + /// is a handle whose origin is the existing vertex. + /// + /// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a + /// handle whose primary edge is the edge on which the point lies. + /// + /// Returns INTRIANGLE if the point lies strictly within a triangle. + /// 'searchtri' is a handle on the triangle that contains the point. + /// + /// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a + /// handle whose primary edge the point is to the right of. This might + /// occur when the circumcenter of a triangle falls just slightly outside + /// the mesh due to floating-point roundoff error. It also occurs when + /// seeking a hole or region point that a foolish user has placed outside + /// the mesh. + /// + /// WARNING: This routine is designed for convex triangulations, and will + /// not generally work after the holes and concavities have been carved. + /// However, it can still be used to find the circumcenter of a triangle, as + /// long as the search is begun from the triangle in question. + public LocateResult PreciseLocate(Point searchpoint, ref Otri searchtri, + bool stopatsubsegment) + { + Otri backtracktri = default(Otri); + Osub checkedge = default(Osub); + Vertex forg, fdest, fapex; + double orgorient, destorient; + bool moveleft; + + // Where are we? + forg = searchtri.Org(); + fdest = searchtri.Dest(); + fapex = searchtri.Apex(); + while (true) + { + // Check whether the apex is the point we seek. + if ((fapex.x == searchpoint.x) && (fapex.y == searchpoint.y)) + { + searchtri.Lprev(); + return LocateResult.OnVertex; + } + // Does the point lie on the other side of the line defined by the + // triangle edge opposite the triangle's destination? + destorient = predicates.CounterClockwise(forg, fapex, searchpoint); + // Does the point lie on the other side of the line defined by the + // triangle edge opposite the triangle's origin? + orgorient = predicates.CounterClockwise(fapex, fdest, searchpoint); + if (destorient > 0.0) + { + if (orgorient > 0.0) + { + // Move left if the inner product of (fapex - searchpoint) and + // (fdest - forg) is positive. This is equivalent to drawing + // a line perpendicular to the line (forg, fdest) and passing + // through 'fapex', and determining which side of this line + // 'searchpoint' falls on. + moveleft = (fapex.x - searchpoint.x) * (fdest.x - forg.x) + + (fapex.y - searchpoint.y) * (fdest.y - forg.y) > 0.0; + } + else + { + moveleft = true; + } + } + else + { + if (orgorient > 0.0) + { + moveleft = false; + } + else + { + // The point we seek must be on the boundary of or inside this + // triangle. + if (destorient == 0.0) + { + searchtri.Lprev(); + return LocateResult.OnEdge; + } + if (orgorient == 0.0) + { + searchtri.Lnext(); + return LocateResult.OnEdge; + } + return LocateResult.InTriangle; + } + } + + // Move to another triangle. Leave a trace 'backtracktri' in case + // floating-point roundoff or some such bogey causes us to walk + // off a boundary of the triangulation. + if (moveleft) + { + searchtri.Lprev(ref backtracktri); + fdest = fapex; + } + else + { + searchtri.Lnext(ref backtracktri); + forg = fapex; + } + backtracktri.Sym(ref searchtri); + + if (mesh.checksegments && stopatsubsegment) + { + // Check for walking through a subsegment. + backtracktri.Pivot(ref checkedge); + if (checkedge.seg.hash != Mesh.DUMMY) + { + // Go back to the last triangle. + backtracktri.Copy(ref searchtri); + return LocateResult.Outside; + } + } + // Check for walking right out of the triangulation. + if (searchtri.tri.id == Mesh.DUMMY) + { + // Go back to the last triangle. + backtracktri.Copy(ref searchtri); + return LocateResult.Outside; + } + + fapex = searchtri.Apex(); + } + } + + /// + /// Find a triangle or edge containing a given point. + /// + /// The point to locate. + /// The triangle to start the search at. + /// Location information. + /// + /// Searching begins from one of: the input 'searchtri', a recently + /// encountered triangle 'recenttri', or from a triangle chosen from a + /// random sample. The choice is made by determining which triangle's + /// origin is closest to the point we are searching for. Normally, + /// 'searchtri' should be a handle on the convex hull of the triangulation. + /// + /// Details on the random sampling method can be found in the Mucke, Saias, + /// and Zhu paper cited in the header of this code. + /// + /// On completion, 'searchtri' is a triangle that contains 'searchpoint'. + /// + /// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri' + /// is a handle whose origin is the existing vertex. + /// + /// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a + /// handle whose primary edge is the edge on which the point lies. + /// + /// Returns INTRIANGLE if the point lies strictly within a triangle. + /// 'searchtri' is a handle on the triangle that contains the point. + /// + /// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a + /// handle whose primary edge the point is to the right of. This might + /// occur when the circumcenter of a triangle falls just slightly outside + /// the mesh due to floating-point roundoff error. It also occurs when + /// seeking a hole or region point that a foolish user has placed outside + /// the mesh. + /// + /// WARNING: This routine is designed for convex triangulations, and will + /// not generally work after the holes and concavities have been carved. + /// + public LocateResult Locate(Point searchpoint, ref Otri searchtri) + { + Otri sampletri = default(Otri); + Vertex torg, tdest; + double searchdist, dist; + double ahead; + + // Record the distance from the suggested starting triangle to the + // point we seek. + torg = searchtri.Org(); + searchdist = (searchpoint.x - torg.x) * (searchpoint.x - torg.x) + + (searchpoint.y - torg.y) * (searchpoint.y - torg.y); + + // If a recently encountered triangle has been recorded and has not been + // deallocated, test it as a good starting point. + if (recenttri.tri != null) + { + if (!Otri.IsDead(recenttri.tri)) + { + torg = recenttri.Org(); + if ((torg.x == searchpoint.x) && (torg.y == searchpoint.y)) + { + recenttri.Copy(ref searchtri); + return LocateResult.OnVertex; + } + dist = (searchpoint.x - torg.x) * (searchpoint.x - torg.x) + + (searchpoint.y - torg.y) * (searchpoint.y - torg.y); + if (dist < searchdist) + { + recenttri.Copy(ref searchtri); + searchdist = dist; + } + } + } + + // TODO: Improve sampling. + sampler.Update(); + + foreach (var t in sampler) + { + sampletri.tri = t; + if (!Otri.IsDead(sampletri.tri)) + { + torg = sampletri.Org(); + dist = (searchpoint.x - torg.x) * (searchpoint.x - torg.x) + + (searchpoint.y - torg.y) * (searchpoint.y - torg.y); + if (dist < searchdist) + { + sampletri.Copy(ref searchtri); + searchdist = dist; + } + } + } + + // Where are we? + torg = searchtri.Org(); + tdest = searchtri.Dest(); + + // Check the starting triangle's vertices. + if ((torg.x == searchpoint.x) && (torg.y == searchpoint.y)) + { + return LocateResult.OnVertex; + } + if ((tdest.x == searchpoint.x) && (tdest.y == searchpoint.y)) + { + searchtri.Lnext(); + return LocateResult.OnVertex; + } + + // Orient 'searchtri' to fit the preconditions of calling preciselocate(). + ahead = predicates.CounterClockwise(torg, tdest, searchpoint); + if (ahead < 0.0) + { + // Turn around so that 'searchpoint' is to the left of the + // edge specified by 'searchtri'. + searchtri.Sym(); + } + else if (ahead == 0.0) + { + // Check if 'searchpoint' is between 'torg' and 'tdest'. + if (((torg.x < searchpoint.x) == (searchpoint.x < tdest.x)) && + ((torg.y < searchpoint.y) == (searchpoint.y < tdest.y))) + { + return LocateResult.OnEdge; + } + } + + return PreciseLocate(searchpoint, ref searchtri, false); + } + } +} diff --git a/Triangle.NET/Triangle/TrianglePool.cs b/src/Triangle/TrianglePool.cs similarity index 94% rename from Triangle.NET/Triangle/TrianglePool.cs rename to src/Triangle/TrianglePool.cs index 2a7c0bf..04cac3d 100644 --- a/Triangle.NET/Triangle/TrianglePool.cs +++ b/src/Triangle/TrianglePool.cs @@ -1,305 +1,309 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet -{ - using System; - using System.Collections.Generic; - using TriangleNet.Geometry; - using TriangleNet.Topology; - - public class TrianglePool : ICollection - { - // Determines the size of each block in the pool. - private const int BLOCKSIZE = 1024; - - // The total number of currently allocated triangles. - int size; - - // The number of triangles currently used. - int count; - - // The pool. - Triangle[][] pool; - - // A stack of free triangles. - Stack stack; - - public TrianglePool() - { - size = 0; - - // On startup, the pool should be able to hold 2^16 triangles. - int n = Math.Max(1, 65536 / BLOCKSIZE); - - pool = new Triangle[n][]; - pool[0] = new Triangle[BLOCKSIZE]; - - stack = new Stack(BLOCKSIZE); - } - - /// - /// Gets a triangle from the pool. - /// - /// - public Triangle Get() - { - Triangle triangle; - - if (stack.Count > 0) - { - triangle = stack.Pop(); - triangle.hash = -triangle.hash - 1; - - Cleanup(triangle); - } - else if (count < size) - { - triangle = pool[count / BLOCKSIZE][count % BLOCKSIZE]; - triangle.id = triangle.hash; - - Cleanup(triangle); - - count++; - } - else - { - triangle = new Triangle(); - triangle.hash = size; - triangle.id = triangle.hash; - - int block = size / BLOCKSIZE; - - if (pool[block] == null) - { - pool[block] = new Triangle[BLOCKSIZE]; - - // Check if the pool has to be resized. - if (block + 1 == pool.Length) - { - Array.Resize(ref pool, 2 * pool.Length); - } - } - - // Add triangle to pool. - pool[block][size % BLOCKSIZE] = triangle; - - count = ++size; - } - - return triangle; - } - - public void Release(Triangle triangle) - { - stack.Push(triangle); - - // Mark the triangle as free (used by enumerator). - triangle.hash = -triangle.hash - 1; - } - - /// - /// Restart the triangle pool. - /// - public TrianglePool Restart() - { - foreach (var triangle in stack) - { - // Reset hash to original value. - triangle.hash = -triangle.hash - 1; - } - - stack.Clear(); - - count = 0; - - return this; - } - - /// - /// Samples a number of triangles from the pool. - /// - /// The number of triangles to sample. - /// - /// - internal IEnumerable Sample(int k, Random random) - { - int i, count = this.Count; - - if (k > count) - { - // TODO: handle Sample special case. - k = count; - } - - Triangle t; - - // TODO: improve sampling code (to ensure no duplicates). - - while (k > 0) - { - i = random.Next(0, count); - - t = pool[i / BLOCKSIZE][i % BLOCKSIZE]; - - if (t.hash >= 0) - { - k--; - yield return t; - } - } - } - - private void Cleanup(Triangle triangle) - { - triangle.label = 0; - triangle.area = 0.0; - triangle.infected = false; - - for (int i = 0; i < 3; i++) - { - triangle.vertices[i] = null; - - triangle.subsegs[i] = default(Osub); - triangle.neighbors[i] = default(Otri); - } - } - - public void Add(Triangle item) - { - throw new NotImplementedException(); - } - - public void Clear() - { - stack.Clear(); - - int blocks = (size / BLOCKSIZE) + 1; - - for (int i = 0; i < blocks; i++) - { - var block = pool[i]; - - // Number of triangles in current block: - int length = (size - i * BLOCKSIZE) % BLOCKSIZE; - - for (int j = 0; j < length; j++) - { - block[j] = null; - } - } - - size = count = 0; - } - - public bool Contains(Triangle item) - { - int i = item.hash; - - if (i < 0 || i > size) - { - return false; - } - - return pool[i / BLOCKSIZE][i % BLOCKSIZE].hash >= 0; - } - - public void CopyTo(Triangle[] array, int index) - { - var enumerator = GetEnumerator(); - - while (enumerator.MoveNext()) - { - array[index] = enumerator.Current; - index++; - } - } - - public int Count - { - get { return count - stack.Count; } - } - - public bool IsReadOnly - { - get { return true; } - } - - public bool Remove(Triangle item) - { - throw new NotImplementedException(); - } - - public IEnumerator GetEnumerator() - { - return new Enumerator(this); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - class Enumerator : IEnumerator - { - // TODO: enumerator should be able to tell if collection changed. - - int count; - - Triangle[][] pool; - - Triangle current; - - int index, offset; - - public Enumerator(TrianglePool pool) - { - this.count = pool.Count; - this.pool = pool.pool; - - index = 0; - offset = 0; - } - - public Triangle Current - { - get { return current; } - } - - public void Dispose() - { - } - - object System.Collections.IEnumerator.Current - { - get { return current; } - } - - public bool MoveNext() - { - while (index < count) - { - current = pool[offset / BLOCKSIZE][offset % BLOCKSIZE]; - - offset++; - - if (current.hash >= 0) - { - index++; - return true; - } - } - - return false; - } - - public void Reset() - { - index = offset = 0; - } - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using System.Collections.Generic; + using TriangleNet.Topology; + + public class TrianglePool : ICollection + { + // Determines the size of each block in the pool. + private const int BLOCKSIZE = 1024; + + // The total number of currently allocated triangles. + int size; + + // The number of triangles currently used. + int count; + + // The pool. + Triangle[][] pool; + + // A stack of free triangles. + Stack stack; + + /// + /// Gets the total number of currently allocated triangles. + /// + public int Capacity => size; + + public TrianglePool() + { + size = 0; + + // On startup, the pool should be able to hold 2^16 triangles. + int n = Math.Max(1, 65536 / BLOCKSIZE); + + pool = new Triangle[n][]; + pool[0] = new Triangle[BLOCKSIZE]; + + stack = new Stack(BLOCKSIZE); + } + + /// + /// Gets a triangle from the pool. + /// + /// + public Triangle Get() + { + Triangle triangle; + + if (stack.Count > 0) + { + triangle = stack.Pop(); + triangle.hash = -triangle.hash - 1; + + Cleanup(triangle); + } + else if (count < size) + { + triangle = pool[count / BLOCKSIZE][count % BLOCKSIZE]; + triangle.id = triangle.hash; + + Cleanup(triangle); + + count++; + } + else + { + triangle = new Triangle(); + triangle.hash = size; + triangle.id = triangle.hash; + + int block = size / BLOCKSIZE; + + if (pool[block] == null) + { + pool[block] = new Triangle[BLOCKSIZE]; + + // Check if the pool has to be resized. + if (block + 1 == pool.Length) + { + Array.Resize(ref pool, 2 * pool.Length); + } + } + + // Add triangle to pool. + pool[block][size % BLOCKSIZE] = triangle; + + count = ++size; + } + + return triangle; + } + + public void Release(Triangle triangle) + { + stack.Push(triangle); + + // Mark the triangle as free (used by enumerator). + triangle.hash = -triangle.hash - 1; + } + + /// + /// Restart the triangle pool. + /// + public TrianglePool Restart() + { + foreach (var triangle in stack) + { + // Reset hash to original value. + triangle.hash = -triangle.hash - 1; + } + + stack.Clear(); + + count = 0; + + return this; + } + + /// + /// Samples a number of triangles from the pool. + /// + /// The number of triangles to sample. + /// + /// + internal IEnumerable Sample(int k, Random random) + { + int i, count = this.Count; + + if (k > count) + { + // TODO: handle Sample special case. + k = count; + } + + Triangle t; + + // TODO: improve sampling code (to ensure no duplicates). + + while (k > 0) + { + i = random.Next(0, count); + + t = pool[i / BLOCKSIZE][i % BLOCKSIZE]; + + if (t.hash >= 0) + { + k--; + yield return t; + } + } + } + + private void Cleanup(Triangle triangle) + { + triangle.label = 0; + triangle.area = 0.0; + triangle.infected = false; + + for (int i = 0; i < 3; i++) + { + triangle.vertices[i] = null; + + triangle.subsegs[i] = default(Osub); + triangle.neighbors[i] = default(Otri); + } + } + + public void Add(Triangle item) + { + throw new NotImplementedException(); + } + + public void Clear() + { + stack.Clear(); + + int blocks = (size / BLOCKSIZE) + 1; + + for (int i = 0; i < blocks; i++) + { + var block = pool[i]; + + // Number of triangles in current block: + int length = (size - i * BLOCKSIZE) % BLOCKSIZE; + + for (int j = 0; j < length; j++) + { + block[j] = null; + } + } + + size = count = 0; + } + + public bool Contains(Triangle item) + { + int i = item.hash; + + if (i < 0 || i > size) + { + return false; + } + + return pool[i / BLOCKSIZE][i % BLOCKSIZE].hash >= 0; + } + + public void CopyTo(Triangle[] array, int index) + { + var enumerator = GetEnumerator(); + + while (enumerator.MoveNext()) + { + array[index] = enumerator.Current; + index++; + } + } + + public int Count + { + get { return count - stack.Count; } + } + + public bool IsReadOnly + { + get { return true; } + } + + public bool Remove(Triangle item) + { + throw new NotImplementedException(); + } + + public IEnumerator GetEnumerator() + { + return new Enumerator(this); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + class Enumerator : IEnumerator + { + // TODO: enumerator should be able to tell if collection changed. + + int count; + + Triangle[][] pool; + + Triangle current; + + int index, offset; + + public Enumerator(TrianglePool pool) + { + this.count = pool.Count; + this.pool = pool.pool; + + index = 0; + offset = 0; + } + + public Triangle Current + { + get { return current; } + } + + public void Dispose() + { + } + + object System.Collections.IEnumerator.Current + { + get { return current; } + } + + public bool MoveNext() + { + while (index < count) + { + current = pool[offset / BLOCKSIZE][offset % BLOCKSIZE]; + + offset++; + + if (current.hash >= 0) + { + index++; + return true; + } + } + + return false; + } + + public void Reset() + { + index = offset = 0; + } + } + } +} diff --git a/Triangle.NET/Triangle/TriangleSampler.cs b/src/Triangle/TriangleSampler.cs similarity index 90% rename from Triangle.NET/Triangle/TriangleSampler.cs rename to src/Triangle/TriangleSampler.cs index 8ff42c4..519a37a 100644 --- a/Triangle.NET/Triangle/TriangleSampler.cs +++ b/src/Triangle/TriangleSampler.cs @@ -1,85 +1,85 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet -{ - using System; - using System.Collections.Generic; - using TriangleNet.Topology; - - /// - /// Used for triangle sampling in the class. - /// - class TriangleSampler : IEnumerable - { - private const int RANDOM_SEED = 110503; - - // Empirically chosen factor. - private const int samplefactor = 11; - - private Random random; - private Mesh mesh; - - // Number of random samples for point location (at least 1). - private int samples = 1; - - // Number of triangles in mesh. - private int triangleCount = 0; - - public TriangleSampler(Mesh mesh) - : this(mesh, new Random(RANDOM_SEED)) - { - } - - public TriangleSampler(Mesh mesh, Random random) - { - this.mesh = mesh; - this.random = random; - } - - /// - /// Reset the sampler. - /// - public void Reset() - { - this.samples = 1; - this.triangleCount = 0; - } - - /// - /// Update sampling parameters if mesh changed. - /// - public void Update() - { - int count = mesh.triangles.Count; - - if (triangleCount != count) - { - triangleCount = count; - - // The number of random samples taken is proportional to the cube root - // of the number of triangles in the mesh. The next bit of code assumes - // that the number of triangles increases monotonically (or at least - // doesn't decrease enough to matter). - while (samplefactor * samples * samples * samples < count) - { - samples++; - } - } - } - - public IEnumerator GetEnumerator() - { - return mesh.triangles.Sample(samples, random).GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using System; + using System.Collections.Generic; + using TriangleNet.Topology; + + /// + /// Used for triangle sampling in the class. + /// + class TriangleSampler : IEnumerable + { + private const int RANDOM_SEED = 110503; + + // Empirically chosen factor. + private const int samplefactor = 11; + + private Random random; + private Mesh mesh; + + // Number of random samples for point location (at least 1). + private int samples = 1; + + // Number of triangles in mesh. + private int triangleCount = 0; + + public TriangleSampler(Mesh mesh) + : this(mesh, new Random(RANDOM_SEED)) + { + } + + public TriangleSampler(Mesh mesh, Random random) + { + this.mesh = mesh; + this.random = random; + } + + /// + /// Reset the sampler. + /// + public void Reset() + { + this.samples = 1; + this.triangleCount = 0; + } + + /// + /// Update sampling parameters if mesh changed. + /// + public void Update() + { + int count = mesh.triangles.Count; + + if (triangleCount != count) + { + triangleCount = count; + + // The number of random samples taken is proportional to the cube root + // of the number of triangles in the mesh. The next bit of code assumes + // that the number of triangles increases monotonically (or at least + // doesn't decrease enough to matter). + while (samplefactor * samples * samples * samples < count) + { + samples++; + } + } + } + + public IEnumerator GetEnumerator() + { + return mesh.triangles.Sample(samples, random).GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/Triangle.NET/Triangle/Voronoi/BoundedVoronoi.cs b/src/Triangle/Voronoi/BoundedVoronoi.cs similarity index 90% rename from Triangle.NET/Triangle/Voronoi/BoundedVoronoi.cs rename to src/Triangle/Voronoi/BoundedVoronoi.cs index dd7f6ca..6f271bc 100644 --- a/Triangle.NET/Triangle/Voronoi/BoundedVoronoi.cs +++ b/src/Triangle/Voronoi/BoundedVoronoi.cs @@ -1,182 +1,195 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Voronoi -{ - using System.Collections.Generic; - using TriangleNet.Geometry; - using TriangleNet.Tools; - using TriangleNet.Topology.DCEL; - - using HVertex = TriangleNet.Topology.DCEL.Vertex; - using TVertex = TriangleNet.Geometry.Vertex; - - public class BoundedVoronoi : VoronoiBase - { - int offset; - - public BoundedVoronoi(Mesh mesh) - : this(mesh, new DefaultVoronoiFactory(), RobustPredicates.Default) - { - } - - public BoundedVoronoi(Mesh mesh, IVoronoiFactory factory, IPredicates predicates) - : base(mesh, factory, predicates, true) - { - // We explicitly told the base constructor to call the Generate method, so - // at this point the basic Voronoi diagram is already created. - offset = base.vertices.Count; - - // Each vertex of the hull will be part of a Voronoi cell. - base.vertices.Capacity = offset + mesh.hullsize; - - // Create bounded Voronoi diagram. - PostProcess(); - - ResolveBoundaryEdges(); - } - - /// - /// Computes edge intersections with mesh boundary edges. - /// - private void PostProcess() - { - foreach (var edge in rays) - { - var twin = edge.twin; - - var v1 = (TVertex)edge.face.generator; - var v2 = (TVertex)twin.face.generator; - - double dir = predicates.CounterClockwise(v1, v2, edge.origin); - - if (dir <= 0) - { - HandleCase1(edge, v1, v2); - } - else - { - HandleCase2(edge, v1, v2); - } - } - } - - /// - /// Case 1: edge origin lies inside the domain. - /// - private void HandleCase1(HalfEdge edge, TVertex v1, TVertex v2) - { - //int mark = GetBoundaryMark(v1); - - // The infinite vertex. - var v = (Point)edge.twin.origin; - - // The half-edge is the bisector of v1 and v2, so the projection onto the - // boundary segment is actually its midpoint. - v.x = (v1.x + v2.x) / 2.0; - v.y = (v1.y + v2.y) / 2.0; - - // Close the cell connected to edge. - var gen = factory.CreateVertex(v1.x, v1.y); - - var h1 = factory.CreateHalfEdge(edge.twin.origin, edge.face); - var h2 = factory.CreateHalfEdge(gen, edge.face); - - edge.next = h1; - h1.next = h2; - h2.next = edge.face.edge; - - gen.leaving = h2; - - // Let the face edge point to the edge leaving at generator. - edge.face.edge = h2; - - base.edges.Add(h1); - base.edges.Add(h2); - - int count = base.edges.Count; - - h1.id = count; - h2.id = count + 1; - - gen.id = offset++; - base.vertices.Add(gen); - } - - /// - /// Case 2: edge origin lies outside the domain. - /// - private void HandleCase2(HalfEdge edge, TVertex v1, TVertex v2) - { - // The vertices of the infinite edge. - var p1 = (Point)edge.origin; - var p2 = (Point)edge.twin.origin; - - // The two edges leaving p1, pointing into the mesh. - var e1 = edge.twin.next; - var e2 = e1.twin.next; - - // Find the two intersections with boundary edge. - IntersectionHelper.IntersectSegments(v1, v2, e1.origin, e1.twin.origin, ref p2); - IntersectionHelper.IntersectSegments(v1, v2, e2.origin, e2.twin.origin, ref p1); - - // The infinite edge will now lie on the boundary. Update pointers: - e1.twin.next = edge.twin; - edge.twin.next = e2; - edge.twin.face = e2.face; - - e1.origin = edge.twin.origin; - - edge.twin.twin = null; - edge.twin = null; - - // Close the cell. - var gen = factory.CreateVertex(v1.x, v1.y); - var he = factory.CreateHalfEdge(gen, edge.face); - - edge.next = he; - he.next = edge.face.edge; - - // Let the face edge point to the edge leaving at generator. - edge.face.edge = he; - - base.edges.Add(he); - - he.id = base.edges.Count; - - gen.id = offset++; - base.vertices.Add(gen); - } - - /* - private int GetBoundaryMark(Vertex v) - { - Otri tri = default(Otri); - Otri next = default(Otri); - Osub seg = default(Osub); - - // Get triangle connected to generator. - v.tri.Copy(ref tri); - v.tri.Copy(ref next); - - // Find boundary triangle. - while (next.triangle.id != -1) - { - next.Copy(ref tri); - next.OnextSelf(); - } - - // Find edge dual to current half-edge. - tri.LnextSelf(); - tri.LnextSelf(); - - tri.SegPivot(ref seg); - - return seg.seg.boundary; - } - //*/ - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Voronoi +{ + using TriangleNet.Geometry; + using TriangleNet.Tools; + using TriangleNet.Topology.DCEL; + + using TVertex = TriangleNet.Geometry.Vertex; + + public class BoundedVoronoi : VoronoiBase + { + int offset; + + public BoundedVoronoi(Mesh mesh) + : this(mesh, new DefaultVoronoiFactory(), RobustPredicates.Default) + { + } + + public BoundedVoronoi(Mesh mesh, IVoronoiFactory factory, IPredicates predicates) + : base(mesh, factory, predicates, true) + { + // We explicitly told the base constructor to call the Generate method, so + // at this point the basic Voronoi diagram is already created. + offset = base.vertices.Count; + + // Each vertex of the hull will be part of a Voronoi cell. + base.vertices.Capacity = offset + mesh.hullsize; + + // Create bounded Voronoi diagram. + PostProcess(); + + ResolveBoundaryEdges(); + } + + /// + /// Computes edge intersections with mesh boundary edges. + /// + private void PostProcess() + { + foreach (var edge in rays) + { + var twin = edge.twin; + + var v1 = (TVertex)edge.face.generator; + var v2 = (TVertex)twin.face.generator; + + double dir = predicates.CounterClockwise(v1, v2, edge.origin); + + if (dir <= 0) + { + HandleCase1(edge, v1, v2); + } + else + { + HandleCase2(edge, v1, v2); + } + } + } + + /// + /// Case 1: edge origin lies inside the domain. + /// + private void HandleCase1(HalfEdge edge, TVertex v1, TVertex v2) + { + //int mark = GetBoundaryMark(v1); + + // The infinite vertex. + var v = (Point)edge.twin.origin; + + // The half-edge is the bisector of v1 and v2, so the projection onto the + // boundary segment is actually its midpoint. + v.x = (v1.x + v2.x) / 2.0; + v.y = (v1.y + v2.y) / 2.0; + + // Close the cell connected to edge. + var gen = factory.CreateVertex(v1.x, v1.y); + + var h1 = factory.CreateHalfEdge(edge.twin.origin, edge.face); + var h2 = factory.CreateHalfEdge(gen, edge.face); + + edge.next = h1; + h1.next = h2; + h2.next = edge.face.edge; + + gen.leaving = h2; + + // Let the face edge point to the edge leaving at generator. + edge.face.edge = h2; + + base.edges.Add(h1); + base.edges.Add(h2); + + int count = base.edges.Count; + + h1.id = count; + h2.id = count + 1; + + gen.id = offset++; + base.vertices.Add(gen); + } + + /// + /// Case 2: edge origin lies outside the domain. + /// + private void HandleCase2(HalfEdge edge, TVertex v1, TVertex v2) + { + // The vertices of the infinite edge. + var p1 = (Point)edge.origin; + var p2 = (Point)edge.twin.origin; + + // The two edges leaving p1, pointing into the mesh. + var e1 = edge.twin.next; + var e2 = e1.twin.next; + + // Check if the neighboring cell was closed before. + if (edge.twin.id != edge.twin.face.edge.id) + { + edge.twin.face.edge.next = e1; + } + else + { + // If the cell isn't closed yet, make sure to update the faces edge pointer. + e1.face.edge = e1; + } + + // Find the two intersections with boundary edge. + IntersectionHelper.IntersectSegments(v1, v2, e1.origin, e1.twin.origin, ref p2); + IntersectionHelper.IntersectSegments(v1, v2, e2.origin, e2.twin.origin, ref p1); + + // The infinite edge will now lie on the boundary. Update pointers: + e1.twin.next = edge.twin; + edge.twin.next = e2; + edge.twin.face = e2.face; + + e1.origin = edge.twin.origin; + + // Dissolve edge from other edges (origin and face stay the same). + edge.twin.twin = null; + edge.twin = null; + + // Close the cell. + var gen = factory.CreateVertex(v1.x, v1.y); + var he = factory.CreateHalfEdge(gen, edge.face); + + gen.leaving = he; + + edge.next = he; + he.next = edge.face.edge; + e2.twin.next = edge; + + // Let the face edge point to the edge leaving at generator. + edge.face.edge = he; + + base.edges.Add(he); + + he.id = base.edges.Count; + + gen.id = offset++; + base.vertices.Add(gen); + } + + /* + private int GetBoundaryMark(Vertex v) + { + Otri tri = default(Otri); + Otri next = default(Otri); + Osub seg = default(Osub); + + // Get triangle connected to generator. + v.tri.Copy(ref tri); + v.tri.Copy(ref next); + + // Find boundary triangle. + while (next.triangle.id != -1) + { + next.Copy(ref tri); + next.OnextSelf(); + } + + // Find edge dual to current half-edge. + tri.LnextSelf(); + tri.LnextSelf(); + + tri.SegPivot(ref seg); + + return seg.seg.boundary; + } + //*/ + } +} diff --git a/Triangle.NET/Triangle/Voronoi/DefaultVoronoiFactory.cs b/src/Triangle/Voronoi/DefaultVoronoiFactory.cs similarity index 95% rename from Triangle.NET/Triangle/Voronoi/DefaultVoronoiFactory.cs rename to src/Triangle/Voronoi/DefaultVoronoiFactory.cs index 6702ff0..c1773e6 100644 --- a/Triangle.NET/Triangle/Voronoi/DefaultVoronoiFactory.cs +++ b/src/Triangle/Voronoi/DefaultVoronoiFactory.cs @@ -1,35 +1,35 @@ - -namespace TriangleNet.Voronoi -{ - using System; - using TriangleNet.Topology.DCEL; - - /// - /// Default factory for Voronoi / DCEL mesh objects. - /// - public class DefaultVoronoiFactory : IVoronoiFactory - { - public void Initialize(int vertexCount, int edgeCount, int faceCount) - { - } - - public void Reset() - { - } - - public Vertex CreateVertex(double x, double y) - { - return new Vertex(x, y); - } - - public HalfEdge CreateHalfEdge(Vertex origin, Face face) - { - return new HalfEdge(origin, face); - } - - public Face CreateFace(Geometry.Vertex vertex) - { - return new Face(vertex); - } - } -} + +namespace TriangleNet.Voronoi +{ + using System; + using TriangleNet.Topology.DCEL; + + /// + /// Default factory for Voronoi / DCEL mesh objects. + /// + public class DefaultVoronoiFactory : IVoronoiFactory + { + public void Initialize(int vertexCount, int edgeCount, int faceCount) + { + } + + public void Reset() + { + } + + public Vertex CreateVertex(double x, double y) + { + return new Vertex(x, y); + } + + public HalfEdge CreateHalfEdge(Vertex origin, Face face) + { + return new HalfEdge(origin, face); + } + + public Face CreateFace(Geometry.Vertex vertex) + { + return new Face(vertex); + } + } +} diff --git a/Triangle.NET/Triangle/Voronoi/IVoronoiFactory.cs b/src/Triangle/Voronoi/IVoronoiFactory.cs similarity index 95% rename from Triangle.NET/Triangle/Voronoi/IVoronoiFactory.cs rename to src/Triangle/Voronoi/IVoronoiFactory.cs index 47276e0..476de74 100644 --- a/Triangle.NET/Triangle/Voronoi/IVoronoiFactory.cs +++ b/src/Triangle/Voronoi/IVoronoiFactory.cs @@ -1,18 +1,18 @@ - -namespace TriangleNet.Voronoi -{ - using TriangleNet.Topology.DCEL; - - public interface IVoronoiFactory - { - void Initialize(int vertexCount, int edgeCount, int faceCount); - - void Reset(); - - Vertex CreateVertex(double x, double y); - - HalfEdge CreateHalfEdge(Vertex origin, Face face); - - Face CreateFace(Geometry.Vertex vertex); - } -} + +namespace TriangleNet.Voronoi +{ + using TriangleNet.Topology.DCEL; + + public interface IVoronoiFactory + { + void Initialize(int vertexCount, int edgeCount, int faceCount); + + void Reset(); + + Vertex CreateVertex(double x, double y); + + HalfEdge CreateHalfEdge(Vertex origin, Face face); + + Face CreateFace(Geometry.Vertex vertex); + } +} diff --git a/Triangle.NET/Triangle/Voronoi/Legacy/BoundedVoronoiLegacy.cs b/src/Triangle/Voronoi/Legacy/BoundedVoronoiLegacy.cs similarity index 96% rename from Triangle.NET/Triangle/Voronoi/Legacy/BoundedVoronoiLegacy.cs rename to src/Triangle/Voronoi/Legacy/BoundedVoronoiLegacy.cs index 643fa0b..c574bcb 100644 --- a/Triangle.NET/Triangle/Voronoi/Legacy/BoundedVoronoiLegacy.cs +++ b/src/Triangle/Voronoi/Legacy/BoundedVoronoiLegacy.cs @@ -1,692 +1,692 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Voronoi.Legacy -{ - using System; - using System.Collections.Generic; - using TriangleNet.Topology; - using TriangleNet.Geometry; - - /// - /// The Bounded Voronoi Diagram is the dual of a PSLG triangulation. - /// - /// - /// 2D Centroidal Voronoi Tessellations with Constraints, 2010, - /// Jane Tournois, Pierre Alliez and Olivier Devillers - /// - [Obsolete("Use TriangleNet.Voronoi.BoundedVoronoi class instead.")] - public class BoundedVoronoiLegacy : IVoronoi - { - IPredicates predicates = RobustPredicates.Default; - - Mesh mesh; - - Point[] points; - List regions; - - // Used for new points on segments. - List segPoints; - int segIndex; - - Dictionary subsegMap; - - bool includeBoundary = true; - - /// - /// Initializes a new instance of the class. - /// - /// Mesh instance. - public BoundedVoronoiLegacy(Mesh mesh) - : this(mesh, true) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Mesh instance. - public BoundedVoronoiLegacy(Mesh mesh, bool includeBoundary) - { - this.mesh = mesh; - this.includeBoundary = includeBoundary; - - Generate(); - } - - /// - /// Gets the list of Voronoi vertices. - /// - public Point[] Points - { - get { return points; } - } - - /// - /// Gets the list of Voronoi regions. - /// - public ICollection Regions - { - get { return regions; } - } - - public IEnumerable Edges - { - get { return EnumerateEdges(); } - } - - /// - /// Computes the bounded voronoi diagram. - /// - private void Generate() - { - mesh.Renumber(); - mesh.MakeVertexMap(); - - // Allocate space for voronoi diagram - this.regions = new List(mesh.vertices.Count); - - this.points = new Point[mesh.triangles.Count]; - this.segPoints = new List(mesh.subsegs.Count * 4); - - ComputeCircumCenters(); - - TagBlindTriangles(); - - foreach (var v in mesh.vertices.Values) - { - // TODO: Need a reliable way to check if a vertex is on a segment - if (v.type == VertexType.FreeVertex || v.label == 0) - { - ConstructCell(v); - } - else if (includeBoundary) - { - ConstructBoundaryCell(v); - } - } - - // Add the new points on segments to the point array. - int length = points.Length; - - Array.Resize(ref points, length + segPoints.Count); - - for (int i = 0; i < segPoints.Count; i++) - { - points[length + i] = segPoints[i]; - } - - this.segPoints.Clear(); - this.segPoints = null; - } - - private void ComputeCircumCenters() - { - Otri tri = default(Otri); - double xi = 0, eta = 0; - Point pt; - - // Compue triangle circumcenters - foreach (var item in mesh.triangles) - { - tri.tri = item; - - pt = predicates.FindCircumcenter(tri.Org(), tri.Dest(), tri.Apex(), ref xi, ref eta); - pt.id = item.id; - - points[item.id] = pt; - } - } - - /// - /// Tag all blind triangles. - /// - /// - /// A triangle is said to be blind if the triangle and its circumcenter - /// lie on two different sides of a constrained edge. - /// - private void TagBlindTriangles() - { - int blinded = 0; - - Stack triangles; - subsegMap = new Dictionary(); - - Otri f = default(Otri); - Otri f0 = default(Otri); - Osub e = default(Osub); - Osub sub1 = default(Osub); - - // Tag all triangles non-blind - foreach (var t in mesh.triangles) - { - // Use the infected flag for 'blinded' attribute. - t.infected = false; - } - - // for each constrained edge e of cdt do - foreach (var ss in mesh.subsegs.Values) - { - // Create a stack: triangles - triangles = new Stack(); - - // for both adjacent triangles fe to e tagged non-blind do - // Push fe into triangles - e.seg = ss; - e.orient = 0; - e.Pivot(ref f); - - if (f.tri.id != Mesh.DUMMY && !f.tri.infected) - { - triangles.Push(f.tri); - } - - e.Sym(); - e.Pivot(ref f); - - if (f.tri.id != Mesh.DUMMY && !f.tri.infected) - { - triangles.Push(f.tri); - } - - // while triangles is non-empty - while (triangles.Count > 0) - { - // Pop f from stack triangles - f.tri = triangles.Pop(); - f.orient = 0; - - // if f is blinded by e (use P) then - if (TriangleIsBlinded(ref f, ref e)) - { - // Tag f as blinded by e - f.tri.infected = true; - blinded++; - - // Store association triangle -> subseg - subsegMap.Add(f.tri.hash, e.seg); - - // for each adjacent triangle f0 to f do - for (f.orient = 0; f.orient < 3; f.orient++) - { - f.Sym(ref f0); - - f0.Pivot(ref sub1); - - // if f0 is finite and tagged non-blind & the common edge - // between f and f0 is unconstrained then - if (f0.tri.id != Mesh.DUMMY && !f0.tri.infected && sub1.seg.hash == Mesh.DUMMY) - { - // Push f0 into triangles. - triangles.Push(f0.tri); - } - } - } - } - } - - blinded = 0; - } - - /// - /// Check if given triangle is blinded by given segment. - /// - /// Triangle. - /// Segments - /// Returns true, if the triangle is blinded. - private bool TriangleIsBlinded(ref Otri tri, ref Osub seg) - { - Point c, pt; - - Vertex torg = tri.Org(); - Vertex tdest = tri.Dest(); - Vertex tapex = tri.Apex(); - - Vertex sorg = seg.Org(); - Vertex sdest = seg.Dest(); - - c = this.points[tri.tri.id]; - - if (SegmentsIntersect(sorg, sdest, c, torg, out pt, true)) - { - return true; - } - - if (SegmentsIntersect(sorg, sdest, c, tdest, out pt, true)) - { - return true; - } - - if (SegmentsIntersect(sorg, sdest, c, tapex, out pt, true)) - { - return true; - } - - return false; - } - - private void ConstructCell(Vertex vertex) - { - VoronoiRegion region = new VoronoiRegion(vertex); - regions.Add(region); - - Otri f = default(Otri); - Otri f_init = default(Otri); - Otri f_next = default(Otri); - Osub sf = default(Osub); - Osub sfn = default(Osub); - - Point cc_f, cc_f_next, p; - - int n = mesh.triangles.Count; - - // Call P the polygon (cell) in construction - List vpoints = new List(); - - // Call f_init a triangle incident to x - vertex.tri.Copy(ref f_init); - - if (f_init.Org() != vertex) - { - throw new Exception("ConstructCell: inconsistent topology."); - } - - // Let f be initialized to f_init - f_init.Copy(ref f); - // Call f_next the next triangle counterclockwise around x - f_init.Onext(ref f_next); - - // repeat ... until f = f_init - do - { - // Call Lffnext the line going through the circumcenters of f and f_next - cc_f = this.points[f.tri.id]; - cc_f_next = this.points[f_next.tri.id]; - - // if f is tagged non-blind then - if (!f.tri.infected) - { - // Insert the circumcenter of f into P - vpoints.Add(cc_f); - - if (f_next.tri.infected) - { - // Call S_fnext the constrained edge blinding f_next - sfn.seg = subsegMap[f_next.tri.hash]; - - // Insert point Lf,f_next /\ Sf_next into P - if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true)) - { - p.id = n + segIndex++; - segPoints.Add(p); - vpoints.Add(p); - } - } - } - else - { - // Call Sf the constrained edge blinding f - sf.seg = subsegMap[f.tri.hash]; - - // if f_next is tagged non-blind then - if (!f_next.tri.infected) - { - // Insert point Lf,f_next /\ Sf into P - if (SegmentsIntersect(sf.Org(), sf.Dest(), cc_f, cc_f_next, out p, true)) - { - p.id = n + segIndex++; - segPoints.Add(p); - vpoints.Add(p); - } - } - else - { - // Call Sf_next the constrained edge blinding f_next - sfn.seg = subsegMap[f_next.tri.hash]; - - // if Sf != Sf_next then - if (!sf.Equal(sfn)) - { - // Insert Lf,fnext /\ Sf and Lf,fnext /\ Sfnext into P - if (SegmentsIntersect(sf.Org(), sf.Dest(), cc_f, cc_f_next, out p, true)) - { - p.id = n + segIndex++; - segPoints.Add(p); - vpoints.Add(p); - } - - if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true)) - { - p.id = n + segIndex++; - segPoints.Add(p); - vpoints.Add(p); - } - } - } - } - - // f <- f_next - f_next.Copy(ref f); - - // Call f_next the next triangle counterclockwise around x - f_next.Onext(); - } - while (!f.Equals(f_init)); - - // Output: Bounded Voronoi cell of x in counterclockwise order. - region.Add(vpoints); - } - - private void ConstructBoundaryCell(Vertex vertex) - { - VoronoiRegion region = new VoronoiRegion(vertex); - regions.Add(region); - - Otri f = default(Otri); - Otri f_init = default(Otri); - Otri f_next = default(Otri); - Otri f_prev = default(Otri); - Osub sf = default(Osub); - Osub sfn = default(Osub); - - Vertex torg, tdest, tapex, sorg, sdest; - Point cc_f, cc_f_next, p; - - int n = mesh.triangles.Count; - - // Call P the polygon (cell) in construction - List vpoints = new List(); - - // Call f_init a triangle incident to x - vertex.tri.Copy(ref f_init); - - if (f_init.Org() != vertex) - { - throw new Exception("ConstructBoundaryCell: inconsistent topology."); - } - // Let f be initialized to f_init - f_init.Copy(ref f); - // Call f_next the next triangle counterclockwise around x - f_init.Onext(ref f_next); - - f_init.Oprev(ref f_prev); - - // Is the border to the left? - if (f_prev.tri.id != Mesh.DUMMY) - { - // Go clockwise until we reach the border (or the initial triangle) - while (f_prev.tri.id != Mesh.DUMMY && !f_prev.Equals(f_init)) - { - f_prev.Copy(ref f); - f_prev.Oprev(); - } - - f.Copy(ref f_init); - f.Onext(ref f_next); - } - - if (f_prev.tri.id == Mesh.DUMMY) - { - // For vertices on the domain boundaray, add the vertex. For - // internal boundaries don't add it. - p = new Point(vertex.x, vertex.y); - - p.id = n + segIndex++; - segPoints.Add(p); - vpoints.Add(p); - } - - // Add midpoint of start triangles' edge. - torg = f.Org(); - tdest = f.Dest(); - p = new Point((torg.x + tdest.x) / 2, (torg.y + tdest.y) / 2); - - p.id = n + segIndex++; - segPoints.Add(p); - vpoints.Add(p); - - // repeat ... until f = f_init - do - { - // Call Lffnext the line going through the circumcenters of f and f_next - cc_f = this.points[f.tri.id]; - - if (f_next.tri.id == Mesh.DUMMY) - { - if (!f.tri.infected) - { - // Add last circumcenter - vpoints.Add(cc_f); - } - - // Add midpoint of last triangles' edge (chances are it has already - // been added, so post process cell to remove duplicates???) - torg = f.Org(); - tapex = f.Apex(); - p = new Point((torg.x + tapex.x) / 2, (torg.y + tapex.y) / 2); - - p.id = n + segIndex++; - segPoints.Add(p); - vpoints.Add(p); - - break; - } - - cc_f_next = this.points[f_next.tri.id]; - - // if f is tagged non-blind then - if (!f.tri.infected) - { - // Insert the circumcenter of f into P - vpoints.Add(cc_f); - - if (f_next.tri.infected) - { - // Call S_fnext the constrained edge blinding f_next - sfn.seg = subsegMap[f_next.tri.hash]; - - // Insert point Lf,f_next /\ Sf_next into P - if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true)) - { - p.id = n + segIndex++; - segPoints.Add(p); - vpoints.Add(p); - } - } - } - else - { - // Call Sf the constrained edge blinding f - sf.seg = subsegMap[f.tri.hash]; - - sorg = sf.Org(); - sdest = sf.Dest(); - - // if f_next is tagged non-blind then - if (!f_next.tri.infected) - { - tdest = f.Dest(); - tapex = f.Apex(); - - // Both circumcenters lie on the blinded side, but we - // have to add the intersection with the segment. - - // Center of f edge dest->apex - Point bisec = new Point((tdest.x + tapex.x) / 2, (tdest.y + tapex.y) / 2); - - // Find intersection of seg with line through f's bisector and circumcenter - if (SegmentsIntersect(sorg, sdest, bisec, cc_f, out p, false)) - { - p.id = n + segIndex++; - segPoints.Add(p); - vpoints.Add(p); - } - - // Insert point Lf,f_next /\ Sf into P - if (SegmentsIntersect(sorg, sdest, cc_f, cc_f_next, out p, true)) - { - p.id = n + segIndex++; - segPoints.Add(p); - vpoints.Add(p); - } - } - else - { - // Call Sf_next the constrained edge blinding f_next - sfn.seg = subsegMap[f_next.tri.hash]; - - // if Sf != Sf_next then - if (!sf.Equal(sfn)) - { - // Insert Lf,fnext /\ Sf and Lf,fnext /\ Sfnext into P - if (SegmentsIntersect(sorg, sdest, cc_f, cc_f_next, out p, true)) - { - p.id = n + segIndex++; - segPoints.Add(p); - vpoints.Add(p); - } - - if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true)) - { - p.id = n + segIndex++; - segPoints.Add(p); - vpoints.Add(p); - } - } - else - { - // Both circumcenters lie on the blinded side, but we - // have to add the intersection with the segment. - - // Center of f_next edge org->dest - Point bisec = new Point((torg.x + tdest.x) / 2, (torg.y + tdest.y) / 2); - - // Find intersection of seg with line through f_next's bisector and circumcenter - if (SegmentsIntersect(sorg, sdest, bisec, cc_f_next, out p, false)) - { - p.id = n + segIndex++; - segPoints.Add(p); - vpoints.Add(p); - } - } - } - } - - // f <- f_next - f_next.Copy(ref f); - - // Call f_next the next triangle counterclockwise around x - f_next.Onext(); - } - while (!f.Equals(f_init)); - - // Output: Bounded Voronoi cell of x in counterclockwise order. - region.Add(vpoints); - } - - /// - /// Determines the intersection point of the line segment defined by points A and B with the - /// line segment defined by points C and D. - /// - /// The first segment AB. - /// Endpoint C of second segment. - /// Endpoint D of second segment. - /// Reference to the intersection point. - /// If false, pa and pb represent a line. - /// Returns true if the intersection point was found, and stores that point in X,Y. - /// Returns false if there is no determinable intersection point, in which case X,Y will - /// be unmodified. - /// - private bool SegmentsIntersect(Point p1, Point p2, Point p3, Point p4, out Point p, bool strictIntersect) - { - p = null; // TODO: Voronoi SegmentsIntersect - - double Ax = p1.x, Ay = p1.y; - double Bx = p2.x, By = p2.y; - double Cx = p3.x, Cy = p3.y; - double Dx = p4.x, Dy = p4.y; - - double distAB, theCos, theSin, newX, ABpos; - - // Fail if either line segment is zero-length. - if (Ax == Bx && Ay == By || Cx == Dx && Cy == Dy) return false; - - // Fail if the segments share an end-point. - if (Ax == Cx && Ay == Cy || Bx == Cx && By == Cy - || Ax == Dx && Ay == Dy || Bx == Dx && By == Dy) - { - return false; - } - - // (1) Translate the system so that point A is on the origin. - Bx -= Ax; By -= Ay; - Cx -= Ax; Cy -= Ay; - Dx -= Ax; Dy -= Ay; - - // Discover the length of segment A-B. - distAB = Math.Sqrt(Bx * Bx + By * By); - - // (2) Rotate the system so that point B is on the positive X axis. - theCos = Bx / distAB; - theSin = By / distAB; - newX = Cx * theCos + Cy * theSin; - Cy = Cy * theCos - Cx * theSin; Cx = newX; - newX = Dx * theCos + Dy * theSin; - Dy = Dy * theCos - Dx * theSin; Dx = newX; - - // Fail if segment C-D doesn't cross line A-B. - if (Cy < 0 && Dy < 0 || Cy >= 0 && Dy >= 0 && strictIntersect) return false; - - // (3) Discover the position of the intersection point along line A-B. - ABpos = Dx + (Cx - Dx) * Dy / (Dy - Cy); - - // Fail if segment C-D crosses line A-B outside of segment A-B. - if (ABpos < 0 || ABpos > distAB && strictIntersect) return false; - - // (4) Apply the discovered position to line A-B in the original coordinate system. - p = new Point(Ax + ABpos * theCos, Ay + ABpos * theSin); - - // Success. - return true; - } - - // TODO: Voronoi enumerate edges - - private IEnumerable EnumerateEdges() - { - // Copy edges - Point first, last; - var edges = new List(this.Regions.Count * 2); - foreach (var region in this.Regions) - { - first = null; - last = null; - - foreach (var pt in region.Vertices) - { - if (first == null) - { - first = pt; - last = pt; - } - else - { - edges.Add(new Edge(last.id, pt.id)); - - last = pt; - } - } - - if (region.Bounded && first != null) - { - edges.Add(new Edge(last.id, first.id)); - } - } - - return edges; - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Voronoi.Legacy +{ + using System; + using System.Collections.Generic; + using TriangleNet.Topology; + using TriangleNet.Geometry; + + /// + /// The Bounded Voronoi Diagram is the dual of a PSLG triangulation. + /// + /// + /// 2D Centroidal Voronoi Tessellations with Constraints, 2010, + /// Jane Tournois, Pierre Alliez and Olivier Devillers + /// + [Obsolete("Use TriangleNet.Voronoi.BoundedVoronoi class instead.")] + public class BoundedVoronoiLegacy : IVoronoi + { + IPredicates predicates = RobustPredicates.Default; + + Mesh mesh; + + Point[] points; + List regions; + + // Used for new points on segments. + List segPoints; + int segIndex; + + Dictionary subsegMap; + + bool includeBoundary = true; + + /// + /// Initializes a new instance of the class. + /// + /// Mesh instance. + public BoundedVoronoiLegacy(Mesh mesh) + : this(mesh, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Mesh instance. + public BoundedVoronoiLegacy(Mesh mesh, bool includeBoundary) + { + this.mesh = mesh; + this.includeBoundary = includeBoundary; + + Generate(); + } + + /// + /// Gets the list of Voronoi vertices. + /// + public Point[] Points + { + get { return points; } + } + + /// + /// Gets the list of Voronoi regions. + /// + public ICollection Regions + { + get { return regions; } + } + + public IEnumerable Edges + { + get { return EnumerateEdges(); } + } + + /// + /// Computes the bounded voronoi diagram. + /// + private void Generate() + { + mesh.Renumber(); + mesh.MakeVertexMap(); + + // Allocate space for voronoi diagram + this.regions = new List(mesh.vertices.Count); + + this.points = new Point[mesh.triangles.Count]; + this.segPoints = new List(mesh.subsegs.Count * 4); + + ComputeCircumCenters(); + + TagBlindTriangles(); + + foreach (var v in mesh.vertices.Values) + { + // TODO: Need a reliable way to check if a vertex is on a segment + if (v.type == VertexType.FreeVertex || v.label == 0) + { + ConstructCell(v); + } + else if (includeBoundary) + { + ConstructBoundaryCell(v); + } + } + + // Add the new points on segments to the point array. + int length = points.Length; + + Array.Resize(ref points, length + segPoints.Count); + + for (int i = 0; i < segPoints.Count; i++) + { + points[length + i] = segPoints[i]; + } + + this.segPoints.Clear(); + this.segPoints = null; + } + + private void ComputeCircumCenters() + { + Otri tri = default(Otri); + double xi = 0, eta = 0; + Point pt; + + // Compue triangle circumcenters + foreach (var item in mesh.triangles) + { + tri.tri = item; + + pt = predicates.FindCircumcenter(tri.Org(), tri.Dest(), tri.Apex(), ref xi, ref eta); + pt.id = item.id; + + points[item.id] = pt; + } + } + + /// + /// Tag all blind triangles. + /// + /// + /// A triangle is said to be blind if the triangle and its circumcenter + /// lie on two different sides of a constrained edge. + /// + private void TagBlindTriangles() + { + int blinded = 0; + + Stack triangles; + subsegMap = new Dictionary(); + + Otri f = default(Otri); + Otri f0 = default(Otri); + Osub e = default(Osub); + Osub sub1 = default(Osub); + + // Tag all triangles non-blind + foreach (var t in mesh.triangles) + { + // Use the infected flag for 'blinded' attribute. + t.infected = false; + } + + // for each constrained edge e of cdt do + foreach (var ss in mesh.subsegs.Values) + { + // Create a stack: triangles + triangles = new Stack(); + + // for both adjacent triangles fe to e tagged non-blind do + // Push fe into triangles + e.seg = ss; + e.orient = 0; + e.Pivot(ref f); + + if (f.tri.id != Mesh.DUMMY && !f.tri.infected) + { + triangles.Push(f.tri); + } + + e.Sym(); + e.Pivot(ref f); + + if (f.tri.id != Mesh.DUMMY && !f.tri.infected) + { + triangles.Push(f.tri); + } + + // while triangles is non-empty + while (triangles.Count > 0) + { + // Pop f from stack triangles + f.tri = triangles.Pop(); + f.orient = 0; + + // if f is blinded by e (use P) then + if (TriangleIsBlinded(ref f, ref e)) + { + // Tag f as blinded by e + f.tri.infected = true; + blinded++; + + // Store association triangle -> subseg + subsegMap.Add(f.tri.hash, e.seg); + + // for each adjacent triangle f0 to f do + for (f.orient = 0; f.orient < 3; f.orient++) + { + f.Sym(ref f0); + + f0.Pivot(ref sub1); + + // if f0 is finite and tagged non-blind & the common edge + // between f and f0 is unconstrained then + if (f0.tri.id != Mesh.DUMMY && !f0.tri.infected && sub1.seg.hash == Mesh.DUMMY) + { + // Push f0 into triangles. + triangles.Push(f0.tri); + } + } + } + } + } + + blinded = 0; + } + + /// + /// Check if given triangle is blinded by given segment. + /// + /// Triangle. + /// Segments + /// Returns true, if the triangle is blinded. + private bool TriangleIsBlinded(ref Otri tri, ref Osub seg) + { + Point c, pt; + + Vertex torg = tri.Org(); + Vertex tdest = tri.Dest(); + Vertex tapex = tri.Apex(); + + Vertex sorg = seg.Org(); + Vertex sdest = seg.Dest(); + + c = this.points[tri.tri.id]; + + if (SegmentsIntersect(sorg, sdest, c, torg, out pt, true)) + { + return true; + } + + if (SegmentsIntersect(sorg, sdest, c, tdest, out pt, true)) + { + return true; + } + + if (SegmentsIntersect(sorg, sdest, c, tapex, out pt, true)) + { + return true; + } + + return false; + } + + private void ConstructCell(Vertex vertex) + { + VoronoiRegion region = new VoronoiRegion(vertex); + regions.Add(region); + + Otri f = default(Otri); + Otri f_init = default(Otri); + Otri f_next = default(Otri); + Osub sf = default(Osub); + Osub sfn = default(Osub); + + Point cc_f, cc_f_next, p; + + int n = mesh.triangles.Count; + + // Call P the polygon (cell) in construction + List vpoints = new List(); + + // Call f_init a triangle incident to x + vertex.tri.Copy(ref f_init); + + if (f_init.Org() != vertex) + { + throw new Exception("ConstructCell: inconsistent topology."); + } + + // Let f be initialized to f_init + f_init.Copy(ref f); + // Call f_next the next triangle counterclockwise around x + f_init.Onext(ref f_next); + + // repeat ... until f = f_init + do + { + // Call Lffnext the line going through the circumcenters of f and f_next + cc_f = this.points[f.tri.id]; + cc_f_next = this.points[f_next.tri.id]; + + // if f is tagged non-blind then + if (!f.tri.infected) + { + // Insert the circumcenter of f into P + vpoints.Add(cc_f); + + if (f_next.tri.infected) + { + // Call S_fnext the constrained edge blinding f_next + sfn.seg = subsegMap[f_next.tri.hash]; + + // Insert point Lf,f_next /\ Sf_next into P + if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true)) + { + p.id = n + segIndex++; + segPoints.Add(p); + vpoints.Add(p); + } + } + } + else + { + // Call Sf the constrained edge blinding f + sf.seg = subsegMap[f.tri.hash]; + + // if f_next is tagged non-blind then + if (!f_next.tri.infected) + { + // Insert point Lf,f_next /\ Sf into P + if (SegmentsIntersect(sf.Org(), sf.Dest(), cc_f, cc_f_next, out p, true)) + { + p.id = n + segIndex++; + segPoints.Add(p); + vpoints.Add(p); + } + } + else + { + // Call Sf_next the constrained edge blinding f_next + sfn.seg = subsegMap[f_next.tri.hash]; + + // if Sf != Sf_next then + if (!sf.Equal(sfn)) + { + // Insert Lf,fnext /\ Sf and Lf,fnext /\ Sfnext into P + if (SegmentsIntersect(sf.Org(), sf.Dest(), cc_f, cc_f_next, out p, true)) + { + p.id = n + segIndex++; + segPoints.Add(p); + vpoints.Add(p); + } + + if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true)) + { + p.id = n + segIndex++; + segPoints.Add(p); + vpoints.Add(p); + } + } + } + } + + // f <- f_next + f_next.Copy(ref f); + + // Call f_next the next triangle counterclockwise around x + f_next.Onext(); + } + while (!f.Equals(f_init)); + + // Output: Bounded Voronoi cell of x in counterclockwise order. + region.Add(vpoints); + } + + private void ConstructBoundaryCell(Vertex vertex) + { + VoronoiRegion region = new VoronoiRegion(vertex); + regions.Add(region); + + Otri f = default(Otri); + Otri f_init = default(Otri); + Otri f_next = default(Otri); + Otri f_prev = default(Otri); + Osub sf = default(Osub); + Osub sfn = default(Osub); + + Vertex torg, tdest, tapex, sorg, sdest; + Point cc_f, cc_f_next, p; + + int n = mesh.triangles.Count; + + // Call P the polygon (cell) in construction + List vpoints = new List(); + + // Call f_init a triangle incident to x + vertex.tri.Copy(ref f_init); + + if (f_init.Org() != vertex) + { + throw new Exception("ConstructBoundaryCell: inconsistent topology."); + } + // Let f be initialized to f_init + f_init.Copy(ref f); + // Call f_next the next triangle counterclockwise around x + f_init.Onext(ref f_next); + + f_init.Oprev(ref f_prev); + + // Is the border to the left? + if (f_prev.tri.id != Mesh.DUMMY) + { + // Go clockwise until we reach the border (or the initial triangle) + while (f_prev.tri.id != Mesh.DUMMY && !f_prev.Equals(f_init)) + { + f_prev.Copy(ref f); + f_prev.Oprev(); + } + + f.Copy(ref f_init); + f.Onext(ref f_next); + } + + if (f_prev.tri.id == Mesh.DUMMY) + { + // For vertices on the domain boundaray, add the vertex. For + // internal boundaries don't add it. + p = new Point(vertex.x, vertex.y); + + p.id = n + segIndex++; + segPoints.Add(p); + vpoints.Add(p); + } + + // Add midpoint of start triangles' edge. + torg = f.Org(); + tdest = f.Dest(); + p = new Point((torg.x + tdest.x) / 2, (torg.y + tdest.y) / 2); + + p.id = n + segIndex++; + segPoints.Add(p); + vpoints.Add(p); + + // repeat ... until f = f_init + do + { + // Call Lffnext the line going through the circumcenters of f and f_next + cc_f = this.points[f.tri.id]; + + if (f_next.tri.id == Mesh.DUMMY) + { + if (!f.tri.infected) + { + // Add last circumcenter + vpoints.Add(cc_f); + } + + // Add midpoint of last triangles' edge (chances are it has already + // been added, so post process cell to remove duplicates???) + torg = f.Org(); + tapex = f.Apex(); + p = new Point((torg.x + tapex.x) / 2, (torg.y + tapex.y) / 2); + + p.id = n + segIndex++; + segPoints.Add(p); + vpoints.Add(p); + + break; + } + + cc_f_next = this.points[f_next.tri.id]; + + // if f is tagged non-blind then + if (!f.tri.infected) + { + // Insert the circumcenter of f into P + vpoints.Add(cc_f); + + if (f_next.tri.infected) + { + // Call S_fnext the constrained edge blinding f_next + sfn.seg = subsegMap[f_next.tri.hash]; + + // Insert point Lf,f_next /\ Sf_next into P + if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true)) + { + p.id = n + segIndex++; + segPoints.Add(p); + vpoints.Add(p); + } + } + } + else + { + // Call Sf the constrained edge blinding f + sf.seg = subsegMap[f.tri.hash]; + + sorg = sf.Org(); + sdest = sf.Dest(); + + // if f_next is tagged non-blind then + if (!f_next.tri.infected) + { + tdest = f.Dest(); + tapex = f.Apex(); + + // Both circumcenters lie on the blinded side, but we + // have to add the intersection with the segment. + + // Center of f edge dest->apex + Point bisec = new Point((tdest.x + tapex.x) / 2, (tdest.y + tapex.y) / 2); + + // Find intersection of seg with line through f's bisector and circumcenter + if (SegmentsIntersect(sorg, sdest, bisec, cc_f, out p, false)) + { + p.id = n + segIndex++; + segPoints.Add(p); + vpoints.Add(p); + } + + // Insert point Lf,f_next /\ Sf into P + if (SegmentsIntersect(sorg, sdest, cc_f, cc_f_next, out p, true)) + { + p.id = n + segIndex++; + segPoints.Add(p); + vpoints.Add(p); + } + } + else + { + // Call Sf_next the constrained edge blinding f_next + sfn.seg = subsegMap[f_next.tri.hash]; + + // if Sf != Sf_next then + if (!sf.Equal(sfn)) + { + // Insert Lf,fnext /\ Sf and Lf,fnext /\ Sfnext into P + if (SegmentsIntersect(sorg, sdest, cc_f, cc_f_next, out p, true)) + { + p.id = n + segIndex++; + segPoints.Add(p); + vpoints.Add(p); + } + + if (SegmentsIntersect(sfn.Org(), sfn.Dest(), cc_f, cc_f_next, out p, true)) + { + p.id = n + segIndex++; + segPoints.Add(p); + vpoints.Add(p); + } + } + else + { + // Both circumcenters lie on the blinded side, but we + // have to add the intersection with the segment. + + // Center of f_next edge org->dest + Point bisec = new Point((torg.x + tdest.x) / 2, (torg.y + tdest.y) / 2); + + // Find intersection of seg with line through f_next's bisector and circumcenter + if (SegmentsIntersect(sorg, sdest, bisec, cc_f_next, out p, false)) + { + p.id = n + segIndex++; + segPoints.Add(p); + vpoints.Add(p); + } + } + } + } + + // f <- f_next + f_next.Copy(ref f); + + // Call f_next the next triangle counterclockwise around x + f_next.Onext(); + } + while (!f.Equals(f_init)); + + // Output: Bounded Voronoi cell of x in counterclockwise order. + region.Add(vpoints); + } + + /// + /// Determines the intersection point of the line segment defined by points A and B with the + /// line segment defined by points C and D. + /// + /// The first segment AB. + /// Endpoint C of second segment. + /// Endpoint D of second segment. + /// Reference to the intersection point. + /// If false, pa and pb represent a line. + /// Returns true if the intersection point was found, and stores that point in X,Y. + /// Returns false if there is no determinable intersection point, in which case X,Y will + /// be unmodified. + /// + private bool SegmentsIntersect(Point p1, Point p2, Point p3, Point p4, out Point p, bool strictIntersect) + { + p = null; // TODO: Voronoi SegmentsIntersect + + double Ax = p1.x, Ay = p1.y; + double Bx = p2.x, By = p2.y; + double Cx = p3.x, Cy = p3.y; + double Dx = p4.x, Dy = p4.y; + + double distAB, theCos, theSin, newX, ABpos; + + // Fail if either line segment is zero-length. + if (Ax == Bx && Ay == By || Cx == Dx && Cy == Dy) return false; + + // Fail if the segments share an end-point. + if (Ax == Cx && Ay == Cy || Bx == Cx && By == Cy + || Ax == Dx && Ay == Dy || Bx == Dx && By == Dy) + { + return false; + } + + // (1) Translate the system so that point A is on the origin. + Bx -= Ax; By -= Ay; + Cx -= Ax; Cy -= Ay; + Dx -= Ax; Dy -= Ay; + + // Discover the length of segment A-B. + distAB = Math.Sqrt(Bx * Bx + By * By); + + // (2) Rotate the system so that point B is on the positive X axis. + theCos = Bx / distAB; + theSin = By / distAB; + newX = Cx * theCos + Cy * theSin; + Cy = Cy * theCos - Cx * theSin; Cx = newX; + newX = Dx * theCos + Dy * theSin; + Dy = Dy * theCos - Dx * theSin; Dx = newX; + + // Fail if segment C-D doesn't cross line A-B. + if (Cy < 0 && Dy < 0 || Cy >= 0 && Dy >= 0 && strictIntersect) return false; + + // (3) Discover the position of the intersection point along line A-B. + ABpos = Dx + (Cx - Dx) * Dy / (Dy - Cy); + + // Fail if segment C-D crosses line A-B outside of segment A-B. + if (ABpos < 0 || ABpos > distAB && strictIntersect) return false; + + // (4) Apply the discovered position to line A-B in the original coordinate system. + p = new Point(Ax + ABpos * theCos, Ay + ABpos * theSin); + + // Success. + return true; + } + + // TODO: Voronoi enumerate edges + + private IEnumerable EnumerateEdges() + { + // Copy edges + Point first, last; + var edges = new List(this.Regions.Count * 2); + foreach (var region in this.Regions) + { + first = null; + last = null; + + foreach (var pt in region.Vertices) + { + if (first == null) + { + first = pt; + last = pt; + } + else + { + edges.Add(new Edge(last.id, pt.id)); + + last = pt; + } + } + + if (region.Bounded && first != null) + { + edges.Add(new Edge(last.id, first.id)); + } + } + + return edges; + } + } +} diff --git a/Triangle.NET/Triangle/Voronoi/Legacy/IVoronoi.cs b/src/Triangle/Voronoi/Legacy/IVoronoi.cs similarity index 88% rename from Triangle.NET/Triangle/Voronoi/Legacy/IVoronoi.cs rename to src/Triangle/Voronoi/Legacy/IVoronoi.cs index 3dd9dc3..d5b681d 100644 --- a/Triangle.NET/Triangle/Voronoi/Legacy/IVoronoi.cs +++ b/src/Triangle/Voronoi/Legacy/IVoronoi.cs @@ -1,32 +1,32 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Voronoi.Legacy -{ - using System.Collections.Generic; - using TriangleNet.Geometry; - - /// - /// Voronoi diagram interface. - /// - public interface IVoronoi - { - /// - /// Gets the list of Voronoi vertices. - /// - Point[] Points { get; } - - /// - /// Gets the list of Voronoi regions. - /// - ICollection Regions { get; } - - /// - /// Gets the list of edges. - /// - IEnumerable Edges { get; } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Voronoi.Legacy +{ + using System.Collections.Generic; + using TriangleNet.Geometry; + + /// + /// Voronoi diagram interface. + /// + public interface IVoronoi + { + /// + /// Gets the list of Voronoi vertices. + /// + Point[] Points { get; } + + /// + /// Gets the list of Voronoi regions. + /// + ICollection Regions { get; } + + /// + /// Gets the list of edges. + /// + IEnumerable Edges { get; } + } +} diff --git a/Triangle.NET/Triangle/Voronoi/Legacy/SimpleVoronoi.cs b/src/Triangle/Voronoi/Legacy/SimpleVoronoi.cs similarity index 95% rename from Triangle.NET/Triangle/Voronoi/Legacy/SimpleVoronoi.cs rename to src/Triangle/Voronoi/Legacy/SimpleVoronoi.cs index 59375b5..c9a5a49 100644 --- a/Triangle.NET/Triangle/Voronoi/Legacy/SimpleVoronoi.cs +++ b/src/Triangle/Voronoi/Legacy/SimpleVoronoi.cs @@ -1,306 +1,306 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Voronoi.Legacy -{ - using System; - using System.Collections.Generic; - using TriangleNet.Topology; - using TriangleNet.Geometry; - using TriangleNet.Tools; - - /// - /// The Voronoi Diagram is the dual of a pointset triangulation. - /// - [Obsolete("Use TriangleNet.Voronoi.StandardVoronoi class instead.")] - public class SimpleVoronoi : IVoronoi - { - IPredicates predicates = RobustPredicates.Default; - - Mesh mesh; - - Point[] points; - Dictionary regions; - - // Stores the endpoints of rays of unbounded Voronoi cells - Dictionary rayPoints; - int rayIndex; - - // Bounding box of the triangles circumcenters. - Rectangle bounds; - - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// Be sure MakeVertexMap has been called (should always be the case). - /// - public SimpleVoronoi(Mesh mesh) - { - this.mesh = mesh; - - Generate(); - } - - /// - /// Gets the list of Voronoi vertices. - /// - public Point[] Points - { - get { return points; } - } - - /// - /// Gets the list of Voronoi regions. - /// - public ICollection Regions - { - get { return regions.Values; } - } - - public IEnumerable Edges - { - get { return EnumerateEdges(); } - } - - /// - /// Gets the Voronoi diagram as raw output data. - /// - /// - /// - /// - /// The Voronoi diagram is the geometric dual of the Delaunay triangulation. - /// Hence, the Voronoi vertices are listed by traversing the Delaunay - /// triangles, and the Voronoi edges are listed by traversing the Delaunay - /// edges. - /// - private void Generate() - { - mesh.Renumber(); - mesh.MakeVertexMap(); - - // Allocate space for voronoi diagram - this.points = new Point[mesh.triangles.Count + mesh.hullsize]; - this.regions = new Dictionary(mesh.vertices.Count); - - rayPoints = new Dictionary(); - rayIndex = 0; - - bounds = new Rectangle(); - - // Compute triangles circumcenters and setup bounding box - ComputeCircumCenters(); - - // Add all Voronoi regions to the map. - foreach (var vertex in mesh.vertices.Values) - { - regions.Add(vertex.id, new VoronoiRegion(vertex)); - } - - // Loop over the mesh vertices (Voronoi generators). - foreach (var region in regions.Values) - { - //if (item.Boundary == 0) - { - ConstructCell(region); - } - } - } - - private void ComputeCircumCenters() - { - Otri tri = default(Otri); - double xi = 0, eta = 0; - Point pt; - - // Compue triangle circumcenters - foreach (var item in mesh.triangles) - { - tri.tri = item; - - pt = predicates.FindCircumcenter(tri.Org(), tri.Dest(), tri.Apex(), ref xi, ref eta); - pt.id = item.id; - - points[item.id] = pt; - - bounds.Expand(pt); - } - - double ds = Math.Max(bounds.Width, bounds.Height); - bounds.Resize(ds / 10, ds / 10); - } - - /// - /// Construct Voronoi region for given vertex. - /// - /// - private void ConstructCell(VoronoiRegion region) - { - var vertex = region.Generator as Vertex; - - var vpoints = new List(); - - Otri f = default(Otri); - Otri f_init = default(Otri); - Otri f_next = default(Otri); - Otri f_prev = default(Otri); - - Osub sub = default(Osub); - - // Call f_init a triangle incident to x - vertex.tri.Copy(ref f_init); - - f_init.Copy(ref f); - f_init.Onext(ref f_next); - - // Check if f_init lies on the boundary of the triangulation. - if (f_next.tri.id == Mesh.DUMMY) - { - f_init.Oprev(ref f_prev); - - if (f_prev.tri.id != Mesh.DUMMY) - { - f_init.Copy(ref f_next); - // Move one triangle clockwise - f_init.Oprev(); - f_init.Copy(ref f); - } - } - - // Go counterclockwise until we reach the border or the initial triangle. - while (f_next.tri.id != Mesh.DUMMY) - { - // Add circumcenter of current triangle - vpoints.Add(points[f.tri.id]); - - region.AddNeighbor(f.tri.id, regions[f.Apex().id]); - - if (f_next.Equals(f_init)) - { - // Voronoi cell is complete (bounded case). - region.Add(vpoints); - return; - } - - f_next.Copy(ref f); - f_next.Onext(); - } - - // Voronoi cell is unbounded - region.Bounded = false; - - Vertex torg, tdest, tapex; - Point intersection; - int sid, n = mesh.triangles.Count; - - // Find the boundary segment id (we use this id to number the endpoints of infinit rays). - f.Lprev(ref f_next); - f_next.Pivot(ref sub); - sid = sub.seg.hash; - - // Last valid f lies at the boundary. Add the circumcenter. - vpoints.Add(points[f.tri.id]); - region.AddNeighbor(f.tri.id, regions[f.Apex().id]); - - // Check if the intersection with the bounding box has already been computed. - if (!rayPoints.TryGetValue(sid, out intersection)) - { - torg = f.Org(); - tapex = f.Apex(); - intersection = IntersectionHelper.BoxRayIntersection(bounds, points[f.tri.id], torg.y - tapex.y, tapex.x - torg.x); - - // Set the correct id for the vertex - intersection.id = n + rayIndex; - - points[n + rayIndex] = intersection; - rayIndex++; - - rayPoints.Add(sid, intersection); - } - - vpoints.Add(intersection); - - // Now walk from f_init clockwise till we reach the boundary. - vpoints.Reverse(); - - f_init.Copy(ref f); - f.Oprev(ref f_prev); - - while (f_prev.tri.id != Mesh.DUMMY) - { - vpoints.Add(points[f_prev.tri.id]); - region.AddNeighbor(f_prev.tri.id, regions[f_prev.Apex().id]); - - f_prev.Copy(ref f); - f_prev.Oprev(); - } - - // Find the boundary segment id. - f.Pivot(ref sub); - sid = sub.seg.hash; - - if (!rayPoints.TryGetValue(sid, out intersection)) - { - // Intersection has not been computed yet. - torg = f.Org(); - tdest = f.Dest(); - - intersection = IntersectionHelper.BoxRayIntersection(bounds, points[f.tri.id], tdest.y - torg.y, torg.x - tdest.x); - - // Set the correct id for the vertex - intersection.id = n + rayIndex; - - rayPoints.Add(sid, intersection); - - points[n + rayIndex] = intersection; - rayIndex++; - } - - vpoints.Add(intersection); - region.AddNeighbor(intersection.id, regions[f.Dest().id]); - - // Add the new points to the region (in counter-clockwise order) - vpoints.Reverse(); - region.Add(vpoints); - } - - // TODO: Voronoi enumerate edges - - private IEnumerable EnumerateEdges() - { - // Copy edges - Point first, last; - var edges = new List(this.Regions.Count * 2); - foreach (var region in this.Regions) - { - var ve = region.Vertices.GetEnumerator(); - - ve.MoveNext(); - - first = last = ve.Current; - - while (ve.MoveNext()) - { - if (region.ID < region.GetNeighbor(last).ID) - { - edges.Add(new Edge(last.id, ve.Current.id)); - } - - last = ve.Current; - } - - if (region.Bounded && region.ID < region.GetNeighbor(last).ID) - { - edges.Add(new Edge(last.id, first.id)); - } - } - - return edges; - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle Copyright (c) 1993, 1995, 1997, 1998, 2002, 2005 Jonathan Richard Shewchuk +// Triangle.NET code by Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Voronoi.Legacy +{ + using System; + using System.Collections.Generic; + using TriangleNet.Topology; + using TriangleNet.Geometry; + using TriangleNet.Tools; + + /// + /// The Voronoi Diagram is the dual of a pointset triangulation. + /// + [Obsolete("Use TriangleNet.Voronoi.StandardVoronoi class instead.")] + public class SimpleVoronoi : IVoronoi + { + IPredicates predicates = RobustPredicates.Default; + + Mesh mesh; + + Point[] points; + Dictionary regions; + + // Stores the endpoints of rays of unbounded Voronoi cells + Dictionary rayPoints; + int rayIndex; + + // Bounding box of the triangles circumcenters. + Rectangle bounds; + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// Be sure MakeVertexMap has been called (should always be the case). + /// + public SimpleVoronoi(Mesh mesh) + { + this.mesh = mesh; + + Generate(); + } + + /// + /// Gets the list of Voronoi vertices. + /// + public Point[] Points + { + get { return points; } + } + + /// + /// Gets the list of Voronoi regions. + /// + public ICollection Regions + { + get { return regions.Values; } + } + + public IEnumerable Edges + { + get { return EnumerateEdges(); } + } + + /// + /// Gets the Voronoi diagram as raw output data. + /// + /// + /// + /// + /// The Voronoi diagram is the geometric dual of the Delaunay triangulation. + /// Hence, the Voronoi vertices are listed by traversing the Delaunay + /// triangles, and the Voronoi edges are listed by traversing the Delaunay + /// edges. + /// + private void Generate() + { + mesh.Renumber(); + mesh.MakeVertexMap(); + + // Allocate space for voronoi diagram + this.points = new Point[mesh.triangles.Count + mesh.hullsize]; + this.regions = new Dictionary(mesh.vertices.Count); + + rayPoints = new Dictionary(); + rayIndex = 0; + + bounds = new Rectangle(); + + // Compute triangles circumcenters and setup bounding box + ComputeCircumCenters(); + + // Add all Voronoi regions to the map. + foreach (var vertex in mesh.vertices.Values) + { + regions.Add(vertex.id, new VoronoiRegion(vertex)); + } + + // Loop over the mesh vertices (Voronoi generators). + foreach (var region in regions.Values) + { + //if (item.Boundary == 0) + { + ConstructCell(region); + } + } + } + + private void ComputeCircumCenters() + { + Otri tri = default(Otri); + double xi = 0, eta = 0; + Point pt; + + // Compue triangle circumcenters + foreach (var item in mesh.triangles) + { + tri.tri = item; + + pt = predicates.FindCircumcenter(tri.Org(), tri.Dest(), tri.Apex(), ref xi, ref eta); + pt.id = item.id; + + points[item.id] = pt; + + bounds.Expand(pt); + } + + double ds = Math.Max(bounds.Width, bounds.Height); + bounds.Resize(ds / 10, ds / 10); + } + + /// + /// Construct Voronoi region for given vertex. + /// + /// + private void ConstructCell(VoronoiRegion region) + { + var vertex = region.Generator as Vertex; + + var vpoints = new List(); + + Otri f = default(Otri); + Otri f_init = default(Otri); + Otri f_next = default(Otri); + Otri f_prev = default(Otri); + + Osub sub = default(Osub); + + // Call f_init a triangle incident to x + vertex.tri.Copy(ref f_init); + + f_init.Copy(ref f); + f_init.Onext(ref f_next); + + // Check if f_init lies on the boundary of the triangulation. + if (f_next.tri.id == Mesh.DUMMY) + { + f_init.Oprev(ref f_prev); + + if (f_prev.tri.id != Mesh.DUMMY) + { + f_init.Copy(ref f_next); + // Move one triangle clockwise + f_init.Oprev(); + f_init.Copy(ref f); + } + } + + // Go counterclockwise until we reach the border or the initial triangle. + while (f_next.tri.id != Mesh.DUMMY) + { + // Add circumcenter of current triangle + vpoints.Add(points[f.tri.id]); + + region.AddNeighbor(f.tri.id, regions[f.Apex().id]); + + if (f_next.Equals(f_init)) + { + // Voronoi cell is complete (bounded case). + region.Add(vpoints); + return; + } + + f_next.Copy(ref f); + f_next.Onext(); + } + + // Voronoi cell is unbounded + region.Bounded = false; + + Vertex torg, tdest, tapex; + Point intersection; + int sid, n = mesh.triangles.Count; + + // Find the boundary segment id (we use this id to number the endpoints of infinit rays). + f.Lprev(ref f_next); + f_next.Pivot(ref sub); + sid = sub.seg.hash; + + // Last valid f lies at the boundary. Add the circumcenter. + vpoints.Add(points[f.tri.id]); + region.AddNeighbor(f.tri.id, regions[f.Apex().id]); + + // Check if the intersection with the bounding box has already been computed. + if (!rayPoints.TryGetValue(sid, out intersection)) + { + torg = f.Org(); + tapex = f.Apex(); + intersection = IntersectionHelper.BoxRayIntersection(bounds, points[f.tri.id], torg.y - tapex.y, tapex.x - torg.x); + + // Set the correct id for the vertex + intersection.id = n + rayIndex; + + points[n + rayIndex] = intersection; + rayIndex++; + + rayPoints.Add(sid, intersection); + } + + vpoints.Add(intersection); + + // Now walk from f_init clockwise till we reach the boundary. + vpoints.Reverse(); + + f_init.Copy(ref f); + f.Oprev(ref f_prev); + + while (f_prev.tri.id != Mesh.DUMMY) + { + vpoints.Add(points[f_prev.tri.id]); + region.AddNeighbor(f_prev.tri.id, regions[f_prev.Apex().id]); + + f_prev.Copy(ref f); + f_prev.Oprev(); + } + + // Find the boundary segment id. + f.Pivot(ref sub); + sid = sub.seg.hash; + + if (!rayPoints.TryGetValue(sid, out intersection)) + { + // Intersection has not been computed yet. + torg = f.Org(); + tdest = f.Dest(); + + intersection = IntersectionHelper.BoxRayIntersection(bounds, points[f.tri.id], tdest.y - torg.y, torg.x - tdest.x); + + // Set the correct id for the vertex + intersection.id = n + rayIndex; + + rayPoints.Add(sid, intersection); + + points[n + rayIndex] = intersection; + rayIndex++; + } + + vpoints.Add(intersection); + region.AddNeighbor(intersection.id, regions[f.Dest().id]); + + // Add the new points to the region (in counter-clockwise order) + vpoints.Reverse(); + region.Add(vpoints); + } + + // TODO: Voronoi enumerate edges + + private IEnumerable EnumerateEdges() + { + // Copy edges + Point first, last; + var edges = new List(this.Regions.Count * 2); + foreach (var region in this.Regions) + { + var ve = region.Vertices.GetEnumerator(); + + ve.MoveNext(); + + first = last = ve.Current; + + while (ve.MoveNext()) + { + if (region.ID < region.GetNeighbor(last).ID) + { + edges.Add(new Edge(last.id, ve.Current.id)); + } + + last = ve.Current; + } + + if (region.Bounded && region.ID < region.GetNeighbor(last).ID) + { + edges.Add(new Edge(last.id, first.id)); + } + } + + return edges; + } + } +} diff --git a/Triangle.NET/Triangle/Voronoi/Legacy/VoronoiRegion.cs b/src/Triangle/Voronoi/Legacy/VoronoiRegion.cs similarity index 94% rename from Triangle.NET/Triangle/Voronoi/Legacy/VoronoiRegion.cs rename to src/Triangle/Voronoi/Legacy/VoronoiRegion.cs index 8d8f9e7..a7e2f2a 100644 --- a/Triangle.NET/Triangle/Voronoi/Legacy/VoronoiRegion.cs +++ b/src/Triangle/Voronoi/Legacy/VoronoiRegion.cs @@ -1,111 +1,111 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Voronoi.Legacy -{ - using System; - using System.Collections.Generic; - using TriangleNet.Topology; - using TriangleNet.Geometry; - - /// - /// Represents a region in the Voronoi diagram. - /// - public class VoronoiRegion - { - int id; - Point generator; - List vertices; - bool bounded; - - // A map (vertex id) -> (neighbor across adjacent edge) - Dictionary neighbors; - - /// - /// Gets the Voronoi region id (which is the same as the generators vertex id). - /// - public int ID - { - get { return id; } - } - - /// - /// Gets the Voronoi regions generator. - /// - public Point Generator - { - get { return generator; } - } - - /// - /// Gets the Voronoi vertices on the regions boundary. - /// - public ICollection Vertices - { - get { return vertices; } - } - - /// - /// Gets or sets whether the Voronoi region is bounded. - /// - public bool Bounded - { - get { return bounded; } - set { bounded = value; } - } - - public VoronoiRegion(Vertex generator) - { - this.id = generator.id; - this.generator = generator; - this.vertices = new List(); - this.bounded = true; - - this.neighbors = new Dictionary(); - } - - public void Add(Point point) - { - this.vertices.Add(point); - } - - public void Add(List points) - { - this.vertices.AddRange(points); - } - - /// - /// Returns the neighbouring Voronoi region, that lies across the edge starting at - /// given vertex. - /// - /// Vertex defining an edge of the region. - /// Neighbouring Voronoi region - /// - /// The edge starting at p is well defined (vertices are ordered counterclockwise). - /// - public VoronoiRegion GetNeighbor(Point p) - { - VoronoiRegion neighbor; - - if (neighbors.TryGetValue(p.id, out neighbor)) - { - return neighbor; - } - - return null; - } - - internal void AddNeighbor(int id, VoronoiRegion neighbor) - { - this.neighbors.Add(id, neighbor); - } - - public override string ToString() - { - return String.Format("R-ID {0}", id); - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Voronoi.Legacy +{ + using System; + using System.Collections.Generic; + using TriangleNet.Topology; + using TriangleNet.Geometry; + + /// + /// Represents a region in the Voronoi diagram. + /// + public class VoronoiRegion + { + int id; + Point generator; + List vertices; + bool bounded; + + // A map (vertex id) -> (neighbor across adjacent edge) + Dictionary neighbors; + + /// + /// Gets the Voronoi region id (which is the same as the generators vertex id). + /// + public int ID + { + get { return id; } + } + + /// + /// Gets the Voronoi regions generator. + /// + public Point Generator + { + get { return generator; } + } + + /// + /// Gets the Voronoi vertices on the regions boundary. + /// + public ICollection Vertices + { + get { return vertices; } + } + + /// + /// Gets or sets whether the Voronoi region is bounded. + /// + public bool Bounded + { + get { return bounded; } + set { bounded = value; } + } + + public VoronoiRegion(Vertex generator) + { + this.id = generator.id; + this.generator = generator; + this.vertices = new List(); + this.bounded = true; + + this.neighbors = new Dictionary(); + } + + public void Add(Point point) + { + this.vertices.Add(point); + } + + public void Add(List points) + { + this.vertices.AddRange(points); + } + + /// + /// Returns the neighbouring Voronoi region, that lies across the edge starting at + /// given vertex. + /// + /// Vertex defining an edge of the region. + /// Neighbouring Voronoi region + /// + /// The edge starting at p is well defined (vertices are ordered counterclockwise). + /// + public VoronoiRegion GetNeighbor(Point p) + { + VoronoiRegion neighbor; + + if (neighbors.TryGetValue(p.id, out neighbor)) + { + return neighbor; + } + + return null; + } + + internal void AddNeighbor(int id, VoronoiRegion neighbor) + { + this.neighbors.Add(id, neighbor); + } + + public override string ToString() + { + return String.Format("R-ID {0}", id); + } + } +} diff --git a/Triangle.NET/Triangle/Voronoi/StandardVoronoi.cs b/src/Triangle/Voronoi/StandardVoronoi.cs similarity index 94% rename from Triangle.NET/Triangle/Voronoi/StandardVoronoi.cs rename to src/Triangle/Voronoi/StandardVoronoi.cs index b2ac312..db26b97 100644 --- a/Triangle.NET/Triangle/Voronoi/StandardVoronoi.cs +++ b/src/Triangle/Voronoi/StandardVoronoi.cs @@ -1,66 +1,66 @@ -// ----------------------------------------------------------------------- -// -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Voronoi -{ - using System.Collections.Generic; - using TriangleNet.Geometry; - using TriangleNet.Tools; - using TriangleNet.Topology.DCEL; - - public class StandardVoronoi : VoronoiBase - { - public StandardVoronoi(Mesh mesh) - : this(mesh, mesh.bounds, new DefaultVoronoiFactory(), RobustPredicates.Default) - { - } - - public StandardVoronoi(Mesh mesh, Rectangle box) - : this(mesh, box, new DefaultVoronoiFactory(), RobustPredicates.Default) - { - } - - public StandardVoronoi(Mesh mesh, Rectangle box, IVoronoiFactory factory, IPredicates predicates) - : base(mesh, factory, predicates, true) - { - // We assume the box to be at least as large as the mesh. - box.Expand(mesh.bounds); - - // We explicitly told the base constructor to call the Generate method, so - // at this point the basic Voronoi diagram is already created. - PostProcess(box); - } - - /// - /// Compute edge intersections with bounding box. - /// - private void PostProcess(Rectangle box) - { - foreach (var edge in rays) - { - // The vertices of the infinite edge. - var v1 = (Point)edge.origin; - var v2 = (Point)edge.twin.origin; - - if (box.Contains(v1) || box.Contains(v2)) - { - // Move infinite vertex v2 onto the box boundary. - IntersectionHelper.BoxRayIntersection(box, v1, v2, ref v2); - } - else - { - // There is actually no easy way to handle the second case. The two edges - // leaving v1, pointing towards the mesh, don't have to intersect the box - // (the could join with edges of other cells outside the box). - - // A general intersection algorithm (DCEL <-> Rectangle) is needed, which - // computes intersections with all edges and discards objects outside the - // box. - } - } - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Voronoi +{ + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Tools; + using TriangleNet.Topology.DCEL; + + public class StandardVoronoi : VoronoiBase + { + public StandardVoronoi(Mesh mesh) + : this(mesh, mesh.bounds, new DefaultVoronoiFactory(), RobustPredicates.Default) + { + } + + public StandardVoronoi(Mesh mesh, Rectangle box) + : this(mesh, box, new DefaultVoronoiFactory(), RobustPredicates.Default) + { + } + + public StandardVoronoi(Mesh mesh, Rectangle box, IVoronoiFactory factory, IPredicates predicates) + : base(mesh, factory, predicates, true) + { + // We assume the box to be at least as large as the mesh. + box.Expand(mesh.bounds); + + // We explicitly told the base constructor to call the Generate method, so + // at this point the basic Voronoi diagram is already created. + PostProcess(box); + } + + /// + /// Compute edge intersections with bounding box. + /// + private void PostProcess(Rectangle box) + { + foreach (var edge in rays) + { + // The vertices of the infinite edge. + var v1 = (Point)edge.origin; + var v2 = (Point)edge.twin.origin; + + if (box.Contains(v1) || box.Contains(v2)) + { + // Move infinite vertex v2 onto the box boundary. + IntersectionHelper.BoxRayIntersection(box, v1, v2, ref v2); + } + else + { + // There is actually no easy way to handle the second case. The two edges + // leaving v1, pointing towards the mesh, don't have to intersect the box + // (the could join with edges of other cells outside the box). + + // A general intersection algorithm (DCEL <-> Rectangle) is needed, which + // computes intersections with all edges and discards objects outside the + // box. + } + } + } + } +} diff --git a/Triangle.NET/Triangle/Voronoi/VoronoiBase.cs b/src/Triangle/Voronoi/VoronoiBase.cs similarity index 88% rename from Triangle.NET/Triangle/Voronoi/VoronoiBase.cs rename to src/Triangle/Voronoi/VoronoiBase.cs index 0122d71..69444de 100644 --- a/Triangle.NET/Triangle/Voronoi/VoronoiBase.cs +++ b/src/Triangle/Voronoi/VoronoiBase.cs @@ -1,291 +1,303 @@ -// ----------------------------------------------------------------------- -// -// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html -// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ -// -// ----------------------------------------------------------------------- - -namespace TriangleNet.Voronoi -{ - using System.Collections.Generic; - - using TriangleNet.Topology; - using TriangleNet.Geometry; - using TriangleNet.Topology.DCEL; - - using Vertex = TriangleNet.Topology.DCEL.Vertex; - - /// - /// The Voronoi diagram is the dual of a pointset triangulation. - /// - public abstract class VoronoiBase : DcelMesh - { - protected IPredicates predicates; - - protected IVoronoiFactory factory; - - // List of infinite half-edges, i.e. half-edges that start at circumcenters of triangles - // which lie on the domain boundary. - protected List rays; - - /// - /// Initializes a new instance of the class. - /// - /// Triangle mesh. - /// Voronoi object factory. - /// Geometric predicates implementation. - /// If set to true, the constuctor will call the Generate - /// method, which builds the Voronoi diagram. - protected VoronoiBase(Mesh mesh, IVoronoiFactory factory, IPredicates predicates, - bool generate) - : base(false) - { - this.factory = factory; - this.predicates = predicates; - - if (generate) - { - Generate(mesh); - } - } - - /// - /// Generate the Voronoi diagram from given triangle mesh.. - /// - /// - /// - protected void Generate(Mesh mesh) - { - mesh.Renumber(); - - base.edges = new List(); - this.rays = new List(); - - // Allocate space for Voronoi diagram. - var vertices = new Vertex[mesh.triangles.Count + mesh.hullsize]; - var faces = new Face[mesh.vertices.Count]; - - if (factory == null) - { - factory = new DefaultVoronoiFactory(); - } - - factory.Initialize(vertices.Length, 2 * mesh.NumberOfEdges, faces.Length); - - // Compute triangles circumcenters. - var map = ComputeVertices(mesh, vertices); - - // Create all Voronoi faces. - foreach (var vertex in mesh.vertices.Values) - { - faces[vertex.id] = factory.CreateFace(vertex); - } - - ComputeEdges(mesh, vertices, faces, map); - - // At this point all edges are computed, but the (edge.next) pointers aren't set. - ConnectEdges(map); - - base.vertices = new List(vertices); - base.faces = new List(faces); - } - - /// - /// Compute the Voronoi vertices (the circumcenters of the triangles). - /// - /// An empty map, which will map all vertices to a list of leaving edges. - protected List[] ComputeVertices(Mesh mesh, Vertex[] vertices) - { - Otri tri = default(Otri); - double xi = 0, eta = 0; - Vertex vertex; - Point pt; - int id; - - // Maps all vertices to a list of leaving edges. - var map = new List[mesh.triangles.Count]; - - // Compue triangle circumcenters - foreach (var t in mesh.triangles) - { - id = t.id; - tri.tri = t; - - pt = predicates.FindCircumcenter(tri.Org(), tri.Dest(), tri.Apex(), ref xi, ref eta); - - vertex = factory.CreateVertex(pt.x, pt.y); - vertex.id = id; - - vertices[id] = vertex; - map[id] = new List(); - } - - return map; - } - - /// - /// Compute the edges of the Voronoi diagram. - /// - /// - /// - /// - /// Empty vertex map. - protected void ComputeEdges(Mesh mesh, Vertex[] vertices, Face[] faces, List[] map) - { - Otri tri, neighbor = default(Otri); - TriangleNet.Geometry.Vertex org, dest; - - double px, py; - int id, nid, count = mesh.triangles.Count; - - Face face, neighborFace; - HalfEdge edge, twin; - Vertex vertex, end; - - // Count infinte edges (vertex id for their endpoints). - int j = 0; - - // Count half-edges (edge ids). - int k = 0; - - // To loop over the set of edges, loop over all triangles, and look at the - // three edges of each triangle. If there isn't another triangle adjacent - // to the edge, operate on the edge. If there is another adjacent triangle, - // operate on the edge only if the current triangle has a smaller id than - // its neighbor. This way, each edge is considered only once. - foreach (var t in mesh.triangles) - { - id = t.id; - - tri.tri = t; - - for (int i = 0; i < 3; i++) - { - tri.orient = i; - tri.Sym(ref neighbor); - - nid = neighbor.tri.id; - - if (id < nid || nid < 0) - { - // Get the endpoints of the current triangle edge. - org = tri.Org(); - dest = tri.Dest(); - - face = faces[org.id]; - neighborFace = faces[dest.id]; - - vertex = vertices[id]; - - // For each edge in the triangle mesh, there's a corresponding edge - // in the Voronoi diagram, i.e. two half-edges will be created. - if (nid < 0) - { - // Unbounded edge, direction perpendicular to the boundary edge, - // pointing outwards. - px = dest.y - org.y; - py = org.x - dest.x; - - end = factory.CreateVertex(vertex.x + px, vertex.y + py); - end.id = count + j++; - - vertices[end.id] = end; - - edge = factory.CreateHalfEdge(end, face); - twin = factory.CreateHalfEdge(vertex, neighborFace); - - // Make (face.edge) always point to an edge that starts at an infinite - // vertex. This will allow traversing of unbounded faces. - face.edge = edge; - face.bounded = false; - - map[id].Add(twin); - - rays.Add(twin); - } - else - { - end = vertices[nid]; - - // Create half-edges. - edge = factory.CreateHalfEdge(end, face); - twin = factory.CreateHalfEdge(vertex, neighborFace); - - // Add to vertex map. - map[nid].Add(edge); - map[id].Add(twin); - } - - vertex.leaving = twin; - end.leaving = edge; - - edge.twin = twin; - twin.twin = edge; - - edge.id = k++; - twin.id = k++; - - this.edges.Add(edge); - this.edges.Add(twin); - } - } - } - } - - /// - /// Connect all edges of the Voronoi diagram. - /// - /// Maps all vertices to a list of leaving edges. - protected virtual void ConnectEdges(List[] map) - { - int length = map.Length; - - // For each half-edge, find its successor in the connected face. - foreach (var edge in this.edges) - { - var face = edge.face.generator.id; - - // The id of the dest vertex of current edge. - int id = edge.twin.origin.id; - - // The edge origin can also be an infinite vertex. Sort them out - // by checking the id. - if (id < length) - { - // Look for the edge that is connected to the current face. Each - // Voronoi vertex has degree 3, so this loop is actually O(1). - foreach (var next in map[id]) - { - if (next.face.generator.id == face) - { - edge.next = next; - break; - } - } - } - } - } - - protected override IEnumerable EnumerateEdges() - { - var edges = new List(this.edges.Count / 2); - - foreach (var edge in this.edges) - { - var twin = edge.twin; - - // Report edge only once. - if (twin == null) - { - edges.Add(new Edge(edge.origin.id, edge.next.origin.id)); - } - else if (edge.id < twin.id) - { - edges.Add(new Edge(edge.origin.id, twin.origin.id)); - } - } - - return edges; - } - } -} +// ----------------------------------------------------------------------- +// +// Triangle.NET Copyright (c) 2012-2022 Christian Woltering +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Voronoi +{ + using System.Collections.Generic; + + using TriangleNet.Topology; + using TriangleNet.Geometry; + using TriangleNet.Topology.DCEL; + + using Vertex = TriangleNet.Topology.DCEL.Vertex; + + /// + /// The Voronoi diagram is the dual of a point set triangulation. + /// + public abstract class VoronoiBase : DcelMesh + { + protected IPredicates predicates; + + protected IVoronoiFactory factory; + + // List of infinite half-edges, i.e. half-edges that start at circumcenters of triangles + // which lie on the domain boundary. + protected List rays; + + /// + /// Initializes a new instance of the class. + /// + /// Triangle mesh. + /// Voronoi object factory. + /// Geometric predicates implementation. + /// If set to true, the constructor will call the Generate + /// method, which builds the Voronoi diagram. + protected VoronoiBase(Mesh mesh, IVoronoiFactory factory, IPredicates predicates, + bool generate) : base(false) + { + this.factory = factory; + this.predicates = predicates; + + if (generate) + { + Generate(mesh); + } + } + + /// + /// Generate the Voronoi diagram from given triangle mesh.. + /// + /// + protected void Generate(Mesh mesh) + { + base.edges = new List(); + this.rays = new List(); + + // Undead vertices cannot be Voronoi cell generators. + int count = mesh.vertices.Count - mesh.undeads; + + // Allocate space for Voronoi diagram. + var vertices = new Vertex[mesh.triangles.Count + mesh.hullsize]; + var faces = new Face[count]; + + if (factory == null) + { + factory = new DefaultVoronoiFactory(); + } + + factory.Initialize(vertices.Length, 2 * mesh.NumberOfEdges, faces.Length); + + // Compute triangles circumcenters. + var map = ComputeVertices(mesh, vertices); + + // Ensure linear numbering of vertices (excluding undeads). + int vid = 0; + + // Create all Voronoi faces, skipping undead vertices. + foreach (var vertex in mesh.vertices.Values) + { + if (vertex.type == VertexType.UndeadVertex) + { + vertex.id = count++; + } + else + { + vertex.id = vid++; + faces[vertex.id] = factory.CreateFace(vertex); + } + } + + ComputeEdges(mesh, vertices, faces, map); + + // At this point all edges are computed, but the (edge.next) pointers aren't set. + ConnectEdges(map); + + base.vertices = new List(vertices); + base.faces = new List(faces); + } + + /// + /// Compute the Voronoi vertices (the circumcenters of the triangles). + /// + /// An empty map, which will map all vertices to a list of leaving edges. + /// + /// This method will also change triangle ids (to ensure linear numbering of triangles). + /// + protected List[] ComputeVertices(Mesh mesh, Vertex[] vertices) + { + Otri tri = default(Otri); + double xi = 0, eta = 0; + Vertex vertex; + Point pt; + int id, i = 0; + + // Maps all vertices to a list of leaving edges. + var map = new List[mesh.triangles.Count]; + + // Compute triangle circumcenters + foreach (var t in mesh.triangles) + { + t.id = id = i++; + tri.tri = t; + + pt = predicates.FindCircumcenter(tri.Org(), tri.Dest(), tri.Apex(), ref xi, ref eta); + + vertex = factory.CreateVertex(pt.x, pt.y); + vertex.id = id; + + vertices[id] = vertex; + map[id] = new List(); + } + + return map; + } + + /// + /// Compute the edges of the Voronoi diagram. + /// + /// + /// + /// + /// Empty vertex map. + protected void ComputeEdges(Mesh mesh, Vertex[] vertices, Face[] faces, List[] map) + { + Otri tri, neighbor = default(Otri); + TriangleNet.Geometry.Vertex org, dest; + + double px, py; + int id, nid, count = mesh.triangles.Count; + + Face face, neighborFace; + HalfEdge edge, twin; + Vertex vertex, end; + + // Count infinite edges (vertex id for their endpoints). + int j = 0; + + // Count half-edges (edge ids). + int k = 0; + + // To loop over the set of edges, loop over all triangles, and look at the + // three edges of each triangle. If there isn't another triangle adjacent + // to the edge, operate on the edge. If there is another adjacent triangle, + // operate on the edge only if the current triangle has a smaller id than + // its neighbor. This way, each edge is considered only once. + foreach (var t in mesh.triangles) + { + id = t.id; + + tri.tri = t; + + for (int i = 0; i < 3; i++) + { + tri.orient = i; + tri.Sym(ref neighbor); + + nid = neighbor.tri.id; + + if (id < nid || nid < 0) + { + // Get the endpoints of the current triangle edge. + org = tri.Org(); + dest = tri.Dest(); + + face = faces[org.id]; + neighborFace = faces[dest.id]; + + vertex = vertices[id]; + + // For each edge in the triangle mesh, there's a corresponding edge + // in the Voronoi diagram, i.e. two half-edges will be created. + if (nid < 0) + { + // Unbounded edge, direction perpendicular to the boundary edge, + // pointing outwards. + px = dest.y - org.y; + py = org.x - dest.x; + + end = factory.CreateVertex(vertex.x + px, vertex.y + py); + end.id = count + j++; + + vertices[end.id] = end; + + edge = factory.CreateHalfEdge(end, face); + twin = factory.CreateHalfEdge(vertex, neighborFace); + + // Make (face.edge) always point to an edge that starts at an infinite + // vertex. This will allow traversing of unbounded faces. + face.edge = edge; + face.bounded = false; + + map[id].Add(twin); + + rays.Add(twin); + } + else + { + end = vertices[nid]; + + // Create half-edges. + edge = factory.CreateHalfEdge(end, face); + twin = factory.CreateHalfEdge(vertex, neighborFace); + + // Add to vertex map. + map[nid].Add(edge); + map[id].Add(twin); + } + + vertex.leaving = twin; + end.leaving = edge; + + edge.twin = twin; + twin.twin = edge; + + edge.id = k++; + twin.id = k++; + + this.edges.Add(edge); + this.edges.Add(twin); + } + } + } + } + + /// + /// Connect all edges of the Voronoi diagram. + /// + /// Maps all vertices to a list of leaving edges. + protected virtual void ConnectEdges(List[] map) + { + int length = map.Length; + + // For each half-edge, find its successor in the connected face. + foreach (var edge in this.edges) + { + var face = edge.face.generator.id; + + // The id of the dest vertex of current edge. + int id = edge.twin.origin.id; + + // The edge origin can also be an infinite vertex. Sort them out + // by checking the id. + if (id < length) + { + // Look for the edge that is connected to the current face. Each + // Voronoi vertex has degree 3, so this loop is actually O(1). + foreach (var next in map[id]) + { + if (next.face.generator.id == face) + { + edge.next = next; + break; + } + } + } + } + } + + protected override IEnumerable EnumerateEdges() + { + var edges = new List(this.edges.Count / 2); + + foreach (var edge in this.edges) + { + var twin = edge.twin; + + // Report edge only once. + if (twin == null) + { + edges.Add(new Edge(edge.origin.id, edge.next.origin.id)); + } + else if (edge.id < twin.id) + { + edges.Add(new Edge(edge.origin.id, twin.origin.id)); + } + } + + return edges; + } + } +}