PHOTOSHOP PLUGINS
PHOTOSHOP: Socket Connection, Cookie Management System, HTTP I/O


/*
// BEGIN__HARVEST_EXCEPTION_ZSTRING

Fotofuze...
filter
6F17BFA7-EFC9-11EA-B960-7B96ED7EA713
 in (PSHOP_ImageMode, RGBMode, CMYKMode, HSLMode, HSBMode, LabMode, RGB48Mode)  && (PSHOP_IsTargetVisible  &&  ! PSHOP_IsTargetSection)
filter

// END__HARVEST_EXCEPTION_ZSTRING
*/


#target photoshop

app.bringToFront();
 
/////////////////////////
// SETUP
/////////////////////////

var inputURL = "";
var requesthtml = "";
var redirectURL = "";
var fotofuzeDomain = "fotofuze.com";
var username = "";
var password = "";
var cookieFile= new File("~/Desktop/FotofuzeCookie.txt"); 
var cookieContents = {};
var now = new Date();
var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
var dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
var timeOutRequests = 0;
    

if(app.documents.length == 0) { 
    alert("No Document Actively Open");
    }


/////////////////////////
// FUNCTIONS
/////////////////////////



////////////////////////////////////////////////
// GENERATE A RANDOM NAME FOR TEMPORARY FILES 
////////////////////////////////////////////////
function generateRandomName(){
    var docName = app.activeDocument.name.replace(/\.[^\.]+$/, '');
    var randomNumber = Math.floor((Math.random() * 100) + 1);
    var randomlyGeneratedName = docName + randomNumber;
    return randomlyGeneratedName;
};



///////////////////////////////////////////////
// CONSTRUCT CURRENT DATE INTO HEADER FORMAT 
///////////////////////////////////////////////
function constructCurrentDate(monthInteger_and_fullYearFlag){
    if(monthInteger_and_fullYearFlag == undefined || !monthInteger_and_fullYearFlag || monthInteger_and_fullYearFlag == false){
        var currentDate = dayNames[now.getDay()] + ", " + now.getDate() + "-" + monthNames[now.getMonth()] + "-" + String(now.getFullYear()).substr(2,2) + " " +                         now.getHours()+":"+now.getMinutes()+":"+now.getSeconds() + " GMT";    
    }
    else{
        var currentDate = dayNames[now.getDay()] + ", " + now.getDate() + "-" + now.getMonth() + "-" + now.getFullYear() + " " +                                                      now.getHours()+":"+now.getMinutes()+":"+now.getSeconds() + " GMT";    
    }
    
    return currentDate;
};



///////////////////////////////////////////////
// READ COOKIE FILE INTO COOKIE CONTENTS 
///////////////////////////////////////////////
function readCookieFile(){
    
    var cookieCache = "";
    cookieFile.open('r');
    cookieCache = cookieFile.read();
    cookieFile.close();

    parseCookie(cookieCache, cookieContents);        
}



///////////////////////////
// PARSE RECIEVED COOKIE 
///////////////////////////
function parseCookie(binary, cookieTarget){
    
    var headerEnd = binary.indexOf("\n\n");
    headerEnd = headerEnd == -1? binary.length: headerEnd; 
    
    var header = binary.substr(0,headerEnd);
    
    //if(header.indexOf(fotofuzeDomain) > -1){ //make sure header comes from Fotofuze
        
        var headerElements = header.split("\n");
        
        for (var i = 0; i < headerElements.length; i++){
            var token = headerElements[i];

            if (token.indexOf("Set-Cookie: ") == 0){
                var cookieToAdd = parseHeaderObject(token, "Set-Cookie: ");
                var cookieValuePosition = cookieToAdd.indexOf("=");
                var cookieKey = cookieToAdd.substr(0, cookieValuePosition);
                var cookieValue = cookieToAdd.substr(cookieValuePosition);
                cookieTarget[cookieKey] = cookieValue;
            }  
        }
    //}
};



////////////////////////////////////////////////
// ITERATE THROUGH COOKIES TO CHECK EXPIRATION
////////////////////////////////////////////////
function iterateThroughCookiesForExpirations(){
    
    var expiredFlag = 0;
    
    for (var cookie in cookieContents) {
        if( cookieContents.hasOwnProperty(cookie) ){
            expiredFlag += checkCookieExpiration(cookie);
        } 
    }
    
    if (expiredFlag > 0){
        writeCookieFile();
    }
}



///////////////////////////////////////////////
// COMPARE COOKIE EXPIRATION TO CURRENT DATE 
///////////////////////////////////////////////
function checkCookieExpiration(Cookie){

    var cookieExpiration = parseHeaderObject(cookieContents[Cookie], "expires=");
    cookieExpiration = cookieExpiration.substr(5,11);
    cookieExpiration = cookieExpiration.replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, ' ');
    
    var cookieDate = new Date(cookieExpiration).getTime();
    var currentDate = new Date().getTime();

    if(currentDate > cookieDate){
        delete cookieContents[Cookie];
        return 1;
    } else {return 0;}

};



///////////////////////////////
// WRITE COOKIE INFO TO FILE 
///////////////////////////////
function writeCookieFile(){
    
    var finalCookies = "";

    for (var cookie in cookieContents) {
        if( cookieContents.hasOwnProperty(cookie) ){       
            finalCookies += "Set-Cookie: " + cookie + cookieContents[cookie] + "\n";  
        } 
    }
    
    cookieFile.open('w');
    cookieFile.encoding = "UTF-8";
    cookieFile.lineFeed = "Unix";
    cookieFile.writeln(finalCookies);
    cookieFile.close();
};



/////////////////////////////////////
// CONSTRUCT A COOKIE FOR POSTS 
/////////////////////////////////////
function constructCookie(){
    
    var cookieString = "";
    
    for (var cookie in cookieContents) {
        if( cookieContents.hasOwnProperty(cookie) ){
            cookieString += "Set-Cookie: " + cookie + cookieContents[cookie] + "\n";
        }
    }
    
    return cookieString;
};



/////////////////////////////////
// PARSE SPECIFIED HEADER DATA 
/////////////////////////////////
function parseHeaderObject(headerObject, Tag, dataEndTag){
    
    var Position = headerObject.indexOf(Tag) + Tag.length; 
    var Length;
    
    if (!dataEndTag || dataEndTag == undefined){
        Length = headerObject.length - Position;
    }else{Length = headerObject.indexOf(dataEndTag, Position) - Position;}

    var Data = headerObject.substr(Position, Length); 

    if(Data.charCodeAt(Data.length-1) == 13)
    {
        Data = Data.substr(0,Data.length - 1);   
    }
    
    return Data;
}



/////////////////////////////////////
// CONSTRUCT A HEADER FOR REQUESTS 
/////////////////////////////////////
function constructHeader(method, targetPath, attachedCookie, postLength){
    
    var OriginAndContent=   "\nUser-Agent: Photoshop" + 
                            "\nContent-Type: application/x-www-form-urlencoded" + 
                            "\nContent-Length: " + postLength +
                            "\nAccept: text/plain, text/html */*";
                            
    var theDate = "\nDate: " + constructCurrentDate();
    
    return  method + " " + 
            targetPath + " HTTP/1.0" +
            "\nHost: " + fotofuzeDomain +
            OriginAndContent + 
            attachedCookie + 
            theDate +
            "\n\n";
};



////////////
// LOG-IN 
////////////
function logIn(){
    username = prompt("Please Enter your Fotofuze Username: ","Zephir62@Gmail.com");  
    var usernameEncoded = encodeURIComponent(username);
    password = prompt("Please Enter your Fotofuze Password for " + username + ": ","ZEPHZEPH");
    var passwordEncoded = encodeURIComponent(password);
    var loginURL = "/auth/login?" + "user=" + usernameEncoded + "&pass=" + passwordEncoded;
    
    Get(fotofuzeDomain, loginURL);
};



////////////////////////////
// POST REQUEST TO SERVER 
////////////////////////////
function Post(domain, targetPath, attachedCookie, postBody){
    
    var port = ":80";
    var socket = new Socket;
    //socket.timeout = 3;
    
    if(postBody == undefined || !postBody){
        postBody = "";
    }
    
    if(attachedCookie == undefined || !attachedCookie){
        attachedCookie = "";
    } 
    
    var Header = constructHeader("POST", targetPath, attachedCookie, postBody.length);
    
    var post = Header + postBody;
    
    alert("Post: " + post);
    
    if (socket.open(domain + port, "binary")){
        socket.write(post); // get the file
        var binary = socket.read(99999999);

        socket.close(); 
        
        if(domain == fotofuzeDomain){
            parseCookie(binary, cookieContents);
            writeCookieFile();
        }
    }
};



///////////////////////////
// GET REQUEST TO SERVER 
///////////////////////////
function Get(domain, targetPath, attachedCookie){
    
    var port = ":80";
    var socket = new Socket;
    //socket.timeout = 3;
    
    var postBody = "";
    
    if(attachedCookie == undefined || !attachedCookie){
        attachedCookie = "";
    }
    
    var Header = constructHeader("GET", targetPath, attachedCookie, postBody.length);
    
    var get = Header;
    
    alert("Get: " + get);
    
    if (socket.open(domain + port, "binary")){
        socket.write(get); // get the file
        var binary = socket.read(99999999);

        socket.close(); 
        
        if(domain == fotofuzeDomain){
            parseCookie(binary, cookieContents);
            writeCookieFile();
        }
        
        //return binary;
    }
};



/////////////////////////////////////////
// SAVE CURRENT DOCUMENT TO A PNG FILE 
/////////////////////////////////////////
function saveDocument(){
    var outputFolder = Folder.selectDialog("Select a folder for the output files");
    var randomName = generateRandomName();
    var outputFile = new File(outputFolder + "/" + randomName + ".png"); //"~/Desktop/"
    var currentLayer = activeDocument.activeLayer;
    
    var pngSaveOptions = new PNGSaveOptions();
    pngSaveOptions.interlaced = false;
    pngSaveOptions.compression = 9;
    pngSaveOptions.transparency = false;
    pngSaveOptions.PNG8 = false;
    pngSaveOptions.includeProfile = false;
    
    activeDocument.saveAs(outputFile, pngSaveOptions, true);
    
    //outputFile.remove();
    return outputFile; // SHOULD WE RETURN THE OUTPUT FILE?
};



/////////////////////////////////////////
// ASK FOTOFUZE IF IT HAS FINISHED YET 
/////////////////////////////////////////
function checkProgress(){
    $.sleep(5000);
    var success = Get(fotofuzeDomain, "targetPath", constructCookie());
    timeOutRequests += 1;
    if(success){
        //DO STUFF
    } else {checkProgress();}
};



///////////////////////////
// GET IMAGE FROM SERVER 
///////////////////////////
function getImage(url){
    
    var html = "";
    var request = "";
    var binary = "";
    var socket = new Socket;
    var http = "http://";
    var domain = "" // the domain for the file we want
    var sImg = ""; // the rest of the url for the file we want
    var port = ":80"; // the port for the file we want

    
    
    // Check for Redirects
    function checkRedirect(binary){
       if (binary.indexOf("Location: ") > 1){
            redirectURL = parseHeaderObject(binary, "Location: ");
            return true;
        } else { return false; } 
    };
    
    

    // Remove header lines from HTTP response
    function removeHeaders(binary){
        var bContinue = true ; // flag for finding end of header
        var line = "";
        var httpheader = "";
        var nFirst = 0;
        var count = 0;
        while (bContinue) {
            line = getLine(binary) ; // each header line
            httpheader = httpheader + line;
            bContinue = line.length >= 2 ; // blank header == end of header
            nFirst = line.length + 1 ;
            binary = binary.substr(nFirst) ;
        }
        if (httpheader.indexOf("Bad Request") != -1 || httpheader.indexOf("Not Found") != -1) {
            alert ("RequestHTML: " + requesthtml + " Request: " + request + " HTTPHeader: " + httpheader);
            var binary = "";
        }
        return binary;
    };



    // Get a response line from the HTML
    function getLine(html){
        var line = "" ;
        for (var i = 0; html.charCodeAt(i) != 10; i++){ // finding line end
            line += html[i] ;
        }
        return line ;
    };    

    
    
    // Place image into Photoshop Layer
    function placeImage(fileRef){
        //create a new smart object  layer using a file
        try { 
            var desc = new ActionDescriptor();
            desc.putPath( charIDToTypeID( "null" ), new File( fileRef ) );
            desc.putEnumerated( charIDToTypeID( "FTcs" ), charIDToTypeID( "QCSt" ), charIDToTypeID( "Qcsa" ) );
            desc.putBoolean( charIDToTypeID( "Lnkd" ), true );
            executeAction( charIDToTypeID( "Plc " ), desc, DialogModes.NO );
            
            activeDocument.activeLayer.merge(); //merge new layer with the one below it
            
        } catch (e) { alert("Placeing file: '" + fileRef + "' failed" + e); }
    };    


 
/////////////////////////
// MAIN
/////////////////////////

    if (url != null && url != ""){

        if ( (url.indexOf(http) != -1)  || (url.indexOf(http.toUpperCase()) != -1)  ) {

            domainPathLength = url.length - http.length; 
            domainPath = url.substr(http.length, domainPathLength);
            pathOffset = domainPath.indexOf("/"); 
            domain = domainPath.substr(0, pathOffset);
            sImgPath = domainPath.substr(pathOffset, domainPath.length - pathOffset );
            
            
            if ( domain != "" && sImgPath != "" && sImgPath != "/" ) {  //&& Name.indexOf(".") != -1
                
                
                var theDate = "\nDate: " + constructCurrentDate();
                requesthtml ="\n\nDmain:" + domain + " Port" + port + " binary\n";
                request =   "GET " + 
                            sImgPath +" HTTP/1.0" +
                            "\nHost: " + domain + 
                            "\nAccept: image/gif, image/x-xbitmap, image/png, image/jpeg, */*" + 
                            theDate + 
                            "\nUser-Agent: Photoshop" + 
                            "\nContent-Type: application/x-www-form-urlencoded" + 
                            "\n\n";

                if (socket.open(domain + port, "binary")){

                    socket.write(request); // get the file
                    var binary = socket.read(99999999);

                    socket.close();
                    
                    if(domain == fotofuzeDomain){
                        parseCookie(binary, cookieContents); // DO WE NEED TO RE-WRITE COOKIE?
                        writeCookieFile();
                    }
                    
                    if (checkRedirect(binary) == true){
                        return;
                    }
                    
                    
                    var randomName = generateRandomName();
                    var fileExtension = sImgPath;
                    while (fileExtension.indexOf(".") != -1 ) {	// Strip Path
                        fileExtension = fileExtension.substr(fileExtension.indexOf(".") + 1 ,);		
                    }
                    
                    var f = File("~/" + randomName + "." + fileExtension); // Image file name
                    f.encoding = "binary"; // set binary mode
                    f.open("w");

                    binary = removeHeaders(binary);
                    f.write(binary);
                    f.close();

                    if (binary.length != 0) {
                        placeImage( f );
                    } 

                    f.remove(); // Remove temporary downloaded files

                    } else { alert("Connection to Domain:" + domain + " Port" + port + " Failed   " + url);}


            } else { alert("Invalid Image URL: " + url ); }


        } else { alert("Invalid URL: " + url ); }


    } else { if ( url == "" ) alert("No URL Entered"); }
    
};
PHOTOSHOP: Isometric Grid Generator




