using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEditor; using System.Reflection; public static class NomaiTextArcBuilder { public static int i = 0; public static SpiralProfile spiralProfile; public static bool removeBakedInRotationAndPosition = true; public static void PlaceAdult() { BuildSpiralGameObject(adultSpiralProfile, "Text Arc Prefab " + (i++)); } public static void PlaceChild() { BuildSpiralGameObject(childSpiralProfile, "Text Arc Prefab " + (i++)); } public static GameObject BuildSpiralGameObject(SpiralProfile profile, string goName="New Nomai Spiral") { var g = new GameObject(goName); g.transform.localPosition = Vector3.zero; g.transform.localEulerAngles = Vector3.zero; var m = new SpiralMesh(profile); m.Randomize(); m.updateMesh(); g.AddComponent().sharedMesh = m.mesh; g.AddComponent().sharedMaterial = new Material(Shader.Find("Sprites/Default")); g.GetComponent().sharedMaterial.color = Color.magenta; var owNomaiTextLine = g.AddComponent(); // // rotate mesh to face up // var norm = m.skeleton[1] - m.skeleton[0]; float r = Mathf.Atan2(-norm.y, norm.x) * Mathf.Rad2Deg; if (m.mirror) r += 180; var ang = m.mirror ? 90-r : -90-r; // using m.sharedMesh causes old meshes to disappear for some reason, idk why var mesh = g.GetComponent().mesh; if (removeBakedInRotationAndPosition) { var meshCopy = mesh; var newVerts = meshCopy.vertices.Select(v => Quaternion.Euler(-90, 0, 0) * Quaternion.Euler(0, ang, 0) * v).ToArray(); meshCopy.vertices = newVerts; meshCopy.RecalculateBounds(); } AssetDatabase.CreateAsset(mesh, "Assets/Spirals/"+(profile.profileName)+"spiral" + (NomaiTextArcBuilder.i) + ".asset"); g.GetComponent().sharedMesh = AssetDatabase.LoadAssetAtPath("Assets/Spirals/"+(profile.profileName)+"spiral" + (NomaiTextArcBuilder.i) + ".asset", typeof(Mesh)) as Mesh; NomaiTextArcBuilder.i++; // // set up NomaiTextArc stuff // var _points = m.skeleton .Select((compiled) => Quaternion.Euler(-90, 0, 0) * Quaternion.Euler(0, ang, 0) * (new Vector3(compiled.x, 0, compiled.y)) // decompile them, rotate them by ang, and then rotate them to be vertical, like the base game spirals are ) .ToList(); var _lengths = _points.Take(_points.Count()-1).Select((point, i) => Vector3.Distance(point, _points[i+1])).ToArray(); var _totalLength = _lengths.Aggregate(0f, (acc, length) => acc + length); var _state = NomaiTextLine.VisualState.UNREAD; var _textLineLocation = NomaiText.Location.UNSPECIFIED; var _center = _points.Aggregate(Vector3.zero, (acc, point) => acc + point) / (float)_points.Count(); var _radius = _points.Aggregate(0f, (acc, point) => Mathf.Max(Vector3.Distance(_center, point), acc)); var _active = true; (typeof (NomaiTextLine)).InvokeMember("_points", BindingFlags.SetField | BindingFlags.Instance | BindingFlags.NonPublic, null, owNomaiTextLine, new object[] { _points.ToArray() }); (typeof (NomaiTextLine)).InvokeMember("_lengths", BindingFlags.SetField | BindingFlags.Instance | BindingFlags.NonPublic, null, owNomaiTextLine, new object[] { _lengths }); (typeof (NomaiTextLine)).InvokeMember("_totalLength", BindingFlags.SetField | BindingFlags.Instance | BindingFlags.NonPublic, null, owNomaiTextLine, new object[] { _totalLength }); (typeof (NomaiTextLine)).InvokeMember("_state", BindingFlags.SetField | BindingFlags.Instance | BindingFlags.NonPublic, null, owNomaiTextLine, new object[] { _state }); (typeof (NomaiTextLine)).InvokeMember("_textLineLocation", BindingFlags.SetField | BindingFlags.Instance | BindingFlags.NonPublic, null, owNomaiTextLine, new object[] { _textLineLocation }); (typeof (NomaiTextLine)).InvokeMember("_center", BindingFlags.SetField | BindingFlags.Instance | BindingFlags.NonPublic, null, owNomaiTextLine, new object[] { _center }); (typeof (NomaiTextLine)).InvokeMember("_radius", BindingFlags.SetField | BindingFlags.Instance | BindingFlags.NonPublic, null, owNomaiTextLine, new object[] { _radius }); (typeof (NomaiTextLine)).InvokeMember("_active", BindingFlags.SetField | BindingFlags.Instance | BindingFlags.NonPublic, null, owNomaiTextLine, new object[] { _active }); return g; } // // // Handle the connection between game objects and spiral meshes // // public struct SpiralProfile { // all of the Vector2 params here refer to a range of valid values public string profileName; public bool canMirror; public Vector2 a; public Vector2 b; public Vector2 endS; public Vector2 skeletonScale; public int numSkeletonPoints; public float uvScale; public float innerWidth; // width at the tip public float outerWidth; // width at the base public Material material; } public static SpiralProfile adultSpiralProfile = new SpiralProfile() { profileName="Adult", canMirror = false, // we don't want to mirror the actual mesh itself anymore, we'll just mirror the game object using localScale.x a = new Vector2(0.5f, 0.5f), b = new Vector2(0.3f, 0.6f), endS = new Vector2(0, 50f), skeletonScale = new Vector2(0.01f, 0.01f), numSkeletonPoints = 51, innerWidth = 0.001f, outerWidth = 0.05f, uvScale = 4.9f, }; public static SpiralProfile childSpiralProfile = new SpiralProfile() { profileName="Child", canMirror = false, // we don't want to mirror the actual mesh itself anymore, we'll just mirror the game object using localScale.x a = new Vector2(0.9f, 0.9f), b = new Vector2(0.305f, 0.4f), endS = new Vector2(16f, 60f), skeletonScale = new Vector2(0.002f, 0.005f), numSkeletonPoints = 51, innerWidth = 0.001f/10f, outerWidth = 2f*0.05f, uvScale = 4.9f/3.5f, }; // // // Construct spiral meshes from the mathematical spirals generated below // // public class SpiralMesh: MathematicalSpiral { public List skeleton; public List skeletonOutsidePoints; public int numSkeletonPoints = 51; // seems to be Mobius' default public float innerWidth = 0.001f; // width at the tip public float outerWidth = 0.05f; //0.107f; // width at the base public float uvScale = 4.9f; //2.9f; private float baseUVScale = 1f / 300f; public float uvOffset = 0; public Mesh mesh; public SpiralMesh(SpiralProfile profile): base(profile) { this.numSkeletonPoints = profile.numSkeletonPoints; this.innerWidth = profile.innerWidth; this.outerWidth = profile.outerWidth; this.uvScale = profile.uvScale; this.uvOffset = UnityEngine.Random.value; } public override void Randomize() { base.Randomize(); uvOffset = UnityEngine.Random.value; // this way even two spirals that are exactly the same shape will look different (this changes the starting point of the handwriting texture) } internal void updateMesh() { skeleton = this.getSkeleton(numSkeletonPoints); skeletonOutsidePoints = this.getSkeletonOutsidePoints(numSkeletonPoints); List vertsSide1 = skeleton.Select((skeletonPoint, index) => { Vector3 normal = new Vector3(cos(skeletonPoint.z), 0, sin(skeletonPoint.z)); float width = lerp(((float) index) / ((float) skeleton.Count()), outerWidth, innerWidth); return new Vector3(skeletonPoint.x, 0, skeletonPoint.y) + width * normal; }).ToList(); List vertsSide2 = skeleton.Select((skeletonPoint, index) => { Vector3 normal = new Vector3(cos(skeletonPoint.z), 0, sin(skeletonPoint.z)); float width = lerp(((float) index) / ((float) skeleton.Count()), outerWidth, innerWidth); return new Vector3(skeletonPoint.x, 0, skeletonPoint.y) - width * normal; }).ToList(); Vector3[] newVerts = vertsSide1.Zip(vertsSide2, (f, s) => new [] { f, s }).SelectMany(f =>f).ToArray(); // interleave vertsSide1 and vertsSide2 List triangles = new List(); for (int i = 0; i children; public float x; public float y; public float ang; public float startS = 42.87957f; // go all the way down to 0, all the way up to 50 public float endS = 342.8796f; SpiralProfile profile; public MathematicalSpiral(SpiralProfile profile) { this.profile = profile; this.Randomize(); } public MathematicalSpiral(float startSOnParent = 0, bool mirror = false, float len = 300, float a = 0.5f, float b = 0.43f, float scale = 0.01f) { this.mirror = mirror; this.a = a; this.b = b; this.startSOnParent = startSOnParent; this.scale = scale; this.children = new List(); this.x = 0; this.y = 0; this.ang = 0; } public virtual void Randomize() { this.a = UnityEngine.Random.Range(profile.a.x, profile.a.y); //0.5f; this.b = UnityEngine.Random.Range(profile.b.x, profile.b.y); this.startS = UnityEngine.Random.Range(profile.endS.x, profile.endS.y); this.scale = UnityEngine.Random.Range(profile.skeletonScale.x, profile.skeletonScale.y); if (profile.canMirror) this.mirror = UnityEngine.Random.value<0.5f; } internal virtual void updateChild(MathematicalSpiral child) { Vector3 pointAndNormal = getDrawnSpiralPointAndNormal(child.startSOnParent); var cx = pointAndNormal.x; var cy = pointAndNormal.y; var cang = pointAndNormal.z; child.x = cx; child.y = cy; child.ang = cang + (child.mirror ? Mathf.PI : 0); } public virtual void addChild(MathematicalSpiral child) { updateChild(child); this.children.Add(child); } public virtual void updateChildren() { this.children.ForEach(child => { updateChild(child); child.updateChildren(); }); } // note: each Vector3 in this list is of form public List getSkeleton(int numPoints) { var skeleton = WalkAlongSpiral(numPoints) .Select(input => { float inputS = input.y; var skeletonPoint = getDrawnSpiralPointAndNormal(inputS); return skeletonPoint; }) .Reverse() .ToList(); return skeleton; } public List getSkeletonOutsidePoints(int numPoints) { var outsidePoints = WalkAlongSpiral(numPoints) .Select(input => { float inputT = input.x; float inputS = input.y; var skeletonPoint = getDrawnSpiralPointAndNormal(inputS); var deriv = spiralDerivative(inputT); var outsidePoint = new Vector2(skeletonPoint.x, skeletonPoint.y) - (new Vector2(-deriv.y, deriv.x)).normalized * 0.1f; return outsidePoint; }) .Reverse() .ToList(); return outsidePoints; } // generate a list of evenly distributed over the whole spiral. `numPoints` number of pairs are generated public IEnumerable WalkAlongSpiral(int numPoints) { var endT = tFromArcLen(endS); var startT = tFromArcLen(startS); var rangeT = endT - startT; for (int i = 0; i