context: /**
   * Kopernicus Planetary System Modifier
   * ====================================
   * Created by: - Bryce C Schroeder (bryce.schroeder@gmail.com)
   * 			   - Nathaniel R. Lewis (linux.robotdude@gmail.com)
   * 
   * Maintained by: - Thomas P.
   * 				  - NathanKell
   * 
  * Additional Content by: Gravitasi, aftokino, KCreator, Padishar, Kragrathea, OvenProofMars, zengei, MrHappyFace
   * ------------------------------------------------------------- 
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 3 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
   * MA 02110-1301  USA
   * 
   * This library is intended to be used as a plugin for Kerbal Space Program
   * which is copyright 2011-2014 Squad. Your usage of Kerbal Space Program
   * itself is governed by the terms of its EULA, not the license above.
   * 
   * https://kerbalspaceprogram.com
   */
  
  using System;
  using System.Reflection;
  using System.Collections;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text.RegularExpressions;
  using System.IO;
  
  using UnityEngine;
  
  namespace Kopernicus
  {
      public class Utility
      {
          /**
           * @return LocalSpace game object
           */
          public static GameObject LocalSpace
          {
              get { return GameObject.Find(PSystemManager.Instance.localSpaceName); }
          }
  
          // Static object representing the deactivator
          private static GameObject deactivator;
  
          /**
           * Get an object which is deactivated, essentially, and children are prefabs
           * @return shared deactivated object for making prefabs
           */
          public static Transform Deactivator
          {
              get
              {
                  if (deactivator == null)
                  {
                      deactivator = new GameObject("__deactivator");
                      deactivator.SetActive(false);
                      UnityEngine.Object.DontDestroyOnLoad(deactivator);
                  }
                  return deactivator.transform;
              }
          }
  
          /**
           * Copy one objects fields to another object via reflection
           * @param source Object to copy fields from
           * @param destination Object to copy fields to
           **/
          public static void CopyObjectFields<T>(T source, T destination, bool log = true)
          {
              // Reflection based copy
              foreach (FieldInfo field in (typeof(T)).GetFields())
              {
                  // Only copy non static fields
                  if (!field.IsStatic)
                  {
                      if (log)
                      {
                          Logger.Active.Log("Copying \"" + field.Name + "\": " + (field.GetValue(destination) ?? "<NULL>") + " => " + (field.GetValue(source) ?? "<NULL>"));
  
                      }
                      field.SetValue(destination, field.GetValue(source));
                  }
              }
          }
  
          /**
           * Recursively searches for a named transform in the Transform heirarchy.  The requirement of
           * such a function is sad.  This should really be in the Unity3D API.  Transform.Find() only
           * searches in the immediate children.
           *
           * @param transform Transform in which is search for named child
           * @param name Name of child to find
           * 
           * @return Desired transform or null if it could not be found
           */
          public static Transform FindInChildren(Transform transform, string name)
          {
              // Is this null?
              if (transform == null)
              {
                  return null;
              }
  
              // Are the names equivalent
              if (transform.name == name)
              {
                  return transform;
              }
  
              // If we did not find a transform, search through the children
              foreach (Transform child in transform)
              {
                  // Recurse into the child
                  Transform t = FindInChildren(child, name);
                  if (t != null)
                  {
                      return t;
                  }
              }
  
              // Return the transform (will be null if it was not found)
              return null;
          }
  
          // Dump an object by reflection
          public static void DumpObjectFields(object o, string title = "---------")
          {
              // Dump the raw PQS of Dres (by reflection)
              Logger.Active.Log("---------" + title + "------------");
              foreach (FieldInfo field in o.GetType().GetFields())
              {
                  if (!field.IsStatic)
                  {
                      Logger.Active.Log(field.Name + " = " + field.GetValue(o));
                  }
              }
              Logger.Active.Log("--------------------------------------");
          }
  
          public static void DumpObjectProperties(object o, string title = "---------")
          {
              // Iterate through all of the properties
              Logger.Active.Log("--------- " + title + " ------------");
              foreach (PropertyInfo property in o.GetType().GetProperties())
              {
                  if (property.CanRead)
                      Logger.Active.Log(property.Name + " = " + property.GetValue(o, null));
              }
              Logger.Active.Log("--------------------------------------");
          }
  
          /**
           * Recursively searches for a named PSystemBody
           *
           * @param body Parent body to begin search in
           * @param name Name of body to find
           * 
           * @return Desired body or null if not found
           */
          public static PSystemBody FindBody(PSystemBody body, string name)
          {
              // Is this the body wer are looking for?
              if (body.celestialBody.bodyName == name)
                  return body;
  
              // Otherwise search children
              foreach (PSystemBody child in body.children)
              {
                  PSystemBody b = FindBody(child, name);
                  if (b != null)
                      return b;
              }
  
              // Return null because we didn't find shit
              return null;
          }
  
          // Copy of above, but finds homeworld
          public static PSystemBody FindHomeBody(PSystemBody body)
          {
              // Is this the body wer are looking for?
              if (body.celestialBody.isHomeWorld)
                  return body;
  
              // Otherwise search children
              foreach (PSystemBody child in body.children)
              {
                  PSystemBody b = FindHomeBody(child);
                  if (b != null)
                      return b;
              }
  
              // Return null because we didn't find shit
              return null;
          }
  
          /** 
           * Get the reference Geosphere of KSP (Jool's scaled space mesh)
           * 
           * @return The reference geosphere (1000 unit radius)
           */
          public static Mesh ReferenceGeosphere()
          {
              return Templates.refGeosphere;
          }
  
          // Print out a tree containing all the objects in the game
          public static void PerformObjectDump()
          {
              Logger.Active.Log("--------- Object Dump -----------");
              foreach (GameObject b in GameObject.FindObjectsOfType(typeof(GameObject)))
              {
                  // Essentially, we iterate through all game objects currently alive and search for 
                  // the ones without a parent.  Extrememly inefficient and terrible, but its just for
                  // exploratory purposes
                  if (b.transform.parent == null)
                  {
                      // Print out the tree of child objects
                      GameObjectWalk(b, "");
                  }
              }
              Logger.Active.Log("---------------------------------");
          }
  
          public static void PrintTransform(Transform t, string title = "")
          {
              Logger.Active.Log("------" + title + "------");
              Logger.Active.Log("Position: " + t.localPosition);
              Logger.Active.Log("Rotation: " + t.localRotation);
              Logger.Active.Log("Scale: " + t.localScale);
              Logger.Active.Log("------------------");
          }
  
          // Print out the tree of components 
          public static void GameObjectWalk(GameObject o, String prefix = "")
          {
              // If null, don't do anything
              if (o == null)
                  return;
  
              // Print this object
              Logger.Active.Log(prefix + o);
              Logger.Active.Log(prefix + " >>> Components <<< ");
              foreach (Component c in o.GetComponents(typeof(Component)))
              {
                  Logger.Active.Log(prefix + " " + c);
              }
              Logger.Active.Log(prefix + " >>> ---------- <<< ");
  
              // Game objects are related to each other via transforms in Unity3D.
              foreach (Transform b in o.transform)
              {
                  if (b != null)
                      GameObjectWalk(b.gameObject, "    " + prefix);
              }
          }
  
          // Print out the celestial bodies
          public static void PSystemBodyWalk(PSystemBody b, String prefix = "")
          {
              Logger.Active.Log(prefix + b.celestialBody.bodyName + ":" + b.flightGlobalsIndex);
              foreach (PSystemBody c in b.children)
              {
                  PSystemBodyWalk(c, prefix + "    ");
              }
          }
  
          // slightly different:
          static public void DumpUpwards(Transform t, string prefix, bool useKLog = true)
          {
              string str = prefix + "Transform " + t.name;
              if (useKLog)
                  Logger.Default.Log(str);
              else
                  Debug.Log(str);
  
              foreach (Component c in t.GetComponents<Component>())
              {
                  str = prefix + " has component " + c.name + " of type " + c.GetType().FullName;
                  if (useKLog)
                      Logger.Default.Log(str);
                  else
                      Debug.Log(str);
              }
              if (t.parent != null)
                  DumpUpwards(t.parent, prefix + "  ");
  
          }
          static public void DumpDownwards(Transform t, string prefix, bool useKLog = true)
          {
              string str = prefix + "Transform " + t.name;
              if (useKLog)
                  Logger.Default.Log(str);
              else
                  Debug.Log(str);
  
              foreach (Component c in t.GetComponents<Component>())
              {
                  str = prefix + " has component " + c.name + " of type " + c.GetType().FullName;
                  if (useKLog)
                      Logger.Default.Log(str);
                  else
                      Debug.Log(str);
              }
              if (t.childCount > 0)
                  for (int i = 0; i < t.childCount; ++i)
                      DumpDownwards(t.GetChild(i), prefix + "  ");
  
          }
  
          public static void UpdateScaledMesh(GameObject scaledVersion, PQS pqs, CelestialBody body, string path, string cacheFile, bool exportBin, bool useSpherical)
          {
              const double rJool = 6000000.0;
              const float rScaled = 1000.0f;
  
              // Compute scale between Jool and this body
              float scale = (float)(body.Radius / rJool);
              scaledVersion.transform.localScale = new Vector3(scale, scale, scale);
  
              Mesh scaledMesh;
              // Attempt to load a cached version of the scale space
              string CacheDirectory = KSPUtil.ApplicationRootPath + path;
              string CacheFile = CacheDirectory + "/" + body.name + ".bin";
  
              if (!string.IsNullOrEmpty(cacheFile))
              {
                  CacheFile = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), cacheFile);
                  CacheDirectory = Path.GetDirectoryName(CacheFile);
  
                  Logger.Active.Log(string.Format("[Kopernicus]: {0} is using custom cache file '{1}' in '{2}'", body.name, CacheFile, CacheDirectory));
              }
  
              Directory.CreateDirectory(CacheDirectory);
  
              if (File.Exists(CacheFile) && exportBin)
              {
                  Logger.Active.Log("[Kopernicus]: Body.PostApply(ConfigNode): Loading cached scaled space mesh: " + body.name);
                  scaledMesh = Utility.DeserializeMesh(CacheFile);
                  Utility.RecalculateTangents(scaledMesh);
                  scaledVersion.GetComponent<MeshFilter>().sharedMesh = scaledMesh;
              }
  
              // Otherwise we have to generate the mesh
              else
              {
                  Logger.Active.Log("[Kopernicus]: Body.PostApply(ConfigNode): Generating scaled space mesh: " + body.name);
                  scaledMesh = ComputeScaledSpaceMesh(body, useSpherical ? null : pqs);
                  Utility.RecalculateTangents(scaledMesh);
                  scaledVersion.GetComponent<MeshFilter>().sharedMesh = scaledMesh;
                  if (exportBin)
                      Utility.SerializeMesh(scaledMesh, CacheFile);
              }
  
              // Apply mesh to the body
              SphereCollider collider = scaledVersion.GetComponent<SphereCollider>();
              if (collider != null) collider.radius = rScaled;
              if (pqs != null && scaledVersion.gameObject != null && scaledVersion.gameObject.transform != null)
              {
                  scaledVersion.gameObject.transform.localScale = Vector3.one * (float)(pqs.radius / rJool);
              }
          }
  
          // Generate the scaled space mesh using PQS (all results use scale of 1)
          public static Mesh ComputeScaledSpaceMesh(CelestialBody body, PQS pqs)
          {
              // We need to get the body for Jool (to steal it's mesh)
              const double rScaledJool = 1000.0f;
              double rMetersToScaledUnits = (float)(rScaledJool / body.Radius);
  
              // Generate a duplicate of the Jool mesh
              Mesh mesh = Utility.DuplicateMesh(Utility.ReferenceGeosphere());
  
              // If this body has a PQS, we can create a more detailed object
              if (pqs != null)
              {
                  // first we enable all maps
                  OnDemand.OnDemandStorage.EnableBody(body.bodyName);
  
                  // In order to generate the scaled space we have to enable the mods.  Since this is
                  // a prefab they don't get disabled as kill game performance.  To resolve this we 
                  // clone the PQS, use it, and then delete it when done
                  GameObject pqsVersionGameObject = UnityEngine.Object.Instantiate(pqs.gameObject) as GameObject;
                  PQS pqsVersion = pqsVersionGameObject.GetComponent<PQS>();
  
                  Type[] blacklist = new Type[] { typeof(OnDemand.PQSMod_OnDemandHandler) };
  
                  // Deactivate blacklisted Mods
                  foreach (PQSMod mod in pqsVersion.GetComponentsInChildren<PQSMod>(true).Where(m => blacklist.Contains(m.GetType())))
                  {
                      mod.modEnabled = false;
                  }
  
                  // Find the PQS mods and enable the PQS-sphere
                  IEnumerable<PQSMod> mods = pqsVersion.GetComponentsInChildren<PQSMod>(true).Where(m => m.modEnabled);
  
                  pqsVersion.ActivateSphere();
                  pqsVersion.isBuildingMaps = true;
  
                  // If we were able to find PQS mods
                  if (mods.Count() > 0)
                  {
                      // Generate the PQS modifications
                      Vector3[] vertices = mesh.vertices;
                      for (int i = 0; i < mesh.vertexCount; i++)
                      {
                          // Get the UV coordinate of this vertex
                          Vector2 uv = mesh.uv[i];
  
                          // Since this is a geosphere, normalizing the vertex gives the direction from center center
                          Vector3 direction = vertices[i];
                          direction.Normalize();
  
                          // Build the vertex data object for the PQS mods
                          PQS.VertexBuildData vertex = new PQS.VertexBuildData();
                          vertex.directionFromCenter = direction;
                          vertex.vertHeight = body.Radius;
                          vertex.u = uv.x;
                          vertex.v = uv.y;
  
                          // Build from the PQS
                          foreach (PQSMod mod in mods)
                          {
                              mod.OnVertexBuild(vertex); // Why in heaven are there mods who modify height in OnVertexBuild() rather than OnVertexBuildHeight()?!?!
                              mod.OnVertexBuildHeight(vertex);
                          }
  
                          // Check for sea level
                          if (body.ocean && vertex.vertHeight < body.Radius)
                              vertex.vertHeight = body.Radius;
  
                          // Adjust the displacement
                          vertices[i] = direction * (float)(vertex.vertHeight * rMetersToScaledUnits);
                      }
                      mesh.vertices = vertices;
                      mesh.RecalculateNormals();
                      mesh.RecalculateBounds();
                  }
  
                  // Cleanup
                  pqsVersion.isBuildingMaps = false;
                  pqsVersion.DeactivateSphere();
                  UnityEngine.Object.Destroy(pqsVersionGameObject);
              }
  
              // Return the generated scaled space mesh
              return mesh;
          }
  
          public static void CopyMesh(Mesh source, Mesh dest)
          {
              //ProfileTimer.Push("CopyMesh");
              Vector3[] verts = new Vector3[source.vertexCount];
              source.vertices.CopyTo(verts, 0);
              dest.vertices = verts;
  
              int[] tris = new int[source.triangles.Length];
              source.triangles.CopyTo(tris, 0);
              dest.triangles = tris;
  
              Vector2[] uvs = new Vector2[source.uv.Length];
              source.uv.CopyTo(uvs, 0);
              dest.uv = uvs;
  
              Vector2[] uv2s = new Vector2[source.uv2.Length];
              source.uv2.CopyTo(uv2s, 0);
              dest.uv2 = uv2s;
  
              Vector3[] normals = new Vector3[source.normals.Length];
              source.normals.CopyTo(normals, 0);
              dest.normals = normals;
  
              Vector4[] tangents = new Vector4[source.tangents.Length];
              source.tangents.CopyTo(tangents, 0);
              dest.tangents = tangents;
  
              Color[] colors = new Color[source.colors.Length];
              source.colors.CopyTo(colors, 0);
              dest.colors = colors;
  
              Color32[] colors32 = new Color32[source.colors32.Length];
              source.colors32.CopyTo(colors32, 0);
              dest.colors32 = colors32;
  
              //ProfileTimer.Pop("CopyMesh");
          }
  
          public static Mesh DuplicateMesh(Mesh source)
          {
              // Create new mesh object
              Mesh dest = new Mesh();
  
              //ProfileTimer.Push("CopyMesh");
              Vector3[] verts = new Vector3[source.vertexCount];
              source.vertices.CopyTo(verts, 0);
              dest.vertices = verts;
  
              int[] tris = new int[source.triangles.Length];
              source.triangles.CopyTo(tris, 0);
              dest.triangles = tris;
  
              Vector2[] uvs = new Vector2[source.uv.Length];
              source.uv.CopyTo(uvs, 0);
              dest.uv = uvs;
  
              Vector2[] uv2s = new Vector2[source.uv2.Length];
              source.uv2.CopyTo(uv2s, 0);
              dest.uv2 = uv2s;
  
              Vector3[] normals = new Vector3[source.normals.Length];
              source.normals.CopyTo(normals, 0);
              dest.normals = normals;
  
              Vector4[] tangents = new Vector4[source.tangents.Length];
              source.tangents.CopyTo(tangents, 0);
              dest.tangents = tangents;
  
              Color[] colors = new Color[source.colors.Length];
              source.colors.CopyTo(colors, 0);
              dest.colors = colors;
  
              Color32[] colors32 = new Color32[source.colors32.Length];
              source.colors32.CopyTo(colors32, 0);
              dest.colors32 = colors32;
  
              //ProfileTimer.Pop("CopyMesh");
              return dest;
          }
  
          // Taken from Nathankell's RSS Utils.cs; uniformly scaled vertices
          public static void ScaleVerts(Mesh mesh, float scaleFactor)
          {
              //ProfileTimer.Push("ScaleVerts");
              Vector3[] vertices = new Vector3[mesh.vertexCount];
              for (int i = 0; i < mesh.vertexCount; i++)
              {
                  Vector3 v = mesh.vertices[i];
                  v *= scaleFactor;
                  vertices[i] = v;
              }
              mesh.vertices = vertices;
              //ProfileTimer.Pop("ScaleVerts");
          }
  
          public static void RecalculateTangents(Mesh theMesh)
          {
              int vertexCount = theMesh.vertexCount;
              Vector3[] vertices = theMesh.vertices;
              Vector3[] normals = theMesh.normals;
              Vector2[] texcoords = theMesh.uv;
              int[] triangles = theMesh.triangles;
              int triangleCount = triangles.Length / 3;
  
              var tangents = new Vector4[vertexCount];
              var tan1 = new Vector3[vertexCount];
              var tan2 = new Vector3[vertexCount];
  
              int tri = 0;
  
              for (int i = 0; i < (triangleCount); i++)
              {
                  int i1 = triangles[tri];
                  int i2 = triangles[tri + 1];
                  int i3 = triangles[tri + 2];
  
                  Vector3 v1 = vertices[i1];
                  Vector3 v2 = vertices[i2];
                  Vector3 v3 = vertices[i3];
  
                  Vector2 w1 = texcoords[i1];
                  Vector2 w2 = texcoords[i2];
                  Vector2 w3 = texcoords[i3];
  
                  float x1 = v2.x - v1.x;
                  float x2 = v3.x - v1.x;
                  float y1 = v2.y - v1.y;
                  float y2 = v3.y - v1.y;
                  float z1 = v2.z - v1.z;
                  float z2 = v3.z - v1.z;
  
                  float s1 = w2.x - w1.x;
                  float s2 = w3.x - w1.x;
                  float t1 = w2.y - w1.y;
                  float t2 = w3.y - w1.y;
  
                  float r = 1.0f / (s1 * t2 - s2 * t1);
                  var sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
                  var tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
  
                  tan1[i1] += sdir;
                  tan1[i2] += sdir;
  
gt:                 tan1[i3] += sdir;