using NewHorizons.Components; using NewHorizons.Components.SizeControllers; using NewHorizons.External.Modules; using NewHorizons.External.Modules.VariableSize; using NewHorizons.Utility; using OWML.Common; using System; using System.Collections.Generic; using UnityEngine; using Logger = NewHorizons.Utility.Logger; namespace NewHorizons.Builder.Body { public static class RingBuilder { public static Shader RingShader; public static Shader RingShader1Pixel; public static Shader UnlitRingShader; public static Shader UnlitRingShader1Pixel; private static readonly int InnerRadius = Shader.PropertyToID("_InnerRadius"); public static GameObject Make(GameObject planetGO, Sector sector, RingModule ring, IModBehaviour mod) { var ringGO = MakeRingGraphics(planetGO, sector, ring, mod); // Funny collider thing var ringVolume = new GameObject("RingVolume"); ringVolume.SetActive(false); ringVolume.transform.parent = ringGO.transform; ringVolume.transform.localPosition = Vector3.zero; ringVolume.transform.localScale = Vector3.one; ringVolume.transform.localRotation = Quaternion.identity; ringVolume.layer = LayerMask.NameToLayer("BasicEffectVolume"); var ringShape = ringVolume.AddComponent(); ringShape.innerRadius = ring.innerRadius; ringShape.outerRadius = ring.outerRadius; ringShape.height = 20f; ringShape.center = Vector3.zero; ringShape.SetCollisionMode(Shape.CollisionMode.Volume); ringShape.SetLayer(Shape.Layer.Default); ringShape.layerMask = -1; ringShape.pointChecksOnly = true; var trigger = ringVolume.AddComponent(); trigger._shape = ringShape; var sfv = ringVolume.AddComponent(); var fluidType = FluidVolume.Type.NONE; if (ring.fluidType != null) try { fluidType = (FluidVolume.Type)Enum.Parse(typeof(FluidVolume.Type), Enum.GetName(typeof(CloudFluidType), ring.fluidType).ToUpper()); } catch (Exception ex) { Logger.LogError( $"Couldn't parse fluid volume type [{ring.fluidType}]: {ex.Message}, {ex.StackTrace}"); } sfv._fluidType = fluidType; sfv._density = 5f; ringVolume.SetActive(true); return ringGO; } public static GameObject MakeRingGraphics(GameObject rootObject, Sector sector, RingModule ring, IModBehaviour mod) { // Properly lit shader doesnt work yet ring.unlit = true; Texture2D ringTexture; try { ringTexture = ImageUtilities.GetTexture(mod, ring.texture); } catch (Exception e) { Logger.LogError($"Couldn't load Ring texture, {e.Message}, {e.StackTrace}"); return null; } var ringGO = new GameObject("Ring"); ringGO.transform.parent = sector?.transform ?? rootObject.transform; ringGO.transform.position = rootObject.transform.position; ringGO.transform.rotation = rootObject.transform.rotation; ringGO.transform.Rotate(ringGO.transform.TransformDirection(Vector3.up), ring.longitudeOfAscendingNode); ringGO.transform.Rotate(ringGO.transform.TransformDirection(Vector3.left), ring.inclination); var ringMF = ringGO.AddComponent(); var ringMesh = ringMF.mesh; var ringMR = ringGO.AddComponent(); var texture = ringTexture; if (RingShader == null) RingShader = Main.NHAssetBundle.LoadAsset("Assets/Shaders/Ring.shader"); if (UnlitRingShader == null) UnlitRingShader = Main.NHAssetBundle.LoadAsset("Assets/Shaders/UnlitTransparent.shader"); if (RingShader1Pixel == null) RingShader1Pixel = Main.NHAssetBundle.LoadAsset("Assets/Shaders/Ring1Pixel.shader"); if (UnlitRingShader1Pixel == null) UnlitRingShader1Pixel = Main.NHAssetBundle.LoadAsset("Assets/Shaders/UnlitRing1Pixel.shader"); var mat = new Material(ring.unlit ? UnlitRingShader : RingShader); if (texture.width == 1) { mat = new Material(ring.unlit ? UnlitRingShader1Pixel : RingShader1Pixel); mat.SetFloat(InnerRadius, 0); } ringMR.receiveShadows = !ring.unlit; mat.mainTexture = texture; mat.renderQueue = 3000; ringMR.material = mat; // Make mesh var segments = (int)Mathf.Clamp(ring.outerRadius, 20, 2000); BuildRingMesh(ringMesh, segments, ring.innerRadius, ring.outerRadius); if (ring.rotationSpeed != 0) { var rot = ringGO.AddComponent(); rot._degreesPerSecond = ring.rotationSpeed; rot._localAxis = Vector3.down; } if (ring.Curve != null) { var levelController = ringGO.AddComponent(); var curve = new AnimationCurve(); foreach (var pair in ring.Curve) curve.AddKey(new Keyframe(pair.Time, pair.Value)); levelController.scaleCurve = curve; } return ringGO; } public static void BuildQuadMesh(Mesh mesh, float width) { Vector3[] vertices; int[] tri; Vector3[] normals; Vector2[] uv; vertices = new Vector3[4]; tri = new int[6]; normals = new Vector3[4]; uv = new Vector2[4]; vertices[0] = new Vector3(-width / 2, 0, -width / 2); vertices[1] = new Vector3(width / 2, 0, -width / 2); vertices[2] = new Vector3(-width / 2, 0, width / 2); vertices[3] = new Vector3(width / 2, 0, width / 2); tri[0] = 0; tri[1] = 2; tri[2] = 1; tri[3] = 2; tri[4] = 3; tri[5] = 1; normals[0] = Vector3.up; normals[1] = Vector3.up; normals[2] = Vector3.up; normals[3] = Vector3.up; uv[0] = new Vector2(0, 0); uv[1] = new Vector2(1, 0); uv[2] = new Vector2(0, 1); uv[3] = new Vector2(1, 1); mesh.vertices = vertices; mesh.triangles = tri; mesh.normals = normals; mesh.uv = uv; } // Thank you https://github.com/boardtobits/planet-ring-mesh/blob/master/PlanetRing.cs public static void BuildRingMesh(Mesh ringMesh, int segments, float innerRadius, float outerRadius) { var vertices = new Vector3[(segments + 1) * 2 * 2]; var triangles = new int[segments * 6 * 2]; var uv = new Vector2[(segments + 1) * 2 * 2]; var halfway = (segments + 1) * 2; for (var i = 0; i < segments + 1; i++) { var progress = i / (float)segments; var angle = progress * 2 * Mathf.PI; var x = Mathf.Sin(angle); var z = Mathf.Cos(angle); vertices[i * 2] = vertices[i * 2 + halfway] = new Vector3(x, 0f, z) * outerRadius; vertices[i * 2 + 1] = vertices[i * 2 + 1 + halfway] = new Vector3(x, 0f, z) * innerRadius; //uv[i * 2] = uv[i * 2 + halfway] = new Vector2(progress, 0f); //uv[i * 2 + 1] = uv[i * 2 + 1 + halfway] = new Vector2(progress, 1f); uv[i * 2] = uv[i * 2 + halfway] = new Vector2(x, z) / 2f + Vector2.one * 0.5f; uv[i * 2 + 1] = uv[i * 2 + 1 + halfway] = new Vector2(0.5f, 0.5f); if (i != segments) { triangles[i * 12] = i * 2; triangles[i * 12 + 1] = triangles[i * 12 + 4] = (i + 1) * 2; triangles[i * 12 + 2] = triangles[i * 12 + 3] = i * 2 + 1; triangles[i * 12 + 5] = (i + 1) * 2 + 1; triangles[i * 12 + 6] = i * 2 + halfway; triangles[i * 12 + 7] = triangles[i * 12 + 10] = i * 2 + 1 + halfway; triangles[i * 12 + 8] = triangles[i * 12 + 9] = (i + 1) * 2 + halfway; triangles[i * 12 + 11] = (i + 1) * 2 + 1 + halfway; } } ringMesh.vertices = vertices; ringMesh.triangles = triangles; ringMesh.uv = uv; ringMesh.RecalculateNormals(); } public static void BuildCircleMesh(Mesh mesh, int segments, float outerRadius) { var angleStep = 360.0f / segments; var vertexList = new List(); var triangleList = new List(); var uv = new List(); var quaternion = Quaternion.Euler(0.0f, angleStep, 0f); // Make first triangle. vertexList.Add(new Vector3(0.0f, 0.0f, 0.0f)); // 1. Circle center. vertexList.Add(new Vector3(0.0f, 0f, outerRadius)); // 2. First vertex on circle outline (radius = 0.5f) vertexList.Add(quaternion * vertexList[1]); // 3. First vertex on circle outline rotated by angle) // Add triangle indices. uv.Add(new Vector2(0.5f, 0.5f)); uv.Add(new Vector2(0.5f, 1f)); uv.Add(quaternion * uv[1]); triangleList.Add(0); triangleList.Add(1); triangleList.Add(2); for (var i = 0; i < segments - 1; i++) { triangleList.Add(0); // Index of circle center. triangleList.Add(vertexList.Count - 1); triangleList.Add(vertexList.Count); vertexList.Add(quaternion * vertexList[vertexList.Count - 1]); uv.Add(quaternion * (uv[uv.Count - 1] - Vector2.one * 0.5f) + Vector3.one * 0.5f); } mesh.vertices = vertexList.ToArray(); mesh.triangles = triangleList.ToArray(); mesh.uv = uv.ToArray(); mesh.RecalculateNormals(); } } }