-
TombEngine 1.6 scripting interface
+
TombEngine 1.7 scripting interface
Welcome to the TombEngine scripting API. This is a work in progress and some information might be wrong or outdated. Please also note that this is primarily a reference document, not a tutorial, so expect descriptions to be fairly sparse.
At the time of writing, there is a tutorial describing the basics of Lua, as well as a number of example scripts, on the TombEngine website.
diff --git a/TombEngine/Renderer/RendererDrawMenu.cpp b/TombEngine/Renderer/RendererDrawMenu.cpp
index 6d9dd8641..a7d9cb809 100644
--- a/TombEngine/Renderer/RendererDrawMenu.cpp
+++ b/TombEngine/Renderer/RendererDrawMenu.cpp
@@ -34,6 +34,7 @@ namespace TEN::Renderer
constexpr auto MenuLoadNumberLeftSide = 80;
constexpr auto MenuLoadNameLeftSide = 150;
+ constexpr auto MenuLoadTimestampRightSide = 600;
// Vertical spacing templates
constexpr auto MenuVerticalLineSpacing = 30;
@@ -657,7 +658,7 @@ namespace TEN::Renderer
// Timestamp
sprintf(stringBuffer, g_GameFlow->GetString(STRING_SAVEGAME_TIMESTAMP), save.Hours, save.Minutes, save.Seconds);
- AddString(MenuRightSideEntry, y, stringBuffer, PRINTSTRING_COLOR_WHITE, SF(selection == n));
+ AddString(MenuLoadTimestampRightSide, y, stringBuffer, PRINTSTRING_COLOR_WHITE, SF(selection == n));
}
GetNextLinePosition(&y);
@@ -1227,7 +1228,7 @@ namespace TEN::Renderer
void Renderer::DrawDebugInfo(RenderView& view)
{
-#ifdef TEST_BUILD
+#if TEST_BUILD
if (CurrentLevel == 0)
{
AddString("TombEngine " + std::string(TEN_VERSION_STRING) + " test build - not for distribution",
diff --git a/TombEngine/version.h b/TombEngine/version.h
index 1f244287b..040d8d685 100644
--- a/TombEngine/version.h
+++ b/TombEngine/version.h
@@ -6,11 +6,11 @@
#define TE_REVISION_NUMBER 0
#define TEN_MAJOR_VERSION 1
-#define TEN_MINOR_VERSION 6
+#define TEN_MINOR_VERSION 7
#define TEN_BUILD_NUMBER 0
-#define TEN_REVISION_NUMBER 6
+#define TEN_REVISION_NUMBER 0
-#define TEST_BUILD 1
+#define TEST_BUILD 0
#define TOSTR(x) #x
#define MAKE_VERSION_STRING(major, minor, build, revision) TOSTR(major) "." TOSTR(minor) "." TOSTR(build) "." TOSTR(revision)
From 4e41bb97deb43bae3b53d2f58ca29a2c9643212a Mon Sep 17 00:00:00 2001
From: Jakub <80340234+Jakub768@users.noreply.github.com>
Date: Wed, 25 Dec 2024 00:30:06 +0000
Subject: [PATCH 16/26] Update CHANGELOG.md
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fb93aac62..b9ff56c8f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,7 +3,7 @@
The dates are in European standard format where date is presented as **YYYY-MM-DD**.
TombEngine releases are located in this repository (alongside with Tomb Editor): https://github.com/TombEngine/TombEditorReleases
-## Version 1.6 - xxxx-xx-xx
+## Version 1.7 - xxxx-xx-xx
### Bug fixes
* Significantly improved renderer performance.
From 222177f45b2a312377457f5ccaef2600e78ae64b Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Wed, 25 Dec 2024 11:21:07 +0300
Subject: [PATCH 17/26] Mirrors (#1519)
* WIP TR4 style mirrors
* Fixed broken previous commit
* Added debris and effects drawing in mirrored rooms;
Some optimizations for early skip non mirrored items;
* Implemented mirrors in file format
* Use default argument value for ReadCount
* Use emplace_back and camera room number
* Fixed items culling in mirror rooms;
Changed file format for having also virtual room;
* Cached reflection matrix of mirrors at load time;
Fixed lighting in mirrored items;
Added bad cull mode after mirrore debrises drawing;
Added mirroring of dynamic lights;
* Fixed ambient occlusion in mirrored rooms
* Fixed random statics positions while shooting
* Added logic for hiding selectively reflecting moveables and statics;
Added enabled flag for disabling mirrros in the future with LUA;
* Simplify renderer code, remove excessive if statements
* Update level.cpp
* Decopypaste more mirror-related code
* More decopypasting, rename inline function, remove unneeded arg
* Remove unnecessary inline, dont reflect room lights
* Fixed billboards, reintroduce ReflectVectorOptionally
* Fixed dynamic lights
* Update RendererDraw.cpp
* Fixed 3D sprites mirroring
* Draw reflections also when Lara is in any of mirrored rooms, not only camera
* Remove unrelated changes
* Remove lambdas
* Rename fields for consistency
* Reorganize renderer code a little
* Rename
* Spawn mirrored dynamic lights in Lara rooms too
* Update RendererDraw.cpp
* Add option to reflect sprites
* Update CHANGELOG.md
* Fixed mirrors on savegame reloading
* Fix Lara not mirroring when using binoculars
* Continuation of previous fix
* Fixed 3D non-sorted sprites mirroring
* Fixed various binocular / lasersight issues
* Fix meshswap, if object is not present
* Don't perform binocular animations if binocular meshswap is not present
* Rename CamOldPos and use more consistent type for it
* Update title.bin
* Fixed #1521
* Fixed #1522
* Interpolate Lara flare light, hide target highlighters in binos
* Fix #1525
* Fixed postprocessing order and precompile shaders to speed-up loading
* Fixed #1524
* Fixed silent crash if several Lara objects are present in level near dynamic light with shadow
* Implement proper soft shadows
* Disable self-shadowing for now, as it's causing visual glitches
* Update RendererInit.cpp
* Update CHANGELOG.md
* Update CHANGELOG.md
* Formatting pass
* Revert "Formatting pass"
This reverts commit 88a8ba24dea9cf7006920c8d7a52b6d047f6d749.
* Reapply "Formatting pass"
This reverts commit becd24da93a2eb33bf78b9be97f6dc6b25d6e744.
* Update RendererFrame.cpp
* Final formatting pass
---------
Co-authored-by: MontyTRC89
Co-authored-by: Jakub <80340234+Jakub768@users.noreply.github.com>
Co-authored-by: Sezz
---
CHANGELOG.md | 6 +-
Documentation/doc/4 enums/Objects.ObjID.html | 2 +-
Documentation/doc/ldoc.css | 480 ++++++-------
Scripts/Gameflow.lua | 2 +-
TombEngine/Game/Hud/TargetHighlighter.cpp | 8 +-
TombEngine/Game/Lara/lara_fire.cpp | 2 +-
TombEngine/Game/Lara/lara_flare.cpp | 3 +-
TombEngine/Game/Lara/lara_helpers.cpp | 107 +--
TombEngine/Game/Lara/lara_optics.cpp | 286 ++++++++
TombEngine/Game/Lara/lara_optics.h | 10 +
TombEngine/Game/camera.cpp | 62 +-
TombEngine/Game/control/los.cpp | 7 +-
TombEngine/Game/effects/effects.cpp | 2 +-
TombEngine/Game/effects/effects.h | 3 +
TombEngine/Game/gui.cpp | 24 +-
.../Objects/Generic/Object/burning_torch.cpp | 27 -
.../Objects/Generic/Object/burning_torch.h | 1 -
TombEngine/Objects/TR5/Trap/LaserBeam.cpp | 2 -
TombEngine/Objects/game_object_ids.h | 2 +-
TombEngine/Renderer/RenderView.cpp | 1 +
TombEngine/Renderer/RenderView.h | 16 +-
TombEngine/Renderer/Renderer.cpp | 11 +-
TombEngine/Renderer/Renderer.h | 139 ++--
TombEngine/Renderer/RendererAntialiasing.cpp | 18 +-
TombEngine/Renderer/RendererDraw.cpp | 662 ++++++++++--------
TombEngine/Renderer/RendererDraw2D.cpp | 28 +-
TombEngine/Renderer/RendererDrawEffect.cpp | 64 +-
TombEngine/Renderer/RendererDrawMenu.cpp | 8 +-
TombEngine/Renderer/RendererEnums.h | 1 +
TombEngine/Renderer/RendererFrame.cpp | 130 ++--
TombEngine/Renderer/RendererHelper.cpp | 12 +
TombEngine/Renderer/RendererInit.cpp | 277 ++++++--
TombEngine/Renderer/RendererLara.cpp | 48 +-
TombEngine/Renderer/RendererPostProcess.cpp | 92 ++-
TombEngine/Renderer/RendererSprites.cpp | 24 +-
TombEngine/Renderer/RendererUtils.cpp | 123 +---
TombEngine/Renderer/RendererUtils.h | 5 -
.../Renderer/Structures/RendererMirror.h | 17 +
.../Renderer/Structures/RendererShader.h | 41 ++
TombEngine/Resources/title.bin | Bin 105784 -> 80388 bytes
.../Internal/TEN/Objects/ObjectIDs.h | 4 +-
TombEngine/Shaders/{HUD.hlsl => HUD.fx} | 0
TombEngine/Shaders/Items.fx | 4 +-
TombEngine/Shaders/Math.hlsli | 2 +
TombEngine/Shaders/PostProcess.fx | 2 +-
TombEngine/Shaders/SMAA.fx | 52 +-
TombEngine/Shaders/Shadows.hlsli | 29 +-
TombEngine/Specific/level.cpp | 31 +-
TombEngine/Specific/level.h | 16 +
TombEngine/TombEngine.vcxproj | 12 +-
50 files changed, 1656 insertions(+), 1249 deletions(-)
create mode 100644 TombEngine/Game/Lara/lara_optics.cpp
create mode 100644 TombEngine/Game/Lara/lara_optics.h
create mode 100644 TombEngine/Renderer/Structures/RendererMirror.h
create mode 100644 TombEngine/Renderer/Structures/RendererShader.h
rename TombEngine/Shaders/{HUD.hlsl => HUD.fx} (100%)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b9ff56c8f..551a93d26 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
* Significantly improved renderer performance.
* Improved engine performance around bridges.
* Improved engine performance if weather or bubble effects are active.
+* Improved engine start-up time.
* Fixed silent crashes if loaded level is corrupted or in incorrect format.
* Fixed occasional crashes if there are static meshes placed within room border walls.
* Fixed climbable pushables clipping Lara under the bridges when pulled.
@@ -30,6 +31,8 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
* Fixed incorrect object camera position.
* Fixed incorrect camera movement near walls after leaving look mode.
* Fixed binocular or lasersight camera not switching off correctly after flyby.
+* Fixed binocular or lasersight camera transitions.
+* Fixed target highlighter still being active in binocular or lasersight mode.
* Fixed Lara's Home entry not working.
* Fixed exploding TR3 bosses.
* Fixed original issue with deactivation of Dart Emitter.
@@ -41,9 +44,10 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
* Fixed Skeleton and Mummy not reacting to shotgun hits.
### New Features
-* Added fast savegame reloading.
+* Added classic mirror effect with ability to reflect moveables and static meshes.
* Added ability to customize many hardcoded parameters, such as flare, weapon, and hair settings.
* Added dynamic shadow casting on objects and static meshes.
+* Added fast savegame reloading.
* Added ricochet sounds and make the effect more prominent.
* Allow camera shake during flybys.
* Allow to run the engine without title level.
diff --git a/Documentation/doc/4 enums/Objects.ObjID.html b/Documentation/doc/4 enums/Objects.ObjID.html
index 91be24388..31d15d280 100644
--- a/Documentation/doc/4 enums/Objects.ObjID.html
+++ b/Documentation/doc/4 enums/Objects.ObjID.html
@@ -170,9 +170,9 @@ ACTOR1_SPEECH_HEAD1
ACTOR1_SPEECH_HEAD2
ACTOR2_SPEECH_HEAD1
ACTOR2_SPEECH_HEAD2
+LARA_BINOCULARS_MESH
LARA_EXTRA_MESH1
LARA_EXTRA_MESH2
-LARA_EXTRA_MESH3
LARA_WATER_MESH
LARA_PETROL_MESH
LARA_DIRT_MESH
diff --git a/Documentation/doc/ldoc.css b/Documentation/doc/ldoc.css
index 306dcd9f5..b00cfcf57 100644
--- a/Documentation/doc/ldoc.css
+++ b/Documentation/doc/ldoc.css
@@ -1,240 +1,240 @@
-
-/* styles for prettification of source */
-pre .comment { color: #558817; }
-pre .constant { color: #a8660d; }
-pre .escape { color: #844631; }
-pre .keyword { color: #aa5050; font-weight: bold; }
-pre .library { color: #0e7c6b; }
-pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; }
-pre .string { color: #8080ff; }
-pre .number { color: #f8660d; }
-pre .operator { color: #2239a8; font-weight: bold; }
-pre .preprocessor, pre .prepro { color: #a33243; }
-pre .global { color: #800080; }
-pre .user-keyword { color: #800080; }
-pre .prompt { color: #558817; }
-pre .url { color: #272fc2; text-decoration: underline; }
-
-body {
- margin-left: 1em;
- margin-right: 1em;
- font-family: arial, helvetica, geneva, sans-serif;
- background-color: #ffffff; margin: 0px;
-}
-
-code, tt { font-family: monospace; font-size: 1.1em; }
-span.parameter { font-family:monospace; }
-span.parameter:after { content:":"; }
-span.types:before { content:"("; }
-span.types:after { content:")"; }
-.type { font-weight: bold; font-style:italic }
-
-body, td, th { font-size: .95em; line-height: 1.2em;}
-p { line-height: 1.2em;}
-
-p, ul { margin: 10px 0 0 0px;}
-
-strong { font-weight: bold;}
-
-here { font-weight: bold; color: #c8751d;}
-
-em { font-style: italic;}
-
-h1 {
- font-size: 1.5em;
- margin: 20px 0 20px 0;
-}
-h2, h3, h4 { margin: 15px 0 10px 0; }
-h2 { font-size: 1.25em; }
-h3 { font-size: 1.15em; }
-h4 { font-size: 1.06em; }
-
-a:link { font-weight: bold; color: #004080; text-decoration: none; }
-a:visited { font-weight: bold; color: #006699; text-decoration: none; }
-a:link:hover { text-decoration: underline; }
-
-hr {
- color:#cccccc;
- background: #00007f;
- height: 1px;
-}
-
-blockquote { margin-left: 3em; }
-
-ul { list-style-type: disc; }
-
-p.name {
- font-family: "Andale Mono", monospace;
- padding-top: 1em;
-}
-
-pre {
- background-color: rgb(245, 245, 245);
- border: 1px solid #C0C0C0; /* silver */
- padding: 10px;
- margin: 10px 0 10px 0;
- overflow: auto;
- font-family: "Andale Mono", monospace;
-}
-
-pre.example {
- font-size: .85em;
-}
-
-table.index { border: 1px #00007f; }
-table.index td { text-align: left; vertical-align: top; }
-
-#container {
- margin-left: 1em;
- margin-right: 1em;
- background-color: #f0f0f0;
-}
-
-#product {
- text-align: center;
- border-bottom: 1px solid #cccccc;
- background-color: #ffffff;
-}
-
-#product big {
- font-size: 2em;
-}
-
-#main {
- background-color: #f0f0f0;
- border-left: 2px solid #cccccc;
-}
-
-#navigation {
- float: left;
- width: 16em;
- vertical-align: top;
- background-color: #f0f0f0;
- overflow: visible;
-}
-
-#navigation h2 {
- background-color:#e7e7e7;
- font-size:1.1em;
- color:#000000;
- text-align: left;
- padding:0.2em;
- border-top:1px solid #dddddd;
- border-bottom:1px solid #dddddd;
-}
-
-#navigation ul
-{
- font-size:1em;
- list-style-type: none;
- margin: 1px 1px 10px 1px;
-}
-
-#navigation li {
- text-indent: -1em;
- display: block;
- margin: 3px 0px 0px 22px;
-}
-
-#navigation li li a {
- margin: 0px 3px 0px -1em;
-}
-
-#content {
- margin-left: 18em;
- padding: 2em;
- width: 900px;
- border-left: 2px solid #cccccc;
- border-right: 2px solid #cccccc;
- background-color: #ffffff;
-}
-
-#about {
- clear: both;
- padding: 5px;
- border-top: 2px solid #cccccc;
- background-color: #ffffff;
-}
-
-@media print {
- body {
- font: 12pt "Times New Roman", "TimeNR", Times, serif;
- }
- a { font-weight: bold; color: #004080; text-decoration: underline; }
-
- #main {
- background-color: #ffffff;
- border-left: 0px;
- }
-
- #container {
- margin-left: 2%;
- margin-right: 2%;
- background-color: #ffffff;
- }
-
- #content {
- padding: 1em;
- background-color: #ffffff;
- }
-
- #navigation {
- display: none;
- }
- pre.example {
- font-family: "Andale Mono", monospace;
- font-size: 10pt;
- page-break-inside: avoid;
- }
-}
-
-table.module_list {
- border-width: 1px;
- border-style: solid;
- border-color: #cccccc;
- border-collapse: collapse;
-}
-table.module_list td {
- border-width: 1px;
- padding: 3px;
- border-style: solid;
- border-color: #cccccc;
-}
-table.module_list td.name { background-color: #f0f0f0; min-width: 250px; }
-table.module_list td.summary { width: 100%; }
-
-
-table.function_list {
- border-width: 1px;
- border-style: solid;
- border-color: #cccccc;
- border-collapse: collapse;
-}
-table.function_list td {
- border-width: 1px;
- padding: 3px;
- border-style: solid;
- border-color: #cccccc;
-}
-table.function_list td.name { background-color: #f0f0f0; min-width: 250px; }
-table.function_list td.summary { width: 100%; }
-
-ul.nowrap {
- white-space:nowrap;
-}
-
-dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;}
-dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;}
-dl.table h3, dl.function h3 {font-size: .95em;}
-
-/* stop sublists from having initial vertical space */
-ul ul { margin-top: 0px; }
-ol ul { margin-top: 0px; }
-ol ol { margin-top: 0px; }
-ul ol { margin-top: 0px; }
-
-/* make the target distinct; helps when we're navigating to a function */
-a:target + * {
- background-color: #FF9;
-}
-
+
+/* styles for prettification of source */
+pre .comment { color: #558817; }
+pre .constant { color: #a8660d; }
+pre .escape { color: #844631; }
+pre .keyword { color: #aa5050; font-weight: bold; }
+pre .library { color: #0e7c6b; }
+pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; }
+pre .string { color: #8080ff; }
+pre .number { color: #f8660d; }
+pre .operator { color: #2239a8; font-weight: bold; }
+pre .preprocessor, pre .prepro { color: #a33243; }
+pre .global { color: #800080; }
+pre .user-keyword { color: #800080; }
+pre .prompt { color: #558817; }
+pre .url { color: #272fc2; text-decoration: underline; }
+
+body {
+ margin-left: 1em;
+ margin-right: 1em;
+ font-family: arial, helvetica, geneva, sans-serif;
+ background-color: #ffffff; margin: 0px;
+}
+
+code, tt { font-family: monospace; font-size: 1.1em; }
+span.parameter { font-family:monospace; }
+span.parameter:after { content:":"; }
+span.types:before { content:"("; }
+span.types:after { content:")"; }
+.type { font-weight: bold; font-style:italic }
+
+body, td, th { font-size: .95em; line-height: 1.2em;}
+p { line-height: 1.2em;}
+
+p, ul { margin: 10px 0 0 0px;}
+
+strong { font-weight: bold;}
+
+here { font-weight: bold; color: #c8751d;}
+
+em { font-style: italic;}
+
+h1 {
+ font-size: 1.5em;
+ margin: 20px 0 20px 0;
+}
+h2, h3, h4 { margin: 15px 0 10px 0; }
+h2 { font-size: 1.25em; }
+h3 { font-size: 1.15em; }
+h4 { font-size: 1.06em; }
+
+a:link { font-weight: bold; color: #004080; text-decoration: none; }
+a:visited { font-weight: bold; color: #006699; text-decoration: none; }
+a:link:hover { text-decoration: underline; }
+
+hr {
+ color:#cccccc;
+ background: #00007f;
+ height: 1px;
+}
+
+blockquote { margin-left: 3em; }
+
+ul { list-style-type: disc; }
+
+p.name {
+ font-family: "Andale Mono", monospace;
+ padding-top: 1em;
+}
+
+pre {
+ background-color: rgb(245, 245, 245);
+ border: 1px solid #C0C0C0; /* silver */
+ padding: 10px;
+ margin: 10px 0 10px 0;
+ overflow: auto;
+ font-family: "Andale Mono", monospace;
+}
+
+pre.example {
+ font-size: .85em;
+}
+
+table.index { border: 1px #00007f; }
+table.index td { text-align: left; vertical-align: top; }
+
+#container {
+ margin-left: 1em;
+ margin-right: 1em;
+ background-color: #f0f0f0;
+}
+
+#product {
+ text-align: center;
+ border-bottom: 1px solid #cccccc;
+ background-color: #ffffff;
+}
+
+#product big {
+ font-size: 2em;
+}
+
+#main {
+ background-color: #f0f0f0;
+ border-left: 2px solid #cccccc;
+}
+
+#navigation {
+ float: left;
+ width: 16em;
+ vertical-align: top;
+ background-color: #f0f0f0;
+ overflow: visible;
+}
+
+#navigation h2 {
+ background-color:#e7e7e7;
+ font-size:1.1em;
+ color:#000000;
+ text-align: left;
+ padding:0.2em;
+ border-top:1px solid #dddddd;
+ border-bottom:1px solid #dddddd;
+}
+
+#navigation ul
+{
+ font-size:1em;
+ list-style-type: none;
+ margin: 1px 1px 10px 1px;
+}
+
+#navigation li {
+ text-indent: -1em;
+ display: block;
+ margin: 3px 0px 0px 22px;
+}
+
+#navigation li li a {
+ margin: 0px 3px 0px -1em;
+}
+
+#content {
+ margin-left: 18em;
+ padding: 2em;
+ width: 900px;
+ border-left: 2px solid #cccccc;
+ border-right: 2px solid #cccccc;
+ background-color: #ffffff;
+}
+
+#about {
+ clear: both;
+ padding: 5px;
+ border-top: 2px solid #cccccc;
+ background-color: #ffffff;
+}
+
+@media print {
+ body {
+ font: 12pt "Times New Roman", "TimeNR", Times, serif;
+ }
+ a { font-weight: bold; color: #004080; text-decoration: underline; }
+
+ #main {
+ background-color: #ffffff;
+ border-left: 0px;
+ }
+
+ #container {
+ margin-left: 2%;
+ margin-right: 2%;
+ background-color: #ffffff;
+ }
+
+ #content {
+ padding: 1em;
+ background-color: #ffffff;
+ }
+
+ #navigation {
+ display: none;
+ }
+ pre.example {
+ font-family: "Andale Mono", monospace;
+ font-size: 10pt;
+ page-break-inside: avoid;
+ }
+}
+
+table.module_list {
+ border-width: 1px;
+ border-style: solid;
+ border-color: #cccccc;
+ border-collapse: collapse;
+}
+table.module_list td {
+ border-width: 1px;
+ padding: 3px;
+ border-style: solid;
+ border-color: #cccccc;
+}
+table.module_list td.name { background-color: #f0f0f0; min-width: 250px; }
+table.module_list td.summary { width: 100%; }
+
+
+table.function_list {
+ border-width: 1px;
+ border-style: solid;
+ border-color: #cccccc;
+ border-collapse: collapse;
+}
+table.function_list td {
+ border-width: 1px;
+ padding: 3px;
+ border-style: solid;
+ border-color: #cccccc;
+}
+table.function_list td.name { background-color: #f0f0f0; min-width: 250px; }
+table.function_list td.summary { width: 100%; }
+
+ul.nowrap {
+ white-space:nowrap;
+}
+
+dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;}
+dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;}
+dl.table h3, dl.function h3 {font-size: .95em;}
+
+/* stop sublists from having initial vertical space */
+ul ul { margin-top: 0px; }
+ol ul { margin-top: 0px; }
+ol ol { margin-top: 0px; }
+ul ol { margin-top: 0px; }
+
+/* make the target distinct; helps when we're navigating to a function */
+a:target + * {
+ background-color: #FF9;
+}
+
diff --git a/Scripts/Gameflow.lua b/Scripts/Gameflow.lua
index 2b1f2f0f3..4467179a4 100644
--- a/Scripts/Gameflow.lua
+++ b/Scripts/Gameflow.lua
@@ -57,7 +57,7 @@ test = Level.new()
test.nameKey = "level_test"
test.scriptFile = "Scripts\\Levels\\New_Level.lua"
test.ambientTrack = "108"
-test.levelFile = "Data\\TestLevel.ten"
+test.levelFile = "Data\\Tut1_water.ten"
test.loadScreenFile = "Screens\\rome.jpg"
-- 0 is no weather, 1 is rain, 2 is snow.
diff --git a/TombEngine/Game/Hud/TargetHighlighter.cpp b/TombEngine/Game/Hud/TargetHighlighter.cpp
index dcbf0c4aa..a1d895216 100644
--- a/TombEngine/Game/Hud/TargetHighlighter.cpp
+++ b/TombEngine/Game/Hud/TargetHighlighter.cpp
@@ -171,8 +171,10 @@ namespace TEN::Hud
void TargetHighlighterController::Update(const ItemInfo& playerItem)
{
- // Check if target highlighter is enabled.
- if (!g_Configuration.EnableTargetHighlighter)
+ const auto& player = GetLaraInfo(playerItem);
+
+ // Check if target highlighter is enabled or lasersight is active.
+ if (!g_Configuration.EnableTargetHighlighter || player.Control.Look.IsUsingBinoculars)
{
if (!_crosshairs.empty())
_crosshairs.clear();
@@ -180,8 +182,6 @@ namespace TEN::Hud
return;
}
- const auto& player = GetLaraInfo(playerItem);
-
// Loop over player targets.
auto itemNumbers = std::vector{};
for (const auto* item : player.TargetList)
diff --git a/TombEngine/Game/Lara/lara_fire.cpp b/TombEngine/Game/Lara/lara_fire.cpp
index bbaeb03ff..fd4fbc5ae 100644
--- a/TombEngine/Game/Lara/lara_fire.cpp
+++ b/TombEngine/Game/Lara/lara_fire.cpp
@@ -572,7 +572,7 @@ void HandleWeapon(ItemInfo& laraItem)
player.Control.HandStatus = HandStatus::WeaponUndraw;
}
}
- else if (player.Inventory.TotalFlares)
+ else if (player.Inventory.TotalFlares && !player.Control.Look.IsUsingBinoculars)
{
if (player.Inventory.TotalFlares != -1)
player.Inventory.TotalFlares--;
diff --git a/TombEngine/Game/Lara/lara_flare.cpp b/TombEngine/Game/Lara/lara_flare.cpp
index 0e99f0e11..219952047 100644
--- a/TombEngine/Game/Lara/lara_flare.cpp
+++ b/TombEngine/Game/Lara/lara_flare.cpp
@@ -20,6 +20,7 @@
#include "Sound/sound.h"
#include "Specific/clock.h"
#include "Specific/level.h"
+#include "Specific/trutils.h"
using namespace TEN::Collision::Point;
using namespace TEN::Entities::Effects;
@@ -496,7 +497,7 @@ bool DoFlareLight(ItemInfo& item, const Vector3i& pos, int flareLife)
auto color = (flareColor * intensity * std::clamp(mult, 0.0f, 1.0f));
// Spawn dynamic light.
- TriggerDynamicPointLight(lightPos, Color(color), falloff, false);
+ TriggerDynamicPointLight(lightPos, Color(color), falloff, false, GetHash(item.Name));
// Spawn lensflare if brightness is not 0.
if (settings.LensflareBrightness > EPSILON)
diff --git a/TombEngine/Game/Lara/lara_helpers.cpp b/TombEngine/Game/Lara/lara_helpers.cpp
index 288703527..2c92a6fbf 100644
--- a/TombEngine/Game/Lara/lara_helpers.cpp
+++ b/TombEngine/Game/Lara/lara_helpers.cpp
@@ -448,70 +448,6 @@ static void ClearPlayerLookAroundActions(const ItemInfo& item)
}
}
-static void SetPlayerOptics(ItemInfo* item)
-{
- constexpr auto OPTIC_RANGE_DEFAULT = ANGLE(0.7f);
-
- auto& player = GetLaraInfo(*item);
-
- bool breakOptics = true;
-
- // Standing; can use optics.
- if (item->Animation.ActiveState == LS_IDLE || item->Animation.AnimNumber == LA_STAND_IDLE)
- breakOptics = false;
-
- // Crouching; can use optics.
- if ((player.Control.IsLow || !IsHeld(In::Crouch)) &&
- (item->Animation.TargetState == LS_CROUCH_IDLE || item->Animation.AnimNumber == LA_CROUCH_IDLE))
- {
- breakOptics = false;
- }
-
- // If lasersight and Look is not held, exit optics.
- if (player.Control.Look.IsUsingLasersight && !IsHeld(In::Look))
- breakOptics = true;
-
- // If lasersight and weapon is holstered, exit optics.
- if (player.Control.Look.IsUsingLasersight && IsHeld(In::Draw))
- breakOptics = true;
-
- // Engage lasersight if available.
- if (!player.Control.Look.IsUsingLasersight && !breakOptics && IsHeld(In::Look))
- {
- if (player.Control.HandStatus == HandStatus::WeaponReady &&
- ((player.Control.Weapon.GunType == LaraWeaponType::HK && player.Weapons[(int)LaraWeaponType::HK].HasLasersight) ||
- (player.Control.Weapon.GunType == LaraWeaponType::Revolver && player.Weapons[(int)LaraWeaponType::Revolver].HasLasersight) ||
- (player.Control.Weapon.GunType == LaraWeaponType::Crossbow && player.Weapons[(int)LaraWeaponType::Crossbow].HasLasersight)))
- {
- player.Control.Look.OpticRange = OPTIC_RANGE_DEFAULT;
- player.Control.Look.IsUsingBinoculars = true;
- player.Control.Look.IsUsingLasersight = true;
- player.Inventory.IsBusy = true;
-
- Camera.DisableInterpolation = true;
- BinocularOldCamera = Camera.oldType;
- return;
- }
- }
-
- if (!breakOptics)
- return;
-
- // Not using optics; return early.
- if (!player.Control.Look.IsUsingBinoculars && !player.Control.Look.IsUsingLasersight)
- return;
-
- player.Control.Look.OpticRange = 0;
- player.Control.Look.IsUsingBinoculars = false;
- player.Control.Look.IsUsingLasersight = false;
- player.Inventory.IsBusy = false;
-
- Camera.DisableInterpolation = true;
- Camera.type = BinocularOldCamera;
- Camera.bounce = 0;
- AlterFOV(LastFOV);
-}
-
static short NormalizeLookAroundTurnRate(short turnRate, short opticRange)
{
constexpr auto ZOOM_LEVEL_MAX = ANGLE(10.0f);
@@ -525,54 +461,15 @@ static short NormalizeLookAroundTurnRate(short turnRate, short opticRange)
void HandlePlayerLookAround(ItemInfo& item, bool invertXAxis)
{
- constexpr auto OPTIC_RANGE_MAX = ANGLE(8.5f);
- constexpr auto OPTIC_RANGE_MIN = ANGLE(0.7f);
- constexpr auto OPTIC_RANGE_RATE = ANGLE(0.35f);
- constexpr auto TURN_RATE_MAX = ANGLE(4.0f);
- constexpr auto TURN_RATE_ACCEL = ANGLE(0.75f);
+ constexpr auto TURN_RATE_MAX = ANGLE(4.0f);
+ constexpr auto TURN_RATE_ACCEL = ANGLE(0.75f);
auto& player = GetLaraInfo(item);
// Set optics.
Camera.type = CameraType::Look;
- SetPlayerOptics(LaraItem);
bool isSlow = IsHeld(In::Walk);
-
- // Zoom optics.
- if (player.Control.Look.IsUsingBinoculars || player.Control.Look.IsUsingLasersight)
- {
- short rangeRate = isSlow ? (OPTIC_RANGE_RATE / 2) : OPTIC_RANGE_RATE;
-
- // NOTE: Zooming allowed with either StepLeft/StepRight or Walk/Sprint.
- if ((IsHeld(In::StepLeft) && !IsHeld(In::StepRight)) ||
- (IsHeld(In::Walk) && !IsHeld(In::Sprint)))
- {
- player.Control.Look.OpticRange -= rangeRate;
- if (player.Control.Look.OpticRange < OPTIC_RANGE_MIN)
- {
- player.Control.Look.OpticRange = OPTIC_RANGE_MIN;
- }
- else
- {
- SoundEffect(SFX_TR4_BINOCULARS_ZOOM, nullptr, SoundEnvironment::Land, 0.9f);
- }
- }
- else if ((IsHeld(In::StepRight) && !IsHeld(In::StepLeft)) ||
- (IsHeld(In::Sprint) && !IsHeld(In::Walk)))
- {
- player.Control.Look.OpticRange += rangeRate;
- if (player.Control.Look.OpticRange > OPTIC_RANGE_MAX)
- {
- player.Control.Look.OpticRange = OPTIC_RANGE_MAX;
- }
- else
- {
- SoundEffect(SFX_TR4_BINOCULARS_ZOOM, nullptr, SoundEnvironment::Land, 1.0f);
- }
- }
- }
-
auto axisCoeff = Vector2::Zero;
// Determine X axis coefficient.
diff --git a/TombEngine/Game/Lara/lara_optics.cpp b/TombEngine/Game/Lara/lara_optics.cpp
new file mode 100644
index 000000000..dc3600c5f
--- /dev/null
+++ b/TombEngine/Game/Lara/lara_optics.cpp
@@ -0,0 +1,286 @@
+#include "framework.h"
+#include "Game/Lara/lara_optics.h"
+
+#include "Game/camera.h"
+#include "Game/control/los.h"
+#include "Game/effects/effects.h"
+#include "Game/items.h"
+#include "Game/Lara/lara.h"
+#include "Game/Lara/lara_flare.h"
+#include "Game/Lara/lara_helpers.h"
+#include "Game/Lara/lara_one_gun.h"
+#include "Game/Lara/lara_struct.h"
+#include "Game/Lara/lara_two_guns.h"
+#include "Game/Setup.h"
+#include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h"
+#include "Specific/Input/Input.h"
+
+using namespace TEN::Input;
+
+static void HandlePlayerOpticZoom(ItemInfo& item)
+{
+ constexpr auto OPTICS_RANGE_MAX = ANGLE(8.5f);
+ constexpr auto OPTICS_RANGE_MIN = ANGLE(0.7f);
+ constexpr auto OPTICS_RANGE_RATE = ANGLE(0.35f);
+
+ auto& player = GetLaraInfo(item);
+ bool isSlow = IsHeld(In::Walk);
+
+ // Zoom optics.
+ if (player.Control.Look.IsUsingBinoculars || player.Control.Look.IsUsingLasersight)
+ {
+ short rangeRate = isSlow ? (OPTICS_RANGE_RATE / 2) : OPTICS_RANGE_RATE;
+
+ // NOTE: Zooming allowed with either StepLeft/StepRight or Walk/Sprint.
+ if ((IsHeld(In::StepLeft) && !IsHeld(In::StepRight)) ||
+ (IsHeld(In::Walk) && !IsHeld(In::Sprint)))
+ {
+ player.Control.Look.OpticRange -= rangeRate;
+ if (player.Control.Look.OpticRange < OPTICS_RANGE_MIN)
+ {
+ player.Control.Look.OpticRange = OPTICS_RANGE_MIN;
+ }
+ else
+ {
+ SoundEffect(SFX_TR4_BINOCULARS_ZOOM, nullptr, SoundEnvironment::Land, 0.9f);
+ }
+ }
+ else if ((IsHeld(In::StepRight) && !IsHeld(In::StepLeft)) ||
+ (IsHeld(In::Sprint) && !IsHeld(In::Walk)))
+ {
+ player.Control.Look.OpticRange += rangeRate;
+ if (player.Control.Look.OpticRange > OPTICS_RANGE_MAX)
+ {
+ player.Control.Look.OpticRange = OPTICS_RANGE_MAX;
+ }
+ else
+ {
+ SoundEffect(SFX_TR4_BINOCULARS_ZOOM, nullptr, SoundEnvironment::Land, 1.0f);
+ }
+ }
+ }
+}
+
+static void HandlePlayerOpticAnimations(ItemInfo& item)
+{
+ auto& player = GetLaraInfo(item);
+
+ if (!player.Control.Look.IsUsingBinoculars && !player.Control.Look.IsUsingLasersight)
+ return;
+
+ int animNumber = Objects[ID_LARA_BINOCULARS_MESH].loaded ? LA_BINOCULARS_IDLE : LA_STAND_IDLE;
+ if (player.Control.Look.IsUsingLasersight)
+ {
+ switch (player.Control.Weapon.GunType)
+ {
+ case LaraWeaponType::Crossbow:
+ animNumber = Objects[ID_CROSSBOW_ANIM].animIndex + 2;
+ break;
+
+ case LaraWeaponType::Revolver:
+ animNumber = Objects[ID_REVOLVER_ANIM].animIndex + 3;
+ break;
+
+ case LaraWeaponType::HK:
+ animNumber = Objects[ID_HK_ANIM].animIndex + 2;
+ break;
+ }
+ }
+ else if (player.Control.Look.IsUsingBinoculars)
+ {
+ // Silently holster any weapon or drop any item currently in hand.
+ if (player.Control.Weapon.GunType == LaraWeaponType::Flare ||
+ player.Control.Weapon.GunType == LaraWeaponType::Torch)
+ {
+ CreateFlare(item, player.Control.Weapon.GunType == LaraWeaponType::Flare ? ID_FLARE_ITEM : ID_BURNING_TORCH_ITEM, 0);
+ UndrawFlareMeshes(item);
+
+ player.Torch.State = TorchState::Holding;
+ player.Torch.IsLit = false;
+ player.Flare.ControlLeft = false;
+ player.Flare.Life = 0;
+ player.Control.Weapon.GunType =
+ player.Control.Weapon.RequestGunType = player.Control.Weapon.LastGunType;
+ }
+ else if (player.Control.Weapon.GunType != LaraWeaponType::None &&
+ player.Control.HandStatus != HandStatus::Free)
+ {
+ if (player.Control.Weapon.GunType <= LaraWeaponType::Uzi)
+ {
+ UndrawPistolMesh(item, player.Control.Weapon.GunType, false);
+ UndrawPistolMesh(item, player.Control.Weapon.GunType, true);
+ }
+ else
+ {
+ if (player.Control.Weapon.WeaponItem != NO_VALUE)
+ {
+ KillItem(player.Control.Weapon.WeaponItem);
+ player.Control.Weapon.WeaponItem = NO_VALUE;
+ }
+ UndrawShotgunMeshes(item, player.Control.Weapon.GunType);
+ }
+
+ player.TargetEntity = nullptr;
+ }
+
+ int objNumber = Objects[ID_LARA_BINOCULARS_MESH].loaded ? ID_LARA_BINOCULARS_MESH : ID_LARA_SKIN;
+ item.Model.MeshIndex[LM_RHAND] = Objects[objNumber].meshIndex + LM_RHAND;
+
+ player.Control.HandStatus = HandStatus::Free;
+ }
+
+ player.LeftArm.Locked =
+ player.RightArm.Locked = false;
+ player.LeftArm.FrameNumber =
+ player.RightArm.FrameNumber = 0;
+ player.LeftArm.AnimNumber =
+ player.RightArm.AnimNumber = animNumber;
+ player.LeftArm.FrameBase =
+ player.RightArm.FrameBase = GetAnimData(animNumber).FramePtr;
+}
+
+static void ResetPlayerOpticAnimations(ItemInfo& item)
+{
+ auto& player = GetLaraInfo(item);
+
+ ResetPlayerFlex(&item);
+
+ player.LeftArm.Locked =
+ player.RightArm.Locked = false;
+ player.LeftArm.AnimNumber =
+ player.RightArm.AnimNumber = 0;
+ player.LeftArm.FrameNumber =
+ player.RightArm.FrameNumber = 0;
+ player.RightArm.FrameBase =
+ player.LeftArm.FrameBase = GetAnimData(item).FramePtr;
+ player.Control.HandStatus = player.Control.Look.IsUsingLasersight ? HandStatus::WeaponReady : HandStatus::Free;
+
+ if (!player.Control.Look.IsUsingLasersight)
+ item.Model.MeshIndex[LM_RHAND] = item.Model.BaseMesh + LM_RHAND;
+
+ player.Control.Look.OpticRange = 0;
+ player.Control.Look.IsUsingBinoculars = player.Control.Look.IsUsingLasersight = false;
+ player.Inventory.IsBusy = false;
+
+ Camera.DisableInterpolation = true;
+ Camera.type = BinocularOldCamera;
+ Camera.bounce = 0;
+ AlterFOV(LastFOV);
+ SetScreenFadeIn(OPTICS_FADE_SPEED);
+}
+
+static void DoOpticsHighlight(const ItemInfo& item, const Vector3i& origin, const Vector3i& target)
+{
+ auto origin2 = GameVector(origin, item.RoomNumber);
+ auto target2 = GameVector(target);
+
+ const auto& binocularsColor = g_GameFlow->GetSettings()->Camera.BinocularLightColor;
+ const auto& lasersightColor = g_GameFlow->GetSettings()->Camera.LasersightLightColor;
+ const auto& color = GetLaraInfo(item).Control.Look.IsUsingLasersight ? lasersightColor : binocularsColor;
+
+ TriggerDynamicLight(origin2.x, origin2.y, origin2.z, 12, color.GetR(), color.GetG(), color.GetB());
+
+ if (!LOS(&origin2, &target2))
+ {
+ int luma = sqrt(SQUARE(origin2.x - target2.x) + SQUARE(origin2.y - target2.y) + SQUARE(origin2.z - target2.z)) * CLICK(1);
+ if ((luma + 8) > 31)
+ luma = 31;
+
+ auto dir = origin2.ToVector3() - target2.ToVector3();
+ dir.Normalize();
+ dir *= BLOCK(1);
+
+ byte r = std::max(0, color.GetR() - luma);
+ byte g = std::max(0, color.GetG() - luma);
+ byte b = std::max(0, color.GetB() - luma);
+ TriggerDynamicLight(target2.x + dir.x, target2.y + dir.y, target2.z + dir.z, luma + 12, r, g, b);
+ }
+}
+
+bool HandlePlayerOptics(ItemInfo& item)
+{
+ auto& player = GetLaraInfo(item);
+
+ bool breakOptics = true;
+
+ // Standing; can use optics.
+ if (item.Animation.ActiveState == LS_IDLE || item.Animation.AnimNumber == LA_STAND_IDLE)
+ breakOptics = false;
+
+ // Crouching; can use optics.
+ if ((player.Control.IsLow || !IsHeld(In::Crouch)) &&
+ (item.Animation.TargetState == LS_CROUCH_IDLE || item.Animation.AnimNumber == LA_CROUCH_IDLE))
+ {
+ breakOptics = false;
+ }
+
+ // If lasersight and Look is not held, exit optics.
+ if (player.Control.Look.IsUsingLasersight && !IsHeld(In::Look))
+ breakOptics = true;
+
+ // If lasersight and weapon is holstered, exit optics.
+ if (player.Control.Look.IsUsingLasersight && IsHeld(In::Draw))
+ breakOptics = true;
+
+ // Engage lasersight if available.
+ if (!breakOptics && !player.Control.Look.IsUsingLasersight && IsHeld(In::Look))
+ {
+ if (player.Control.HandStatus == HandStatus::WeaponReady &&
+ ((player.Control.Weapon.GunType == LaraWeaponType::HK && player.Weapons[(int)LaraWeaponType::HK].HasLasersight) ||
+ (player.Control.Weapon.GunType == LaraWeaponType::Revolver && player.Weapons[(int)LaraWeaponType::Revolver].HasLasersight) ||
+ (player.Control.Weapon.GunType == LaraWeaponType::Crossbow && player.Weapons[(int)LaraWeaponType::Crossbow].HasLasersight)))
+ {
+ player.Control.Look.OpticRange = OPTICS_RANGE_DEFAULT;
+ player.Control.Look.IsUsingBinoculars = true;
+ player.Control.Look.IsUsingLasersight = true;
+ player.Inventory.IsBusy = true;
+
+ Camera.DisableInterpolation = true;
+ BinocularOldCamera = Camera.oldType;
+ SetScreenFadeIn(OPTICS_FADE_SPEED);
+ }
+ }
+
+ // Not using optics; return early.
+ if (!player.Control.Look.IsUsingBinoculars && !player.Control.Look.IsUsingLasersight)
+ return true;
+
+ AlterFOV(7 * (ANGLE(11.5f) - player.Control.Look.OpticRange), false);
+
+ // Handle various binocular controls.
+ if (!player.Control.Look.IsUsingLasersight)
+ {
+ if (IsClicked(In::Deselect) ||
+ IsClicked(In::Roll) ||
+ IsClicked(In::Jump) ||
+ IsClicked(In::Draw) ||
+ IsClicked(In::Look) ||
+ IsHeld(In::Flare))
+ {
+ breakOptics = true;
+ }
+ }
+
+ // Handle lasersight highlight.
+ if (player.Control.Look.IsUsingLasersight || IsHeld(In::Action))
+ {
+ if (!player.Control.Look.IsUsingLasersight)
+ ClearAction(In::Action);
+
+ auto origin = Camera.pos.ToVector3i();
+ auto target = Camera.target.ToVector3i();
+ DoOpticsHighlight(item, origin, target);
+ }
+
+ if (!breakOptics)
+ {
+ HandlePlayerOpticAnimations(item);
+ HandlePlayerOpticZoom(item);
+ return true;
+ }
+ else
+ {
+ ResetPlayerOpticAnimations(item);
+ return false;
+ }
+}
diff --git a/TombEngine/Game/Lara/lara_optics.h b/TombEngine/Game/Lara/lara_optics.h
new file mode 100644
index 000000000..57206a566
--- /dev/null
+++ b/TombEngine/Game/Lara/lara_optics.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include "Specific/clock.h"
+
+struct ItemInfo;
+
+constexpr auto OPTICS_FADE_SPEED = 6.0f / FPS;
+constexpr auto OPTICS_RANGE_DEFAULT = ANGLE(0.7f);
+
+bool HandlePlayerOptics(ItemInfo& item);
diff --git a/TombEngine/Game/camera.cpp b/TombEngine/Game/camera.cpp
index 7067def78..2ff2778d9 100644
--- a/TombEngine/Game/camera.cpp
+++ b/TombEngine/Game/camera.cpp
@@ -12,6 +12,7 @@
#include "Game/Lara/lara.h"
#include "Game/Lara/lara_fire.h"
#include "Game/Lara/lara_helpers.h"
+#include "Game/Lara/lara_optics.h"
#include "Game/room.h"
#include "Game/savegame.h"
#include "Game/Setup.h"
@@ -50,6 +51,7 @@ struct OLD_CAMERA
};
bool ItemCameraOn;
+GameVector LastPosition;
GameVector LastTarget;
GameVector LastIdeal;
GameVector Ideals[5];
@@ -58,7 +60,6 @@ int CameraSnaps = 0;
int TargetSnaps = 0;
GameVector LookCamPosition;
GameVector LookCamTarget;
-Vector3i CamOldPos;
CAMERA_INFO Camera;
GameVector ForcedFixedCamera;
int UseForcedFixedCamera;
@@ -945,50 +946,13 @@ void BinocularCamera(ItemInfo* item)
{
auto& player = GetLaraInfo(*item);
- if (!player.Control.Look.IsUsingLasersight)
- {
- if (IsClicked(In::Deselect) ||
- IsClicked(In::Draw) ||
- IsClicked(In::Look) ||
- IsHeld(In::Flare))
- {
- ResetPlayerFlex(item);
- player.Control.Look.OpticRange = 0;
- player.Control.Look.IsUsingBinoculars = false;
- player.Inventory.IsBusy = false;
-
- Camera.type = BinocularOldCamera;
- Camera.DisableInterpolation = true;
- Camera.target = LastTarget;
- AlterFOV(LastFOV);
- return;
- }
-
- if (IsHeld(In::Action))
- {
- ClearAction(In::Action);
-
- auto origin = Camera.pos.ToVector3i();
- auto target = Camera.target.ToVector3i();
- LaraTorch(&origin, &target);
- }
- }
-
AlterFOV(7 * (ANGLE(11.5f) - player.Control.Look.OpticRange), false);
int x = item->Pose.Position.x;
- int y = item->Pose.Position.y - CLICK(2);
+ int y = item->Pose.Position.y + GameBoundingBox(item).Y1;
int z = item->Pose.Position.z;
auto pointColl = GetPointCollision(Vector3i(x, y, z), item->RoomNumber);
- if (pointColl.GetCeilingHeight() <= (y - CLICK(1)))
- {
- y -= CLICK(1);
- }
- else
- {
- y = pointColl.GetCeilingHeight() + CLICK(0.25f);
- }
Camera.pos.x = x;
Camera.pos.y = y;
@@ -1083,18 +1047,24 @@ static bool CalculateDeathCamera(const ItemInfo& item)
void CalculateCamera(const CollisionInfo& coll)
{
- CamOldPos.x = Camera.pos.x;
- CamOldPos.y = Camera.pos.y;
- CamOldPos.z = Camera.pos.z;
+ if (ItemCameraOn)
+ return;
+
+ if (!HandlePlayerOptics(*LaraItem))
+ {
+ Camera.pos = LastPosition;
+ Camera.target = LastTarget;
+ }
if (Lara.Control.Look.IsUsingBinoculars)
{
BinocularCamera(LaraItem);
return;
}
-
- if (ItemCameraOn)
- return;
+ else
+ {
+ LastPosition = Camera.pos;
+ }
if (UseForcedFixedCamera != 0)
{
@@ -1386,7 +1356,7 @@ void ItemPushCamera(GameBoundingBox* bounds, Pose* pos, short radius)
auto pointColl = GetPointCollision(Camera.pos.ToVector3i(), Camera.pos.RoomNumber);
if (pointColl.GetFloorHeight() == NO_HEIGHT || Camera.pos.y > pointColl.GetFloorHeight() || Camera.pos.y < pointColl.GetCeilingHeight())
- Camera.pos = GameVector(CamOldPos, pointColl.GetRoomNumber());
+ Camera.pos = GameVector(LastPosition.ToVector3i(), pointColl.GetRoomNumber());
}
bool CheckItemCollideCamera(ItemInfo* item)
diff --git a/TombEngine/Game/control/los.cpp b/TombEngine/Game/control/los.cpp
index faea4f675..861b92bb4 100644
--- a/TombEngine/Game/control/los.cpp
+++ b/TombEngine/Game/control/los.cpp
@@ -262,6 +262,7 @@ bool GetTargetOnLOS(GameVector* origin, GameVector* target, bool drawTarget, boo
{
Lara.Control.Weapon.HasFired = true;
Lara.Control.Weapon.Fired = true;
+ Lara.RightArm.GunFlash = Weapons[(int)Lara.Control.Weapon.GunType].FlashTime;
if (Lara.Control.Weapon.GunType == LaraWeaponType::Revolver)
SoundEffect(SFX_TR4_REVOLVER_FIRE, nullptr);
@@ -442,12 +443,6 @@ bool GetTargetOnLOS(GameVector* origin, GameVector* target, bool drawTarget, boo
}
}
- if (drawTarget && (hasHit || !result))
- {
- auto& color = g_GameFlow->GetSettings()->Camera.LasersightLightColor;
- TriggerDynamicLight(target2.x, target2.y, target2.z, 64, color.GetR(), color.GetG(), color.GetB());
- }
-
return hitProcessed;
}
diff --git a/TombEngine/Game/effects/effects.cpp b/TombEngine/Game/effects/effects.cpp
index 0594939cb..196a5da53 100644
--- a/TombEngine/Game/effects/effects.cpp
+++ b/TombEngine/Game/effects/effects.cpp
@@ -1260,7 +1260,7 @@ void TriggerDynamicSpotLight(const Vector3& pos, const Vector3& dir, const Color
// Deprecated. Use above version instead.
void TriggerDynamicLight(int x, int y, int z, short falloff, byte r, byte g, byte b)
{
- g_Renderer.AddDynamicPointLight(Vector3(x, y, z), (float)(falloff * UCHAR_MAX), Color(r / (float)UCHAR_MAX, g / (float)UCHAR_MAX, b / (float)UCHAR_MAX), false);
+ g_Renderer.AddDynamicPointLight(Vector3(x, y, z), (float)(falloff * UCHAR_MAX), Color(r / (float)CHAR_MAX, g / (float)CHAR_MAX, b / (float)CHAR_MAX), false);
}
void SpawnPlayerWaterSurfaceEffects(const ItemInfo& item, int waterHeight, int waterDepth)
diff --git a/TombEngine/Game/effects/effects.h b/TombEngine/Game/effects/effects.h
index 3da3f6ab9..bfb9c5b4f 100644
--- a/TombEngine/Game/effects/effects.h
+++ b/TombEngine/Game/effects/effects.h
@@ -300,8 +300,11 @@ void ControlWaterfallMist(short itemNumber);
void TriggerWaterfallMist(const ItemInfo& item);
void KillAllCurrentItems(short itemNumber);
void TriggerDynamicLight(int x, int y, int z, short falloff, byte r, byte g, byte b);
+
+// TODO: use Spawn prefix instead.
void TriggerDynamicPointLight(const Vector3& pos, const Color& color, float falloff, bool castShadows = false, int hash = 0);
void TriggerDynamicSpotLight(const Vector3& pos, const Vector3& dir, const Color& color, float radius, float falloff, float distance, bool castShadows = false, int hash = 0);
+
void TriggerRocketFlame(int x, int y, int z, int xv, int yv, int zv, int itemNumber);
void TriggerRocketSmoke(int x, int y, int z);
void TriggerFlashSmoke(int x, int y, int z, short roomNumber);
diff --git a/TombEngine/Game/gui.cpp b/TombEngine/Game/gui.cpp
index 26b9ad59c..d5b38f83a 100644
--- a/TombEngine/Game/gui.cpp
+++ b/TombEngine/Game/gui.cpp
@@ -12,6 +12,7 @@
#include "Game/Lara/lara.h"
#include "Game/Lara/lara_fire.h"
#include "Game/Lara/lara_helpers.h"
+#include "Game/Lara/lara_optics.h"
#include "Game/Lara/lara_one_gun.h"
#include "Game/Lara/lara_two_guns.h"
#include "Game/pickup/pickup.h"
@@ -2057,8 +2058,6 @@ namespace TEN::Gui
auto& player = GetLaraInfo(item);
- short prevOpticRange = player.Control.Look.OpticRange;
- player.Control.Look.OpticRange = 0;
player.Inventory.OldBusy = false;
item.MeshBits = ALL_JOINT_BITS;
@@ -2201,26 +2200,11 @@ namespace TEN::Gui
(player.Control.IsLow && !IsHeld(In::Crouch))) &&
!UseSpotCam && !TrackCameraInit)
{
- Camera.DisableInterpolation = true;
- player.Control.Look.OpticRange = ANGLE(0.7f);
+ SetScreenFadeIn(OPTICS_FADE_SPEED);
+ BinocularOldCamera = Camera.oldType;
+ player.Control.Look.OpticRange = OPTICS_RANGE_DEFAULT;
player.Control.Look.IsUsingBinoculars = true;
player.Inventory.OldBusy = true;
-
- // TODO: To prevent Lara from crouching or performing other actions, the inherent state of
- // LA_BINOCULARS_IDLE must be changed to LS_IDLE. @Sezz 2022.05.19
- //SetAnimation(item, LA_BINOCULARS_IDLE);
-
- if (player.Control.HandStatus != HandStatus::Free)
- player.Control.HandStatus = HandStatus::WeaponUndraw;
- }
-
- if (prevOpticRange != ANGLE(0.0f))
- {
- player.Control.Look.OpticRange = prevOpticRange;
- }
- else
- {
- BinocularOldCamera = Camera.oldType;
}
InventoryItemChosen = NO_VALUE;
diff --git a/TombEngine/Objects/Generic/Object/burning_torch.cpp b/TombEngine/Objects/Generic/Object/burning_torch.cpp
index f12876454..075bd3122 100644
--- a/TombEngine/Objects/Generic/Object/burning_torch.cpp
+++ b/TombEngine/Objects/Generic/Object/burning_torch.cpp
@@ -296,33 +296,6 @@ namespace TEN::Entities::Generic
}
}
- void LaraTorch(Vector3i* origin, Vector3i* target)
- {
- auto pos1 = GameVector(*origin, LaraItem->RoomNumber);
- auto pos2 = GameVector(*target);
-
- const auto& color = g_GameFlow->GetSettings()->Camera.BinocularLightColor;
-
- TriggerDynamicLight(pos1.x, pos1.y, pos1.z, 12, color.GetR(), color.GetG(), color.GetB());
-
- if (!LOS(&pos1, &pos2))
- {
- int l = sqrt(pow(pos1.x - pos2.x, 2) + pow(pos1.y - pos2.y, 2) + pow(pos1.z - pos2.z, 2)) * CLICK(1);
-
- if (l + 8 > 31)
- l = 31;
-
- auto dir = pos1.ToVector3() - pos2.ToVector3();
- dir.Normalize();
- dir *= BLOCK(1);
-
- byte r = std::max(0, color.GetR() - l);
- byte g = std::max(0, color.GetG() - l);
- byte b = std::max(0, color.GetB() - l);
- TriggerDynamicLight(pos2.x + dir.x, pos2.y + dir.y, pos2.z + dir.z, l + 12, r, g, b);
- }
- }
-
void FireCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
{
auto* torchItem = &g_Level.Items[itemNumber];
diff --git a/TombEngine/Objects/Generic/Object/burning_torch.h b/TombEngine/Objects/Generic/Object/burning_torch.h
index be8d62f55..668a7b415 100644
--- a/TombEngine/Objects/Generic/Object/burning_torch.h
+++ b/TombEngine/Objects/Generic/Object/burning_torch.h
@@ -10,6 +10,5 @@ namespace TEN::Entities::Generic
void DoFlameTorch();
void GetFlameTorch();
void TorchControl(short itemNumber);
- void LaraTorch(Vector3i* origin, Vector3i* target);
void FireCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll);
}
diff --git a/TombEngine/Objects/TR5/Trap/LaserBeam.cpp b/TombEngine/Objects/TR5/Trap/LaserBeam.cpp
index bf7252333..2154be045 100644
--- a/TombEngine/Objects/TR5/Trap/LaserBeam.cpp
+++ b/TombEngine/Objects/TR5/Trap/LaserBeam.cpp
@@ -37,8 +37,6 @@ namespace TEN::Entities::Traps
Radius = (item.TriggerFlags == 0) ? RADIUS_STEP : (abs(item.TriggerFlags) * RADIUS_STEP);
IsLethal = (item.TriggerFlags > 0);
IsHeavyActivator = (item.TriggerFlags <= 0);
-
- Update(item);
}
static void SpawnLaserSpark(const GameVector& pos, short angle, int count, const Vector4& colorStart)
diff --git a/TombEngine/Objects/game_object_ids.h b/TombEngine/Objects/game_object_ids.h
index 5bf09dace..cd5b96e2b 100644
--- a/TombEngine/Objects/game_object_ids.h
+++ b/TombEngine/Objects/game_object_ids.h
@@ -32,9 +32,9 @@ enum GAME_OBJECT_ID : short
ID_ACTOR1_SPEECH_HEAD2,
ID_ACTOR2_SPEECH_HEAD1,
ID_ACTOR2_SPEECH_HEAD2,
+ ID_LARA_BINOCULARS_MESH,
ID_LARA_EXTRA_MESH1,
ID_LARA_EXTRA_MESH2,
- ID_LARA_EXTRA_MESH3,
ID_LARA_WATER_MESH,
ID_LARA_PETROL_MESH,
ID_LARA_DIRT_MESH,
diff --git a/TombEngine/Renderer/RenderView.cpp b/TombEngine/Renderer/RenderView.cpp
index 1f02db401..10dd2c347 100644
--- a/TombEngine/Renderer/RenderView.cpp
+++ b/TombEngine/Renderer/RenderView.cpp
@@ -53,6 +53,7 @@ namespace TEN::Renderer
FogBulbsToDraw.clear();
LensFlaresToDraw.clear();
TransparentObjectsToDraw.clear();
+ Mirrors.clear();
}
RenderViewCamera::RenderViewCamera(CAMERA_INFO* cam, float roll, float fov, float n, float f, int w, int h)
diff --git a/TombEngine/Renderer/RenderView.h b/TombEngine/Renderer/RenderView.h
index b028b4788..bd0f9337e 100644
--- a/TombEngine/Renderer/RenderView.h
+++ b/TombEngine/Renderer/RenderView.h
@@ -15,6 +15,7 @@
#include "Renderer/Structures/RendererSortableObject.h"
#include "Renderer/Structures/RendererSpriteToDraw.h"
#include "Renderer/Structures/RendererLensFlare.h"
+#include "Renderer/Structures/RendererMirror.h"
namespace TEN::Renderer
{
@@ -45,14 +46,15 @@ namespace TEN::Renderer
RenderViewCamera Camera;
D3D11_VIEWPORT Viewport;
- std::vector RoomsToDraw = {};
- std::vector LightsToDraw = {};
- std::vector FogBulbsToDraw = {};
- std::vector SpritesToDraw = {};
- std::vector DisplaySpritesToDraw = {};
- std::map> SortedStaticsToDraw = {};
+ std::vector RoomsToDraw = {};
+ std::vector LightsToDraw = {};
+ std::vector FogBulbsToDraw = {};
+ std::vector SpritesToDraw = {};
+ std::vector DisplaySpritesToDraw = {};
+ std::map> SortedStaticsToDraw = {};
std::vector TransparentObjectsToDraw = {};
- std::vector LensFlaresToDraw = {};
+ std::vector LensFlaresToDraw = {};
+ std::vector Mirrors = {};
RenderView(CAMERA_INFO* cam, float roll, float fov, float nearPlane, float farPlane, int w, int h);
RenderView(const Vector3& pos, const Vector3& dir, const Vector3& up, int w, int h, int room, float nearPlane, float farPlane, float fov);
diff --git a/TombEngine/Renderer/Renderer.cpp b/TombEngine/Renderer/Renderer.cpp
index 0db0ed2d0..4fb6b4322 100644
--- a/TombEngine/Renderer/Renderer.cpp
+++ b/TombEngine/Renderer/Renderer.cpp
@@ -219,6 +219,9 @@ namespace TEN::Renderer
lights[index].Direction = Vector3::Lerp(light.PrevDirection, light.Direction, GetInterpolationFactor());
}
+ ReflectVectorOptionally(lights[index].Position);
+ ReflectVectorOptionally(lights[index].Direction);
+
// Bitmask light type to filter it in the shader later.
return (1 << (31 - (int)light.Type));
}
@@ -253,11 +256,13 @@ namespace TEN::Renderer
_stInstancedStaticMeshBuffer.StaticMeshes[instanceID].NumLights = (int)lights.size() | lightTypeMask;
}
- void Renderer::BindMoveableLights(std::vector& lights, int roomNumber, int prevRoomNumber, float fade)
+ void Renderer::BindMoveableLights(std::vector& lights, int roomNumber, int prevRoomNumber, float fade, bool shadow)
{
- int lightTypeMask = 0;
+ constexpr int SHADOWABLE_MASK = (1 << 16);
+ int lightTypeMask = 0;
int numLights = 0;
+
for (int i = 0; i < lights.size(); i++)
{
float fadedCoeff = 1.0f;
@@ -281,7 +286,7 @@ namespace TEN::Renderer
numLights++;
}
- _stItem.NumLights = numLights | lightTypeMask;
+ _stItem.NumLights = numLights | lightTypeMask | (shadow ? SHADOWABLE_MASK : 0);
}
void Renderer::BindConstantBufferVS(ConstantBufferRegister constantBufferType, ID3D11Buffer** buffer)
diff --git a/TombEngine/Renderer/Renderer.h b/TombEngine/Renderer/Renderer.h
index 99a621ab0..365fbcb83 100644
--- a/TombEngine/Renderer/Renderer.h
+++ b/TombEngine/Renderer/Renderer.h
@@ -65,6 +65,7 @@
#include "Renderer/Structures/RendererRoomAmbientMap.h"
#include "Renderer/Structures/RendererObject.h"
#include "Renderer/Structures/RendererStar.h"
+#include "Structures/RendererShader.h"
enum GAME_OBJECT_ID : short;
enum class SphereSpaceType;
@@ -124,43 +125,31 @@ namespace TEN::Renderer
// Shaders
- ComPtr _vsRooms;
- ComPtr _vsRoomsAnimatedTextures;
- ComPtr _psRooms;
- ComPtr _psRoomsTransparent;
- ComPtr _vsItems;
- ComPtr _psItems;
- ComPtr _vsStatics;
- ComPtr _psStatics;
- ComPtr _vsSky;
- ComPtr _psSky;
- ComPtr _vsSprites;
- ComPtr _psSprites;
- ComPtr _vsInstancedSprites;
- ComPtr _psInstancedSprites;
- ComPtr _vsInstancedStaticMeshes;
- ComPtr _psInstancedStaticMeshes;
- ComPtr _vsSolid;
- ComPtr _psSolid;
- ComPtr _vsInventory;
- ComPtr _psInventory;
- ComPtr _vsFullScreenQuad;
- ComPtr _psFullScreenQuad;
- ComPtr _vsShadowMap;
- ComPtr _psShadowMap;
- ComPtr _vsHUD;
- ComPtr _psHUDColor;
- ComPtr _psHUDTexture;
- ComPtr _psHUDBarColor;
- ComPtr _vsGBufferRooms;
- ComPtr _vsGBufferRoomsAnimated;
- ComPtr _vsGBufferItems;
- ComPtr _vsGBufferStatics;
- ComPtr _vsGBufferInstancedStatics;
- ComPtr _psGBuffer;
- ComPtr _vsRoomAmbient;
- ComPtr _vsRoomAmbientSky;
- ComPtr _psRoomAmbient;
+ RendererShader _sRooms;
+ RendererShader _sRoomsAnimated;
+ RendererShader _sRoomsTransparent;
+ RendererShader _sRoomAmbient;
+ RendererShader _sRoomAmbientSky;
+ RendererShader _sItems;
+ RendererShader _sStatics;
+ RendererShader _sInstancedStatics;
+ RendererShader _sSprites;
+ RendererShader _sInstancedSprites;
+ RendererShader _sSky;
+ RendererShader _sSolid;
+ RendererShader _sInventory;
+ RendererShader _sFullScreenQuad;
+ RendererShader _sShadowMap;
+ RendererShader _sHUD;
+ RendererShader _sHUDColor;
+ RendererShader _sHUDTexture;
+ RendererShader _sHUDBarColor;
+ RendererShader _sGBuffer;
+ RendererShader _sGBufferRooms;
+ RendererShader _sGBufferRoomsAnimated;
+ RendererShader _sGBufferItems;
+ RendererShader _sGBufferStatics;
+ RendererShader _sGBufferInstancedStatics;
// Constant buffers
@@ -352,17 +341,13 @@ namespace TEN::Renderer
RenderTarget2D _SMAAEdgesRenderTarget;
RenderTarget2D _SMAABlendRenderTarget;
- ComPtr _SMAAEdgeDetectionVS;
- ComPtr _SMAALumaEdgeDetectionPS;
- ComPtr _SMAAColorEdgeDetectionPS;
- ComPtr _SMAADepthEdgeDetectionPS;
- ComPtr _SMAABlendingWeightCalculationVS;
- ComPtr _SMAABlendingWeightCalculationPS;
- ComPtr _SMAANeighborhoodBlendingVS;
- ComPtr _SMAANeighborhoodBlendingPS;
-
- ComPtr _vsFXAA;
- ComPtr _psFXAA;
+ RendererShader _sSMAAEdgeDetection;
+ RendererShader _sSMAALumaEdgeDetection;
+ RendererShader _sSMAAColorEdgeDetection;
+ RendererShader _sSMAADepthEdgeDetection;
+ RendererShader _sSMAABlendingWeightCalculation;
+ RendererShader _sSMAANeighborhoodBlending;
+ RendererShader _sFXAA;
// Post-process
@@ -372,33 +357,34 @@ namespace TEN::Renderer
VertexBuffer _fullscreenTriangleVertexBuffer;
ComPtr _fullscreenTriangleInputLayout = nullptr;
- ComPtr _vsPostProcess;
- ComPtr _psPostProcessCopy;
- ComPtr _psPostProcessMonochrome;
- ComPtr _psPostProcessNegative;
- ComPtr _psPostProcessExclusion;
- ComPtr _psPostProcessFinalPass;
- ComPtr _psPostProcessLensFlare;
+ RendererShader _sPostProcess;
+ RendererShader _sPostProcessMonochrome;
+ RendererShader _sPostProcessNegative;
+ RendererShader _sPostProcessExclusion;
+ RendererShader _sPostProcessFinalPass;
+ RendererShader _sPostProcessLensFlare;
bool _doingFullscreenPass = false;
// SSAO
- ComPtr _vsSSAO;
- ComPtr _psSSAO;
- ComPtr _psSSAOBlur;
Texture2D _SSAONoiseTexture;
RenderTarget2D _SSAORenderTarget;
RenderTarget2D _SSAOBlurredRenderTarget;
std::vector _SSAOKernel;
+ RendererShader _sSSAO;
+ RendererShader _sSSAOBlur;
+
// New ambient light techinque
+
RenderTarget2D _roomAmbientMapFront;
RenderTarget2D _roomAmbientMapBack;
// Special effects
std::vector _causticTextures;
+ RendererMirror* _currentMirror = nullptr;
// Transparency
@@ -410,11 +396,13 @@ namespace TEN::Renderer
// High framerate.
float _interpolationFactor = 0.0f;
-
bool _graphicsSettingsChanged = false;
// Private functions
+ RendererShader CompileOrLoadShader(const std::string& fileName, const std::string& funcName, ShaderType type, const D3D_SHADER_MACRO* defines = nullptr);
+ void BindShader(const RendererShader& shader);
+
void ApplySMAA(RenderTarget2D* renderTarget, RenderView& view);
void ApplyFXAA(RenderTarget2D* renderTarget, RenderView& view);
void BindTexture(TextureRegister registerType, TextureBase* texture, SamplerStateRegister samplerType);
@@ -422,7 +410,7 @@ namespace TEN::Renderer
void BindRoomLights(std::vector& lights);
void BindStaticLights(std::vector& lights);
void BindInstancedStaticLights(std::vector& lights, int instanceID);
- void BindMoveableLights(std::vector& lights, int roomNumber, int prevRoomNumber, float fade);
+ void BindMoveableLights(std::vector& lights, int roomNumber, int prevRoomNumber, float fade, bool shadow);
void BindRenderTargetAsTexture(TextureRegister registerType, RenderTarget2D* target, SamplerStateRegister samplerType);
void BindConstantBufferVS(ConstantBufferRegister constantBufferType, ID3D11Buffer** buffer);
void BindConstantBufferPS(ConstantBufferRegister constantBufferType, ID3D11Buffer** buffer);
@@ -431,6 +419,7 @@ namespace TEN::Renderer
void UpdateAnimation(RendererItem* item, RendererObject& obj, const AnimFrameInterpData& frameData, int mask, bool useObjectWorldRotation = false);
bool CheckPortal(short parentRoomNumber, RendererDoor* door, Vector4 viewPort, Vector4* clipPort, RenderView& renderView);
void GetVisibleRooms(short from, short to, Vector4 viewPort, bool water, int count, bool onlyRooms, RenderView& renderView);
+ void CollectMirrors(RenderView& renderView);
void CollectRooms(RenderView& renderView, bool onlyRooms);
void CollectItems(short roomNumber, RenderView& renderView);
void CollectStatics(short roomNumber, RenderView& renderView);
@@ -450,11 +439,12 @@ namespace TEN::Renderer
void InitializeMenuBars(int y);
void InitializeSky();
void DrawAllStrings();
+ void PrepareDynamicLight(RendererLight& light);
void PrepareLaserBarriers(RenderView& view);
void PrepareSingleLaserBeam(RenderView& view);
void DrawHorizonAndSky(RenderView& renderView, ID3D11DepthStencilView* depthTarget);
void DrawRooms(RenderView& view, RendererPass rendererPass);
- void DrawItems(RenderView& view, RendererPass rendererPass);
+ void DrawItems(RenderView& view, RendererPass rendererPass, bool onlyPlayer = false);
void DrawAnimatingItem(RendererItem* item, RenderView& view, RendererPass rendererPass);
void DrawWaterfalls(RendererItem* item, RenderView& view, int fps, RendererPass rendererPass);
void DrawBaddyGunflashes(RenderView& view);
@@ -469,6 +459,8 @@ namespace TEN::Renderer
void PrepareWeatherParticles(RenderView& view);
void PrepareDrips(RenderView& view);
void PrepareBubbles(RenderView& view);
+ void DoRenderPass(RendererPass pass, RenderView& view, bool drawMirrors);
+ void DrawObjects(RendererPass pass, RenderView& view, bool player, bool moveables, bool statics, bool sprites);
void DrawEffects(RenderView& view, RendererPass rendererPass);
void DrawEffect(RenderView& view, RendererEffect* effect, RendererPass rendererPass);
void PrepareSplashes(RenderView& view);
@@ -534,6 +526,7 @@ namespace TEN::Renderer
void SetScissor(RendererRectangle rectangle);
bool SetupBlendModeAndAlphaTest(BlendMode blendMode, RendererPass rendererPass, int drawPass);
void SortAndPrepareSprites(RenderView& view);
+ void SortTransparentFaces(RenderView& view);
void ResetItems();
void ResetScissor();
void ResetDebugVariables();
@@ -572,6 +565,29 @@ namespace TEN::Renderer
void CreateSSAONoiseTexture();
void InitializeSMAA();
+ bool IsRoomReflected(RenderView& renderView, int roomNumber);
+
+ inline bool IgnoreReflectionPassForRoom(int roomNumber)
+ {
+ return (_currentMirror != nullptr && roomNumber != _currentMirror->RoomNumber);
+ }
+
+ inline void ReflectVectorOptionally(Vector3& vector)
+ {
+ if (_currentMirror == nullptr)
+ return;
+
+ vector = Vector3::Transform(vector, _currentMirror->ReflectionMatrix);
+ }
+
+ inline void ReflectMatrixOptionally(Matrix& matrix)
+ {
+ if (_currentMirror == nullptr)
+ return;
+
+ matrix = matrix * _currentMirror->ReflectionMatrix;
+ }
+
inline void DrawIndexedTriangles(int count, int baseIndex, int baseVertex)
{
_context->DrawIndexed(count, baseIndex, baseVertex);
@@ -647,7 +663,6 @@ namespace TEN::Renderer
void FreeRendererData();
void AddDynamicPointLight(const Vector3& pos, float radius, const Color& color, bool castShadows, int hash = 0);
void AddDynamicSpotLight(const Vector3& pos, const Vector3& dir, float radius, float falloff, float distance, const Color& color, bool castShadows, int hash = 0);
- void StoreInterpolatedDynamicLightData(RendererLight& light);
void RenderLoadingScreen(float percentage);
void RenderFreezeMode(float interpFactor, bool staticBackground);
void UpdateProgress(float value);
diff --git a/TombEngine/Renderer/RendererAntialiasing.cpp b/TombEngine/Renderer/RendererAntialiasing.cpp
index 165a14494..b78fd9b1b 100644
--- a/TombEngine/Renderer/RendererAntialiasing.cpp
+++ b/TombEngine/Renderer/RendererAntialiasing.cpp
@@ -14,7 +14,7 @@ namespace TEN::Renderer
ResetScissor();
// Common vertex shader to all fullscreen effects
- _context->VSSetShader(_vsPostProcess.Get(), nullptr, 0);
+ BindShader(_sPostProcess);
// We draw a fullscreen triangle
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
@@ -30,7 +30,6 @@ namespace TEN::Renderer
_context->ClearRenderTargetView(_SMAASceneRenderTarget.RenderTargetView.Get(), clearColor);
_context->OMSetRenderTargets(1, _SMAASceneRenderTarget.RenderTargetView.GetAddressOf(), nullptr);
- _context->PSSetShader(_psPostProcessCopy.Get(), nullptr, 0);
BindRenderTargetAsTexture(TextureRegister::ColorMap, renderTarget, SamplerStateRegister::PointWrap);
DrawTriangles(3, 0);
@@ -41,8 +40,8 @@ namespace TEN::Renderer
SetCullMode(CullMode::CounterClockwise);
_context->OMSetRenderTargets(1, _SMAAEdgesRenderTarget.RenderTargetView.GetAddressOf(), nullptr);
- _context->VSSetShader(_SMAAEdgeDetectionVS.Get(), nullptr, 0);
- _context->PSSetShader(_SMAAColorEdgeDetectionPS.Get(), nullptr, 0);
+ BindShader(_sSMAAEdgeDetection);
+ BindShader(_sSMAAColorEdgeDetection);
_stSMAABuffer.BlendFactor = 1.0f;
_cbSMAABuffer.UpdateData(_stSMAABuffer, _context.Get());
@@ -60,8 +59,7 @@ namespace TEN::Renderer
// 2) Blend weights calculation.
_context->OMSetRenderTargets(1, _SMAABlendRenderTarget.RenderTargetView.GetAddressOf(), nullptr);
- _context->VSSetShader(_SMAABlendingWeightCalculationVS.Get(), nullptr, 0);
- _context->PSSetShader(_SMAABlendingWeightCalculationPS.Get(), nullptr, 0);
+ BindShader(_sSMAABlendingWeightCalculation);
_stSMAABuffer.SubsampleIndices = Vector4::Zero;
_cbSMAABuffer.UpdateData(_stSMAABuffer, _context.Get());
@@ -78,8 +76,7 @@ namespace TEN::Renderer
// 3) Neighborhood blending.
_context->OMSetRenderTargets(1, renderTarget->RenderTargetView.GetAddressOf(), nullptr);
- _context->VSSetShader(_SMAANeighborhoodBlendingVS.Get(), nullptr, 0);
- _context->PSSetShader(_SMAANeighborhoodBlendingPS.Get(), nullptr, 0);
+ BindShader(_sSMAANeighborhoodBlending);
BindRenderTargetAsTexture(static_cast(0), &_SMAASceneRenderTarget, SamplerStateRegister::LinearClamp);
BindRenderTargetAsTexture(static_cast(1), &_SMAASceneSRGBRenderTarget, SamplerStateRegister::LinearClamp);
@@ -103,7 +100,7 @@ namespace TEN::Renderer
ResetScissor();
// Common vertex shader to all fullscreen effects
- _context->VSSetShader(_vsPostProcess.Get(), nullptr, 0);
+ BindShader(_sPostProcess);
// We draw a fullscreen triangle
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
@@ -119,7 +116,6 @@ namespace TEN::Renderer
_context->ClearRenderTargetView(_tempRenderTarget.RenderTargetView.Get(), clearColor);
_context->OMSetRenderTargets(1, _tempRenderTarget.RenderTargetView.GetAddressOf(), nullptr);
- _context->PSSetShader(_psPostProcessCopy.Get(), nullptr, 0);
BindRenderTargetAsTexture(TextureRegister::ColorMap, renderTarget, SamplerStateRegister::PointWrap);
DrawTriangles(3, 0);
@@ -127,7 +123,7 @@ namespace TEN::Renderer
_context->ClearRenderTargetView(renderTarget->RenderTargetView.Get(), Colors::Black);
_context->OMSetRenderTargets(1, renderTarget->RenderTargetView.GetAddressOf(), nullptr);
- _context->PSSetShader(_psFXAA.Get(), nullptr, 0);
+ BindShader(_sFXAA);
_stPostProcessBuffer.ViewportWidth = _screenWidth;
_stPostProcessBuffer.ViewportHeight = _screenHeight;
diff --git a/TombEngine/Renderer/RendererDraw.cpp b/TombEngine/Renderer/RendererDraw.cpp
index 81bf29a01..fc18ca9e5 100644
--- a/TombEngine/Renderer/RendererDraw.cpp
+++ b/TombEngine/Renderer/RendererDraw.cpp
@@ -167,8 +167,7 @@ namespace TEN::Renderer
UINT offset = 0;
// Set shaders
- _context->VSSetShader(_vsShadowMap.Get(), nullptr, 0);
- _context->PSSetShader(_psShadowMap.Get(), nullptr, 0);
+ BindShader(_sShadowMap);
_context->IASetVertexBuffers(0, 1, _moveablesVertexBuffer.Buffer.GetAddressOf(), &stride, &offset);
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
@@ -210,6 +209,12 @@ namespace TEN::Renderer
for (int k = 0; k < obj.ObjectMeshes.size(); k++)
{
+ if (item->MeshIds.size() <= k)
+ {
+ TENLog("Mesh structure was not properly initialized for object " + GetObjectName((GAME_OBJECT_ID)item->ObjectID));
+ break;
+ }
+
auto* mesh = GetMesh(item->MeshIds[k]);
for (auto& bucket : mesh->Buckets)
@@ -253,6 +258,9 @@ namespace TEN::Renderer
if (gunshell->counter <= 0)
continue;
+ if (IgnoreReflectionPassForRoom(gunshell->roomNumber))
+ continue;
+
objectID = gunshell->objectNumber;
auto translation = Matrix::CreateTranslation(gunshell->pos.Position.ToVector3());
@@ -267,6 +275,7 @@ namespace TEN::Renderer
auto prevWorldMatrix = prevRotMatrix * prevTranslation;
worldMatrix = Matrix::Lerp(prevWorldMatrix, worldMatrix, GetInterpolationFactor());
+ ReflectMatrixOptionally(worldMatrix);
_stInstancedStaticMeshBuffer.StaticMeshes[gunShellCount].World = worldMatrix;
_stInstancedStaticMeshBuffer.StaticMeshes[gunShellCount].Ambient = room.AmbientLight;
@@ -281,8 +290,7 @@ namespace TEN::Renderer
{
auto& moveableObject = *_moveableObjects[objectID];
- _context->VSSetShader(_vsInstancedStaticMeshes.Get(), nullptr, 0);
- _context->PSSetShader(_psInstancedStaticMeshes.Get(), nullptr, 0);
+ BindShader(_sInstancedStatics);
unsigned int stride = sizeof(Vertex);
unsigned int offset = 0;
@@ -359,8 +367,7 @@ namespace TEN::Renderer
SetDepthState(DepthState::Read);
SetCullMode(CullMode::None);
- _context->VSSetShader(_vsSolid.Get(), nullptr, 0);
- _context->PSSetShader(_psSolid.Get(), nullptr, 0);
+ BindShader(_sSolid);
auto worldMatrix = Matrix::CreateOrthographicOffCenter(0, _screenWidth, _screenHeight, 0, _viewport.MinDepth, _viewport.MaxDepth);
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINELIST);
@@ -465,9 +472,7 @@ namespace TEN::Renderer
void Renderer::DrawRats(RenderView& view, RendererPass rendererPass)
{
if (!Objects[ID_RATS_EMITTER].loaded)
- {
return;
- }
if (rendererPass == RendererPass::CollectTransparentFaces)
{
@@ -475,6 +480,9 @@ namespace TEN::Renderer
{
auto* rat = &Rats[i];
+ if (IgnoreReflectionPassForRoom(rat->RoomNumber))
+ continue;
+
if (rat->On)
{
RendererMesh* mesh = GetMesh(Objects[ID_RATS_EMITTER].meshIndex + (rand() % 8));
@@ -515,6 +523,9 @@ namespace TEN::Renderer
{
auto* rat = &Rats[i];
+ if (IgnoreReflectionPassForRoom(rat->RoomNumber))
+ continue;
+
if (rat->On)
{
activeRatsExist = true;
@@ -526,13 +537,12 @@ namespace TEN::Renderer
{
if (rendererPass == RendererPass::GBuffer)
{
- _context->VSSetShader(_vsGBufferStatics.Get(), nullptr, 0);
- _context->PSSetShader(_psGBuffer.Get(), nullptr, 0);
+ BindShader(_sGBuffer);
+ BindShader(_sGBufferStatics);
}
else
{
- _context->VSSetShader(_vsStatics.Get(), nullptr, 0);
- _context->PSSetShader(_psStatics.Get(), nullptr, 0);
+ BindShader(_sStatics);
}
UINT stride = sizeof(Vertex);
@@ -551,34 +561,31 @@ namespace TEN::Renderer
if (rat->On)
{
- RendererMesh* mesh = GetMesh(Objects[ID_RATS_EMITTER].meshIndex + (rand() % 8));
+ const auto& mesh = *GetMesh(Objects[ID_RATS_EMITTER].meshIndex + (rand() % 8));
- _stStatic.World = rat->Transform;
+ auto world = rat->Transform;
+ ReflectMatrixOptionally(world);
+
+ _stStatic.World = world;
_stStatic.Color = Vector4::One;
_stStatic.AmbientLight = _rooms[rat->RoomNumber].AmbientLight;
if (rendererPass != RendererPass::GBuffer)
- {
BindStaticLights(_rooms[rat->RoomNumber].LightsToDraw);
- }
_cbStatic.UpdateData(_stStatic, _context.Get());
- for (auto& bucket : mesh->Buckets)
+ for (const auto& bucket : mesh.Buckets)
{
if (bucket.NumVertices == 0)
- {
continue;
- }
int passes = rendererPass == RendererPass::Opaque && bucket.BlendMode == BlendMode::AlphaTest ? 2 : 1;
for (int p = 0; p < passes; p++)
{
if (!SetupBlendModeAndAlphaTest(bucket.BlendMode, rendererPass, p))
- {
continue;
- }
BindTexture(TextureRegister::ColorMap, &std::get<0>(_moveablesTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp);
BindTexture(TextureRegister::NormalMap, &std::get<1>(_moveablesTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp);
@@ -649,13 +656,12 @@ namespace TEN::Renderer
{
if (rendererPass == RendererPass::GBuffer)
{
- _context->VSSetShader(_vsGBufferStatics.Get(), nullptr, 0);
- _context->PSSetShader(_psGBuffer.Get(), nullptr, 0);
+ BindShader(_sGBuffer);
+ BindShader(_sGBufferStatics);
}
else
{
- _context->VSSetShader(_vsStatics.Get(), nullptr, 0);
- _context->PSSetShader(_psStatics.Get(), nullptr, 0);
+ BindShader(_sStatics);
}
unsigned int stride = sizeof(Vertex);
@@ -722,6 +728,9 @@ namespace TEN::Renderer
if (!bat.On)
continue;
+ if (IgnoreReflectionPassForRoom(bat.RoomNumber))
+ continue;
+
for (auto& bucket : mesh.Buckets)
{
if (!IsSortedBlendMode(bucket.BlendMode))
@@ -755,13 +764,19 @@ namespace TEN::Renderer
{
const auto& bat = Bats[i];
+ if (IgnoreReflectionPassForRoom(bat.RoomNumber))
+ continue;
+
if (bat.On)
{
auto& room = _rooms[bat.RoomNumber];
auto transformMatrix = Matrix::Lerp(bat.PrevTransform, bat.Transform, GetInterpolationFactor());
- _stInstancedStaticMeshBuffer.StaticMeshes[batCount].World = transformMatrix;
+ auto world = transformMatrix;
+ ReflectMatrixOptionally(world);
+
+ _stInstancedStaticMeshBuffer.StaticMeshes[batCount].World = world;
_stInstancedStaticMeshBuffer.StaticMeshes[batCount].Ambient = room.AmbientLight;
_stInstancedStaticMeshBuffer.StaticMeshes[batCount].Color = Vector4::One;
_stInstancedStaticMeshBuffer.StaticMeshes[batCount].LightMode = (int)mesh.LightMode;
@@ -777,13 +792,12 @@ namespace TEN::Renderer
{
if (rendererPass == RendererPass::GBuffer)
{
- _context->VSSetShader(_vsGBufferInstancedStatics.Get(), nullptr, 0);
- _context->PSSetShader(_psGBuffer.Get(), nullptr, 0);
+ BindShader(_sGBuffer);
+ BindShader(_sGBufferInstancedStatics);
}
else
{
- _context->VSSetShader(_vsInstancedStaticMeshes.Get(), nullptr, 0);
- _context->PSSetShader(_psInstancedStaticMeshes.Get(), nullptr, 0);
+ BindShader(_sInstancedStatics);
}
unsigned int stride = sizeof(Vertex);
@@ -834,6 +848,9 @@ namespace TEN::Renderer
if (!beetle.On)
continue;
+ if (IgnoreReflectionPassForRoom(beetle.RoomNumber))
+ continue;
+
auto transformMatrix = Matrix::Lerp(beetle.PrevTransform, beetle.Transform, GetInterpolationFactor());
for (auto& bucket : mesh.Buckets)
@@ -867,42 +884,47 @@ namespace TEN::Renderer
for (int i = 0; i < TEN::Entities::TR4::NUM_BEETLES; i++)
{
const auto& beetle = TEN::Entities::TR4::BeetleSwarm[i];
+
+ if (!beetle.On)
+ continue;
- if (beetle.On)
+ if (IgnoreReflectionPassForRoom(beetle.RoomNumber))
+ continue;
+
+ auto& room = _rooms[beetle.RoomNumber];
+
+ auto transformMatrix = Matrix::Lerp(beetle.PrevTransform, beetle.Transform, GetInterpolationFactor());
+
+ auto world = transformMatrix;
+ ReflectMatrixOptionally(world);
+
+ _stInstancedStaticMeshBuffer.StaticMeshes[beetleCount].World = world;
+ _stInstancedStaticMeshBuffer.StaticMeshes[beetleCount].Ambient = room.AmbientLight;
+ _stInstancedStaticMeshBuffer.StaticMeshes[beetleCount].Color = Vector4::One;
+ _stInstancedStaticMeshBuffer.StaticMeshes[beetleCount].LightMode = (int)mesh.LightMode;
+
+ if (rendererPass != RendererPass::GBuffer)
{
- auto& room = _rooms[beetle.RoomNumber];
-
- auto transformMatrix = Matrix::Lerp(beetle.PrevTransform, beetle.Transform, GetInterpolationFactor());
-
- _stInstancedStaticMeshBuffer.StaticMeshes[beetleCount].World = transformMatrix;
- _stInstancedStaticMeshBuffer.StaticMeshes[beetleCount].Ambient = room.AmbientLight;
- _stInstancedStaticMeshBuffer.StaticMeshes[beetleCount].Color = Vector4::One;
- _stInstancedStaticMeshBuffer.StaticMeshes[beetleCount].LightMode = (int)mesh.LightMode;
-
- if (rendererPass != RendererPass::GBuffer)
- {
- auto lights = std::vector{};
- for (int i = 0; i < std::min((int)room.LightsToDraw.size(), MAX_LIGHTS_PER_ITEM); i++)
- lights.push_back(room.LightsToDraw[i]);
+ auto lights = std::vector{};
+ for (int i = 0; i < std::min((int)room.LightsToDraw.size(), MAX_LIGHTS_PER_ITEM); i++)
+ lights.push_back(room.LightsToDraw[i]);
- BindInstancedStaticLights(lights, beetleCount);
- }
-
- beetleCount++;
+ BindInstancedStaticLights(lights, beetleCount);
}
+ beetleCount++;
+
if (beetleCount == INSTANCED_STATIC_MESH_BUCKET_SIZE ||
(i == TEN::Entities::TR4::NUM_BEETLES - 1 && beetleCount > 0))
{
if (rendererPass == RendererPass::GBuffer)
{
- _context->VSSetShader(_vsGBufferInstancedStatics.Get(), nullptr, 0);
- _context->PSSetShader(_psGBuffer.Get(), nullptr, 0);
+ BindShader(_sGBuffer);
+ BindShader(_sGBufferInstancedStatics);
}
else
{
- _context->VSSetShader(_vsInstancedStaticMeshes.Get(), nullptr, 0);
- _context->PSSetShader(_psInstancedStaticMeshes.Get(), nullptr, 0);
+ BindShader(_sInstancedStatics);
}
unsigned int stride = sizeof(Vertex);
@@ -954,6 +976,9 @@ namespace TEN::Renderer
if (!locust.on)
continue;
+ if (IgnoreReflectionPassForRoom(locust.roomNumber))
+ continue;
+
auto& mesh = *GetMesh(Objects[ID_LOCUSTS].meshIndex + (-locust.counter & 3));
for (auto& bucket : mesh.Buckets)
@@ -990,6 +1015,9 @@ namespace TEN::Renderer
if (!locust.on)
continue;
+ if (IgnoreReflectionPassForRoom(locust.roomNumber))
+ continue;
+
activeLocustsExist = true;
break;
}
@@ -998,13 +1026,12 @@ namespace TEN::Renderer
{
if (rendererPass == RendererPass::GBuffer)
{
- _context->VSSetShader(_vsGBufferStatics.Get(), nullptr, 0);
- _context->PSSetShader(_psGBuffer.Get(), nullptr, 0);
+ BindShader(_sGBuffer);
+ BindShader(_sGBufferStatics);
}
else
{
- _context->VSSetShader(_vsStatics.Get(), nullptr, 0);
- _context->PSSetShader(_psStatics.Get(), nullptr, 0);
+ BindShader(_sStatics);
}
unsigned int stride = sizeof(Vertex);
@@ -1025,7 +1052,10 @@ namespace TEN::Renderer
auto& mesh = *GetMesh(Objects[ID_LOCUSTS].meshIndex + (-locust.counter & 3));
- _stStatic.World = Matrix::Lerp(locust.PrevTransform, locust.Transform, GetInterpolationFactor());
+ auto world = Matrix::Lerp(locust.PrevTransform, locust.Transform, GetInterpolationFactor());
+ ReflectMatrixOptionally(world);
+
+ _stStatic.World = world;
_stStatic.Color = Vector4::One;
_stStatic.AmbientLight = _rooms[locust.roomNumber].AmbientLight;
_cbStatic.UpdateData(_stStatic, _context.Get());
@@ -1059,8 +1089,7 @@ namespace TEN::Renderer
SetBlendMode(BlendMode::Additive);
SetCullMode(CullMode::None);
- _context->VSSetShader(_vsSolid.Get(), nullptr, 0);
- _context->PSSetShader(_psSolid.Get(), nullptr, 0);
+ BindShader(_sSolid);
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINELIST);
@@ -1093,8 +1122,7 @@ namespace TEN::Renderer
SetBlendMode(BlendMode::Additive);
SetCullMode(CullMode::None);
- _context->VSSetShader(_vsSolid.Get(), nullptr, 0);
- _context->PSSetShader(_psSolid.Get(), nullptr, 0);
+ BindShader(_sSolid);
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
_context->IASetInputLayout(_inputLayout.Get());
@@ -1529,7 +1557,7 @@ namespace TEN::Renderer
RendererLight dynamicLight = {};
- dynamicLight.Color = Vector3(color.x, color.y, color.z) * 2.0f;
+ dynamicLight.Color = Vector3(color.x, color.y, color.z);
if (falloff < 8)
dynamicLight.Color *= (falloff / 8.0f);
@@ -1556,8 +1584,7 @@ namespace TEN::Renderer
dynamicLight.Luma = Luma(dynamicLight.Color);
dynamicLight.Hash = hash;
- StoreInterpolatedDynamicLightData(dynamicLight);
- _dynamicLights[_dynamicLightList].push_back(dynamicLight);
+ PrepareDynamicLight(dynamicLight);
}
void Renderer::AddDynamicPointLight(const Vector3& pos, float radius, const Color& color, bool castShadows, int hash)
@@ -1565,9 +1592,9 @@ namespace TEN::Renderer
if (_isLocked || g_GameFlow->LastFreezeMode != FreezeMode::None)
return;
- RendererLight dynamicLight = {};
+ auto dynamicLight = RendererLight{};
- dynamicLight.Color = Vector3(color.x, color.y, color.z) * 2.0f;
+ dynamicLight.Color = Vector3(color.x, color.y, color.z);
if (radius < BLOCK(2))
dynamicLight.Color *= (radius / BLOCK(2));
@@ -1582,33 +1609,55 @@ namespace TEN::Renderer
dynamicLight.Luma = Luma(dynamicLight.Color);
dynamicLight.Hash = hash;
- StoreInterpolatedDynamicLightData(dynamicLight);
- _dynamicLights[_dynamicLightList].push_back(dynamicLight);
+ PrepareDynamicLight(dynamicLight);
}
- void Renderer::StoreInterpolatedDynamicLightData(RendererLight& light)
+ void Renderer::PrepareDynamicLight(RendererLight& light)
{
- // Hash is not provided, do not search for same light in old buffer.
- if (light.Hash == 0)
- return;
+ // If hash is provided, search for same light in previous buffer.
+ if (light.Hash != 0)
+ {
+ // Determine previous buffer index.
+ const auto& prevList = _dynamicLights[1 - _dynamicLightList];
- // Determine the previous buffer index.
- const auto& previousList = _dynamicLights[1 - _dynamicLightList];
+ // Find light in previous buffer with same hash.
+ auto it = std::find_if(
+ prevList.begin(), prevList.end(),
+ [&light](const auto& prevLight)
+ {
+ return (prevLight.Hash == light.Hash);
+ });
- // Find a light in the previous buffer with the same Hash.
- auto it = std::find_if(previousList.begin(), previousList.end(),
- [&light](const auto& prevLight)
+ if (it != prevList.end())
{
- return prevLight.Hash == light.Hash;
- });
+ // If matching light is found, copy it.
+ const auto& prevLight = *it;
+ light.PrevPosition = prevLight.Position;
+ light.PrevDirection = prevLight.Direction;
+ }
+ }
- if (it == previousList.end())
- return;
+ // Queue dynamic light.
+ _dynamicLights[_dynamicLightList].push_back(light);
- // If a matching light is found, copy its data.
- const auto& prevLight = *it;
- light.PrevPosition = prevLight.Position;
- light.PrevDirection = prevLight.Direction;
+ // Check if light is spawned in mirrored room and create reflection.
+ for (const auto& mirror : g_Level.Mirrors)
+ {
+ if (!mirror.ReflectLights)
+ continue;
+
+ // TODO: Avoid LaraItem global.
+ if ((Camera.pos.RoomNumber == mirror.RoomNumber || LaraItem->RoomNumber == mirror.RoomNumber) &&
+ IsPointInRoom(light.Position, mirror.RoomNumber))
+ {
+ auto reflectedLight = light;
+ reflectedLight.Position = Vector3::Transform(light.Position, mirror.ReflectionMatrix);
+ reflectedLight.Direction = Vector3::Transform(light.Direction, mirror.ReflectionMatrix);
+ reflectedLight.Hash = 0;
+
+ _dynamicLights[_dynamicLightList].push_back(reflectedLight);
+ }
+ }
}
void Renderer::PrepareScene()
@@ -1661,12 +1710,11 @@ namespace TEN::Renderer
ResetDebugVariables();
- _doingFullscreenPass = false;
-
auto& level = *g_GameFlow->GetLevel(CurrentLevel);
// Prepare scene to draw.
auto time1 = std::chrono::high_resolution_clock::now();
+ CollectMirrors(view);
CollectRooms(view, false);
auto time = std::chrono::high_resolution_clock::now();
_timeRoomsCollector = (std::chrono::duration_cast(time - time1)).count() / 1000000;
@@ -1773,7 +1821,7 @@ namespace TEN::Renderer
cameraConstantBuffer.FogMinDistance = level.GetFogMinDistance();
cameraConstantBuffer.FogMaxDistance = level.GetFogMaxDistance();
}
- else
+ else
{
cameraConstantBuffer.FogMaxDistance = 0;
cameraConstantBuffer.FogColor = Vector4::Zero;
@@ -1792,10 +1840,10 @@ namespace TEN::Renderer
cameraConstantBuffer.FogBulbs[i].Color = view.FogBulbsToDraw[i].Color;
cameraConstantBuffer.FogBulbs[i].SquaredCameraToFogBulbDistance = SQUARE(view.FogBulbsToDraw[i].Distance);
cameraConstantBuffer.FogBulbs[i].FogBulbToCameraVector = view.FogBulbsToDraw[i].FogBulbToCameraVector;
- }
+ }
_cbCameraMatrices.UpdateData(cameraConstantBuffer, _context.Get());
-
+
ID3D11RenderTargetView* pRenderViewPtrs[2];
// Bind main render target.
@@ -1803,7 +1851,7 @@ namespace TEN::Renderer
// Draw horizon and sky.
DrawHorizonAndSky(view, _renderTarget.DepthStencilView.Get());
-
+
// Build G-Buffer (normals + depth).
_context->ClearRenderTargetView(_normalsRenderTarget.RenderTargetView.Get(), Colors::Black);
_context->ClearRenderTargetView(_depthRenderTarget.RenderTargetView.Get(), Colors::White);
@@ -1812,85 +1860,30 @@ namespace TEN::Renderer
pRenderViewPtrs[1] = _depthRenderTarget.RenderTargetView.Get();
_context->OMSetRenderTargets(2, &pRenderViewPtrs[0], _renderTarget.DepthStencilView.Get());
- DrawRooms(view, RendererPass::GBuffer);
- DrawItems(view, RendererPass::GBuffer);
- DrawStatics(view, RendererPass::GBuffer);
- DrawSpiders(view, RendererPass::GBuffer);
- DrawScarabs(view, RendererPass::GBuffer);
- DrawGunShells(view, RendererPass::GBuffer);
- DrawBats(view, RendererPass::GBuffer);
- DrawEffects(view, RendererPass::GBuffer);
- DrawRats(view, RendererPass::GBuffer);
- DrawLocusts(view, RendererPass::GBuffer);
+ // Render G-Buffer pass.
+ DoRenderPass(RendererPass::GBuffer, view, true);
// Calculate ambient occlusion.
if (g_Configuration.EnableAmbientOcclusion)
- {
- _doingFullscreenPass = true;
CalculateSSAO(view);
- _doingFullscreenPass = false;
- }
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
_context->IASetInputLayout(_inputLayout.Get());
- SetBlendMode(BlendMode::Opaque);
- SetCullMode(CullMode::CounterClockwise);
- SetDepthState(DepthState::Write);
-
_context->RSSetViewports(1, &view.Viewport);
ResetScissor();
-
+
// Bind main render target again. Main depth buffer is already filled and avoids overdraw in following steps.
_context->OMSetRenderTargets(1, _renderTarget.RenderTargetView.GetAddressOf(), _renderTarget.DepthStencilView.Get());
+
+ DoRenderPass(RendererPass::Opaque, view, true);
+ DoRenderPass(RendererPass::Additive, view, true);
+ DoRenderPass(RendererPass::CollectTransparentFaces, view, false);
+ SortTransparentFaces(view);
+
+ DoRenderPass(RendererPass::Transparent, view, true);
+ DoRenderPass(RendererPass::GunFlashes, view, true); // HACK: Gunflashes are drawn after everything because they are near camera.
- // Draw opaque, alpha test, and fast alpha blend faces.
- DrawRooms(view, RendererPass::Opaque);
- DrawItems(view, RendererPass::Opaque);
- DrawStatics(view, RendererPass::Opaque);
- DrawSpiders(view, RendererPass::Opaque);
- DrawScarabs(view, RendererPass::Opaque);
- DrawGunShells(view, RendererPass::Opaque);
- DrawBats(view, RendererPass::Opaque);
- DrawEffects(view, RendererPass::Opaque);
- DrawRats(view, RendererPass::Opaque);
- DrawLocusts(view, RendererPass::Opaque);
- DrawDebris(view, RendererPass::Opaque);
- DrawSprites(view, RendererPass::Opaque);
- DrawFishSwarm(view, RendererPass::Opaque);
-
- // Draw additive faces.
- DrawRooms(view, RendererPass::Additive);
- DrawItems(view, RendererPass::Additive);
- DrawStatics(view, RendererPass::Additive);
- DrawSpiders(view, RendererPass::Additive);
- DrawScarabs(view, RendererPass::Additive);
- DrawBats(view, RendererPass::Additive);
- DrawEffects(view, RendererPass::Additive);
- DrawRats(view, RendererPass::Additive);
- DrawLocusts(view, RendererPass::Additive);
- DrawDebris(view, RendererPass::Additive);
- DrawSprites(view, RendererPass::Additive);
- DrawFishSwarm(view, RendererPass::Additive);
-
- // Collect all non-commutative transparent faces.
- // NOTE: Sorted sprites already collected at beginning of frame.
- DrawRooms(view, RendererPass::CollectTransparentFaces);
- DrawItems(view, RendererPass::CollectTransparentFaces);
- DrawStatics(view, RendererPass::CollectTransparentFaces);
- DrawBats(view, RendererPass::CollectTransparentFaces);
- DrawEffects(view, RendererPass::CollectTransparentFaces);
- DrawRats(view, RendererPass::CollectTransparentFaces);
- DrawLocusts(view, RendererPass::CollectTransparentFaces);
- DrawFishSwarm(view, RendererPass::CollectTransparentFaces);
-
- // Draw sorted faces.
- DrawSortedFaces(view);
-
- // HACK: Gunflashes drawn after everything because they are very near the camera.
- DrawGunFlashes(view);
- DrawBaddyGunflashes(view);
-
// Draw 3D debug lines and triangles.
DrawLines3D(view);
DrawTriangles3D(view);
@@ -1906,33 +1899,8 @@ namespace TEN::Renderer
if (renderMode != SceneRenderMode::NoPostprocess)
{
- _doingFullscreenPass = true;
-
- // Apply antialiasing.
- switch (g_Configuration.AntialiasingMode)
- {
- case AntialiasingMode::None:
- break;
-
- case AntialiasingMode::Low:
- ApplyFXAA(&_renderTarget, view);
- break;
-
- case AntialiasingMode::Medium:
- case AntialiasingMode::High:
- ApplySMAA(&_renderTarget, view);
- break;
- }
-
- // Draw post-process effects (cinematic bars, fade, flash, HDR, tone mapping, etc.).
DrawPostprocess(renderTarget, view, renderMode);
-
- _doingFullscreenPass = false;
-
- // Draw binoculars or lasersight overlay.
DrawOverlays(view);
-
- // Draw 2D debug lines.
DrawLines2D();
}
@@ -1960,7 +1928,7 @@ namespace TEN::Renderer
SetBlendMode(BlendMode::Opaque);
SetCullMode(CullMode::CounterClockwise);
- _context->PSSetShader(_psRoomAmbient.Get(), nullptr, 0);
+ BindShader(_sRoomAmbient);
// Bind and clear render target
_context->ClearRenderTargetView(renderTarget->RenderTargetView.Get(), Colors::Black);
@@ -2010,7 +1978,7 @@ namespace TEN::Renderer
if (levelPtr->Horizon)
{
- _context->VSSetShader(_vsRoomAmbientSky.Get(), nullptr, 0);
+ BindShader(_sRoomAmbientSky);
if (Lara.Control.Look.OpticRange != 0)
AlterFOV(ANGLE(DEFAULT_FOV) - Lara.Control.Look.OpticRange, false);
@@ -2092,7 +2060,7 @@ namespace TEN::Renderer
_context->ClearDepthStencilView(renderTarget->DepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
}
- _context->VSSetShader(_vsRoomAmbient.Get(), nullptr, 0);
+ BindShader(_sRoomAmbient);
// Draw rooms
UINT stride = sizeof(Vertex);
@@ -2213,7 +2181,82 @@ namespace TEN::Renderer
RenderScene(&_dumpScreenRenderTarget, _gameCamera, renderMode);
}
- void Renderer::DrawItems(RenderView& view, RendererPass rendererPass)
+ void Renderer::DoRenderPass(RendererPass pass, RenderView& view, bool drawMirrors)
+ {
+ // Reset GPU state.
+ SetBlendMode(BlendMode::Opaque);
+ SetCullMode(CullMode::CounterClockwise);
+ SetDepthState(DepthState::Write);
+
+ // Draw room geometry first if applicable for a given pass.
+ if (pass != RendererPass::Transparent && pass != RendererPass::GunFlashes)
+ DrawRooms(view, pass);
+
+ // Draw all objects.
+ DrawObjects(pass, view, true, true, true, true);
+
+ // If mirrors are in view, render mirrored objects for every mirror.
+ if (drawMirrors && !view.Mirrors.empty())
+ {
+ SetCullMode(CullMode::Clockwise);
+ for (auto& mirror : view.Mirrors)
+ {
+ _currentMirror = &mirror;
+ DrawObjects(pass, view, mirror.ReflectPlayer, mirror.ReflectMoveables, mirror.ReflectStatics, mirror.ReflectSprites);
+ _currentMirror = nullptr;
+ }
+
+ SetCullMode(CullMode::CounterClockwise);
+ }
+ }
+
+ void Renderer::DrawObjects(RendererPass pass, RenderView& view, bool player, bool moveables, bool statics, bool sprites)
+ {
+ switch (pass)
+ {
+ case RendererPass::Transparent:
+ DrawSortedFaces(view);
+ break;
+
+ case RendererPass::GunFlashes:
+ DrawGunFlashes(view);
+ DrawBaddyGunflashes(view);
+ break;
+
+ default:
+ if (moveables)
+ {
+ DrawItems(view, pass);
+ DrawEffects(view, pass);
+ DrawGunShells(view, pass);
+ DrawSpiders(view, pass);
+ DrawScarabs(view, pass);
+ DrawBats(view, pass);
+ DrawRats(view, pass);
+ DrawLocusts(view, pass);
+ DrawFishSwarm(view, pass);
+ }
+ else if (player)
+ {
+ DrawItems(view, pass, true);
+ DrawGunShells(view, pass);
+ }
+
+ if (statics)
+ {
+ DrawStatics(view, pass);
+ DrawDebris(view, pass); // Debris mostly originate from shatter statics.
+ }
+
+ // Sorted sprites already collected at beginning of frame.
+ if (sprites && pass != RendererPass::CollectTransparentFaces)
+ DrawSprites(view, pass);
+
+ break;
+ }
+ }
+
+ void Renderer::DrawItems(RenderView& view, RendererPass rendererPass, bool onlyPlayer)
{
unsigned int stride = sizeof(Vertex);
unsigned int offset = 0;
@@ -2224,26 +2267,34 @@ namespace TEN::Renderer
// Set shaders.
if (rendererPass == RendererPass::GBuffer)
{
- _context->VSSetShader(_vsGBufferItems.Get(), nullptr, 0);
- _context->PSSetShader(_psGBuffer.Get(), nullptr, 0);
+ BindShader(_sGBuffer);
+ BindShader(_sGBufferItems);
}
else
{
- _context->VSSetShader(_vsItems.Get(), nullptr, 0);
- _context->PSSetShader(_psItems.Get(), nullptr, 0);
+ BindShader(_sItems);
}
BindRenderTargetAsTexture(TextureRegister::SSAO, &_SSAOBlurredRenderTarget, SamplerStateRegister::PointWrap);
for (auto room : view.RoomsToDraw)
{
+ if (IgnoreReflectionPassForRoom(room->RoomNumber))
+ continue;
+
for (auto itemToDraw : room->ItemsToDraw)
{
+ if (_currentMirror != nullptr && (g_Level.Items[itemToDraw->ItemNumber].Flags & IFLAG_CLEAR_BODY))
+ continue;
+
+ if (onlyPlayer && itemToDraw->ObjectID != ID_LARA)
+ continue;
+
switch (itemToDraw->ObjectID)
{
case ID_LARA:
DrawLara(view, rendererPass);
- break;
+ continue;
case ID_WATERFALL1:
case ID_WATERFALL2:
@@ -2258,7 +2309,7 @@ namespace TEN::Renderer
default:
DrawAnimatingItem(itemToDraw, view, rendererPass);
- break;
+ continue;
}
}
}
@@ -2327,6 +2378,8 @@ namespace TEN::Renderer
// Bind item main properties
_stItem.World = item->InterpolatedWorld;
+ ReflectMatrixOptionally(_stItem.World);
+
_stItem.Color = item->Color;
_stItem.AmbientLight = item->AmbientLight;
memcpy(_stItem.BonesMatrices, item->InterpolatedAnimTransforms, sizeof(Matrix) * MAX_BONES);
@@ -2334,7 +2387,8 @@ namespace TEN::Renderer
for (int k = 0; k < moveableObj.ObjectMeshes.size(); k++)
_stItem.BoneLightModes[k] = (int)moveableObj.ObjectMeshes[k]->LightMode;
- BindMoveableLights(item->LightsToDraw, item->RoomNumber, item->PrevRoomNumber, item->LightFade);
+ bool acceptsShadows = moveableObj.ShadowType == ShadowMode::None;
+ BindMoveableLights(item->LightsToDraw, item->RoomNumber, item->PrevRoomNumber, item->LightFade, acceptsShadows);
_cbItem.UpdateData(_stItem, _context.Get());
for (int k = 0; k < moveableObj.ObjectMeshes.size(); k++)
@@ -2349,22 +2403,19 @@ namespace TEN::Renderer
void Renderer::DrawStatics(RenderView& view, RendererPass rendererPass)
{
if (_staticTextures.size() == 0 || view.SortedStaticsToDraw.size() == 0)
- {
return;
- }
if (rendererPass != RendererPass::CollectTransparentFaces)
{
#ifdef DISABLE_INSTANCING
if (rendererPass == RendererPass::GBuffer)
{
- _context->VSSetShader(_vsGBufferStatics.Get(), NULL, 0);
- _context->PSSetShader(_psGBuffer.Get(), NULL, 0);
+ BindShader(_sGBuffer);
+ BindShader(_sGBufferStatics);
}
else
{
- _context->VSSetShader(_vsStatics.Get(), NULL, 0);
- _context->PSSetShader(_psStatics.Get(), NULL, 0);
+ BindShader(_sStatics);
}
// Bind vertex and index buffer
@@ -2393,6 +2444,9 @@ namespace TEN::Renderer
RendererStatic* current = statics[s];
RendererRoom* room = &_rooms[current->RoomNumber];
+ if (IgnoreReflectionPassForRoom(current->RoomNumber))
+ continue;
+
_stStatic.World = current->World;
_stStatic.Color = current->Color;
_stStatic.AmbientLight = room->AmbientLight;
@@ -2437,13 +2491,12 @@ namespace TEN::Renderer
#else
if (rendererPass == RendererPass::GBuffer)
{
- _context->VSSetShader(_vsGBufferInstancedStatics.Get(), NULL, 0);
- _context->PSSetShader(_psGBuffer.Get(), NULL, 0);
+ BindShader(_sGBuffer);
+ BindShader(_sGBufferInstancedStatics);
}
else
{
- _context->VSSetShader(_vsInstancedStaticMeshes.Get(), NULL, 0);
- _context->PSSetShader(_psInstancedStaticMeshes.Get(), NULL, 0);
+ BindShader(_sInstancedStatics);
}
// Bind vertex and index buffer
@@ -2456,14 +2509,14 @@ namespace TEN::Renderer
for (auto it = view.SortedStaticsToDraw.begin(); it != view.SortedStaticsToDraw.end(); it++)
{
- std::vector statics = it->second;
+ auto statics = it->second;
- RendererStatic* refStatic = statics[0];
- RendererObject& refStaticObj = GetStaticRendererObject(refStatic->ObjectNumber);
+ auto* refStatic = statics[0];
+ auto& refStaticObj = GetStaticRendererObject(refStatic->ObjectNumber);
if (refStaticObj.ObjectMeshes.size() == 0)
continue;
- RendererMesh* refMesh = refStaticObj.ObjectMeshes[0];
+ auto* refMesh = refStaticObj.ObjectMeshes[0];
int staticsCount = (int)statics.size();
int bucketSize = INSTANCED_STATIC_MESH_BUCKET_SIZE;
@@ -2471,57 +2524,58 @@ namespace TEN::Renderer
while (baseStaticIndex < staticsCount)
{
- int k = 0;
- int instanceCount = std::min(bucketSize, staticsCount - baseStaticIndex);
+ int instancesCount = 0;
int max = std::min(baseStaticIndex + bucketSize, staticsCount);
for (int s = baseStaticIndex; s < max; s++)
{
- RendererStatic* current = statics[s];
- RendererRoom* room = &_rooms[current->RoomNumber];
+ auto* current = statics[s];
+ auto* room = &_rooms[current->RoomNumber];
- _stInstancedStaticMeshBuffer.StaticMeshes[k].World = current->World;
- _stInstancedStaticMeshBuffer.StaticMeshes[k].Color = current->Color;
- _stInstancedStaticMeshBuffer.StaticMeshes[k].Ambient = room->AmbientLight;
- _stInstancedStaticMeshBuffer.StaticMeshes[k].LightMode = (int)refMesh->LightMode;
+ if (IgnoreReflectionPassForRoom(current->RoomNumber))
+ continue;
+
+ auto world = current->World;
+ ReflectMatrixOptionally(world);
+
+ _stInstancedStaticMeshBuffer.StaticMeshes[instancesCount].World = world;
+ _stInstancedStaticMeshBuffer.StaticMeshes[instancesCount].Color = current->Color;
+ _stInstancedStaticMeshBuffer.StaticMeshes[instancesCount].Ambient = room->AmbientLight;
+ _stInstancedStaticMeshBuffer.StaticMeshes[instancesCount].LightMode = (int)refMesh->LightMode;
if (rendererPass != RendererPass::GBuffer)
- {
- BindInstancedStaticLights(current->LightsToDraw, k);
- }
+ BindInstancedStaticLights(current->LightsToDraw, instancesCount);
- k++;
+ instancesCount++;
}
- _cbInstancedStaticMeshBuffer.UpdateData(_stInstancedStaticMeshBuffer, _context.Get());
-
baseStaticIndex += bucketSize;
- for (auto& bucket : refMesh->Buckets)
+ if (instancesCount > 0)
{
- if (bucket.NumVertices == 0)
- {
- continue;
- }
+ _cbInstancedStaticMeshBuffer.UpdateData(_stInstancedStaticMeshBuffer, _context.Get());
- int passes = rendererPass == RendererPass::Opaque && bucket.BlendMode == BlendMode::AlphaTest ? 2 : 1;
-
- for (int p = 0; p < passes; p++)
+ for (const auto& bucket : refMesh->Buckets)
{
- if (!SetupBlendModeAndAlphaTest(bucket.BlendMode, rendererPass, p))
- {
+ if (bucket.NumVertices == 0)
continue;
+
+ int passes = rendererPass == RendererPass::Opaque && bucket.BlendMode == BlendMode::AlphaTest ? 2 : 1;
+ for (int p = 0; p < passes; p++)
+ {
+ if (!SetupBlendModeAndAlphaTest(bucket.BlendMode, rendererPass, p))
+ continue;
+
+ BindTexture(TextureRegister::ColorMap,
+ &std::get<0>(_staticTextures[bucket.Texture]),
+ SamplerStateRegister::AnisotropicClamp);
+ BindTexture(TextureRegister::NormalMap,
+ &std::get<1>(_staticTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp);
+
+ DrawIndexedInstancedTriangles(bucket.NumIndices, instancesCount, bucket.StartIndex, 0);
+
+ _numInstancedStaticsDrawCalls++;
}
-
- BindTexture(TextureRegister::ColorMap,
- &std::get<0>(_staticTextures[bucket.Texture]),
- SamplerStateRegister::AnisotropicClamp);
- BindTexture(TextureRegister::NormalMap,
- &std::get<1>(_staticTextures[bucket.Texture]), SamplerStateRegister::AnisotropicClamp);
-
- DrawIndexedInstancedTriangles(bucket.NumIndices, instanceCount, bucket.StartIndex, 0);
-
- _numInstancedStaticsDrawCalls++;
}
}
}
@@ -2530,18 +2584,17 @@ namespace TEN::Renderer
}
else
{
- // Collect sorted blend modes faces ordered by room, if transparent pass
-
+ // Collect sorted blend modes faces ordered by room if doing transparent pass.
for (auto it = view.SortedStaticsToDraw.begin(); it != view.SortedStaticsToDraw.end(); it++)
{
- std::vector statics = it->second;
+ auto statics = it->second;
- RendererStatic* refStatic = statics[0];
- RendererObject& refStaticObj = GetStaticRendererObject(refStatic->ObjectNumber);
+ auto* refStatic = statics[0];
+ auto& refStaticObj = GetStaticRendererObject(refStatic->ObjectNumber);
if (refStaticObj.ObjectMeshes.size() == 0)
continue;
- RendererMesh* refMesh = refStaticObj.ObjectMeshes[0];
+ auto* refMesh = refStaticObj.ObjectMeshes[0];
for (int i = 0; i < statics.size(); i++)
{
@@ -2550,15 +2603,13 @@ namespace TEN::Renderer
auto& bucket = refMesh->Buckets[j];
if (bucket.NumVertices == 0)
- {
continue;
- }
if (IsSortedBlendMode(bucket.BlendMode))
{
for (int p = 0; p < bucket.Polygons.size(); p++)
{
- RendererSortableObject object;
+ auto object = RendererSortableObject{};
object.ObjectType = RendererObjectType::Static;
object.Bucket = &bucket;
@@ -2625,11 +2676,11 @@ namespace TEN::Renderer
{
if (rendererPass == RendererPass::GBuffer)
{
- _context->PSSetShader(_psGBuffer.Get(), nullptr, 0);
+ BindShader(_sGBuffer);
}
else
{
- _context->PSSetShader(_psRooms.Get(), nullptr, 0);
+ BindShader(_sRooms);
}
UINT stride = sizeof(Vertex);
@@ -2693,11 +2744,11 @@ namespace TEN::Renderer
{
if (rendererPass != RendererPass::GBuffer)
{
- _context->VSSetShader((animated == 0) ? _vsRooms.Get() : _vsRoomsAnimatedTextures.Get(), nullptr, 0);
+ if (animated == 0) BindShader(_sRooms); else BindShader(_sRoomsAnimated);
}
else
{
- _context->VSSetShader((animated == 0) ? _vsGBufferRooms.Get() : _vsGBufferRoomsAnimated.Get(), nullptr, 0);
+ if (animated == 0) BindShader(_sGBufferRooms); else BindShader(_sGBufferRoomsAnimated);
}
for (const auto& bucket : room.Buckets)
@@ -2796,9 +2847,7 @@ namespace TEN::Renderer
// Draw sky.
auto rotation = Matrix::CreateRotationX(PI);
- _context->VSSetShader(_vsSky.Get(), nullptr, 0);
- _context->PSSetShader(_psSky.Get(), nullptr, 0);
-
+ BindShader(_sSky);
BindTexture(TextureRegister::ColorMap, &_skyTexture, SamplerStateRegister::AnisotropicClamp);
_context->IASetVertexBuffers(0, 1, _skyVertexBuffer.Buffer.GetAddressOf(), &stride, &offset);
@@ -2839,8 +2888,7 @@ namespace TEN::Renderer
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
- _context->VSSetShader(_vsInstancedSprites.Get(), nullptr, 0);
- _context->PSSetShader(_psInstancedSprites.Get(), nullptr, 0);
+ BindShader(_sInstancedSprites);
// Set up vertex buffer and parameters.
UINT stride = sizeof(Vertex);
@@ -2979,8 +3027,7 @@ namespace TEN::Renderer
_context->IASetVertexBuffers(0, 1, _moveablesVertexBuffer.Buffer.GetAddressOf(), &stride, &offset);
_context->IASetIndexBuffer(_moveablesIndexBuffer.Buffer.Get(), DXGI_FORMAT_R32_UINT, 0);
- _context->VSSetShader(_vsSky.Get(), nullptr, 0);
- _context->PSSetShader(_psSky.Get(), nullptr, 0);
+ BindShader(_sSky);
auto& moveableObj = *_moveableObjects[ID_HORIZON];
@@ -3024,8 +3071,7 @@ namespace TEN::Renderer
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
- _context->VSSetShader(_vsInstancedSprites.Get(), nullptr, 0);
- _context->PSSetShader(_psInstancedSprites.Get(), nullptr, 0);
+ BindShader(_sInstancedSprites);
// Set up vertex buffer and parameters.
unsigned int stride = sizeof(Vertex);
@@ -3070,11 +3116,6 @@ namespace TEN::Renderer
// Clear just the Z-buffer to start drawing on top of horizon.
_context->ClearDepthStencilView(depthTarget, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
-
- // Reset the GPU state
- SetDepthState(DepthState::Write);
- SetBlendMode(BlendMode::Opaque);
- SetCullMode(CullMode::CounterClockwise);
}
void Renderer::Render(float interpFactor)
@@ -3231,7 +3272,7 @@ namespace TEN::Renderer
return true;
}
- void Renderer::DrawSortedFaces(RenderView& view)
+ void Renderer::SortTransparentFaces(RenderView& view)
{
std::sort(
view.TransparentObjectsToDraw.begin(),
@@ -3241,15 +3282,21 @@ namespace TEN::Renderer
return (a.Distance > b.Distance);
}
);
+ }
+ void Renderer::DrawSortedFaces(RenderView& view)
+ {
for (int i = 0; i < view.TransparentObjectsToDraw.size(); i++)
{
- RendererSortableObject* object = &view.TransparentObjectsToDraw[i];
- RendererObjectType lastObjectType = (i > 0 ? view.TransparentObjectsToDraw[i - 1].ObjectType : RendererObjectType::Unknown);
+ auto* object = &view.TransparentObjectsToDraw[i];
+ auto lastObjectType = (i > 0 ? view.TransparentObjectsToDraw[i - 1].ObjectType : RendererObjectType::Unknown);
_sortedPolygonsVertices.clear();
_sortedPolygonsIndices.clear();
+ if (_currentMirror != nullptr && object->ObjectType == RendererObjectType::Room)
+ continue;
+
if (object->ObjectType == RendererObjectType::Room)
{
while (i < view.TransparentObjectsToDraw.size() &&
@@ -3259,7 +3306,7 @@ namespace TEN::Renderer
view.TransparentObjectsToDraw[i].Bucket->BlendMode == object->Bucket->BlendMode &&
_sortedPolygonsIndices.size() + (view.TransparentObjectsToDraw[i].Polygon->Shape == 0 ? 6 : 3) < MAX_TRANSPARENT_VERTICES)
{
- RendererSortableObject* currentObject = &view.TransparentObjectsToDraw[i];
+ auto* currentObject = &view.TransparentObjectsToDraw[i];
_sortedPolygonsIndices.bulk_push_back(
_roomsIndices.data(),
currentObject->Polygon->BaseIndex,
@@ -3270,9 +3317,7 @@ namespace TEN::Renderer
DrawRoomSorted(object, lastObjectType, view);
if (i == view.TransparentObjectsToDraw.size())
- {
return;
- }
i--;
}
@@ -3285,7 +3330,7 @@ namespace TEN::Renderer
view.TransparentObjectsToDraw[i].Bucket->BlendMode == object->Bucket->BlendMode &&
_sortedPolygonsIndices.size() + (view.TransparentObjectsToDraw[i].Polygon->Shape == 0 ? 6 : 3) < MAX_TRANSPARENT_VERTICES)
{
- RendererSortableObject* currentObject = &view.TransparentObjectsToDraw[i];
+ auto* currentObject = &view.TransparentObjectsToDraw[i];
_sortedPolygonsIndices.bulk_push_back(
_moveablesIndices.data(),
currentObject->Polygon->BaseIndex,
@@ -3296,9 +3341,7 @@ namespace TEN::Renderer
DrawItemSorted(object, lastObjectType, view);
if (i == view.TransparentObjectsToDraw.size())
- {
return;
- }
i--;
}
@@ -3312,7 +3355,7 @@ namespace TEN::Renderer
view.TransparentObjectsToDraw[i].Bucket->BlendMode == object->Bucket->BlendMode &&
_sortedPolygonsIndices.size() + (view.TransparentObjectsToDraw[i].Polygon->Shape == 0 ? 6 : 3) < MAX_TRANSPARENT_VERTICES)
{
- RendererSortableObject* currentObject = &view.TransparentObjectsToDraw[i];
+ auto* currentObject = &view.TransparentObjectsToDraw[i];
_sortedPolygonsIndices.bulk_push_back(
_staticsIndices.data(),
currentObject->Polygon->BaseIndex,
@@ -3323,9 +3366,7 @@ namespace TEN::Renderer
DrawStaticSorted(object, lastObjectType, view);
if (i == view.TransparentObjectsToDraw.size())
- {
return;
- }
i--;
}
@@ -3338,7 +3379,7 @@ namespace TEN::Renderer
view.TransparentObjectsToDraw[i].Bucket->BlendMode == object->Bucket->BlendMode &&
_sortedPolygonsIndices.size() + (view.TransparentObjectsToDraw[i].Polygon->Shape == 0 ? 6 : 3) < MAX_TRANSPARENT_VERTICES)
{
- RendererSortableObject* currentObject = &view.TransparentObjectsToDraw[i];
+ auto* currentObject = &view.TransparentObjectsToDraw[i];
_sortedPolygonsIndices.bulk_push_back(
_staticsIndices.data(),
currentObject->Polygon->BaseIndex,
@@ -3349,9 +3390,7 @@ namespace TEN::Renderer
DrawMoveableAsStaticSorted(object, lastObjectType, view);
if (i == view.TransparentObjectsToDraw.size())
- {
return;
- }
i--;
}
@@ -3400,23 +3439,25 @@ namespace TEN::Renderer
uv2 = spr->Sprite->UV[2];
uv3 = spr->Sprite->UV[3];
+ auto world = GetWorldMatrixForSprite(currentObject->Sprite, view);
+
Vertex v0;
- v0.Position = Vector3::Transform(p0t, currentObject->World);
+ v0.Position = Vector3::Transform(p0t, world);
v0.UV = uv0;
v0.Color = spr->c1;
Vertex v1;
- v1.Position = Vector3::Transform(p1t, currentObject->World);
+ v1.Position = Vector3::Transform(p1t, world);
v1.UV = uv1;
v1.Color = spr->c2;
Vertex v2;
- v2.Position = Vector3::Transform(p2t, currentObject->World);
+ v2.Position = Vector3::Transform(p2t, world);
v2.UV = uv2;
v2.Color = spr->c3;
Vertex v3;
- v3.Position = Vector3::Transform(p3t, currentObject->World);
+ v3.Position = Vector3::Transform(p3t, world);
v3.UV = uv3;
v3.Color = spr->c4;
@@ -3449,7 +3490,7 @@ namespace TEN::Renderer
ROOM_INFO* nativeRoom = &g_Level.Rooms[objectInfo->Room->RoomNumber];
- _context->PSSetShader(_psRooms.Get(), nullptr, 0);
+ BindShader(_sRooms);
UINT stride = sizeof(Vertex);
UINT offset = 0;
@@ -3465,14 +3506,8 @@ namespace TEN::Renderer
SetScissor(objectInfo->Room->ClipBounds);
- if (objectInfo->Bucket->Animated == 0)
- {
- _context->VSSetShader(_vsRooms.Get(), nullptr, 0);
- }
- else
- {
- _context->VSSetShader(_vsRoomsAnimatedTextures.Get(), nullptr, 0);
- }
+ if (objectInfo->Bucket->Animated != 0)
+ BindShader(_sRoomsAnimated);
SetBlendMode(objectInfo->Bucket->BlendMode);
SetAlphaTest(AlphaTestMode::None, ALPHA_TEST_THRESHOLD);
@@ -3538,11 +3573,11 @@ namespace TEN::Renderer
SetBlendMode(objectInfo->Bucket->BlendMode);
SetAlphaTest(AlphaTestMode::None, ALPHA_TEST_THRESHOLD);
- _context->VSSetShader(_vsItems.Get(), nullptr, 0);
- _context->PSSetShader(_psItems.Get(), nullptr, 0);
+ BindShader(_sItems);
// Bind main item properties.
- _stItem.World = objectInfo->Item->InterpolatedWorld;
+ Matrix world = objectInfo->Item->InterpolatedWorld;
+ _stItem.World = world;
_stItem.Color = objectInfo->Item->Color;
_stItem.AmbientLight = objectInfo->Item->AmbientLight;
memcpy(_stItem.BonesMatrices, objectInfo->Item->InterpolatedAnimTransforms, sizeof(Matrix) * MAX_BONES);
@@ -3551,7 +3586,8 @@ namespace TEN::Renderer
for (int k = 0; k < moveableObj.ObjectMeshes.size(); k++)
_stItem.BoneLightModes[k] = (int)moveableObj.ObjectMeshes[k]->LightMode;
- BindMoveableLights(objectInfo->Item->LightsToDraw, objectInfo->Item->RoomNumber, objectInfo->Item->PrevRoomNumber, objectInfo->Item->LightFade);
+ bool acceptsShadows = moveableObj.ShadowType == ShadowMode::None;
+ BindMoveableLights(objectInfo->Item->LightsToDraw, objectInfo->Item->RoomNumber, objectInfo->Item->PrevRoomNumber, objectInfo->Item->LightFade, acceptsShadows);
_cbItem.UpdateData(_stItem, _context.Get());
BindTexture(
@@ -3578,10 +3614,11 @@ namespace TEN::Renderer
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
_context->IASetInputLayout(_inputLayout.Get());
- _context->VSSetShader(_vsStatics.Get(), nullptr, 0);
- _context->PSSetShader(_psStatics.Get(), nullptr, 0);
+ BindShader(_sStatics);
+
+ auto world = objectInfo->Static->World;
+ _stStatic.World = world;
- _stStatic.World = objectInfo->Static->World;
_stStatic.Color = objectInfo->Static->Color;
_stStatic.AmbientLight = objectInfo->Room->AmbientLight;
_stStatic.LightMode = (int)GetStaticRendererObject(objectInfo->Static->ObjectNumber).ObjectMeshes[0]->LightMode;
@@ -3615,10 +3652,11 @@ namespace TEN::Renderer
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
_context->IASetInputLayout(_inputLayout.Get());
- _context->VSSetShader(_vsStatics.Get(), nullptr, 0);
- _context->PSSetShader(_psStatics.Get(), nullptr, 0);
+ BindShader(_sStatics);
+
+ auto world = objectInfo->World;
+ _stStatic.World = world;
- _stStatic.World = objectInfo->World;
_stStatic.Color = Vector4::One;
_stStatic.AmbientLight = objectInfo->Room->AmbientLight;
_stStatic.LightMode = (int)objectInfo->Mesh->LightMode;
@@ -3646,15 +3684,17 @@ namespace TEN::Renderer
void Renderer::CalculateSSAO(RenderView& view)
{
+ _doingFullscreenPass = true;
+
SetBlendMode(BlendMode::Opaque);
SetCullMode(CullMode::CounterClockwise);
SetDepthState(DepthState::Write);
// Common vertex shader to all full screen effects
- _context->VSSetShader(_vsPostProcess.Get(), nullptr, 0);
+ BindShader(_sPostProcess);
// SSAO pixel shader
- _context->PSSetShader(_psSSAO.Get(), nullptr, 0);
+ BindShader(_sSSAO);
_context->ClearRenderTargetView(_SSAORenderTarget.RenderTargetView.Get(), Colors::White);
_context->OMSetRenderTargets(1, _SSAORenderTarget.RenderTargetView.GetAddressOf(), nullptr);
@@ -3698,7 +3738,7 @@ namespace TEN::Renderer
DrawTriangles(3, 0);
// Blur step
- _context->PSSetShader(_psSSAOBlur.Get(), nullptr, 0);
+ BindShader(_sSSAOBlur);
_context->ClearRenderTargetView(_SSAOBlurredRenderTarget.RenderTargetView.Get(), Colors::Black);
_context->OMSetRenderTargets(1, _SSAOBlurredRenderTarget.RenderTargetView.GetAddressOf(), nullptr);
@@ -3706,6 +3746,8 @@ namespace TEN::Renderer
BindRenderTargetAsTexture(TextureRegister::SSAO, &_SSAORenderTarget, SamplerStateRegister::PointWrap);
DrawTriangles(3, 0);
+
+ _doingFullscreenPass = false;
}
void Renderer::InterpolateCamera(float interpFactor)
diff --git a/TombEngine/Renderer/RendererDraw2D.cpp b/TombEngine/Renderer/RendererDraw2D.cpp
index 93194e04e..698b9dea9 100644
--- a/TombEngine/Renderer/RendererDraw2D.cpp
+++ b/TombEngine/Renderer/RendererDraw2D.cpp
@@ -132,8 +132,8 @@ namespace TEN::Renderer
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
_context->IASetIndexBuffer(bar.IndexBufferBorder.Buffer.Get(), DXGI_FORMAT_R32_UINT, 0);
- _context->VSSetShader(_vsHUD.Get(), nullptr, 0);
- _context->PSSetShader(_psHUDTexture.Get(), nullptr, 0);
+ BindShader(_sHUD);
+ BindShader(_sHUDTexture);
SetBlendMode(BlendMode::Opaque);
SetDepthState(DepthState::None);
@@ -161,8 +161,8 @@ namespace TEN::Renderer
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
_context->IASetIndexBuffer(bar.InnerIndexBuffer.Buffer.Get(), DXGI_FORMAT_R32_UINT, 0);
- _context->VSSetShader(_vsHUD.Get(), nullptr, 0);
- _context->PSSetShader(_psHUDBarColor.Get(), nullptr, 0);
+ BindShader(_sHUD);
+ BindShader(_sHUDBarColor);
_stHUDBar.Percent = percent;
_stHUDBar.Poisoned = isPoisoned;
@@ -195,8 +195,8 @@ namespace TEN::Renderer
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
_context->IASetIndexBuffer(g_LoadingBar->IndexBufferBorder.Buffer.Get(), DXGI_FORMAT_R32_UINT, 0);
- _context->VSSetShader(_vsHUD.Get(), NULL, 0);
- _context->PSSetShader(_psHUDTexture.Get(), NULL, 0);
+ BindShader(_sHUD);
+ BindShader(_sHUDTexture);
SetBlendMode(BlendMode::Opaque);
SetDepthState(DepthState::None);
@@ -220,8 +220,8 @@ namespace TEN::Renderer
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
_context->IASetIndexBuffer(g_LoadingBar->InnerIndexBuffer.Buffer.Get(), DXGI_FORMAT_R32_UINT, 0);
- _context->VSSetShader(_vsHUD.Get(), nullptr, 0);
- _context->PSSetShader(_psHUDBarColor.Get(), nullptr, 0);
+ BindShader(_sHUD);
+ BindShader(_sHUDBarColor);
_stHUDBar.Percent = percentage / 100.0f;
_stHUDBar.Poisoned = false;
@@ -305,8 +305,7 @@ namespace TEN::Renderer
vertices[3].UV.y = 1.0f;
vertices[3].Color = Vector4(1.0f, 0.0f, 0.0f, 1.0f);
- _context->VSSetShader(_vsFullScreenQuad.Get(), nullptr, 0);
- _context->PSSetShader(_psFullScreenQuad.Get(), nullptr, 0);
+ BindShader(_sFullScreenQuad);
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
_context->IASetInputLayout(_inputLayout.Get());
@@ -342,8 +341,7 @@ namespace TEN::Renderer
if (renderView.DisplaySpritesToDraw.empty())
return;
- _context->VSSetShader(_vsFullScreenQuad.Get(), nullptr, 0);
- _context->PSSetShader(_psFullScreenQuad.Get(), nullptr, 0);
+ BindShader(_sFullScreenQuad);
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
_context->IASetInputLayout(_inputLayout.Get());
@@ -465,8 +463,7 @@ namespace TEN::Renderer
vertices[3].UV.y = uvEnd.y;
vertices[3].Color = colorVec4;
- _context->VSSetShader(_vsFullScreenQuad.Get(), nullptr, 0);
- _context->PSSetShader(_psFullScreenQuad.Get(), nullptr, 0);
+ BindShader(_sFullScreenQuad);
_context->PSSetShaderResources(0, 1, &texture);
@@ -543,8 +540,7 @@ namespace TEN::Renderer
vertices[3].UV.y = uvEnd.y;
vertices[3].Color = Vector4(color.x, color.y, color.z, 1.0f);
- _context->VSSetShader(_vsFullScreenQuad.Get(), nullptr, 0);
- _context->PSSetShader(_psFullScreenQuad.Get(), nullptr, 0);
+ BindShader(_sFullScreenQuad);
_context->PSSetShaderResources(0, 1, &texture);
auto* sampler = _renderStates->AnisotropicClamp();
diff --git a/TombEngine/Renderer/RendererDrawEffect.cpp b/TombEngine/Renderer/RendererDrawEffect.cpp
index 66a845e45..24c605741 100644
--- a/TombEngine/Renderer/RendererDrawEffect.cpp
+++ b/TombEngine/Renderer/RendererDrawEffect.cpp
@@ -1010,20 +1010,19 @@ namespace TEN::Renderer
}
}
- bool Renderer::DrawGunFlashes(RenderView& view)
+ bool Renderer::DrawGunFlashes(RenderView& view)
{
if (!Lara.RightArm.GunFlash && !Lara.LeftArm.GunFlash)
return false;
- if (Lara.Control.Look.OpticRange > 0)
+ if (Lara.Control.Look.OpticRange > 0 && _currentMirror == nullptr)
return false;
const auto& settings = g_GameFlow->GetSettings()->Weapons[(int)Lara.Control.Weapon.GunType - 1];
if (!settings.MuzzleFlash)
return false;
- _context->VSSetShader(_vsStatics.Get(), nullptr, 0);
- _context->PSSetShader(_psStatics.Get(), nullptr, 0);
+ BindShader(_sStatics);
unsigned int stride = sizeof(Vertex);
unsigned int offset = 0;
@@ -1111,6 +1110,7 @@ namespace TEN::Renderer
worldMatrix = itemPtr->AnimTransforms[LM_LHAND] * itemPtr->World;
worldMatrix = tMatrix * worldMatrix;
worldMatrix = rotMatrix * worldMatrix;
+ ReflectMatrixOptionally(worldMatrix);
_stStatic.World = worldMatrix;
_cbStatic.UpdateData(_stStatic, _context.Get());
@@ -1125,6 +1125,7 @@ namespace TEN::Renderer
worldMatrix = itemPtr->AnimTransforms[LM_RHAND] * itemPtr->World;
worldMatrix = tMatrix * worldMatrix;
worldMatrix = rotMatrix * worldMatrix;
+ ReflectMatrixOptionally(worldMatrix);
_stStatic.World = worldMatrix;
_cbStatic.UpdateData(_stStatic, _context.Get());
@@ -1142,8 +1143,7 @@ namespace TEN::Renderer
void Renderer::DrawBaddyGunflashes(RenderView& view)
{
- _context->VSSetShader(_vsStatics.Get(), nullptr, 0);
- _context->PSSetShader(_psStatics.Get(), nullptr, 0);
+ BindShader(_sStatics);
unsigned int stride = sizeof(Vertex);
unsigned int offset = 0;
@@ -1153,6 +1153,9 @@ namespace TEN::Renderer
for (auto* rRoomPtr : view.RoomsToDraw)
{
+ if (IgnoreReflectionPassForRoom(rRoomPtr->RoomNumber))
+ continue;
+
for (auto* rItemPtr : rRoomPtr->ItemsToDraw)
{
auto& nativeItem = g_Level.Items[rItemPtr->ItemNumber];
@@ -1202,6 +1205,8 @@ namespace TEN::Renderer
if (creature.MuzzleFlash[0].ApplyZRotation)
worldMatrix = rotMatrixZ * worldMatrix;
+ ReflectMatrixOptionally(worldMatrix);
+
_stStatic.World = worldMatrix;
_cbStatic.UpdateData(_stStatic, _context.Get());
@@ -1242,6 +1247,8 @@ namespace TEN::Renderer
if (creature.MuzzleFlash[1].ApplyZRotation)
worldMatrix = rotMatrixZ * worldMatrix;
+ ReflectMatrixOptionally(worldMatrix);
+
_stStatic.World = worldMatrix;
_cbStatic.UpdateData(_stStatic, _context.Get());
@@ -1278,12 +1285,23 @@ namespace TEN::Renderer
auto spriteMatrix = Matrix::Identity;
auto scaleMatrix = Matrix::CreateScale(sprite->Width * sprite->Scale, sprite->Height * sprite->Scale, sprite->Scale);
+ auto spritePos = sprite->pos;
+
+ if (sprite->Type == SpriteType::ThreeD)
+ {
+ ReflectMatrixOptionally(spriteMatrix);
+ }
+ else
+ {
+ ReflectVectorOptionally(spritePos);
+ }
+
switch (sprite->Type)
{
case SpriteType::Billboard:
{
auto cameraUp = Vector3(view.Camera.View._12, view.Camera.View._22, view.Camera.View._32);
- spriteMatrix = scaleMatrix * Matrix::CreateRotationZ(sprite->Rotation) * Matrix::CreateBillboard(sprite->pos, Camera.pos.ToVector3(), cameraUp);
+ spriteMatrix = scaleMatrix * Matrix::CreateRotationZ(sprite->Rotation) * Matrix::CreateBillboard(spritePos, Camera.pos.ToVector3(), cameraUp);
}
break;
@@ -1292,7 +1310,7 @@ namespace TEN::Renderer
auto rotMatrix = Matrix::CreateRotationY(sprite->Rotation);
auto quadForward = Vector3(0.0f, 0.0f, 1.0f);
spriteMatrix = scaleMatrix * Matrix::CreateConstrainedBillboard(
- sprite->pos,
+ spritePos,
Camera.pos.ToVector3(),
sprite->ConstrainAxis,
nullptr,
@@ -1302,9 +1320,9 @@ namespace TEN::Renderer
case SpriteType::LookAtBillboard:
{
- auto tMatrix = Matrix::CreateTranslation(sprite->pos);
+ auto translationMatrix = Matrix::CreateTranslation(spritePos);
auto rotMatrix = Matrix::CreateRotationZ(sprite->Rotation) * Matrix::CreateLookAt(Vector3::Zero, sprite->LookAtAxis, Vector3::UnitZ);
- spriteMatrix = scaleMatrix * rotMatrix * tMatrix;
+ spriteMatrix = scaleMatrix * rotMatrix * translationMatrix;
}
break;
@@ -1316,11 +1334,14 @@ namespace TEN::Renderer
return spriteMatrix;
}
- void Renderer::DrawEffect(RenderView& view, RendererEffect* effect, RendererPass rendererPass)
+ void Renderer::DrawEffect(RenderView& view, RendererEffect* effect, RendererPass rendererPass)
{
const auto& room = _rooms[effect->RoomNumber];
- _stStatic.World = effect->InterpolatedWorld;
+ auto world = effect->InterpolatedWorld;
+ ReflectMatrixOptionally(world);
+
+ _stStatic.World = world;
_stStatic.Color = effect->Color;
_stStatic.AmbientLight = effect->AmbientLight;
_stStatic.LightMode = (int)LightMode::Dynamic;
@@ -1350,10 +1371,9 @@ namespace TEN::Renderer
}
}
- void Renderer::DrawEffects(RenderView& view, RendererPass rendererPass)
+ void Renderer::DrawEffects(RenderView& view, RendererPass rendererPass)
{
- _context->VSSetShader(_vsStatics.Get(), nullptr, 0);
- _context->PSSetShader(_psStatics.Get(), nullptr, 0);
+ BindShader(_sStatics);
unsigned int stride = sizeof(Vertex);
unsigned int offset = 0;
@@ -1363,6 +1383,9 @@ namespace TEN::Renderer
for (auto* roomPtr : view.RoomsToDraw)
{
+ if (IgnoreReflectionPassForRoom(roomPtr->RoomNumber))
+ continue;
+
for (auto* effectPtr : roomPtr->EffectsToDraw)
{
const auto& room = _rooms[effectPtr->RoomNumber];
@@ -1381,6 +1404,9 @@ namespace TEN::Renderer
{
if (deb.active)
{
+ if (IgnoreReflectionPassForRoom(deb.roomNumber))
+ continue;
+
activeDebrisExist = true;
break;
}
@@ -1388,8 +1414,7 @@ namespace TEN::Renderer
if (activeDebrisExist)
{
- _context->VSSetShader(_vsStatics.Get(), nullptr, 0);
- _context->PSSetShader(_psStatics.Get(), nullptr, 0);
+ BindShader(_sStatics);
SetCullMode(CullMode::None);
@@ -1399,6 +1424,9 @@ namespace TEN::Renderer
{
if (deb.active)
{
+ if (IgnoreReflectionPassForRoom(deb.roomNumber))
+ continue;
+
if (!SetupBlendModeAndAlphaTest(deb.mesh.blendMode, rendererPass, 0))
continue;
@@ -1419,6 +1447,7 @@ namespace TEN::Renderer
_cbStatic.UpdateData(_stStatic, _context.Get());
auto matrix = Matrix::Lerp(deb.PrevTransform, deb.Transform, GetInterpolationFactor());
+ ReflectMatrixOptionally(matrix);
Vertex vtx0;
vtx0.Position = Vector3::Transform(deb.mesh.Positions[0], matrix);
@@ -1453,7 +1482,6 @@ namespace TEN::Renderer
SetBlendMode(BlendMode::Opaque, true);
SetDepthState(DepthState::Write, true);
- SetCullMode(CullMode::CounterClockwise, true);
}
}
diff --git a/TombEngine/Renderer/RendererDrawMenu.cpp b/TombEngine/Renderer/RendererDrawMenu.cpp
index a7d9cb809..1da3c1df4 100644
--- a/TombEngine/Renderer/RendererDrawMenu.cpp
+++ b/TombEngine/Renderer/RendererDrawMenu.cpp
@@ -817,8 +817,7 @@ namespace TEN::Renderer
_context->IASetIndexBuffer(_moveablesIndexBuffer.Buffer.Get(), DXGI_FORMAT_R32_UINT, 0);
// Set shaders.
- _context->VSSetShader(_vsInventory.Get(), nullptr, 0);
- _context->PSSetShader(_psInventory.Get(), nullptr, 0);
+ BindShader(_sInventory);
// Set matrices.
CCameraMatrixBuffer hudCamera;
@@ -890,7 +889,7 @@ namespace TEN::Renderer
if (!texture.Texture)
return;
- int timeout = 20;
+ int timeout = 10;
float currentFade = FADE_FACTOR;
while (timeout || currentFade > 0.0f)
@@ -1011,8 +1010,7 @@ namespace TEN::Renderer
_context->IASetIndexBuffer(_moveablesIndexBuffer.Buffer.Get(), DXGI_FORMAT_R32_UINT, 0);
// Set shaders
- _context->VSSetShader(_vsInventory.Get(), nullptr, 0);
- _context->PSSetShader(_psInventory.Get(), nullptr, 0);
+ BindShader(_sInventory);
if (CurrentLevel == 0)
{
diff --git a/TombEngine/Renderer/RendererEnums.h b/TombEngine/Renderer/RendererEnums.h
index 23df7a369..e71f4af86 100644
--- a/TombEngine/Renderer/RendererEnums.h
+++ b/TombEngine/Renderer/RendererEnums.h
@@ -226,6 +226,7 @@ enum class RendererPass
CollectTransparentFaces,
Additive,
GBuffer,
+ GunFlashes,
RoomAmbient
};
diff --git a/TombEngine/Renderer/RendererFrame.cpp b/TombEngine/Renderer/RendererFrame.cpp
index 5b919f28b..c47b5f33d 100644
--- a/TombEngine/Renderer/RendererFrame.cpp
+++ b/TombEngine/Renderer/RendererFrame.cpp
@@ -14,10 +14,12 @@
#include "Renderer/RenderView.h"
#include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h"
#include "Specific/level.h"
+#include "Specific/trutils.h"
using namespace TEN::Entities::Effects;
using namespace TEN::Collision::Sphere;
using namespace TEN::Math;
+using namespace TEN::Utils;
namespace TEN::Renderer
{
@@ -355,102 +357,128 @@ namespace TEN::Renderer
_visitedRoomsStack.pop_back();
}
+ void Renderer::CollectMirrors(RenderView& renderView)
+ {
+ // Collect mirrors first because they are needed while collecting moveables.
+ for (const auto& mirror : g_Level.Mirrors)
+ {
+ // TODO: Avoid LaraItem global.
+ if (mirror.RoomNumber != Camera.pos.RoomNumber && mirror.RoomNumber != LaraItem->RoomNumber)
+ continue;
+
+ if (!mirror.Enabled)
+ continue;
+
+ auto& rendererMirror = renderView.Mirrors.emplace_back();
+ rendererMirror.RoomNumber = mirror.RoomNumber;
+ rendererMirror.ReflectionMatrix = mirror.ReflectionMatrix;
+ rendererMirror.ReflectPlayer = mirror.ReflectPlayer;
+ rendererMirror.ReflectMoveables = mirror.ReflectMoveables;
+ rendererMirror.ReflectStatics = mirror.ReflectStatics;
+ rendererMirror.ReflectSprites = mirror.ReflectSprites;
+ rendererMirror.ReflectLights = mirror.ReflectLights;
+ }
+ }
+
void Renderer::CollectItems(short roomNumber, RenderView& renderView)
{
if (_rooms.size() < roomNumber)
return;
- auto& room = _rooms[roomNumber];
- auto* r = &g_Level.Rooms[room.RoomNumber];
+ auto& rendererRoom = _rooms[roomNumber];
+ const auto& room = g_Level.Rooms[rendererRoom.RoomNumber];
- short itemNum = NO_VALUE;
- for (itemNum = r->itemNumber; itemNum != NO_VALUE; itemNum = g_Level.Items[itemNum].NextItem)
+ bool isRoomReflected = IsRoomReflected(renderView, roomNumber);
+
+ short itemNumber = NO_VALUE;
+ for (itemNumber = room.itemNumber; itemNumber != NO_VALUE; itemNumber = g_Level.Items[itemNumber].NextItem)
{
- auto* item = &g_Level.Items[itemNum];
+ const auto& item = g_Level.Items[itemNumber];
- if (item->ObjectNumber == ID_LARA && itemNum == g_Level.Items[itemNum].NextItem)
+ if (item.ObjectNumber == ID_LARA && itemNumber == g_Level.Items[itemNumber].NextItem)
break;
- if (item->Status == ITEM_INVISIBLE)
+ if (item.Status == ITEM_INVISIBLE)
continue;
- if (item->ObjectNumber == ID_LARA && (Lara.Control.Look.OpticRange || SpotcamOverlay || SpotcamDontDrawLara))
+ if (item.ObjectNumber == ID_LARA && (SpotcamOverlay || SpotcamDontDrawLara))
continue;
- if (item->ObjectNumber == ID_LARA && CurrentLevel == 0 && !g_GameFlow->IsLaraInTitleEnabled())
+ if (item.ObjectNumber == ID_LARA && CurrentLevel == 0 && !g_GameFlow->IsLaraInTitleEnabled())
continue;
- if (!_moveableObjects[item->ObjectNumber].has_value())
+ if (!_moveableObjects[item.ObjectNumber].has_value())
continue;
- auto& obj = _moveableObjects[item->ObjectNumber].value();
+ auto& obj = _moveableObjects[item.ObjectNumber].value();
if (obj.DoNotDraw)
continue;
- // Clip object by frustum only if it doesn't cast shadows. Otherwise we may see
- // disappearing shadows if object gets out of frustum.
-
- if (obj.ShadowType == ShadowMode::None)
+ // Clip object by frustum only if it doesn't cast shadows and is not in mirror room,
+ // otherwise disappearing shadows or reflections may be seen if object gets out of frustum.
+ if (!isRoomReflected && obj.ShadowType == ShadowMode::None)
{
// Get all spheres and check if frustum intersects any of them.
- auto spheres = GetSpheres(itemNum);
+ auto spheres = GetSpheres(itemNumber);
bool inFrustum = false;
for (int i = 0; !inFrustum, i < spheres.size(); i++)
+ {
// Blow up sphere radius by half for cases of too small calculated spheres.
if (renderView.Camera.Frustum.SphereInFrustum(spheres[i].Center, spheres[i].Radius * 1.5f))
inFrustum = true;
+ }
if (!inFrustum)
continue;
}
- auto newItem = &_items[itemNum];
+ auto& newItem = _items[itemNumber];
- newItem->ItemNumber = itemNum;
- newItem->ObjectID = item->ObjectNumber;
- newItem->Color = item->Model.Color;
- newItem->Position = item->Pose.Position.ToVector3();
- newItem->Translation = Matrix::CreateTranslation(newItem->Position.x, newItem->Position.y, newItem->Position.z);
- newItem->Rotation = item->Pose.Orientation.ToRotationMatrix();
- newItem->Scale = Matrix::CreateScale(1.0f);
- newItem->World = newItem->Rotation * newItem->Translation;
+ newItem.ItemNumber = itemNumber;
+ newItem.ObjectID = item.ObjectNumber;
+ newItem.Color = item.Model.Color;
+ newItem.Position = item.Pose.Position.ToVector3();
+ newItem.Translation = Matrix::CreateTranslation(newItem.Position.x, newItem.Position.y, newItem.Position.z);
+ newItem.Rotation = item.Pose.Orientation.ToRotationMatrix();
+ newItem.Scale = Matrix::CreateScale(1.0f);
+ newItem.World = newItem.Rotation * newItem.Translation;
// Disable interpolation either when renderer slot or item slot has flag.
- // Renderer slot has no interpolation flag set in case it is fetched for the first time (e.g. item first time in frustum).
- newItem->DisableInterpolation = item->DisableInterpolation || newItem->DisableInterpolation;
+ // Renderer slot has no interpolation flag set in case it is fetched for first time (e.g. item first time in frustum).
+ newItem.DisableInterpolation = item.DisableInterpolation || newItem.DisableInterpolation;
- if (newItem->DisableInterpolation)
+ if (newItem.DisableInterpolation)
{
- // NOTE: Interpolation alwasy returns same result.
- newItem->PrevPosition = newItem->Position;
- newItem->PrevTranslation = newItem->Translation;
- newItem->PrevRotation = newItem->Rotation;
- newItem->PrevWorld = newItem->World;
+ // NOTE: Interpolation always returns same result.
+ newItem.PrevPosition = newItem.Position;
+ newItem.PrevTranslation = newItem.Translation;
+ newItem.PrevRotation = newItem.Rotation;
+ newItem.PrevWorld = newItem.World;
- // Otherwise all frames until the next ControlPhase will not be interpolated.
- newItem->DisableInterpolation = false;
+ // Otherwise all frames until next ControlPhase will not be interpolated.
+ newItem.DisableInterpolation = false;
for (int j = 0; j < MAX_BONES; j++)
- newItem->PrevAnimTransforms[j] = newItem->AnimTransforms[j];
+ newItem.PrevAnimTransforms[j] = newItem.AnimTransforms[j];
}
- // Force interpolation only for Lara in player freeze mode.
- bool forceValue = g_GameFlow->CurrentFreezeMode == FreezeMode::Player && item->ObjectNumber == ID_LARA;
+ // Force interpolation only for player in player freeze mode.
+ bool forceValue = g_GameFlow->CurrentFreezeMode == FreezeMode::Player && item.ObjectNumber == ID_LARA;
- newItem->InterpolatedPosition = Vector3::Lerp(newItem->PrevPosition, newItem->Position, GetInterpolationFactor(forceValue));
- newItem->InterpolatedTranslation = Matrix::Lerp(newItem->PrevTranslation, newItem->Translation, GetInterpolationFactor(forceValue));
- newItem->InterpolatedRotation = Matrix::Lerp(newItem->InterpolatedRotation, newItem->Rotation, GetInterpolationFactor(forceValue));
- newItem->InterpolatedWorld = Matrix::Lerp(newItem->PrevWorld, newItem->World, GetInterpolationFactor(forceValue));
+ newItem.InterpolatedPosition = Vector3::Lerp(newItem.PrevPosition, newItem.Position, GetInterpolationFactor(forceValue));
+ newItem.InterpolatedTranslation = Matrix::Lerp(newItem.PrevTranslation, newItem.Translation, GetInterpolationFactor(forceValue));
+ newItem.InterpolatedRotation = Matrix::Lerp(newItem.InterpolatedRotation, newItem.Rotation, GetInterpolationFactor(forceValue));
+ newItem.InterpolatedWorld = Matrix::Lerp(newItem.PrevWorld, newItem.World, GetInterpolationFactor(forceValue));
for (int j = 0; j < MAX_BONES; j++)
- newItem->InterpolatedAnimTransforms[j] = Matrix::Lerp(newItem->PrevAnimTransforms[j], newItem->AnimTransforms[j], GetInterpolationFactor(forceValue));
+ newItem.InterpolatedAnimTransforms[j] = Matrix::Lerp(newItem.PrevAnimTransforms[j], newItem.AnimTransforms[j], GetInterpolationFactor(forceValue));
- CalculateLightFades(newItem);
- CollectLightsForItem(newItem);
+ CalculateLightFades(&newItem);
+ CollectLightsForItem(&newItem);
- room.ItemsToDraw.push_back(newItem);
+ rendererRoom.ItemsToDraw.push_back(&newItem);
}
}
@@ -465,6 +493,8 @@ namespace TEN::Renderer
if (r->mesh.empty())
return;
+ bool isRoomReflected = IsRoomReflected(renderView, roomNumber);
+
for (int i = 0; i < room.Statics.size(); i++)
{
auto* mesh = &room.Statics[i];
@@ -493,9 +523,9 @@ namespace TEN::Renderer
if (obj.ObjectMeshes.empty())
continue;
- if (!renderView.Camera.Frustum.SphereInFrustum(mesh->Sphere.Center, mesh->Sphere.Radius))
+ if (!isRoomReflected && !renderView.Camera.Frustum.SphereInFrustum(mesh->Sphere.Center, mesh->Sphere.Radius))
continue;
-
+
// Collect the lights
std::vector lights;
std::vector cachedRoomLights;
@@ -717,13 +747,11 @@ namespace TEN::Renderer
void Renderer::CollectLightsForRoom(short roomNumber, RenderView &renderView)
{
if (_rooms.size() < roomNumber)
- {
return;
- }
RendererRoom& room = _rooms[roomNumber];
ROOM_INFO* r = &g_Level.Rooms[roomNumber];
-
+
// Collect dynamic lights for rooms
for (int i = 0; i < _dynamicLights[_dynamicLightList].size(); i++)
{
diff --git a/TombEngine/Renderer/RendererHelper.cpp b/TombEngine/Renderer/RendererHelper.cpp
index 2e6a96965..0a28218af 100644
--- a/TombEngine/Renderer/RendererHelper.cpp
+++ b/TombEngine/Renderer/RendererHelper.cpp
@@ -600,6 +600,18 @@ namespace TEN::Renderer
return rendererItem->BoneOrientations[boneID];
}
+ bool Renderer::IsRoomReflected(RenderView& renderView, int roomNumber)
+ {
+ for (const auto& mirror : renderView.Mirrors)
+ {
+ // TODO: Avoid LaraItem global.
+ if (roomNumber == mirror.RoomNumber && (Camera.pos.RoomNumber == mirror.RoomNumber || LaraItem->RoomNumber == mirror.RoomNumber))
+ return true;
+ }
+
+ return false;
+ }
+
void Renderer::SaveScreenshot()
{
char buffer[64];
diff --git a/TombEngine/Renderer/RendererInit.cpp b/TombEngine/Renderer/RendererInit.cpp
index 08d9b11a9..d0c0f6655 100644
--- a/TombEngine/Renderer/RendererInit.cpp
+++ b/TombEngine/Renderer/RendererInit.cpp
@@ -1,19 +1,23 @@
#include "framework.h"
-#include
-#include
-#include
+
+// TODO: To framework.
+#include
+#include
+
#include "Renderer/Renderer.h"
+#include "Renderer/RendererUtils.h"
+#include "Renderer/SMAA/AreaTex.h"
+#include "Renderer/SMAA/SearchTex.h"
#include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h"
#include "Specific/configuration.h"
#include "Specific/memory/Vector.h"
#include "Specific/trutils.h"
#include "Specific/winmain.h"
-#include "Renderer/SMAA/AreaTex.h"
-#include "Renderer/SMAA/SearchTex.h"
-#include
extern GameConfiguration g_Configuration;
+using namespace TEN::Renderer::Utils;
+
static std::wstring GetAssetPath(const wchar_t* fileName)
{
return TEN::Utils::ToWString(g_GameFlow->GetGameDir()) + fileName;
@@ -35,12 +39,9 @@ namespace TEN::Renderer
_renderStates = std::make_unique(_device.Get());
// Load shaders
- ComPtr blob;
const D3D_SHADER_MACRO roomDefinesAnimated[] = { "ANIMATED", "", nullptr, nullptr };
const D3D_SHADER_MACRO roomDefinesShadowMap[] = { "SHADOW_MAP", "", nullptr, nullptr };
- _vsRooms = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\Rooms.fx"), "VS", "vs_5_0", nullptr, blob);
-
// Initialize input layout using first vertex shader.
D3D11_INPUT_ELEMENT_DESC inputLayoutItems[] =
{
@@ -57,50 +58,41 @@ namespace TEN::Renderer
{ "DRAWINDEX", 0, DXGI_FORMAT_R32_UINT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "HASH", 0, DXGI_FORMAT_R32_SINT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }
};
- Utils::throwIfFailed(_device->CreateInputLayout(inputLayoutItems, 12, blob->GetBufferPointer(), blob->GetBufferSize(), &_inputLayout));
- _vsRoomsAnimatedTextures = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\Rooms.fx"), "VS", "vs_5_0", &roomDefinesAnimated[0], blob);
- _psRooms = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\Rooms.fx"), "PS", "ps_5_0", nullptr, blob);
- _vsItems = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\Items.fx"), "VS", "vs_5_0", nullptr, blob);
- _psItems = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\Items.fx"), "PS", "ps_5_0", nullptr, blob);
- _vsStatics = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\Statics.fx"), "VS", "vs_5_0", nullptr, blob);
- _psStatics = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\Statics.fx"), "PS", "ps_5_0", nullptr, blob);
- _vsSky = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\Sky.fx"), "VS", "vs_5_0", nullptr, blob);
- _psSky = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\Sky.fx"), "PS", "ps_5_0", nullptr, blob);
- _vsSprites = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\Sprites.fx"), "VS", "vs_5_0", nullptr, blob);
- _psSprites = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\Sprites.fx"), "PS", "ps_5_0", nullptr, blob);
- _vsSolid = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\Solid.fx"), "VS", "vs_5_0", nullptr, blob);
- _psSolid = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\Solid.fx"), "PS", "ps_5_0", nullptr, blob);
- _vsInventory = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\Inventory.fx"), "VS", "vs_5_0", nullptr, blob);
- _psInventory = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\Inventory.fx"), "PS", "ps_5_0", nullptr, blob);
- _vsFullScreenQuad = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\FullScreenQuad.fx"), "VS", "vs_5_0", nullptr, blob);
- _psFullScreenQuad = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\FullScreenQuad.fx"), "PS", "ps_5_0", nullptr, blob);
- _vsShadowMap = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\ShadowMap.fx"), "VS", "vs_5_0", nullptr, blob);
- _psShadowMap = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\ShadowMap.fx"), "PS", "ps_5_0", nullptr, blob);
- _vsHUD = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\HUD.hlsl"), "VS", "vs_5_0", nullptr, blob);
- _psHUDColor = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\HUD.hlsl"), "PSColoredHUD", "ps_5_0", nullptr, blob);
- _psHUDTexture = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\HUD.hlsl"), "PSTexturedHUD", "ps_5_0", nullptr, blob);
- _psHUDBarColor = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\HUD.hlsl"), "PSTexturedHUDBar", "ps_5_0", nullptr, blob);
- _vsInstancedStaticMeshes = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\InstancedStatics.fx"), "VS", "vs_5_0", nullptr, blob);
- _psInstancedStaticMeshes = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\InstancedStatics.fx"), "PS", "ps_5_0", nullptr, blob);
- _vsInstancedSprites = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\InstancedSprites.fx"), "VS", "vs_5_0", nullptr, blob);
- _psInstancedSprites = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\InstancedSprites.fx"), "PS", "ps_5_0", nullptr, blob);
- _vsGBufferRooms = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\GBuffer.fx"), "VSRooms", "vs_5_0", nullptr, blob);
- _vsGBufferRoomsAnimated = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\GBuffer.fx"), "VSRooms", "vs_5_0", &roomDefinesAnimated[0], blob);
- _vsGBufferItems = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\GBuffer.fx"), "VSItems", "vs_5_0", nullptr, blob);
- _vsGBufferStatics = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\GBuffer.fx"), "VSStatics", "vs_5_0", nullptr, blob);
- _vsGBufferInstancedStatics = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\GBuffer.fx"), "VSInstancedStatics", "vs_5_0", nullptr, blob);
- _psGBuffer = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\GBuffer.fx"), "PS", "ps_5_0", nullptr, blob);
- _vsRoomAmbient = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\RoomAmbient.fx"), "VS", "vs_5_0", nullptr, blob);
- _vsRoomAmbientSky = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\RoomAmbient.fx"), "VSSky", "vs_5_0", nullptr, blob);
- _psRoomAmbient = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\RoomAmbient.fx"), "PS", "ps_5_0", nullptr, blob);
- _vsFXAA = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\FXAA.fx"), "VS", "vs_5_0", nullptr, blob);
- _psFXAA = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\FXAA.fx"), "PS", "ps_5_0", nullptr, blob);
- _psSSAO = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\SSAO.fx"), "PS", "ps_5_0", nullptr, blob);
- _psSSAOBlur = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\SSAO.fx"), "PSBlur", "ps_5_0", nullptr, blob);
+ _sRooms = CompileOrLoadShader("Rooms", "", ShaderType::PixelAndVertex);
+ Utils::throwIfFailed(_device->CreateInputLayout(inputLayoutItems, 12, _sRooms.Vertex.Blob->GetBufferPointer(), _sRooms.Vertex.Blob->GetBufferSize(), &_inputLayout));
+
+ _sRoomsAnimated = CompileOrLoadShader("Rooms", "", ShaderType::Vertex, &roomDefinesAnimated[0]);
+ _sItems = CompileOrLoadShader("Items", "", ShaderType::PixelAndVertex);
+ _sStatics = CompileOrLoadShader("Statics", "", ShaderType::PixelAndVertex);
+ _sSky = CompileOrLoadShader("Sky", "", ShaderType::PixelAndVertex);
+ _sSprites = CompileOrLoadShader("Sprites", "", ShaderType::PixelAndVertex);
+ _sSolid = CompileOrLoadShader("Solid", "", ShaderType::PixelAndVertex);
+ _sInventory = CompileOrLoadShader("Inventory", "", ShaderType::PixelAndVertex);
+ _sFullScreenQuad = CompileOrLoadShader("FullScreenQuad", "", ShaderType::PixelAndVertex);
+ _sShadowMap = CompileOrLoadShader("ShadowMap", "", ShaderType::PixelAndVertex, &roomDefinesShadowMap[0]);
+ _sHUD = CompileOrLoadShader("HUD", "", ShaderType::Vertex);
+ _sHUDColor = CompileOrLoadShader("HUD", "ColoredHUD", ShaderType::Pixel);
+ _sHUDTexture = CompileOrLoadShader("HUD", "TexturedHUD", ShaderType::Pixel);
+ _sHUDBarColor = CompileOrLoadShader("HUD", "TexturedHUDBar", ShaderType::Pixel);
+ _sInstancedStatics = CompileOrLoadShader("InstancedStatics", "", ShaderType::PixelAndVertex);
+ _sInstancedSprites = CompileOrLoadShader("InstancedSprites", "", ShaderType::PixelAndVertex);
+
+ _sGBuffer = CompileOrLoadShader("GBuffer", "", ShaderType::Pixel);
+ _sGBufferRooms = CompileOrLoadShader("GBuffer", "Rooms", ShaderType::Vertex);
+ _sGBufferRoomsAnimated = CompileOrLoadShader("GBuffer", "Rooms", ShaderType::Vertex, &roomDefinesAnimated[0]);
+ _sGBufferItems = CompileOrLoadShader("GBuffer", "Items", ShaderType::Vertex);
+ _sGBufferStatics = CompileOrLoadShader("GBuffer", "Statics", ShaderType::Vertex);
+ _sGBufferInstancedStatics = CompileOrLoadShader("GBuffer", "InstancedStatics", ShaderType::Vertex);
+
+ _sRoomAmbient = CompileOrLoadShader("RoomAmbient", "", ShaderType::PixelAndVertex);
+ _sRoomAmbientSky = CompileOrLoadShader("RoomAmbient", "Sky", ShaderType::Vertex);
+ _sFXAA = CompileOrLoadShader("FXAA", "", ShaderType::Pixel);
+ _sSSAO = CompileOrLoadShader("SSAO", "", ShaderType::Pixel);
+ _sSSAOBlur = CompileOrLoadShader("SSAO", "Blur", ShaderType::Pixel);
const D3D_SHADER_MACRO transparentDefines[] = { "TRANSPARENT", "", nullptr, nullptr };
- _psRoomsTransparent = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\Rooms.fx"), "PS", "ps_5_0", &transparentDefines[0], blob);
+ _sRoomsTransparent = CompileOrLoadShader("Rooms", "", ShaderType::Pixel, &transparentDefines[0]);
// Initialize constant buffers
_cbCameraMatrices = CreateConstantBuffer();
@@ -292,9 +284,7 @@ namespace TEN::Renderer
_fullscreenTriangleVertexBuffer = VertexBuffer(_device.Get(), 3, &vertices[0]);
- ComPtr blob;
-
- _vsPostProcess = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\PostProcess.fx"), "VS", "vs_5_0", nullptr, blob);
+ _sPostProcess = CompileOrLoadShader("PostProcess", "", ShaderType::PixelAndVertex);
D3D11_INPUT_ELEMENT_DESC postProcessInputLayoutItems[] =
{
@@ -302,14 +292,15 @@ namespace TEN::Renderer
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }
};
- Utils::throwIfFailed(_device->CreateInputLayout(postProcessInputLayoutItems, 3, blob->GetBufferPointer(), blob->GetBufferSize(), &_fullscreenTriangleInputLayout));
-
- _psPostProcessCopy = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\PostProcess.fx"), "PSCopy", "ps_5_0", nullptr, blob);
- _psPostProcessMonochrome = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\PostProcess.fx"), "PSMonochrome", "ps_5_0", nullptr, blob);
- _psPostProcessNegative = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\PostProcess.fx"), "PSNegative", "ps_5_0", nullptr, blob);
- _psPostProcessExclusion = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\PostProcess.fx"), "PSExclusion", "ps_5_0", nullptr, blob);
- _psPostProcessFinalPass = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\PostProcess.fx"), "PSFinalPass", "ps_5_0", nullptr, blob);
- _psPostProcessLensFlare = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\PostProcess.fx"), "PSLensFlare", "ps_5_0", nullptr, blob);
+
+ Utils::throwIfFailed(_device->CreateInputLayout(postProcessInputLayoutItems, 3,
+ _sPostProcess.Vertex.Blob->GetBufferPointer(), _sPostProcess.Vertex.Blob->GetBufferSize(), &_fullscreenTriangleInputLayout));
+
+ _sPostProcessMonochrome = CompileOrLoadShader("PostProcess", "Monochrome", ShaderType::Pixel);
+ _sPostProcessNegative = CompileOrLoadShader("PostProcess", "Negative", ShaderType::Pixel);
+ _sPostProcessExclusion = CompileOrLoadShader("PostProcess", "Exclusion", ShaderType::Pixel);
+ _sPostProcessFinalPass = CompileOrLoadShader("PostProcess", "FinalPass", ShaderType::Pixel);
+ _sPostProcessLensFlare = CompileOrLoadShader("PostProcess", "LensFlare", ShaderType::Pixel);
}
void Renderer::CreateSSAONoiseTexture()
@@ -597,16 +588,12 @@ namespace TEN::Renderer
auto null = D3D10_SHADER_MACRO{ nullptr, nullptr };
defines.push_back(null);
- auto blob = ComPtr{};
- _SMAALumaEdgeDetectionPS = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\SMAA.fx"), "DX11_SMAALumaEdgeDetectionPS", "ps_5_0", defines.data(), blob);
- _SMAAColorEdgeDetectionPS = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\SMAA.fx"), "DX11_SMAAColorEdgeDetectionPS", "ps_5_0", defines.data(), blob);
- _SMAADepthEdgeDetectionPS = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\SMAA.fx"), "DX11_SMAADepthEdgeDetectionPS", "ps_5_0", defines.data(), blob);
- _SMAABlendingWeightCalculationPS = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\SMAA.fx"), "DX11_SMAABlendingWeightCalculationPS", "ps_5_0", defines.data(), blob);
- _SMAANeighborhoodBlendingPS = Utils::compilePixelShader(_device.Get(), GetAssetPath(L"Shaders\\SMAA.fx"), "DX11_SMAANeighborhoodBlendingPS", "ps_5_0", defines.data(), blob);
- _SMAAEdgeDetectionVS = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\SMAA.fx"), "DX11_SMAAEdgeDetectionVS", "vs_5_0", defines.data(), blob);
- _SMAABlendingWeightCalculationVS = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\SMAA.fx"), "DX11_SMAABlendingWeightCalculationVS", "vs_5_0", defines.data(), blob);
- _SMAANeighborhoodBlendingVS = Utils::compileVertexShader(_device.Get(), GetAssetPath(L"Shaders\\SMAA.fx"), "DX11_SMAANeighborhoodBlendingVS", "vs_5_0", defines.data(), blob);
-
+ _sSMAALumaEdgeDetection = CompileOrLoadShader("SMAA", "LumaEdgeDetection", ShaderType::Pixel, defines.data());
+ _sSMAAColorEdgeDetection = CompileOrLoadShader("SMAA", "ColorEdgeDetection", ShaderType::Pixel, defines.data());
+ _sSMAADepthEdgeDetection = CompileOrLoadShader("SMAA", "DepthEdgeDetection", ShaderType::Pixel, defines.data());
+ _sSMAABlendingWeightCalculation = CompileOrLoadShader("SMAA", "BlendingWeightCalculation", ShaderType::PixelAndVertex, defines.data());
+ _sSMAANeighborhoodBlending = CompileOrLoadShader("SMAA", "NeighborhoodBlending", ShaderType::PixelAndVertex, defines.data());
+ _sSMAAEdgeDetection = CompileOrLoadShader("SMAA", "EdgeDetection", ShaderType::Vertex, defines.data());
}
void Renderer::InitializeCommonTextures()
@@ -688,4 +675,152 @@ namespace TEN::Renderer
UpdateWindow(WindowsHandle);
}
+
+ void Renderer::BindShader(const RendererShader& shader)
+ {
+ if (shader.Vertex.Shader != nullptr) _context->VSSetShader(shader.Vertex.Shader.Get(), nullptr, 0);
+ if (shader.Pixel.Shader != nullptr) _context->PSSetShader(shader.Pixel.Shader.Get(), nullptr, 0);
+ if (shader.Compute.Shader != nullptr) _context->CSSetShader(shader.Compute.Shader.Get(), nullptr, 0);
+ }
+
+ RendererShader Renderer::CompileOrLoadShader(const std::string& fileName, const std::string& funcName, ShaderType type, const D3D_SHADER_MACRO* defines)
+ {
+ RendererShader result = {};
+
+ // We need to increment the counter to avoid overwriting compiled shaders with the same source file name.
+ static int compileCounter = 0;
+
+ // Define paths for native (uncompiled) shaders and compiled shaders.
+ std::wstring shaderPath = GetAssetPath(L"Shaders\\");
+ std::wstring compiledShaderPath = shaderPath + L"Bin\\";
+ std::wstring wideFileName = TEN::Utils::ToWString(fileName);
+
+ // Ensure the /Bin subdirectory exists.
+ std::filesystem::create_directories(compiledShaderPath);
+
+ // Helper function to load or compile a shader.
+ auto loadOrCompileShader = [this, type, defines, shaderPath, compiledShaderPath]
+ (const std::wstring& baseFileName, const std::string& shaderType, const std::string& functionName, const char* model, ComPtr& bytecode)
+ {
+ // Construct the full paths using GetAssetPath.
+ auto prefix = ((compileCounter < 10) ? L"0" : L"") + std::to_wstring(compileCounter) + L"_";
+ auto csoFileName = compiledShaderPath + prefix + baseFileName + L"." + std::wstring(shaderType.begin(), shaderType.end()) + L".cso";
+ auto srcFileName = shaderPath + baseFileName;
+
+ // Try both .hlsl and .fx extensions for the source shader.
+ auto srcFileNameWithExtension = srcFileName + L".hlsl";
+ if (!std::filesystem::exists(srcFileNameWithExtension))
+ {
+ srcFileNameWithExtension = srcFileName + L".fx";
+ if (!std::filesystem::exists(srcFileNameWithExtension))
+ {
+ TENLog("Shader source file not found: " + TEN::Utils::ToString(srcFileNameWithExtension), LogLevel::Error);
+ throw std::runtime_error("Shader source file not found");
+ }
+ }
+
+ // Check modification dates of the source and compiled files.
+ bool shouldRecompile = false;
+ if (std::filesystem::exists(csoFileName))
+ {
+ auto csoTime = std::filesystem::last_write_time(csoFileName);
+ auto srcTime = std::filesystem::last_write_time(srcFileNameWithExtension);
+ shouldRecompile = srcTime > csoTime; // Recompile if the source is newer.
+ }
+
+ // Load compiled shader if it exists and is up to date.
+ if (!shouldRecompile)
+ {
+ std::ifstream csoFile(csoFileName, std::ios::binary);
+
+ if (csoFile.is_open())
+ {
+ // Load compiled shader.
+ csoFile.seekg(0, std::ios::end);
+ size_t fileSize = csoFile.tellg();
+ csoFile.seekg(0, std::ios::beg);
+
+ std::vector buffer(fileSize);
+ csoFile.read(buffer.data(), fileSize);
+ csoFile.close();
+
+ D3DCreateBlob(fileSize, &bytecode);
+ memcpy(bytecode->GetBufferPointer(), buffer.data(), fileSize);
+
+ return;
+ }
+ }
+
+ // Set up compilation flags according to the build configuration.
+ unsigned int flags = D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_PACK_MATRIX_ROW_MAJOR;
+
+ if constexpr (DebugBuild)
+ flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
+ else
+ flags |= D3DCOMPILE_OPTIMIZATION_LEVEL3 | D3DCOMPILE_IEEE_STRICTNESS;
+
+ auto trimmedFileName = std::filesystem::path(srcFileNameWithExtension).filename().string();
+ TENLog("Compiling shader: " + trimmedFileName, LogLevel::Info);
+
+ // Compile shader.
+ ComPtr errors;
+ HRESULT res = D3DCompileFromFile(srcFileNameWithExtension.c_str(), defines, D3D_COMPILE_STANDARD_FILE_INCLUDE,
+ (shaderType + functionName).c_str(), model, flags, 0, bytecode.GetAddressOf(), errors.GetAddressOf());
+
+ if (FAILED(res))
+ {
+ if (errors)
+ {
+ auto error = std::string(static_cast(errors->GetBufferPointer()));
+ TENLog(error, LogLevel::Error);
+ throw std::runtime_error(error);
+ }
+ else
+ {
+ TENLog("Error while compiling shader: " + trimmedFileName, LogLevel::Error);
+ throwIfFailed(res);
+ }
+ }
+
+ // Save compiled shader to .cso file.
+ std::ofstream outCsoFile(csoFileName, std::ios::binary);
+ if (outCsoFile.is_open())
+ {
+ outCsoFile.write(reinterpret_cast(bytecode->GetBufferPointer()), bytecode->GetBufferSize());
+ outCsoFile.close();
+ }
+ };
+
+ // Load or compile and create pixel shader.
+ if (type == ShaderType::Pixel || type == ShaderType::PixelAndVertex)
+ {
+ loadOrCompileShader(wideFileName, "PS", funcName, "ps_5_0", result.Pixel.Blob);
+ throwIfFailed(_device->CreatePixelShader(result.Pixel.Blob->GetBufferPointer(), result.Pixel.Blob->GetBufferSize(),
+ nullptr, result.Pixel.Shader.GetAddressOf()
+ ));
+ }
+
+ // Load or compile and create vertex shader.
+ if (type == ShaderType::Vertex || type == ShaderType::PixelAndVertex)
+ {
+ loadOrCompileShader(wideFileName, "VS", funcName, "vs_5_0", result.Vertex.Blob);
+ throwIfFailed(_device->CreateVertexShader(result.Vertex.Blob->GetBufferPointer(), result.Vertex.Blob->GetBufferSize(),
+ nullptr, result.Vertex.Shader.GetAddressOf()
+ ));
+ }
+
+ // Load or compile and create compute shader.
+ if (type == ShaderType::Compute)
+ {
+ loadOrCompileShader(wideFileName, "CS", funcName, "cs_5_0", result.Compute.Blob);
+ throwIfFailed(_device->CreateComputeShader(result.Compute.Blob->GetBufferPointer(), result.Compute.Blob->GetBufferSize(),
+ nullptr, result.Compute.Shader.GetAddressOf()
+ ));
+ }
+
+ // Increment the compile counter.
+ compileCounter++;
+
+ return result;
+ }
}
\ No newline at end of file
diff --git a/TombEngine/Renderer/RendererLara.cpp b/TombEngine/Renderer/RendererLara.cpp
index 64fedd83f..7b825d2c5 100644
--- a/TombEngine/Renderer/RendererLara.cpp
+++ b/TombEngine/Renderer/RendererLara.cpp
@@ -111,10 +111,19 @@ void Renderer::UpdateLaraAnimations(bool force)
auto frameData = GetFrameInterpData(*LaraItem);
UpdateAnimation(&rItem, playerObject, frameData, mask);
+ auto gunType = Lara.Control.Weapon.GunType;
+ auto handStatus = Lara.Control.HandStatus;
+
+ // HACK: Treat binoculars as two-handed weapon.
+ if (Lara.Control.Look.IsUsingBinoculars)
+ {
+ gunType = LaraWeaponType::Shotgun;
+ handStatus = HandStatus::WeaponReady;
+ }
+
// Then the arms, based on current weapon status.
- if (Lara.Control.Weapon.GunType != LaraWeaponType::Flare &&
- (Lara.Control.HandStatus == HandStatus::Free || Lara.Control.HandStatus == HandStatus::Busy) ||
- Lara.Control.Weapon.GunType == LaraWeaponType::Flare && !Lara.Flare.ControlLeft)
+ if (gunType != LaraWeaponType::Flare && (handStatus == HandStatus::Free || handStatus == HandStatus::Busy) ||
+ gunType == LaraWeaponType::Flare && !Lara.Flare.ControlLeft)
{
// Both arms
mask = MESH_BITS(LM_LINARM) | MESH_BITS(LM_LOUTARM) | MESH_BITS(LM_LHAND) | MESH_BITS(LM_RINARM) | MESH_BITS(LM_ROUTARM) | MESH_BITS(LM_RHAND);
@@ -124,15 +133,14 @@ void Renderer::UpdateLaraAnimations(bool force)
else
{
// While handling weapon, extra rotation may be applied to arms.
- if (Lara.Control.Weapon.GunType == LaraWeaponType::Pistol ||
- Lara.Control.Weapon.GunType == LaraWeaponType::Uzi)
+ if (gunType == LaraWeaponType::Revolver)
{
- playerObject.LinearizedBones[LM_LINARM]->ExtraRotation *= Lara.LeftArm.Orientation.ToQuaternion();
+ playerObject.LinearizedBones[LM_LINARM]->ExtraRotation =
playerObject.LinearizedBones[LM_RINARM]->ExtraRotation *= Lara.RightArm.Orientation.ToQuaternion();
}
else
{
- playerObject.LinearizedBones[LM_LINARM]->ExtraRotation =
+ playerObject.LinearizedBones[LM_LINARM]->ExtraRotation *= Lara.LeftArm.Orientation.ToQuaternion();
playerObject.LinearizedBones[LM_RINARM]->ExtraRotation *= Lara.RightArm.Orientation.ToQuaternion();
}
@@ -140,7 +148,7 @@ void Renderer::UpdateLaraAnimations(bool force)
ArmInfo* rightArm = &Lara.RightArm;
// HACK: Back guns are handled differently.
- switch (Lara.Control.Weapon.GunType)
+ switch (gunType)
{
case LaraWeaponType::Shotgun:
case LaraWeaponType::HK:
@@ -152,7 +160,7 @@ void Renderer::UpdateLaraAnimations(bool force)
// Left arm
mask = MESH_BITS(LM_LINARM) | MESH_BITS(LM_LOUTARM) | MESH_BITS(LM_LHAND);
- if (shouldAnimateUpperBody(Lara.Control.Weapon.GunType))
+ if (shouldAnimateUpperBody(gunType))
mask |= MESH_BITS(LM_TORSO) | MESH_BITS(LM_HEAD);
auto shotgunFrameData = AnimFrameInterpData
@@ -278,14 +286,11 @@ void Renderer::UpdateLaraAnimations(bool force)
rItem.DoneAnimations = true;
}
-void TEN::Renderer::Renderer::DrawLara(RenderView& view, RendererPass rendererPass)
+void Renderer::DrawLara(RenderView& view, RendererPass rendererPass)
{
- // Don't draw player if using optics.
- if (Lara.Control.Look.OpticRange != 0 || SpotcamDontDrawLara)
- return;
-
- // Don't draw player if on title level and disabled.
- if (CurrentLevel == 0 && !g_GameFlow->IsLaraInTitleEnabled())
+ // TODO: Avoid Lara global.
+ // Don't draw player if using optics (but still draw reflections).
+ if (Lara.Control.Look.OpticRange != 0 && _currentMirror == nullptr)
return;
auto* item = &_items[LaraItem->Index];
@@ -305,7 +310,9 @@ void TEN::Renderer::Renderer::DrawLara(RenderView& view, RendererPass rendererPa
RendererRoom* room = &_rooms[LaraItem->RoomNumber];
- _stItem.World = item->InterpolatedWorld; // _laraWorldMatrix;
+ _stItem.World = item->InterpolatedWorld;
+ ReflectMatrixOptionally(_stItem.World);
+
_stItem.Color = item->Color;
_stItem.AmbientLight = item->AmbientLight;
memcpy(_stItem.BonesMatrices, item->InterpolatedAnimTransforms, laraObj.AnimationTransforms.size() * sizeof(Matrix));
@@ -313,7 +320,9 @@ void TEN::Renderer::Renderer::DrawLara(RenderView& view, RendererPass rendererPa
{
_stItem.BoneLightModes[k] = (int)GetMesh(nativeItem->Model.MeshIndex[k])->LightMode;
}
- BindMoveableLights(item->LightsToDraw, item->RoomNumber, item->PrevRoomNumber, item->LightFade);
+
+ bool acceptsShadows = laraObj.ShadowType == ShadowMode::None;
+ BindMoveableLights(item->LightsToDraw, item->RoomNumber, item->PrevRoomNumber, item->LightFade, acceptsShadows);
_cbItem.UpdateData(_stItem, _context.Get());
for (int k = 0; k < laraSkin.ObjectMeshes.size(); k++)
@@ -347,6 +356,7 @@ void Renderer::DrawLaraHair(RendererItem* itemToDraw, RendererRoom* room, Render
_stItem.World = Matrix::Identity;
_stItem.BonesMatrices[0] = itemToDraw->InterpolatedAnimTransforms[HairUnit::GetRootMeshID(i)] * itemToDraw->InterpolatedWorld;
+ ReflectMatrixOptionally(_stItem.BonesMatrices[0]);
for (int i = 0; i < unit.Segments.size(); i++)
{
@@ -356,6 +366,8 @@ void Renderer::DrawLaraHair(RendererItem* itemToDraw, RendererRoom* room, Render
Quaternion::Lerp(segment.PrevOrientation, segment.Orientation, GetInterpolationFactor(forceValue))) *
Matrix::CreateTranslation(
Vector3::Lerp(segment.PrevPosition, segment.Position, GetInterpolationFactor(forceValue)));
+
+ ReflectMatrixOptionally(worldMatrix);
_stItem.BonesMatrices[i + 1] = worldMatrix;
_stItem.BoneLightModes[i] = (int)LightMode::Dynamic;
diff --git a/TombEngine/Renderer/RendererPostProcess.cpp b/TombEngine/Renderer/RendererPostProcess.cpp
index 656a6e90b..91370ef08 100644
--- a/TombEngine/Renderer/RendererPostProcess.cpp
+++ b/TombEngine/Renderer/RendererPostProcess.cpp
@@ -6,6 +6,24 @@ namespace TEN::Renderer
{
void Renderer::DrawPostprocess(RenderTarget2D* renderTarget, RenderView& view, SceneRenderMode renderMode)
{
+ _doingFullscreenPass = true;
+
+ // Apply antialiasing.
+ switch (g_Configuration.AntialiasingMode)
+ {
+ case AntialiasingMode::None:
+ break;
+
+ case AntialiasingMode::Low:
+ ApplyFXAA(&_renderTarget, view);
+ break;
+
+ case AntialiasingMode::Medium:
+ case AntialiasingMode::High:
+ ApplySMAA(&_renderTarget, view);
+ break;
+ }
+
SetBlendMode(BlendMode::Opaque);
SetCullMode(CullMode::CounterClockwise);
SetDepthState(DepthState::Write);
@@ -24,7 +42,7 @@ namespace TEN::Renderer
_cbPostProcessBuffer.UpdateData(_stPostProcessBuffer, _context.Get());
// Common vertex shader to all fullscreen effects.
- _context->VSSetShader(_vsPostProcess.Get(), nullptr, 0);
+ BindShader(_sPostProcess);
// Draw fullscreen triangle.
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
@@ -40,7 +58,6 @@ namespace TEN::Renderer
_context->ClearRenderTargetView(_postProcessRenderTarget[0].RenderTargetView.Get(), clearColor);
_context->OMSetRenderTargets(1, _postProcessRenderTarget[0].RenderTargetView.GetAddressOf(), nullptr);
- _context->PSSetShader(_psPostProcessCopy.Get(), nullptr, 0);
BindRenderTargetAsTexture(TextureRegister::ColorMap, &_renderTarget, SamplerStateRegister::PointWrap);
DrawTriangles(3, 0);
@@ -48,44 +65,13 @@ namespace TEN::Renderer
int currentRenderTarget = 0;
int destRenderTarget = 1;
- // Apply color scheme.
- if (_postProcessMode != PostProcessMode::None && _postProcessStrength > EPSILON)
- {
- _context->ClearRenderTargetView(_postProcessRenderTarget[destRenderTarget].RenderTargetView.Get(), clearColor);
- _context->OMSetRenderTargets(1, _postProcessRenderTarget[destRenderTarget].RenderTargetView.GetAddressOf(), nullptr);
-
- switch (_postProcessMode)
- {
- case PostProcessMode::Monochrome:
- _context->PSSetShader(_psPostProcessMonochrome.Get(), nullptr, 0);
- break;
-
- case PostProcessMode::Negative:
- _context->PSSetShader(_psPostProcessNegative.Get(), nullptr, 0);
- break;
-
- case PostProcessMode::Exclusion:
- _context->PSSetShader(_psPostProcessExclusion.Get(), nullptr, 0);
- break;
-
- default:
- return;
- }
-
- BindRenderTargetAsTexture(TextureRegister::ColorMap, &_postProcessRenderTarget[currentRenderTarget], SamplerStateRegister::PointWrap);
- DrawTriangles(3, 0);
-
- destRenderTarget = (destRenderTarget == 1) ? 0 : 1;
- currentRenderTarget = (currentRenderTarget == 1) ? 0 : 1;
- }
-
// Lens flares.
if (!view.LensFlaresToDraw.empty())
{
_context->ClearRenderTargetView(_postProcessRenderTarget[destRenderTarget].RenderTargetView.Get(), clearColor);
_context->OMSetRenderTargets(1, _postProcessRenderTarget[destRenderTarget].RenderTargetView.GetAddressOf(), nullptr);
- _context->PSSetShader(_psPostProcessLensFlare.Get(), nullptr, 0);
+ BindShader(_sPostProcessLensFlare);
for (int i = 0; i < view.LensFlaresToDraw.size(); i++)
{
@@ -102,8 +88,39 @@ namespace TEN::Renderer
currentRenderTarget = (currentRenderTarget == 1) ? 0 : 1;
}
+ // Apply color scheme.
+ if (_postProcessMode != PostProcessMode::None && _postProcessStrength > EPSILON)
+ {
+ _context->ClearRenderTargetView(_postProcessRenderTarget[destRenderTarget].RenderTargetView.Get(), clearColor);
+ _context->OMSetRenderTargets(1, _postProcessRenderTarget[destRenderTarget].RenderTargetView.GetAddressOf(), nullptr);
+
+ switch (_postProcessMode)
+ {
+ case PostProcessMode::Monochrome:
+ BindShader(_sPostProcessMonochrome);
+ break;
+
+ case PostProcessMode::Negative:
+ BindShader(_sPostProcessNegative);
+ break;
+
+ case PostProcessMode::Exclusion:
+ BindShader(_sPostProcessExclusion);
+ break;
+
+ default:
+ return;
+ }
+
+ BindRenderTargetAsTexture(TextureRegister::ColorMap, &_postProcessRenderTarget[currentRenderTarget], SamplerStateRegister::PointWrap);
+ DrawTriangles(3, 0);
+
+ destRenderTarget = (destRenderTarget == 1) ? 0 : 1;
+ currentRenderTarget = (currentRenderTarget == 1) ? 0 : 1;
+ }
+
// Do final pass.
- _context->PSSetShader(_psPostProcessFinalPass.Get(), nullptr, 0);
+ BindShader(_sPostProcessFinalPass);
_context->ClearRenderTargetView(renderTarget->RenderTargetView.Get(), Colors::Black);
_context->OMSetRenderTargets(1, renderTarget->RenderTargetView.GetAddressOf(), nullptr);
@@ -111,6 +128,8 @@ namespace TEN::Renderer
BindTexture(TextureRegister::ColorMap, &_postProcessRenderTarget[currentRenderTarget], SamplerStateRegister::PointWrap);
DrawTriangles(3, 0);
+
+ _doingFullscreenPass = false;
}
PostProcessMode Renderer::GetPostProcessMode()
@@ -152,7 +171,7 @@ namespace TEN::Renderer
ResetScissor();
// Common vertex shader to all fullscreen effects
- _context->VSSetShader(_vsPostProcess.Get(), nullptr, 0);
+ BindShader(_sPostProcess);
// We draw a fullscreen triangle
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
@@ -167,7 +186,6 @@ namespace TEN::Renderer
_context->ClearRenderTargetView(dest->RenderTargetView.Get(), clearColor);
_context->OMSetRenderTargets(1, dest->RenderTargetView.GetAddressOf(), nullptr);
- _context->PSSetShader(_psPostProcessCopy.Get(), nullptr, 0);
BindRenderTargetAsTexture(TextureRegister::ColorMap, source, SamplerStateRegister::PointWrap);
DrawTriangles(3, 0);
}
diff --git a/TombEngine/Renderer/RendererSprites.cpp b/TombEngine/Renderer/RendererSprites.cpp
index 417155097..a4cc72c7c 100644
--- a/TombEngine/Renderer/RendererSprites.cpp
+++ b/TombEngine/Renderer/RendererSprites.cpp
@@ -243,7 +243,6 @@ namespace TEN::Renderer
object.Centre = rDrawSprite.pos;
object.Distance = distance;
object.Sprite = &rDrawSprite;
- object.World = GetWorldMatrixForSprite(&rDrawSprite, view);
view.TransparentObjectsToDraw.push_back(object);
}
@@ -280,8 +279,7 @@ namespace TEN::Renderer
SetDepthState(DepthState::Read);
SetCullMode(CullMode::None);
- _context->VSSetShader(_vsInstancedSprites.Get(), nullptr, 0);
- _context->PSSetShader(_psInstancedSprites.Get(), nullptr, 0);
+ BindShader(_sInstancedSprites);
// Set up vertex buffer and parameters.
unsigned int stride = sizeof(Vertex);
@@ -342,8 +340,7 @@ namespace TEN::Renderer
SetDepthState(DepthState::Read);
SetCullMode(CullMode::None);
- _context->VSSetShader(_vsSprites.Get(), nullptr, 0);
- _context->PSSetShader(_psSprites.Get(), nullptr, 0);
+ BindShader(_sSprites);
wasGPUSet = true;
}
@@ -363,21 +360,29 @@ namespace TEN::Renderer
vertex0.UV = rDrawSprite.Sprite->UV[0];
vertex0.Color = rDrawSprite.c1;
+ ReflectVectorOptionally(vertex0.Position);
+
auto vertex1 = Vertex{};
vertex1.Position = rDrawSprite.vtx2;
vertex1.UV = rDrawSprite.Sprite->UV[1];
vertex1.Color = rDrawSprite.c2;
+ ReflectVectorOptionally(vertex1.Position);
+
auto vertex2 = Vertex{};
vertex2.Position = rDrawSprite.vtx3;
vertex2.UV = rDrawSprite.Sprite->UV[2];
vertex2.Color = rDrawSprite.c3;
+ ReflectVectorOptionally(vertex2.Position);
+
auto vertex3 = Vertex{};
vertex3.Position = rDrawSprite.vtx4;
vertex3.UV = rDrawSprite.Sprite->UV[3];
vertex3.Color = rDrawSprite.c4;
+ ReflectVectorOptionally(vertex3.Position);
+
_primitiveBatch->DrawTriangle(vertex0, vertex1, vertex3);
_primitiveBatch->DrawTriangle(vertex1, vertex2, vertex3);
@@ -406,8 +411,7 @@ namespace TEN::Renderer
SetBlendMode(object->Sprite->BlendMode);
SetAlphaTest(AlphaTestMode::GreatherThan, ALPHA_TEST_THRESHOLD);
- _context->VSSetShader(_vsInstancedSprites.Get(), nullptr, 0);
- _context->PSSetShader(_psInstancedSprites.Get(), nullptr, 0);
+ BindShader(_sInstancedSprites);
// Set up vertex buffer and parameters.
UINT stride = sizeof(Vertex);
@@ -452,8 +456,7 @@ namespace TEN::Renderer
SetBlendMode(object->Sprite->BlendMode);
SetAlphaTest(AlphaTestMode::GreatherThan, ALPHA_TEST_THRESHOLD);
- _context->VSSetShader(_vsSprites.Get(), nullptr, 0);
- _context->PSSetShader(_psSprites.Get(), nullptr, 0);
+ BindShader(_sSprites);
_stSprite.IsSoftParticle = object->Sprite->SoftParticle ? 1 : 0;
_stSprite.RenderType = (int)object->Sprite->renderType;
@@ -498,8 +501,7 @@ namespace TEN::Renderer
UINT stride = sizeof(Vertex);
UINT offset = 0;
- _context->VSSetShader(_vsSprites.Get(), nullptr, 0);
- _context->PSSetShader(_psSprites.Get(), nullptr, 0);
+ BindShader(_sSprites);
_sortedPolygonsVertexBuffer.Update(_context.Get(), _sortedPolygonsVertices.data(), 0, (int)_sortedPolygonsVertices.size());
diff --git a/TombEngine/Renderer/RendererUtils.cpp b/TombEngine/Renderer/RendererUtils.cpp
index b6601bbe2..ae6b6a030 100644
--- a/TombEngine/Renderer/RendererUtils.cpp
+++ b/TombEngine/Renderer/RendererUtils.cpp
@@ -1,20 +1,18 @@
#include "framework.h"
+
#include
#include
#include
#include
#include
#include
+
#include "Renderer/Renderer.h"
#include "Specific/trutils.h"
+#include "Structures/RendererShader.h"
namespace TEN::Renderer::Utils
{
- using std::wstring;
- using std::string;
- using Microsoft::WRL::ComPtr;
- using std::vector;
-
void throwIfFailed(const HRESULT& res)
{
if (FAILED(res))
@@ -44,119 +42,4 @@ namespace TEN::Renderer::Utils
throw std::runtime_error("An error occured!");
}
}
-
- ComPtr compileVertexShader(ID3D11Device* device, const std::wstring& fileName, const std::string& function, const std::string& model, const D3D_SHADER_MACRO * defines, ComPtr& bytecode)
- {
- ComPtr errors;
- HRESULT res = (D3DCompileFromFile(fileName.c_str(), defines, D3D_COMPILE_STANDARD_FILE_INCLUDE, function.c_str(), model.c_str(), GetShaderFlags(), 0, bytecode.GetAddressOf(),errors.GetAddressOf()));
- if (FAILED(res))
- {
- ID3D10Blob* errorObj = errors.Get();
- if (errorObj != nullptr)
- {
- auto error = std::string((char*)errorObj->GetBufferPointer());
- TENLog(error, LogLevel::Error);
- throw std::runtime_error(error);
- }
- else
- {
- TENLog("Error while compiling VS shader: " + TEN::Utils::ToString(fileName.c_str()), LogLevel::Error);
- throwIfFailed(res);
- }
- }
-
- ComPtr shader;
- throwIfFailed(device->CreateVertexShader(bytecode->GetBufferPointer(), bytecode->GetBufferSize(), nullptr, shader.GetAddressOf()));
-
- if constexpr (DebugBuild)
- {
- char buffer[100];
- unsigned int size = (unsigned int)std::wcstombs(buffer, fileName.c_str(), 100);
- shader->SetPrivateData(WKPDID_D3DDebugObjectName, size, buffer);
- }
-
- return shader;
- }
-
- ComPtr compilePixelShader(ID3D11Device* device, const wstring& fileName, const string& function, const string& model, const D3D_SHADER_MACRO* defines, ComPtr& bytecode)
- {
- ComPtr errors;
- HRESULT res = (D3DCompileFromFile(fileName.c_str(), defines, D3D_COMPILE_STANDARD_FILE_INCLUDE, function.c_str(), model.c_str(), GetShaderFlags(), 0, bytecode.GetAddressOf(), errors.GetAddressOf()));
- if (FAILED(res))
- {
- ID3D10Blob* errorObj = errors.Get();
- if (errorObj != nullptr)
- {
- auto error = std::string((char*)errorObj->GetBufferPointer());
- TENLog(error, LogLevel::Error);
- throw std::runtime_error(error);
- }
- else
- {
- TENLog("Error while compiling PS shader: " + TEN::Utils::ToString(fileName.c_str()), LogLevel::Error);
- throwIfFailed(res);
- }
- }
-
- ComPtr shader;
- throwIfFailed(device->CreatePixelShader(bytecode->GetBufferPointer(), bytecode->GetBufferSize(), nullptr, shader.GetAddressOf()));
-
- if constexpr (DebugBuild)
- {
- char buffer[100];
- unsigned int size = (unsigned int)std::wcstombs(buffer, fileName.c_str(), 100);
- shader->SetPrivateData(WKPDID_D3DDebugObjectName, size, buffer);
- }
-
- return shader;
- }
-
- ComPtr compileComputeShader(ID3D11Device* device, const wstring& fileName, const string& function, const string& model, const D3D_SHADER_MACRO* defines, ComPtr& bytecode)
- {
- auto errors = ComPtr{};
- auto res = D3DCompileFromFile(fileName.c_str(), defines, D3D_COMPILE_STANDARD_FILE_INCLUDE, function.c_str(), model.c_str(), GetShaderFlags(), 0, bytecode.GetAddressOf(), errors.GetAddressOf());
- if (FAILED(res))
- {
- auto* errorObj = errors.Get();
- if (errorObj != nullptr)
- {
- auto error = std::string((char*)errorObj->GetBufferPointer());
- TENLog(error, LogLevel::Error);
- throw std::runtime_error(error);
- }
- else
- {
- TENLog("Error while compiling CS shader: " + TEN::Utils::ToString(fileName.c_str()), LogLevel::Error);
- throwIfFailed(res);
- }
- }
-
- auto shader = ComPtr{};
- throwIfFailed(device->CreateComputeShader(bytecode->GetBufferPointer(), bytecode->GetBufferSize(), nullptr, shader.GetAddressOf()));
-
- if constexpr (DebugBuild)
- {
- char buffer[100];
- unsigned int size = (unsigned int)std::wcstombs(buffer, fileName.c_str(), 100);
- shader->SetPrivateData(WKPDID_D3DDebugObjectName, size, buffer);
- }
-
- return shader;
- }
-
- constexpr unsigned int Utils::GetShaderFlags()
- {
- unsigned int flags = D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_PACK_MATRIX_ROW_MAJOR;
-
- if constexpr (DebugBuild)
- {
- flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
- }
- else
- {
- flags |= D3DCOMPILE_OPTIMIZATION_LEVEL3 | D3DCOMPILE_IEEE_STRICTNESS;
- }
-
- return flags;
- }
}
diff --git a/TombEngine/Renderer/RendererUtils.h b/TombEngine/Renderer/RendererUtils.h
index 2efd92ba2..8fc12b203 100644
--- a/TombEngine/Renderer/RendererUtils.h
+++ b/TombEngine/Renderer/RendererUtils.h
@@ -9,9 +9,4 @@ namespace TEN::Renderer::Utils
void throwIfFailed(const HRESULT& res);
void throwIfFailed(const HRESULT& res, const std::string& info);
void throwIfFailed(const HRESULT& res, const std::wstring& info);
-
- [[nodiscard]] Microsoft::WRL::ComPtr compileVertexShader(ID3D11Device* device, const std::wstring& fileName, const std::string& function, const std::string& model, const D3D_SHADER_MACRO* defines, Microsoft::WRL::ComPtr& bytecode);
- constexpr [[nodiscard]] unsigned int GetShaderFlags();
- [[nodiscard]] Microsoft::WRL::ComPtr compilePixelShader(ID3D11Device* device, const std::wstring& fileName, const std::string& function, const std::string& model, const D3D_SHADER_MACRO* defines, Microsoft::WRL::ComPtr& bytecode);
- [[nodiscard]] Microsoft::WRL::ComPtr compileComputeShader(ID3D11Device* device, const std::wstring& fileName, const std::string& function, const std::string& model, const D3D_SHADER_MACRO* defines, Microsoft::WRL::ComPtr& bytecode);
}
diff --git a/TombEngine/Renderer/Structures/RendererMirror.h b/TombEngine/Renderer/Structures/RendererMirror.h
new file mode 100644
index 000000000..c361046b2
--- /dev/null
+++ b/TombEngine/Renderer/Structures/RendererMirror.h
@@ -0,0 +1,17 @@
+#pragma once
+
+namespace TEN::Renderer::Structures
+{
+ struct RendererMirror
+ {
+ int RoomNumber = 0;
+ Plane Plane = SimpleMath::Plane();
+ Matrix ReflectionMatrix = Matrix::Identity;
+
+ bool ReflectPlayer = false;
+ bool ReflectMoveables = false;
+ bool ReflectStatics = false;
+ bool ReflectSprites = false;
+ bool ReflectLights = false;
+ };
+}
diff --git a/TombEngine/Renderer/Structures/RendererShader.h b/TombEngine/Renderer/Structures/RendererShader.h
new file mode 100644
index 000000000..ec865bf33
--- /dev/null
+++ b/TombEngine/Renderer/Structures/RendererShader.h
@@ -0,0 +1,41 @@
+#pragma once
+#include
+#include
+
+using Microsoft::WRL::ComPtr;
+
+namespace TEN::Renderer::Structures
+{
+ enum class ShaderType
+ {
+ Pixel,
+ Vertex,
+ PixelAndVertex,
+ Compute
+ };
+
+ struct RendererPixelShaderAndBlob
+ {
+ ComPtr Shader = nullptr;
+ ComPtr Blob = nullptr;
+ };
+
+ struct RendererVertexShaderAndBlob
+ {
+ ComPtr Shader = nullptr;
+ ComPtr Blob = nullptr;
+ };
+
+ struct RendererComputeShaderAndBlob
+ {
+ ComPtr Shader = nullptr;
+ ComPtr Blob = nullptr;
+ };
+
+ struct RendererShader
+ {
+ RendererPixelShaderAndBlob Pixel = {};
+ RendererVertexShaderAndBlob Vertex = {};
+ RendererComputeShaderAndBlob Compute = {};
+ };
+}
diff --git a/TombEngine/Resources/title.bin b/TombEngine/Resources/title.bin
index 9475b7db030cd203a9c71f36bdfa1694079a1852..2ec56664610a5eef8a15f27a6657b7715fe2c424 100644
GIT binary patch
literal 80388
zcmWG>^f14f35TW{`ET7>-n|Kor0xibdeHm003+vaie
z{=!W?8e1+d(lCq+3iMcLaBfAoMx>S}_bv0?Q-qdrc&>#zQh_1zh-y-KG|FS?dLR>_#JP}#()9-%EK5
zVJKi|`2TKN!Obo8by^F`HbxYwpJB&T^Wpxzx*dj?;tU`1S@U?Xi2s@0D9wyT{D0ho
zV_1X_?4Ri@^oU)MWg*8DSNT2zcT6>Zb|1iF1ZjsO2H75)v|eW&z`L`jNd
z*AeUdt>VIQT7R-*&Rjg0XWbMq;XZfdgX>v;`j*{UKQ&-Nb2LX&NBF=kt_cG&D>SM@8{kqaav&Indka)|9v)O1Q#s%$@}5-+yAC-zSygs
zNqAHLRsu8QeW=%)w!YV;m39+b1dZ+!@si!dKoe8YN*#QNX8UqcyRxWBIn0uj-~*XQ;YcBXDDN;`4{_e
z+v=LX*RHN#=QyRl{^gFk)v@oas?Pl1F8lYqeVy*T{nIL*ul<&E$h)<^x5LuvM(+Rf
zy(x|H+TWA;`Ts<5vRdwFRp_XA`kr+yXU&VxudSz9+`9RO=etB*?w8+;`J0x_Y5RNf
z%KzQ!^{XYa8W(gQS|0YFzxe-uzw+;1U+SxNiNF7QyhQ$eN#?~W`8l6+y{rzbcya!A
zYCg9$zl_BOoxj`7l7)pQUtSyj%CtMF{kfk{+`M-m>h>sp-t$>6KK?$h^r}3MUA(1F
z_V6cvX|D|VvQGECFW;PMv4~SMs^x6WL!<0MX5F@xy((*VKIExkyUMfKDpR6&JwLTe
z`C0txPx0T=KDeujxnJES{(Sf1=k^{PVl-&Ut^f&ztCPdwg02+x2Cx=a*E5^{ihZG5_x)%m1oon>MSiD$dIP*}u0_
zy{kT~CRpdc+3$t3|3Ccs=7HnJdFxE)39q(s-gNw0wcon+k+;4d)z4b;G%EG_fkmm`
zTT*?)YxA!ka(K`*|Efz>@6Qd9SJI82PUsh&t)*uEw6brKP4lyO({p>JUiHVl2>;JqJM{jW5#E>1TwQXtcJqGqo4=>lhg^^GycE+}?sPC^^8IPb
zy&wPWy(!#0le>B2I{s5uPn5XYq9qRNYK7gJD1E1U(Y&v}L^D+Ot%|%>9$hfc*mpyc
zaFP2m8#h0fum5k|cCJ3X@6FxoXT@G8oxk?<yDpTlVtC^{-TTGmS?-)$gQkdIPK;$vs+;c
zb$qWCOcC+AI%&~T#jkaWlceM8?Jb2S9yxJS{=`!0B{98|{-1fjYr5s+Tn1(87rAYL>H8}lF5hKD`+wY89b{~3FSOSpWwp^fn~;^e46I7rOY0tNdAPds
zB>T0`o25UinQwd3_iNMcHG9tQ-;@99JEP{oef!FTukVQa{$tI*6FcfonI$wm
zX}A=&XZF9^$uBdSvZ6e1KYRCT$ELnT1||NkS4t-bT5i_M@0@T^{iyfWqxKv|#;N%6W8A8H)mxYHbsEp*P1TtDZ<6?g
zA5*JcVwSsU>S=!wy%)EMB(8Ufq3lr1tUZ`Qph|NBL}J
z=5DI%6EV3yzhLq4S7$BfUwc}2NTg7A!4gny-zYxy|1&p~#hk>D84xB6n7tx46|;
zP`YiO{i?Oqew*EoE4us7E78xnU^mUD_y6&R{hR(f@=ATLy?6Vr+xNBBBH1=fubh(C
zY1Zdu+waT%5$c{9d3;LIb@SB1U&&{Z|6Tw2;)=_C_FnaC+{vET`UUqax?l9&*F{yH~f#Oc7n=CG7s5{oK9$O1Zj!J1cibx=f7B{Mp_Y
zF2C$sssG!_2R^OM`)&7d{r3601AUnMsYYuLeuelS4jdYd!m+`BO7%_bhFT)ynIy3(CHTw@Fnkc-;H_8sEFZ
zIAw=}NA=B*$MkXOS6G^A(*dzj#$2dcKTc*lW1yozC&KJ9Lkx
z9F31v+W|*!lU_
z9k;@B3-e|<-w?Epig@wQvgSwY&It!+8U_CiZ~XPS`q|!pCoY~S*iq&vYbX?C8l$D%
zxAoF)!RARM$&5y(d2deEGy;s@9bjToRn|V0Q>+`l2rg7(T>sKCb(g@}lWUjdn4Yv$`TOOnSNSG~
z)tQ}EPp8N_t?HN&ApA!4={fN^+vZH@)40@85p%Y?^Pkm5m17@HwTC&+lAYKczwb}s
zj^CD(PQB82{U*TgzVYYFPK(UmWEc3JTJS1m+5)5MDsCf%&I;QYDR-Zab+;!L>b!dU
z?!@+cTKlf$@cx_^l_ucol;t3&uBP9e5i4!F#%JQZ#Y&>R{wCJSyTU5BJ$3c{KgB7;
zJYk{{>(8aEwm+qgP5h~R(B(+iPsbonTkn+Q=DZ0fUg+#(yE)yo=vAEM%X=bfvBJwP
z$$JWa+$nu<(n6ng74frN&ZJ$^jbdWHIDNHUg|@J}e&qR>P1hIYXP)^NBeO>5@0TCp
z`yKtn|6AfDx32=
zpyWxjO8?K4f5lnOxra?2i7&ExVOV>pIBNNI*4!Cp*1xu@l=iG#HxHGkA>5FyZ
zE0xkyW9we;?%cJisrQ0&%dFnZTpn)C_e!>9hPQOHylne#b22+Dc1PCDb=T~_m~nmJ
zcG{XA9V~q{R-A7I+wxM=^plB^X|}D2N7mH$oLFeup*8X6p~*Xhiyhpiw#rTJzonek
z7%RqeiDBm7eMwXKn?15aB`0&$Ds0^TVyj4==Hz9mMrtb$c=25;%0Btu(Er>o?&V77
z=jF>LzdXO~+P3iL+AE$_XUWNL-j}%e+pWOd#e2@(`oJ_f!jkvN9PdRbi@$F=*3j#y
zxH$Y!CJT33@XU=t!g{w({rH>KYowp)t^Q`#k178O6I?%SENFBR-1B8cX_d}oHumyGU4#aOFk_RzI{DRe72eE77bpxtP_HB
z@6>kq)c;L%J`l~kuDZdWJ=?|cWBcTl=kxAJo~=J!aHVdINSB3<t3ZL${(zz=*
zr!m>3C0y@EX^Y|BF!9nvy&KX0w!W=Y3i9rF&iOQ5@4?Je%U~_#WUZ#GvZr&7t$SR)
z#XX+8;9Q667nk;Dvst}41(nx#{rGAywf*!ev(?IB_qSB9t9&%`+T-a;G4&i$tztX2
z9gUxNy;^wBubp>_to3~#RvXE`^}nStRcMK33%}uYFP7fz_w5b!gABElEUb2BiTn8}
zq)X->n5a~CP;Z-f?O9%-*%ih*j@n1RZEZesA*uhMQ~#{5F%wIV9I!g*W>dB9-=4b(
zp*HSuiuWJf{CuRT!T&9v>FQIDy6rP8XQa2RIVT)jDC)Vrzh
z&V#$+^7{=(Hh6hP%3hy(Qd2vHYxAKGol@7D>`r$!zUI$du+l=EGjLLmiiFwvxzn?X
zy7D3%Y|kl+lFJy@i_hQtJZEou
z_upMr*@h7^<|`&R>d&v4fBue-`JP`+iZ?GLYuK!HnbY@Xb=;=S+CGnXUVC+x6*tT`byN$=zqQ
zm2Zn=iN9KOdl&25MXm+b|DG`Ch@73j;n*SR<;v3EZz;P>lUe@o)JNm2E%JVQ{!E-F
z%lLE2>*J{(yo}a`?>v4n_x+)9s#3GZ(`5Y_UVbTGZ!_IX$PC$aR;A%$=6hgTl2+rRo(Qup=gHhl-@-r@9&
z6zSq?0fN6Z6)e)k-kiB^P+%qt*8U2b=CM3AX}2f7yM|$#+`A#xI+_1YPx9KlAj(7N(?4|JjoA9)50G=PuS{6*#%=s+hgKhILk+Qo4=8ojEFd
zHkD-8yj)#;BreA5MbY!erkf`JJzLFm<9f)f&vP%h8K3*jwzRFHXP4hJ_0DS@dx{q1
zT5QX2wtC6v+bgR4*iP`p^9S$4zD&1URy?aFx;XHOy6;zoU`;(cC$6o+53bC68e?r#
zcUKvPE)vH*1DCDaDMa|xdIt#Pn`41ZBv>tjf
zOK4N-7e>ottIkXoiV$0B`K8aK`sdaKLX$f7^g?2uhn>;a8D{?
zS-LH?J78w7xyyB7*47ss54BIn*F32@%X#Za+JC+F)F3PEHycH3t7N~u?%TY2ij2MO
zqVGHRJuA7kFMEE?pJms+th+kPcD}Aa>Ajxj%+_NEW>Wkj`Df*zFi5$Bv-
zY@U6Sbt&~Zq<=)~DZ5mxGER+_yzZFwerrplJX
z_uNWnC2h8tCUS9d-j~-*NiCP(?);GvdHqnqcc)@yV~3ve6Z@wp*%Z|;*l4kIT71!p
ziP;D49C$jfD(&a1BlGWXS^KNo?!)&?|62)@Dt1ha*?#<)bk=G13-%lnwP)w+b5DD)
z+bXE+WM28w6AC(#mAo!ppITnr`y}I-@#^6Y0oNB2Svi~Ez5eo#f30-o+0{&$+p@p$
zw06g@tD9D_=uD=Qf@zZgA*@aPOKEYEGT;U
zc7Fe#eYgCt#O|w|>y?!{f6dN!n~M}T_b=~OyrnD0NmNt^v;J=WLG
zabh)Jw{XY3Nt<)5%_q%~yH^%^%6fsOYUb(ByYibNo}G#NnmSwHq{eDpF~3#$i+-x_
z=RHxr?y|RA{~hzbmwOFrtGk;x?oXdEBQsR0DaSxXY=GpDRe5^mM
zWA~DP>!Lwc!qNsguUGwzz7bLRef{4w*EyM0``NcvEpD6IvF?m|U~9$O!&8};%n$m(
zon&$`{m1w7=e5M@{=W#+oh2?U`|nC)>ZRLN%QnB$iu!v=(fiBR<@ys}_Q<$|vrEsg
zSG4avcqgSqsiSRG_haoF)o<_nPY!yb`u2y=$(mZ3y-5>vwy$VA_w{|8M~-jB`#_uM
zG7bER2oS^iVsDbbnlwtMVL$&r1t
zlgnL+^W1H@nPPRfzxtg%o3-Nm!%#)L=Q@|NjGv^be|HMnXWrW%^GHrhIXK_CaLs8U
zo}eYsPsDO%^=GaVQ*qI(%MtH2J$YV9M`C`an8uDz3w_&emrqb_&2g&n-|0EYx_#0F
z7R%3i2TQLkm?P|W#P{WL`#RM*pNkK2@fIAqwcv`zltpf38FzeZEMnis%3Y4|OY6Mx
z^o;2g-=KL#Uz^yzPpHYWn-b15U`$0Ln)<`|cAtVvm2BR#&ee+JCq=Sm2Ui*FwFM*W1*h
z`$JY-Uz95Id^6vx7uh{IH81o`_v|y=Q#rS?N#=c1RH;Jevc8Mmd>OwY=It{2|1WgD
z-1)
z=`6Lr-pOLe%OWye!t7^=`t6y$!0N&!iRF{*Cr2q&@2ULhFvrqb>bqyLfz0>3D^`*h
zr3I~CD5#fg@DNP3*?jq8kDk$DOUf8=b=0B_R&ZZ5l
zWk;OW|NRy^#eKS7oBl%GDa9_gHg2EIa;^LQ?zqzEICrh=<=4XI+;t3evc&9U$0td&f6WP%4emPxi)o`tV(&p_2nw6
zOEnhhxqmNPx+P8IXE(?85B%EFdE
zTa#u#kKHcsn_Ri;OpWd9KhLMlKmYo--Q1jS!AmFpe402}?aCih)A!Y)f#H*yh-4?SaEq(qj#N}wd@gtWT?)IDRcWx~4-~T>X
z%~rVVhgL;aRos?PqwJW{Efoh1@;-4cdeZc4_szZBJ!{V1sb99{l*=W%;R0Y1kU{eZj&j{EOr#
z=1CWS<#XJ+fBwB^^L#$4UGr4z598|S7f5>PR>@hJbiCf{l3Ew*w9QvG3iwFwJaNg*
z?u6(Sxnm;HXIG#8Biw)gN3oWJ}-6cyKiPy)CQf(NgJgd0W5P_OLsRsYjzt
zv;28ZyiBwCxurY&`q#RH<)7~e)y+x1_OrkL^3tHz@{i{$j|-G1{ZQ6C5?VUN>G0)c
z96Wn3s&`mj(w3B7)@Z%vde*NKR-&qtRM-AkC#zY!M#p~p_YUr}8fED>`;yF6zuxfp
zxZ#A?at8gqpPv7cwYJ_}eE)}Nu2RfBpJitMe_pSg%_6PIK5JF(+0!%H@20KJW@R!l
z99zgH||C#OHD-WqmRGI(js=aPS4
z0{@9TbeUFOapd>piZ|tJn$}7dEB$-0`?s*D)S5*-E8kpRbW>E&<>AC-S6b~9AAM|M
zelv~t$;r2;TY6u5chr=2{Yu={@Ors$j`scyt@gZLA384IJSe~K?dQN^iJH}q9-Y0L
ze|Ej_^J^w9`=ZT~ABk>R%C$Lc@+^;wON~Amcd6c;@hfb}g(j9IS{b{i>u&qVV*6Ej
z)g1Zv{e^F8<_6F1l8JEJ^2#RXz}1;9{Z%KGwEH?^zg*NfU@>ualzH9R@G~M7(^M;L
zmdBhr$rt|U=(CC5?o*Amx8{pj?VDZxKWyg*_k-7)r?s8j&hz_JtlR|<1HPtI5spPzeq?{QX5_p=*44X^2Js!vV7>vX^9Fk@-)
znV_4EFCHy(j$Lf=u4Mh3U$L{dt@)*(Y501=QRT#MeXADFuKl)tPe(-Wlo@Z0{!B5l
z*)!eO1TC=JXu6~_+U&Kx4^xnBn=XCo$BqRd<&-A&xN@HTB(-YgJD?cscvpsla
zqFL&+d=~4H6y0~7w!Mq{-i2|T=XMDeFEQMk++3vbXzkxOUVSQYWs%FXl0U@9@_1YR
z|Fb-&da3cLR@2=9o=c*AXUg7Qw)$GG@rTE=KFzMwO@C|3<#|7DmO<^MsqkSJ=2SnNY3p!*N^jl%xyDi&Ztk3IPb^E}UON&>}>^7DR
zv|Gl#bN;nL)yU1-*2~IYX!I@W`t(nkk$tko*IL6<>!%l}Jr?xsP1wFZsDr1ze~Qx`
zMXAnnLVvP5ZD*;T*f4EIsiO!Ri@02J`o5Pex&dra6BNZyFLHL-)xy1KhFgt!;rmyW
zeCuDldhT{})uYMYoQZl8_6et_-}w2wZn?_a75ZAfaXS`RU*9`pU+(3Ew34%0iXrv$
zT)tSx)oTB4lD(vrreVH|eeYd~sqf@Ms*lZRtUnU>MLN6BtMYhsZ_NKa$IZKYR(m8(
zyIB`ze`wMF3(o}QH+lARZ$5p3u{daA^)lDV9h@0ECzWq!ejO73P)}*=k-~TS_qLZE
zYh`KC`*yGuLJQYW=2XA$!yH^g%b~mWXFtx{Whxz6j_1;fVfF
z!TtZmMdeVoD~_}OohbgghyUrW?!)n?rY0Ldv#eWXx?SdjGUwmfJ6sb>rtER~^DM7(
zXKBY_HSxQ=`K6ytIap^KF6(Ewc!NiA#sc@1Y|ghnyJuYdb7w^6{Qp@|I
z?aBFc{q-sCx1a8*QhmlJ|Ff<}GH&~a*G#pX?B+X~>Nw}j6u&CzB|UA)*3$J!=R?yb
zZf=PTzB4m5r#)KYt$fE`mzr184wm29*2_74{jo@{f+gpE&gOH!A+kf-oOM_IdeaBt
ztZpZln$&t%{zxj@Bj!9vS
zk~cPAm+m!n!N%$B%kTYv>9k8>%IJoI5bwBjD0?D)0zWowEp}2
zKh3gm>f|M-cX*xJt>?oXx;?DKapjAWV<-1mZJnpS>-D6M<$q^nhb0zXdG5#Nzsz@4
z>W;^jt*QMljKsg(KhZC;#IethS>v{Nc>Nh$)jQz3Xw4sy8#mX;#>eB9=o9OU_
z>yhvzRf~iswe&T&re?0);AnI18tZ*Y%~L1EZku%8h;`jQ>-hSVX)1qeBsU5yI{rX(
zY6j~9zu&J9^=7Fs-Fx$_#;0@h;U+zS@A2LycUIM330SPFqINC$PQ}fxoqQKXo=*Lg
zF=5C0l_$P+x4MOIXOYdjJxO$ezoT5|%*Tgjm33U$RB7^yDbUWYDNe!KV@LI;=HN2F
z@*lI3qs=Gu8K$0`b7cW@aqYP)e_qImYNhQ~t6cnjLR(kUWKBcA=_|c2?QvCl-Fsc`
zg|^I2X;meAl{I@u;#r(K(lbOEMSy5Ep7cp|NqNfAO8#RVOAtKVbQZ
zW$x#-C&RbD(&1XO=v~(B>9%Q`X1txbctx1UE>-b~H52`%dXqNVn8i&!U*{wI>(aBC
zIT0tMR_v)z3_TN`n!T|1tYfq!KhMECdMz?1mvQ?(`t828VB)_fQW8l7wSN*NWxorxcyk70SJMTH0^xu-Rl18)AnN$N6
z_sM=ea{V*M`I^8PX5mx9R|`)LHve(qcIQ;_jNUEnKTWRYe6G8?V|M)so@Dh?b)9{?#(}$tzT5P_u1JkZWA)Ue)Li8#~uKjpB%=+2#$bJ9W
zUl;p2{)`U~>L_1(Z^c)|HDRlndmqKL9J>0*aI;Ck^IOHd?~U00p0lpek=?gpLem=G
zbxR-0$M@&mKyZddma)w>ZTg8_h
zceszV+&j>rDpl*6cEbE>$NooVE3(S+4ojO=-RSt-(>wR|kClH`g|2yd-aNH6tNO(2
z8cnCIo0WQw>X%F_)7|wp>vZqW1TOYK?d-jl@uguALUaEVm^j7`2fCL0%?kNo5(W)crETf+cB?picU^B`*m;2{4c@>>i}~Ngmz?mr@X&Dg?Fw~iwdS7JU>v^{M*JN|2zPK@a$r`~+#ktz|uM2yw
z2{rGZHGf$_tjCx2y9JC&3%_*u2UV2aQ&GSHI3&H}PET_^XTM*r}*Zub+pPo7eltZ{^mWKW*MT
zp`^)~U;38@sWq1`dAl@9=u72EIa8J0s~0!uM3n}emmHQ^0G3Th)os=!`
z(^o}v%cG=;0#$F9x7D^x@XLxZ4s0u3-=TNv_`9F``+n+mrvJUMx-LD4$3Lsucw3|G
zw}h_4J0eP2Rv6+}
z{(ZyZRSVcI^~TOSbW}b3$i-i81Z@m{yPnI-TXX1c@fMM{mBH@s)MB++ocYASo1ge{b|M>-}ySUBbNw$kJ8y@P5`uDJg1
zb$LgQuMhX>U+%%bXYxx6TyOiGE!sN$(#pU^r70z%j>gZPox90Dy{0
zY7BiH7qK1nsyY0X$J)fr`JKG*!fU?AEauDZyA=`;eQWz+)}0L%F;jk7{S2_EPhI^x-*$&^-gD*7
z3miPJ{k{auPT#8#7Vxk*`}~3M=03~)=I*cgv`0lS#B3T{O$@u8ojox=zLanjIeX2<>x)(%>-%;lc
zxIXIxuj|f#vqG-VSs3~Kt^Crt7h_fOLyhG2Sa~MDt>{enA*h?3>zetcV!r1ry?eJ$
z-MO{r$?FCALg79u`wkZUTGg1++qxri`Q#NQOdv%ALQH
zyq^@mm79?1ZSu^i)1+mpLyWQc-;EzFjRQ4G?d;xsvQau1Uoj=#&RXS!1ZViy9T6FO
zzw=ru#E1TTYgH}(t^f6$Sr_IR?G{?ie|pcV>s2SG*w@`^bN~EG^<=cFpmdAs#ZSM*
zkG^`UwEXZ(mT9ND%9a{l{CwQ?b9~8?W3yjvD%p~;+4tbDzzm_8`7)EdiXHc`FPZJb
ztbBLcr$_!`$Jd-Tk4#%4|3pn!K0HpZXhX5a2FH+==S>Bi_cvCF9=f?{KTCS_)Qd}-
zzT7dYFKk_O?@7|beYbY4d$LC3&?=oHNf&ZtZVLWY{&lAN%X#j%U9urlzetE~iujap
z$@$4KrSnHl&zlpJb>waMyx%M0M2oj>Hh6yIj9^JOqw(*n#mybH&yM8Gyb5**&fPd2y
zx2aPmY?akxlD)a;_Wjau)E4H9J=D(3CBxi(@S%AYoGe9mn$uBdVf`J
z{Xt{z#f!~$mkGKq?lAdWVy(o>U9d4$_GyLP;ndpPq^G~NJ>r&MInA=%Gc#pw(5syc
zH^pDdN*)$64!Li7n49_i;YWHAzw@jYz0^^7D=csJ$8Pbz+N63B(a6PRNeA<+x1Y>%
z*#GsUT-@zDGc)e(eOsh%f6rx7_w7r0=Z;u}t}OkxE28p^msYN*#nR3#M)9S^&llOu
zf1+ymVeRH3!+qU4{&{O$P98Q5+dbE^^ywV#iOr2{F}{)aymCE{NeVM%`{6yF1`cpTjoVfIR
zliY^9^`&{esmU^7&(?}WDNbB#QzSU?)eHHnQ5QF*Z(E!6PlErmg|_mkNpde#T(*_Y
zPuOQEynu5wrES^s+ppHn`F;Fas;}G)!!IwRx1F@(bvd{*a@J+#{%d+0RN~I+pY##PUu*R;
za^vbp!AFmF9eOw6{cSc?m7egvbsEWPEBA9@lx-W9e604pz976e9{>ADZ=j6ub(-qnqMR=
zDStZ0hbaQ>I5l^-{
z9DOS5Q1R(GXEu5V>G$$q(v-RHX;k;~LhwaFR%LCUbf*-4sSk-o!M?dUojU#54`j8x
zu6f$_R+y;^zWyXz7Ep86CwAXXshsbMGsG${$=3_rIy1GpHse{*Nj8vyeUVnR
zEqLRjWy<<`YdEs`zuWk4N+cxdq89%S9%k8x9OxEhEE#vy4xA|`Et661fzw(yGzt^b!oBr19-A1pO
z_bcybyB|6h+v#uE|7YUWYd12sIP`mzJWNu1Bt0|hw(gm2)#(Z%w?5c}Z+@~)`uW1X
z?qvUS$K36#VrToYF1a=L_U=7KiL<7QuaJ5m87$#jav~?9q~KZKp8j|9w0f@cY!P!|
z|8zexc42f*hw@#U`pVe{zCICQJ*rr>Y{rY74`R*M$IpKEKS!(e*8|>*l1eUr4mjVb
z4Y^_aL`gDmLgMzz+lsz_mCE|=*DLr^CG+?8?XmmQ&IGZzWv)?Mx8@M<>?QL1terV_
zOPLu=d2`!WPG+9)2F@>D2^%I^KXrR+6mA!Gw06uHw5B?0!_uuK$GT;Xzd6RPmysA!?|yjmA!*euOOLCsU2{CFVv^F^MPJ0(
zuXC=u?6yx_mqWoONK)^`xodqwE`35118q9rJh05xG%qw5EO6f7#2!c<*}`JXlh}Ki?!UG)7Zc3?5|Ej;|V)NTxO<$t3Rq@@<(?S;OrrT#M
zUf1gT>`qkwkEp$WG){WQwFdr>cD`}>ee5E`i>ow@)HRc~spKeKmo~qiCb=NusKm{6
z$HS&C`}#SzW=F4Uzk*T5?CZt9)We;(t#z7b^ho;PyZZSOXEf7J-`rRGZ_!otmoL}#7qT}#5s3&|_pJ2tJo%v`+DCp(eA!kSFXR2_lhSss%`+Hn|=`Q^I_T>j=!I*7#4zZTaKKAvZ^7)(zuYygN#+ANf
zbnmlUZ1&ym;?h}{1U~KLDVnx@^X#o5GM48qF~488&rK3w1K5+ZtSlMa`T&gSp0|IX7jDaqS$?c4Q2BPGx7`UUQXk4*BO
zZLafn(z0(?=fC9&*0Z=1mbB?rw2s*P+hq?AUwM9R)0%nTQ>6S7Qc63PZH?M&roi~6
z@@{w($LAHo<%_rekK3H`{~&Xi@TSFQ{lza$G%enJN=|W#P}wc#_K=tv$3$i3*WbKb
zv1+;XkI8&;Sdi^$mDe1UV~
z-SZ~%FY>f4d3?0eCuqkq*N(%Q_C4$NF4MhMTE1?J=5MQ=Qw^0rzBc%3BY9HpTG@4<
zWkM2vt=IT0<2Uf(o+f!(ZSUNe8C&wVIN62fyxSIkLh!~;wce14OP-0?{xtbOb@1QjgH`Kxxu048ZbG){UFlV!vN7+{vl28eHQZe&
zWqv-i-lqQbF(Iecjmg(ix}H5?e#*<4(s%h}#gcpFr)MebcG+3wxkU4b2=AiHO7)A(
zn|S8_wZ6>Ot$O}Txo6GW>;#Wj3no{@E$dQ?E>uxooSXabql%UB+S#A$8kNs3`Vzm#
z^8WQl4bx|Eh1L9;V;FNXz*Zv(K9>}V5;T7Ked{#Qo}OiT
zZhVpT_WzKCa9{@RyU8&>eHIma>SrLc~m
z^91eYOCc(M;x>r*--@1_+RPyBR`
zxwz&hQ?t0>)y%Vz(@$9)QeyFcI>mIU%-VhXzFppw_UNJMV!^7J-%=LN=1iP5U8!0(x9+@gU!T~H1`W=WpBA@$i}n|hdZXLXt2NbjZKx05u9CUimy}#G4-;SCz0Fx_
z>T{kmHrxBp>*fhO$&Nn1C%ZDivgX9RDNFNar0&_X%ggoOBi;DpQcL&>>vOaJwX~g5
zUzfwHz_(^wj@H%r%GTA~|8J%~GT&4qy~<>>@%IpK!|H?=3p1GCrk#58je)au^Zama
z$y$4fz?o-l9~hQQ+^r%%@tWd_;1dsLi4?{1=q@(e+ST(qW>%Hrzy2BS_io=l)AY^j
z!?(9-K~ZY?OM7#*?B{iCiOZiF@Wy;*UcYl}aogf&VWsgdT7J2v3xa;!S@%FC#3S
z=E|}iYV%$l&pCCLCM@e-<;g?G$V%>oU)k3)YE8yBv-`leO4TS&WKok#V{
zzgaU?j&3vi(I&7(%l>0hShRAT-lD?a>eDWGEbw>9mN2jCW;vU8tme}0*s3?bFS})C
z?R_Bp{CeT-$$xHVX}5&xKK2O-F3hOEb3yoLtS-+Ft_4fO|C}^Wd=fgv&G_EG^~p7T
zx`&TG`nlD6&b{fjYp$e7tn=Le=zvGnkB?2UlSFEx?d5I=?Nn-B&uGWAQ%mzhBDb$i
z&5HM%?v&oTyK@yiEJ?&f=Xkw(8}LSLZXl=-Dsm>-up*MFRT)b-u(@2cruNxBdPUXTS2D5#jmN|JTK{
ztY7ywn