-
-
Save saltire/b949dd723032e3d40dd2db972f312b65 to your computer and use it in GitHub Desktop.
MegaSphere logo assembler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
public class logoAssembler : MonoBehaviour { | |
[SerializeField] bool debugMe; | |
[SerializeField] bool remakePixels; | |
[SerializeField] bool autoPlay; | |
[SerializeField][Range(0,10)] float autoDur = 10; | |
[Space] | |
[SerializeField][Range(0,1)] float transition; | |
[Space] | |
[SerializeField] Vector2 animSpotsFork = new Vector2(.11f,-3.4f); | |
[SerializeField] Vector2 animLinesFork = new Vector2(-.6f,-3.7f); | |
[Space] | |
[SerializeField] Sprite sprite; // sprite's texture needs to set to readable in import options | |
[SerializeField] Material material; | |
// main texture should be a gradient, with wrap set to clamp | |
// left and right most pixels are black, with white somewhere in middle (close to right for same look) | |
[Space] | |
public Gradient lineColors; // random colors for lines | |
public Gradient spotColors; // random colors for spots | |
public Gradient spotColorOverTime; // change (multiply) spots color over their lifetime | |
[Space] | |
[SerializeField] int totalOffsets = 20; // line presets | |
[SerializeField] int vertsPerOffset = 8; // how zaggy line needs to be | |
[SerializeField][Range(0.0625f,10f)] float minOffsetDist=10; | |
[SerializeField][Range(0.0625f,50f)] float maxOffsetDist=9.7f; | |
[SerializeField][Range(0.0625f,10f)] float cLifeOffset=3.93f; | |
[SerializeField][Range(0f,2f)] float particleOffsetScale=2f; | |
[Space] | |
[SerializeField] Vector2 lineOffsetFork=new Vector2(0,11.6f); | |
[SerializeField] float pixelSize = .0625f; | |
[Space] | |
[SerializeField] float uvDistScale=5.4f; | |
[SerializeField] Vector2 lineUVMin=new Vector2(0,.4f); | |
[SerializeField] Vector2 lineUVMax=new Vector2(1,.6f); | |
[Space] | |
[SerializeField] Vector2 spotOffsetFork=new Vector2(1,0); | |
[SerializeField] Vector2 spotUVMin=new Vector2(.985f,1); | |
[SerializeField] Vector2 spotUVMax=new Vector2(.985f,1); | |
[Space] | |
[SerializeField] float lineZDepth; | |
[SerializeField] float spotZDepth; | |
Mesh mesh; | |
MeshFilter mf; | |
MeshRenderer mr; | |
List<Vector3> verts; | |
List<Vector2> uvs; | |
List<int> tris; | |
List<Color> colors; | |
Vector2[][] offsets; | |
List<pixel> pixels; | |
float time; | |
[System.Serializable] | |
struct pixel{ | |
public float offset; | |
public float lifetime; | |
public List<Vector2> path; | |
public List<Vector3> lineVerts; | |
public List<int> lineTris; | |
public List<Vector2> lineUVs; | |
public bool hasLine; | |
public Color lineColor; | |
public Color spotColor; | |
} | |
// draw in scene | |
void OnDrawGizmos(){ | |
if(Application.isPlaying) return; | |
if(debugMe){ | |
if(autoPlay){ | |
// i make deltatime from time i get in another script (drawDebugs.time), but it will work with something like | |
// transition += 1/60f; | |
transition += Mathf.Clamp(drawDebugs.time-time,.0166f,.033f) / autoDur; | |
if(transition>1) | |
transition = transition.frac(); | |
time = drawDebugs.time; | |
} | |
updateMesh(); | |
}else cleanUp(); | |
} | |
// draw when running | |
void Update(){ | |
updateMesh(); | |
} | |
void initMe(){ | |
mf = GetComponent<MeshFilter>(); | |
if(mf==null) | |
mf=gameObject.AddComponent<MeshFilter>(); | |
mr = GetComponent<MeshRenderer>(); | |
if(mr==null) | |
mr=gameObject.AddComponent<MeshRenderer>(); | |
mr.sharedMaterial = material; | |
if(mesh==null) { | |
mesh = new Mesh(); | |
mesh.MarkDynamic(); | |
} | |
else | |
mesh.Clear(); | |
mf.sharedMesh = mesh; | |
if(verts==null) verts = new List<Vector3>(); | |
if(uvs==null) uvs = new List<Vector2>(); | |
if(tris==null) tris = new List<int>(); | |
if(colors==null) colors = new List<Color>(); | |
makeLinePresets(); | |
makePixels(); | |
} | |
void cleanUp(){ | |
if(mf!=null){ | |
DestroyImmediate(mf); | |
DestroyImmediate(GetComponent<MeshRenderer>()); | |
} | |
mesh = null; | |
verts = null; | |
tris = null; | |
uvs = null; | |
pixels = null; | |
colors = null; | |
} | |
void updateMesh(){ | |
if(mf==null || remakePixels) | |
initMe(); | |
// clear all mesh lists | |
verts.Clear(); | |
tris.Clear(); | |
uvs.Clear(); | |
colors.Clear(); | |
// where's the offset for lines and spots | |
float lineT = animLinesFork.lerp(transition); | |
float spotT = animSpotsFork.lerp(transition); | |
float halfPixel = pixelSize/2; | |
for(int l=0;l<pixels.Count;l++){ | |
var pixel = pixels[l]; | |
if(!pixel.hasLine) { | |
goto spot; | |
} | |
// adding a line to mesh | |
float pathOffset = lineT+pixel.offset; | |
float uOffset = Mathf.LerpUnclamped(lineOffsetFork.x,lineOffsetFork.y,pathOffset); | |
float uvMin = pixel.lineUVs[0].x + uOffset; | |
float uvMax = pixel.lineUVs[pixel.lineUVs.Count-1].x + uOffset; | |
// if this line is visible, add it to mesh | |
if(uvMax>0 && uvMin<1){ | |
// offset triangle indexes with current count | |
for(int i=0;i<pixel.lineTris.Count;i++) | |
tris.Add(pixel.lineTris[i]+verts.Count); | |
// pick line verts from pixel's cache | |
verts.AddRange(pixel.lineVerts); | |
// pick UVs from pixel's cache, but offset them with our timing, pick and set colors | |
var pUVs = pixel.lineUVs; | |
for(int i=0;i<pUVs.Count;i++){ | |
uvs.Add(new Vector2(pUVs[i].x+uOffset, pUVs[i].y)); | |
colors.Add(pixel.lineColor); | |
} | |
} | |
// adding a spot to mesh | |
spot:{ | |
// get our time offsets | |
pathOffset = spotT+pixel.offset; | |
uOffset = PowUp( Mathf.Clamp01( Mathf.LerpUnclamped(spotOffsetFork.x,spotOffsetFork.y,pathOffset)), 2); | |
// add spot mesh data if its visible | |
if(uOffset>0){ | |
// its just a quad | |
int vcount = verts.Count; | |
tris.Add(vcount); | |
tris.Add(vcount+1); | |
tris.Add(vcount+2); | |
tris.Add(vcount+3); | |
tris.Add(vcount); | |
tris.Add(vcount+2); | |
// position is picked from cached line | |
Vector2 pixPos = lerpOnPath(pixel.path, uOffset); | |
verts.Add(new Vector3(pixPos.x+halfPixel, pixPos.y+halfPixel,spotZDepth)); | |
verts.Add(new Vector3(pixPos.x+halfPixel, pixPos.y-halfPixel,spotZDepth)); | |
verts.Add(new Vector3(pixPos.x-halfPixel, pixPos.y-halfPixel,spotZDepth)); | |
verts.Add(new Vector3(pixPos.x-halfPixel, pixPos.y+halfPixel,spotZDepth)); | |
uvs.Add(spotUVMax); | |
uvs.Add(new Vector2(spotUVMax.x, spotUVMin.y)); | |
uvs.Add(spotUVMin); | |
uvs.Add(new Vector2(spotUVMin.x, spotUVMax.y)); | |
Color spotColor = pixel.spotColor * spotColorOverTime.Evaluate(uOffset); | |
colors.Add(spotColor); | |
colors.Add(spotColor); | |
colors.Add(spotColor); | |
colors.Add(spotColor); | |
} | |
} | |
} | |
// add all our lists to mesh if we have verticies or just disable renderer | |
if(verts.Count>0){ | |
mesh.Clear(); | |
mesh.SetVertices(verts); | |
mesh.SetUVs(0,uvs); | |
mesh.SetColors(colors); | |
mesh.SetTriangles(tris,0); | |
mesh.RecalculateBounds(); | |
mr.enabled=true; | |
}else{ | |
mr.enabled=false; | |
} | |
} | |
void makePixels(){ | |
if(pixels==null) | |
pixels = new List<pixel>(); | |
else | |
pixels.Clear(); | |
if(uvDistScale==0) uvDistScale=.0001f; | |
int width=(int)sprite.textureRect.width; | |
int height=(int)sprite.textureRect.height; | |
int ox=(int)sprite.textureRect.position.x; | |
int oy=(int)sprite.textureRect.position.y; | |
Vector2 max = sprite.size()/2; | |
Vector2 min = -max; | |
float halfPixel = pixelSize/2; | |
for (int x = 0; x < width; x++) | |
for (int y = 0; y < height; y++) { | |
// get pixel color value from sprite's texture | |
Color c = sprite.texture.GetPixel(x+ox, y+oy); | |
if(c.a>0) { // alpha>0 creates a spot/line | |
var newPixel = new pixel(); | |
newPixel.offset = c.r*cLifeOffset; | |
Vector2[] origPath = offsets[pixels.Count%totalOffsets]; | |
newPixel.path = new List<Vector2>(); | |
newPixel.hasLine = c.g>0; // green > 0 means line exist, othersize its just 1px spot | |
var finalPosition = new Vector2( | |
Mathf.Lerp(min.x, max.x, (float)x/(float)width), | |
Mathf.Lerp(min.y, max.y, (float)y/(float)height)); | |
// pixel path a spot will take. one of presets is picked and scaled | |
for(int p=0;p<origPath.Length;p++) | |
newPixel.path.Add(origPath[origPath.Length-1-p] * particleOffsetScale + finalPosition); | |
// cache line's mesh data | |
if(newPixel.hasLine){ | |
newPixel.lineColor = lineColors.Evaluate(Random.value); | |
newPixel.lineTris = new List<int>(); | |
newPixel.lineVerts = new List<Vector3>(); | |
newPixel.lineUVs = new List<Vector2>(); | |
float dist = 0; | |
int totalTris = (newPixel.path.Count-1)*6; | |
int t=0; | |
for(int p=0;p<newPixel.path.Count;p++){ | |
if(t<totalTris){ | |
int p2 = p*2; | |
newPixel.lineTris.Add(p2); | |
newPixel.lineTris.Add(p2+2); | |
newPixel.lineTris.Add(p2+1); | |
newPixel.lineTris.Add(p2+1); | |
newPixel.lineTris.Add(p2+2); | |
newPixel.lineTris.Add(p2+3); | |
t+=6; | |
} | |
if(p>0){ | |
dist += (newPixel.path[p]-newPixel.path[p-1]).magnitude/uvDistScale; | |
newPixel.lineUVs.Add(new Vector2(dist,lineUVMin.y)); | |
newPixel.lineUVs.Add(new Vector2(dist,lineUVMax.y)); | |
}else{ | |
newPixel.lineUVs.Add(new Vector2(lineUVMin.x,lineUVMin.y)); | |
newPixel.lineUVs.Add(new Vector2(lineUVMin.x,lineUVMax.y)); | |
} | |
newPixel.lineVerts.Add(new Vector3(0,0,lineZDepth)); | |
newPixel.lineVerts.Add(new Vector3(0,0,lineZDepth)); | |
} | |
Mathfx.wideLine(ref newPixel.path, ref newPixel.lineVerts, halfPixel, 0, newPixel.path.Count-1); | |
} | |
newPixel.spotColor = spotColors.Evaluate(Random.value); | |
pixels.Add(newPixel); | |
} | |
} | |
} | |
int offsetscacheID; // dumb optimization for tweaking | |
// make randomized broken line presets | |
void makeLinePresets() | |
{ | |
int id = totalOffsets*176 + vertsPerOffset*661 + (int)(maxOffsetDist*32) - (int)(minOffsetDist*32); | |
if(offsets!=null && offsetscacheID==id) return; | |
offsetscacheID = id; | |
offsets = new Vector2[totalOffsets][]; | |
for(int o = 0; o<totalOffsets; o++){ | |
offsets[o] = new Vector2[vertsPerOffset]; | |
Vector2 dir = new Vector2(Random.value>.5f?1:-1,Random.value>.5f?1:-1); | |
float dist = 0; | |
for(int v=0; v<vertsPerOffset; v++){ | |
if(v==0) | |
offsets[o][v] = Vector2.zero; | |
else{ | |
Vector2 move = random45Vector(dir); | |
offsets[o][v] = offsets[o][v-1] + move; | |
dist+=move.magnitude; | |
} | |
} | |
// normalize and bring to one length | |
for(int v=0; v<vertsPerOffset; v++) | |
offsets[o][v] = (offsets[o][v]/dist) * maxOffsetDist; | |
} | |
} | |
Vector2 random45Vector(Vector2 dir){ | |
if(Random.value>.5f && dir.x!=0) | |
return new Vector2( dir.x, Random.value>.5f?dir.y:0) * Random.Range(minOffsetDist,maxOffsetDist); | |
else if(dir.y!=0) | |
return new Vector2( Random.value>.5f?dir.x:0, dir.y) * Random.Range(minOffsetDist,maxOffsetDist); | |
else | |
return dir * Random.Range(minOffsetDist,maxOffsetDist); | |
} | |
// get poistion on a path with a float (0-1) | |
public static Vector2 lerpOnPath(List<Vector2> path, float value){ | |
if(path==null || path.Count==0) return Vector2.zero; | |
if(path.Count==1) return path[0]; | |
if(float.IsNaN(value)){ | |
Debug.Log("LerpOnPath: NAN value"); | |
return path[0]; | |
} | |
if(value>=1) return path[path.Count-1]; | |
if(value<=0) return path[0]; | |
float p = path.Count-1; | |
float v = value*p; | |
return Vector2.Lerp (path[Mathf.FloorToInt(v)], path[Mathf.CeilToInt(v)], Mathf.Repeat(value, 1/p)*p); | |
} | |
public static float PowUp(float value, float power){ | |
return 1-Mathf.Pow(1-value,power); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment