From 572e5d2bca25bc0b06ee935b589bbbb531fafed0 Mon Sep 17 00:00:00 2001 From: "Nick J. Connors" Date: Wed, 26 Jan 2022 16:08:55 -0500 Subject: [PATCH] Rearrange prop building + start tornado support --- NewHorizons/Builder/Props/DetailBuilder.cs | 135 ++++++++++ NewHorizons/Builder/Props/PropBuildManager.cs | 79 ++++++ NewHorizons/Builder/Props/PropBuilder.cs | 241 ------------------ NewHorizons/Builder/Props/ScatterBuilder.cs | 78 ++++++ NewHorizons/Builder/Props/TornadoBuilder.cs | 89 +++++++ NewHorizons/External/PropModule.cs | 9 + 6 files changed, 390 insertions(+), 241 deletions(-) create mode 100644 NewHorizons/Builder/Props/DetailBuilder.cs create mode 100644 NewHorizons/Builder/Props/PropBuildManager.cs delete mode 100644 NewHorizons/Builder/Props/PropBuilder.cs create mode 100644 NewHorizons/Builder/Props/ScatterBuilder.cs create mode 100644 NewHorizons/Builder/Props/TornadoBuilder.cs diff --git a/NewHorizons/Builder/Props/DetailBuilder.cs b/NewHorizons/Builder/Props/DetailBuilder.cs new file mode 100644 index 00000000..a98c12b5 --- /dev/null +++ b/NewHorizons/Builder/Props/DetailBuilder.cs @@ -0,0 +1,135 @@ +using NewHorizons.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Random = UnityEngine.Random; +using Logger = NewHorizons.Utility.Logger; +using NewHorizons.External; +using OWML.Common; + +namespace NewHorizons.Builder.Props +{ + public static class DetailBuilder + { + public static void Make(GameObject go, Sector sector, IPlanetConfig config, IModAssets assets, string uniqueModName) + { + foreach (var detail in config.Props.Details) + { + if (detail.assetBundle != null) + { + var prefab = PropBuildManager.LoadPrefab(detail.assetBundle, detail.path, uniqueModName, assets); + MakeDetail(go, sector, prefab, detail.position, detail.rotation, detail.scale, detail.alignToNormal, detail.generateColliders); + } + else if (detail.objFilePath != null) + { + try + { + var prefab = assets.Get3DObject(detail.objFilePath, detail.mtlFilePath); + prefab.SetActive(false); + MakeDetail(go, sector, prefab, detail.position, detail.rotation, detail.scale, detail.alignToNormal, detail.generateColliders); + } + catch (Exception e) + { + Logger.LogError($"Could not load 3d object {detail.objFilePath} with texture {detail.mtlFilePath} : {e.Message}"); + } + } + else MakeDetail(go, sector, detail.path, detail.position, detail.rotation, detail.scale, detail.alignToNormal, detail.generateColliders); + } + } + + public static GameObject MakeDetail(GameObject go, Sector sector, string propToClone, MVector3 position, MVector3 rotation, float scale, bool alignWithNormal, bool generateColliders) + { + var prefab = GameObject.Find(propToClone); + + //TODO: this is super costly + if (prefab == null) prefab = SearchUtilities.FindObjectOfTypeAndName(propToClone.Split(new char[] { '\\', '/' }).Last()); + if (prefab == null) Logger.LogError($"Couldn't find detail {propToClone}"); + return MakeDetail(go, sector, prefab, position, rotation, scale, alignWithNormal, generateColliders); + } + + public static GameObject MakeDetail(GameObject go, Sector sector, GameObject prefab, MVector3 position, MVector3 rotation, float scale, bool alignWithNormal, bool generateColliders) + { + if (prefab == null) return null; + + GameObject prop = GameObject.Instantiate(prefab, sector.transform); + prop.SetActive(false); + + List assetBundles = new List(); + foreach (var streamingHandle in prop.GetComponentsInChildren()) + { + var assetBundle = streamingHandle.assetBundle; + if (!assetBundles.Contains(assetBundle)) + { + assetBundles.Add(assetBundle); + } + } + + foreach (var assetBundle in assetBundles) + { + sector.OnOccupantEnterSector += ((SectorDetector sd) => StreamingManager.LoadStreamingAssets(assetBundle)); + } + + foreach (var component in prop.GetComponents().Concat(prop.GetComponentsInChildren())) + { + // Enable all children or something + var enabledField = component.GetType().GetField("enabled"); + if (enabledField != null && enabledField.FieldType == typeof(bool)) enabledField.SetValue(component, true); + + // TODO: Make this work or smthng + if (component is GhostIK) (component as GhostIK).enabled = false; + if (component is GhostEffects) (component as GhostEffects).enabled = false; + + if (component is SectoredMonoBehaviour) + { + (component as SectoredMonoBehaviour).SetSector(sector); + } + + if (component is AnglerfishController) + { + try + { + (component as AnglerfishController)._chaseSpeed += OWPhysics.CalculateOrbitVelocity(go.GetAttachedOWRigidbody(), go.GetComponent().GetPrimaryBody().GetAttachedOWRigidbody()).magnitude; + } + catch (Exception e) + { + Logger.LogError($"Couldn't update AnglerFish chase speed: {e.Message}"); + } + } + + // Mesh colliders + if (generateColliders) + { + if (component is MeshFilter && component.gameObject.GetComponent() == null) + { + var mesh = (component as MeshFilter).mesh; + if (mesh.isReadable) component.gameObject.AddComponent(); + else Logger.LogError($"Couldn't change mesh for {component.gameObject.name} because it is not readable"); + } + } + } + + prop.transform.position = position == null ? go.transform.position : go.transform.TransformPoint((Vector3)position); + + Quaternion rot = rotation == null ? Quaternion.identity : Quaternion.Euler((Vector3)rotation); + prop.transform.localRotation = rot; + if (alignWithNormal) + { + var up = prop.transform.localPosition.normalized; + var front = Vector3.Cross(up, Vector3.left); + if (front.sqrMagnitude == 0f) front = Vector3.Cross(up, Vector3.forward); + if (front.sqrMagnitude == 0f) front = Vector3.Cross(up, Vector3.up); + + prop.transform.LookAt(prop.transform.position + front, up); + } + + prop.transform.localScale = scale != 0 ? Vector3.one * scale : prefab.transform.localScale; + + prop.SetActive(true); + + return prop; + } + } +} diff --git a/NewHorizons/Builder/Props/PropBuildManager.cs b/NewHorizons/Builder/Props/PropBuildManager.cs new file mode 100644 index 00000000..09f1af96 --- /dev/null +++ b/NewHorizons/Builder/Props/PropBuildManager.cs @@ -0,0 +1,79 @@ +using NewHorizons.External; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Random = UnityEngine.Random; +using Logger = NewHorizons.Utility.Logger; +using System.Reflection; +using NewHorizons.Utility; +using OWML.Common; + +namespace NewHorizons.Builder.Props +{ + public static class PropBuildManager + { + public static void Make(GameObject go, Sector sector, IPlanetConfig config, IModAssets assets, string uniqueModName) + { + if (config.Props.Scatter != null) + { + ScatterBuilder.Make(go, sector, config, assets, uniqueModName); + } + if(config.Props.Details != null) + { + DetailBuilder.Make(go, sector, config, assets, uniqueModName); + } + if(config.Props.Geysers != null) + { + foreach(var geyserInfo in config.Props.Geysers) + { + GeyserBuilder.Make(go, sector, geyserInfo); + } + } + if(config.Props.Tornados != null) + { + foreach(var tornadoInfo in config.Props.Tornados) + { + TornadoBuilder.Make(go, sector, tornadoInfo); + } + } + } + + public static GameObject LoadPrefab(string assetBundle, string path, string uniqueModName, IModAssets assets) + { + string key = uniqueModName + "." + assetBundle; + AssetBundle bundle; + GameObject prefab; + + try + { + if (Main.AssetBundles.ContainsKey(key)) bundle = Main.AssetBundles[key]; + else + { + bundle = assets.LoadBundle(assetBundle); + Main.AssetBundles[key] = bundle; + } + } + catch (Exception e) + { + Logger.LogError($"Couldn't load AssetBundle {assetBundle} : {e.Message}"); + return null; + } + + try + { + prefab = bundle.LoadAsset(path); + prefab.SetActive(false); + } + catch (Exception e) + { + Logger.Log($"Couldn't load asset {path} from AssetBundle {assetBundle} : {e.Message}"); + return null; + } + + return prefab; + } + } +} diff --git a/NewHorizons/Builder/Props/PropBuilder.cs b/NewHorizons/Builder/Props/PropBuilder.cs deleted file mode 100644 index e9df7125..00000000 --- a/NewHorizons/Builder/Props/PropBuilder.cs +++ /dev/null @@ -1,241 +0,0 @@ -using NewHorizons.External; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using UnityEngine; -using Random = UnityEngine.Random; -using Logger = NewHorizons.Utility.Logger; -using System.Reflection; -using NewHorizons.Utility; -using OWML.Common; - -namespace NewHorizons.Builder.Props -{ - public static class PropBuilder - { - public static void Make(GameObject go, Sector sector, IPlanetConfig config, IModAssets assets, string uniqueModName) - { - if (config.Props.Scatter != null) - { - PropBuilder.MakeScatter(go, config.Props.Scatter, config.Base.SurfaceSize, sector, assets, uniqueModName, config); - } - if(config.Props.Details != null) - { - foreach(var detail in config.Props.Details) - { - if(detail.assetBundle != null) - { - var prefab = LoadPrefab(detail.assetBundle, detail.path, uniqueModName, assets); - MakeDetail(go, sector, prefab, detail.position, detail.rotation, detail.scale, detail.alignToNormal, detail.generateColliders); - } - else if(detail.objFilePath != null) - { - try - { - var prefab = assets.Get3DObject(detail.objFilePath, detail.mtlFilePath); - prefab.SetActive(false); - MakeDetail(go, sector, prefab, detail.position, detail.rotation, detail.scale, detail.alignToNormal, detail.generateColliders); - } - catch(Exception e) - { - Logger.LogError($"Could not load 3d object {detail.objFilePath} with texture {detail.mtlFilePath} : {e.Message}"); - } - } - else MakeDetail(go, sector, detail.path, detail.position, detail.rotation, detail.scale, detail.alignToNormal, detail.generateColliders); - } - } - if(config.Props.Geysers != null) - { - foreach(var geyserInfo in config.Props.Geysers) - { - GeyserBuilder.Make(go, sector, geyserInfo); - } - } - } - - public static GameObject MakeDetail(GameObject go, Sector sector, string propToClone, MVector3 position, MVector3 rotation, float scale, bool alignWithNormal, bool generateColliders) - { - var prefab = GameObject.Find(propToClone); - - //TODO: this is super costly - if (prefab == null) prefab = SearchUtilities.FindObjectOfTypeAndName(propToClone.Split(new char[] { '\\', '/' }).Last()); - if (prefab == null) Logger.LogError($"Couldn't find detail {propToClone}"); - return MakeDetail(go, sector, prefab, position, rotation, scale, alignWithNormal, generateColliders); - } - - public static GameObject MakeDetail(GameObject go, Sector sector, GameObject prefab, MVector3 position, MVector3 rotation, float scale, bool alignWithNormal, bool generateColliders) - { - if (prefab == null) return null; - - GameObject prop = GameObject.Instantiate(prefab, sector.transform); - prop.SetActive(false); - - List assetBundles = new List(); - foreach (var streamingHandle in prop.GetComponentsInChildren()) - { - var assetBundle = streamingHandle.assetBundle; - if (!assetBundles.Contains(assetBundle)) - { - assetBundles.Add(assetBundle); - } - } - - foreach (var assetBundle in assetBundles) - { - sector.OnOccupantEnterSector += ((SectorDetector sd) => StreamingManager.LoadStreamingAssets(assetBundle)); - } - - foreach(var component in prop.GetComponents().Concat(prop.GetComponentsInChildren())) - { - // Enable all children or something - var enabledField = component.GetType().GetField("enabled"); - if (enabledField != null && enabledField.FieldType == typeof(bool)) enabledField.SetValue(component, true); - - // TODO: Make this work or smthng - if (component is GhostIK) (component as GhostIK).enabled = false; - if (component is GhostEffects) (component as GhostEffects).enabled = false; - - if(component is SectoredMonoBehaviour) - { - (component as SectoredMonoBehaviour).SetSector(sector); - } - - if (component is AnglerfishController) - { - try - { - (component as AnglerfishController)._chaseSpeed += OWPhysics.CalculateOrbitVelocity(go.GetAttachedOWRigidbody(), go.GetComponent().GetPrimaryBody().GetAttachedOWRigidbody()).magnitude; - } - catch (Exception e) - { - Logger.LogError($"Couldn't update AnglerFish chase speed: {e.Message}"); - } - } - - // Mesh colliders - if (generateColliders) - { - if(component is MeshFilter && component.gameObject.GetComponent() == null) - { - var mesh = (component as MeshFilter).mesh; - if (mesh.isReadable) component.gameObject.AddComponent(); - else Logger.LogError($"Couldn't change mesh for {component.gameObject.name} because it is not readable"); - } - } - } - - prop.transform.position = position == null ? go.transform.position : go.transform.TransformPoint((Vector3)position); - - Quaternion rot = rotation == null ? Quaternion.identity : Quaternion.Euler((Vector3)rotation); - prop.transform.localRotation = rot; - if (alignWithNormal) - { - var up = prop.transform.localPosition.normalized; - var front = Vector3.Cross(up, Vector3.left); - if (front.sqrMagnitude == 0f) front = Vector3.Cross(up, Vector3.forward); - if (front.sqrMagnitude == 0f) front = Vector3.Cross(up, Vector3.up); - - prop.transform.LookAt(prop.transform.position + front, up); - } - - prop.transform.localScale = scale != 0 ? Vector3.one * scale : prefab.transform.localScale; - - prop.SetActive(true); - - return prop; - } - - private static void MakeScatter(GameObject go, PropModule.ScatterInfo[] scatterInfo, float radius, Sector sector, IModAssets assets, string uniqueModName, IPlanetConfig config) - { - var heightMap = config.HeightMap; - - var area = 4f * Mathf.PI * radius * radius; - var points = RandomUtility.FibonacciSphere((int)(area * 10)); - - Texture2D heightMapTexture = null; - if (heightMap != null) - { - try - { - heightMapTexture = assets.GetTexture(heightMap.HeightMap); - } - catch (Exception) { } - } - - foreach (var propInfo in scatterInfo) - { - GameObject prefab; - if (propInfo.assetBundle != null) prefab = LoadPrefab(propInfo.assetBundle, propInfo.path, uniqueModName, assets); - else prefab = GameObject.Find(propInfo.path); - for(int i = 0; i < propInfo.count; i++) - { - var randomInd = (int)Random.Range(0, points.Count); - var point = points[randomInd]; - - var height = radius; - if(heightMapTexture != null) - { - var sphericals = CoordinateUtilities.CartesianToSpherical(point); - float longitude = sphericals.x; - float latitude = sphericals.y; - - float sampleX = heightMapTexture.width * longitude / 360f; - float sampleY = heightMapTexture.height * latitude / 180f; - - float relativeHeight = heightMapTexture.GetPixel((int)sampleX, (int)sampleY).r; - height = (relativeHeight * (heightMap.MaxHeight - heightMap.MinHeight) + heightMap.MinHeight); - - // Because heightmaps are dumb gotta rotate it 90 degrees around the x axis bc UHHHHHHHHHHHHH - point = Quaternion.Euler(90, 0, 0) * point; - - // Keep things mostly above water - if (config.Water != null && height - 1f < config.Water.Size) continue; - } - - var prop = MakeDetail(go, sector, prefab, (MVector3)(point.normalized * height), null, propInfo.scale, true, propInfo.generateColliders); - if(propInfo.offset != null) prop.transform.localPosition += prop.transform.TransformVector(propInfo.offset); - if(propInfo.rotation != null) prop.transform.rotation *= Quaternion.Euler(propInfo.rotation); - points.RemoveAt(randomInd); - if (points.Count == 0) return; - } - } - } - - private static GameObject LoadPrefab(string assetBundle, string path, string uniqueModName, IModAssets assets) - { - string key = uniqueModName + "." + assetBundle; - AssetBundle bundle; - GameObject prefab; - - try - { - if (Main.AssetBundles.ContainsKey(key)) bundle = Main.AssetBundles[key]; - else - { - bundle = assets.LoadBundle(assetBundle); - Main.AssetBundles[key] = bundle; - } - } - catch (Exception e) - { - Logger.LogError($"Couldn't load AssetBundle {assetBundle} : {e.Message}"); - return null; - } - - try - { - prefab = bundle.LoadAsset(path); - prefab.SetActive(false); - } - catch (Exception e) - { - Logger.Log($"Couldn't load asset {path} from AssetBundle {assetBundle} : {e.Message}"); - return null; - } - - return prefab; - } - } -} diff --git a/NewHorizons/Builder/Props/ScatterBuilder.cs b/NewHorizons/Builder/Props/ScatterBuilder.cs new file mode 100644 index 00000000..455e06f9 --- /dev/null +++ b/NewHorizons/Builder/Props/ScatterBuilder.cs @@ -0,0 +1,78 @@ +using NewHorizons.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Random = UnityEngine.Random; +using Logger = NewHorizons.Utility.Logger; +using NewHorizons.External; +using OWML.Common; + +namespace NewHorizons.Builder.Props +{ + public static class ScatterBuilder + { + public static void Make(GameObject go, Sector sector, IPlanetConfig config, IModAssets assets, string uniqueModName) + { + MakeScatter(go, config.Props.Scatter, config.Base.SurfaceSize, sector, assets, uniqueModName, config); + } + + private static void MakeScatter(GameObject go, PropModule.ScatterInfo[] scatterInfo, float radius, Sector sector, IModAssets assets, string uniqueModName, IPlanetConfig config) + { + var heightMap = config.HeightMap; + + var area = 4f * Mathf.PI * radius * radius; + var points = RandomUtility.FibonacciSphere((int)(area * 10)); + + Texture2D heightMapTexture = null; + if (heightMap != null) + { + try + { + heightMapTexture = assets.GetTexture(heightMap.HeightMap); + } + catch (Exception) { } + } + + foreach (var propInfo in scatterInfo) + { + GameObject prefab; + if (propInfo.assetBundle != null) prefab = PropBuildManager.LoadPrefab(propInfo.assetBundle, propInfo.path, uniqueModName, assets); + else prefab = GameObject.Find(propInfo.path); + for (int i = 0; i < propInfo.count; i++) + { + var randomInd = (int)Random.Range(0, points.Count); + var point = points[randomInd]; + + var height = radius; + if (heightMapTexture != null) + { + var sphericals = CoordinateUtilities.CartesianToSpherical(point); + float longitude = sphericals.x; + float latitude = sphericals.y; + + float sampleX = heightMapTexture.width * longitude / 360f; + float sampleY = heightMapTexture.height * latitude / 180f; + + float relativeHeight = heightMapTexture.GetPixel((int)sampleX, (int)sampleY).r; + height = (relativeHeight * (heightMap.MaxHeight - heightMap.MinHeight) + heightMap.MinHeight); + + // Because heightmaps are dumb gotta rotate it 90 degrees around the x axis bc UHHHHHHHHHHHHH + point = Quaternion.Euler(90, 0, 0) * point; + + // Keep things mostly above water + if (config.Water != null && height - 1f < config.Water.Size) continue; + } + + var prop = DetailBuilder.MakeDetail(go, sector, prefab, (MVector3)(point.normalized * height), null, propInfo.scale, true, propInfo.generateColliders); + if (propInfo.offset != null) prop.transform.localPosition += prop.transform.TransformVector(propInfo.offset); + if (propInfo.rotation != null) prop.transform.rotation *= Quaternion.Euler(propInfo.rotation); + points.RemoveAt(randomInd); + if (points.Count == 0) return; + } + } + } + } +} diff --git a/NewHorizons/Builder/Props/TornadoBuilder.cs b/NewHorizons/Builder/Props/TornadoBuilder.cs new file mode 100644 index 00000000..47bbc937 --- /dev/null +++ b/NewHorizons/Builder/Props/TornadoBuilder.cs @@ -0,0 +1,89 @@ +using NewHorizons.External; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Logger = NewHorizons.Utility.Logger; + +namespace NewHorizons.Builder.Props +{ + public static class TornadoBuilder + { + public static string tornadoParentName = "Tornados"; + + public static void Make(GameObject go, Sector sector, PropModule.TornadoInfo info) + { + // If we are given elevation choose a random position + Vector3 position; + float elevation = 0f; + + if (info.position != null) + { + position = info.position; + elevation = position.magnitude; + } + else if (info.elevation != 0f) + { + position = UnityEngine.Random.insideUnitSphere * info.elevation; + elevation = info.elevation; + } + else + { + Logger.LogError($"Couldn't make tornado for {go.name}: No elevation or position was given"); + return; + } + + var prefab = GameObject.Find("GiantsDeep_Body/Sector_GD/Sector_GDInterior/Tornadoes_GDInterior/MovingTornadoes/Root/UpTornado_Pivot (2)"); + + // Default radius is 40, height is 837.0669 + + var tornado = GameObject.Instantiate(prefab, sector.transform); + tornado.SetActive(false); + + tornado.transform.localPosition = Vector3.zero; + + var scale = Vector3.one; + var height = 837.0669f; + if (info.scale != null) + { + scale = new Vector3(info.scale.X / 40f, info.scale.Y / 837.0669f, info.scale.Z / 40f); + height = info.scale.Y; + } + + tornado.transform.localScale = scale; + + var tornadoController = tornado.GetComponent(); + tornadoController.SetSector(sector); + var n = position.normalized; + tornadoController._bottomBone.localPosition = n * elevation; + tornadoController._midBone.localPosition = n * (elevation + height/2f); + tornadoController._topBone.localPosition = n * (elevation + height); + + tornadoController._snapBonesToSphere = true; + tornadoController._wander = true; + tornadoController._wanderRate = 0.02f; + tornadoController._wanderDegreesX = 360f; + tornadoController._wanderDegreesZ = 360f; + + /* + tornadoController._formationDuration = 1f; + tornadoController._collapseDuration = 1f; + sector.OnOccupantEnterSector += ((sectorDetector) => + { + tornadoController.StartFormation(); + }); + sector.OnOccupantExitSector += ((sectorDetector) => + { + if (!sector.ContainsOccupant(DynamicOccupant.Player | DynamicOccupant.Probe | DynamicOccupant.Ship)) + { + tornadoController.StartCollapse(); + } + }); + */ + + tornado.SetActive(true); + } + } +} diff --git a/NewHorizons/External/PropModule.cs b/NewHorizons/External/PropModule.cs index ac5ecbe5..96e5e5de 100644 --- a/NewHorizons/External/PropModule.cs +++ b/NewHorizons/External/PropModule.cs @@ -13,6 +13,7 @@ namespace NewHorizons.External public DetailInfo[] Details; public RaftInfo[] Rafts; public GeyserInfo[] Geysers; + public TornadoInfo[] Tornados; public class ScatterInfo { @@ -47,5 +48,13 @@ namespace NewHorizons.External { public MVector3 position; } + + public class TornadoInfo + { + public float elevation; + public MVector3 position; + public MVector3 scale; + public MColor tint; + } } }