// This script will create a new Isometric Grid of various dimensions on it's own art layer.
// 
// Author: Matt Olick 
// Version: 1.0.1
// Date: April 2020
// Homepage: http://www.mattOlick.com/
//
// This script is released under the GPLv3 Software License. You are free to use it for free or commercial purposes.
// If you make any modifications or improvements to this script, please do share them back with us.

/*
// BEGIN__HARVEST_EXCEPTION_ZSTRING

Isometric Grid Generator
filter
6F17BFA7-EFC9-11EA-B960-7B96ED7EA770
 in (PSHOP_ImageMode, RGBMode, CMYKMode, HSLMode, HSBMode, LabMode, RGB48Mode)  && (PSHOP_IsTargetVisible  &&  ! PSHOP_IsTargetSection)
filter

// END__HARVEST_EXCEPTION_ZSTRING
*/


#target photoshop

app.bringToFront();
 
/////////////////////////
// SETUP
/////////////////////////
const ScriptTitle = "Isometric Grid Generator";

const UserOptionsKey = "isoGridGen_UserOptions";

const UserOptionsWidthKey = "gridWidthSize";
const UserOptionsHeightKey = "gridHeightSize";
const UserOptionsResizeKey = "matchGridResize";

function Ruler_savePrefs()
{
    prevRulerUnits = app.preferences.rulerUnits;
}
function Ruler_setPixels()
{
    app.preferences.rulerUnits = Units.PIXELS;
}
function Ruler_setPoints()
{
    app.preferences.rulerUnits = Units.POINTS;
}
function Ruler_returnPrefs()
{
    app.preferences.rulerUnits = prevRulerUnits;
}  


/////////////////////////
// MAIN
/////////////////////////

if(app.documents.length == 0) { 
    alert("No Document Actively Open");
    }

else{
    main();
}

function main()
{   
    Ruler_savePrefs();
    
     // Get the user's input options
    var userOptions = getUserOptions();
    
    if ( userOptions == false ) { 
        return;
    }
    
    var srcDoc = app.activeDocument;
    
    // Make sure to match ppi to 72, to match Points Ruler
    if(srcDoc.resolution != 72) { srcDoc.resizeImage(undefined, undefined, 72, ResampleMethod.NONE); }
    
    var targetLayer = srcDoc.artLayers.add();
    
    Ruler_setPixels();
    gridWidth = userOptions[UserOptionsWidthKey];
    gridHeight = userOptions[UserOptionsHeightKey];
    Canvas_setSize(srcDoc);
    
    Ruler_setPoints();
    //gridWidth = userOptions[UserOptionsWidthKey]*(72/srcDoc.resolution);
    //gridHeight = userOptions[UserOptionsHeightKey]*(72/srcDoc.resolution);
    createGrid(srcDoc, targetLayer);
    
    if(userOptions[UserOptionsResizeKey] == true)
    {
        Ruler_setPixels();
        Canvas_returnSize(srcDoc);
    }
    
    Ruler_returnPrefs();
}


function Canvas_setSize(srcDoc)
{
    prevWidth = srcDoc.width;
    prevHeight = srcDoc.height;
    
    var newWidth = prevWidth;
    var newHeight = prevHeight;
    
    var remainderWidth = parseInt( srcDoc.width % gridWidth );
    var remainderHeight = parseInt( srcDoc.height % gridHeight );
    
    if(remainderWidth > 0)
    { newWidth += gridWidth - remainderWidth; }
    
    if(remainderHeight > 0)
    { newHeight += gridHeight - remainderHeight; }

    srcDoc.resizeCanvas( UnitValue(newWidth,"px"), UnitValue(newHeight,"px") );
}

function Canvas_returnSize(srcDoc)
{
    srcDoc.resizeCanvas( UnitValue(prevWidth,"px"), UnitValue(prevHeight,"px") );
}



/////////////////////////
// USER OPTIONS
/////////////////////////

function putActionDescValue(desc, method, key, value)
{
    if ( key == undefined || value == undefined ) {
        return;
    }

    desc[method](app.stringIDToTypeID(key), value);
}

function getActionDescValue(desc, method, key, defaultValue)
{
    if ( desc.hasKey(app.stringIDToTypeID(key)) ) {
        return desc[method](app.stringIDToTypeID(key)); 
    } 

    return defaultValue;
}



function storeLastUserOptions(userOptions)
{
    var desc = new ActionDescriptor();
    
    putActionDescValue(desc, "putInteger", UserOptionsWidthKey, userOptions[UserOptionsWidthKey]);
    putActionDescValue(desc, "putInteger", UserOptionsHeightKey, userOptions[UserOptionsHeightKey]);
    putActionDescValue(desc, "putBoolean", UserOptionsResizeKey, userOptions[UserOptionsResizeKey]);

    app.putCustomOptions( UserOptionsKey, desc, true );
}


function retrieveLastUserOptions()
{
    try { 
        var desc = app.getCustomOptions(UserOptionsKey);
    } catch( e ) { return { }; }
    
    var result = { };
    
    result[UserOptionsWidthKey] = getActionDescValue(desc, "getInteger", UserOptionsWidthKey, 0);  
    result[UserOptionsHeightKey] = getActionDescValue(desc, "getInteger", UserOptionsHeightKey, 0); 
    result[UserOptionsResizeKey] = getActionDescValue(desc, "getBoolean", UserOptionsResizeKey, false); 
    
    return result;
}

function indexOf(array, obj)
{
    for( var i = 0; i < array.length; ++i ) 
    {
        if ( array[i] == obj ) {
            return i;
        }
    }

    return -1;
}


function getUserOptions()
{
    var lastUserOptions = retrieveLastUserOptions();
    
    var sizeItems = [8, 16, 32, 64, 128, 256, 512];
    var sizeItemsStrings = ["8", "16", "32", "64", "128", "256", "512"];
    
    var dlg = new Window('dialog', ScriptTitle);
    
    dlg.widthSizeGrp = dlg.add('group', undefined, );
    dlg.widthSizeGrp.orientation = "row";
    dlg.widthSizeGrp.alignment = 'left';
    dlg.widthSizeGrp.dropDownWidthSize = dlg.widthSizeGrp.add('DropDownList', undefined, sizeItemsStrings);
    dlg.widthSizeGrp.dropDownWidthSize.text = "Width Size: ";
    dlg.widthSizeGrp.dropDownWidthSize.selection = 0;
    
    dlg.heightSizeGrp = dlg.add('group', undefined, );
    dlg.heightSizeGrp.orientation = "row";
    dlg.heightSizeGrp.alignment = 'left';
    dlg.heightSizeGrp.dropDownHeightSize = dlg.heightSizeGrp.add('DropDownList', undefined, sizeItemsStrings);
    dlg.heightSizeGrp.dropDownHeightSize.text = "Height Size: ";
    dlg.heightSizeGrp.dropDownHeightSize.selection = 0;
    
    dlg.resizeGrp = dlg.add('group');
    dlg.resizeGrp.orientation = 'row';
    dlg.resizeGrp.alignment = 'left';
    dlg.resizeGrp.resizeCheck = dlg.resizeGrp.add('CheckBox', undefined, "Return Canvas to Non-Grid Size?");
    
    dlg.buttonGrp = dlg.add('group');
    dlg.buttonGrp.okButton = dlg.buttonGrp.add('button', undefined, 'OK');
    dlg.buttonGrp.cancelButton = dlg.buttonGrp.add('button', undefined, 'Cancel');
        
    
    // Before presenting, update from user options (if applicable)
    if ( lastUserOptions[UserOptionsWidthKey] > 0 ) 
    { 
        dlg.widthSizeGrp.dropDownWidthSize.selection = indexOf(sizeItems, lastUserOptions[UserOptionsWidthKey]); 
    }
    
    if ( lastUserOptions[UserOptionsHeightKey] > 0 ) 
    { 
        dlg.heightSizeGrp.dropDownHeightSize.selection = indexOf(sizeItems, lastUserOptions[UserOptionsHeightKey]); 
    }
    if ( lastUserOptions[UserOptionsResizeKey] !== undefined ) 
    { 
        dlg.resizeGrp.resizeCheck.value = lastUserOptions[UserOptionsResizeKey]; 
    }
        
        
    dlg.center();
    var dlgResult = dlg.show(); // 1 = ok, 2 = cancel
    
    
    // Note: .selection as a string is the value in the drop down, as an int it's the item's index!
    var userOptions = { };
    userOptions[UserOptionsWidthKey] = parseInt(sizeItems[parseInt(dlg.widthSizeGrp.dropDownWidthSize.selection)]);
    userOptions[UserOptionsHeightKey] = parseInt(sizeItems[parseInt(dlg.heightSizeGrp.dropDownHeightSize.selection)]);
    userOptions[UserOptionsResizeKey] = dlg.resizeGrp.resizeCheck.value;
    
    storeLastUserOptions(userOptions);
    
    if ( dlgResult == 2 ) {
        return false;
    }    

    return userOptions;
}






/////////////////////////
// LOGIC
/////////////////////////


function createGrid(srcDoc, targetLayer)
{
    //Photoshop counts from Top-Left corner

    var totalX_iterations = (srcDoc.width - gridWidth) / gridWidth;
    var totalY_iterations = (srcDoc.height - gridHeight) / gridHeight;
    
    var gridBegin_x = gridWidth/2;
    var gridBegin_y = gridHeight/2;
    
    
    //left -- bottom to top -- downslope
    for(var y = totalY_iterations; y>= 0;y--)
    {
        var startX = parseInt(0);
        var startY = parseInt(gridBegin_y + gridHeight*y);

        var endX = parseInt(gridBegin_x + gridWidth*(totalY_iterations-y));
        var endY = parseInt(srcDoc.height);

        drawLine(   startX, 
                    startY, 
                    endX, 
                    endY, srcDoc);
    }
    
    //top -- left to right -- downslope
    for(var x = 0; x<= totalX_iterations;x++)
    {
        var startX = parseInt(gridBegin_x + gridWidth*x);
        var startY = parseInt(0);

        var endX = parseInt(gridBegin_x + (totalY_iterations+1 + x)*gridWidth);
        var endY = parseInt(srcDoc.height);

        drawLine(   startX, 
                    startY, 
                    endX, 
                    endY, srcDoc);
    }
    
    
    //left -- top to bottom -- upslope
    for(var y = 0; y<= totalY_iterations;y++)
    {
        var startX = parseInt(0);
        var startY = parseInt(gridBegin_y + gridHeight*y);

        var endX = parseInt(gridBegin_x + gridWidth*y);
        var endY = parseInt(0);

        drawLine(   startX, 
                    startY, 
                    endX, 
                    endY, srcDoc);
    }
    
    //bottom -- left to right -- upslope
    for(var x = 0; x<= totalX_iterations;x++)
    {
        var startX = parseInt(gridBegin_x + gridWidth*x);
        var startY = parseInt(srcDoc.height);

        var endX = parseInt(gridBegin_x + (totalY_iterations+1 + x)*gridWidth);
        var endY = parseInt(0);

        drawLine(   startX, 
                    startY, 
                    endX, 
                    endY, srcDoc);
    }
}


function drawLine(startX, startY, endX, endY, srcDoc)
{
    //alert("sX " + startX + "sY " + startY + "eX " + endX + "eY " + endY );

    var startPoint = new PathPointInfo();
    startPoint.kind = PointKind.CORNERPOINT;
    startPoint.anchor = [startX, startY];
    startPoint.leftDirection = startPoint.anchor;
    startPoint.rightDirection = startPoint.anchor;
    
    var endPoint = new PathPointInfo();
    endPoint.kind = PointKind.CORNERPOINT;
    endPoint.anchor = [endX, endY];
    endPoint.leftDirection = endPoint.anchor;
    endPoint.rightDirection = endPoint.anchor;
    
    var lineSubPath = new SubPathInfo();
    lineSubPath.operation = ShapeOperation.SHAPEXOR;
    lineSubPath.closed = false;
    lineSubPath.entireSubPath = [startPoint, endPoint];
    
    var gridLine = srcDoc.pathItems.add("GridLine", [lineSubPath]);
    
    srcDoc.pathItems[0].subPathItems[0].pathPoints[0].anchor = [0, 0];
    //alert(lineSubPath.entireSubPath[0].anchor);
    //alert(gridLine.subPathItems[0].pathPoints[0].anchor);
    
    gridLine.strokePath(ToolType.PENCIL);
    
    gridLine.remove();
}


