├── LICENSE.txt
├── IKCulling.sln
├── README.md
├── IKCulling
├── ResoniteIKCulling.csproj
└── ResoniteIKCulling.cs
└── .gitignore
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 KyuubiYoru
4 |
5 | Copyright (c) 2024 Raidriar796
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
--------------------------------------------------------------------------------
/IKCulling.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.31424.327
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ResoniteIKCulling", "IKCulling\ResoniteIKCulling.csproj", "{33E73217-CC81-4B89-9313-BF50DD67E491}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {33E73217-CC81-4B89-9313-BF50DD67E491}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {33E73217-CC81-4B89-9313-BF50DD67E491}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {33E73217-CC81-4B89-9313-BF50DD67E491}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {33E73217-CC81-4B89-9313-BF50DD67E491}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {849F04CB-422F-4B25-89A3-BFD1AF70368D}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ResoniteIkCulling
2 |
3 | A [ResoniteModLoader](https://github.com/resonite-modding-group/ResoniteModLoader) mod for [Resonite](https://resonite.com/) that disables the IK of Users who are behind you or far away. Includes IK throttling.
4 |
5 | ## Requirements
6 | - [ResoniteModLoader](https://github.com/resonite-modding-group/ResoniteModLoader) 2.6.0 or later
7 | - [ResoniteModSettings](https://github.com/badhaloninja/ResoniteModSettings) for live config editing
8 |
9 | ## Installation
10 | 1. Install [ResoniteModLoader](https://github.com/resonite-modding-group/ResoniteModLoader).
11 | 2. Place [ResoniteIKCulling.dll](https://github.com/Raidriar796/ResoniteIkCulling/releases/latest/download/ResoniteIKCulling.dll) into your `rml_mods` folder. This folder should be at `C:\Program Files (x86)\Steam\steamapps\common\Resonite\rml_mods` for a default install. You can create it if it's missing, or if you launch the game once with ResoniteModLoader installed it will create the folder for you.
12 | 3. Start the game. If you want to verify that the mod is working you can check your Resonite logs.
13 |
14 | ## How does it work?
15 |
16 | This mod tries to reduce the amount of work that the CPU does when calculating IK. This is done by interrupting the normal IK solve process by first checking a handful of conditions to determine if the IK should or should not solve, then it either skips the solving process or lets it proceed. This does add slightly more work before each IK solve so the default options are configured to reduce as much work as possible while trying to be unintrusive.
17 |
18 | ## How much does this improve performance?
19 |
20 | In most situations, not much if at all. This is because IK itself is rarely the bottleneck of any given session. Despite this, it is overall reducing the work the CPU has to do as IK is one of the heaviest components you'll commonly run into, especially since the vast majority of avatars utilize Resonite's IK system. There is often a lot going on in any given session, so at the very least you may see an improvement in thermals or power consumption, but you may not see benefits in framerate or frametimes unless you use throttling options or a session is actually being bottlenecked by IK.
21 |
22 | ## Info
23 | This is a fork of [IKCulling](https://github.com/KyuubiYoru/IkCulling/) for that aims to improve and maintain the mod.
24 |
--------------------------------------------------------------------------------
/IKCulling/ResoniteIKCulling.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | {CE8BB933-8E61-419C-A334-FA3CFC5E4E72}
4 | Library
5 | Properties
6 | ResoniteIKCulling
7 | ResoniteIKCulling
8 | Raidriar796
9 | Copyright © 2025 Raidriar796
10 | 2.7.0
11 | 2.7.0.0
12 | 2.7.0.0
13 | true
14 | net9.0
15 | 512
16 | latest
17 | true
18 | false
19 | false
20 | None
21 |
22 |
23 | C:\Program Files (x86)\Steam\steamapps\common\Resonite\
24 | $(HOME)/.steam/steam/steamapps/common/Resonite/
25 |
26 |
27 |
28 |
29 | $(ResonitePath)Libraries\ResoniteModLoader.dll
30 | False
31 |
32 |
33 | $(ResonitePath)rml_libs\0Harmony.dll
34 | $(ResonitePath)rml_libs\0Harmony.dll
35 | False
36 |
37 |
38 | $(ResonitePath)FrooxEngine.dll
39 | False
40 |
41 |
42 | $(ResonitePath)Elements.Core.dll
43 | False
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | [Aa][Rr][Mm]/
24 | [Aa][Rr][Mm]64/
25 | bld/
26 | [Bb]in/
27 | [Oo]bj/
28 | [Ll]og/
29 |
30 | # Visual Studio 2015/2017 cache/options directory
31 | .vs/
32 | # Uncomment if you have tasks that create the project's static files in wwwroot
33 | #wwwroot/
34 |
35 | # Visual Studio 2017 auto generated files
36 | Generated\ Files/
37 |
38 | # MSTest test Results
39 | [Tt]est[Rr]esult*/
40 | [Bb]uild[Ll]og.*
41 |
42 | # NUNIT
43 | *.VisualState.xml
44 | TestResult.xml
45 |
46 | # Build Results of an ATL Project
47 | [Dd]ebugPS/
48 | [Rr]eleasePS/
49 | dlldata.c
50 |
51 | # Benchmark Results
52 | BenchmarkDotNet.Artifacts/
53 |
54 | # .NET Core
55 | project.lock.json
56 | project.fragment.lock.json
57 | artifacts/
58 |
59 | # StyleCop
60 | StyleCopReport.xml
61 |
62 | # Files built by Visual Studio
63 | *_i.c
64 | *_p.c
65 | *_h.h
66 | *.ilk
67 | *.meta
68 | *.obj
69 | *.iobj
70 | *.pch
71 | *.pdb
72 | *.ipdb
73 | *.pgc
74 | *.pgd
75 | *.rsp
76 | *.sbr
77 | *.tlb
78 | *.tli
79 | *.tlh
80 | *.tmp
81 | *.tmp_proj
82 | *_wpftmp.csproj
83 | *.log
84 | *.vspscc
85 | *.vssscc
86 | .builds
87 | *.pidb
88 | *.svclog
89 | *.scc
90 |
91 | # Chutzpah Test files
92 | _Chutzpah*
93 |
94 | # Visual C++ cache files
95 | ipch/
96 | *.aps
97 | *.ncb
98 | *.opendb
99 | *.opensdf
100 | *.sdf
101 | *.cachefile
102 | *.VC.db
103 | *.VC.VC.opendb
104 |
105 | # Visual Studio profiler
106 | *.psess
107 | *.vsp
108 | *.vspx
109 | *.sap
110 |
111 | # Visual Studio Trace Files
112 | *.e2e
113 |
114 | # TFS 2012 Local Workspace
115 | $tf/
116 |
117 | # Guidance Automation Toolkit
118 | *.gpState
119 |
120 | # ReSharper is a .NET coding add-in
121 | _ReSharper*/
122 | *.[Rr]e[Ss]harper
123 | *.DotSettings.user
124 |
125 | # JustCode is a .NET coding add-in
126 | .JustCode
127 |
128 | # TeamCity is a build add-in
129 | _TeamCity*
130 |
131 | # DotCover is a Code Coverage Tool
132 | *.dotCover
133 |
134 | # AxoCover is a Code Coverage Tool
135 | .axoCover/*
136 | !.axoCover/settings.json
137 |
138 | # Visual Studio code coverage results
139 | *.coverage
140 | *.coveragexml
141 |
142 | # NCrunch
143 | _NCrunch_*
144 | .*crunch*.local.xml
145 | nCrunchTemp_*
146 |
147 | # MightyMoose
148 | *.mm.*
149 | AutoTest.Net/
150 |
151 | # Web workbench (sass)
152 | .sass-cache/
153 |
154 | # Installshield output folder
155 | [Ee]xpress/
156 |
157 | # DocProject is a documentation generator add-in
158 | DocProject/buildhelp/
159 | DocProject/Help/*.HxT
160 | DocProject/Help/*.HxC
161 | DocProject/Help/*.hhc
162 | DocProject/Help/*.hhk
163 | DocProject/Help/*.hhp
164 | DocProject/Help/Html2
165 | DocProject/Help/html
166 |
167 | # Click-Once directory
168 | publish/
169 |
170 | # Publish Web Output
171 | *.[Pp]ublish.xml
172 | *.azurePubxml
173 | # Note: Comment the next line if you want to checkin your web deploy settings,
174 | # but database connection strings (with potential passwords) will be unencrypted
175 | *.pubxml
176 | *.publishproj
177 |
178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
179 | # checkin your Azure Web App publish settings, but sensitive information contained
180 | # in these scripts will be unencrypted
181 | PublishScripts/
182 |
183 | # NuGet Packages
184 | *.nupkg
185 | # The packages folder can be ignored because of Package Restore
186 | **/[Pp]ackages/*
187 | # except build/, which is used as an MSBuild target.
188 | !**/[Pp]ackages/build/
189 | # Uncomment if necessary however generally it will be regenerated when needed
190 | #!**/[Pp]ackages/repositories.config
191 | # NuGet v3's project.json files produces more ignorable files
192 | *.nuget.props
193 | *.nuget.targets
194 |
195 | # Microsoft Azure Build Output
196 | csx/
197 | *.build.csdef
198 |
199 | # Microsoft Azure Emulator
200 | ecf/
201 | rcf/
202 |
203 | # Windows Store app package directories and files
204 | AppPackages/
205 | BundleArtifacts/
206 | Package.StoreAssociation.xml
207 | _pkginfo.txt
208 | *.appx
209 |
210 | # Visual Studio cache files
211 | # files ending in .cache can be ignored
212 | *.[Cc]ache
213 | # but keep track of directories ending in .cache
214 | !?*.[Cc]ache/
215 |
216 | # Others
217 | ClientBin/
218 | ~$*
219 | *~
220 | *.dbmdl
221 | *.dbproj.schemaview
222 | *.jfm
223 | *.pfx
224 | *.publishsettings
225 | orleans.codegen.cs
226 |
227 | # Including strong name files can present a security risk
228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
229 | #*.snk
230 |
231 | # Since there are multiple workflows, uncomment next line to ignore bower_components
232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
233 | #bower_components/
234 |
235 | # RIA/Silverlight projects
236 | Generated_Code/
237 |
238 | # Backup & report files from converting an old project file
239 | # to a newer Visual Studio version. Backup files are not needed,
240 | # because we have git ;-)
241 | _UpgradeReport_Files/
242 | Backup*/
243 | UpgradeLog*.XML
244 | UpgradeLog*.htm
245 | ServiceFabricBackup/
246 | *.rptproj.bak
247 |
248 | # SQL Server files
249 | *.mdf
250 | *.ldf
251 | *.ndf
252 |
253 | # Business Intelligence projects
254 | *.rdl.data
255 | *.bim.layout
256 | *.bim_*.settings
257 | *.rptproj.rsuser
258 | *- Backup*.rdl
259 |
260 | # Microsoft Fakes
261 | FakesAssemblies/
262 |
263 | # GhostDoc plugin setting file
264 | *.GhostDoc.xml
265 |
266 | # Node.js Tools for Visual Studio
267 | .ntvs_analysis.dat
268 | node_modules/
269 |
270 | # Visual Studio 6 build log
271 | *.plg
272 |
273 | # Visual Studio 6 workspace options file
274 | *.opt
275 |
276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
277 | *.vbw
278 |
279 | # Visual Studio LightSwitch build output
280 | **/*.HTMLClient/GeneratedArtifacts
281 | **/*.DesktopClient/GeneratedArtifacts
282 | **/*.DesktopClient/ModelManifest.xml
283 | **/*.Server/GeneratedArtifacts
284 | **/*.Server/ModelManifest.xml
285 | _Pvt_Extensions
286 |
287 | # Paket dependency manager
288 | .paket/paket.exe
289 | paket-files/
290 |
291 | # FAKE - F# Make
292 | .fake/
293 |
294 | # JetBrains Rider
295 | .idea/
296 | *.sln.iml
297 |
298 | # CodeRush personal settings
299 | .cr/personal
300 |
301 | # Python Tools for Visual Studio (PTVS)
302 | __pycache__/
303 | *.pyc
304 |
305 | # Cake - Uncomment if you are using it
306 | # tools/**
307 | # !tools/packages.config
308 |
309 | # Tabs Studio
310 | *.tss
311 |
312 | # Telerik's JustMock configuration file
313 | *.jmconfig
314 |
315 | # BizTalk build output
316 | *.btp.cs
317 | *.btm.cs
318 | *.odx.cs
319 | *.xsd.cs
320 |
321 | # OpenCover UI analysis results
322 | OpenCover/
323 |
324 | # Azure Stream Analytics local run output
325 | ASALocalRun/
326 |
327 | # MSBuild Binary and Structured Log
328 | *.binlog
329 |
330 | # NVidia Nsight GPU debugger configuration file
331 | *.nvuser
332 |
333 | # MFractors (Xamarin productivity tool) working folder
334 | .mfractor/
335 |
336 | # Local History for Visual Studio
337 | .localhistory/
338 |
339 | # BeatPulse healthcheck temp database
340 | healthchecksdb
341 | Libraries/0Harmony.dll
342 | Libraries/BaseX.dll
343 | Libraries/FrooxEngine.dll
344 | Libraries/NeosModLoader.dll
345 | Libraries/UnityEngine.dll
346 | Libraries/UnityNeos.dll
347 |
--------------------------------------------------------------------------------
/IKCulling/ResoniteIKCulling.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Elements.Core;
4 | using FrooxEngine;
5 | using FrooxEngine.FinalIK;
6 | using HarmonyLib;
7 | using ResoniteModLoader;
8 |
9 | namespace IkCulling
10 | {
11 | public class IkCulling : ResoniteMod
12 | {
13 | public static ModConfiguration Config;
14 |
15 | [AutoRegisterConfigKey]
16 | public static readonly ModConfigurationKey Enabled =
17 | new ModConfigurationKey(
18 | "Enabled",
19 | "Enabled",
20 | () => true);
21 |
22 | [AutoRegisterConfigKey]
23 | public static readonly ModConfigurationKey DummySpacer1 =
24 | new ModConfigurationKey(
25 | " ",
26 | "");
27 |
28 | [AutoRegisterConfigKey]
29 | public static readonly ModConfigurationKey DummySpacer2 =
30 | new ModConfigurationKey(
31 | "DummySpacer2",
32 | "Culling Behavior Options:");
33 |
34 | [AutoRegisterConfigKey]
35 | public static readonly ModConfigurationKey ScaleComp =
36 | new ModConfigurationKey(
37 | "ScaleComp",
38 | "Type of scale compensation used for distance checks.",
39 | () => ScaleCompType.None);
40 |
41 | [AutoRegisterConfigKey]
42 | public static readonly ModConfigurationKey FOV =
43 | new ModConfigurationKey(
44 | "FOV",
45 | "Enable to force specific culling FOV, automatic when disabled.",
46 | () => null, false, v => v <= 180 && v >= 0 || v == null);
47 |
48 | [AutoRegisterConfigKey]
49 | public static readonly ModConfigurationKey MinUserCount =
50 | new ModConfigurationKey(
51 | "MinUserCount",
52 | "Minimum amount of users in the world to enable culling.",
53 | () => 3, false, v => v >= 0);
54 |
55 | [AutoRegisterConfigKey]
56 | public static readonly ModConfigurationKey MinCullingRange =
57 | new ModConfigurationKey(
58 | "MinCullingRange",
59 | "Minimum range for IK to always be enabled, useful for mirrors.",
60 | () => 4f, false, v => v >= 0);
61 |
62 | [AutoRegisterConfigKey]
63 | public static readonly ModConfigurationKey MaxViewRange =
64 | new ModConfigurationKey(
65 | "MaxViewRange",
66 | "Maximum range before fully disabling IK.",
67 | () => 30f, false, v => v >= 0);
68 |
69 | [AutoRegisterConfigKey]
70 | public static readonly ModConfigurationKey DummySpacer3 =
71 | new ModConfigurationKey(
72 | " ",
73 | "");
74 |
75 | [AutoRegisterConfigKey]
76 | public static readonly ModConfigurationKey DummySpacer4 =
77 | new ModConfigurationKey(
78 | "DummySpacer4",
79 | "Extra Culling Options:");
80 |
81 | [AutoRegisterConfigKey]
82 | public static readonly ModConfigurationKey DisableAfkUser =
83 | new ModConfigurationKey(
84 | "DisableAfkUser",
85 | "Disable IK of users not in the session.",
86 | () => true);
87 |
88 | [AutoRegisterConfigKey]
89 | public static readonly ModConfigurationKey DisableIkWithoutUser =
90 | new ModConfigurationKey(
91 | "DisableIkWithoutUser",
92 | "Disable IK without an active user.",
93 | () => true);
94 |
95 | [AutoRegisterConfigKey]
96 | public static readonly ModConfigurationKey DisableOnDashboard =
97 | new ModConfigurationKey(
98 | "DisableOnInactiveUser",
99 | "Disable IK if the SteamVR/Oculus dash is open or the window is not focused on desktop mode.",
100 | () => true);
101 |
102 | [AutoRegisterConfigKey]
103 | public static readonly ModConfigurationKey DummySpacer5 =
104 | new ModConfigurationKey(
105 | " ",
106 | "");
107 |
108 | [AutoRegisterConfigKey]
109 | public static readonly ModConfigurationKey DummySpacer6 =
110 | new ModConfigurationKey(
111 | "DummySpacer6",
112 | "Throttling Options:");
113 |
114 | [AutoRegisterConfigKey]
115 | public static readonly ModConfigurationKey UpdateRate =
116 | new ModConfigurationKey(
117 | "UpdateRate",
118 | "Update rate for IK.",
119 | () => IkUpdateRate.Full);
120 |
121 | [FrooxEngine.Range(0, 100)]
122 | [AutoRegisterConfigKey]
123 | public static readonly ModConfigurationKey IkUpdateFalloff =
124 | new ModConfigurationKey(
125 | "IkUpdateFalloff",
126 | "Reduce IK updates based on distance, threshold is 0 - 100% relative to Max Range.",
127 | () => 50, false, v => v <= 100 && v >= 0);
128 |
129 | public override string Name => "ResoniteIkCulling";
130 | public override string Author => "Raidriar796 & KyuubiYoru";
131 | public override string Version => "2.7.0";
132 | public override string Link => "https://github.com/Raidriar796/ResoniteIkCulling";
133 |
134 | public override void OnEngineInit()
135 | {
136 | Harmony harmony = new Harmony("net.raidriar796.ResoniteIkCulling");
137 | harmony.PatchAll();
138 | Config = GetConfiguration();
139 | Config.Save(true);
140 |
141 | Engine.Current.RunPostInit(() =>
142 | {
143 | //Calling the methods on start instead of declaring the
144 | //variables with method calls because rml doesn't like that
145 | UpdateMinRange();
146 | UpdateMaxRange();
147 | UpdateFalloff();
148 |
149 | // Engine.Current.InputInterface.VRActiveChanged += (value) => { UpdateFOV(); };
150 | Engine.Current.WorldManager.WorldFocused += (value) => { UpdateFOV(); };
151 | });
152 |
153 | //Sets up variables to avoid redundant calculations per ik per frame
154 | //by saving the result of calculations that infrequently change
155 | FOV.OnChanged += (value) => { UpdateFOV(); };
156 | MinCullingRange.OnChanged += (value) => { UpdateMinRange(); };
157 | MaxViewRange.OnChanged += (value) => { UpdateMaxRange(); };
158 | IkUpdateFalloff.OnChanged += (value) => { UpdateFalloff(); };
159 | }
160 |
161 | //Variables
162 | private static float FOVDegToDot = 0f;
163 | private const float VRDegToDot = -0.1736482f; //MathX.Cos(MathX.Deg2Rad * 100.0f
164 | private static float MinCullingRangeSqr = 0f;
165 | private static float MaxViewRangeSqr = 0f;
166 | private static float PercentAsFloat = 0f;
167 | private static float Threshold = 0f;
168 | private static float FalloffStep1 = 0f;
169 | private static float FalloffStep2 = 0f;
170 | private static float FalloffStep3 = 0f;
171 | private static float FalloffStep4 = 0f;
172 | private static float FalloffStep5 = 0f;
173 | public enum ScaleCompType
174 | {
175 | None,
176 | Relative,
177 | YourUserScale,
178 | OtherUserScale
179 | }
180 | public enum IkUpdateRate
181 | {
182 | Full,
183 | Half,
184 | Quarter,
185 | Eighth
186 | }
187 |
188 | //Populated to every IK in dictionary.
189 | //We probably didn't need to do this, but in the event
190 | //that more variables per IK are needed, this is here.
191 | public class Variables
192 | {
193 | public byte UpdateIndex = 1;
194 | }
195 |
196 | public static void UpdateFOV()
197 | {
198 | if (!Config.GetValue(FOV).HasValue)
199 | {
200 | if (Engine.Current.WorldManager.FocusedWorld.LocalUser.GetScreen() == null)
201 | {
202 | //Value assigned when in VR
203 | FOVDegToDot = VRDegToDot;
204 | }
205 | else
206 | {
207 | //Value assigned when in desktop
208 | FOVDegToDot = MathX.Cos(MathX.Deg2Rad * Engine.Current.WorldManager.FocusedWorld.LocalUserDesktopFOV);
209 | }
210 | }
211 | else
212 | {
213 | //Value assigned when user manually specifies FOV
214 | FOVDegToDot = MathX.Cos(MathX.Deg2Rad * Config.GetValue(FOV).Value);
215 | }
216 | }
217 |
218 | public static void UpdateMinRange()
219 | {
220 | MinCullingRangeSqr = MathX.Pow(Config.GetValue(MinCullingRange), 2f);
221 | }
222 |
223 | public static void UpdateMaxRange()
224 | {
225 | MaxViewRangeSqr = MathX.Pow(Config.GetValue(MaxViewRange), 2f);
226 | }
227 |
228 | public static void UpdateFalloff()
229 | {
230 | PercentAsFloat = Config.GetValue(IkUpdateFalloff) * 0.01f;
231 |
232 | Threshold = MathX.Lerp(0f, Config.GetValue(MaxViewRange), PercentAsFloat);
233 |
234 | FalloffStep1 = MathX.Pow(Threshold, 2f);
235 |
236 | FalloffStep2 = MathX.Pow(MathX.Lerp(Threshold, Config.GetValue(MaxViewRange), 0.2f), 2f);
237 |
238 | FalloffStep3 = MathX.Pow(MathX.Lerp(Threshold, Config.GetValue(MaxViewRange), 0.4f), 2f);
239 |
240 | FalloffStep4 = MathX.Pow(MathX.Lerp(Threshold, Config.GetValue(MaxViewRange), 0.6f), 2f);
241 |
242 | FalloffStep5 = MathX.Pow(MathX.Lerp(Threshold, Config.GetValue(MaxViewRange), 0.8f), 2f);
243 | }
244 |
245 | static Dictionary vrikList = new Dictionary();
246 |
247 | [HarmonyPatch("OnAwake")]
248 | [HarmonyPostfix]
249 | private static void AddToList(VRIKAvatar __instance)
250 | {
251 | //Adds IK to list as new instances appear
252 | if (!vrikList.ContainsKey(__instance))
253 | {
254 | vrikList.Add(__instance, new Variables());
255 | }
256 |
257 | foreach (var item in vrikList)
258 | {
259 | if (item.Key == null)
260 | {
261 | vrikList.Remove(item.Key);
262 | }
263 | }
264 | }
265 |
266 | [HarmonyPatch(typeof(VRIKAvatar))]
267 | public class IkCullingPatch
268 | {
269 | [HarmonyPrefix]
270 | [HarmonyPatch("OnCommonUpdate")]
271 | private static bool OnCommonUpdatePrefix(VRIKAvatar __instance)
272 | {
273 | try
274 | {
275 | //IkCulling is disabled
276 | if (!Config.GetValue(Enabled)) return true;
277 |
278 | //Ik is disabled
279 | if (!__instance.Enabled) return false;
280 |
281 | //User is Headless
282 | if (ModLoader.IsHeadless) return false;
283 |
284 | //Always skip local Ik
285 | if (__instance.IsUnderLocalUser && __instance.IsEquipped) return true;
286 |
287 | //Too few users
288 | if (__instance.Slot.World.UserCount < Config.GetValue(MinUserCount)) return true;
289 |
290 | //Platform dash is open or user is not focused on window
291 | if (Config.GetValue(DisableOnDashboard))
292 | {
293 | if (__instance.LocalUser.VR_Active)
294 | {
295 | if (__instance.LocalUser.IsPlatformDashOpened) return false;
296 | }
297 | else
298 | {
299 | if (!Engine.Current.InputInterface.IsWindowFocused) return false;
300 | }
301 | }
302 |
303 | //No active user
304 | if (Config.GetValue(DisableIkWithoutUser) && !__instance.IsEquipped) return false;
305 |
306 | //Users not present
307 | if (Config.GetValue(DisableAfkUser) && __instance.Slot.ActiveUser != null &&
308 | !__instance.Slot.ActiveUser.IsPresentInWorld) return false;
309 |
310 | float3 playerPos = __instance.Slot.World.LocalUserViewPosition;
311 | float3 ikPos = __instance.HeadProxy.GlobalPosition;
312 |
313 | float dist = MathX.DistanceSqr(playerPos, ikPos);
314 |
315 | float LocalUserScale = __instance.LocalUserRoot.GlobalScale;
316 |
317 | switch (Config.GetValue(ScaleComp))
318 | {
319 | case ScaleCompType.None:
320 | break;
321 |
322 | case ScaleCompType.Relative:
323 | dist /= LocalUserScale * LocalUserScale;
324 | if (__instance.IsEquipped)
325 | {
326 | dist /= __instance.Slot.ActiveUser.Root.GlobalScale * __instance.Slot.ActiveUser.Root.GlobalScale;
327 | }
328 | break;
329 |
330 | case ScaleCompType.YourUserScale:
331 | dist /= LocalUserScale * LocalUserScale;
332 | break;
333 |
334 | case ScaleCompType.OtherUserScale:
335 | if (__instance.IsEquipped)
336 | {
337 | dist /= __instance.Slot.ActiveUser.Root.GlobalScale * __instance.Slot.ActiveUser.Root.GlobalScale;
338 | }
339 | break;
340 |
341 | default:
342 | break;
343 | }
344 |
345 | //Checks if IK is within min range and in view
346 | if (dist > MinCullingRangeSqr &&
347 | MathX.Dot((ikPos - playerPos).Normalized, __instance.Slot.World.LocalUserViewRotation * float3.Forward) < FOVDegToDot)
348 | return false;
349 |
350 | //Check if IK is outside of max range
351 | if (dist > MaxViewRangeSqr)
352 | return false;
353 |
354 | //IK throttling
355 | if ((Config.GetValue(IkUpdateFalloff) < 100) ||
356 | (Config.GetValue(UpdateRate) != IkUpdateRate.Full))
357 | {
358 | //Adds an IK instance to the list if it's not already
359 | if (!vrikList.ContainsKey(__instance))
360 | {
361 | vrikList.Add(__instance, new Variables());
362 | return true;
363 | }
364 | byte skipCount = 1;
365 |
366 | //Update skips for falloff
367 | if (Config.GetValue(IkUpdateFalloff) < 100)
368 | {
369 | if (dist > FalloffStep5)
370 | {
371 | skipCount = 6;
372 | }
373 | else if (dist > FalloffStep4)
374 | {
375 | skipCount = 5;
376 | }
377 | else if (dist > FalloffStep3)
378 | {
379 | skipCount = 4;
380 | }
381 | else if (dist > FalloffStep2)
382 | {
383 | skipCount = 3;
384 | }
385 | else if (dist > FalloffStep1)
386 | {
387 | skipCount = 2;
388 | }
389 | }
390 |
391 | //Update skips lower update rate
392 | else switch (Config.GetValue(UpdateRate))
393 | {
394 | case IkUpdateRate.Half:
395 | skipCount = 2;
396 | break;
397 |
398 | case IkUpdateRate.Quarter:
399 | skipCount = 4;
400 | break;
401 |
402 | case IkUpdateRate.Eighth:
403 | skipCount = 8;
404 | break;
405 |
406 | default:
407 | skipCount = 1;
408 | break;
409 | }
410 |
411 | //Update skips for falloff + lower update rate
412 | if (Config.GetValue(UpdateRate) != IkUpdateRate.Full && (Config.GetValue(IkUpdateFalloff) < 100))
413 | {
414 | switch (Config.GetValue(UpdateRate))
415 | {
416 | case IkUpdateRate.Half:
417 | skipCount *= 2;
418 | break;
419 |
420 | case IkUpdateRate.Quarter:
421 | skipCount *= 4;
422 | break;
423 |
424 | case IkUpdateRate.Eighth:
425 | skipCount *= 8;
426 | break;
427 |
428 | default:
429 | skipCount = 1;
430 | break;
431 | }
432 | }
433 | //The part that actually skips updates
434 | if (vrikList[__instance].UpdateIndex > skipCount) vrikList[__instance].UpdateIndex = 1;
435 | if (vrikList[__instance].UpdateIndex == skipCount)
436 | {
437 | vrikList[__instance].UpdateIndex = 1;
438 | return true;
439 | }
440 | else
441 | {
442 | vrikList[__instance].UpdateIndex += 1;
443 | return false;
444 | }
445 | }
446 | }
447 | catch (Exception e)
448 | {
449 | Msg("Error OnCommonUpdatePatch");
450 | Debug(e.Message);
451 | Debug(e.StackTrace);
452 | return true;
453 | }
454 | return true;
455 | }
456 | }
457 |
458 | //Used to search through the entire FBT calibrator to enable "AutoUpdate" on every IK
459 | public static void CalibratorForceIkAutoUpdate(FullBodyCalibrator __instance)
460 | {
461 | var allVRIK = __instance.Slot.GetComponentsInChildren();
462 | foreach (var vrik in allVRIK)
463 | {
464 | vrik.AutoUpdate.Value = true;
465 | }
466 | }
467 |
468 | //Forces IK to be active when FBT calibrator is created
469 | [HarmonyPatch(typeof(FullBodyCalibrator), "OnAwake")]
470 | class FullBodyCalibratorPatch
471 | {
472 | static void Postfix(FullBodyCalibrator __instance)
473 | {
474 | __instance.RunInUpdates(3, () =>
475 | {
476 | CalibratorForceIkAutoUpdate(__instance);
477 | });
478 | }
479 | }
480 |
481 | //Forces IK to be active when pressing "Calibrate Avatar" on the FBT calibrator
482 | [HarmonyPatch(typeof(FullBodyCalibrator), "CalibrateAvatar")]
483 | class FullBodyCalibratorAvatarPatch
484 | {
485 | static void Postfix(FullBodyCalibrator __instance)
486 | {
487 | CalibratorForceIkAutoUpdate(__instance);
488 | }
489 | }
490 | }
491 | }
492 |
--------------------------------------------------------------------------------