diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c045256 --- /dev/null +++ b/.gitignore @@ -0,0 +1,497 @@ +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# content below from: https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +## 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/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# 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 + +# 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/ + +# 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 +*.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 + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# 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 +# 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 + +# 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 + +# 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 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/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# 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/ + + +build/ +v17/ +.vs/ +x64/ + + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Xcode ### +## User settings +xcuserdata/ + +## Xcode 8 and earlier +*.xcscmblueprint +*.xccheckout + +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcodeproj/project.xcworkspace/ +!*.xcworkspace/contents.xcworkspacedata +/*.gcno +**/xcshareddata/WorkspaceSettings.xcsettings + + +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + diff --git a/Documentation/Compiling_PicoGKRuntime.md b/Documentation/Compiling_PicoGKRuntime.md new file mode 100644 index 0000000..64ab2b5 --- /dev/null +++ b/Documentation/Compiling_PicoGKRuntime.md @@ -0,0 +1,68 @@ +# Compiling the PicoGK Runtime + +We compiled and tested PicoGKRuntime on Windows 64 bit and MacOS 14 Sonoma. Since Mac is our primary work environnment, we have tested it on Mac significantly more than on Windows. There is, however, nothing fundamentally platform-specific about PicoGK. The main platform dependencies are well-established libraries like OpenVDB. Our code is very straightforward and mostly header-only C++ code. + +## What you need + +On Windows, you need **Visual Studio 2022 Community Edition** (or higher) with C++ support installed (bare bones C++ is enough). + +On Mac, you need the latest version of **XCode** with C++ support, and the XCode command line tools. + +In addition you need a current version of **CMake** (Download at https://cmake.org/) + +## Installing dependent libraries + +First clone the **PicoGKRuntime** repository to your machine. + +Make sure to initialize the submodules (**git submodule update --init --recursive**), so that the OpenVDB submodule is properly initialized. + +PicoGKRuntime has no dependencies besides **OpenVDB** and **GLFW** (which is fetched automatically), but those libraries have plenty of dependencies (boost, blosc, etc). + +To facilitate the installation of these dependencies, we have provided you with two scripts that download and install everything needed. + +On Mac, please run **PicoGKRuntime/Install_Dependencies/Mac.sh** + +On Windows, please run **PicoGKRuntime/Install_Dependencies/Win.bat** +Note: you may have to run Win.bat twice, due to a bug we haven't found yet. It works after the second time. + +The installation of the dependencies may take a while, especially on Windows. + +After you have done this, you can move onto compiling the PicoGK Runtime. + +## Preparing the PicoGK Runtime Build Environment + +Start the CMake GUI client and specify the path to the PicoGKRuntime repository in **"Where is the source code"**. + +Specify the Build subfolder under **"Where to build the libraries"**. It should like this + +image-20231017134154856 + +Hit **Configure** and accept all defaults. After Configure has run without errors, click **Generate**. + +Note: On Mac, we advise to use "Unix Makefiles" as target. If you target XCode, you will get an error in the OpenVDB CMake setup. We have reached out to the OpenVDB team why this happens. You can safely comment out the offending lines in the OpenVDB CMake files, but this should not happen and it seems like an issue on their side. + +Now you can compile PicoGKRuntime on your system. + +## Compiling + +On Mac, go to the **Build** subdirectory in **Terminal** and type **make** [enter] to run the make tool. The build process should start and you will get the compiled picogk.1.0.dylib in the Dist subfolder of PicoGKRuntime. + +On Windows, open the resulting project in Visual Studio and compile (use the release version). + +## Using the compiled Runtime + +You either copy the resulting library to /usr/local/lib on Mac or the System32 folder on Windows (or any other folder that is in your system path). + +A cleaner way, when you are still testing is to modify the path in the PicoGK C# library. You do this by opening PicoGK__Config.cs and pointing the path to your compiled library: + +image-20231017164620416 + +One last thing — on Mac, you may have to sign the library using the **codesign** tool. Otherwise the library may not load. + +## Code signing on the Mac + +The necessary command line is **codesign -s LEAP71 picogk.1.0.dylib** — you need to have a valid code signing certificate for this (we used one we named LEAP71 — you can self issue this certificate, but it's a few steps). + +Here is a relevant Apple article how to create self-signed certificates: https://support.apple.com/en-ae/guide/keychain-access/kyca8916/mac + +[You may have to adjust your security settings as described here.](../Runtime/readme.md) diff --git a/Documentation/README.md b/Documentation/README.md new file mode 100644 index 0000000..8686168 --- /dev/null +++ b/Documentation/README.md @@ -0,0 +1,58 @@ +# PicoGK Documentation + +![PicoGKSketched](images/PicoGKSketched.jpg) + +## Getting Started + +**PicoGK is available and tested on MacOS X at this time. A Windows release will follow in the coming weeks.** + +As a Computational Engineer, you write code to create sophisticated geometry. If you don't know how to code, there are many good tutorials available online. + +PicoGK uses the C# language, a modern, fast, expressive and strongly-typed language that allow you to build complex algorithms using a simple syntax. If you are new to coding, don't despair! We have tried to make it as simple as possible to get started. + +To install PicoGK, [follow this link](installation.md) for installation instructions. + +Now let's dive in. + +## Your first PicoGK App + +For experts, here are the quick steps: In your **Program.cs** you call **PicoGK.Library.Go** with the voxel size and the task you want to execute. There are many example tasks in the **Examples** subfolder of the PicoGK repository. + +**For detailed steps, read on.** + +We assume you have followed the [Installation Guide](installation.md) and have created an empty Console application in Visual Studio. + +Open your Visual Studio project. **Program.cs** should already be open. + +It should look something like this + +image-20231014161615187 + +Replace the text there with the following: + +```c# +PicoGK.Library.Go( 0.5f, + PicoGKExamples.BooleanShowCase.Task); +``` + +image-20231014184822502 + +And run it. + +![image-20231014184919894](images/image-20231014184919894.png)If everything is right, a window will open, showing you a few spheres that are combined using Boolean operations. You can click and drag in the viewer to rotate, scroll to zoom, and use the cursor keys to rotate by 15º. The viewer is basic but functional. + +If you are not seeing this window, you can check out the console window at the bottom of Visual Studio to see any error messages. These messages are also written to **PicoGK.log** in your Documents folder (you can change the location if you don't like that). One thing you may have to do on Mac is adjust the security settings, so PicoGK can load the pre-compiled PicoGKRuntime. [If you want to compile it yourself, here are the instructions](Compiling_PicoGKRuntime.md). + +[Drop us a note in the discussion section of this repository](https://github.com/leap71/PicoGK/discussions), if you have any issues, and we will do our best to help. The most common problem is that the PicoGK Runtime library cannot be found. Double check if you copied it to the right system folder. + +Congratulations, you can now code in PicoGK! + +Check out the other examples from the Examples subdirectory of PicoGK. A fun way to work your way through is by simply typing a dot behind the PicoGKExamples namespace ID, as below, and you will see all the options. Just don't forget to add "Task" at the end. + +If you dive into the code, you will see that it's super simple to create interesting things, even with the basic PicoGK functions. + +image-20231014185209784 + +If you want to seriously dive in, [you should work using the LEAP 71 Shape Kernel.](https://github.com/leap71/ShapeKernel) and build your [Computational Engineering Models](https://leap71.com/computationalengineering/) on top. + +If you'd like to dive into the details of compiling and developing the [PicoGKRuntime, you will find a setup guide here](Compiling_PicoGKRuntime.md). diff --git a/Documentation/images/9CF66413-8BA1-4E18-9BA7-F5254235B44A.jpeg b/Documentation/images/9CF66413-8BA1-4E18-9BA7-F5254235B44A.jpeg new file mode 100644 index 0000000..9f707e9 Binary files /dev/null and b/Documentation/images/9CF66413-8BA1-4E18-9BA7-F5254235B44A.jpeg differ diff --git a/Documentation/images/PicoGKSketched.jpg b/Documentation/images/PicoGKSketched.jpg new file mode 100644 index 0000000..e8f5a3e Binary files /dev/null and b/Documentation/images/PicoGKSketched.jpg differ diff --git a/Documentation/images/image-20231014161615187.png b/Documentation/images/image-20231014161615187.png new file mode 100644 index 0000000..064c553 Binary files /dev/null and b/Documentation/images/image-20231014161615187.png differ diff --git a/Documentation/images/image-20231014182542392.png b/Documentation/images/image-20231014182542392.png new file mode 100644 index 0000000..83ad279 Binary files /dev/null and b/Documentation/images/image-20231014182542392.png differ diff --git a/Documentation/images/image-20231014183009658.png b/Documentation/images/image-20231014183009658.png new file mode 100644 index 0000000..29b101d Binary files /dev/null and b/Documentation/images/image-20231014183009658.png differ diff --git a/Documentation/images/image-20231014183143914.png b/Documentation/images/image-20231014183143914.png new file mode 100644 index 0000000..36f6e7f Binary files /dev/null and b/Documentation/images/image-20231014183143914.png differ diff --git a/Documentation/images/image-20231014183312778.png b/Documentation/images/image-20231014183312778.png new file mode 100644 index 0000000..4b202af Binary files /dev/null and b/Documentation/images/image-20231014183312778.png differ diff --git a/Documentation/images/image-20231014184436833.png b/Documentation/images/image-20231014184436833.png new file mode 100644 index 0000000..38473d4 Binary files /dev/null and b/Documentation/images/image-20231014184436833.png differ diff --git a/Documentation/images/image-20231014184822502.png b/Documentation/images/image-20231014184822502.png new file mode 100644 index 0000000..5a6deec Binary files /dev/null and b/Documentation/images/image-20231014184822502.png differ diff --git a/Documentation/images/image-20231014184919894.png b/Documentation/images/image-20231014184919894.png new file mode 100644 index 0000000..38141bc Binary files /dev/null and b/Documentation/images/image-20231014184919894.png differ diff --git a/Documentation/images/image-20231014185209784.png b/Documentation/images/image-20231014185209784.png new file mode 100644 index 0000000..fbbc0ab Binary files /dev/null and b/Documentation/images/image-20231014185209784.png differ diff --git a/Documentation/images/image-20231017134154856.png b/Documentation/images/image-20231017134154856.png new file mode 100644 index 0000000..4cdb0b0 Binary files /dev/null and b/Documentation/images/image-20231017134154856.png differ diff --git a/Documentation/images/image-20231017180157568.png b/Documentation/images/image-20231017180157568.png new file mode 100644 index 0000000..4402990 Binary files /dev/null and b/Documentation/images/image-20231017180157568.png differ diff --git a/Documentation/images/image-20231017180729775.png b/Documentation/images/image-20231017180729775.png new file mode 100644 index 0000000..c750ebc Binary files /dev/null and b/Documentation/images/image-20231017180729775.png differ diff --git a/Documentation/installation.md b/Documentation/installation.md new file mode 100644 index 0000000..ad0018d --- /dev/null +++ b/Documentation/installation.md @@ -0,0 +1,57 @@ +# PicoGK Installation + +## If you have used C# and Github before + +- Create a new **C# Console** application project using the latest version of Microsoft Visual Studio + +- Create a new repository for your project using **git**. + +- Add the **[PicoGK repository](https://github.com/leap71/PicoGK)** as a submodule and initialize it. +- Go into the **PicoGK** submodule folder, and navigate to the subfolder **Runtime** +- If you are on Mac, copy the **.dylib** file you see there to **/usr/local/lib** +- If you are on Windows, copy the **.dll** file to **C:\SYSTEM32** +- If you don't want to pollute these directories, you can change the path to the PicoGK Runtime in PicoGK__Config.c# + +Return to the [Getting Started](README.md) page to see how to run your first PicoGK app. + +## Beginner's Installation Guide + +PicoGK uses C#, a modern and elegant language, that can be learned quickly, but has powerful features. In order to use C#, you have to install Microsoft Visual Studio for either Mac or Windows. + +- If you are on Mac, [you can download it here.](https://visualstudio.microsoft.com/vs/mac/) +- If you are on Windows, [you can download it here.](https://visualstudio.microsoft.com/vs/community/) + +When installing, make sure you enable C# support and desktop app development. You don't need anything else. + +Open Visual Studio, and create a new "Console" application. + +We are showing Mac screenshots, Windows looks similar. + +image-20231014182542392 + +Click continue until you have to give your project a name. + +image-20231014183009658 + +Click **Create**. + +Your screen should look like this or similar. Click on the run button to compile and run your program. + +image-20231014183143914 + +You will see something like that in the console output at the bottom. Congratulations, you have run your first C# program. + +image-20231014183312778 + +Now, let's install the PicoGK library. + +- Download [the ZIP from this link](https://github.com/leap71/PicoGK/archive/refs/heads/main.zip), which is the PicoGK library. +- Copy the ZIP to the folder which contains Program.cs (in the project you just created) +- Unzip it. Now your folder should contain Program.cs, and a subfolder named PicoGK +- Go into the PicoGK folder, and navigate to the subfolder Runtime +- If you are on Mac, copy the **.dylib** files you see there to /usr/local/lib +- If you are on Windows, copy the **.dll** files to C:\SYSTEM32 + +Now you are all set and can write your first PicoGK application. + +Return to the [Getting Started](README.md#your-first-picogk-app) page to see how to run your first PicoGK app. \ No newline at end of file diff --git a/Examples/Ex_BooleanShowCase.cs b/Examples/Ex_BooleanShowCase.cs new file mode 100644 index 0000000..5547025 --- /dev/null +++ b/Examples/Ex_BooleanShowCase.cs @@ -0,0 +1,123 @@ +// +// SPDX-License-Identifier: CC0-1.0 +// +// This example code file is released to the public under Creative Commons CC0. +// See https://creativecommons.org/publicdomain/zero/1.0/legalcode +// +// To the extent possible under law, LEAP 71 has waived all copyright and +// related or neighboring rights to this PicoGK example code file. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using PicoGK; +using System.Numerics; + +namespace PicoGKExamples +{ + /////////////////////////////////////////////////////////////////////////// + // Below is a static class that implements a single static function + // that can be called from Library::Go() + + class BooleanShowCase + { + public class Sphere : IImplicit + { + public Sphere( float fRadius, + Vector3 vecCenter) + { + m_fRadius = fRadius; + m_vecC = vecCenter; + oBB = new BBox3( vecCenter - new Vector3(fRadius), + vecCenter + new Vector3(fRadius)); + } + + public float fSignedDistance(in Vector3 vecSample) + { + Vector3 vecPt = vecSample - m_vecC; + // Move sample point to origin by subtracting center + + return float.Sqrt( vecPt.X * vecPt.X + + vecPt.Y * vecPt.Y + + vecPt.Z * vecPt.Z) - m_fRadius; + } + + public readonly BBox3 oBB; + float m_fRadius; + Vector3 m_vecC; + } + + public static void Task() + { + try + { + Library.oViewer().SetGroupMaterial(0, "EE", 0f, 1f); + + Library.oViewer().SetGroupMaterial(1, "FF000033", 0f, 1f); + // Transparent Red + + Library.oViewer().SetGroupMaterial(2, "00FF0033", 0f, 1f); + // Transparent Green + + Sphere oSphere1A = new Sphere(20f, new Vector3(-5f, 0, 0)); + Sphere oSphere1B = new Sphere(20f, new Vector3(5f, 0, 0)); + + // Boolean Add showcase + + Voxels vox1 = new Voxels(oSphere1A, oSphere1A.oBB); + vox1.BoolAdd(new Voxels(oSphere1B, oSphere1B.oBB)); + Library.oViewer().Add(vox1); + + Sphere oSphere2A = new Sphere(20f, new Vector3(-5f + 90, 0, 0)); + Sphere oSphere2B = new Sphere(20f, new Vector3(5f + 90, 0, 0)); + + // Boolean Subtract showcase + + Voxels vox2A = new Voxels(oSphere2A, oSphere2A.oBB); + Voxels vox2B = new Voxels(oSphere2B, oSphere2B.oBB); + + vox2A.BoolSubtract(vox2B); + + Library.oViewer().Add(vox2A); + Library.oViewer().Add(vox2B, 1); + + // Boolean Intersect showcase + + Sphere oSphere3A = new Sphere(20f, new Vector3(-5f + 180, 0, 0)); + Sphere oSphere3B = new Sphere(20f, new Vector3(5f + 180, 0, 0)); + + Voxels vox3A = new Voxels(oSphere3A, oSphere3A.oBB); + Voxels vox3B = new Voxels(oSphere3B, oSphere3B.oBB); + Voxels vox3 = new Voxels(vox3A); + + vox3.BoolIntersect(vox3B); + + Library.oViewer().Add(vox3); + Library.oViewer().Add(vox3A, 2); + Library.oViewer().Add(vox3B, 2); + + Voxels vox = new Voxels(); + + vox.BoolAdd(vox3); + vox.BoolAdd(vox2A); + vox.BoolAdd(vox1); + + Mesh msh = new Mesh(vox); + msh.SaveToStlFile( Path.Combine(Library.strLogFolder, + "Booleans.stl")); + } + + catch (Exception e) + { + Library.Log($"Failed run example: \n{e.Message}"); ; + } + } + } +} + diff --git a/Examples/Ex_BooleanTwoMeshCubes.cs b/Examples/Ex_BooleanTwoMeshCubes.cs new file mode 100644 index 0000000..daee921 --- /dev/null +++ b/Examples/Ex_BooleanTwoMeshCubes.cs @@ -0,0 +1,74 @@ +// +// SPDX-License-Identifier: CC0-1.0 +// +// This example code file is released to the public under Creative Commons CC0. +// See https://creativecommons.org/publicdomain/zero/1.0/legalcode +// +// To the extent possible under law, LEAP 71 has waived all copyright and +// related or neighboring rights to this PicoGK example code file. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using PicoGK; +using System.Diagnostics; +using System.Numerics; + +namespace PicoGKExamples +{ + /////////////////////////////////////////////////////////////////////////// + // Below is a static class that implements a single static function + // that can be called from Library::Go() + + class BooleanTwoMeshCubes + { + public static void Task() + { + try + { + // Create a 100mm cube at the origin + Mesh mshCube1 = Utils.mshCreateCube(new Vector3(100.0f)); + + // Create a 50mm cube offset 30mm diagonally + Mesh mshCube2 = Utils.mshCreateCube( new Vector3(50.0f), + new Vector3(30f)); + + // Render both objects into voxel fields + Voxels voxCube1 = new Voxels(mshCube1); + Voxels voxCube2 = new Voxels(mshCube2); + + // Subtract (cut away), voxCube2 from voxCube1 + voxCube1.BoolSubtract(voxCube2); + + // Make a copy of the resulting object + Voxels voxShell = new(voxCube1); + + // Offset by 2mm + voxShell.Offset(2f); + + // Cut out voxCube1 to create a shell + voxShell.BoolSubtract(voxCube1); + + // Subtract Cube2 again to expose the inside of the shell + voxShell.BoolSubtract(voxCube2); + + // Add result to viewer + Library.oViewer().Add(voxShell); + + voxShell.mshAsMesh().SaveToStlFile( + Path.Combine(Library.strLogFolder, "Shell.stl")); + } + + catch (Exception e) + { + Library.Log($"Failed run example: \n{e.Message}"); ; + } + } + } +} diff --git a/Examples/Ex_ImplicitSphere.cs b/Examples/Ex_ImplicitSphere.cs new file mode 100644 index 0000000..09b4b09 --- /dev/null +++ b/Examples/Ex_ImplicitSphere.cs @@ -0,0 +1,113 @@ +// +// SPDX-License-Identifier: CC0-1.0 +// +// This example code file is released to the public under Creative Commons CC0. +// See https://creativecommons.org/publicdomain/zero/1.0/legalcode +// +// To the extent possible under law, LEAP 71 has waived all copyright and +// related or neighboring rights to this PicoGK example code file. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using PicoGK; +using System.Diagnostics; +using System.Numerics; + +namespace PicoGKExamples +{ + + /////////////////////////////////////////////////////////////////////////// + // Below is a static class that implements a single static function + // that can be called from Library::Go() + + class ImplicitSphereExample + { + // Let's derive a "Sphere" class from the Interface IImplict + // The main function it needs to implement is "fSignedDistance" + // This function returns the value for the signed distance field + // at the specified coordinate + // This simple sphere is always at the origin to illustrate the + // simplicity of signed distance functions + + public class ImplicitSphere : IImplicit + { + public ImplicitSphere(float fRadius) + { + m_fRadius = fRadius; + } + + public float fSignedDistance(in Vector3 vecPt) + { + return float.Sqrt( vecPt.X * vecPt.X + + vecPt.Y * vecPt.Y + + vecPt.Z * vecPt.Z) - m_fRadius; + } + + float m_fRadius; + } + + public static void Task() + { + try + { + // let's use a transparent red color for group 0 (default) + Library.oViewer().SetGroupMaterial(0, "EE000055", 0f, 1f); + + // Let's instantiate one implicit sphere, with the radius 50mm + ImplicitSphere oSphere = new ImplicitSphere(50f); + + // Create a new voxel field, which renders the lattice + // we are passing the bounding box of the sphere, so that + // we know which area in the voxel field to evaluate + + Voxels vox = new Voxels( oSphere, + new BBox3( new Vector3(-55f), + new Vector3(55f))); + + + // Let's show what we got + Library.oViewer().Add(vox); + + // To visualize how the voxels are stored internally + // let's get a slice and show how it looks + + // Get the dimensions of the discrete voxel field + vox.GetVoxelDimensions( out int nXSize, + out int nYSize, + out int nZSize); + + + // Create a new grayscale image to store the signed distance + // field in + ImageGrayScale imgSDFSlice = new ImageGrayScale(nXSize, nZSize); + + // Get a slice in the middle of the stack + vox.GetVoxelSlice(nZSize / 2, ref imgSDFSlice); + + // Let's get a fancy "color encoded" SDF, that shows the + // bands + ImageColor imgCoded = imgSDFSlice.imgGetColorCodedSDF(6.0f); + + // Save to your log folder for you to inspect + TgaIo.SaveTga( Path.Combine(Library.strLogFolder, "SDF.tga"), + imgCoded); + + TgaIo.SaveTga( Path.Combine(Library.strLogFolder, "SDFBW.tga"), + imgSDFSlice); + } + + catch (Exception e) + { + Library.Log($"Failed to run example: \n{e.Message}"); ; + } + } + } +} + diff --git a/Examples/Ex_ImplicitsExample.cs b/Examples/Ex_ImplicitsExample.cs new file mode 100644 index 0000000..94171b4 --- /dev/null +++ b/Examples/Ex_ImplicitsExample.cs @@ -0,0 +1,219 @@ +// +// SPDX-License-Identifier: CC0-1.0 +// +// This example code file is released to the public under Creative Commons CC0. +// See https://creativecommons.org/publicdomain/zero/1.0/legalcode +// +// To the extent possible under law, LEAP 71 has waived all copyright and +// related or neighboring rights to this PicoGK example code file. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using PicoGK; +using System.Numerics; + +namespace PicoGKExamples +{ + /////////////////////////////////////////////////////////////////////////// + // Below is a static class that implements a single static function + // that can be called from Library::Go() + + class ImplicitsExample + { + // Let's derive a "Gyroid" class from the Interface IImplict + // The main function it needs to implement is "fSignedDistance" + // This function returns the value for the signed distance field + // at the specified coordinate + + public class ImplicitGyroid : IImplicit + { + public ImplicitGyroid( float fUnitSize, + float fWallThickness) + { + m_fScale = MathF.PI / 0.5f; + m_fUnitSize = fUnitSize; + m_fWallThickness = fWallThickness; + } + + public float fSignedDistance(in Vector3 vecPt) + { + // Calculate the normalized coordinates within the gyroid space + double nx = vecPt.X / m_fUnitSize; + double ny = vecPt.Y / m_fUnitSize; + double nz = vecPt.Z / m_fUnitSize; + + // Calculate the gyroid surface equation + double fDist + = Math.Sin(m_fScale * nx) * Math.Cos(m_fScale * ny) + + Math.Sin(m_fScale * ny) * Math.Cos(m_fScale * nz) + + Math.Sin(m_fScale * nz) * Math.Cos(m_fScale * nx); + + // Apply thickness to the gyroid surface + return (float)(Math.Abs(fDist) - 0.5f * m_fWallThickness); + } + + float m_fUnitSize; + float m_fScale; + float m_fWallThickness; + } + + // Let's also derive a implicit "Lattice" class, that renders a lattice + + class ImplicitLattice : IImplicit + { + public ImplicitLattice( Vector3 vecA, + Vector3 vecB, + float fA, + float fB, + bool bRoundCap) + { + m_vecA = vecA; + m_vecB = vecB; + m_fA = fA; + m_fB = fB; + m_bRoundCap = bRoundCap; + + oBB.Include(vecA); + oBB.Include(vecB); + oBB.Grow(float.Max(fA, fB)); + } + + public float fSignedDistance(in Vector3 vecP) + { + Vector3 vecAB = m_vecB - m_vecA; + Vector3 vecAP = vecP - m_vecA; + float t = Vector3.Dot(vecAP, vecAB) / Vector3.Dot(vecAB, vecAB); + t = Math.Clamp(t, 0f, 1f); // Ensure t is within the line segment + + Vector3 vecNearest = m_vecA + t * vecAB; + float fDistToLine = Vector3.Distance(vecP, vecNearest); + + if (t <= 0f) + { + // Point is before the line segment + return fDistToLine - (m_bRoundCap ? m_fA : 0f); + } + else if (t >= 1f) + { + // Point is after the line segment + return fDistToLine - (m_bRoundCap ? m_fB : 0f); + } + else + { + // Point is inside the line segment + float fR = m_fA + t * (m_fB - m_fA); + return fDistToLine - fR; + } + } + + public BBox3 oBB; + + Vector3 m_vecA; + Vector3 m_vecB; + float m_fA; + float m_fB; + bool m_bRoundCap; + }; + + public static void Task() + { + try + { + Library.oViewer().SetGroupMaterial(0, "EE0000", 0f, 1f); + + // Let's instantiate one implicit lattice, and store the + // parameters which will be used in the signed distance function + ImplicitLattice oLattice + = new ImplicitLattice( new Vector3(0f, 0f, 0f), + new Vector3(0f, 0f, 50f), + 5.0f, + 10.0f, + true); + + // Create a new voxel field, which renders the lattice + // we are passing the bounding box of the lattice, so that + // we know which area in the voxel field to evaluate + + Voxels voxL = new( oLattice, + oLattice.oBB); + + // Let's show what we got + Library.oViewer().Add(voxL); + + // Let's also instantiate a gyroid, storing the parameters + // which will be used in the signed distance function + ImplicitGyroid oGyroid = new ImplicitGyroid(25f, 1.5f); + + // Let's create a little box 50x50x50mm at position x 150 + // that is filled with a gyroid pattern + // The first argument is the gyroid object that is queried for + // the signed distance + // the second argument is again the bounding box which is + // evaluated + Voxels voxG = new( oGyroid, + new BBox3( 150, 0, 0, + 200, 50, 50)); + + // Extract a slice from the voxel field and save it to + // your log folder + + voxG.GetVoxelDimensions(out int nXSize, + out int nYSize, + out int nZSize); + + ImageGrayScale imgSDFSlice = new ImageGrayScale(nXSize, nZSize); + + voxG.GetVoxelSlice(nZSize / 2, ref imgSDFSlice); + + ImageColor imgCoded = imgSDFSlice.imgGetColorCodedSDF(6.0f); + + TgaIo.SaveTga( + Path.Combine(Library.strLogFolder, "SDF.tga"), + imgCoded); + + TgaIo.SaveTga( + Path.Combine(Library.strLogFolder, "SDFBW.tga"), + imgSDFSlice); + + // Let's show it + Library.oViewer().Add(voxG); + + // Now, for the fun of it, let's create another lattice + // and fill it with a gyroid + ImplicitLattice oLattice2 + = new ImplicitLattice( new Vector3(50f, 0f, 0f), + new Vector3(50f, 0f, 50f), + 10.0f, + 15.0f, + true); + + // Again, let's evaluate the lattice signed distance function + // in the bounding box of the lattice + Voxels voxI = new Voxels( oLattice2, + oLattice2.oBB); + + // The voxel field now contains a solid lattice + // Now let's evaluate the Gyroid signed distance function + // for every voxel that is already set in the voxel field + voxI.IntersectImplicit(oGyroid); + + // Now we have a lattice beam that is filled with a gyroid + // Let's show it + Library.oViewer().Add(voxI); + } + + catch (Exception e) + { + Library.Log($"Failed to run example: \n{e.Message}"); ; + } + } + } +} + diff --git a/Examples/Ex_LoadDisplaySaveSTL.cs b/Examples/Ex_LoadDisplaySaveSTL.cs new file mode 100644 index 0000000..72bac69 --- /dev/null +++ b/Examples/Ex_LoadDisplaySaveSTL.cs @@ -0,0 +1,84 @@ +// +// SPDX-License-Identifier: CC0-1.0 +// +// This example code file is released to the public under Creative Commons CC0. +// See https://creativecommons.org/publicdomain/zero/1.0/legalcode +// +// To the extent possible under law, LEAP 71 has waived all copyright and +// related or neighboring rights to this PicoGK example code file. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using PicoGK; +using System.Diagnostics; +using System.Numerics; + +namespace PicoGKExamples +{ + /////////////////////////////////////////////////////////////////////////// + // Below is a static class that implements a single static function + // that can be called from Library::Go() + + class LoadDisplaySaveSTL + { + public static void Task() + { + try + { + // Create a mesh from an existing STL file + Mesh msh = Mesh.mshFromStlFile( + Path.Combine( Utils.strPicoGKSourceCodeFolder(), + "Examples/Testfiles/Teapot.stl")); + + // Add it to the viewer + Library.oViewer().Add(msh); + + // Save it + msh.SaveToStlFile( + Path.Combine( Library.strLogFolder, + "Saved.stl")); + + // Create a voxel field from it + Voxels vox = new Voxels(msh); + + // Create a mesh from the voxels again + // (it now has a new topology, based on the voxels) + Mesh mshFromVox = new Mesh(vox); + + // Let's save that mesh as well, so we can compare + mshFromVox.SaveToStlFile( + Path.Combine( Library.strLogFolder, + "SavedFromVox.stl")); + + Library.oViewer().SetGroupMaterial(0, "22", 0.05f, 0.8f); + + Thread.Sleep(2000); + + // Remove the previous object and show voxels instead + Library.oViewer().RemoveAllObjects(); + Library.oViewer().Add(vox); + + Thread.Sleep(2000); + + Library.oViewer().SetGroupMaterial(0, "AA", 0.5f, 0.9f); + + // Remove the previous object and show mesh from voxels instead + Library.oViewer().RemoveAllObjects(); + Library.oViewer().Add(mshFromVox); + } + + catch (Exception e) + { + Library.Log($"Failed to run example: \n{e.Message}"); ; + } + } + } +} + diff --git a/Examples/Ex_SmoothenObject.cs b/Examples/Ex_SmoothenObject.cs new file mode 100644 index 0000000..436bab3 --- /dev/null +++ b/Examples/Ex_SmoothenObject.cs @@ -0,0 +1,63 @@ +// +// SPDX-License-Identifier: CC0-1.0 +// +// This example code file is released to the public under Creative Commons CC0. +// See https://creativecommons.org/publicdomain/zero/1.0/legalcode +// +// To the extent possible under law, LEAP 71 has waived all copyright and +// related or neighboring rights to this PicoGK example code file. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using PicoGK; +using System.Numerics; + +namespace PicoGKExamples +{ + /////////////////////////////////////////////////////////////////////////// + // Below is a static class that implements a single static function + // that can be called from Library::Go() + + class SmoothenObject + { + public static void Task() + { + try + { + // Create a mesh from an existing STL file + Mesh msh = Mesh.mshFromStlFile( + Path.Combine( Utils.strPicoGKSourceCodeFolder(), + "Examples/Testfiles/Teapot.stl")); + + // Add it to the viewer (moving it 20mm to the side) + Library.oViewer().Add( + msh.mshCreateTransformed( new Vector3(1.0f), + new Vector3(20f, 0f,0f))); + + // Create a voxel field from the mesh + Voxels vox = new Voxels(msh); + + // Use the TripleOffset function to smoothen the object + // this offsets the object 1mm inwards + // then offsets it 2mm outwards + // then offsets it 1mm inwards again + // eliminating detail of less than 1mm + vox.TripleOffset(1.0f); + Library.oViewer().Add(vox); + } + + catch (Exception e) + { + Library.Log($"Failed to run example: \n{e.Message}"); ; + } + } + } +} + diff --git a/Examples/Testfiles/Teapot.stl b/Examples/Testfiles/Teapot.stl new file mode 100644 index 0000000..d225918 Binary files /dev/null and b/Examples/Testfiles/Teapot.stl differ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b09cd78 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/PicoGK_Animation.cs b/PicoGK_Animation.cs new file mode 100644 index 0000000..9d07a9b --- /dev/null +++ b/PicoGK_Animation.cs @@ -0,0 +1,213 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Diagnostics; + +namespace PicoGK +{ + public class Animation + { + public interface IAction + { + // time from 0..1 + void Do(float fTime); + } + + public enum EType { Once, Repeat, Wiggle }; + + public enum EEasing { Linear, EaseOut, EaseIn, EaseInOut }; + + public Animation( IAction xAction, + float fDurationInSeconds, + EType eType, + EEasing eEasing) + { + m_xAction = xAction; + m_fDuration = fDurationInSeconds; + m_eType = eType; + m_eEasing = eEasing; + } + + public void End() + { + m_xAction.Do(1.0f); + } + + public bool bAnimate(float fCurrentTime) + { + if (m_fStartTime == 0.0f) + { + // Start + m_xAction.Do(0.0f); + m_fStartTime = fCurrentTime; + return true; + } + + if ((fCurrentTime - m_fStartTime) >= m_fDuration) + { + // At end + + m_xAction.Do(1.0f); + + if (m_eType == EType.Once) + return false; // We are done + + if (m_eType == EType.Wiggle) + m_bReverse = !m_bReverse; + + if ((fCurrentTime - m_fStartTime) > m_fDuration) + m_fStartTime += m_fDuration; + + return true; // Go on + } + + // in the midst + + float fPos = (fCurrentTime - m_fStartTime) / m_fDuration; + + if (m_bReverse) + fPos = 1.0f - fPos; + + if (m_eEasing == EEasing.Linear) + { + m_xAction.Do(fPos); + } + else + { + float fStep0 = 0.2f; + float fStep1 = 0.8f; + + if (m_eEasing == EEasing.EaseIn) + { + fStep0 = 0.5f; + fStep1 = 1.1f; + } + else if (m_eEasing == EEasing.EaseOut) + { + fStep0 = -0.1f; + fStep1 = 0.5f; + } + + m_xAction.Do(fSmoothStep(fStep0, fStep1, fPos)); + } + + return true; + } + + float m_fStartTime = 0.0f; + bool m_bReverse = false; + + IAction m_xAction; + float m_fDuration; + EType m_eType; + EEasing m_eEasing; + + public static float fSmoothStep(float edge0, float edge1, float x) + { + x = Math.Clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); + return x * x * (3 - 2 * x); + } + } + + public class AnimationQueue + { + public AnimationQueue() + { + m_oWatch.Start(); + } + + public void Clear() + { + lock (m_oAnimations) + { + foreach (Animation anim in m_oAnimations) + { + anim.End(); + } + + m_oAnimations.Clear(); + } + } + + public bool bPulse() + { + float fCurrentTimeSeconds = m_oWatch.ElapsedMilliseconds / 1000.0f; + bool bUpdateNeeded = false; + + lock (m_oAnimations) + { + List oToRemove = new(); + + foreach (Animation anim in m_oAnimations) + { + bUpdateNeeded = true; + if (!anim.bAnimate(fCurrentTimeSeconds)) + oToRemove.Add(anim); + + m_fLastActionTime = fCurrentTimeSeconds; + } + + foreach (Animation anim in oToRemove) + { + bUpdateNeeded = true; + + if (!anim.bAnimate(fCurrentTimeSeconds)) + m_oAnimations.Remove(anim); + } + } + + return bUpdateNeeded; + } + + public bool bIsIdle() + { + float fIdleSeconds = (m_oWatch.ElapsedMilliseconds / 1000.0f) - m_fLastActionTime; + return (fIdleSeconds > m_fIdleTime); + } + + public void Add(Animation oAnim) + { + lock (m_oAnimations) + { + m_oAnimations.Add(oAnim); + } + } + + Stopwatch m_oWatch = new Stopwatch(); + float m_fLastActionTime = 0.0f; + float m_fIdleTime = 5.0f; + List m_oAnimations = new List(); + } +} \ No newline at end of file diff --git a/PicoGK_BBox.cs b/PicoGK_BBox.cs new file mode 100644 index 0000000..ba50e09 --- /dev/null +++ b/PicoGK_BBox.cs @@ -0,0 +1,407 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Diagnostics; +using System.Numerics; +using System.Runtime.InteropServices; + +namespace PicoGK +{ + /// + /// 2D Bounding Box object + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BBox2 + { + /// + /// Creates an empty Bounding Box + /// + public BBox2() + { + vecMin.X = float.MaxValue; + vecMin.Y = float.MaxValue; + + vecMax.X = float.MinValue; + vecMax.Y = float.MinValue; + } + + public BBox2( float fMinX, + float fMinY, + float fMaxX, + float fMaxY) + { + vecMin.X = fMinX; + vecMin.Y = fMinY; + + vecMax.X = fMaxX; + vecMax.Y = fMaxY; + + // Making sure you have set this correctly + Debug.Assert(vecMin.X <= vecMax.X); + Debug.Assert(vecMin.Y <= vecMax.Y); + } + + /// + /// Creates a Bounding Box with the specified min/max + /// + /// Minimum Coord + /// Maximum Coord + public BBox2( in Vector2 vecSetMin, + in Vector2 vecSetMax) + { + vecMin = vecSetMin; + vecMax = vecSetMax; + + // Making sure you have set this correctly + Debug.Assert(vecMin.X <= vecMax.X); + Debug.Assert(vecMin.Y <= vecMax.Y); + } + + /// + /// Is the BoundingBox empty? + /// + /// True: Empty + public bool bIsEmpty() + { + if (vecMin.X == float.MaxValue) + { + Debug.Assert(vecMin.Y == float.MaxValue); + Debug.Assert(vecMax.X == float.MinValue); + Debug.Assert(vecMax.Y == float.MinValue); + return true; + } + + return false; + } + + /// + /// Include the specified vector in the bounding box + /// + /// Vector to include + public void Include(Vector2 vec) + { + vecMin.X = Math.Min(vecMin.X, vec.X); + vecMin.Y = Math.Min(vecMin.Y, vec.Y); + vecMax.X = Math.Max(vecMax.X, vec.X); + vecMax.Y = Math.Max(vecMax.Y, vec.Y); + } + + /// + /// Include the specified Bounding Box in this Box + /// + /// Bounding Box to include + public void Include(BBox2 oBox) + { + Include(oBox.vecMin); + Include(oBox.vecMax); + } + + /// + /// Grows the bounding box by the specified value on each side + /// I.E. the width, for example is width + 2*fGrowBy afterwards + /// + /// + public void Grow(float fGrowBy) + { + if (bIsEmpty()) + { + vecMin.X = -fGrowBy; + vecMin.Y = -fGrowBy; + vecMax.X = fGrowBy; + vecMax.Y = fGrowBy; + } + else + { + vecMin.X -= fGrowBy; + vecMin.Y -= fGrowBy; + vecMax.X += fGrowBy; + vecMax.Y += fGrowBy; + } + } + + /// + /// Size of the Bounding Box + /// + /// A vector corresponding to width and height + public Vector2 vecSize() + { + return vecMax - vecMin; + } + + /// + /// Center point of the bounding box + /// + /// Vector representing the center point + public Vector2 vecCenter() + { + return vecMin + vecSize() / 2; + } + + /// + /// A string representation of the Bounding Box + /// + /// + public override string ToString() + { + return ""; + } + + public Vector2 vecMin; + public Vector2 vecMax; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BBox3 + { + /// + /// Create an empty Bounding Box + /// + public BBox3() + { + vecMin.X = float.MaxValue; + vecMin.Y = float.MaxValue; + vecMin.Z = float.MaxValue; + + vecMax.X = float.MinValue; + vecMax.Y = float.MinValue; + vecMax.Z = float.MinValue; + } + + public BBox3( float fMinX, + float fMinY, + float fMinZ, + float fMaxX, + float fMaxY, + float fMaxZ) + { + vecMin.X = fMinX; + vecMin.Y = fMinY; + vecMin.Z = fMinZ; + + vecMax.X = fMaxX; + vecMax.Y = fMaxY; + vecMax.Z = fMaxZ; + + // Making sure you have set this correctly + Debug.Assert(vecMin.X <= vecMax.X); + Debug.Assert(vecMin.Y <= vecMax.Y); + Debug.Assert(vecMin.Z <= vecMax.Z); + } + + /// + /// Create a Bounding Box based on the specified min/max vectors + /// + /// Minimum vector + /// Maximum vector + public BBox3( in Vector3 vecSetMin, + in Vector3 vecSetMax) + { + vecMin = vecSetMin; + vecMax = vecSetMax; + + // Making sure you have set this correctly + Debug.Assert(vecMin.X <= vecMax.X); + Debug.Assert(vecMin.Y <= vecMax.Y); + Debug.Assert(vecMin.Z <= vecMax.Z); + } + + /// + /// Size of the Bounding Box + /// + /// A 3D Vector corresponding to width, height, depth of the box + public Vector3 vecSize() + { + return vecMax - vecMin; + } + + /// + /// Is the Bounding Box empty> + /// + /// True: Empty + public bool bIsEmpty() + { + if (vecMin.X == float.MaxValue) + { + Debug.Assert(vecMin.Y == float.MaxValue); + Debug.Assert(vecMin.Z == float.MaxValue); + Debug.Assert(vecMax.X == float.MinValue); + Debug.Assert(vecMax.Y == float.MinValue); + Debug.Assert(vecMax.Z == float.MinValue); + return true; + } + + return false; + } + + /// + /// Include the specified vector in the Bounding Box + /// + /// Vector to include + public void Include(Vector3 vec) + { + vecMin.X = Math.Min(vecMin.X, vec.X); + vecMin.Y = Math.Min(vecMin.Y, vec.Y); + vecMin.Z = Math.Min(vecMin.Z, vec.Z); + vecMax.X = Math.Max(vecMax.X, vec.X); + vecMax.Y = Math.Max(vecMax.Y, vec.Y); + vecMax.Z = Math.Max(vecMax.Z, vec.Z); + } + + /// + /// Include the specified Bounding Box into this box + /// + /// The box to include + public void Include(BBox3 oBox) + { + Include(oBox.vecMin); + Include(oBox.vecMax); + } + + /// + /// Include the specified 2D Bounding Box, with optional Z coord + /// + /// 2D Bounding Box to include + /// At which Z coord + public void Include(BBox2 oBox, float fZ = 0.0f) + { + Include(new Vector3(oBox.vecMin.X, oBox.vecMin.Y, fZ)); + Include(new Vector3(oBox.vecMax.X, oBox.vecMax.Y, fZ)); + } + + /// + /// Grows the bounding box by the specified value on each side + /// I.E. the width, for example is width + 2*fGrowBy afterwards + /// + /// + public void Grow(float fGrowBy) + { + if (bIsEmpty()) + { + vecMin.X = -fGrowBy; + vecMin.Y = -fGrowBy; + vecMin.Z = -fGrowBy; + vecMax.X = fGrowBy; + vecMax.Y = fGrowBy; + vecMax.Z = fGrowBy; + } + else + { + vecMin.X -= fGrowBy; + vecMin.Y -= fGrowBy; + vecMin.Z -= fGrowBy; + vecMax.X += fGrowBy; + vecMax.Y += fGrowBy; + vecMax.Z += fGrowBy; + } + } + + /// + /// Center of the Bounding Box + /// + /// The center vector of the Bounding Box + public Vector3 vecCenter() + { + return vecMin + vecSize() / 2; + } + + /// + /// Fit the specified Bounding Box into this box, returning Scale and Offset + /// + /// Bounding box to fit into this box + /// How much does it need to be scaled? + /// How much does it need to be offset after scale + /// + public BBox3 oFitInto( in BBox3 oBounds, + out float fScale, + out Vector3 vecOffset) + { + Vector3 vecNewMin = vecMin; + Vector3 vecNewMax = vecMax; + + float fScaleX = oBounds.vecSize().X / vecSize().X; + float fScaleY = oBounds.vecSize().Y / vecSize().Y; + float fScaleZ = oBounds.vecSize().Z / vecSize().Z; + + fScale = Math.Min(Math.Min(fScaleX, fScaleY), fScaleZ); + + vecNewMin *= fScale; + vecNewMax *= fScale; + + BBox3 oBB = new BBox3(vecNewMin, vecNewMax); + vecOffset = oBounds.vecCenter() - oBB.vecCenter(); + + oBB.vecMin += vecOffset; + oBB.vecMax += vecOffset; + + return oBB; + } + + /// + /// A function to return a random point in a Bounding Box + /// + /// Random object + /// A random vector inside of this Bounding Box + public Vector3 vecRandomVectorInside(ref Random oRand) + { + return new Vector3( oRand.NextSingle() * vecSize().X + vecMin.X, + oRand.NextSingle() * vecSize().Y + vecMin.Y, + oRand.NextSingle() * vecSize().Z + vecMin.Z); + } + + /// + /// Return the 2D extent of this Bounding Box + /// + /// A 2D Bounding Box with the X/Y extent of this Bounding Box + public BBox2 oAsBoundingBox2() + { + BBox2 oBB2 = new(); + oBB2.Include(new Vector2(vecMin.X, vecMin.Y)); + oBB2.Include(new Vector2(vecMax.X, vecMax.Y)); + return oBB2; + } + + /// + /// Return the Bounding Box as string + /// + /// String with the extent of the box + public override string ToString() + { + return ""; + } + + public Vector3 vecMin; + public Vector3 vecMax; + } +} \ No newline at end of file diff --git a/PicoGK_Cli.cs b/PicoGK_Cli.cs new file mode 100644 index 0000000..2f7281d --- /dev/null +++ b/PicoGK_Cli.cs @@ -0,0 +1,681 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Text; +using System.Numerics; +using System.Diagnostics; + +namespace PicoGK +{ + /// + /// ASCII CLI (Common Layer Interface) I/O based on + /// https://www.hmilch.net/downloads/cli_format.html#:~:text=CLI%20is%20intended%20as%20a,data%20structure%20of%20the%20machine. + /// Probably doesn't work if each $$ command is not in a separate line (\n) the spec is not clear whether this should be possible + /// The parser could probably be written in a more concise way if it generalized the structure $$ .. / for commands. + /// But this is generally not a well-though-out format. + /// + public class CliIo + { + public class Result + { + public PolySliceStack oSlices = new(); + public BBox3 oBBoxFile = new(); + public bool bBinary = false; + public float fUnitsHeader = 0.0f; + public bool b32BitAlign = false; + public UInt32 nVersion = 0; + public string strHeaderDate = ""; + public UInt32 nLayers = 0; + public string strWarnings = ""; + } + + public static void WriteSlicesToCliFile(PolySliceStack oSlices, + string strFilePath, + string strDate = "", + float fUnitsInMM = 0.0f) + { + if (fUnitsInMM <= 0.0f) + fUnitsInMM = 1.0f; + + if (strDate == "") + strDate = DateTime.Now.ToString("yyyy-MM-dd"); + + using (FileStream oFile = new(strFilePath, FileMode.Create, FileAccess.Write)) + { + using (StreamWriter oTextWriter = new(oFile, Encoding.ASCII)) + { + oTextWriter.WriteLine("$$HEADERSTART"); + oTextWriter.WriteLine("$$ASCII"); + oTextWriter.WriteLine("$$UNITS/{0, 00000000}", fUnitsInMM); + oTextWriter.WriteLine("$$VERSION/200"); + oTextWriter.WriteLine("$$LABEL/1,default"); + oTextWriter.WriteLine("$$DATE/" + strDate); + + + string strDim = oSlices.oBBox().vecMin.X.ToString("00000000.00000") + "," + + oSlices.oBBox().vecMin.Y.ToString("00000000.00000") + "," + + oSlices.oBBox().vecMin.Z.ToString("00000000.00000") + "," + + oSlices.oBBox().vecMax.X.ToString("00000000.00000") + "," + + oSlices.oBBox().vecMax.Y.ToString("00000000.00000") + "," + + oSlices.oBBox().vecMax.Z.ToString("00000000.00000"); + + oTextWriter.WriteLine("$$DIMENSION/{0}", strDim); + oTextWriter.WriteLine("$$LAYERS/{0}", (oSlices.nCount() + 1).ToString("00000")); + oTextWriter.WriteLine("$$HEADEREND"); + oTextWriter.WriteLine("$$GEOMETRYSTART"); + + // Add the zero layer at the bottom + oTextWriter.WriteLine("$$LAYER/0.0"); + + // Now add all the actual layers + for (int nLayer = 0; nLayer < oSlices.nCount(); nLayer++) + { + PolySlice oSlice = oSlices.oSliceAt(nLayer); + oTextWriter.WriteLine("$$LAYER/{0}", (oSlice.fZPos() / fUnitsInMM).ToString("0.00000")); + + for (int nPass = 0; nPass < 3; nPass++) + { + for (int nPolyline = 0; nPolyline < oSlice.nCountours(); nPolyline++) + { + PolyContour oPoly = oSlice.oCountourAt(nPolyline); + + if ((nPass == 0) && (oPoly.eWinding() != PolyContour.EWinding.COUNTERCLOCKWISE)) + { + // Outside contours first + continue; + } + else if ((nPass == 1) && (oPoly.eWinding() != PolyContour.EWinding.CLOCKWISE)) + { + // Inside contours second + continue; + } + else if ((nPass == 2) && (oPoly.eWinding() != PolyContour.EWinding.UNKNOWN)) + { + // Unknown contours last + continue; + } + + int nWinding = 2; // open/unknown/degenerate + + if (oPoly.eWinding() == PolyContour.EWinding.CLOCKWISE) + nWinding = 0; // internal + else if (oPoly.eWinding() == PolyContour.EWinding.COUNTERCLOCKWISE) + nWinding = 1; // external + + // Create a StringBuilder instance + StringBuilder oPolyLine = new StringBuilder(); + + // Append the initial part + oPolyLine.Append("$$POLYLINE/1,"); + oPolyLine.Append(nWinding.ToString()); + oPolyLine.Append(","); + oPolyLine.Append(oPoly.nCount().ToString()); + + for (int nVertex = 0; nVertex < oPoly.nCount(); nVertex++) + { + oPolyLine.Append(","); + oPolyLine.Append((oPoly.vecVertex(nVertex).X / fUnitsInMM).ToString("0.00000")); + oPolyLine.Append(","); + oPolyLine.Append((oPoly.vecVertex(nVertex).Y / fUnitsInMM).ToString("0.00000")); + } + + oTextWriter.WriteLine(oPolyLine.ToString()); + + } + } + } + + oTextWriter.WriteLine("$$GEOMETRYEND"); + } + } + } + + public static Result oSlicesFromCliFile(string strFilePath) + { + Result oResult = new(); + + using (FileStream oFile = new(strFilePath, FileMode.Open, FileAccess.Read)) + { + long nFileSize = oFile.Length; + + // It's not entirely clear from the sta + using (StreamReader oTextReader = new(oFile, Encoding.ASCII)) + { + string? strLine; + bool bInComment = false; + + UInt32 nLabel = UInt32.MaxValue; + string strLabel = ""; + + UInt32 nHeaderLayerNumbers = UInt32.MaxValue; + + bool bHeaderStarted = false; + bool bHeaderEnded = false; + + int iCurrentFileLine = 0; + + while ((!bHeaderEnded) && (strLine = oTextReader.ReadLine()) != null) + { + iCurrentFileLine++; + + // It is not entirely clear from the documentation + // But it appears that the CLI files are organized with + // newlines separating the commands + // So, multiple commands in one line are likely not + // possible, and probably also not multi-line comments + // We are trying to ignore the newlines anyway and + // see how for we get + while (strLine != "") + { + strLine.Trim(); + if (strLine.StartsWith("//")) + { + bInComment = true; + strLine = strLine.Substring(2); + } + + if (bInComment) + { + int iEndComment = strLine.IndexOf("//"); + + if (iEndComment == -1) + { + strLine = ""; + // multi line comment + continue; + } + + strLine = strLine.Substring(iEndComment + 2); + strLine.Trim(); + bInComment = false; + } + + if (!bHeaderStarted) + { + int iHeaderStarted = strLine.IndexOf("$$HEADERSTART"); + if (iHeaderStarted == -1) + continue; // Ignore everything until header starts + + strLine = strLine.Substring(iHeaderStarted + "$$HEADERSTART".Length); + strLine.Trim(); + bHeaderStarted = true; + continue; + } + + if (!strLine.StartsWith("$$")) + continue; // Next round + + if (strLine.StartsWith("$$BINARY")) + { + strLine = strLine.Substring("$$BINARY".Length); + oResult.bBinary = true; + continue; + } + + if (strLine.StartsWith("$$ASCII")) + { + strLine = strLine.Substring("$$ASCII".Length); + oResult.bBinary = false; + continue; + } + + if (strLine.StartsWith("$$ALIGN")) + { + strLine = strLine.Substring("$$ALIGN".Length); + oResult.b32BitAlign = true; + continue; + } + + if (strLine.StartsWith("$$UNITS")) + { + strLine = strLine.Substring("$$UNITS".Length); + + string strParam = ""; + if (!bExtractParameter(ref strLine, ref strParam)) + throw new ArgumentException("Missing parameter after $$UNITS"); + + if (!float.TryParse(strParam, out oResult.fUnitsHeader)) + throw new ArgumentException("Invalid parameter for $$UNITS: " + strParam); + + if (oResult.fUnitsHeader <= 0.0f) + throw new ArgumentException("Invalid parameter for $$UNITS: " + strParam); + + continue; + } + + if (strLine.StartsWith("$$VERSION")) + { + strLine = strLine.Substring("$$VERSION".Length); + + string strParam = ""; + if (!bExtractParameter(ref strLine, ref strParam)) + throw new ArgumentException("Missing parameter after $$VERSION"); + + //if (!UInt32.TryParse(strParam, out oResult.nVersion)) + // throw new ArgumentException("Invalid parameter for $$VERSION: " + strParam); + + continue; + } + + if (strLine.StartsWith("$$LABEL")) + { + strLine = strLine.Substring("$$LABEL".Length); + + string strParam = ""; + if (!bExtractParameter(ref strLine, ref strParam)) + throw new ArgumentException("Missing parameter after $$LABEL"); + + if (nLabel != UInt32.MaxValue) + throw new NotSupportedException("Currently we do not support multiple labels and objects in one CLI file"); + + if (!UInt32.TryParse(strParam, out nLabel)) + throw new ArgumentException("Invalid parameter for $$LABEL: " + strParam); + + if (!bExtractParameter(ref strLine, ref strParam)) + throw new ArgumentException("Missing parameter after $$LABEL (text)"); + + strLabel = strParam; + strLabel.Trim(); + + continue; + } + + if (strLine.StartsWith("$$DATE")) + { + strLine = strLine.Substring("$$DATE".Length); + + string strParam = ""; + if (!bExtractParameter(ref strLine, ref strParam)) + throw new ArgumentException("Missing parameter after $$DATE"); + + strParam.Trim(); + + oResult.strHeaderDate = strParam; + continue; + } + + if (strLine.StartsWith("$$DIMENSION")) + { + strLine = strLine.Substring("$$DIMENSION".Length); + + float f = 0.0f; + + string strParam = ""; + + if (!bExtractParameter(ref strLine, ref strParam)) + throw new ArgumentException("Missing parameter (xMin) after $$DIMENSION"); + + if (!float.TryParse(strParam, out f)) + throw new ArgumentException("Invalid parameter (xMin) for $$DIMENSION: " + strParam); + + oResult.oBBoxFile.vecMin.X = f; + + if (!bExtractParameter(ref strLine, ref strParam)) + throw new ArgumentException("Missing parameter (yMin) after $$DIMENSION"); + + if (!float.TryParse(strParam, out f)) + throw new ArgumentException("Invalid parameter (yMin) for $$DIMENSION: " + strParam); + + oResult.oBBoxFile.vecMin.Y = f; + + if (!bExtractParameter(ref strLine, ref strParam)) + throw new ArgumentException("Missing parameter (zMin) after $$DIMENSION"); + + if (!float.TryParse(strParam, out f)) + throw new ArgumentException("Invalid parameter (zMin) for $$DIMENSION: " + strParam); + + oResult.oBBoxFile.vecMin.Z = f; + + if (!bExtractParameter(ref strLine, ref strParam)) + throw new ArgumentException("Missing parameter (xMax) after $$DIMENSION"); + + if (!float.TryParse(strParam, out f)) + throw new ArgumentException("Invalid parameter (xMax) for $$DIMENSION: " + strParam); + + oResult.oBBoxFile.vecMax.X = f; + + if (!bExtractParameter(ref strLine, ref strParam)) + throw new ArgumentException("Missing parameter (yMax) after $$DIMENSION"); + + if (!float.TryParse(strParam, out f)) + throw new ArgumentException("Invalid parameter (yMax) for $$DIMENSION: " + strParam); + + oResult.oBBoxFile.vecMax.Y = f; + + if (!bExtractParameter(ref strLine, ref strParam)) + throw new ArgumentException("Missing parameter (zMax) after $$DIMENSION"); + + if (!float.TryParse(strParam, out f)) + throw new ArgumentException("Invalid parameter (zMax) for $$DIMENSION: " + strParam); + + oResult.oBBoxFile.vecMax.Z = f; + + continue; + } + + if (strLine.StartsWith("$$LAYERS")) + { + strLine = strLine.Substring("$$LAYERS".Length); + + string strParam = ""; + if (!bExtractParameter(ref strLine, ref strParam)) + throw new ArgumentException("Missing parameter after $$LAYERS"); + + if (!UInt32.TryParse(strParam, out nHeaderLayerNumbers)) + throw new ArgumentException("Invalid parameter for $$LAYERS: " + strParam); + + continue; + } + + if (strLine.StartsWith("$$HEADEREND")) + { + bHeaderEnded = true; + break; + } + } // while not empty + } // while able to read file + + if (!bHeaderEnded) + throw new ArgumentException("End of file while searching for valid header"); + + // Now read the actual geometry + + if (oResult.bBinary) + throw new NotSupportedException("Binary CLI Files are not yet supported"); + + bool bGeometryStarted = false; + bool bGeometryEnded = false; + + PolySlice? oCurrentSlice = null; + + DateTime timePast = DateTime.Now; + + List oSlices = new(); + float fPrevZPos = float.MinValue; + + while ((!bGeometryEnded) && (strLine = oTextReader.ReadLine()) != null) + { + iCurrentFileLine++; + + //if (iCurrentFileLine > 10000) + // break; + + while (strLine != "") + { + DateTime time = DateTime.Now; + TimeSpan span = time - timePast; + + if (span.Seconds > 1) + { + float fPercentRead = 100.0f * oFile.Position / (float)nFileSize; + Console.WriteLine("Read: {0}% completed Line {1}: '{2}'...", Math.Floor(fPercentRead), iCurrentFileLine, Utils.strShorten(strLine, 20)); + timePast = time; + } + + strLine.Trim(); + if (strLine.StartsWith("//")) + { + bInComment = true; + strLine = strLine.Substring(2); + } + + if (bInComment) + { + int iEndComment = strLine.IndexOf("//"); + + if (iEndComment == -1) + { + strLine = ""; + // multi line comment + continue; + } + + strLine = strLine.Substring(iEndComment + 2); + strLine.Trim(); + bInComment = false; + } + + if (!bGeometryStarted) + { + int iHeaderStarted = strLine.IndexOf("$$GEOMETRYSTART"); + if (iHeaderStarted == -1) + continue; // Ignore everything until header starts + + strLine = strLine.Substring(iHeaderStarted + "$$GEOMETRYSTART".Length); + strLine.Trim(); + bGeometryStarted = true; + continue; + } + + if (strLine.StartsWith("$$LAYER")) + { + strLine = strLine.Substring("$$LAYER".Length); + + string strParam = ""; + if (!bExtractParameter(ref strLine, ref strParam)) + throw new ArgumentException("Missing parameter after $$LAYER"); + + float fZPos = 0.0f; + if (!float.TryParse(strParam, out fZPos)) + throw new ArgumentException("Invalid parameter for $$LAYER: " + strParam); + + fZPos *= oResult.fUnitsHeader; + + if (fPrevZPos != float.MinValue) // no previous layer + { + if (fZPos < fPrevZPos) + throw new ArgumentException("Z position in current layer is smaller than in previous " + strParam); + + fPrevZPos = fZPos; + } + else + { + fPrevZPos = 0.0f; + } + + if (fZPos > 0.0f) + { + if (oCurrentSlice != null) + { + oSlices.Add(oCurrentSlice); + } + + oCurrentSlice = new(fZPos); + } + + continue; + } + + if (strLine.StartsWith("$$POLYLINE")) + { + if (oCurrentSlice == null) + throw new ArgumentException("There should not be contours at z position 0"); + + Debug.Assert(oCurrentSlice != null); + + strLine = strLine.Substring("$$POLYLINE".Length); + + string strParam = ""; + + if (!bExtractParameter(ref strLine, ref strParam)) + throw new ArgumentException("Missing parameter after $$POLYLINE"); + + UInt32 nID = 0; + if (!UInt32.TryParse(strParam, out nID)) + throw new ArgumentException("Invalid parameter for $$POLYLINE: " + strParam); + + if (nLabel == UInt32.MaxValue) + nLabel = nID; // If no label, we label it with the first ID we encounter + + if (nID != nLabel) + throw new NotSupportedException("We do not support CLI labels and multiple models yet"); + + if (!bExtractParameter(ref strLine, ref strParam)) + throw new ArgumentException("Missing parameter after $$POLYLINE"); + + UInt32 nWinding = 0; + if (!UInt32.TryParse(strParam, out nWinding)) + throw new ArgumentException("Invalid parameter for $$POLYLINE direction: " + strParam); + + PolyContour.EWinding eWinding = PolyContour.EWinding.UNKNOWN; + if (nWinding == 0) + eWinding = PolyContour.EWinding.CLOCKWISE; + else if (nWinding == 1) + eWinding = PolyContour.EWinding.COUNTERCLOCKWISE; + else if (nWinding != 2) + throw new ArgumentException("Invalid parameter for $$POLYLINE direction: " + strParam); + + if (!bExtractParameter(ref strLine, ref strParam)) + throw new ArgumentException("Missing parameter polygon count after $$POLYLINE"); + + UInt32 nCount = 0; + if (!UInt32.TryParse(strParam, out nCount)) + throw new ArgumentException("Invalid parameter for $$POLYLINE polygon count: " + strParam); + + List oVertices = new(); + + while (nCount > 0) + { + time = DateTime.Now; + span = time - timePast; + + if (span.Seconds > 10) + { + float fPercentRead = 100.0f * oFile.Position / (float)nFileSize; + Console.WriteLine("Read: {0}% Vertex countdown: {1}", fPercentRead, nCount); + timePast = time; + } + + float fX = 0.0f; + + if (!bExtractParameter(ref strLine, ref strParam)) + throw new ArgumentException("Missing vertices in $$POLYLINE"); + + if (!float.TryParse(strParam, out fX)) + throw new ArgumentException("Invalid parameter (X) for $$POLYLINE vertex: " + strParam); + + float fY = 0.0f; + + if (!bExtractParameter(ref strLine, ref strParam)) + throw new ArgumentException("Missing vertices in $$POLYLINE"); + + if (!float.TryParse(strParam, out fY)) + throw new ArgumentException("Invalid parameter (Y) for $$POLYLINE vertex: " + strParam); + + fX *= oResult.fUnitsHeader; + fY *= oResult.fUnitsHeader; + + oVertices.Add(new Vector2(fX, fY)); + nCount--; + } + + if (oVertices.Count < 3) + { + oResult.strWarnings += "Line: " + iCurrentFileLine.ToString() + " Discarding POLYLINE with " + oVertices.Count.ToString() + " vertices which is degenerate\n"; + continue; + } + + PolyContour oPoly = new(oVertices); + + if (oPoly.eWinding() == PolyContour.EWinding.UNKNOWN) + { + oResult.strWarnings += "Line: " + iCurrentFileLine.ToString() + " Discarding POLYLINE with area 0 (degenerate) - defined with winding " + PolyContour.strWindingAsString(eWinding) + "\n"; + continue; + } + + if (oPoly.eWinding() != eWinding) + { + oResult.strWarnings += "Line: " + iCurrentFileLine.ToString() + " POLYLINE defined with winding " + PolyContour.strWindingAsString(eWinding) + " actual winding is " + PolyContour.strWindingAsString(oPoly.eWinding()) + " (using actual)\n"; + } + + oCurrentSlice.AddContour(oPoly); + continue; + } + + if (strLine.StartsWith("$$GEOMETRYEND")) + { + bGeometryEnded = true; + break; // we are done + } + + if (strLine.StartsWith("$$")) + { + oResult.strWarnings += "Line: " + iCurrentFileLine.ToString() + " Unsupported command " + Utils.strShorten(strLine, 20) + "\n"; + // unknown command + strLine = ""; + continue; + } + + Console.WriteLine("Line: {0} Unknown command", iCurrentFileLine); + Console.WriteLine(strLine); + strLine = ""; + } + } + + oResult.oSlices.AddSlices(oSlices); + } + } + + return oResult; + } + + public CliIo() + { + } + + private static bool bExtractParameter(ref string strLine, + ref string strParam) + { + if ((strLine.StartsWith('/')) || (strLine.StartsWith(','))) + strLine = strLine.Substring(1); + else + return false; // no parameter + + char[] achSep = { '$', '/', ',' }; + + int iEnd = strLine.IndexOfAny(achSep); + if (iEnd != -1) + { + strParam = strLine.Substring(0, iEnd); + strLine = strLine.Substring(strParam.Length); + return true; + } + + strParam = strLine; + strLine = ""; + return true; + } + } +} diff --git a/PicoGK_Color.cs b/PicoGK_Color.cs new file mode 100644 index 0000000..d32191b --- /dev/null +++ b/PicoGK_Color.cs @@ -0,0 +1,318 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Runtime.InteropServices; + +namespace PicoGK +{ + /// + /// A floating point color value with R,G,B,A values + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public partial struct ColorFloat + { + public float R; + public float G; + public float B; + public float A; + + /// + /// Create a color from a hex string + /// #FF0000 is red, for example (# is optional) + /// #FF000000 is a fully transparent color (0 is transparent FF/1.0 is full opaque) + /// #FF is grayscale (white) + /// #FF99 is semi-transparent white + /// + /// A 6 character or 8 character string with the color + /// Throws an exception if different sizes + public ColorFloat(string strHex) + { + if (strHex.StartsWith("#")) + strHex = strHex.Substring(1); // Remove the '#' character if present + + if ( (strHex.Length == 2) || + (strHex.Length == 4)) + { + // Grayscale "FF" or "FFFF" + R = Convert.ToInt32(strHex.Substring(0, 2), 16) / 255f; + G = R; + B = G; + + A = (strHex.Length == 2) ? 1.0f : Convert.ToInt32(strHex.Substring(2, 2), 16) / 255f; + } + else if ( (strHex.Length == 6) || + (strHex.Length == 8)) + { + R = Convert.ToInt32(strHex.Substring(0, 2), 16) / 255f; + G = Convert.ToInt32(strHex.Substring(2, 2), 16) / 255f; + B = Convert.ToInt32(strHex.Substring(4, 2), 16) / 255f; + + A = (strHex.Length == 6) ? 1.0f : Convert.ToInt32(strHex.Substring(6, 2), 16) / 255f; + } + else + { + throw new ArgumentException($"Invalid Hexcode format: {strHex}. The Hexcode should be either 2, 4 (Gray), 6 or 8 (Color) characters long (not counting optional leading #)."); + } + } + + /// + /// Allows you to pass a hex string to any function that requires a FloatColor + /// + /// Hexcode string + public static implicit operator ColorFloat(string hex) + { + return new ColorFloat(hex); + } + + /// + /// Creates a gray color based on the float value and optional alpha + /// + /// Gray value from 0.0 .. 1.0 + /// Optional Alpha, 0.0 is transparent + public ColorFloat( float fGray, + float fAlpha = 1.0f) + { + R = fGray; + G = fGray; + B = fGray; + A = fAlpha; + } + + /// + /// A color value from the 3/4 color components + /// + /// Red value + /// Green value + /// Blue value + /// Optional Alpha, 1.0 is fully opaque + public ColorFloat( float fR, + float fG, + float fB, + float fAlpha = 1.0f) + { + R = fR; + G = fG; + B = fB; + A = fAlpha; + } + + public ColorFloat(ColorRgb24 clr) + { + R = clr.R / 255.0f; + G = clr.G / 255.0f; + B = clr.B / 255.0f; + A = 1.0f; + } + + public ColorFloat(ColorRgba32 clr) + { + R = clr.R / 255.0f; + G = clr.G / 255.0f; + B = clr.B / 255.0f; + A = clr.A / 255.0f; + } + + public ColorFloat(ColorBgr24 clr) + { + R = clr.R / 255.0f; + G = clr.G / 255.0f; + B = clr.B / 255.0f; + A = 1.0f; + } + + public ColorFloat(ColorBgra32 clr) + { + R = clr.R / 255.0f; + G = clr.G / 255.0f; + B = clr.B / 255.0f; + A = clr.A / 255.0f; + } + + /// + /// Return a random color + /// + /// Random color + public static ColorFloat clrRandom(Random? oRand = null) + { + Random oRandom = oRand ?? new Random(); + + return new ColorFloat(oRandom.NextSingle(), + oRandom.NextSingle(), + oRandom.NextSingle()); + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ColorRgb24 + { + public ColorRgb24( byte byR, + byte byG, + byte byB) + { + R = byR; + G = byG; + B = byB; + } + + public ColorRgb24(ColorFloat clr) + { + R = (byte)(clr.R * 255.0f); + G = (byte)(clr.G * 255.0f); + B = (byte)(clr.B * 255.0f); + } + + /// + /// Allows you to pass a ColorFloat to any function that needs a ColorRgb24 + /// + /// The ColorFloat to use + public static implicit operator ColorRgb24(ColorFloat clr) + { + return new ColorRgb24(clr); + } + + public byte R; + public byte G; + public byte B; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ColorRgba32 + { + public ColorRgba32( byte byR, + byte byG, + byte byB, + byte byA = 255) + { + R = byR; + G = byG; + B = byB; + A = byA; + } + + public ColorRgba32(ColorFloat clr) + { + R = (byte)(clr.R * 255.0f); + G = (byte)(clr.G * 255.0f); + B = (byte)(clr.B * 255.0f); + A = (byte)(clr.A * 255.0f); + } + + /// + /// Allows you to pass a ColorFloat to any function that needs a ColorRgba32 + /// + /// The ColorFloat to use + public static implicit operator ColorRgba32(ColorFloat clr) + { + return new ColorRgba32(clr); + } + + public byte R; + public byte G; + public byte B; + public byte A; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ColorBgr24 + { + public ColorBgr24( byte byB, + byte byG, + byte byR) + { + B = byB; + G = byG; + R = byR; + } + + public ColorBgr24( ColorFloat clr) + { + R = (byte)(clr.R * 255.0f); + G = (byte)(clr.G * 255.0f); + B = (byte)(clr.B * 255.0f); + } + + /// + /// Allows you to pass a ColorFloat to any function that needs a ColorBgr24 + /// + /// The ColorFloat to use + public static implicit operator ColorBgr24(ColorFloat clr) + { + return new ColorBgr24(clr); + } + + public byte B; + public byte G; + public byte R; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ColorBgra32 + { + public ColorBgra32( byte byB, + byte byG, + byte byR, + byte byA = 255) + { + B = byB; + G = byG; + R = byR; + A = byA; + } + + public ColorBgra32( ColorFloat clr) + { + R = (byte)(clr.R * 255.0f); + G = (byte)(clr.G * 255.0f); + B = (byte)(clr.B * 255.0f); + A = (byte)(clr.A * 255.0f); + } + + /// + /// Allows you to pass a ColorFloat to any function that needs a ColorBgra32 + /// + /// The ColorFloat to use + public static implicit operator ColorBgra32(ColorFloat clr) + { + return new ColorBgra32(clr); + } + + public byte B; + public byte G; + public byte R; + public byte A; + } + +} + diff --git a/PicoGK_Csv.cs b/PicoGK_Csv.cs new file mode 100644 index 0000000..34553b7 --- /dev/null +++ b/PicoGK_Csv.cs @@ -0,0 +1,217 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace PicoGK +{ + public class CsvTable + { + public CsvTable( string strFilePath, + string strDelimiters = ",") + { + using (StreamReader oReader = new StreamReader(strFilePath)) + { + string? strLine = null; + + bool bFirst = true; + + // Read and process each line in the file + while ((strLine = oReader.ReadLine()) != null) + { + if (string.IsNullOrWhiteSpace(strLine)) + continue; + + string[] astrParts = strLine.Split(strDelimiters); + + List oColumns = new List(); + + foreach (string str in astrParts) + { + oColumns.Add(str.Trim()); + } + + if (bFirst) + { + m_oColumnIDs = oColumns; + bFirst = false; + } + else + { + m_oRows.Add(oColumns); + } + + m_nMaxColumnCount = Math.Max(m_nMaxColumnCount, oColumns.Count()); + } + + if (m_oColumnIDs is null) + throw new FileLoadException($"No content in CSV file {strFilePath}"); + } + } + + public void Save( string strFilePath, + string strDelimiter = ",") + { + using (StreamWriter oWriter = new StreamWriter(strFilePath)) + { + // Write column headers + { + string strLine = ""; + foreach (string str in m_oColumnIDs) + { + strLine += str + strDelimiter; + } + + strLine = strLine.Substring(0, strLine.Length - 1); // trim off trailing delimiter + oWriter.WriteLine(strLine); + } + + foreach (List oColumns in m_oRows) + { + string strLine = ""; + foreach (string str in oColumns) + { + strLine += str + strDelimiter; + } + + strLine = strLine.Substring(0, strLine.Length - 1); // trim off trailing delimiter + oWriter.WriteLine(strLine); + } + } + } + + public int nRowCount() + { + return m_oRows.Count; + } + + public int nMaxColumnCount() + { + return m_nMaxColumnCount; + } + + public string strGetAt( int nRow, + int nColumn) + { + if (nRow >= m_oRows.Count) + return ""; + + List oColumns = m_oRows[nRow]; + if (nColumn >= oColumns.Count) + return ""; + + return oColumns[nColumn]; + } + + public void SetKeyColumn(int nColumn) + { + m_nKeyColumn = nColumn; + } + + public bool bGetAt(in string strKey, + ref float fVal) + { + string str = ""; + + if (!bGetAt(strKey, ref str)) + return false; + + float.TryParse(str, out fVal); + return true; + } + + public bool bGetAt( in string strKey, + ref string strVal) + { + string[] astr = strKey.Split("."); + + if (astr.Count() != 2) + { + // Expecting "RowName.ColumnName" + return false; + } + + int nColumn; + if (!bFindColumn( astr[1], + out nColumn)) + { + return false; + } + + + foreach (List oColumns in m_oRows) + { + if (oColumns.Count <= m_nKeyColumn) + continue; // no value in KeyColumn + + if (oColumns[m_nKeyColumn].Equals(astr[0], StringComparison.OrdinalIgnoreCase)) + { + if (nColumn < oColumns.Count) + { + strVal = oColumns[nColumn]; + } + + return true; + } + } + + return false; + } + + public bool bFindColumn( string strColumnName, + out int nColumn) + { + // find column + int i = 0; + foreach (string str in m_oColumnIDs) + { + if (str.Equals(strColumnName, StringComparison.OrdinalIgnoreCase)) + { + nColumn = i; + return true; + } + i++; + } + + nColumn = -1; + return false; + } + + List m_oColumnIDs; + List> m_oRows = new List>(); + int m_nKeyColumn = 0; + int m_nMaxColumnCount = 0; + + } // class PicoGKCsv +} // namespace + diff --git a/PicoGK_Image.cs b/PicoGK_Image.cs new file mode 100644 index 0000000..74b1f1f --- /dev/null +++ b/PicoGK_Image.cs @@ -0,0 +1,448 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace PicoGK +{ + public abstract partial class Image + { + public enum EType + { + BW, + GRAY, + COLOR + }; + + public readonly int nWidth; + public readonly int nHeight; + public readonly EType eType; + + public abstract ColorFloat clrValue(int x, int y); + public abstract float fValue(int x, int y); + public abstract bool bValue(int x, int y); + + public abstract void SetValue(int x, int y, in ColorFloat clr); + public abstract void SetValue(int x, int y, float fGray); + public abstract void SetValue(int x, int y, bool bValue); + + public byte byGetValue(int x, int y) + { + return (byte)(Math.Clamp(fValue(x, y), 0.0f, 1.0f) * 255.0f); + } + + public void SetValue(int x, int y, byte byValue) + { + SetValue(x, y, byValue / 255.0f); + } + + public ColorBgr24 sGetBgr24(int x, int y) + { + ColorFloat clr = clrValue(x, y); + ColorBgr24 sClr; + sClr.B = (byte)(Math.Clamp(clr.B, 0.0f, 1.0f) * 255.0f); + sClr.G = (byte)(Math.Clamp(clr.G, 0.0f, 1.0f) * 255.0f); + sClr.R = (byte)(Math.Clamp(clr.R, 0.0f, 1.0f) * 255.0f); + return sClr; + } + + public void SetBgr24(int x, int y, ColorBgr24 sClr) + { + SetValue(x, y, new ColorFloat(sClr)); + } + + public ColorRgb24 sGetRgb24(int x, int y) + { + ColorFloat clr = clrValue(x, y); + ColorRgb24 sClr; + sClr.R = (byte)(Math.Clamp(clr.R, 0.0f, 1.0f) * 255.0f); + sClr.G = (byte)(Math.Clamp(clr.G, 0.0f, 1.0f) * 255.0f); + sClr.B = (byte)(Math.Clamp(clr.B, 0.0f, 1.0f) * 255.0f); + return sClr; + } + + public void SetRgb24(int x, int y, ColorRgb24 sClr) + { + SetValue(x, y, new ColorFloat(sClr)); + } + + public void DrawLine(int x0, int y0, int x1, int y1, ColorFloat clr) + { + int dx = Math.Abs(x1 - x0); + int dy = Math.Abs(y1 - y0); + int sx = (x0 < x1) ? 1 : -1; + int sy = (y0 < y1) ? 1 : -1; + int err = dx - dy; + + while (true) + { + SetValue(x0, y0, clr); + + if (x0 == x1 && y0 == y1) + { + break; + } + + int e2 = 2 * err; + if (e2 > -dy) + { + err -= dy; + x0 += sx; + } + if (e2 < dx) + { + err += dx; + y0 += sy; + } + } + } + + public void DrawLine(int x0, int y0, int x1, int y1, float fGrayscale) + { + int dx = Math.Abs(x1 - x0); + int dy = Math.Abs(y1 - y0); + int sx = (x0 < x1) ? 1 : -1; + int sy = (y0 < y1) ? 1 : -1; + int err = dx - dy; + + while (true) + { + SetValue(x0, y0, fGrayscale); + + if (x0 == x1 && y0 == y1) + { + break; + } + + int e2 = 2 * err; + if (e2 > -dy) + { + err -= dy; + x0 += sx; + } + if (e2 < dx) + { + err += dx; + y0 += sy; + } + } + } + + public void DrawLine(int x0, int y0, int x1, int y1, bool bValue) + { + int dx = Math.Abs(x1 - x0); + int dy = Math.Abs(y1 - y0); + int sx = (x0 < x1) ? 1 : -1; + int sy = (y0 < y1) ? 1 : -1; + int err = dx - dy; + + while (true) + { + SetValue(x0, y0, bValue); + + if (x0 == x1 && y0 == y1) + { + break; + } + + int e2 = 2 * err; + if (e2 > -dy) + { + err -= dy; + x0 += sx; + } + if (e2 < dx) + { + err += dx; + y0 += sy; + } + } + } + + protected Image( int _nWidth, + int _nHeight, + EType _eType) + { + Debug.Assert(_nWidth > 0); + Debug.Assert(_nHeight > 0); + + nWidth = _nWidth; + nHeight = _nHeight; + eType = _eType; + } + + } + + public abstract partial class ImageBWAbstract : Image + { + public ImageBWAbstract( int _nWidth, + int _nHeight) + : base(_nWidth, + _nHeight, + EType.BW) + { + } + + public override float fValue(int x, int y) + { + return bValue(x, y) ? 1.0f : 0.0f; + } + + public override ColorFloat clrValue(int x, int y) + { + return new ColorFloat(bValue(x, y) ? 1.0f : 0.0f); + } + + public override void SetValue(int x, int y, float fValue) + { + SetValue(x, y, fValue > 0.5f); + } + + public override void SetValue(int x, int y, in ColorFloat clr) + { + SetValue(x, y, ((clr.R + clr.G + clr.B) / 3.0f) > 0.5f); + } + } + + public abstract partial class ImageGrayscaleAbstract : Image + { + public ImageGrayscaleAbstract( int _nWidth, + int _nHeight) + : base(_nWidth, + _nHeight, + EType.GRAY) + { + } + + public override ColorFloat clrValue(int x, int y) + { + float f = fValue(x, y); + return new ColorFloat(f, f, f); + } + + public override bool bValue(int x, int y) + { + return fValue(x, y) > 0.5f; + } + + public override void SetValue(int x, int y, bool bValue) + { + SetValue(x, y, bValue ? 1.0f : 0.0f); + } + + public override void SetValue(int x, int y, in ColorFloat clr) + { + SetValue(x, y, (clr.R + clr.G + clr.B) / 3.0f); + } + } + + public abstract partial class ImageColorAbstract : Image + { + public ImageColorAbstract( int _iWidth, + int _iHeight) + : base(_iWidth, + _iHeight, + EType.COLOR) + { + } + + public override float fValue(int x, int y) + { + ColorFloat clr = clrValue(x, y); + return (clr.R + clr.G + clr.B) / 3.0f; + } + + public override bool bValue(int x, int y) + { + return fValue(x, y) > 0.5f; + } + + public override void SetValue(int x, int y, float f) + { + SetValue(x, y, new ColorFloat(f)); + } + + public override void SetValue(int x, int y, bool bValue) + { + SetValue(x, y, new ColorFloat(bValue ? 1.0f : 0.0f)); + } + } + + public partial class ImageGrayScale : ImageGrayscaleAbstract + { + public ImageGrayScale( int _nWidth, + int _nHeight) + : base( _nWidth, + _nHeight) + { + m_afValues = new float[nWidth * nHeight]; + } + + public override void SetValue(int x, int y, float fGray) + { + if ( (x < 0) || + (y < 0) || + (x >= nWidth) || + (y >= nHeight)) + { + return; + } + + m_afValues[x + (y * nWidth)] = fGray; + } + + public override float fValue(int x, int y) + { + if ( (x < 0) || + (y < 0) || + (x >= nWidth) || + (y >= nHeight)) + { + return 0.0f; + } + + return m_afValues[x + (y * nWidth)]; + } + + public ImageColor imgGetColorCodedSDF(float fBackground) + { + ImageColor img = new(nWidth, nHeight); + + ColorFloat clrInsideBackground = new("006600"); + ColorFloat clrOutsideBackground = new("00"); + + for (int x=0; x= fBackground) + img.SetValue(x, y, clrOutsideBackground); + else + { + fV /= fBackground; + img.SetValue(x, y, new ColorFloat(1f - fV, 1f - fV, 1f - fV)); + } + } + } + } + + return img; + } + + public static ImageGrayScale imgGetInterpolated( ImageGrayScale oImg1, + ImageGrayScale oImg2, + float fWeight = 0.5f) + { + Debug.Assert(oImg1.nWidth == oImg2.nWidth); + Debug.Assert(oImg1.nHeight == oImg2.nHeight); + + if (fWeight <= 0.0f) + return oImg1; + + if (fWeight >= 1.0f) + return oImg2; + + float fFac1 = 1.0f - fWeight; + + ImageGrayScale oNew = new(oImg1.nWidth, oImg1.nHeight); + + for (int n=0; n= nWidth) || + (y >= nHeight)) + { + return; + } + + m_aclrValues[x + (y * nWidth)] = clr; + } + + public override ColorFloat clrValue(int x, int y) + { + if ( (x < 0) || + (y < 0) || + (x >= nWidth) || + (y >= nHeight)) + { + return new ColorFloat(0, 0, 0); + } + + return m_aclrValues[x + (y * nWidth)]; + } + + ColorFloat[] m_aclrValues; + } +} + diff --git a/PicoGK_ImageIo.cs b/PicoGK_ImageIo.cs new file mode 100644 index 0000000..dba883f --- /dev/null +++ b/PicoGK_ImageIo.cs @@ -0,0 +1,265 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace PicoGK +{ + class TgaIo + { + public static void SaveTga( string strFilename, + in Image img) + { + using (var oFile = File.Open(strFilename, FileMode.Create)) + { + using (var oWriter = new BinaryWriter(oFile)) + { + SaveTga(oWriter, img); + } + } + } + + public static void SaveTga( in BinaryWriter oWriter, + in Image img) + { + if (img.nWidth > ushort.MaxValue) + throw new ArgumentOutOfRangeException("The width of the image " + nameof(img) + " is too large to store in a TGA"); + + if (img.nHeight > ushort.MaxValue) + throw new ArgumentOutOfRangeException("The height of the image " + nameof(img) + " is too large to store in a TGA"); + + ushort nSizeX = (ushort)img.nWidth; + ushort nSizeY = (ushort)img.nHeight; + + STgaHeader sHeader = new STgaHeader(nSizeX, nSizeY); + + bool bColor; + + if ( (img.eType == Image.EType.BW) || + (img.eType == Image.EType.GRAY)) + { + bColor = false; + sHeader.byImageType = 3; + sHeader.byPixelDepth = 8; + } + else + { + bColor = true; + sHeader.byImageType = 2; + sHeader.byPixelDepth = 24; + } + + var oHeaderSpan = MemoryMarshal.CreateSpan(ref sHeader, 1); + oWriter.Write(MemoryMarshal.AsBytes(oHeaderSpan)); + + if (bColor) + { + for (int y = 0; y < nSizeY; y++) + { + for (int x = 0; x < nSizeX; x++) + { + ColorBgr24 sClr = img.sGetBgr24(x, y); + var oSpan = MemoryMarshal.CreateSpan(ref sClr, 1); + oWriter.Write(MemoryMarshal.AsBytes(oSpan)); + } + } + } + else + { + for (int y = 0; y < nSizeY; y++) + { + for (int x = 0; x < nSizeX; x++) + { + oWriter.Write(img.byGetValue(x, y)); + } + } + } + } + + public static void GetFileInfo( string strFilename, + out Image.EType eType, + out int nWidth, + out int nHeight) + + { + using (var oFile = File.Open(strFilename, FileMode.Open)) + { + using (var oReader = new BinaryReader(oFile)) + { + GetFileInfo( in oReader, + out eType, + out nWidth, + out nHeight); + } + } + } + + public static void GetFileInfo( in BinaryReader oReader, + out Image.EType eType, + out int nWidth, + out int nHeight) + { + STgaHeader sHeader = new STgaHeader(0, 0); + + var oHeaderSpan = MemoryMarshal.CreateSpan(ref sHeader, 1); + oReader.Read(MemoryMarshal.AsBytes(oHeaderSpan)); + + nWidth = sHeader.ushImageWidth; + nHeight = sHeader.ushImageHeight; + + eType = Image.EType.GRAY; + + if (sHeader.byImageType == 2) + { + eType = Image.EType.COLOR; + } + else if (sHeader.byImageType != 3) + { + throw new ArgumentException("TGA has unsupported format (expecting grayscale or color)"); + } + } + + public static void LoadTga( string strFilename, + out Image img) + { + using (var oFile = File.Open(strFilename, FileMode.Open)) + { + using (var oReader = new BinaryReader(oFile)) + { + LoadTga(oReader, out img); + } + } + } + + public static void LoadTga( in BinaryReader oReader, + out Image img) + { + STgaHeader sHeader = new STgaHeader(0, 0); + + var oHeaderSpan = MemoryMarshal.CreateSpan(ref sHeader, 1); + oReader.Read(MemoryMarshal.AsBytes(oHeaderSpan)); + + bool bColor = false; + + if (sHeader.byImageType == 2) + { + bColor = true; + } + else if (sHeader.byImageType != 3) + { + throw new ArgumentException("TGA has unsupported format (expecting grayscale or color)"); + } + + if (bColor) + { + if (sHeader.byPixelDepth != 24) + throw new ArgumentException("TGA has unsupported bit depth (expecting 24) for color TGAs"); + + img = new ImageColor(sHeader.ushImageWidth, sHeader.ushImageHeight); + } + else + { + if (sHeader.byPixelDepth != 8) + throw new ArgumentException("TGA has unsupported bit depth (expecting 8) for grayscale TGAs"); + + img = new ImageGrayScale(sHeader.ushImageWidth, sHeader.ushImageHeight); + } + + ColorBgr24 sClr = new ColorBgr24(); + var oBgrSpan = MemoryMarshal.CreateSpan(ref sClr, 1); + + for (int y = 0; y < sHeader.ushImageHeight; y++) + { + for (int x = 0; x < sHeader.ushImageWidth; x++) + { + if (bColor) + { + oReader.Read(MemoryMarshal.AsBytes(oBgrSpan)); + img.SetBgr24(x, y, sClr); + } + else + { + byte[] aby = new byte[1]; + + oReader.Read(aby); + img.SetValue(x, y, aby[0] / 255.0f); + } + } + } + } + + // TGA Header + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct STgaHeader + { + byte byIDLength; + byte byColorMapType; + public byte byImageType; + byte byColorMapSpec1; + byte byColorMapSpec2; + byte byColorMapSpec3; + byte byColorMapSpec4; + byte byColorMapSpec5; + ushort ushXOrigin; + ushort ushYOrigin; + public ushort ushImageWidth; + public ushort ushImageHeight; + public byte byPixelDepth; + byte byImageDesc; + + public STgaHeader(ushort ushWidth, ushort ushHeight) + { + byIDLength = 0; + byColorMapType = 0; + byImageType = 3; // Grayscale + byColorMapSpec1 = 0; + byColorMapSpec2 = 0; + byColorMapSpec3 = 0; + byColorMapSpec4 = 0; + byColorMapSpec5 = 0; + ushXOrigin = 0; + ushYOrigin = 0; + ushImageWidth = ushWidth; + ushImageHeight = ushHeight; + byPixelDepth = 8; // Grayscale + byImageDesc = 32; + } + } + + } +} + diff --git a/PicoGK_Lattice.cs b/PicoGK_Lattice.cs new file mode 100644 index 0000000..195b953 --- /dev/null +++ b/PicoGK_Lattice.cs @@ -0,0 +1,84 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Diagnostics; +using System.Numerics; + +namespace PicoGK +{ + public partial class Lattice + { + public Lattice() + { + m_hThis = _hCreate(); + Debug.Assert(m_hThis != IntPtr.Zero); + } + + public void AddSphere( in Vector3 vecCenter, + float fRadius) + { + _AddSphere(m_hThis, vecCenter, fRadius); + } + + public void AddBeam( in Vector3 vecA, + float fRadA, + in Vector3 vecB, + float fRadB, + bool bRoundCap = true) + { + _AddBeam( m_hThis, + in vecA, + in vecB, + fRadA, + fRadB, + bRoundCap); + } + + public void AddBeam( in Vector3 vecA, + in Vector3 vecB, + float fRadA, + float fRadB, + bool bRoundCap = true) + { + _AddBeam( m_hThis, + in vecA, + in vecB, + fRadA, + fRadB, + bRoundCap); + } + } + +} \ No newline at end of file diff --git a/PicoGK_Library.cs b/PicoGK_Library.cs new file mode 100644 index 0000000..40cc974 --- /dev/null +++ b/PicoGK_Library.cs @@ -0,0 +1,403 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Diagnostics; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Text; + +namespace PicoGK +{ + public partial class Library + { + /// + /// Returns the library name (from the C++ side) + /// + /// The name of the dynamically loaded C++ library + public static string strName() + { + const int iLen = 255; + StringBuilder oBuilder = new StringBuilder(iLen); + _GetName(oBuilder); + return oBuilder.ToString(); + } + + /// + /// Returns the library version (from the C++ side) + /// + /// The library version of the C++ library + public static string strVersion() + { + const int iLen = 255; + StringBuilder oBuilder = new StringBuilder(iLen); + _GetVersion(oBuilder); + return oBuilder.ToString(); + } + + /// + /// Returns internal build info, such as build date/time + /// of the C++ library + /// + /// Internal build info of the C++ library + public static string strBuildInfo() + { + const int iLen = 255; + StringBuilder oBuilder = new StringBuilder(iLen); + _GetBuildInfo(oBuilder); + return oBuilder.ToString(); + } + + /// + /// This is the one library function that you call to run your code + /// it sets up the PicoGK library, with the specified voxel size and + /// builds the PicoGK environment with viewer, log and other internals + /// The fnTask you pass is called after everything is set up correctly + /// inside of fnTask, you do your processing, displaying it in + /// Library::oTheViewer and logging info with Library::Log() + /// + /// + /// The global voxel size in MM, for example 0.1 + /// + /// + /// The task to be executed (it will run in a separate thread) + /// + /// + /// The folder where you want the log file (defaults to your + /// documents folder + /// + /// + /// The file name for your log. Defaults to PicoGK_ with date and time + /// appended. If your specify the same log file name here, it prevents + /// PicoGK from creating a new log file name everytime. + /// + /// + /// This is purely a helper for you, it's not used internally. But you + /// can access this folder name throug Library::strSrcFolder, which is + /// convenient. + /// + /// + /// Throws an exception, for a number of scenarios, for example, if the + /// library cannot be found, folders, etc. cannot be created, etc. + /// Always handle the exception to understand what's going on. + /// + public static void Go( float _fVoxelSizeMM, + ThreadStart fnTask, + string strLogFolder = "", + string strLogFileName = "", + string strSrcFolder = "", + string strLightsFile = "") + { + Debug.Assert(_fVoxelSizeMM > 0.0f); + fVoxelSizeMM = _fVoxelSizeMM; + + TestAssumptions(); + + if (strLogFolder == "") + strLogFolder = Utils.strDocumentsFolder(); + + if (strLogFileName == "") + strLogFileName = "PicoGK.log"; + + string strLog = Path.Combine( strLogFolder, + strLogFileName); + + using (LogFile oLog = new LogFile(strLog)) + { + + lock (oMtxLog) + { + if (oTheLog is not null) + throw new Exception("You cannot call PicoGK.Library.Go() more than once per app (1)"); + + if (oTheViewer is not null) + throw new Exception("You cannot call PicoGK.Library.Go() more than once per app (2)"); + oTheLog = oLog; + + Library.strLogFolder = strLogFolder; + Library.strSrcFolder = strSrcFolder; + } + + Log("Welcome to PicoGK"); + + // Create a config using physical coordinates + _Init(fVoxelSizeMM); + // Done creating C++ Library + + if (strLightsFile == "") + { + string strSearched = ""; + + strLightsFile = Path.Combine(Utils.strPicoGKSourceCodeFolder(), "ViewerEnvironment/PicoGKDefaultEnv.zip"); + + if (!File.Exists(strLightsFile)) + { + strSearched += strLightsFile + "\n"; + + if (strSrcFolder == "") + { + strLightsFile = Path.Combine(strSrcFolder, "PicoGKDefaultEnv.zip"); + if (!File.Exists(strLightsFile)) + { + strSearched += strLightsFile + "\n"; + } + else + { + Log($"Could not find a lights file - your viewer will look quite dark.\nSearched in\n{strSearched}\n"); + Log("You can fix this by placing the file PicoGKLights.zip into one of these folders"); + Log("or providing the file as a parameter at Library.Go()"); + } + } + } + } + + Log("Creating Viewer"); + + Viewer? oViewer = null; + + try + { + oViewer = new Viewer("PicoGK", new Vector2(2048f, 1024f)); + } + + catch (Exception e) + { + Log("Failed to create viewer"); + Log(e.ToString()); + + throw new Exception("Failed to create all necessary objects"); + } + + using (oViewer) + { + if (!bSetup()) + { + Log("!! Failed to initialize !!"); + return; + } + + try + { + oViewer.LoadLightSetup(strLightsFile); + oViewer.SetBackgroundColor("FF"); + } + + catch (Exception e) + { + Log($"Failed to load Light Setup - your viewer will look dark\n{e.Message}"); + } + + + lock (oMtxViewer) + { + oTheViewer = oViewer; + } + + Thread oThread = new Thread(fnTask); + + Log("Starting tasks.\n"); + oThread.Start(); + + while (oViewer.bPoll()) + { + Thread.Sleep(5); // 200 Hz is plenty + } + + m_bAppExit = true; + Log("Viewer Window Closed"); + } + } + } + + /// + /// Checks whether the task started using Go() should continue, and returns true if that's the case or false otherwise. + /// If your task can take a non-trivial amount of time, check this function periodically. + /// If it returns false, exit the task function as soon as possible. + /// + /// If true, the bContinueTask function will only take into consideration if the application is about to exit. Any pending EndTask() requests will be ignored. + /// + public static bool bContinueTask(bool bAppExitOnly = false) + { + return !m_bAppExit && (bAppExitOnly || m_bContinueTask); + } + + /// + /// Requests the task started by the Go() function to end. + /// Note that it's the responsability of the task to call the bContinueTask() function periodically and to honor these requests. + /// + public static void EndTask() + { + m_bContinueTask = false; + } + + /// + /// Cancels any pending request to end the task. + /// + public static void CancelEndTaskRequest() + { + m_bContinueTask = true; + } + + static bool m_bAppExit = false; + static bool m_bContinueTask = true; + + /// + /// Thread-safe loging function + /// + /// + /// Use like Console.Write and others, so you can do Library.Log($"My variable {fVariable}") etc. + /// + /// + /// Will throw an exception if called before you call Library::Go, which shouldn't happen + /// + public static void Log(in string strFormat, params object[] args) + { + lock (oMtxLog) + { + if (oTheLog == null) + throw new Exception("Trying to access Log before Library::Go() was called"); + + oTheLog.Log(strFormat, args); + } + } + + /// + /// Thread-safe access to the viewer + /// + /// The viewer object + /// + /// Only throws an exception if called before you call Library::Go, which shouldn't happen + /// + public static Viewer oViewer() + { + lock (oMtxViewer) + { + if (oTheViewer is null) + throw new Exception("Trying to access Viewer before Library::Go() was called"); + + return oTheViewer; + } + } + + /// + /// This is an internal helper that tests if the data types have the + /// memory layout that we assume, so we don't run into interoperability issues + /// with the C++ side + /// + private static void TestAssumptions() + { + // Test a few assumptions + // Built in data type Vector3 is implicit, + // so should be compatible with our own + // structs, but let's be sure + + Vector3 vec3 = new(); + Vector2 vec2 = new(); + Matrix4x4 mat4 = new(); + Coord xyz = new(0, 0, 0); + Triangle tri = new(0, 0, 0); + ColorFloat clr = new(0f); + BBox2 oBB2 = new(); + BBox3 oBB3 = new(); + + Debug.Assert(sizeof(bool) == 1); // 8 bit for bool assumed + Debug.Assert(Marshal.SizeOf(vec3) == ((32 * 3) / 8)); // 3 x 32 bit float + Debug.Assert(Marshal.SizeOf(vec2) == ((32 * 2) / 8)); // 2 x 32 bit float + Debug.Assert(Marshal.SizeOf(mat4) == ((32 * 16) / 8)); // 4 x 4 x 32 bit float + Debug.Assert(Marshal.SizeOf(xyz) == ((32 * 3) / 8)); // 3 x 32 bit integer + Debug.Assert(Marshal.SizeOf(tri) == ((32 * 3) / 8)); // 3 x 32 bit integer + Debug.Assert(Marshal.SizeOf(clr) == ((32 * 4) / 8)); // 4 x 32 bit float + Debug.Assert(Marshal.SizeOf(oBB2) == ((32 * 2 * 2) / 8)); // 2 x vec2 + Debug.Assert(Marshal.SizeOf(oBB3) == ((32 * 3 * 2) / 8)); // 2 x vec3 + + // If any of these assert, then something is wrong with the + // memory layout, and the interface to compatible C libraries + // will fail - this should never happen, as all these types + // are well-defined + } + + /// + /// Logs the information from the library, usually the first line of + /// defence, if something is misconfigured, for example the library path + /// Also attempts to create all data types - if this blows up, then + /// something is wrong with the C++/C# interplay + /// + /// + private static bool bSetup() + { + try + { + Log($"PicoGK: {Library.strName()}"); + Log($" {Library.strVersion()}"); + Log($" {Library.strBuildInfo()}\n"); + Log($"VoxelSize: {Library.fVoxelSizeMM} (mm)"); + + Log("Happy Computational Engineering!\n\n"); + } + + catch (Exception e) + { + Log("Failed to get PicoGK library info:\n\n{0}", e.Message); + return false; + } + + try + { + Lattice lat = new(); + Voxels vox = new(); + Mesh msh = new(); + Voxels voxM = new(msh); + Voxels voxL = new(lat); + PolyLine oPoly = new("FF0000"); + } + + catch (Exception e) + { + Log("Failed to instantiate basic PicoGK types:\n\n{0}", e.Message); + return false; + } + + return true; + } + + public static float fVoxelSizeMM = 0.0f; + public static string strLogFolder = ""; + public static string strSrcFolder = ""; + + private static object oMtxLog = new object(); + private static object oMtxViewer = new object(); + private static LogFile? oTheLog = null; + private static Viewer? oTheViewer = null; + } +} \ No newline at end of file diff --git a/PicoGK_Log.cs b/PicoGK_Log.cs new file mode 100644 index 0000000..ae7c858 --- /dev/null +++ b/PicoGK_Log.cs @@ -0,0 +1,166 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace PicoGK +{ + public class LogFile : IDisposable + { + public LogFile(in string strFileName = "") + { + string strFile = strFileName; + + if (strFile == "") + { + strFile = Path.Combine( Utils.strDocumentsFolder(), + Utils.strDateTimeFilename("PicoGK_", ".log")); + } + + m_oWriter = new StreamWriter(strFile, false); + + if (m_oWriter is null) + throw new FileNotFoundException("Unable to create file " + strFile); + + m_oStopwatch = new Stopwatch(); + m_oStopwatch.Start(); + + m_fTimeStartSeconds = m_oStopwatch.ElapsedMilliseconds / 1000.0f; + m_fLastTimeSeconds = 0.0f; + + Log("Opened " + strFile); + Log("\n----------------------------------------\n"); + LogTime(); + Log("\n----------------------------------------\n"); + Log("System Info:\n"); + Log("Machine Name: {0}", Environment.MachineName); + Log("Operating System {0}", RuntimeInformation.OSDescription); + Log("Version: {0}", Environment.OSVersion); + Log("OS Architecture: {0}", RuntimeInformation.OSArchitecture); + Log("Proc Architecture: {0}", RuntimeInformation.ProcessArchitecture); + Log("64 Bit OS: {0}", Environment.Is64BitOperatingSystem ? "Yes" : "No"); + Log("64 Bit Process: {0}", Environment.Is64BitProcess ? "Yes" : "No"); + Log("Processor Count: {0}", Environment.ProcessorCount); + Log("Working Set: {0}MB", Environment.WorkingSet / 1024 / 1024); + Log("C# Framework: {0}", RuntimeInformation.FrameworkDescription); + Log("C# CLR Version: {0}", Environment.Version); + Log("PicoGK Path: {0}", Config.strPicoGKLib); + Log("Command Line: {0}", Environment.CommandLine); + + Log("\n----------------------------------------\n"); + } + + public void Log(in string strFormat, + params object[] args) + { + + float fSeconds = (m_oStopwatch.ElapsedMilliseconds / 1000.0f) - m_fTimeStartSeconds; + float fDiff = fSeconds - m_fLastTimeSeconds; + + string strPrefix = string.Format("{0,7:0.}s ", fSeconds) + + string.Format("{0,6:0.0}+ ", fDiff); + + string[] lines = string.Format(strFormat, args).Split(new char[] { '\n' }); + + lock (m_oMtx) + { + foreach (string str in lines) + { + Console.WriteLine(strPrefix + str); + m_oWriter?.WriteLine(strPrefix + str); + + m_oWriter?.Flush(); + m_fLastTimeSeconds = fSeconds; + } + } + } + + public void LogTime() + { + Log("Current time (UTC): " + DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss (UTC)")); + Log("Current local time: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss (zzz)")); + } + + ~LogFile() + { + Dispose(false); + } + + public void Dispose() + { + // Dispose of unmanaged resources. + Dispose(true); + // Suppress finalization. + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool bDisposing) + { + if (m_bDisposed) + { + return; + } + + if (bDisposing) + { + lock (m_oMtx) + { + // Release managed resources (e.g., close the file). + if (m_oWriter != null) + { + Log("\n----------------------------------------\n"); + Log("Closing log file."); + LogTime(); + Log("Done."); + m_oWriter.Dispose(); + m_oWriter = null; + } + } + } + + m_bDisposed = true; + } + + object m_oMtx = new object(); + StreamWriter? m_oWriter = null; + Stopwatch m_oStopwatch; + float m_fTimeStartSeconds; + float m_fLastTimeSeconds; + bool m_bDisposed = false; + + } +} // namespace PicoGK + diff --git a/PicoGK_Mesh.cs b/PicoGK_Mesh.cs new file mode 100644 index 0000000..110a909 --- /dev/null +++ b/PicoGK_Mesh.cs @@ -0,0 +1,224 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Diagnostics; +using System.Numerics; + +namespace PicoGK +{ + public partial class Mesh + { + /// + /// Create an empty Mesh + /// + public Mesh() + { + m_hThis = _hCreate(); + Debug.Assert(m_hThis != IntPtr.Zero); + } + + /// + /// Create a mesh from the specified voxels object + /// + /// Voxels to create a mesh from + public Mesh(in Voxels vox) + { + m_hThis = _hCreateFromVoxels(vox.m_hThis); + Debug.Assert(m_hThis != IntPtr.Zero); + } + + /// + /// Create a transformed mesh by offsetting and scaling it + /// + /// Scale the mesh (first step) + /// Offset the mesh (second step) + /// A new mesh that has the transformation applied + public Mesh mshCreateTransformed( Vector3 vecScale, + Vector3 vecOffset) + { + Mesh mshTrans = new Mesh(); + for (int n = 0; n < nTriangleCount(); n++) + { + GetTriangle( n, + out Vector3 A, + out Vector3 B, + out Vector3 C); + + A *= vecScale.X; + B *= vecScale.Y; + C *= vecScale.Z; + + A += vecOffset; + B += vecOffset; + C += vecOffset; + + mshTrans.nAddTriangle(A, B, C); + } + + return mshTrans; + } + + /// + /// Add a new vertex to the mesh so that it can be used in mesh triangles + /// + /// The vertex to add + /// The index of the vertex (to be used in triangles) + public int nAddVertex(in Vector3 vec) + { + return _nAddVertex(m_hThis, vec); + } + + /// + /// Get the vertex at the specified index + /// + /// The vertex index + /// The vertex coordinate + public Vector3 vecVertexAt(int nVertex) + { + Vector3 vec = new (); + _GetVertex(m_hThis, nVertex, ref vec); + return vec; + } + + /// + /// Get the number of vertices in the mesh + /// + /// The number of vertices in the mesh + public int nVertexCount() + { + return _nVertexCount(m_hThis); + } + + /// + /// Add a triangle to the mesh with the specified vertex indices + /// + /// Triangle with the vertex indices set to existing vertices + /// The triangle index of the added triangle + public int nAddTriangle(in Triangle t) + { + return _nAddTriangle(m_hThis, t); + } + + //// + /// Add a triangle to the mesh with the specified vertex indices + /// + /// First vertex in the triangle + /// Second vertex in the triangle + /// Third vertex in the triangle + /// The triangle index of the added triangle in the mesh + public int nAddTriangle(int A, int B, int C) + { + return nAddTriangle(new Triangle(A, B, C)); + } + + /// + /// Return number of triangles in the mesh + /// + /// Triangle count in mesh + public int nTriangleCount() + { + return _nTriangleCount(m_hThis); + } + + /// + /// Add a triangle specified by the three vertices to the mesh + /// First adds the vertices and then the triangle based on the vertex + /// index + /// + /// First vertex of the triangle + /// Second vertex of the triangle + /// Third vertex of the triangle + /// The triangle index of the added triangle in the mesh + public int nAddTriangle( in Vector3 vecA, + in Vector3 vecB, + in Vector3 vecC) + { + int A = nAddVertex(vecA); + int B = nAddVertex(vecB); + int C = nAddVertex(vecC); + return nAddTriangle(new Triangle(A, B, C)); + } + + /// + /// Get the triangle with the specified index + /// + /// Triangle index in the mesh + /// Triangle with the vertex indices set + public Triangle oTriangleAt(int nTriangle) + { + Triangle t = new(); + _GetTriangle( m_hThis, + nTriangle, + ref t); + + return t; + } + + /// + /// Get the triangle with the specified index + /// + /// Triangle index in the mesh + /// First vertex in the triangle + /// Second vertex in the triangle + /// Third vertex in the triangle + public void GetTriangle( int nTriangle, + out Vector3 vecA, + out Vector3 vecB, + out Vector3 vecC) + { + vecA = new(); + vecB = new(); + vecC = new(); + + _GetTriangleV( m_hThis, + nTriangle, + ref vecA, + ref vecB, + ref vecC); + } + + /// + /// Return the BoundingBox of the Mesh + /// + /// BoundingBox of the Mesh + public BBox3 oBoundingBox() + { + BBox3 oBBox = new BBox3(); + _GetBoundingBox(m_hThis, ref oBBox); + return oBBox; + } + } + +} \ No newline at end of file diff --git a/PicoGK_MeshIo.cs b/PicoGK_MeshIo.cs new file mode 100644 index 0000000..311c4be --- /dev/null +++ b/PicoGK_MeshIo.cs @@ -0,0 +1,374 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Text; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Diagnostics; + +namespace PicoGK +{ + public partial class Mesh + { + public enum EStlUnit { AUTO, MM, CM, M, FT, IN }; + + /// + /// Loads a mesh from an STL file + /// By default, it tries to find a UNITS= info in the header and uses that + /// to scale the mesh automatically to mm. If none is found mm is assumed + /// You can override this behaviour and explicitly specify a unit + /// To explicitly scale the STL, provide the fPostScale parameter + /// vecPostOffsetMM is applied last, offsetting the mesh + /// + /// + /// Path to the file. If not found or not able to open, an exception is thrown + /// + /// Fuse vertices that are close to each other (make mesh watertight) + /// Units to load + /// Scale parameter to be applied before offset + /// Offset parameter to be applied last + /// Returns a valid mesh in any case. If file was invalid, the mesh may be empty. + public static Mesh mshFromStlFile( string strFilePath, + EStlUnit eLoadUnit = EStlUnit.AUTO, // use units from file, or mm when not spec'd + float fPostScale = 1.0f, + Vector3? vecPostOffsetMM = null) + { + Mesh oMesh; + + using (FileStream oFile = new FileStream(strFilePath, FileMode.Open, FileAccess.Read)) + { + oMesh = oFromStlFile( oFile, + eLoadUnit, + fPostScale, + vecPostOffsetMM); + } + + return oMesh; + } + + + public static Mesh oFromStlFile( FileStream fileStream, + EStlUnit eLoadUnit = EStlUnit.AUTO, // use units from file, or mm when not spec'd + float fPostScale = 1.0f, + Vector3? vecPostOffsetMM = null) + { + string strHeader = ""; + + Vector3 vecOffset = new Vector3(0.0f); + + if (vecPostOffsetMM is not null) + vecOffset = (Vector3)vecPostOffsetMM; + + + using (BinaryReader oReader = new BinaryReader(fileStream, Encoding.ASCII)) + { + byte[] byHeader = new byte[80]; + oReader.Read(byHeader, 0, 80); + strHeader = Encoding.ASCII.GetString(byHeader).Trim(); + string strOriginal = strHeader; + + bool bAscii = strHeader.StartsWith("solid"); + + if (bAscii) + strHeader = strHeader.Substring("solid".Length); + + float fScale = 1.0f; // 1mm default + + if (eLoadUnit == EStlUnit.AUTO) + { + // See if there is the inofficial "UNIT" tag inside the header + int iUnit = strHeader.IndexOf("UNITS=", StringComparison.OrdinalIgnoreCase); + if (iUnit != -1) + { + strHeader = strHeader.Substring(iUnit + "units=".Length); + + if (strHeader.StartsWith(" m")) + eLoadUnit = EStlUnit.M; + else if (strHeader.StartsWith("mm")) + eLoadUnit = EStlUnit.MM; + else if (strHeader.StartsWith("cm")) + eLoadUnit = EStlUnit.CM; + else if (strHeader.StartsWith("ft")) + eLoadUnit = EStlUnit.FT; + else if (strHeader.StartsWith("in")) + eLoadUnit = EStlUnit.IN; + + // We don't support lightyears... + } + } + + Mesh oMesh = new Mesh(); + oMesh.m_strLoadHeaderData = strOriginal; + oMesh.m_eLoadUnits = eLoadUnit; + + if (bAscii) + { + oMesh.DoReadMeshFromAsciiStl(oReader, + eLoadUnit, + fPostScale, + vecOffset); + } + else + { + oMesh.DoReadMeshFromBinaryStl(oReader, + eLoadUnit, + fScale, + vecOffset); + } + + return oMesh; + } + } + + + + /// + /// Saves a Mesh to STL file + /// If eUnit is auto, then, if this mesh was loaded from an STL before + /// the same units as before are being used (stored in public property + /// m_eLoadUnits). If not loaded from an STL, defaults to mm. + /// Internal mesh units are always mm, so if a unit other than mm is + /// used, the mesh coordinates are scaled appropriately (divided by 10 + /// for CM units, for example). Our STL loaders use the UNIT= parameter + /// in the header, so whatever units you use, you will always get an STL + /// with the same dimensions if you load it again using our loader. + /// However many STL importers out there, ignore the UNIT info in the + /// header. + /// You can explicitly offset the mesh before saving (offset in mm) + /// after offset, a scale parameter can be applied. + /// The last operation is always the conversion to the unit + /// + /// File path. If not writable, exceptions is thrown + /// + /// If loaded previously, defaults to original units. + /// MM if not previously loaded. Can be overridden to save to a specifc + /// unit. + /// Offset applied while still in mm units + /// Scale applied after offset, while still in mm units + public void SaveToStlFile( string strFilePath, + EStlUnit eUnit = EStlUnit.AUTO, + Vector3? vecOffsetMM = null, + float fScale = 1.0f) + { + using (FileStream oFile = new FileStream(strFilePath, FileMode.Create, FileAccess.Write)) + { + SaveToStlFile(oFile, eUnit, vecOffsetMM, fScale); + } + } + + public void SaveToStlFile( FileStream oFile, + EStlUnit eUnit = EStlUnit.AUTO, + Vector3? vecOffsetMM = null, + float fScale = 1.0f) + { + Vector3 vecOffset = new Vector3(0.0f); + + if (vecOffsetMM is not null) + vecOffset = (Vector3)vecOffsetMM; + + if (eUnit == EStlUnit.AUTO) + eUnit = m_eLoadUnits; + + + using (BinaryWriter oWriter = new BinaryWriter(oFile, Encoding.ASCII)) + { + string strHeader = "PicoGK "; + + switch (eUnit) + { + case EStlUnit.CM: + strHeader += "UNITS=cm"; + break; + case EStlUnit.M: + strHeader += "UNITS= m"; + break; + case EStlUnit.FT: + strHeader += "UNITS=ft"; + break; + case EStlUnit.IN: + strHeader += "UNITS=in"; + break; + default: + strHeader += "UNITS=mm"; + break; + } + + strHeader = strHeader.PadRight(80, ' '); + + oWriter.Write(Encoding.ASCII.GetBytes(strHeader), 0, 80); + + UInt32 n32Triangles = (uint)nTriangleCount(); + oWriter.Write(n32Triangles); + + SStlTriangle sTriangle = new SStlTriangle(); + Span abyMemory = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref sTriangle, 1)); + + for (int n = 0; n < nTriangleCount(); n++) + { + GetTriangle(n, + out Vector3 v1, + out Vector3 v2, + out Vector3 v3); + + + TransformToUnit(ref v1, vecOffset, fScale, eUnit); + TransformToUnit(ref v2, vecOffset, fScale, eUnit); + TransformToUnit(ref v3, vecOffset, fScale, eUnit); + + + Vector3 vecNormal = Vector3.Cross((v2 - v1), (v3 - v1)); + vecNormal /= vecNormal.Length(); + + sTriangle.NormalX = vecNormal.X; + sTriangle.NormalY = vecNormal.Y; + sTriangle.NormalZ = vecNormal.Z; + sTriangle.V1X = v1.X; + sTriangle.V1Y = v1.Y; + sTriangle.V1Z = v1.Z; + sTriangle.V2X = v2.X; + sTriangle.V2Y = v2.Y; + sTriangle.V2Z = v2.Z; + sTriangle.V3X = v3.X; + sTriangle.V3Y = v3.Y; + sTriangle.V3Z = v3.Z; + sTriangle.AttributeByteCount = 0; + + oWriter.Write(abyMemory); + } + } + } + + void DoReadMeshFromAsciiStl(BinaryReader oReader, + EStlUnit eLoadUnit, + float fPostScale, + Vector3 vecPostOffsetMM) + { + + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct SStlTriangle + { + public float NormalX; + public float NormalY; + public float NormalZ; + public float V1X; + public float V1Y; + public float V1Z; + public float V2X; + public float V2Y; + public float V2Z; + public float V3X; + public float V3Y; + public float V3Z; + public ushort AttributeByteCount; + } + + void DoReadMeshFromBinaryStl(BinaryReader oReader, + EStlUnit eLoadUnit, + float fPostScale, + Vector3 vecPostOffsetMM) + { + UInt32 nNumberOfTriangles = oReader.ReadUInt32(); + + SStlTriangle sTriangle = new SStlTriangle(); + var oTriangleSpan = MemoryMarshal.CreateSpan(ref sTriangle, 1); + + while (nNumberOfTriangles > 0) + { + oReader.Read(MemoryMarshal.AsBytes(oTriangleSpan)); + + Vector3 v1 = new Vector3(sTriangle.V1X, sTriangle.V1Y, sTriangle.V1Z); + Vector3 v2 = new Vector3(sTriangle.V2X, sTriangle.V2Y, sTriangle.V2Z); + Vector3 v3 = new Vector3(sTriangle.V3X, sTriangle.V3Y, sTriangle.V3Z); + + TransformFromUnit(ref v1, eLoadUnit, fPostScale, vecPostOffsetMM); + TransformFromUnit(ref v2, eLoadUnit, fPostScale, vecPostOffsetMM); + TransformFromUnit(ref v3, eLoadUnit, fPostScale, vecPostOffsetMM); + + nAddTriangle(v1, v2, v3); + + nNumberOfTriangles--; + } + } + + void TransformToUnit(ref Vector3 v, + Vector3 vecPostOffsetMM, + float fMultiplier, + EStlUnit eUnit) + { + float fUnitDivider = fMultiplierFromUnit(eUnit); + + v += vecPostOffsetMM; + v *= fMultiplier; + v /= fUnitDivider; + } + + void TransformFromUnit(ref Vector3 v, + EStlUnit eUnit, + float fMultiplier, + Vector3 vecPostOffsetMM) + { + float fUnitDivider = fMultiplierFromUnit(eUnit); + + v *= fUnitDivider; + v *= fMultiplier; + v += vecPostOffsetMM; + } + + float fMultiplierFromUnit(EStlUnit eUnit) + { + switch (eUnit) + { + case EStlUnit.CM: + return 10.0f; + case EStlUnit.M: + return 1000.0f; + case EStlUnit.FT: + return 304.8f; + case EStlUnit.IN: + return 25.4f; + } + + // nothing to do + Debug.Assert(eUnit == EStlUnit.AUTO || + eUnit == EStlUnit.MM); + return 1.0f; + } + + public string m_strLoadHeaderData = ""; + public EStlUnit m_eLoadUnits = EStlUnit.AUTO; + } +} diff --git a/PicoGK_MeshMath.cs b/PicoGK_MeshMath.cs new file mode 100644 index 0000000..f06e6aa --- /dev/null +++ b/PicoGK_MeshMath.cs @@ -0,0 +1,99 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Numerics; + +namespace PicoGK +{ + public partial class Mesh + { + public bool bFindTriangleFromSurfacePoint( Vector3 vecSurfacePoint, + out int nTriangle) + { + for (int n = 0; n < nTriangleCount(); n++) + { + GetTriangle( n, + out Vector3 vecA, + out Vector3 vecB, + out Vector3 vecC); + + if (bPointLiesOnTriangle(vecSurfacePoint, vecA, vecB, vecC)) + { + nTriangle = n; + return true; + } + } + + nTriangle = int.MaxValue; + return false; + } + + static public bool bPointLiesOnTriangle( Vector3 vecP, + Vector3 vecA, + Vector3 vecB, + Vector3 vecC) + { + // Move the triangle so that the point becomes the + // triangles origin + + Vector3 a = vecA - vecP; + Vector3 b = vecB - vecP; + Vector3 c = vecC - vecP; + + // Compute the normal vectors for triangles: + // u = normal of PBC + // v = normal of PCA + // w = normal of PAB + + Vector3 u = Vector3.Cross(b, c); + Vector3 v = Vector3.Cross(c, a); + Vector3 w = Vector3.Cross(a, b); + + // Test to see if the normals are facing + // the same direction, return false if not + if (Vector3.Dot(u, v) < 0) + { + return false; + } + if (Vector3.Dot(u, w) < 0) + { + return false; + } + + // All normals facing the same way, return true + return true; + } + } +} diff --git a/PicoGK_PolyLine.cs b/PicoGK_PolyLine.cs new file mode 100644 index 0000000..9aefb18 --- /dev/null +++ b/PicoGK_PolyLine.cs @@ -0,0 +1,106 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Numerics; + +namespace PicoGK +{ + /// + /// A colored 3D polyline for use in the viewer + /// + public partial class PolyLine + { + /// + /// Create a new polyline with the specified color + /// + /// + public PolyLine(ColorFloat clr) + { + m_hThis = _hCreate(in clr); + } + + /// + /// Add a vertex to the polyline + /// + /// The specified vertex + /// The vertex index + public int nAddVertex(in Vector3 vec) + { + m_oBoundingBox.Include(vec); + return _nAddVertex( m_hThis, + in vec); + } + + /// + /// Return number of vertices in the PolyLine + /// + /// Number of vertices + public int nVertexCount() + { + return _nVertexCount(m_hThis); + } + + /// + /// Get the vertex in the polyline at the specified vertex index + /// + /// Vertex index to retrieve + /// Coordinate of the vertex + public Vector3 vecVertexAt(int nIndex) + { + Vector3 vec = new(); + _GetVertex(m_hThis, nIndex, ref vec); + return vec; + } + + /// + /// Return the color of the PolyLine + /// + /// PolyLine color + public void GetColor(out ColorFloat clr) + { + clr = new(); + _GetColor(m_hThis, ref clr); + } + + /// + /// Return BoundingBox of PolyLine + /// + /// Bounding Box + public BBox3 oBoundingBox() => m_oBoundingBox; + + BBox3 m_oBoundingBox = new BBox3(); + } +} + diff --git a/PicoGK_Slice.cs b/PicoGK_Slice.cs new file mode 100644 index 0000000..bc55451 --- /dev/null +++ b/PicoGK_Slice.cs @@ -0,0 +1,332 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Numerics; +using System.Diagnostics; + +namespace PicoGK +{ + public class PolyContour + { + public enum EWinding + { + UNKNOWN, + CLOCKWISE, + COUNTERCLOCKWISE + } + + public static string strWindingAsString(EWinding eWinding) + { + if (eWinding == EWinding.COUNTERCLOCKWISE) + return "[counter-clockwise]"; + + if (eWinding == EWinding.CLOCKWISE) + return "[clockwise]"; + + return "[unknown/degenerate]"; + } + + public static EWinding eDetectWinding(List oVertices) + { + int nVertices = oVertices.Count; + + if (nVertices < 3) + return EWinding.UNKNOWN; + + float fArea = 0f; + + for (int i = 0; i < nVertices; i++) + { + int j = (i + 1) % nVertices; + fArea += (oVertices[j].X - oVertices[i].X) * + (oVertices[j].Y + oVertices[i].Y); + } + + if (fArea > 0.0f) + return EWinding.CLOCKWISE; + + if (fArea < 0.0f) + return EWinding.COUNTERCLOCKWISE; + + return EWinding.UNKNOWN; // Degenerate case + } + + public PolyContour( List oVertices, + EWinding eWinding = EWinding.UNKNOWN) + { + Debug.Assert(oVertices.Count > 2, + "Polyline with less than 3 points makes no sense"); + + m_oVertices = new List(oVertices); + + if (eWinding == EWinding.UNKNOWN) + { + m_eWinding = eDetectWinding(m_oVertices); + } + else + { + m_eWinding = eWinding; + Debug.Assert(m_eWinding == eDetectWinding(m_oVertices), + "Detected Winding that is not correct"); + } + + m_oBBox = new(); + + foreach (Vector2 vec in m_oVertices) + { + m_oBBox.Include(vec); + } + } + + public void AddVertex(Vector2 vec) + { + m_oVertices.Add(vec); + } + + public void DetectWinding() + { + m_eWinding = eDetectWinding(m_oVertices); + } + + public EWinding eWinding() { return m_eWinding; } + public List oVertices() { return m_oVertices; } + + public void AsSvgPolyline(out string str) + { + str = " m_oVertices; + EWinding m_eWinding; + BBox2 m_oBBox; + + public BBox2 oBBox() => m_oBBox; + public int nCount() => m_oVertices.Count; + public Vector2 vecVertex(int n) => m_oVertices[n]; + } + + public class PolySlice + { + public PolySlice(float fZPos) + { + m_fZPos = fZPos; + m_oContours = new(); + m_oBBox = new(); + } + + public void AddContour(PolyContour oPoly) + { + m_oBBox.Include(oPoly.oBBox()); + m_oContours.Add(oPoly); + } + + public void SaveToSvgFile( string strPath, + bool bSolid, + BBox2? oBBoxToUse = null) + { + + BBox2 oBBoxView = oBBoxToUse ?? m_oBBox; + + using (StreamWriter writer = new(strPath)) + { + writer.WriteLine(""); + writer.WriteLine("\n"); + + float fSizeX = oBBoxView.vecMax.X - oBBoxView.vecMin.X; + float fSizeY = oBBoxView.vecMax.Y - oBBoxView.vecMin.Y; + + writer.WriteLine(""); + + writer.WriteLine(""); + + if (!bSolid) + { + foreach (PolyContour oPoly in m_oContours) + { + string str = ""; + oPoly.AsSvgPolyline(out str); + writer.WriteLine(str); + } + } + else + { + string strContour = " "; + writer.WriteLine(strContour); + } + + writer.WriteLine(""); + writer.WriteLine(""); + } + } + + public float fZPos() => m_fZPos; + public BBox2 oBBox() => m_oBBox; + public int nCountours() => m_oContours.Count; + public PolyContour oCountourAt(int i) => m_oContours[i]; + + List m_oContours; + float m_fZPos; + BBox2 m_oBBox; + } + + public class PolySliceStack + { + public PolySliceStack() + { + m_oSlices = new(); + m_oBBox = new(); + } + + public void AddSlices(List oSlices) + { + foreach (PolySlice oSlice in oSlices) + { + m_oBBox.Include(oSlice.oBBox(), oSlice.fZPos()); + m_oSlices.Add(oSlice); + } + } + + public void AddToViewer( Viewer oViewer, + ColorFloat? clrOutside = null, + ColorFloat? clrInside = null, + ColorFloat? clrDegenerate = null, + int nGroup = 0) + { + if (clrDegenerate is null) + clrDegenerate = "#AAAAAAAA"; + + if (clrInside is null) + clrInside = "#AAAAAAAA"; + + if (clrOutside is null) + clrOutside = "#FF0000AA"; + + foreach (PolySlice oSlice in m_oSlices) + { + for (int n = 0; n < oSlice.nCountours(); n++) + { + PolyContour oContour = oSlice.oCountourAt(n); + + ColorFloat? clr = clrDegenerate; + + if (oContour.eWinding() == PolyContour.EWinding.CLOCKWISE) + clr = clrInside; + else if (oContour.eWinding() == PolyContour.EWinding.COUNTERCLOCKWISE) + clr = clrOutside; + + PolyLine oPolyLine = new PolyLine((ColorFloat)clr); + + foreach (Vector2 vec in oContour.oVertices()) + { + oPolyLine.nAddVertex(new Vector3(vec.X, vec.Y, oSlice.fZPos())); + } + + oViewer.Add(oPolyLine, nGroup); + } + } + } + + public int nCount() => m_oSlices.Count(); + public PolySlice oSliceAt(int n) => m_oSlices[n]; + public BBox3 oBBox() => m_oBBox; + + List m_oSlices; + BBox3 m_oBBox; + } +} diff --git a/PicoGK_Types.cs b/PicoGK_Types.cs new file mode 100644 index 0000000..6f0f482 --- /dev/null +++ b/PicoGK_Types.cs @@ -0,0 +1,78 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Runtime.InteropServices; + +namespace PicoGK +{ + // Note these classes define the memory layout to be compatible with + // the PicoGK C++ types. Do not add data members to these classes, even + // if they are defined as "partial" to be extended with additional methods + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public partial struct Coord + { + public int X; + public int Y; + public int Z; + + public Coord( int x, + int y, + int z) + { + this.X = x; + this.Y = y; + this.Z = z; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public partial struct Triangle + { + public int A; + public int B; + public int C; + + public Triangle( int a, + int b, + int c) + { + this.A = a; + this.B = b; + this.C = c; + } + } +} + diff --git a/PicoGK_Utils.cs b/PicoGK_Utils.cs new file mode 100644 index 0000000..740eafa --- /dev/null +++ b/PicoGK_Utils.cs @@ -0,0 +1,482 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Numerics; + +namespace PicoGK +{ + public class Utils + { + /// + /// Returns the path to the home folder (cross platform compatible) + /// + /// Path to the home folder + /// Excepts, if not found + static public string strHomeFolder() + { + string? str = null; + + if (Environment.OSVersion.Platform == PlatformID.Unix) + { + str = Environment.GetEnvironmentVariable("HOME"); + } + else if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + str = Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%"); + } + + if (str is null) + { + throw new Exception("Could not find home folder for " + Environment.OSVersion.Platform.ToString()); + } + + return str; + } + + /// + /// Returns the path to the documents folder (cross platform compatible) + /// + /// The path th documents folder + /// Excepts, if unable to find + static public string strDocumentsFolder() + { + string? str = null; + + if (Environment.OSVersion.Platform == PlatformID.Unix) + { + str = Environment.GetEnvironmentVariable("HOME"); + + if (str is null) + { + throw new Exception("Could not find home folder for " + Environment.OSVersion.Platform.ToString()); + } + + return Path.Combine(str, "Documents"); + } + else if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + str = Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%"); + } + + if (str is null) + { + throw new Exception("Could not find home folder for " + Environment.OSVersion.Platform.ToString()); + } + + return str; + } + + static public string strPicoGKSourceCodeFolder() + { + string strPath = Environment.CommandLine; + for (int n = 0; n < 4; n++) + { + strPath = Path.GetDirectoryName(strPath) ?? ""; + } + + strPath = Path.Combine(strPath, "PicoGK"); + + return strPath; + } + + /// + /// Returns a file name in the form 20230930_134500 to be used in log files etc. + /// + /// Prepended before the date/time stamp + /// Appended after the date/time stamp + /// + static public string strDateTimeFilename( in string strPrefix, + in string strPostfix) + { + return strPrefix + DateTimeOffset.Now.ToString("yyyyMMdd_HHmmss") + strPostfix; + } + + /// + /// Shorted a string, IF it is too long. If it is not too long, nothing happens + /// C# is lacking such a function (all other functions throw exceptions) + /// + /// String to shorten (if too long) + /// Number of max characters in the string + /// + public static string strShorten(string str, int iMaxCharacters) + { + if (str.Length < iMaxCharacters) + return str; + + // in their infinite wisdom, the coders of C# decided to + // throw an exception, if str is shorter than iMaxCharacters + // that's why this function is even necessary + return str[..iMaxCharacters]; + } + + static public void SetMatrixRow( ref Matrix4x4 mat, uint n, + float f1, float f2, float f3, float f4) + { + // An insane person wrote Matrix4x4 + + switch (n) + { + case 0: + mat.M11 = f1; + mat.M12 = f2; + mat.M13 = f3; + mat.M14 = f4; + break; + case 1: + mat.M21 = f1; + mat.M22 = f2; + mat.M23 = f3; + mat.M24 = f4; + break; + case 2: + mat.M31 = f1; + mat.M32 = f2; + mat.M33 = f3; + mat.M34 = f4; + break; + case 3: + mat.M41 = f1; + mat.M42 = f2; + mat.M43 = f3; + mat.M44 = f4; + break; + default: + throw new ArgumentOutOfRangeException("Matrix 4x4 row index i 0..3"); + } + } + + static public Matrix4x4 matLookAt( Vector3 vecEye, + Vector3 vecLookAt) + { + Vector3 vecZ = new(0.0f, 0.0f, 1.0f); + + Vector3 vecView = Vector3.Normalize(vecEye - vecLookAt); + Vector3 vecRight = Vector3.Normalize(Vector3.Cross(vecZ, vecView)); + Vector3 vecUp = Vector3.Cross(vecView, vecRight); + + Matrix4x4 mat = new Matrix4x4(); + + SetMatrixRow(ref mat, 0, vecRight.X, vecUp.X, vecView.X, 0f); + SetMatrixRow(ref mat, 1, vecRight.Y, vecUp.Y, vecView.Y, 0f); + SetMatrixRow(ref mat, 2, vecRight.Z, vecUp.Z, vecView.Z, 0f); + + SetMatrixRow(ref mat, 3, -Vector3.Dot(vecRight, vecEye), + -Vector3.Dot(vecUp, vecEye), + -Vector3.Dot(vecView, vecEye), + 1.0f); + + return mat; + } + + static public Mesh mshCreateCube( Vector3? vecScale = null, + Vector3? vecOffsetMM = null) + { + Vector3 vecS = vecScale ?? new Vector3(1.0f); + Vector3 vecOffset = vecOffsetMM ?? new Vector3(0.0f); + + Mesh oMesh = new Mesh(); + + Vector3[] cubeVertices = + { + new Vector3(-0.5f * vecS.X, -0.5f * vecS.Y, -0.5f * vecS.Z) + vecOffset, + new Vector3(-0.5f * vecS.X, -0.5f * vecS.Y, 0.5f * vecS.Z) + vecOffset, + new Vector3(-0.5f * vecS.X, 0.5f * vecS.Y, -0.5f * vecS.Z) + vecOffset, + new Vector3(-0.5f * vecS.X, 0.5f * vecS.Y, 0.5f * vecS.Z) + vecOffset, + new Vector3( 0.5f * vecS.X, -0.5f * vecS.Y, -0.5f * vecS.Z) + vecOffset, + new Vector3( 0.5f * vecS.X, -0.5f * vecS.Y, 0.5f * vecS.Z) + vecOffset, + new Vector3( 0.5f * vecS.X, 0.5f * vecS.Y, -0.5f * vecS.Z) + vecOffset, + new Vector3( 0.5f * vecS.X, 0.5f * vecS.Y, 0.5f * vecS.Z) + vecOffset + }; + + // Front face + oMesh.nAddTriangle(cubeVertices[0], cubeVertices[1], cubeVertices[3]); + oMesh.nAddTriangle(cubeVertices[0], cubeVertices[3], cubeVertices[2]); + + // Back face + oMesh.nAddTriangle(cubeVertices[4], cubeVertices[6], cubeVertices[7]); + oMesh.nAddTriangle(cubeVertices[4], cubeVertices[7], cubeVertices[5]); + + // Left face + oMesh.nAddTriangle(cubeVertices[0], cubeVertices[2], cubeVertices[6]); + oMesh.nAddTriangle(cubeVertices[0], cubeVertices[6], cubeVertices[4]); + + // Right face + oMesh.nAddTriangle(cubeVertices[1], cubeVertices[5], cubeVertices[7]); + oMesh.nAddTriangle(cubeVertices[1], cubeVertices[7], cubeVertices[3]); + + // Top face + oMesh.nAddTriangle(cubeVertices[2], cubeVertices[3], cubeVertices[7]); + oMesh.nAddTriangle(cubeVertices[2], cubeVertices[7], cubeVertices[6]); + + // Bottom face + oMesh.nAddTriangle(cubeVertices[0], cubeVertices[4], cubeVertices[5]); + oMesh.nAddTriangle(cubeVertices[0], cubeVertices[5], cubeVertices[1]); + + return oMesh; + } + + static public Mesh mshCreateCylinder( Vector3? vecScale = null, + Vector3? vecOffsetMM = null, + int iSides = 0) + { + Vector3 vecS = vecScale ?? new Vector3(1.0f); + Vector3 vecOffset = vecOffsetMM ?? new Vector3(0.0f); + + float fA = vecS.X * 0.5f; + float fB = vecS.Y * 0.5f; + + if (iSides <= 0) + { + //Ramanujan's ellipse perimeter + //P ≈ π [ 3 (a + b) - √[(3a + b) (a + 3b) ]] + //P ≈ π(a + b) [ 1 + (3h) / (10 + √(4 - 3h) ) ], where h = (a - b)2/(a + b)2 + + float fP = MathF.PI * (3.0f * (fA + fB) - MathF.Sqrt((3.0f * fA + fB) * (fA + 3.0f * fB))); + iSides = 2 * (int)MathF.Ceiling(fP); + } + + if (iSides < 3) + { + iSides = 3; + } + + Mesh oMesh = new Mesh(); + + Vector3 vecBottomCenter = vecOffset; + vecBottomCenter.Z -= vecS.Z * 0.5f; + Vector3 vecTopCenter = vecBottomCenter; + vecTopCenter.Z += vecS.Z; + Vector3 vecPrevBottom = new Vector3(fA, 0, 0) + vecBottomCenter; + Vector3 vecPrevTop = vecPrevBottom; + vecPrevTop.Z += vecS.Z; + + float fStep = MathF.PI * 2.0f / iSides; + + for (int i = 1; i <= iSides; ++i) + { + float fAngle = i * fStep; + + Vector3 vecThisBottom = new Vector3(MathF.Cos(fAngle) * fA, MathF.Sin(fAngle) * fB, 0.0f) + vecBottomCenter; + Vector3 vecThisTop = vecThisBottom; + vecThisTop.Z += vecS.Z; + + //top cap + oMesh.nAddTriangle(vecTopCenter, vecPrevTop, vecThisTop); + + //side + oMesh.nAddTriangle(vecPrevBottom, vecThisBottom, vecPrevTop); + oMesh.nAddTriangle(vecThisBottom, vecThisTop, vecPrevTop); + + //bottom cap + oMesh.nAddTriangle(vecBottomCenter, vecThisBottom, vecPrevBottom); + + vecPrevBottom = vecThisBottom; + vecPrevTop = vecThisTop; + } + + return oMesh; + } + + static public Mesh mshCreateCone(Vector3? vecScale = null, + Vector3? vecOffsetMM = null, + int iSides = 0) + { + Vector3 vecS = vecScale ?? new Vector3(1.0f); + Vector3 vecOffset = vecOffsetMM ?? new Vector3(0.0f); + + float fA = vecS.X * 0.5f; + float fB = vecS.Y * 0.5f; + + if (iSides <= 0) + { + float fP = MathF.PI * (3.0f * (fA + fB) - MathF.Sqrt((3.0f * fA + fB) * (fA + 3.0f * fB))); + iSides = 2 * (int)MathF.Ceiling(fP); + } + + if (iSides < 3) + { + iSides = 3; + } + + Mesh oMesh = new Mesh(); + + Vector3 vecBottomCenter = vecOffset; + vecBottomCenter.Z -= vecS.Z * 0.5f; + Vector3 vecTop = vecBottomCenter; + vecTop.Z += vecS.Z; + Vector3 vecPrevBottom = new Vector3(fA, 0, 0) + vecBottomCenter; + + float fStep = MathF.PI * 2.0f / iSides; + + for (int i = 1; i <= iSides; ++i) + { + float fAngle = i * fStep; + + Vector3 vecThisBottom = new Vector3(MathF.Cos(fAngle) * fA, MathF.Sin(fAngle) * fB, 0.0f) + vecBottomCenter; + + //side + oMesh.nAddTriangle(vecPrevBottom, vecThisBottom, vecTop); + + //bottom cap + oMesh.nAddTriangle(vecBottomCenter, vecThisBottom, vecPrevBottom); + + vecPrevBottom = vecThisBottom; + } + + return oMesh; + } + + static void GeoSphereTriangle(Vector3 vecA, + Vector3 vecB, + Vector3 vecC, + Vector3 vecOffset, + Vector3 vecRadii, + int iRecursionDepth, + Mesh oTarget) + { + if (iRecursionDepth > 0) + { + Vector3 vecAB = vecOffset + ((vecA + vecB) * 0.5f - vecOffset); + Vector3 vecBC = vecOffset + ((vecB + vecC) * 0.5f - vecOffset); + Vector3 vecCA = vecOffset + ((vecC + vecA) * 0.5f - vecOffset); + + vecAB *= vecRadii / vecAB.Length(); + vecBC *= vecRadii / vecBC.Length(); + vecCA *= vecRadii / vecCA.Length(); + + GeoSphereTriangle(vecA, vecAB, vecCA, vecOffset, vecRadii, iRecursionDepth - 1, oTarget); + GeoSphereTriangle(vecAB, vecB, vecBC, vecOffset, vecRadii, iRecursionDepth - 1, oTarget); + GeoSphereTriangle(vecAB, vecBC, vecCA, vecOffset, vecRadii, iRecursionDepth - 1, oTarget); + GeoSphereTriangle(vecCA, vecBC, vecC, vecOffset, vecRadii, iRecursionDepth - 1, oTarget); + } + else + { + oTarget.nAddTriangle(vecA, vecB, vecC); + } + } + + static float fSquared(float fX) + { + return fX * fX; + } + + static float fApproxEllipsoidSurfaceArea(Vector3 vecABC) + { + return 4.0f * MathF.PI * MathF.Pow(( + MathF.Pow(vecABC.X * vecABC.Y, 1.6f) + + MathF.Pow(vecABC.Y * vecABC.Z, 1.6f) + + MathF.Pow(vecABC.Z * vecABC.X, 1.6f)) / 3.0f, 1.0f / 1.6f); + } + + static public Mesh mshCreateGeoSphere(Vector3? vecScale = null, + Vector3? vecOffsetMM = null, + int iSubdivisions = 0) + { + Vector3 vecS = vecScale ?? new Vector3(1.0f); + Vector3 vecOffset = vecOffsetMM ?? new Vector3(0.0f); + + Mesh oMesh = new Mesh(); + + Vector3 vecRadii = vecS * 0.5f; + Vector3 vecRadii2 = vecRadii * vecRadii; + + float fCoeff = fSquared(2.0f * MathF.Sin(MathF.PI * 0.2f)); + Vector3 vecPenta = new Vector3( + (2.0f * MathF.Sqrt(fCoeff * vecRadii2.X - vecRadii2.X)) / fCoeff, + (2.0f * MathF.Sqrt(fCoeff * vecRadii2.Y - vecRadii2.Y)) / fCoeff, + (2.0f * MathF.Sqrt(fCoeff * vecRadii2.Z - vecRadii2.Z)) / fCoeff); + + float fPentaDZ = MathF.Sqrt(vecRadii2.Z - fSquared(vecPenta.Z)); + + Vector3[] avecPOffs = new Vector3[5]; + + for (int i = 0; i < 5; i++) + { + float fAngle = 0.4f * MathF.PI * i; + avecPOffs[i] = new Vector3(vecPenta.X * MathF.Cos(fAngle), vecPenta.Y * MathF.Sin(fAngle), fPentaDZ); + } + + //estimate the number of subdivisions based on the sphere or ellipsoid surface area + + if (iSubdivisions <= 0) + { + int iTargetTriangles = (int)MathF.Ceiling(fApproxEllipsoidSurfaceArea(vecRadii)); + + iSubdivisions = 1; + int iTriangles = 80; + + while (iSubdivisions < 8 && iTriangles < iTargetTriangles) + { + ++iSubdivisions; + iTriangles = 20 * (1 << (2 * iSubdivisions)); + } + } + + //top cap + Vector3 vecCap = vecOffset; + + vecCap.Z += vecRadii.Z; + + for (int i = 0; i < 5; i++) + { + GeoSphereTriangle(vecCap, vecOffset + avecPOffs[i], vecOffset + avecPOffs[(i + 1) % 5], vecOffset, vecRadii, iSubdivisions, oMesh); + } + + //10 triangles around + GeoSphereTriangle(vecOffset + avecPOffs[4], vecOffset - avecPOffs[2], vecOffset + avecPOffs[0], vecOffset, vecRadii, iSubdivisions, oMesh); + GeoSphereTriangle(vecOffset + avecPOffs[4], vecOffset - avecPOffs[1], vecOffset - avecPOffs[2], vecOffset, vecRadii, iSubdivisions, oMesh); + GeoSphereTriangle(vecOffset + avecPOffs[3], vecOffset - avecPOffs[1], vecOffset + avecPOffs[4], vecOffset, vecRadii, iSubdivisions, oMesh); + GeoSphereTriangle(vecOffset + avecPOffs[3], vecOffset - avecPOffs[0], vecOffset - avecPOffs[1], vecOffset, vecRadii, iSubdivisions, oMesh); + GeoSphereTriangle(vecOffset + avecPOffs[2], vecOffset - avecPOffs[0], vecOffset + avecPOffs[3], vecOffset, vecRadii, iSubdivisions, oMesh); + GeoSphereTriangle(vecOffset + avecPOffs[2], vecOffset - avecPOffs[4], vecOffset - avecPOffs[0], vecOffset, vecRadii, iSubdivisions, oMesh); + GeoSphereTriangle(vecOffset + avecPOffs[1], vecOffset - avecPOffs[4], vecOffset + avecPOffs[2], vecOffset, vecRadii, iSubdivisions, oMesh); + GeoSphereTriangle(vecOffset + avecPOffs[1], vecOffset - avecPOffs[3], vecOffset - avecPOffs[4], vecOffset, vecRadii, iSubdivisions, oMesh); + GeoSphereTriangle(vecOffset + avecPOffs[0], vecOffset - avecPOffs[3], vecOffset + avecPOffs[1], vecOffset, vecRadii, iSubdivisions, oMesh); + GeoSphereTriangle(vecOffset + avecPOffs[0], vecOffset - avecPOffs[2], vecOffset - avecPOffs[3], vecOffset, vecRadii, iSubdivisions, oMesh); + + //bottom cap + vecCap.Z = vecOffset.Z - vecRadii.Z; + + for (int i = 0; i < 5; i++) + { + GeoSphereTriangle(vecCap, vecOffset - avecPOffs[(i + 1) % 5], vecOffset - avecPOffs[i], vecOffset, vecRadii, iSubdivisions, oMesh); + } + + return oMesh; + } + + } + +} \ No newline at end of file diff --git a/PicoGK_Viewer.cs b/PicoGK_Viewer.cs new file mode 100644 index 0000000..a0c5a2e --- /dev/null +++ b/PicoGK_Viewer.cs @@ -0,0 +1,621 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Diagnostics; +using System.IO.Compression; +using System.Numerics; + +namespace PicoGK +{ + public partial class Viewer + { + public Viewer( string strTitle, + Vector2 vecSize) + { + m_iMainThreadID = Environment.CurrentManagedThreadId; + + m_oHandler.AddAction(new KeyAction( + new RotateToNextRoundAngleAction( + RotateToNextRoundAngleAction.EDir.Dir_Down), + EKeys.Key_Down)); + + m_oHandler.AddAction(new KeyAction( + new RotateToNextRoundAngleAction( + RotateToNextRoundAngleAction.EDir.Dir_Up), + EKeys.Key_Up)); + + m_oHandler.AddAction(new KeyAction( + new RotateToNextRoundAngleAction( + RotateToNextRoundAngleAction.EDir.Dir_Left), + EKeys.Key_Left)); + + m_oHandler.AddAction(new KeyAction( + new RotateToNextRoundAngleAction( + RotateToNextRoundAngleAction.EDir.Dir_Right), + EKeys.Key_Right)); + + AddKeyHandler(m_oHandler); + + // Assign the delegate functions, to make sure the + // garbage collector doesn't destroy them during the + // lifetime of the objects + + m_fnInfoCB = InfoCB; + m_fnUpdateCB = UpdateCB; + m_fnKeyPressedCB = KeyPressedCB; + m_fnMouseMovedCB = MouseMovedCB; + m_fnMouseButtonCB = MouseButtonCB; + m_fnScrollWheelCB = ScrollWheelCB; + m_fnWindowSizeCB = WindowSizeCB; + + m_hThis = _hCreate( strTitle, + vecSize, + m_fnInfoCB, + m_fnUpdateCB, + m_fnKeyPressedCB, + m_fnMouseMovedCB, + m_fnMouseButtonCB, + m_fnScrollWheelCB, + m_fnWindowSizeCB); + + Debug.Assert(m_hThis != IntPtr.Zero); + } + + public bool bPoll() + { + Debug.Assert(m_iMainThreadID == Environment.CurrentManagedThreadId); + // Call this function only from the main() thread of the application + + Thread.Yield(); + + bool bUpdateNeeded = false; + + lock (m_oAnims) + { + if (m_oAnims.bPulse()) + bUpdateNeeded = true; + } + + lock (m_oActions) + { + while (m_oActions.Count > 0) + { + IViewerAction oAction = m_oActions.Dequeue(); + oAction.Do(this); + bUpdateNeeded = true; + } + } + + if (bUpdateNeeded) + _RequestUpdate(m_hThis); + + Debug.Assert(_bIsValid(m_hThis)); + return _bPoll(m_hThis); + } + + public void RequestUpdate() + { + lock (m_oActions) + { + m_oActions.Enqueue(new RequestUpdateAction()); + } + } + + public void LoadLightSetup(string strFilePath) + { + using (ZipArchive oZip = ZipFile.OpenRead(strFilePath)) + { + // Find the entry for the diffTexture + ZipArchiveEntry? oDiffuseEntry = oZip.GetEntry("Diffuse.dds"); + if (oDiffuseEntry == null) + { + throw new FileNotFoundException("Diffuse.dds texture entry not found in the ZIP archive."); + } + + // Find the entry for the specTexture + ZipArchiveEntry? oSpecularEntry = oZip.GetEntry("Specular.dds"); + if (oSpecularEntry == null) + { + throw new FileNotFoundException("SpecTexture entry not found in the ZIP archive."); + } + + byte[] abyDiffuseData; + using (Stream oDiffuseStream = oDiffuseEntry.Open()) + { + using (MemoryStream oDiffuseMemStream = new MemoryStream()) + { + oDiffuseStream.CopyTo(oDiffuseMemStream); + abyDiffuseData = oDiffuseMemStream.ToArray(); + } + } + + byte[] abySpecularData; + using (Stream oSpecularStream = oSpecularEntry.Open()) + { + using (MemoryStream oSpecularMemStream = new MemoryStream()) + { + oSpecularStream.CopyTo(oSpecularMemStream); + abySpecularData = oSpecularMemStream.ToArray(); + } + } + + lock (m_oActions) + { + m_oActions.Enqueue(new LoadLightSetupAction( abyDiffuseData, + abySpecularData)); + } + } + } + + public void Add( in Voxels vox, + int nGroupID = 0) + { + Mesh msh = new Mesh(vox); + + lock (m_oVoxels) + { + m_oVoxels.Add(vox, msh); + } + + Add(msh, nGroupID); + } + + public void Remove(Voxels vox) + { + Mesh? msh; + + lock (m_oVoxels) + { + if (!m_oVoxels.TryGetValue(vox, out msh)) + { + throw new Exception("Tried to remove voxels that were never added"); + } + + m_oVoxels.Remove(vox); + } + + Remove(msh); + } + + public void Add( Mesh msh, + int nGroupID = 0) + { + lock (m_oActions) + { + m_oActions.Enqueue(new AddMeshAction(msh, nGroupID)); + } + } + + public void Remove(Mesh msh) + { + lock (m_oActions) + { + m_oActions.Enqueue(new RemoveMeshAction(msh)); + } + } + + public void Add( PolyLine oPoly, + int nGroupID = 0) + { + lock (m_oActions) + { + m_oActions.Enqueue(new AddPolyLineAction(oPoly, nGroupID)); + } + } + + public void Remove(PolyLine oPoly) + { + lock (m_oActions) + { + m_oActions.Enqueue(new RemovePolyLineAction(oPoly)); + } + } + + public void RemoveAllObjects() + { + lock (m_oActions) + { + m_oActions.Enqueue(new RemoveAllObjectsAction()); + } + } + + public void RequestScreenShot(string strScreenShotPath) + { + lock (m_oActions) + { + m_oActions.Enqueue(new RequestScreenShotAction(strScreenShotPath)); + } + } + + public void SetGroupVisible(int nGroupID, + bool bVisible) + { + lock (m_oActions) + { + m_oActions.Enqueue(new SetGroupVisibleAction(nGroupID, bVisible)); + } + } + + public void SetGroupStatic( int nGroupID, + bool bStatic) + { + lock (m_oActions) + { + m_oActions.Enqueue(new SetGroupStaticAction(nGroupID, bStatic)); + } + } + + public void SetGroupMaterial (int nGroupID, + ColorFloat clr, + float fMetallic, + float fRoughness) + { + lock (m_oActions) + { + m_oActions.Enqueue(new SetGroupMaterialAction( nGroupID, + clr, + fMetallic, + fRoughness)); + } + } + + public void SetGroupMatrix(int nGroupID, + Matrix4x4 mat) + { + lock (m_oActions) + { + m_oActions.Enqueue(new SetGroupMatrixAction( nGroupID, + mat)); + } + } + + public void SetBackgroundColor(ColorFloat clr) + { + m_clrBackground = clr; + RequestUpdate(); + } + + public void AdjustViewAngles( float fOrbitRelative, + float fElevationRelative) + { + SetViewAngles( m_fOrbit + fOrbitRelative, + m_fElevation += fElevationRelative); + } + + public void SetViewAngles( float fOrbit, + float fElevation) + { + m_fElevation = fElevation; + + if (m_fElevation > 180.0f) + fElevation = 90.0f; + else if (m_fElevation < 180.0f) + fElevation = -90.0f; + + m_fOrbit = fOrbit; + + while (m_fOrbit > 360.0f) + m_fOrbit -= 360.0f; + while (m_fOrbit < 0.0f) + m_fOrbit += 360.0f; + + RequestUpdate(); + } + + public void SetFov(float fAngle) + { + m_fFov = fAngle; + RequestUpdate(); + } + + public void LogStatistics() + { + float fTriangles = 0; + float fVertices = 0; + ulong nMeshes = 0; + + lock (m_oMeshes) + { + foreach (Mesh msh in m_oMeshes) + { + fTriangles += (float)msh.nTriangleCount(); + fVertices += (float)msh.nVertexCount(); + nMeshes++; + } + } + + fTriangles /= 1000000.0f; + fVertices /= 1000000.0f; + + Library.Log($"Viewer Stats:"); + Library.Log($" Number of Meshes: {nMeshes}"); + lock (m_oVoxels) + { + Library.Log($" Voxel Objects: {m_oVoxels.Count()}"); + } + Library.Log($" Total Triangles: {fTriangles:F1} mio"); + Library.Log($" Total Vertices: {fVertices:F1} mio"); + } + + public float m_fElevation = 30.0f; + public float m_fOrbit = 45.0f; + float m_fFov = 45.0f; + float m_fZoom = 1.0f; + bool m_bPerspective = true; + + int m_iMainThreadID = -1; + + ColorFloat m_clrBackground = new(0.3f); + List m_oMeshes = new(); + List m_oPolyLines = new(); + Dictionary m_oVoxels = new(); + BBox3 m_oBBox = new(); + + void RecalculateBoundingBox() + { + Debug.Assert(m_iMainThreadID == Environment.CurrentManagedThreadId); + // Call this function only from the main() thread of the application + + // start from fresh + m_oBBox = new(); + + lock (m_oMeshes) + { + foreach (Mesh msh in m_oMeshes) + { + m_oBBox.Include(msh.oBoundingBox()); + } + } + + lock (m_oPolyLines) + { + foreach (PolyLine poly in m_oPolyLines) + { + m_oBBox.Include(poly.oBoundingBox()); + } + } + } + + void DoRemove(Mesh msh) + { + Debug.Assert(m_iMainThreadID == Environment.CurrentManagedThreadId); + // Call this function only from the main() thread of the application + + lock (m_oMeshes) + { + if (!m_oMeshes.Contains(msh)) + throw new Exception("Tried to remove mesh that was never added"); + + m_oMeshes.Remove(msh); + + _RemoveMesh(m_hThis, + msh.m_hThis); + } + + RecalculateBoundingBox(); + RequestUpdate(); + } + + void DoRemove(PolyLine poly) + { + Debug.Assert(m_iMainThreadID == Environment.CurrentManagedThreadId); + // Call this function only from the main() thread of the application + + lock (m_oPolyLines) + { + if (!m_oPolyLines.Contains(poly)) + throw new Exception("Tried to remove mesh that was never added"); + + m_oPolyLines.Remove(poly); + + _RemovePolyLine( m_hThis, + poly.m_hThis); + } + + RecalculateBoundingBox(); + RequestUpdate(); + } + + Matrix4x4 m_matModelTrans = Matrix4x4.Identity; + Matrix4x4 m_matModelViewProjection = Matrix4x4.Identity; + Matrix4x4 m_matModelViewStatic = Matrix4x4.Identity; + Matrix4x4 m_matProjectionStatic = Matrix4x4.Identity; + Matrix4x4 m_matStatic = Matrix4x4.Identity; + Vector3 m_vecEye = new Vector3(1.0f); + Vector3 m_vecEyeStatic = new Vector3(0f, 10f, 0f); + Vector2 m_vecPrevPos = new(); + bool m_bOrbit = false; + + ///////// Internals + + void InfoCB( string strMessage, + bool bFatalError) + { + Library.Log(strMessage); + } + + void UpdateCB( IntPtr hViewer, + in Vector2 vecViewport, + ref ColorFloat clrBackground, + ref Matrix4x4 matModelViewProjection, + ref Matrix4x4 matModelTransform, + ref Matrix4x4 matStatic, + ref Vector3 vecEyePosition, + ref Vector3 vecEyeStatic) + { + try + { + Debug.Assert(hViewer == m_hThis); + + Vector3 vecSceneCenter = m_oBBox.vecCenter(); + + double fR = ((m_oBBox.vecMax - vecSceneCenter).Length() * 3.0f) * m_fZoom; + double fRElev = Math.Cos((double)m_fElevation * Math.PI / 180.0f) * fR; + + m_vecEye.X = (float)(Math.Cos((double)m_fOrbit * Math.PI / 180.0) * fRElev); + m_vecEye.Y = (float)(Math.Sin((double)m_fOrbit * Math.PI / 180.0) * fRElev); + m_vecEye.Z = (float)(Math.Sin((double)m_fElevation * Math.PI / 180.0) * fR); + + float fFar = (vecSceneCenter - m_vecEye).Length() * 2.0f; + + Matrix4x4 matModelView = Utils.matLookAt(m_vecEye, vecSceneCenter); + + Matrix4x4 matProjection; + + if (m_bPerspective) + { + matProjection = Matrix4x4.CreatePerspectiveFieldOfView( + (float)(m_fFov * Math.PI / 180.0), + vecViewport.X / vecViewport.Y, + 0.1f, + fFar); + } + else + { + matProjection = Matrix4x4.CreateOrthographic( m_oBBox.vecSize().X * 2, + m_oBBox.vecSize().Y * 2, + 0.1f, + fFar); + } + + m_matModelViewStatic = Utils.matLookAt(m_vecEyeStatic, new Vector3(0, 0, 0)); + m_matProjectionStatic = Matrix4x4.CreateOrthographic(100f * vecViewport.X / vecViewport.Y, 100f, 0.1f, 100f); + + m_matModelViewProjection = matModelView * matProjection; + m_matStatic = m_matModelViewStatic * m_matProjectionStatic; + + vecEyeStatic = m_vecEyeStatic; + vecEyePosition = m_vecEye; + matStatic = m_matStatic; + matModelViewProjection = m_matModelViewProjection; + matModelTransform = m_matModelTrans; + clrBackground = m_clrBackground; + } + + catch (Exception e) + { + Library.Log($"Caught exception in Viewer update callback:\n{e.ToString()}\n"); + } + } + + void KeyPressedCB(IntPtr hViewer, + int iKey, + int iScancode, + int iAction, + int iModifiers) + { + Debug.Assert(hViewer == m_hThis); + + EKeys eKey = (EKeys)iKey; + + if ((iAction == 0) || (iAction == 1)) // ignore 2, which is repeat, only down and up + { + foreach (IKeyHandler xHandler in m_oKeyHandlers) + { + if (xHandler.bHandleEvent(this, + eKey, iAction == 1, // Pressed + (iModifiers & 0x0001) != 0, + (iModifiers & 0x0002) != 0, + (iModifiers & 0x0004) != 0, + (iModifiers & 0x0008) != 0)) + return; // Handled + } + } + } + + void MouseMovedCB(IntPtr hViewer, + in Vector2 vecMousePos) + { + Debug.Assert(hViewer == m_hThis); + if (m_bOrbit) + { + Vector2 vecDist = vecMousePos - m_vecPrevPos; + AdjustViewAngles(-vecDist.X / 2.0f, vecDist.Y / 2); + m_vecPrevPos = vecMousePos; + + lock (m_oAnims) + { + m_oAnims.Clear(); + } + } + } + + void MouseButtonCB(IntPtr hViewer, + int iButton, + int iAction, + int iModifiers, + in Vector2 vecMousePos) + { + Debug.Assert(hViewer == m_hThis); + if (iAction == 1) + { + m_bOrbit = true; + m_vecPrevPos = vecMousePos; + + lock (m_oAnims) + { + m_oAnims.Clear(); + } + } + else if (iAction == 0) + { + m_bOrbit = false; + } + } + + void ScrollWheelCB(IntPtr hViewer, + in Vector2 vecScrollWheel, + in Vector2 vecMousePos) + { + Debug.Assert(hViewer == m_hThis); + + m_fZoom -= vecScrollWheel.Y / 50f; + + if (m_fZoom < 0.1f) + m_fZoom = 0.1f; + + RequestUpdate(); + } + + void WindowSizeCB( IntPtr hViewer, + in Vector2 vecWindowSize) + { + Debug.Assert(hViewer == m_hThis); + + RequestUpdate(); + } + } +} + \ No newline at end of file diff --git a/PicoGK_ViewerActions.cs b/PicoGK_ViewerActions.cs new file mode 100644 index 0000000..031c70e --- /dev/null +++ b/PicoGK_ViewerActions.cs @@ -0,0 +1,370 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Numerics; +using System.Runtime.InteropServices; + +namespace PicoGK +{ + public partial class Viewer + { + public interface IViewerAction + { + void Do(Viewer oViewer); + }; + + Queue m_oActions = new(); + + class SetGroupVisibleAction : IViewerAction + { + public SetGroupVisibleAction( int nGroupID, + bool bVisible) + { + m_nGroupID = nGroupID; + m_bVisible = bVisible; + } + + public void Do(Viewer oViewer) + { + _SetGroupVisible( oViewer.m_hThis, + m_nGroupID, + m_bVisible); + } + + int m_nGroupID; + bool m_bVisible; + } + + class SetGroupStaticAction : IViewerAction + { + public SetGroupStaticAction( int nGroupID, + bool bStatic) + { + m_nGroupID = nGroupID; + m_bStatic = bStatic; + } + + public void Do(Viewer oViewer) + { + _SetGroupStatic( oViewer.m_hThis, + m_nGroupID, + m_bStatic); + } + + int m_nGroupID; + bool m_bStatic; + } + + class SetGroupMaterialAction : IViewerAction + { + public SetGroupMaterialAction( int nGroupID, + ColorFloat clr, + float fMetallic, + float fRoughness) + { + m_nGroupID = nGroupID; + m_clr = clr; + m_fMetallic = fMetallic; + m_fRoughness = fRoughness; + } + + public void Do(Viewer oViewer) + { + _SetGroupMaterial( oViewer.m_hThis, + m_nGroupID, + m_clr, + m_fMetallic, + m_fRoughness); + } + + int m_nGroupID; + ColorFloat m_clr; + float m_fMetallic; + float m_fRoughness; + } + + class SetGroupMatrixAction : IViewerAction + { + public SetGroupMatrixAction( int nGroupID, + Matrix4x4 mat) + { + m_nGroupID = nGroupID; + m_mat = mat; + } + + public void Do(Viewer oViewer) + { + _SetGroupMatrix( oViewer.m_hThis, + m_nGroupID, + m_mat); + } + + int m_nGroupID; + Matrix4x4 m_mat; + } + + class RequestUpdateAction : IViewerAction + { + public RequestUpdateAction() + { + + } + + public void Do(Viewer oViewer) + { + _RequestUpdate(oViewer.m_hThis); + } + } + + class RequestScreenShotAction : IViewerAction + { + public RequestScreenShotAction(string strScreenShotPath) + { + m_strScreenShotPath = strScreenShotPath; + } + + public void Do(Viewer oViewer) + { + _RequestScreenShot( oViewer.m_hThis, + m_strScreenShotPath); + } + + string m_strScreenShotPath; + } + + class AddMeshAction : IViewerAction + { + public AddMeshAction( Mesh msh, + int nGroupID) + { + m_msh = msh; + m_nGroupID = nGroupID; + } + + public void Do(Viewer oViewer) + { + oViewer.m_oBBox.Include(m_msh.oBoundingBox()); + + lock (oViewer.m_oMeshes) + { + oViewer.m_oMeshes.Add(m_msh); + } + + _AddMesh( oViewer.m_hThis, + m_nGroupID, + m_msh.m_hThis); + } + + Mesh m_msh; + int m_nGroupID; + } + + class RemoveMeshAction : IViewerAction + { + public RemoveMeshAction(Mesh msh) + { + m_msh = msh; + } + + public void Do(Viewer oViewer) + { + oViewer.DoRemove(m_msh); + } + + Mesh m_msh; + } + + class AddPolyLineAction : IViewerAction + { + public AddPolyLineAction(PolyLine poly, + int nGroupID) + { + m_poly = poly; + m_nGroupID = nGroupID; + } + + public void Do(Viewer oViewer) + { + oViewer.m_oBBox.Include(m_poly.oBoundingBox()); + + _AddPolyLine(oViewer.m_hThis, + m_nGroupID, + m_poly.m_hThis); + + lock (oViewer.m_oPolyLines) + { + oViewer.m_oPolyLines.Add(m_poly); + } + } + + PolyLine m_poly; + int m_nGroupID; + } + + class RemovePolyLineAction : IViewerAction + { + public RemovePolyLineAction(PolyLine poly) + { + m_poly = poly; + } + + public void Do(Viewer oViewer) + { + _RemovePolyLine( oViewer.m_hThis, + m_poly.m_hThis); + } + + PolyLine m_poly; + } + + class RemoveAllObjectsAction : IViewerAction + { + public void Do(Viewer oViewer) + { + lock (oViewer.m_oPolyLines) + { + foreach (PolyLine oPoly in oViewer.m_oPolyLines) + { + _RemovePolyLine(oViewer.m_hThis, oPoly.m_hThis); + } + + oViewer.m_oPolyLines.Clear(); + } + + lock (oViewer.m_oVoxels) + { + oViewer.m_oVoxels.Clear(); + } + + lock (oViewer.m_oMeshes) + { + foreach (Mesh oMesh in oViewer.m_oMeshes) + { + _RemoveMesh(oViewer.m_hThis, oMesh.m_hThis); + } + + oViewer.m_oMeshes.Clear(); + } + } + } + + class LoadLightSetupAction : IViewerAction + { + public LoadLightSetupAction( byte [] abyDiffuseDds, + byte [] abySpecularDds) + { + m_abyDiffuseDds = abyDiffuseDds; + m_abySpecularDds = abySpecularDds; + } + + public void Do(Viewer oViewer) + { + if (!_bLoadLightSetup( oViewer.m_hThis, + m_abyDiffuseDds, + m_abyDiffuseDds.Length, + m_abySpecularDds, + m_abySpecularDds.Length)) + { + Library.Log($"Failed to load light setup"); + } + } + + byte [] m_abyDiffuseDds; + byte [] m_abySpecularDds; + } + + public class RotateToNextRoundAngleAction : IViewerAction + { + public enum EDir + { + Dir_Up, + Dir_Down, + Dir_Left, + Dir_Right + } + + public RotateToNextRoundAngleAction(EDir eDir) + { + m_eDir = eDir; + } + + public void Do(Viewer oViewer) + { + oViewer.RemoveAllAnimations(); + + Vector2 vecTo = new Vector2( oViewer.m_fOrbit, + oViewer.m_fElevation); + + switch (m_eDir) + { + case EDir.Dir_Left: + case EDir.Dir_Right: + { + int iStep = (int)(vecTo.X / 45.0f); + + float fStep = (m_eDir == EDir.Dir_Left) ? 45f : -45f; + vecTo.X = (float)iStep * 45f + fStep; + } + break; + + case EDir.Dir_Up: + case EDir.Dir_Down: + { + int iStep = (int)(vecTo.Y / 45.0f); + float fStep = (m_eDir == EDir.Dir_Up) ? 45f : -45f; + vecTo.Y = (float)iStep * 45f + fStep; + } + break; + } + + Animation.IAction xAction + = new AnimViewRotate( oViewer, + new Vector2(oViewer.m_fOrbit, + oViewer.m_fElevation), + vecTo); + + Animation oAnim + = new Animation( xAction, 0.7f, + Animation.EType.Once, + Animation.EEasing.EaseOut); + + oViewer.AddAnimation(oAnim); + } + + EDir m_eDir; + } + } +} + \ No newline at end of file diff --git a/PicoGK_ViewerAnimation.cs b/PicoGK_ViewerAnimation.cs new file mode 100644 index 0000000..f00269e --- /dev/null +++ b/PicoGK_ViewerAnimation.cs @@ -0,0 +1,117 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Numerics; + +namespace PicoGK +{ + public partial class Viewer + { + public class AnimGroupMatrixRotate : Animation.IAction + { + public AnimGroupMatrixRotate(Viewer oViewer, + int nGroup, + Matrix4x4 matInit, + Vector3 vecAxis, + float fDegrees) + { + m_oViewer = oViewer; + m_nGroup = nGroup; + m_matInit = matInit; + m_vecAxis = vecAxis; + m_fDegrees = fDegrees; + } + + public void Do(float fFactor) + { + double dAngle = (fFactor * m_fDegrees * Math.PI) / 180.0; + Matrix4x4 matMul = Matrix4x4.CreateFromQuaternion( Quaternion.CreateFromAxisAngle(m_vecAxis, + (float)dAngle)); + + Matrix4x4 mat = m_matInit * matMul; + m_oViewer.SetGroupMatrix(m_nGroup, mat); + } + + Viewer m_oViewer; + Matrix4x4 m_matInit; + Vector3 m_vecAxis; + float m_fDegrees; + int m_nGroup; + } + + public class AnimViewRotate : Animation.IAction + { + public AnimViewRotate(Viewer oViewer, + Vector2 vecFrom, + Vector2 vecTo) + { + m_oViewer = oViewer; + m_vecFrom = vecFrom; + m_vecTo = vecTo; + } + + public void Do(float fFactor) + { + Vector2 vec = (m_vecTo - m_vecFrom) * fFactor; + vec += m_vecFrom; + + m_oViewer.SetViewAngles(vec.X, vec.Y); + } + + Viewer m_oViewer; + Vector2 m_vecFrom; + Vector2 m_vecTo; + } + + public void AddAnimation(Animation oAnim) + { + lock (m_oAnims) + { + m_oAnims.Add(oAnim); + } + } + + public void RemoveAllAnimations() + { + lock (m_oAnims) + { + m_oAnims.Clear(); + } + } + + AnimationQueue m_oAnims = new(); + } +} + \ No newline at end of file diff --git a/PicoGK_ViewerKeyboard.cs b/PicoGK_ViewerKeyboard.cs new file mode 100644 index 0000000..69f600d --- /dev/null +++ b/PicoGK_ViewerKeyboard.cs @@ -0,0 +1,211 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace PicoGK +{ + public partial class Viewer + { + LinkedList m_oKeyHandlers = new(); + KeyHandler m_oHandler = new(); + + public void AddKeyHandler(IKeyHandler xKeyHandler) + { + // Add to the beginning, so the last added is processed first + m_oKeyHandlers.AddFirst(xKeyHandler); + } + + public interface IKeyHandler + { + bool bHandleEvent( Viewer oViewer, + EKeys eKey, + bool bPressed, + bool bShift, + bool bCtrl, + bool bAlt, + bool bCmd); + } + + public enum EKeys + { + Key_Space = 32, + Key_0 = 48, + Key_1, + Key_2, + Key_3, + Key_4, + Key_5, + Key_6, + Key_7, + Key_8, + Key_9, + Key_A = 65, + Key_B, + Key_C, + Key_D, + Key_E, + Key_F, + Key_G, + Key_H, + Key_I, + Key_J, + Key_K, + Key_L, + Key_M, + Key_N, + Key_O, + Key_P, + Key_Q, + Key_R, + Key_S, + Key_T, + Key_U, + Key_V, + Key_W, + Key_X, + Key_Y, + Key_Z = 90, + Key_ESC = 256, + Key_Enter, + Key_Tab, + Key_Backspace, + Key_Insert, + Key_Delete, + Key_Right, + Key_Left, + Key_Down, + Key_Up, + Key_PgUp, + Key_PgDn, + Key_Home, + Key_End = 269, + Key_F1 = 290, + Key_F2, + Key_F3, + Key_F4, + Key_F5, + Key_F6, + Key_F7, + Key_F8, + Key_F9, + Key_F10, + Key_F11, + Key_F12 + }; + + public class KeyAction + { + public KeyAction( IViewerAction xAction, + EKeys eKey, + bool bPressed = false, // Handle on release by default + bool bShift = false, + bool bCtrl = false, + bool bAlt = false, + bool bCmd = false) + { + m_xAction = xAction; + m_eKey = eKey; + m_bPressed = bPressed; + m_bShift = bShift; + m_bCtrl = bCtrl; + m_bAlt = bAlt; + m_bCmd = bCmd; + } + + public bool bKeyEquals( EKeys eKey, + bool bPressed, + bool bShift, + bool bCtrl, + bool bAlt, + bool bCmd) + { + return ( (m_eKey == eKey) && + (m_bPressed == bPressed) && + (m_bShift == bShift) && + (m_bCtrl == bCtrl) && + (m_bAlt == bAlt) && + (m_bCmd == bCmd)); + } + + public void Do(Viewer oViewer) + { + m_xAction.Do(oViewer); + } + + IViewerAction m_xAction; + EKeys m_eKey; + bool m_bPressed; + bool m_bShift; + bool m_bCtrl; + bool m_bAlt; + bool m_bCmd; + } + + public class KeyHandler : IKeyHandler + { + public void AddAction(KeyAction oAction) + { + m_oKeyActions.AddFirst(oAction); + } + + LinkedList m_oKeyActions = new(); + + public bool bHandleEvent( Viewer oViewer, + EKeys eKey, + bool bPressed, + bool bShift, + bool bCtrl, + bool bAlt, + bool bCmd) + { + foreach (KeyAction oAction in m_oKeyActions) + { + if (oAction.bKeyEquals( eKey, + bPressed, + bShift, + bCtrl, + bAlt, + bCmd)) + { + oAction.Do(oViewer); + return true; + } + } + + return false; + } + } + } +} + \ No newline at end of file diff --git a/PicoGK_Voxels.cs b/PicoGK_Voxels.cs new file mode 100644 index 0000000..63f28e7 --- /dev/null +++ b/PicoGK_Voxels.cs @@ -0,0 +1,352 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Diagnostics; +using System.Numerics; +using System.Runtime.InteropServices; + +namespace PicoGK +{ + /// + /// Function signature for signed distance implicts + /// + public interface IImplicit + { + /// + /// Return the signed distance to the iso surface + /// + /// Real world point to sample + /// + /// Distance to the Iso surface in real world values + /// 0.0 is at the surface + /// Negative values indicate the inside of the object + /// Positive values indicate the outside of the object + /// + public abstract float fSignedDistance(in Vector3 vec); + } + + public partial class Voxels + { + /// + /// Default constructor, builds a new empty voxel field + /// + public Voxels() + { + m_hThis = _hCreate(); + Debug.Assert(m_hThis != IntPtr.Zero); + } + + /// + /// Copy constructor, create a duplicate + /// of the supplied voxel field + /// + /// Source to copy from + public Voxels(in Voxels voxSource) + { + m_hThis = _hCreateCopy(voxSource.m_hThis); + Debug.Assert(m_hThis != IntPtr.Zero); + } + + /// + /// Creates a new voxel field and renders it using the + /// implicit function specified + /// + /// Object producing a signed distance field + public Voxels( in IImplicit xImplicit, + in BBox3 oBounds) : this() + { + RenderImplicit(xImplicit, oBounds); + } + + /// + /// Creates a new voxel field form a mesh + /// + /// The mesh that is rendered into the voxels + public Voxels(in Mesh msh) : this() + { + RenderMesh(msh); + } + + /// + /// Creates a new voxel field from a lattice + /// + /// The lattice used + public Voxels(in Lattice lat) : this() + { + RenderLattice(lat); + } + + /// + /// Return the current voxel field as a mesh + /// + /// The meshed result of the voxel field + public Mesh mshAsMesh() + { + return new Mesh(this); + } + + /// + /// Performs a boolean union between two voxel fields + /// Our voxelfield will have all the voxels set that the operands also has set + /// + /// Voxels to add to our field + public void BoolAdd(in Voxels voxOperand) + => _BoolAdd(m_hThis, voxOperand.m_hThis); + + /// + /// Performs a boolean difference between the two voxel fields + /// Our voxel field's voxel will have all the matter removed + /// that is set in the operand + /// + /// Voxels to remove from our field + public void BoolSubtract(in Voxels voxOperand) + => _BoolSubtract(m_hThis, voxOperand.m_hThis); + + /// + /// Performs a boolean intersection between two voxel fields. + /// Our fields will have all voxels removed, that are not + /// inside the Operand's field + /// + /// Voxels masking our voxel field + public void BoolIntersect(in Voxels voxOperand) + => _BoolIntersect(m_hThis, voxOperand.m_hThis); + + /// + /// Performs a smooth boolean union, with the objects merged + /// into each other over the specified distance + /// + /// The voxels to add to ours + /// The distance in MM over which to merge + public void BoolAddSmoothVx(in Voxels voxOperand, float fDist) + => _BoolAddSmooth(m_hThis, voxOperand.m_hThis, fDist); + + /// + /// Performs a smooth boolean union, with the objects merged + /// into each other over the specified distance + /// + /// The voxels to add to ours + /// The distance in millimeters over which to merge + public void BoolAddSmooth(in Voxels voxOperand, float fDistMM) + => _BoolAddSmooth( m_hThis, voxOperand.m_hThis, fDistMM); + + /// + /// Offsets the voxel field by the specified distance. + /// The surface of the voxel field is moved outward or inward + /// Outward is positive, inward is negative + /// + /// The distance to move the surface outward (positive) or inward (negative) in millimeters + public void Offset(float fDistMM) + => _Offset(m_hThis, fDistMM); + + /// + /// Offsets the voxel field twice, but the specified distances + /// Outwards is positive, inwards is negative + /// + /// First offset distance in mm + /// Second distance in mm + public void DoubleOffset( float fDist1MM, + float fDist2MM) + => _DoubleOffset(m_hThis, fDist1MM, fDist2MM); + + /// + /// Offsets the voxel field three times by the specified distance. + /// First it offsets inwards by the specified distance + /// Then it offsets twice the distance outwards + /// Then it offsets the distance inwards again + /// This is useful to smoothen a voxel field. By offsetting inwards + /// you eliminate all convex detail below a certain threshold + /// by offsetting outwards, you eliminated concave detail below a threshold + /// by offsetting inwards again, you are back to the size of the object + /// that you started with, but without the detail + /// Usually call this with a positive number, although you can reverse + /// the operations by using a negative number + /// + /// Distance to move (in mm) + public void TripleOffset( float fDistMM) + => _TripleOffset(m_hThis, fDistMM); + + /// + /// Renders a mesh into the voxel field, combining it with + /// the existing content + /// + /// The mesh to render (needs to be a closed surface) + public void RenderMesh(in Mesh msh) + => _RenderMesh(m_hThis, msh.m_hThis); + + /// + /// Render an implicit signed distance function into the voxels + /// overwriting the existing content with the voxels where the implicit + /// function returns <= 0 + /// You will often want to use IntersectImplicit instead + /// + /// Implicit object with signed distance function + /// Bounding box in which to render the implicit + public void RenderImplicit( in IImplicit xImp, + in BBox3 oBounds) + => _RenderImplicit(m_hThis, in oBounds, xImp.fSignedDistance); + + /// + /// Render an implicit signed distance function into the voxels + /// but using the existing voxels as a mask. + /// If the voxel field contains a voxel at a given position, the voxel + /// will be set to true if the signed distance function returns <= 0 + /// and false if the signed distance function returns > 0 + /// So a voxel field, containting a filled sphere, will contain a + /// Gyroid Sphere, if used with a Gyroid implict + /// + /// Implicit object with signed distance function< + public void IntersectImplicit(in IImplicit xImp) + => _IntersectImplicit(m_hThis, xImp.fSignedDistance); + + /// + /// Renders a lattice into the voxel field, combining it with + /// the existing content + /// + /// The lattice to render + public void RenderLattice(in Lattice lat) + => _RenderLattice(m_hThis, lat.m_hThis); + + /// + /// Projects the slices at the start Z position upwards or downwards, + /// until it reaches the end Z position. + /// + /// Start voxel slice in mm + /// End voxel slice in mm + public void ProjectZSlice( float fStartZMM, + float fEndZMM) + => _ProjectZSlice( m_hThis, fStartZMM, fEndZMM); + + /// + /// Returns true if the voxel fields contain the same content + /// + /// Voxels to compare to + /// + public bool bIsEqual(in Voxels voxOther) + => _bIsEqual(m_hThis, voxOther.m_hThis); + + /// + /// This function evaluates the entire voxel field and returns + /// the volume of all voxels in cubic millimeters and the Bounding Box + /// in real world coordinates + /// Note this function is potentially slow, as it needs to traverse the + /// entire voxel field + /// + /// Cubic MMs of volume filled with voxels + /// The real world bounding box of the voxels + public void CalculateProperties(out float fVolumeCubicMM, + out BBox3 oBBox) + { + oBBox = new(); + _CalculateProperties(m_hThis, out fVolumeCubicMM, ref oBBox); + } + + /// + /// Returns the closest point from the search point on the surface + /// of the voxel field + /// + /// Search position + /// Point on the surface + /// True if point is found, false if field is empty + public bool bClosestPointOnSurface( in Vector3 vecSearch, + out Vector3 vecSurfacePoint) + { + vecSurfacePoint = new(); + return _bClosestPointOnSurface( m_hThis, + in vecSearch, + ref vecSurfacePoint); + } + + /// + /// Casts a ray to the surface of a voxel field and finds the + /// the point on the surface where the ray intersects + /// + /// Search point + /// Direction to search in + /// Point on the surface + /// True, point found. False, no surface in this direction + public bool bRayCastToSurface( in Vector3 vecSearch, + in Vector3 vecDirection, + out Vector3 vecSurfacePoint) + { + vecSurfacePoint = new(); + return _bRayCastToSurface( m_hThis, + in vecSearch, + in vecDirection, + ref vecSurfacePoint); + } + + /// + /// Returns the dimensions of the voxel field in discrete voxels + /// + /// Size in x direction in voxels + /// Size in y direction in voxels + /// Size in z direction in voxels + public void GetVoxelDimensions( out int nXSize, + out int nYSize, + out int nZSize) + { + _GetVoxelDimensions( m_hThis, + out nXSize, + out nYSize, + out nZSize); + } + + /// + /// Returns a signed distance-field-encoded slice of the voxel field + /// To use it, use GetVoxelDimensions to find out the size of the voxel + /// field in voxel units. Then allocate a new grayscale image to copy + /// the data into, and pass it as a reference. Since GetVoxelDimensions + /// is potentially an "expensive" function, we are putting the burden + /// on you to allocate an image and don't create it for you. You can + /// also re-use the image if you want to save an entire image stack + /// + /// Slice to retrieve. 0 is at the bottom. + /// Pre-allocated grayscale image to receive the values + public void GetVoxelSlice( in int nZSlice, + ref ImageGrayScale img) + { + GCHandle oPinnedArray = GCHandle.Alloc(img.m_afValues, GCHandleType.Pinned); + try + { + IntPtr afBufferPtr = oPinnedArray.AddrOfPinnedObject(); + _GetVoxelSlice(m_hThis, nZSlice, afBufferPtr); + } + finally + { + oPinnedArray.Free(); + } + } + } +} \ No newline at end of file diff --git a/PicoGK__Config.cs b/PicoGK__Config.cs new file mode 100644 index 0000000..b9162da --- /dev/null +++ b/PicoGK__Config.cs @@ -0,0 +1,51 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace PicoGK +{ + public partial class Config + { + // Path to the PicoGK Runtime + // If you want to load the runtime from a specific path, for example + // during development, you can specify it here. + + // Make sure the path ends in / unless it's empty + + public const string strPicoGKLibPath = ""; + + // Example: + // public const string strPicoGKLibPath = "/Users/myuser/PicoGKRuntime/" + } +} \ No newline at end of file diff --git a/PicoGK__Interop.cs b/PicoGK__Interop.cs new file mode 100644 index 0000000..9d0167d --- /dev/null +++ b/PicoGK__Interop.cs @@ -0,0 +1,606 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// +// PicoGK ("peacock") is a compact software kernel for computational geometry, +// specifically for use in Computational Engineering Models (CEM). +// +// For more information, please visit https://picogk.org +// +// PicoGK is developed and maintained by LEAP 71 - © 2023 by LEAP 71 +// https://leap71.com +// +// Computational Engineering will profoundly change our physical world in the +// years ahead. Thank you for being part of the journey. +// +// We have developed this library to be used widely, for both commercial and +// non-commercial projects alike. Therefore, we have released it under a +// permissive open-source license. +// +// The foundation of PicoGK is a thin layer on top of the powerful open-source +// OpenVDB project, which in turn uses many other Free and Open Source Software +// libraries. We are grateful to be able to stand on the shoulders of giants. +// +// LEAP 71 licenses this file to you under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with the +// License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, THE SOFTWARE IS +// PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Numerics; +using System.Runtime.InteropServices; +using System.Text; + +namespace PicoGK +{ + public partial class Config + { + public const string strPicoGKLib = strPicoGKLibPath + "picogk.1.0.dylib"; + } + + // private interfaces to external PicoGK Runtime library + + public partial class Library + { + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Library_Init", CharSet = CharSet.Ansi)] + private static extern void _Init(float fVoxelSizeMM); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Library_GetName", CharSet = CharSet.Ansi)] + private static extern void _GetName(StringBuilder psz); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Library_GetVersion", CharSet = CharSet.Ansi)] + private static extern void _GetVersion(StringBuilder psz); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Library_GetBuildInfo", CharSet = CharSet.Ansi)] + private static extern void _GetBuildInfo(StringBuilder psz); + } + + public partial class Mesh : IDisposable + { + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Mesh_hCreate")] + internal static extern IntPtr _hCreate(); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Mesh_hCreateFromVoxels")] + internal static extern IntPtr _hCreateFromVoxels(IntPtr hVoxels); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Mesh_bIsValid")] + private static extern bool _IsValid(IntPtr hThis); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Mesh_Destroy")] + private static extern void _Destroy(IntPtr hThis); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Mesh_nAddVertex")] + private static extern int _nAddVertex(IntPtr hThis, + in Vector3 V); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Mesh_nVertexCount")] + private static extern int _nVertexCount(IntPtr hThis); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Mesh_GetVertex")] + private static extern void _GetVertex(IntPtr hThis, + int nVertex, + ref Vector3 V); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Mesh_nAddTriangle")] + private static extern int _nAddTriangle(IntPtr hThis, + in Triangle T); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Mesh_nTriangleCount")] + private static extern int _nTriangleCount(IntPtr hThis); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Mesh_GetTriangle")] + private static extern void _GetTriangle( IntPtr hThis, + int nTriangle, + ref Triangle T); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Mesh_GetTriangleV")] + private static extern void _GetTriangleV( IntPtr hThis, + int nTriangle, + ref Vector3 vecA, + ref Vector3 vecB, + ref Vector3 vecC); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Mesh_GetBoundingBox")] + private static extern void _GetBoundingBox( IntPtr hThis, + ref BBox3 oBBox); + + // Dispose Pattern + + ~Mesh() + { + Dispose(false); + } + + public void Dispose() + { + // Dispose of unmanaged resources. + Dispose(true); + // Suppress finalization. + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool bDisposing) + { + if (m_bDisposed) + { + return; + } + + if (bDisposing) + { + // dispose managed state (managed objects). + // Nothing to do in this class + } + + if (m_hThis != IntPtr.Zero) + { + _Destroy(m_hThis); + m_hThis = IntPtr.Zero; + } + + m_bDisposed = true; + } + + bool m_bDisposed = false; + internal IntPtr m_hThis = IntPtr.Zero; + + } + + public partial class Lattice : IDisposable + { + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Lattice_hCreate")] + internal static extern IntPtr _hCreate(); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Lattice_bIsValid")] + private static extern bool _bIsValid(IntPtr hThis); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Lattice_Destroy")] + private static extern void _Destroy(IntPtr hThis); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Lattice_AddSphere")] + private static extern void _AddSphere( IntPtr hThis, + in Vector3 vecCenter, + float fRadius); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Lattice_AddBeam")] + private static extern void _AddBeam( IntPtr hThis, + in Vector3 vecA, + in Vector3 vecB, + float fRadiusA, + float fRadiusB, + bool bRoundCap); + + // Dispose Pattern + + ~Lattice() + { + Dispose(false); + } + + public void Dispose() + { + // Dispose of unmanaged resources. + Dispose(true); + // Suppress finalization. + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool bDisposing) + { + if (m_bDisposed) + { + return; + } + + if (bDisposing) + { + // dispose managed state (managed objects). + // Nothing to do in this class + } + + if (m_hThis != IntPtr.Zero) + { + _Destroy(m_hThis); + m_hThis = IntPtr.Zero; + } + + m_bDisposed = true; + } + + bool m_bDisposed = false; + internal IntPtr m_hThis = IntPtr.Zero; + } + + public partial class Voxels : IDisposable + { + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_hCreate")] + internal static extern IntPtr _hCreate(); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_hCreateCopy")] + internal static extern IntPtr _hCreateCopy(IntPtr hSource); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_bIsValid")] + private static extern bool _bIsValid(IntPtr hThis); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_Destroy")] + private static extern void _Destroy(IntPtr hThis); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_BoolAdd")] + private static extern void _BoolAdd( IntPtr hThis, + IntPtr hOther); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_BoolSubtract")] + private static extern void _BoolSubtract( IntPtr hThis, + IntPtr hOther); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_BoolIntersect")] + private static extern void _BoolIntersect( IntPtr hThis, + IntPtr hOther); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_BoolAddSmooth")] + private static extern void _BoolAddSmooth( IntPtr hThis, + IntPtr hOther, + float fSmoothDistance); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_Offset")] + private static extern void _Offset( IntPtr hThis, + float fOffset); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_DoubleOffset")] + private static extern void _DoubleOffset( IntPtr hThis, + float fOffset1, + float fOffset2); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_TripleOffset")] + private static extern void _TripleOffset( IntPtr hThis, + float fOffset); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_RenderMesh")] + private static extern void _RenderMesh( IntPtr hThis, + IntPtr hMesh); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate float CallbackImplicitDistance(in Vector3 vec); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_RenderImplicit")] + private static extern void _RenderImplicit( IntPtr hThis, + in BBox3 oBounds, + CallbackImplicitDistance Callback); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_IntersectImplicit")] + private static extern void _IntersectImplicit( IntPtr hThis, + CallbackImplicitDistance Callback); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_RenderLattice")] + private static extern void _RenderLattice( IntPtr hThis, + IntPtr hLattice); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_ProjectZSlice")] + private static extern void _ProjectZSlice( IntPtr hThis, + float fStartX, + float fEndX); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_bIsInside")] + private static extern bool _bIsInside( IntPtr hThis, + in Vector3 vecTestPoint); + + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_bIsEqual")] + private static extern bool _bIsEqual( IntPtr hThis, + IntPtr hOther); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_CalculateProperties")] + private extern static void _CalculateProperties( IntPtr hThis, + out float pfVolume, + ref BBox3 oBBox); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_bClosestPointOnSurface")] + private extern static bool _bClosestPointOnSurface( IntPtr hThis, + in Vector3 vecSearch, + ref Vector3 vecSurfacePoint); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_bRayCastToSurface")] + private extern static bool _bRayCastToSurface( IntPtr hThis, + in Vector3 vecSearch, + in Vector3 vecNormal, + ref Vector3 vecSurfacePoint); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_GetVoxelDimensions")] + private extern static void _GetVoxelDimensions( IntPtr hThis, + out int nXSize, + out int nYSize, + out int nZSize); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Voxels_GetSlice")] + private extern static void _GetVoxelSlice( IntPtr hThis, + int nZSlice, + IntPtr afBuffer); + + // Dispose Pattern + + ~Voxels() + { + Dispose(false); + } + + public void Dispose() + { + // Dispose of unmanaged resources. + Dispose(true); + // Suppress finalization. + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool bDisposing) + { + if (m_bDisposed) + { + return; + } + + if (bDisposing) + { + // dispose managed state (managed objects). + // Nothing to do in this class + } + + if (m_hThis != IntPtr.Zero) + { + _Destroy(m_hThis); + m_hThis = IntPtr.Zero; + } + + m_bDisposed = true; + } + + bool m_bDisposed = false; + internal IntPtr m_hThis = IntPtr.Zero; + } + + public partial class PolyLine : IDisposable + { + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PolyLine_hCreate")] + public static extern IntPtr _hCreate(in ColorFloat clr); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PolyLine_bIsValid")] + private static extern bool _bIsValid(IntPtr hThis); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PolyLine_Destroy")] + private static extern void _Destroy(IntPtr hThis); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PolyLine_nAddVertex")] + private static extern int _nAddVertex( IntPtr hThis, + in Vector3 vec); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PolyLine_nVertexCount")] + private static extern int _nVertexCount(IntPtr hThis); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PolyLine_GetVertex")] + private static extern void _GetVertex( IntPtr hThis, + int nIndex, + ref Vector3 vec); + + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "PolyLine_GetColor")] + private static extern void _GetColor( IntPtr hThis, + ref ColorFloat clr); + + // Dispose Pattern + + ~PolyLine() + { + Dispose(false); + } + + public void Dispose() + { + // Dispose of unmanaged resources. + Dispose(true); + // Suppress finalization. + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool bDisposing) + { + if (m_bDisposed) + { + return; + } + + if (bDisposing) + { + // dispose managed state (managed objects). + // Nothing to do in this class + } + + if (m_hThis != IntPtr.Zero) + { + _Destroy(m_hThis); + m_hThis = IntPtr.Zero; + } + + m_bDisposed = true; + } + + bool m_bDisposed = false; + internal IntPtr m_hThis = IntPtr.Zero; + } + + public partial class Viewer : IDisposable + { + // Define delegates for the callback functions + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + public delegate void InfoCallback( string strMessage, + bool bFatalError); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void UpdateCallback( IntPtr hViewer, + in Vector2 vecViewport, + ref ColorFloat clrBackground, + ref Matrix4x4 matModelViewProjection, + ref Matrix4x4 matModelTransform, + ref Matrix4x4 matStatic, + ref Vector3 vecEyePosition, + ref Vector3 vecEyeStatic); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void KeyPressedCallback( IntPtr hViewer, + int iKey, + int iScancode, + int iAction, + int iModifiers); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void MouseMovedCallback( IntPtr poViewer, + in Vector2 vecMousePos); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void MouseButtonCallback( IntPtr hViewer, + int iButton, + int iAction, + int iModifiers, + in Vector2 vecMousePos); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void ScrollWheelCallback( IntPtr hViewer, + in Vector2 vecScrollWheel, + in Vector2 vecMousePos); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void WindowSizelCallback( IntPtr hViewer, + in Vector2 vecWindowSize); + + // Define the P/Invoke signature for Viewer_hCreate + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Viewer_hCreate", CharSet = CharSet.Ansi)] + public static extern IntPtr _hCreate( string strWindowTitle, + in Vector2 vecSize, + InfoCallback fnInfoCallback, + UpdateCallback fnUpdateCallback, + KeyPressedCallback fnKeyPressedCallback, + MouseMovedCallback fnMouseMoveCallback, + MouseButtonCallback fnMouseButtonCallback, + ScrollWheelCallback fnScrollWheelCallback, + WindowSizelCallback fnWindowSizeCallback); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Viewer_bIsValid")] + private static extern bool _bIsValid(IntPtr hThis); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Viewer_Destroy")] + private static extern void _Destroy(IntPtr hThis); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Viewer_RequestUpdate")] + private static extern void _RequestUpdate(IntPtr hThis); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Viewer_bPoll")] + private static extern bool _bPoll(IntPtr hThis); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Viewer_RequestScreenShot")] + private static extern bool _RequestScreenShot( IntPtr hThis, + string strScreenShotPath); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Viewer_bLoadLightSetup")] + private static extern bool _bLoadLightSetup( IntPtr hThis, + byte [] abyDiffuseDdsBuffer, + int nDiffuseSize, + byte [] pSpecularDdsBuffer, + int nSpecularSize); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Viewer_RequestClose")] + private static extern void _RequestClose(IntPtr hThis); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Viewer_AddMesh")] + private static extern void _AddMesh( IntPtr hThis, + int nGroup, + IntPtr hMesh); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Viewer_RemoveMesh")] + private static extern void _RemoveMesh( IntPtr hThis, + IntPtr hMesh); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Viewer_AddPolyLine")] + private static extern void _AddPolyLine( IntPtr hThis, + int nGroupID, + IntPtr hPolyLine); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Viewer_RemovePolyLine")] + private static extern void _RemovePolyLine( IntPtr hThis, + IntPtr hPolyLine); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Viewer_SetGroupVisible")] + private static extern void _SetGroupVisible( IntPtr hThis, + int nGroupID, + bool bVisible); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Viewer_SetGroupStatic")] + private static extern void _SetGroupStatic( IntPtr hThis, + int nGroupID, + bool bStatic); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Viewer_SetGroupMaterial")] + private static extern void _SetGroupMaterial( IntPtr hThis, + int nGroupID, + in ColorFloat clr, + float fMetallic, + float fRoughness); + + [DllImport(Config.strPicoGKLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Viewer_SetGroupMatrix")] + private static extern void _SetGroupMatrix( IntPtr hThis, + int nGroupID, + in Matrix4x4 mat); + + // Dispose Pattern + + ~Viewer() + { + Dispose(false); + } + + public void Dispose() + { + // Dispose of unmanaged resources. + Dispose(true); + // Suppress finalization. + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool bDisposing) + { + + if (m_bDisposed) + { + return; + } + + if (bDisposing) + { + // dispose managed state (managed objects). + // Nothing to do in this class + } + + if (m_hThis != IntPtr.Zero) + { + _Destroy(m_hThis); + m_hThis = IntPtr.Zero; + } + + m_bDisposed = true; + } + + InfoCallback m_fnInfoCB; + UpdateCallback m_fnUpdateCB; + KeyPressedCallback m_fnKeyPressedCB; + MouseMovedCallback m_fnMouseMovedCB; + MouseButtonCallback m_fnMouseButtonCB; + ScrollWheelCallback m_fnScrollWheelCB; + WindowSizelCallback m_fnWindowSizeCB; + + bool m_bDisposed = false; + IntPtr m_hThis = IntPtr.Zero; + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1268eb1 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# Welcome to PicoGK + +PicoGK ("peacock") is a compact, robust, and fast geometry kernel for Computational Engineering. + +PicoGK was developed by Lin Kayser, and is maintained by [LEAP 71](www.leap71.com). PicoGK is released under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) (SPDX Apache-2.0). + +For more information, please visit https://PicoGK.org + +We believe that [Computational Engineering](https://leap71.com/computationalengineering/) will profoundly change the way we design physical objects in the years to come. But it needs a neutral, transparent and free and open-source platform in order to be adopted widely. + +By developing and maintaining PicoGK, we hope to make a contribution to accelerate the progress of invention. + +![9CF66413-8BA1-4E18-9BA7-F5254235B44A](Documentation/images/9CF66413-8BA1-4E18-9BA7-F5254235B44A.jpeg) + +We are indebted to other Open-Source projects, without which writing PicoGK would have been an impossible task. Most notably we use the Academy Software Foundation's [OpenVDB library](https://www.openvdb.org/), which provides the majority of the functionality neccessary to build a robust geometry kernel. In many ways, PicoGK is a thin-yet-powerful layer of functionality on top of OpenVDB. + +The PicoGK viewer relies on the [GLFW library](https://www.glfw.org/), for simple cross-platform OpenGL user interface functionality. + +PicoGK consists of two modules. + +- **PicoGK**, which is the C#-based project that you have currently open, and + +- **PicoGKRuntime**, a C++-based runtime library, which is in a [separate GitHub project](https://github.com/leap71/PicoGKRuntime). + +**PicoGK** provides an framework based on [Microsoft's open-source C# language](https://dotnet.microsoft.com/en-us/languages/csharp). It depends on PicoGKRuntime for the heavy lifting. + +**PicoGKRuntime** exposes a minimal "extern C" interface, which can be implemented by other geometry kernels, including commercial ones. + +We encourage other software vendors to support the PicoGKRuntime API and release commercial alternatives to our open-source project. PicoGK was developed as a reliable common interface that you, the engineer, can always fall back on when creating your own intellectual property. + +**PicoGK**, the C# code, which you have open right now, provides rich functionality based on the PicoGKRuntime interface. It makes it as simple as possible to build computational geometry. + +You will, however, ususally not use PicoGK directly to build Computational Engineering Models. We encourage you to look at our other open-source project, the [LEAP 71 ShapeKernel](https://github.com/leap71/ShapeKernel), which was written by Josefine Lissner, and forms the basis of all objects we create at LEAP 71 and uses PicoGK as the foundation. + +[Please head over to our documentation, for how to get started with PicoGK.](Documentation/README.md) + diff --git a/Runtime/README.md b/Runtime/README.md new file mode 100644 index 0000000..e8863eb --- /dev/null +++ b/Runtime/README.md @@ -0,0 +1,11 @@ +# PicoGK Runtime security on MacOS X + +If you receive the following warning on Mac + +image-20231017180157568 + +Please head over to your Privacy and Security settings and click on **Allow Anyway** — [alternatively you can compile](../Documentation/Compiling_PicoGKRuntime.md) and self-sign the runtime yourself. + +image-20231017180729775 + +After you have changed this, it should work. \ No newline at end of file diff --git a/ViewerEnvironment/Barcelona.zip b/ViewerEnvironment/Barcelona.zip new file mode 100644 index 0000000..dc705f5 Binary files /dev/null and b/ViewerEnvironment/Barcelona.zip differ diff --git a/ViewerEnvironment/DarkStudio.zip b/ViewerEnvironment/DarkStudio.zip new file mode 100644 index 0000000..225dc70 Binary files /dev/null and b/ViewerEnvironment/DarkStudio.zip differ diff --git a/ViewerEnvironment/PicoGKDefaultEnv.zip b/ViewerEnvironment/PicoGKDefaultEnv.zip new file mode 100644 index 0000000..c78a862 Binary files /dev/null and b/ViewerEnvironment/PicoGKDefaultEnv.zip differ diff --git a/ViewerEnvironment/SkyBlue.zip b/ViewerEnvironment/SkyBlue.zip new file mode 100644 index 0000000..6e213ed Binary files /dev/null and b/ViewerEnvironment/SkyBlue.zip differ diff --git a/ViewerEnvironment/SkyNeutral.zip b/ViewerEnvironment/SkyNeutral.zip new file mode 100644 index 0000000..9cd55af Binary files /dev/null and b/ViewerEnvironment/SkyNeutral.zip differ