PHOTOSHOP: Texture Atlas Generator



// This script will export a texture atlas as a new document given either a set of documents or a layered document.
// 
// Author: Matt Olick 
// Version: 1.0.2
// Date: June 2018
// Homepage: http://www.mattOlick.com/
//
// This script is released under the GPLv3 Software License. You are free to use it for free or commercial purposes.
// If you make any modifications or improvements to this script, please do share them back with us.

/*
// BEGIN__HARVEST_EXCEPTION_ZSTRING

Texture Atlas Generator
filter
6F17BFA7-EFC9-11EA-B960-7B96ED7EA769
 in (PSHOP_ImageMode, RGBMode, CMYKMode, HSLMode, HSBMode, LabMode, RGB48Mode)  && (PSHOP_IsTargetVisible  &&  ! PSHOP_IsTargetSection)
filter

// END__HARVEST_EXCEPTION_ZSTRING
*/


#target photoshop

app.bringToFront();
 
/////////////////////////
// SETUP
/////////////////////////
const ScriptTitle = "Texture Atlas Generator";

const UserOptionsKey = "texAtlasGen_UserOptions";
const UserOptionsDocNameKey = "documentName";
const UserOptionsSourceKey = "Current Document Layers";

const UserOptionsLayerSetKey = "layerSetIndex";

const UserOptionsResizeKey = "mipMapping";

const UserOptionsSourceAllDocs = 0;
const UserOptionsSourceDocument = 1;
const UserOptionsSourceLayerSet = 2;
const UserOptionsSourceFrameAnim = 3;

function Ruler_setPixels()
{
    prevRulerUnits = app.preferences.rulerUnits;
    app.preferences.rulerUnits = Units.PIXELS;
}
function Ruler_returnPrefs()
{
    app.preferences.rulerUnits = prevRulerUnits;
}  


/////////////////////////
// MAIN
/////////////////////////

if(app.documents.length == 0) { 
    alert("No Document Actively Open");
    }

else{
    main();
}

function main()
{
    Ruler_setPixels();
    
    var srcDoc = app.activeDocument;
    
    // Fix for broken resolution images
    if(srcDoc.resolution != 72) { srcDoc.resizeImage(undefined, undefined, 72, ResampleMethod.NONE); }
    
     // Get the user's input options
    var userOptions = getUserOptions();
    
    if ( userOptions == false ) { 
        return;
    }
    
    var destDoc = createDestDoc(userOptions, srcDoc);
    
    returnAllCanvasSizes(destDoc);
    
    //Remove white base layer created with new documents
    app.activeDocument = destDoc;
    destDoc.layers[destDoc.layers.length-1].remove();
    
    layoutLayersAsSprites(destDoc, userOptions);
    
    Ruler_returnPrefs();
}



/////////////////////////
// USER OPTIONS
/////////////////////////

function putActionDescValue(desc, method, key, value)
{
    if ( key == undefined || value == undefined ) {
        return;
    }

    desc[method](app.stringIDToTypeID(key), value);
}

function getActionDescValue(desc, method, key, defaultValue)
{
    if ( desc.hasKey(app.stringIDToTypeID(key)) ) {
        return desc[method](app.stringIDToTypeID(key)); 
    } 

    return defaultValue;
}



function storeLastUserOptions(userOptions)
{
    var desc = new ActionDescriptor();
    
    putActionDescValue(desc, "putString", UserOptionsDocNameKey, userOptions[UserOptionsDocNameKey]);
    putActionDescValue(desc, "putInteger", UserOptionsSourceKey, userOptions[UserOptionsSourceKey]);
    putActionDescValue(desc, "putInteger", UserOptionsLayerSetKey, userOptions[UserOptionsLayerSetKey]);
    putActionDescValue(desc, "putBoolean", UserOptionsResizeKey, userOptions[UserOptionsResizeKey]);
    
    app.putCustomOptions( UserOptionsKey, desc, true );
}


function retrieveLastUserOptions()
{
    try { 
        var desc = app.getCustomOptions(UserOptionsKey);
    } catch( e ) { return { }; }
    
    var result = { };
    
    result[UserOptionsDocNameKey] = getActionDescValue(desc, "getString", UserOptionsDocNameKey, "");
    result[UserOptionsSourceKey] = getActionDescValue(desc, "getInteger", UserOptionsSourceKey, 0);        
    result[UserOptionsLayerSetKey] = getActionDescValue(desc, "getInteger", UserOptionsLayerSetKey, 0);   
    result[UserOptionsResizeKey] = getActionDescValue(desc, "getBoolean", UserOptionsResizeKey, false); 
    
    return result;
}


function getUserOptions()
{
    var lastUserOptions = retrieveLastUserOptions();
    
    var sourceItems = ["All Open Documents", "Current Document Layers", "Specific Layer Group", "Frame Animation"];
    var layerSetItems = [];
    var documentItems = [];

    for( i = 0; i < app.activeDocument.layerSets.length; i++ ) {
        var layerSet = app.activeDocument.layerSets[i];
        layerSetItems[layerSetItems.length] = layerSet.name;
    }
    
    for( i = 0; i < app.documents.length; i++ ) {
        var documentSet = app.documents[i];
        documentItems[documentItems.length] = documentSet.name;
    }

    if ( layerSetItems.length == 0 ) { layerSetItems[0] = "No Layer Groups Available"; }
    if ( documentItems.length == 0 ) { documentItems[0] = "No Documents Available"; }
    
    var dlg = new Window('dialog', ScriptTitle);
    
    dlg.sourceGrp = dlg.add('group', undefined);
    dlg.sourceGrp.orientation = "column";
    dlg.sourceGrp.alignment = "left";    
    dlg.sourceGrp.dropDownSource = dlg.sourceGrp.add('DropDownList', undefined, sourceItems);
    dlg.sourceGrp.dropDownSource.text = "Source: ";
    dlg.sourceGrp.dropDownSource.selection = 0;
    dlg.sourceGrp.dropDownSource.onChange = function() { 
        userOptionsDlg_updateControls(dlg);
    };
   
    dlg.sourceGrp.dropDownLayerSet = dlg.sourceGrp.add('DropDownList', undefined, layerSetItems);
    dlg.sourceGrp.dropDownLayerSet.text = "Layer Group: ";
    dlg.sourceGrp.dropDownLayerSet.selection = 0;
    
    
    dlg.resizeGrp = dlg.add('group');
    dlg.resizeGrp.orientation = 'row';
    dlg.resizeGrp.alignment = 'left';
    dlg.resizeGrp.resizeCheck = dlg.resizeGrp.add('CheckBox', undefined, "Use Powers of 2 for MipMapping?");
    
    
    dlg.buttonGrp = dlg.add('group');
    dlg.buttonGrp.okButton = dlg.buttonGrp.add('button', undefined, 'OK');
    dlg.buttonGrp.cancelButton = dlg.buttonGrp.add('button', undefined, 'Cancel');
        
    
    // Before presenting, update from user options (if applicable)
    if ( lastUserOptions[UserOptionsSourceKey] > 0 ) { dlg.sourceGrp.dropDownSource.selection = lastUserOptions[UserOptionsSourceKey]; }
    
    if ( (lastUserOptions[UserOptionsDocNameKey] == app.activeDocument.name) && (lastUserOptions[UserOptionsLayerSetKey] < app.activeDocument.layerSets.length) ) {
        dlg.sourceGrp.dropDownLayerSet.selection = lastUserOptions[UserOptionsLayerSetKey];
    }
    
    if ( lastUserOptions[UserOptionsResizeKey] !== undefined ) 
    { 
        dlg.resizeGrp.resizeCheck.value = lastUserOptions[UserOptionsResizeKey]; 
    }
        
    userOptionsDlg_updateControls(dlg); 
    dlg.center();
    var dlgResult = dlg.show(); // 1 = ok, 2 = cancel
    
    
    // Note: .selection as a string is the value in the drop down, as an int it's the item's index!
    var userOptions = { };
    userOptions[UserOptionsDocNameKey] = app.activeDocument.name;
    userOptions[UserOptionsSourceKey] = parseInt(dlg.sourceGrp.dropDownSource.selection);
    userOptions[UserOptionsLayerSetKey] = parseInt(dlg.sourceGrp.dropDownLayerSet.selection);
    userOptions[UserOptionsResizeKey] = dlg.resizeGrp.resizeCheck.value;
    
    storeLastUserOptions(userOptions);
    
    if ( dlgResult == 2 ) {
        return false;
    }    

    return userOptions;
}

function userOptionsDlg_updateControls(dlg)
{
    if ( dlg.sourceGrp.dropDownSource.selection == UserOptionsSourceLayerSet ) {
        dlg.sourceGrp.dropDownLayerSet.visible = true;
        dlg.sourceGrp.dropDownLayerSet.text = "Layer Group: ";
    } else {
        dlg.sourceGrp.dropDownLayerSet.visible = false;
        dlg.sourceGrp.dropDownLayerSet.text = "";
    }
}






/////////////////////////
// LOGIC
/////////////////////////


function createDestDoc(userOptions, srcDoc)
{
    if ( userOptions === false ) {
        return false;
    }    

    var sourceType = userOptions[UserOptionsSourceKey];
    var layerSetIndex = userOptions[UserOptionsLayerSetKey];
    var destDoc = false;
    
    destDoc = app.documents.add(srcDoc.width, srcDoc.height, srcDoc.resolution, srcDoc.name + " Sheet");

    if ( sourceType == UserOptionsSourceAllDocs ) 
    {
        var biggestCanvasSize = findBiggestCanvas();
        matchAllCanvasSizes(biggestCanvasSize);
        copy_perDocument(destDoc);
    }
    else if ( sourceType == UserOptionsSourceDocument ) 
    {        
        copy_perLayer(srcDoc, destDoc, srcDoc.layers);
    }
    else if ( sourceType == UserOptionsSourceLayerSet ) 
    {
        if ( layerSetIndex >= srcDoc.layerSets.length ) {
            alert ("The 'Specific Layer Set' option was selected, but there is no selected layer set in the open document.");
        }
    
        if ( srcDoc.layerSets[layerSetIndex].layers.length == 0 ) {
            alert ("The selected layer set contains no layers.");
        }
      
        copy_perLayer(srcDoc, destDoc, srcDoc.layerSets[layerSetIndex].layers);        
    }
    else if (sourceType == UserOptionsSourceFrameAnim)
    {    
        copy_perFrame(srcDoc, destDoc);
    }

    return destDoc;
}


function findBiggestCanvas()
{
    var biggestCanvas = {width: 0, height: 0};
    
    // Find and return the biggest canvas size of all open documents
    for( var i = 0; i < app.documents.length; i++ ) 
    {
        // Fix for broken resolution images
        app.activeDocument = app.documents[i];
        app.documents[i].resizeImage(undefined, undefined, 72, ResampleMethod.NONE);
    
        // Compare canvas sizes to find the biggest size
        if(app.documents[i].width > biggestCanvas.width)
        {
            biggestCanvas.width = app.documents[i].width;
        }
        if(app.documents[i].height > biggestCanvas.height)
        {
            biggestCanvas.height = app.documents[i].height;
        }
    }
    
    return biggestCanvas;
}

function matchAllCanvasSizes(biggestCanvasSize)
{
    prevSize = new Array();
    
    for( var i = 0; i < app.documents.length; i++ ) 
    {
        app.activeDocument = app.documents[i];
        
        prevSize[i] = {width: app.documents[i].width, height: app.documents[i].height};
        
        app.documents[i].resizeCanvas(biggestCanvasSize.width, biggestCanvasSize.height);
    }
}

function returnAllCanvasSizes(destDoc)
{
    for( var i = 0; i < app.documents.length; i++ ) 
    {
        if(app.documents[i] != destDoc)
        {
            app.activeDocument = app.documents[i];

            app.documents[i].resizeCanvas(prevSize[i].width, prevSize[i].height);
        }
    }
}
    
function copy_perDocument(destDoc)
{
    // Copy Merged Documents Layers across to new image Texture Atlas
    for( var i = 0; i < app.documents.length; i++ ) 
    {
        if(app.documents[i] != destDoc)
        {
            app.activeDocument = app.documents[i];
            
            processLayers(destDoc);
        }
    }
}
    
 
function copy_perLayer(srcDoc, destDoc, layers)
{
    app.activeDocument = srcDoc;    
    
    if ( srcDoc == undefined || destDoc == undefined || layers == undefined ) {
        alert ("source, destination, or layers were undefined.");
    }
    
    var wasVisible = [];
    var wasParentVisible = undefined;

    // Pre process
    for( var i = 0; i < layers.length; i++ ) {
        wasVisible[i] = layers[i].visible;
    }

    if ( layers[0].parent.visible !== undefined ) {
        wasParentVisible = layers[0].parent.visible;
        layers[0].parent.visible = true;
    }
    
    // Do the work
    for( var i = layers.length-1; i > -1; i-- ) 
    {
        app.activeDocument = srcDoc;
        
        // Don't process background layer as it causes issues (probably don't want it as a sprite anyway)
        if ( layers[i].isBackgroundLayer == true ) { 
            continue;
        }

        // Turn off all layers (but don't mess with the background layer)
        for( var j = 0; j < layers.length; j++ ) {
            if ( layers[j].isBackgroundLayer == false ) {
                layers[j].visible = false;
            }
        }

        // Turn on active layer
        layers[i].visible = true;
        
        // Move to Texture Atlas
        processLayers(destDoc);
    }

    // Undo Pre-Process
    app.activeDocument = srcDoc;    

    for( var i = 0; i < layers.length; i++ ) {
        layers[i].visible = wasVisible[i];    
    }

    if ( layers[0].parent.visible !== undefined ) {
        layers[0].parent.visible = wasParentVisible;
    }
}


function copy_perFrame(srcDoc, destDoc)
{
    app.activeDocument = srcDoc; 

    var frameCount = getFrameCount();
        
    if ( frameCount == 0 ) { 
        alert ("No animation frames were found in this file.");
    }
    else {
        // Copy Merged Frames across to new image
        for( var i = 1; i <= frameCount; i++ ) 
        {
            app.activeDocument = srcDoc;
            goToFrame(i);
            processLayers(destDoc);      
        }
    }  
}



function processLayers(destDoc) 
{
    selectAllLayers();
    duplicateSelectedLayers();

    if( (app.activeDocument.layers.length > 2 || app.activeDocument.layerSets.length > 1) )
    {
        mergeSelectedLayers();
    }

    var layer = app.activeDocument.activeLayer.duplicate(destDoc, ElementPlacement.PLACEATEND);
    deleteSelectedLayers();

    app.activeDocument = destDoc;
    layer.name = "Frame " + i;     
}




function layoutLayersAsSprites(destDoc, userOptions)
{
    // Figure out atlas size
    var frameWidth = parseInt(destDoc.width);
    var frameHeight = parseInt(destDoc.height);
    var frameCount = destDoc.layers.length;
    
    if(userOptions[UserOptionsResizeKey] == true)
    {
        var atlasSize = atlasResizeForMipMapping(frameWidth, frameHeight, frameCount);
    }
    else
    {
        var atlasSize = atlasResize(frameWidth, frameHeight, frameCount);
    }
    
    destDoc.resizeCanvas(atlasSize.width, atlasSize.height, AnchorPosition.TOPLEFT);
    
    // Layout Layers
    var x = 0;
    var y = 0;
    
    for( var i = 0; i < frameCount; i++ ) 
    {         
        try { 
            destDoc.layers[i].translate(x, y);
        } catch ( e ) { }
        
        x += frameWidth;
        
        if ( (x+frameWidth) > atlasSize.width ) 
        {
            x = 0;
            y += frameHeight;
        }
    }

    //flatten image
    selectAllLayers ();
    mergeSelectedLayers ();
}


function atlasResizeForMipMapping(frameWidth, frameHeight, frameCount)
{
    var atlasWidth = 2;
    var atlasHeight = 2;
    var didFit = false;
       
    //determine the resolution of the texture atlas for bin-packing
    while( didFit == false ) 
    {   
        var gridWidth = Math.floor(atlasWidth / frameWidth);
        if(gridWidth == 0) {atlasWidth *= 2; continue;}
        
        var requiredGridHeight = Math.ceil(frameCount / gridWidth);
        var requiredPixelHeight = requiredGridHeight * frameHeight;
        
        if ( requiredPixelHeight > atlasHeight ) 
        {
            if ( (atlasWidth <= atlasHeight) ) { atlasWidth *= 2; }
            else { atlasHeight *= 2; }
            
            didFit = false;
        } 
        else 
        {
            didFit = true;
        }
    }

    var result = {width: atlasWidth, height: atlasHeight};
    
    return result;
}



function atlasResize(frameWidth, frameHeight, frameCount)
{
    var atlasWidth = frameWidth;
    var atlasHeight = frameHeight;
    var didFit = false;
    
    //determine the resolution of the texture atlas for bin-packing
    while( didFit == false ) 
    {   
        var gridWidth = Math.floor(atlasWidth / frameWidth);
        if(gridWidth == 0) {atlasWidth += frameWidth; continue;}
        
        var requiredGridHeight = Math.ceil(frameCount / gridWidth);
        var requiredPixelHeight = requiredGridHeight * frameHeight;
        
        if ( requiredPixelHeight > atlasHeight ) 
        {
            if ( (atlasWidth <= atlasHeight) ) { atlasWidth += frameWidth; }
            else { atlasHeight += frameHeight; }
            
            didFit = false;
        } 
        else 
        {
            didFit = true;
        }
    }

    var result = {width: atlasWidth, height: atlasHeight};
    
    return result;
}


/////////////////////////
// LAYER FUNCTIONS
/////////////////////////    
    
// Select all layers in the active document
function selectAllLayers()
{
    var idselectAllLayers = stringIDToTypeID( "selectAllLayers" );
    var desc4 = new ActionDescriptor();
    var idnull = charIDToTypeID( "null" );
    var ref1 = new ActionReference();
    var idLyr = charIDToTypeID( "Lyr " );
    var idOrdn = charIDToTypeID( "Ordn" );
    var idTrgt = charIDToTypeID( "Trgt" );
    ref1.putEnumerated( idLyr, idOrdn, idTrgt );
    desc4.putReference( idnull, ref1 );
    executeAction( idselectAllLayers, desc4, DialogModes.NO );
}


// Duplicate all layers that are currently selected
function duplicateSelectedLayers()
{
    var idDplc = charIDToTypeID( "Dplc" );
    var desc5 = new ActionDescriptor();
    var idnull = charIDToTypeID( "null" );
    var ref2 = new ActionReference();
    var idLyr = charIDToTypeID( "Lyr " );
    var idOrdn = charIDToTypeID( "Ordn" );
    var idTrgt = charIDToTypeID( "Trgt" );
    ref2.putEnumerated( idLyr, idOrdn, idTrgt );
    desc5.putReference( idnull, ref2 );
    var idVrsn = charIDToTypeID( "Vrsn" );
    desc5.putInteger( idVrsn, 2 );
    executeAction( idDplc, desc5, DialogModes.NO );
}


// Merge all currently selected layers
function mergeSelectedLayers()
{
    var idMrgtwo = charIDToTypeID( "Mrg2" );
    
    try {
        executeAction( idMrgtwo, undefined, DialogModes.NO );
    } catch( e ) { }
}


// Delete all selected layers
function deleteSelectedLayers()
{
    // Delete selected layers
    var idDlt = charIDToTypeID( "Dlt " );
    var desc8 = new ActionDescriptor();
    var idnull = charIDToTypeID( "null" );
    var ref6 = new ActionReference();
    var idLyr = charIDToTypeID( "Lyr " );
    var idOrdn = charIDToTypeID( "Ordn" );
    var idTrgt = charIDToTypeID( "Trgt" );
    ref6.putEnumerated( idLyr, idOrdn, idTrgt );
    desc8.putReference( idnull, ref6 );
    
    try{
        executeAction( idDlt, desc8, DialogModes.NO );
    } catch(e) { }
}



/////////////////////////
// FRAME FUNCTIONS
/////////////////////////   

// Count the number of frames in the current frame animation.
function getFrameCount()
{
    for( var f = 1; f < 999; f++ ) 
    {
        if ( goToFrame(f) == false ) {
            return f - 1;
        }
    }

    return 0;
}


// Jump to the given frame in the frame animation in the active document
function goToFrame(frameIndex)
{
    try { 
        var desc = new ActionDescriptor();  
        var ref1 = new ActionReference();  
        ref1.putIndex( stringIDToTypeID( "animationFrameClass" ), frameIndex);  
        desc.putReference( charIDToTypeID( "null" ), ref1 );  
        executeAction( charIDToTypeID( "slct" ), desc, DialogModes.NO );  
        
        return true;
        
     } catch(e) {

     }
     
     return false;
}

UNITY
C#: IK Solver
using UnityEngine;

// CHARACTER MUST BE BUILT WITH Z BEING THE HEIGHT OF THE CHARACTER (instead of Y), OR WHICHEVER FORWARD-VECTOR UNITY IS SET TO
[ExecuteInEditMode]
public class IKsolver : MonoBehaviour
{
	public Transform aPoint, bPoint, cTarget;
    [HideInInspector] // HideInInspector forces serialization
	public float aLength=0f, bLength=0f, cLength=0f; 

	void Awake() {
		SetLengths();
	}
	void OnValidate() {
		SetLengths();
	}
	
	void SetLengths() {
		if(aPoint != null && bPoint != null && cTarget != null) {
			aLength = (bPoint.position - aPoint.position).magnitude;
			bLength = (cTarget.position - bPoint.position).magnitude;
			cLength = (cTarget.position  - aPoint.position).magnitude; 
		}
	}

	void Update() {
		if(aLength > 0f && bLength > 0f && cLength > 0f) {
			cLength = (cTarget.position  - aPoint.position).magnitude; 

			if(cLength > aLength+bLength)
				ClampTarget();

			BaseRotation();
			Solve(); 
		}
	}
	
	Vector3 cTargetdir;
	void ClampTarget() {
		cTargetdir = (cTarget.position - aPoint.position).normalized;
		cTarget.position = aPoint.position + cTargetdir*(aLength+bLength);
	}
	
	Vector3 targetDir;
	Vector3 poleNormal;
	Vector3 upAxis;
	Vector3 targetPoleDir;
	
	public Vector3 offsetRotation;
	Quaternion targetRotation;
	void BaseRotation()
	{
		targetDir = cTarget.position - aPoint.position;
		
		// pole vector constraint
		poleNormal = cTarget.rotation * Vector3.left;
		targetPoleDir = Vector3.ProjectOnPlane(targetDir, poleNormal);
		
		// get up axis
		upAxis = Vector3.Cross(poleNormal, targetPoleDir);

		targetRotation = Quaternion.LookRotation(targetDir, upAxis);
		aPoint.rotation = targetRotation * Quaternion.Euler(offsetRotation.x, offsetRotation.y, offsetRotation.z);

		bPoint.localRotation = Quaternion.identity;
	}
	
	Vector3 jointBend;
	void Solve()
	{
		float a = aLength;
		float b = bLength;
		float c = cLength;

		var B = Mathf.Acos((a * a + c * c - b * b) / (2 * a * c)) * Mathf.Rad2Deg;
		var C = Mathf.Acos((a * a + b * b - c * c) / (2 * a * b)) * Mathf.Rad2Deg;

		if (!float.IsNaN(B) && !float.IsNaN(C)) {
			jointBend = cTarget.localRotation * (Vector3.right*jointDir);
		
			var aPointRotation = Quaternion.AngleAxis((-B), jointBend);
			aPoint.localRotation *= aPointRotation; 
			
			var bPointRotation = Quaternion.AngleAxis((180-C), jointBend);
			bPoint.localRotation = bPointRotation;
		}
	}

	public bool invertJointDirection;
	public float jointDir = 1f;
	
	public Vector3 cPos = Vector3.up*1000f;
	public void SetBindPose() {
		cPos = cTarget.position;  Debug.Log("bind pose set");
	}
	public void GotoBindPose() {
		cTarget.position = cPos;
	}
}
C#: Networking, Sync Position & Rotation

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

[NetworkSettings(sendInterval = 0.07f)]

public class Sync_Position : NetworkBehaviour {

	[SyncVar]
    public Vector3 serverPosition;
    
    private Coroutine UpdatePositionServer_Coroutine;
    public bool snapPosition;
    private Coroutine SnapPosition_Coroutine;
    
    public float threshold_Radius;
    
    public Rigidbody syncObject;
    
    
    void Awake()
    {
        syncObject = gameObject.GetComponent();
        
        //RADIUS WORKS BY PREVENTING SERVER FROM UPDATING CLIENT'S LATEST STATE, UNLESS THERE IS A LARGE DISPARITY.
        threshold_Radius = 0.05f;     
    }
    
    void Start () {
        serverPosition = transform.position;
        
        if(!isServer) {return;}
        
        UpdatePositionServer_Coroutine = StartCoroutine( UpdatePosition_Server(0.07f) ); 
        SnapPosition_Coroutine = StartCoroutine(SnapPosition(3f));
        
	}
    
    
    
    //SERVER RESPONSIBILITES::
    IEnumerator UpdatePosition_Server(float delay)
    {
        if(isServer)
        {
            WaitForSeconds Wait = new WaitForSeconds(delay);
            while(true)
            {
                yield return Wait;
                
                if (syncObject.velocity.magnitude > 0.01)
                {

                    //RECONCILE WITH CLIENT IF LARGE DISPARITY BETWEEN CLIENT-SIDE PREDICTION AND SERVER POSITIONS
                    if (Vector3.Distance(transform.position, serverPosition) > threshold_Radius) 
                    {
                        serverPosition = transform.position;
                    }
                }
            }
        }
    }
    
    //SNAP THE PLAYER TO THE SERVER POSITION EVERY ONCE IN A WHILE, JUST INCASE THEY HAVE DE-SYNCED FROM THE RADIUS OF TRUST
    IEnumerator SnapPosition(float delay)
    {
        WaitForSeconds Wait = new WaitForSeconds(delay);
        while(true)
        {
            yield return Wait;
            Rpc_SnapPosition(transform.position);
        }
    }
    
