Skip to content

Instantly share code, notes, and snippets.

@antonkudin
Last active May 2, 2019 10:34
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save antonkudin/ad2ccf97fd95861ca5190f28e0e27aa6 to your computer and use it in GitHub Desktop.
Save antonkudin/ad2ccf97fd95861ca5190f28e0e27aa6 to your computer and use it in GitHub Desktop.
MegaSphere logo assembler
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 = Mathf.Lerp(animLinesFork.x, animLinesFork.y, transition);
float spotT = Mathf.Lerp(animSpotsFork.x, animSpotsFork.y, 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));
}
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);
}
public static void wideLine(ref List<Vector2> points, ref List<Vector3> verts, float width, int start, int end, float mLimit=2){
Vector2 pDir = Vector2.zero;
Vector2 dir = Vector2.zero;
for(int i = start; i<end; i++) {
Vector2 point = points[i];
dir = points[i+1] - point;
var normal = new Vector2(-dir.y, dir.x).normalized;
int di = i*2;
if(i>start){
Vector2 tn = (dir.normalized + pDir.normalized).normalized;
Vector2 mt = new Vector2(-tn.y,tn.x);
float dot = Vector2.Dot(mt, normal);
float length;
if(dot<=0 || 1/dot>mLimit)
length = width*mLimit;
else
length = width / dot;
mt = mt*length;
verts[di] = new Vector3(point.x + mt.x, point.y+mt.y, verts[di].z);
verts[di+1] = new Vector3(point.x - mt.x, point.y-mt.y, verts[di+1].z);
}else{
normal = normal*width;
verts[di] = new Vector3(point.x+normal.x, point.y+normal.y, verts[di].z);
verts[di+1] = new Vector3(point.x-normal.x, point.y-normal.y, verts[di+1].z);
}
pDir = dir;
}
end = end-1;
var lDir = new Vector2(dir.y,-dir.x).normalized * width;
Vector2 lastPoint = points[end + 1];
int lastI = end * 2 + 2;
verts[lastI] = new Vector3(lastPoint.x - lDir.x, lastPoint.y - lDir.y, verts[lastI].z);
verts[lastI+1] = new Vector3(lastPoint.x + lDir.x, lastPoint.y + lDir.y, verts[lastI+1].z);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment