diff --git a/NewHorizons/Builder/StarSystem/SkyboxBuilder.cs b/NewHorizons/Builder/StarSystem/SkyboxBuilder.cs index 44d27945..5035f77c 100644 --- a/NewHorizons/Builder/StarSystem/SkyboxBuilder.cs +++ b/NewHorizons/Builder/StarSystem/SkyboxBuilder.cs @@ -1,12 +1,17 @@ using NewHorizons.External.Configs; using NewHorizons.Utility; using OWML.Common; +using System; using UnityEngine; using Logger = NewHorizons.Utility.Logger; namespace NewHorizons.Builder.StarSystem { - public class SkyboxBuilder + public static class SkyboxBuilder { + private static readonly int _skyboxLayer = LayerMask.NameToLayer("Skybox"); + private static readonly Shader _unlitShader = Shader.Find("Unlit/Texture"); + + [Obsolete] public static void Make(StarSystemConfig.SkyboxConfig info, IModBehaviour mod) { Logger.Log("Building Skybox"); @@ -17,5 +22,131 @@ namespace NewHorizons.Builder.StarSystem camera.clearFlags = CameraClearFlags.Skybox; } } + + public static void Make(StarSystemConfig.SkyboxModule module, IModBehaviour mod) + { + Logger.Log("Building Skybox"); + BuildSkySphere(module, mod); + } + + public static GameObject BuildSkySphere(StarSystemConfig.SkyboxModule module, IModBehaviour mod) + { + var skybox = SearchUtilities.Find("Skybox"); + + var rightTex = ImageUtilities.GetTexture(mod, module.rightPath); + var leftTex = ImageUtilities.GetTexture(mod, module.leftPath); + var topTex = ImageUtilities.GetTexture(mod, module.topPath); + var bottomTex = ImageUtilities.GetTexture(mod, module.bottomPath); + var frontTex = ImageUtilities.GetTexture(mod, module.frontPath); + var backTex = ImageUtilities.GetTexture(mod, module.backPath); + + var mesh = BuildSkySphereFaceMesh(module.useCube ? 1 : 32); + + var skySphere = new GameObject("Sky Sphere"); + skySphere.transform.SetParent(skybox.transform, false); + skySphere.layer = _skyboxLayer; + skySphere.transform.localScale = Vector3.one * 5f; + + BuildSkySphereFace(skySphere, "Right", Quaternion.Euler(0f, 90f, 0f), mesh, rightTex); + BuildSkySphereFace(skySphere, "Left", Quaternion.Euler(0f, 270f, 0f), mesh, leftTex); + BuildSkySphereFace(skySphere, "Top", Quaternion.Euler(270f, 0f, 0f), mesh, topTex); + BuildSkySphereFace(skySphere, "Bottom", Quaternion.Euler(90f, 0f, 0f), mesh, bottomTex); + BuildSkySphereFace(skySphere, "Front", Quaternion.Euler(0f, 0f, 0f), mesh, frontTex); + BuildSkySphereFace(skySphere, "Back", Quaternion.Euler(0f, 180f, 0f), mesh, backTex); + + return skySphere; + } + + public static GameObject BuildSkySphereFace(GameObject skySphere, string name, Quaternion rotation, Mesh mesh, Texture2D tex) + { + if (!tex) + { + Logger.LogError($"Failed to load texture for skybox {name.ToLower()} face"); + return null; + } + + var go = new GameObject(name) + { + layer = _skyboxLayer + }; + + var mf = go.AddComponent(); + mf.sharedMesh = mesh; + + var mat = new Material(_unlitShader) + { + name = $"Sky Sphere {name}", + mainTexture = tex + }; + + var mr = go.AddComponent(); + mr.sharedMaterial = mat; + + var sr = go.AddComponent(); + Delay.RunWhen(() => SkyboxRenderer.s_active.Contains(sr), () => + { + SkyboxRenderer.s_active.Remove(sr); + SkyboxRenderer.s_active.Insert(0, sr); + }); + + go.transform.SetParent(skySphere.transform, false); + go.transform.localRotation = rotation; + go.transform.localScale = Vector3.one; + + return go; + } + + public static Mesh BuildSkySphereFaceMesh(int quadsPerAxis) + { + var mesh = new Mesh + { + name = $"Sky Sphere Face" + }; + + var vertices = new Vector3[(quadsPerAxis + 1) * (quadsPerAxis + 1)]; + var normals = new Vector3[vertices.Length]; + var uvs = new Vector2[vertices.Length]; + var tris = new int[quadsPerAxis * quadsPerAxis * 2 * 3]; + + for (var x = 0; x <= quadsPerAxis; x++) + { + for (var y = 0; y <= quadsPerAxis; y++) + { + var i = y * (quadsPerAxis + 1) + x; + var fx = (float)x / quadsPerAxis; + var fy = (float)y / quadsPerAxis; + vertices[i] = new Vector3(-0.5f + fx, -0.5f + fy, 0.5f).normalized; + normals[i] = -vertices[i].normalized; + uvs[i] = new Vector2(fx, fy); + } + } + + int t = 0; + for (var x = 0; x < quadsPerAxis; x++) + { + for (var y = 0; y < quadsPerAxis; y++) + { + var i0 = (y + 1) * (quadsPerAxis + 1) + (x + 0); + var i1 = (y + 1) * (quadsPerAxis + 1) + (x + 1); + var i2 = (y + 0) * (quadsPerAxis + 1) + (x + 1); + var i3 = (y + 0) * (quadsPerAxis + 1) + (x + 0); + + tris[t++] = i0; + tris[t++] = i1; + tris[t++] = i2; + + tris[t++] = i2; + tris[t++] = i3; + tris[t++] = i0; + } + } + + mesh.vertices = vertices; + mesh.normals = normals; + mesh.uv = uvs; + mesh.triangles = tris; + + return mesh; + } } } \ No newline at end of file diff --git a/NewHorizons/External/Configs/StarSystemConfig.cs b/NewHorizons/External/Configs/StarSystemConfig.cs index dac5e069..d190244a 100644 --- a/NewHorizons/External/Configs/StarSystemConfig.cs +++ b/NewHorizons/External/Configs/StarSystemConfig.cs @@ -43,8 +43,14 @@ namespace NewHorizons.External.Configs /// /// Customize the skybox for this system /// + [Obsolete("skybox is deprecated, please use Skybox instead")] public SkyboxConfig skybox; + /// + /// Customize the skybox for this system + /// + public SkyboxModule Skybox; + /// /// Set to `true` if you want to spawn here after dying, not Timber Hearth. You can still warp back to the main star /// system. @@ -117,6 +123,7 @@ namespace NewHorizons.External.Configs public int[] z; } + [Obsolete("SkyboxConfig is deprecated, please use SkyboxModule instead")] [JsonObject] public class SkyboxConfig { @@ -134,6 +141,52 @@ namespace NewHorizons.External.Configs /// Path to the material within the asset bundle specified by `assetBundle` to use for the skybox /// public string path; + + } + + [JsonObject] + public class SkyboxModule + { + + /// + /// Whether to destroy the star field around the player + /// + public bool destroyStarField; + + /// + /// Whether to use a cube for the skybox instead of a smooth sphere + /// + public bool useCube; + + /// + /// Relative filepath to the texture to use for the skybox's positive X direction + /// + public string rightPath; + + /// + /// Relative filepath to the texture to use for the skybox's negative X direction + /// + public string leftPath; + + /// + /// Relative filepath to the texture to use for the skybox's positive Y direction + /// + public string topPath; + + /// + /// Relative filepath to the texture to use for the skybox's negative Y direction + /// + public string bottomPath; + + /// + /// Relative filepath to the texture to use for the skybox's positive Z direction + /// + public string frontPath; + + /// + /// Relative filepath to the texture to use for the skybox's negative Z direction + /// + public string backPath; } /// @@ -162,7 +215,10 @@ namespace NewHorizons.External.Configs // If current one is null take the other factRequiredForWarp = string.IsNullOrEmpty(factRequiredForWarp) ? otherConfig.factRequiredForWarp : factRequiredForWarp; +#pragma warning disable CS0618 // Type or member is obsolete skybox = skybox == null ? otherConfig.skybox : skybox; +#pragma warning restore CS0618 // Type or member is obsolete + Skybox = Skybox == null ? otherConfig.Skybox : Skybox; travelAudio = string.IsNullOrEmpty(travelAudio) ? otherConfig.travelAudio : travelAudio; // False by default so if one is true go true @@ -187,6 +243,14 @@ namespace NewHorizons.External.Configs #pragma warning disable 612, 618 if (!string.IsNullOrEmpty(travelAudioClip)) travelAudio = travelAudioClip; if (!string.IsNullOrEmpty(travelAudioFilePath)) travelAudio = travelAudioFilePath; + if (skybox != null) + { + if (Skybox == null) + { + Skybox = new SkyboxModule(); + Skybox.destroyStarField = skybox.destroyStarField; + } + } } } } \ No newline at end of file diff --git a/NewHorizons/Handlers/SystemCreationHandler.cs b/NewHorizons/Handlers/SystemCreationHandler.cs index 545c0cde..571502e8 100644 --- a/NewHorizons/Handlers/SystemCreationHandler.cs +++ b/NewHorizons/Handlers/SystemCreationHandler.cs @@ -12,15 +12,27 @@ namespace NewHorizons.Handlers { var skybox = SearchUtilities.Find("Skybox/Starfield"); - if (system.Config.skybox?.destroyStarField ?? false) + if (system.Config.Skybox?.destroyStarField ?? false) { Object.Destroy(skybox); } + if (system.Config.Skybox?.rightPath != null || + system.Config.Skybox?.leftPath != null || + system.Config.Skybox?.topPath != null || + system.Config.Skybox?.bottomPath != null || + system.Config.Skybox?.frontPath != null || + system.Config.Skybox?.bottomPath != null) + { + SkyboxBuilder.Make(system.Config.Skybox, system.Mod); + } + +#pragma warning disable CS0618, CS0612 // Type or member is obsolete if (system.Config.skybox?.assetBundle != null && system.Config.skybox?.path != null) { SkyboxBuilder.Make(system.Config.skybox, system.Mod); } +#pragma warning restore CS0618, CS0612 // Type or member is obsolete if (system.Config.enableTimeLoop) { diff --git a/NewHorizons/Schemas/star_system_schema.json b/NewHorizons/Schemas/star_system_schema.json index 1779f8ce..3e9eadb1 100644 --- a/NewHorizons/Schemas/star_system_schema.json +++ b/NewHorizons/Schemas/star_system_schema.json @@ -28,9 +28,9 @@ "type": "boolean", "description": "Should the player not be able to view the map in this system?" }, - "skybox": { + "Skybox": { "description": "Customize the skybox for this system", - "$ref": "#/definitions/SkyboxConfig" + "$ref": "#/definitions/SkyboxModule" }, "startHere": { "type": "boolean", @@ -87,21 +87,41 @@ } }, "definitions": { - "SkyboxConfig": { + "SkyboxModule": { "type": "object", "additionalProperties": false, "properties": { - "assetBundle": { - "type": "string", - "description": "Path to the Unity asset bundle to load the skybox material from" - }, "destroyStarField": { "type": "boolean", "description": "Whether to destroy the star field around the player" }, - "path": { + "useCube": { + "type": "boolean", + "description": "Whether to use a cube for the skybox instead of a smooth sphere" + }, + "rightPath": { "type": "string", - "description": "Path to the material within the asset bundle specified by `assetBundle` to use for the skybox" + "description": "Relative filepath to the texture to use for the skybox's positive X direction" + }, + "leftPath": { + "type": "string", + "description": "Relative filepath to the texture to use for the skybox's negative X direction" + }, + "topPath": { + "type": "string", + "description": "Relative filepath to the texture to use for the skybox's positive Y direction" + }, + "bottomPath": { + "type": "string", + "description": "Relative filepath to the texture to use for the skybox's negative Y direction" + }, + "frontPath": { + "type": "string", + "description": "Relative filepath to the texture to use for the skybox's positive Z direction" + }, + "backPath": { + "type": "string", + "description": "Relative filepath to the texture to use for the skybox's negative Z direction" } } },