    [ClientRpc]
    public void Rpc_SnapPosition(Vector3 snappedPosition)
    {
        if(serverPosition != snappedPosition)
        {
            serverPosition = snappedPosition;
            if(snapPosition)
            {
                transform.position = snappedPosition;
                snapPosition = false;
            }
            else if (Vector3.Distance(transform.position, serverPosition) > threshold_Radius)
            {
                transform.position = snappedPosition;
            }
        }
    }
    
    
    
    
    //CLIENT RESPONSIBILITES::
    void FixedUpdate()
    {
    //INTERPOLATION
        if(!isServer)
        {
            if(Vector3.Distance(transform.position, serverPosition) > threshold_Radius)
            {
                if(snapPosition)
                {
                    if(Vector3.Distance(transform.position, serverPosition) < threshold_Radius*3f)
                    { transform.position = serverPosition; snapPosition = false;}
                }
                
                transform.position = Vector3.Lerp(transform.position, serverPosition, GameState.Instance.fixedTime_Framerate);
            }  
        }
    }
}



using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

[NetworkSettings(sendInterval = 0.07f)]

public class Sync_Rotation : NetworkBehaviour {

	[SyncVar]
    public Quaternion serverRotation;
    
    private Coroutine UpdateRotation_Coroutine;
    private Coroutine SnapRotation_Coroutine;
    
    private float threshold_Angle;
    
    public bool snapRotation;
    
    void OnCollisionEnter()
    {
        snapRotation = true;
    }
    
    void Start () 
    {
        threshold_Angle = 90f;
        
        if(!isServer) {return;}
        UpdateRotation_Coroutine = StartCoroutine( UpdateRotation_Server(0.07f) ); 
        SnapRotation_Coroutine = StartCoroutine(SnapRotation(3f));
	}
    
    
    
    //SERVER RESPONSIBILITES::
    IEnumerator UpdateRotation_Server (float delay)
    {
        if(isServer)
        {
            WaitForSeconds Wait = new WaitForSeconds(delay);
            while(true)
            {
                yield return Wait;
                 
                 if (Vector3.Angle(transform.rotation * transform.forward, serverRotation * transform.forward) > threshold_Angle) 
                 {
                     serverRotation = transform.rotation;
                 }
                 
            }
        }
    }
    
    IEnumerator SnapRotation(float delay)
    {
        WaitForSeconds Wait = new WaitForSeconds(delay);
        while(true)
        {
            yield return Wait;
            Rpc_SnapRotation(transform.rotation);
        }
    }
    
    [ClientRpc]
    void Rpc_SnapRotation(Quaternion snappedRotation)
    {
        if(serverRotation != snappedRotation)
        {
            transform.rotation = snappedRotation;
            serverRotation = snappedRotation;
        }  
    }
    
    
    
    
    //CLIENT RESPONSIBILITES::
    void FixedUpdate()
    {
        if(!isServer)
        {
            //INTERPOLATION
            if (Vector3.Angle(transform.rotation * transform.forward, serverRotation * transform.forward) > threshold_Angle) {
                if(snapRotation)
                {
                    transform.rotation = serverRotation;
                    snapRotation = false;
                }
                
                transform.rotation = Quaternion.Slerp(transform.rotation, serverRotation, GameState.Instance.fixedTime_Framerate);
            }
        }
    }
    
}


C#: Line-by-Line Text Writer
using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class LineTextWriter : MonoBehaviour 
{

    public Text[] TextField;
    private int _currentIndex_Text;
    
    public float CharatersPerSecond = 30f; //15 for letter-by-letter,
    public float newLineDelay = 0.33f;
    private string[] _textToWrite;
    private int _currentIndex_Letter;
    
    private bool initiated = false;
    
    private IEnumerator coroutine;
    
    public bool Word_by_Word = false;
    public bool Line_by_Line = false;

	private void Start () 
	{
        _currentIndex_Letter = 0;
        _currentIndex_Text = 0;
    
        if(TextField == null) {TextField = new Text[1]; TextField[0] = GetComponent();}
        
        _textToWrite = new string[TextField.Length];
        
        StartCoroutine (FrameDelay(1f / CharatersPerSecond));
	}
    
    IEnumerator FrameDelay(float delay)
    {
        yield return null;
        
        if(TextField[_currentIndex_Text] != null)
        {
            for(int i = 0; i < TextField.Length; i++)
            {
                _textToWrite[i] = TextField[i].text;
                TextField[i].text = "";
            }
            
            initiated = true;
            coroutine = Running(delay);
            StartCoroutine(coroutine);
        }
    }
    
    IEnumerator Running(float delay)
    {
        yield return new WaitForSeconds(0.5f);
    
        WaitForSeconds characterWait = new WaitForSeconds(delay);
        WaitForSeconds newLineWait = new WaitForSeconds(newLineDelay);
        string stringToWrite;
        
        while(initiated)
        {
            yield return characterWait;
            
            if(_currentIndex_Letter >= _textToWrite[_currentIndex_Text].Length)
            {
                _currentIndex_Text++;
                _currentIndex_Letter = 0;
                yield return newLineWait;
                
                if(_currentIndex_Text >= TextField.Length)
                {
                    WrapUp();
                    yield break;
                }
            }
            
            SetAnim_Talk();
            
            stringToWrite = _textToWrite[_currentIndex_Text].Substring(_currentIndex_Letter, 1);
                
            if(Word_by_Word)
            {
                int i = 1;
                while(!_textToWrite[_currentIndex_Text].Substring(_currentIndex_Letter, 1).Contains(" "))
                {
                    _currentIndex_Letter++;
                    if (_currentIndex_Letter >= _textToWrite[_currentIndex_Text].Length) { break; }
                    stringToWrite += _textToWrite[_currentIndex_Text].Substring(_currentIndex_Letter, 1);
                    
                    if(i < 9) { yield return characterWait; i++;}
                }
            }
            
            else if(Line_by_Line)
            {
                int i = 1;
                while(_currentIndex_Letter < _textToWrite[_currentIndex_Text].Length)
                {
                    _currentIndex_Letter++;
                    stringToWrite += _textToWrite[_currentIndex_Text].Substring(_currentIndex_Letter, 1);
                    if(i < 9) { yield return characterWait; i++;}
                }
            }
            
            TextField[_currentIndex_Text].text += stringToWrite;
            _currentIndex_Letter++;

            if (stringToWrite == System.Environment.NewLine 
            || stringToWrite.Contains(".") 
            || stringToWrite.Contains("!") 
            || stringToWrite.Contains("?")
            || stringToWrite.Contains(",")
            || stringToWrite.Contains(">"))
            {
                yield return newLineWait;
            }
        }
    }
    
    private void Update ()
    {
        if (Input.GetMouseButton (0)) 
        {
            WrapUp();
            StopCoroutine(coroutine);
        }
    }
    
    private void WrapUp()
    {
        for(int i = 0; i < TextField.Length; i++)
        {
            TextField[i].text = _textToWrite[i];
        }
        
        initiated = false;
    }
}
C#: Transform Throw & Jump Tweening
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;

public static class JumpObject
{
	/* EXAMPLE USAGE
	this.CreateJumpRoutine(transform, duration, height, offset, 
			delegate() { m_CanPickUp = true; });
	*/		
	
	public static Coroutine CreateJumpRoutine(this MonoBehaviour value, Transform targetTransform, float duration, float jumpHeight, Vector3 targetOffset, Action onComplete = null) {
        return value.StartCoroutine(JumpRoutine(targetTransform, duration, jumpHeight, targetOffset, onComplete));
    }
	
	private static IEnumerator JumpRoutine(Transform targetTransform, float duration, float jumpHeight, Vector3 targetOffset, Action onComplete)
	{
		float deltaTime = Time.deltaTime;
		WaitForSeconds wait = new WaitForSeconds(deltaTime);
	
		Vector3 startPosition = targetTransform.position;
		Vector3 apexPosition = new Vector3(	startPosition.x + (targetOffset.x/2), 
									startPosition.y + jumpHeight,
									0f);
		Vector3 endPosition = startPosition + targetOffset;	
	
		float lengthA = (apexPosition - startPosition).magnitude;
		float lengthB = (endPosition - apexPosition).magnitude;
		float ratioA = lengthA / (lengthB + lengthA);
		
		Vector3 velocityStep;
		float totalSteps;
		float progressStep;
		
		float durationA = duration * ratioA;
		float progress = 0;
		while(progress < 1f)
		{
			totalSteps = (int)(durationA / Time.deltaTime);
			progressStep = 1f / totalSteps;
			progress += progressStep;	
			
			velocityStep = new Vector3( Mathf.Lerp(startPosition.x, apexPosition.x, progress),
										Mathf.Lerp(startPosition.y, apexPosition.y, easeOutSine(progress)), 
										0f);
			targetTransform.position = velocityStep;
			
			yield return null;
		}
		
		float durationB = duration - durationA;
		progress = 0;
		while(progress < 1f)
		{
			totalSteps = (int)(durationB / Time.deltaTime);
			progressStep = 1f / totalSteps;
			progress += progressStep;
				
			velocityStep = new Vector3( Mathf.Lerp(apexPosition.x, endPosition.x, progress),
										Mathf.Lerp(apexPosition.y, endPosition.y, easeInSine(progress)), 
										0f);
			targetTransform.position = velocityStep;
			
			yield return null;
		}
		
		targetTransform.position = endPosition;
		onComplete?.Invoke();
	}
	
	// https://easings.net/
	
	private static float easeOutSine(float x) {
		return Mathf.Sin((x * Mathf.PI) / 2f);
	}
	
	private static float easeInSine(float x) {
	  	return 1 - Mathf.Cos((x * Mathf.PI) / 2);
	}
}

C#: Isometric Y Sorting
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(SpriteRenderer))]
[ExecuteInEditMode]
public class SpriteSort : MonoBehaviour
{
    public const float tileWidth = 2.6660f *0.5f;
    public Vector3 bounds;
    [HideInInspector]
    public Vector3 boundsMax;

    [HideInInspector]
    public SpriteRenderer SpriteRend;
    void Awake()
    {
        SpriteRend = GetComponent();
        
        Collider2D[] ResultsFront = new Collider2D[MaxCache];
        Collider2D[] ResultsBehind = new Collider2D[MaxCache];
    }

    void Start()
    {
        SetCamera();
        SetObjectToCameraPos();
        lastObjPos = transform.position;
        SpriteRend.sortingOrder = SortByPosition();
    }
    
    const float PixelsPerUnit = 10f;
    int SortByPosition()
    {
        return -(int)((transform.position.y+bounds.y)*PixelsPerUnit);
    }
    
    [HideInInspector]
    public Camera currentCamera;
    void SetCamera() {
        currentCamera = Camera.main;
    }
    
    void SetObjectToCameraPos()
    {
        if(System.Object.ReferenceEquals(currentCamera, null)) 
            SetCamera();
        else
            refreshPos = Mathf.Abs(currentCamera.WorldToViewportPoint(transform.position+bounds).y) %1;
    }

    bool invoked = false;
    float refreshPos;
    const float refreshRate = 0.1f;
    void Update()
    {
        if(!invoked)
        {
            SetObjectToCameraPos();
            Invoke("CheckSort", (1f-refreshPos)*refreshRate );
            invoked = true;
        }
    }
    
    Vector3 ObjPos;
    Vector3 lastObjPos;
    Vector3 CamPos;
    Vector3 lastCamPos;
    void CheckSort()
    {
        // only re-sort moving objects
        ObjPos = transform.position;
        if(ObjPos != lastObjPos) {
            SpriteRend.sortingOrder = GetSortOrder();
            lastObjPos = ObjPos;
        }
        
        // sort downward from camera top
        // this will group objects into batches, based upon Y position, for further performance
        if(!System.Object.ReferenceEquals(currentCamera, null))
        {
            CamPos = currentCamera.transform.position;
            if(CamPos != lastCamPos) {
                SetObjectToCameraPos();
                lastCamPos = CamPos;
            }
        }
        else
            SetCamera();
            
        Invoke("ReadyForNextIteration", refreshPos*refreshRate );
    }
    
    void ReadyForNextIteration()
    {
        invoked = false;
    }
    
    const int MaxCache = 30;
    Collider2D[] ResultsFront = new Collider2D[MaxCache];
    int FrontLength=0;
    Collider2D[] ResultsBehind = new Collider2D[MaxCache];
    int BehindLength=0;
    SpriteRenderer resultRend;
    int GetSortOrder()
    {
        // clear previous results
        for(int i=0; i();
                    if(!System.Object.ReferenceEquals(resultRend, null)) {
                        resultOrder = resultRend.sortingOrder;
                        if(resultOrder>BehindClosestMax)
                            BehindClosestMax=resultOrder;   
                    }
                }
            }  
            
        return BehindClosestMax+1;
    }
}

C#: Procedural 9-Slice 3D Primitives
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]

[ExecuteInEditMode]
public class PrimitiveConstructor_Box : MonoBehaviour
{
	private MeshFilter mf;
	private Mesh BoxMesh;
	
	private Vector3 lossyScale;
	
	private List<Vector3> VertexList;
	private List<Vector2> UV_List;
	private List<int> TriangleList;

	private float edgeMargin = 1f / 4f;
	private float uWidth = 1f / 4f;
	// the texture includes both the top and side art stacked vertically
	private float vHeight = 1f / 8f;


    void Start()
    {
        mf = GetComponent<MeshFilter>();
		// record scale so we only update when changes occur
		lossyScale = transform.lossyScale;
		
		CreatePrimitive ();
    }

    void Update()
    {
        if ((transform.lossyScale - lossyScale).magnitude > 0.01f)
		{
			lossyScale = transform.lossyScale;
			CreatePrimitive ();
		}
    }
	
	
	void CreatePrimitive()
	{
		ClearMesh ();
		
		// meshing is calculated Bottom to Top, Left to Right
		CalculateVertex ();
		CalculateUV ();
		CalculateTriangle ();
		
		BoxMesh.vertices = VertexList.ToArray();	
		BoxMesh.uv = UV_List.ToArray();	
		BoxMesh.triangles = TriangleList.ToArray();	

		BoxMesh.RecalculateNormals();
		BoxMesh.RecalculateBounds();
	}
	
	void ClearMesh()
	{
		VertexList = new List<Vector3>();
		UV_List = new List<Vector2>();
		TriangleList = new List<int>();

		BoxMesh = mf.sharedMesh;
		if (BoxMesh)
			BoxMesh.Clear();
		
		BoxMesh = new Mesh ();
		mf.mesh = BoxMesh;
	}
	
	
	void CalculateVertex()
	{
		// face order: bottom, top, front, back, left, right
		for(int face=0; face<6; ++face)
		{
			//Vertex cache
			Vector3 vertPos = Vector3.zero;
			
			// top, back, and right faces have constant maximum value for a respective vector component
			switch (face) {
				case 1: vertPos.y = 1f; break;
				case 3: vertPos.z = 1f; break;
				case 5: vertPos.x = 1f; break;
			}
			
			// ROWS
			float rowPos = 0f;
			
			float rowScale;
			if (face < 2) rowScale = lossyScale.z;
			else rowScale = lossyScale.y;
				
			for (int row = 0; row < 4; row++)
			{	
				switch (row) 
				{
					case 0: rowPos = 0f; break;
					case 1: rowPos = edgeMargin / rowScale; break;
					case 2: rowPos = 1f - edgeMargin / rowScale; break;
					case 3: rowPos = 1f; break;
				}
				
				// Rows on Faces 0 (Bottom) and 1 (Top) operate on Z axis, while other faces Rows operate on Y axis
				if (face < 2)
					vertPos.z = rowPos;
				else
					vertPos.y = rowPos;
				
				
				// COLUMNS
				float columnPos = 0f;
				float columnScale;
				if (face < 4) 
					columnScale = lossyScale.x;
				else 
					columnScale = lossyScale.z;

				for (int column = 0; column < 4; ++column)
				{
					switch(column)
					{
						case 0: columnPos = 0f; break;
						case 1: columnPos = edgeMargin / columnScale; break;
						case 2: columnPos = 1f - edgeMargin / columnScale; break;
						case 3: columnPos = 1f; break;
					}	
					
					// Back and Left faces are reversed
					if(face == 3 || face == 4)
						columnPos = 1f - columnPos;
						
					if(face < 4)
						vertPos.x = columnPos;
					else
						vertPos.z = columnPos;
						
					VertexList.Add(vertPos);	
				}
			}
		}
	}
	
	
	void CalculateUV()
	{
		// face order: bottom, top, front, back, left, right
		for(int face=0; face<6; ++face)
		{
			Vector2 uvPos = Vector2.zero;
			
			// ROWS	
			// let's just put all the UVs for the bottom face in the middle of the side-texture
			if(face == 0)
				uvPos.y = 0.5f * 0.5f;
			// top face's texture coordinates start at half vertical-height of texture		
			if(face == 1)
				uvPos.y = 0.5f;
	
			for (int row = 0; row < 4; row++)
			{
				if(face != 0) {
					if(row == 2)
						uvPos.y += vHeight*2f;
					else if(row != 0)
						uvPos.y += vHeight;
				}
				
				
				// COLUMNS
				uvPos.x = 0f;
				for (int column = 0; column < 4; ++column)
				{
					if(face != 0) {
						if(column == 2)
							uvPos.x += uWidth*2f;
						else if(column != 0)
							uvPos.x += uWidth;
					}
						
					UV_List.Add(uvPos);	
				}
			}
		}
	}
	
	
	void CalculateTriangle()
	{
		int prevIteration = 0;
		
		// face order: bottom, top, front, back, left, right
		for(int face=0; face<6; ++face)
		{
			// triangle cache
			int[] quad = new int[6];
			
			for(int row=0; row<3; ++row)
			{
				for(int column=0; column<3; ++column)
				{
					// calculation for generating triangle based upon vertex order
					int triCalc = prevIteration + (row * 4) + column;
					
					for(int i=0; i<quad.Length; ++i)
					{
						switch(i){
							case 0: quad[0] = triCalc; break;
							case 1: quad[1] = triCalc + 4 + 1; break;
							case 2: quad[2] = triCalc + 4; break;
							
							case 3: quad[3] = triCalc; break;
							case 4: quad[4] = triCalc + 1; break;
							case 5: quad[5] = triCalc + 4 + 1; break;
						}
					}
					
					TriangleList.Add (quad[0]);
					TriangleList.Add (quad[2]);
					TriangleList.Add (quad[1]);
					TriangleList.Add (quad[3]);
					TriangleList.Add (quad[5]);
					TriangleList.Add (quad[4]);
				}
			}
			
			prevIteration += 4 * 4;
		}
	}
}


C#: Voxel-on-Voxel Destruction
// ADD THIS TO QB IMPORTED OBJECT, NEXT TO VOLUME SCRIPT
// ADD MESH COLLIDER TO CHUNK VIA VOLUME SCRIPT
[RequireComponent(typeof(Rigidbody))]
public sealed class CuttableMatrix : MonoBehaviour
{
	public Volume volume;
    private Frame currentFrame;
    
    public Color32 damageColor;
	
    private Vector3 impactVelocity;

	private float voxelSize;
	private Vector3 voxelCenter;
	private float voxelParticleSize;
	private int xSize;
    private int ySize;
    private int zSize;

    void Awake()
    {
        volume = GetComponent();
        currentFrame = volume.GetCurrentFrame();
        
        voxelSize = volume.VoxelSize;
		voxelCenter =  Vector3.one*(voxelSize/2f);
        voxelParticleSize = voxelSize*0.75f;
									   
		xSize = currentFrame.XSize;
        ySize = currentFrame.YSize;
        zSize = currentFrame.ZSize;		
		
		cuttableArrayExtents[0] = new int[3];
        cuttableArrayExtents[1] = new int[3]; 
    }
	
	
	/////////
	// CUTTING EVENT
	/////////

	private bool cutSomething;

	private Voxel[] cuttableVoxelArray;
	private Voxel[] cuttingVoxelArray;
	private Frame cuttingFrame;
	
	private int voxelArrayPosition;
	private PicaVoxelPoint voxelPointPos;
	private Vector3 voxelWorldPosition;
	
	Matrix4x4 cuttableMatrixTransInverse = Matrix4x4.identity;
	Matrix4x4 cuttableMatrixTrans = Matrix4x4.identity;
	Matrix4x4 cuttingMatrixTrans = Matrix4x4.identity;
	
    public void cutVolume(Volume inVolume, Vector3 impactVelocity_World)
    {
		cutSomething = false;
	
		impactVelocity = impactVelocity_World;
		
		cuttableVoxelArray = currentFrame.Voxels;
		cuttingFrame = inVolume.GetCurrentFrame();
		cuttingVoxelArray = cuttingFrame.Voxels;
		
		cuttableMatrixTransInverse = currentFrame.transform.worldToLocalMatrix;
		cuttableMatrixTrans = currentFrame.transform.localToWorldMatrix;
		cuttingMatrixTrans = cuttingFrame.transform.localToWorldMatrix;
		
		DefineArrays();
		
		ArrayBoolean();
		
		for(int z=booleanArrayExtentsMin[2]; z -1 && voxelPointPos.Y > -1 && voxelPointPos.Z > -1 && voxelPointPos.X < xSize && voxelPointPos.Y < ySize && voxelPointPos.Z < zSize)
						{
							voxelArrayPosition = GetVoxelArrayPositionFromPoint(voxelPointPos.X,voxelPointPos.Y,voxelPointPos.Z); 
							if(cuttableVoxelArray[voxelArrayPosition].Active)
							{
								ColorAdjacentVoxels(voxelPointPos.X, voxelPointPos.Y, voxelPointPos.Z);
								cuttableVoxelArray[voxelArrayPosition].State = VoxelState.Hidden;
								SpawnParticles();

								// we found a voxel to cut!
								cutSomething = true;
							}
						}
					}
				}
			}
		}
		// update in batches
		if(cutSomething) {
			currentFrame.Voxels = cuttableVoxelArray;
			currentFrame.UpdateAllChunksNextFrame();
		}
		else
			impactEvent(false);
	}
	
	
	private PicaVoxelPoint[] cuttableBounds_inCuttingSpace = new PicaVoxelPoint[2];
    private int[][] cuttableArrayExtents = new int[2][];
    private int[] cuttingArrayExtents = new int[3];
    private int[] booleanArrayExtentsMin = new int[3];
    private int[] booleanArrayExtentsMax = new int[3];
	
	void DefineArrays()
    {
		cuttableBounds_inCuttingSpace[0] = cuttingFrame.GetVoxelArrayPosition( 
											 GetVoxelApproximateWorldPosition(0, 0, 0) );
        cuttableBounds_inCuttingSpace[1] = cuttingFrame.GetVoxelArrayPosition(
											 GetVoxelApproximateWorldPosition(xSize, ySize, zSize) );
		for(int i=0; i<2; ++i)
        {
            cuttableArrayExtents[i][0] = cuttableBounds_inCuttingSpace[i].X;
            cuttableArrayExtents[i][1] = cuttableBounds_inCuttingSpace[i].Y;
            cuttableArrayExtents[i][2] = cuttableBounds_inCuttingSpace[i].Z;
        }
		
		cuttingArrayExtents[0] = cuttingFrame.XSize;
        cuttingArrayExtents[1] = cuttingFrame.YSize;
        cuttingArrayExtents[2] = cuttingFrame.ZSize;

        for(int i=0; i<3; ++i) {
			booleanArrayExtentsMin[i]=cuttingArrayExtents[i];
            booleanArrayExtentsMax[i]=0;
		}
	}
	
	void ArrayBoolean()
    {
        // get smallest and largest values 
        for(int p=0; p<2; ++p)
        {
            for(int i=0; i<3; ++i)
            {
                // get smallest values of cuttable array extents
                booleanArrayExtentsMin[i] = Mathf.Min(booleanArrayExtentsMin[i],cuttableArrayExtents[p][i]);
                // get largest values of cuttable array extents
                booleanArrayExtentsMax[i] = Mathf.Max(booleanArrayExtentsMax[i],cuttableArrayExtents[p][i]);
            }
        }

        // clamp smallest and largest values
        for(int i=0; i<3; ++i)
        {   
                // clamp smallest values to 0
                booleanArrayExtentsMin[i] = Mathf.Max(booleanArrayExtentsMin[i],0);
                // clamp largest values to cutting array
                booleanArrayExtentsMax[i] = Mathf.Min(booleanArrayExtentsMax[i],cuttingArrayExtents[i]);
        }
    }
	
	private const int adjacentVoxelsLength = 18;
    private int[] adjacentVoxels = new int[18];
    private int[] adjacentMath = new int[] 
    {
        -1, 0,  0,
        1,  0,  0,
        0, -1,  0,
        0,  1,  0,
        0,  0, -1,
        0,  0,  1
    };
    private void ColorAdjacentVoxels(int x, int y, int z)
    {
        // Get adjacent voxel positions
        for(int i=0; i -1 && adjacentVoxels[v+1] > -1 && adjacentVoxels[v+2] > -1 
            && adjacentVoxels[v] < xSize && adjacentVoxels[v+1] < ySize && adjacentVoxels[v+2] < zSize)
            {   
                // GetVoxelArrayPositionFromPoint
                int index = adjacentVoxels[v] + xSize * (adjacentVoxels[v+1] + ySize * adjacentVoxels[v+2]);
                cuttableVoxelArray[ index ].Color = damageColor;
            }
        }
    }
	
	private void SpawnParticles()
    {
        //////// Let's make pieces fly off where it got sliced
        // Get Velocity Direction of voxels to fly off

        //directionFromVolumeCenter = (voxelWorldPosition - volumeWorldCenter).normalized;

        if(UnityEngine.Random.Range(0f, 3f) < 1f)
            VoxelParticleSystem.Instance.SpawnSingle( voxelWorldPosition, 
                                                      damageColor, 
                                                      voxelParticleSize, 
                                                      impactVelocity*3f + Random.onUnitSphere);
    }
	
	
	Vector3 localVoxelPos = Vector3.zero;
	PicaVoxelPoint localVoxelPoint = new PicaVoxelPoint(0,0,0);
	PicaVoxelPoint GetVoxelPointPosition(Vector3 worldPos)
	{
		//localVoxelPos = currentFrame.transform.InverseTransformPoint(worldPos);
		localVoxelPos = cuttableMatrixTransInverse.MultiplyPoint3x4(worldPos);
		localVoxelPoint.X = (int)(localVoxelPos.x / voxelSize);
		localVoxelPoint.Y = (int)(localVoxelPos.y / voxelSize);
		localVoxelPoint.Z = (int)(localVoxelPos.z / voxelSize);
		return localVoxelPoint;
	}
	
	Vector3 GetVoxelWorldPosition(int x, int y, int z)
    {
		localVoxelPos.x = x * voxelSize;
		localVoxelPos.y = y * voxelSize;
		localVoxelPos.z = z * voxelSize;
		localVoxelPos += voxelCenter;
        return cuttingMatrixTrans.MultiplyPoint3x4(localVoxelPos);
		//return frame.transform.TransformPoint(funcVoxelWorldPos);
    }
	Vector3 GetVoxelApproximateWorldPosition(int x, int y, int z)
    {
        return cuttableMatrixTrans.MultiplyPoint3x4(new Vector3(x * voxelSize, y * voxelSize, z * voxelSize));
		//return currentFrame.transform.TransformPoint(new Vector3(x * voxelSize, y * voxelSize, z * voxelSize));
    }
	
	private int GetVoxelArrayPositionFromPoint(PicaVoxelPoint point, Frame frame)
    {
        return point.X + frame.XSize * (point.Y + frame.YSize * point.Z);
    }
    private int GetVoxelArrayPositionFromPoint(int x, int y, int z, Frame frame)
    {
        return x + frame.XSize * (y + frame.YSize * z);
    }
    private int GetVoxelArrayPositionFromPoint(int x, int y, int z)
    {
        return x + xSize * (y + ySize * z);
    }
