Initial push
This commit is contained in:
+428
@@ -0,0 +1,428 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.env
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
|
||||
[Dd]ebug/x64/
|
||||
[Dd]ebugPublic/x64/
|
||||
[Rr]elease/x64/
|
||||
[Rr]eleases/x64/
|
||||
bin/x64/
|
||||
obj/x64/
|
||||
|
||||
[Dd]ebug/x86/
|
||||
[Dd]ebugPublic/x86/
|
||||
[Rr]elease/x86/
|
||||
[Rr]eleases/x86/
|
||||
bin/x86/
|
||||
obj/x86/
|
||||
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
[Aa][Rr][Mm]64[Ee][Cc]/
|
||||
bld/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Build results on 'Bin' directories
|
||||
**/[Bb]in/*
|
||||
# Uncomment if you have tasks that rely on *.refresh files to move binaries
|
||||
# (https://github.com/github/gitignore/pull/3736)
|
||||
#!**/[Bb]in/*.refresh
|
||||
|
||||
# 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.*
|
||||
*.trx
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Approval Tests result files
|
||||
*.received.*
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.idb
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
||||
!Directory.Build.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
**/.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
**/.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
**/.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
**/__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
#tools/**
|
||||
#!tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
MSBuild_Logs/
|
||||
|
||||
# AWS SAM Build and Temporary Artifacts folder
|
||||
.aws-sam
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
**/.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
**/.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
**/.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
@@ -0,0 +1,5 @@
|
||||
# WpfOpenGLSubwindowing
|
||||
|
||||
## Info
|
||||
This Project aims to share my experience with aOpenGL (OpenTK), AssimpNet and GLWpfControl to load 3D models in C#.
|
||||
Currently utilizes an Orbit Camera to view Model fixed to Center.
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.0.11217.181
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfOpenGLSubwindowing", "WpfOpenGLSubwindowing\WpfOpenGLSubwindowing.csproj", "{1E038559-E21A-419A-8C83-EAB9A873B8F8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfOpenGLSubwindowingClasses", "..\WpfOpenGLSubwindowingClasses\WpfOpenGLSubwindowingClasses\WpfOpenGLSubwindowingClasses.csproj", "{ABBFC151-B834-02DE-CE06-2CEEB90CD87F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{1E038559-E21A-419A-8C83-EAB9A873B8F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1E038559-E21A-419A-8C83-EAB9A873B8F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1E038559-E21A-419A-8C83-EAB9A873B8F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1E038559-E21A-419A-8C83-EAB9A873B8F8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{ABBFC151-B834-02DE-CE06-2CEEB90CD87F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{ABBFC151-B834-02DE-CE06-2CEEB90CD87F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{ABBFC151-B834-02DE-CE06-2CEEB90CD87F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{ABBFC151-B834-02DE-CE06-2CEEB90CD87F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {20CD5B98-9C10-4C9B-8D20-B0CDC0ED1A34}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,9 @@
|
||||
<Application x:Class="WpfOpenGLSubwindowing.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:WpfOpenGLSubwindowing"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Windows;
|
||||
|
||||
namespace WpfOpenGLSubwindowing
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
@@ -0,0 +1,80 @@
|
||||
<Window x:Class="WpfOpenGLSubwindowing.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:WpfOpenGLSubwindowing"
|
||||
xmlns:vm="clr-namespace:WpfOpenGLSubwindowing.ViewModel"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:gl="clr-namespace:OpenTK.Wpf;assembly=GLWpfControl"
|
||||
mc:Ignorable="d"
|
||||
Title="{Binding Title}" Height="450" Width="800"
|
||||
>
|
||||
|
||||
<Window.DataContext>
|
||||
<vm:MainWindowViewModel/>
|
||||
</Window.DataContext>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*"/>
|
||||
<ColumnDefinition Width="5*"/>
|
||||
<ColumnDefinition Width="5*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" CornerRadius="7.5" BorderBrush="Black" BorderThickness="1" Margin="5">
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
|
||||
<Button Command="{Binding LoadModelCommand}" Content="Load Model" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="20"/>
|
||||
<CheckBox Command="{Binding EnableAxisLinesCommand}" Content="Enable Axis Lines" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="20"/>
|
||||
<ListBox SelectedItem="{Binding SelectedDrawType}" ItemsSource="{Binding DrawTypes}" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="20"/>
|
||||
<TextBlock Text="Background Color:" Margin="5,0,0,10"/>
|
||||
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="5" Margin="5" Padding="5">
|
||||
<StackPanel>
|
||||
<WrapPanel HorizontalAlignment="Stretch">
|
||||
<TextBlock Text="R:" Margin="0,0,10,0"/>
|
||||
<Slider Value="{Binding ColorR}" Minimum="0.0" Maximum="1.0" Width="{Binding Path=ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type WrapPanel}}}"/>
|
||||
</WrapPanel>
|
||||
<WrapPanel>
|
||||
<TextBlock Text="G:" Margin="0,0,10,0"/>
|
||||
<Slider Value="{Binding ColorG}" Minimum="0.0" Maximum="1.0" Width="{Binding Path=ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type WrapPanel}}}"/>
|
||||
</WrapPanel>
|
||||
<WrapPanel>
|
||||
<TextBlock Text="B:" Margin="0,0,10,0"/>
|
||||
<Slider Value="{Binding ColorB}" Minimum="0.0" Maximum="1.0" Width="{Binding Path=ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type WrapPanel}}}"/>
|
||||
</WrapPanel>
|
||||
<WrapPanel>
|
||||
<TextBlock Text="A:" Margin="0,0,10,0"/>
|
||||
<Slider Value="{Binding ColorA}" Minimum="0.0" Maximum="1.0" Width="{Binding Path=ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type WrapPanel}}}"/>
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Grid.Column="1" CornerRadius="7.5" BorderBrush="Black" BorderThickness="1" Margin="5" Padding="5">
|
||||
<gl:GLWpfControl x:Name="OpenTkControl"
|
||||
Render="OpenTkControl_Render"
|
||||
Loaded="OpenTkControl_Loaded"
|
||||
MouseDown="OpenTkControl_MouseDown"
|
||||
MouseMove="OpenTkControl_MouseMove"
|
||||
MouseUp="OpenTkControl_MouseUp"
|
||||
MouseWheel="OpenTkControl_MouseWheel"
|
||||
SizeChanged="OpenTkControl_SizeChanged"
|
||||
MouseLeave="OpenTkControl_LostFocus"
|
||||
>
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="Unloaded">
|
||||
<i:InvokeCommandAction Command="{Binding ClosingGLCommand}"/>
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
</gl:GLWpfControl>
|
||||
</Border>
|
||||
<Border Grid.Column="2" CornerRadius="7.5" BorderBrush="Black" BorderThickness="1" Margin="5" Padding="5">
|
||||
<Grid x:Name="HwndRootGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="0*"/>
|
||||
<RowDefinition Height="1*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0" x:Name="HwndFirstGrid"/>
|
||||
<Grid Grid.Row="1" x:Name="HwndSecondGrid"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,122 @@
|
||||
using OpenTK.Mathematics;
|
||||
using OpenTK.Wpf;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using WpfOpenGLSubwindowing.ViewModel;
|
||||
using WpfOpenGLSubwindowingClasses.Camera;
|
||||
using WpfOpenGLSubwindowingClasses.Controls;
|
||||
using WpfOpenGLSubwindowingClasses.Renderer;
|
||||
|
||||
namespace WpfOpenGLSubwindowing
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private OpenGlHwndHost _hostTop;
|
||||
private OpenGlHwndHost _hostBottom;
|
||||
|
||||
private OpenGLInstance _glTop;
|
||||
public OpenGLInstance SecondaryGLInstance;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
if (DataContext is not MainWindowViewModel vm) return;
|
||||
|
||||
vm.MainWindowInstance = this;
|
||||
|
||||
Render = vm.OnRender;
|
||||
LoadedHandler = vm.OnLoadGL;
|
||||
|
||||
MouseDownHandler = OrbitCamera.MouseDownEvent;
|
||||
MouseMoveHandler = OrbitCamera.MouseMoveEvent;
|
||||
MouseUpHandler = OrbitCamera.MouseUpEvent;
|
||||
MouseWheelHandler = OrbitCamera.MouseWheelEvent;
|
||||
MouseLostFocusHandler = OrbitCamera.MouseLostFocusEvent;
|
||||
|
||||
var settings = new GLWpfControlSettings
|
||||
{
|
||||
MajorVersion = 4,
|
||||
MinorVersion = 6,
|
||||
Samples = 16,
|
||||
};
|
||||
|
||||
OpenTkControl.Start(settings);
|
||||
OpenTkControl.RenderContinuously = true;
|
||||
|
||||
// _glTop = new OpenGLInstance(@"Shaders\BasicShader.vert", @"Shaders\BasicShader.frag", @"Shaders\ColorShader.vert", @"Shaders\ColorShader.frag");
|
||||
SecondaryGLInstance = new OpenGLInstance(@"Shaders\BasicShader.vert", @"Shaders\BasicShader.frag", @"Shaders\ColorShader.vert", @"Shaders\ColorShader.frag");
|
||||
|
||||
// _hostTop = new OpenGlHwndHost(_glTop) { ShareResourcesWithFirst = true };
|
||||
_hostBottom = new OpenGlHwndHost(SecondaryGLInstance) { ShareResourcesWithFirst = true };
|
||||
|
||||
// HwndFirstGrid.Children.Add(_hostTop);
|
||||
HwndSecondGrid.Children.Add(_hostBottom);
|
||||
|
||||
// _glTop.LoadModelAsync(@"Models\12140_Skull_v3_L2.obj", replaceWhenLoaded: true);
|
||||
// SecondaryGLInstance.LoadModelAsync(@"Models\12140_Skull_v3_L2.obj", replaceWhenLoaded: true);
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
base.OnClosed(e);
|
||||
// Sauber beenden
|
||||
_glTop?.Close();
|
||||
SecondaryGLInstance?.Close();
|
||||
}
|
||||
|
||||
public Action<TimeSpan> Render;
|
||||
public Action<GLWpfControl> LoadedHandler;
|
||||
public Action<object, MouseButtonEventArgs> MouseDownHandler;
|
||||
public Action<object, MouseEventArgs> MouseMoveHandler;
|
||||
public Action<object, MouseButtonEventArgs> MouseUpHandler;
|
||||
public Action<object, MouseWheelEventArgs> MouseWheelHandler;
|
||||
public Action<object, RoutedEventArgs> MouseLostFocusHandler;
|
||||
public Action<object, SizeChangedEventArgs> SizeChangedHandler;
|
||||
|
||||
private void OpenTkControl_Render(TimeSpan obj) => Render(obj);
|
||||
private void OpenTkControl_Loaded(object sender, RoutedEventArgs e) => LoadedHandler(OpenTkControl);
|
||||
private void OpenTkControl_MouseDown(object sender, MouseButtonEventArgs e) => MouseDownHandler(sender, e);
|
||||
private void OpenTkControl_MouseMove(object sender, MouseEventArgs e) => MouseMoveHandler(sender, e);
|
||||
private void OpenTkControl_MouseUp(object sender, MouseButtonEventArgs e) => MouseUpHandler(sender, e);
|
||||
private void OpenTkControl_MouseWheel(object sender, MouseWheelEventArgs e) => MouseWheelHandler(sender, e);
|
||||
private void OpenTkControl_LostFocus(object sender, RoutedEventArgs e) => MouseLostFocusHandler(sender, e);
|
||||
|
||||
|
||||
private void OpenTkControl_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
var visual = sender as Visual ?? Application.Current.MainWindow;
|
||||
|
||||
double dpiX = 1.0, dpiY = 1.0;
|
||||
var ps = PresentationSource.FromVisual(visual);
|
||||
if (ps?.CompositionTarget != null)
|
||||
{
|
||||
dpiX = ps.CompositionTarget.TransformToDevice.M11;
|
||||
dpiY = ps.CompositionTarget.TransformToDevice.M22;
|
||||
}
|
||||
|
||||
int pxW = Math.Max(1, (int)Math.Round(e.NewSize.Width * dpiX));
|
||||
int pxH = Math.Max(1, (int)Math.Round(e.NewSize.Height * dpiY));
|
||||
|
||||
OrbitCamera.Width = pxW;
|
||||
OrbitCamera.Height = pxH;
|
||||
|
||||
float aspect = pxH > 0 ? (float)pxW / (float)pxH : 1f;
|
||||
|
||||
OrbitCamera.UpdateProjection(MathHelper.DegreesToRadians(OrbitCamera.Zoom), aspect);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.Win32;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Wpf;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Windows;
|
||||
using WpfOpenGLSubwindowingClasses.Camera;
|
||||
using WpfOpenGLSubwindowingClasses.ModelHandling;
|
||||
using WpfOpenGLSubwindowingClasses.Renderer;
|
||||
|
||||
namespace WpfOpenGLSubwindowing.ViewModel
|
||||
{
|
||||
public partial class MainWindowViewModel : ObservableObject
|
||||
{
|
||||
public int ViewportWidth { get; set; } = 1280;
|
||||
public int ViewportHeight { get; set; } = 720;
|
||||
public string Title { get; set; } = "WpfOpenGLSubwindowing";
|
||||
|
||||
public MainWindow? MainWindowInstance { get; set; }
|
||||
|
||||
private WpfOpenGLSubwindowingClasses.Renderer.OpenGLInstance? _renderer;
|
||||
private CancellationTokenSource _rCTS = new();
|
||||
|
||||
public float? ColorR
|
||||
{
|
||||
get => _renderer?.BackgroundColor.R ?? 1.0f;
|
||||
set
|
||||
{
|
||||
if (_renderer == null || value == null || _renderer.BackgroundColor.R == value) return;
|
||||
_renderer.BackgroundColor = new OpenTK.Mathematics.Color4((float)value, _renderer.BackgroundColor.G, _renderer.BackgroundColor.B, _renderer.BackgroundColor.A);
|
||||
OnPropertyChanged(nameof(ColorR));
|
||||
}
|
||||
}
|
||||
|
||||
public float? ColorG
|
||||
{
|
||||
get => _renderer?.BackgroundColor.G ?? 1.0f;
|
||||
set
|
||||
{
|
||||
if (_renderer == null || value == null || _renderer.BackgroundColor.G == value) return;
|
||||
_renderer.BackgroundColor = new OpenTK.Mathematics.Color4(_renderer.BackgroundColor.R, (float)value, _renderer.BackgroundColor.B, _renderer.BackgroundColor.A);
|
||||
OnPropertyChanged(nameof(ColorG));
|
||||
}
|
||||
}
|
||||
|
||||
public float? ColorB
|
||||
{
|
||||
get => _renderer?.BackgroundColor.B ?? 1.0f;
|
||||
set
|
||||
{
|
||||
if (_renderer == null || value == null || _renderer.BackgroundColor.B == value) return;
|
||||
_renderer.BackgroundColor = new OpenTK.Mathematics.Color4(_renderer.BackgroundColor.R, _renderer.BackgroundColor.G, (float)value, _renderer.BackgroundColor.A);
|
||||
OnPropertyChanged(nameof(ColorB));
|
||||
}
|
||||
}
|
||||
|
||||
public float? ColorA
|
||||
{
|
||||
get => _renderer?.BackgroundColor.A ?? 1.0f;
|
||||
set
|
||||
{
|
||||
if (_renderer == null || value == null || _renderer.BackgroundColor.A == value) return;
|
||||
_renderer.BackgroundColor = new OpenTK.Mathematics.Color4(_renderer.BackgroundColor.R, _renderer.BackgroundColor.G, _renderer.BackgroundColor.B, (float)value);
|
||||
OnPropertyChanged(nameof(ColorA));
|
||||
}
|
||||
}
|
||||
|
||||
GLWpfControl? _view;
|
||||
private bool _disposeRequested;
|
||||
|
||||
public IRelayCommand<GLWpfControl> ClosingGLCommand { get; }
|
||||
public IRelayCommand LoadModelCommand { get; }
|
||||
public IRelayCommand EnableAxisLinesCommand { get; }
|
||||
|
||||
public PrimitiveType SelectedDrawType
|
||||
{
|
||||
get;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
if (_renderer is not null) _renderer.SetPolygonMode(SelectedDrawType);
|
||||
}
|
||||
} = PrimitiveType.Triangles;
|
||||
|
||||
public List<PrimitiveType> DrawTypes { get; } = new()
|
||||
{
|
||||
PrimitiveType.Triangles,
|
||||
PrimitiveType.Lines,
|
||||
PrimitiveType.Points,
|
||||
};
|
||||
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
ClosingGLCommand = new RelayCommand<GLWpfControl>(_ => _disposeRequested = true);
|
||||
LoadModelCommand = new RelayCommand(LoadModel);
|
||||
EnableAxisLinesCommand = new RelayCommand(() => { if (_renderer is not null) _renderer.DisplayAxisLines = !_renderer.DisplayAxisLines; });
|
||||
}
|
||||
|
||||
public void OnLoadGL(GLWpfControl? view)
|
||||
{
|
||||
if (view == null) return;
|
||||
|
||||
_view = view;
|
||||
_renderer = new OpenGLInstance(@"Shaders\BasicShader.vert", @"Shaders\BasicShader.frag", @"Shaders\ColorShader.vert", @"Shaders\ColorShader.frag");
|
||||
}
|
||||
|
||||
public void OnRender(TimeSpan delta)
|
||||
{
|
||||
if (_renderer == null || _view == null)
|
||||
return;
|
||||
|
||||
if (_disposeRequested)
|
||||
{
|
||||
_renderer.Dispose();
|
||||
_renderer = null;
|
||||
|
||||
_view.Render -= OnRender;
|
||||
_view = null;
|
||||
_disposeRequested = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_renderer.Render();
|
||||
}
|
||||
|
||||
private void LoadModel()
|
||||
{
|
||||
string path = @"Models/12140_Skull_v3_L2.obj";
|
||||
OpenFileDialog dialog = new();
|
||||
// dialog.Filter = "obj files (*.obj)|*.obj";
|
||||
if(dialog.ShowDialog() == true)
|
||||
{
|
||||
path = dialog.FileName;
|
||||
}
|
||||
|
||||
_renderer?.LoadModelAsync(path, true);
|
||||
MainWindowInstance?.SecondaryGLInstance.LoadModelAsync(path, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
|
||||
# File Created: 21.12.2011 14:29:19
|
||||
|
||||
newmtl 12140_Skull_v3
|
||||
Ns 24.0000
|
||||
Ni 1.5000
|
||||
d 1.0000
|
||||
Tr 0.0000
|
||||
Tf 1.0000 1.0000 1.0000
|
||||
illum 2
|
||||
Ka 1.0000 1.0000 1.0000
|
||||
Kd 1.0000 1.0000 1.0000
|
||||
Ks 0.1170 0.1170 0.1170
|
||||
Ke 0.0000 0.0000 0.0000
|
||||
map_Ka Skull.jpg
|
||||
map_Kd Skull.jpg
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 157 KiB |
@@ -0,0 +1,95 @@
|
||||
#version 410 core
|
||||
|
||||
in DATA {
|
||||
vec3 Normal;
|
||||
vec3 color;
|
||||
vec2 texCoord;
|
||||
vec3 WorldPos;
|
||||
} data_in;
|
||||
|
||||
vec2 texCoord = data_in.texCoord;
|
||||
vec3 Normal = data_in.Normal;
|
||||
vec3 crntPos = data_in.WorldPos;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
uniform sampler2D diffuse0;
|
||||
uniform sampler2D specular0;
|
||||
|
||||
uniform vec4 material_ambient;
|
||||
uniform vec4 material_diffuse;
|
||||
uniform vec4 material_specular;
|
||||
uniform float material_shininess;
|
||||
|
||||
uniform bool hasDiffuseTexture;
|
||||
uniform bool hasSpecularTexture;
|
||||
|
||||
uniform vec4 lightColor;
|
||||
uniform vec3 lightPos;
|
||||
uniform vec3 camPos;
|
||||
|
||||
vec4 directLight()
|
||||
{
|
||||
vec3 final_ambient_color;
|
||||
vec3 final_diffuse_color;
|
||||
vec3 final_specular_color;
|
||||
|
||||
if(hasDiffuseTexture){
|
||||
final_diffuse_color = texture(diffuse0, texCoord).rgb;
|
||||
final_ambient_color = material_ambient.rgb;
|
||||
}
|
||||
else{
|
||||
final_ambient_color = material_ambient.rgb;
|
||||
final_diffuse_color = material_diffuse.rgb;
|
||||
}
|
||||
|
||||
if(hasSpecularTexture){
|
||||
final_specular_color = texture(specular0, texCoord).rgb;
|
||||
}
|
||||
else {
|
||||
final_specular_color = material_specular.rgb;
|
||||
}
|
||||
|
||||
// ambient lighting
|
||||
float ambientStrength = 0.20f;
|
||||
vec3 ambient = ambientStrength * final_ambient_color;
|
||||
|
||||
// diffuse lighting
|
||||
vec3 normal = normalize(Normal);
|
||||
vec3 lightDirection = normalize(lightPos - crntPos);
|
||||
float diff = max(dot(normal, lightDirection), 0.0f);
|
||||
vec3 diffuse = diff * final_diffuse_color;
|
||||
|
||||
// specular lighting
|
||||
vec3 specular = vec3(0.0f);
|
||||
if(diff > 0.0f){
|
||||
float specularStrength = 0.50f;
|
||||
vec3 viewDirection = normalize(camPos - crntPos);
|
||||
vec3 reflectionDirection = reflect(-lightDirection, normal);
|
||||
|
||||
// Blinn-Phong Model (Halway Vector)
|
||||
vec3 halfwayDir = normalize(lightDirection + viewDirection);
|
||||
float spec = pow(max(dot(normal, halfwayDir), 0.0f), material_shininess);
|
||||
specular = specularStrength * spec * final_specular_color;
|
||||
}
|
||||
|
||||
return vec4(ambient + diffuse + specular, 1.0f) * lightColor;
|
||||
}
|
||||
|
||||
vec4 noLight()
|
||||
{
|
||||
vec3 final_color;
|
||||
|
||||
if(hasDiffuseTexture){
|
||||
final_color = texture(diffuse0, texCoord).rgb;
|
||||
}
|
||||
else{
|
||||
final_color = material_diffuse.rgb;
|
||||
}
|
||||
|
||||
return vec4(final_color, 1.0f); // Rückgabe der Farbe mit voller Deckkraft
|
||||
}
|
||||
|
||||
void main(){
|
||||
outColor = directLight();
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#version 410 core
|
||||
|
||||
layout (location = 0) in vec3 aPos;
|
||||
layout (location = 1) in vec3 aNormal;
|
||||
layout (location = 2) in vec3 aColor;
|
||||
layout (location = 3) in vec2 aTexCoord;
|
||||
|
||||
out DATA {
|
||||
vec3 Normal;
|
||||
vec3 color;
|
||||
vec2 texCoord;
|
||||
vec3 WorldPos;
|
||||
} data_out;
|
||||
|
||||
uniform mat4 model;
|
||||
// uniform mat4 camMatrix;
|
||||
uniform mat4 view;
|
||||
uniform mat4 proj;
|
||||
uniform mat4 translation;
|
||||
uniform mat4 rotation;
|
||||
uniform mat4 scale;
|
||||
|
||||
void main() {
|
||||
mat4 M = translation * rotation * scale * model;
|
||||
vec4 worldPos = M * vec4(aPos, 1.0);
|
||||
gl_Position = proj * view * worldPos;
|
||||
data_out.WorldPos = worldPos.xyz;
|
||||
data_out.Normal = mat3(transpose(inverse(M))) * aNormal;
|
||||
data_out.color = aColor;
|
||||
data_out.texCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#version 410 core
|
||||
|
||||
in vec3 vColor;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
void main(){
|
||||
FragColor = vec4(vColor, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#version 410 core
|
||||
|
||||
layout(location=0) in vec3 aPos;
|
||||
layout(location=1) in vec3 aColor;
|
||||
|
||||
uniform mat4 view;
|
||||
uniform mat4 proj;
|
||||
uniform mat4 model;
|
||||
|
||||
out vec3 vColor;
|
||||
|
||||
void main(){
|
||||
vColor = aColor;
|
||||
gl_Position = proj * view * model * vec4(aPos,1.0);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net10.0-windows7.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Models\12140_Skull_v3_L2.mtl" />
|
||||
<None Remove="Models\12140_Skull_v3_L2.obj" />
|
||||
<None Remove="Models\Skull.jpg" />
|
||||
<None Remove="Shaders\BasicShader.frag" />
|
||||
<None Remove="Shaders\BasicShader.vert" />
|
||||
<None Remove="Shaders\ColorShader.frag" />
|
||||
<None Remove="Shaders\ColorShader.vert" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Models\12140_Skull_v3_L2.mtl">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Models\12140_Skull_v3_L2.obj">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Models\Skull.jpg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Shaders\BasicShader.frag">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Shaders\BasicShader.vert">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Shaders\ColorShader.frag">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Shaders\ColorShader.vert">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AssimpNetter" Version="6.0.2.1" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
|
||||
<PackageReference Include="OpenTK" Version="4.9.4" />
|
||||
<PackageReference Include="OpenTK.GLWpfControl" Version="4.3.3" />
|
||||
<PackageReference Include="StbImageSharp" Version="2.30.15" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\WpfOpenGLSubwindowingClasses\WpfOpenGLSubwindowingClasses\WpfOpenGLSubwindowingClasses.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,3 @@
|
||||
<Solution>
|
||||
<Project Path="WpfOpenGLSubwindowingClasses/WpfOpenGLSubwindowingClasses.csproj" />
|
||||
</Solution>
|
||||
@@ -0,0 +1,141 @@
|
||||
using OpenTK.Mathematics;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.Camera
|
||||
{
|
||||
public static class OrbitCamera
|
||||
{
|
||||
public static Matrix4 View;
|
||||
public static Matrix4 Projection;
|
||||
public static Matrix4 ViewMatrix;
|
||||
|
||||
public static Vector3 Target = Vector3.Zero;
|
||||
public static float OrbitRadius;
|
||||
|
||||
public static double Yaw = -90.0f;
|
||||
public static double Pitch = 0.0f;
|
||||
|
||||
public static float MouseSensitivity = 0.1f;
|
||||
public static float ScrollSpeed = 0.1f;
|
||||
public static float Zoom = 45.0f;
|
||||
|
||||
public static int Width, Height;
|
||||
public static float Near = 0.1f, Far = 1000f;
|
||||
|
||||
public static Vector3 Position = Vector3.Zero;
|
||||
public static Vector3 Front = new Vector3(0.0f, 0.0f, -1.0f);
|
||||
public static Vector3 Up = Vector3.UnitY;
|
||||
public static Vector3 Right = Vector3.UnitX;
|
||||
public static Vector3 WorldUp = Vector3.UnitY;
|
||||
|
||||
private static double _lastX = 0.0f;
|
||||
private static double _lastY = 0.0f;
|
||||
|
||||
public static float MinOrbitRadius = 0.1f;
|
||||
public static float MaxOrbitRadius = 10000f;
|
||||
|
||||
public static bool IsLeftMousePressed;
|
||||
|
||||
public static void Initialize(Vector3 initialPosition, Vector3 worldUp)
|
||||
{
|
||||
Target = Vector3.Zero;
|
||||
WorldUp = worldUp;
|
||||
|
||||
OrbitRadius = (initialPosition - Target).Length;
|
||||
if (OrbitRadius < 0.1f) OrbitRadius = 0.1f;
|
||||
|
||||
var toCamera = Vector3.Normalize(initialPosition - Target);
|
||||
|
||||
Pitch = MathHelper.RadiansToDegrees(MathF.Asin(toCamera.Y));
|
||||
Pitch = Math.Clamp(Pitch, -89.0f, 89.0f);
|
||||
|
||||
Yaw = MathHelper.RadiansToDegrees(MathF.Atan2(toCamera.Z, toCamera.X));
|
||||
|
||||
RecalculateCameraPositionAndVectors();
|
||||
}
|
||||
|
||||
public static void RecalculateCameraPositionAndVectors()
|
||||
{
|
||||
float yawRad = MathHelper.DegreesToRadians((float)Yaw);
|
||||
float pitchRad = MathHelper.DegreesToRadians((float)Pitch);
|
||||
|
||||
var dir = new Vector3(
|
||||
MathF.Cos(yawRad) * MathF.Cos(pitchRad),
|
||||
MathF.Sin(pitchRad),
|
||||
MathF.Sin(yawRad) * MathF.Cos(pitchRad)
|
||||
);
|
||||
|
||||
Position = Target - dir * OrbitRadius;
|
||||
|
||||
Front = Vector3.Normalize(Target - Position);
|
||||
Right = Vector3.Normalize(Vector3.Cross(Front, WorldUp));
|
||||
Up = Vector3.Normalize(Vector3.Cross(Right, Front));
|
||||
|
||||
UpdateView();
|
||||
}
|
||||
|
||||
public static void UpdateView() => View = Matrix4.LookAt(Position, Target, Up);
|
||||
public static void UpdateProjection(float fov, float aspectRatio) => Projection = Matrix4.CreatePerspectiveFieldOfView(fov, aspectRatio, Near, Far);
|
||||
|
||||
public static void MouseDownEvent(object _, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed) IsLeftMousePressed = true;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
public static void MouseMoveEvent(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (sender is not IInputElement control) return;
|
||||
var point = e.GetPosition(control);
|
||||
|
||||
if (IsLeftMousePressed) ProcessMouseMovement(point.X - _lastX, point.Y - _lastY);
|
||||
|
||||
_lastX = point.X;
|
||||
_lastY = point.Y;
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
public static void MouseUpEvent(object _, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Released) IsLeftMousePressed = false;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
public static void MouseWheelEvent(object _, MouseWheelEventArgs e)
|
||||
{
|
||||
ProcessMouseScroll(e.Delta);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
public static void MouseLostFocusEvent(object _, RoutedEventArgs __) => IsLeftMousePressed = false;
|
||||
|
||||
private static void ProcessMouseMovement(double xoff, double yoff, bool constrainPitch = true)
|
||||
{
|
||||
xoff *= MouseSensitivity;
|
||||
yoff *= MouseSensitivity;
|
||||
Yaw += xoff;
|
||||
Pitch -= yoff;
|
||||
|
||||
if (Yaw > 360.0) Yaw -= 360.0;
|
||||
else if (Yaw < 0.0) Yaw += 360.0;
|
||||
|
||||
if (constrainPitch)
|
||||
{
|
||||
if (Pitch > 89.0f) Pitch = 89.0f;
|
||||
if (Pitch < -89.0f) Pitch = -89.0f;
|
||||
}
|
||||
|
||||
RecalculateCameraPositionAndVectors();
|
||||
}
|
||||
|
||||
private static void ProcessMouseScroll(float yoff)
|
||||
{
|
||||
float steps = yoff / 120f;
|
||||
OrbitRadius = Math.Clamp(OrbitRadius - steps * ScrollSpeed, MinOrbitRadius, MaxOrbitRadius);
|
||||
RecalculateCameraPositionAndVectors();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using Assimp;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.ModelHandling;
|
||||
|
||||
public sealed class AxisLines : IDisposable
|
||||
{
|
||||
private int _vao, _vbo, _count;
|
||||
|
||||
public void Create(float length = 1f)
|
||||
{
|
||||
float L = length;
|
||||
float[] data = new float[]
|
||||
{
|
||||
// X (red)
|
||||
0,0,0, 1,0,0, L,0,0, 1,0,0,
|
||||
// Y (green)
|
||||
0,0,0, 0,1,0, 0,L,0, 0,1,0,
|
||||
// Z (blue)
|
||||
0,0,0, 0,0,1, 0,0,-L, 0,0,1,
|
||||
};
|
||||
|
||||
_count = data.Length / 6;
|
||||
|
||||
_vao = GL.GenVertexArray();
|
||||
_vbo = GL.GenBuffer();
|
||||
|
||||
GL.BindVertexArray(_vao);
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, _vbo);
|
||||
GL.BufferData(BufferTarget.ArrayBuffer, data.Length * sizeof(float), data, BufferUsageHint.StaticDraw);
|
||||
|
||||
int stride = 6 * sizeof(float);
|
||||
GL.EnableVertexAttribArray(0);
|
||||
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0);
|
||||
GL.EnableVertexAttribArray(1);
|
||||
GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, stride, 3 * sizeof(float));
|
||||
|
||||
GL.BindVertexArray(0);
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
|
||||
}
|
||||
|
||||
public void Draw(Shader.Shader shader, Matrix4 view, Matrix4 proj, Matrix4 model, float lineWidth = 2f, bool depthTest = true)
|
||||
{
|
||||
if (_vao == 0) return;
|
||||
if (!depthTest) GL.Disable(EnableCap.DepthTest);
|
||||
|
||||
GL.LineWidth(lineWidth);
|
||||
|
||||
shader.Use();
|
||||
|
||||
shader.SetMatrix4("view", view);
|
||||
shader.SetMatrix4("proj", proj);
|
||||
shader.SetMatrix4("model", model);
|
||||
|
||||
GL.BindVertexArray(_vao);
|
||||
GL.DrawArrays(OpenTK.Graphics.OpenGL4.PrimitiveType.Lines, 0, _count);
|
||||
GL.BindVertexArray(0);
|
||||
|
||||
if (!depthTest) GL.Enable(EnableCap.DepthTest);
|
||||
GL.LineWidth(1f);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_vbo != 0) GL.DeleteBuffer(_vbo);
|
||||
if (_vao != 0) GL.DeleteVertexArray(_vao);
|
||||
_vbo = _vao = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
using Assimp;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Mathematics;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using PrimitiveType = OpenTK.Graphics.OpenGL4.PrimitiveType;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.ModelHandling
|
||||
{
|
||||
public sealed class Mesh : IDisposable
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct Vertex(Vector3 pos, Vector3 normal, Vector3 color, Vector2 uv)
|
||||
{
|
||||
public Vector3 Position = pos;
|
||||
public Vector3 Normal = normal;
|
||||
public Vector3 Color = color;
|
||||
public Vector2 TexCoord = uv;
|
||||
}
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public Vertex[] Vertices { get; set; } = Array.Empty<Vertex>();
|
||||
public int[] Indices { get; set; } = Array.Empty<int>();
|
||||
public string? DiffuseTexturePath { get; set; }
|
||||
public int DiffuseTextureHandle { get; set; } = 0;
|
||||
public string? SpecularTexturePath { get; set; }
|
||||
public int SpecularTextureHandle { get; set; } = 0;
|
||||
|
||||
public int MaterialIndex { get; set; } = -1;
|
||||
|
||||
private int _vao;
|
||||
private int _vbo;
|
||||
private int _ebo;
|
||||
|
||||
public Color4 AmbientColor { get; set; } = new(0.2f, 0.2f, 0.2f, 1.0f);
|
||||
public Color4 DiffuseColor { get; set; } = new(0.8f, 0.8f, 0.8f, 1.0f);
|
||||
public Color4 SpecularColor { get; set; } = new(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
public float Shininess { get; set; } = 32.0f;
|
||||
|
||||
public bool HasGpuBuffers => _vao != 0;
|
||||
|
||||
public void CreateGpuBuffers()
|
||||
{
|
||||
if (HasGpuBuffers || Vertices.Length == 0 || Indices.Length == 0)
|
||||
return;
|
||||
|
||||
_vao = GL.GenVertexArray();
|
||||
_vbo = GL.GenBuffer();
|
||||
_ebo = GL.GenBuffer();
|
||||
|
||||
GL.BindVertexArray(_vao);
|
||||
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, _vbo);
|
||||
GL.BufferData(BufferTarget.ArrayBuffer, Vertices.Length * Unsafe.SizeOf<Vertex>(), Vertices, BufferUsageHint.StaticDraw);
|
||||
|
||||
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _ebo);
|
||||
GL.BufferData(BufferTarget.ElementArrayBuffer, Indices.Length * sizeof(int), Indices, BufferUsageHint.StaticDraw);
|
||||
|
||||
int stride = Unsafe.SizeOf<Vertex>();
|
||||
|
||||
var posOff = (int)Marshal.OffsetOf<Vertex>(nameof(Vertex.Position));
|
||||
var nrmOff = (int)Marshal.OffsetOf<Vertex>(nameof(Vertex.Normal));
|
||||
var colOff = (int)Marshal.OffsetOf<Vertex>(nameof(Vertex.Color));
|
||||
var uvOff = (int)Marshal.OffsetOf<Vertex>(nameof(Vertex.TexCoord));
|
||||
|
||||
// position (vec3) @ location 0
|
||||
GL.EnableVertexAttribArray(0);
|
||||
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, posOff);
|
||||
|
||||
// normal (vec3) @ location 1
|
||||
GL.EnableVertexAttribArray(1);
|
||||
GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, stride, nrmOff);
|
||||
|
||||
// color (vec3) @ location 2
|
||||
GL.EnableVertexAttribArray(2);
|
||||
GL.VertexAttribPointer(2, 3, VertexAttribPointerType.Float, false, stride, colOff);
|
||||
|
||||
// texcoord (vec2) @ location 3
|
||||
GL.EnableVertexAttribArray(3);
|
||||
GL.VertexAttribPointer(3, 2, VertexAttribPointerType.Float, false, stride, uvOff);
|
||||
|
||||
GL.BindVertexArray(0);
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
|
||||
}
|
||||
|
||||
public void Draw(Shader.Shader shader, Matrix4 M, Vector3 translation, OpenTK.Mathematics.Quaternion rotation, Vector3 scale, PrimitiveType? drawModeOverride = null)
|
||||
{
|
||||
if (!HasGpuBuffers) return;
|
||||
if (drawModeOverride == null) drawModeOverride = PrimitiveType.Triangles;
|
||||
|
||||
var T = Matrix4.CreateTranslation(translation);
|
||||
var R = Matrix4.CreateFromQuaternion(rotation);
|
||||
var S = Matrix4.CreateScale(scale);
|
||||
|
||||
// activate shader in beforehand -> activation call each mesh is redundant
|
||||
|
||||
shader.SetMatrix4("translation", T);
|
||||
shader.SetMatrix4("rotation", R);
|
||||
shader.SetMatrix4("scale", S);
|
||||
shader.SetMatrix4("model", M);
|
||||
shader.SetVector4("material_ambient", (Vector4)AmbientColor);
|
||||
shader.SetVector4("material_diffuse", (Vector4)DiffuseColor);
|
||||
shader.SetVector4("material_specular", (Vector4)SpecularColor);
|
||||
shader.SetFloat("material_shininess", Shininess);
|
||||
|
||||
// Textur-Handling
|
||||
bool hasDiffuseTexture = DiffuseTextureHandle != 0;
|
||||
shader.SetBool("hasDiffuseTexture", hasDiffuseTexture);
|
||||
|
||||
if (hasDiffuseTexture)
|
||||
{
|
||||
GL.ActiveTexture(TextureUnit.Texture0);
|
||||
GL.BindTexture(TextureTarget.Texture2D, DiffuseTextureHandle);
|
||||
shader.SetInt("diffuse0", 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.ActiveTexture(TextureUnit.Texture0);
|
||||
GL.BindTexture(TextureTarget.Texture2D, 0);
|
||||
}
|
||||
|
||||
// Specular Texture Handling
|
||||
bool hasSpecularTexture = SpecularTextureHandle != 0;
|
||||
shader.SetBool("hasSpecularTexture", hasSpecularTexture);
|
||||
|
||||
if (hasSpecularTexture)
|
||||
{
|
||||
GL.ActiveTexture(TextureUnit.Texture1);
|
||||
GL.BindTexture(TextureTarget.Texture2D, SpecularTextureHandle);
|
||||
shader.SetInt("specular0", 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.ActiveTexture(TextureUnit.Texture1);
|
||||
GL.BindTexture(TextureTarget.Texture2D, 0);
|
||||
}
|
||||
|
||||
GL.BindVertexArray(_vao);
|
||||
GL.DrawElements(drawModeOverride.Value, Indices.Length, DrawElementsType.UnsignedInt, 0);
|
||||
GL.BindVertexArray(0);
|
||||
|
||||
// Opt: unbind tex
|
||||
GL.ActiveTexture(TextureUnit.Texture0);
|
||||
GL.BindTexture(TextureTarget.Texture2D, 0);
|
||||
GL.ActiveTexture(TextureUnit.Texture1);
|
||||
GL.BindTexture(TextureTarget.Texture2D, 0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_vao != 0) { GL.DeleteVertexArray(_vao); _vao = 0; }
|
||||
if (_vbo != 0) { GL.DeleteBuffer(_vbo); _vbo = 0; }
|
||||
if (_ebo != 0) { GL.DeleteBuffer(_ebo); _ebo = 0; }
|
||||
if (DiffuseTextureHandle != 0) { GL.DeleteTexture(DiffuseTextureHandle); DiffuseTextureHandle = 0; }
|
||||
if (SpecularTextureHandle != 0) { GL.DeleteTexture(SpecularTextureHandle); SpecularTextureHandle = 0; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Mathematics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.ModelHandling
|
||||
{
|
||||
public class Model(string path, List<Mesh> meshes) : IDisposable
|
||||
{
|
||||
public string Path { get; set; } = path;
|
||||
public List<Mesh> Meshes { get; set; } = meshes;
|
||||
public OpenTK.Mathematics.Vector3 CenterOffset { get; private set; } = OpenTK.Mathematics.Vector3.Zero;
|
||||
public Matrix4 ModelMatrix { get; private set; } = Matrix4.Identity;
|
||||
|
||||
public OpenTK.Mathematics.Vector3 Translation { get; set; } = OpenTK.Mathematics.Vector3.Zero;
|
||||
public OpenTK.Mathematics.Quaternion Rotation { get; set; } = OpenTK.Mathematics.Quaternion.Identity;
|
||||
public OpenTK.Mathematics.Vector3 Scale { get; set; } = OpenTK.Mathematics.Vector3.One;
|
||||
|
||||
public void ComputeCenterOffset(OpenTK.Mathematics.Vector3 target = default)
|
||||
{
|
||||
var (min, max) = ComputeGlobalBounds();
|
||||
var currentCenter = (min + max) * 0.5f;
|
||||
CenterOffset = target - currentCenter;
|
||||
// ModelMatrix = Matrix4.CreateTranslation(CenterOffset);
|
||||
Translation = CenterOffset;
|
||||
}
|
||||
|
||||
public void CenterModelAroundVec(OpenTK.Mathematics.Vector3? targetCenter = null)
|
||||
{
|
||||
var c = OpenTK.Mathematics.Vector3.Zero;
|
||||
if (targetCenter is not null) c = targetCenter.Value;
|
||||
|
||||
var (min, max) = ComputeGlobalBounds();
|
||||
var currentCenter = (min + max) * 0.5f;
|
||||
var offset = c - currentCenter;
|
||||
|
||||
foreach (var mesh in Meshes)
|
||||
{
|
||||
var verts = mesh.Vertices;
|
||||
|
||||
for (int i = 0; i < verts.Length; i++)
|
||||
{
|
||||
var v = verts[i];
|
||||
verts[i] = new Mesh.Vertex(v.Position + offset, v.Normal, v.Color, v.TexCoord);
|
||||
}
|
||||
|
||||
mesh.Vertices = verts;
|
||||
}
|
||||
}
|
||||
|
||||
public (OpenTK.Mathematics.Vector3 min, OpenTK.Mathematics.Vector3 max) ComputeGlobalBounds()
|
||||
{
|
||||
var min = new OpenTK.Mathematics.Vector3(float.MaxValue);
|
||||
var max = new OpenTK.Mathematics.Vector3(float.MinValue);
|
||||
|
||||
foreach (var mesh in Meshes)
|
||||
{
|
||||
foreach (var v in mesh.Vertices)
|
||||
{
|
||||
min = OpenTK.Mathematics.Vector3.ComponentMin(min, v.Position);
|
||||
max = OpenTK.Mathematics.Vector3.ComponentMax(max, v.Position);
|
||||
}
|
||||
}
|
||||
|
||||
return (min, max);
|
||||
}
|
||||
|
||||
public void ApplyUniformScale(float? s = null)
|
||||
{
|
||||
if (s == null) s = ComputeScaleToMaxExtent(this, 5f);
|
||||
if (s <= 0f) return;
|
||||
|
||||
foreach (var mesh in Meshes)
|
||||
{
|
||||
var verts = mesh.Vertices;
|
||||
for (int i = 0; i < verts.Length; i++)
|
||||
{
|
||||
var v = verts[i];
|
||||
verts[i] = new Mesh.Vertex(v.Position * (float)s, v.Normal, v.Color, v.TexCoord);
|
||||
}
|
||||
mesh.Vertices = verts;
|
||||
}
|
||||
}
|
||||
|
||||
static float ComputeScaleToMaxExtent(Model model, float targetMaxExtent)
|
||||
{
|
||||
var (min, max) = model.ComputeGlobalBounds();
|
||||
var extent = max - min;
|
||||
float maxExtent = Math.Max(extent.X, Math.Max(extent.Y, extent.Z));
|
||||
if (maxExtent <= 0f) return 1f;
|
||||
return targetMaxExtent / maxExtent;
|
||||
}
|
||||
|
||||
public void Draw(Shader.Shader shader, PrimitiveType? drawModeOverride = null)
|
||||
{
|
||||
if (drawModeOverride == null) drawModeOverride = PrimitiveType.Triangles;
|
||||
foreach (var mesh in Meshes) mesh.Draw(shader, Matrix4.Identity, Translation, Rotation, Scale, drawModeOverride);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach(var mesh in Meshes)
|
||||
{
|
||||
mesh.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+309
@@ -0,0 +1,309 @@
|
||||
using Assimp;
|
||||
using Assimp.Configs;
|
||||
using OpenTK.Mathematics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.MemoryMappedFiles;
|
||||
using System.Numerics;
|
||||
using System.Windows.Documents;
|
||||
|
||||
using Vector2 = OpenTK.Mathematics.Vector2;
|
||||
using Vector3 = OpenTK.Mathematics.Vector3;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.ModelHandling
|
||||
{
|
||||
public sealed class ModelLoader(bool logging = false, bool logToDebug = false)
|
||||
{
|
||||
public bool Logging { get; set; } = logging;
|
||||
public bool LogToDebug { get; set; } = logToDebug;
|
||||
|
||||
public Model Load(string objPath, bool loadTextures = true)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(objPath))
|
||||
throw new ArgumentException("Pfad ist leer.", nameof(objPath));
|
||||
if (!File.Exists(objPath))
|
||||
throw new FileNotFoundException("Obj-Datei nicht gefunden.", objPath);
|
||||
string baseDir = Path.GetDirectoryName(objPath)!;
|
||||
|
||||
var ctx = new AssimpContext();
|
||||
ctx.SetConfig(new Assimp.Configs.FBXImportCamerasConfig(false));
|
||||
ctx.SetConfig(new NormalSmoothingAngleConfig(60.0f));
|
||||
|
||||
var flags =
|
||||
PostProcessSteps.Triangulate |
|
||||
PostProcessSteps.JoinIdenticalVertices |
|
||||
PostProcessSteps.GenerateSmoothNormals |
|
||||
PostProcessSteps.CalculateTangentSpace |
|
||||
PostProcessSteps.FlipUVs |
|
||||
PostProcessSteps.ImproveCacheLocality |
|
||||
PostProcessSteps.OptimizeMeshes |
|
||||
PostProcessSteps.PreTransformVertices
|
||||
;
|
||||
|
||||
Scene? scene = null;
|
||||
try
|
||||
{
|
||||
/*
|
||||
var mmf = MemoryMappedFile.CreateFromFile(objPath);
|
||||
var stream = mmf.CreateViewStream();
|
||||
scene = ctx.ImportFileFromStream(stream, flags);
|
||||
*/
|
||||
scene = ctx.ImportFile(objPath, flags);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine("Assimp Importer Failed: " + e);
|
||||
}
|
||||
|
||||
if (scene == null || scene.MeshCount == 0)
|
||||
throw new InvalidOperationException("Import has no meshes.");
|
||||
|
||||
var result = new List<Mesh>(scene.MeshCount);
|
||||
|
||||
for (int m = 0; m < scene.MeshCount; m++)
|
||||
{
|
||||
var aMesh = scene.Meshes[m];
|
||||
var mesh = new Mesh
|
||||
{
|
||||
Name = string.IsNullOrEmpty(aMesh.Name) ? $"Mesh_{m}" : aMesh.Name,
|
||||
MaterialIndex = aMesh.MaterialIndex
|
||||
};
|
||||
|
||||
// Vertices
|
||||
var vertices = new Mesh.Vertex[aMesh.VertexCount];
|
||||
for (int i = 0; i < aMesh.VertexCount; i++)
|
||||
{
|
||||
var pos = ToVec3(aMesh.Vertices[i]);
|
||||
var normal = aMesh.HasNormals ? ToVec3(aMesh.Normals[i]) : Vector3.UnitY;
|
||||
|
||||
Vector2 uv = Vector2.Zero;
|
||||
if (aMesh.HasTextureCoords(0))
|
||||
{
|
||||
var t = aMesh.TextureCoordinateChannels[0][i];
|
||||
uv = new Vector2(t.X, t.Y);
|
||||
}
|
||||
|
||||
Vector3 vertexColor = Vector3.One;
|
||||
if (aMesh.HasVertexColors(0))
|
||||
{
|
||||
var assimpColor = aMesh.VertexColorChannels[0][i];
|
||||
vertexColor = new Vector3(assimpColor.X, assimpColor.Y, assimpColor.Z);
|
||||
}
|
||||
|
||||
vertices[i] = new Mesh.Vertex(pos, normal, vertexColor, uv);
|
||||
}
|
||||
|
||||
// Indices
|
||||
var indices = new List<int>(aMesh.FaceCount * 3);
|
||||
for (int f = 0; f < aMesh.FaceCount; f++)
|
||||
{
|
||||
var face = aMesh.Faces[f];
|
||||
|
||||
if (face.IndexCount == 3)
|
||||
{
|
||||
indices.Add(face.Indices[0]);
|
||||
indices.Add(face.Indices[1]);
|
||||
indices.Add(face.Indices[2]);
|
||||
}
|
||||
else if (face.IndexCount > 3)
|
||||
{
|
||||
for (int k = 1; k < face.IndexCount - 1; k++)
|
||||
{
|
||||
indices.Add(face.Indices[0]);
|
||||
indices.Add(face.Indices[k]);
|
||||
indices.Add(face.Indices[k + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mesh.Vertices = vertices;
|
||||
mesh.Indices = indices.ToArray();
|
||||
|
||||
// Material
|
||||
if (aMesh.MaterialIndex >= 0 && aMesh.MaterialIndex < scene.MaterialCount)
|
||||
{
|
||||
var mat = scene.Materials[aMesh.MaterialIndex];
|
||||
|
||||
// Diffuse (Kd)
|
||||
if (mat.HasColorDiffuse)
|
||||
{
|
||||
var c = mat.ColorDiffuse;
|
||||
mesh.DiffuseColor = new(c.X, c.Y, c.Z, c.W);
|
||||
Log($"Material '{mat.Name}' Diffuse Color (Kd): {mesh.DiffuseColor}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"Material '{mat.Name}' has no Diffuse Color (Kd), using default.");
|
||||
}
|
||||
|
||||
// Ambient (Ka)
|
||||
if (mat.HasColorAmbient)
|
||||
{
|
||||
var c = mat.ColorAmbient;
|
||||
mesh.AmbientColor = new(c.X, c.Y, c.Z, c.W);
|
||||
Log($"Material '{mat.Name}' Ambient Color (Ka): {mesh.AmbientColor}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"Material '{mat.Name}' has no Ambient Color (Ka), using default.");
|
||||
}
|
||||
|
||||
// Specular (Ks)
|
||||
if (mat.HasColorSpecular)
|
||||
{
|
||||
var c = mat.ColorSpecular;
|
||||
mesh.SpecularColor = new(c.X, c.Y, c.Z, c.W);
|
||||
Log($"Material '{mat.Name}' Specular Color (Ks): {mesh.SpecularColor}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"Material '{mat.Name}' has no Specular Color (Ks), using default.");
|
||||
}
|
||||
|
||||
// Shininess (Ns)
|
||||
if (mat.HasShininess)
|
||||
{
|
||||
mesh.Shininess = mat.Shininess;
|
||||
Log($"Material '{mat.Name}' Shininess (Ns): {mesh.Shininess}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"Material '{mat.Name}' has no Shininess (Ns), using default.");
|
||||
}
|
||||
|
||||
// Load textures from files
|
||||
string? diffusePath = TryResolveTexturePath(mat, TextureType.Diffuse, baseDir);
|
||||
if (diffusePath != null)
|
||||
{
|
||||
mesh.DiffuseTexturePath = diffusePath;
|
||||
if (loadTextures)
|
||||
{
|
||||
try
|
||||
{
|
||||
Texture diffuseTexture = TextureManager.LoadTextureFromFile(diffusePath);
|
||||
mesh.DiffuseTextureHandle = diffuseTexture.Handle;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"Error loading diffuse texture '{diffusePath}': {ex.Message}");
|
||||
mesh.DiffuseTextureHandle = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string? specularPath = TryResolveTexturePath(mat, TextureType.Specular, baseDir);
|
||||
if (specularPath != null)
|
||||
{
|
||||
mesh.SpecularTexturePath = specularPath;
|
||||
if (loadTextures) {
|
||||
try
|
||||
{
|
||||
Texture specularTexture = TextureManager.LoadTextureFromFile(specularPath);
|
||||
mesh.SpecularTextureHandle = specularTexture.Handle;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"Error loading specular texture '{specularPath}': {ex.Message}");
|
||||
mesh.SpecularTextureHandle = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
return new(objPath, result);
|
||||
}
|
||||
|
||||
private static OpenTK.Mathematics.Vector3 ToVec3(System.Numerics.Vector3 v) => new OpenTK.Mathematics.Vector3(v.X, v.Y, v.Z);
|
||||
|
||||
private static string? TryResolveTexturePath(Material mat, TextureType type, string baseDir, bool logging = false, bool logToDebug = false)
|
||||
{
|
||||
Log($"--- Resolving texture for type: {type} ---", logging, logToDebug);
|
||||
|
||||
if (mat.GetMaterialTextureCount(type) <= 0)
|
||||
{
|
||||
Log($"No textures of type {type} found in material.", logging, logToDebug);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (mat.GetMaterialTexture(type, 0, out TextureSlot slot))
|
||||
{
|
||||
var raw = slot.FilePath;
|
||||
Log($"Raw texture path from material: '{raw}'", logging, logToDebug);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
Log($"Raw texture path is null or whitespace.", logging, logToDebug);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (raw.StartsWith("*"))
|
||||
{
|
||||
Log($"Raw texture path starts with an asterisk (Assimp internal reference).", logging, logToDebug);
|
||||
return null; // Assimp internal reference, not a file path
|
||||
}
|
||||
|
||||
var candidate1 = Path.IsPathRooted(raw) ? raw : Path.Combine(baseDir, raw);
|
||||
Log($"Candidate 1 path: '{candidate1}'", logging, logToDebug);
|
||||
if (File.Exists(candidate1))
|
||||
{
|
||||
Log($"Found texture at candidate 1: '{candidate1}'", logging, logToDebug);
|
||||
return candidate1;
|
||||
}
|
||||
Log($"Candidate 1 not found.", logging, logToDebug);
|
||||
|
||||
var normalized = raw.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar);
|
||||
var candidate2 = Path.Combine(baseDir, normalized);
|
||||
Log($"Candidate 2 path (normalized): '{candidate2}'", logging, logToDebug);
|
||||
if (File.Exists(candidate2))
|
||||
{
|
||||
Log($"Found texture at candidate 2: '{candidate2}'", logging, logToDebug);
|
||||
return candidate2;
|
||||
}
|
||||
Log($"Candidate 2 not found.", logging, logToDebug);
|
||||
|
||||
var fileName = Path.GetFileName(raw);
|
||||
Log($"Searching for file name '{fileName}' in '{baseDir}' and subdirectories.", logging, logToDebug);
|
||||
try
|
||||
{
|
||||
foreach (var f in Directory.EnumerateFiles(baseDir, "*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (string.Equals(Path.GetFileName(f), fileName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Log($"Found texture by filename search: '{f}'", logging, logToDebug);
|
||||
return f;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"Error during directory enumeration for texture: {ex.Message}", logging, logToDebug);
|
||||
}
|
||||
Log($"File name '{fileName}' not found in '{baseDir}' or subdirectories.", logging, logToDebug);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"Failed to get material texture slot for type {type}.", logging, logToDebug);
|
||||
}
|
||||
|
||||
Log($"--- Failed to resolve texture path for type: {type} ---", logging, logToDebug);
|
||||
return null;
|
||||
}
|
||||
|
||||
void Log(string message)
|
||||
{
|
||||
if(Logging && !LogToDebug) Console.WriteLine(message);
|
||||
else if(Logging && LogToDebug) Debug.WriteLine(message);
|
||||
}
|
||||
|
||||
static void Log(string message, bool logging, bool logToDebug)
|
||||
{
|
||||
if (logging && !logToDebug) Console.WriteLine(message);
|
||||
else if (logging && logToDebug) Debug.WriteLine(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using StbImageSharp; // NEU: Import für StbImageSharp
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.ModelHandling
|
||||
{
|
||||
public class Texture : IDisposable
|
||||
{
|
||||
public int Handle { get; private set; }
|
||||
public string? Path { get; private set; }
|
||||
|
||||
public Texture(int glHandle, string? path)
|
||||
{
|
||||
Handle = glHandle;
|
||||
Path = path;
|
||||
}
|
||||
|
||||
public static Texture LoadFromFile(string path, bool logging = false, bool logToDebug = false)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
Log($"Error: Texture file not found at '{path}'", logging, logToDebug);
|
||||
return new Texture(0, path);
|
||||
}
|
||||
|
||||
int handle = 0;
|
||||
|
||||
try
|
||||
{
|
||||
handle = GL.GenTexture();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or element is obsolete
|
||||
if (e is ExecutionEngineException)
|
||||
{
|
||||
Log("This is exception should not be thrown by runtime anymore -> unexpected!", logging, logToDebug);
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
GL.ActiveTexture(TextureUnit.Texture0);
|
||||
GL.BindTexture(TextureTarget.Texture2D, handle);
|
||||
|
||||
StbImage.stbi_set_flip_vertically_on_load(1);
|
||||
|
||||
ImageResult? image = null;
|
||||
|
||||
try
|
||||
{
|
||||
using var fs = File.OpenRead(path);
|
||||
image = ImageResult.FromStream(fs, ColorComponents.RedGreenBlueAlpha);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"Error loading image with StbImageSharp from '{path}': {ex.Message}", logging, logToDebug);
|
||||
GL.DeleteTexture(handle);
|
||||
return new Texture(0, path);
|
||||
}
|
||||
finally
|
||||
{
|
||||
StbImage.stbi_set_flip_vertically_on_load(0);
|
||||
}
|
||||
|
||||
if (image == null)
|
||||
{
|
||||
Log($"Error: StbImageSharp returned null for '{path}'", logging, logToDebug);
|
||||
GL.DeleteTexture(handle);
|
||||
return new Texture(0, path);
|
||||
}
|
||||
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, image.Width, image.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, image.Data);
|
||||
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
|
||||
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
|
||||
|
||||
GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
|
||||
|
||||
return new Texture(handle, path);
|
||||
}
|
||||
|
||||
public void Use(TextureUnit unit)
|
||||
{
|
||||
GL.ActiveTexture(unit);
|
||||
GL.BindTexture(TextureTarget.Texture2D, Handle);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Handle != 0)
|
||||
{
|
||||
GL.DeleteTexture(Handle);
|
||||
Handle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void Log(string message, bool logging, bool logToDebug)
|
||||
{
|
||||
if (logging && !logToDebug) Console.WriteLine(message);
|
||||
else if (logging && logToDebug) Debug.WriteLine(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.ModelHandling
|
||||
{
|
||||
public static class TextureManager
|
||||
{
|
||||
static private List<Texture> _textures = new();
|
||||
public static IReadOnlyList<Texture> Textures => _textures;
|
||||
|
||||
public static Texture LoadTextureFromFile(string path)
|
||||
{
|
||||
var tex = Textures.FirstOrDefault(x => x.Path == path);
|
||||
if (tex is not null) return tex;
|
||||
|
||||
tex = Texture.LoadFromFile(path);
|
||||
AddTexture(tex);
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
||||
public static void AddTexture(Texture texture)
|
||||
{
|
||||
if(_textures.Any(x => x.Path == texture.Path)) return;
|
||||
if (!_textures.Contains(texture)) _textures.Add(texture);
|
||||
}
|
||||
|
||||
public static void RemoveTexture(Texture texture) => _textures.Remove(texture);
|
||||
public static void RemoveTexture(string path)
|
||||
{
|
||||
var tex = _textures.FirstOrDefault(x => x.Path == path);
|
||||
if (tex is null) return;
|
||||
_textures.Remove(tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Graphics.Wgl;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using WpfOpenGLSubwindowingClasses.Interop;
|
||||
using WpfOpenGLSubwindowingClasses.Renderer;
|
||||
using Wgl = WpfOpenGLSubwindowingClasses.Interop.Wgl;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.Controls
|
||||
{
|
||||
public class OpenGlHwndHost : HwndHost
|
||||
{
|
||||
private IntPtr _hwnd;
|
||||
private IntPtr _hdc;
|
||||
private IntPtr _hglrc;
|
||||
private bool _initializedBindings;
|
||||
private double _dpiX = 1.0, _dpiY = 1.0;
|
||||
|
||||
private static IntPtr s_sharedMasterCtx = IntPtr.Zero;
|
||||
|
||||
public OpenGlHwndHost() { } // for xaml
|
||||
|
||||
public OpenGLInstance Instance
|
||||
{
|
||||
get => (OpenGLInstance)GetValue(InstanceProperty);
|
||||
set => SetValue(InstanceProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty InstanceProperty =
|
||||
DependencyProperty.Register(nameof(Instance), typeof(OpenGLInstance),
|
||||
typeof(OpenGlHwndHost),
|
||||
new PropertyMetadata(null, OnInstanceChanged));
|
||||
|
||||
public bool ShareResourcesWithFirst
|
||||
{
|
||||
get => (bool)GetValue(ShareResourcesWithFirstProperty);
|
||||
set => SetValue(ShareResourcesWithFirstProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ShareResourcesWithFirstProperty =
|
||||
DependencyProperty.Register(nameof(ShareResourcesWithFirst), typeof(bool),
|
||||
typeof(OpenGlHwndHost), new PropertyMetadata(true));
|
||||
|
||||
public bool DebugContext { get; set; } = false;
|
||||
|
||||
const int WS_CHILD = 0x40000000;
|
||||
const int WS_VISIBLE = 0x10000000;
|
||||
|
||||
public OpenGlHwndHost(OpenGLInstance instance) : this()
|
||||
{
|
||||
Instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
|
||||
{
|
||||
var dpi = VisualTreeHelper.GetDpi(Application.Current.MainWindow);
|
||||
_dpiX = dpi.DpiScaleX;
|
||||
_dpiY = dpi.DpiScaleY;
|
||||
|
||||
_hwnd = Wgl.CreateWindowEx(0, "STATIC", "", WS_CHILD | WS_VISIBLE,
|
||||
0, 0, 1, 1,
|
||||
hwndParent.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
|
||||
|
||||
_hdc = Wgl.GetDC(_hwnd);
|
||||
if (_hdc == IntPtr.Zero) throw new InvalidOperationException("GetDC failed.");
|
||||
|
||||
if (!Wgl.ApplyDefaultPixelFormat(_hdc))
|
||||
throw new InvalidOperationException("SetPixelFormat failed.");
|
||||
|
||||
IntPtr share = (ShareResourcesWithFirst && s_sharedMasterCtx != IntPtr.Zero) ? s_sharedMasterCtx : IntPtr.Zero;
|
||||
|
||||
_hglrc = Wgl.CreateBestContext(_hdc, shareWith: share, debug: DebugContext, coreProfile: true);
|
||||
if (_hglrc == IntPtr.Zero)
|
||||
throw new InvalidOperationException("wglCreateContext/CreateBestContext failed.");
|
||||
|
||||
if (s_sharedMasterCtx == IntPtr.Zero && ShareResourcesWithFirst)
|
||||
s_sharedMasterCtx = _hglrc;
|
||||
|
||||
Wgl.wglMakeCurrent(_hdc, _hglrc);
|
||||
|
||||
if (!_initializedBindings)
|
||||
{
|
||||
GL.LoadBindings(new WglBindingsContext());
|
||||
_initializedBindings = true;
|
||||
}
|
||||
|
||||
var size = GetPixelSize();
|
||||
InstanceResize(size.width, size.height);
|
||||
|
||||
CompositionTarget.Rendering += OnRendering;
|
||||
|
||||
return new HandleRef(this, _hwnd);
|
||||
}
|
||||
|
||||
protected override void DestroyWindowCore(HandleRef hwnd)
|
||||
{
|
||||
CompositionTarget.Rendering -= OnRendering;
|
||||
|
||||
Wgl.wglMakeCurrent(IntPtr.Zero, IntPtr.Zero);
|
||||
|
||||
if (_hglrc != IntPtr.Zero)
|
||||
{
|
||||
Wgl.wglDeleteContext(_hglrc);
|
||||
_hglrc = IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (_hdc != IntPtr.Zero)
|
||||
{
|
||||
Wgl.ReleaseDC(_hwnd, _hdc);
|
||||
_hdc = IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (hwnd.Handle != IntPtr.Zero)
|
||||
{
|
||||
Wgl.DestroyWindow(hwnd.Handle);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Instance?.Dispose();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
|
||||
{
|
||||
base.OnRenderSizeChanged(sizeInfo);
|
||||
var size = GetPixelSize();
|
||||
InstanceResize(size.width, size.height);
|
||||
}
|
||||
|
||||
private void OnRendering(object sender, EventArgs e)
|
||||
{
|
||||
if (_hdc == IntPtr.Zero || _hglrc == IntPtr.Zero) return;
|
||||
|
||||
Wgl.wglMakeCurrent(_hdc, _hglrc);
|
||||
Instance.Render();
|
||||
Wgl.SwapBuffers(_hdc);
|
||||
Wgl.wglMakeCurrent(IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
|
||||
private (int width, int height) GetPixelSize()
|
||||
{
|
||||
int w = (int)Math.Max(1, Math.Round(ActualWidth * _dpiX));
|
||||
int h = (int)Math.Max(1, Math.Round(ActualHeight * _dpiY));
|
||||
return (w, h);
|
||||
}
|
||||
|
||||
private void InstanceResize(int pixelWidth, int pixelHeight)
|
||||
{
|
||||
try
|
||||
{
|
||||
Instance.EnqueueOnGlThread(() =>
|
||||
{
|
||||
Instance.Resize(pixelWidth, pixelHeight);
|
||||
});
|
||||
}
|
||||
catch { /* ignore */ }
|
||||
}
|
||||
|
||||
private static void OnInstanceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var host = (OpenGlHwndHost)d;
|
||||
if (host._hwnd != IntPtr.Zero && host.Instance != null)
|
||||
{
|
||||
var size = host.GetPixelSize();
|
||||
host.InstanceResize(size.width, size.height);
|
||||
}
|
||||
}
|
||||
|
||||
public IntPtr Hwnd => _hwnd;
|
||||
public IntPtr Hglrc => _hglrc;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
using Assimp;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Mathematics;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using WpfOpenGLSubwindowingClasses.Camera;
|
||||
using WpfOpenGLSubwindowingClasses.ModelHandling;
|
||||
using WpfOpenGLSubwindowingClasses.Shader;
|
||||
using PrimitiveType = OpenTK.Graphics.OpenGL4.PrimitiveType;
|
||||
using Quaternion = OpenTK.Mathematics.Quaternion;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.Renderer
|
||||
{
|
||||
public class OpenGLInstance(string modelVertexShaderPath, string modelFragmentShaderPath, string axesVertexShaderPath, string axesFragmentShaderPath)
|
||||
{
|
||||
private bool _initialized;
|
||||
private bool _disposeRequested;
|
||||
|
||||
private string _modelVertexShaderPath = modelVertexShaderPath;
|
||||
private string _modelFragmentShaderPath = modelFragmentShaderPath;
|
||||
private string _axesVertexShaderPath = axesVertexShaderPath;
|
||||
private string _axesFragmentShaderPath = axesFragmentShaderPath;
|
||||
|
||||
private Shader.Shader? _shader;
|
||||
private Shader.Shader? _colorShader;
|
||||
private AxisLines _axisLines = new();
|
||||
public bool DisplayAxisLines { get; set; } = false;
|
||||
|
||||
PrimitiveType? DrawModeOverride = null;
|
||||
|
||||
List<WpfOpenGLSubwindowingClasses.ModelHandling.Model> Models = new();
|
||||
|
||||
public CancellationToken CancellationToken { get; set; }
|
||||
public Color4 BackgroundColor { get; set; } = Color4.White;
|
||||
|
||||
readonly ConcurrentQueue<Action> _glQueue = new();
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (_initialized) return;
|
||||
|
||||
GL.ClearColor(.0f, .0f, .0f, .0f);
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
GL.Enable(EnableCap.Multisample);
|
||||
GL.Disable(EnableCap.CullFace);
|
||||
|
||||
// Shader
|
||||
_shader = new Shader.Shader(_modelVertexShaderPath, _modelFragmentShaderPath);
|
||||
_shader.Use();
|
||||
|
||||
_colorShader = new Shader.Shader(_axesVertexShaderPath, _axesFragmentShaderPath);
|
||||
_axisLines.Create(5f);
|
||||
|
||||
OrbitCamera.Initialize(new Vector3(-5, -3, 5), Vector3.UnitY);
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
public void EnqueueOnGlThread(Action a) => _glQueue.Enqueue(a);
|
||||
|
||||
public void Render()
|
||||
{
|
||||
if (_disposeRequested || CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_initialized) Initialize();
|
||||
if (_shader == null ||_colorShader == null) return;
|
||||
|
||||
while (_glQueue.TryDequeue(out var a)) a();
|
||||
|
||||
_shader.Use();
|
||||
|
||||
GL.Viewport(0, 0, OrbitCamera.Width, OrbitCamera.Height);
|
||||
|
||||
_shader.SetMatrix4("view", OrbitCamera.View);
|
||||
_shader.SetMatrix4("proj", OrbitCamera.Projection);
|
||||
_shader.SetVector3("camPos", OrbitCamera.Position);
|
||||
|
||||
_shader.SetVector4("lightColor", new Vector4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
|
||||
var lightPos = OrbitCamera.Position - OrbitCamera.Front * 20f;
|
||||
_shader.SetVector3("lightPos", lightPos);
|
||||
|
||||
GL.ClearColor(BackgroundColor);
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||
|
||||
foreach(var model in Models) model.Draw(_shader, DrawModeOverride);
|
||||
|
||||
if (DisplayAxisLines) _axisLines.Draw(_colorShader, OrbitCamera.View, OrbitCamera.Projection, Matrix4.Identity, 1f);
|
||||
}
|
||||
|
||||
public void SetPolygonMode(PrimitiveType type) => DrawModeOverride = type;
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_disposeRequested = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_initialized) return;
|
||||
|
||||
GL.BindVertexArray(0);
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
|
||||
|
||||
_shader?.Dispose();
|
||||
_shader = null;
|
||||
|
||||
_colorShader?.Dispose();
|
||||
_colorShader = null;
|
||||
|
||||
_axisLines?.Dispose();
|
||||
|
||||
foreach (var model in Models) model.Dispose();
|
||||
Models.Clear();
|
||||
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
public void Resize(int width, int height)
|
||||
{
|
||||
OrbitCamera.Width = width;
|
||||
OrbitCamera.Height = height;
|
||||
|
||||
float aspect = (width > 0 && height > 0) ? (float)width / height : 1f;
|
||||
float fovY = MathHelper.DegreesToRadians(OrbitCamera.Zoom);
|
||||
OrbitCamera.UpdateProjection(fovY, aspect);
|
||||
|
||||
}
|
||||
|
||||
public void LoadModel(string path, bool replaceWhenLoaded = false)
|
||||
{
|
||||
if (Models.Any(x => x.Path == path)) return;
|
||||
|
||||
var loader = new ModelLoader();
|
||||
WpfOpenGLSubwindowingClasses.ModelHandling.Model? model = null;
|
||||
try
|
||||
{
|
||||
model = loader.Load(path);
|
||||
}
|
||||
catch { return; }
|
||||
if (model is null) return;
|
||||
|
||||
model.CenterModelAroundVec();
|
||||
model.ComputeCenterOffset();
|
||||
model.ApplyUniformScale(ComputeScaleToTargetRadius(model, 2f));
|
||||
model.Rotation = Quaternion.FromAxisAngle(Vector3.UnitX, MathHelper.DegreesToRadians(-90f));
|
||||
|
||||
if (replaceWhenLoaded) ClearModels();
|
||||
|
||||
Models.Add(model);
|
||||
|
||||
EnqueueOnGlThread(() =>
|
||||
{
|
||||
foreach (var m in model.Meshes) m.CreateGpuBuffers();
|
||||
});
|
||||
|
||||
FocusCameraToModel(model);
|
||||
}
|
||||
|
||||
public async void LoadModelAsync(string path, bool replaceWhenLoaded = false)
|
||||
{
|
||||
if (Models.Any(x => x.Path == path)) return;
|
||||
|
||||
var loader = new ModelLoader();
|
||||
WpfOpenGLSubwindowingClasses.ModelHandling.Model? model;
|
||||
try
|
||||
{
|
||||
model = await Task.Run(() => loader.Load(path, loadTextures: false)); // load textures later on ui thread
|
||||
}
|
||||
catch { return; }
|
||||
if (model is null) return;
|
||||
|
||||
model.CenterModelAroundVec();
|
||||
model.ComputeCenterOffset();
|
||||
model.ApplyUniformScale(ComputeScaleToTargetRadius(model, 2f));
|
||||
model.Rotation = Quaternion.FromAxisAngle(Vector3.UnitX, MathHelper.DegreesToRadians(-90f));
|
||||
|
||||
if (replaceWhenLoaded) ClearModels();
|
||||
|
||||
Models.Add(model);
|
||||
|
||||
EnqueueOnGlThread(() =>
|
||||
{
|
||||
foreach (var m in model.Meshes)
|
||||
{
|
||||
m.CreateGpuBuffers();
|
||||
|
||||
// Load images on UI Thread so GL is not throwing errors
|
||||
if (!string.IsNullOrEmpty(m.DiffuseTexturePath) && m.DiffuseTextureHandle == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var diffuseTexture = Texture.LoadFromFile(m.DiffuseTexturePath);
|
||||
m.DiffuseTextureHandle = diffuseTexture.Handle;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error loading diffuse texture '{m.DiffuseTexturePath}': {ex.Message}");
|
||||
m.DiffuseTextureHandle = 0;
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(m.SpecularTexturePath) && m.SpecularTextureHandle == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var specularTexture = TextureManager.LoadTextureFromFile(m.SpecularTexturePath);
|
||||
m.SpecularTextureHandle = specularTexture.Handle;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error loading specular texture '{m.SpecularTexturePath}': {ex.Message}");
|
||||
m.SpecularTextureHandle = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
FocusCameraToModel(model);
|
||||
}
|
||||
|
||||
public void ClearModels()
|
||||
{
|
||||
foreach(var model in Models) model.Dispose();
|
||||
Models.Clear();
|
||||
}
|
||||
|
||||
static float ComputeScaleToTargetRadius(WpfOpenGLSubwindowingClasses.ModelHandling.Model model, float targetRadius)
|
||||
{
|
||||
var (min, max) = model.ComputeGlobalBounds();
|
||||
var center = (min + max) * 0.5f;
|
||||
float r = 0f;
|
||||
foreach (var m in model.Meshes)
|
||||
foreach (var v in m.Vertices)
|
||||
r = Math.Max(r, (v.Position - center).Length);
|
||||
return r > 0f ? targetRadius / r : 1f;
|
||||
}
|
||||
|
||||
public void FocusCameraToModel(WpfOpenGLSubwindowingClasses.ModelHandling.Model model)
|
||||
{
|
||||
var (min, max) = model.ComputeGlobalBounds();
|
||||
var center = (min + max) * 0.5f;
|
||||
|
||||
float r = 0f;
|
||||
foreach (var m in model.Meshes)
|
||||
foreach (var v in m.Vertices)
|
||||
r = Math.Max(r, (v.Position - center).Length);
|
||||
|
||||
OrbitCamera.Target = center;
|
||||
|
||||
float aspect = (OrbitCamera.Width > 0 && OrbitCamera.Height > 0) ? (float)OrbitCamera.Width / OrbitCamera.Height : 1f;
|
||||
float fovY = MathHelper.DegreesToRadians(OrbitCamera.Zoom);
|
||||
float fovX = 2f * MathF.Atan(MathF.Tan(fovY / 2f) * aspect);
|
||||
|
||||
float dY = r / MathF.Tan(fovY / 2f);
|
||||
float dX = r / MathF.Tan(fovX / 2f);
|
||||
float dist = MathF.Max(dX, dY) * 1.2f;
|
||||
|
||||
OrbitCamera.MinOrbitRadius = r * 1.05f;
|
||||
OrbitCamera.MaxOrbitRadius = r * 50f;
|
||||
OrbitCamera.OrbitRadius = Math.Clamp(dist, OrbitCamera.MinOrbitRadius, OrbitCamera.MaxOrbitRadius);
|
||||
|
||||
/* no override
|
||||
float near = Math.Max(0.05f, OrbitCamera.OrbitRadius - 2f * r);
|
||||
float far = OrbitCamera.OrbitRadius + 4f * r;
|
||||
near = Math.Max(0.01f, Math.Min(near, far * 0.5f));
|
||||
|
||||
OrbitCamera.Near = near;
|
||||
OrbitCamera.Far = far;
|
||||
*/
|
||||
|
||||
OrbitCamera.RecalculateCameraPositionAndVectors();
|
||||
OrbitCamera.UpdateProjection(fovY, aspect);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Mathematics;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.Shader
|
||||
{
|
||||
public class Shader : IDisposable
|
||||
{
|
||||
public int Program;
|
||||
private bool disposedValue = false;
|
||||
|
||||
public bool Logging { get; set; } = false;
|
||||
public bool LogToDebug { get; set; } = false;
|
||||
|
||||
public Shader(string vertexPath, string fragmentPath, bool logging = false, bool logToDebug = false)
|
||||
{
|
||||
Logging = logging;
|
||||
LogToDebug = logToDebug;
|
||||
|
||||
string VertexShaderSource = File.ReadAllText(vertexPath);
|
||||
string FragmentShaderSource = File.ReadAllText(fragmentPath);
|
||||
|
||||
(int VertexShader, int FragmentShader) = GenerateAndBindShaders(VertexShaderSource, FragmentShaderSource);
|
||||
|
||||
if (CompileShaders(VertexShader, FragmentShader) == 0) throw new Exception("Failed to compile shaders");
|
||||
if (LinkShadersToProgram(VertexShader, FragmentShader) == 0) throw new Exception("Failed to link Shaders to Program");
|
||||
|
||||
Cleanup(VertexShader, FragmentShader);
|
||||
}
|
||||
|
||||
public void SetMatrix4(string name, Matrix4 value) => GL.UniformMatrix4(GL.GetUniformLocation(Program, name), false, ref value);
|
||||
public void SetVector3(string name, Vector3 value) => GL.Uniform3(GL.GetUniformLocation(Program, name), value);
|
||||
public void SetVector4(string name, Vector4 value) => GL.Uniform4(GL.GetUniformLocation(Program, name), value);
|
||||
public void SetColor4(string name, Color4 value) => GL.Uniform4(GL.GetUniformLocation(Program, name), value);
|
||||
public void SetFloat(string name, float value) => GL.Uniform1(GL.GetUniformLocation(Program, name), value);
|
||||
public void SetInt(string name, int value) => GL.Uniform1(GL.GetUniformLocation(Program, name), value);
|
||||
public void SetBool(string name, bool value) => GL.Uniform1(GL.GetUniformLocation(Program, name), value ? 1 : 0);
|
||||
|
||||
private (int, int) GenerateAndBindShaders(string VertexShaderSource, string FragmentShaderSource)
|
||||
{
|
||||
int VertexShader, FragmentShader;
|
||||
|
||||
// Generate and Bind
|
||||
VertexShader = GL.CreateShader(ShaderType.VertexShader);
|
||||
GL.ShaderSource(VertexShader, VertexShaderSource);
|
||||
|
||||
FragmentShader = GL.CreateShader(ShaderType.FragmentShader);
|
||||
GL.ShaderSource(FragmentShader, FragmentShaderSource);
|
||||
|
||||
return (VertexShader, FragmentShader);
|
||||
}
|
||||
|
||||
private int CompileShaders(int VertexShader, int FragmentShader)
|
||||
{
|
||||
// Compile
|
||||
GL.CompileShader(VertexShader);
|
||||
|
||||
GL.GetShader(VertexShader, ShaderParameter.CompileStatus, out int success1);
|
||||
if (success1 == 0)
|
||||
{
|
||||
string infoLog = GL.GetShaderInfoLog(VertexShader);
|
||||
Log(infoLog);
|
||||
return 0;
|
||||
}
|
||||
|
||||
GL.CompileShader(FragmentShader);
|
||||
|
||||
GL.GetShader(FragmentShader, ShaderParameter.CompileStatus, out int success2);
|
||||
if (success2 == 0)
|
||||
{
|
||||
string infoLog = GL.GetShaderInfoLog(FragmentShader);
|
||||
Log(infoLog);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private int LinkShadersToProgram(int VertexShader, int FragmentShader)
|
||||
{
|
||||
Program = GL.CreateProgram();
|
||||
|
||||
GL.AttachShader(Program, VertexShader);
|
||||
GL.AttachShader(Program, FragmentShader);
|
||||
|
||||
GL.LinkProgram(Program);
|
||||
|
||||
GL.GetProgram(Program, GetProgramParameterName.LinkStatus, out int success);
|
||||
if (success == 0)
|
||||
{
|
||||
string infoLog = GL.GetProgramInfoLog(Program);
|
||||
Log(infoLog);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private void Cleanup(int VertexShader, int FragmentShader)
|
||||
{
|
||||
GL.DetachShader(Program, VertexShader);
|
||||
GL.DetachShader(Program, FragmentShader);
|
||||
GL.DeleteShader(FragmentShader);
|
||||
GL.DeleteShader(VertexShader);
|
||||
}
|
||||
|
||||
public void Use() => GL.UseProgram(Program);
|
||||
|
||||
public virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
GL.DeleteProgram(Program);
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
~Shader()
|
||||
{
|
||||
if (disposedValue == false)
|
||||
{
|
||||
Log("GPU Resource leak! Did you forget to call Dispose()?");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
void Log(string message)
|
||||
{
|
||||
if (Logging && !LogToDebug) Console.WriteLine(message);
|
||||
else if (Logging && LogToDebug) Debug.WriteLine(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
#version 410 core
|
||||
|
||||
in DATA {
|
||||
vec3 Normal;
|
||||
vec3 color;
|
||||
vec2 texCoord;
|
||||
vec3 WorldPos;
|
||||
} data_in;
|
||||
|
||||
vec2 texCoord = data_in.texCoord;
|
||||
vec3 Normal = data_in.Normal;
|
||||
vec3 crntPos = data_in.WorldPos;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
uniform sampler2D diffuse0;
|
||||
uniform sampler2D specular0;
|
||||
|
||||
uniform vec4 material_ambient;
|
||||
uniform vec4 material_diffuse;
|
||||
uniform vec4 material_specular;
|
||||
uniform float material_shininess;
|
||||
|
||||
uniform bool hasDiffuseTexture;
|
||||
uniform bool hasSpecularTexture;
|
||||
|
||||
uniform vec4 lightColor;
|
||||
uniform vec3 lightPos;
|
||||
uniform vec3 camPos;
|
||||
|
||||
vec4 directLight()
|
||||
{
|
||||
vec3 final_ambient_color;
|
||||
vec3 final_diffuse_color;
|
||||
vec3 final_specular_color;
|
||||
|
||||
if(hasDiffuseTexture){
|
||||
final_diffuse_color = texture(diffuse0, texCoord).rgb;
|
||||
final_ambient_color = material_ambient.rgb;
|
||||
}
|
||||
else{
|
||||
final_ambient_color = material_ambient.rgb;
|
||||
final_diffuse_color = material_diffuse.rgb;
|
||||
}
|
||||
|
||||
if(hasSpecularTexture){
|
||||
final_specular_color = texture(specular0, texCoord).rgb;
|
||||
}
|
||||
else {
|
||||
final_specular_color = material_specular.rgb;
|
||||
}
|
||||
|
||||
// ambient lighting
|
||||
float ambientStrength = 0.20f;
|
||||
vec3 ambient = ambientStrength * final_ambient_color;
|
||||
|
||||
// diffuse lighting
|
||||
vec3 normal = normalize(Normal);
|
||||
vec3 lightDirection = normalize(lightPos - crntPos);
|
||||
float diff = max(dot(normal, lightDirection), 0.0f);
|
||||
vec3 diffuse = diff * final_diffuse_color;
|
||||
|
||||
// specular lighting
|
||||
vec3 specular = vec3(0.0f);
|
||||
if(diff > 0.0f){
|
||||
float specularStrength = 0.50f;
|
||||
vec3 viewDirection = normalize(camPos - crntPos);
|
||||
vec3 reflectionDirection = reflect(-lightDirection, normal);
|
||||
|
||||
// Blinn-Phong Model (Halway Vector)
|
||||
vec3 halfwayDir = normalize(lightDirection + viewDirection);
|
||||
float spec = pow(max(dot(normal, halfwayDir), 0.0f), material_shininess);
|
||||
specular = specularStrength * spec * final_specular_color;
|
||||
}
|
||||
|
||||
return vec4(ambient + diffuse + specular, 1.0f) * lightColor;
|
||||
}
|
||||
|
||||
vec4 noLight()
|
||||
{
|
||||
vec3 final_color;
|
||||
|
||||
if(hasDiffuseTexture){
|
||||
final_color = texture(diffuse0, texCoord).rgb;
|
||||
}
|
||||
else{
|
||||
final_color = material_diffuse.rgb;
|
||||
}
|
||||
|
||||
return vec4(final_color, 1.0f); // Rückgabe der Farbe mit voller Deckkraft
|
||||
}
|
||||
|
||||
void main(){
|
||||
outColor = directLight();
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#version 410 core
|
||||
|
||||
layout (location = 0) in vec3 aPos;
|
||||
layout (location = 1) in vec3 aNormal;
|
||||
layout (location = 2) in vec3 aColor;
|
||||
layout (location = 3) in vec2 aTexCoord;
|
||||
|
||||
out DATA {
|
||||
vec3 Normal;
|
||||
vec3 color;
|
||||
vec2 texCoord;
|
||||
vec3 WorldPos;
|
||||
} data_out;
|
||||
|
||||
uniform mat4 model;
|
||||
// uniform mat4 camMatrix;
|
||||
uniform mat4 view;
|
||||
uniform mat4 proj;
|
||||
uniform mat4 translation;
|
||||
uniform mat4 rotation;
|
||||
uniform mat4 scale;
|
||||
|
||||
void main() {
|
||||
mat4 M = translation * rotation * scale * model;
|
||||
vec4 worldPos = M * vec4(aPos, 1.0);
|
||||
gl_Position = proj * view * worldPos;
|
||||
data_out.WorldPos = worldPos.xyz;
|
||||
data_out.Normal = mat3(transpose(inverse(M))) * aNormal;
|
||||
data_out.color = aColor;
|
||||
data_out.texCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#version 410 core
|
||||
|
||||
in vec3 vColor;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
void main(){
|
||||
FragColor = vec4(vColor, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#version 410 core
|
||||
|
||||
layout(location=0) in vec3 aPos;
|
||||
layout(location=1) in vec3 aColor;
|
||||
|
||||
uniform mat4 view;
|
||||
uniform mat4 proj;
|
||||
uniform mat4 model;
|
||||
|
||||
out vec3 vColor;
|
||||
|
||||
void main(){
|
||||
vColor = aColor;
|
||||
gl_Position = proj * view * model * vec4(aPos,1.0);
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.Interop
|
||||
{
|
||||
internal static class Wgl
|
||||
{
|
||||
// PIXELFORMATDESCRIPTOR
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct PIXELFORMATDESCRIPTOR
|
||||
{
|
||||
public ushort nSize;
|
||||
public ushort nVersion;
|
||||
public uint dwFlags;
|
||||
public byte iPixelType;
|
||||
public byte cColorBits;
|
||||
public byte cRedBits;
|
||||
public byte cRedShift;
|
||||
public byte cGreenBits;
|
||||
public byte cGreenShift;
|
||||
public byte cBlueBits;
|
||||
public byte cBlueShift;
|
||||
public byte cAlphaBits;
|
||||
public byte cAlphaShift;
|
||||
public byte cAccumBits;
|
||||
public byte cAccumRedBits;
|
||||
public byte cAccumGreenBits;
|
||||
public byte cAccumBlueBits;
|
||||
public byte cAccumAlphaBits;
|
||||
public byte cDepthBits;
|
||||
public byte cStencilBits;
|
||||
public byte cAuxBuffers;
|
||||
public byte iLayerType;
|
||||
public byte bReserved;
|
||||
public uint dwLayerMask;
|
||||
public uint dwVisibleMask;
|
||||
public uint dwDamageMask;
|
||||
}
|
||||
|
||||
// PFD Flags
|
||||
public const uint PFD_DRAW_TO_WINDOW = 0x00000004;
|
||||
public const uint PFD_SUPPORT_OPENGL = 0x00000020;
|
||||
public const uint PFD_DOUBLEBUFFER = 0x00000001;
|
||||
public const byte PFD_TYPE_RGBA = 0;
|
||||
public const byte PFD_MAIN_PLANE = 0;
|
||||
|
||||
// Window Styles
|
||||
public const int WS_CHILD = 0x40000000;
|
||||
public const int WS_VISIBLE = 0x10000000;
|
||||
public const int WS_CLIPSIBLINGS = 0x04000000;
|
||||
public const int WS_CLIPCHILDREN = 0x02000000;
|
||||
|
||||
// WGL_ARB_create_context constants
|
||||
public const int WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
|
||||
public const int WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092;
|
||||
public const int WGL_CONTEXT_FLAGS_ARB = 0x2094;
|
||||
public const int WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126;
|
||||
public const int WGL_CONTEXT_CORE_PROFILE_BIT_ARB = 0x0001;
|
||||
public const int WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x0002;
|
||||
public const int WGL_CONTEXT_DEBUG_BIT_ARB = 0x0001;
|
||||
public const int WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x0002;
|
||||
|
||||
// Win32/GDI
|
||||
[DllImport("user32.dll")] public static extern IntPtr GetDC(IntPtr hWnd);
|
||||
[DllImport("user32.dll")] public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
|
||||
[DllImport("gdi32.dll")] public static extern int ChoosePixelFormat(IntPtr hdc, ref PIXELFORMATDESCRIPTOR pfd);
|
||||
[DllImport("gdi32.dll")] public static extern bool SetPixelFormat(IntPtr hdc, int format, ref PIXELFORMATDESCRIPTOR pfd);
|
||||
[DllImport("gdi32.dll")] public static extern bool SwapBuffers(IntPtr hdc);
|
||||
|
||||
// WGL core
|
||||
[DllImport("opengl32.dll")] public static extern IntPtr wglCreateContext(IntPtr hdc);
|
||||
[DllImport("opengl32.dll")] public static extern bool wglMakeCurrent(IntPtr hdc, IntPtr hglrc);
|
||||
[DllImport("opengl32.dll")] public static extern bool wglDeleteContext(IntPtr hglrc);
|
||||
[DllImport("opengl32.dll")] public static extern bool wglShareLists(IntPtr hglrcSrc, IntPtr hglrcDst);
|
||||
[DllImport("opengl32.dll")] public static extern IntPtr wglGetProcAddress(string name);
|
||||
|
||||
// Win32 window creation
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
public static extern IntPtr CreateWindowEx(
|
||||
int exStyle,
|
||||
string className,
|
||||
string windowName,
|
||||
int style,
|
||||
int x, int y, int width, int height,
|
||||
IntPtr parent, IntPtr menu, IntPtr instance, IntPtr param);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool DestroyWindow(IntPtr hwnd);
|
||||
|
||||
// Kernel32 for function address fallback
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
|
||||
public static extern IntPtr LoadLibrary(string lpFileName);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true)]
|
||||
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
|
||||
|
||||
// Convenience: Make/Done Current
|
||||
public static bool MakeCurrent(IntPtr hdc, IntPtr hglrc) => wglMakeCurrent(hdc, hglrc);
|
||||
public static void DoneCurrent() => wglMakeCurrent(IntPtr.Zero, IntPtr.Zero);
|
||||
public static bool DeleteContext(IntPtr hglrc) => wglDeleteContext(hglrc);
|
||||
|
||||
// Set a reasonable default PixelFormat (RGBA, double-buffer, depth/stencil)
|
||||
public static bool ApplyDefaultPixelFormat(IntPtr hdc, int colorBits = 24, int depthBits = 24, int stencilBits = 8)
|
||||
{
|
||||
var pfd = new PIXELFORMATDESCRIPTOR
|
||||
{
|
||||
nSize = (ushort)Marshal.SizeOf<PIXELFORMATDESCRIPTOR>(),
|
||||
nVersion = 1,
|
||||
dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
|
||||
iPixelType = PFD_TYPE_RGBA,
|
||||
cColorBits = (byte)colorBits,
|
||||
cDepthBits = (byte)depthBits,
|
||||
cStencilBits = (byte)stencilBits,
|
||||
iLayerType = PFD_MAIN_PLANE
|
||||
};
|
||||
|
||||
int format = ChoosePixelFormat(hdc, ref pfd);
|
||||
if (format == 0) return false;
|
||||
return SetPixelFormat(hdc, format, ref pfd);
|
||||
}
|
||||
|
||||
// Delegates for WGL extensions
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
private delegate IntPtr wglCreateContextAttribsARB_t(IntPtr hdc, IntPtr hShareContext, int[] attribList);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
private delegate bool wglSwapIntervalEXT_t(int interval);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
private delegate int wglGetSwapIntervalEXT_t();
|
||||
|
||||
private static wglSwapIntervalEXT_t? s_swapIntervalEXT;
|
||||
private static wglGetSwapIntervalEXT_t? s_getSwapIntervalEXT;
|
||||
|
||||
private static void EnsureSwapIntervalDelegates()
|
||||
{
|
||||
if (s_swapIntervalEXT == null)
|
||||
{
|
||||
var p = wglGetProcAddress("wglSwapIntervalEXT");
|
||||
if (p != IntPtr.Zero)
|
||||
s_swapIntervalEXT = (wglSwapIntervalEXT_t)Marshal.GetDelegateForFunctionPointer(p, typeof(wglSwapIntervalEXT_t));
|
||||
}
|
||||
if (s_getSwapIntervalEXT == null)
|
||||
{
|
||||
var p = wglGetProcAddress("wglGetSwapIntervalEXT");
|
||||
if (p != IntPtr.Zero)
|
||||
s_getSwapIntervalEXT = (wglGetSwapIntervalEXT_t)Marshal.GetDelegateForFunctionPointer(p, typeof(wglGetSwapIntervalEXT_t));
|
||||
}
|
||||
}
|
||||
|
||||
// Try to set VSync (interval 0=off, 1=on)
|
||||
public static bool TrySetSwapInterval(int interval)
|
||||
{
|
||||
EnsureSwapIntervalDelegates();
|
||||
return s_swapIntervalEXT != null && s_swapIntervalEXT(interval);
|
||||
}
|
||||
|
||||
// Get current swap interval (-1 if unavailable)
|
||||
public static int GetSwapInterval()
|
||||
{
|
||||
EnsureSwapIntervalDelegates();
|
||||
return s_getSwapIntervalEXT != null ? s_getSwapIntervalEXT() : -1;
|
||||
}
|
||||
|
||||
// Create a "best" OpenGL context:
|
||||
// - tries wglCreateContextAttribsARB for core/modern versions
|
||||
// - falls back to legacy if needed
|
||||
// - optionally shares resources with 'shareWith' via wglShareLists
|
||||
public static IntPtr CreateBestContext(IntPtr hdc, IntPtr shareWith = default, bool debug = false, bool coreProfile = true)
|
||||
{
|
||||
// Create temp legacy context to load the ARB function (requires current context)
|
||||
IntPtr temp = wglCreateContext(hdc);
|
||||
if (temp != IntPtr.Zero) wglMakeCurrent(hdc, temp);
|
||||
|
||||
IntPtr proc = wglGetProcAddress("wglCreateContextAttribsARB");
|
||||
IntPtr realCtx = IntPtr.Zero;
|
||||
|
||||
if (proc != IntPtr.Zero)
|
||||
{
|
||||
var createAttribs = (wglCreateContextAttribsARB_t)Marshal.GetDelegateForFunctionPointer(proc, typeof(wglCreateContextAttribsARB_t));
|
||||
int flags = 0;
|
||||
if (debug) flags |= WGL_CONTEXT_DEBUG_BIT_ARB;
|
||||
if (coreProfile) flags |= WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB;
|
||||
|
||||
// Try descending versions
|
||||
int[][] versions =
|
||||
{
|
||||
new[]{4,6}, new[]{4,5}, new[]{4,4}, new[]{4,3}, new[]{4,2}, new[]{4,1}, new[]{4,0},
|
||||
new[]{3,3}, new[]{3,2}
|
||||
};
|
||||
|
||||
foreach (var v in versions)
|
||||
{
|
||||
int[] attribs =
|
||||
{
|
||||
WGL_CONTEXT_MAJOR_VERSION_ARB, v[0],
|
||||
WGL_CONTEXT_MINOR_VERSION_ARB, v[1],
|
||||
WGL_CONTEXT_FLAGS_ARB, flags,
|
||||
WGL_CONTEXT_PROFILE_MASK_ARB, coreProfile ? WGL_CONTEXT_CORE_PROFILE_BIT_ARB : WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
|
||||
0
|
||||
};
|
||||
realCtx = createAttribs(hdc, shareWith, attribs);
|
||||
if (realCtx != IntPtr.Zero) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to legacy
|
||||
if (realCtx == IntPtr.Zero)
|
||||
{
|
||||
realCtx = wglCreateContext(hdc);
|
||||
if (realCtx == IntPtr.Zero) return IntPtr.Zero;
|
||||
if (shareWith != IntPtr.Zero)
|
||||
wglShareLists(shareWith, realCtx);
|
||||
}
|
||||
|
||||
// Cleanup temp
|
||||
if (temp != IntPtr.Zero)
|
||||
{
|
||||
wglMakeCurrent(IntPtr.Zero, IntPtr.Zero);
|
||||
wglDeleteContext(temp);
|
||||
}
|
||||
|
||||
return realCtx;
|
||||
}
|
||||
|
||||
// Helper to create a child window using the built-in "STATIC" class.
|
||||
// This is suitable as a render target HWND for OpenGL.
|
||||
public static IntPtr CreateChildWindow(IntPtr parent, int x, int y, int width, int height, bool visible = true)
|
||||
{
|
||||
int style = WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | (visible ? WS_VISIBLE : 0);
|
||||
return CreateWindowEx(
|
||||
0,
|
||||
"STATIC",
|
||||
"",
|
||||
style,
|
||||
x, y, width, height,
|
||||
parent, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics.Wgl;
|
||||
using System;
|
||||
using WpfOpenGLSubwindowingClasses.Interop;
|
||||
|
||||
namespace WpfOpenGLSubwindowingClasses.Interop
|
||||
{
|
||||
internal sealed class WglBindingsContext : IBindingsContext
|
||||
{
|
||||
private readonly IntPtr _opengl32 = Wgl.LoadLibrary("opengl32.dll");
|
||||
|
||||
public IntPtr GetProcAddress(string procName)
|
||||
{
|
||||
var ptr = Wgl.wglGetProcAddress(procName);
|
||||
if (ptr == IntPtr.Zero && _opengl32 != IntPtr.Zero)
|
||||
ptr = Wgl.GetProcAddress(_opengl32, procName);
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Shaders\BasicShader.frag" />
|
||||
<None Remove="Shaders\BasicShader.vert" />
|
||||
<None Remove="Shaders\ColorShader.frag" />
|
||||
<None Remove="Shaders\ColorShader.vert" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Shaders\BasicShader.frag">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Shaders\BasicShader.vert">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Shaders\ColorShader.frag">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Shaders\ColorShader.vert">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AssimpNetter" Version="6.0.2.1" />
|
||||
<PackageReference Include="OpenTK" Version="4.9.4" />
|
||||
<PackageReference Include="StbImageSharp" Version="2.30.15" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user