diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8e930f5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,321 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# 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 + diff --git a/RendererDirect3D12/RendererDirect3D12.cpp b/RendererDirect3D12/RendererDirect3D12.cpp new file mode 100644 index 0000000..e95862c --- /dev/null +++ b/RendererDirect3D12/RendererDirect3D12.cpp @@ -0,0 +1,6 @@ +// RendererDirect3D12.cpp : Defines the exported functions for the DLL application. +// + +#include "stdafx.h" + + diff --git a/RendererDirect3D12/RendererDirect3D12.vcxproj b/RendererDirect3D12/RendererDirect3D12.vcxproj new file mode 100644 index 0000000..0d3c9a2 --- /dev/null +++ b/RendererDirect3D12/RendererDirect3D12.vcxproj @@ -0,0 +1,99 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {53923514-84B2-4B78-889A-8709C6BFA3A5} + Win32Proj + RendererDirect3D12 + 10.0.17763.0 + + + + DynamicLibrary + true + v141 + Unicode + + + DynamicLibrary + false + v141 + true + Unicode + + + + + + + + + + + + + + + true + + + false + + + + Use + Level3 + Disabled + true + _DEBUG;RENDERERDIRECT3D12_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;RENDERERDIRECT3D12_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + + + + + + + + + Create + Create + + + + + + \ No newline at end of file diff --git a/RendererDirect3D12/RendererDirect3D12.vcxproj.filters b/RendererDirect3D12/RendererDirect3D12.vcxproj.filters new file mode 100644 index 0000000..2c8dcf7 --- /dev/null +++ b/RendererDirect3D12/RendererDirect3D12.vcxproj.filters @@ -0,0 +1,36 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/RendererDirect3D12/dllmain.cpp b/RendererDirect3D12/dllmain.cpp new file mode 100644 index 0000000..465ae72 --- /dev/null +++ b/RendererDirect3D12/dllmain.cpp @@ -0,0 +1,19 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "stdafx.h" + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/RendererDirect3D12/stdafx.cpp b/RendererDirect3D12/stdafx.cpp new file mode 100644 index 0000000..fd4f341 --- /dev/null +++ b/RendererDirect3D12/stdafx.cpp @@ -0,0 +1 @@ +#include "stdafx.h" diff --git a/RendererDirect3D12/stdafx.h b/RendererDirect3D12/stdafx.h new file mode 100644 index 0000000..f380517 --- /dev/null +++ b/RendererDirect3D12/stdafx.h @@ -0,0 +1,16 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include + + + +// reference additional headers your program requires here diff --git a/RendererDirect3D12/targetver.h b/RendererDirect3D12/targetver.h new file mode 100644 index 0000000..87c0086 --- /dev/null +++ b/RendererDirect3D12/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/RendererVulkan/RendererVulkan.cpp b/RendererVulkan/RendererVulkan.cpp new file mode 100644 index 0000000..ca9230f --- /dev/null +++ b/RendererVulkan/RendererVulkan.cpp @@ -0,0 +1,6 @@ +// RendererVulkan.cpp : Defines the exported functions for the DLL application. +// + +#include "stdafx.h" + + diff --git a/RendererVulkan/RendererVulkan.vcxproj b/RendererVulkan/RendererVulkan.vcxproj new file mode 100644 index 0000000..1eb722a --- /dev/null +++ b/RendererVulkan/RendererVulkan.vcxproj @@ -0,0 +1,99 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {C9195A1C-9224-4B40-BBBC-AA90EF3BE3E0} + Win32Proj + RendererVulkan + 10.0.17763.0 + + + + DynamicLibrary + true + v141 + Unicode + + + DynamicLibrary + false + v141 + true + Unicode + + + + + + + + + + + + + + + true + + + false + + + + Use + Level3 + Disabled + true + _DEBUG;RENDERERVULKAN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;RENDERERVULKAN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + + + + + + + + + Create + Create + + + + + + \ No newline at end of file diff --git a/RendererVulkan/RendererVulkan.vcxproj.filters b/RendererVulkan/RendererVulkan.vcxproj.filters new file mode 100644 index 0000000..2356819 --- /dev/null +++ b/RendererVulkan/RendererVulkan.vcxproj.filters @@ -0,0 +1,36 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/RendererVulkan/dllmain.cpp b/RendererVulkan/dllmain.cpp new file mode 100644 index 0000000..465ae72 --- /dev/null +++ b/RendererVulkan/dllmain.cpp @@ -0,0 +1,19 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "stdafx.h" + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/RendererVulkan/stdafx.cpp b/RendererVulkan/stdafx.cpp new file mode 100644 index 0000000..fd4f341 --- /dev/null +++ b/RendererVulkan/stdafx.cpp @@ -0,0 +1 @@ +#include "stdafx.h" diff --git a/RendererVulkan/stdafx.h b/RendererVulkan/stdafx.h new file mode 100644 index 0000000..f380517 --- /dev/null +++ b/RendererVulkan/stdafx.h @@ -0,0 +1,16 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include + + + +// reference additional headers your program requires here diff --git a/RendererVulkan/targetver.h b/RendererVulkan/targetver.h new file mode 100644 index 0000000..87c0086 --- /dev/null +++ b/RendererVulkan/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/Verus.sln b/Verus.sln new file mode 100644 index 0000000..7a03404 --- /dev/null +++ b/Verus.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2036 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Verus", "Verus\Verus.vcxproj", "{B154D670-E4B1-4D8A-885C-69546A5BD833}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VerusTest", "VerusTest\VerusTest.vcxproj", "{D7085182-35B9-4689-ADBA-B77087AD16BF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererDirect3D12", "RendererDirect3D12\RendererDirect3D12.vcxproj", "{53923514-84B2-4B78-889A-8709C6BFA3A5}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererVulkan", "RendererVulkan\RendererVulkan.vcxproj", "{C9195A1C-9224-4B40-BBBC-AA90EF3BE3E0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B154D670-E4B1-4D8A-885C-69546A5BD833}.Debug|x64.ActiveCfg = Debug|x64 + {B154D670-E4B1-4D8A-885C-69546A5BD833}.Debug|x64.Build.0 = Debug|x64 + {B154D670-E4B1-4D8A-885C-69546A5BD833}.Release|x64.ActiveCfg = Release|x64 + {B154D670-E4B1-4D8A-885C-69546A5BD833}.Release|x64.Build.0 = Release|x64 + {D7085182-35B9-4689-ADBA-B77087AD16BF}.Debug|x64.ActiveCfg = Debug|x64 + {D7085182-35B9-4689-ADBA-B77087AD16BF}.Debug|x64.Build.0 = Debug|x64 + {D7085182-35B9-4689-ADBA-B77087AD16BF}.Release|x64.ActiveCfg = Release|x64 + {D7085182-35B9-4689-ADBA-B77087AD16BF}.Release|x64.Build.0 = Release|x64 + {53923514-84B2-4B78-889A-8709C6BFA3A5}.Debug|x64.ActiveCfg = Debug|x64 + {53923514-84B2-4B78-889A-8709C6BFA3A5}.Debug|x64.Build.0 = Debug|x64 + {53923514-84B2-4B78-889A-8709C6BFA3A5}.Release|x64.ActiveCfg = Release|x64 + {53923514-84B2-4B78-889A-8709C6BFA3A5}.Release|x64.Build.0 = Release|x64 + {C9195A1C-9224-4B40-BBBC-AA90EF3BE3E0}.Debug|x64.ActiveCfg = Debug|x64 + {C9195A1C-9224-4B40-BBBC-AA90EF3BE3E0}.Debug|x64.Build.0 = Debug|x64 + {C9195A1C-9224-4B40-BBBC-AA90EF3BE3E0}.Release|x64.ActiveCfg = Release|x64 + {C9195A1C-9224-4B40-BBBC-AA90EF3BE3E0}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0D2FFAEF-1E59-46F0-BDDF-1634F6790F83} + EndGlobalSection +EndGlobal diff --git a/Verus/Verus.props b/Verus/Verus.props new file mode 100644 index 0000000..e3afd6f --- /dev/null +++ b/Verus/Verus.props @@ -0,0 +1,11 @@ + + + + + + C:\Home\Projects\Verus\verus\Verus\src;C:\Home\Middleware\bullet3-2.88\src;C:\Home\Middleware\bullet3-2.88\Extras;C:\Home\Middleware\glm-0.9.9.3\glm;C:\Home\Middleware\json-3.5.0;C:\Home\Middleware\libogg-1.3.3\include;C:\Home\Middleware\libvorbis-1.3.6\include;C:\Home\Middleware\SDL2-2.0.9\include;C:\Home\Middleware\tinyxml2-7.0.1;C:\Home\Middleware\vectormath;C:\Home\Middleware\zlib-1.2.11;C:\Program Files %28x86%29\OpenAL 1.1 SDK\include;$(IncludePath) + C:\Home\Middleware\bullet3-2.88\lib\Debug;C:\Home\Middleware\libogg-1.3.3\lib;C:\Home\Middleware\libvorbis-1.3.6\lib2;C:\Home\Middleware\SDL2-2.0.9\lib\x64;C:\Home\Middleware\tinyxml2-7.0.1;C:\Home\Middleware\zlib-1.2.11;C:\Program Files %28x86%29\OpenAL 1.1 SDK\libs\Win64;$(LibraryPath) + + + + \ No newline at end of file diff --git a/Verus/Verus.vcxproj b/Verus/Verus.vcxproj new file mode 100644 index 0000000..e63f8f2 --- /dev/null +++ b/Verus/Verus.vcxproj @@ -0,0 +1,275 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {B154D670-E4B1-4D8A-885C-69546A5BD833} + Win32Proj + Verus + 10.0.16299.0 + + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + true + + + false + + + + Use + Level3 + Disabled + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + verus.h + $(ProjectDir)src;%(AdditionalIncludeDirectories) + + + Windows + true + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + verus.h + $(ProjectDir)src;%(AdditionalIncludeDirectories) + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NotUsing + NotUsing + 4244 + 4244 + + + NotUsing + NotUsing + 4244 + 4244 + + + NotUsing + NotUsing + 4267 + 4267 + _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + + + Create + Create + + + + + + \ No newline at end of file diff --git a/Verus/Verus.vcxproj.filters b/Verus/Verus.vcxproj.filters new file mode 100644 index 0000000..ffd24eb --- /dev/null +++ b/Verus/Verus.vcxproj.filters @@ -0,0 +1,549 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {6dd7f6ca-0929-4d46-9f6d-71ecf8c91abd} + + + {f756df7f-0ce7-4098-895a-2c1d03fe386b} + + + {afbabad8-439c-4609-b622-a881c8d6d034} + + + {9c9fdfe5-4d92-491c-bff1-7442a538ac24} + + + {d5e876a9-20a7-460a-89e4-c6b1cd902307} + + + {d44bf87b-c74b-42a1-9b54-dfe744447364} + + + {ebb05836-e3a7-452d-8e05-a9007ffe1320} + + + {5ece1220-9707-4688-9429-dcde547cb05f} + + + {bcb0dace-45fa-45b5-85d6-c80026a9577b} + + + {b02345a2-66ea-4995-8a5b-b7b6df5e4470} + + + {779ebfcc-422d-4f9a-a3de-3d5d416612a0} + + + {9026283b-bf5f-489c-9008-4093adeb4ce6} + + + {2c793d5f-0a67-4166-8490-3ef154bdb4fc} + + + {28848bdf-d48d-40a2-92a2-6946c609e14e} + + + {6c75fda0-ca2a-4925-9260-1d069a48b1b7} + + + {35c65d58-6412-4aa5-bb70-9f3ab9ee37a2} + + + {b10e474d-ad34-4fb3-9a41-875a17a54d36} + + + {8487cc8d-90f6-4f48-83f8-433947c79c5c} + + + + + src\CGI + + + src\CGI + + + src\CGI + + + src\CGI + + + src\CGI + + + src\IO + + + src\IO + + + src\IO + + + src + + + src\D + + + src\D + + + src\D + + + src\D + + + src\D + + + src\D + + + src\Security + + + src\Security + + + src\Global + + + src\Global + + + src\Global + + + src\Global + + + src\Global + + + src\Global + + + src\Global + + + src\Global + + + src\Global + + + src\Global + + + src\Global + + + src\Global + + + src\Global + + + src\Global + + + src\Global + + + src\Math + + + src\Game + + + src\Game + + + src\Game + + + src\Global + + + src\Global + + + src\Net + + + src\Net + + + src\Net + + + src\Global + + + src\Audio + + + src\Audio + + + src\Audio + + + src\Math + + + src\Math + + + src\Math + + + src\Global + + + src\IO + + + src\Global + + + src\Audio + + + src\Global + + + src\Audio + + + src\Global + + + src\Audio + + + src\ThirdParty + + + src\ThirdParty + + + src\ThirdParty\utf8 + + + src\ThirdParty\utf8 + + + src\ThirdParty\utf8 + + + src\Global + + + src\ThirdParty\libb64 + + + src\ThirdParty\libb64 + + + src\ThirdParty + + + src\IO + + + src\IO + + + src\Global + + + src\Scene + + + src\Scene + + + src\Math + + + src\Math + + + src\Math + + + src\Math + + + src\AI + + + src\AI + + + src\AI + + + src\Scene + + + src\Scene + + + src\IO + + + src\Math + + + src\Anim + + + src\Anim + + + src\Anim + + + src\Anim + + + src\IO + + + src\Net + + + src\Game + + + src\Global + + + src\Input + + + src\Input + + + src\App + + + src\App + + + src\App + + + src\IO + + + + + src\CGI + + + src\CGI + + + src\CGI + + + src\CGI + + + src\IO + + + src\IO + + + src + + + src\D + + + src\Global + + + src\Global + + + src\Global + + + src\Math + + + src\Game + + + src\Game + + + src\Global + + + src\Global + + + src\Net + + + src\Net + + + src\Net + + + src\Audio + + + src\Audio + + + src\Audio + + + src\Math + + + src\Math + + + src\Math + + + src\IO + + + src\Audio + + + src\Audio + + + src\Global + + + src\Audio + + + src\Global + + + src\ThirdParty\libb64 + + + src\ThirdParty\libb64 + + + src\ThirdParty + + + src\IO + + + src\IO + + + src\Global + + + src\Scene + + + src\Scene + + + src\Math + + + src\Math + + + src\Math + + + src\Math + + + src\AI + + + src\AI + + + src\AI + + + src\Scene + + + src\Scene + + + src\Math + + + src\Anim + + + src\Anim + + + src\Anim + + + src\Anim + + + src\IO + + + src\Net + + + src\Game + + + src\Global + + + src\Input + + + src\Input + + + src\App + + + src\App + + + src\App + + + src\IO + + + \ No newline at end of file diff --git a/Verus/src/AI/AI.cpp b/Verus/src/AI/AI.cpp new file mode 100644 index 0000000..cc44d60 --- /dev/null +++ b/Verus/src/AI/AI.cpp @@ -0,0 +1 @@ +#include "verus.h" diff --git a/Verus/src/AI/AI.h b/Verus/src/AI/AI.h new file mode 100644 index 0000000..29f78a9 --- /dev/null +++ b/Verus/src/AI/AI.h @@ -0,0 +1,4 @@ +#pragma once + +#include "TaskDriver.h" +#include "Turret.h" diff --git a/Verus/src/AI/TaskDriver.cpp b/Verus/src/AI/TaskDriver.cpp new file mode 100644 index 0000000..ca86f09 --- /dev/null +++ b/Verus/src/AI/TaskDriver.cpp @@ -0,0 +1,72 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::AI; + +TaskDriver::TaskDriver() +{ +} + +TaskDriver::~TaskDriver() +{ +} + +void TaskDriver::Update() +{ + VERUS_QREF_TIMER; + VERUS_QREF_UTILS; + + if (_cooldownTimer > 0) + { + _cooldownTimer -= dt; + if (_cooldownTimer < 0) + _cooldownTimer = 0; + else + return; + } + + RMode mode = _mapModes.find(_currentMode)->second; + + VERUS_FOREACH(TMapTasks, mode._mapTasks, it) + { + RTask task = it->second; + task._time += dt; + if (task._time >= task._deadline) + { + if (utils.GetRandom().NextFloat() * 100 < task._chance) + { + if (_pDelegate) + _pDelegate->TaskDriver_OnTask(_C(task._name), _C(_currentMode)); + _cooldownTimer = _cooldown; + } + task._deadline = task._intervalMin + task._intervalSize*utils.GetRandom().NextFloat(); + task._time = 0; + } + } +} + +void TaskDriver::AddMode(CSZ name) +{ + if (_mapModes.find(name) != _mapModes.end()) + return; + Mode mode; + mode._name = name; + _mapModes[name] = mode; + _currentMode = name; +} + +void TaskDriver::AddTask(CSZ name, float chance, float intervalMin, float intervalMax, CSZ mode) +{ + VERUS_QREF_UTILS; + + RMode thisMode = _mapModes.find(mode ? mode : _currentMode)->second; + + Task task; + task._name = name; + task._chance = chance; + task._intervalMin = intervalMin; + task._intervalSize = intervalMax - intervalMin; + task._deadline = task._intervalMin + task._intervalSize*utils.GetRandom().NextFloat(); + + thisMode._mapTasks[name] = task; +} diff --git a/Verus/src/AI/TaskDriver.h b/Verus/src/AI/TaskDriver.h new file mode 100644 index 0000000..dde5a40 --- /dev/null +++ b/Verus/src/AI/TaskDriver.h @@ -0,0 +1,58 @@ +#pragma once + +namespace verus +{ + namespace AI + { + struct TaskDriverDelegate + { + virtual void TaskDriver_OnTask(CSZ task, CSZ mode) = 0; + }; + VERUS_TYPEDEFS(TaskDriverDelegate); + + class TaskDriver + { + struct Task + { + String _name; + float _chance = 100; + float _time = 0; + float _deadline = 0; + float _intervalMin = 0; + float _intervalSize = 0; + }; + VERUS_TYPEDEFS(Task); + + typedef Map TMapTasks; + + struct Mode + { + String _name; + TMapTasks _mapTasks; + }; + VERUS_TYPEDEFS(Mode); + + typedef Map TMapModes; + + TMapModes _mapModes; + String _currentMode; + PTaskDriverDelegate _pDelegate = nullptr; + float _cooldown = 1; + float _cooldownTimer = 0; + + public: + TaskDriver(); + ~TaskDriver(); + + void Update(); + + Str GetCurrentMode() const { return _C(_currentMode); } + void SetCurrentMode(CSZ name) { _currentMode = name; } + void SetDelegate(PTaskDriverDelegate p) { _pDelegate = p; } + + void AddMode(CSZ name); + void AddTask(CSZ name, float chance, float intervalMin, float intervalMax, CSZ mode = nullptr); + }; + VERUS_TYPEDEFS(TaskDriver); + } +} diff --git a/Verus/src/AI/Turret.cpp b/Verus/src/AI/Turret.cpp new file mode 100644 index 0000000..eb1c1ea --- /dev/null +++ b/Verus/src/AI/Turret.cpp @@ -0,0 +1,27 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::AI; + +void Turret::Update() +{ + VERUS_QREF_TIMER; + + float delta = _targetYaw - _actualYaw; + if (delta <= -VERUS_PI) delta += VERUS_2PI; + if (delta >= +VERUS_PI) delta -= VERUS_2PI; + if (delta < 0) + _actualYaw -= Math::Max(_yawSpeed*dt, -delta); + else + _actualYaw += Math::Min(_yawSpeed*dt, delta); + _actualYaw = Math::WrapAngle(_actualYaw); +} + +void Turret::LookAt(RcVector3 rayFromTurret, bool instantly) +{ + _targetYaw = Math::WrapAngle(atan2( + static_cast(rayFromTurret.getX()), + static_cast(rayFromTurret.getZ()))); + if (instantly) + _actualYaw = _targetYaw; +} diff --git a/Verus/src/AI/Turret.h b/Verus/src/AI/Turret.h new file mode 100644 index 0000000..6c1894f --- /dev/null +++ b/Verus/src/AI/Turret.h @@ -0,0 +1,34 @@ +#pragma once + +namespace verus +{ + namespace AI + { + class Turret + { + float _targetPitch = 0; + float _targetYaw = 0; + float _actualPitch = 0; + float _actualYaw = 0; + float _pitchSpeed = 1; + float _yawSpeed = 1; + + public: + void Update(); + + float GetTargetPitch() const { return _targetPitch; } + float GetTargetYaw() const { return _targetYaw; } + float GetActualPitch() const { return _actualPitch; } + float GetActualYaw() const { return _actualYaw; } + + // Speed: + float GetPitchSpeed() const { return _pitchSpeed; } + float GetYawSpeed() const { return _yawSpeed; } + void SetPitchSpeed(float x) { _pitchSpeed = x; } + void SetYawSpeed(float x) { _yawSpeed = x; } + + void LookAt(RcVector3 rayFromTurret, bool instantly = false); + }; + VERUS_TYPEDEFS(Turret); + } +} diff --git a/Verus/src/Anim/Anim.cpp b/Verus/src/Anim/Anim.cpp new file mode 100644 index 0000000..67333b2 --- /dev/null +++ b/Verus/src/Anim/Anim.cpp @@ -0,0 +1,11 @@ +#include "verus.h" + +namespace verus +{ + void Make_Anim() + { + } + void Free_Anim() + { + } +} diff --git a/Verus/src/Anim/Anim.h b/Verus/src/Anim/Anim.h new file mode 100644 index 0000000..1c115b9 --- /dev/null +++ b/Verus/src/Anim/Anim.h @@ -0,0 +1,11 @@ +#pragma once + +#include "Motion.h" +#include "Skeleton.h" +#include "Animation.h" + +namespace verus +{ + void Make_Anim(); + void Free_Anim(); +} diff --git a/Verus/src/Anim/Animation.cpp b/Verus/src/Anim/Animation.cpp new file mode 100644 index 0000000..30edcaa --- /dev/null +++ b/Verus/src/Anim/Animation.cpp @@ -0,0 +1,309 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Anim; + +// Collection: + +Collection::Collection() +{ +} + +Collection::~Collection() +{ + DeleteAll(); +} + +void Collection::Async_Run(CSZ url, RcBlob blob) +{ + IO::StreamPtr sp(blob); + CSZ niceName = strrchr(url, '/'); + niceName = niceName ? niceName + 1 : url; + RMotionData md = _map[niceName]; + md._motion.Init(); + md._motion.Deserialize(sp); + if (md._duration) + md._motion.ComputePlaybackSpeed(md._duration); +} + +void Collection::AddMotion(CSZ name, bool loop, float duration) +{ + CSZ niceName = strrchr(name, '/'); + niceName = niceName ? niceName + 1 : name; + RMotionData md = _map[niceName]; + md._duration = duration; + md._loop = loop; + IO::Async::I().Load(name, this); +} + +void Collection::DeleteAll() +{ + IO::Async::Cancel(this); + _map.clear(); +} + +PMotionData Collection::Find(CSZ name) +{ + return TStoreMotions::Find(name); +} + +int Collection::GetMaxBones() +{ + int num = 0; + VERUS_FOREACH(TStoreMotions::TMap, _map, it) + num = Math::Max(num, it->second._motion.GetNumBones()); + return num; +} + +// Animation: + +Animation::Animation() +{ +} + +Animation::~Animation() +{ +} + +void Animation::Update(int numAlphaTracks, PAlphaTrack pAlphaTracks) +{ + VERUS_QREF_TIMER; + + // Async BindCollection(): + if (_vTriggerStates.size() != _pCollection->GetMaxBones()) + _vTriggerStates.resize(_pCollection->GetMaxBones()); + + // Async BindSkeleton(): + if (!_blendMotion.GetNumBones() && _pSkeleton->GetNumBones()) + _pSkeleton->InsertBonesIntoMotion(_blendMotion); + + if (_playing) + { + if (_blending) + { + _blendTimer += dt; + if (_blendTimer >= _blendDuration) + _blending = false; + } + else if (!_currentMotion.empty()) + { + RMotionData md = *_pCollection->Find(_C(_currentMotion)); + const float len = md._motion.GetDuration(); + if (_time < len + 0.5f) + { + _time += dt; + md._motion.ProcessTriggers(_time, this, GetTriggerStatesArray()); + if (_time >= len) + { + if (_pDelegate) + _pDelegate->Animation_OnEnd(_C(_currentMotion)); + _time = md._loop ? fmod(_time, len) : len + 1; + md._motion.ResetTriggers(GetTriggerStatesArray()); // New loop, reset triggers. + } + } + } + } + + if (!_currentMotion.empty() && numAlphaTracks >= 0) // Alpha track (-1) should not modify the skeleton. + { + RMotionData md = *_pCollection->Find(_C(_currentMotion)); + + int numAlphaMotions = 0; + Skeleton::AlphaMotion alphaMotions[4]; + + for (int i = 0; i < numAlphaTracks && i < 4; ++i) + { + alphaMotions[i]._pMotion = pAlphaTracks[i]._pAnimation->GetMotion(); // Can be in blend state. + alphaMotions[i]._rootBone = pAlphaTracks[i]._rootBone; + alphaMotions[i]._alpha = pAlphaTracks[i]._pAnimation->GetAlpha(); + alphaMotions[i]._time = pAlphaTracks[i]._pAnimation->GetTime(); + numAlphaMotions++; + } + + if (_blending) + { + md._motion.BindBlendMotion(&_blendMotion, _blendTimer / _blendDuration); + _pSkeleton->ApplyMotion(md._motion, _time, numAlphaMotions, alphaMotions); + } + else + { +#ifdef VERUS_COMPARE_MODE + _pSkeleton->ApplyMotion(md._motion, 0, numAlphaMotions, alphaMotions); +#else + _pSkeleton->ApplyMotion(md._motion, _time, numAlphaMotions, alphaMotions); +#endif + } + } +} + +void Animation::BindCollection(PCollection p) +{ + _pCollection = p; +} + +void Animation::BindSkeleton(PSkeleton p) +{ + _pSkeleton = p; +} + +void Animation::SetCurrentMotion(CSZ name) +{ + // Reset triggers: + if (!_currentMotion.empty()) + _pCollection->Find(_C(_currentMotion))->_motion.ResetTriggers(GetTriggerStatesArray()); + if (name) + { + _pCollection->Find(name)->_motion.ResetTriggers(GetTriggerStatesArray()); + _currentMotion = name; + } + else + _currentMotion.clear(); +} + +void Animation::Play() +{ + _playing = true; +} + +void Animation::Stop() +{ + _playing = false; + _time = 0; +} + +void Animation::Pause() +{ + _playing = false; +} + +void Animation::BlendTo(CSZ name, Range duration, int randTime, PMotion pMotionFrom) +{ + VERUS_QREF_UTILS; + + PMotion pMotion = pMotionFrom; + if (!_currentMotion.empty() || pMotionFrom) + { + if (!pMotionFrom) + pMotion = &_pCollection->Find(_C(_currentMotion))->_motion; + if (_blending) // Already blending? + pMotion->BindBlendMotion(&_blendMotion, _blendTimer / _blendDuration); + pMotion->BakeMotionAt(_time, _blendMotion); // Capture current pose. + pMotion->BindBlendMotion(nullptr, 0); + } + + if ((0 == duration._max) || (_currentMotion.empty() && name && _prevMotion == name && _blendTimer / _blendDuration < 0.5f)) + { + _blending = false; // Special case for alpha tracks. + } + else + { + _blending = true; + _blendDuration = (_currentMotion == (name ? name : "")) ? duration._min : duration._max; + _blendTimer = 0; + } + + _prevMotion = _currentMotion; + _currentMotion = name ? name : ""; + + // Reset triggers: + if (pMotion) + pMotion->ResetTriggers(GetTriggerStatesArray()); + if (name) + { + pMotion = &_pCollection->Find(name)->_motion; + pMotion->ResetTriggers(GetTriggerStatesArray()); + } + + // Reset time: + _time = (randTime >= 0 && pMotion) ? (randTime / 255.f)*pMotion->GetDuration() : 0; + if (_time) + pMotion->SkipTriggers(_time, GetTriggerStatesArray()); +} + +bool Animation::BlendToNew(std::initializer_list names, Range duration, int randTime, PMotion pMotionFrom) +{ + for (auto name : names) + { + if (_currentMotion == name) + return false; + } + BlendTo(*names.begin(), duration, randTime, pMotionFrom); + return true; +} + +void Animation::Motion_OnTrigger(CSZ name, int state) +{ + if (_pDelegate) + _pDelegate->Animation_OnTrigger(name, state); +} + +PMotion Animation::GetMotion() +{ + PMotion p = nullptr; + if (_currentMotion.empty()) + { + if (!_prevMotion.empty()) + p = &_pCollection->Find(_C(_prevMotion))->_motion; + } + else + { + p = &_pCollection->Find(_C(_currentMotion))->_motion; + } + if (p && _blending) + p->BindBlendMotion(&_blendMotion, _blendTimer / _blendDuration); + return p; +} + +float Animation::GetAlpha(CSZ name) const +{ + bool matchCurrentMotion = false; + bool matchPrevMotion = false; + if (name) + { + matchCurrentMotion = Str::StartsWith(_C(_currentMotion), name); + matchPrevMotion = Str::StartsWith(_C(_prevMotion), name); + } + const bool doBlend = name ? (matchCurrentMotion || matchPrevMotion) : true; + if (_blending && doBlend) + { + bool fadeIn = _prevMotion.empty() && !_currentMotion.empty(); + bool fadeOut = !_prevMotion.empty() && _currentMotion.empty(); + if (name) + { + fadeIn = !matchPrevMotion && matchCurrentMotion; + fadeOut = matchPrevMotion && !matchCurrentMotion; + } + if (fadeIn) + return Math::SmoothStep(0, 1, _blendTimer / _blendDuration); + if (fadeOut) + return Math::SmoothStep(0, 1, 1 - _blendTimer / _blendDuration); + } + if (name) + return matchCurrentMotion ? 1.f : 0.f; + else + return _currentMotion.empty() ? 0.f : 1.f; +} + +float Animation::GetTime() +{ + if (_currentMotion.empty()) + { + if (_prevMotion.empty()) + return _time; + else // When alpha goes to zero, use last motion frame: + return _pCollection->Find(_C(_prevMotion))->_motion.GetDuration(); + } + else + { + return _time; + } +} + +bool Animation::IsNearEdge(float t, Edge edge) +{ + PMotion pMotion = GetMotion(); + if (!pMotion) + return false; + const float at = _time / pMotion->GetDuration(); + return ((edge&Edge::begin) && at < t) || ((edge&Edge::end) && at >= 1 - t); +} diff --git a/Verus/src/Anim/Animation.h b/Verus/src/Anim/Animation.h new file mode 100644 index 0000000..7286041 --- /dev/null +++ b/Verus/src/Anim/Animation.h @@ -0,0 +1,109 @@ +#pragma once + +namespace verus +{ + namespace Anim + { + struct AnimationDelegate + { + virtual void Animation_OnEnd(CSZ name) {} + virtual void Animation_OnTrigger(CSZ name, int state) {} + }; + VERUS_TYPEDEFS(AnimationDelegate); + + //! Objects of this type are stored in Collection. + struct MotionData + { + Motion _motion; + float _duration = 0; + bool _loop = true; + }; + VERUS_TYPEDEFS(MotionData); + + typedef StoreUnique TStoreMotions; + //! The collection of motion objects (Motion) that can be reused by multiple animation objects (Animation). + class Collection : private TStoreMotions, public IO::AsyncCallback + { + public: + Collection(); + ~Collection(); + + virtual void Async_Run(CSZ url, RcBlob blob) override; + + void AddMotion(CSZ name, bool loop = true, float duration = 0); + void DeleteAll(); + PMotionData Find(CSZ name); + int GetMaxBones(); + }; + VERUS_TYPEDEFS(Collection); + + //! Holds the collection (Collection) of motion objects (Motion) and provides the ability to interpolate between them. + + //! Note: triggers will work properly with 'multiple animations' + 'single + //! collection' only if you add motions before binding collection. + //! + class Animation : public MotionDelegate + { + public: + enum class Edge : int + { + begin = (1 << 0), + end = (1 << 1) + }; + + struct AlphaTrack + { + Animation* _pAnimation = nullptr; + CSZ _rootBone = nullptr; + }; + VERUS_TYPEDEFS(AlphaTrack); + + private: + PCollection _pCollection = nullptr; + PAnimationDelegate _pDelegate = nullptr; + PSkeleton _pSkeleton = nullptr; + Motion _blendMotion; + String _currentMotion; + String _prevMotion; + Vector _vTriggerStates; + float _time = 0; + float _blendDuration = 0; + float _blendTimer = 0; + bool _blending = false; + bool _playing = false; + + public: + Animation(); + ~Animation(); + + void Update(int numAlphaTracks = 0, PAlphaTrack pAlphaTracks = nullptr); + + void BindCollection(PCollection p); + void BindSkeleton(PSkeleton p); + void SetCurrentMotion(CSZ name); + PAnimationDelegate SetDelegate(PAnimationDelegate p) { return Utils::Swap(_pDelegate, p); } + + void Play(); + void Stop(); + void Pause(); + + Str GetCurrentMotionName() const { return _C(_currentMotion); } + + void BlendTo(CSZ name, + Range duration = 0.5f, int randTime = -1, PMotion pMotionFrom = nullptr); + bool BlendToNew(std::initializer_list names, + Range duration = 0.5f, int randTime = -1, PMotion pMotionFrom = nullptr); + bool IsBlending() const { return _blending; } + + virtual void Motion_OnTrigger(CSZ name, int state) override; + + PMotion GetMotion(); + float GetAlpha(CSZ name = nullptr) const; + float GetTime(); + bool IsNearEdge(float t = 0.1f, Edge edge = Edge::begin | Edge::end); + + int* GetTriggerStatesArray() { return _vTriggerStates.empty() ? nullptr : _vTriggerStates.data(); } + }; + VERUS_TYPEDEFS(Animation); + } +} diff --git a/Verus/src/Anim/Motion.cpp b/Verus/src/Anim/Motion.cpp new file mode 100644 index 0000000..d89c081 --- /dev/null +++ b/Verus/src/Anim/Motion.cpp @@ -0,0 +1,972 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Anim; + +// Motion::Bone::Rotation: + +Motion::Bone::Rotation::Rotation() +{ +} + +Motion::Bone::Rotation::Rotation(RcQuat q) +{ + _q = q; +} + +Motion::Bone::Rotation::Rotation(RcVector3 euler) +{ + euler.EulerToQuaternion(_q); +} + +// Motion::Bone: + +Motion::Bone::Bone(PMotion pMotion) : + _pMotion(pMotion) +{ +} + +Motion::Bone::~Bone() +{ + DeleteAll(); +} + +void Motion::Bone::DeleteAll() +{ + _mapRot.clear(); + _mapPos.clear(); + _mapScale.clear(); + _mapTrigger.clear(); +} + +// Insert: +void Motion::Bone::InsertKeyframeRotation(int frame, RcQuat q) +{ + _mapRot[frame] = Rotation(q); +} +void Motion::Bone::InsertKeyframeRotation(int frame, RcVector3 euler) +{ + _mapRot[frame] = Rotation(euler); +} +void Motion::Bone::InsertKeyframePosition(int frame, RcVector3 pos) +{ + _mapPos[frame] = pos; +} +void Motion::Bone::InsertKeyframeScale(int frame, RcVector3 scale) +{ + _mapScale[frame] = scale; +} +void Motion::Bone::InsertKeyframeTrigger(int frame, int state) +{ + _mapTrigger[frame] = state; +} + +// Delete: +void Motion::Bone::DeleteKeyframeRotation(int frame) +{ + VERUS_IF_FOUND_IN(TMapRot, _mapRot, frame, it) + _mapRot.erase(it); +} +void Motion::Bone::DeleteKeyframePosition(int frame) +{ + VERUS_IF_FOUND_IN(TMapPos, _mapPos, frame, it) + _mapPos.erase(it); +} +void Motion::Bone::DeleteKeyframeScale(int frame) +{ + VERUS_IF_FOUND_IN(TMapScale, _mapScale, frame, it) + _mapScale.erase(it); +} +void Motion::Bone::DeleteKeyframeTrigger(int frame) +{ + VERUS_IF_FOUND_IN(TMapTrigger, _mapTrigger, frame, it) + _mapTrigger.erase(it); +} + +// Find: +bool Motion::Bone::FindKeyframeRotation(int frame, RVector3 euler, RQuat q) const +{ + euler = Vector3(0); + q = Quat::identity(); + VERUS_IF_FOUND_IN(TMapRot, _mapRot, frame, it) + { + euler.EulerFromQuaternion(it->second._q); + q = it->second._q; + return true; + } + return false; +} +bool Motion::Bone::FindKeyframePosition(int frame, RVector3 pos) const +{ + pos = Vector3(0); + VERUS_IF_FOUND_IN(TMapPos, _mapPos, frame, it) + { + pos = it->second; + return true; + } + return false; +} +bool Motion::Bone::FindKeyframeScale(int frame, RVector3 scale) const +{ + scale = Vector3(1, 1, 1); + VERUS_IF_FOUND_IN(TMapScale, _mapScale, frame, it) + { + scale = it->second; + return true; + } + return false; +} +bool Motion::Bone::FindKeyframeTrigger(int frame, int& state) const +{ + state = 0; + VERUS_IF_FOUND_IN(TMapTrigger, _mapTrigger, frame, it) + { + state = it->second; + return true; + } + return false; +} + +void Motion::Bone::ComputeRotationAt(float time, RVector3 euler, RQuat q) const +{ + Rotation prev, next, null(Quat(0)); + const float alpha = GetAlpha(_mapRot, prev, next, null, time); + q = VMath::slerp(alpha, prev._q, next._q); + + PMotion pBlendMotion = _pMotion->GetBlendMotion(); + if (pBlendMotion) + { + PBone pBone = pBlendMotion->FindBone(_C(_name)); + if (pBone) + { + Vector3 eulerBlend; + Quat qBlend; + if (pBone->FindKeyframeRotation(0, eulerBlend, qBlend)) + q = VMath::slerp(_pMotion->GetBlendAlpha(), qBlend, q); + } + } + + euler.EulerFromQuaternion(q); +} + +void Motion::Bone::ComputePositionAt(float time, RVector3 pos) const +{ + Vector3 prev, next, null(0); + const float alpha = GetAlpha(_mapPos, prev, next, null, time); + pos = VMath::lerp(alpha, prev, next); + + PMotion pBlendMotion = _pMotion->GetBlendMotion(); + if (pBlendMotion) + { + PBone pBone = pBlendMotion->FindBone(_C(_name)); + if (pBone) + { + Vector3 posBlend; + if (pBone->FindKeyframePosition(0, posBlend)) + pos = VMath::lerp(_pMotion->GetBlendAlpha(), posBlend, pos); + } + } +} + +void Motion::Bone::ComputeScaleAt(float time, RVector3 scale) const +{ + Vector3 prev, next, null(1, 1, 1); + const float alpha = GetAlpha(_mapScale, prev, next, null, time); + scale = VMath::lerp(alpha, prev, next); + + PMotion pBlendMotion = _pMotion->GetBlendMotion(); + if (pBlendMotion) + { + PBone pBone = pBlendMotion->FindBone(_C(_name)); + if (pBone) + { + Vector3 scaleBlend; + if (pBone->FindKeyframeScale(0, scaleBlend)) + scale = VMath::lerp(_pMotion->GetBlendAlpha(), scaleBlend, scale); + } + } +} + +void Motion::Bone::ComputeTriggerAt(float time, int& state) const +{ + if (_mapTrigger.empty()) + { + state = 0; + return; + } + const int frame = int(_pMotion->GetFps()*time); + TMapTrigger::const_iterator it = _mapTrigger.upper_bound(frame); // Find frame after 'time'. + if (it != _mapTrigger.begin()) + { + it--; + state = it->second; + } + else // All keyframes are after 'time': + { + state = 0; + } +} + +void Motion::Bone::ComputeMatrixAt(float time, RTransform3 mat) +{ + Quat q; + Vector3 scale, euler, pos; + ComputeRotationAt(time, euler, q); + ComputePositionAt(time, pos); + ComputeScaleAt(time, scale); + mat = VMath::appendScale(Transform3(q, pos), scale); +} + +void Motion::Bone::MoveKeyframe(int direction, Type type, int frame) +{ + const int frameDest = (direction >= 0) ? frame + 1 : frame - 1; + if (frameDest < 0 || frameDest >= _pMotion->GetNumFrames()) + return; + + switch (type) + { + case Type::rotation: + { + VERUS_IF_FOUND_IN(TMapRot, _mapRot, frame, it) + { + if (_mapRot.find(frameDest) == _mapRot.end()) + { + _mapRot[frameDest] = it->second; + _mapRot.erase(it); + } + } + } + break; + case Type::position: + { + VERUS_IF_FOUND_IN(TMapPos, _mapPos, frame, it) + { + if (_mapPos.find(frameDest) == _mapPos.end()) + { + _mapPos[frameDest] = it->second; + _mapPos.erase(it); + } + } + } + break; + case Type::scale: + { + VERUS_IF_FOUND_IN(TMapScale, _mapScale, frame, it) + { + if (_mapScale.find(frameDest) == _mapScale.end()) + { + _mapScale[frameDest] = it->second; + _mapScale.erase(it); + } + } + } + break; + case Type::trigger: + { + VERUS_IF_FOUND_IN(TMapTrigger, _mapTrigger, frame, it) + { + if (_mapTrigger.find(frameDest) == _mapTrigger.end()) + { + _mapTrigger[frameDest] = it->second; + _mapTrigger.erase(it); + } + } + } + break; + } +} + +void Motion::Bone::Serialize(IO::RStream stream) +{ + const int numKeyframesRot = Utils::Cast32(_mapRot.size()); + const int numKeyframesPos = Utils::Cast32(_mapPos.size()); + const int numKeyframesScale = Utils::Cast32(_mapScale.size()); + const int numKeyframesTrigger = Utils::Cast32(_mapTrigger.size()); + + stream << numKeyframesRot; + VERUS_FOREACH_CONST(TMapRot, _mapRot, it) + { + stream << it->first; + stream.Write(&it->second._q, 16); + } + + stream << numKeyframesPos; + VERUS_FOREACH_CONST(TMapPos, _mapPos, it) + { + stream << it->first; + stream.Write(&it->second, 12); + } + + stream << numKeyframesScale; + VERUS_FOREACH_CONST(TMapScale, _mapScale, it) + { + stream << it->first; + stream.Write(&it->second, 12); + } + + stream << numKeyframesTrigger; + VERUS_FOREACH_CONST(TMapTrigger, _mapTrigger, it) + { + stream << it->first; + stream << it->second; + } +} + +void Motion::Bone::Deserialize(IO::RStream stream) +{ + int numKeyframesRot, numKeyframesPos, numKeyframesScale, numKeyframesTrigger, frame, state; + Vector3 temp; + Quat q; + + stream >> numKeyframesRot; + VERUS_FOR(i, numKeyframesRot) + { + stream >> frame; + stream.Read(&q, 16); + InsertKeyframeRotation(frame, q); + } + + stream >> numKeyframesPos; + VERUS_FOR(i, numKeyframesPos) + { + stream >> frame; + stream.Read(&temp, 12); + InsertKeyframePosition(frame, temp); + } + + stream >> numKeyframesScale; + VERUS_FOR(i, numKeyframesScale) + { + stream >> frame; + stream.Read(&temp, 12); + InsertKeyframeScale(frame, temp); + } + + stream >> numKeyframesTrigger; + VERUS_FOR(i, numKeyframesTrigger) + { + stream >> frame; + stream >> state; + InsertKeyframeTrigger(frame, state); + } +} + +void Motion::Bone::DeleteRedundantKeyframes() +{ + if (2 == _mapRot.size()) + { + if (!memcmp( + &_mapRot.begin()->second, + &_mapRot.rbegin()->second, + 16)) + _mapRot.erase(--_mapRot.end()); + } + if (1 == _mapRot.size()) + { + RQuat q = _mapRot.begin()->second._q; + if (q.IsIdentity()) + _mapRot.clear(); + } + + if (2 == _mapPos.size()) + { + if (!memcmp( + &_mapPos.begin()->second, + &_mapPos.rbegin()->second, + 12)) + _mapPos.erase(--_mapPos.end()); + } + + if (2 == _mapScale.size()) + { + if (!memcmp( + &_mapScale.begin()->second, + &_mapScale.rbegin()->second, + 12)) + _mapScale.erase(--_mapScale.end()); + } +} + +void Motion::Bone::DeleteOddKeyframes() +{ + { + TMapRot::iterator it = _mapRot.begin(); + while (it != _mapRot.end()) + { + if (it->first & 0x1) + _mapRot.erase(it++); + else + ++it; + } + } + { + TMapPos::iterator it = _mapPos.begin(); + while (it != _mapPos.end()) + { + if (it->first & 0x1) + _mapPos.erase(it++); + else + ++it; + } + } + { + TMapScale::iterator it = _mapScale.begin(); + while (it != _mapScale.end()) + { + if (it->first & 0x1) + _mapScale.erase(it++); + else + ++it; + } + } +} + +void Motion::Bone::InsertLoopKeyframes() +{ + if (!_mapRot.empty()) + _mapRot[_pMotion->GetNumFrames()] = _mapRot.begin()->second; + if (!_mapPos.empty()) + _mapPos[_pMotion->GetNumFrames()] = _mapPos.begin()->second; + if (!_mapScale.empty()) + _mapScale[_pMotion->GetNumFrames()] = _mapScale.begin()->second; +} + +void Motion::Bone::Cut(int frame, bool before) +{ + if (before && !frame) + return; + + { + TMapRot::iterator it = _mapRot.begin(); + while (it != _mapRot.end()) + { + if (before) + { + if (it->first >= frame) + _mapRot[it->first - frame] = it->second; + _mapRot.erase(it++); + } + else + { + if (it->first > frame) + _mapRot.erase(it++); + else + ++it; + } + } + } + { + TMapPos::iterator it = _mapPos.begin(); + while (it != _mapPos.end()) + { + if (before) + { + if (it->first >= frame) + _mapPos[it->first - frame] = it->second; + _mapPos.erase(it++); + } + else + { + if (it->first > frame) + _mapPos.erase(it++); + else + ++it; + } + } + } + { + TMapScale::iterator it = _mapScale.begin(); + while (it != _mapScale.end()) + { + if (before) + { + if (it->first >= frame) + _mapScale[it->first - frame] = it->second; + _mapScale.erase(it++); + } + else + { + if (it->first > frame) + _mapScale.erase(it++); + else + ++it; + } + } + } +} + +void Motion::Bone::Fix(bool speedLimit) +{ + const float e = 0.02f; + + // Remove rotation keyframes: + if (true) + { + Quat qBase(0); + TMapRot::iterator it = _mapRot.begin(); + bool hasBroken = false; + while (it != _mapRot.end()) + { + RcQuat q = it->second._q; + const bool broken = + Math::IsNaN(q.getX()) || + Math::IsNaN(q.getY()) || + Math::IsNaN(q.getZ()) || + Math::IsNaN(q.getW()); + if (broken) + hasBroken = true; + const bool same = qBase.IsEqual(q, e); + if (same || broken) + _mapRot.erase(it++); + else + { + qBase = q; + ++it; + } + } + } + + // Remove position keyframes: + if (true) + { + Vector3 base(0); + TMapPos::iterator it = _mapPos.begin(); + while (it != _mapPos.end()) + { + RcVector3 pos = it->second; + const bool broken = + Math::IsNaN(pos.getX()) || + Math::IsNaN(pos.getY()) || + Math::IsNaN(pos.getZ()); + const bool same = base.IsEqual(pos, e); + if (same || broken) + _mapPos.erase(it++); + else + { + base = pos; + ++it; + } + } + } + + // Limit rotation speed: + if (speedLimit && Skeleton::IsKinectBone(_C(_name))) + { + VERUS_FOREACH(TMapRot, _mapRot, it) + { + RQuat q = it->second._q; + if (it->first >= 1) + { + Quat qPrev; + Vector3 euler; + ComputeRotationAt((it->first - 1)*_pMotion->GetFpsInv(), euler, qPrev); + const float threshold = 0.5f; + const Quat qPrevInv = VMath::inverse(Matrix3(qPrev)); + const Quat qD = q * qPrevInv; + const float a = abs(glm::angle(qD.GLM())); + if (a > threshold) + { + const float back = Math::Clamp((a - threshold) / threshold, 0.f, 0.75f); + q = VMath::slerp(back, q, qPrev); + } + } + } + } +} + +void Motion::Bone::ApplyScaleBias(RcVector3 scale, RcVector3 bias) +{ + VERUS_FOREACH(TMapPos, _mapPos, it) + it->second = VMath::mulPerElem(it->second, scale) + bias; +} + +void Motion::Bone::Scatter(int srcFrom, int srcTo, int dMin, int dMax) +{ + VERUS_QREF_UTILS; + int start = srcFrom; + const int range = dMax - dMin; + while (true) + { + start += dMin + utils.GetRandom().Next() % range; + if (start >= _pMotion->GetNumFrames()) + break; + for (int i = srcFrom; i < srcTo; ++i) + { + Quat q; + Vector3 euler, pos; + if (FindKeyframeRotation(i, euler, q)) + InsertKeyframeRotation(i + start, q); + if (FindKeyframePosition(i, pos)) + InsertKeyframePosition(i + start, pos); + } + } +} + +int Motion::Bone::GetLastKeyframe() const +{ + int frame = 0; + if (!_mapRot.empty()) + frame = Math::Max(frame, _mapRot.rbegin()->first); + if (!_mapPos.empty()) + frame = Math::Max(frame, _mapPos.rbegin()->first); + if (!_mapScale.empty()) + frame = Math::Max(frame, _mapScale.rbegin()->first); + if (!_mapTrigger.empty()) + frame = Math::Max(frame, _mapTrigger.rbegin()->first); + return frame; +} + +// Motion: + +Motion::Motion() +{ +} + +Motion::~Motion() +{ + Done(); +} + +void Motion::Init() +{ + VERUS_INIT(); +} + +void Motion::Done() +{ + DeleteAllBones(); + VERUS_DONE(Motion); +} + +Motion::PBone Motion::GetBoneByIndex(int index) +{ + int i = 0; + for (auto& kv : _mapBones) + { + if (i == index) + return &kv.second; + i++; + } + return nullptr; +} + +int Motion::GetBoneIndex(CSZ name) const +{ + int i = 0; + VERUS_FOREACH_CONST(TMapBones, _mapBones, it) + { + if (it->first == name) + return i; + i++; + } + return -1; +} + +Motion::PBone Motion::InsertBone(CSZ name) +{ + PBone pBone = FindBone(name); + if (pBone) + return pBone; + Bone bone(this); + bone.Rename(name); + _mapBones[name] = bone; + return &_mapBones[name]; +} + +void Motion::DeleteBone(CSZ name) +{ + VERUS_IF_FOUND_IN(TMapBones, _mapBones, name, it) + _mapBones.erase(it); +} + +void Motion::DeleteAllBones() +{ + _mapBones.clear(); +} + +Motion::PBone Motion::FindBone(CSZ name) +{ + VERUS_IF_FOUND_IN(TMapBones, _mapBones, name, it) + return &it->second; + return nullptr; +} + +void Motion::Serialize(IO::RStream stream) +{ + const UINT32 magic = '2NAX'; + stream << magic; + + const UINT16 version = xanVersion; + stream << version; + + stream << _numFrames; + stream << _fps; + stream << GetNumBones(); + + for (auto& kv : _mapBones) + { + RBone bone = kv.second; + stream.WriteString(_C(bone.GetName())); + bone.Serialize(stream); + } +} + +void Motion::Deserialize(IO::RStream stream) +{ + UINT32 magic = 0; + stream >> magic; + if ('2NAX' != magic) + throw VERUS_RECOVERABLE << "Deserialize(), Invalid XAN format"; + + UINT16 version = 0; + stream >> version; + if (xanVersion != version) + throw VERUS_RECOVERABLE << "Deserialize(), Invalid XAN version"; + + stream >> _numFrames; + stream >> _fps; + if (_numFrames < 0 || _numFrames > maxNumFrames) + throw VERUS_RECOVERABLE << "Deserialize(), Invalid number of frames in XAN"; + if (_fps <= 0 || _fps > maxFps) + throw VERUS_RECOVERABLE << "Deserialize(), Invalid FPS in XAN"; + + SetFps(_fps); + + int numBones = 0; + stream >> numBones; + if (numBones < 0 || numBones > maxNumBones) + throw VERUS_RECOVERABLE << "Deserialize(), Invalid number of bones in XAN"; + + char buffer[IO::Stream::bufferSize] = {}; + VERUS_FOR(i, numBones) + { + stream.ReadString(buffer); + PBone pBone = InsertBone(buffer); + pBone->Deserialize(stream); + } +} + +void Motion::BakeMotionAt(float time, Motion& dest) const +{ + float nativeTime = time * _playbackSpeed; + if (_reversed) + nativeTime = GetNativeDuration() - nativeTime; + + VERUS_FOREACH_CONST(TMapBones, _mapBones, it) + { + PcBone pBone = &it->second; + PBone pBoneDest = dest.FindBone(_C(it->first)); + if (pBoneDest) + { + Vector3 rot, pos, scale; + Quat q; + + pBone->ComputeRotationAt(nativeTime, rot, q); + pBone->ComputePositionAt(nativeTime, pos); + pBone->ComputeScaleAt(nativeTime, scale); + + pBoneDest->InsertKeyframeRotation(0, q); + pBoneDest->InsertKeyframePosition(0, pos); + pBoneDest->InsertKeyframeScale(0, scale); + } + } +} + +void Motion::BindBlendMotion(PMotion p, float alpha) +{ + _pBlendMotion = p; + _blendAlpha = alpha; +} + +void Motion::DeleteRedundantKeyframes() +{ + for (auto& kv : _mapBones) + kv.second.DeleteRedundantKeyframes(); +} + +void Motion::DeleteOddKeyframes() +{ + for (auto& kv : _mapBones) + kv.second.DeleteOddKeyframes(); +} + +void Motion::InsertLoopKeyframes() +{ + for (auto& kv : _mapBones) + kv.second.InsertLoopKeyframes(); +} + +void Motion::Cut(int frame, bool before) +{ + for (auto& kv : _mapBones) + kv.second.Cut(frame, before); + _numFrames = before ? _numFrames - frame : frame + 1; +} + +void Motion::Fix(bool speedLimit) +{ + for (auto& kv : _mapBones) + kv.second.Fix(speedLimit); +} + +void Motion::ProcessTriggers(float time, PMotionDelegate p, int* pUserTriggerStates) +{ + VERUS_RT_ASSERT(p); + + float nativeTime = time * _playbackSpeed; + if (_reversed) + nativeTime = GetNativeDuration() - nativeTime; + + int i = 0; + for (auto& kv : _mapBones) + { + PBone pBone = &kv.second; + int state = 0; + if (!(_reversed && nativeTime < _fpsInv*0.5f)) // Avoid edge case for the first frame in reverse. + pBone->ComputeTriggerAt(nativeTime, state); + const int last = pUserTriggerStates ? pUserTriggerStates[i] : pBone->GetLastTriggerState(); + if (state != last) + { + p->Motion_OnTrigger(_C(pBone->GetName()), state); + if (pUserTriggerStates) + pUserTriggerStates[i] = state; + else + pBone->SetLastTriggerState(state); + } + i++; + } +} + +void Motion::ResetTriggers(int* pUserTriggerStates) +{ + int i = 0; + for (auto& kv : _mapBones) + { + PBone pBone = &kv.second; + int state = 0; + if (_reversed) + pBone->ComputeTriggerAt(GetNativeDuration(), state); + if (pUserTriggerStates) + pUserTriggerStates[i] = state; + else + pBone->SetLastTriggerState(state); + i++; + } +} + +void Motion::SkipTriggers(float time, int* pUserTriggerStates) +{ + float nativeTime = time * _playbackSpeed; + if (_reversed) + nativeTime = GetNativeDuration() - nativeTime; + + int i = 0; + for (auto& kv : _mapBones) + { + PBone pBone = &kv.second; + int state; + pBone->ComputeTriggerAt(nativeTime, state); + if (pUserTriggerStates) + pUserTriggerStates[i] = state; + else + pBone->SetLastTriggerState(state); + i++; + } +} + +void Motion::ApplyScaleBias(CSZ name, RcVector3 scale, RcVector3 bias) +{ + PBone pBone = FindBone(name); + if (pBone) + pBone->ApplyScaleBias(scale, bias); +} + +void Motion::SetPlaybackSpeed(float x) +{ + VERUS_RT_ASSERT(x != 0); + _playbackSpeed = x; + _reversed = x < 0; + if (_reversed) + _playbackSpeed = -_playbackSpeed; + _playbackSpeedInv = 1 / _playbackSpeed; +} + +void Motion::ComputePlaybackSpeed(float duration) +{ + VERUS_RT_ASSERT(duration != 0); + _playbackSpeed = GetNativeDuration() / duration; + _reversed = duration < 0; + if (_reversed) + _playbackSpeed = -_playbackSpeed; + _playbackSpeedInv = 1 / _playbackSpeed; +} + +void Motion::Exec(CSZ code) +{ + if (Str::StartsWith(code, "copy ")) + { + char boneSrc[80] = {}; + char boneDst[80] = {}; + sscanf(code, "%*s %s %s", boneSrc, boneDst); + PBone pSrc = FindBone(boneSrc); + PBone pDst = FindBone(boneDst); + pDst->_mapRot = pSrc->_mapRot; + pDst->_mapPos = pSrc->_mapPos; + } + if (Str::StartsWith(code, "scatter ")) + { + int srcFrom = 0, srcTo = 0; + int dMin = 0, dMax = 0; + char bone[80] = {}; + sscanf(code, "%*s %d %d %d %d %s", &srcFrom, &srcTo, &dMin, &dMax, bone); + const int srcSize = srcTo - srcFrom; + if (dMin < srcSize) + { + const int d = srcSize - dMin; + dMin += d; + dMax += d; + } + PBone p = FindBone(bone + 5); + if (p) + p->Scatter(srcFrom, srcTo, dMin, dMax); + } + if (Str::StartsWith(code, "delete ")) + { + char what[80] = {}; + char name[80] = {}; + sscanf(code, "%*s %s %s", what, name); + if (strlen(name) > 5) + { + PBone p = FindBone(name + 5); + if (p) + { + if (!strcmp(what, "rot")) + p->_mapRot.clear(); + if (!strcmp(what, "pos")) + p->_mapPos.clear(); + if (!strcmp(what, "scale")) + p->_mapScale.clear(); + } + } + else + { + for (auto& bone : _mapBones) + { + if (!strcmp(what, "rot")) + bone.second._mapRot.clear(); + if (!strcmp(what, "pos")) + bone.second._mapPos.clear(); + if (!strcmp(what, "scale")) + bone.second._mapScale.clear(); + } + } + } +} + +int Motion::GetLastKeyframe() const +{ + int frame = 0; + VERUS_FOREACH_CONST(TMapBones, _mapBones, it) + frame = Math::Max(frame, it->second.GetLastKeyframe()); + return frame; +} diff --git a/Verus/src/Anim/Motion.h b/Verus/src/Anim/Motion.h new file mode 100644 index 0000000..775430c --- /dev/null +++ b/Verus/src/Anim/Motion.h @@ -0,0 +1,237 @@ +#pragma once + +namespace verus +{ + namespace Anim + { + struct MotionDelegate + { + virtual void Motion_OnTrigger(CSZ name, int state) = 0; + }; + VERUS_TYPEDEFS(MotionDelegate); + + //! Motion holds a series of keyframes and provides the ability to interpolate between them. + + //! Motion can be stored in XAN format. Motion's rate can be + //! scaled and even reversed. Animation object (Animation) can be used to + //! handle multiple motion objects. + //! + class Motion : public Object + { + public: + class Bone : public AllocatorAware + { + friend class Motion; + + class Rotation + { + public: + Quat _q; + + Rotation(); + Rotation(RcQuat q); + Rotation(RcVector3 euler); + }; + VERUS_TYPEDEFS(Rotation); + + typedef Map TMapRot; + typedef Map TMapPos; + typedef Map TMapScale; + typedef Map TMapTrigger; + + String _name; + Motion* _pMotion = nullptr; + TMapRot _mapRot; //!< Rotation keyframes. + TMapPos _mapPos; //!< Position keyframes. + TMapScale _mapScale; //!< Scaling keyframes. + TMapTrigger _mapTrigger; //!< Trigger keyframes. + int _lastTriggerState = 0; + + template + float GetAlpha(const TMap& m, T& prev, T& next, const T& null, float time) const + { + if (m.empty()) // No frames at all, so return null. + { + prev = null; + next = null; + return 0; + } + float alpha; + const int frame = static_cast(_pMotion->GetFps()*time); // Frame is before or at 'time'. + typename TMap::const_iterator it = m.upper_bound(frame); // Find frame after 'time'. + if (it != m.end()) // There are frames greater (after 'time'): + { + if (it != m.begin()) // And there are less than (before 'time'), full interpolation: + { + typename TMap::const_iterator itPrev = it; + itPrev--; + const float prevTime = itPrev->first*_pMotion->GetFpsInv(); + const float nextTime = it->first*_pMotion->GetFpsInv(); + const float delta = nextTime - prevTime; + const float offset = nextTime - time; + alpha = 1 - offset / delta; + prev = itPrev->second; + next = it->second; + } + else // But there are no less than: + { + const float nextTime = it->first*_pMotion->GetFpsInv(); + const float delta = nextTime; + const float offset = time; + alpha = offset / delta; + prev = null; + next = it->second; + } + } + else // There are no frames greater, but there are less than: + { + it--; + alpha = 0; + prev = it->second; + next = null; + } + return alpha; + } + + public: + enum class Type : int + { + rotation, + position, + scale, + trigger + }; + + Bone(Motion* pMotion = nullptr); + ~Bone(); + + Str GetName() const { return _C(_name); } + void Rename(CSZ name) { _name = name; } + + int GetLastTriggerState() const { return _lastTriggerState; } + void SetLastTriggerState(int state) { _lastTriggerState = state; } + + void DeleteAll(); + + void InsertKeyframeRotation(int frame, RcQuat q); + void InsertKeyframeRotation(int frame, RcVector3 euler); + void InsertKeyframePosition(int frame, RcVector3 pos); + void InsertKeyframeScale(int frame, RcVector3 scale); + void InsertKeyframeTrigger(int frame, int state); + + void DeleteKeyframeRotation(int frame); + void DeleteKeyframePosition(int frame); + void DeleteKeyframeScale(int frame); + void DeleteKeyframeTrigger(int frame); + + bool FindKeyframeRotation(int frame, RVector3 euler, RQuat q) const; + bool FindKeyframePosition(int frame, RVector3 pos) const; + bool FindKeyframeScale(int frame, RVector3 scale) const; + bool FindKeyframeTrigger(int frame, int& state) const; + + void ComputeRotationAt(float time, RVector3 euler, RQuat q) const; + void ComputePositionAt(float time, RVector3 pos) const; + void ComputeScaleAt(float time, RVector3 scale) const; + void ComputeTriggerAt(float time, int& state) const; + void ComputeMatrixAt(float time, RTransform3 mat); + + void MoveKeyframe(int direction, Type type, int frame); + + int GetNumKeysRotation() const { return Utils::Cast32(_mapRot.size()); } + int GetNumKeysPosition() const { return Utils::Cast32(_mapPos.size()); } + int GetNumKeysScale() const { return Utils::Cast32(_mapScale.size()); } + int GetNumKeysTrigger() const { return Utils::Cast32(_mapTrigger.size()); } + + VERUS_P(void Serialize(IO::RStream stream)); + VERUS_P(void Deserialize(IO::RStream stream)); + + void DeleteRedundantKeyframes(); + void DeleteOddKeyframes(); + void InsertLoopKeyframes(); + void Cut(int frame, bool before); + void Fix(bool speedLimit); + + void ApplyScaleBias(RcVector3 scale, RcVector3 bias); + + void Scatter(int srcFrom, int srcTo, int dMin, int dMax); + + int GetLastKeyframe() const; + }; + VERUS_TYPEDEFS(Bone); + + private: + enum { xanVersion = 0x0101, maxFps = 10000, maxNumBones = 10000, maxNumFrames = 32 * 1024 * 1024 }; + + typedef Map TMapBones; + + TMapBones _mapBones; + Motion* _pBlendMotion = nullptr; + int _numFrames = 50; + int _fps = 10; + float _fpsInv = 0.1f; + float _blendAlpha = 0; + float _playbackSpeed = 1; + float _playbackSpeedInv = 1; + bool _reversed = false; + + public: + Motion(); + ~Motion(); + + void Init(); + void Done(); + + int GetFps() const { return _fps; } + float GetFpsInv() const { return _fpsInv; } + void SetFps(int fps) { _fps = fps; _fpsInv = 1.f / _fps; } + + int GetNumFrames() const { return _numFrames; } + void SetNumFrames(int num) { _numFrames = num; } + + int GetNumBones() const { return Utils::Cast32(_mapBones.size()); } + + float GetDuration() const { return GetNativeDuration()*_playbackSpeedInv; } + float GetNativeDuration() const { return _numFrames * _fpsInv; } + + PBone GetBoneByIndex(int index); + int GetBoneIndex(CSZ name) const; + + PBone InsertBone(CSZ name); + void DeleteBone(CSZ name); + void DeleteAllBones(); + PBone FindBone(CSZ name); + + void Serialize(IO::RStream stream); + void Deserialize(IO::RStream stream); + + void BakeMotionAt(float time, Motion& dest) const; + void BindBlendMotion(Motion* p, float alpha); + Motion* GetBlendMotion() const { return _pBlendMotion; } + float GetBlendAlpha() const { return _blendAlpha; } + + void DeleteRedundantKeyframes(); + void DeleteOddKeyframes(); + void InsertLoopKeyframes(); + void Cut(int frame, bool before = true); + void Fix(bool speedLimit); + + // Triggers: + void ProcessTriggers(float time, PMotionDelegate p, int* pUserTriggerStates = nullptr); + void ResetTriggers(int* pUserTriggerStates = nullptr); + void SkipTriggers(float time, int* pUserTriggerStates = nullptr); + + void ApplyScaleBias(CSZ name, RcVector3 scale, RcVector3 bias); + + float GetPlaybackSpeed() const { return _playbackSpeed; } + float GetPlaybackSpeedInv() const { return _playbackSpeedInv; } + void SetPlaybackSpeed(float x); + void ComputePlaybackSpeed(float duration); + bool IsReversed() const { return _reversed; } + + void Exec(CSZ code); + + int GetLastKeyframe() const; + }; + VERUS_TYPEDEFS(Motion); + } +} diff --git a/Verus/src/Anim/Skeleton.cpp b/Verus/src/Anim/Skeleton.cpp new file mode 100644 index 0000000..7007f59 --- /dev/null +++ b/Verus/src/Anim/Skeleton.cpp @@ -0,0 +1,1247 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Anim; + +Skeleton::Skeleton() +{ +} + +Skeleton::~Skeleton() +{ + Done(); +} + +void Skeleton::operator=(RcSkeleton that) +{ + Init(); + for (const auto& kv : that._mapBones) + _mapBones[kv.first] = kv.second; + _numPrimaryBones = that._numPrimaryBones; +} + +void Skeleton::Init() +{ + VERUS_INIT(); +} + +void Skeleton::Done() +{ + EndRagdoll(); + VERUS_DONE(Skeleton); +} + +void Skeleton::Draw(bool bindPose, int selected) +{ +#if 0 + VERUS_QREF_DR; + dr.Begin(CGL::CDebugRender::T_LINES, nullptr, false); + for (const auto& kv : _mapBones) + { + PcBone pBone = &kv.second; + PcBone pParent = FindBone(_C(pBone->_parentName)); + if (pParent) + { + if (bindPose) + { + dr.AddLine( + pParent->_matFromBoneSpace.getTranslation(), + pBone->_matFromBoneSpace.getTranslation(), + VERUS_COLOR_WHITE); + } + else + { + dr.AddLine( + (pParent->_matFinal*pParent->_matFromBoneSpace).getTranslation(), + (pBone->_matFinal*pBone->_matFromBoneSpace).getTranslation(), + VERUS_COLOR_WHITE); + } + } + } + for (const auto& kv : _mapBones) + { + PcBone pBone = &kv.second; + const Transform3 mat = bindPose ? pBone->_matFromBoneSpace : pBone->_matFinal*pBone->_matFromBoneSpace; + + const float scale = 0.04f; + Point3 a(0); + Point3 x(scale, 0, 0); + Point3 y(0, scale, 0); + Point3 z(0, 0, scale); + a = mat * a; + x = mat * x; + y = mat * y; + z = mat * z; + + dr.AddLine(a, x, VERUS_COLOR_RGBA(255, 0, 0, 255)); + dr.AddLine(a, y, VERUS_COLOR_RGBA(0, 255, 0, 255)); + dr.AddLine(a, z, VERUS_COLOR_RGBA(0, 0, 255, 255)); + + if (pBone->_shaderIndex == selected) + { + const float scale2 = scale * 0.5f; + Point3 x(scale2, 0, 0); + Point3 y(0, scale2, 0); + Point3 z(0, 0, scale2); + x = mat * x; + y = mat * y; + z = mat * z; + + dr.AddLine(x, y, VERUS_COLOR_RGBA(255, 255, 0, 255)); + dr.AddLine(y, z, VERUS_COLOR_RGBA(255, 255, 0, 255)); + dr.AddLine(z, x, VERUS_COLOR_RGBA(255, 255, 0, 255)); + } + } + dr.End(); +#endif +} + +Skeleton::PBone Skeleton::InsertBone(RBone bone) +{ + PBone p = FindBone(_C(bone._name)); + if (p) + return p; + + bone._shaderIndex = Utils::Cast32(_mapBones.size()); // Assume bones are added in same order as matrices in shader. + _mapBones[bone._name] = bone; + return FindBone(_C(bone._name)); +} + +Skeleton::PBone Skeleton::FindBone(CSZ name) +{ + VERUS_IF_FOUND_IN(TMapBones, _mapBones, name, it) + return &it->second; + return nullptr; +} + +Skeleton::PcBone Skeleton::FindBone(CSZ name) const +{ + VERUS_IF_FOUND_IN(TMapBones, _mapBones, name, it) + return &it->second; + return nullptr; +} + +Skeleton::PBone Skeleton::FindBoneByIndex(int index) +{ + for (auto& kv : _mapBones) + { + if (kv.second._shaderIndex == index) + return &kv.second; + } + return nullptr; +} + +void Skeleton::ApplyMotion(RMotion motion, float time, int numAlphaMotions, PAlphaMotion pAlphaMotions) +{ + if (_ragdollMode) + { + for (auto& kv : _mapBones) + { + RBone bone = kv.second; + if (bone._pBody) + { + bone._matFinal = _matRagdollToWorldInv * Transform3(bone._pBody->getWorldTransform())*bone._matToActorSpace; + } + else + { + bone._matFinal = Transform3::identity(); + if (bone._shaderIndex >= 0) + { + PBone pParent = FindBone(_C(bone._parentName)); + while (pParent) + { + if (pParent->_pBody) + { + bone._matFinal = _matRagdollToWorldInv * Transform3(pParent->_pBody->getWorldTransform())*pParent->_matToActorSpace; + break; + } + pParent = FindBone(_C(pParent->_parentName)); + } + } + } + bone._matFinalInv = VMath::inverse(bone._matFinal); + } + return; + } + + _pCurrentMotion = &motion; + _currentTime = time * _pCurrentMotion->GetPlaybackSpeed(); // To native time. + if (_pCurrentMotion->IsReversed()) + _currentTime = _pCurrentMotion->GetNativeDuration() - _currentTime; + + _numAlphaMotions = numAlphaMotions; + _pAlphaMotions = pAlphaMotions; + + ResetBones(); + for (auto& kv : _mapBones) + { + RBone bone = kv.second; + if (!bone._ready) + { + _pCurrentBone = &bone; + _matParents = Transform3::identity(); + RecursiveBoneUpdate(); + } + } + + // Reset blend motion! + motion.BindBlendMotion(nullptr, 0); + VERUS_FOR(i, _numAlphaMotions) + { + if (_pAlphaMotions[i]._pMotion) + _pAlphaMotions[i]._pMotion->BindBlendMotion(nullptr, 0); + } +} + +void Skeleton::FillMatrixArray(mataff* p) const +{ +#if 0 + VERUS_QREF_RENDER; + for (const auto& kv : _mapBones) + { + RcBone bone = kv.second; + if (bone._shaderIndex >= 0 && bone._shaderIndex < render.GetMaxNumBones()) + p[bone._shaderIndex] = bone._matFinal.ConstBufferFormat(); + } +#endif +} + +void Skeleton::ResetFinalPose() +{ + for (auto& kv : _mapBones) + { + kv.second._matFinal = Transform3::identity(); + kv.second._matFinalInv = Transform3::identity(); + } +} + +void Skeleton::ResetBones() +{ + for (auto& kv : _mapBones) + kv.second._ready = false; +} + +void Skeleton::RecursiveBoneUpdate() +{ + Transform3 mat; + PBone pCurrentBone = _pCurrentBone; + + Motion::PBone pMotionBone = _pCurrentMotion->FindBone(_C(pCurrentBone->_name)); + + if (pMotionBone) + { + Quat q; + Vector3 scale, euler, pos; + pMotionBone->ComputeRotationAt(_currentTime, euler, q); + pMotionBone->ComputePositionAt(_currentTime, pos); + pMotionBone->ComputeScaleAt(_currentTime, scale); + + // Blend with other motions: + VERUS_FOR(i, _numAlphaMotions) + { + if (!_pAlphaMotions[i]._pMotion) + continue; + + PMotion pAlphaMotion = _pAlphaMotions[i]._pMotion; + Motion::PBone pAlphaBone = pAlphaMotion->FindBone(_C(pCurrentBone->_name)); + const float alpha = _pAlphaMotions[i]._alpha; + float time = _pAlphaMotions[i]._time*pAlphaMotion->GetPlaybackSpeed(); // To native time. + if (pAlphaMotion->IsReversed()) + time = pAlphaMotion->GetNativeDuration() - time; + if (pAlphaBone && alpha > 0 && + (pCurrentBone->_name == _pAlphaMotions[i]._rootBone || + IsParentOf(_C(pCurrentBone->_name), _pAlphaMotions[i]._rootBone))) + { + // Alpha motion can also be in blend state. + Quat qA; + Vector3 scaleA, eulerA, posA; + pAlphaBone->ComputeRotationAt(time, eulerA, qA); + pAlphaBone->ComputePositionAt(time, posA); + pAlphaBone->ComputeScaleAt(time, scaleA); + + // Mix with alpha motion: + q = VMath::slerp(alpha, q, qA); + pos = VMath::lerp(alpha, pos, posA); + scale = VMath::lerp(alpha, scale, scaleA); + } + } + + const Transform3 matSRT = VMath::appendScale(Transform3(q, pos), scale); + const Transform3 matBone = pCurrentBone->_matExternal*matSRT; + mat = pCurrentBone->_matFromBoneSpace*matBone*pCurrentBone->_matToBoneSpace*pCurrentBone->_matAdapt; + } + else + mat = Transform3::identity(); + + _pCurrentBone = FindBone(_C(pCurrentBone->_parentName)); + if (_pCurrentBone) + { + if (_pCurrentBone->_ready) + _matParents = _pCurrentBone->_matFinal; + else + RecursiveBoneUpdate(); + } + else if (pMotionBone = _pCurrentMotion->FindBone(RootName())) + { + Quat q; + Vector3 scale, euler, pos; + pMotionBone->ComputeRotationAt(_currentTime, euler, q); + pMotionBone->ComputePositionAt(_currentTime, pos); + pMotionBone->ComputeScaleAt(_currentTime, scale); + const Transform3 matSRT = VMath::appendScale(Transform3(q, pos), scale); + mat = matSRT * mat; + } + + _matParents = _matParents * mat; + pCurrentBone->_matFinal = _matParents; + pCurrentBone->_matFinalInv = VMath::inverse(_matParents); + pCurrentBone->_ready = true; +} + +void Skeleton::VisitBones(std::function fn) +{ + for (auto& kv : _mapBones) + if (Continue::yes != fn(kv.second)) + return; +} + +void Skeleton::VisitBones(std::function fn) const +{ + for (const auto& kv : _mapBones) + if (Continue::yes != fn(kv.second)) + return; +} + +void Skeleton::InsertBonesIntoMotion(RMotion motion) const +{ + for (const auto& kv : _mapBones) + motion.InsertBone(_C(kv.first)); +} + +void Skeleton::DeleteOutsiders(RMotion motion) const +{ + const int num = motion.GetNumBones(); + Vector vNames; + VERUS_FOR(i, num) + if (_mapBones.find(_C(motion.GetBoneByIndex(i)->GetName())) == _mapBones.end() && + motion.GetBoneByIndex(i)->GetName() != RootName()) + vNames.push_back(_C(motion.GetBoneByIndex(i)->GetName())); + VERUS_FOREACH_CONST(Vector, vNames, it) + motion.DeleteBone(_C(*it)); +} + +void Skeleton::AdjustPrimaryBones(const Vector& vPrimaryBones) +{ + typedef Map TMapSort; + TMapSort mapSort; + + enum { secondaryOffset = 1000000 }; + + const bool inverse = (vPrimaryBones.end() != std::find(vPrimaryBones.begin(), vPrimaryBones.end(), "-")); + int addPri = 0; + int addSec = secondaryOffset; + if (inverse) + std::swap(addPri, addSec); + + for (const auto& kv : _mapBones) + { + RcBone bone = kv.second; + if (vPrimaryBones.end() != std::find(vPrimaryBones.begin(), vPrimaryBones.end(), bone._name)) + mapSort[bone._shaderIndex + addPri] = bone._name; + else + mapSort[bone._shaderIndex + addSec] = bone._name; + } + + _numPrimaryBones = 0; + + for (const auto& kv : mapSort) + { + if (kv.first < secondaryOffset) // Primary bone: + { + PBone pBone = FindBone(_C(kv.second)); + const int newIndex = Utils::Cast32(_mapPrimary.size()); + _mapPrimary[pBone->_shaderIndex] = newIndex; + pBone->_shaderIndex = newIndex; + _numPrimaryBones++; + } + else // Secondary bone: + { + PBone pBone = FindBone(_C(kv.second)); + PcBone pParent = FindBone(_C(pBone->_parentName)); + bool isPrimary = false; + while (!isPrimary) + { + isPrimary = (vPrimaryBones.end() != std::find(vPrimaryBones.begin(), vPrimaryBones.end(), pParent->_name)); + if (inverse) + isPrimary = !isPrimary; + if (!isPrimary) + pParent = FindBone(_C(pParent->_parentName)); + } + _mapPrimary[pBone->_shaderIndex] = pParent->_shaderIndex; + pBone->_shaderIndex = -1; // Do not set any vertex shader's register. + } + } +} + +int Skeleton::RemapBoneIndex(int index) const +{ + VERUS_IF_FOUND_IN(TMapPrimary, _mapPrimary, index, it) + return it->second; + return 0; +} + +bool Skeleton::IsParentOf(CSZ bone, CSZ parent) const +{ + PcBone pBone = FindBone(bone); + while (pBone) + { + if (pBone->_parentName == parent) + return true; + pBone = FindBone(_C(pBone->_parentName)); + } + return false; +} + +void Skeleton::LoadRigInfo(CSZ url) +{ + Vector v; + IO::FileSystem::LoadResource(url, v, IO::FileSystem::LoadDesc(true)); + LoadRigInfoFromPtr(v.data()); +} + +void Skeleton::LoadRigInfoFromPtr(const BYTE* p) +{ + for (auto& kv : _mapBones) + { + RBone bone = kv.second; + bone._rigRot = Vector3(0); + bone._cRot = Vector3(0); + bone._cLimits = Vector3(0); + bone._boxSize = Vector3(0); + bone._width = 0; + bone._length = 0; + bone._mass = 0.01f; + bone._rigBone = true; + bone._hinge = false; + bone._noCollision = false; + } + + float massCheck = 0; + if (p) + { + for (auto& kv : _mapBones) + { + RBone bone = kv.second; + bone._rigBone = false; + } + + tinyxml2::XMLDocument doc; + doc.Parse(reinterpret_cast(p)); + if (doc.Error()) + return; + + tinyxml2::XMLElement* pElem = nullptr; + tinyxml2::XMLElement* pRoot = doc.FirstChildElement(); + if (!pRoot) + return; + + pElem = pRoot->FirstChildElement("mass"); + if (pElem) + _mass = static_cast(atof(pElem->GetText())); + + for (pElem = pRoot->FirstChildElement("bone"); pElem; pElem = pElem->NextSiblingElement("bone")) + { + PBone pBone = FindBone(pElem->Attribute("name")); + if (pBone) + { + pBone->_rigBone = true; + pElem->QueryFloatAttribute("w", &pBone->_width); + pElem->QueryFloatAttribute("l", &pBone->_length); + CSZ rot = pElem->Attribute("rot"); + if (rot) + pBone->_rigRot.FromString(rot); + + CSZ climit = pElem->Attribute("climit"); + if (climit) + pBone->_cLimits.FromString(climit); + + CSZ ctype = pElem->Attribute("ctype"); + if (ctype) + { + if (!strcmp(ctype, "hinge")) + pBone->_hinge = true; + if (!strcmp(ctype, "free")) + pBone->_cLimits.setZ(-100); + } + + CSZ crot = pElem->Attribute("crot"); + if (crot) + pBone->_cRot.FromString(crot); + + CSZ boxSize = pElem->Attribute("boxSize"); + if (boxSize) + pBone->_boxSize.FromString(boxSize); + + float pmass = 0; + pElem->QueryFloatAttribute("m", &pmass); + if (pmass != 0) + pBone->_mass = pmass * _mass; + + pElem->QueryBoolAttribute("noCollision", &pBone->_noCollision); + + massCheck += pBone->_mass; + } + } + } + if (massCheck > 1000) + { + VERUS_LOG_DEBUG("Ton"); + } +} + +void Skeleton::BeginRagdoll(RcTransform3 matW, RcVector3 impulse, CSZ bone) +{ +#if 0 + VERUS_QREF_BULLET; + + EndRagdoll(); + + _matRagdollToWorld = matW; + _matRagdollToWorldInv = VMath::inverse(matW); + + // RagdollDemo values: + const float dampL = 0.05f; + const float dampA = 0.85f*1.15f; + const float daTime = 0.8f; + const float sleepL = 1.6f; + const float sleepA = 2.5f; + + for (auto& kv : _mapBones) + kv.second._ready = true; + + for (auto& kv : _mapBones) + { + PBone pBone = &kv.second; + PBone pParent = FindBone(_C(pBone->_parentName)); + + if (pParent && pParent->_rigBone && !pParent->_pShape) // Create a shape for parent bone: + { + const Point3 a = pParent->_matFromBoneSpace.getTranslation(); + const Point3 b = pBone->_matFromBoneSpace.getTranslation(); + + if (VMath::distSqr(a, b) < 0.01f*0.01f) // Too short, unsuitable? + { + pParent->_ready = false; // Mark it. + continue; + } + + // Pick the correct child bone: + if (0 == pParent->_length) + { + const Vector3 test = VMath::normalizeApprox(Vector3(pParent->_matToBoneSpace*b)); + if (test.getX() < 0.9f) // Bone should point to child bone. + continue; + } + + const float len = (pParent->_length != 0) ? + pParent->_length : Math::Max(static_cast(VMath::dist(a, b)), 0.01f); + const float w = (pParent->_width != 0) ? pParent->_width : len * 0.3f; + pParent->_length = len; + pParent->_width = w; + + if (pParent->_boxSize.IsZero()) + { + pParent->_pShape = new btCapsuleShape(w, len); + } + else + { + if (0 == pParent->_boxSize.getY()) + pParent->_boxSize.setY(len*0.5f); + pParent->_pShape = new btBoxShape(pParent->_boxSize.Bullet()); + } + + Transform3 matBody; + if (pParent->_rigRot.IsZero()) + { + matBody = pParent->_matFromBoneSpace* + Transform3(Matrix3::rotationZ(Math::ToRadians(90)), Vector3(len*0.5f, 0, 0)); + } + else + { + const Transform3 matR = Transform3::rotationZYX(pParent->_rigRot); + const Transform3 matT = Transform3(Matrix3::rotationZ(Math::ToRadians(90)), Vector3(len*0.5f, 0, 0)); + matBody = pParent->_matFromBoneSpace*matR*matT; + } + pParent->_matToActorSpace = VMath::inverse(matBody); + matBody = _matRagdollToWorld * pParent->_matFinal*matBody; + + short group = 1, mask = -1; + if (pParent->_noCollision) + group = mask = 0; + const btTransform t = matBody.Bullet(); + pParent->_pBody = bullet.AddNewRigidBody(pParent->_mass, t, pParent->_pShape, group, mask); + pParent->_pBody->setFriction(Physics::CBullet::GetFriction(Physics::Material::leather)); + pParent->_pBody->setRestitution(Physics::CBullet::GetRestitution(Physics::Material::leather)*0.5f); + pParent->_pBody->setDamping(dampL, dampA); + pParent->_pBody->setDeactivationTime(daTime); + pParent->_pBody->setSleepingThresholds(sleepL, sleepA); + } + } + + // Aligns constraint to good starting point: + const Transform3 matInitC = Transform3::rotationZYX(Vector3(-VERUS_PI / 2, 0, -VERUS_PI / 2)); + + // Create leaf actors and joints: + for (auto& kv : _mapBones) + { + PBone pBone = &kv.second; + PBone pParent = pBone; + do + { + pParent = FindBone(_C(pParent->_parentName)); + } while (pParent && (!pParent->_rigBone || !pParent->_ready)); // Skip unsuitable bones. + + bool isLeaf = false; + if (pBone->_rigBone && !pBone->_pShape && pBone->_ready) + { + const float len = (pBone->_length != 0) ? pBone->_length : 0.1f; + const float w = (pBone->_width != 0) ? pBone->_width : len * 0.3f; + pBone->_length = len; + pBone->_width = w; + + pBone->_pShape = new btCapsuleShape(w, len); // Leaf is always a capsule. + + Transform3 matBody; + if (pBone->_rigRot.IsZero()) + { + matBody = pBone->_matFromBoneSpace* + Transform3(Matrix3::rotationZ(Math::ToRadians(90)), Vector3(len*0.5f, 0, 0)); + } + else + { + const Transform3 matR = Transform3::rotationZYX(pBone->_rigRot); + const Transform3 matT = Transform3(Matrix3::rotationZ(Math::ToRadians(90)), Vector3(len*0.5f, 0, 0)); + matBody = pBone->_matFromBoneSpace*matR*matT; + } + pBone->_matToActorSpace = VMath::inverse(matBody); + matBody = _matRagdollToWorld * pBone->_matFinal*matBody; + + short group = 1, mask = -1; + if (pBone->_noCollision) + group = mask = 0; + const btTransform t = matBody.Bullet(); + pBone->_pBody = bullet.AddNewRigidBody(pBone->_mass, t, pBone->_pShape, group, mask); + pBone->_pBody->setFriction(Physics::CBullet::GetFriction(Physics::Material::leather)); + pBone->_pBody->setRestitution(Physics::CBullet::GetRestitution(Physics::Material::leather)*0.5f); + pBone->_pBody->setDamping(dampL, dampA); + pBone->_pBody->setDeactivationTime(daTime); + pBone->_pBody->setSleepingThresholds(sleepL, sleepA); + + isLeaf = true; + } + + if (pBone->_pShape && pParent && pParent->_pShape && pBone->_cLimits.getZ() > -99) // Connect bones? + { + btTransform localA, localB; + const Transform3 matToParentSpace = pParent->_matToActorSpace*VMath::inverse(pBone->_matToActorSpace); + + const Transform3 matR = Transform3::rotationZYX(pBone->_cRot); + const Transform3 matT = Transform3::translation(Vector3(0, pBone->_length*0.5f, 0)); + localA = Transform3(matT*matInitC*matR).Bullet(); + localB = Transform3(matToParentSpace*matT*matInitC*matR).Bullet(); + + if (pBone->_hinge) + { + btHingeConstraint* pHingeC = new btHingeConstraint(*pBone->_pBody, *pParent->_pBody, localA, localB); + if (!pBone->_cLimits.IsZero()) + pHingeC->setLimit(pBone->_cLimits.getX(), pBone->_cLimits.getY()); + else + pHingeC->setLimit(-VERUS_PI / 2, VERUS_PI / 2); + pBone->_pConstraint = pHingeC; + bullet.GetWorld()->addConstraint(pBone->_pConstraint, true); + } + else + { + btConeTwistConstraint* pConeC = new btConeTwistConstraint(*pBone->_pBody, *pParent->_pBody, localA, localB); + if (!pBone->_cLimits.IsZero()) + pConeC->setLimit(pBone->_cLimits.getX(), pBone->_cLimits.getY(), pBone->_cLimits.getZ(), 0.9f); + else + pConeC->setLimit(VERUS_PI / 4, VERUS_PI / 4, VERUS_PI / 4, 0.9f); + if (!isLeaf) + pConeC->setFixThresh(1); + pBone->_pConstraint = pConeC; + bullet.GetWorld()->addConstraint(pBone->_pConstraint, true); + } + } + + if (pBone->_name == bone && pBone->_pBody) + pBone->_pBody->applyCentralImpulse(impulse.Bullet()); + } + + _ragdollMode = true; +#endif +} + +void Skeleton::EndRagdoll() +{ +#if 0 + VERUS_QREF_BULLET; + + for (auto& kv : _mapBones) + { + RBone bone = kv.second; + if (bone._pConstraint) + { + bullet.GetWorld()->removeConstraint(bone._pConstraint); + delete bone._pConstraint; + bone._pConstraint = nullptr; + } + } + + for (auto& kv : _mapBones) + { + RBone bone = kv.second; + if (bone._pBody) + { + bullet.GetWorld()->removeRigidBody(bone._pBody); + delete bone._pBody->getMotionState(); + delete bone._pBody; + bone._pBody = nullptr; + } + if (bone._pShape) + { + delete bone._pShape; + bone._pShape = nullptr; + } + } + + _ragdollMode = false; +#endif +} + +void Skeleton::BakeMotion(RMotion motion, int frame, bool kinect) +{ + for (const auto& kv : _mapBones) + { + RcBone bone = kv.second; + if (kinect && !IsKinectBone(_C(bone._name))) + continue; + PcBone pParent = FindBone(_C(bone._parentName)); + + Transform3 matParent, matFromParentSpace; + if (pParent) + { + matParent = pParent->_matFinal*pParent->_matFromBoneSpace; + matFromParentSpace = pParent->_matFromBoneSpace; + } + else + { + matParent = Transform3::identity(); + matFromParentSpace = Transform3::identity(); + } + + Motion::PBone pMotionBone = motion.FindBone(_C(bone._name)); + if (!pMotionBone) + pMotionBone = motion.InsertBone(_C(bone._name)); + + // Actor in parent actor's space is transformed to bind pose space + // and then to bone space: + const Transform3 mat = + bone._matToBoneSpace*matFromParentSpace*VMath::inverse(matParent)* + bone._matFinal*bone._matFromBoneSpace; + + Quat q(mat.getUpper3x3()); + Vector3 pos = mat.getTranslation(); + + if (glm::epsilonEqual(q.getX(), 0, 1e-4f)) q.setX(0); + if (glm::epsilonEqual(q.getY(), 0, 1e-4f)) q.setY(0); + if (glm::epsilonEqual(q.getZ(), 0, 1e-4f)) q.setZ(0); + if (glm::epsilonEqual(q.getW(), 1, 1e-4f)) q.setW(1); + if (glm::epsilonEqual(pos.getX(), 0, 1e-4f)) pos.setX(0); + if (glm::epsilonEqual(pos.getY(), 0, 1e-4f)) pos.setY(0); + if (glm::epsilonEqual(pos.getZ(), 0, 1e-4f)) pos.setZ(0); + + if (!Math::IsNaN(q.getX()) && + !Math::IsNaN(q.getY()) && + !Math::IsNaN(q.getZ()) && + !Math::IsNaN(q.getW())) + pMotionBone->InsertKeyframeRotation(frame, q); + if (!kinect) + pMotionBone->InsertKeyframePosition(frame, pos); + } + + // Set values for non-kinect bones: + if (kinect) + { + Vector3 euler; + Quat q, qI(0); + Motion::PBone pSrcBone, pDstBone, pExtBone; + + pSrcBone = motion.FindBone("ShoulderLeft"); + pDstBone = motion.FindBone("ShoulderLeft0"); + if (pSrcBone && pDstBone) + { + pSrcBone->FindKeyframeRotation(frame, euler, q); + pSrcBone->InsertKeyframeRotation(frame, VMath::slerp(0.8f, qI, q)); + pDstBone->InsertKeyframeRotation(frame, VMath::slerp(0.2f, qI, q)); + } + + pSrcBone = motion.FindBone("ShoulderRight"); + pDstBone = motion.FindBone("ShoulderRight0"); + if (pSrcBone && pDstBone) + { + pSrcBone->FindKeyframeRotation(frame, euler, q); + pSrcBone->InsertKeyframeRotation(frame, VMath::slerp(0.8f, qI, q)); + pDstBone->InsertKeyframeRotation(frame, VMath::slerp(0.2f, qI, q)); + } + + pSrcBone = motion.FindBone("Spine"); + pDstBone = motion.FindBone("Spine1"); + pExtBone = motion.FindBone("Spine2"); + if (pSrcBone && pDstBone && pExtBone) + { + pSrcBone->FindKeyframeRotation(frame, euler, q); + pSrcBone->InsertKeyframeRotation(frame, VMath::slerp(0.6f, qI, q)); + pDstBone->InsertKeyframeRotation(frame, VMath::slerp(0.2f, qI, q)); + pExtBone->InsertKeyframeRotation(frame, VMath::slerp(0.2f, qI, q)); + } + } +} + +void Skeleton::AdaptBindPoseOf(RcSkeleton that) +{ + // TODO: improve. + + const Point3 origin(0); + for (auto& kv : _mapBones) + { + RBone boneDst = kv.second; + PBone pParentDst = FindBone(_C(boneDst._parentName)); + PcBone pBoneSrc = that.FindBone(_C(boneDst._name)); + PcBone pParentSrc = pBoneSrc ? that.FindBone(_C(pBoneSrc->_parentName)) : nullptr; + + if (boneDst._parentName != "Shoulder.L" && + boneDst._parentName != "Shoulder.R") + continue; + + if (pParentDst && pParentSrc) + { + const Point3 d0 = pParentDst->_matFromBoneSpace*origin; + const Point3 d1 = boneDst._matFromBoneSpace*origin; + const Point3 s0 = pParentSrc->_matFromBoneSpace*origin; + const Point3 s1 = pBoneSrc->_matFromBoneSpace*origin; + + Vector3 vd = d1 - d0; + Vector3 vs = s1 - s0; + const float ld = VMath::length(vd); + const float ls = VMath::length(vs); + if (ld < 1e-6f || ls < 1e-6f) + continue; + vd /= ld; + vs /= ls; + const Matrix3 matWorldR = Matrix3::MakeRotateTo(vd, vs); + + const Point3 offset = pParentDst->_matFromBoneSpace*origin; + const Transform3 matToBoneOrigin = Transform3::translation(-Vector3(offset)); + const Transform3 matFromBoneOrigin = Transform3::translation(Vector3(offset)); + + pParentDst->_matAdapt = matFromBoneOrigin * Transform3(matWorldR, Vector3(0))*matToBoneOrigin; + } + } +} + +void Skeleton::SimpleIK(CSZ boneDriven, CSZ boneDriver, RcVector3 dirDriverSpace, RcVector3 dirDesiredMeshSpace, float limitDot, float alpha) +{ + PBone pBoneDriven = FindBone(boneDriven); + Vector3 dirActual = dirDriverSpace; + if (boneDriver) + { + PBone pBoneDriver = FindBone(boneDriver); + dirActual = pBoneDriver->_matFinal.getUpper3x3()*pBoneDriver->_matFromBoneSpace.getUpper3x3()*dirDriverSpace; + } + Vector3 dirDesired = dirDesiredMeshSpace; + dirDesired.LimitDot(dirActual, limitDot); + const Matrix3 matR = Matrix3::MakeRotateTo( + dirActual, + VMath::normalizeApprox(VMath::lerp(alpha, dirActual, dirDesired))); + const Matrix3 matDrivenFrom = pBoneDriven->_matFinal.getUpper3x3()*pBoneDriven->_matFromBoneSpace.getUpper3x3(); + const Matrix3 matDrivenTo = VMath::inverse(matDrivenFrom); + pBoneDriven->_matExternal.setUpper3x3(matDrivenTo*matR*matDrivenFrom); +} + +void Skeleton::ProcessKinectData(const BYTE* p, RMotion motion, int frame) +{ + for (auto& kv : _mapBones) + { + kv.second._matFinal = Transform3::identity(); + kv.second._matExternal = Transform3::identity(); + } + + UINT64 timestamp; + memcpy(×tamp, p, 8); + + Vector3 skeletonPos; + float temp; + memcpy(&temp, p + 8, 4); skeletonPos.setX(temp); + memcpy(&temp, p + 12, 4); skeletonPos.setY(temp); + memcpy(&temp, p + 16, 4); skeletonPos.setZ(temp); + + ProcessKinectJoint(p + 20, "AnkleLeft", skeletonPos); + ProcessKinectJoint(p + 36, "AnkleRight", skeletonPos); + + ProcessKinectJoint(p + 52, "ElbowLeft", skeletonPos); + ProcessKinectJoint(p + 68, "ElbowRight", skeletonPos); + + ProcessKinectJoint(p + 84, "FootLeft", skeletonPos); + ProcessKinectJoint(p + 100, "FootRight", skeletonPos); + + ProcessKinectJoint(p + 116, "HandLeft", skeletonPos); + ProcessKinectJoint(p + 132, "HandRight", skeletonPos); + + ProcessKinectJoint(p + 148, "Head", skeletonPos); + + ProcessKinectJoint(p + 164, "HipCenter", skeletonPos); + ProcessKinectJoint(p + 180, "HipLeft", skeletonPos); + ProcessKinectJoint(p + 196, "HipRight", skeletonPos); + + ProcessKinectJoint(p + 212, "KneeLeft", skeletonPos); + ProcessKinectJoint(p + 228, "KneeRight", skeletonPos); + + ProcessKinectJoint(p + 244, "ShoulderCenter", skeletonPos); + ProcessKinectJoint(p + 260, "ShoulderLeft", skeletonPos); + ProcessKinectJoint(p + 276, "ShoulderRight", skeletonPos); + + ProcessKinectJoint(p + 292, "Spine", skeletonPos); + + ProcessKinectJoint(p + 308, "WristLeft", skeletonPos); + ProcessKinectJoint(p + 324, "WristRight", skeletonPos); + + // Twist: + float hipTwist = 0; + PBone pBoneL = FindBone("HipLeft"); + PBone pBoneR = FindBone("HipRight"); + if (pBoneL && pBoneR) + { + const Vector3 hipAxis = VMath::normalize( + pBoneL->_matExternal.getTranslation() - + pBoneR->_matExternal.getTranslation()); + const glm::vec3 a(1, 0, 0); + const glm::vec3 b = hipAxis.GLM(); + const glm::vec3 r(0, 1, 0); + //hipTwist = glm::orientedAngle(a, b, r); + hipTwist *= 1 - abs(hipAxis.getY()); + } + float shTwist = 0; + pBoneL = FindBone("ShoulderLeft"); + pBoneR = FindBone("ShoulderRight"); + if (pBoneL && pBoneR) + { + const Vector3 shAxis = VMath::normalize( + pBoneL->_matExternal.getTranslation() - + pBoneR->_matExternal.getTranslation()); + const glm::vec3 a(1, 0, 0); + const glm::vec3 b = shAxis.GLM(); + const glm::vec3 r(0, 1, 0); + //shTwist = glm::orientedAngle(a, b, r); + shTwist *= 1 - abs(shAxis.getY()); + } + + for (auto& kv : _mapBones) + { + RBone bone = kv.second; + if (!IsKinectBone(_C(bone._name))) + continue; +#if 0 + char xml[800]; + sprintf_s(xml, "

", _C(bone._name), + _C(bone._matExternal.GetTranslationPart().ToString3())); + VERUS_OUTPUT_DEBUG_STRING(xml); +#endif + PBone pParent = FindBone(_C(bone._parentName)); + while (pParent && !IsKinectBone(_C(pParent->_name))) + pParent = FindBone(_C(pParent->_parentName)); + if (pParent) + { + const Vector3 bindPosePos = pParent->_matFromBoneSpace.getTranslation(); + const Vector3 kinePosePos = pParent->_matExternal.getTranslation(); + const Vector3 bindPoseDir = VMath::normalize(bone._matFromBoneSpace.getTranslation() - bindPosePos); + const Vector3 kinePoseDir = VMath::normalize(bone._matExternal.getTranslation() - kinePosePos); + + Matrix3 matR3; + matR3.RotateTo(bindPoseDir, kinePoseDir); + const Transform3 matR(matR3, Vector3(0)); + const Transform3 matT = Transform3::translation(kinePosePos - bindPosePos); + const Transform3 matOffset = Transform3::translation(kinePosePos); + + // Twist: + const float twist = IsParentOf(_C(bone._parentName), "Spine") ? shTwist : hipTwist; + const float angle = kinePoseDir.getY()*twist; + const Transform3 matTwist = Transform3(Quat::rotation(angle, kinePoseDir), Vector3(0)); + + // Kinect's bind pose correction: + const Transform3 m = matTwist * matR*pParent->_matToActorSpace; + + const bool secondary = + strstr(_C(bone._name), "Left") || + strstr(_C(bone._name), "Right"); + if (!secondary || pParent->_matFinal.IsIdentity()) + pParent->_matFinal = matOffset * m*VMath::inverse(matOffset)*matT; + } + } + + VERUS_FOR(i, 4) + { + for (auto& kv : _mapBones) + { + RBone bone = kv.second; + if (IsKinectBone(_C(bone._name)) && !IsKinectLeafBone(_C(bone._name))) + continue; + PBone pParent = FindBone(_C(bone._parentName)); + if (pParent) + bone._matFinal = pParent->_matFinal; + else + bone._matFinal = Transform3::identity(); + } + } + + BakeMotion(motion, frame, true); + + for (auto& kv : _mapBones) + { + kv.second._matFinal = Transform3::identity(); + kv.second._matExternal = Transform3::identity(); + } +} + +void Skeleton::ProcessKinectJoint(const BYTE* p, CSZ name, RcVector3 skeletonPos) +{ + int id; + memcpy(&id, p, 4); + + Vector3 pos; + float temp; + memcpy(&temp, p + 4, 4); pos.setX(temp); + memcpy(&temp, p + 8, 4); pos.setY(temp); + memcpy(&temp, p + 12, 4); pos.setZ(temp); + pos -= skeletonPos; + pos += Vector3(0, 1, 0); + + pos = Transform3::rotationY(VERUS_PI)*pos; + + PBone pBone = FindBone(name); + if (pBone) + pBone->_matExternal = Transform3::translation(pos); +} + +void Skeleton::LoadKinectBindPose(CSZ xml) +{ +#if 0 + tinyxml2::XMLDocument doc; + doc.Parse(xml); + if (doc.Error()) + return; + + tinyxml2::XMLElement* pElem = nullptr; + tinyxml2::XMLElement* pRoot = doc.FirstChildElement(); + if (!pRoot) + return; + + Map mapData; + for (pElem = pRoot->FirstChildElement("p"); pElem; pElem = pElem->NextSiblingElement("p")) + { + Vector3 pos; + pos.FromString(pElem->Attribute("v")); + mapData[pElem->Attribute("n")] = pos; + } + + for (auto& kv : _mapBones) + { + RBone bone = kv.second; + if (!IsKinectBone(_C(bone._name))) + continue; + PBone pParent = FindBone(_C(bone._parentName)); + while (pParent && !IsKinectBone(_C(pParent->_name))) + pParent = FindBone(_C(pParent->_parentName)); + if (pParent) + { + const Vector3 bindPosePos = pParent->_matFromBoneSpace.getTranslation(); + const Vector3 kinePosePos = mapData[pParent->_name]; + const Vector3 bindPoseDir = VMath::normalize(bone._matFromBoneSpace.getTranslation() - bindPosePos); + const Vector3 kinePoseDir = VMath::normalize(mapData[bone._name] - kinePosePos); + + Matrix3 matR3; + matR3.RotateTo(bindPoseDir, kinePoseDir); + matR3 = VMath::inverse(matR3); + + const bool secondary = + strstr(_C(bone._name), "Left") || + strstr(_C(bone._name), "Right"); + if (!secondary || pParent->_matToActorSpace.IsIdentity()) + pParent->_matToActorSpace = Transform3(matR3, Vector3(0)); + } + } +#endif +} + +bool Skeleton::IsKinectBone(CSZ name) +{ + const CSZ names[] = + { + "AnkleLeft", + "AnkleRight", + "ElbowLeft", + "ElbowRight", + "FootLeft", + "FootRight", + "HandLeft", + "HandRight", + "Head", + "HipCenter", + "HipLeft", + "HipRight", + "KneeLeft", + "KneeRight", + "ShoulderCenter", + "ShoulderLeft", + "ShoulderRight", + "Spine", + "WristLeft", + "WristRight" + }; + return std::binary_search(names, names + VERUS_ARRAY_LENGTH(names), + name, [](CSZ a, CSZ b) {return strcmp(a, b) < 0; }); +} + +bool Skeleton::IsKinectLeafBone(CSZ name) +{ + const CSZ names[] = + { + "FootLeft", + "FootRight", + "Head", + "WristLeft", + "WristRight" + }; + return std::binary_search(names, names + VERUS_ARRAY_LENGTH(names), + name, [](CSZ a, CSZ b) {return strcmp(a, b) < 0; }); +} + +void Skeleton::FixateFeet(RMotion motion) +{ + enum class Foot : int + { + none, + left, + right + } foot = Foot::none; + + Point3 posFixed, posBase, posHeadPrev; + int frameEvent = 0; + + PBone pAnkleL = FindBone("AnkleLeft"); + PBone pAnkleR = FindBone("AnkleRight"); + PBone pHead = FindBone("Head"); + + if (!pAnkleL || !pAnkleR) + return; + + Vector vPos; + vPos.resize(motion.GetNumFrames()); + VERUS_FOR(i, motion.GetNumFrames()) + { + ApplyMotion(motion, i*motion.GetFpsInv()); + + const Point3 posAnkleL = (pAnkleL->_matFinal*pAnkleL->_matFromBoneSpace).getTranslation(); + const Point3 posAnkleR = (pAnkleR->_matFinal*pAnkleR->_matFromBoneSpace).getTranslation(); + + const Foot target = (posAnkleL.getY() < posAnkleR.getY()) ? Foot::left : Foot::right; + Point3 pos; + if (foot != target && (i - frameEvent > 5 || !i)) + { + frameEvent = i; + foot = target; + posFixed = (foot == Foot::left) ? posAnkleL : posAnkleR; + if (i) + posFixed += Vector3(vPos[i - 1]); + posFixed.setY(0.05f); + } + pos = (foot == Foot::left) ? posAnkleL : posAnkleR; + + vPos[i] = posFixed - pos; + + // Stabilize head position: + const Point3 posHead = (pHead->_matFinal*pHead->_matFromBoneSpace).getTranslation() + vPos[i]; + if (i) + { + Vector3 d = posHeadPrev - posHead; + d = VMath::mulPerElem(d, Vector3(0.5f, 0, 0.5f)); + vPos[i] += d; + } + posHeadPrev = posHead; + } + + // Add significant keyframes: + const float e = 0.01f; + Motion::PBone pRoot = motion.InsertBone(RootName()); + VERUS_FOR(i, motion.GetNumFrames()) + { + const Point3 p = vPos[i]; + const bool same = p.IsEqual(posBase, e); + if (!same) + { + posBase = p; + pRoot->InsertKeyframePosition(i, p); + } + } +} + +Vector3 Skeleton::GetHighestSpeed(RMotion motion, CSZ name, RcVector3 scale, bool positive) +{ + const int numFrames = motion.GetNumFrames(); + const float dt = motion.GetFpsInv(); + const float dti = float(motion.GetFps()); + + Vector vDX; + Vector vDY; + Vector vDZ; + vDX.reserve(numFrames); + vDY.reserve(numFrames); + vDZ.reserve(numFrames); + + Point3 prevPos(0); + VERUS_FOR(i, numFrames) + { + ApplyMotion(motion, i*dt); + PBone pBone = FindBone(name); + Point3 pos = VMath::mulPerElem(pBone->_matFromBoneSpace.getTranslation(), scale); + pos = pBone->_matFinal*pos; + if (i) + { + vDX.push_back((pos.getX() - prevPos.getX())*dti); + vDY.push_back((pos.getY() - prevPos.getY())*dti); + vDZ.push_back((pos.getZ() - prevPos.getZ())*dti); + } + prevPos = pos; + } + if (positive) + { + std::sort(vDX.begin(), vDX.end(), std::greater()); + std::sort(vDY.begin(), vDY.end(), std::greater()); + std::sort(vDZ.begin(), vDZ.end(), std::greater()); + } + else + { + std::sort(vDX.begin(), vDX.end()); + std::sort(vDY.begin(), vDY.end()); + std::sort(vDZ.begin(), vDZ.end()); + } + + const int offA = motion.GetNumFrames() / 32; + const int offB = motion.GetNumFrames() / 24; + const int offC = motion.GetNumFrames() / 16; + const int offD = motion.GetNumFrames() / 8; + return Vector3( + 0.25f*(vDX[offA] + vDX[offB] + vDX[offC] + vDX[offD]), + 0.25f*(vDY[offA] + vDY[offB] + vDY[offC] + vDY[offD]), + 0.25f*(vDZ[offA] + vDZ[offB] + vDZ[offC] + vDZ[offD])); +} diff --git a/Verus/src/Anim/Skeleton.h b/Verus/src/Anim/Skeleton.h new file mode 100644 index 0000000..8db7390 --- /dev/null +++ b/Verus/src/Anim/Skeleton.h @@ -0,0 +1,153 @@ +#pragma once + +namespace verus +{ + namespace Anim + { + //! Standard skeleton, which can be animated using motion object (Motion). + + //! X3D format can support up to 256 bones, but the hardware has certain + //! limitations. With Shader Model 2.0 the shader can hold about 32 bone + //! matrices. Other bones must be remapped using Primary Bones concept. + //! + //! Alpha Motions (AlphaMotion) add the ability to mix multiple motions, + //! just like images are mixed with alpha channel. Alpha Motion affects + //! it's root bone and all descendants of that bone. + //! + //! A ragdoll can be created automatically or configured using XML .rig file. + //! Ragdoll's simulation can start from any motion frame and any simulated pose + //! can be baked back into motion object, allowing smooth transition to ragdoll + //! simulation and back to motion. + //! + class Skeleton : public Object + { + public: + struct AlphaMotion + { + PMotion _pMotion = nullptr; + CSZ _rootBone = nullptr; + float _alpha = 0; + float _time = 0; + }; + VERUS_TYPEDEFS(AlphaMotion); + + struct Bone : public AllocatorAware + { + Transform3 _matToBoneSpace = Transform3::identity(); + Transform3 _matFromBoneSpace = Transform3::identity(); + Transform3 _matFinal = Transform3::identity(); + Transform3 _matFinalInv = Transform3::identity(); + Transform3 _matExternal = Transform3::identity(); + Transform3 _matAdapt = Transform3::identity(); + Transform3 _matToActorSpace = Transform3::identity(); + Vector3 _rigRot = Vector3(0); //!< Rotation angles of ragdoll's rigid component. + Vector3 _cRot = Vector3(0); //!< Constraint's rotation angles. + Vector3 _cLimits = Vector3(0); //!< Constraint's limits. + Vector3 _boxSize = Vector3(0); //!< For a box shape. + String _name; + String _parentName; + btCollisionShape* _pShape = nullptr; + btRigidBody* _pBody = nullptr; + btTypedConstraint* _pConstraint = nullptr; + float _width = 0; + float _length = 0; + float _mass = 0; + int _shaderIndex = 0; //!< Index of a matrix in the vertex shader. + bool _ready = false; + bool _rigBone = true; + bool _hinge = false; //!< btHingeConstraint vs btConeTwistConstraint. + bool _noCollision = false; + }; + VERUS_TYPEDEFS(Bone); + + private: + typedef Map TMapBones; + typedef Map TMapPrimary; + + Transform3 _matParents = Transform3::identity(); + Transform3 _matRagdollToWorld = Transform3::identity(); + Transform3 _matRagdollToWorldInv = Transform3::identity(); + TMapBones _mapBones; + TMapPrimary _mapPrimary; + PBone _pCurrentBone = nullptr; + PMotion _pCurrentMotion = nullptr; + PAlphaMotion _pAlphaMotions = nullptr; + float _currentTime = 0; + float _mass = 0; + int _numPrimaryBones = 0; + int _numAlphaMotions = 0; + bool _ragdollMode = false; + + public: + Skeleton(); + ~Skeleton(); + + void operator=(const Skeleton& that); + + void Init(); + void Done(); + + void Draw(bool bindPose = true, int selected = -1); + + static CSZ RootName() { return "$ROOT"; } + + PBone InsertBone(RBone bone); + PBone FindBone(CSZ name); + PcBone FindBone(CSZ name) const; + //! Uses shader's array index to find a bone. + PBone FindBoneByIndex(int index); + + //! Sets the current pose using motion object (Motion). + void ApplyMotion(RMotion motion, float time, + int numAlphaMotions = 0, PAlphaMotion pAlphaMotions = nullptr); + + //! Fills the array of matrices that will be used by a shader. + void FillMatrixArray(mataff* p) const; + + void ResetFinalPose(); + + VERUS_P(void ResetBones()); + VERUS_P(void RecursiveBoneUpdate()); + + int GetNumBones() const { return _numPrimaryBones ? _numPrimaryBones : Utils::Cast32(_mapBones.size()); } + + void VisitBones(std::function fn); + void VisitBones(std::function fn) const; + + //! Adds skeleton's bones to motion object (Motion). + void InsertBonesIntoMotion(RMotion motion) const; + //! Removes motion's bones, which are not skeleton's bones. + void DeleteOutsiders(RMotion motion) const; + + void AdjustPrimaryBones(const Vector& vPrimaryBones); + int RemapBoneIndex(int index) const; + + bool IsParentOf(CSZ bone, CSZ parent) const; + + void LoadRigInfo(CSZ url); + void LoadRigInfoFromPtr(const BYTE* p); + void BeginRagdoll(RcTransform3 matW, RcVector3 impulse = Vector3(0), CSZ bone = "Spine2"); + void EndRagdoll(); + bool IsRagdollMode() const { return _ragdollMode; } + RcTransform3 GetRagdollToWorldMatrix() { return _matRagdollToWorld; } + + //! Saves the current pose into motion object (Motion) at some frame. + void BakeMotion(RMotion motion, int frame = 0, bool kinect = false); + void AdaptBindPoseOf(const Skeleton& that); + void SimpleIK(CSZ boneDriven, CSZ boneDriver, RcVector3 dirDriverSpace, RcVector3 dirDesiredMeshSpace, float limitDot, float alpha); + + void ProcessKinectData(const BYTE* p, RMotion motion, int frame = 0); + void ProcessKinectJoint(const BYTE* p, CSZ name, RcVector3 skeletonPos); + void LoadKinectBindPose(CSZ xml); + static bool IsKinectBone(CSZ name); + static bool IsKinectLeafBone(CSZ name); + + void FixateFeet(RMotion motion); + + //! Tries to compute the highest speed at which a bone would move with this motion applied. + //! Can be used to sync animation and movement to fix the sliding feet problem. + Vector3 GetHighestSpeed(RMotion motion, CSZ name, RcVector3 scale = Vector3(1, 0, 1), bool positive = false); + }; + VERUS_TYPEDEFS(Skeleton); + } +} diff --git a/Verus/src/App/App.cpp b/Verus/src/App/App.cpp new file mode 100644 index 0000000..05879c9 --- /dev/null +++ b/Verus/src/App/App.cpp @@ -0,0 +1,52 @@ +#include "verus.h" + +namespace verus +{ + void Make_App() + { + } + void Free_App() + { + } +} + +using namespace verus; + +void App::RunEventLoop() +{ + SDL_Event event = {}; + bool done = false; + + do + { + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_QUIT: + { + done = true; + } + break; + case SDL_WINDOWEVENT: + { + switch (event.window.event) + { + case SDL_WINDOWEVENT_MINIMIZED: + break; + case SDL_WINDOWEVENT_RESTORED: + break; + } + } + break; + case SDL_USEREVENT: + { + } + break; + } + } + + if (done) + break; + } while (!done); +} diff --git a/Verus/src/App/App.h b/Verus/src/App/App.h new file mode 100644 index 0000000..88fc6a2 --- /dev/null +++ b/Verus/src/App/App.h @@ -0,0 +1,18 @@ +#pragma once + +#include "Settings.h" +#include "Window.h" + +namespace verus +{ + void Make_App(); + void Free_App(); +} + +namespace verus +{ + namespace App + { + void RunEventLoop(); + } +} diff --git a/Verus/src/App/Settings.cpp b/Verus/src/App/Settings.cpp new file mode 100644 index 0000000..10749d3 --- /dev/null +++ b/Verus/src/App/Settings.cpp @@ -0,0 +1,272 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::App; + +Settings::Settings() +{ + VERUS_ZERO_MEM(_commandLine); +#ifdef _WIN32 + if (0x419 == GetUserDefaultUILanguage()) + _uiLang = "RU"; +#endif + SetQuality(Quality::medium); +} + +Settings::~Settings() +{ +} + +void Settings::LoadValidateSave() +{ + Load(); + Validate(); + Save(); +} + +void Settings::Load() +{ + Json::Load(); + + _quality = static_cast(GetI("quality", +_quality)); + _direct3D = GetI("direct3D", _direct3D); + _gpuAnisotropyLevel = GetI("gpuAnisotropyLevel", _gpuAnisotropyLevel); + _gpuAntialiasingLevel = GetI("gpuAntialiasingLevel", _gpuAntialiasingLevel); + _gpuDepthTexture = GetI("gpuDepthTexture", _gpuDepthTexture); + _gpuForcedProfile = GetS("gpuForcedProfile", _C(_gpuForcedProfile)); + _gpuOffscreenRender = GetB("gpuOffscreenRender", _gpuOffscreenRender); + _gpuParallaxMapping = GetB("gpuParallaxMapping", _gpuParallaxMapping); + _gpuPerPixelLighting = GetB("gpuPerPixelLighting", _gpuPerPixelLighting); + _gpuTextureLodLevel = GetI("gpuTextureLodLevel", _gpuTextureLodLevel); + _gpuTrilinearFilter = GetB("gpuTrilinearFilter", _gpuTrilinearFilter); + _inputMouseSensitivity = GetF("inputMouseSensitivity", _inputMouseSensitivity); + _postProcessBloom = GetB("postProcessBloom", _postProcessBloom); + _postProcessCinema = GetB("postProcessCinema", _postProcessCinema); + _postProcessMotionBlur = GetB("postProcessMotionBlur", _postProcessMotionBlur); + _postProcessSSAO = GetB("postProcessSSAO", _postProcessSSAO); + _sceneGrassDensity = GetI("sceneGrassDensity", _sceneGrassDensity); + _sceneShadowQuality = static_cast(GetI("sceneShadowQuality", +_sceneShadowQuality)); + _sceneWaterQuality = static_cast(GetI("sceneWaterQuality", +_sceneWaterQuality)); + _screenFOV = GetF("screenFOV", _screenFOV); + _screenSizeHeight = GetI("screenSizeHeight", _screenSizeHeight); + _screenSizeWidth = GetI("screenSizeWidth", _screenSizeWidth); + _screenVSync = GetB("screenVSync", _screenVSync); + _screenWindowed = GetB("screenWindowed", _screenWindowed); + _uiLang = GetS("uiLang", _C(_uiLang)); +} + +void Settings::Save() +{ + Set("quality", +_quality); + Set("direct3D", _direct3D); + Set("gpuAnisotropyLevel", _gpuAnisotropyLevel); + Set("gpuAntialiasingLevel", _gpuAntialiasingLevel); + Set("gpuDepthTexture", _gpuDepthTexture); + Set("gpuForcedProfile", _C(_gpuForcedProfile)); + Set("gpuOffscreenRender", _gpuOffscreenRender); + Set("gpuParallaxMapping", _gpuParallaxMapping); + Set("gpuPerPixelLighting", _gpuPerPixelLighting); + Set("gpuTextureLodLevel", _gpuTextureLodLevel); + Set("gpuTrilinearFilter", _gpuTrilinearFilter); + Set("inputMouseSensitivity", _inputMouseSensitivity); + Set("postProcessBloom", _postProcessBloom); + Set("postProcessCinema", _postProcessCinema); + Set("postProcessMotionBlur", _postProcessMotionBlur); + Set("postProcessSSAO", _postProcessSSAO); + Set("sceneGrassDensity", _sceneGrassDensity); + Set("sceneShadowQuality", +_sceneShadowQuality); + Set("sceneWaterQuality", +_sceneWaterQuality); + Set("screenFOV", _screenFOV); + Set("screenSizeHeight", _screenSizeHeight); + Set("screenSizeWidth", _screenSizeWidth); + Set("screenVSync", _screenVSync); + Set("screenWindowed", _screenWindowed); + Set("uiLang", _C(_uiLang)); + + Json::Save(); +} + +void Settings::Validate() +{ + _quality = Math::Clamp(_quality, Quality::low, Quality::ultra); + _gpuAnisotropyLevel = Math::Clamp(_gpuAnisotropyLevel, 0, 16); + _gpuAntialiasingLevel = Math::Clamp(_gpuAntialiasingLevel, 0, 16); + _gpuTextureLodLevel = Math::Clamp(_gpuTextureLodLevel, 0, 4); + _screenFOV = Math::Clamp(_screenFOV, 60, 90); + _screenSizeHeight = Math::Clamp(_screenSizeHeight, 270, 0x2000); + _screenSizeWidth = Math::Clamp(_screenSizeWidth, 480, 0x2000); + + if (_gpuForcedProfile == "sm_2_0") + { + _postProcessMotionBlur = false; + _postProcessSSAO = false; + _sceneShadowQuality = Math::Min(_sceneShadowQuality, ShadowQuality::filtered); + } + + if (!_gpuPerPixelLighting) + { + _gpuParallaxMapping = false; + _sceneWaterQuality = Math::Clamp(_sceneWaterQuality, WaterQuality::solidColor, WaterQuality::distortedReflection); + } + _postProcessBloom = _postProcessBloom && _gpuOffscreenRender; + _sceneGrassDensity = Math::Clamp(_sceneGrassDensity, 15, 1500); + _sceneShadowQuality = Math::Clamp(_sceneShadowQuality, ShadowQuality::none, ShadowQuality::cascaded); + _sceneWaterQuality = Math::Clamp(_sceneWaterQuality, WaterQuality::solidColor, WaterQuality::trueWavesRefraction); + + if (_sceneShadowQuality >= ShadowQuality::cascaded) + _gpuDepthTexture = 1; + + if (_uiLang != "RU" && _uiLang != "EN") + _uiLang = "EN"; +} + +void Settings::ParseCommandLineArgs(int argc, wchar_t* argv[]) +{ + Vector v; + VERUS_FOR(i, argc) + v.push_back(Str::WideToUtf8(argv[i])); + + Vector vArgv; + VERUS_FOR(i, argc) + vArgv.push_back(const_cast(_C(v[i]))); + + ParseCommandLineArgs(argc, vArgv.data()); +} + +void Settings::ParseCommandLineArgs(int argc, char* argv[]) +{ + auto IsArg = [&argv](int i, CSZ arg) + { + return !strcmp(argv[i], arg); + }; + + VERUS_FOR(i, argc) + { + _commandLine.normalMapOnlyLS = _commandLine.normalMapOnlyLS || IsArg(i, "--normal-map-only-ls"); + _commandLine.physicsDebug = _commandLine.physicsDebug || IsArg(i, "--physics-debug"); + _commandLine.physicsDebugFull = _commandLine.physicsDebugFull || IsArg(i, "--physics-debug-full"); + _commandLine.profiler = _commandLine.profiler || IsArg(i, "--profiler"); + _commandLine.testTexToPix = _commandLine.testTexToPix || IsArg(i, "--test-tex-to-pix"); + } + + VERUS_QREF_UTILS; + //utils.SetupPathsEx(); // Only now we can finally do this. + + SetFilename("Settings.json"); + const String pathName = _C(GetFilename()); + + VERUS_FOR(i, argc) + { + if (IsArg(i, "--q-low")) + { + const bool ret = IO::FileSystem::Delete(_C(pathName)); + SetQuality(Quality::low); + } + if (IsArg(i, "--q-medium")) + { + const bool ret = IO::FileSystem::Delete(_C(pathName)); + SetQuality(Quality::medium); + } + if (IsArg(i, "--q-high")) + { + const bool ret = IO::FileSystem::Delete(_C(pathName)); + SetQuality(Quality::high); + } + if (IsArg(i, "--q-ultra")) + { + const bool ret = IO::FileSystem::Delete(_C(pathName)); + SetQuality(Quality::ultra); + } + } +} + +void Settings::SetQuality(Quality q) +{ + _quality = q; + _direct3D = 9; + _gpuAnisotropyLevel = 0; + _gpuAntialiasingLevel = 0; + _gpuDepthTexture = 0; + _gpuForcedProfile = ""; + _gpuOffscreenRender = true; + _gpuParallaxMapping = false; + _gpuPerPixelLighting = true; + _gpuTextureLodLevel = 0; + _gpuTrilinearFilter = false; + _postProcessBloom = false; + _postProcessCinema = false; + _postProcessMotionBlur = false; + _postProcessSSAO = false; + _sceneGrassDensity = 1000; + _sceneShadowQuality = ShadowQuality::sharp; + _sceneWaterQuality = WaterQuality::distortedReflection; + _screenVSync = false; + _screenWindowed = true; + + switch (q) + { + case Quality::low: + _gpuDepthTexture = -1; + _gpuForcedProfile = "sm_3_0"; + _sceneGrassDensity = 500; + _sceneShadowQuality = ShadowQuality::none; + _sceneWaterQuality = WaterQuality::solidColor; + _screenSizeHeight = 600; + _screenSizeWidth = 800; + break; + case Quality::medium: + _gpuAnisotropyLevel = 4; + _gpuForcedProfile = "sm_3_0"; + _screenSizeHeight = 768; + _screenSizeWidth = 1024; + break; + case Quality::high: + _direct3D = 11; + _gpuAnisotropyLevel = 8; + _gpuTrilinearFilter = true; + _postProcessBloom = true; + _postProcessCinema = true; + _postProcessSSAO = true; + _sceneGrassDensity = 1500; + _sceneShadowQuality = ShadowQuality::filtered; + _sceneWaterQuality = WaterQuality::trueWavesReflection; + _screenSizeHeight = 720; + _screenSizeWidth = 1280; + break; + case Quality::ultra: + _direct3D = 11; + _gpuAnisotropyLevel = 16; + _gpuDepthTexture = 1; + _gpuTrilinearFilter = true; + _postProcessBloom = true; + _postProcessCinema = true; + _postProcessMotionBlur = true; + _postProcessSSAO = true; + _sceneGrassDensity = 1500; + _sceneShadowQuality = ShadowQuality::cascaded; + _sceneWaterQuality = WaterQuality::trueWavesRefraction; + _screenSizeHeight = 1080; + _screenSizeWidth = 1980; + break; + } + +#ifdef _WIN32 +# ifndef _DEBUG + _screenSizeHeight = GetSystemMetrics(SM_CYSCREEN); + _screenSizeWidth = GetSystemMetrics(SM_CXSCREEN); + _screenWindowed = false; +# endif +#else // Linux? + _direct3D = 0; + _gpuForcedProfile = "arb1"; +#endif +} + +void Settings::MatchScreen() +{ + SDL_DisplayMode dm; + if (SDL_GetCurrentDisplayMode(0, &dm) < 0) + throw VERUS_RUNTIME_ERROR << "SDL_GetCurrentDisplayMode(), " << SDL_GetError(); + _screenSizeWidth = dm.w; + _screenSizeHeight = dm.h; +} diff --git a/Verus/src/App/Settings.h b/Verus/src/App/Settings.h new file mode 100644 index 0000000..0c2638b --- /dev/null +++ b/Verus/src/App/Settings.h @@ -0,0 +1,86 @@ +#pragma once + +namespace verus +{ + namespace App + { + class Settings : public Singleton, public IO::Json + { + public: + enum class Quality : int + { + low, + medium, + high, + ultra + }; + + enum class ShadowQuality : int + { + none, + sharp, + filtered, + cascaded + }; + + enum class WaterQuality : int + { + solidColor, + simpleReflection, + distortedReflection, + trueWavesReflection, + trueWavesRefraction + }; + + Quality _quality = Quality::medium; + int _direct3D = 9; + int _gpuAnisotropyLevel = 4; + int _gpuAntialiasingLevel = 0; + int _gpuDepthTexture = 0; + String _gpuForcedProfile = "sm_3_0"; + bool _gpuOffscreenRender = false; + bool _gpuParallaxMapping = false; + bool _gpuPerPixelLighting = true; + int _gpuTextureLodLevel = 0; + bool _gpuTrilinearFilter = false; + float _inputMouseSensitivity = 1; + bool _postProcessBloom = false; + bool _postProcessCinema = false; + bool _postProcessMotionBlur = false; + bool _postProcessSSAO = false; + int _sceneGrassDensity = 1000; + ShadowQuality _sceneShadowQuality = ShadowQuality::sharp; + WaterQuality _sceneWaterQuality = WaterQuality::solidColor; + float _screenFOV = 70; + int _screenSizeHeight = 720; + int _screenSizeWidth = 1280; + bool _screenVSync = false; + bool _screenWindowed = true; + String _uiLang = "EN"; + struct + { + bool normalMapOnlyLS; + bool physicsDebug; + bool physicsDebugFull; + bool profiler; + bool testTexToPix; + } _commandLine; + + Settings(); + ~Settings(); + + void LoadValidateSave(); + void Load(); + void Save(); + void Validate(); + + void ParseCommandLineArgs(int argc, wchar_t* argv[]); + void ParseCommandLineArgs(int argc, char* argv[]); + + void SetQuality(Quality q); + + void MatchScreen(); + }; + VERUS_TYPEDEFS(Settings); + } +} diff --git a/Verus/src/App/Window.cpp b/Verus/src/App/Window.cpp new file mode 100644 index 0000000..f1f06e5 --- /dev/null +++ b/Verus/src/App/Window.cpp @@ -0,0 +1,58 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::App; + +// Window::Desc: + +void Window::Desc::ApplySettings() +{ + VERUS_QREF_SETTINGS; + _width = settings._screenSizeWidth; + _height = settings._screenSizeHeight; + _fullscreen = !settings._screenWindowed; +} + +// Window: + +Window::Window() +{ +} + +Window::~Window() +{ + Done(); +} + +void Window::Init(RcDesc descConst) +{ + VERUS_INIT(); + + Desc desc = descConst; + if (desc._useSettings) + desc.ApplySettings(); + + Uint32 flags = 0; + if (desc._fullscreen) + flags |= SDL_WINDOW_FULLSCREEN; + + _pWnd = SDL_CreateWindow( + desc._title ? desc._title : "", + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + desc._width, + desc._height, + flags); + if (!_pWnd) + throw VERUS_RECOVERABLE << "SDL_CreateWindow(), " << SDL_GetError(); +} + +void Window::Done() +{ + if (_pWnd) + { + SDL_DestroyWindow(_pWnd); + _pWnd = nullptr; + } + VERUS_DONE(Window); +} diff --git a/Verus/src/App/Window.h b/Verus/src/App/Window.h new file mode 100644 index 0000000..c04c786 --- /dev/null +++ b/Verus/src/App/Window.h @@ -0,0 +1,31 @@ +#pragma once + +namespace verus +{ + namespace App + { + class Window : public Object + { + SDL_Window* _pWnd = nullptr; + + public: + struct Desc + { + CSZ _title = nullptr; + int _width = 0; + int _height = 0; + bool _fullscreen = false; + bool _useSettings = true; + + void ApplySettings(); + }; + VERUS_TYPEDEFS(Desc); + + Window(); + ~Window(); + + void Init(RcDesc desc = Desc()); + void Done(); + }; + } +} diff --git a/Verus/src/Audio/Audio.cpp b/Verus/src/Audio/Audio.cpp new file mode 100644 index 0000000..3da5143 --- /dev/null +++ b/Verus/src/Audio/Audio.cpp @@ -0,0 +1,13 @@ +#include "verus.h" + +namespace verus +{ + void Make_Audio() + { + Audio::AudioSystem::Make(); + } + void Free_Audio() + { + Audio::AudioSystem::Free(); + } +} diff --git a/Verus/src/Audio/Audio.h b/Verus/src/Audio/Audio.h new file mode 100644 index 0000000..43b3f80 --- /dev/null +++ b/Verus/src/Audio/Audio.h @@ -0,0 +1,13 @@ +#pragma once + +#include "OggCallbacks.h" +#include "Source.h" +#include "Sound.h" +#include "StreamPlayer.h" +#include "AudioSystem.h" + +namespace verus +{ + void Make_Audio(); + void Free_Audio(); +} diff --git a/Verus/src/Audio/AudioSystem.cpp b/Verus/src/Audio/AudioSystem.cpp new file mode 100644 index 0000000..382e0b7 --- /dev/null +++ b/Verus/src/Audio/AudioSystem.cpp @@ -0,0 +1,142 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Audio; + +AudioSystem::AudioSystem() +{ +} + +AudioSystem::~AudioSystem() +{ + Done(); +} + +void AudioSystem::Init() +{ + VERUS_INIT(); + + const ALCchar* deviceName = nullptr; +#ifdef _WIN32 + deviceName = "DirectSound3D"; +#endif + + if (!(_pDevice = alcOpenDevice(deviceName))) + throw VERUS_RUNTIME_ERROR << "alcOpenDevice()"; + + if (!(_pContext = alcCreateContext(_pDevice, nullptr))) + throw VERUS_RUNTIME_ERROR << "alcCreateContext(), " << alcGetError(_pDevice); + + if (!alcMakeContextCurrent(_pContext)) + throw VERUS_RUNTIME_ERROR << "alcMakeContextCurrent(), " << alcGetError(_pDevice); + + int vMajor = 0, vMinor = 0; + alcGetIntegerv(_pDevice, ALC_MAJOR_VERSION, sizeof(vMajor), &vMajor); + alcGetIntegerv(_pDevice, ALC_MINOR_VERSION, sizeof(vMinor), &vMinor); + StringStream ss; + ss << vMajor << "." << vMinor; + _version = ss.str(); + + _deviceSpecifier = alcGetString(_pDevice, ALC_DEVICE_SPECIFIER); +} + +void AudioSystem::Done() +{ + DeleteAllStreams(); + DeleteAllSounds(); + + alcMakeContextCurrent(0); + if (_pContext) + { + alcDestroyContext(_pContext); + _pContext = nullptr; + } + if (_pDevice) + { + alcCloseDevice(_pDevice); + _pDevice = nullptr; + } + + VERUS_DONE(AudioSystem); +} + +void AudioSystem::Update() +{ + if (!IsInitialized()) + return; + VERUS_UPDATE_ONCE_CHECK; + + VERUS_QREF_TIMER; + + VERUS_FOR(i, VERUS_ARRAY_LENGTH(_streamPlayers)) + _streamPlayers[i].Update(); + + // Update every frame: + for (auto& x : TStoreSounds::_map) + x.second.Update(); + + // Update ~15 times per second: + if (timer.IsEventEvery(67)) + { + for (auto& x : TStoreSounds::_map) + x.second.UpdateHRTF(); + + if (VMath::lengthSqr(_listenerDirection) > 0.1f && + VMath::lengthSqr(_listenerUp) > 0.1f) + { + const float orien[6] = + { + _listenerDirection.getX(), + _listenerDirection.getY(), + _listenerDirection.getZ(), + _listenerUp.getX(), + _listenerUp.getY(), + _listenerUp.getZ() + }; + alListenerfv(AL_POSITION, _listenerPosition.ToPointer()); + alListenerfv(AL_VELOCITY, _listenerVelocity.ToPointer()); + alListenerfv(AL_ORIENTATION, orien); // Zero-length vectors can crash OpenAL! + } + } +} + +void AudioSystem::DeleteAllStreams() +{ + VERUS_FOR(i, VERUS_ARRAY_LENGTH(_streamPlayers)) + _streamPlayers[i].Done(); +} + +PSound AudioSystem::InsertSound(CSZ url) +{ + return TStoreSounds::Insert(url); +} + +PSound AudioSystem::FindSound(CSZ url) +{ + return TStoreSounds::Find(url); +} + +void AudioSystem::DeleteSound(CSZ url) +{ + TStoreSounds::Delete(url); +} + +void AudioSystem::DeleteAllSounds() +{ + TStoreSounds::DeleteAll(); +} + +void AudioSystem::UpdateListener(RcPoint3 pos, RcVector3 dir, RcVector3 vel, RcVector3 up) +{ + _listenerPosition = pos; + _listenerDirection = dir; + _listenerVelocity = vel; + _listenerUp = up; +} + +float AudioSystem::ComputeTravelDelay(RcPoint3 pos) const +{ + const float speed = alGetFloat(AL_SPEED_OF_SOUND); + const float dist = VMath::dist(pos, _listenerPosition); + return Math::Clamp(dist / speed, 0, 3); +} diff --git a/Verus/src/Audio/AudioSystem.h b/Verus/src/Audio/AudioSystem.h new file mode 100644 index 0000000..5205ef9 --- /dev/null +++ b/Verus/src/Audio/AudioSystem.h @@ -0,0 +1,44 @@ +#pragma once + +namespace verus +{ + namespace Audio + { + typedef StoreUnique TStoreSounds; + class AudioSystem : public Singleton, public Object, private TStoreSounds + { + Point3 _listenerPosition = Point3(0); + Vector3 _listenerDirection = Vector3(0, 0, 1); + Vector3 _listenerVelocity = Vector3(0); + Vector3 _listenerUp = Vector3(0, 1, 0); + ALCdevice* _pDevice = nullptr; + ALCcontext* _pContext = nullptr; + String _version; + String _deviceSpecifier; + StreamPlayer _streamPlayers[4]; + + public: + AudioSystem(); + ~AudioSystem(); + + void Init(); + void Done(); + + void Update(); + + RStreamPlayer GetStreamPlayer(int index) { return _streamPlayers[index]; } + void DeleteAllStreams(); + + PSound InsertSound(CSZ url); + PSound FindSound(CSZ url); + void DeleteSound(CSZ url); + void DeleteAllSounds(); + + void UpdateListener(RcPoint3 pos, RcVector3 dir, RcVector3 vel, RcVector3 up); + float ComputeTravelDelay(RcPoint3 pos) const; + + static CSZ GetSingletonFailMessage() { return "Make_Audio(); // FAIL.\r\n"; } + }; + VERUS_TYPEDEFS(AudioSystem); + } +} diff --git a/Verus/src/Audio/OggCallbacks.cpp b/Verus/src/Audio/OggCallbacks.cpp new file mode 100644 index 0000000..496930b --- /dev/null +++ b/Verus/src/Audio/OggCallbacks.cpp @@ -0,0 +1,46 @@ +#include "verus.h" + +namespace verus +{ + namespace Audio + { + size_t read_func(void* ptr, size_t size, size_t nmemb, void* datasource) + { + POggDataSource pDS = static_cast(datasource); + INT64 readLen = size * nmemb; + const INT64 maxLen = pDS->_size - pDS->_cursor; + if (readLen > maxLen) + readLen = maxLen; + memcpy(ptr, pDS->_p + pDS->_cursor, size_t(readLen)); + pDS->_cursor += readLen; + return size_t(readLen); + } + + int seek_func(void* datasource, ogg_int64_t offset, int whence) + { + POggDataSource pDS = static_cast(datasource); + switch (whence) + { + case SEEK_SET: pDS->_cursor = offset; return 0; + case SEEK_CUR: pDS->_cursor += offset; return 0; + case SEEK_END: pDS->_cursor = pDS->_size - offset; return 0; + } + return -1; + } + + int close_func(void* datasource) + { + POggDataSource pDS = static_cast(datasource); + pDS->_cursor = 0; + return 0; + } + + long tell_func(void* datasource) + { + POggDataSource pDS = static_cast(datasource); + return long(pDS->_cursor); + } + + const ov_callbacks g_oggCallbacks = { read_func, seek_func, close_func, tell_func }; + } +} diff --git a/Verus/src/Audio/OggCallbacks.h b/Verus/src/Audio/OggCallbacks.h new file mode 100644 index 0000000..e197c32 --- /dev/null +++ b/Verus/src/Audio/OggCallbacks.h @@ -0,0 +1,22 @@ +#pragma once + +namespace verus +{ + namespace Audio + { + struct OggDataSource + { + const BYTE* _p = nullptr; + INT64 _size = 0; + INT64 _cursor = 0; + }; + VERUS_TYPEDEFS(OggDataSource); + + size_t read_func(void*, size_t, size_t, void*); + int seek_func(void*, ogg_int64_t, int); + int close_func(void*); + long tell_func(void*); + + extern const ov_callbacks g_oggCallbacks; + } +} diff --git a/Verus/src/Audio/Sound.cpp b/Verus/src/Audio/Sound.cpp new file mode 100644 index 0000000..875d65b --- /dev/null +++ b/Verus/src/Audio/Sound.cpp @@ -0,0 +1,183 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Audio; + +// Sound: + +Sound::Sound() +{ +} + +Sound::~Sound() +{ + Done(); +} + +void Sound::Init(RcDesc desc) +{ + if (_refCount) + return; + + VERUS_INIT(); + + _url = desc._url; + _refCount = 1; + if (desc._is3D) SetFlag(SoundFlags::is3D); + if (desc._loop) SetFlag(SoundFlags::loop); + _gain = desc._gain; + _pitch = desc._pitch; + _referenceDistance = desc._referenceDistance; + if (desc._randomOffset) SetFlag(SoundFlags::randOff); + + IO::Async::I().Load(desc._url, this); +} + +bool Sound::Done() +{ + _refCount--; + if (_refCount <= 0) + { + IO::Async::Cancel(this); + VERUS_FOR(i, VERUS_ARRAY_LENGTH(_sources)) + _sources[i].Done(); + if (_buffer) + alDeleteBuffers(1, &_buffer); + VERUS_DONE(Sound); + return true; + } + return false; +} + +void Sound::Async_Run(CSZ url, RcBlob blob) +{ + VERUS_RT_ASSERT(_url == url); + VERUS_RT_ASSERT(!_buffer); + VERUS_RT_ASSERT(blob._size); + + OggDataSource oggds; + oggds._p = blob._p; + oggds._size = blob._size; + + OggVorbis_File ovf; + vorbis_info* povi; + const int ret = ov_open_callbacks(&oggds, &ovf, 0, 0, g_oggCallbacks); + if (ret < 0) + throw VERUS_RUNTIME_ERROR << "ov_open_callbacks(), " << ret; + + povi = ov_info(&ovf, -1); + const ALenum format = (povi->channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16; + + alGenBuffers(1, &_buffer); + + _length = static_cast(ov_time_total(&ovf, -1)); + const INT64 pcmSize = ov_pcm_total(&ovf, -1) * 2 * povi->channels + 1; + Vector vRaw; + vRaw.resize(pcmSize); + int bitstream, offset = 0; + long num = ov_read(&ovf, reinterpret_cast(vRaw.data()), Utils::Cast32(vRaw.size()), 0, 2, 1, &bitstream); + do + { + offset += num; + num = ov_read(&ovf, reinterpret_cast(&vRaw[offset]), Utils::Cast32(vRaw.size()) - offset, 0, 2, 1, &bitstream); + } while (num > 0); + alBufferData(_buffer, format, vRaw.data(), offset, povi->rate); + ov_clear(&ovf); + + SetFlag(SoundFlags::loaded); +} + +void Sound::Update() +{ + if (!IsLoaded()) + return; + VERUS_UPDATE_ONCE_CHECK; + + VERUS_FOR(i, VERUS_ARRAY_LENGTH(_sources)) + _sources[i].Update(); +} + +void Sound::UpdateHRTF() +{ + if (!IsLoaded()) + return; + const bool is3D = IsFlagSet(SoundFlags::is3D); + VERUS_FOR(i, VERUS_ARRAY_LENGTH(_sources)) + _sources[i].UpdateHRTF(is3D); +} + +SourcePtr Sound::NewSource(PSourcePtr pID, Source::RcDesc desc) +{ + SourcePtr source; + if (IsLoaded()) + { + source.Attach(_sources + _next, this); + VERUS_CIRCULAR_ADD(_next, VERUS_ARRAY_LENGTH(_sources)); + + if (source->_sid) // Delete existing? + alDeleteSources(1, &source->_sid); + + alGenSources(1, &source->_sid); + const ALuint sid = source->_sid; + if (IsFlagSet(SoundFlags::is3D)) + { + alSourcef(sid, AL_REFERENCE_DISTANCE, _referenceDistance); + alSourcef(sid, AL_ROLLOFF_FACTOR, 4); + } + else // No 3D effect? + { + alSourcei(sid, AL_SOURCE_RELATIVE, AL_TRUE); + alSource3f(sid, AL_POSITION, 0, 0, 0); + alSource3f(sid, AL_VELOCITY, 0, 0, 0); + alSourcef(sid, AL_ROLLOFF_FACTOR, 0); + } + alSourcef(sid, AL_PITCH, _pitch.GetRandomValue()*desc._pitch); + alSourcei(sid, AL_LOOPING, IsFlagSet(SoundFlags::loop) ? 1 : 0); + alSourcei(sid, AL_BUFFER, _buffer); + alSourcef(sid, AL_GAIN, _gain.GetRandomValue()*desc._gain); + + if (desc._secOffset < 0) + { + VERUS_QREF_UTILS; + ALint size = 0; + alGetBufferi(_buffer, AL_SIZE, &size); + const ALint offset = utils.GetRandom().Next() % size; + alSourcei(sid, AL_BYTE_OFFSET, offset); + } + else if (desc._secOffset > 0) + { + alSourcef(sid, AL_SEC_OFFSET, desc._secOffset); + } + } + if (pID) // Adjust this source later? + { + if (desc._stopSource && *pID) + (*pID)->Stop(); + *pID = source; + } + return source; +} + +float Sound::GetRandomOffset() const +{ + return _length * Utils::I().GetRandom().NextFloat(); +} + +// SoundPtr: + +void SoundPtr::Init(Sound::RcDesc desc) +{ + VERUS_QREF_ASYS; + VERUS_RT_ASSERT(!_p); + _p = asys.InsertSound(desc._url); + _p->Init(desc); +} + +void SoundPwn::Done() +{ + if (_p) + { + AudioSystem::I().DeleteSound(_C(_p->GetUrl())); + _p = nullptr; + } +} diff --git a/Verus/src/Audio/Sound.h b/Verus/src/Audio/Sound.h new file mode 100644 index 0000000..e6752f7 --- /dev/null +++ b/Verus/src/Audio/Sound.h @@ -0,0 +1,141 @@ +#pragma once + +namespace verus +{ + namespace Audio + { + struct SoundFlags + { + enum + { + loaded = (ObjectFlags::user << 0), + is3D = (ObjectFlags::user << 1), + loop = (ObjectFlags::user << 2), + randOff = (ObjectFlags::user << 3), + user = (ObjectFlags::user << 4) + }; + }; + + class Sound : public Object, public IO::AsyncCallback + { + Source _sources[8]; + String _url; + ALuint _buffer = 0; + int _next = 0; + int _refCount = 0; + Range _gain = 1; + Range _pitch = 1; + float _length = 0; + float _referenceDistance = 4; + + public: + struct Desc + { + CSZ _url = nullptr; + Range _gain = 0.8f; + Range _pitch = 1; + float _referenceDistance = 4; + bool _is3D = false; + bool _loop = false; + bool _randomOffset = false; + + Desc(CSZ url) : _url(url) {} + Desc& Set3D(bool b = true) { _is3D = b; return *this; } + Desc& SetLoop(bool b = true) { _loop = b; return *this; } + Desc& SetGain(const Range& gain) { _gain = gain; return *this; } + Desc& SetPitch(const Range& pitch) { _pitch = pitch; return *this; } + Desc& SetRandomOffset(bool b = true) { _randomOffset = b; return *this; } + Desc& SetReferenceDistance(float rd) { _referenceDistance = rd; return *this; } + }; + VERUS_TYPEDEFS(Desc); + + Sound(); + ~Sound(); + + void Init(RcDesc desc); + bool Done(); + + virtual void Async_Run(CSZ url, RcBlob blob) override; + bool IsLoaded() const { return IsFlagSet(SoundFlags::loaded); } + void AddRef() { _refCount++; } + + Str GetUrl() const { return _C(_url); } + + void Update(); + void UpdateHRTF(); + + SourcePtr NewSource(PSourcePtr pID = nullptr, Source::RcDesc desc = Source::Desc()); + + const Range& GetGain() const { return _gain; } + const Range& GetPitch() const { return _pitch; } + + float GetLength() const { return _length; } + + float GetRandomOffset() const; + void SetRandomOffset(bool b) { b ? SetFlag(SoundFlags::randOff) : ResetFlag(SoundFlags::randOff); } + bool HasRandomOffset() const { return IsFlagSet(SoundFlags::randOff); } + }; + VERUS_TYPEDEFS(Sound); + + class SoundPtr : public Ptr + { + public: + void Init(Sound::RcDesc desc); + }; + VERUS_TYPEDEFS(SoundPtr); + + class SoundPwn : public SoundPtr + { + public: + ~SoundPwn() { Done(); } + void Done(); + }; + VERUS_TYPEDEFS(SoundPwn); + + template + class SoundPwns + { + SoundPwn _sounds[NUM]; + int _prev = 0; + + public: + SoundPwns() + { + } + + ~SoundPwns() + { + Done(); + } + + void Init(Sound::RcDesc desc) + { + char buffer[200]; + VERUS_FOR(i, NUM) + { + sprintf_s(buffer, desc._url, i); + Sound::Desc descs = desc; + descs._url = buffer; + _sounds[i].Init(descs); + } + } + + void Done() + { + VERUS_FOR(i, NUM) + _sounds[i].Done(); + } + + RSoundPtr operator[](int i) + { + if (i < 0) + i = Utils::I().GetRandom().Next() & 0xFF; // Only positive. + i %= NUM; + if (i == _prev) + i = (i + 1) % NUM; + _prev = i; + return _sounds[i]; + } + }; + } +} diff --git a/Verus/src/Audio/Source.cpp b/Verus/src/Audio/Source.cpp new file mode 100644 index 0000000..f955d26 --- /dev/null +++ b/Verus/src/Audio/Source.cpp @@ -0,0 +1,136 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Audio; + +// Source: + +Source::Source() +{ +} + +Source::~Source() +{ + Done(); +} + +void Source::Done() +{ + if (_sid) + { + alDeleteSources(1, &_sid); + _sid = 0; + _pSound = nullptr; + } +} + +void Source::Update() +{ + if (!_sid) + return; + + VERUS_QREF_TIMER; + + int state; + alGetSourcei(_sid, AL_SOURCE_STATE, &state); + if (AL_INITIAL == state) + { + _travelDelay -= dt; + if (_travelDelay <= 0) + { + if (_pSound->HasRandomOffset()) + alSourcef(_sid, AL_SEC_OFFSET, _pSound->GetRandomOffset()); + alSourcePlay(_sid); + } + } +} + +void Source::UpdateHRTF(bool is3D) +{ + if (!_sid) + return; + + int state; + alGetSourcei(_sid, AL_SOURCE_STATE, &state); + if (AL_STOPPED == state) + { + alDeleteSources(1, &_sid); + _sid = 0; + } + else if (is3D) + { + alSourcefv(_sid, AL_POSITION, _position.ToPointer()); + alSourcefv(_sid, AL_DIRECTION, _direction.ToPointer()); + alSourcefv(_sid, AL_VELOCITY, _velocity.ToPointer()); + } +} + +void Source::Play() +{ + if (!this) + return; // For NewSource()-> pattern. + if (_pSound && _pSound->IsLoaded()) + { + if (_pSound->HasRandomOffset()) + alSourcef(_sid, AL_SEC_OFFSET, _pSound->GetRandomOffset()); + alSourcePlay(_sid); + } +} + +void Source::PlayAt(RcPoint3 pos, RcVector3 dir, RcVector3 vel, float delay) +{ + if (!this) + return; // For NewSource()-> pattern. + VERUS_QREF_ASYS; + if (_pSound && _pSound->IsLoaded()) + { + _travelDelay = asys.ComputeTravelDelay(pos) + delay; + MoveTo(pos, dir, vel); + UpdateHRTF(_pSound->IsFlagSet(SoundFlags::is3D)); + } +} + +void Source::Stop() +{ + if (_pSound && _pSound->IsLoaded()) + alSourceStop(_sid); +} + +void Source::MoveTo(RcPoint3 pos, RcVector3 dir, RcVector3 vel) +{ + if (_pSound && _pSound->IsLoaded()) + { + _position = pos; + _direction = dir; + _velocity = vel; + } +} + +void Source::SetGain(float gain) +{ + if (_pSound && _pSound->IsLoaded()) + alSourcef(_sid, AL_GAIN, gain*_pSound->GetGain().GetRandomValue()); +} + +void Source::SetPitch(float pitch) +{ + if (_pSound && _pSound->IsLoaded()) + alSourcef(_sid, AL_PITCH, pitch*_pSound->GetPitch().GetRandomValue()); +} + +// SourcePtr: + +void SourcePtr::Attach(Source* pSource, Sound* pSound) +{ + _p = pSource; + _p->_pSound = pSound; +} + +void SourcePtr::Done() +{ + if (_p) + { + _p->Done(); + _p = nullptr; + } +} diff --git a/Verus/src/Audio/Source.h b/Verus/src/Audio/Source.h new file mode 100644 index 0000000..5ae6b20 --- /dev/null +++ b/Verus/src/Audio/Source.h @@ -0,0 +1,55 @@ +#pragma once + +namespace verus +{ + namespace Audio + { + class Source + { + friend class SourcePtr; // _pSound @ Attach(). + friend class Sound; // _sid @ NewSource(). + + Point3 _position = Point3(0); + Vector3 _direction = Vector3(0); + Vector3 _velocity = Vector3(0); + Sound* _pSound = nullptr; + ALuint _sid = 0; + float _travelDelay = 0; + + public: + struct Desc + { + float _secOffset = 0; + float _gain = 1; + float _pitch = 1; + bool _stopSource = true; + }; + VERUS_TYPEDEFS(Desc); + + Source(); + ~Source(); + + void Done(); + + void Update(); + void UpdateHRTF(bool is3D); + + void Play(); + void PlayAt(RcPoint3 pos, RcVector3 dir = Vector3(0), RcVector3 vel = Vector3(0), float delay = 0); + void Stop(); + void MoveTo(RcPoint3 pos, RcVector3 dir = Vector3(0), RcVector3 vel = Vector3(0)); + + void SetGain(float gain); + void SetPitch(float pitch); + }; + VERUS_TYPEDEFS(Source); + + class SourcePtr : public Ptr + { + public: + void Attach(Source* pSource, Sound* pSound); + void Done(); + }; + VERUS_TYPEDEFS(SourcePtr); + } +} diff --git a/Verus/src/Audio/StreamPlayer.cpp b/Verus/src/Audio/StreamPlayer.cpp new file mode 100644 index 0000000..1815b22 --- /dev/null +++ b/Verus/src/Audio/StreamPlayer.cpp @@ -0,0 +1,340 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Audio; + +// Track: + +Track::Track() +{ + _loaded = false; +} + +Track::~Track() +{ + Done(); +} + +void Track::Init(CSZ url) +{ + IO::Async::I().Load(url, this); +} + +void Track::Done() +{ + IO::Async::Cancel(this); +} + +void Track::Async_Run(CSZ url, RcBlob blob) +{ + _vOggEncodedTrack.assign(blob._p, blob._p + blob._size); + + _ds._p = _vOggEncodedTrack.data(); + _ds._size = _vOggEncodedTrack.size(); + _ds._cursor = 0; + + _loaded = true; +} + +// StreamPlayer: + +StreamPlayer::StreamPlayer() +{ + VERUS_ZERO_MEM(_oggVorbisFile); + VERUS_ZERO_MEM(_buffers); +} + +StreamPlayer::~StreamPlayer() +{ + Done(); +} + +void StreamPlayer::Init() +{ + VERUS_INIT(); + + _fade.Set(1, 0); + _fade.SetLimits(0, 1); + + alGenBuffers(2, _buffers); + alGenSources(1, &_source); + + alSourcei(_source, AL_SOURCE_RELATIVE, AL_TRUE); + alSource3f(_source, AL_POSITION, 0, 0, 0); + alSource3f(_source, AL_VELOCITY, 0, 0, 0); + alSourcef(_source, AL_GAIN, _gain); + alSourcef(_source, AL_ROLLOFF_FACTOR, 0); + + _vSmallBuffer.resize(441 * 32); + _vMediumBuffer.resize(441 * 32 * 32); + _vTracks.reserve(8); + + ResetFlag(StreamPlayerFlags::stopThread); + ResetFlag(StreamPlayerFlags::noLock); + _thread = std::thread(&StreamPlayer::ThreadProc, this); +} + +void StreamPlayer::Done() +{ + StopThread(); + _nativeTrack.Done(); + + if (_source) + { + alDeleteSources(1, &_source); + _source = 0; + } + if (_buffers[0]) + { + alDeleteBuffers(2, _buffers); + _buffers[0] = _buffers[1] = 0; + } + + ov_clear(&_oggVorbisFile); + + VERUS_DONE(StreamPlayer); +} + +void StreamPlayer::Update() +{ + if (!IsInitialized()) + return; + VERUS_UPDATE_ONCE_CHECK; + + VERUS_QREF_TIMER; + + _fade.UpdateClamped(dt); + + const float level = Math::SmoothStep(0.f, 1.f, _fade.GetValue()); + alSourcef(_source, AL_GAIN, _gain*level); +} + +void StreamPlayer::AddTrack(PTrack pTrack) +{ + { + VERUS_LOCK(*this); + _vTracks.push_back(pTrack); + if (!_pTrack) + _pTrack = pTrack; + } + _cv.notify_one(); +} + +void StreamPlayer::DeleteTrack(PTrack pTrack) +{ + { + VERUS_LOCK(*this); + VERUS_WHILE(Vector, _vTracks, it) + { + if (*it == pTrack) + it = _vTracks.erase(it); + else + it++; + } + if (_pTrack == pTrack) + { + SetFlag(StreamPlayerFlags::noLock); + SwitchToTrack(nullptr); + ResetFlag(StreamPlayerFlags::noLock); + } + } + _cv.notify_one(); +} + +void StreamPlayer::SwitchToTrack(PTrack pTrack) +{ + VERUS_LOCK_EX(*this, IsFlagSet(StreamPlayerFlags::noLock)); + + VERUS_FOR(i, int(_vTracks.size())) + { + if (_vTracks[i] == pTrack) + { + _currentTrack = i; + break; + } + } + + _pTrack = pTrack; + _pVorbisInfo = nullptr; + ov_clear(&_oggVorbisFile); + + if (!_pTrack || !_pTrack->IsLoaded()) // Possible to start decoding? + return; + + const int ret = ov_open_callbacks(_pTrack->GetOggDataSource(), &_oggVorbisFile, 0, 0, g_oggCallbacks); + if (ret < 0) + throw VERUS_RUNTIME_ERROR << "ov_open_callbacks(), " << ret; + + _pVorbisInfo = ov_info(&_oggVorbisFile, -1); + _format = (_pVorbisInfo->channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16; +} + +void StreamPlayer::Play() +{ + SetFlag(StreamPlayerFlags::play); + alSourcePlay(_source); + _cv.notify_one(); +} + +void StreamPlayer::Stop() +{ + ResetFlag(StreamPlayerFlags::play); + alSourceStop(_source); + _cv.notify_one(); +} + +void StreamPlayer::Seek(int pos) +{ + { + VERUS_LOCK(*this); + + if (!_pVorbisInfo) + return; + + int queued; + alGetSourcei(_source, AL_BUFFERS_QUEUED, &queued); + while (queued > 0) + { + ALuint buffer; + alSourceUnqueueBuffers(_source, 1, &buffer); + queued--; + } + ov_pcm_seek(&_oggVorbisFile, pos); + } + _cv.notify_one(); +} + +void StreamPlayer::ThreadProc() +{ + VERUS_RT_ASSERT(IsInitialized()); + try + { + SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW); + while (!IsFlagSet(StreamPlayerFlags::stopThread)) + { + VERUS_LOCK(*this); + + _cv.wait_for(lock, std::chrono::milliseconds(530)); + + if (IsFlagSet(StreamPlayerFlags::play)) + { + SetFlag(StreamPlayerFlags::noLock); + if (_pTrack) + { + if (_pTrack->IsLoaded() && !_pVorbisInfo) // Loaded but not started? + { + SwitchToTrack(_pTrack); + VERUS_RT_ASSERT(_pVorbisInfo); + } + + if (_pVorbisInfo) // Can get the data? + { + int queued; + alGetSourcei(_source, AL_BUFFERS_QUEUED, &queued); + if (!queued) + { + FillBuffer(_buffers[0]); + FillBuffer(_buffers[1]); + alSourceQueueBuffers(_source, 2, _buffers); + alSourcePlay(_source); + } + + int processed; + alGetSourcei(_source, AL_BUFFERS_PROCESSED, &processed); + +#ifdef VERUS_DEBUG + char debug[80]; + sprintf_s(debug, "ThreadProc() processed=%d", processed); + VERUS_LOG_DEBUG(debug); +#endif + + while (processed > 0) + { + ALuint buffer; + alSourceUnqueueBuffers(_source, 1, &buffer); + FillBuffer(buffer); + alSourceQueueBuffers(_source, 1, &buffer); + processed--; + } + + int state; + alGetSourcei(_source, AL_SOURCE_STATE, &state); + if (AL_STOPPED == state) + alSourcePlay(_source); + } + } + else // No track? Stop the music: + { + alSourceStop(_source); + } + ResetFlag(StreamPlayerFlags::noLock); + } + } + alSourceStop(_source); + } + catch (D::RcRuntimeError e) + { + //Utils::CLog::Error(e.what(), e.GetThreadID(), e.GetFile(), e.GetLine()); + } + catch (const std::exception& e) + { + VERUS_LOG_ERROR(e.what()); + } +} + +void StreamPlayer::StopThread() +{ + if (_thread.joinable()) + { + SetFlag(StreamPlayerFlags::stopThread); + Stop(); + _thread.join(); + } +} + +void StreamPlayer::FillBuffer(ALuint buffer) +{ + VERUS_RT_ASSERT(IsInitialized()); + int bitstream, oggCursor = 0, safeSize = Utils::Cast32(_vMediumBuffer.size() - _vSmallBuffer.size()); + while (oggCursor < safeSize) + { + long num = -1; + if (_pTrack && _pTrack->IsLoaded()) + num = ov_read(&_oggVorbisFile, reinterpret_cast(_vSmallBuffer.data()), Utils::Cast32(_vSmallBuffer.size()), 0, 2, 1, &bitstream); + if (num > 0) + { + memcpy(&_vMediumBuffer[oggCursor], _vSmallBuffer.data(), num); + oggCursor += num; + } + else if (0 == num) // No more data? + { + if (!_vTracks.empty()) // Next one in the playlist? + { + _currentTrack++; + _currentTrack %= _vTracks.size(); + SwitchToTrack(_vTracks[_currentTrack]); + } + else + ov_raw_seek(&_oggVorbisFile, 0); // Start from the beginning. + } + else + safeSize = 0; // Still loading? Exit. + } + if (_pVorbisInfo) + alBufferData(buffer, _format, _vMediumBuffer.data(), oggCursor, _pVorbisInfo->rate); +} + +void StreamPlayer::FadeIn(float time) +{ + _fade.Plan(1, time); +} + +void StreamPlayer::FadeOut(float time) +{ + _fade.Plan(0, time); +} + +void StreamPlayer::Mute() +{ + _fade.Set(0, 0); +} diff --git a/Verus/src/Audio/StreamPlayer.h b/Verus/src/Audio/StreamPlayer.h new file mode 100644 index 0000000..e22eb96 --- /dev/null +++ b/Verus/src/Audio/StreamPlayer.h @@ -0,0 +1,81 @@ +#pragma once + +namespace verus +{ + namespace Audio + { + class Track : public IO::AsyncCallback + { + Vector _vOggEncodedTrack; + OggDataSource _ds; + std::atomic_bool _loaded; + + public: + Track(); + ~Track(); + + POggDataSource GetOggDataSource() { return &_ds; } + void Init(CSZ url); + void Done(); + + virtual void Async_Run(CSZ url, RcBlob blob) override; + bool IsLoaded() const { return _loaded; } + }; + VERUS_TYPEDEFS(Track); + + struct StreamPlayerFlags + { + enum + { + stopThread = (ObjectFlags::user << 0), + play = (ObjectFlags::user << 1), + noLock = (ObjectFlags::user << 2) + }; + }; + + class StreamPlayer : public Object, public Lockable + { + Track _nativeTrack; + Vector _vTracks; + Vector _vSmallBuffer; + Vector _vMediumBuffer; + std::thread _thread; + std::condition_variable _cv; + vorbis_info* _pVorbisInfo = nullptr; + PTrack _pTrack = nullptr; + OggVorbis_File _oggVorbisFile; + ALuint _buffers[2]; + ALuint _source = 0; + ALenum _format = 0; + Linear _fade; + float _gain = 0.25f; + int _currentTrack = 0; + + public: + StreamPlayer(); + ~StreamPlayer(); + + void Init(); + void Done(); + + void Update(); + + void AddTrack(PTrack pTrack); + void DeleteTrack(PTrack pTrack); + void SwitchToTrack(PTrack pTrack); + + void Play(); + void Stop(); + void Seek(int pos); + + VERUS_P(void ThreadProc()); + VERUS_P(void StopThread()); + VERUS_P(void FillBuffer(ALuint buffer)); + + void FadeIn(float time); + void FadeOut(float time); + void Mute(); + }; + VERUS_TYPEDEFS(StreamPlayer); + } +} diff --git a/Verus/src/CGI/BaseGeometry.cpp b/Verus/src/CGI/BaseGeometry.cpp new file mode 100644 index 0000000..4e7eb85 --- /dev/null +++ b/Verus/src/CGI/BaseGeometry.cpp @@ -0,0 +1,4 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::CGI; diff --git a/Verus/src/CGI/BaseGeometry.h b/Verus/src/CGI/BaseGeometry.h new file mode 100644 index 0000000..da6f740 --- /dev/null +++ b/Verus/src/CGI/BaseGeometry.h @@ -0,0 +1,11 @@ +#pragma once + +namespace verus +{ + namespace CGI + { + class BaseGeometry + { + }; + } +} diff --git a/Verus/src/CGI/BasePipeline.cpp b/Verus/src/CGI/BasePipeline.cpp new file mode 100644 index 0000000..4e7eb85 --- /dev/null +++ b/Verus/src/CGI/BasePipeline.cpp @@ -0,0 +1,4 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::CGI; diff --git a/Verus/src/CGI/BasePipeline.h b/Verus/src/CGI/BasePipeline.h new file mode 100644 index 0000000..716683f --- /dev/null +++ b/Verus/src/CGI/BasePipeline.h @@ -0,0 +1,11 @@ +#pragma once + +namespace verus +{ + namespace CGI + { + class BasePipeline + { + }; + } +} diff --git a/Verus/src/CGI/BaseShader.cpp b/Verus/src/CGI/BaseShader.cpp new file mode 100644 index 0000000..4e7eb85 --- /dev/null +++ b/Verus/src/CGI/BaseShader.cpp @@ -0,0 +1,4 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::CGI; diff --git a/Verus/src/CGI/BaseShader.h b/Verus/src/CGI/BaseShader.h new file mode 100644 index 0000000..d2ee14e --- /dev/null +++ b/Verus/src/CGI/BaseShader.h @@ -0,0 +1,11 @@ +#pragma once + +namespace verus +{ + namespace CGI + { + class BaseShader + { + }; + } +} diff --git a/Verus/src/CGI/BaseTexture.cpp b/Verus/src/CGI/BaseTexture.cpp new file mode 100644 index 0000000..4e7eb85 --- /dev/null +++ b/Verus/src/CGI/BaseTexture.cpp @@ -0,0 +1,4 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::CGI; diff --git a/Verus/src/CGI/BaseTexture.h b/Verus/src/CGI/BaseTexture.h new file mode 100644 index 0000000..94d30cd --- /dev/null +++ b/Verus/src/CGI/BaseTexture.h @@ -0,0 +1,11 @@ +#pragma once + +namespace verus +{ + namespace CGI + { + class BaseTexture + { + }; + } +} diff --git a/Verus/src/CGI/CGI.h b/Verus/src/CGI/CGI.h new file mode 100644 index 0000000..ab2c8f6 --- /dev/null +++ b/Verus/src/CGI/CGI.h @@ -0,0 +1,6 @@ +#pragma once + +#include "BaseGeometry.h" +#include "BasePipeline.h" +#include "BaseShader.h" +#include "BaseTexture.h" diff --git a/Verus/src/D/AssertionCompileTime.h b/Verus/src/D/AssertionCompileTime.h new file mode 100644 index 0000000..3060c41 --- /dev/null +++ b/Verus/src/D/AssertionCompileTime.h @@ -0,0 +1,3 @@ +#pragma once + +#define VERUS_CT_ASSERT(x) static_assert(x, "VERUS_CT_ASSERT") diff --git a/Verus/src/D/AssertionRunTime.h b/Verus/src/D/AssertionRunTime.h new file mode 100644 index 0000000..a27b14a --- /dev/null +++ b/Verus/src/D/AssertionRunTime.h @@ -0,0 +1,14 @@ +#pragma once + +#ifdef _DEBUG +# define VERUS_RT_ASSERT(x) assert(x) +# define VERUS_RT_FAIL(x) assert(!x) +#else +# ifdef VERUS_RELEASE_DEBUG +# define VERUS_RT_ASSERT(x) SDL_assert_release(x) +# define VERUS_RT_FAIL(x) SDL_assert_release(!x) +# else +# define VERUS_RT_ASSERT(x) +# define VERUS_RT_FAIL(x) +# endif +#endif diff --git a/Verus/src/D/D.h b/Verus/src/D/D.h new file mode 100644 index 0000000..73df3d5 --- /dev/null +++ b/Verus/src/D/D.h @@ -0,0 +1,7 @@ +#pragma once + +#include "AssertionRunTime.h" +#include "AssertionCompileTime.h" +#include "RuntimeError.h" +#include "Recoverable.h" +#include "Log.h" diff --git a/Verus/src/D/Log.cpp b/Verus/src/D/Log.cpp new file mode 100644 index 0000000..e0c3d1e --- /dev/null +++ b/Verus/src/D/Log.cpp @@ -0,0 +1,134 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::D; + +std::mutex D::Log::s_mutex; + +void Log::Error(CSZ txt, std::thread::id tid, CSZ filename, UINT32 line) +{ + std::lock_guard lock(s_mutex); + + const time_t t = time(0); + const tm* pTM = localtime(&t); + char timestamp[80]; + strftime(timestamp, sizeof(timestamp), "%Y.%m.%d %H:%M:%S", pTM); + + CSZ p = strrchr(filename, '/'); + if (!p) + p = strrchr(filename, '\\'); + filename = p ? p + 1 : filename; + + String pathName; + //if (Global::IsValidSingleton()) + //{ + // pathName = Global::I().GetWritablePath() + "/Errors.txt"; + //} + //else + { + String temp = getenv("TEMP"); + pathName = temp + "/CorrErrors.txt"; + } + + IO::File file; + if (file.Open(_C(pathName), "a")) + { + if (file.GetSize() > 100 * 1024) + { + file.Close(); + file.Open(_C(pathName), "w"); + } + StringStream ss; + ss << timestamp << " " << tid << " " << filename << ":" << line << " ERROR: " << txt << "\n"; + file.Write(_C(ss.str()), ss.str().length()); + } +} + +void Log::Session(CSZ txt) +{ + /* + std::lock_guard lock(s_mutex); + + String pathName(Global::I().GetWritablePath()); + pathName += "/SessionLog.txt"; + IO::File file; + if (file.Create(_C(pathName), "a")) + { + StringStream ss; + ss.fill('0'); + ss << '['; + ss.width(8); + ss << SDL_GetTicks() << "ms] " << txt << "\n"; + file.Write(_C(ss.str()), ss.str().length()); + } + */ +} + +void Log::DebugString(CSZ txt) +{ + /* + StringStream ss; + ss << verus::Global::GetNextCounterValue() << ": " << txt << "\n"; + +#ifdef _WIN32 + OutputDebugStringA(_C(ss.str())); +#endif + */ +} + +void Log::Write(CSZ txt, std::thread::id tid, CSZ filename, UINT32 line, Severity severity) +{ + char timestamp[40]; + FormatTime(timestamp, sizeof(timestamp)); + CSZ severityLetter = GetSeverityLetter(severity); + const String filenameEx = ExtractFilename(filename); + StringStream ss; + ss << timestamp << " [" << severityLetter << "] [" << tid << "] [" << filenameEx << ":" << line << "] " << txt << std::endl; + const String s = ss.str(); + + IO::File file; + if (file.Open("Log.txt", "a")) + { + if (file.GetSize() > 100 * 1024) + { + file.Close(); + file.Open("Log.txt", "w"); + } + file.Write(_C(s), s.length()); + } +} + +void Log::FormatTime(char* buffer, size_t size) +{ + const auto now = std::chrono::system_clock::now(); + const time_t tnow = std::chrono::system_clock::to_time_t(now); + const auto trimmed = std::chrono::system_clock::from_time_t(tnow); + const int ms = static_cast(std::chrono::duration_cast(now - trimmed).count()); + char msText[16]; + sprintf_s(msText, ".%06d", ms); + tm* pTM = localtime(&tnow); + strftime(buffer, size, "%Y-%m-%d %H:%M:%S", pTM); + strcat(buffer, msText); +} + +CSZ Log::GetSeverityLetter(Severity severity) +{ + switch (severity) + { + case Severity::error: return "E"; + case Severity::warning: return "W"; + case Severity::info: return "I"; + case Severity::debug: return "D"; + } + return "X"; +} + +String Log::ExtractFilename(CSZ filename) +{ + CSZ p = strrchr(filename, '/'); + if (!p) + p = strrchr(filename, '\\'); + filename = p ? p + 1 : filename; + p = strrchr(filename, '.'); + return p ? String(filename, p) : String(filename); +} diff --git a/Verus/src/D/Log.h b/Verus/src/D/Log.h new file mode 100644 index 0000000..97cb4cd --- /dev/null +++ b/Verus/src/D/Log.h @@ -0,0 +1,48 @@ +#pragma once + +//#define VERUS_LOG_ERROR(txt) {verus::D::Log::Error(txt, std::this_thread::get_id(), __FILE__, __LINE__);} +//#define VERUS_LOG_ERROR_STREAM(txt) {StringStream ss; ss << txt; verus::D::Log::Error(_C(ss.str()), std::this_thread::get_id(), __FILE__, __LINE__);} +//#define VERUS_LOG_SESSION(txt) {verus::D::Log::Session(txt);} +//#define VERUS_LOG_SESSION_STREAM(txt) {StringStream ss; ss << txt; verus::D::Log::Session(_C(ss.str()));} +//#ifdef _DEBUG +//# define VERUS_LOG_DEBUG(x) {verus::D::Log::DebugString(x);} +//#else +//# define VERUS_LOG_DEBUG(x) {} +//#endif + +#define VERUS_LOG_ERROR(txt) {StringStream ss; ss << txt; D::Log::I().Write(_C(ss.str()), std::this_thread::get_id(), __FILE__, __LINE__, D::Log::Severity::error);} +#define VERUS_LOG_WARN(txt) {StringStream ss; ss << txt; D::Log::I().Write(_C(ss.str()), std::this_thread::get_id(), __FILE__, __LINE__, D::Log::Severity::warning);} +#define VERUS_LOG_INFO(txt) {StringStream ss; ss << txt; D::Log::I().Write(_C(ss.str()), std::this_thread::get_id(), __FILE__, __LINE__, D::Log::Severity::info);} +#define VERUS_LOG_DEBUG(txt) {StringStream ss; ss << txt; D::Log::I().Write(_C(ss.str()), std::this_thread::get_id(), __FILE__, __LINE__, D::Log::Severity::debug);} + +namespace verus +{ + namespace D + { + class Log : public Singleton + { + public: + enum class Severity : int + { + error, + warning, + info, + debug + }; + + private: + static std::mutex s_mutex; + + public: + static void Error(CSZ txt, std::thread::id tid, CSZ filename, UINT32 line); + static void Session(CSZ txt); + static void DebugString(CSZ txt); + + void Write(CSZ txt, std::thread::id tid, CSZ filename, UINT32 line, Severity severity); + + static void FormatTime(char* buffer, size_t size); + static CSZ GetSeverityLetter(Severity severity); + static String ExtractFilename(CSZ filename); + }; + } +} diff --git a/Verus/src/D/Recoverable.h b/Verus/src/D/Recoverable.h new file mode 100644 index 0000000..c789848 --- /dev/null +++ b/Verus/src/D/Recoverable.h @@ -0,0 +1,32 @@ +#pragma once + +namespace verus +{ + namespace D + { + class Recoverable : public RuntimeError + { + public: + Recoverable(std::thread::id tid = std::this_thread::get_id(), CSZ file = __FILE__, UINT32 line = __LINE__) : RuntimeError(tid, file, line) {} + Recoverable(const Recoverable& that) + { + *this = that; + } + Recoverable& operator=(const Recoverable& that) + { + RuntimeError::operator=(that); + return *this; + } + + template + Recoverable& operator<<(const T& t) + { + RuntimeError::operator<<(t); + return *this; + } + }; + VERUS_TYPEDEFS(Recoverable); + } +} + +#define VERUS_RECOVERABLE verus::D::Recoverable(std::this_thread::get_id(), __FILE__, __LINE__) diff --git a/Verus/src/D/RuntimeError.h b/Verus/src/D/RuntimeError.h new file mode 100644 index 0000000..6f24470 --- /dev/null +++ b/Verus/src/D/RuntimeError.h @@ -0,0 +1,67 @@ +#pragma once + +namespace verus +{ + namespace D + { + //! See: https://marknelson.us/posts/2007/11/13/no-exceptions.html + class RuntimeError : public std::exception + { + mutable StringStream _ss; + mutable String _what; + std::thread::id _tid; + String _file; + UINT32 _line = 0; + + public: + RuntimeError(std::thread::id tid = std::this_thread::get_id(), CSZ file = __FILE__, UINT32 line = __LINE__) : + _tid(tid), _line(line) + { + CSZ p = strrchr(file, '/'); + if (!p) + p = strrchr(file, '\\'); + _file = p ? p + 1 : file; + } + RuntimeError(const RuntimeError& that) + { + *this = that; + } + RuntimeError& operator=(const RuntimeError& that) + { + _what += that._what; + _what += that._ss.str(); + _tid = that._tid; + _file = that._file; + _line = that._line; + return *this; + } + virtual ~RuntimeError() throw() {} + + virtual CSZ what() const throw() + { + if (_ss.str().size()) + { + _what += _ss.str(); + _ss.str(""); + } + return _C(_what); + } + + std::thread::id GetThreadID() const { return _tid; } + CSZ GetFile() const { return _C(_file); } + UINT32 GetLine() const { return _line; } + + template + RuntimeError& operator<<(const T& t) + { + _ss << t; + return *this; + } + + bool IsRaised() const { return !!strlen(what()); } + }; + VERUS_TYPEDEFS(RuntimeError); + } +} + +#define VERUS_RUNTIME_ERROR verus::D::RuntimeError(std::this_thread::get_id(), __FILE__, __LINE__) diff --git a/Verus/src/Game/BaseGame.cpp b/Verus/src/Game/BaseGame.cpp new file mode 100644 index 0000000..e582576 --- /dev/null +++ b/Verus/src/Game/BaseGame.cpp @@ -0,0 +1,316 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Game; + +#if 0 +struct MyRenderDelegate : CGL::CRenderDelegate +{ + PBaseGame _p = nullptr; + + MyRenderDelegate(PBaseGame p) : _p(p) {} + ~MyRenderDelegate() {} + + virtual void Render_OnDraw() override + { + VERUS_QREF_RENDER; + render.BindOffscreenRT(); + _p->BaseGame_Draw(); + } + + virtual void Render_OnDrawOverlay() override + { + _p->BaseGame_DrawOverlay(); + } + + virtual void Render_OnPresent() override + { + VERUS_QREF_RENDER; + render->Present(); + } + + virtual void Render_OnDrawCubeMap() override + { + } +}; +VERUS_TYPEDEFS(MyRenderDelegate); +#endif + +struct BaseGame::Pimpl : AllocatorAware +{ + PBaseGame _p = nullptr; + Scene::MainCamera _camera; + //CCharacter _cameraCharacter; + bool _defaultCameraMovement = true; + bool _showFPS = true; + bool _debugBullet = false; + bool _exitOnEscape = true; + + Pimpl(PBaseGame p) : _p(p) + { + } + + ~Pimpl() + { + } + + bool HandleUserEvents(SDL_Event* pEvent) + { + switch (pEvent->user.code) + { + case 0: return true; // Exit. + } + return false; + } +}; + +BaseGame::BaseGame() +{ + Utils::MakeEx(&_alloc); + _p = new Pimpl(this); +} + +BaseGame::~BaseGame() +{ + delete _p; + + _engineInit.Free(); + + Utils::FreeEx(&_alloc); + SDL_Quit(); + //CGL::CRender::DoneWin32(); +} + +void BaseGame::Initialize(VERUS_MAIN_DEFAULT_ARGS) +{ + VERUS_SDL_CENTERED; + + //Settings::Make(); + //VERUS_QREF_SETTINGS; + //settings.ParseCommandLineArgs(argc, argv); + //settings.LoadValidateSave(); + + const int ret = SDL_Init(SDL_INIT_EVERYTHING); + if (ret) + throw VERUS_RUNTIME_ERROR << "SDL_Init(), " << ret; + + // Allocate memory: + _engineInit.Make(); + +#ifdef _DEBUG + //Utils::TestAll(); +#endif + + // Initialization: + //_engineInit.Init(this, new MyRenderDelegate(this), true); + + // Configure: + //VERUS_QREF_RENDER; + //VERUS_QREF_SM; + //_p->_camera.SetAspectRatio(render.GetWindowAspectRatio()); + //sm.SetCamera(&_p->_camera); + + BaseGame_LoadContent(); +} + +void BaseGame::Run() +{ + VERUS_QREF_TIMER; + VERUS_QREF_KM; + //VERUS_QREF_BULLET; + //VERUS_QREF_RENDER; + VERUS_QREF_ASYNC; + VERUS_QREF_ASYS; + + SDL_SetRelativeMouseMode(SDL_TRUE); + + timer.Update(); + + SDL_Event event; + bool done = false; + + do // The Game Loop. + { + if (_p->_exitOnEscape && km.IsKeyPressed(SDL_SCANCODE_ESCAPE)) + Exit(); + + while (SDL_PollEvent(&event)) + { + if (!km.HandleSdlEvent(event)) + { + switch (event.type) + { + case SDL_QUIT: + { + done = true; + } + break; + case SDL_WINDOWEVENT: + { + switch (event.window.event) + { + case SDL_WINDOWEVENT_MINIMIZED: + BaseGame_OnDeactivated(); + break; + case SDL_WINDOWEVENT_RESTORED: + BaseGame_OnActivated(); + break; + } + } + break; + case SDL_USEREVENT: + { + if (_p->HandleUserEvents(&event)) + { + SDL_Delay(100); + done = true; + } + } + break; + } + } + } + + if (done) + break; + + // + // UPDATE + // At this point the user sees a frame drawn some time ago. + // + + async.Update(); + + timer.Update(); // Now we know how much time has passed. Maybe 1 hour! + + // Process input for simulation: + if (_p->_defaultCameraMovement) + { + const float speed = km.IsKeyPressed(SDL_SCANCODE_SPACE) ? 20.f : 2.f; + //if (km.IsKeyPressed(SDL_SCANCODE_W)) + // _p->_cameraCharacter.MoveFront(speed); + //if (km.IsKeyPressed(SDL_SCANCODE_S)) + // _p->_cameraCharacter.MoveFront(-speed); + //if (km.IsKeyPressed(SDL_SCANCODE_A)) + // _p->_cameraCharacter.MoveSide(-speed); + //if (km.IsKeyPressed(SDL_SCANCODE_D)) + // _p->_cameraCharacter.MoveSide(speed); + } + BaseGame_HandleInput(); + + //bullet.Simulate(); // x += v*dt. + + // Update game state after simulation: + if (_p->_defaultCameraMovement) + { + //_p->_cameraCharacter.Update(); // Get position from Bullet. + //_p->_camera.MoveEyeTo(_p->_cameraCharacter.GetPosition()); + //_p->_camera.MoveAtTo(_p->_cameraCharacter.GetPosition() + _p->_cameraCharacter.GetDirectionFront()); + //if (Scene::CWater::IsValidSingleton()) + { + // VERUS_QREF_WATER; + // if (water.IsInitialized()) + // _p->_camera.ExcludeWaterLine(); + } + _p->_camera.Update(); + } + BaseGame_Update(); + + asys.Update(); + + // Draw current frame: + //if (render.Draw()) + { +#ifdef VERUS_DEBUG + if (_p->_debugBullet) + bullet.DebugDraw(); +#endif + //render.DrawToScreen(); + } + km.ResetClickState(); + //render.Present(); // This can take a while. + + // Show FPS: + if (_p->_showFPS && timer.IsEventEvery(500)) + { + char title[64]; + CSZ renderer = ""; + //switch (render.GetRenderer()) + { + //case CGL::RENDERER_OPENGL: renderer = "OpenGL"; break; + //case CGL::RENDERER_DIRECT3D9: renderer = "Direct3D 9"; break; + //case CGL::RENDERER_DIRECT3D11: renderer = "Direct3D 11"; break; + //case CGL::RENDERER_DIRECT3D12: renderer = "Direct3D 12"; break; + } + //sprintf_s(title, "[%s] - %.1f FPS", renderer, render.GetFps()); + //SDL_SetWindowTitle(render.GetWindow(), title); + } + } while (!done); // The Game Loop. + + BaseGame_UnloadContent(); + + SDL_SetRelativeMouseMode(SDL_FALSE); +} + +void BaseGame::Exit() +{ + Utils::ExitSdlLoop(); +} + +void BaseGame::KeyMapper_OnMouseMove(int x, int y) +{ + //VERUS_QREF_CONST_SETTINGS; + + //const float fx = x * 0.006f*settings.m_inputMouseSensitivity; + //const float fy = y * 0.006f*settings.m_inputMouseSensitivity; + + if (_p->_defaultCameraMovement) + { + //_p->_cameraCharacter.TurnPitch(fy); + //_p->_cameraCharacter.TurnYaw(fx); + } + + //BaseGame_OnMouseMove(fx, fy); +} + +void BaseGame::KeyMapper_OnKey(int scancode) +{ + BaseGame_OnKey(scancode); +} + +void BaseGame::KeyMapper_OnChar(wchar_t c) +{ + BaseGame_OnChar(c); +} + +Scene::RCamera BaseGame::GetCamera() +{ + return _p->_camera; +} + +#if 0 +RCharacter BaseGame::GetCameraCharacter() +{ + return _p->_cameraCharacter; +} +#endif + +void BaseGame::ActivateDefaultCameraMovement(bool b) +{ + _p->_defaultCameraMovement = b; +} + +void BaseGame::ShowFPS(bool b) +{ + _p->_showFPS = b; +} + +void BaseGame::DebugBullet(bool b) +{ + _p->_debugBullet = b; +} + +bool BaseGame::IsDebugBulletMode() const +{ + return _p->_debugBullet; +} diff --git a/Verus/src/Game/BaseGame.h b/Verus/src/Game/BaseGame.h new file mode 100644 index 0000000..1ea38eb --- /dev/null +++ b/Verus/src/Game/BaseGame.h @@ -0,0 +1,50 @@ +#pragma once + +namespace verus +{ + namespace Game + { + class BaseGame : public Input::KeyMapperDelegate + { + struct Pimpl; + Pimpl* _p = nullptr; + AlignedAllocator _alloc; + EngineInit _engineInit; + + public: + BaseGame(); + ~BaseGame(); + + void Initialize(VERUS_MAIN_DEFAULT_ARGS); + void Run(); + void Exit(); + + virtual void BaseGame_LoadContent() = 0; + virtual void BaseGame_UnloadContent() = 0; + virtual void BaseGame_HandleInput() = 0; + virtual void BaseGame_Update() = 0; + virtual void BaseGame_Draw() = 0; + virtual void BaseGame_DrawOverlay() = 0; + virtual void BaseGame_OnActivated() {} + virtual void BaseGame_OnDeactivated() {} + virtual void BaseGame_OnMouseMove(float x, float y) {} + virtual void BaseGame_OnKey(int scancode) {} + virtual void BaseGame_OnChar(wchar_t c) {} + + virtual void KeyMapper_OnMouseMove(int x, int y) override; + virtual void KeyMapper_OnKey(int scancode) override; + virtual void KeyMapper_OnChar(wchar_t c) override; + + // Internal objects: + Scene::RCamera GetCamera(); + //RCharacter GetCameraCharacter(); + + // Configurations: + void ActivateDefaultCameraMovement(bool b); + void ShowFPS(bool b); + void DebugBullet(bool b); + bool IsDebugBulletMode() const; + }; + VERUS_TYPEDEFS(BaseGame); + } +} diff --git a/Verus/src/Game/Game.h b/Verus/src/Game/Game.h new file mode 100644 index 0000000..24f424a --- /dev/null +++ b/Verus/src/Game/Game.h @@ -0,0 +1,5 @@ +#pragma once + +#include "State.h" +#include "StateMachine.h" +#include "BaseGame.h" diff --git a/Verus/src/Game/State.cpp b/Verus/src/Game/State.cpp new file mode 100644 index 0000000..1a94ff6 --- /dev/null +++ b/Verus/src/Game/State.cpp @@ -0,0 +1,25 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Game; + +bool State::IsValidNextState(PState p) +{ + return true; +} + +void State::OnEnter(PState pPrev) +{ +} + +void State::HandleInput() +{ +} + +void State::Update() +{ +} + +void State::OnExit(PState pNext) +{ +} diff --git a/Verus/src/Game/State.h b/Verus/src/Game/State.h new file mode 100644 index 0000000..e8535d3 --- /dev/null +++ b/Verus/src/Game/State.h @@ -0,0 +1,21 @@ +#pragma once + +namespace verus +{ + namespace Game + { + class StateMachine; + + struct State + { + StateMachine* _pStateMachine = nullptr; + + virtual bool IsValidNextState(State* p); + virtual void OnEnter(State* pPrev); + virtual void HandleInput(); + virtual void Update(); + virtual void OnExit(State* pNext); + }; + VERUS_TYPEDEFS(State); + } +} diff --git a/Verus/src/Game/StateMachine.cpp b/Verus/src/Game/StateMachine.cpp new file mode 100644 index 0000000..d865e23 --- /dev/null +++ b/Verus/src/Game/StateMachine.cpp @@ -0,0 +1,100 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Game; + +void StateMachine::HandleInput() +{ + PcState pCheck = _pCurrentState; + _pCurrentState->_pStateMachine = this; + _pCurrentState->HandleInput(); + _changed = (_pCurrentState != pCheck); +} + +void StateMachine::Update() +{ + VERUS_UPDATE_ONCE_CHECK; + + PcState pCheck = _pCurrentState; + _pCurrentState->_pStateMachine = this; + _pCurrentState->Update(); + _changed = (_pCurrentState != pCheck); +} + +PState StateMachine::GetCurrentState() const +{ + return _pCurrentState; +} + +PState StateMachine::GetRequestedState() const +{ + return _pRequestedState; +} + +bool StateMachine::CanEnterState(RState state, bool allowSameState) +{ + state._pStateMachine = this; + if (&state == _pCurrentState && !allowSameState) + return false; + return _pCurrentState ? _pCurrentState->IsValidNextState(&state) : true; +} + +bool StateMachine::EnterState(RState state, bool allowSameState) +{ + state._pStateMachine = this; + if (!CanEnterState(state, allowSameState)) + return false; + + const UINT32 frame = /*CGL::CRender::I().GetNumFrames();*/ 0; + VERUS_RT_ASSERT(frame != _prevFrame); // Don't change state multiple times per frame! + _prevFrame = frame; + + PState pPrevState = _pCurrentState; + if (_pCurrentState) + { + PcState pCheck = _pCurrentState; + _pCurrentState->OnExit(&state); + VERUS_RT_ASSERT(pCheck == _pCurrentState); // Don't change state while changing state! + } + _pCurrentState = &state; + { + PcState pCheck = _pCurrentState; + _pCurrentState->OnEnter(pPrevState); + VERUS_RT_ASSERT(pCheck == _pCurrentState); // Don't change state while changing state! + } + + _pRequestedState = nullptr; + return true; +} + +bool StateMachine::EnterRequestedState() +{ + if (_pRequestedState == _pCurrentState) + _pRequestedState = nullptr; + if (_pRequestedState) + return EnterState(*_pRequestedState); + else + return false; +} + +void StateMachine::ReenterState() +{ + if (_pCurrentState) + { + { + PcState pCheck = _pCurrentState; + _pCurrentState->OnExit(_pCurrentState); + VERUS_RT_ASSERT(pCheck == _pCurrentState); + } + { + PcState pCheck = _pCurrentState; + _pCurrentState->OnEnter(_pCurrentState); + VERUS_RT_ASSERT(pCheck == _pCurrentState); + } + } +} + +void StateMachine::RequestState(RState state) +{ + _pRequestedState = &state; +} diff --git a/Verus/src/Game/StateMachine.h b/Verus/src/Game/StateMachine.h new file mode 100644 index 0000000..52d5f7d --- /dev/null +++ b/Verus/src/Game/StateMachine.h @@ -0,0 +1,28 @@ +#pragma once + +namespace verus +{ + namespace Game + { + class StateMachine : public Object + { + PState _pCurrentState = nullptr; + PState _pRequestedState = nullptr; + UINT32 _prevFrame = -1; + bool _changed = false; + + public: + void HandleInput(); + void Update(); + PState GetCurrentState() const; + PState GetRequestedState() const; + bool CanEnterState(RState state, bool allowSameState = false); + bool EnterState(RState state, bool allowSameState = false); + bool EnterRequestedState(); + void ReenterState(); + void RequestState(RState state); + bool IsStateChanged() const { return _changed; } + }; + VERUS_TYPEDEFS(StateMachine); + } +} diff --git a/Verus/src/Global/AlignedAllocator.h b/Verus/src/Global/AlignedAllocator.h new file mode 100644 index 0000000..b1d683d --- /dev/null +++ b/Verus/src/Global/AlignedAllocator.h @@ -0,0 +1,39 @@ +#pragma once + +namespace verus +{ + //! This allocator will reserve aligned memory blocks. + class AlignedAllocator : public BaseAllocator + { + INT64 _memUsedTotal = 0; + int _numMalloc = 0; + int _numFree = 0; + int _mallocDelta = 0; + + public: + AlignedAllocator() {} + + virtual void* malloc(size_t size) override + { + _memUsedTotal += size; + _numMalloc++; + _mallocDelta++; + return _mm_malloc(size, VERUS_MEMORY_ALIGNMENT); + } + + virtual void* realloc(void* memory, size_t size) override + { + void* p = _mm_malloc(size, VERUS_MEMORY_ALIGNMENT); + memcpy(p, memory, size); + _mm_free(memory); + return p; + } + + virtual void free(void* memory) override + { + _numFree++; + _mallocDelta--; + _mm_free(memory); + } + }; +} diff --git a/Verus/src/Global/AllocatorAware.cpp b/Verus/src/Global/AllocatorAware.cpp new file mode 100644 index 0000000..5e299d6 --- /dev/null +++ b/Verus/src/Global/AllocatorAware.cpp @@ -0,0 +1,57 @@ +#include "verus.h" + +using namespace verus; + +void* AllocatorAware::operator new(size_t size) +{ + VERUS_QREF_UTILS; + return utils.GetAllocator()->malloc(size); +} + +void* AllocatorAware::operator new[](size_t size) +{ + VERUS_QREF_UTILS; + return utils.GetAllocator()->malloc(size); +} + +void AllocatorAware::operator delete(void* p) +{ + VERUS_QREF_UTILS; + utils.GetAllocator()->free(p); +} + +void AllocatorAware::operator delete[](void* p) +{ + VERUS_QREF_UTILS; + utils.GetAllocator()->free(p); +} + +void* AllocatorAware::operator new(size_t size, void* place) +{ + return place; +} + +void AllocatorAware::operator delete(void* p, void* place) +{ +} + +void* AllocatorAware::UtilsMalloc(size_t size) +{ + if (Utils::IsValidSingleton()) + { + VERUS_QREF_UTILS; + return utils.GetAllocator()->malloc(size); + } + return nullptr; +} + +bool AllocatorAware::UtilsFree(void* p) +{ + if (Utils::IsValidSingleton()) + { + VERUS_QREF_UTILS; + utils.GetAllocator()->free(p); + return true; + } + return false; +} diff --git a/Verus/src/Global/AllocatorAware.h b/Verus/src/Global/AllocatorAware.h new file mode 100644 index 0000000..c7836d4 --- /dev/null +++ b/Verus/src/Global/AllocatorAware.h @@ -0,0 +1,20 @@ +#pragma once + +namespace verus +{ + //! Objects of this class will use the allocator provided by Utils. + class AllocatorAware + { + public: + void* operator new(size_t size); + void* operator new[](size_t size); + void operator delete(void* p); + void operator delete[](void* p); + // Placement: + void* operator new(size_t size, void* place); + void operator delete(void* p, void* place); + + static void* UtilsMalloc(size_t size); + static bool UtilsFree(void* p); + }; +} diff --git a/Verus/src/Global/BaseAllocator.h b/Verus/src/Global/BaseAllocator.h new file mode 100644 index 0000000..8cadaec --- /dev/null +++ b/Verus/src/Global/BaseAllocator.h @@ -0,0 +1,13 @@ +#pragma once + +namespace verus +{ + class BaseAllocator + { + public: + virtual void* malloc(size_t size) = 0; + virtual void* realloc(void* memory, size_t size) = 0; + virtual void free(void* memory) = 0; + }; + VERUS_TYPEDEFS(BaseAllocator); +} diff --git a/Verus/src/Global/Basic.h b/Verus/src/Global/Basic.h new file mode 100644 index 0000000..9e4c159 --- /dev/null +++ b/Verus/src/Global/Basic.h @@ -0,0 +1,8 @@ +#pragma once + +#include "Macros.h" +#include "Typedef.h" +#include "EnumClass.h" +#include "QuickRefs.h" +#include "AllocatorAware.h" +#include "Singleton.h" diff --git a/Verus/src/Global/Blob.h b/Verus/src/Global/Blob.h new file mode 100644 index 0000000..d135690 --- /dev/null +++ b/Verus/src/Global/Blob.h @@ -0,0 +1,13 @@ +#pragma once + +namespace verus +{ + struct Blob + { + const BYTE* _p; + const INT64 _size; + + Blob(const BYTE* p = nullptr, const INT64 size = 0) : _p(p), _size(size) {} + }; + VERUS_TYPEDEFS(Blob); +} diff --git a/Verus/src/Global/Convert.cpp b/Verus/src/Global/Convert.cpp new file mode 100644 index 0000000..e66f233 --- /dev/null +++ b/Verus/src/Global/Convert.cpp @@ -0,0 +1,359 @@ +#include "verus.h" + +using namespace verus; + +// F to I: +BYTE Convert::UnormToUint8(float x) { return BYTE(x*UCHAR_MAX + 0.5f); } +UINT16 Convert::UnormToUint16(float x) { return UINT16(x*USHRT_MAX + 0.5f); } +char Convert::SnormToSint8(float x) { return char((x >= 0) ? x * SCHAR_MAX + 0.5f : x * SCHAR_MAX - 0.5f); } +short Convert::SnormToSint16(float x) { return short((x >= 0) ? x * SHRT_MAX + 0.5f : x * SHRT_MAX - 0.5f); } + +// I to F: +float Convert::Uint8ToUnorm(BYTE x) { return x * (1.f / UCHAR_MAX); } +float Convert::Uint16ToUnorm(UINT16 x) { return x * (1.f / USHRT_MAX); } +float Convert::Sint8ToSnorm(char x) { if (x == SCHAR_MIN) x = SCHAR_MIN + 1; return x * (1.f / SCHAR_MAX); } +float Convert::Sint16ToSnorm(short x) { if (x == SHRT_MIN) x = SHRT_MIN + 1; return x * (1.f / SHRT_MAX); } + +// AoF to AoI: +void Convert::UnormToUint8(const float* pIn, BYTE* pOut, int num) { VERUS_FOR(i, num) pOut[i] = UnormToUint8(pIn[i]); } +void Convert::UnormToUint16(const float* pIn, UINT16* pOut, int num) { VERUS_FOR(i, num) pOut[i] = UnormToUint16(pIn[i]); } +void Convert::SnormToSint8(const float* pIn, char* pOut, int num) { VERUS_FOR(i, num) pOut[i] = SnormToSint8(pIn[i]); } +void Convert::SnormToSint16(const float* pIn, short* pOut, int num) { VERUS_FOR(i, num) pOut[i] = SnormToSint16(pIn[i]); } + +// AoI to AoF: +void Convert::Uint8ToUnorm(const BYTE* pIn, float* pOut, int num) { VERUS_FOR(i, num) pOut[i] = Uint8ToUnorm(pIn[i]); } +void Convert::Uint16ToUnorm(const UINT16* pIn, float* pOut, int num) { VERUS_FOR(i, num) pOut[i] = Uint16ToUnorm(pIn[i]); } +void Convert::Sint8ToSnorm(const char* pIn, float* pOut, int num) { VERUS_FOR(i, num) pOut[i] = Sint8ToSnorm(pIn[i]); } +void Convert::Sint16ToSnorm(const short* pIn, float* pOut, int num) { VERUS_FOR(i, num) pOut[i] = Sint16ToSnorm(pIn[i]); } + +// I to I: +short Convert::Sint8ToSint16(char x) { return x * SHRT_MAX / SCHAR_MAX; } +char Convert::Sint16ToSint8(short x) { return x * SCHAR_MAX / SHRT_MAX; } +void Convert::Sint8ToSint16(const char* pIn, short* pOut, int num) { VERUS_FOR(i, num) pOut[i] = Sint8ToSint16(pIn[i]); } +void Convert::Sint16ToSint8(const short* pIn, char* pOut, int num) { VERUS_FOR(i, num) pOut[i] = Sint16ToSint8(pIn[i]); } + +void Convert::ToDeviceNormal(const char* pIn, char* pOut) +{ + // For UBYTE4 type normal: + // OpenGL glNormalPointer() only accepts signed bytes (GL_BYTE) + // Direct3D 9 only accepts unsigned bytes (D3DDECLTYPE_UBYTE4) So it goes. + //VERUS_QREF_RENDER; + //if (CGL::RENDERER_DIRECT3D9 == render.GetRenderer()) + //{ + // VERUS_FOR(i, 3) + // pOut[i] = pIn[i] + 127; + //} +} + +UINT32 Convert::ToDeviceColor(UINT32 color) +{ + // OpenGL stores color as RGBA. Direct3D 9 as BGRA. + // See also GL_EXT_vertex_array_bgra. + //VERUS_QREF_RENDER; + //return (CGL::RENDERER_DIRECT3D9 == render.GetRenderer()) ? VERUS_COLOR_TO_D3D(color) : color; + return 0; +} + +float Convert::Byte256ToSFloat(BYTE in) +{ + return float(in)*(1 / 128.f) - 1; +} + +UINT16 Convert::Uint8x4ToUint4x4(UINT32 in) +{ + int x[4] = + { + (in >> 28) & 0xF, + (in >> 20) & 0xF, + (in >> 12) & 0xF, + (in >> 4) & 0xF + }; + return (x[0] << 12) | (x[1] << 8) | (x[2] << 4) | (x[3]); +} + +UINT32 Convert::Uint4x4ToUint8x4(UINT16 in) +{ + int x[4] = + { + (in >> 12) & 0xF, + (in >> 8) & 0xF, + (in >> 4) & 0xF, + (in >> 0) & 0xF + }; + return (x[0] << 28) | (x[1] << 20) | (x[2] << 12) | (x[3] << 4); +} + +UINT32 Convert::Color16To32(UINT16 in) +{ + const int b = ((in >> 0) & 0x1F) << 3; + const int g = ((in >> 5) & 0x3F) << 2; + const int r = ((in >> 11) & 0x1F) << 3; + return VERUS_COLOR_RGBA(r, g, b, 255); +} + +void Convert::ColorInt32ToFloat(UINT32 in, float* out, bool sRGB) +{ + const float gamma = sRGB ? 2.2f : 1.f; + out[0] = pow(float(int((in >> 0) & 0xFF))*(1 / 255.f), gamma); + out[1] = pow(float(int((in >> 8) & 0xFF))*(1 / 255.f), gamma); + out[2] = pow(float(int((in >> 16) & 0xFF))*(1 / 255.f), gamma); + out[3] = float(int((in >> 24) & 0xFF))*(1 / 255.f); +} + +UINT32 Convert::ColorFloatToInt32(const float* in, bool sRGB) +{ + const float gamma = sRGB ? 1 / 2.2f : 1.f; + const int r = Math::Clamp(int(pow(in[0], gamma)*255.5f), 0, 255); + const int g = Math::Clamp(int(pow(in[1], gamma)*255.5f), 0, 255); + const int b = Math::Clamp(int(pow(in[2], gamma)*255.5f), 0, 255); + const int a = Math::Clamp(int(in[3] * 255.5f), 0, 255); + return VERUS_COLOR_RGBA(r, g, b, a); +} + +void Convert::ColorTextToFloat4(CSZ sz, float* out, bool sRGB) +{ + int color[4] = {}; + if (!sz) + { + } + else if (6 == strlen(sz) && !strchr(sz, ' ')) + { + color[0] = Str::ByteFromHex(sz + 0); + color[1] = Str::ByteFromHex(sz + 2); + color[2] = Str::ByteFromHex(sz + 4); + color[3] = 255; + } + else if (8 == strlen(sz) && !strchr(sz, ' ')) + { + color[0] = Str::ByteFromHex(sz + 0); + color[1] = Str::ByteFromHex(sz + 2); + color[2] = Str::ByteFromHex(sz + 4); + color[3] = Str::ByteFromHex(sz + 6); + } + else + { + const int num = sscanf(sz, "%d %d %d %d", + color + 0, + color + 1, + color + 2, + color + 3); + if (3 == num) + color[3] = 255; + } + const float gamma = sRGB ? 2.2f : 1.f; + out[0] = pow(float(color[0])*(1 / 255.f), gamma); + out[1] = pow(float(color[1])*(1 / 255.f), gamma); + out[2] = pow(float(color[2])*(1 / 255.f), gamma); + out[3] = float(color[3])*(1 / 255.f); +} + +UINT32 Convert::ColorTextToInt32(CSZ sz) +{ + int color[4] = {}; + if (6 == strlen(sz) && !strchr(sz, ' ')) + { + color[0] = Str::ByteFromHex(sz + 0); + color[1] = Str::ByteFromHex(sz + 2); + color[2] = Str::ByteFromHex(sz + 4); + color[3] = 255; + } + else if (8 == strlen(sz) && !strchr(sz, ' ')) + { + color[0] = Str::ByteFromHex(sz + 0); + color[1] = Str::ByteFromHex(sz + 2); + color[2] = Str::ByteFromHex(sz + 4); + color[3] = Str::ByteFromHex(sz + 6); + } + else + { + sscanf(sz, "%d %d %d %d", + color + 0, + color + 1, + color + 2, + color + 3); + } + return VERUS_COLOR_RGBA(color[0], color[1], color[2], color[3]); +} + +void Convert::ToCorrectNormal(const char* in, char* out) +{ + // For UBYTE4 type normal: + // OpenGL glNormalPointer() only accepts signed bytes (GL_BYTE) + // Direct3D only accepts unsigned bytes (D3DDECLTYPE_UBYTE4) So it goes. + //VERUS_QREF_RENDER; + //if (CGL::RENDERER_OPENGL != render.GetRenderer()) + //{ + // VERUS_FOR(i, 3) + // out[i] = in[i] + 125; + //} +} + +UINT32 Convert::ToCorrectColor(UINT32 in) +{ + // OpenGL stores color as RGBA. Direct3D 9 as BGRA. + // See also GL_EXT_vertex_array_bgra. + //VERUS_QREF_RENDER; + //return (CGL::RENDERER_DIRECT3D9 == render.GetRenderer()) ? VERUS_COLOR_TO_D3D(in) : in; + return 0; +} + +void Convert::ByteToChar3(const BYTE* in, char* out) +{ + VERUS_FOR(i, 3) + out[i] = int(in[i]) - 125; +} + +void Convert::ByteToShort3(const BYTE* in, short* out) +{ + VERUS_FOR(i, 3) + out[i] = (int(in[i]) << 8) - 32000; +} + +UINT16 Convert::QuantizeFloat(float f, float mn, float mx) +{ + const float range = mx - mn; + return UINT16(Math::Clamp((f - mn) / range * USHRT_MAX, 0.f, float(USHRT_MAX))); +} + +float Convert::DequantizeFloat(UINT16 i, float mn, float mx) +{ + const float range = mx - mn; + return float(i) / USHRT_MAX * range + mn; +} + +BYTE Convert::QuantizeFloatToByte(float f, float mn, float mx) +{ + const float range = mx - mn; + return BYTE(Math::Clamp((f - mn) / range * UCHAR_MAX, 0.f, float(UCHAR_MAX))); +} + +float Convert::DequantizeFloatFromByte(BYTE i, float mn, float mx) +{ + const float range = mx - mn; + return float(i) / UCHAR_MAX * range + mn; +} + +String Convert::ToBase64(const Vector& vBin) +{ + Vector vBase64; + vBase64.resize(vBin.size() * 2); + base64_encodestate state; + base64_init_encodestate(&state); + const int len = base64_encode_block(reinterpret_cast(vBin.data()), Utils::Cast32(vBin.size()), vBase64.data(), &state); + const int len2 = base64_encode_blockend(&vBase64[len], &state); + vBase64.resize(len + len2); + return String(vBase64.begin(), vBase64.end()); +} + +Vector Convert::ToBinFromBase64(CSZ base64) +{ + Vector vBin; + vBin.resize(strlen(base64)); + base64_decodestate state; + base64_init_decodestate(&state); + const int len = base64_decode_block(base64, Utils::Cast32(strlen(base64)), reinterpret_cast(vBin.data()), &state); + vBin.resize(len); + return vBin; +} + +String Convert::ByteToHex(BYTE b) +{ + static const char hexval[] = "0123456789ABCDEF"; + char ret[3]; + ret[0] = hexval[(b >> 4) & 0xF]; + ret[1] = hexval[(b >> 0) & 0xF]; + ret[2] = 0; + return ret; +} + +String Convert::ToHex(const Vector& vBin) +{ + static const char hexval[] = "0123456789ABCDEF"; + const int num = Utils::Cast32(vBin.size()); + Vector vHex; + vHex.resize(num * 2); + VERUS_FOR(i, num) + { + vHex[(i << 1) + 0] = hexval[(vBin[i] >> 4) & 0xF]; + vHex[(i << 1) + 1] = hexval[(vBin[i] >> 0) & 0xF]; + } + return String(vHex.begin(), vHex.end()); +} + +Vector Convert::ToBinFromHex(CSZ hex) +{ + Vector vBin; + const size_t len = strlen(hex); + if (0 == len || (len & 0x1)) + return vBin; + const int lenBin = Utils::Cast32(len / 2); + vBin.resize(lenBin); + VERUS_FOR(i, lenBin) + { + const char hi = hex[(i << 1)]; + const char lo = hex[(i << 1) + 1]; + + if /**/ (hi >= '0' && hi <= '9') vBin[i] = ((hi - '0')) << 4; + else if (hi >= 'a' && hi <= 'f') vBin[i] = ((hi - 'a') + 10) << 4; + else if (hi >= 'A' && hi <= 'F') vBin[i] = ((hi - 'A') + 10) << 4; + + if /**/ (lo >= '0' && lo <= '9') vBin[i] |= (lo - '0'); + else if (lo >= 'a' && lo <= 'f') vBin[i] |= (lo - 'a') + 10; + else if (lo >= 'A' && lo <= 'F') vBin[i] |= (lo - 'A') + 10; + } + return vBin; +} + +Vector Convert::ToMd5(const Vector& vBin) +{ + MD5Hash md5; + md5.update(vBin.data(), Utils::Cast32(vBin.size())); + md5.finalize(); + + Vector vOut = ToBinFromHex(_C(md5.hexdigest())); + return vOut; +} + +String Convert::ToMd5String(const Vector& vBin) +{ + return ToHex(ToMd5(vBin)); +} + +void Convert::Test() +{ + BYTE dataUint8[] = { 0, 127, 255 }; + UINT16 dataUint16[] = { 0, 127, 65535, 65534 }; + char dataSint8[] = { 64, -127, -128 }; + short dataSint16[] = { 32767, -32766, -32768, -32767 }; + float dataFloat[4]; + + Uint8ToUnorm(dataUint8, dataFloat, 3); + UnormToUint8(dataFloat, dataUint8, 3); + Uint16ToUnorm(dataUint16, dataFloat, 4); + UnormToUint16(dataFloat, dataUint16, 4); + Sint8ToSnorm(dataSint8, dataFloat, 3); + SnormToSint8(dataFloat, dataSint8, 3); + Sint16ToSnorm(dataSint16, dataFloat, 4); + SnormToSint16(dataFloat, dataSint16, 4); + + Vector vBin; + vBin.resize(16); + + Random random; + random.NextArray(vBin); + + const String base64 = ToBase64(vBin); + const Vector vBinBase64 = ToBinFromBase64(_C(base64)); + + const String hex = ToHex(vBin); + const Vector vBinHex = ToBinFromHex(_C(hex)); + + VERUS_RT_ASSERT(std::equal(vBin.begin(), vBin.end(), vBinHex.begin())); + VERUS_RT_ASSERT(std::equal(vBin.begin(), vBin.end(), vBinBase64.begin())); + + CSZ txt = "Hello World 1! Hello World 2! Hello World 3!"; + vBin.assign(txt, txt + strlen(txt)); + const Vector vMd5 = ToMd5(vBin); + const String md5 = ToMd5String(vBin); + VERUS_RT_ASSERT(md5 == "C84AAFD3D09E719514977BF3624F4D85"); +} diff --git a/Verus/src/Global/Convert.h b/Verus/src/Global/Convert.h new file mode 100644 index 0000000..2e98a84 --- /dev/null +++ b/Verus/src/Global/Convert.h @@ -0,0 +1,82 @@ +#pragma once + +namespace verus +{ + //! This class converts different data types. + class Convert + { + public: + // Single floating-point to integer: + static BYTE UnormToUint8(float x); + static UINT16 UnormToUint16(float x); + static char SnormToSint8(float x); + static short SnormToSint16(float x); + + // Single integer to Floating-point: + static float Uint8ToUnorm(BYTE x); + static float Uint16ToUnorm(UINT16 x); + static float Sint8ToSnorm(char x); + static float Sint16ToSnorm(short x); + + // Array of floating-points to integers: + static void UnormToUint8(const float* pIn, BYTE* pOut, int num); + static void UnormToUint16(const float* pIn, UINT16* pOut, int num); + static void SnormToSint8(const float* pIn, char* pOut, int num); + static void SnormToSint16(const float* pIn, short* pOut, int num); + + // Array of integers to floating-points: + static void Uint8ToUnorm(const BYTE* pIn, float* pOut, int num); + static void Uint16ToUnorm(const UINT16* pIn, float* pOut, int num); + static void Sint8ToSnorm(const char* pIn, float* pOut, int num); + static void Sint16ToSnorm(const short* pIn, float* pOut, int num); + + // Integers to integers: + static short Sint8ToSint16(char x); + static char Sint16ToSint8(short x); + static void Sint8ToSint16(const char* pIn, short* pOut, int num); + static void Sint16ToSint8(const short* pIn, char* pOut, int num); + + /* ??? */ static void ToDeviceNormal(const char* pIn, char* pOut); + /* ??? */ static UINT32 ToDeviceColor(UINT32 color); + + /* ??? */ static float Byte256ToSFloat(BYTE in); + + // 4 bits per channel: + static UINT16 Uint8x4ToUint4x4(UINT32 in); + static UINT32 Uint4x4ToUint8x4(UINT16 in); + + // Colors: + static UINT32 Color16To32(UINT16 in); + static void ColorInt32ToFloat(UINT32 in, float* out, bool sRGB = true); + static UINT32 ColorFloatToInt32(const float* in, bool sRGB = true); + static void ColorTextToFloat4(CSZ sz, float* out, bool sRGB = true); + static UINT32 ColorTextToInt32(CSZ sz); + + // Floating-point quantization: + static UINT16 QuantizeFloat(float f, float mn, float mx); + static float DequantizeFloat(UINT16 i, float mn, float mx); + static BYTE QuantizeFloatToByte(float f, float mn, float mx); + static float DequantizeFloatFromByte(BYTE i, float mn, float mx); + + /* ??? */ static void ToCorrectNormal(const char* in, char* out); + /* ??? */ static UINT32 ToCorrectColor(UINT32 in); + + /* ??? */ static void ByteToChar3(const BYTE* in, char* out); + /* ??? */ static void ByteToShort3(const BYTE* in, short* out); + + // Base64: + static String ToBase64(const Vector& vBin); + static Vector ToBinFromBase64(CSZ base64); + + // Hexadecimal: + static String ByteToHex(BYTE b); + static String ToHex(const Vector& vBin); + static Vector ToBinFromHex(CSZ hex); + + // MD5: + static Vector ToMd5(const Vector& vBin); + static String ToMd5String(const Vector& vBin); + + static void Test(); + }; +} diff --git a/Verus/src/Global/EngineInit.cpp b/Verus/src/Global/EngineInit.cpp new file mode 100644 index 0000000..8bf3f87 --- /dev/null +++ b/Verus/src/Global/EngineInit.cpp @@ -0,0 +1,122 @@ +#include "verus.h" + +using namespace verus; + +void EngineInit::Make() +{ +#if 0 + if (_makeUtils) + Make_Utils(); + if (_makeNet) + Make_Net(); + if (_makeIO) + Make_IO(); + if (_makeInput) + Make_Input(); + if (_makeAudio) + Make_Audio(); + if (_makeCGL) + Make_CGL(); + if (_makePhysics) + Make_Physics(); + if (_makeEffects) + Make_Effects(); + if (_makeExtra) + Make_Extra(); + if (_makeScene) + Make_Scene(); + if (_makeGUI) + Make_GUI(); +#endif +} + +void EngineInit::Free() +{ +#if 0 + if (_makeGUI) + Free_GUI(); + if (_makeScene) + Free_Scene(); + if (_makeExtra) + Free_Extra(); + if (_makeEffects) + Free_Effects(); + if (_makePhysics) + Free_Physics(); + if (_makeCGL) + Free_CGL(); + if (_makeAudio) + Free_Audio(); + if (_makeInput) + Free_Input(); + if (_makeIO) + Free_IO(); + if (_makeNet) + Free_Net(); + if (_makeUtils) + Free_Utils(); +#endif +} + +void EngineInit::Init(Input::PKeyMapperDelegate pKeyMapperDelegate, CGI::RenderDelegate* pRenderDelegate, bool createWindow) +{ +#if 0 + if (_makeUtils) + Utils::CTimer::I().Init(); + + if (_makeIO) + IO::CAsync::I().Init(); + + // "A.P.I.": + if (_makeAudio) + Audio::CAudio::I().Init(); + if (_makePhysics) + Physics::CBullet::I().Init(); + if (_makeInput) + { + Input::CKeyMapper::I().Init(); + Input::CKeyMapper::I().SetDelegate(pKeyMapperDelegate); + } + + if (_makeCGL) + { + CGL::CRender::I().Init(pRenderDelegate, createWindow); +#ifdef _WIN32 + CGL::CRender::InitWin32(_C(CUtils::I().GetWritablePath()), 101); +#endif + } + + // Static init: + if (_makeEffects) + Effects::CParticles::InitStatic(); + if (_makeGUI) + GUI::CFont::InitStatic(); + if (_makeScene) + { + Scene::CMesh::InitStatic(); + Scene::CTerrain::InitStatic(); + Scene::CForest::InitStatic(); + } + + // Helpers: + if (_makeCGL) + CGL::CDebugRender::I().Init(); + if (_makeScene) + Scene::CHelpers::I().Init(); + + // Effects: + if (_makeEffects) + { + Effects::CBlur::I().Init(); + Effects::CBloom::I().Init(); + Effects::CSsao::I().Init(); + } + + // Materials & textures: + if (_makeScene) + Scene::CMaterialManager::I().Init(); + + if (_makeGUI) + GUI::CGUI::I().Init(); +#endif +} diff --git a/Verus/src/Global/EngineInit.h b/Verus/src/Global/EngineInit.h new file mode 100644 index 0000000..98cd223 --- /dev/null +++ b/Verus/src/Global/EngineInit.h @@ -0,0 +1,38 @@ +#pragma once + +namespace verus +{ + namespace Input + { + struct KeyMapperDelegate; + } + namespace CGI + { + struct RenderDelegate; + } +} + +namespace verus +{ + class EngineInit + { + public: + bool _makeUtils = true; + bool _makeNet = true; + bool _makeIO = true; + bool _makeInput = true; + bool _makeAudio = true; + bool _makeCGL = true; + bool _makePhysics = true; + bool _makeEffects = true; + bool _makeExtra = false; + bool _makeScene = true; + bool _makeGUI = true; + + void Make(); + void Free(); + + void Init(Input::KeyMapperDelegate* pKeyMapperDelegate, CGI::RenderDelegate* pRenderDelegate, bool createWindow); + }; + VERUS_TYPEDEFS(EngineInit); +} diff --git a/Verus/src/Global/EnumClass.h b/Verus/src/Global/EnumClass.h new file mode 100644 index 0000000..fc0356d --- /dev/null +++ b/Verus/src/Global/EnumClass.h @@ -0,0 +1,42 @@ +#pragma once + +namespace verus +{ + template::value>, typename U = std::underlying_type_t> + inline U operator&(T a, T b) + { + return static_cast(a)&static_cast(b); + } + + template::value>, typename U = std::underlying_type_t> + inline T operator|(T a, T b) + { + return static_cast(static_cast(a) | static_cast(b)); + } + + template::value>, typename U = std::underlying_type_t> + inline T& operator&=(T& a, T b) + { + a = static_cast(static_cast(a)&static_cast(b)); + return a; + } + + template::value>, typename U = std::underlying_type_t> + inline T& operator|=(T& a, T b) + { + a = static_cast(static_cast(a) | static_cast(b)); + return a; + } + + template::value>, typename U = std::underlying_type_t> + inline T operator~(T a) + { + return static_cast(~static_cast(a)); + } + + template::value>, typename U = std::underlying_type_t> + inline U operator+(T a) + { + return static_cast(a); + } +} diff --git a/Verus/src/Global/Global.cpp b/Verus/src/Global/Global.cpp new file mode 100644 index 0000000..4a2403f --- /dev/null +++ b/Verus/src/Global/Global.cpp @@ -0,0 +1,15 @@ +#include "verus.h" + +int g_numSingletonAlloc; + +namespace verus +{ + void Make_Global() + { + Timer::Make(); + } + void Free_Global() + { + Timer::Free(); + } +} diff --git a/Verus/src/Global/Global.h b/Verus/src/Global/Global.h new file mode 100644 index 0000000..2e61628 --- /dev/null +++ b/Verus/src/Global/Global.h @@ -0,0 +1,26 @@ +#pragma once + +#define VERUS_MEMORY_ALIGNMENT 16 + +#include "BaseAllocator.h" +#include "AlignedAllocator.h" +#include "STL.h" +#include "Store.h" +#include "Blob.h" +#include "Random.h" +#include "Utils.h" +#include "Parallel.h" +#include "Range.h" +#include "Object.h" +#include "Lockable.h" +#include "Linear.h" +#include "Str.h" +#include "Convert.h" +#include "Timer.h" +#include "EngineInit.h" + +namespace verus +{ + void Make_Global(); + void Free_Global(); +} diff --git a/Verus/src/Global/Linear.h b/Verus/src/Global/Linear.h new file mode 100644 index 0000000..e5b3f00 --- /dev/null +++ b/Verus/src/Global/Linear.h @@ -0,0 +1,103 @@ +#pragma once + +namespace verus +{ + template + class Linear + { + T _value; + T _speed; + T _min; + T _max; + + public: + Linear() : _value(0), _speed(0), _min(0), _max(1) {} + + T& GetValue() { return _value; } + const T& GetValue() const { return _value; } + + T& GetSpeed() { return _speed; } + const T& GetSpeed() const { return _speed; } + + void Set(const T& value, const T& speed) + { + _value = value; + _speed = speed; + } + + void SetValue(const T& value) + { + _value = value; + } + + void SetSpeed(const T& speed) + { + _speed = speed; + } + + void SetLimits(const T& mn, const T& mx) + { + _min = mn; + _max = mx; + } + + void Plan(const T& value, float time) + { + const T delta = value - _value; + _speed = delta / time; + } + + void SpinTo(const T& value, float speed) + { + if (value >= _value) + { + _speed = speed; + _min = _value; + _max = value; + } + else + { + _speed = -speed; + _min = value; + _max = _value; + } + } + + void Reverse() + { + _speed = -_speed; + if (_speed >= 0) + { + _max = -_min; + _min = _value; + } + else + { + _min = -_max; + _max = _value; + } + } + + void UpdateUnlimited(float dt) + { + _value += _speed * dt; + } + + void UpdateClamped(float dt) + { + _value += _speed * dt; + _value = Math::Clamp(_value, _min, _max); + } + + void UpdateClampedV(float dt) + { + _value += _speed * dt; + _value = _value.Clamp(_min, _max); + } + + void WrapFloatV() + { + _value = _value.Mod(1); + } + }; +} diff --git a/Verus/src/Global/Lockable.h b/Verus/src/Global/Lockable.h new file mode 100644 index 0000000..4e27d86 --- /dev/null +++ b/Verus/src/Global/Lockable.h @@ -0,0 +1,17 @@ +#pragma once + +#define VERUS_LOCK(o) std::unique_lock lock = std::unique_lock((o).GetMutex()) +#define VERUS_LOCK_EX(o, no) std::unique_lock lock = no ?\ + std::unique_lock((o).GetMutex(), std::defer_lock) : std::unique_lock((o).GetMutex()) + +namespace verus +{ + class Lockable + { + std::mutex _mutex; + + public: + std::mutex& GetMutex() { return _mutex; } + }; + VERUS_TYPEDEFS(Lockable); +} diff --git a/Verus/src/Global/Macros.h b/Verus/src/Global/Macros.h new file mode 100644 index 0000000..4a8b3ea --- /dev/null +++ b/Verus/src/Global/Macros.h @@ -0,0 +1,66 @@ +#pragma once + +#define VERUS_CRNL "\r\n" +#define VERUS_WHITESPACE " \t\r\n" + +#define VERUS_ARRAY_LENGTH(x) (sizeof(x)/sizeof(x[0])) +#define VERUS_LITERAL_LENGTH(x) (sizeof(x)-1) +#define VERUS_ZERO_MEM(x) memset(&x, 0, sizeof(x)) + +#define VERUS_BUFFER_OFFSET(x) ((char*)nullptr+(x)) + +#define VERUS_BITMASK_SET(flags, mask) ((flags) |= (mask)) +#define VERUS_BITMASK_UNSET(flags, mask) ((flags) &= ~(mask)) + +#define VERUS_MAIN_DEFAULT_ARGS int argc, char* argv[] + +#define VERUS_P(x) private: x; public: +#define VERUS_PD(x) protected: x; private: +#define VERUS_FRIEND(c, type) friend c type; type + +#define VERUS_SMART_DELETE(p) {if(p) {delete p; p = nullptr;}} +#define VERUS_SMART_DELETE_ARRAY(p) {if(p) {delete [] p; p = nullptr;}} + +#define _C(x) ((x).c_str()) + +#define VERUS_P_FOR(i, to) Parallel::For(0, to, [&](int i) +#define VERUS_FOR(i, to) for(int i = 0; i < to; ++i) +#define VERUS_FOREACH(T, v, it) for(T::iterator it=(v).begin(), itEnd=(v).end(); it!=itEnd; ++it) +#define VERUS_FOREACH_CONST(T, v, it) for(T::const_iterator it=(v).begin(), itEnd=(v).end(); it!=itEnd; ++it) +#define VERUS_FOREACH_REVERSE(T, v, it) for(T::reverse_iterator it=(v).rbegin(), itEnd=(v).rend(); it!=itEnd; ++it) +#define VERUS_FOREACH_REVERSE_CONST(T, v, it) for(T::const_reverse_iterator it=(v).rbegin(), itEnd=(v).rend(); it!=itEnd; ++it) +#define VERUS_FOREACH_X(T, v, it) for(T::iterator it=(v).begin(), itEnd=(v).end(); it!=itEnd;) +#define VERUS_FOREACH_X_CONST(T, v, it) for(T::const_iterator it=(v).begin(), itEnd=(v).end(); it!=itEnd;) +#define VERUS_FOREACH_X_REVERSE(T, v, it) for(T::reverse_iterator it=(v).rbegin(), itEnd=(v).rend(); it!=itEnd;) +#define VERUS_FOREACH_X_REVERSE_CONST(T, v, it) for(T::const_reverse_iterator it=(v).rbegin(), itEnd=(v).rend(); it!=itEnd;) +// For loops with erase() use this: +#define VERUS_WHILE(T, v, it) T::iterator it=(v).begin(); while(it!=(v).end()) + +#define VERUS_IF_FOUND_IN(T, map, x, it)\ + auto it = map.find(x);\ + if(it != map.end()) + +#define VERUS_TYPEDEFS(x)\ + typedef x& R##x;\ + typedef const x& Rc##x;\ + typedef x* P##x;\ + typedef const x* Pc##x + +// Circular buffer's size must be power of two: +#define VERUS_CIRCULAR_ADD(x, mx)\ + x++;\ + x &= mx-1 +#define VERUS_CIRCULAR_IS_FULL(r, w, mx)\ + (((w+1)&(mx-1)) == r) + +// Colors: +#define VERUS_COLOR_RGBA(r,g,b,a) ((UINT32)((((a)&0xFF)<<24)|(((b)&0xFF)<<16)|(((g)&0xFF)<<8)|((r)&0xFF))) +#define VERUS_COLOR_TO_D3D(color) (((color)&0xFF00FF00)|(((color)>>16)&0xFF)|(((color)&0xFF)<<16)) +#define VERUS_COLOR_WHITE VERUS_COLOR_RGBA(255, 255, 255, 255) +#define VERUS_COLOR_BLACK VERUS_COLOR_RGBA(0, 0, 0, 255) + +#ifdef _WIN32 +# define VERUS_SDL_CENTERED _putenv("SDL_VIDEO_WINDOW_POS"); _putenv("SDL_VIDEO_CENTERED=1") +#else +# define VERUS_SDL_CENTERED putenv("SDL_VIDEO_WINDOW_POS"); putenv("SDL_VIDEO_CENTERED=1") +#endif diff --git a/Verus/src/Global/Object.cpp b/Verus/src/Global/Object.cpp new file mode 100644 index 0000000..d3a34a8 --- /dev/null +++ b/Verus/src/Global/Object.cpp @@ -0,0 +1,39 @@ +#include "verus.h" + +using namespace verus; + +Object::Object() +{ + _flags = 0; +} + +Object::~Object() +{ + VERUS_RT_ASSERT(!IsInitialized()); +} + +void Object::Init() +{ + VERUS_RT_ASSERT(!IsInitialized()); + SetFlag(ObjectFlags::init); +} + +void Object::Done() +{ + _flags = 0; +} + +#ifdef VERUS_DEBUG +void Object::UpdateOnceCheck() +{ + const UINT32 numFrames = CGL::CRender::I().GetNumFrames(); + VERUS_RT_ASSERT(_updateOnceFrame <= numFrames); + _updateOnceFrame = numFrames + 1; +} + +void Object::UpdateOnceCheckDraw() +{ + const UINT32 numFrames = CGL::CRender::I().GetNumFrames(); + VERUS_RT_ASSERT(_updateOnceFrame > numFrames); +} +#endif diff --git a/Verus/src/Global/Object.h b/Verus/src/Global/Object.h new file mode 100644 index 0000000..eef6350 --- /dev/null +++ b/Verus/src/Global/Object.h @@ -0,0 +1,57 @@ +#pragma once + +#ifdef VERUS_DEBUG +# define VERUS_UPDATE_ONCE_CHECK UpdateOnceCheck() +# define VERUS_UPDATE_ONCE_CHECK_DRAW UpdateOnceCheckDraw() +#else +# define VERUS_UPDATE_ONCE_CHECK +# define VERUS_UPDATE_ONCE_CHECK_DRAW +#endif + +#define VERUS_INIT()\ + Object::Init(); + +#define VERUS_DONE(x)\ + if(Object::IsInitialized()) {\ + Object::Done();\ + this->~x();\ + new(this) x();} + +namespace verus +{ + struct ObjectFlags + { + enum + { + init = (1 << 0), + user = (1 << 1) + }; + }; + + class Object + { + std::atomic_uint _flags; +#ifdef VERUS_DEBUG + UINT32 _updateOnceFrame = 0; +#endif + + protected: + Object(); + virtual ~Object(); + + void Init(); + void Done(); + +#ifdef VERUS_DEBUG + void UpdateOnceCheck(); + void UpdateOnceCheckDraw(); +#endif + + public: + bool IsInitialized() const { return _flags & ObjectFlags::init; } + + bool IsFlagSet(UINT32 mask) const { return !!(_flags&mask); } + void SetFlag(UINT32 mask) { _flags |= mask; } + void ResetFlag(UINT32 mask) { _flags &= ~mask; } + }; +} diff --git a/Verus/src/Global/Parallel.h b/Verus/src/Global/Parallel.h new file mode 100644 index 0000000..ba720b6 --- /dev/null +++ b/Verus/src/Global/Parallel.h @@ -0,0 +1,44 @@ +#pragma once + +namespace verus +{ + class Parallel + { + public: + template + static void For(int from, int to, TFunc func, int minTime = 10, int minShare = 1) + { + const int total = to - from; + VERUS_RT_ASSERT(minShare <= total); + const UINT32 numCoresLimit = total / minShare; + const int numCores = Math::Min(std::thread::hardware_concurrency(), numCoresLimit); + const int numExThreads = numCores - 1; + const int share = total / numCores; + const int extra = total - share * numExThreads; + std::vector v; + v.reserve(numExThreads); + const std::chrono::high_resolution_clock::time_point t0 = std::chrono::high_resolution_clock::now(); + VERUS_FOR(i, numExThreads) + { + v.push_back(std::thread([i, share, func]() + { + const int from = share * i; + const int to = from + share; + for (int j = from; j < to; ++j) + func(j); + })); + } + { + const int from = share * numExThreads; + const int to = from + extra; + for (int j = from; j < to; ++j) + func(j); + } + VERUS_FOREACH(std::vector, v, it) + it->join(); + const std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now(); + const std::chrono::milliseconds d = std::chrono::duration_cast(t1 - t0); + //VERUS_RT_ASSERT(d.count() >= minTime); + } + }; +} diff --git a/Verus/src/Global/QuickRefs.h b/Verus/src/Global/QuickRefs.h new file mode 100644 index 0000000..91cdf72 --- /dev/null +++ b/Verus/src/Global/QuickRefs.h @@ -0,0 +1,29 @@ +#pragma once + +#define VERUS_QREF_ASYNC IO::RAsync async = IO::Async::I() +#define VERUS_QREF_ASYS Audio::RAudioSystem asys = Audio::AudioSystem::I() +#define VERUS_QREF_ATMO Scene::RAtmosphere atmo = Scene::Atmosphere::I() +#define VERUS_QREF_BLOOM Effects::RBloom bloom = Effects::Bloom::I() +#define VERUS_QREF_BLUR Effects::RBlur blur = Effects::Blur::I() +#define VERUS_QREF_BULLET Physics::RBullet bullet = Physics::Bullet::I() +#define VERUS_QREF_CONST_SETTINGS RcSettings settings = CSettings::IConst() +#define VERUS_QREF_DEPTH Effects::RDepth depth = Effects::Depth::I() +#define VERUS_QREF_DR CGL::RDebugRender dr = CGL::DebugRender::I() +#define VERUS_QREF_FSYS IO::RFileSys fsys = IO::FileSys::I() +#define VERUS_QREF_GAME Game::RGame game = Game::Game::I() +#define VERUS_QREF_GRASS Scene::RGrass grass = Scene::Grass::I() +#define VERUS_QREF_GUI GUI::RGUI gui = GUI::GUI::I() +#define VERUS_QREF_HELPERS Scene::RHelpers helpers = Scene::Helpers::I() +#define VERUS_QREF_KM Input::RKeyMapper km = Input::KeyMapper::I() +#define VERUS_QREF_LS Scene::RLandscape ls = Scene::Landscape::I() +#define VERUS_QREF_MM Scene::RMaterialManager mm = Scene::MaterialManager::I() +#define VERUS_QREF_MP Net::RMultiplayer mp = Net::Multiplayer::I() +#define VERUS_QREF_PHYSICS Physics::RPhysics physics = Physics::Physics::I() +#define VERUS_QREF_RENDER CGL::RRender render = CGL::Render::I() +#define VERUS_QREF_SETTINGS App::RSettings settings = App::Settings::I() +#define VERUS_QREF_SM Scene::RSceneManager sm = Scene::SceneManager::I() +#define VERUS_QREF_SSAO Effects::RSsao ssao = Effects::Ssao::I() +#define VERUS_QREF_TIMER RTimer timer = Timer::I(); const float dt = timer.GetDeltaTime() +#define VERUS_QREF_TIMER_GUI RTimer timer = Timer::I(); const float dt = timer.GetDeltaTime(Timer::Type::gui) +#define VERUS_QREF_UTILS RUtils utils = Utils::I() +#define VERUS_QREF_WATER Scene::RWater water = Scene::Water::I() diff --git a/Verus/src/Global/Random.cpp b/Verus/src/Global/Random.cpp new file mode 100644 index 0000000..b32706e --- /dev/null +++ b/Verus/src/Global/Random.cpp @@ -0,0 +1,56 @@ +#include "verus.h" + +using namespace verus; + +Random::Random() +{ + std::random_device rd; + Seed(rd()); +} + +Random::Random(UINT32 seed) : + _mt(seed) +{ +} + +Random::~Random() +{ +} + +UINT32 Random::Next() +{ + return _mt(); +} + +double Random::NextDouble() +{ + return static_cast(Next()) / std::numeric_limits::max(); +} + +float Random::NextFloat() +{ + return static_cast(Next()) / std::numeric_limits::max(); +} + +void Random::NextArray(UINT32* p, int num) +{ + VERUS_FOR(i, num) + p[i] = Next(); +} + +void Random::NextArray(float* p, int num) +{ + VERUS_FOR(i, num) + p[i] = NextFloat(); +} + +void Random::NextArray(Vector& v) +{ + VERUS_FOREACH(Vector, v, it) + *it = Next(); +} + +void Random::Seed(UINT32 seed) +{ + _mt.seed(seed); +} diff --git a/Verus/src/Global/Random.h b/Verus/src/Global/Random.h new file mode 100644 index 0000000..3bed06a --- /dev/null +++ b/Verus/src/Global/Random.h @@ -0,0 +1,27 @@ +#pragma once + +namespace verus +{ + class Random + { + std::mt19937 _mt; + + public: + Random(); + Random(UINT32 seed); + ~Random(); + + UINT32 Next(); + double NextDouble(); + float NextFloat(); + + void NextArray(UINT32* p, int num); + void NextArray(float* p, int num); + void NextArray(Vector& v); + + std::mt19937& GetGenerator() { return _mt; } + + void Seed(UINT32 seed); + }; + VERUS_TYPEDEFS(Random); +} diff --git a/Verus/src/Global/Range.h b/Verus/src/Global/Range.h new file mode 100644 index 0000000..1544654 --- /dev/null +++ b/Verus/src/Global/Range.h @@ -0,0 +1,19 @@ +#pragma once + +namespace verus +{ + template + class Range + { + public: + T _min; + T _max; + + Range() : _min(0), _max(0) {} + Range(T x) : _min(x), _max(x) {} + Range(T mn, T mx) : _min(mn), _max(mx) { VERUS_RT_ASSERT(_min <= _max); } + + T GetRange() const { return _max - _min; } + T GetRandomValue() const { return _min + Utils::I().GetRandom().NextFloat()*GetRange(); } + }; +} diff --git a/Verus/src/Global/STL.h b/Verus/src/Global/STL.h new file mode 100644 index 0000000..12c36c6 --- /dev/null +++ b/Verus/src/Global/STL.h @@ -0,0 +1,112 @@ +#pragma once + +namespace verus +{ + //! Custom allocator for the Standard Template Library. Will try to use the allocator provided by Utils. + template + class AllocatorAwareSTL + { + void operator=(const AllocatorAwareSTL&); + + public: + typedef T value_type; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + + AllocatorAwareSTL() {} + AllocatorAwareSTL(const AllocatorAwareSTL&) {} + ~AllocatorAwareSTL() {} + + template + AllocatorAwareSTL(const AllocatorAwareSTL<_Other>& other) {} + + template + struct rebind + { + typedef AllocatorAwareSTL other; + }; + + pointer address(reference r) const + { + return &r; + } + + const_pointer address(const_reference r) const + { + return &r; + } + + pointer allocate(size_type n) + { + pointer p = static_cast(AllocatorAware::UtilsMalloc(n * sizeof(value_type))); + if (p) + { + return p; + } + else + { + p = static_cast(malloc(n * sizeof(value_type))); + char txt[80]; + sprintf_s(txt, "allocate(), malloc() 0x%p", p); + VERUS_LOG_DEBUG(txt); + return p; + } + } + + void deallocate(pointer p, size_type) + { + if (!AllocatorAware::UtilsFree(p)) + { + char txt[80]; + sprintf_s(txt, "deallocate(), free() 0x%p", p); + VERUS_LOG_DEBUG(txt); + free(p); + } + } + + void construct(pointer p, const value_type& val) + { + new(p)value_type(val); + } + + void destroy(pointer p) + { + p->~value_type(); + } + + size_type max_size() const + { + return ULONG_MAX / sizeof(value_type); + } + }; + + //! This function will compare two allocators. + template + bool operator==(const AllocatorAwareSTL& l, const AllocatorAwareSTL& r) + { + return true; + } + + //! This function will compare two allocators. + template + bool operator!=(const AllocatorAwareSTL& l, const AllocatorAwareSTL& r) + { + return !(l == r); + } +} + +namespace verus +{ + template using Vector = std::vector >; + template using List = std::list >; + template using Set = std::set , AllocatorAwareSTL>; + template using MultiSet = std::multiset , AllocatorAwareSTL>; + template using HashSet = std::unordered_set , std::equal_to, AllocatorAwareSTL>; + template using Map = std::map , AllocatorAwareSTL>; + template using MultiMap = std::multimap , AllocatorAwareSTL>; + template using HashMap = std::unordered_map , std::equal_to, AllocatorAwareSTL>; +} diff --git a/Verus/src/Global/Singleton.h b/Verus/src/Global/Singleton.h new file mode 100644 index 0000000..721993c --- /dev/null +++ b/Verus/src/Global/Singleton.h @@ -0,0 +1,74 @@ +#pragma once + +extern int g_numSingletonAlloc; + +namespace verus +{ + //! Restricts the instantiation of a class to one object. + template + class Singleton : public AllocatorAware + { + static T* s_pSingleton; + + Singleton(const Singleton& that); + Singleton& operator=(const Singleton& that); + + public: + Singleton() + { + s_pSingleton = static_cast(this); + } + + virtual ~Singleton() + { + } + + static inline void Make() + { + if (s_pSingleton) + return; + new T(); + g_numSingletonAlloc++; + } + + static inline void Free() + { + if (s_pSingleton) + { + g_numSingletonAlloc--; + delete s_pSingleton; + } + s_pSingleton = nullptr; + } + + static inline T& I() { Test(); return *s_pSingleton; } + static inline T* P() { Test(); return s_pSingleton; } + + static inline const T& IConst() { Test(); return *s_pSingleton; } + static inline const T* PConst() { Test(); return s_pSingleton; } + + static inline bool IsValidSingleton() + { + return s_pSingleton != nullptr; + } + + static inline void Assign(T* p) { s_pSingleton = p; } + + static inline void Test() + { +#ifdef _DEBUG + //if (!IsValidSingleton()) + // VERUS_LOG_DEBUG(T::GetSingletonFailMessage()); + //VERUS_RT_ASSERT(s_pSingleton); +#endif + }; + + static inline CSZ GetSingletonFailMessage() { return "Singleton FAIL.\r\n"; } + + static inline void TestAllocCount() + { + //VERUS_RT_ASSERT(!g_numSingletonAlloc); + } + }; + template T* Singleton::s_pSingleton = nullptr; +} diff --git a/Verus/src/Global/Store.h b/Verus/src/Global/Store.h new file mode 100644 index 0000000..8494a81 --- /dev/null +++ b/Verus/src/Global/Store.h @@ -0,0 +1,177 @@ +#pragma once + +namespace verus +{ + //! A convenient way to store a collection of objects. + + //! It's a good idea not to use new/delete explicitly. Use STL allocator. + //! 'Unique' class uses map and supports reference counting. + //! The Insert method returns a raw pointer which should be wrapped inside a Ptr. + //! + //! Types of pointers: + //! - raw pointer - very dumb, no automatic initialization. + //! - Ptr wrapper - like a smart pointer, but even smarter. + //! - Pwn wrapper - owning Ptr, basically calls Done in destructor. + //! Use Ptr for function parameters. Use Pwn for class members. + //! + template + class Store + { + protected: + typedef List TList; + + TList _list; + + public: + TValue* Insert() + { + _list.emplace_back(); + return &_list.back(); + } + + void Delete(TValue* p) + { + _list.remove_if([p](const TValue& v) + { + return p == &v; + }); + } + + void DeleteAll() + { + _list.clear(); + } + + TValue& GetStoredAt(int index) + { + auto it = _list.begin(); + std::advance(it, index); + return *it; + } + + int GetNumStored() const + { + return _list.size(); + } + }; + + template + class StoreUnique + { + protected: + typedef Map TMap; + + TMap _map; + + public: + TValue* Insert(const TKey& key) + { + VERUS_IF_FOUND_IN(TMap, _map, key, it) + { + it->second.AddRef(); + return &it->second; + } + return &_map[key]; + } + + TValue* Find(const TKey& key) + { + VERUS_IF_FOUND_IN(TMap, _map, key, it) + return &it->second; + return nullptr; + } + + void Delete(const TKey& key) + { + VERUS_IF_FOUND_IN(TMap, _map, key, it) + { + if (it->second.Done()) + _map.erase(it); + } + } + + void DeleteAll() + { + for (auto& x : _map) + { + while (!x.second.Done()); + } + _map.clear(); + } + + TValue& GetStoredAt(int index) + { + auto it = _map.begin(); + std::advance(it, index); + return *it; + } + + int GetNumStored() const + { + return _map.size(); + } + }; + + template + class Ptr + { + protected: + T* _p; + + public: + Ptr(T* p = nullptr) : _p(p) {} + ~Ptr() {} + + T& operator*() const { return *_p; } + T* operator->() const { return _p; } + explicit operator bool() const { return !!_p; } + bool operator==(const Ptr& that) const { return _p == that._p; } + bool operator!=(const Ptr& that) const { return _p != that._p; } + bool operator<(const Ptr& that) const { return _p < that._p; } + T* Attach(T* ptr) + { + T* p = _p; + _p = ptr; + return p; + } + T* Detach() + { + T* p = _p; + _p = nullptr; + return p; + } + }; + + template + class Pwns + { + T _t[NUM]; + + public: + Pwns() + { + } + + ~Pwns() + { + Done(); + } + + void Done() + { + VERUS_FOR(i, NUM) + _t[i].Done(); + } + + const T& operator[](int i) const + { + VERUS_RT_ASSERT(i >= 0 && i < NUM); + return _t[i]; + } + T& operator[](int i) + { + VERUS_RT_ASSERT(i >= 0 && i < NUM); + return _t[i]; + } + }; +} diff --git a/Verus/src/Global/Str.cpp b/Verus/src/Global/Str.cpp new file mode 100644 index 0000000..a1818f1 --- /dev/null +++ b/Verus/src/Global/Str.cpp @@ -0,0 +1,560 @@ +#include "verus.h" + +using namespace verus; + +WideString Str::Utf8ToWide(RcString utf8) +{ + Vector vWide; + utf8::utf8to16(utf8.begin(), utf8::find_invalid(utf8.begin(), utf8.end()), std::back_inserter(vWide)); + return WideString(vWide.begin(), vWide.end()); +} + +String Str::WideToUtf8(RcWideString wide) +{ + Vector vUtf8; + utf8::utf16to8(wide.begin(), utf8::find_invalid(wide.begin(), wide.end()), std::back_inserter(vUtf8)); + return String(vUtf8.begin(), vUtf8.end()); +} + +void Str::Utf8ToWide(CSZ utf8, WSZ wide, int length) +{ +#ifdef _WIN32 + MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wide, length); +#else + const WideString s = Utf8ToWide(utf8); + wcsncpy(wide, s.c_str(), length); + wide[length - 1] = 0; +#endif +} + +void Str::WideToUtf8(CWSZ wide, SZ utf8, int length) +{ +#ifdef _WIN32 + WideCharToMultiByte(CP_UTF8, 0, wide, -1, utf8, length, 0, 0); +#else + const String s = WideToUtf8(wide); + strncpy(utf8, s.c_str(), length); + utf8[length - 1] = 0; +#endif +} + +String Str::ResizeUtf8(RcString utf8, int len) +{ + String ret = utf8; + bool valid = true; + do + { + ret = ret.substr(0, len); + valid = utf8::is_valid(ret.begin(), ret.end()); + len--; + } while (len > 0 && !valid); + return ret; +} + +bool Str::StartsWith(CSZ s, CSZ begin, bool caseSensitive) +{ + return caseSensitive ? !strncmp(s, begin, strlen(begin)) : !_strnicmp(s, begin, strlen(begin)); +} + +bool Str::EndsWith(CSZ s, CSZ end, bool caseSensitive) +{ + const size_t lenString = strlen(s); + const size_t lenEnd = strlen(end); + if (lenEnd > lenString) + return false; + CSZ test = s + lenString - lenEnd; + return caseSensitive ? !strcmp(test, end) : !_stricmp(test, end); +} + +CSZ Str::Find(CSZ s, CSZ sub, bool caseSensitive) +{ + return caseSensitive ? strstr(s, sub) : StrStrIA(s, sub); +} + +void Str::ToLower(SZ s) +{ + while (*s) + { + *s = tolower(*s); + s++; + } +} + +void Str::ToUpper(SZ s) +{ + while (*s) + { + *s = toupper(*s); + s++; + } +} + +int Str::ReplaceAll(RString s, CSZ was, CSZ with) +{ + const size_t len = strlen(was); + const size_t lenNew = strlen(with); + size_t pos = s.find(was); + int num = 0; + while (pos < 1024 * 1024) + { + s.replace(pos, len, with); + pos = s.find(was, pos + lenNew); + num++; + } + return num; +} + +void Str::ReplaceExtension(RString s, CSZ ext) +{ + size_t d = s.rfind("."); + if (d != String::npos) + { + size_t num = s.length() - d; + s.replace(d, num, ext); + } +} + +void Str::ReplaceFilename(RString s, CSZ name) +{ + size_t d = s.rfind("/"); + if (d != String::npos) + { + size_t num = s.length() - d; + s.replace(d + 1, num, name); + } +} + +String Str::FromInt(int n) +{ + StringStream ss; + ss << n; + return ss.str(); +} + +void Str::Explode(CSZ s, CSZ delimiter, Vector& pieces) +{ + pieces.clear(); + const size_t lenDelim = strlen(delimiter); + CSZ found = strstr(s, delimiter); + while (found) + { + if (found - s > 0) + pieces.push_back(String(s, found)); + s = found + lenDelim; + found = strstr(s, delimiter); + } + if (strlen(s) > 0) + pieces.push_back(s); +} + +void Str::Trim(RString s) +{ + auto pos = s.find_last_not_of(VERUS_WHITESPACE); + if (pos != String::npos) + { + s.erase(pos + 1); + pos = s.find_first_not_of(VERUS_WHITESPACE); + if (pos != String::npos) + s.erase(0, pos); + } + else + s.erase(s.begin(), s.end()); +} + +void Str::TrimVector(Vector& v) +{ + VERUS_WHILE(Vector, v, it) + { + Trim(*it); + if ((*it).empty()) + it = v.erase(it); + else + it++; + } +} + +void Str::ReadLines(CSZ s, Vector& lines) +{ + Explode(s, "\n", lines); + TrimVector(lines); +} + +BYTE Str::ByteFromHex(CSZ s) +{ + BYTE ret = 0; + if (strlen(s) >= 2) + { + const char hi = s[0]; + const char lo = s[1]; + + if /**/ (hi >= '0' && hi <= '9') ret = ((hi - '0')) << 4; + else if (hi >= 'a' && hi <= 'f') ret = ((hi - 'a') + 10) << 4; + else if (hi >= 'A' && hi <= 'F') ret = ((hi - 'A') + 10) << 4; + + if /**/ (lo >= '0' && lo <= '9') ret |= (lo - '0'); + else if (lo >= 'a' && lo <= 'f') ret |= (lo - 'a') + 10; + else if (lo >= 'A' && lo <= 'F') ret |= (lo - 'A') + 10; + } + return ret; +} + +String Str::GetDate(bool andTime) +{ + time_t t; + time(&t); + tm* pTM = localtime(&t); + char buffer[40]; + strftime(buffer, sizeof(buffer), andTime ? "%Y-%m-%d %H-%M-%S" : "%Y-%m-%d", pTM); + return buffer; +} + +glm::vec2 Str::FromStringVec2(CSZ s) +{ + glm::vec2 v; + sscanf(s, "%f %f", &v.x, &v.y); + return v; +} + +glm::vec3 Str::FromStringVec3(CSZ s) +{ + glm::vec3 v; + sscanf(s, "%f %f %f", &v.x, &v.y, &v.z); + return v; +} + +glm::vec4 Str::FromStringVec4(CSZ s) +{ + glm::vec4 v; + sscanf(s, "%f %f %f %f", &v.x, &v.y, &v.z, &v.w); + return v; +} + +String Str::ToString(int i) +{ + char buffer[20]; + sprintf_s(buffer, "%d", i); + return buffer; +} + +String Str::ToString(float f) +{ + char buffer[20]; + sprintf_s(buffer, "%g", f); + return buffer; +} + +String Str::ToString(const glm::vec2& v) +{ + char buffer[40]; + sprintf_s(buffer, "%g %g", v.x, v.y); + return buffer; +} + +String Str::ToString(const glm::vec3& v) +{ + char buffer[60]; + sprintf_s(buffer, "%g %g %g", v.x, v.y, v.z); + return buffer; +} + +String Str::ToString(const glm::vec4& v) +{ + char buffer[80]; + sprintf_s(buffer, "%g %g %g %g", v.x, v.y, v.z, v.w); + return buffer; +} + +glm::vec4 Str::FromColorString(CSZ sz, bool sRGB) +{ + float rgba[4]; + Convert::ColorTextToFloat4(sz, rgba, sRGB); + return glm::make_vec4(rgba); +} + +String Str::ToColorString(const glm::vec4& v, bool sRGB) +{ + const UINT32 color = Convert::ColorFloatToInt32(glm::value_ptr(v), sRGB); + char txt[20]; + sprintf_s(txt, "%02X%02X%02X%02X", + (color >> 0) & 0xFF, + (color >> 8) & 0xFF, + (color >> 16) & 0xFF, + (color >> 24) & 0xFF); + return txt; +} + +String Str::XmlEscape(CSZ s) +{ + StringStream ss; + const int len = Utils::Cast32(strlen(s)); + VERUS_FOR(i, len) + { + switch (s[i]) + { + case '&': ss << "&"; break; + case '<': ss << "<"; break; + case '>': ss << ">"; break; + case '\"': ss << """; break; + case '\'': ss << "'"; break; + default: ss << s[i]; + } + } + return ss.str(); +} + +String Str::XmlUnescape(CSZ s) +{ + StringStream ss; + const int len = Utils::Cast32(strlen(s)); + VERUS_FOR(i, len) + { + if (s[i] == '&') + { + if /**/ (!strncmp(s + i, "&", 5)) { ss << '&'; i += 4; } + else if (!strncmp(s + i, "<", 4)) { ss << '<'; i += 3; } + else if (!strncmp(s + i, ">", 4)) { ss << '>'; i += 3; } + else if (!strncmp(s + i, """, 6)) { ss << '\"'; i += 5; } + else if (!strncmp(s + i, "'", 6)) { ss << '\''; i += 5; } + } + else + ss << s[i]; + } + return ss.str(); +} + +String Str::UrlEncode(CSZ s) +{ + String ret; + ret.reserve(strlen(s) * 3); + while (*s) + { + if ((*s >= 'A' && *s <= 'Z') || + (*s >= 'a' && *s <= 'z') || + (*s >= '0' && *s <= '9')) + { + ret.append(1, *s); + } + else + { + ret += "%"; + ret += Convert::ByteToHex(*s); + } + s++; + } + return ret; +} + +String Str::FixCyrillicX(CSZ s, bool trans) +{ + String ret; + +#ifdef _WIN32 + + while (*s) + { + if (*s == '\\') + { + s = strchr(s + 1, '\\'); + if (!s) + break; + else + s++; + } + + if (!(*s)) + break; + + if (*s == ' ' && trans) + { + ret += '_'; + } + else if (*s > 0 && *s < 128) + { + ret += *s; + } + else + { + if (trans) + { + switch (*s) + { + case 'А': + case 'а': ret += 'a'; break; + case 'Б': + case 'б': ret += 'b'; break; + case 'В': + case 'в': ret += 'v'; break; + case 'Г': + case 'г': ret += 'g'; break; + case 'Д': + case 'д': ret += 'd'; break; + case 'Е': + case 'е': ret += 'e'; break; + case 'Ё': + case 'ё': ret += 'e'; break; + case 'Ж': + case 'ж': ret += "zh"; break; + case 'З': + case 'з': ret += 'z'; break; + case 'И': + case 'и': ret += 'i'; break; + case 'Й': + case 'й': ret += "i"; break; + + case 'К': + case 'к': ret += 'k'; break; + case 'Л': + case 'л': ret += 'l'; break; + + case 'М': + case 'м': ret += 'm'; break; + case 'Н': + case 'н': ret += 'n'; break; + case 'О': + case 'о': ret += 'o'; break; + case 'П': + case 'п': ret += 'p'; break; + case 'Р': + case 'р': ret += 'r'; break; + case 'С': + case 'с': ret += 's'; break; + case 'Т': + case 'т': ret += 't'; break; + + case 'У': + case 'у': ret += 'u'; break; + case 'Ф': + case 'ф': ret += 'f'; break; + case 'Х': + case 'х': ret += 'h'; break; + case 'Ц': + case 'ц': ret += 'c'; break; + case 'Ч': + case 'ч': ret += "ch"; break; + case 'Ш': + case 'ш': ret += "sh"; break; + case 'Щ': + case 'щ': ret += "sh"; break; + case 'Ъ': + case 'ъ': ret += ""; break; + case 'Ы': + case 'ы': ret += 'q'; break; + case 'Ь': + case 'ь': ret += ""; break; + case 'Э': + case 'э': ret += 'e'; break; + case 'Ю': + case 'ю': ret += "yu"; break; + case 'Я': + case 'я': ret += "ya"; break; + } + } + else + { + ret += *s; + } + } + s++; + } + + if (!trans) + { + char bufferA[256]; + wchar_t buffer[256]; + MultiByteToWideChar(1251, 0, ret.c_str(), -1, buffer, 256); + WideCharToMultiByte(CP_UTF8, 0, buffer, -1, bufferA, 256, 0, 0); + ret = bufferA; + } + +#endif + + return ret; +} + +String Str::CyrillicWideToAnsi(CWSZ s) +{ + String ansi; + ansi.reserve(wcslen(s)); + while (*s) + { + if (*s < 128) + { + ansi += char(*s); + } + else if (*s >= 0x410 && *s < 0x450) + { + ansi += char(*s - 0x410 + 0xC0); + } + else if (*s == 0x401) + { + ansi += char(0xA8); + } + else if (*s == 0x451) + { + ansi += char(0xB8); + } + else + { + ansi += '?'; + } + s++; + } + return ansi; +} + +bool Str::IsMultiLang(CSZ s) +{ + while (*s) + { + if (int(*s) >= 128) + return true; + s++; + } + return false; +} + +void Str::CyrillicToUppercase(SZ s) +{ + while (*s) + { + if (*s >= 'а' && *s <= 'я') + *s = *s - ('а' - 'А'); + if (*s == 'ё') + *s = 'Ё'; + s++; + } +} + +void Str::Test() +{ + String utf8Text = Str::WideToUtf8(L"Test"); + WideString wideText = Str::Utf8ToWide(utf8Text); + + VERUS_RT_ASSERT(Str::StartsWith("Foobar!", "Foo")); + VERUS_RT_ASSERT(!Str::StartsWith("Foobar!", "Bar")); + VERUS_RT_ASSERT(Str::EndsWith("Foobar!", "bar!")); + VERUS_RT_ASSERT(!Str::EndsWith("Foobar!", "Foo")); + + char makeLower[] = "HelloWorld!"; + Str::ToLower(makeLower); + VERUS_RT_ASSERT(!strcmp(makeLower, "helloworld!")); + + String replace = "Use Windows. New Windows is good!"; + ReplaceAll(replace, "Windows", "Linux"); + VERUS_RT_ASSERT(replace == "Use Linux. New Linux is good!"); + + String five = Str::FromInt(5); + VERUS_RT_ASSERT(five == "5"); + + Vector exRes; + Str::Explode("Hello|World|Foo|Bar||!|0|1|", "|", exRes); + Str::Explode("YoFooYoBarYoYoHelloYoWorld", "Yo", exRes); + + String trimMe = " Trim Me "; + Str::Trim(trimMe); + VERUS_RT_ASSERT(trimMe == "Trim Me"); + + Vector lines; + Str::ReadLines("Line1\r\nLine2\nLine3\n", lines); + VERUS_RT_ASSERT(lines.size() == 3); +} diff --git a/Verus/src/Global/Str.h b/Verus/src/Global/Str.h new file mode 100644 index 0000000..c4ba225 --- /dev/null +++ b/Verus/src/Global/Str.h @@ -0,0 +1,94 @@ +#pragma once + +namespace verus +{ + class Str + { + public: + CSZ _sz; + + Str(CSZ sz) : _sz(sz) {} + void operator=(CSZ sz) { _sz = sz; } + + bool operator==(const Str& that) const { return !strcmp(_sz, that._sz); } + bool operator!=(const Str& that) const { return 0 != strcmp(_sz, that._sz); } + + size_t Size() const { return strlen(_sz) + 1; } + size_t Length() const { return strlen(_sz); } + bool IsEmpty() const { return !_sz || !(*_sz); } + + CSZ Find(CSZ sz) const { return strstr(_sz, sz); } + + CSZ c_str() const { return _sz; } + + // UTF8 <-> Wide: + static WideString Utf8ToWide(RcString utf8); + static String WideToUtf8(RcWideString wide); + static void Utf8ToWide(CSZ utf8, WSZ wide, int length); + static void WideToUtf8(CWSZ wide, SZ utf8, int length); + + static String ResizeUtf8(RcString utf8, int len); + + static bool StartsWith(CSZ s, CSZ begin, bool caseSensitive = true); + static bool EndsWith(CSZ s, CSZ end, bool caseSensitive = true); + static CSZ Find(CSZ s, CSZ sub, bool caseSensitive = true); + static void ToLower(SZ s); + static void ToUpper(SZ s); + static int ReplaceAll(RString s, CSZ was, CSZ with); + static void ReplaceExtension(RString s, CSZ ext); + static void ReplaceFilename(RString s, CSZ name); + static String FromInt(int n); + static void Explode(CSZ s, CSZ delimiter, Vector& pieces); + static void Trim(RString s); + static void TrimVector(Vector& v); + static void ReadLines(CSZ s, Vector& lines); + static BYTE ByteFromHex(CSZ s); + static String GetDate(bool andTime = true); + + // From/To string: + static glm::vec2 FromStringVec2(CSZ s); + static glm::vec3 FromStringVec3(CSZ s); + static glm::vec4 FromStringVec4(CSZ s); + static String ToString(int i); + static String ToString(float f); + static String ToString(const glm::vec2& v); + static String ToString(const glm::vec3& v); + static String ToString(const glm::vec4& v); + static glm::vec4 FromColorString(CSZ sz, bool sRGB = true); + static String ToColorString(const glm::vec4& v, bool sRGB = true); + + // XML: + static String XmlEscape(CSZ s); + static String XmlUnescape(CSZ s); + + // Web: + static String UrlEncode(CSZ s); + + // Cyrillic: + static String FixCyrillicX(CSZ s, bool trans); + static String CyrillicWideToAnsi(CWSZ s); + static bool IsMultiLang(CSZ s); + static void CyrillicToUppercase(SZ s); + + static void Test(); + }; + + class WideStr + { + public: + CWSZ _sz; + + WideStr(CWSZ sz) : _sz(sz) {} + void operator=(CWSZ sz) { _sz = sz; } + + bool operator==(const WideStr& that) const { return !wcscmp(_sz, that._sz); } + bool operator!=(const WideStr& that) const { return 0 != wcscmp(_sz, that._sz); } + + size_t Size() const { return (wcslen(_sz) + 1) << 1; } + size_t Length() const { return wcslen(_sz); } + + CWSZ Find(CWSZ sz) const { return wcsstr(_sz, sz); } + + CWSZ c_str() const { return _sz; } + }; +} diff --git a/Verus/src/Global/SyntaxHighlight.h b/Verus/src/Global/SyntaxHighlight.h new file mode 100644 index 0000000..deb4a66 --- /dev/null +++ b/Verus/src/Global/SyntaxHighlight.h @@ -0,0 +1,26 @@ +BOOL +BYTE +VERUS_INTERFACE +CSZ +FALSE +ID +INT16 +INT32 +INT64 +NULL +offsetof +PRIVATE +PROTECTED +PUBLIC +Rcfloat4 +Rcfloat4x4 +Rfloat4 +Rfloat4x4 +size_t +SZ +time_t +TRUE +UINT16 +UINT32 +UINT64 +VOID diff --git a/Verus/src/Global/Timer.cpp b/Verus/src/Global/Timer.cpp new file mode 100644 index 0000000..2f2bfa9 --- /dev/null +++ b/Verus/src/Global/Timer.cpp @@ -0,0 +1,144 @@ +#include "verus.h" + +using namespace verus; + +Timer::Timer() +{ +} + +Timer::~Timer() +{ +} + +void Timer::Init() +{ + const TTimePoint timeNow = std::chrono::high_resolution_clock::now(); + VERUS_FOR(i, +Type::count) + { + _data[i]._t = _data[i]._dt = 0; + _data[i]._timePrev = timeNow; + } +} + +void Timer::Update() +{ + const TTimePoint timeNow = std::chrono::high_resolution_clock::now(); + VERUS_FOR(i, +Type::count) + { + _data[i]._dtPrev = _data[i]._dt; + + _data[i]._dt = std::chrono::duration_cast>( + timeNow - _data[i]._timePrev).count()*GetGameSpeed(static_cast(i)); + if (_data[i]._dt > 0.25f) // Prevent long gaps. + _data[i]._dt = 0.25f; + + _data[i]._t += _data[i]._dt; + _data[i]._timePrev2 = _data[i]._timePrev; + _data[i]._timePrev = timeNow; + + _data[i]._dtInv = _data[i]._dt > 0.0001f ? 1 / _data[i]._dt : 10000; + _data[i]._dtSq = _data[i]._dt*_data[i]._dt; + _data[i]._tcvValue = _data[i]._dtPrev > 0.0001f ? _data[i]._dt / _data[i]._dtPrev : 1; + + _data[i]._smoothValue = Math::Clamp((1 / 30.f - _data[i]._dt) * 30, 0.f, 0.5f); + } + + const int num = Utils::Cast32(_vCountdowns.size()); + VERUS_FOR(i, num) + { + if (_vCountdowns[i]._active && _vCountdowns[i]._secondsRemain > 0) + _vCountdowns[i]._secondsRemain -= GetDeltaTime(); + } +} + +bool Timer::IsEventEvery(int ms) const +{ + auto a = std::chrono::duration_cast(_data[+Type::game]._timePrev.time_since_epoch()); + auto b = std::chrono::duration_cast(_data[+Type::game]._timePrev2.time_since_epoch()); + return a.count() / ms != b.count() / ms; +} + +int Timer::InsertCountdown(float duration, int existingID) +{ + const int num = Utils::Cast32(_vCountdowns.size()); + + // Use existing: + if (existingID >= 0 && existingID < num) + { + _vCountdowns[existingID]._active = true; + _vCountdowns[existingID]._secondsRemain = duration; + _vCountdowns[existingID]._secondsTotal = duration; + return existingID; + } + + // Find free one: + VERUS_FOR(i, num) + { + if (!_vCountdowns[i]._active) + { + _vCountdowns[i]._active = true; + _vCountdowns[i]._secondsRemain = duration; + _vCountdowns[i]._secondsTotal = duration; + return i; + } + } + + // Add new one: + _vCountdowns.resize(num + 1); + _vCountdowns[num]._active = true; + _vCountdowns[num]._secondsRemain = duration; + _vCountdowns[num]._secondsTotal = duration; + return num; +} + +void Timer::DeleteCountdown(int id) +{ + const int num = Utils::Cast32(_vCountdowns.size()); + if (id >= 0 && id < num) + _vCountdowns[id]._active = false; +} + +void Timer::DeleteAllCountdowns() +{ + _vCountdowns.clear(); +} + +bool Timer::GetCountdownData(int id, float& remain) +{ + if (id >= 0 && id < _vCountdowns.size()) + { + if (_vCountdowns[id]._active) + { + if (_vCountdowns[id]._secondsRemain <= 0) + { + remain = 0; + return true; + } + else + { + remain = _vCountdowns[id]._secondsRemain; + return false; + } + } + else + return true; + } + return true; +} + +// PerfTimer: + +void PerfTimer::Begin() +{ + _begin = std::chrono::high_resolution_clock::now(); +} + +void PerfTimer::End(CSZ func) +{ + const double d = std::chrono::duration_cast>( + std::chrono::high_resolution_clock::now() - _begin).count() * 1000; + + char info[400]; + sprintf_s(info, "[PerfTimer] End() func=%s d=%f", func, d); + VERUS_LOG_DEBUG(info); +} diff --git a/Verus/src/Global/Timer.h b/Verus/src/Global/Timer.h new file mode 100644 index 0000000..203ee91 --- /dev/null +++ b/Verus/src/Global/Timer.h @@ -0,0 +1,91 @@ +#pragma once + +namespace verus +{ + class Timer : public Singleton + { + public: + enum class Type + { + game, + gui, + count + }; + + private: + typedef std::chrono::high_resolution_clock::time_point TTimePoint; + + struct Countdown + { + float _secondsTotal = 0; + float _secondsRemain = 0; + bool _active = false; + }; + + struct Data + { + TTimePoint _timePrev; + TTimePoint _timePrev2; + double _t = 0; + float _dt = 0; + float _dtInv = 0; + float _dtSq = 0; + float _dtPrev = 0; + float _tcvValue = 0; // Time-Corrected Verlet. + float _smoothValue = 0; + float _gameSpeed = 1; + bool _pause = false; + }; + + Vector _vCountdowns; + Data _data[static_cast(Type::count)]; + + public: + Timer(); + ~Timer(); + + void Init(); + void Update(); + + bool IsEventEvery(int ms) const; + + float GetTime(Type type = Type::game) const { return static_cast(_data[+type]._t); } + float GetDeltaTime(Type type = Type::game) const { return _data[+type]._dt; } + float GetGameSpeed(Type type = Type::game) const { return _data[+type]._pause ? 0 : _data[+type]._gameSpeed; } + void SetGameSpeed(float speed, Type type = Type::game) { _data[+type]._gameSpeed = speed; } + + // Extra: + float GetDeltaTimeInv(Type type = Type::game) const { return _data[+type]._dtInv; } + float GetDeltaTimeSq(Type type = Type::game) const { return _data[+type]._dtSq; } + float GetPreviousDeltaTime(Type type = Type::game) const { return _data[+type]._dtPrev; } + float GetVerletValue(Type type = Type::game) const { return _data[+type]._tcvValue; } + float GetSmoothValue(Type type = Type::game) const { return _data[+type]._smoothValue; } + + void SetPause(bool pause, Type type = Type::game) { _data[+type]._pause = pause; } + + UINT32 GetTicks() const { return SDL_GetTicks(); } + + int InsertCountdown(float duration, int existingID = -1); + void DeleteCountdown(int id); + void DeleteAllCountdowns(); + bool GetCountdownData(int id, float& remain); + + static CSZ GetSingletonFailMessage() { return "Make_Utils(); // FAIL.\r\n"; } + }; + VERUS_TYPEDEFS(Timer); + + class PerfTimer + { + typedef std::chrono::high_resolution_clock::time_point TTimePoint; + + TTimePoint _begin; + + public: + void Begin(); + void End(CSZ func); + }; + VERUS_TYPEDEFS(PerfTimer); +} + +#define VERUS_PERF_BEGIN Utils::PerfTimer perfTimer; perfTimer.Begin(); +#define VERUS_PERF_END perfTimer.End(__FUNCTION__); diff --git a/Verus/src/Global/Typedef.h b/Verus/src/Global/Typedef.h new file mode 100644 index 0000000..f8de745 --- /dev/null +++ b/Verus/src/Global/Typedef.h @@ -0,0 +1,38 @@ +#pragma once + +#define FALSE 0 +#define TRUE 1 +typedef int BOOL; +typedef unsigned char BYTE; +typedef short INT16; +typedef unsigned short UINT16; +typedef int INT32; +typedef unsigned int UINT32; +#ifdef _WIN32 +typedef __int64 INT64; +typedef unsigned __int64 UINT64; +#else +typedef long long INT64; +typedef unsigned long long UINT64; +#endif + +#ifndef _WIN32 +# define _stricmp strcasecmp +# define _strnicmp strncasecmp +#endif + +namespace verus +{ + typedef char* SZ; + typedef const char* CSZ; + typedef wchar_t* WSZ; + typedef const wchar_t* CWSZ; + typedef std::string String; + typedef std::wstring WideString; + typedef std::stringstream StringStream; + typedef glm::quat Quaternion; + + VERUS_TYPEDEFS(String); + VERUS_TYPEDEFS(WideString); + VERUS_TYPEDEFS(Quaternion); +} diff --git a/Verus/src/Global/Utils.cpp b/Verus/src/Global/Utils.cpp new file mode 100644 index 0000000..4693356 --- /dev/null +++ b/Verus/src/Global/Utils.cpp @@ -0,0 +1,56 @@ +#include "verus.h" + +using namespace verus; + +Utils::Utils(PBaseAllocator pAlloc) +{ + SetAllocator(pAlloc); + srand(static_cast(time(nullptr))); +} + +Utils::~Utils() +{ +} + +void Utils::MakeEx(PBaseAllocator pAlloc) +{ + const float mx = FLT_MAX; + const UINT32 imx = *(UINT32*)&mx; + VERUS_RT_ASSERT(imx == 0x7f7fffff); + + Utils* p = static_cast(pAlloc->malloc(sizeof(Utils))); + p = new(p)Utils(pAlloc); +} + +void Utils::FreeEx(PBaseAllocator pAlloc) +{ + if (IsValidSingleton()) + { + Free_Global(); + P()->~Utils(); + pAlloc->free(P()); + Assign(nullptr); + } + TestAllocCount(); +} + +void Utils::ExitSdlLoop() +{ + SDL_Event event = {}; + event.type = SDL_USEREVENT; + SDL_PushEvent(&event); +} + +INT32 Utils::Cast32(INT64 x) +{ + if (x < std::numeric_limits::min() || x > std::numeric_limits::max()) + throw VERUS_RECOVERABLE << "Cast32 failed, x=" << x; + return static_cast(x); +} + +UINT32 Utils::Cast32(UINT64 x) +{ + if (x < std::numeric_limits::min() || x > std::numeric_limits::max()) + throw VERUS_RECOVERABLE << "Cast32 failed, x=" << x; + return static_cast(x); +} diff --git a/Verus/src/Global/Utils.h b/Verus/src/Global/Utils.h new file mode 100644 index 0000000..e2bca9d --- /dev/null +++ b/Verus/src/Global/Utils.h @@ -0,0 +1,44 @@ +#pragma once + +namespace verus +{ + class Utils : public Singleton + { + Random _random; + PBaseAllocator _pAllocator; + + public: + Utils(PBaseAllocator pAlloc); + ~Utils(); + + //! Construct Utils using the provided allocator: + static void MakeEx(PBaseAllocator pAlloc); + //! Destruct Utils using the provided allocator: + static void FreeEx(PBaseAllocator pAlloc); + + RRandom GetRandom() { return _random; } + + // System-wide allocator: + PBaseAllocator GetAllocator() { return _pAllocator; } + void SetAllocator(PBaseAllocator p) { _pAllocator = p; } + + static void ExitSdlLoop(); + + static INT32 Cast32(INT64 x); + static UINT32 Cast32(UINT64 x); + + template + static T* Swap(T*& pDst, T*& pSrc) + { + T* pPrev = pDst; + pDst = pSrc; + return pPrev; + } + + static CSZ GetSingletonFailMessage() + { + return "verus::Utils::SetupPaths(); // FAIL.\r\ncorr::Utils::MakeEx(&alloc); // FAIL.\r\n"; + } + }; + VERUS_TYPEDEFS(Utils); +} diff --git a/Verus/src/IO/Async.cpp b/Verus/src/IO/Async.cpp new file mode 100644 index 0000000..5b3ef0b --- /dev/null +++ b/Verus/src/IO/Async.cpp @@ -0,0 +1,254 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::IO; + +Async::Async() +{ + _stopThread = false; + VERUS_ZERO_MEM(_queue); +} + +Async::~Async() +{ + Done(); +} + +void Async::Init() +{ + VERUS_INIT(); + + _stopThread = false; + _thread = std::thread(&Async::ThreadProc, this); +} + +void Async::Done() +{ + if (_thread.joinable()) + { + _stopThread = true; + _cv.notify_one(); + _thread.join(); + } + VERUS_DONE(Async); +} + +void Async::Load(CSZ url, PAsyncCallback pCallback, RcTaskDesc desc) +{ + VERUS_RT_ASSERT(IsInitialized()); + VERUS_RT_ASSERT(!_inUpdate); // Not allowed to call Load() in Update(). + + // If the queue is full we must block until something gets processed: + bool full = false; + do + { + { + VERUS_LOCK(*this); + + for (auto& kv : _mapTasks) + { + if (!strcmp(url, _C(kv.first) + orderLength)) + { + // This resource is already scheduled. + // Just add a new owner, if it's not already there. + RTask task = kv.second; + if (std::find(task._vOwners.begin(), task._vOwners.end(), pCallback) == task._vOwners.end()) + task._vOwners.push_back(pCallback); + return; + } + } + + full = VERUS_CIRCULAR_IS_FULL(_cursorRead, _cursorWrite, VERUS_ARRAY_LENGTH(_queue)); + + if (!full) + { + char order[16]; + sprintf_s(order, "%08X", _order); // The order of Load() and callbacks is preserved. + _order++; + String key = order; + key += url; + + RTask task = _mapTasks[key]; + task._vOwners.push_back(pCallback); + task._desc = desc; + _queue[_cursorWrite] = _C(_mapTasks.find(key)->first); + VERUS_CIRCULAR_ADD(_cursorWrite, VERUS_ARRAY_LENGTH(_queue)); + } + } + + if (full) + std::this_thread::sleep_for(std::chrono::milliseconds(290)); + } while (full); + + _cv.notify_one(); +} + +void Async::_Cancel(PAsyncCallback pCallback) +{ + VERUS_RT_ASSERT(IsInitialized()); + VERUS_LOCK(*this); + for (auto& kv : _mapTasks) + { + RTask task = kv.second; + VERUS_WHILE(Vector, task._vOwners, it) + { + if (*it == pCallback) + it = task._vOwners.erase(it); + else + ++it; + } + } +} + +void Async::Cancel(PAsyncCallback pCallback) +{ + if (IsValidSingleton()) + I()._Cancel(pCallback); +} + +void Async::Update() +{ + VERUS_RT_ASSERT(IsInitialized()); + VERUS_LOCK(*this); + + if (_ex.IsRaised()) + throw _ex; + + _inUpdate = true; + VERUS_WHILE(TMapTasks, _mapTasks, itTask) + { + RTask task = itTask->second; + if (task._loaded) // Only loaded task can be processed: + { +#ifdef VERUS_DEBUG + VERUS_LOG_DEBUG("Update() task=" << itTask->first); +#endif + VERUS_RT_ASSERT(task._desc._runOnMainThread); + if (!task._v.empty()) + { + for (const auto& pOwner : task._vOwners) + pOwner->Async_Run(_C(itTask->first) + orderLength, Blob(task._v.data(), task._v.size())); + } + itTask = _mapTasks.erase(itTask); + // Task complete. + if (_singleMode) + break; + } + else + ++itTask; + } + if (_mapTasks.empty()) + _order = 0; + _inUpdate = false; + + if (_flush) // Reset timer after flushing (update can take a long time): + { + VERUS_QREF_TIMER; + timer.Update(); + timer.Update(); + _flush = false; + } +} + +void Async::Flush() +{ + while (true) + { + { + VERUS_LOCK(*this); + if (_ex.IsRaised()) + throw _ex; + if (_cursorRead == _cursorWrite) + return; + _flush = true; + } + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } +} + +void Async::ThreadProc() +{ + VERUS_RT_ASSERT(IsInitialized()); + try + { + SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW); + while (true) + { + CSZ key = nullptr; + PTask pTask = nullptr; // Assume that this address will not change in the map. + TMapTasks::iterator itTask; + + { + // Get the next task: + VERUS_LOCK(*this); + + // If there are no tasks, unlock and wait: + _cv.wait(lock, [=]() {return (_cursorRead != _cursorWrite) || _stopThread; }); + + if (_stopThread) + break; // Quit. + + // Locked and should have a task: + itTask = _mapTasks.end(); + key = _queue[_cursorRead]; + if (key) + { + itTask = _mapTasks.find(key); + if (itTask != _mapTasks.end()) + pTask = &_mapTasks[key]; + } + } + + if (key && (!pTask->_desc._checkExist || FileSystem::FileExist(key + orderLength))) + { + try + { + FileSystem::LoadResource(key + orderLength, pTask->_v, + FileSystem::LoadDesc(pTask->_desc._nullTerm, pTask->_desc._texturePart)); + } + catch (D::RcRuntimeError) + { + if (!pTask->_desc._checkExist) + throw; + } + } + +#ifdef VERUS_DEBUG + VERUS_LOG_DEBUG("ThreadProc() key=" << key); +#endif + + { + // Finalize this task: + VERUS_LOCK(*this); + pTask->_loaded = true; + // Is it safe to run a callback on this loader thread? + if (!pTask->_desc._runOnMainThread) + { +#ifdef VERUS_DEBUG + VERUS_LOG_DEBUG("ThreadProc(), runOnMainThread key=" << key); +#endif + if (!pTask->_v.empty()) + { + for (const auto& pOwner : pTask->_vOwners) + pOwner->Async_Run(key + orderLength, Blob(pTask->_v.data(), pTask->_v.size())); + } + _mapTasks.erase(itTask); + // Task complete. + if (_mapTasks.empty()) + _order = 0; + } + VERUS_CIRCULAR_ADD(_cursorRead, VERUS_ARRAY_LENGTH(_queue)); + } + } + } + catch (D::RcRuntimeError e) + { + VERUS_LOCK(*this); + _ex = e; + } + catch (const std::exception& e) + { + VERUS_LOCK(*this); + _ex = VERUS_RUNTIME_ERROR << e.what(); + } +} diff --git a/Verus/src/IO/Async.h b/Verus/src/IO/Async.h new file mode 100644 index 0000000..e562f0f --- /dev/null +++ b/Verus/src/IO/Async.h @@ -0,0 +1,82 @@ +#pragma once + +namespace verus +{ + namespace IO + { + struct AsyncCallback + { + virtual void Async_Run(CSZ url, RcBlob blob) = 0; + }; + VERUS_TYPEDEFS(AsyncCallback); + + //! Load resources asynchronously. + //! Load method just adds the url to a queue. + //! Load and Cancel are virtual so that they can be safely called from another + //! DLL. Internally they can allocate and free memory. + class Async : public Singleton, public Object, public Lockable + { + public: + struct TaskDesc + { + int _texturePart = 0; + bool _nullTerm = false; + bool _checkExist = false; + bool _runOnMainThread = true; + + TaskDesc(bool nullTerm = false, bool checkExist = false, int texturePart = 0, bool runOnMainThread = true) : + _nullTerm(nullTerm), + _checkExist(checkExist), + _texturePart(texturePart), + _runOnMainThread(runOnMainThread) {} + }; + VERUS_TYPEDEFS(TaskDesc); + + private: + struct Task + { + Vector _vOwners; + Vector _v; + TaskDesc _desc; + bool _loaded = false; + }; + VERUS_TYPEDEFS(Task); + + typedef Map TMapTasks; + + enum { orderLength = 8 }; + + TMapTasks _mapTasks; + std::thread _thread; + std::condition_variable _cv; + D::RuntimeError _ex; + CSZ _queue[64]; + int _cursorRead = 0; + int _cursorWrite = 0; + UINT32 _order = 0; + std::atomic_bool _stopThread; + bool _inUpdate = false; + bool _flush = false; + bool _singleMode = false; + + public: + Async(); + ~Async(); + + void Init(); + void Done(); + + virtual void Load(CSZ url, PAsyncCallback pCallback, RcTaskDesc desc = TaskDesc()); + VERUS_P(virtual void _Cancel(PAsyncCallback pCallback)); + static void Cancel(PAsyncCallback pCallback); + + virtual void Update(); + + void Flush(); + void SetSingleMode(bool b) { _singleMode = b; } + + VERUS_P(void ThreadProc()); + }; + VERUS_TYPEDEFS(Async); + } +} diff --git a/Verus/src/IO/DDSHeader.cpp b/Verus/src/IO/DDSHeader.cpp new file mode 100644 index 0000000..2fa455a --- /dev/null +++ b/Verus/src/IO/DDSHeader.cpp @@ -0,0 +1,107 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::IO; + +DDSHeader::DDSHeader() +{ + VERUS_CT_ASSERT(128 == sizeof(DDSHeader)); + VERUS_ZERO_MEM(*this); + memcpy(magic, "DDS ", 4); + size = 124; + flags = Flags::caps | Flags::height | Flags::width | Flags::pixelFormat; + pixelFormat.size = 32; + caps.caps1 = Caps1::texture; +} + +bool DDSHeader::Validate() const +{ + const Flags reqFlags = Flags::caps | Flags::height | Flags::width | Flags::pixelFormat; + return !memcmp(magic, "DDS ", 4) && + 124 == size && + static_cast(flags & reqFlags) == reqFlags && + 32 == pixelFormat.size && + (caps.caps1 & Caps1::texture); +} + +bool DDSHeader::IsDxt1() const +{ + return (pixelFormat.flags & PixelFormatFlags::fourCC) && (pixelFormat.fourCC == FourCC::dxt1); +} + +bool DDSHeader::IsDxt3() const +{ + return (pixelFormat.flags & PixelFormatFlags::fourCC) && (pixelFormat.fourCC == FourCC::dxt3); +} + +bool DDSHeader::IsDxt5() const +{ + return (pixelFormat.flags & PixelFormatFlags::fourCC) && (pixelFormat.fourCC == FourCC::dxt5); +} + +bool DDSHeader::IsDxt() const +{ + return IsDxt1() || IsDxt3() || IsDxt5(); +} + +bool DDSHeader::IsBGRA8() const +{ + return + (pixelFormat.flags & PixelFormatFlags::rgb) && + (pixelFormat.flags & PixelFormatFlags::alphaPixels) && + (pixelFormat.rgbBitCount == 32) && + (pixelFormat.rBitMask == 0xFF0000u) && + (pixelFormat.gBitMask == 0xFF00u) && + (pixelFormat.bBitMask == 0xFFu) && + (pixelFormat.rgbAlphaBitMask == 0xFF000000u); +} + +bool DDSHeader::IsBGR8() const +{ + return + (pixelFormat.flags & PixelFormatFlags::rgb) && + !(pixelFormat.flags & PixelFormatFlags::alphaPixels) && + (pixelFormat.rgbBitCount == 24) && + (pixelFormat.rBitMask == 0xFF0000u) && + (pixelFormat.gBitMask == 0xFF00u) && + (pixelFormat.bBitMask == 0xFFu); +} + +int DDSHeader::ComputeDxtLevelSize(int w, int h, bool dxt1) +{ + int w4 = w >> 2; + int h4 = h >> 2; + if (!w4) w4 = 1; + if (!h4) h4 = 1; + return w4 * h4*(dxt1 ? 8 : 16); +} + +int DDSHeader::GetNumParts() const +{ + if (mipmapCount > 0 && IsDxt()) + { + const int numParts = int(mipmapCount) - 9; + return (numParts > 0) ? numParts : 1; + } + else + return 1; +} + +int DDSHeader::SkipParts(int numSkip) +{ + const int numParts = GetNumParts(); + if (numSkip >= 256) + numSkip = 8 + numParts - Math::HighestBit(numSkip); + numSkip = Math::Clamp(numSkip, 0, numParts - 1); + if (numSkip) + { + mipmapCount -= numSkip; + height >>= numSkip; + width >>= numSkip; + if (!height) height = 1; + if (!width) width = 1; + pitchOrLinearSize = ComputeDxtLevelSize(width, height, IsDxt1()); + } + reserved2 = numSkip; + return numSkip; +} diff --git a/Verus/src/IO/DDSHeader.h b/Verus/src/IO/DDSHeader.h new file mode 100644 index 0000000..7d44e66 --- /dev/null +++ b/Verus/src/IO/DDSHeader.h @@ -0,0 +1,105 @@ +#pragma once + +namespace verus +{ + namespace IO + { + struct DDSHeader + { + enum class Flags : UINT32 + { + caps /**/ = 0x00000001, + height /**/ = 0x00000002, + width /**/ = 0x00000004, + pitch /**/ = 0x00000008, + pixelFormat /**/ = 0x00001000, + mipmapCount /**/ = 0x00020000, + linearSize /**/ = 0x00080000, + depth /**/ = 0x00800000 + }; + + enum class PixelFormatFlags : UINT32 + { + alphaPixels /**/ = 0x00000001, + alpha /**/ = 0x00000002, + fourCC /**/ = 0x00000004, + indexed /**/ = 0x00000020, + rgb /**/ = 0x00000040, + compressed /**/ = 0x00000080, + luminance /**/ = 0x00020000 + }; + + enum class Caps1 : UINT32 + { + complex /**/ = 0x00000008, + texture /**/ = 0x00001000, + mipmap /**/ = 0x00400000 + }; + + enum class Caps2 : UINT32 + { + cubemap /**/ = 0x00000200, + cubemapPositiveX /**/ = 0x00000400, + cubemapNegativeX /**/ = 0x00000800, + cubemapPositiveY /**/ = 0x00001000, + cubemapNegativeY /**/ = 0x00002000, + cubemapPositiveZ /**/ = 0x00004000, + cubemapNegativeZ /**/ = 0x00008000, + volume /**/ = 0x00200000 + }; + + enum class FourCC : UINT32 + { + dxt1 = '1TXD', + dxt2 = '2TXD', + dxt3 = '3TXD', + dxt4 = '4TXD', + dxt5 = '5TXD' + }; + + struct PixelFormat + { + UINT32 size; + PixelFormatFlags flags; + FourCC fourCC; + UINT32 rgbBitCount; + UINT32 rBitMask; + UINT32 gBitMask; + UINT32 bBitMask; + UINT32 rgbAlphaBitMask; + }; + + struct Caps + { + Caps1 caps1; + Caps2 caps2; + UINT32 reserved[2]; + }; + + char magic[4]; + UINT32 size; + Flags flags; + UINT32 height; + UINT32 width; + UINT32 pitchOrLinearSize; + UINT32 depth; + UINT32 mipmapCount; + UINT32 reserved1[11]; + PixelFormat pixelFormat; + Caps caps; + UINT32 reserved2; + + DDSHeader(); + bool Validate() const; + bool IsDxt1() const; + bool IsDxt3() const; + bool IsDxt5() const; + bool IsDxt() const; + bool IsBGRA8() const; + bool IsBGR8() const; + static int ComputeDxtLevelSize(int w, int h, bool dxt1 = true); + int GetNumParts() const; + int SkipParts(int numSkip); + }; + } +} diff --git a/Verus/src/IO/File.cpp b/Verus/src/IO/File.cpp new file mode 100644 index 0000000..351210c --- /dev/null +++ b/Verus/src/IO/File.cpp @@ -0,0 +1,61 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::IO; + +File::File() +{ +} + +File::~File() +{ + Close(); +} + +bool File::Open(CSZ pathName, CSZ mode) +{ + Close(); + +#ifdef _WIN32 + _pFile = fopen(pathName, mode); + //CWideString url = CStr::Utf8ToWide(pathName); + //CWideString mode_w = CStr::Utf8ToWide(mode); + //_pFile = _wfopen(_C(url), _C(mode_w)); +#else + _pFile = fopen(pathName, mode); +#endif + return nullptr != _pFile; +} + +void File::Close() +{ + if (_pFile) + { + fclose(_pFile); + _pFile = nullptr; + } +} + +INT64 File::Read(void* p, INT64 size) +{ + VERUS_RT_ASSERT(_pFile); + memset(p, 0, static_cast(size)); + return fread(p, 1, static_cast(size), _pFile); +} + +INT64 File::Write(const void* p, INT64 size) +{ + VERUS_RT_ASSERT(_pFile); + return fwrite(p, 1, static_cast(size), _pFile); +} + +void File::Seek(INT64 offset, int origin) +{ + const int ret = fseek(_pFile, static_cast(offset), origin); + VERUS_RT_ASSERT(!ret); +} + +INT64 File::GetPosition() +{ + return ftell(_pFile); +} diff --git a/Verus/src/IO/File.h b/Verus/src/IO/File.h new file mode 100644 index 0000000..41c4b29 --- /dev/null +++ b/Verus/src/IO/File.h @@ -0,0 +1,30 @@ +#pragma once + +namespace verus +{ + namespace IO + { + //! Treat a file as a stream. + //! + class File : public SeekableStream + { + FILE* _pFile = nullptr; + + public: + File(); + ~File(); + + bool Open(CSZ pathName, CSZ mode = "rb"); + void Close(); + + virtual INT64 Read(void* p, INT64 size) override; + virtual INT64 Write(const void* p, INT64 size) override; + + virtual void Seek(INT64 offset, int origin) override; + virtual INT64 GetPosition() override; + + FILE* GetFile() { return _pFile; } + }; + VERUS_TYPEDEFS(File); + } +} diff --git a/Verus/src/IO/FileSystem.cpp b/Verus/src/IO/FileSystem.cpp new file mode 100644 index 0000000..c247b0e --- /dev/null +++ b/Verus/src/IO/FileSystem.cpp @@ -0,0 +1,564 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::IO; + +FileSystem::FileSystem() +{ +} + +FileSystem::~FileSystem() +{ +} + +size_t FileSystem::FindColonForPAK(CSZ url) +{ + const char* pColon = strchr(url, ':'); + if (!pColon) + return String::npos; + const size_t at = pColon - url; + if (at < 2) // "C:\..." + return String::npos; + return at; +} + +void FileSystem::ReadHeaderPAK(RFile file, UINT32& magic, INT64& entriesOffset, INT64& entriesSize) +{ + file >> magic; + if (magic != 'KAP2') + throw VERUS_RUNTIME_ERROR << "ReadHeaderPAK(), Invalid magic number in PAK"; + + file >> entriesOffset; + file >> entriesSize; + if (entriesSize%entrySize) + throw VERUS_RUNTIME_ERROR << "ReadHeaderPAK(), Invalid size of entries in PAK"; +} + +void FileSystem::PreloadCache(CSZ pak, CSZ types[]) +{ + StringStream ss; + //ss << Utils::I().GetModulePath() << "/Data/" << pak; + File file; + if (!file.Open(_C(ss.str()))) + return; + + UINT32 magic; + INT64 entriesOffset, entriesSize; + ReadHeaderPAK(file, magic, entriesOffset, entriesSize); + + char value[querySize]; + + auto LoadThisFile = [types](CSZ value) + { + CSZ* typesAt = types; + while (*typesAt) + { + if (Str::EndsWith(value, *typesAt)) + return true; + typesAt++; + } + return false; + }; + + char fileEntry[entrySize] = {}; + INT64 numEntries = entriesSize / entrySize; + INT64 dataOffset, dataSize; + file.Seek(entriesOffset, SEEK_SET); + while (numEntries) + { + file >> fileEntry; + + strcpy(value, fileEntry); + Str::CyrillicToUppercase(value); + + if (LoadThisFile(value)) + { + memcpy(&dataOffset, fileEntry + querySize, sizeof(INT64)); + memcpy(&dataSize, fileEntry + querySize + sizeof(INT64), sizeof(INT64)); + + const String password = ConvertFilenameToPassword(fileEntry); + + Vector vCip, vZip, vData; + const INT64 entryOffset = file.GetPosition(); + file.Seek(dataOffset, SEEK_SET); // [unzipped size][encrypted data]. + INT64 size; + file >> size; + vData.resize(size_t(size + 1)); // For null-terminated string. + vCip.resize(size_t(dataSize - sizeof(INT64))); + vZip.resize(size_t(dataSize - sizeof(INT64))); + file.Read(vCip.data(), vCip.size()); + Security::CipherRC4::Decrypt(password, vCip, vZip); + uLongf destLen = Utils::Cast32(size); + const int ret = uncompress(vData.data(), &destLen, vZip.data(), Utils::Cast32(vZip.size())); + if (ret != Z_OK) + throw VERUS_RUNTIME_ERROR << "uncompress(), " << ret; + + _cacheSize += vData.size(); + String key(pak); + Str::ReplaceExtension(key, ":"); + key += fileEntry; + _mapCache[key] = std::move(vData); + + file.Seek(entryOffset, SEEK_SET); // Go back to entries. + } + + numEntries--; + } +} + +void FileSystem::LoadResource(CSZ url, Vector& vData, RcLoadDesc desc) +{ + String pathNamePAK, pakEntry; + const size_t colon = FindColonForPAK(url); + if (colon != String::npos) // "Foo:Bar.ext" format -> in PAK file: + { + String strUrl(url); + StringStream ss; + //ss << Utils::I().GetModulePath() << "/Data/"; + ss << strUrl.substr(0, colon) << ".pak"; + const String name = strUrl.substr(colon + 1); + const WideString wide = Str::Utf8ToWide(name); + pathNamePAK = ss.str(); + pakEntry = Str::CyrillicWideToAnsi(_C(wide)); + } + + File arPAK; + if (pathNamePAK.empty() || pakEntry.empty()) // System file name? + return LoadResourceFromFile(url, vData, desc); + if (!arPAK.Open(_C(pathNamePAK))) // PAK not found? Try system file. + return LoadResourceFromFile(url, vData, desc); + + LoadResourceFromPAK(url, vData, desc, arPAK, _C(pakEntry)); +} + +void FileSystem::LoadResourceFromFile(CSZ url, Vector& vData, RcLoadDesc desc) +{ + String strUrl(url), pathNameProject(url); + const bool shader = strUrl.find("Shaders:") != String::npos; + const size_t colon = FindColonForPAK(url); + if (colon != String::npos) + { + if (shader/* && !Utils::I().GetForcedCgPath().empty()*/) + { + strUrl.replace(0, 8, "/"); + StringStream ss; + //ss << Utils::I().GetForcedCgPath() << strUrl; + //pathNameProject = String(_C(Utils::I().GetProjectPath())) + "/" + strUrl; + strUrl = ss.str(); + } + else + { + strUrl.replace(colon, 1, "/"); + StringStream ss; + //ss << Utils::I().GetModulePath() << "/Data/" << strUrl; + //pathNameProject = String(_C(Utils::I().GetProjectPath())) + "/" + strUrl; + strUrl = ss.str(); + } + } + + File file; + if (file.Open(_C(strUrl)) || file.Open(_C(pathNameProject))) + { + const INT64 size = file.GetSize(); + if (size >= 0) + { + if (Str::EndsWith(url, ".dds", false)) + { + LoadTextureParts(file, url, desc._texturePart, vData); + } + else + { + const size_t vsize = desc._nullTerm ? static_cast(size) + 1 : static_cast(size); + vData.resize(vsize); + file.Read(vData.data(), size); + } + } + } + else if (desc._mandatory) + throw VERUS_RUNTIME_ERROR << "LoadResourceFromFile(), File not found: " << url; +} + +void FileSystem::LoadResourceFromCache(CSZ url, Vector& vData, bool mandatory) +{ + if (_mapCache.empty()) // Cache not ready? + return LoadResourceFromFile(url, vData, LoadDesc(true, 0, mandatory)); + + auto it = _mapCache.find(url); + if (it != _mapCache.end()) + vData = it->second; + else if (mandatory) + throw VERUS_RUNTIME_ERROR << "LoadResourceFromCache(), File not found in cache: " << url; +} + +void FileSystem::LoadResourceFromPAK(CSZ url, Vector& vData, RcLoadDesc desc, RFile file, CSZ pakEntry) +{ + UINT32 magic; + INT64 entriesOffset, entriesSize; + ReadHeaderPAK(file, magic, entriesOffset, entriesSize); + + char query[querySize]; + char value[querySize]; + strcpy(query, pakEntry); + Str::CyrillicToUppercase(query); + + bool found = false; + char fileEntry[entrySize] = {}; + INT64 numEntries = entriesSize / entrySize; + INT64 dataOffset, dataSize; + file.Seek(entriesOffset, SEEK_SET); + while (numEntries) + { + file >> fileEntry; + + strcpy(value, fileEntry); + Str::CyrillicToUppercase(value); + + if (!_stricmp(query, value)) + { + memcpy(&dataOffset, fileEntry + querySize, sizeof(INT64)); + memcpy(&dataSize, fileEntry + querySize + sizeof(INT64), sizeof(INT64)); + found = true; + break; + } + numEntries--; + } + if (!found) // Resource is not in PAK file? + { + if (Str::EndsWith(pakEntry, ".primary")) + return; + return LoadResourceFromFile(url, vData, desc); + } + + const String password = ConvertFilenameToPassword(fileEntry); + + Vector vCip, vZip; + file.Seek(dataOffset, SEEK_SET); + if (Str::EndsWith(query, ".dds", false)) + { + const int maxParts = 8; + INT64 partEntries[maxParts * 3] = {}; + + DDSHeader header; + file >> header; + if (!header.Validate()) + throw VERUS_RUNTIME_ERROR << "LoadResourceFromPAK(), Invalid DDS header: " << url; + const int numParts = header.GetNumParts(); + const int numPartsSkip = header.SkipParts(desc._texturePart); + + if (numParts > maxParts) + throw VERUS_RUNTIME_ERROR << "LoadResourceFromPAK(), Too many parts in PAK"; + + INT64 sizeTotal = sizeof(header); + VERUS_FOR(part, numParts) + { + file >> partEntries[part * 3 + 0]; + file >> partEntries[part * 3 + 1]; + file >> partEntries[part * 3 + 2]; + if (part >= numPartsSkip) + sizeTotal += partEntries[part * 3 + 1]; + } + + vData.resize(size_t(sizeTotal)); + memcpy(vData.data(), &header, sizeof(header)); + INT64 at = sizeof(header); + + VERUS_FOR(part, numParts) + { + const INT64 offsetPart = partEntries[part * 3 + 0]; + const INT64 sizePart = partEntries[part * 3 + 1]; + const INT64 sizePartZip = partEntries[part * 3 + 2]; + INT64 size = 0; + if (part >= numPartsSkip) + { + file.Seek(dataOffset + offsetPart, SEEK_SET); + file >> size; + if (size != sizePart) + throw VERUS_RUNTIME_ERROR << "LoadResourceFromPAK(), Invalid size in PAK"; + vCip.resize(size_t(sizePartZip - sizeof(INT64))); + vZip.resize(size_t(sizePartZip - sizeof(INT64))); + file.Read(vCip.data(), vCip.size()); + Security::CipherRC4::Decrypt(password, vCip, vZip); + uLongf destLen = Utils::Cast32(size); + const int ret = uncompress(vData.data() + at, &destLen, vZip.data(), Utils::Cast32(vZip.size())); + if (ret != Z_OK) + throw VERUS_RUNTIME_ERROR << "uncompress(), " << ret; + } + at += size; + } + } + else + { + INT64 size; + file >> size; + const INT64 vsize = desc._nullTerm ? size + 1 : size; + vData.resize(size_t(vsize)); + vCip.resize(size_t(dataSize - sizeof(INT64))); + vZip.resize(size_t(dataSize - sizeof(INT64))); + file.Read(vCip.data(), vCip.size()); + Security::CipherRC4::Decrypt(password, vCip, vZip); + uLongf destLen = Utils::Cast32(size); + const int ret = uncompress(vData.data(), &destLen, vZip.data(), Utils::Cast32(vZip.size())); + if (ret != Z_OK) + throw VERUS_RUNTIME_ERROR << "uncompress(), " << ret; + } +} + +void FileSystem::LoadTextureParts(RFile file, CSZ url, int texturePart, Vector& vData) +{ + DDSHeader header; + file >> header; + if (!header.Validate()) + throw VERUS_RUNTIME_ERROR << "LoadTextureParts(), Invalid DDS header: " << url; + + const int maxW = header.width; + const int maxH = header.height; + + const int numParts = header.GetNumParts(); + const int numPartsCheck = numParts - 1; + const int numPartsSkip = header.SkipParts(texturePart); + + size_t sizeSkip = 0; + VERUS_FOR(part, numPartsCheck) + { + const int w = maxW >> part; + const int h = maxH >> part; + const size_t sizePart = DDSHeader::ComputeDxtLevelSize(w, h, header.IsDxt1()); + if (part < numPartsSkip) + sizeSkip += sizePart; + } + + const INT64 size = file.GetSize(); + vData.resize(size_t(size) - sizeSkip); + memcpy(vData.data(), &header, sizeof(header)); + file.Seek(sizeSkip, SEEK_CUR); + file.Read(vData.data() + sizeof(header), size - sizeof(header) - sizeSkip); +} + +String FileSystem::ConvertFilenameToPassword(CSZ fileEntry) +{ + String password; + password.resize(strlen(fileEntry)); + for (size_t i = 0; i < password.size(); ++i) + password[i] = ~(((fileEntry[i] >> 4) & 0x0F) | ((fileEntry[i] << 4) & 0xF0)); + return password; +} + +bool FileSystem::FileExist(CSZ url) +{ + String path(url), pak, project; + const size_t colon = FindColonForPAK(url); + if (colon != String::npos) + { + StringStream ssPak; + //ssPak << Utils::I().GetModulePath() << "/Data/" << path.substr(0, colon) << ".pak"; + pak = ssPak.str(); + + path.replace(colon, 1, "/"); + //project = String(_C(Utils::I().GetProjectPath())) + "/" + path; + + StringStream ss; + //ss << Utils::I().GetModulePath() << "/Data/" << path; + path = ss.str(); + } + File file; + if (file.Open(_C(path))) // Normal filename: + { + return true; + } + if (file.Open(_C(pak))) // PAK filename: + { + return true; + } + if (file.Open(_C(project))) // File in another project dir: + { + return true; + } + return false; +} + +bool FileSystem::Delete(CSZ pathName) +{ +#ifdef _WIN32 + const WideString ws = Str::Utf8ToWide(pathName); + return !_wremove(_C(ws)); +#else + return !remove(pathName); +#endif +} + +String FileSystem::ConvertAbsolutePathToRelative(RcString path) +{ + String s; + const size_t data = path.rfind("\\Data\\"); + if (data != String::npos) + { + s = path.substr(data + 6); + const size_t colon = s.find('\\'); + if (colon != String::npos) + s.replace(colon, 1, ":"); + } + Str::ReplaceAll(s, "\\", "/"); + return s; +} + +String FileSystem::ConvertRelativePathToAbsolute(RcString path, bool useProjectDir) +{ + VERUS_QREF_UTILS; + String systemPath = path; + const size_t colon = systemPath.find(':'); + if (colon != String::npos) + systemPath[colon] = '/'; + //if (useProjectDir && *_C(utils.GetProjectPath())) + // systemPath = String(_C(utils.GetProjectPath())) + "/" + systemPath; + //else + // systemPath = utils.GetModulePath() + "/Data/" + systemPath; + return systemPath; +} + +String FileSystem::ReplaceFilename(CSZ pathName, CSZ filename) +{ + CSZ slash = strrchr(pathName, '\\'); + if (!slash) + slash = strrchr(pathName, '/'); + if (slash) + { + const size_t pathSize = slash - pathName; + const size_t size = pathSize + 1 + strlen(filename) + 1; + Vector name; + name.resize(size); + strncpy(name.data(), pathName, pathSize); + name[pathSize] = '/'; + strcat(name.data(), filename); + return name.data(); + } + return filename; +} + +#if 0 +void FileSystem::SaveImage(CSZ pathName, const UINT32* p, int w, int h, bool upsideDown, ILenum type) +{ + struct RAII + { + ILuint name; + RAII() : name(0) {} + ~RAII() + { + if (name) + ilDeleteImages(1, &name); + } + } raii; + + Vector vFlip; + if (!upsideDown) + { + vFlip.resize(w*h); + VERUS_FOR(i, h) + memcpy(&vFlip[i*w], &p[(h - i - 1)*w], w * 4); + p = vFlip.data(); + } + + raii.name = ilGenImage(); + ilBindImage(raii.name); + ilTexImage(w, h, 1, 4, IL_RGBA, IL_UNSIGNED_BYTE, (void*)p); + Vector v; + v.resize(w*h * 8); + ilSaveL(type, v.data(), v.size()); + const ILuint sizeOut = ilGetLumpPos(); + IO::File file; + if (file.Open(pathName, "wb")) + { + file.Write(v.data(), sizeOut); + file.Close(); + } + else + throw VERUS_RUNTIME_ERROR << "SaveImage(), Open()"; +} +#endif + +void FileSystem::SaveDDS(CSZ pathName, const UINT32* p, int w, int h, int d) +{ + IO::File file; + if (!file.Open(pathName, "wb")) + throw VERUS_RUNTIME_ERROR << "SaveDDS(), Open()"; + + DDSHeader header; + header.flags |= DDSHeader::Flags::linearSize; + header.height = h; + header.width = w; + header.pitchOrLinearSize = w * h * 4 * Math::Max(1, abs(d)); + header.depth = abs(d); + header.pixelFormat.flags |= DDSHeader::PixelFormatFlags::alphaPixels | DDSHeader::PixelFormatFlags::rgb; + header.pixelFormat.rgbBitCount = 32; + header.pixelFormat.rBitMask = 0x00FF0000; + header.pixelFormat.gBitMask = 0x0000FF00; + header.pixelFormat.bBitMask = 0x000000FF; + header.pixelFormat.rgbAlphaBitMask = 0xFF000000; + if (d > 0) + { + header.flags |= DDSHeader::Flags::depth; + header.caps.caps1 = DDSHeader::Caps1::complex; + header.caps.caps2 = DDSHeader::Caps2::volume; + } + else if (-6 == d) + { + header.caps.caps1 = DDSHeader::Caps1::complex; + header.caps.caps2 = DDSHeader::Caps2::cubemap | + DDSHeader::Caps2::cubemapPositiveX | + DDSHeader::Caps2::cubemapNegativeX | + DDSHeader::Caps2::cubemapPositiveY | + DDSHeader::Caps2::cubemapNegativeY | + DDSHeader::Caps2::cubemapPositiveZ | + DDSHeader::Caps2::cubemapNegativeZ; + } + file.Write(&header, sizeof(header)); + file.Write(p, header.pitchOrLinearSize); +} + +void FileSystem::SaveString(CSZ pathName, CSZ s) +{ + File file; + if (file.Open(pathName, "wb")) + file.Write(s, strlen(s)); + else + throw VERUS_RUNTIME_ERROR << "Create(SaveString)"; +} + +// Image: + +Image::Image() +{ +} + +Image::~Image() +{ + Done(); +} + +void Image::Init(CSZ url) +{ +#if 0 + VERUS_INIT(); + FileSystem::LoadResourceFromFile(url, _v); + ilGenImages(1, &_name); + ilBindImage(_name); + ilLoadL(IL_TYPE_UNKNOWN, _v.data(), _v.size()); + _width = ilGetInteger(IL_IMAGE_WIDTH); + _height = ilGetInteger(IL_IMAGE_HEIGHT); + _bytesPerPixel = ilGetInteger(IL_IMAGE_BYTES_PER_PIXEL); + _p = ilGetData(); +#endif +} + +void Image::Done() +{ +#if 0 + if (_name) + { + ilBindImage(_name); + ilDeleteImages(1, &_name); + _name = 0; + } + _v.clear(); + VERUS_DONE(Image); +#endif +} diff --git a/Verus/src/IO/FileSystem.h b/Verus/src/IO/FileSystem.h new file mode 100644 index 0000000..2e1e51d --- /dev/null +++ b/Verus/src/IO/FileSystem.h @@ -0,0 +1,79 @@ +#pragma once + +namespace verus +{ + namespace IO + { + class FileSystem : public Singleton + { + typedef Map> TMapCache; + + TMapCache _mapCache; + INT64 _cacheSize = 0; + + public: + struct LoadDesc + { + int _texturePart = 0; + bool _nullTerm = false; + bool _mandatory = true; + + LoadDesc(bool nullTerm = false, int texturePart = 0, bool mandatory = true) : + _nullTerm(nullTerm), _texturePart(texturePart), _mandatory(mandatory) {} + }; + VERUS_TYPEDEFS(LoadDesc); + + enum { querySize = 240, entrySize = 256 }; + + FileSystem(); + ~FileSystem(); + + static size_t FindColonForPAK(CSZ url); + static void ReadHeaderPAK(RFile file, UINT32& magic, INT64& entriesOffset, INT64& entriesSize); + + void PreloadCache(CSZ pak, CSZ types[]); + + static void LoadResource /**/(CSZ url, Vector& vData, RcLoadDesc desc = LoadDesc()); + static void LoadResourceFromFile /**/(CSZ url, Vector& vData, RcLoadDesc desc = LoadDesc()); + + void LoadResourceFromCache(CSZ url, Vector& vData, bool mandatory = true); + + VERUS_P(static void LoadResourceFromPAK(CSZ url, Vector& vData, RcLoadDesc desc, RFile file, CSZ pakEntry)); + + static void LoadTextureParts(RFile file, CSZ url, int texturePart, Vector& vData); + static String ConvertFilenameToPassword(CSZ fileEntry); + + static bool FileExist(CSZ url); + static bool Delete(CSZ pathName); + + // Path & filename: + static String ConvertAbsolutePathToRelative(RcString path); + static String ConvertRelativePathToAbsolute(RcString path, bool useProjectDir); + static String ReplaceFilename(CSZ pathName, CSZ filename); + + // Save data: + //static void SaveImage /**/(CSZ pathName, const UINT32* p, int w, int h, bool upsideDown = false, ILenum type = IL_PSD); + static void SaveDDS /**/(CSZ pathName, const UINT32* p, int w, int h, int d = 0); + static void SaveString /**/(CSZ pathName, CSZ s); + }; + VERUS_TYPEDEFS(FileSystem); + + class Image : public Object + { + public: + Vector _v; + BYTE* _p = nullptr; + //ILuint _name = 0; + int _width = 0; + int _height = 0; + int _bytesPerPixel = 0; + + Image(); + ~Image(); + + void Init(CSZ url); + void Done(); + }; + VERUS_TYPEDEFS(Image); + } +} diff --git a/Verus/src/IO/IO.cpp b/Verus/src/IO/IO.cpp new file mode 100644 index 0000000..112c822 --- /dev/null +++ b/Verus/src/IO/IO.cpp @@ -0,0 +1,17 @@ +#include "verus.h" + +namespace verus +{ + VERUS_CT_ASSERT(IO::Stream::bufferSize == 256); + + void Make_IO() + { + IO::FileSystem::Make(); + IO::Async::Make(); + } + void Free_IO() + { + IO::Async::Free(); + IO::FileSystem::Free(); + } +} diff --git a/Verus/src/IO/IO.h b/Verus/src/IO/IO.h new file mode 100644 index 0000000..ab68db9 --- /dev/null +++ b/Verus/src/IO/IO.h @@ -0,0 +1,16 @@ +#pragma once + +#include "DDSHeader.h" +#include "Stream.h" +#include "StreamPtr.h" +#include "File.h" +#include "FileSystem.h" +#include "Async.h" +#include "Json.h" +#include "Xml.h" + +namespace verus +{ + void Make_IO(); + void Free_IO(); +} diff --git a/Verus/src/IO/Json.cpp b/Verus/src/IO/Json.cpp new file mode 100644 index 0000000..52f6ff3 --- /dev/null +++ b/Verus/src/IO/Json.cpp @@ -0,0 +1,124 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::IO; + +Json::Json() +{ +} + +Json::Json(CSZ pathName) : _pathName(pathName) +{ +} + +Json::~Json() +{ +} + +void Json::SetFilename(CSZ name) +{ + String pathName; + CSZ pSlash = strchr(name, '/'); + if (!pSlash) + { + //pathName = CUtils::I().GetWritablePath() + "/" + name; + //name = _C(pathName); + } + _pathName = name; +} + +void Json::Load(bool fromCache) +{ + Vector vData; + if (fromCache) + { + IO::FileSystem::I().LoadResourceFromCache(_C(_pathName), vData); + _json = nlohmann::json::parse(reinterpret_cast(vData.data())); + } + else + { + StringStream ss; + ss << "Load() url=" << _pathName; + VERUS_LOG_INFO(_C(ss.str())); + + const size_t colon = _pathName.find(':'); + if (colon != String::npos && colon > 1) + { + IO::FileSystem::LoadResource(_C(_pathName), vData, IO::FileSystem::LoadDesc(true)); + _json = nlohmann::json::parse(reinterpret_cast(vData.data())); + } + else + { + IO::File file; + if (file.Open(_C(_pathName))) + { + file.ReadAll(vData, true); + _json = nlohmann::json::parse(reinterpret_cast(vData.data())); + } + } + } +} + +void Json::Save() +{ + StringStream ss; + ss << "Save() url=" << _pathName; + IO::File file; + if (file.Open(_C(_pathName), "w")) + { + const String s = _json.dump(1, '\t'); + file.WriteText(_C(s)); + ss << ", OK"; + } + VERUS_LOG_INFO(_C(ss.str())); +} + +void Json::Set(CSZ name, CSZ v, bool ifNull) +{ + if (ifNull && _json.find(name) != _json.end()) + return; + _json[name] = v; +} + +void Json::Set(CSZ name, int v, bool ifNull) +{ + if (ifNull && _json.find(name) != _json.end()) + return; + _json[name] = v; +} + +void Json::Set(CSZ name, float v, bool ifNull) +{ + if (ifNull && _json.find(name) != _json.end()) + return; + _json[name] = v; +} + +void Json::Set(CSZ name, bool v, bool ifNull) +{ + if (ifNull && _json.find(name) != _json.end()) + return; + _json[name] = v; +} + +CSZ Json::GetS(CSZ name, CSZ def) +{ + if (_json.find(name) == _json.end()) + return def; + return _json[name].get_ref().c_str(); +} + +int Json::GetI(CSZ name, int def) +{ + return _json.value(name, def); +} + +float Json::GetF(CSZ name, float def) +{ + return _json.value(name, def); +} + +bool Json::GetB(CSZ name, bool def) +{ + return _json.value(name, def); +} diff --git a/Verus/src/IO/Json.h b/Verus/src/IO/Json.h new file mode 100644 index 0000000..040d848 --- /dev/null +++ b/Verus/src/IO/Json.h @@ -0,0 +1,34 @@ +#pragma once + +namespace verus +{ + namespace IO + { + class Json + { + nlohmann::json _json; + String _pathName; + + public: + Json(); + Json(CSZ pathName); + ~Json(); + + Str GetFilename() const { return _C(_pathName); } + void SetFilename(CSZ name); + void Load(bool fromCache = false); + void Save(); + + void Set(CSZ name, CSZ v, bool ifNull = false); + void Set(CSZ name, int v, bool ifNull = false); + void Set(CSZ name, float v, bool ifNull = false); + void Set(CSZ name, bool v, bool ifNull = false); + + CSZ GetS(CSZ name, CSZ def = nullptr); + int GetI(CSZ name, int def = 0); + float GetF(CSZ name, float def = 0); + bool GetB(CSZ name, bool def = false); + }; + VERUS_TYPEDEFS(Json); + } +} diff --git a/Verus/src/IO/Stream.h b/Verus/src/IO/Stream.h new file mode 100644 index 0000000..8328dcf --- /dev/null +++ b/Verus/src/IO/Stream.h @@ -0,0 +1,114 @@ +#pragma once + +namespace verus +{ + namespace IO + { + class Stream + { + public: + enum { bufferSize = UCHAR_MAX + 1 }; + + Stream() {} + virtual ~Stream() {} + + virtual BYTE* GetPointer() { return nullptr; } + + virtual INT64 Read(void* p, INT64 size) = 0; + virtual INT64 Write(const void* p, INT64 size) = 0; + + INT64 ReadString(SZ sz) + { + BYTE len; + (*this) >> len; + const INT64 ret = Read(sz, len); + sz[len] = 0; + return ret + 1; + } + + INT64 WriteString(CSZ sz) + { + const size_t len = strlen(sz); + if (len > UCHAR_MAX) + throw VERUS_RUNTIME_ERROR << "WriteString(), Invalid string length"; + (*this) << static_cast(len); + const INT64 ret = Write(sz, len); + return ret + 1; + } + + void WriteText(CSZ sz) + { + Write(sz, strlen(sz)); + } + + Stream& operator<<(CSZ txt) + { + Write(txt, strlen(txt)); + return *this; + } + + template Stream& operator<<(const T& x) + { + Write(&x, sizeof(T)); + return *this; + } + + template Stream& operator>>(T& x) + { + Read(&x, sizeof(T)); + return *this; + } + }; + VERUS_TYPEDEFS(Stream); + + class SeekableStream : public Stream + { + INT64 _blockOffset = 0; + + public: + SeekableStream() {} + virtual ~SeekableStream() {} + + virtual void Seek(INT64 offset, int origin) = 0; + virtual INT64 GetPosition() = 0; + + // Adds a placeholder for block size and begins a block: + void BeginBlock() + { + VERUS_RT_ASSERT(!_blockOffset); + _blockOffset = GetPosition(); + const INT64 zero = 0; + Write(&zero, sizeof(zero)); + } + + // Ends a block by placing it's size into placeholder: + void EndBlock() + { + VERUS_RT_ASSERT(_blockOffset); + const INT64 offset = GetPosition(); + const INT64 delta = offset - _blockOffset - sizeof(INT64); + Seek(_blockOffset, SEEK_SET); + Write(&delta, sizeof(delta)); + Seek(0, SEEK_END); + _blockOffset = 0; + } + + INT64 GetSize() + { + const INT64 was = GetPosition(); + Seek(0, SEEK_END); + const INT64 size = GetPosition(); + Seek(was, SEEK_SET); + return size; + } + + void ReadAll(Vector& vData, bool nullTerm = false) + { + const INT64 size = GetSize(); + vData.resize(nullTerm ? size + 1 : size); + Read(vData.data(), size); + } + }; + VERUS_TYPEDEFS(SeekableStream); + } +} diff --git a/Verus/src/IO/StreamPtr.h b/Verus/src/IO/StreamPtr.h new file mode 100644 index 0000000..d771b06 --- /dev/null +++ b/Verus/src/IO/StreamPtr.h @@ -0,0 +1,39 @@ +#pragma once + +namespace verus +{ + namespace IO + { + class StreamPtr : public Stream + { + const BYTE* _p; + INT64 _size; + INT64 _offset; + + public: + StreamPtr(RcBlob blob) : _p(blob._p), _size(blob._size), _offset(0) {} + ~StreamPtr() {} + + INT64 GetOffset() const { return _offset; } + INT64 GetSize() const { return _size; } + bool IsEnd() const { return _offset >= _size; } + + void Advance(INT64 a) { _offset += a; } + + virtual INT64 Read(void* p, INT64 size) override + { + VERUS_RT_ASSERT(_offset + size <= _size); + memcpy(p, _p + _offset, size); + _offset += size; + return size; + } + + virtual INT64 Write(const void* p, INT64 size) override + { + VERUS_RT_FAIL("Write()"); + return 0; + } + }; + VERUS_TYPEDEFS(StreamPtr); + } +} diff --git a/Verus/src/IO/Xml.cpp b/Verus/src/IO/Xml.cpp new file mode 100644 index 0000000..47d8ede --- /dev/null +++ b/Verus/src/IO/Xml.cpp @@ -0,0 +1,148 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::IO; + +Xml::Xml() +{ +} + +Xml::Xml(CSZ pathName) : _pathName(pathName) +{ +} + +Xml::~Xml() +{ +} + +void Xml::SetFilename(CSZ name) +{ + String pathName; + CSZ pSlash = strchr(name, '/'); + if (!pSlash) + { + //pathName = CUtils::I().GetWritablePath() + "/" + name; + name = _C(pathName); + } + _pathName = name; +} + +void Xml::Load(bool fromCache) +{ + Vector vData; + if (fromCache) + { + IO::FileSystem::I().LoadResourceFromCache(_C(_pathName), vData); + _doc.Parse(reinterpret_cast(vData.data())); + } + else + { + StringStream ss; + ss << "Load() url=" << _pathName; + VERUS_LOG_INFO(_C(ss.str())); + + const size_t colon = _pathName.find(':'); + if (colon != String::npos && colon > 1) + { + IO::FileSystem::LoadResource(_C(_pathName), vData, IO::FileSystem::LoadDesc(true)); + _doc.Parse(reinterpret_cast(vData.data())); + } + else + { + IO::File file; + if (file.Open(_C(_pathName))) + _doc.LoadFile(file.GetFile()); + } + } +} + +void Xml::Save() +{ + StringStream ss; + ss << "Save() url=" << _pathName; + IO::File file; + if (file.Open(_C(_pathName), "w")) + { + _doc.SaveFile(file.GetFile()); + ss << ", OK"; + } + VERUS_LOG_INFO(_C(ss.str())); +} + +tinyxml2::XMLElement* Xml::GetRoot() +{ + tinyxml2::XMLElement* pElem = _doc.FirstChildElement(); + if (!pElem) + { + _doc.Clear(); + _doc.LinkEndChild(_doc.NewDeclaration()); + _doc.LinkEndChild(_doc.NewElement("root")); + pElem = _doc.FirstChildElement(); + } + return pElem; +} + +tinyxml2::XMLElement* Xml::AddElement(CSZ name) +{ + tinyxml2::XMLElement* pElem = _doc.NewElement(name); + GetRoot()->LinkEndChild(pElem); + return pElem; +} + +void Xml::Set(CSZ name, CSZ v, bool ifNull) +{ + tinyxml2::XMLElement* pElem = GetRoot()->FirstChildElement(name); + if (!pElem) + { + GetRoot()->LinkEndChild(_doc.NewElement(name)); + pElem = GetRoot()->FirstChildElement(name); + } + else if (ifNull) + return; + pElem->SetAttribute("v", v); +} + +void Xml::Set(CSZ name, int v, bool ifNull) +{ + char txt[16]; + sprintf_s(txt, "%d", v); + Set(name, txt, ifNull); +} + +void Xml::Set(CSZ name, float v, bool ifNull) +{ + char txt[16]; + sprintf_s(txt, "%g", v); + Set(name, txt, ifNull); +} + +void Xml::Set(CSZ name, bool v, bool ifNull) +{ + char txt[16]; + sprintf_s(txt, "%d", v ? 1 : 0); + Set(name, txt, ifNull); +} + +CSZ Xml::GetS(CSZ name, CSZ def) +{ + tinyxml2::XMLElement* pElem = GetRoot()->FirstChildElement(name); + return pElem ? pElem->Attribute("v") : def; +} + +int Xml::GetI(CSZ name, int def) +{ + CSZ v = GetS(name); + return v ? atoi(v) : def; +} + +float Xml::GetF(CSZ name, float def) +{ + CSZ v = GetS(name); + return v ? static_cast(atof(v)) : def; +} + +bool Xml::GetB(CSZ name, bool def) +{ + CSZ v = GetS(name); + return v ? !!atoi(v) : def; +} diff --git a/Verus/src/IO/Xml.h b/Verus/src/IO/Xml.h new file mode 100644 index 0000000..54e1905 --- /dev/null +++ b/Verus/src/IO/Xml.h @@ -0,0 +1,37 @@ +#pragma once + +namespace verus +{ + namespace IO + { + class Xml + { + tinyxml2::XMLDocument _doc; + String _pathName; + + public: + Xml(); + Xml(CSZ pathName); + ~Xml(); + + Str GetFilename() const { return _C(_pathName); } + void SetFilename(CSZ name); + void Load(bool fromCache = false); + void Save(); + + tinyxml2::XMLElement* GetRoot(); + tinyxml2::XMLElement* AddElement(CSZ name); + + void Set(CSZ name, CSZ v, bool ifNull = false); + void Set(CSZ name, int v, bool ifNull = false); + void Set(CSZ name, float v, bool ifNull = false); + void Set(CSZ name, bool v, bool ifNull = false); + + CSZ GetS(CSZ name, CSZ def = nullptr); + int GetI(CSZ name, int def = 0); + float GetF(CSZ name, float def = 0); + bool GetB(CSZ name, bool def = false); + }; + VERUS_TYPEDEFS(Xml); + } +} diff --git a/Verus/src/Input/Input.cpp b/Verus/src/Input/Input.cpp new file mode 100644 index 0000000..757b7aa --- /dev/null +++ b/Verus/src/Input/Input.cpp @@ -0,0 +1,13 @@ +#include "verus.h" + +namespace verus +{ + void Make_Input() + { + Input::KeyMapper::Make(); + } + void Free_Input() + { + Input::KeyMapper::Free(); + } +} diff --git a/Verus/src/Input/Input.h b/Verus/src/Input/Input.h new file mode 100644 index 0000000..30f0ca1 --- /dev/null +++ b/Verus/src/Input/Input.h @@ -0,0 +1,9 @@ +#pragma once + +#include "KeyMapper.h" + +namespace verus +{ + void Make_Input(); + void Free_Input(); +} diff --git a/Verus/src/Input/KeyMapper.cpp b/Verus/src/Input/KeyMapper.cpp new file mode 100644 index 0000000..3ea542c --- /dev/null +++ b/Verus/src/Input/KeyMapper.cpp @@ -0,0 +1,457 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Input; + +KeyMapper::KeyMapper() +{ + VERUS_ZERO_MEM(_kbStatePressed); + VERUS_ZERO_MEM(_kbStateDownEvent); + VERUS_ZERO_MEM(_kbStateUpEvent); + VERUS_ZERO_MEM(_mouseStatePressed); + VERUS_ZERO_MEM(_mouseStateDownEvent); + VERUS_ZERO_MEM(_mouseStateUpEvent); + VERUS_ZERO_MEM(_mouseStateDoubleClick); + VERUS_ZERO_MEM(_joyStateAxis); + VERUS_ZERO_MEM(_joyStatePressed); + VERUS_ZERO_MEM(_joyStateDownEvent); + VERUS_ZERO_MEM(_joyStateUpEvent); +} + +KeyMapper::~KeyMapper() +{ + Done(); +} + +void KeyMapper::Init() +{ + VERUS_INIT(); + + const int num = SDL_NumJoysticks(); + _vJoysticks.reserve(num); + VERUS_FOR(i, num) + { + SDL_Joystick* pJoystick = SDL_JoystickOpen(i); + if (pJoystick) + _vJoysticks.push_back(pJoystick); + } +} + +void KeyMapper::Done() +{ + VERUS_FOREACH_CONST(Vector, _vJoysticks, it) + SDL_JoystickClose(*it); + _vJoysticks.clear(); + + VERUS_DONE(KeyMapper); +} + +bool KeyMapper::HandleSdlEvent(SDL_Event& event) +{ + VERUS_RT_ASSERT(IsInitialized()); + //VERUS_QREF_CONST_SETTINGS; + + static bool firstTime = true; + + switch (event.type) + { + // Keyboard: + case SDL_KEYDOWN: + { + //if (settings.m_screenWindowed && SDL_SCANCODE_KP_ENTER == event.key.keysym.scancode) + { + const SDL_bool rel = SDL_GetRelativeMouseMode(); + SDL_SetRelativeMouseMode(rel ? SDL_FALSE : SDL_TRUE); + } + OnKeyDown(event.key.keysym.scancode); + } + break; + case SDL_KEYUP: + { + OnKeyUp(event.key.keysym.scancode); + } + break; + case SDL_TEXTINPUT: + { + wchar_t wide[4]; + Str::Utf8ToWide(event.text.text, wide, 4); + OnChar(wide[0]); + } + break; + + // Mouse: + case SDL_MOUSEMOTION: + { + if (!firstTime) + OnMouseMove(event.motion.xrel, event.motion.yrel); + else + firstTime = false; + } + break; + case SDL_MOUSEBUTTONDOWN: + { + if (1 == event.button.clicks) + { + if (event.button.button < VERUS_BUTTON_WHEELUP) + OnMouseDown(event.button.button); + } + else if (2 == event.button.clicks) + { + if (event.button.button < VERUS_BUTTON_WHEELUP) + OnMouseDoubleClick(event.button.button); + } + } + break; + case SDL_MOUSEBUTTONUP: + { + if (1 == event.button.clicks) + { + if (event.button.button < VERUS_BUTTON_WHEELUP) + OnMouseUp(event.button.button); + } + } + break; + case SDL_MOUSEWHEEL: + { + if (event.wheel.y >= 0) + { + OnMouseDown(VERUS_BUTTON_WHEELUP); + OnMouseUp(VERUS_BUTTON_WHEELUP); + } + else + { + OnMouseDown(VERUS_BUTTON_WHEELDOWN); + OnMouseUp(VERUS_BUTTON_WHEELDOWN); + } + } + break; + + // Joystick: + case SDL_JOYAXISMOTION: + { + OnJoyAxis(event.jaxis.axis, event.jaxis.value); + } + break; + case SDL_JOYBUTTONDOWN: + { + OnJoyDown(event.jbutton.button); + } + break; + case SDL_JOYBUTTONUP: + { + OnJoyUp(event.jbutton.button); + } + break; + + default: + return false; + } + return true; +} + +void KeyMapper::Load(Action* pAction) +{ + StringStream ss; + //ss << CUtils::I().GetWritablePath() << "/Keys.xml"; + + tinyxml2::XMLDocument xmlDoc; + + IO::File file; + if (file.Open(_C(ss.str()), "rb")) + { + if (xmlDoc.LoadFile(file.GetFile())) + xmlDoc.Clear(); + file.Close(); + } + tinyxml2::XMLHandle hDoc(&xmlDoc); + tinyxml2::XMLHandle hRoot = tinyxml2::XMLHandle(hDoc.FirstChildElement().ToElement()); + if (!hRoot.ToElement()) + { + tinyxml2::XMLElement* pRoot = xmlDoc.NewElement("keys"); + xmlDoc.Clear(); + xmlDoc.LinkEndChild(xmlDoc.NewDeclaration()); + xmlDoc.LinkEndChild(pRoot); + //hRoot = pRoot; + } +} + +bool KeyMapper::IsKeyPressed(int id) const +{ + id = Math::Clamp(id, 0, VERUS_INPUT_MAX_KB - 1); + return _kbStatePressed[id] || TranslateJoyPress(id, false); +} + +bool KeyMapper::IsKeyDownEvent(int id) const +{ + id = Math::Clamp(id, 0, VERUS_INPUT_MAX_KB - 1); + return _kbStateDownEvent[id]; +} + +bool KeyMapper::IsKeyUpEvent(int id) const +{ + id = Math::Clamp(id, 0, VERUS_INPUT_MAX_KB - 1); + return _kbStateUpEvent[id]; +} + +bool KeyMapper::IsMousePressed(int id) const +{ + id = Math::Clamp(id, 0, VERUS_INPUT_MAX_MOUSE - 1); + return _mouseStatePressed[id] || TranslateJoyPress(id, true); +} + +bool KeyMapper::IsMouseDownEvent(int id) const +{ + id = Math::Clamp(id, 0, VERUS_INPUT_MAX_MOUSE - 1); + return _mouseStateDownEvent[id]; +} + +bool KeyMapper::IsMouseUpEvent(int id) const +{ + id = Math::Clamp(id, 0, VERUS_INPUT_MAX_MOUSE - 1); + return _mouseStateUpEvent[id]; +} + +bool KeyMapper::IsMouseDoubleClick(int id) const +{ + id = Math::Clamp(id, 0, VERUS_INPUT_MAX_MOUSE - 1); + return _mouseStateDoubleClick[id]; +} + +bool KeyMapper::IsActionPressed(int actionID) const +{ + return false; +} + +bool KeyMapper::IsActionDownEvent(int actionID) const +{ + return false; +} + +bool KeyMapper::IsActionUpEvent(int actionID) const +{ + return false; +} + +float KeyMapper::GetJoyAxisState(int id) const +{ + id = Math::Clamp(id, 0, JOY_AXIS_MAX - 1); + return _joyStateAxis[id]; +} + +void KeyMapper::BuildLookup() +{ +} + +void KeyMapper::ResetClickState() +{ + VERUS_ZERO_MEM(_kbStateDownEvent); + VERUS_ZERO_MEM(_kbStateUpEvent); + VERUS_ZERO_MEM(_mouseStateDownEvent); + VERUS_ZERO_MEM(_mouseStateUpEvent); + VERUS_ZERO_MEM(_mouseStateDoubleClick); + VERUS_ZERO_MEM(_joyStateDownEvent); + VERUS_ZERO_MEM(_joyStateUpEvent); +} + +void KeyMapper::OnKeyDown(int id) +{ + id = Math::Clamp(id, 0, VERUS_INPUT_MAX_KB - 1); + _kbStatePressed[id] = true; + _kbStateDownEvent[id] = true; + if (_pKeyMapperDelegate) + _pKeyMapperDelegate->KeyMapper_OnKey(id); +} + +void KeyMapper::OnKeyUp(int id) +{ + id = Math::Clamp(id, 0, VERUS_INPUT_MAX_KB - 1); + _kbStatePressed[id] = false; + _kbStateUpEvent[id] = true; +} + +void KeyMapper::OnChar(wchar_t c) +{ + if (c && _pKeyMapperDelegate) + _pKeyMapperDelegate->KeyMapper_OnChar(c); +} + +void KeyMapper::OnMouseMove(int x, int y) +{ + if (_pKeyMapperDelegate) + _pKeyMapperDelegate->KeyMapper_OnMouseMove(x, y); +} + +void KeyMapper::OnMouseDown(int id) +{ + id = Math::Clamp(id, 0, VERUS_INPUT_MAX_MOUSE - 1); + _mouseStatePressed[id] = true; + _mouseStateDownEvent[id] = true; +} + +void KeyMapper::OnMouseUp(int id) +{ + id = Math::Clamp(id, 0, VERUS_INPUT_MAX_MOUSE - 1); + _mouseStatePressed[id] = false; + _mouseStateUpEvent[id] = true; +} + +void KeyMapper::OnMouseDoubleClick(int id) +{ + id = Math::Clamp(id, 0, VERUS_INPUT_MAX_MOUSE - 1); + _mouseStateDoubleClick[id] = true; +} + +void KeyMapper::OnJoyAxis(int id, int value) +{ + id = Math::Clamp(id, 0, JOY_AXIS_MAX - 1); + const float amount = value * (1.f / SHRT_MAX); + const bool oneWay = (JOY_AXIS_TRIGGERLEFT == id || JOY_AXIS_TRIGGERRIGHT == id); + float fixedAmount = amount; + if (oneWay) + { + fixedAmount = fixedAmount * 0.5f + 0.5f; + fixedAmount = Math::Clamp((fixedAmount - VERUS_INPUT_JOYAXIS_THRESHOLD) / (1 - VERUS_INPUT_JOYAXIS_THRESHOLD), 0, 1); + } + else + { + fixedAmount = Math::Clamp((abs(fixedAmount) - VERUS_INPUT_JOYAXIS_THRESHOLD) / (1 - VERUS_INPUT_JOYAXIS_THRESHOLD), 0, 1); + fixedAmount *= glm::sign(amount); + } + _joyStateAxis[id] = fixedAmount; +} + +void KeyMapper::OnJoyDown(int id) +{ + id = Math::Clamp(id, 0, JOY_BUTTON_MAX - 1); + _joyStatePressed[id] = true; + _joyStateDownEvent[id] = true; + TranslateJoy(id, false); +} + +void KeyMapper::OnJoyUp(int id) +{ + id = Math::Clamp(id, 0, JOY_BUTTON_MAX - 1); + _joyStatePressed[id] = false; + _joyStateUpEvent[id] = true; + TranslateJoy(id, true); +} + +void KeyMapper::TranslateJoy(int id, bool up) +{ + switch (id) + { + case JOY_BUTTON_DPAD_UP: + break; + case JOY_BUTTON_DPAD_DOWN: + break; + case JOY_BUTTON_DPAD_LEFT: + break; + case JOY_BUTTON_DPAD_RIGHT: + break; + case JOY_BUTTON_START: + break; + case JOY_BUTTON_BACK: + break; + case JOY_BUTTON_LEFTSTICK: + break; + case JOY_BUTTON_RIGHTSTICK: + break; + case JOY_BUTTON_LEFTSHOULDER: + up ? OnMouseUp(VERUS_BUTTON_WHEELDOWN) : OnMouseDown(VERUS_BUTTON_WHEELDOWN); + break; + case JOY_BUTTON_RIGHTSHOULDER: + up ? OnMouseUp(VERUS_BUTTON_WHEELUP) : OnMouseDown(VERUS_BUTTON_WHEELUP); + break; + case JOY_BUTTON_A: + up ? OnKeyUp(SDL_SCANCODE_SPACE) : OnKeyDown(SDL_SCANCODE_SPACE); + break; + case JOY_BUTTON_B: + up ? OnKeyUp(SDL_SCANCODE_LCTRL) : OnKeyDown(SDL_SCANCODE_LCTRL); + break; + case JOY_BUTTON_X: + up ? OnMouseUp(SDL_BUTTON_LEFT) : OnMouseDown(SDL_BUTTON_LEFT); + break; + case JOY_BUTTON_Y: + break; + case JOY_BUTTON_GUIDE: + break; + } +} + +bool KeyMapper::TranslateJoyPress(int id, bool mouse) const +{ + if (mouse) + { + switch (id) + { + case SDL_BUTTON_LEFT: + return _joyStateAxis[JOY_AXIS_TRIGGERRIGHT] >= 0.25f; + } + } + else + { + const float forward = 0.1f; + const float walk = 0.25f; + const float run = 0.6f; + switch (id) + { + case SDL_SCANCODE_A: // Strafe left when attacking? + { + if (IsMousePressed(SDL_BUTTON_LEFT)) + return _joyStateAxis[JOY_AXIS_LEFTX] < -walk; + } + break; + case SDL_SCANCODE_D: // Strafe right when attacking? + { + if (IsMousePressed(SDL_BUTTON_LEFT)) + return _joyStateAxis[JOY_AXIS_LEFTX] >= walk; + } + break; + case SDL_SCANCODE_S: + { + if (IsMousePressed(SDL_BUTTON_LEFT)) + { + return _joyStateAxis[JOY_AXIS_LEFTY] >= walk; + } + else + { + if (_joyStateAxis[JOY_AXIS_LEFTY] < forward) + return false; + const glm::vec2 v(_joyStateAxis[JOY_AXIS_LEFTX], _joyStateAxis[JOY_AXIS_LEFTY]); + const float len = glm::length(v); + return len >= walk; + } + } + break; + case SDL_SCANCODE_W: + { + if (IsMousePressed(SDL_BUTTON_LEFT)) + { + return _joyStateAxis[JOY_AXIS_LEFTY] < -walk; + } + else + { + if (_joyStateAxis[JOY_AXIS_LEFTY] >= forward) + return false; + const glm::vec2 v(_joyStateAxis[JOY_AXIS_LEFTX], _joyStateAxis[JOY_AXIS_LEFTY]); + const float len = glm::length(v); + return len >= walk; + } + } + break; + case SDL_SCANCODE_SPACE: + { + return _joyStateAxis[JOY_AXIS_TRIGGERLEFT] >= 0.25f; + } + break; + case SDL_SCANCODE_LSHIFT: + { + const glm::vec2 v(_joyStateAxis[JOY_AXIS_LEFTX], _joyStateAxis[JOY_AXIS_LEFTY]); + const float len = glm::length(v); + return len >= walk && len < run; + } + break; + } + } + return false; +} diff --git a/Verus/src/Input/KeyMapper.h b/Verus/src/Input/KeyMapper.h new file mode 100644 index 0000000..4508099 --- /dev/null +++ b/Verus/src/Input/KeyMapper.h @@ -0,0 +1,132 @@ +#pragma once + +#define VERUS_INPUT_JOYAXIS_THRESHOLD 0.25f +#define VERUS_INPUT_MAX_KB SDL_NUM_SCANCODES +#define VERUS_INPUT_MAX_MOUSE 8 +#define VERUS_BUTTON_WHEELUP 6 +#define VERUS_BUTTON_WHEELDOWN 7 + +namespace verus +{ + namespace Input + { + struct KeyMapperDelegate + { + virtual void KeyMapper_OnMouseMove(int x, int y) = 0; + virtual void KeyMapper_OnKey(int scancode) = 0; + virtual void KeyMapper_OnChar(wchar_t c) = 0; + }; + VERUS_TYPEDEFS(KeyMapperDelegate); + + enum + { + JOY_AXIS_LEFTX, + JOY_AXIS_LEFTY, + JOY_AXIS_RIGHTX, + JOY_AXIS_RIGHTY, + JOY_AXIS_TRIGGERLEFT, + JOY_AXIS_TRIGGERRIGHT, + JOY_AXIS_MAX + }; + + enum + { + JOY_BUTTON_DPAD_UP, + JOY_BUTTON_DPAD_DOWN, + JOY_BUTTON_DPAD_LEFT, + JOY_BUTTON_DPAD_RIGHT, + JOY_BUTTON_START, + JOY_BUTTON_BACK, + JOY_BUTTON_LEFTSTICK, + JOY_BUTTON_RIGHTSTICK, + JOY_BUTTON_LEFTSHOULDER, + JOY_BUTTON_RIGHTSHOULDER, + JOY_BUTTON_A, + JOY_BUTTON_B, + JOY_BUTTON_X, + JOY_BUTTON_Y, + JOY_BUTTON_GUIDE, + JOY_BUTTON_MAX + }; + + class KeyMapper : public Singleton, public Object + { + struct Action + { + int _key; + int _keyEx; + int _actionID; + String _actionName; + }; + + typedef Map TMapLookup; + + Vector _mapAction; + TMapLookup _mapLookupByKey; + TMapLookup _mapLookupByAction; + bool _kbStatePressed[VERUS_INPUT_MAX_KB]; + bool _kbStateDownEvent[VERUS_INPUT_MAX_KB]; + bool _kbStateUpEvent[VERUS_INPUT_MAX_KB]; + bool _mouseStatePressed[VERUS_INPUT_MAX_MOUSE]; + bool _mouseStateDownEvent[VERUS_INPUT_MAX_MOUSE]; + bool _mouseStateUpEvent[VERUS_INPUT_MAX_MOUSE]; + bool _mouseStateDoubleClick[VERUS_INPUT_MAX_MOUSE]; + PKeyMapperDelegate _pKeyMapperDelegate = nullptr; + Vector _vJoysticks; + float _joyStateAxis[JOY_AXIS_MAX]; + bool _joyStatePressed[JOY_BUTTON_MAX]; + bool _joyStateDownEvent[JOY_BUTTON_MAX]; + bool _joyStateUpEvent[JOY_BUTTON_MAX]; + + public: + KeyMapper(); + ~KeyMapper(); + + void Init(); + void Done(); + + bool HandleSdlEvent(SDL_Event& event); + + void Load(Action* pAction); + + bool IsKeyPressed(int id) const; + bool IsKeyDownEvent(int id) const; + bool IsKeyUpEvent(int id) const; + + bool IsMousePressed(int id) const; + bool IsMouseDownEvent(int id) const; + bool IsMouseUpEvent(int id) const; + bool IsMouseDoubleClick(int id) const; + + bool IsActionPressed(int actionID) const; + bool IsActionDownEvent(int actionID) const; + bool IsActionUpEvent(int actionID) const; + + float GetJoyAxisState(int id) const; + + void BuildLookup(); + + void ResetClickState(); + + VERUS_P(void OnKeyDown(int id)); + VERUS_P(void OnKeyUp(int id)); + VERUS_P(void OnChar(wchar_t c)); + + VERUS_P(void OnMouseMove(int x, int y)); + VERUS_P(void OnMouseDown(int id)); + VERUS_P(void OnMouseUp(int id)); + VERUS_P(void OnMouseDoubleClick(int id)); + + VERUS_P(void OnJoyAxis(int id, int value)); + VERUS_P(void OnJoyDown(int id)); + VERUS_P(void OnJoyUp(int id)); + VERUS_P(void TranslateJoy(int id, bool up)); + VERUS_P(bool TranslateJoyPress(int id, bool mouse) const); + + PKeyMapperDelegate SetDelegate(PKeyMapperDelegate p) { return Utils::Swap(_pKeyMapperDelegate, p); } + + static CSZ GetSingletonFailMessage() { return "Make_Input(); // FAIL.\r\n"; } + }; + VERUS_TYPEDEFS(KeyMapper); + } +} diff --git a/Verus/src/Math/Bounds.cpp b/Verus/src/Math/Bounds.cpp new file mode 100644 index 0000000..66c7540 --- /dev/null +++ b/Verus/src/Math/Bounds.cpp @@ -0,0 +1,296 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Math; + +Bounds::Bounds() +{ + Reset(); +} + +Bounds::Bounds(RcPoint3 mn, RcPoint3 mx) : + _min(mn), + _max(mx) +{ + VERUS_RT_ASSERT(glm::all(glm::lessThanEqual(_min.GLM(), _max.GLM()))); +} + +Bounds::~Bounds() +{ +} + +bool Bounds::IsNull() const +{ + const Point3 mn(+FLT_MAX, +FLT_MAX, +FLT_MAX); + const Point3 mx(-FLT_MAX, -FLT_MAX, -FLT_MAX); + return + !memcmp(&mn, &_min, sizeof(float) * 3) && + !memcmp(&mx, &_max, sizeof(float) * 3); +} + +RBounds Bounds::Set(RcPoint3 mn, RcPoint3 mx) +{ + _min = mn; + _max = mx; + VERUS_RT_ASSERT(glm::all(glm::lessThanEqual(_min.GLM(), _max.GLM()))); + return *this; +} + +RBounds Bounds::Set(float mn, float mx, int axis) +{ + VERUS_RT_ASSERT(mn <= mx); + _min.setElem(axis, mn); + _max.setElem(axis, mx); + return *this; +} + +RBounds Bounds::Set(RcBounds that, int axis) +{ + _min.setElem(axis, that._min.getElem(axis)); + _max.setElem(axis, that._max.getElem(axis)); + return *this; +} + +RBounds Bounds::Reset() +{ + _min = Point3(+FLT_MAX, +FLT_MAX, +FLT_MAX); + _max = Point3(-FLT_MAX, -FLT_MAX, -FLT_MAX); + return *this; +} + +RBounds Bounds::Include(RcPoint3 point) +{ + _min = VMath::minPerElem(_min, point); + _max = VMath::maxPerElem(_max, point); + return *this; +} + +RBounds Bounds::CombineWith(RcBounds that) +{ + _min = VMath::minPerElem(_min, that._min); + _max = VMath::maxPerElem(_max, that._max); + return *this; +} + +RBounds Bounds::MoveBy(RcVector3 offset) +{ + _min += offset; + _max += offset; + return *this; +} + +Bounds Bounds::MakeFromCenterExtents(RcPoint3 center, RcVector3 extents) +{ + return Bounds(center - extents, center + extents); +} + +Bounds Bounds::MakeFromOrientedBox(RcBounds that, RcTransform3 tr) +{ + Point3 points[8]; + that.GetCorners(points); + VERUS_FOR(i, 8) + points[i] = tr * points[i]; + Bounds bounds(points[7], points[7]); + VERUS_FOR(i, 7) + bounds.Include(points[i]); + return bounds; +} + +RBounds Bounds::ScaleBy(float scale) +{ + *this = MakeFromCenterExtents(GetCenter(), GetExtents()*scale); + return *this; +} + +RBounds Bounds::FattenBy(float d) +{ + _min -= Vector3(d, d, d); + _max += Vector3(d, d, d); + return *this; +} + +bool Bounds::IsInside(RcPoint3 point) const +{ + if ((point.getX() < _min.getX()) || (point.getX() >= _max.getX())) return false; + if ((point.getY() < _min.getY()) || (point.getY() >= _max.getY())) return false; + if ((point.getZ() < _min.getZ()) || (point.getZ() >= _max.getZ())) return false; + return true; +} + +bool Bounds::IsInside2D(RcPoint3 point) const +{ + if ((point.getX() < _min.getX()) || (point.getX() >= _max.getX())) return false; + if ((point.getY() < _min.getY()) || (point.getY() >= _max.getY())) return false; + return true; +} + +bool Bounds::IsOverlappingWith(RcBounds that) const +{ + if ((that._min.getX() >= _max.getX()) || (_min.getX() >= that._max.getX())) return false; + if ((that._min.getY() >= _max.getY()) || (_min.getY() >= that._max.getY())) return false; + if ((that._min.getZ() >= _max.getZ()) || (_min.getZ() >= that._max.getZ())) return false; + return true; +} + +bool Bounds::IsOverlappingWith2D(RcBounds that, int axis) const +{ + static const int i[3] = { 1, 0, 0 }; + static const int j[3] = { 2, 2, 1 }; + const int ii = i[axis]; + const int jj = j[axis]; + if ((that._min.getElem(ii) >= _max.getElem(ii)) || (_min.getElem(ii) >= that._max.getElem(ii))) return false; + if ((that._min.getElem(jj) >= _max.getElem(jj)) || (_min.getElem(jj) >= that._max.getElem(jj))) return false; + return true; +} + +void Bounds::GetCorners(PPoint3 points) const +{ + VERUS_FOR(i, 8) + { + points[i] = Point3( + ((i >> 0) & 0x1) ? _max.getX() : _min.getX(), + ((i >> 1) & 0x1) ? _max.getY() : _min.getY(), + ((i >> 2) & 0x1) ? _max.getZ() : _min.getZ()); + } +} + +void Bounds::GetCorners(PMatrix4 m) const +{ + float minmax[8]; + memcpy(minmax + 0, _min.ToPointer(), 12); + memcpy(minmax + 4, _max.ToPointer(), 12); + float data[8][4]; + VERUS_FOR(i, 4) + { + const UINT32 i0 = i; + const UINT32 i1 = i + 4; + data[0][i] = minmax[0 + (((i0 >> 0) & 0x1) << 2)]; + data[1][i] = minmax[1 + (((i0 >> 1) & 0x1) << 2)]; + data[2][i] = minmax[2 + (((i0 >> 2) & 0x1) << 2)]; + data[3][i] = 1; + data[4][i] = minmax[0 + (((i1 >> 0) & 0x1) << 2)]; + data[5][i] = minmax[1 + (((i1 >> 1) & 0x1) << 2)]; + data[6][i] = minmax[2 + (((i1 >> 2) & 0x1) << 2)]; + data[7][i] = 1; + } + memcpy(m, data, 128); +} + +Sphere Bounds::GetSphere() const +{ + return Sphere(GetCenter(), VMath::length(GetExtents())); +} + +Matrix4 Bounds::GetMatrix() const +{ + const Matrix4 matT = Matrix4::translation( + Vector3(GetCenter() - VMath::mulPerElem(GetExtents(), Vector3(0, 1, 0)))); + return VMath::appendScale(matT, GetDimensions()); +} + +Transform3 Bounds::GetDrawTransform() const +{ + return Math::BoundsDrawMatrix(_min, _max); +} + +RBounds Bounds::MirrorY() +{ + const float miny = _min.getY(); + const float maxy = _max.getY(); + _min.setY(-maxy); + _max.setY(-miny); + return *this; +} + +RBounds Bounds::Wrap() +{ + _min = glm::fract(_min.GLM()); + _max = glm::fract(_max.GLM()); + return *this; +} + +void Bounds::GetQuadrant2D(int id, RBounds that) const +{ + const Point3 center = GetCenter(); + switch (id) + { + case 0: // 000: + that._min = Point3(_min.getX(), _min.getY()); + that._max = Point3(center.getX(), center.getY()); + break; + case 1: // 001: + that._min = Point3(center.getX(), _min.getY()); + that._max = Point3(_max.getX(), center.getY()); + break; + case 2: // 010: + that._min = Point3(_min.getX(), center.getY()); + that._max = Point3(center.getX(), _max.getY()); + break; + case 3: // 011: + that._min = Point3(center.getX(), center.getY()); + that._max = Point3(_max.getX(), _max.getY()); + break; + } +} + +void Bounds::GetQuadrant3D(int id, RBounds that) const +{ + const Point3 center = GetCenter(); + GetQuadrant2D(id & 0x3, that); + switch (id & 0x4) + { + case 0: // 0xx: + that._min.setZ(_min.getZ()); + that._max.setZ(center.getZ()); + break; + case 4: // 1xx: + that._min.setZ(center.getZ()); + that._max.setZ(_max.getZ()); + break; + } +} + +float Bounds::GetDiagonal() const +{ + return VMath::length(GetDimensions()); +} + +float Bounds::GetSmartSize() const +{ + const float num = 1 / 3.f; + return VMath::dot(GetDimensions(), Vector3(num, num, num)); +} + +float Bounds::GetMaxSide() const +{ + const Vector3 d = GetDimensions(); + const float* p = d.ToPointer(); + return *std::max_element(p, p + 3); +} + +Vector3 Bounds::GetExtentsFromOrigin(RcVector3 scale) const +{ + const Vector3 mn = VMath::mulPerElem(Vector3(_min), scale); + const Vector3 mx = VMath::mulPerElem(Vector3(_max), scale); + return VMath::maxPerElem( + VMath::absPerElem(mn), + VMath::absPerElem(mx)); +} + +float Bounds::GetMaxExtentFromOrigin(RcVector3 scale) const +{ + const Vector3 d = GetExtentsFromOrigin(scale); + const float* p = d.ToPointer(); + return *std::max_element(p, p + 3); +} + +RBounds Bounds::ToUnitBounds() +{ + const float mn = Math::Min(_min.getX(), _min.getZ()); + const float mx = Math::Max(_max.getX(), _max.getZ()); + _min.setX(mn); + _min.setZ(mn); + _max.setX(mx); + _max.setZ(mx); + return *this; +} diff --git a/Verus/src/Math/Bounds.h b/Verus/src/Math/Bounds.h new file mode 100644 index 0000000..135c714 --- /dev/null +++ b/Verus/src/Math/Bounds.h @@ -0,0 +1,67 @@ +#pragma once + +namespace verus +{ + namespace Math + { + class Bounds + { + Point3 _min; + Point3 _max; + + public: + Bounds(); + Bounds(RcPoint3 mn, RcPoint3 mx); + ~Bounds(); + + RcPoint3 GetMin() const { return _min; } + RcPoint3 GetMax() const { return _max; } + bool IsNull() const; + + Point3 GetCenter() const { return VMath::lerp(0.5f, _min, _max); } + Vector3 GetDimensions() const { return (_max - _min); } + Vector3 GetExtents() const { return (_max - _min)*0.5f; } + + Bounds& Set(RcPoint3 mn, RcPoint3 mx); + Bounds& Set(float mn, float mx, int axis); + Bounds& Set(const Bounds& that, int axis); + + Bounds& Reset(); + Bounds& Include(RcPoint3 point); + Bounds& CombineWith(const Bounds& that); + Bounds& MoveBy(RcVector3 offset); + + static Bounds MakeFromCenterExtents(RcPoint3 center, RcVector3 extents); + static Bounds MakeFromOrientedBox(const Bounds& that, RcTransform3 tr); + + Bounds& ScaleBy(float scale); + Bounds& FattenBy(float d); + + bool IsInside(RcPoint3 point) const; + bool IsInside2D(RcPoint3 point) const; + bool IsOverlappingWith(const Bounds& that) const; + bool IsOverlappingWith2D(const Bounds& that, int axis) const; + + void GetCorners(PPoint3 points) const; + void GetCorners(PMatrix4 m) const; + Sphere GetSphere() const; + Matrix4 GetMatrix() const; + Transform3 GetDrawTransform() const; + + Bounds& MirrorY(); + Bounds& Wrap(); + + void GetQuadrant2D(int id, Bounds& that) const; + void GetQuadrant3D(int id, Bounds& that) const; + + float GetDiagonal() const; + float GetSmartSize() const; + float GetMaxSide() const; + Vector3 GetExtentsFromOrigin(RcVector3 scale = Vector3::Replicate(1)) const; + float GetMaxExtentFromOrigin(RcVector3 scale = Vector3::Replicate(1)) const; + + Bounds& ToUnitBounds(); + }; + VERUS_TYPEDEFS(Bounds); + } +} diff --git a/Verus/src/Math/Frustum.cpp b/Verus/src/Math/Frustum.cpp new file mode 100644 index 0000000..a6849c5 --- /dev/null +++ b/Verus/src/Math/Frustum.cpp @@ -0,0 +1,175 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Math; + +Frustum::Frustum() +{ + VERUS_ZERO_MEM(_planes); + VERUS_ZERO_MEM(_corners); +} + +Frustum::~Frustum() +{ +} + +Frustum Frustum::MakeFromMatrix(RcMatrix4 m) +{ + Frustum f; + f._matI = VMath::inverse(m); + float z = 0; + //if (CGL::RENDERER_OPENGL == CGL::CRender::I()->GetRenderer()) + // z = -1; + const Vector4 corners[10] = + { + Vector4(+1, +1, z, 1), + Vector4(-1, +1, z, 1), + Vector4(+1, -1, z, 1), + Vector4(-1, -1, z, 1), + Vector4(+1, +1, 1, 1), + Vector4(-1, +1, 1, 1), + Vector4(+1, -1, 1, 1), + Vector4(-1, -1, 1, 1), + Vector4(+0, +0, z, 1), + Vector4(+0, +0, 1, 1) + }; + VERUS_FOR(i, 10) + { + Vector4 inv = f._matI*corners[i]; + inv /= inv.getW(); + f._corners[i] = inv.getXYZ(); + }; + const Matrix4 mvp = VMath::transpose(m); + f._planes[+Name::left] /**/ = mvp.getCol3() + mvp.getCol0(); + f._planes[+Name::right] /**/ = mvp.getCol3() - mvp.getCol0(); + f._planes[+Name::bottom] /**/ = mvp.getCol3() + mvp.getCol1(); + f._planes[+Name::top] /**/ = mvp.getCol3() - mvp.getCol1(); + f._planes[+Name::nearest] /**/ = mvp.getCol3() + mvp.getCol2(); + f._planes[+Name::furthest] /**/ = mvp.getCol3() - mvp.getCol2(); + VERUS_FOR(i, 6) + f._planes[i].Normalize(); + return f; +} + +RFrustum Frustum::FromMatrix(RcMatrix4 m) +{ + *this = MakeFromMatrix(m); + return *this; +} + +Relation Frustum::ContainsSphere(RcSphere sphere) const +{ + const Point3 c = sphere.GetCenter(); + const float r = sphere.GetRadius(); + VERUS_FOR(i, 6) + { + const float dist = _planes[i].DistanceTo(c); + if (dist <= -r) + return Relation::outside; + if (abs(dist) < r) + return Relation::intersect; + } + return Relation::inside; +} + +Relation Frustum::ContainsAabb(RcBounds aabb) const +{ + int totalIn = 0, inCount, allPointsIn; + Vector4 res0, res1; + Matrix4 m[2]; + aabb.GetCorners(m); + VERUS_FOR(plane, 6) + { + inCount = 8; + + res0 = m[0] * _planes[plane]; + res1 = m[1] * _planes[plane]; + + if (res0.getX() < 0) inCount--; + if (res0.getY() < 0) inCount--; + if (res0.getZ() < 0) inCount--; + if (res0.getW() < 0) inCount--; + if (res1.getX() < 0) inCount--; + if (res1.getY() < 0) inCount--; + if (res1.getZ() < 0) inCount--; + if (res1.getW() < 0) inCount--; + + allPointsIn = inCount >> 3; + if (!inCount) + return Relation::outside; // For some plane all points are outside. + totalIn += allPointsIn; + } + if (6 == totalIn) + return Relation::inside; // All points are inside. + return Relation::intersect; +} + +void Frustum::Draw() +{ +#if 0 + VERUS_QREF_DR; + VERUS_QREF_RENDER; + + dr.Begin(CGL::CDebugRender::T_LINES); + + render.GetStateBlockOpaqueNoZ()->Apply(); + + const UINT32 color = VERUS_COLOR_RGBA(0, 0, 0, 255); + + dr.AddLine(_corners[0], _corners[1], color); + dr.AddLine(_corners[1], _corners[3], color); + dr.AddLine(_corners[3], _corners[2], color); + dr.AddLine(_corners[2], _corners[0], color); + + dr.AddLine(_corners[4], _corners[5], color); + dr.AddLine(_corners[5], _corners[7], color); + dr.AddLine(_corners[7], _corners[6], color); + dr.AddLine(_corners[6], _corners[4], color); + + dr.AddLine(_corners[0], _corners[4], color); + dr.AddLine(_corners[1], _corners[5], color); + dr.AddLine(_corners[2], _corners[6], color); + dr.AddLine(_corners[3], _corners[7], color); + + dr.AddLine(_corners[8], _corners[9], color); + + dr.End(); +#endif +} + +Vector4 Frustum::GetBounds(RcMatrix4 m, float& zNear, float& zFar) const +{ + Vector4 ret( + +FLT_MAX, + +FLT_MAX, + -FLT_MAX, + -FLT_MAX); + zNear = -FLT_MAX; + zFar = FLT_MAX; + VERUS_FOR(i, 8) + { + const Vector4 matSpace = m * _corners[i]; + ret = Vector4( + Math::Min(ret.getX(), matSpace.getX()), + Math::Min(ret.getY(), matSpace.getY()), + Math::Max(ret.getZ(), matSpace.getX()), + Math::Max(ret.getW(), matSpace.getY())); + + // In RH-system closer objects have larger z values! + zNear = Math::Max(zNear, matSpace.getZ()); + zFar = Math::Min(zFar, matSpace.getZ()); + } + return ret; +} + +RFrustum Frustum::SetNearPlane(RcPoint3 eye, RcVector3 front, float zNear) +{ + _planes[+Name::nearest] = Plane(front, eye + front * zNear); + return *this; +} + +RFrustum Frustum::SetFarPlane(RcPoint3 eye, RcVector3 front, float zFar) +{ + _planes[+Name::furthest] = Plane(-front, eye + front * zFar); + return *this; +} diff --git a/Verus/src/Math/Frustum.h b/Verus/src/Math/Frustum.h new file mode 100644 index 0000000..199db50 --- /dev/null +++ b/Verus/src/Math/Frustum.h @@ -0,0 +1,43 @@ +#pragma once + +namespace verus +{ + namespace Math + { + class Frustum + { + enum class Name : int + { + left, + right, + bottom, + top, + nearest, + furthest, + count + }; + + Matrix4 _matI = Matrix4::identity(); + Plane _planes[static_cast(Name::count)]; + Point3 _corners[10]; + + public: + Frustum(); + ~Frustum(); + + static Frustum MakeFromMatrix(RcMatrix4 m); + Frustum& FromMatrix(RcMatrix4 m); + Relation ContainsSphere(RcSphere sphere) const; + Relation ContainsAabb(RcBounds aabb) const; + void Draw(); + + RcPoint3 GetEyePosition() const { return _corners[8]; } + + Vector4 GetBounds(RcMatrix4 m, float& zNear, float& zFar) const; + + Frustum& SetNearPlane(RcPoint3 eye, RcVector3 front, float zNear); + Frustum& SetFarPlane(RcPoint3 eye, RcVector3 front, float zFar); + }; + VERUS_TYPEDEFS(Frustum); + } +} diff --git a/Verus/src/Math/Math.cpp b/Verus/src/Math/Math.cpp new file mode 100644 index 0000000..b8c103a --- /dev/null +++ b/Verus/src/Math/Math.cpp @@ -0,0 +1,85 @@ +#include "verus.h" + +using namespace verus; + +bool Math::IsPowerOfTwo(int x) +{ + if (x <= 0) + return false; + return !(x&(x - 1)); +} + +int Math::HighestBit(int x) +{ + int bit = -1; + for (int tmp = x; tmp; tmp >>= 1, ++bit) {} + return bit; +} + +bool Math::IsNaN(float x) +{ + // 'isnan' : is not a member of 'std' :( + volatile float y = x; + return y != y; +} + +float Math::ToRadians(float deg) +{ + return deg * (VERUS_PI*(1 / 180.f)); +} + +float Math::ToDegrees(float rad) +{ + return rad * (180 / VERUS_PI); +} + +float Math::WrapAngle(float rad) +{ + return rad - floor(rad*(1 / VERUS_2PI) + 0.5f)*VERUS_2PI; +} + +float Math::Lerp(float a, float b, float t) +{ + return a + t * (b - a); +} + +float Math::SmoothStep(float a, float b, float t) +{ + const float x = Clamp((t - a) / (b - a), 0.f, 1.f); + return x * x*(3 - (x + x)); +} + +float Math::LinearToSin(float t) +{ + return sin(t*VERUS_PI); +} + +float Math::LinearToCos(float t) +{ + return cos(t*VERUS_PI)*-0.5f + 0.5f; // [1 to -1] -> [0 to 1]. +} + +Transform3 Math::BoundsDrawMatrix(RcPoint3 mn, RcPoint3 mx) +{ + const Vector3 d = mx - mn; + const Point3 c = VMath::lerp(0.5f, mn, mx); + return VMath::appendScale(Transform3::translation(c - Point3(0, d.getY()*0.5f, 0)), d); +} + +Vector3 Math::TriangleNormal(RcPoint3 a, RcPoint3 b, RcPoint3 c) +{ + return VMath::normalize(VMath::cross(a - b, a - c)); +} + +float Math::TriangleArea( + const glm::vec3& p0, + const glm::vec3& p1, + const glm::vec3& p2) +{ + // https://en.wikipedia.org/wiki/Heron%27s_formula + const float a = glm::length(p2 - p1); + const float b = glm::length(p0 - p2); + const float c = glm::length(p1 - p0); + const float s = (a + b + c)*0.5f; + return sqrt(s*(s - a)*(s - b)*(s - c)); +} diff --git a/Verus/src/Math/Math.h b/Verus/src/Math/Math.h new file mode 100644 index 0000000..6dab544 --- /dev/null +++ b/Verus/src/Math/Math.h @@ -0,0 +1,87 @@ +#pragma once + +#define VERUS_PI 3.141592654f +#define VERUS_2PI 6.283185307f +#define VERUS_E 2.718281828f +#define VERUS_GR 1.618034f + +#define VERUS_FLOAT_THRESHOLD 1e-4f + +namespace verus +{ + enum class Relation : int + { + outside, + inside, + intersect + }; + enum class Continue : int + { + no, + yes + }; + + typedef glm::mat4 matrix; + typedef glm::mat3x4 mataff; + + namespace VMath = Vectormath::SSE; +} + +#include "Vector.h" +#include "Matrix.h" +#include "Quat.h" +#include "Sphere.h" +#include "Bounds.h" +#include "Plane.h" +#include "Frustum.h" +#include "NormalComputer.h" + +namespace verus +{ + namespace Math + { + // Bits: + bool IsPowerOfTwo(int x); + int HighestBit(int x); + bool IsNaN(float x); + + // Angles: + float ToRadians(float deg); + float ToDegrees(float rad); + float WrapAngle(float rad); + + // Interpolation, splines: + float Lerp(float a, float b, float t); + float SmoothStep(float a, float b, float t); + float LinearToSin(float t); + float LinearToCos(float t); + + // Shapes: + Transform3 BoundsDrawMatrix(RcPoint3 mn, RcPoint3 mx); + Vector3 TriangleNormal(RcPoint3 a, RcPoint3 b, RcPoint3 c); + float TriangleArea(const glm::vec3& p0, const glm::vec3& p1, const glm::vec3& p2); + + // Clamp: + template + T Clamp(T x, T mn, T mx) + { + if (x <= mn) + return mn; + else if (x >= mx) + return mx; + return x; + } + + // Minimum & maximum: + template + T Min(T a, T b) + { + return a < b ? a : b; + } + template + T Max(T a, T b) + { + return a > b ? a : b; + } + }; +} diff --git a/Verus/src/Math/Matrix.cpp b/Verus/src/Math/Matrix.cpp new file mode 100644 index 0000000..3ed7704 --- /dev/null +++ b/Verus/src/Math/Matrix.cpp @@ -0,0 +1,320 @@ +#include "verus.h" + +using namespace verus; + +// Matrix3: + +Matrix3& Matrix3::operator=(const VMath::Matrix3& that) +{ +#ifdef _DEBUG + const float* p = reinterpret_cast(&that); + VERUS_FOR(i, 12) + { + VERUS_RT_ASSERT(!Math::IsNaN(p[i])); + } +#endif + VMath::Matrix3::operator=(that); + return *this; +} + +bool Matrix3::IsOrthogonal(float e) const +{ + const Vector3 x = getCol0(); + const Vector3 y = getCol1(); + const Vector3 z = getCol2(); + const float lx = VMath::length(x); + const float ly = VMath::length(y); + const float lz = VMath::length(z); + const float dotXY = VMath::dot(x, y); + const float dotYZ = VMath::dot(y, z); + const float dotZX = VMath::dot(z, x); + return + glm::epsilonEqual(lx, 1, e) && + glm::epsilonEqual(ly, 1, e) && + glm::epsilonEqual(lz, 1, e) && + glm::epsilonEqual(dotXY, 0, e) && + glm::epsilonEqual(dotYZ, 0, e) && + glm::epsilonEqual(dotZX, 0, e); +} + +Matrix3 Matrix3::MakeAimZ(RcVector3 zAxis, PcVector3 pUp) +{ + if (zAxis.IsZero()) + return Matrix3::identity(); + const Vector3 zNorm = VMath::normalizeApprox(zAxis); + if (zNorm.getY() > 0.999f) + return Matrix3::rotationX(-VERUS_PI * 0.5f); + if (zNorm.getY() < -0.999f) + return Matrix3::rotationX(VERUS_PI*0.5f); + return VMath::transpose( + Matrix4::lookAt(Point3(0), Point3(-zAxis), pUp ? *pUp : Vector3(0, 1, 0)).getUpper3x3()); +} + +Matrix3 Matrix3::AimZ(RcVector3 zAxis, PcVector3 pUp) +{ + *this = MakeAimZ(zAxis, pUp); + return *this; +} + +Matrix3 Matrix3::MakeRotateTo(RcVector3 v0, RcVector3 v1) +{ + const float e = 1e-6f; + Quat q; + const float d = VMath::dot(v0, v1); + if (d >= 1 - e) + { + return Matrix3::identity(); + } + if (d < e - 1) + { + Vector3 axis = VMath::cross(Vector3(1, 0, 0), v0); + if (VMath::lengthSqr(axis) < e*e) + axis = VMath::cross(Vector3(0, 1, 0), v0); + axis = VMath::normalize(axis); + q = Quat::rotation(VERUS_PI, axis); + } + else + { + const float s = sqrt((1 + d) * 2); + const float invs = 1 / s; + const Vector3 c = VMath::cross(v0, v1); + q = Quat(c*invs, s*0.5f); + q = VMath::normalize(q); + } + return Matrix3(q); +} + +Matrix3 Matrix3::RotateTo(RcVector3 v0, RcVector3 v1) +{ + *this = MakeRotateTo(v0, v1); + return *this; +} + +Matrix3 Matrix3::Lerp(RcMatrix3 a, RcMatrix3 b, float t) +{ + Matrix3 m; + m.setCol0(VMath::lerp(t, a.getCol0(), b.getCol0())); + m.setCol1(VMath::lerp(t, a.getCol1(), b.getCol1())); + m.setCol2(VMath::lerp(t, a.getCol2(), b.getCol2())); + return m; +} + +// Matrix4: + +RMatrix4 Matrix4::operator=(const VMath::Matrix4& that) +{ +#ifdef _DEBUG + const float* p = reinterpret_cast(&that); + VERUS_FOR(i, 16) + { + VERUS_RT_ASSERT(!Math::IsNaN(p[i])); + } +#endif + VMath::Matrix4::operator=(that); + return *this; +} + +Matrix4::Matrix4(const glm::mat4& that) +{ + memcpy(this, &that, sizeof(*this)); +} + +glm::mat4 Matrix4::GLM() const +{ + glm::mat4 m; + memcpy(&m, this, sizeof(m)); + return m; +} + +matrix Matrix4::ConstBufferFormat() const +{ + Matrix4 m2(*this); + m2 = VMath::transpose(m2); + glm::mat4 m; + memcpy(&m, &m2, sizeof(m)); + return matrix(m); +} + +matrix Matrix4::ConstBufferFormatIdentity() +{ + Matrix4 m2(Matrix4::identity()); + glm::mat4 m; + memcpy(&m, &m2, sizeof(m)); + return matrix(m); +} + +void Matrix4::InstFormat(VMath::Vector4* p) const +{ + const Matrix4 m = VMath::transpose(*this); + memcpy(p, &m, 3 * sizeof(Vector4)); +} + +Matrix4 Matrix4::MakePerspective(float fovY, float aspectRatio, float zNear, float zFar) +{ + VERUS_RT_ASSERT(fovY); + VERUS_RT_ASSERT(aspectRatio); + VERUS_RT_ASSERT(zNear < zFar); + //if (CGL::RENDERER_OPENGL == CGL::CRender::I()->GetRenderer()) + { + return Matrix4::perspective(fovY, aspectRatio, zNear, zFar); + } + //else + { + Matrix4 m; + const float yScale = 1 / tan(fovY*0.5f); + const float xScale = yScale / aspectRatio; + const float zScale = zFar / (zNear - zFar); + memset(&m, 0, sizeof(m)); + m.setElem(0, 0, xScale); + m.setElem(1, 1, yScale); + m.setElem(2, 2, zScale); + m.setElem(3, 2, zNear*zScale); + m.setElem(2, 3, -1); + return m; + } +} + +Matrix4 Matrix4::MakeOrtho(float w, float h, float zNear, float zFar) +{ + VERUS_RT_ASSERT(w); + VERUS_RT_ASSERT(h); + VERUS_RT_ASSERT(zNear < zFar); + //if (CGL::RENDERER_OPENGL == CGL::CRender::I()->GetRenderer()) + { + return Matrix4::orthographic(w*-0.5f, w*0.5f, h*-0.5f, h*0.5f, zNear, zFar); + } + //else + { + Matrix4 m; + const float zScale = 1 / (zNear - zFar); + memset(&m, 0, sizeof(m)); + m.setElem(0, 0, 2 / w); + m.setElem(1, 1, 2 / h); + m.setElem(2, 2, zScale); + m.setElem(3, 3, 1); + m.setElem(3, 2, zNear*zScale); + return m; + } +} + +// Transform3: + +RTransform3 Transform3::operator=(const VMath::Transform3& that) +{ +#ifdef _DEBUG + const float* p = reinterpret_cast(&that); + VERUS_FOR(i, 16) + { + VERUS_RT_ASSERT(!Math::IsNaN(p[i])); + } +#endif + VMath::Transform3::operator=(that); + return *this; +} + +Transform3::Transform3(const btTransform& t) +{ + t.getOpenGLMatrix(reinterpret_cast(this)); +} + +btTransform Transform3::Bullet() const +{ + btTransform t; + t.setFromOpenGLMatrix(ToPointer()); + return t; +} + +Transform3::Transform3(const glm::mat4& that) +{ + memcpy(this, &that, sizeof(*this)); +} + +glm::mat4 Transform3::GLM() const +{ + glm::mat4 m; + const Matrix4 m2(*this); + memcpy(&m, &m2, sizeof(m)); + return m; +} + +mataff Transform3::ConstBufferFormat() const +{ + Matrix4 m2(*this); + m2 = VMath::transpose(m2); + glm::mat4 m; + memcpy(&m, &m2, sizeof(m)); + return mataff(m); +} + +mataff Transform3::ConstBufferFormatIdentity() +{ + Matrix4 m2(Matrix4::identity()); + glm::mat4 m; + memcpy(&m, &m2, sizeof(m)); + return mataff(m); +} + +void Transform3::InstFormat(VMath::Vector4* p) const +{ + Matrix4 m(*this); + m = VMath::transpose(m); + memcpy(p, &m, 3 * sizeof(Vector4)); +} + +bool Transform3::IsIdentity() +{ + const Matrix4 x(*this); + const Matrix4 i = Matrix4::identity(); + return !memcmp(&x, &i, sizeof(x)); +} + +Transform3 Transform3::Normalize(bool scalePos) +{ + const float xl = VMath::length(getCol0()); + const float yl = VMath::length(getCol1()); + const float zl = VMath::length(getCol2()); + setCol0(getCol0() / xl); + setCol1(getCol1() / yl); + setCol2(getCol2() / zl); + if (scalePos) + setTranslation(VMath::divPerElem(getTranslation(), Vector3(xl, yl, zl))); + return *this; +} + +Vector3 Transform3::GetScale() const +{ + return Vector3( + VMath::length(getCol0()), + VMath::length(getCol1()), + VMath::length(getCol2())); +} + +String Transform3::ToString() const +{ + StringStream ss; + VERUS_FOR(i, 4) + { + VERUS_FOR(j, 3) + { + ss << getElem(i, j); + if (!(i == 3 && j == 2)) + ss << " "; + } + } + return ss.str(); +} + +RTransform3 Transform3::FromString(CSZ sz) +{ + float* p = const_cast(ToPointer()); + sscanf(sz, + "%f %f %f " + "%f %f %f " + "%f %f %f " + "%f %f %f", + p + 0, p + 1, p + 2, + p + 4, p + 5, p + 6, + p + 8, p + 9, p + 10, + p + 12, p + 13, p + 14); + return *this; +} diff --git a/Verus/src/Math/Matrix.h b/Verus/src/Math/Matrix.h new file mode 100644 index 0000000..158b08d --- /dev/null +++ b/Verus/src/Math/Matrix.h @@ -0,0 +1,93 @@ +#pragma once + +namespace verus +{ + class Matrix3 : public VMath::Matrix3 + { + public: + Matrix3() {} + Matrix3( + const VMath::Vector3& col0, + const VMath::Vector3& col1, + const VMath::Vector3& col2) : VMath::Matrix3(col0, col1, col2) {} + Matrix3(const VMath::Quat& q) : VMath::Matrix3(q) {} + Matrix3(const VMath::Matrix3& that) : VMath::Matrix3(that) {} + Matrix3& operator=(const VMath::Matrix3& that); + + const float* ToPointer() const { return reinterpret_cast(this); } + + bool IsOrthogonal(float e = VERUS_FLOAT_THRESHOLD) const; + + static Matrix3 MakeAimZ(RcVector3 zAxis, PcVector3 pUp = nullptr); + Matrix3 AimZ(RcVector3 zAxis, PcVector3 pUp = nullptr); + static Matrix3 MakeRotateTo(RcVector3 v0, RcVector3 v1); + Matrix3 RotateTo(RcVector3 v0, RcVector3 v1); + + static Matrix3 Lerp(const Matrix3& a, const Matrix3& b, float t); + }; + VERUS_TYPEDEFS(Matrix3); + + class Matrix4 : public VMath::Matrix4 + { + public: + Matrix4() {} + Matrix4( + const VMath::Vector4& col0, + const VMath::Vector4& col1, + const VMath::Vector4& col2, + const VMath::Vector4& col3) : VMath::Matrix4(col0, col1, col2, col3) {} + Matrix4(const VMath::Transform3& that) : VMath::Matrix4(that) {} + Matrix4(const VMath::Matrix4& that) : VMath::Matrix4(that) {} + Matrix4& operator=(const VMath::Matrix4& that); + + Matrix4(const glm::mat4& that); + glm::mat4 GLM() const; + + matrix ConstBufferFormat() const; + static matrix ConstBufferFormatIdentity(); + void InstFormat(VMath::Vector4* p) const; + + const float* ToPointer() const { return reinterpret_cast(this); } + + static Matrix4 MakePerspective(float fovY, float aspectRatio, float zNear, float zFar); + static Matrix4 MakeOrtho(float w, float h, float zNear, float zFar); + }; + VERUS_TYPEDEFS(Matrix4); + + class Transform3 : public VMath::Transform3 + { + public: + Transform3() {} + Transform3( + const VMath::Vector3& col0, + const VMath::Vector3& col1, + const VMath::Vector3& col2, + const VMath::Vector3& col3) : VMath::Transform3(col0, col1, col2, col3) {} + Transform3(const VMath::Matrix3& that, const VMath::Vector3& v) : VMath::Transform3(that, v) {} + Transform3(const VMath::Quat& q, const VMath::Vector3& v) : VMath::Transform3(q, v) {} + Transform3(const VMath::Transform3& that) : VMath::Transform3(that) {} + Transform3(const VMath::Matrix4& that) : VMath::Transform3(that.getUpper3x3(), that.getTranslation()) {} + Transform3& operator=(const VMath::Transform3& that); + + Transform3(const btTransform& t); + btTransform Bullet() const; + + Transform3(const glm::mat4& that); + glm::mat4 GLM() const; + + mataff ConstBufferFormat() const; + static mataff ConstBufferFormatIdentity(); + void InstFormat(VMath::Vector4* p) const; + + const float* ToPointer() const { return reinterpret_cast(this); } + + bool IsIdentity(); + + Transform3 Normalize(bool scalePos = false); + Vector3 GetScale() const; + + String ToString() const; + Transform3& FromString(CSZ sz); + }; + VERUS_TYPEDEFS(Transform3); +} diff --git a/Verus/src/Math/NormalComputer.cpp b/Verus/src/Math/NormalComputer.cpp new file mode 100644 index 0000000..8bbb372 --- /dev/null +++ b/Verus/src/Math/NormalComputer.cpp @@ -0,0 +1,150 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Math; + +void NormalComputer::ComputeNormals( + const Vector& vIndices, + const Vector& vPositions, + Vector& vNormals, + float areaBasedNormals) +{ + const int numVerts = Utils::Cast32(vPositions.size()); + const int numFaces = Utils::Cast32(vIndices.size() / 3); + vNormals.resize(numVerts); + + Vector vFaceNormals; + vFaceNormals.resize(numFaces); + + Vector vFaceAreas; + vFaceAreas.resize(numFaces); + + VERUS_P_FOR(i, numFaces) + { + const int a = vIndices[i * 3 + 0]; + const int b = vIndices[i * 3 + 1]; + const int c = vIndices[i * 3 + 2]; + + float area = TriangleArea(vPositions[a], vPositions[b], vPositions[c]); + area = glm::mix(1.f, area, areaBasedNormals); + vFaceAreas[i] = area; + + const glm::vec3 v0(vPositions[a]); + const glm::vec3 v1(vPositions[b]); + const glm::vec3 v2(vPositions[c]); + const glm::vec3 n = glm::triangleNormal(v0, v1, v2); + vFaceNormals[i] = glm::normalize(n)*vFaceAreas[i]; + }); + + VERUS_P_FOR(i, numVerts) + { + const int numFaces = Utils::Cast32(vIndices.size() / 3); + const float threshold = 0.001f*0.001f; // 1 mm. + VERUS_FOR(j, numFaces) + { + const int a = vIndices[j * 3 + 0]; + const int b = vIndices[j * 3 + 1]; + const int c = vIndices[j * 3 + 2]; + + if (a == i || + b == i || + c == i) + { + vNormals[i] += vFaceNormals[j]; + } + else + { + const glm::vec3 prevNormal = vNormals[i]; + const float d0 = glm::distance2(vPositions[i], vPositions[a]); + const float d1 = glm::distance2(vPositions[i], vPositions[b]); + const float d2 = glm::distance2(vPositions[i], vPositions[c]); + + if (d0 < threshold || d1 < threshold || d2 < threshold) + { + vNormals[i] += vFaceNormals[j]; + if (glm::length2(vNormals[i]) < 0.01f*0.01f) // Coplanar surface fix (1% of unit length): + vNormals[i] = prevNormal; + } + } + } + vNormals[i] = glm::normalize(vNormals[i]); + }); +} + +void NormalComputer::ComputeTangentSpace( + const Vector& vIndices, + const Vector& vPositions, + const Vector& vNormals, + const Vector& vTexCoords, + Vector& vTan, + Vector& vBin) +{ + const int numVerts = Utils::Cast32(vPositions.size()); + const int numFaces = Utils::Cast32(vIndices.size() / 3); + vTan.resize(numVerts); + vBin.resize(numVerts); + std::mutex m; + const float degree = cos(glm::radians(1.f)); + + VERUS_P_FOR(i, numFaces) + { + const int a = vIndices[i * 3 + 0]; + const int b = vIndices[i * 3 + 1]; + const int c = vIndices[i * 3 + 2]; + + const glm::vec3 p1 = vPositions[a]; + const glm::vec3 p2 = vPositions[b]; + const glm::vec3 p3 = vPositions[c]; + const glm::vec2 t1 = vTexCoords[a]; + const glm::vec2 t2 = vTexCoords[b]; + const glm::vec2 t3 = vTexCoords[c]; + + const glm::vec3 e1 = p2 - p1; + const glm::vec3 e2 = p3 - p1; + const glm::vec2 et1 = t2 - t1; + const glm::vec2 et2 = t3 - t1; + + float tmp = 0; + const float det = et1.x*et2.y - et1.y*et2.x; + if (abs(det) < 1e-6f) + tmp = 1; + else + tmp = 1 / det; + + const glm::vec3 tan = glm::normalize((e1*et2.y - e2 * et1.y)*tmp); + const glm::vec3 bin = glm::normalize((e2*et1.x - e1 * et2.x)*tmp); + + { + std::lock_guard lock(m); + vTan[a] += tan; + vTan[b] += tan; + vTan[c] += tan; + vBin[a] += bin; + vBin[b] += bin; + vBin[c] += bin; + } + }, 0); + + VERUS_P_FOR(i, numVerts) + { + vTan[i] = glm::normalize(vTan[i]); + vBin[i] = glm::normalize(vBin[i]); + + const glm::vec3 tmpT = vTan[i]; + const glm::vec3 tmpB = vBin[i]; + const glm::vec3 tmpN = vNormals[i]; + const glm::vec3 newT = glm::normalize(tmpT - (glm::dot(tmpN, tmpT)*tmpN)); + const glm::vec3 newB = glm::normalize(tmpB - (glm::dot(tmpN, tmpB)*tmpN) - (glm::dot(newT, tmpB)*newT)); + + vTan[i] = newT; + vBin[i] = newB; + + if (IsNaN(vTan[i].x) || IsNaN(vTan[i].y) || IsNaN(vTan[i].z)) + vTan[i] = glm::vec3(1, 0, 0); + if (IsNaN(vBin[i].x) || IsNaN(vBin[i].y) || IsNaN(vBin[i].z)) + vBin[i] = glm::vec3(0, 0, 1); + + if (glm::dot(vBin[i], vTan[i]) >= degree) + vBin[i] = glm::cross(tmpN, vTan[i]); + }, 0); +} diff --git a/Verus/src/Math/NormalComputer.h b/Verus/src/Math/NormalComputer.h new file mode 100644 index 0000000..22eb904 --- /dev/null +++ b/Verus/src/Math/NormalComputer.h @@ -0,0 +1,26 @@ +#pragma once + +namespace verus +{ + namespace Math + { + class NormalComputer + { + public: + static void ComputeNormals( + const Vector& vIndices, + const Vector& vPositions, + Vector& vNormals, + float areaBasedNormals); + + static void ComputeTangentSpace( + const Vector& vIndices, + const Vector& vPositions, + const Vector& vNormals, + const Vector& vTexCoords, + Vector& vTan, + Vector& vBin); + }; + VERUS_TYPEDEFS(NormalComputer); + } +} diff --git a/Verus/src/Math/Plane.cpp b/Verus/src/Math/Plane.cpp new file mode 100644 index 0000000..fc4608f --- /dev/null +++ b/Verus/src/Math/Plane.cpp @@ -0,0 +1,61 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Math; + +Plane::Plane() +{ +} + +Plane::Plane(RcVector4 that) : + Vector4(that) +{ +} + +Plane::Plane(RcVector3 normal, float d) : + Vector4(normal, d) +{ +} + +Plane::Plane(RcVector3 normal, RcPoint3 point) : + Vector4(normal, -VMath::dot(normal, Vector3(point))) +{ +} + +Plane::Plane(RcPoint3 pointA, RcPoint3 pointB, RcPoint3 pointC) +{ + const Vector3 normal = Math::TriangleNormal(pointA, pointB, pointC); + *this = Plane(normal, pointA); +} + +Plane::~Plane() +{ +} + +float Plane::DistanceTo(RcPoint3 point) const +{ + return VMath::dot(*this, Vector4(Vector3(point), 1)); +} + +Plane& Plane::Normalize() +{ + *this = *this / VMath::length(getXYZ()); + return *this; +} + +bool Plane::IntersectSegment(RcPoint3 pointA, RcPoint3 pointB, RPoint3 point, float* t) +{ + float distA = DistanceTo(pointA); + float distB = DistanceTo(pointB); + if ((distA >= 0 && distB >= 0) || (distA < 0 && distB < 0)) + return false; + distA = abs(distA); + distB = abs(distB); + const float tt = distA / (distA + distB); + const Vector3 edge = pointB - pointA; + const Vector3 offset = edge * tt; + point = pointA + offset; + if (t) + *t = tt; + return true; +} diff --git a/Verus/src/Math/Plane.h b/Verus/src/Math/Plane.h new file mode 100644 index 0000000..13fe13a --- /dev/null +++ b/Verus/src/Math/Plane.h @@ -0,0 +1,26 @@ +#pragma once + +namespace verus +{ + namespace Math + { + class Plane : public Vector4 + { + public: + Plane(); + Plane(RcVector4 that); + Plane(RcVector3 normal, float d); + Plane(RcVector3 normal, RcPoint3 point); + Plane(RcPoint3 pointA, RcPoint3 pointB, RcPoint3 pointC); + Plane& operator=(RcVector4 that) { Vector4::operator=(that); return *this; } + ~Plane(); + + float DistanceTo(RcPoint3 point) const; + + Plane& Normalize(); + + bool IntersectSegment(RcPoint3 pointA, RcPoint3 pointB, RPoint3 point, float* t = nullptr); + }; + VERUS_TYPEDEFS(Plane); + } +} diff --git a/Verus/src/Math/Quat.cpp b/Verus/src/Math/Quat.cpp new file mode 100644 index 0000000..bb58109 --- /dev/null +++ b/Verus/src/Math/Quat.cpp @@ -0,0 +1,28 @@ +#include "verus.h" + +using namespace verus; + +bool Quat::IsEqual(RcQuat that, float e) const +{ + return + glm::epsilonEqual(static_cast(getX()), static_cast(that.getX()), e) && + glm::epsilonEqual(static_cast(getY()), static_cast(that.getY()), e) && + glm::epsilonEqual(static_cast(getZ()), static_cast(that.getZ()), e) && + glm::epsilonEqual(static_cast(getW()), static_cast(that.getW()), e); +} + +bool Quat::IsIdentity() const +{ + const float epsilon = 0.001f; + const bool unitPos = + abs(getW() - 1) < epsilon && + abs(getX() - 0) < epsilon && + abs(getY() - 0) < epsilon && + abs(getZ() - 0) < epsilon; + const bool unitNeg = + abs(getW() + 1) < epsilon && + abs(getX() + 0) < epsilon && + abs(getY() + 0) < epsilon && + abs(getZ() + 0) < epsilon; + return unitPos || unitNeg; +} diff --git a/Verus/src/Math/Quat.h b/Verus/src/Math/Quat.h new file mode 100644 index 0000000..541412f --- /dev/null +++ b/Verus/src/Math/Quat.h @@ -0,0 +1,32 @@ +#pragma once + +namespace verus +{ + class Quat : public VMath::Quat + { + public: + Quat() {} + Quat(float x, float y = 0, float z = 0, float w = 1) : VMath::Quat(x, y, z, w) {} + Quat( + const VMath::FloatInVec& x, + const VMath::FloatInVec& y = VMath::FloatInVec(0), + const VMath::FloatInVec& z = VMath::FloatInVec(0), + const VMath::FloatInVec& w = VMath::FloatInVec(1)) : VMath::Quat(x, y, z, w) {} + Quat(const VMath::Vector3& v, float w) : VMath::Quat(v, w) {} + Quat(const VMath::Vector3& v, const VMath::FloatInVec& w) : VMath::Quat(v, w) {} + Quat(const VMath::Vector4& v) : VMath::Quat(v) {} + Quat(const VMath::Matrix3& m) : VMath::Quat(m) {} + Quat(const VMath::Quat& that) : VMath::Quat(that) {} + Quat& operator=(const VMath::Quat& that) { VMath::Quat::operator=(that); return *this; } + + Quat(const glm::quat& that) : VMath::Quat(that.x, that.y, that.z, that.w) {} + glm::quat GLM() const { return glm::quat(getW(), getX(), getY(), getZ()); } + + const float* ToPointer() const { return reinterpret_cast(this); } + + bool IsEqual(const Quat& that, float e) const; + + bool IsIdentity() const; + }; + VERUS_TYPEDEFS(Quat); +} diff --git a/Verus/src/Math/Sphere.cpp b/Verus/src/Math/Sphere.cpp new file mode 100644 index 0000000..132276f --- /dev/null +++ b/Verus/src/Math/Sphere.cpp @@ -0,0 +1,29 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Math; + +bool Sphere::IsInside(RcPoint3 point) const +{ + return VMath::distSqr(GetCenter(), point) < GetRadiusSq(); +} + +bool Sphere::IsOverlappingWith(RcSphere that) const +{ + const float border = GetRadius() + that.GetRadius(); + return VMath::distSqr(GetCenter(), that.GetCenter()) < border*border; +} + +void Sphere::EvenlyDistPoints(int num, Vector& vPoints) +{ + vPoints.reserve(num); + const float inc = VERUS_PI * (3 - sqrt(5.f)); + const float off = 2.f / num; + VERUS_FOR(i, num) + { + const float y = i * off - 1 + (off*0.5f); + const float r = sqrt(1 - y * y); + const float phi = i * inc; + vPoints.push_back(Point3(cos(phi)*r, y, sin(phi)*r)); + } +} diff --git a/Verus/src/Math/Sphere.h b/Verus/src/Math/Sphere.h new file mode 100644 index 0000000..20b21ae --- /dev/null +++ b/Verus/src/Math/Sphere.h @@ -0,0 +1,29 @@ +#pragma once + +namespace verus +{ + namespace Math + { + class Sphere + { + Vector4 _center_radius; + + public: + Sphere() {} + Sphere(RcPoint3 center, float r) : _center_radius(Vector3(center), r) {} + + Point3 GetCenter() const { return _center_radius.getXYZ(); } + float GetRadius() const { return _center_radius.getW(); } + float GetRadiusSq() const { return _center_radius.getW()*_center_radius.getW(); } + + Sphere& SetCenter(RcPoint3 center) { _center_radius.setXYZ(Vector3(center)); return *this; } + Sphere& SetRadius(float r) { _center_radius.setW(r); return *this; } + + bool IsInside(RcPoint3 point) const; + bool IsOverlappingWith(const Sphere& that) const; + + static void EvenlyDistPoints(int num, Vector& vPoints); + }; + VERUS_TYPEDEFS(Sphere); + } +} diff --git a/Verus/src/Math/Vector.cpp b/Verus/src/Math/Vector.cpp new file mode 100644 index 0000000..0b718c1 --- /dev/null +++ b/Verus/src/Math/Vector.cpp @@ -0,0 +1,366 @@ +#include "verus.h" + +using namespace verus; + +// Vector3: + +Vector3 Vector3::MakeFromPointer(const float* p) +{ + return Vector3(p[0], p[1], p[2]); +} + +bool Vector3::IsZero() const +{ + const Vector3 zero(0); + return !memcmp(this, &zero, sizeof(float) * 3); +} + +bool Vector3::IsEqual(RcVector3 that, float e) const +{ + return + glm::epsilonEqual(static_cast(getX()), static_cast(that.getX()), e) && + glm::epsilonEqual(static_cast(getY()), static_cast(that.getY()), e) && + glm::epsilonEqual(static_cast(getZ()), static_cast(that.getZ()), e); +} + +bool Vector3::IsLessThan(RcVector3 that, float e) const +{ + if (!glm::epsilonEqual(static_cast(getX()), static_cast(that.getX()), e)) return getX() < that.getX(); + if (!glm::epsilonEqual(static_cast(getY()), static_cast(that.getY()), e)) return getY() < that.getY(); + if (!glm::epsilonEqual(static_cast(getZ()), static_cast(that.getZ()), e)) return getZ() < that.getZ(); + return false; +} + +Vector3 Vector3::Clamp(float mn, float mx) const +{ + return Vector3( + Math::Clamp(static_cast(getX()), mn, mx), + Math::Clamp(static_cast(getY()), mn, mx), + Math::Clamp(static_cast(getZ()), mn, mx)); +} + +Vector3 Vector3::Clamp(RcVector3 mn, RcVector3 mx) const +{ + return Vector3( + Math::Clamp(static_cast(getX()), static_cast(mn.getX()), static_cast(mx.getX())), + Math::Clamp(static_cast(getY()), static_cast(mn.getY()), static_cast(mx.getY())), + Math::Clamp(static_cast(getZ()), static_cast(mn.getZ()), static_cast(mx.getZ()))); +} + +Vector3 Vector3::Floor() const +{ + return Vector3( + floor(getX()), + floor(getY()), + floor(getZ())); +} + +Vector3 Vector3::Pow(float p) const +{ + return Vector3( + pow(getX(), p), + pow(getY(), p), + pow(getZ(), p)); +} + +Vector3 Vector3::Mod(float x) const +{ + return Vector3( + fmod(static_cast(getX()), x), + fmod(static_cast(getY()), x), + fmod(static_cast(getZ()), x)); +} + +Vector3 Vector3::Mod(RcVector3 that) const +{ + return Vector3( + fmod(static_cast(getX()), static_cast(that.getX())), + fmod(static_cast(getY()), static_cast(that.getY())), + fmod(static_cast(getZ()), static_cast(that.getZ()))); +} + +String Vector3::ToString(bool compact) const +{ + char txt[96]; + const float x = getX(); + const float y = getY(); + const float z = getZ(); + sprintf_s(txt, compact ? "%g %g %g" : "%f %f %f", x, y, z); + return txt; +} + +String Vector3::ToString2(bool compact) const +{ + char txt[64]; + const float x = getX(); + const float y = getY(); + sprintf_s(txt, compact ? "%g %g" : "%f %f", x, y); + return txt; +} + +RVector3 Vector3::FromString(CSZ sz) +{ + float x = 0, y = 0, z = 0; + if (sz) + sscanf(sz, "%f %f %f", &x, &y, &z); + *this = Vector3(x, y, z); + return *this; +} + +void Vector3::EulerFromQuaternion(RcQuat q) +{ + *this = glm::eulerAngles(q.GLM()); +} + +void Vector3::EulerToQuaternion(RQuat q) const +{ + const glm::mat4 mat = glm::eulerAngleYXZ( + static_cast(getY()), + static_cast(getX()), + static_cast(getZ())); + q = glm::quat_cast(mat); +} + +void Vector3::LimitDot(RcVector3 v, float d) +{ + const float dWas = VMath::dot(*this, v); + if (dWas >= d) + return; + const float delta = d - dWas; + const Point3 p0 = *this + v * delta; + const Point3 p1 = v * d; + const float len = VMath::dist(p0, p1); + if (len < VERUS_FLOAT_THRESHOLD) + return; + const float lenTarget = sqrt(1 - d * d); + const float ratio = lenTarget / len; + const Vector3 perp = p0 - p1; + *this = p1 + perp * ratio; +} + +Vector3 Vector3::Reflect(RcVector3 normal, float bounce) const +{ + return (*this) - normal * ((1 + bounce)*VMath::dot(*this, normal)); +} + +// Vector4: + +Vector4 Vector4::MakeFromPointer(const float* p) +{ + return Vector4(p[0], p[1], p[2], p[3]); +} + +bool Vector4::IsZero() const +{ + const Vector4 zero(0); + return !memcmp(this, &zero, sizeof(float) * 4); +} + +Vector4 Vector4::Clamp(float mn, float mx) const +{ + return Vector4( + Math::Clamp(static_cast(getX()), mn, mx), + Math::Clamp(static_cast(getY()), mn, mx), + Math::Clamp(static_cast(getZ()), mn, mx), + Math::Clamp(static_cast(getW()), mn, mx)); +} + +Vector4 Vector4::Clamp(RcVector4 mn, RcVector4 mx) const +{ + return Vector4( + Math::Clamp(static_cast(getX()), static_cast(mn.getX()), static_cast(mx.getX())), + Math::Clamp(static_cast(getY()), static_cast(mn.getY()), static_cast(mx.getY())), + Math::Clamp(static_cast(getZ()), static_cast(mn.getZ()), static_cast(mx.getZ())), + Math::Clamp(static_cast(getW()), static_cast(mn.getW()), static_cast(mx.getW()))); +} + +Vector4 Vector4::Mod(float x) const +{ + return Vector4( + fmod(static_cast(getX()), x), + fmod(static_cast(getY()), x), + fmod(static_cast(getZ()), x), + fmod(static_cast(getW()), x)); +} + +Vector4 Vector4::Mod(const Vector4& that) const +{ + return Vector4( + fmod(static_cast(getX()), static_cast(that.getX())), + fmod(static_cast(getY()), static_cast(that.getY())), + fmod(static_cast(getZ()), static_cast(that.getZ())), + fmod(static_cast(getW()), static_cast(that.getW()))); +} + +String Vector4::ToString(bool compact) const +{ + char txt[128]; + const float x = getX(); + const float y = getY(); + const float z = getZ(); + const float w = getW(); + sprintf_s(txt, compact ? "%g %g %g %g" : "%f %f %f %f", x, y, z, w); + return txt; +} + +RVector4 Vector4::FromString(CSZ sz) +{ + float x = 0, y = 0, z = 0, w = 0; + if (sz) + sscanf(sz, "%f %f %f %f", &x, &y, &z, &w); + *this = Vector4(x, y, z, w); + return *this; +} + +Vector4 Vector4::MakeFromColor(UINT32 color, bool sRGB) +{ + Vector4 c; + c.FromColor(color, sRGB); + return c; +} + +RVector4 Vector4::FromColor(UINT32 color, bool sRGB) +{ + float rgba[4]; + Convert::ColorInt32ToFloat(color, rgba, sRGB); + *this = MakeFromPointer(rgba); + return *this; +} + +UINT32 Vector4::ToColor(bool sRGB) const +{ + return Convert::ColorFloatToInt32(ToPointer(), sRGB); +} + +RVector4 Vector4::FromColorString(CSZ sz, bool sRGB) +{ + float rgba[4]; + Convert::ColorTextToFloat4(sz, rgba, sRGB); + *this = MakeFromPointer(rgba); + return *this; +} + +String Vector4::ToColorString(bool sRGB) +{ + const UINT32 color = Convert::ColorFloatToInt32(ToPointer(), sRGB); + char txt[16]; + sprintf_s(txt, "%02X%02X%02X%02X", + (color >> 0) & 0xFF, + (color >> 8) & 0xFF, + (color >> 16) & 0xFF, + (color >> 24) & 0xFF); + return txt; +} + +Vector4 Vector4::MakeRectangle(float l, float t, float r, float b) +{ + return Vector4(l, t, r - l, b - t); +} + +float Vector4::Left() const +{ + return getX(); +} + +float Vector4::Top() const +{ + return getY(); +} + +float Vector4::Width() const +{ + return getZ(); +} + +float Vector4::Height() const +{ + return getW(); +} + +float Vector4::Right() const +{ + return getX() + getZ(); +} + +float Vector4::Bottom() const +{ + return getY() + getW(); +} + +bool Vector4::IsInsideRect(float x, float y) const +{ + return + x >= getX() && + y >= getY() && + x < Right() && + y < Bottom(); +} + +Vector4 Vector4::ToGuiCoord() const +{ + Vector4 res = *this; + res /= res.getW(); + return VMath::mulPerElem(res, Vector4(0.5f, -0.5f, 1, 1)) + Vector4(0.5f, 0.5f); +} + +// Point3: + +Point3 Point3::MakeFromPointer(const float* p) +{ + return Point3(p[0], p[1], p[2]); +} + +void Point3::ToArray3(float* p) const +{ + memcpy(p, this, sizeof(float) * 3); +} + +bool Point3::IsZero() const +{ + const Point3 zero(0); + return !memcmp(this, &zero, sizeof(float) * 3); +} + +bool Point3::IsEqual(RcPoint3 that, float e) const +{ + return + glm::epsilonEqual(static_cast(getX()), static_cast(that.getX()), e) && + glm::epsilonEqual(static_cast(getY()), static_cast(that.getY()), e) && + glm::epsilonEqual(static_cast(getZ()), static_cast(that.getZ()), e); +} + +bool Point3::IsLessThan(RcPoint3 that, float e) const +{ + if (!glm::epsilonEqual(static_cast(getX()), static_cast(that.getX()), e)) return getX() < that.getX(); + if (!glm::epsilonEqual(static_cast(getY()), static_cast(that.getY()), e)) return getY() < that.getY(); + if (!glm::epsilonEqual(static_cast(getZ()), static_cast(that.getZ()), e)) return getZ() < that.getZ(); + return false; +} + +String Point3::ToString(bool compact) const +{ + char txt[96]; + const float x = getX(); + const float y = getY(); + const float z = getZ(); + sprintf_s(txt, compact ? "%g %g %g" : "%f %f %f", x, y, z); + return txt; +} + +String Point3::ToString2(bool compact) const +{ + char txt[64]; + const float x = getX(); + const float y = getY(); + sprintf_s(txt, compact ? "%g %g" : "%f %f", x, y); + return txt; +} + +RPoint3 Point3::FromString(CSZ sz) +{ + float x = 0, y = 0, z = 0; + if (sz) + sscanf(sz, "%f %f %f", &x, &y, &z); + *this = Point3(x, y, z); + return *this; +} diff --git a/Verus/src/Math/Vector.h b/Verus/src/Math/Vector.h new file mode 100644 index 0000000..24043a5 --- /dev/null +++ b/Verus/src/Math/Vector.h @@ -0,0 +1,183 @@ +#pragma once + +namespace verus +{ + class Quat; + + class Vector3 : public VMath::Vector3 + { + public: + Vector3() {} + Vector3(float x, float y = 0, float z = 0) : VMath::Vector3(x, y, z) {} + Vector3( + const VMath::FloatInVec& x, + const VMath::FloatInVec& y = VMath::FloatInVec(0), + const VMath::FloatInVec& z = VMath::FloatInVec(0)) : VMath::Vector3(x, y, z) {} + Vector3(const VMath::Point3& that) : VMath::Vector3(that) {} + Vector3(const VMath::Vector3& that) : VMath::Vector3(that) {} + Vector3& operator=(const VMath::Vector3& that) { VMath::Vector3::operator=(that); return *this; } + + // Bullet: + Vector3(const btVector3& that) : VMath::Vector3(that.x(), that.y(), that.z()) {} + btVector3 Bullet() const { return btVector3(getX(), getY(), getZ()); } + + // GLM (vec3 & vec2): + static Vector3 MakeXZ(const glm::vec2& that) { return VMath::Vector3(that.x, 0, that.y); } + Vector3(const glm::vec2& that) : VMath::Vector3(that.x, that.y, 0) {} + Vector3(const glm::vec3& that) : VMath::Vector3(that.x, that.y, that.z) {} + glm::vec3 GLM() const { return glm::vec3(getX(), getY(), getZ()); } + glm::vec2 GLM2() const { return glm::vec2(getX(), getY()); } + + static Vector3 Replicate(float x) { return VMath::Vector3(x); } + + // Pointer: + static Vector3 MakeFromPointer(const float* p); + const float* ToPointer() const { return reinterpret_cast(this); } + + // Compare: + bool IsZero() const; + bool IsEqual(const Vector3& that, float e) const; + bool IsLessThan(const Vector3& that, float e) const; + + // Math: + Vector3 Clamp(float mn, float mx) const; + Vector3 Clamp(const Vector3& mn, const Vector3& mx) const; + Vector3 Floor() const; + Vector3 Pow(float p) const; + Vector3 Mod(float x) const; + Vector3 Mod(const Vector3& that) const; + + // String: + String ToString(bool compact = false) const; + String ToString2(bool compact = false) const; + Vector3& FromString(CSZ sz); + + void EulerFromQuaternion(const Quat& q); + void EulerToQuaternion(Quat& q) const; + + void LimitDot(const Vector3& v, float d); + Vector3 Reflect(const Vector3& normal, float bounce = 1) const; + }; + VERUS_TYPEDEFS(Vector3); + + class Vector4 : public VMath::Vector4 + { + public: + Vector4() {} + Vector4(float x, float y = 0, float z = 0, float w = 0) : VMath::Vector4(x, y, z, w) {} + Vector4( + const VMath::FloatInVec& x, + const VMath::FloatInVec& y = VMath::FloatInVec(0), + const VMath::FloatInVec& z = VMath::FloatInVec(0), + const VMath::FloatInVec& w = VMath::FloatInVec(0)) : VMath::Vector4(x, y, z, w) {} + Vector4(const VMath::Vector3& that, float w) : VMath::Vector4(that, w) {} + Vector4(const VMath::Vector3& that, const VMath::FloatInVec& w) : VMath::Vector4(that, w) {} + Vector4(const VMath::Vector4& that) : VMath::Vector4(that) {} + Vector4& operator=(const VMath::Vector4& that) { VMath::Vector4::operator=(that); return *this; } + + // Bullet: + Vector4(const btVector3& that) : VMath::Vector4(that.x(), that.y(), that.z(), that.w()) {} + btVector3 Bullet() const { return btVector3(getX(), getY(), getZ()); } + + // GLM (vec4): + static Vector4 MakeXZ(const glm::vec2& that) { return VMath::Vector4(that.x, 0, that.y, 0); } + Vector4(const glm::vec2& that) : VMath::Vector4(that.x, that.y, 0, 0) {} + Vector4(const glm::vec3& that) : VMath::Vector4(that.x, that.y, that.z, 0) {} + Vector4(const glm::vec4& that) : VMath::Vector4(that.x, that.y, that.z, that.w) {} + glm::vec4 GLM() const { return glm::vec4(getX(), getY(), getZ(), getW()); } + + static Vector4 Replicate(float x) { return VMath::Vector4(x); } + + // Pointer: + static Vector4 MakeFromPointer(const float* p); + const float* ToPointer() const { return reinterpret_cast(this); } + + // Compare: + bool IsZero() const; + + // Math: + Vector4 Clamp(float mn, float mx) const; + Vector4 Clamp(const Vector4& mn, const Vector4& mx) const; + Vector4 Mod(float x) const; + Vector4 Mod(const Vector4& that) const; + + // String: + String ToString(bool compact = false) const; + Vector4& FromString(CSZ sz); + + // Color: + static Vector4 MakeFromColor(UINT32 color, bool sRGB = true); + Vector4& FromColor(UINT32 color, bool sRGB = true); + UINT32 ToColor(bool sRGB = true) const; + String ToColorString(bool sRGB = true); + Vector4& FromColorString(CSZ sz, bool sRGB = true); + + // Rectangle: + static Vector4 MakeRectangle(float l, float t, float r, float b); + float Left() const; + float Top() const; + float Width() const; + float Height() const; + float Right() const; + float Bottom() const; + bool IsInsideRect(float x, float y) const; + + Vector4 ToGuiCoord() const; + }; + VERUS_TYPEDEFS(Vector4); + + class Point3 : public VMath::Point3 + { + public: + Point3() {} + Point3(float x, float y = 0, float z = 0) : VMath::Point3(x, y, z) {} + Point3( + const VMath::FloatInVec& x, + const VMath::FloatInVec& y = VMath::FloatInVec(0), + const VMath::FloatInVec& z = VMath::FloatInVec(0)) : VMath::Point3(x, y, z) {} + Point3(const VMath::Vector3& that) : VMath::Point3(that) {} + Point3(const VMath::Point3& that) : VMath::Point3(that) {} + Point3& operator=(const VMath::Point3& that) { VMath::Point3::operator=(that); return *this; } + + // Bullet: + Point3(const btVector3& that) : VMath::Point3(that.x(), that.y(), that.z()) {} + btVector3 Bullet() const { return btVector3(getX(), getY(), getZ()); } + + // GLM (vec3 & vec2): + static Point3 MakeXZ(const glm::vec2& that) { return VMath::Point3(that.x, 0, that.y); } + Point3(const glm::vec2& that) : VMath::Point3(that.x, that.y, 0) {} + Point3(const glm::vec3& that) : VMath::Point3(that.x, that.y, that.z) {} + glm::vec3 GLM() const { return glm::vec3(getX(), getY(), getZ()); } + glm::vec2 GLM2() const { return glm::vec2(getX(), getY()); } + + static Point3 Replicate(float x) { return Point3(x); } + + // Pointer: + static Point3 MakeFromPointer(const float* p); + const float* ToPointer() const { return reinterpret_cast(this); } + void ToArray3(float* p) const; + + // Compare: + bool IsZero() const; + bool IsEqual(const Point3& that, float e) const; + bool IsLessThan(const Point3& that, float e) const; + + // String: + String ToString(bool compact = false) const; + String ToString2(bool compact = false) const; + Point3& FromString(CSZ sz); + }; + VERUS_TYPEDEFS(Point3); + + class float4 : public glm::vec4 + { + public: + float4() {} + float4(float s) : glm::vec4(s, 0, 0, 0) {} + float4(const glm::vec4& v) : glm::vec4(v) {} + float4(const glm::vec3& v) : glm::vec4(v, 0) {} + float4(RcVector3 v) : glm::vec4(v.GLM(), 0) {} + float4(RcVector4 v) : glm::vec4(v.GLM()) {} + float4(RcPoint3 v) : glm::vec4(v.GLM(), 0) {} + }; +} diff --git a/Verus/src/Net/Addr.cpp b/Verus/src/Net/Addr.cpp new file mode 100644 index 0000000..8a1217a --- /dev/null +++ b/Verus/src/Net/Addr.cpp @@ -0,0 +1,114 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Net; + +Addr::Addr(const sockaddr_in& sa) +{ + _addr = ntohl(sa.sin_addr.S_un.S_addr); + _port = ntohs(sa.sin_port); +} + +Addr::Addr(CSZ addr, int port) +{ + _port = port; + if (!addr) + _addr = 0; + else + FromString(addr); // This can update port. +} + +bool Addr::operator==(RcAddr that) const +{ + return + _addr == that._addr && + _port == that._port; +} + +Addr Addr::Localhost(int port) +{ + Addr addr; + addr._addr = 0x7F000001; + addr._port = port; + return addr; +} + +bool Addr::IsLocalhost() const +{ + return 0x7F000001 == _addr; +} + +void Addr::FromString(CSZ addr) +{ + char addrBuff[40]; + CSZ pColon = strchr(addr, ':'); + if (pColon) // "IP:Port" format? + { + const int len = Math::Min(Utils::Cast32(pColon - addr), sizeof(addrBuff) - 1); + strncpy(addrBuff, addr, len); + addrBuff[len] = 0; + _port = atoi(addr + len + 1); + addr = addrBuff; + } + + const int ret = inet_pton(AF_INET, addr, &_addr); + if (1 != ret) + { + struct RAII + { + addrinfo* pAddrInfo; + + RAII() : pAddrInfo(nullptr) {} + ~RAII() + { + if (pAddrInfo) + { + freeaddrinfo(pAddrInfo); + pAddrInfo = nullptr; + } + } + } raii; + + addrinfo hints = {}; + hints.ai_family = AF_INET; + const int ret = getaddrinfo(addr, 0, &hints, &raii.pAddrInfo); + if (ret) + { + throw VERUS_RECOVERABLE << "getaddrinfo(), " << ret; + } + else + { + const UINT16 port = _port; + const addrinfo* pAI = nullptr; + for (pAI = raii.pAddrInfo; pAI; pAI = pAI->ai_next) + { + if (PF_INET == pAI->ai_family) + { + *this = Addr(*reinterpret_cast(pAI->ai_addr)); + break; + } + } + _port = port; + } + } + else + _addr = ntohl(_addr); +} + +String Addr::ToString(bool addPort) const +{ + sockaddr_in sa; + sa.sin_addr.S_un.S_addr = htonl(_addr); + char s[64] = {}; + inet_ntop(AF_INET, &sa.sin_addr, s, sizeof(s)); + return String(s) + (addPort ? ":" + std::to_string(_port) : ""); +} + +sockaddr_in Addr::ToSockAddr() const +{ + sockaddr_in sa = {}; + sa.sin_family = AF_INET; + sa.sin_addr.S_un.S_addr = htonl(_addr); + sa.sin_port = htons(_port); + return sa; +} diff --git a/Verus/src/Net/Addr.h b/Verus/src/Net/Addr.h new file mode 100644 index 0000000..b06167e --- /dev/null +++ b/Verus/src/Net/Addr.h @@ -0,0 +1,38 @@ +#pragma once + +namespace verus +{ + namespace Net + { + //! Holds IP address and port. + class Addr + { + public: + union + { + UINT32 _addr = 0; + BYTE _a[4]; + }; + UINT16 _port = 0; + + Addr() = default; + Addr(const sockaddr_in& sa); + Addr(CSZ addr, int port); + + bool operator==(const Addr& that) const; + + //! Returns localhost address 127.0.0.1. + static Addr Localhost(int port = 0); + + bool IsNull() const { return !_port; } + bool IsLocalhost() const; + + //! Accepts IP:Port or URL, which is resolved using getaddrinfo(). + void FromString(CSZ addr); + String ToString(bool addPort = false) const; + + sockaddr_in ToSockAddr() const; + }; + VERUS_TYPEDEFS(Addr); + } +} diff --git a/Verus/src/Net/HttpFile.cpp b/Verus/src/Net/HttpFile.cpp new file mode 100644 index 0000000..3aaa788 --- /dev/null +++ b/Verus/src/Net/HttpFile.cpp @@ -0,0 +1,182 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Net; + +HttpFile::HttpFile() +{ + _ready = false; + VERUS_ZERO_MEM(_buffer); +} + +HttpFile::~HttpFile() +{ + Close(); +} + +bool HttpFile::Open(CSZ url) +{ + try + { + if (strncmp(url, "http://", schemeLength)) + return false; + int port = httpPort; + + // Key points: + CSZ colon = strchr(url + schemeLength, ':'); + CSZ slash = strchr(url + schemeLength, '/'); + CSZ q = strchr(url + schemeLength, '?'); + + if (colon) + port = atoi(colon + 1); + + // Parse host: + CSZ end = url + strlen(url); + if (q) + end = q; + if (slash) + end = slash; + if (colon) + end = colon; + String host(url + schemeLength, end); + + _socket.Connect(Addr(_C(host), port)); + + // Form request: + String req("GET "); + req.reserve(200); + req += slash ? slash : "/"; + req += " HTTP/1.0" VERUS_CRNL; + req += "Host: " + host + VERUS_CRNL VERUS_CRNL; + + // Send it: + _socket.Send(_C(req), Utils::Cast32(req.length())); + + String header; + header.reserve(400); + bool done = false; + while (!done) + { + const int size = _socket.Recv(_buffer, sizeof(_buffer)); + if (size > 0) + header.append(_buffer, _buffer + size); + const size_t endh = header.find(VERUS_CRNL VERUS_CRNL); + if (!size) + { + done = true; + } + if (endh != String::npos) + { + _contentPart = Utils::Cast32(header.length() - endh - 4); + memcpy(_buffer, _C(header) + endh + 4, _contentPart); + done = true; + } + } + + CSZ p = _C(header); + while (p) + { + const size_t span = strcspn(p, VERUS_CRNL); + if (!span) + break; + CSZ end = p + span; + CSZ colon = strchr(p, ':'); + if (colon > end) + colon = nullptr; + String key, value; + if (colon) + { + key.assign(p, colon); + value.assign(colon + 2, end); + } + else + { + key.assign("Title"); + value.assign(p, end); + } + +#ifdef _DEBUG + char info[128]; + sprintf_s(info, "%s: %s", _C(key), _C(value)); + VERUS_LOG_DEBUG(info); +#endif + + _mapHeader[key] = value; + p += span; + if (!strncmp(p, VERUS_CRNL VERUS_CRNL, 4)) + break; + p += strspn(p, VERUS_CRNL); + } + + _size = atoi(_C(_mapHeader["Content-Length"])); + } + catch (D::RcRecoverable) + { + return false; + } + return true; +} + +void HttpFile::Close() +{ + if (_thread.joinable()) + _thread.join(); + _socket.Close(); +} + +INT64 HttpFile::Read(void* p, INT64 size) +{ + const INT64 inBuffer = Math::Max(_contentPart - _offset, INT64(0)); + const INT64 fromBuffer = Math::Min(inBuffer, size); + const INT64 fromNet = size - fromBuffer; + const INT64 end = _offset + size; + if (fromBuffer) + { + memcpy(p, _buffer + _offset, static_cast(fromBuffer)); + _offset += fromBuffer; + } + const INT64 offsetStart = _offset; + while (fromNet && _offset != end) + { + const int ret = _socket.Recv( + static_cast(p) + fromBuffer + (_offset - offsetStart), + Utils::Cast32(end - _offset)); + if (ret <= 0) + break; + _offset += ret; + } + return fromBuffer + (_offset - offsetStart); +} + +INT64 HttpFile::Write(const void* p, INT64 size) +{ + return 0; +} + +INT64 HttpFile::GetSize() +{ + return _size; +} + +void HttpFile::DownloadAsync(CSZ url, bool nullTerm) +{ + _url = url; + _nullTerm = nullTerm; + _ready = false; + _thread = std::thread(&HttpFile::ThreadProc, this); +} + +void HttpFile::ThreadProc() +{ + SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW); + if (Open(_C(_url))) + { + const INT64 size = GetSize(); + if (size <= maxContentLength) + { + _vData.resize(size_t(_nullTerm ? size + 1 : size)); + Read(_vData.data(), size); + } + } + _ready = true; +} diff --git a/Verus/src/Net/HttpFile.h b/Verus/src/Net/HttpFile.h new file mode 100644 index 0000000..327b52c --- /dev/null +++ b/Verus/src/Net/HttpFile.h @@ -0,0 +1,45 @@ +#pragma once + +namespace verus +{ + namespace Net + { + //! Provides ability to read files from the internet using HTTP. + class HttpFile : public IO::Stream + { + typedef Map TMapHeader; + + enum { httpPort = 80, schemeLength = 7, maxContentLength = 1024 * 1024 }; + + String _url; + Vector _vData; + Socket _socket; + std::thread _thread; + TMapHeader _mapHeader; + BYTE _buffer[200]; + INT64 _offset = 0; + INT64 _size = 0; + int _contentPart = 0; + std::atomic_bool _ready; + bool _nullTerm = false; + + public: + HttpFile(); + ~HttpFile(); + + bool Open(CSZ url); + void Close(); + + virtual INT64 Read(void* p, INT64 size) override; + virtual INT64 Write(const void* p, INT64 size) override; + + INT64 GetSize(); + + bool IsReady() const { return _ready; } + void DownloadAsync(CSZ url, bool nullTerm = false); + VERUS_P(void ThreadProc()); + const BYTE* GetData() const { return _vData.data(); } + }; + VERUS_TYPEDEFS(HttpFile); + } +} diff --git a/Verus/src/Net/Net.cpp b/Verus/src/Net/Net.cpp new file mode 100644 index 0000000..f4814f4 --- /dev/null +++ b/Verus/src/Net/Net.cpp @@ -0,0 +1,13 @@ +#include "verus.h" + +namespace verus +{ + void Make_Net() + { + Net::Socket::Startup(); + } + void Free_Net() + { + Net::Socket::Cleanup(); + } +} diff --git a/Verus/src/Net/Net.h b/Verus/src/Net/Net.h new file mode 100644 index 0000000..007baa0 --- /dev/null +++ b/Verus/src/Net/Net.h @@ -0,0 +1,11 @@ +#pragma once + +#include "Addr.h" +#include "Socket.h" +#include "HttpFile.h" + +namespace verus +{ + void Make_Net(); + void Free_Net(); +} diff --git a/Verus/src/Net/Socket.cpp b/Verus/src/Net/Socket.cpp new file mode 100644 index 0000000..71ddd60 --- /dev/null +++ b/Verus/src/Net/Socket.cpp @@ -0,0 +1,283 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Net; + +//#define VERUS_SOCKET_LOG + +WSADATA Socket::s_wsaData; + +// Socket::Client: + +void Socket::Client::ThreadProc() +{ + const int size = Utils::Cast32(_vBuffer.size()); + int read = 0; + Vector vLocal; + vLocal.resize(size); + int ret = 1; + while (ret > 0) + { + ret = recv(_socket, reinterpret_cast(&vLocal[read]), size - read, 0); + if (ret > 0) + read += ret; + if (read = size) + { + VERUS_LOCK(*_pListener); + memcpy(_vBuffer.data(), vLocal.data(), size); + read = 0; + } + } + + closesocket(_socket); + + { + VERUS_LOCK(*_pListener); + _pListener = nullptr; + _socket = INVALID_SOCKET; + } +} + +// Socket: + +Socket::Socket() +{ +} + +Socket::~Socket() +{ + Close(); +} + +void Socket::Startup() +{ + VERUS_LOG_INFO("Startup()"); + WSAStartup(MAKEWORD(2, 2), &s_wsaData); +} + +void Socket::Cleanup() +{ + VERUS_LOG_INFO("Cleanup()"); + WSACleanup(); +} + +void Socket::Listen(int port) +{ + VERUS_RT_ASSERT(INVALID_SOCKET == _socket); + + struct RAII + { + addrinfo* pAddrInfo; + + RAII() : pAddrInfo(nullptr) {} + ~RAII() + { + if (pAddrInfo) + { + freeaddrinfo(pAddrInfo); + pAddrInfo = nullptr; + } + } + } raii; + + int ret = 0; + + addrinfo hints = {}; + hints.ai_flags = AI_PASSIVE; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + char porttext[16]; + sprintf_s(porttext, "%d", port); + if (ret = getaddrinfo(0, porttext, &hints, &raii.pAddrInfo)) + throw VERUS_RECOVERABLE << "getaddrinfo(), " << ret; + + _socket = socket( + raii.pAddrInfo->ai_family, + raii.pAddrInfo->ai_socktype, + raii.pAddrInfo->ai_protocol); + if (INVALID_SOCKET == _socket) + throw VERUS_RECOVERABLE << "socket(), " << WSAGetLastError(); + + if (bind(_socket, + raii.pAddrInfo->ai_addr, + Utils::Cast32(raii.pAddrInfo->ai_addrlen))) + { + closesocket(_socket); + throw VERUS_RECOVERABLE << "bind(), " << WSAGetLastError(); + } + + if (listen(_socket, SOMAXCONN)) + { + closesocket(_socket); + throw VERUS_RECOVERABLE << "listen(), " << WSAGetLastError(); + } + + _vClients.resize(_maxNumOfClients); + _thread = std::thread(&Socket::ThreadProc, this); +} + +void Socket::Connect(RcAddr addr) +{ + VERUS_RT_ASSERT(INVALID_SOCKET == _socket); + _socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (_socket != INVALID_SOCKET) + { + const sockaddr_in sa = addr.ToSockAddr(); + const int ret = connect(_socket, reinterpret_cast(&sa), sizeof(sa)); + if (SOCKET_ERROR != ret) + return; + else + { + closesocket(_socket); + throw VERUS_RECOVERABLE << "connect(), " << WSAGetLastError(); + } + } + else + throw VERUS_RECOVERABLE << "socket(), " << WSAGetLastError(); +} + +void Socket::Udp(int port) +{ + VERUS_RT_ASSERT(INVALID_SOCKET == _socket); + _socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (_socket != INVALID_SOCKET) + { + sockaddr_in sa = {}; + sa.sin_family = AF_INET; + sa.sin_addr.s_addr = htonl(INADDR_ANY); + sa.sin_port = htons(port); + if (bind(_socket, reinterpret_cast(&sa), sizeof(sa)) < 0) + { + closesocket(_socket); + throw VERUS_RECOVERABLE << "bind(), " << WSAGetLastError(); + } + // Non-blocking mode: + u_long mode = 1; + if (ioctlsocket(_socket, FIONBIO, &mode)) + { + closesocket(_socket); + throw VERUS_RECOVERABLE << "ioctlsocket(FIONBIO), " << WSAGetLastError(); + } + } + else + throw VERUS_RECOVERABLE << "socket(), " << WSAGetLastError(); +} + +void Socket::Close() +{ + if (_socket != INVALID_SOCKET) + { + closesocket(_socket); + _socket = INVALID_SOCKET; + } + + CloseAllClients(); + + if (_thread.joinable()) + _thread.join(); +} + +int Socket::Send(const void* p, int size) const +{ + if (!size) + size = Utils::Cast32(strlen(static_cast(p))); + return send(_socket, static_cast(p), size, 0); +} + +int Socket::SendTo(const void* p, int size, RcAddr addr) const +{ + sockaddr_in sa = addr.ToSockAddr(); + if (!size) + size = Utils::Cast32(strlen(static_cast(p))); +#ifdef VERUS_SOCKET_LOG + VERUS_LOG_SESSION(_C("sendto, " + addr.ToString(true) + ", " + std::to_string(size) + ", [0]=" + std::to_string(((BYTE*)p)[0]))); +#endif + return sendto(_socket, static_cast(p), size, 0, reinterpret_cast(&sa), sizeof(sa)); +} + +int Socket::Recv(void* p, int size) const +{ + return recv(_socket, static_cast(p), size, 0); +} + +int Socket::RecvFrom(void* p, int size, RAddr addr) const +{ + sockaddr_in sa = {}; + int saLen = sizeof(sa); + const int ret = recvfrom(_socket, static_cast(p), size, 0, reinterpret_cast(&sa), &saLen); + addr = Addr(sa); +#ifdef VERUS_SOCKET_LOG + if (ret > 0) + VERUS_LOG_SESSION(_C("recvfrom, " + addr.ToString(true) + ", " + std::to_string(ret) + ", [0]=" + std::to_string(((BYTE*)p)[0]))); +#endif + return ret; +} + +void Socket::ThreadProc() +{ + SOCKET s = 0; + while (s != INVALID_SOCKET) + { + s = accept(_socket, 0, 0); + if (s != INVALID_SOCKET) + { + VERUS_LOCK(*this); + const int slot = GetFreeClientSlot(); + if (slot >= 0) + { + _vClients[slot] = new Client; + PClient pClient = _vClients[slot]; + pClient->_pListener = this; + pClient->_socket = s; + pClient->_vBuffer.resize(_clientBufferSize); + pClient->_thread = std::thread(&Client::ThreadProc, pClient); + } + else + { + closesocket(s); + } + } + } +} + +void Socket::GetLatestClientBuffer(int id, BYTE* p) +{ + VERUS_LOCK(*this); + if ((int)_vClients.size() > id && _vClients[id] && _vClients[id]->_pListener) + memcpy(p, _vClients[id]->_vBuffer.data(), _clientBufferSize); +} + +int Socket::GetFreeClientSlot() const +{ + VERUS_FOR(i, _maxNumOfClients) + { + if (!_vClients[i]) + return i; + } + return -1; +} + +void Socket::CloseAllClients() +{ + VERUS_RT_ASSERT(INVALID_SOCKET == _socket); + + { + VERUS_LOCK(*this); + VERUS_FOREACH(Vector, _vClients, it) + if (*it) + closesocket((*it)->_socket); + } + VERUS_FOREACH(Vector, _vClients, it) + { + if (*it && (*it)->_thread.joinable()) + (*it)->_thread.join(); + } + VERUS_FOREACH(Vector, _vClients, it) + { + if (*it) + delete *it; + } + _vClients.clear(); +} diff --git a/Verus/src/Net/Socket.h b/Verus/src/Net/Socket.h new file mode 100644 index 0000000..d622207 --- /dev/null +++ b/Verus/src/Net/Socket.h @@ -0,0 +1,59 @@ +#pragma once + +namespace verus +{ + namespace Net + { + class Socket : public Lockable + { + class Client + { + friend class Socket; + + Socket* _pListener = nullptr; + SOCKET _socket = INVALID_SOCKET; + std::thread _thread; + Vector _vBuffer; + + void ThreadProc(); + }; + VERUS_TYPEDEFS(Client); + + static WSADATA s_wsaData; + SOCKET _socket = INVALID_SOCKET; + std::thread _thread; + Vector _vClients; + int _maxNumOfClients = 1; + int _clientBufferSize = 0; + + public: + Socket(); + ~Socket(); + + static void Startup(); + static void Cleanup(); + + void Listen(int port); + void Connect(RcAddr addr); + void Udp(int port); + void Close(); + + int Send(const void* p, int size) const; + int SendTo(const void* p, int size, RcAddr addr) const; + int Recv(void* p, int size) const; + int RecvFrom(void* p, int size, RAddr addr) const; + + void SetMaxNumberOfClients(int num) { _maxNumOfClients = num; } + void SetClientBufferSize(int size) { _clientBufferSize = size; } + + VERUS_P(void ThreadProc()); + + void GetLatestClientBuffer(int id, BYTE* p); + + VERUS_P(int GetFreeClientSlot() const); + + VERUS_P(void CloseAllClients()); + }; + VERUS_TYPEDEFS(Socket); + } +} diff --git a/Verus/src/Scene/BaseMesh.cpp b/Verus/src/Scene/BaseMesh.cpp new file mode 100644 index 0000000..58c75cf --- /dev/null +++ b/Verus/src/Scene/BaseMesh.cpp @@ -0,0 +1,765 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Scene; + +BaseMesh::BaseMesh() +{ + VERUS_ZERO_MEM(_posDeq); + VERUS_ZERO_MEM(_tc0Deq); + VERUS_ZERO_MEM(_tc1Deq); + VERUS_CT_ASSERT(16 == sizeof(VertexStream0)); + VERUS_CT_ASSERT(16 == sizeof(VertexStream1)); + VERUS_CT_ASSERT(16 == sizeof(VertexStream2)); + VERUS_CT_ASSERT(8 == sizeof(VertexStream3)); +} + +BaseMesh::~BaseMesh() +{ + Done(); +} + +void BaseMesh::Init(CSZ url) +{ + VERUS_INIT(); + _url = url; + IO::Async::I().Load(url, this); +} + +void BaseMesh::Done() +{ + IO::Async::Cancel(this); + VERUS_DONE(BaseMesh); +} + +void BaseMesh::Async_Run(CSZ url, RcBlob blob) +{ + if (_url == url) + { + Load(blob); + LoadPrimaryBones(); + LoadRig(); + LoadMimic(); + if (_loadKinectBindPose) + LoadKinectBindPose(); + return; + } + + if (Str::EndsWith(url, ".bullet")) + { + btBvhTriangleMeshShape* pShape = nullptr; + btBulletWorldImporter bwi(0); + if (bwi.loadFileFromMemory((char*)blob._p, Utils::Cast32(blob._size))) + { + const int numShapes = bwi.getNumCollisionShapes(); + if (numShapes) + pShape = static_cast(bwi.getCollisionShapeByIndex(0)); + } + return; + } +} + +void BaseMesh::Load(RcBlob blob) +{ + IO::StreamPtr sp(blob); + UINT32 magic = 0; + sp >> magic; + if (magic != ' D3X') + { + if (magic == 'D3X<') + { + return LoadX3D3(blob); + } + else + { + throw VERUS_RECOVERABLE << "Load(), Invalid magic number"; + } + } + VERUS_LOG_WARN("Load(), Old X3D version"); +} + +void BaseMesh::LoadX3D3(RcBlob blob) +{ + IO::StreamPtr sp(blob); + + bool hasNormals = false; + bool hasTBN = false; + UINT32 temp; + UINT32 blockType = 0; + INT64 blockSize = 0; + char buffer[IO::Stream::bufferSize] = {}; + sp.Read(buffer, 5); + sp >> temp; + sp >> temp; + sp.ReadString(buffer); + VERUS_RT_ASSERT(!strcmp(buffer, "3.0")); + + while (!sp.IsEnd()) + { + sp >> temp; + sp >> blockType; + sp >> blockSize; + switch (blockType) + { + case '>XI<': + { + sp.ReadString(buffer); + _numFaces = atoi(buffer); + VERUS_RT_ASSERT(_vIndices.empty()); + _vIndices.resize(_numFaces * 3); + UINT16* pIB = _vIndices.data(); + VERUS_FOR(i, _numFaces) + sp.Read(&pIB[i * 3], 6); + } + break; + case '>XV<': + { + sp.ReadString(buffer); + _numVerts = atoi(buffer); +#ifdef VERUS_DEBUG + Vector vPolyCheck; + vPolyCheck.reserve(_numFaces); + VERUS_FOR(i, _numFaces) + { + UINT16 indices[3]; + memcpy(indices, &_vIndices[i * 3], sizeof(indices)); + std::sort(indices, indices + 3); + const UINT64 tag = UINT64(indices[0]) | UINT64(indices[1]) << 16 | UINT64(indices[2]) << 32; + vPolyCheck.push_back(tag); + } + std::sort(vPolyCheck.begin(), vPolyCheck.end()); + if (std::adjacent_find(vPolyCheck.begin(), vPolyCheck.end()) != vPolyCheck.end()) + VERUS_LOG_SESSION(_C("WARNING: Duplicate triangle in " + _url)); + Vector vSorted; + vSorted.assign(_vIndices.begin(), _vIndices.end()); + std::sort(vSorted.begin(), vSorted.end()); + bool isolatedVertex = false; + std::adjacent_find(vSorted.begin(), vSorted.end(), [&isolatedVertex](const UINT16& a, const UINT16& b) + { + if (b - a > 1) + isolatedVertex = true; + return false; + }); + if (isolatedVertex) + VERUS_LOG_SESSION(_C("WARNING: Isolated vertex in " + _url)); + if (_numVerts <= vSorted.back()) + VERUS_LOG_SESSION(_C("WARNING: Index out of bounds in " + _url)); +#endif + sp.Read(&_posDeq[0], 12); + sp.Read(&_posDeq[3], 12); + VERUS_RT_ASSERT(_vStream0.empty()); + _vStream0.resize(_numVerts); + VertexStream0* pVB = _vStream0.data(); + short mn = 0, mx = 0; + VERUS_FOR(i, _numVerts) + { + sp.Read(pVB[i]._pos, 6); +#ifdef VERUS_DEBUG + const short* pMin = std::min_element(pVB[i]._pos, pVB[i]._pos + 3); + const short* pMax = std::max_element(pVB[i]._pos, pVB[i]._pos + 3); + mn = CMath::Min(mn, *pMin); + mx = CMath::Max(mx, *pMax); +#endif + } +#ifdef VERUS_DEBUG + VERUS_RT_ASSERT(mn < -32700); + VERUS_RT_ASSERT(mx > +32700); +#endif + } + break; + case '>LN<': + { + hasNormals = true; + VertexStream0* pVB = _vStream0.data(); + VERUS_FOR(i, _numVerts) + { + sp.Read(pVB[i]._nrm, 3); +#ifdef VERUS_DEBUG + const int len = + pVB[i]._nrm[0] * pVB[i]._nrm[0] + + pVB[i]._nrm[1] * pVB[i]._nrm[1] + + pVB[i]._nrm[2] * pVB[i]._nrm[2]; + VERUS_RT_ASSERT(len > 125 * 125); +#endif + Convert::ToDeviceNormal(pVB[i]._nrm, pVB[i]._nrm); + } + } + break; + case '>ST<': + { + hasTBN = true; + VERUS_RT_ASSERT(_vStream2.empty()); + _vStream2.resize(_numVerts); + VertexStream2* pVB = _vStream2.data(); + char temp[3]; + VERUS_FOR(i, _numVerts) + { + sp.Read(temp, 3); + Convert::Sint8ToSint16(temp, pVB[i]._tan, 3); // Single byte is not supported in OpenGL. + sp.Read(temp, 3); + Convert::Sint8ToSint16(temp, pVB[i]._bin, 3); +#ifdef VERUS_DEBUG + const int lenT = + pVB[i]._tan[0] * pVB[i]._tan[0] + + pVB[i]._tan[1] * pVB[i]._tan[1] + + pVB[i]._tan[2] * pVB[i]._tan[2]; + VERUS_RT_ASSERT(lenT > 32100 * 32100); + const int lenB = + pVB[i]._bin[0] * pVB[i]._bin[0] + + pVB[i]._bin[1] * pVB[i]._bin[1] + + pVB[i]._bin[2] * pVB[i]._bin[2]; + VERUS_RT_ASSERT(lenB > 32100 * 32100); +#endif + } + } + break; + case '>0T<': + { + sp.Read(&_tc0Deq[0], 8); + sp.Read(&_tc0Deq[2], 8); + VertexStream0* pVB = _vStream0.data(); + short mn = 0, mx = 0; + VERUS_FOR(i, _numVerts) + { + sp.Read(pVB[i]._tc0, 4); +#ifdef VERUS_DEBUG + const short* pMin = std::min_element(pVB[i]._tc0, pVB[i]._tc0 + 2); + const short* pMax = std::max_element(pVB[i]._tc0, pVB[i]._tc0 + 2); + mn = CMath::Min(mn, *pMin); + mx = CMath::Max(mx, *pMax); +#endif + } +#ifdef VERUS_DEBUG + VERUS_RT_ASSERT(mn < -32700); + VERUS_RT_ASSERT(mx > +32700); +#endif + } + break; + case '>1T<': + { + sp.Read(&_tc1Deq[0], 8); + sp.Read(&_tc1Deq[2], 8); + VERUS_RT_ASSERT(_vStream3.empty()); + _vStream3.resize(_numVerts); + VertexStream3* pVB = _vStream3.data(); + VERUS_FOR(i, _numVerts) + sp.Read(&pVB[i]._tc1, 4); + } + break; + case '>HB<': + { +#if 0 + BYTE numBone = 0; + sp >> numBone; + VERUS_RT_ASSERT(numBone <= VERUS_MAX_NUM_BONES); + _numBones = numBone; + _skeleton.Init(); + VERUS_FOR(i, _numBones) + { + Anim::Skeleton::Bone bone; + sp.ReadString(buffer); bone.m_name = buffer; + sp.ReadString(buffer); bone.m_parentName = buffer; + Transform3 matOffset; + sp.Read(&matOffset, 64); + bone.m_matToBoneSpace = matOffset; + bone.m_matFromBoneSpace = VMath::orthoInverse(matOffset); + _skeleton.InsertBone(bone); + } +#endif + } + break; + case '>SS<': + { + VERUS_RT_ASSERT(_vStream1.empty()); + _vStream1.resize(_numVerts); + VertexStream1* pVB = _vStream1.data(); + bool abnormal = false; + VERUS_FOR(i, _numVerts) + { + BYTE data[8]; + sp.Read(data, 8); + VERUS_FOR(j, 4) + { + pVB[i]._bw[j] = data[j]; + pVB[i]._bi[j] = data[j + 4]; + } +#ifdef VERUS_DEBUG + const int sum = + pVB[i]._bw[0] + + pVB[i]._bw[1] + + pVB[i]._bw[2] + + pVB[i]._bw[3]; + if (sum != 255) + abnormal = true; +#endif + } + if (abnormal) + VERUS_LOG_WARN(_C("WARNING: Abnormal skin weights in " + _url)); + } + break; + case '>RS<': + { + VertexStream0* pVB = _vStream0.data(); + BYTE index = 0; + VERUS_FOR(i, _numVerts) + { + sp >> index; + pVB[i]._pos[3] = index; + } + _rigidSkeleton = true; + } + break; + default: + sp.Advance(blockSize); + } + } + + if (_loadOnly) + return; + + if (!hasTBN) + { + _vStream2.resize(_numVerts); + ComputeTangentSpace(); + } + + CreateDeviceBuffers(); +} + +void BaseMesh::LoadPrimaryBones() +{ +#if 0 + if (!m_skeleton.IsInitialized()) + return; + + String url(_url); + Str::ReplaceExtension(url, ".primary"); + Vector vData; + IO::FileSystem::I().LoadResourceFromCache(_C(url), vData, false); + if (vData.size() <= 1) + return; + Vector vPrimaryBones; + Str::ReadLines(reinterpret_cast(vData.data()), vPrimaryBones); + if (!vPrimaryBones.empty()) + { + _skeleton.AdjustPrimaryBones(vPrimaryBones); + VERUS_FOREACH(Vector, _vStream1, it) + { + VERUS_FOR(i, 4) + it->bi[i] = _skeleton.RemapBoneIndex(it->bi[i]); + } + BufferDataVB(_vStream1.data(), 1); + } +#endif +} + +void BaseMesh::LoadRig() +{ +#if 0 + if (!m_skeleton.IsInitialized()) + return; + + String url(_url); + Str::ReplaceExtension(url, ".rig"); + Vector vData; + IO::FileSystem::I().LoadResourceFromCache(_C(url), vData, false); + if (vData.size() <= 1) + return; + _skeleton.LoadRigInfoFromPtr(vData.data()); +#endif +} + +void BaseMesh::LoadMimic() +{ +#if 0 + if (m_mimicUrl.empty()) + return; + + Vector vData; + IO::FileSystem::I().LoadResourceFromCache(_C(m_mimicUrl), vData, false); + if (vData.size() > 1) + { + VERUS_RT_ASSERT(!(_vStream0.empty() || _vStream2.empty())); + VERUS_RT_ASSERT(vData.size() - 1 == _numVerts * 6); + CSZ ps = reinterpret_cast(vData.data()); + VERUS_FOR(i, _numVerts) + { + int r, g, b; + sscanf(ps + i * 6, "%d %d %d", &r, &g, &b); + _vStream0[i]._pos[3] = r * SHRT_MAX / 9; + _vStream2[i]._tan[3] = g * SHRT_MAX / 9; + _vStream2[i]._bin[3] = b * SHRT_MAX / 9; + } + BufferDataVB(_vStream0.data(), 0); + BufferDataVB(_vStream2.data(), 2); + } + + vData.clear(); + String mim(m_mimicUrl); + Str::ReplaceExtension(mim, ".mim"); + IO::FileSystem::I().LoadResourceFromCache(_C(mim), vData, false); + if (vData.size() > 1) + { + _mimic.Init(); + _mimic.LoadFromPtr(vData.data()); + } +#endif +} + +void BaseMesh::LoadKinectBindPose() +{ +#if 0 + Vector vData; + IO::FileSystem::I().LoadResourceFromCache("Misc:KinectBindPose.xml", vData, false); + if (vData.size() > 1) + _skeleton.LoadKinectBindPose(reinterpret_cast(vData.data())); +#endif +} + +void BaseMesh::ComputeDeq(glm::vec3& scale, glm::vec3& bias, const glm::vec3& extents, const glm::vec3& minPos) +{ + scale = extents * (1.f / SHRT_MAX); + bias = minPos + extents; +} + +void BaseMesh::QuantizeV(glm::vec3& v, const glm::vec3& extents, const glm::vec3& minPos) +{ + v = ((v - minPos - extents) / extents)*float(SHRT_MAX); + v.x = (v.x >= 0) ? v.x + 0.5f : v.x - 0.5f; + v.y = (v.y >= 0) ? v.y + 0.5f : v.y - 0.5f; + v.z = (v.z >= 0) ? v.z + 0.5f : v.z - 0.5f; + v = glm::clamp(v, glm::vec3(-SHRT_MAX), glm::vec3(SHRT_MAX)); +} + +void BaseMesh::DequantizeUsingDeq2D(const short* in, const float* deq, glm::vec2& out) +{ + out.x = in[0] * deq[0] + deq[2]; + out.y = in[1] * deq[1] + deq[3]; +} + +void BaseMesh::DequantizeUsingDeq2D(const short* in, const float* deq, RPoint3 out) +{ + out = Point3( + in[0] * deq[0] + deq[2], + in[1] * deq[1] + deq[3], + 0); +} + +void BaseMesh::DequantizeUsingDeq3D(const short* in, const float* deq, glm::vec3& out) +{ + out.x = in[0] * deq[0] + deq[3]; + out.y = in[1] * deq[1] + deq[4]; + out.z = in[2] * deq[2] + deq[5]; +} + +void BaseMesh::DequantizeUsingDeq3D(const short* in, const float* deq, RPoint3 out) +{ + out = Point3( + in[0] * deq[0] + deq[3], + in[1] * deq[1] + deq[4], + in[2] * deq[2] + deq[5]); +} + +void BaseMesh::DequantizeNrm(const char* in, glm::vec3& out) +{ + out.x = Convert::Sint8ToSnorm(in[0]); + out.y = Convert::Sint8ToSnorm(in[1]); + out.z = Convert::Sint8ToSnorm(in[2]); +} + +void BaseMesh::DequantizeNrm(const char* in, RVector3 out) +{ + out = Vector3( + Convert::Sint8ToSnorm(in[0]), + Convert::Sint8ToSnorm(in[1]), + Convert::Sint8ToSnorm(in[2])); +} + +void BaseMesh::DequantizeTanBin(const short* in, RVector3 out) +{ + out = Vector3( + Convert::Sint16ToSnorm(in[0]), + Convert::Sint16ToSnorm(in[1]), + Convert::Sint16ToSnorm(in[2])); +} + +void BaseMesh::ComputeTangentSpace() +{ + Vector vV, vN, vTan, vBin; + Vector vTex; + vV.resize(_numVerts); + vTex.resize(_numVerts); + + VERUS_FOR(i, _numVerts) + { + DequantizeUsingDeq3D(_vStream0[i]._pos, _posDeq, vV[i]); + DequantizeUsingDeq2D(_vStream0[i]._tc0, _tc0Deq, vTex[i]); + } + + Math::NormalComputer::ComputeNormals(_vIndices, vV, vN, 1); + VERUS_FOR(i, _numVerts) + Convert::SnormToSint8(&vN[i].x, _vStream0[i]._nrm, 3); + + if (!_vStream2.empty()) + { + Math::NormalComputer::ComputeTangentSpace(_vIndices, vV, vN, vTex, vTan, vBin); + VERUS_FOR(i, _numVerts) + { + Convert::SnormToSint16(&vTan[i].x, _vStream2[i]._tan, 3); + Convert::SnormToSint16(&vBin[i].x, _vStream2[i]._bin, 3); + } + } +} + +btBvhTriangleMeshShape* BaseMesh::InitShape(RcTransform3 tr, CSZ url) +{ + if (!_numVerts) + return nullptr; + + DoneShape(); + + String finalUrl = url ? url : _url; + String cacheFilename; + if (!finalUrl.empty()) + { + String filename = finalUrl; + Str::ReplaceAll(filename, ":", "-"); + Str::ReplaceAll(filename, "/", "."); + if (url) + filename += ".INST"; + filename = "Models:Bullet_Cache/" + filename + ".bullet"; + cacheFilename = IO::FileSystem::ConvertRelativePathToAbsolute(filename, true); + if (IO::FileSystem::FileExist(_C(filename))) + { + Vector vMesh; + IO::FileSystem::LoadResource(_C(filename), vMesh); + + btBulletWorldImporter bwi(0); + if (!vMesh.empty() && + bwi.loadFileFromMemory(reinterpret_cast(vMesh.data()), Utils::Cast32(vMesh.size()))) + { + const int numShapes = bwi.getNumCollisionShapes(); + if (numShapes) + _pShape = static_cast(bwi.getCollisionShapeByIndex(0)); + } + + return _pShape; + } + } + + Vector vVert; + vVert.resize(_numVerts); + VERUS_FOR(i, _numVerts) + { + DequantizeUsingDeq3D(_vStream0[i]._pos, _posDeq, vVert[i]); + vVert[i] = tr * vVert[i]; + } + + btTriangleMesh mesh(_vIndices.empty()); + VERUS_FOR(i, _numFaces) + { + const int i0 = _vIndices.empty() ? _vIndices32[i * 3 + 0] : _vIndices[i * 3 + 0]; + const int i1 = _vIndices.empty() ? _vIndices32[i * 3 + 1] : _vIndices[i * 3 + 1]; + const int i2 = _vIndices.empty() ? _vIndices32[i * 3 + 2] : _vIndices[i * 3 + 2]; + + const btVector3 v0 = vVert[i0].Bullet(); + const btVector3 v1 = vVert[i1].Bullet(); + const btVector3 v2 = vVert[i2].Bullet(); + + mesh.addTriangle(v0, v1, v2, true); + } + + _pShape = new btBvhTriangleMeshShape(&mesh, true); + + if (!cacheFilename.empty()) + { + btDefaultSerializer s; + s.startSerialization(); + _pShape->serializeSingleShape(&s); + s.finishSerialization(); + IO::File file; + if (file.Open(_C(cacheFilename), "wb")) + file.Write(s.getBufferPointer(), s.getCurrentBufferSize()); + } + + return _pShape; +} + +void BaseMesh::DoneShape() +{ + VERUS_SMART_DELETE(_pShape); +} + +void BaseMesh::GetBounds(RPoint3 mn, RPoint3 mx) const +{ + const short smin[] = { -SHRT_MAX, -SHRT_MAX, -SHRT_MAX }; + const short smax[] = { +SHRT_MAX, +SHRT_MAX, +SHRT_MAX }; + DequantizeUsingDeq3D(smin, _posDeq, mn); + DequantizeUsingDeq3D(smax, _posDeq, mx); +} + +Math::Bounds BaseMesh::GetBounds() const +{ + Point3 mn, mx; + GetBounds(mn, mx); + return Math::Bounds(mn, mx); +} + +void BaseMesh::VisitVertices(std::function fn) +{ + VERUS_FOR(i, _numVerts) + { + Point3 pos; + DequantizeUsingDeq3D(_vStream0[i]._pos, _posDeq, pos); + if (_vStream1.empty()) + { + if (Continue::no == fn(pos, -1)) + break; + } + else + { + bool done = false; + VERUS_FOR(j, 4) + { + if (_vStream1[i]._bw[j] > 0) + { + if (Continue::no == fn(pos, _vStream1[i]._bi[j])) + { + done = true; + break; + } + } + } + if (done) + break; + } + } +} + +String BaseMesh::ToXmlString() const +{ + Point3 mn, mx; + GetBounds(mn, mx); + + StringStream ss; + ss << "" << VERUS_CRNL; + ss << "" << VERUS_CRNL; + ss << "" << VERUS_CRNL; + ss << "" << VERUS_CRNL; + + // IB: + ss << ""; + if (_vIndices.empty()) + { + VERUS_FOR(i, (int)_vIndices32.size()) + { + if (i) + ss << ' '; + ss << _vIndices32[i]; + } + } + else + { + VERUS_FOR(i, (int)_vIndices.size()) + { + if (i) + ss << ' '; + ss << _vIndices[i]; + } + } + ss << "" << VERUS_CRNL; + + // Position: + ss << "

"; + VERUS_FOR(i, _numVerts) + { + Point3 pos; + DequantizeUsingDeq3D(_vStream0[i]._pos, _posDeq, pos); + if (i) + ss << ' '; + ss << pos.ToString(); + } + ss << "

" << VERUS_CRNL; + + // Normal: + ss << ""; + VERUS_FOR(i, _numVerts) + { + Vector3 norm; + DequantizeNrm(_vStream0[i]._nrm, norm); + if (i) + ss << ' '; + ss << norm.ToString(); + } + ss << "" << VERUS_CRNL; + + // TC 0: + ss << ""; + VERUS_FOR(i, _numVerts) + { + Point3 tc; + DequantizeUsingDeq2D(_vStream0[i]._tc0, _tc0Deq, tc); + if (i) + ss << ' '; + ss << tc.ToString2(); + } + ss << "" << VERUS_CRNL; + + // TC 1: + if (false) + { + ss << ""; + VERUS_FOR(i, _numVerts) + { + Point3 tc; + DequantizeUsingDeq2D(_vStream3[i]._tc1, _tc1Deq, tc); + if (i) + ss << ' '; + ss << tc.ToString2(); + } + ss << "" << VERUS_CRNL; + } + + ss << ""; + + return ss.str(); +} + +String BaseMesh::ToObjString() const +{ + StringStream ss; + + VERUS_FOR(i, _numVerts) + { + Point3 pos; + DequantizeUsingDeq3D(_vStream0[i]._pos, _posDeq, pos); + ss << "v " << Point3(VMath::scale(pos, 100)).ToString() << VERUS_CRNL; + } + + ss << VERUS_CRNL; + + const int numFaces = Utils::Cast32(_vIndices.size() / 3); + if (numFaces) + { + VERUS_FOR(i, numFaces) + { + ss << "f "; + ss << _vIndices[i * 3 + 0] + 1 << ' '; + ss << _vIndices[i * 3 + 1] + 1 << ' '; + ss << _vIndices[i * 3 + 2] + 1 << VERUS_CRNL; + } + } + else + { + const int numFaces = Utils::Cast32(_vIndices32.size() / 3); + VERUS_FOR(i, numFaces) + { + ss << "f "; + ss << _vIndices32[i * 3 + 0] + 1 << ' '; + ss << _vIndices32[i * 3 + 1] + 1 << ' '; + ss << _vIndices32[i * 3 + 2] + 1 << VERUS_CRNL; + } + } + + return ss.str(); +} diff --git a/Verus/src/Scene/BaseMesh.h b/Verus/src/Scene/BaseMesh.h new file mode 100644 index 0000000..d1ffe83 --- /dev/null +++ b/Verus/src/Scene/BaseMesh.h @@ -0,0 +1,111 @@ +#pragma once + +namespace verus +{ + namespace Scene + { + class BaseMesh : public Object, public IO::AsyncCallback, public AllocatorAware + { + protected: + struct VertexStream0 // 16 bytes, common. + { + short _pos[4]; + short _tc0[2]; + char _nrm[4]; + }; + + struct VertexStream1 // 16 bytes, skinned mesh. + { + short _bw[4]; + short _bi[4]; + }; + + struct VertexStream2 // 16 bytes, per-pixel lighting / normal maps. + { + short _tan[4]; + short _bin[4]; + }; + + struct VertexStream3 // 8 bytes, static light maps / extra color. + { + short _tc1[2]; + BYTE _clr[4]; + }; + + Vector _vIndices; + Vector _vIndices32; + Vector _vStream0; + Vector _vStream1; + Vector _vStream2; + Vector _vStream3; + String _url; + btBvhTriangleMeshShape* _pShape = nullptr; + int _numVerts = 0; + int _numFaces = 0; + int _numBones = 0; + float _posDeq[6]; + float _tc0Deq[4]; + float _tc1Deq[4]; + bool _loadKinectBindPose = false; + bool _loadOnly = false; + bool _rigidSkeleton = false; + bool _async_initShape = false; + + public: + BaseMesh(); + virtual ~BaseMesh(); + + void Init(CSZ url); + void Done(); + + Str GetUrl() const { return _C(_url); } + + int GetNumVerts() const { return _numVerts; } + int GetNumFaces() const { return _numFaces; } + int GetNumBones() const { return _numBones; } + + virtual void Async_Run(CSZ url, RcBlob blob) override; + + VERUS_P(void Load(RcBlob blob)); + VERUS_P(void LoadX3D3(RcBlob blob)); + bool IsLoaded() const { return !!_numVerts; } + + // Load extra: + VERUS_P(void LoadPrimaryBones()); + VERUS_P(void LoadRig()); + VERUS_P(void LoadMimic()); + VERUS_P(void LoadKinectBindPose()); + + // Quantization / dequantization: + static void ComputeDeq(glm::vec3& scale, glm::vec3& bias, const glm::vec3& extents, const glm::vec3& minPos); + static void QuantizeV(glm::vec3& v, const glm::vec3& extents, const glm::vec3& minPos); + static void DequantizeUsingDeq2D(const short* in, const float* deq, glm::vec2& out); + static void DequantizeUsingDeq2D(const short* in, const float* deq, RPoint3 out); + static void DequantizeUsingDeq3D(const short* in, const float* deq, glm::vec3& out); + static void DequantizeUsingDeq3D(const short* in, const float* deq, RPoint3 out); + static void DequantizeNrm(const char* in, glm::vec3& out); + static void DequantizeNrm(const char* in, RVector3 out); + static void DequantizeTanBin(const short* in, RVector3 out); + + void ComputeTangentSpace(); + + // GPU: + virtual void CreateDeviceBuffers() {} + virtual void BufferDataVB(const void* p, int streamID) {} + + // Physics: + btBvhTriangleMeshShape* GetShape() const { return _pShape; } + btBvhTriangleMeshShape* InitShape(RcTransform3 tr, CSZ url = nullptr); + void DoneShape(); + + // Bounds: + void GetBounds(RPoint3 mn, RPoint3 mx) const; + Math::Bounds GetBounds() const; + + void VisitVertices(std::function fn); + + String ToXmlString() const; + String ToObjString() const; + }; + } +} diff --git a/Verus/src/Scene/Camera.cpp b/Verus/src/Scene/Camera.cpp new file mode 100644 index 0000000..9ac7046 --- /dev/null +++ b/Verus/src/Scene/Camera.cpp @@ -0,0 +1,187 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Scene; + +// Camera: + +void Camera::UpdateInternal() +{ + if (_update & Update::v) + UpdateView(); + + if (_update & Update::p) + { + if (0 == _fovY) + _matP = Matrix4::MakeOrtho(_w, _h, _zNear, _zFar); + else + _matP = Matrix4::MakePerspective(_fovY, _aspectRatio, _zNear, _zFar); + } +} + +void Camera::Update() +{ + UpdateInternal(); + + if (+_update) + UpdateVP(); + + _update = Update::none; +} + +void Camera::UpdateView() +{ + _dirFront = VMath::normalizeApprox(_posAt - _posEye); +#ifdef VERUS_COMPARE_MODE + _dirFront = VMath::normalizeApprox(Vector3(glm::round(m_dirFront.GLM()*glm::vec3(4, 4, 4)))); + _posEye = Point3(int(_posEye.getX()), int(_posEye.getY()), int(_posEye.getZ())); + _matV = Matrix4::lookAt(_posEye, _posEye + _dirFront, _dirUp); +#else + _matV = Matrix4::lookAt(_posEye, _posAt, _dirUp); +#endif + _matVi = VMath::orthoInverse(_matV); +} + +void Camera::UpdateVP() +{ + _matVP = _matP * _matV; + _frustum.FromMatrix(_matVP); +} + +void Camera::UpdateFFP() +{ + //VERUS_QREF_RENDER; + //render->SetTransform(CGL::TS_VIEW, _matV); + //render->SetTransform(CGL::TS_PROJECTION, _matP); +} + +Vector4 Camera::GetZNearFarEx() const +{ + //VERUS_QREF_RENDER; + //if (CGL::RENDERER_OPENGL != render.GetRenderer()) + // return Vector4(_zNear, _zFar, _zFar / (_zFar - _zNear), _zFar*_zNear / (_zNear - _zFar)); + //else + return Vector4(_zNear, _zFar, (_zFar + _zNear) / (_zFar - _zNear), -2 * _zFar*_zNear / (_zFar - _zNear)); +} + +void Camera::SetFrustumNear(float zNear) +{ + _frustum.SetNearPlane(_posEye, _dirFront, zNear); +} + +void Camera::SetFrustumFar(float zFar) +{ + _frustum.SetFarPlane(_posEye, _dirFront, zFar); +} + +void Camera::SetFOVH(float x) +{ + SetFOV(2 * atan(tan(x*0.5f) / _aspectRatio)); +} + +void Camera::GetClippingSpacePlane(RVector4 plane) const +{ + const Matrix4 m = VMath::transpose(VMath::inverse(_matVP)); + plane = m * plane; +} + +void Camera::ExcludeWaterLine(float h) +{ + const float y = _posEye.getY(); + if (y > -h && y < h) + { + const float t = h - y; + const Vector3 offset(0, t, 0); + _posEye = _posEye + offset; + _posAt = _posAt + offset; + } +} + +void Camera::SaveState(int slot) +{ + StringStream ss; + //ss << CUtils::I().GetWritablePath() << "/CameraState.xml"; + //Utils::CXml xml; + //xml.SetFilename(_C(ss.str())); + //xml.Load(); + //char text[16]; + //sprintf_s(text, "slot%d", slot); + //xml.Set(text, _C(_posEye.ToString() + "|" + _posAt.ToString())); +} + +void Camera::LoadState(int slot) +{ + StringStream ss; + //ss << CUtils::I().GetWritablePath() << "/CameraState.xml"; + //Utils::CXml xml; + //xml.SetFilename(_C(ss.str())); + //xml.Load(); + //char text[16]; + //sprintf_s(text, "slot%d", slot); + //CSZ v = xml.GetS(text); + //if (v) + //{ + // CSZ p = strchr(v, '|'); + // _posEye.FromString(v); + // _posAt.FromString(p + 1); + // _update |= Update::v; + // Update(); + //} +} + +// MainCamera: + +void MainCamera::operator=(RcMainCamera that) +{ + Camera::operator=(that); + _currentFrame = -1; +} + +void MainCamera::Update() +{ + UpdateInternal(); + + if (+_update) + UpdateVP(); + + _update = Update::none; +} + +void MainCamera::UpdateVP() +{ + //VERUS_QREF_RENDER; + //if (m_currentFrame != render.GetNumFrames()) + { + _matPrevVP = GetMatrixVP(); + //m_currentFrame = render.GetNumFrames(); + } + Camera::UpdateVP(); +} + +void MainCamera::GetPickingRay(RPoint3 pos, RVector3 dir) const +{ + VERUS_RT_ASSERT(_pCpp); + //VERUS_QREF_RENDER; + + int x, y; + _pCpp->GetPos(x, y); + //const Vector3 v( + // ((float(x + x) / render.GetWindowWidth()) - 1) / GetMatrixP().getElem(0, 0), + // -((float(y + y) / render.GetWindowHeight()) - 1) / GetMatrixP().getElem(1, 1), + // -1); + //dir = GetMatrixVi().getUpper3x3()*v; + pos = GetMatrixVi().getTranslation(); +} + +float MainCamera::ComputeMotionBlur(RcPoint3 pos, RcPoint3 posPrev) const +{ + Vector4 screenPos = GetMatrixVP()*pos; + Vector4 screenPosPrev = GetMatrixPrevVP()*posPrev; + screenPos /= screenPos.getW(); + screenPos.setZ(0); + screenPos.setW(0); + screenPosPrev /= screenPosPrev.getW(); + screenPosPrev.setZ(0); + screenPosPrev.setW(0); + return VMath::length(screenPos - screenPosPrev) * 10; +} diff --git a/Verus/src/Scene/Camera.h b/Verus/src/Scene/Camera.h new file mode 100644 index 0000000..4fb7e3c --- /dev/null +++ b/Verus/src/Scene/Camera.h @@ -0,0 +1,122 @@ +#pragma once + +namespace verus +{ + namespace Scene + { + //! Basic camera without extra features. For shadow map computations, etc. + class Camera + { + protected: + enum class Update : BYTE + { + none, + v = (1 << 0), + p = (1 << 1) + }; + + private: + Math::Frustum _frustum; + Transform3 _matV = Transform3::identity(); + Transform3 _matVi = Transform3::identity(); + Matrix4 _matP = Matrix4::identity(); + Matrix4 _matVP = Matrix4::identity(); + VERUS_PD(Point3 _posEye = Point3(0)); + VERUS_PD(Point3 _posAt = Point3(0, 0, -1)); + VERUS_PD(Vector3 _dirUp = Vector3(0, 1, 0)); + VERUS_PD(Vector3 _dirFront = Vector3(0, 0, -1)); + float _fovY = VERUS_PI / 4; // Zero FOV means ortho. + float _aspectRatio = 1; + float _zNear = 0.1f; // 10 cm. + float _zFar = 10000; // 10 km. + float _w = 80; + float _h = 60; + VERUS_PD(Update _update = Update::none); + VERUS_PD(void UpdateInternal()); + + public: + virtual void Update(); + void UpdateView(); + void UpdateVP(); + void UpdateFFP(); + + Vector4 GetZNearFarEx() const; + + // Frustum: + void SetFrustumNear(float zNear); + void SetFrustumFar(float zFar); + Math::RFrustum GetFrustum() { return _frustum; } + + // Positions: + RcPoint3 GetPositionEye() const { return _posEye; } + RcPoint3 GetPositionAt() const { return _posAt; } + void MoveEyeTo(RcPoint3 pos) { _update |= Update::v; _posEye = pos; } + void MoveAtTo(RcPoint3 pos) { _update |= Update::v; _posAt = pos; } + + // Directions: + RcVector3 GetDirectionFront() const { return _dirFront; } + RcVector3 GetDirectionUp() const { return _dirUp; } + void SetDirectionUp(RcVector3 dir) { _update |= Update::v; _dirUp = dir; } + + // Matrices: + RcTransform3 GetMatrixV() const { return _matV; } + RcTransform3 GetMatrixVi() const { return _matVi; } + RcMatrix4 GetMatrixP() const { return _matP; } + RcMatrix4 GetMatrixVP() const { return _matVP; } + + // Perspective: + float GetFOV() const { return _fovY; } + float GetAspectRatio() const { return _aspectRatio; } + float GetZNear() const { return _zNear; } + float GetZFar() const { return _zFar; } + float GetWidth() const { return _w; } + float GetHeight() const { return _h; } + void SetFOVH(float x); + void SetFOV(float x) { _update |= Update::p; _fovY = x; } + void SetAspectRatio(float x) { _update |= Update::p; _aspectRatio = x; } + void SetZNear(float x) { _update |= Update::p; _zNear = x; } + void SetZFar(float x) { _update |= Update::p; _zFar = x; } + void SetWidth(float x) { _update |= Update::p; _w = (0 == x) ? _aspectRatio * _h : x; } + void SetHeight(float x) { _update |= Update::p; _h = x; } + + void GetClippingSpacePlane(RVector4 plane) const; + + void ExcludeWaterLine(float h = 0.25f); + + // State: + virtual void SaveState(int slot); + virtual void LoadState(int slot); + }; + VERUS_TYPEDEFS(Camera); + + //! The interface for getting the cursor's position. + struct CursorPosProvider + { + virtual void GetPos(int& x, int& y) = 0; + }; + VERUS_TYPEDEFS(CursorPosProvider); + + //! More advanced camera, used to show the world to the user. With motion blur. + class MainCamera : public Camera + { + Matrix4 _matPrevVP = Matrix4::identity(); // For motion blur. + PCursorPosProvider _pCpp = nullptr; + UINT32 _currentFrame = -1; + + public: + void operator=(const MainCamera& that); + + virtual void Update() override; + void UpdateVP(); + + void GetPickingRay(RPoint3 pos, RVector3 dir) const; + + RcMatrix4 GetMatrixPrevVP() const { return _matPrevVP; } + + void SetCursorPosProvider(PCursorPosProvider p) { _pCpp = p; } + + float ComputeMotionBlur(RcPoint3 pos, RcPoint3 posPrev) const; + }; + VERUS_TYPEDEFS(MainCamera); + } +} diff --git a/Verus/src/Scene/Mesh.cpp b/Verus/src/Scene/Mesh.cpp new file mode 100644 index 0000000..59ecfe1 --- /dev/null +++ b/Verus/src/Scene/Mesh.cpp @@ -0,0 +1,4 @@ +#include "verus.h" + +using namespace verus; +using namespace verus::Scene; diff --git a/Verus/src/Scene/Mesh.h b/Verus/src/Scene/Mesh.h new file mode 100644 index 0000000..ee7e4be --- /dev/null +++ b/Verus/src/Scene/Mesh.h @@ -0,0 +1,11 @@ +#pragma once + +namespace verus +{ + namespace Scene + { + class Mesh : public BaseMesh + { + }; + } +} diff --git a/Verus/src/Scene/Scene.cpp b/Verus/src/Scene/Scene.cpp new file mode 100644 index 0000000..159980f --- /dev/null +++ b/Verus/src/Scene/Scene.cpp @@ -0,0 +1,11 @@ +#include "verus.h" + +namespace verus +{ + void Make_Scene() + { + } + void Free_Scene() + { + } +} diff --git a/Verus/src/Scene/Scene.h b/Verus/src/Scene/Scene.h new file mode 100644 index 0000000..6b2fc7e --- /dev/null +++ b/Verus/src/Scene/Scene.h @@ -0,0 +1,11 @@ +#pragma once + +#include "Camera.h" +#include "BaseMesh.h" +#include "Mesh.h" + +namespace verus +{ + void Make_Scene(); + void Free_Scene(); +} diff --git a/Verus/src/Security/CipherRC4.h b/Verus/src/Security/CipherRC4.h new file mode 100644 index 0000000..b417a91 --- /dev/null +++ b/Verus/src/Security/CipherRC4.h @@ -0,0 +1,78 @@ +#pragma once + +namespace verus +{ + namespace Security + { + class CipherRC4 + { + public: + static inline void Encrypt( + RcString password, + const Vector& vData, + Vector& vCipher, + size_t skip = 787) + { + vCipher.resize(vData.size()); + size_t i, j, a; + BYTE key[256]; + BYTE box[256]; + for (i = 0; i < 256; ++i) + { + key[i] = password[i%password.length()]; + box[i] = static_cast(i); + } + for (j = i = 0; i < 256; ++i) + { + j = (j + box[i] + key[i]) & 0xFF; + std::swap(box[i], box[j]); + } + const size_t num = vData.size() + skip; + for (a = j = i = 0; i < num; ++i) + { + a = (a + 1) & 0xFF; + j = (j + box[a]) & 0xFF; + std::swap(box[a], box[j]); + const BYTE k = box[(box[a] + box[j]) & 0xFF]; + if (i >= skip) + vCipher[i - skip] = vData[i - skip] ^ k; + } + } + + static inline void Decrypt( + RcString password, + const Vector& vCipher, + Vector& vData, + size_t skip = 787) + { + Encrypt(password, vCipher, vData, skip); + } + + static inline void Test() + { + // See: https://tools.ietf.org/html/rfc6229 + + const BYTE offset0000_256[] = { 0xEA, 0xA6, 0xBD, 0x25, 0x88, 0x0B, 0xF9, 0x3D, 0x3F, 0x5D, 0x1E, 0x4C, 0xA2, 0x61, 0x1D, 0x91 }; + const BYTE offset1536_256[] = { 0x3E, 0x34, 0x13, 0x5C, 0x79, 0xDB, 0x01, 0x02, 0x00, 0x76, 0x76, 0x51, 0xCF, 0x26, 0x30, 0x73 }; + String password; + password.resize(32); + VERUS_FOR(i, 32) + password[i] = i + 1; + Vector v, vCip; + v.resize(16); + Encrypt(password, v, vCip, 0); + VERUS_RT_ASSERT(!memcmp(offset0000_256, vCip.data(), vCip.size())); + Encrypt(password, v, vCip, 1536); + VERUS_RT_ASSERT(!memcmp(offset1536_256, vCip.data(), vCip.size())); + + password.resize(5); + const BYTE offset0768_40[] = { 0xEB, 0x62, 0x63, 0x8D, 0x4F, 0x0B, 0xA1, 0xFE, 0x9F, 0xCA, 0x20, 0xE0, 0x5B, 0xF8, 0xFF, 0x2B }; + const BYTE offset3072_40[] = { 0xEC, 0x0E, 0x11, 0xC4, 0x79, 0xDC, 0x32, 0x9D, 0xC8, 0xDA, 0x79, 0x68, 0xFE, 0x96, 0x56, 0x81 }; + Encrypt(password, v, vCip, 768); + VERUS_RT_ASSERT(!memcmp(offset0768_40, vCip.data(), vCip.size())); + Encrypt(password, v, vCip, 3072); + VERUS_RT_ASSERT(!memcmp(offset3072_40, vCip.data(), vCip.size())); + } + }; + } +} diff --git a/Verus/src/Security/Security.h b/Verus/src/Security/Security.h new file mode 100644 index 0000000..05d49c6 --- /dev/null +++ b/Verus/src/Security/Security.h @@ -0,0 +1,3 @@ +#pragma once + +#include "CipherRC4.h" diff --git a/Verus/src/ThirdParty/ThirdParty.h b/Verus/src/ThirdParty/ThirdParty.h new file mode 100644 index 0000000..6c7345b --- /dev/null +++ b/Verus/src/ThirdParty/ThirdParty.h @@ -0,0 +1,21 @@ +#pragma once + +// Base64: +// https://sourceforge.net/projects/libb64/ +#ifdef _WIN32 +extern "C" +{ +# include "libb64/cencode.h" +# include "libb64/cdecode.h" +} +#else +# include "libb64/cencode.h" +# include "libb64/cdecode.h" +#endif + +// MD5: +#include "md5.h" + +// UTF-8: +// https://sourceforge.net/projects/utfcpp/ +#include "utf8.h" diff --git a/Verus/src/ThirdParty/libb64/cdecode.c b/Verus/src/ThirdParty/libb64/cdecode.c new file mode 100644 index 0000000..01bae75 --- /dev/null +++ b/Verus/src/ThirdParty/libb64/cdecode.c @@ -0,0 +1,88 @@ +/* +cdecoder.c - c source to a base64 decoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#include "cdecode.h" + +int base64_decode_value(char value_in) +{ + static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; + static const char decoding_size = sizeof(decoding); + value_in -= 43; + if (value_in < 0 || value_in >= decoding_size) return -1; + return decoding[(int)value_in]; +} + +void base64_init_decodestate(base64_decodestate* state_in) +{ + state_in->step = step_a; + state_in->plainchar = 0; +} + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in) +{ + const char* codechar = code_in; + char* plainchar = plaintext_out; + char fragment; + + *plainchar = state_in->plainchar; + + switch (state_in->step) + { + while (1) + { + case step_a: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_a; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar = (fragment & 0x03f) << 2; + case step_b: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_b; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x030) >> 4; + *plainchar = (fragment & 0x00f) << 4; + case step_c: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_c; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03c) >> 2; + *plainchar = (fragment & 0x003) << 6; + case step_d: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_d; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03f); + } + } + /* control should not reach here */ + return plainchar - plaintext_out; +} + diff --git a/Verus/src/ThirdParty/libb64/cdecode.h b/Verus/src/ThirdParty/libb64/cdecode.h new file mode 100644 index 0000000..5729853 --- /dev/null +++ b/Verus/src/ThirdParty/libb64/cdecode.h @@ -0,0 +1,29 @@ +/* +cdecode.h - c header for a base64 decoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CDECODE_H +#define BASE64_CDECODE_H + +typedef enum +{ + step_a, step_b, step_c, step_d +} base64_decodestep; + +typedef struct +{ + base64_decodestep step; + char plainchar; +} base64_decodestate; + +void base64_init_decodestate(base64_decodestate* state_in); + +int base64_decode_value(char value_in); + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in); + +#endif /* BASE64_CDECODE_H */ + diff --git a/Verus/src/ThirdParty/libb64/cencode.c b/Verus/src/ThirdParty/libb64/cencode.c new file mode 100644 index 0000000..a8c8fee --- /dev/null +++ b/Verus/src/ThirdParty/libb64/cencode.c @@ -0,0 +1,109 @@ +/* +cencoder.c - c source to a base64 encoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#include "cencode.h" + +const int CHARS_PER_LINE = 72; + +void base64_init_encodestate(base64_encodestate* state_in) +{ + state_in->step = step_A; + state_in->result = 0; + state_in->stepcount = 0; +} + +char base64_encode_value(char value_in) +{ + static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (value_in > 63) return '='; + return encoding[(int)value_in]; +} + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) +{ + const char* plainchar = plaintext_in; + const char* const plaintextend = plaintext_in + length_in; + char* codechar = code_out; + char result; + char fragment; + + result = state_in->result; + + switch (state_in->step) + { + while (1) + { + case step_A: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_A; + return codechar - code_out; + } + fragment = *plainchar++; + result = (fragment & 0x0fc) >> 2; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x003) << 4; + case step_B: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_B; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0f0) >> 4; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x00f) << 2; + case step_C: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_C; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0c0) >> 6; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x03f) >> 0; + *codechar++ = base64_encode_value(result); + + ++(state_in->stepcount); + if (state_in->stepcount == CHARS_PER_LINE/4) + { + *codechar++ = '\n'; + state_in->stepcount = 0; + } + } + } + /* control should not reach here */ + return codechar - code_out; +} + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in) +{ + char* codechar = code_out; + + switch (state_in->step) + { + case step_B: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + *codechar++ = '='; + break; + case step_C: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + break; + case step_A: + break; + } + *codechar++ = '\n'; + + return codechar - code_out; +} + diff --git a/Verus/src/ThirdParty/libb64/cencode.h b/Verus/src/ThirdParty/libb64/cencode.h new file mode 100644 index 0000000..cf32131 --- /dev/null +++ b/Verus/src/ThirdParty/libb64/cencode.h @@ -0,0 +1,32 @@ +/* +cencode.h - c header for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CENCODE_H +#define BASE64_CENCODE_H + +typedef enum +{ + step_A, step_B, step_C +} base64_encodestep; + +typedef struct +{ + base64_encodestep step; + char result; + int stepcount; +} base64_encodestate; + +void base64_init_encodestate(base64_encodestate* state_in); + +char base64_encode_value(char value_in); + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in); + +#endif /* BASE64_CENCODE_H */ + diff --git a/Verus/src/ThirdParty/md5.cpp b/Verus/src/ThirdParty/md5.cpp new file mode 100644 index 0000000..849f261 --- /dev/null +++ b/Verus/src/ThirdParty/md5.cpp @@ -0,0 +1,362 @@ +/* MD5 + converted to C++ class by Frank Thilo (thilo@unix-ag.org) + for bzflag (http://www.bzflag.org) + + based on: + + md5.h and md5.c + reference implemantion of RFC 1321 + + Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + +*/ + +/* interface header */ +#include "md5.h" + +/* system implementation headers */ +#include +#include + +// Constants for MD5Transform routine. +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +/////////////////////////////////////////////// + +// F, G, H and I are basic MD5 functions. +inline MD5Hash::uint4 MD5Hash::F(uint4 x, uint4 y, uint4 z) { + return x&y | ~x&z; +} + +inline MD5Hash::uint4 MD5Hash::G(uint4 x, uint4 y, uint4 z) { + return x&z | y&~z; +} + +inline MD5Hash::uint4 MD5Hash::H(uint4 x, uint4 y, uint4 z) { + return x^y^z; +} + +inline MD5Hash::uint4 MD5Hash::I(uint4 x, uint4 y, uint4 z) { + return y ^ (x | ~z); +} + +// rotate_left rotates x left n bits. +inline MD5Hash::uint4 MD5Hash::rotate_left(uint4 x, int n) { + return (x << n) | (x >> (32-n)); +} + +// FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +// Rotation is separate from addition to prevent recomputation. +inline void MD5Hash::FF(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { + a = rotate_left(a+ F(b,c,d) + x + ac, s) + b; +} + +inline void MD5Hash::GG(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { + a = rotate_left(a + G(b,c,d) + x + ac, s) + b; +} + +inline void MD5Hash::HH(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { + a = rotate_left(a + H(b,c,d) + x + ac, s) + b; +} + +inline void MD5Hash::II(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { + a = rotate_left(a + I(b,c,d) + x + ac, s) + b; +} + +////////////////////////////////////////////// + +// default ctor, just initailize +MD5Hash::MD5Hash() +{ + init(); +} + +////////////////////////////////////////////// + +// nifty shortcut ctor, compute MD5 for string and finalize it right away +MD5Hash::MD5Hash(const std::string &text) +{ + init(); + update(text.c_str(), text.length()); + finalize(); +} + +////////////////////////////// + +void MD5Hash::init() +{ + finalized=false; + + count[0] = 0; + count[1] = 0; + + // load magic initialization constants. + state[0] = 0x67452301; + state[1] = 0xefcdab89; + state[2] = 0x98badcfe; + state[3] = 0x10325476; +} + +////////////////////////////// + +// decodes input (unsigned char) into output (uint4). Assumes len is a multiple of 4. +void MD5Hash::decode(uint4 output[], const uint1 input[], size_type len) +{ + for (unsigned int i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((uint4)input[j]) | (((uint4)input[j+1]) << 8) | + (((uint4)input[j+2]) << 16) | (((uint4)input[j+3]) << 24); +} + +////////////////////////////// + +// encodes input (uint4) into output (unsigned char). Assumes len is +// a multiple of 4. +void MD5Hash::encode(uint1 output[], const uint4 input[], size_type len) +{ + for (size_type i = 0, j = 0; j < len; i++, j += 4) { + output[j] = input[i] & 0xff; + output[j+1] = (input[i] >> 8) & 0xff; + output[j+2] = (input[i] >> 16) & 0xff; + output[j+3] = (input[i] >> 24) & 0xff; + } +} + +////////////////////////////// + +// apply MD5 algo on a block +void MD5Hash::transform(const uint1 block[blocksize]) +{ + uint4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + decode (x, block, blocksize); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + // Zeroize sensitive information. + memset(x, 0, sizeof x); +} + +////////////////////////////// + +// MD5 block update operation. Continues an MD5 message-digest +// operation, processing another message block +void MD5Hash::update(const unsigned char input[], size_type length) +{ + // compute number of bytes mod 64 + size_type index = count[0] / 8 % blocksize; + + // Update number of bits + if ((count[0] += (length << 3)) < (length << 3)) + count[1]++; + count[1] += (length >> 29); + + // number of bytes we need to fill in buffer + size_type firstpart = 64 - index; + + size_type i; + + // transform as many times as possible. + if (length >= firstpart) + { + // fill buffer first, transform + memcpy(&buffer[index], input, firstpart); + transform(buffer); + + // transform chunks of blocksize (64 bytes) + for (i = firstpart; i + blocksize <= length; i += blocksize) + transform(&input[i]); + + index = 0; + } + else + i = 0; + + // buffer remaining input + memcpy(&buffer[index], &input[i], length-i); +} + +////////////////////////////// + +// for convenience provide a verson with signed char +void MD5Hash::update(const char input[], size_type length) +{ + update((const unsigned char*)input, length); +} + +////////////////////////////// + +// MD5 finalization. Ends an MD5 message-digest operation, writing the +// the message digest and zeroizing the context. +MD5Hash& MD5Hash::finalize() +{ + static unsigned char padding[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + if (!finalized) { + // Save number of bits + unsigned char bits[8]; + encode(bits, count, 8); + + // pad out to 56 mod 64. + size_type index = count[0] / 8 % 64; + size_type padLen = (index < 56) ? (56 - index) : (120 - index); + update(padding, padLen); + + // Append length (before padding) + update(bits, 8); + + // Store state in digest + encode(digest, state, 16); + + // Zeroize sensitive information. + memset(buffer, 0, sizeof buffer); + memset(count, 0, sizeof count); + + finalized=true; + } + + return *this; +} + +////////////////////////////// + +// return hex representation of digest as string +std::string MD5Hash::hexdigest() const +{ + if (!finalized) + return ""; + + char buf[33]; + for (int i=0; i<16; i++) + sprintf(buf+i*2, "%02x", digest[i]); + buf[32]=0; + + return std::string(buf); +} + +////////////////////////////// + +std::ostream& operator<<(std::ostream& out, MD5Hash md5) +{ + return out << md5.hexdigest(); +} + +////////////////////////////// + +std::string md5(const std::string str) +{ + MD5Hash md5 = MD5Hash(str); + + return md5.hexdigest(); +} \ No newline at end of file diff --git a/Verus/src/ThirdParty/md5.h b/Verus/src/ThirdParty/md5.h new file mode 100644 index 0000000..b8286d5 --- /dev/null +++ b/Verus/src/ThirdParty/md5.h @@ -0,0 +1,93 @@ +/* MD5 + converted to C++ class by Frank Thilo (thilo@unix-ag.org) + for bzflag (http://www.bzflag.org) + + based on: + + md5.h and md5.c + reference implementation of RFC 1321 + + Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + +*/ + +#ifndef BZF_MD5_H +#define BZF_MD5_H + +#include +#include + + +// a small class for calculating MD5 hashes of strings or byte arrays +// it is not meant to be fast or secure +// +// usage: 1) feed it blocks of uchars with update() +// 2) finalize() +// 3) get hexdigest() string +// or +// MD5(std::string).hexdigest() +// +// assumes that char is 8 bit and int is 32 bit +class MD5Hash +{ +public: + typedef unsigned int size_type; // must be 32bit + + MD5Hash(); + MD5Hash(const std::string& text); + void update(const unsigned char *buf, size_type length); + void update(const char *buf, size_type length); + MD5Hash& finalize(); + std::string hexdigest() const; + friend std::ostream& operator<<(std::ostream&, MD5Hash md5); + +private: + void init(); + typedef unsigned char uint1; // 8bit + typedef unsigned int uint4; // 32bit + enum {blocksize = 64}; // VC6 won't eat a const static int here + + void transform(const uint1 block[blocksize]); + static void decode(uint4 output[], const uint1 input[], size_type len); + static void encode(uint1 output[], const uint4 input[], size_type len); + + bool finalized; + uint1 buffer[blocksize]; // bytes that didn't fit in last 64 byte chunk + uint4 count[2]; // 64bit counter for number of bits (lo, hi) + uint4 state[4]; // digest so far + uint1 digest[16]; // the result + + // low level logic operations + static inline uint4 F(uint4 x, uint4 y, uint4 z); + static inline uint4 G(uint4 x, uint4 y, uint4 z); + static inline uint4 H(uint4 x, uint4 y, uint4 z); + static inline uint4 I(uint4 x, uint4 y, uint4 z); + static inline uint4 rotate_left(uint4 x, int n); + static inline void FF(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); + static inline void GG(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); + static inline void HH(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); + static inline void II(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); +}; + +std::string md5(const std::string str); + +#endif \ No newline at end of file diff --git a/Verus/src/ThirdParty/utf8.h b/Verus/src/ThirdParty/utf8.h new file mode 100644 index 0000000..82b13f5 --- /dev/null +++ b/Verus/src/ThirdParty/utf8.h @@ -0,0 +1,34 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include "utf8/checked.h" +#include "utf8/unchecked.h" + +#endif // header guard diff --git a/Verus/src/ThirdParty/utf8/checked.h b/Verus/src/ThirdParty/utf8/checked.h new file mode 100644 index 0000000..1331155 --- /dev/null +++ b/Verus/src/ThirdParty/utf8/checked.h @@ -0,0 +1,327 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include "core.h" +#include + +namespace utf8 +{ + // Base for the exceptions that may be thrown from the library + class exception : public ::std::exception { + }; + + // Exceptions that may be thrown from the library functions. + class invalid_code_point : public exception { + uint32_t cp; + public: + invalid_code_point(uint32_t cp) : cp(cp) {} + virtual const char* what() const throw() { return "Invalid code point"; } + uint32_t code_point() const {return cp;} + }; + + class invalid_utf8 : public exception { + uint8_t u8; + public: + invalid_utf8 (uint8_t u) : u8(u) {} + virtual const char* what() const throw() { return "Invalid UTF-8"; } + uint8_t utf8_octet() const {return u8;} + }; + + class invalid_utf16 : public exception { + uint16_t u16; + public: + invalid_utf16 (uint16_t u) : u16(u) {} + virtual const char* what() const throw() { return "Invalid UTF-16"; } + uint16_t utf16_word() const {return u16;} + }; + + class not_enough_room : public exception { + public: + virtual const char* what() const throw() { return "Not enough space"; } + }; + + /// The library API - functions intended to be called by the users + + template + octet_iterator append(uint32_t cp, octet_iterator result) + { + if (!utf8::internal::is_code_point_valid(cp)) + throw invalid_code_point(cp); + + if (cp < 0x80) // one octet + *(result++) = static_cast(cp); + else if (cp < 0x800) { // two octets + *(result++) = static_cast((cp >> 6) | 0xc0); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else if (cp < 0x10000) { // three octets + *(result++) = static_cast((cp >> 12) | 0xe0); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else { // four octets + *(result++) = static_cast((cp >> 18) | 0xf0); + *(result++) = static_cast(((cp >> 12) & 0x3f) | 0x80); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + return result; + } + + template + output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement) + { + while (start != end) { + octet_iterator sequence_start = start; + internal::utf_error err_code = utf8::internal::validate_next(start, end); + switch (err_code) { + case internal::UTF8_OK : + for (octet_iterator it = sequence_start; it != start; ++it) + *out++ = *it; + break; + case internal::NOT_ENOUGH_ROOM: + throw not_enough_room(); + case internal::INVALID_LEAD: + out = utf8::append (replacement, out); + ++start; + break; + case internal::INCOMPLETE_SEQUENCE: + case internal::OVERLONG_SEQUENCE: + case internal::INVALID_CODE_POINT: + out = utf8::append (replacement, out); + ++start; + // just one replacement mark for the sequence + while (start != end && utf8::internal::is_trail(*start)) + ++start; + break; + } + } + return out; + } + + template + inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) + { + static const uint32_t replacement_marker = utf8::internal::mask16(0xfffd); + return utf8::replace_invalid(start, end, out, replacement_marker); + } + + template + uint32_t next(octet_iterator& it, octet_iterator end) + { + uint32_t cp = 0; + internal::utf_error err_code = utf8::internal::validate_next(it, end, cp); + switch (err_code) { + case internal::UTF8_OK : + break; + case internal::NOT_ENOUGH_ROOM : + throw not_enough_room(); + case internal::INVALID_LEAD : + case internal::INCOMPLETE_SEQUENCE : + case internal::OVERLONG_SEQUENCE : + throw invalid_utf8(*it); + case internal::INVALID_CODE_POINT : + throw invalid_code_point(cp); + } + return cp; + } + + template + uint32_t peek_next(octet_iterator it, octet_iterator end) + { + return utf8::next(it, end); + } + + template + uint32_t prior(octet_iterator& it, octet_iterator start) + { + // can't do much if it == start + if (it == start) + throw not_enough_room(); + + octet_iterator end = it; + // Go back until we hit either a lead octet or start + while (utf8::internal::is_trail(*(--it))) + if (it == start) + throw invalid_utf8(*it); // error - no lead byte in the sequence + return utf8::peek_next(it, end); + } + + /// Deprecated in versions that include "prior" + template + uint32_t previous(octet_iterator& it, octet_iterator pass_start) + { + octet_iterator end = it; + while (utf8::internal::is_trail(*(--it))) + if (it == pass_start) + throw invalid_utf8(*it); // error - no lead byte in the sequence + octet_iterator temp = it; + return utf8::next(temp, end); + } + + template + void advance (octet_iterator& it, distance_type n, octet_iterator end) + { + for (distance_type i = 0; i < n; ++i) + utf8::next(it, end); + } + + template + typename std::iterator_traits::difference_type + distance (octet_iterator first, octet_iterator last) + { + typename std::iterator_traits::difference_type dist; + for (dist = 0; first < last; ++dist) + utf8::next(first, last); + return dist; + } + + template + octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) + { + while (start != end) { + uint32_t cp = utf8::internal::mask16(*start++); + // Take care of surrogate pairs first + if (utf8::internal::is_lead_surrogate(cp)) { + if (start != end) { + uint32_t trail_surrogate = utf8::internal::mask16(*start++); + if (utf8::internal::is_trail_surrogate(trail_surrogate)) + cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; + else + throw invalid_utf16(static_cast(trail_surrogate)); + } + else + throw invalid_utf16(static_cast(cp)); + + } + // Lone trail surrogate + else if (utf8::internal::is_trail_surrogate(cp)) + throw invalid_utf16(static_cast(cp)); + + result = utf8::append(cp, result); + } + return result; + } + + template + u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) + { + while (start != end) { + uint32_t cp = utf8::next(start, end); + if (cp > 0xffff) { //make a surrogate pair + *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); + *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); + } + else + *result++ = static_cast(cp); + } + return result; + } + + template + octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) + { + while (start != end) + result = utf8::append(*(start++), result); + + return result; + } + + template + u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) + { + while (start != end) + (*result++) = utf8::next(start, end); + + return result; + } + + // The iterator class + template + class iterator : public std::iterator { + octet_iterator it; + octet_iterator range_start; + octet_iterator range_end; + public: + iterator () {} + explicit iterator (const octet_iterator& octet_it, + const octet_iterator& range_start, + const octet_iterator& range_end) : + it(octet_it), range_start(range_start), range_end(range_end) + { + if (it < range_start || it > range_end) + throw std::out_of_range("Invalid utf-8 iterator position"); + } + // the default "big three" are OK + octet_iterator base () const { return it; } + uint32_t operator * () const + { + octet_iterator temp = it; + return utf8::next(temp, range_end); + } + bool operator == (const iterator& rhs) const + { + if (range_start != rhs.range_start || range_end != rhs.range_end) + throw std::logic_error("Comparing utf-8 iterators defined with different ranges"); + return (it == rhs.it); + } + bool operator != (const iterator& rhs) const + { + return !(operator == (rhs)); + } + iterator& operator ++ () + { + utf8::next(it, range_end); + return *this; + } + iterator operator ++ (int) + { + iterator temp = *this; + utf8::next(it, range_end); + return temp; + } + iterator& operator -- () + { + utf8::prior(it, range_start); + return *this; + } + iterator operator -- (int) + { + iterator temp = *this; + utf8::prior(it, range_start); + return temp; + } + }; // class iterator + +} // namespace utf8 + +#endif //header guard + + diff --git a/Verus/src/ThirdParty/utf8/core.h b/Verus/src/ThirdParty/utf8/core.h new file mode 100644 index 0000000..693d388 --- /dev/null +++ b/Verus/src/ThirdParty/utf8/core.h @@ -0,0 +1,329 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include + +namespace utf8 +{ + // The typedefs for 8-bit, 16-bit and 32-bit unsigned integers + // You may need to change them to match your system. + // These typedefs have the same names as ones from cstdint, or boost/cstdint + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; + +// Helper code - not intended to be directly called by the library users. May be changed at any time +namespace internal +{ + // Unicode constants + // Leading (high) surrogates: 0xd800 - 0xdbff + // Trailing (low) surrogates: 0xdc00 - 0xdfff + const uint16_t LEAD_SURROGATE_MIN = 0xd800u; + const uint16_t LEAD_SURROGATE_MAX = 0xdbffu; + const uint16_t TRAIL_SURROGATE_MIN = 0xdc00u; + const uint16_t TRAIL_SURROGATE_MAX = 0xdfffu; + const uint16_t LEAD_OFFSET = LEAD_SURROGATE_MIN - (0x10000 >> 10); + const uint32_t SURROGATE_OFFSET = 0x10000u - (LEAD_SURROGATE_MIN << 10) - TRAIL_SURROGATE_MIN; + + // Maximum valid value for a Unicode code point + const uint32_t CODE_POINT_MAX = 0x0010ffffu; + + template + inline uint8_t mask8(octet_type oc) + { + return static_cast(0xff & oc); + } + template + inline uint16_t mask16(u16_type oc) + { + return static_cast(0xffff & oc); + } + template + inline bool is_trail(octet_type oc) + { + return ((utf8::internal::mask8(oc) >> 6) == 0x2); + } + + template + inline bool is_lead_surrogate(u16 cp) + { + return (cp >= LEAD_SURROGATE_MIN && cp <= LEAD_SURROGATE_MAX); + } + + template + inline bool is_trail_surrogate(u16 cp) + { + return (cp >= TRAIL_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + } + + template + inline bool is_surrogate(u16 cp) + { + return (cp >= LEAD_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + } + + template + inline bool is_code_point_valid(u32 cp) + { + return (cp <= CODE_POINT_MAX && !utf8::internal::is_surrogate(cp)); + } + + template + inline typename std::iterator_traits::difference_type + sequence_length(octet_iterator lead_it) + { + uint8_t lead = utf8::internal::mask8(*lead_it); + if (lead < 0x80) + return 1; + else if ((lead >> 5) == 0x6) + return 2; + else if ((lead >> 4) == 0xe) + return 3; + else if ((lead >> 3) == 0x1e) + return 4; + else + return 0; + } + + template + inline bool is_overlong_sequence(uint32_t cp, octet_difference_type length) + { + if (cp < 0x80) { + if (length != 1) + return true; + } + else if (cp < 0x800) { + if (length != 2) + return true; + } + else if (cp < 0x10000) { + if (length != 3) + return true; + } + + return false; + } + + enum utf_error {UTF8_OK, NOT_ENOUGH_ROOM, INVALID_LEAD, INCOMPLETE_SEQUENCE, OVERLONG_SEQUENCE, INVALID_CODE_POINT}; + + /// Helper for get_sequence_x + template + utf_error increase_safely(octet_iterator& it, octet_iterator end) + { + if (++it == end) + return NOT_ENOUGH_ROOM; + + if (!utf8::internal::is_trail(*it)) + return INCOMPLETE_SEQUENCE; + + return UTF8_OK; + } + + #define UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(IT, END) {utf_error ret = increase_safely(IT, END); if (ret != UTF8_OK) return ret;} + + /// get_sequence_x functions decode utf-8 sequences of the length x + template + utf_error get_sequence_1(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + return UTF8_OK; + } + + template + utf_error get_sequence_2(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 6) & 0x7ff) + ((*it) & 0x3f); + + return UTF8_OK; + } + + template + utf_error get_sequence_3(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (*it) & 0x3f; + + return UTF8_OK; + } + + template + utf_error get_sequence_4(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (utf8::internal::mask8(*it) << 6) & 0xfff; + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (*it) & 0x3f; + + return UTF8_OK; + } + + #undef UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR + + template + utf_error validate_next(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + // Save the original value of it so we can go back in case of failure + // Of course, it does not make much sense with i.e. stream iterators + octet_iterator original_it = it; + + uint32_t cp = 0; + // Determine the sequence length based on the lead octet + typedef typename std::iterator_traits::difference_type octet_difference_type; + const octet_difference_type length = utf8::internal::sequence_length(it); + + // Get trail octets and calculate the code point + utf_error err = UTF8_OK; + switch (length) { + case 0: + return INVALID_LEAD; + case 1: + err = utf8::internal::get_sequence_1(it, end, cp); + break; + case 2: + err = utf8::internal::get_sequence_2(it, end, cp); + break; + case 3: + err = utf8::internal::get_sequence_3(it, end, cp); + break; + case 4: + err = utf8::internal::get_sequence_4(it, end, cp); + break; + } + + if (err == UTF8_OK) { + // Decoding succeeded. Now, security checks... + if (utf8::internal::is_code_point_valid(cp)) { + if (!utf8::internal::is_overlong_sequence(cp, length)){ + // Passed! Return here. + code_point = cp; + ++it; + return UTF8_OK; + } + else + err = OVERLONG_SEQUENCE; + } + else + err = INVALID_CODE_POINT; + } + + // Failure branch - restore the original value of the iterator + it = original_it; + return err; + } + + template + inline utf_error validate_next(octet_iterator& it, octet_iterator end) { + uint32_t ignored; + return utf8::internal::validate_next(it, end, ignored); + } + +} // namespace internal + + /// The library API - functions intended to be called by the users + + // Byte order mark + const uint8_t bom[] = {0xef, 0xbb, 0xbf}; + + template + octet_iterator find_invalid(octet_iterator start, octet_iterator end) + { + octet_iterator result = start; + while (result != end) { + utf8::internal::utf_error err_code = utf8::internal::validate_next(result, end); + if (err_code != internal::UTF8_OK) + return result; + } + return result; + } + + template + inline bool is_valid(octet_iterator start, octet_iterator end) + { + return (utf8::find_invalid(start, end) == end); + } + + template + inline bool starts_with_bom (octet_iterator it, octet_iterator end) + { + return ( + ((it != end) && (utf8::internal::mask8(*it++)) == bom[0]) && + ((it != end) && (utf8::internal::mask8(*it++)) == bom[1]) && + ((it != end) && (utf8::internal::mask8(*it)) == bom[2]) + ); + } + + //Deprecated in release 2.3 + template + inline bool is_bom (octet_iterator it) + { + return ( + (utf8::internal::mask8(*it++)) == bom[0] && + (utf8::internal::mask8(*it++)) == bom[1] && + (utf8::internal::mask8(*it)) == bom[2] + ); + } +} // namespace utf8 + +#endif // header guard + + diff --git a/Verus/src/ThirdParty/utf8/unchecked.h b/Verus/src/ThirdParty/utf8/unchecked.h new file mode 100644 index 0000000..cb24271 --- /dev/null +++ b/Verus/src/ThirdParty/utf8/unchecked.h @@ -0,0 +1,228 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include "core.h" + +namespace utf8 +{ + namespace unchecked + { + template + octet_iterator append(uint32_t cp, octet_iterator result) + { + if (cp < 0x80) // one octet + *(result++) = static_cast(cp); + else if (cp < 0x800) { // two octets + *(result++) = static_cast((cp >> 6) | 0xc0); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else if (cp < 0x10000) { // three octets + *(result++) = static_cast((cp >> 12) | 0xe0); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else { // four octets + *(result++) = static_cast((cp >> 18) | 0xf0); + *(result++) = static_cast(((cp >> 12) & 0x3f)| 0x80); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + return result; + } + + template + uint32_t next(octet_iterator& it) + { + uint32_t cp = utf8::internal::mask8(*it); + typename std::iterator_traits::difference_type length = utf8::internal::sequence_length(it); + switch (length) { + case 1: + break; + case 2: + it++; + cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f); + break; + case 3: + ++it; + cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); + ++it; + cp += (*it) & 0x3f; + break; + case 4: + ++it; + cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); + ++it; + cp += (utf8::internal::mask8(*it) << 6) & 0xfff; + ++it; + cp += (*it) & 0x3f; + break; + } + ++it; + return cp; + } + + template + uint32_t peek_next(octet_iterator it) + { + return utf8::unchecked::next(it); + } + + template + uint32_t prior(octet_iterator& it) + { + while (utf8::internal::is_trail(*(--it))) ; + octet_iterator temp = it; + return utf8::unchecked::next(temp); + } + + // Deprecated in versions that include prior, but only for the sake of consistency (see utf8::previous) + template + inline uint32_t previous(octet_iterator& it) + { + return utf8::unchecked::prior(it); + } + + template + void advance (octet_iterator& it, distance_type n) + { + for (distance_type i = 0; i < n; ++i) + utf8::unchecked::next(it); + } + + template + typename std::iterator_traits::difference_type + distance (octet_iterator first, octet_iterator last) + { + typename std::iterator_traits::difference_type dist; + for (dist = 0; first < last; ++dist) + utf8::unchecked::next(first); + return dist; + } + + template + octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) + { + while (start != end) { + uint32_t cp = utf8::internal::mask16(*start++); + // Take care of surrogate pairs first + if (utf8::internal::is_lead_surrogate(cp)) { + uint32_t trail_surrogate = utf8::internal::mask16(*start++); + cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; + } + result = utf8::unchecked::append(cp, result); + } + return result; + } + + template + u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) + { + while (start < end) { + uint32_t cp = utf8::unchecked::next(start); + if (cp > 0xffff) { //make a surrogate pair + *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); + *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); + } + else + *result++ = static_cast(cp); + } + return result; + } + + template + octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) + { + while (start != end) + result = utf8::unchecked::append(*(start++), result); + + return result; + } + + template + u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) + { + while (start < end) + (*result++) = utf8::unchecked::next(start); + + return result; + } + + // The iterator class + template + class iterator : public std::iterator { + octet_iterator it; + public: + iterator () {} + explicit iterator (const octet_iterator& octet_it): it(octet_it) {} + // the default "big three" are OK + octet_iterator base () const { return it; } + uint32_t operator * () const + { + octet_iterator temp = it; + return utf8::unchecked::next(temp); + } + bool operator == (const iterator& rhs) const + { + return (it == rhs.it); + } + bool operator != (const iterator& rhs) const + { + return !(operator == (rhs)); + } + iterator& operator ++ () + { + ::std::advance(it, utf8::internal::sequence_length(it)); + return *this; + } + iterator operator ++ (int) + { + iterator temp = *this; + ::std::advance(it, utf8::internal::sequence_length(it)); + return temp; + } + iterator& operator -- () + { + utf8::unchecked::prior(it); + return *this; + } + iterator operator -- (int) + { + iterator temp = *this; + utf8::unchecked::prior(it); + return temp; + } + }; // class iterator + + } // namespace utf8::unchecked +} // namespace utf8 + + +#endif // header guard + diff --git a/Verus/src/verus.cpp b/Verus/src/verus.cpp new file mode 100644 index 0000000..d0c9ed1 Binary files /dev/null and b/Verus/src/verus.cpp differ diff --git a/Verus/src/verus.h b/Verus/src/verus.h new file mode 100644 index 0000000..412af18 Binary files /dev/null and b/Verus/src/verus.h differ diff --git a/VerusTest/VerusTest.vcxproj b/VerusTest/VerusTest.vcxproj new file mode 100644 index 0000000..68b0de2 --- /dev/null +++ b/VerusTest/VerusTest.vcxproj @@ -0,0 +1,105 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {D7085182-35B9-4689-ADBA-B77087AD16BF} + Win32Proj + VerusTest + 10.0.16299.0 + + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + true + + + false + + + + Use + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + Create + Create + + + + + + {b154d670-e4b1-4d8a-885c-69546a5bd833} + + + + + + \ No newline at end of file diff --git a/VerusTest/VerusTest.vcxproj.filters b/VerusTest/VerusTest.vcxproj.filters new file mode 100644 index 0000000..d7e2a19 --- /dev/null +++ b/VerusTest/VerusTest.vcxproj.filters @@ -0,0 +1,33 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/VerusTest/main.cpp b/VerusTest/main.cpp new file mode 100644 index 0000000..0eabc92 Binary files /dev/null and b/VerusTest/main.cpp differ diff --git a/VerusTest/stdafx.cpp b/VerusTest/stdafx.cpp new file mode 100644 index 0000000..08343af Binary files /dev/null and b/VerusTest/stdafx.cpp differ diff --git a/VerusTest/stdafx.h b/VerusTest/stdafx.h new file mode 100644 index 0000000..b30c161 Binary files /dev/null and b/VerusTest/stdafx.h differ diff --git a/VerusTest/targetver.h b/VerusTest/targetver.h new file mode 100644 index 0000000..f0debec Binary files /dev/null and b/VerusTest/targetver.h differ diff --git a/Visualizers/verus.natvis b/Visualizers/verus.natvis new file mode 100644 index 0000000..a6641ca --- /dev/null +++ b/Visualizers/verus.natvis @@ -0,0 +1,111 @@ + + + + + {{x={x}}} + + x + + + + {{x={x} y={y}}} + + x + y + + + + {{x={x} y={y} z={z}}} + + x + y + z + + + + {{x={x} y={y} z={z} w={w}}} + + x + y + z + w + + + + mat4 T={value[3]} + + value[0] + value[1] + value[2] + value[3] + + + + {{(x={x} y={y} z={z}) w={w}}} + + x + y + z + w + + + + + + + + {{x={mVec128.m128_f32[0]} y={mVec128.m128_f32[1]} z={mVec128.m128_f32[2]}}} + + + + {{x={mVec128.m128_f32[0]} y={mVec128.m128_f32[1]} z={mVec128.m128_f32[2]} w={mVec128.m128_f32[3]}}} + + + + {{x={mVec128.m128_f32[0]} y={mVec128.m128_f32[1]} z={mVec128.m128_f32[2]}}} + + + + Matrix3 + + mCol0 + mCol1 + mCol2 + + + + + Matrix4 T={mCol3} + + mCol0 + mCol1 + mCol2 + mCol3 + + + + + Transform3 T={mCol3} + + mCol0 + mCol1 + mCol2 + mCol3 + + + + + {{(x={mVec128.m128_f32[0]} y={mVec128.m128_f32[1]} z={mVec128.m128_f32[2]}) w={mVec128.m128_f32[3]}}} + + + + + + empty + Ptr {*_p} + + _p + + + +