SHADERS
CG Shader: Horizon-Based Ambient Occlusion
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

[ExecuteInEditMode]
public class SSAO_Cavity_CameraEffect : MonoBehaviour
{
	public bool resetMaterial;
	private bool setupSuccess;
	
	public Shader shader;

	private Material material;
	private Texture2D noiseTexture;
	
	private Camera cam;

	void Awake()
	{
		SetupMaterial();
	}
	
    void OnValidate()
	{
		if(resetMaterial)
		{
			DestroyImmediate(material);
			material = null;
			resetMaterial = false;
		}
		SetupMaterial();
	}
	
	private float AOradiusToScreen;
	private float edgeRadiusToScreen;
	private float negInvRadius2;
	private Vector2 uvToView;
	private Vector2 texelSize;
	void SetupMaterial()
	{
		if(shader == null)
			setupSuccess = false;
			
		else if(material == null && shader != null)
		{
			this.material = new Material(shader);
			material.hideFlags = HideFlags.HideAndDontSave;
			
			CreateNoiseTexture();
			material.SetTexture("_NoiseTex", noiseTexture);
		} 	
		if(cam == null)
		{
			cam = Camera.main;
			cam.depthTextureMode |= DepthTextureMode.DepthNormals;
		}
		if(material != null)
		{
			float camWidth = cam.pixelWidth;
            float camHeight = cam.pixelHeight;
			
			// this gets half of the length of the farplane of the camera
			// according to a normalized triangle, ie. farplane distance = 1
			float tanHalfFovY = Mathf.Tan(0.5f * cam.fieldOfView * Mathf.Deg2Rad);
			// get X length of farplane by multiplying by the ratio of width/height of camera
			float tanHalfFovX = tanHalfFovY * (camWidth / camHeight);
			// expand the length of the farplane to it's full length by using *2
			uvToView = new Vector4(2.0f * tanHalfFovX, -2.0f * tanHalfFovY);
			
			// this calculates the X delta distance between pixels along the farplane
			float projScale = camWidth / tanHalfFovX;
			
			// *0.5f seems unnecessary here
			AOradiusToScreen = radiusPixels * 0.5f * projScale;
			negInvRadius2 = -1.0f / (radiusPixels * radiusPixels);
			edgeRadiusToScreen = edgeRadius * 0.5f * projScale;
			
			texelSize = new Vector4(1f / camWidth, 1f / camHeight);

			setupSuccess = true;
		}
	}
	
	[SerializeField] private bool debug = false;
	[Range(0.3f, 5f)][SerializeField] private float radiusPixels = 1.2f;
	[Range(0f, 4f)][SerializeField] private float AO_Intensity = 1f;
	[Range(0.3f, 1f)][SerializeField] private float feathering = 0.85f;
	[Range(1f, 6f)][SerializeField] private float cavityRadius = 3f;
	[Range(0f, 4f)][SerializeField] private float cavityIntensity = 0.325f;
	[SerializeField] private bool edges = false;
	[Range(0f, 1f)][SerializeField] private float edgeRadius = 0.12f;
	[Range(0f, 4f)][SerializeField] private float edgeIntensity = 0.44f;
	[Range(0f, 20f)][SerializeField] private float wireRadius = 2f;
	[Range(0f, 1f)][SerializeField] private float wireIntensity = 0.073f;
	void OnRenderImage(RenderTexture source, RenderTexture destination) 
	{
		if(setupSuccess)
		{		
			int screenWidth = Screen.width;
			int screenHeight = Screen.height;
			
			material.SetFloat("_AOIntensity", AO_Intensity);
			material.SetFloat("_Feathering", feathering);
			
			material.SetFloat("_RadiusToScreen", AOradiusToScreen);
			material.SetFloat("_NegInvRadius2", negInvRadius2);
			
			material.SetVector("_UVtoView", uvToView);
			material.SetVector("_TexelSize", texelSize);
			
			//ao
			var tmp = RenderTexture.GetTemporary(screenWidth, screenHeight);
			Graphics.Blit(source, tmp, material, 0);
			material.SetTexture("_OcclusionTex", tmp);		
			
			//cavity
			var tmp1 = RenderTexture.GetTemporary(screenWidth, screenHeight);
			material.SetFloat("_CavityIntensity", cavityIntensity);
			material.SetFloat("_CavityRadius", cavityRadius);
			Graphics.Blit(source, tmp1, material, 1);
			material.SetTexture("_CavityTex", tmp1);
					
			//edges
			material.SetFloat("_Edges", edges ? 1.0f : 0.0f);
			var tmp2 = RenderTexture.GetTemporary(screenWidth, screenHeight);
			var tmp3 = RenderTexture.GetTemporary(screenWidth, screenHeight); 
			if(edges) {
				material.SetFloat("_EdgeIntensity", edgeIntensity);
				material.SetFloat("_EdgeRadius", edgeRadiusToScreen);
				Graphics.Blit(source, tmp2, material, 2);
				material.SetTexture("_EdgesTex", tmp2);
				
				//wireframe
				material.SetFloat("_WireIntensity", wireIntensity);
				material.SetFloat("_WireRadius", wireRadius);
				Graphics.Blit(source, tmp3, material, 3);
				material.SetTexture("_WireTex", tmp3);
			}
			
			//blur
			var tmp4 = RenderTexture.GetTemporary(Screen.width, Screen.height);
			Graphics.Blit(source, tmp4, material, 4);
			material.SetTexture("_OcclusionTex", tmp4);	
			
			//composite
			if(!debug)
				Graphics.Blit(source, destination, material, 5);
			else 
				Graphics.Blit(source, destination, material, 6);
				
			RenderTexture.ReleaseTemporary(tmp);
			RenderTexture.ReleaseTemporary(tmp1);
			RenderTexture.ReleaseTemporary(tmp2);
			RenderTexture.ReleaseTemporary(tmp3);
			RenderTexture.ReleaseTemporary(tmp4);
		}
		else
			Debug.Log("Setup Failed, please Reset Material with the NoiseTex and Shader");
	}
	
	private static float[] MersenneTwister = new float[] {	
		0.795755f, 0.581869f, 0.970863f, 0.770839f, 0.704620f, 0.245404f, 0.905371f, 0.772047f, 0.893339f, 0.734592f, 0.157900f, 0.310195f, 
		
		0.013235f, 0.520863f, 0.253941f, 0.623978f, 0.330538f, 0.115472f, 0.611865f, 0.413736f, 0.019519f, 0.062350f, 0.259302f, 0.127570f,
		
		0.911142f, 0.761439f, 0.673785f, 0.379541f, 0.732038f, 0.974906f, 0.384873f, 0.776879f, 0.956725f, 0.645520f, 0.708315f, 0.583199f, 
		
		0.236644f, 0.992380f, 0.981091f, 0.619804f, 0.810866f, 0.360499f, 0.361497f, 0.557862f, 0.839955f, 0.132871f, 0.417807f, 0.220779f, 
		
		0.730747f, 0.076690f, 0.408562f, 0.660104f, 0.428921f, 0.311342f, 0.587871f, 0.206406f, 0.137980f, 0.620309f, 0.462196f, 0.219485f, 
		
		0.835646f, 0.795892f, 0.044437f, 0.891731f, 0.501241f, 0.229229f, 0.899218f, 0.707733f, 0.415115f, 0.175218f, 0.757357f, 0.568868f,
	};
	
	private void CreateNoiseTexture()
    {
		noiseTexture = new Texture2D(4,4, TextureFormat.RGB24, false, true);
		noiseTexture.filterMode = FilterMode.Point;
        noiseTexture.wrapMode = TextureWrapMode.Repeat;
		
		int z = 0;
		for(int y=-1; y<5; ++y)
			for(int x=-1; x<5; ++x){
				float r = MersenneTwister[z++];
				float g = MersenneTwister[z++];
				Color noiseCol = new Color(r,g,0f);
				noiseTexture.SetPixel(x, y, noiseCol);
			}
			
		noiseTexture.Apply();
	}
}





///// HLSL SHADER 

		Pass
        {
			Name "HBAO - Occlusion"
		
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }
			
			sampler2D _NoiseTex;
			sampler2D_float _CameraDepthTexture;
			
			float2 _UVtoView;
			float2 _TexelSize;
			float _Feathering;
			float _NegInvRadius2;
			float _RadiusToScreen;
			
			inline float FetchDepth(float2 uv)
			{
				return LinearEyeDepth( SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv) );
			}
			
			inline float3 FetchViewPos(float2 uv)
			{
				float depth = FetchDepth(uv);
				uv = uv * _UVtoView;
				return float3((uv) * depth, depth); 
			}   //orthographic, float3((uv)*float2(1,depth),depth)
			
			
			inline float3 MinDiff(float3 P, float3 Pr, float3 Pl)
			{
				float3 V1 = Pr - P;
				float3 V2 = P - Pl;
				return (dot(V1,V1) < dot(V2,V2)) ? V1 : V2;
			}
			
			float3 ReconstructNormal(float2 uv, float2 texelDelta, float3 P)
			{
				float3 Pr = FetchViewPos(uv + float2(texelDelta.x, 0));
				float3 Pl = FetchViewPos(uv + float2(-texelDelta.x, 0));
				float3 Pt = FetchViewPos(uv + float2(0, texelDelta.y));
				float3 Pb = FetchViewPos(uv + float2(0, -texelDelta.y));
				return normalize( cross( MinDiff(P,Pr,Pl), MinDiff(P,Pt,Pb) ) );
			}
			
			
			inline float Falloff(float distanceSquare) {
				// 1 scalar mad instruction
				return distanceSquare * _NegInvRadius2 + 1.0;
			}
			
			//-----------------------------------------------
			// P = view-space position at the kernel center
			// N = view-space normal at the kernel center
			// S = view-space position of the current sample
			//-----------------------------------------------
			float ComputeAO(float3 P, float3 N, float3 S)
			{
				float3 V = S - P;
				float VdotV = dot(V, V);
				float NdotV = dot(N, V) * 1.0/sqrt(VdotV);

				// Use saturate(x) instead of max(x,0.f) because that is faster on Kepler
				//angleBias of 0.05
				return saturate(NdotV - 0.05) * saturate(Falloff(VdotV));
			}
			
			float ComputeCoarseAO(float2 uv, float radius, float2 rand, float3 P, float3 N)
			{
				const float2 vec[8] = { float2(1,0),
										float2(0.5f,0.5f),
										float2(0,1),
										float2(-0.5f,0.5f),
										float2(-1,0),
										float2(-0.5f,-0.5f),
										float2(0,-1),
										float2(0.5f,-0.5f) 
									  };
				
				float stepSizePixels = radius / (8 + 1);

				float AO = 0;
				
				UNITY_UNROLL
				for(uint d=0; d<8; ++d) //numDirections
				{	
					// Compute normalized 2D direction
					float2 direction = reflect(vec[d], rand) * _Feathering;
					
					// Jitter starting sample within the first step
        			float rayPixels = (rand.x * stepSizePixels + 1.0);
					
					for(uint s=0; s<4; ++s) //numSteps
					{
						float2 snappedUV = round(rayPixels * direction) * _TexelSize + uv;
						float3 S = FetchViewPos(snappedUV);
						
						rayPixels += stepSizePixels;
						
						AO += ComputeAO(P, N, S);
					}
				}
				
				return AO/(8*4);
			}
			
            fixed4 frag (v2f i) : SV_Target
            {
				float3 P = FetchViewPos(i.uv);
				float3 N = ReconstructNormal(i.uv, _TexelSize, P);
				
				// Compute projection of disk of radius into screen space
  				float radius = _RadiusToScreen / P.z;
				
				// angle, jitter
				float2 rand = normalize(tex2D(_NoiseTex, i.vertex*0.25).xy ); 
				
				float AO = ComputeCoarseAO(i.uv, radius, rand, P, N);

                return fixed4(AO.xxx, 1);
            }
            ENDCG
        }

CG Shader: 2D Isometric Water

Shader "Unlit/waterShader_2"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
		_Color("Color", Color) = (0.85,0.875,0.83,0.93)
		_DistortTex ("Distort Texture", 2D) = "white" {}
		_WaveReflectAlpha ("Wave Reflection Alpha", Range(0,0.1)) = 0.03
		_WaveCrestAlpha ("Wave Crest Alpha", Range(0,10)) = 4.39
		_WaveOne_UVScale ("Wave 1 UV Scale", Float) = 1
		_WaveTwo_UVScale ("Wave 2 UV Scale", Float) = 0.75
		_WaveOne_ScrollSpeedSine("Wave 1 ScrollSpeed Sine", Float) = 1
		_WaveOne_ScrollSpeedX("Wave 1 ScrollSpeed X", Float) = 2
		_WaveOne_ScrollSpeedY("Wave 1 ScrollSpeed Y", Float) = 1
		_WaveTwo_ScrollSpeedSine("Wave 2 ScrollSpeed Sine", Float) = 1
		_WaveTwo_ScrollSpeedX("Wave 2 ScrollSpeed X", Float) = -3
		_WaveTwo_ScrollSpeedY("Wave 2 ScrollSpeed Y", Float) = -2
		
		_Speed("Displace Speed", Range(0.01, 1)) = 0.353
		_Frequency("Displace Frequency", Range(2,100)) = 89.1
		_ResolutionX("Resolution X", Range(1,2)) = 1.789
		_ResolutionY("Resolution Y", Range(1,2)) = 1.694
		_AmountX("Displace Amount X", Range(0,0.1)) = 0.0067
		_AmountY("Displace Amount Y", Range(0,0.1)) = 0.0044
    }
    SubShader
    {
        Tags { 
        "Queue" = "Transparent+1" 
        "IgnoreProjector"="True" 
        "RenderType"="Transparent" 
        }
        LOD 100
        Cull Off
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha
		
		GrabPass{
            "_BackgroundTex"
        }

        Pass
        {
			Stencil
			{
				Ref 5
				Comp Always
				Pass Replace
			}
		
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
				float2 uv_dis : TEXCOORD2;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
				float4 grabUV: TEXCOORD1;
				float2 uv_dis: TEXCOORD2;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
			sampler2D _DistortTex;
            float4 _DistortTex_ST;
			sampler2D _BackgroundTex;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				o.uv_dis = TRANSFORM_TEX(v.uv_dis, _DistortTex);
				
				o.grabUV = ComputeGrabScreenPos(o.vertex);

                return o;
            }
			
			fixed4 _Color;
			float _WaveReflectAlpha;
			float _WaveCrestAlpha;

			float _WaveOne_UVScale;
			float _WaveTwo_UVScale;
			float _WaveOne_ScrollSpeedSine;
			float _WaveOne_ScrollSpeedX;
			float _WaveOne_ScrollSpeedY;
			float _WaveTwo_ScrollSpeedSine;
			float _WaveTwo_ScrollSpeedX;
			float _WaveTwo_ScrollSpeedY;
			
			float _Speed;
			float _Frequency;
			float _ResolutionX;
			float _ResolutionY;
			float _AmountX;
			float _AmountY;

            fixed4 frag (v2f i) : SV_Target
            {
				// wave distortion for textures
				fixed displaceWaveX = floor(sin((i.uv.x + _Time*_Speed) * _Frequency) * _ResolutionX)/_ResolutionX;
				fixed displaceWaveY = floor(sin((i.uv.y + _Time*_Speed) * _Frequency) * _ResolutionY)/_ResolutionY;
				float2 displaceUV = float2(displaceWaveY * _AmountY, displaceWaveX * _AmountX);
			
                fixed4 tileMap = tex2D(_MainTex, i.uv + displaceUV);
				
				//scroll textures
				fixed2 scrollUVone = i.uv_dis;
				fixed scrollX = _WaveOne_ScrollSpeedX * sin(_Time * _WaveOne_ScrollSpeedSine);
				fixed scrollY = _WaveOne_ScrollSpeedY * sin(_Time * _WaveOne_ScrollSpeedSine);
				scrollUVone += fixed2( scrollX, scrollY );
				fixed4 waveOne = tex2D(_DistortTex, scrollUVone*_WaveOne_UVScale + fixed2(displaceUV.x*_DistortTex_ST.x, displaceUV.y*_DistortTex_ST.y));
				
				fixed2 scrollUVtwo = i.uv_dis;
				scrollX = _WaveTwo_ScrollSpeedX * sin(_Time * _WaveTwo_ScrollSpeedSine);
				scrollY = _WaveTwo_ScrollSpeedY * sin(_Time * _WaveTwo_ScrollSpeedSine);
				scrollUVtwo += fixed2( scrollX, scrollY );
				fixed4 waveTwo = tex2D(_DistortTex, scrollUVtwo*_WaveTwo_UVScale + fixed2(displaceUV.x*_DistortTex_ST.x, displaceUV.y*_DistortTex_ST.y));
				
				//tileMap.r+waveOne.r accentuates the edges with the wave pattern
				//*tileMap.a isolates the edges
				//*waveOne.r isolates the wave pattern along the edges
				waveOne = (tileMap.r * _WaveCrestAlpha + waveOne.r) * tileMap.a * waveOne.r;
				
				// Grab Pass background
				fixed4 grabUV = i.grabUV + fixed4(displaceUV.x, displaceUV.y, displaceUV.x, displaceUV.y);
				fixed4 backCol = tex2Dproj(_BackgroundTex, UNITY_PROJ_COORD(grabUV));
				
				
				//waveOne - (waveTwo-tileMap.r) removes the edges from WaveTwo pattern, and removes this resulting pattern from waveOne
				fixed4 finCol = ( waveOne - (waveTwo-tileMap.r) ) * _WaveReflectAlpha;
				
				finCol += backCol;
				
				finCol.a = finCol.a * tileMap.a;
				
				if (finCol.a<0.5) discard; 
				
                return finCol * _Color;
            }
            ENDCG
        }
    }
}
CG Shader: Dissolve to ashes
Shader "Custom/Dissolve" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _SliceAmount("Slice Amount", Range(0.0, 1.0)) = 0
		
		_BWAmount("Black White LERP", Range(0.0, 1.0)) = 0
 
        _BurnSize("Burn Size", Range(0.0, 1.0)) = 0.15
        _BurnRamp("Burn Ramp (RGB)", 2D) = "white" {}
        _BurnColor("Burn Color", Color) = (1,1,1,1)
 
        _EmissionAmount("Emission amount", float) = 2.0
		
		_PerlinOctaves ("Perlin Octaves", Range(1,1)) = 1
		_PerlinPeriod ("Perlin Period", Range(1,13)) = 1
		_PerlinBrightness ("Perlin Brightness", Range(0,0.6)) = 0.6
		_PerlinContrast  ("Perlin Contrast", Range(0,5)) = 5

		_WorleyOctaves ("Worley Octaves", Range(4,4)) = 4 //2 thru 6?
		_WorleyPeriod ("Worley Period", Range(9,13)) = 10
		_WorleyBrightness ("Worley Brightness", Float) = 1.2
		_WorleyContrast ("Worley Contrast", Float) = 1
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
        Cull Off
        CGPROGRAM
        #pragma surface surf Lambert addshadow
        #pragma target 3.0
		
		#include "CloudNoiseLib.cginc"
 
        fixed4 _Color;
        sampler2D _MainTex;
        sampler2D _SliceGuide;
        sampler2D _BumpMap;
        sampler2D _BurnRamp;
        fixed4 _BurnColor;
        float _BurnSize;
        float _SliceAmount;
        float _EmissionAmount;
		
		float _BWAmount;
		
		int _PerlinOctaves;
		int _PerlinPeriod;
		float _PerlinBrightness;
 		float _PerlinContrast;
		
		int _WorleyOctaves;
		int _WorleyPeriod;
		float _WorleyBrightness;
		float _WorleyContrast;
 
        struct Input {
            float2 uv_MainTex;
        };
		
 
 		float fbm_perlin (float3 st, int octaves, int rep)
		{
			// Initial values
			float value 	= 0;
			float amplitude = 0.5;
			float frequency = 0;

			for (int i = 0; i < octaves; i++)
			{
				value 		+= amplitude * pnoise(st, rep);
				rep			*= 2; // Modify the repetition instead of the texture coordinates
				amplitude 	*= 0.5;
			}

			return value * 0.5 + 0.5; // [-1, 1] -> [0, 1]
		}

		float fbm_worley (float3 st, int octaves, int rep)
		{
			// Initial values
			float value 	= 0;
			float amplitude = 0.5;
			float frequency = 0;

			for (int i = 0; i < octaves; i++)
			{
				value 		+= amplitude * (1 - worley(st, 1, false, rep).x);
				rep			*= 2; // Modify the repetition instead of the texture coordinates
				amplitude 	*= 0.5;
			}

			return value;
		}
 
 
        void surf (Input IN, inout SurfaceOutput o) {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
			fixed4 bw = tex2D (_MainTex, IN.uv_MainTex) * _Color;
			
			
			//resolution of noise
			fixed4 test = 0;
			float3 st = float3(floor(IN.uv_MainTex.x*128)/128, floor(IN.uv_MainTex.y*128)/128, 0);
			
			// Perlin
			float perlin = fbm_perlin(st, _PerlinOctaves, _PerlinPeriod);
			perlin = (perlin-0.5) * _PerlinContrast + 0.5;
			perlin += _PerlinBrightness - 1;

			// Worley
			float worley = fbm_worley(st, _WorleyOctaves, _WorleyPeriod);
			worley = (worley-0.5) * _WorleyContrast + 0.5;
			worley += _WorleyBrightness - 1;
			
			//frameRate
			float sliceAmount = floor(_SliceAmount*12)/12;
			
			//limit the colors in the noise
			test.r = floor((worley - perlin * (1-worley) - sliceAmount)*10)/10;
			
			
			//match luminance for black-white to viewer's eye perception
			float lum = bw.r*.3 + bw.g*.59 + bw.b*.11;
			bw.rgb = float3(lum*0.6,lum*0.6,lum*0.6);
			
			
			//clip blackwhite
			bw.a = 0;
			if(test.r > 0) {
				bw.a = 1;
			}
			
			//clip color
			c.a = 0;
            if(test.r < 0) {
				c.a = 1;
			}
			
			//lerp color to bw
			c.rgb = lerp(c.rgb, bw.rgb, _BWAmount);
			
			
			//clip(test);
            if (test.r < _BurnSize && _SliceAmount > 0 && test.r > 0) {
                o.Emission = tex2D(_BurnRamp, float2(test.r * (1 / _BurnSize), 0)) * _BurnColor * _EmissionAmount;
            }
 
            o.Albedo = c.rgb*c.a + bw.rgb*bw.a;
            o.Alpha = c.a + bw.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

CG Shader: Volumetric Fog

Shader "Custom/Volumetric_Shader" {
	Properties{
		_Color ("Color", Color) = (1.0, 0.639, 0, 0.7)
	}
	SubShader {
        Tags{ "RenderType" = "Transparent" "Queue" = "Transparent" "IgnoreProjector" = "True" "ForceNoShadowCasting" = "True"  "LightMode" = "ForwardBase" }
        ZWrite Off
        Lighting Off
        Blend SrcAlpha OneMinusSrcAlpha
        
        
        LOD 300
        
        Pass {
            Cull Back
            
			CGPROGRAM
			
            #pragma target 3.0
			#pragma vertex vert
			#pragma fragment frag
            #include "UnityCG.cginc"
			
            float4 _MainTex_ST;
			uniform float4 _Color;
	

			struct vertexInput {
				float4 vertex : POSITION;			
				float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
			};
			struct vertexOutput {
				float4 pos : SV_POSITION;
				float4 posWorld : TEXCOORD1;
                float3 normalDir : TEXCOORD2;
                float4 color : COLOR0;
                float2 uv : TEXCOORD0;
			}; 
			

			vertexOutput vert(vertexInput v) { //appdata_base
				vertexOutput o;
                UNITY_INITIALIZE_OUTPUT(vertexOutput, o);

				o.posWorld = mul(unity_ObjectToWorld, v.vertex);
                o.normalDir = normalize( mul( float4( v.normal, 0.0 ), unity_WorldToObject ).xyz );
				o.pos = UnityObjectToClipPos(v.vertex);

                o.uv = TRANSFORM_TEX (v.uv, _MainTex);
                o.color = float4 (1.0, 1.0, 1.0, 1);
				return o;

			}
		 

			float4 frag(vertexOutput i) : SV_Target{
                float3 normalDirection = i.normalDir;
				float3 viewDirection = normalize( _WorldSpaceCameraPos.xyz - i.posWorld.xyz );
				
                float3 rim = saturate( dot( viewDirection, normalDirection ) );
                
                return float4(_Color.rgb, (clamp(distance(_WorldSpaceCameraPos.xyz, i.posWorld.xyz)*0.005, 0.0, 1.0)) *_Color.a *rim.x);  
			}
			
			ENDCG	
		}	
        
        
        Pass {
            Cull Front
            
			CGPROGRAM
			
            #pragma target 3.0
			#pragma vertex vert
			#pragma fragment frag
            #include "UnityCG.cginc"
			
            float4 _MainTex_ST;
			uniform float4 _Color;
	

			struct vertexInput {
				float4 vertex : POSITION;			
				float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
			};
			struct vertexOutput {
				float4 pos : SV_POSITION;
				float4 posWorld : TEXCOORD1;
                float3 normalDir : TEXCOORD2;
                float4 color : COLOR0;
                float2 uv : TEXCOORD0;
			}; 
			

			vertexOutput vert(vertexInput v) { //appdata_base
				vertexOutput o;
                UNITY_INITIALIZE_OUTPUT(vertexOutput, o);
                v.normal.xyz = v.normal*-1;
				o.posWorld = mul(unity_ObjectToWorld, v.vertex);
                o.normalDir = normalize( mul( float4( v.normal, 0.0 ), unity_WorldToObject ).xyz );
				o.pos = UnityObjectToClipPos(v.vertex);
                
                o.uv = TRANSFORM_TEX (v.uv, _MainTex);
                o.color = float4 (1.0, 1.0, 1.0, 1);
				return o;

			}
		 

			float4 frag(vertexOutput i) : SV_Target{
                float3 normalDirection = i.normalDir;
				float3 viewDirection = normalize( _WorldSpaceCameraPos.xyz - i.posWorld.xyz );
				
                float3 rim = saturate( dot( viewDirection, normalDirection ) );
                
                return float4(_Color.rgb, (clamp(distance(_WorldSpaceCameraPos.xyz, i.posWorld.xyz)*0.005, 0.0, 1.0) ) *_Color.a *rim.x);  
			}
			
			ENDCG	
		}	
        
	} 
    
	FallBack "Unlit"
}