﻿///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Here are a few parameters that you can change to modify the behavior of the patterm
// Feel free to modify the values, don't change the variable names

modelParameters = {
    // Density of the covered pattern
    // For example,density of 1 places patters such that if they were aligned one next to another
    // they would cover all pixels. Keep in mind that since the patterns overlap, you will see gaps.
    density : 2,   // use a value between 0.1 to 10. The default is 2.
    
    // Maximum distance from a path 
    // If a path is selected the patterns are placed randomly around it, with a give maximum 
    // distance of the pattern ceter from the parth. The default is 20 pixels.
    maxDistanceFromPath : 20, // in pixels

    // Variation of color of the pattern. 
    // For example, value of 0.2 means that each of the red, green, and blue color components
    // will be multiplied by a DIFFERENT random value from interval 0.8 and 1.2. 
    // Set to 0 if you do not want to modify the pattern color.
    colorRandomness : 0.05,    // use a value between 0 and 1. The default is 0.05.

    // Variation of pattern brightness. 
    // For example, value of 0.6 means that each of the red, green, and blue color components
    // will be multiplied by THE SAME random value from interval 0.4 and 1..6 
    // Set to 0 if you do not want to modify the pattern brightness.
    brightnessRandomness : 0.1,   // use a value between 0 and 1. The default is 0.1.

    // You include random scaling of the patter
    // If you don't want any variation, set both to 1
    patternScaleFrom : 0.5,   // use a values between 0.1 and 3 . The default is 0.5.
    patternScaleTo : 1,          // use a values between 0.1 and 3 , larger or equal to patternScaleFrom. The default is 1.
     
    // Shoud we apply random rotation?
    applyRotation : true   // the default is true - we are doing rotations
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// You can modify the code below but keep in mind that as with any scripting
// you can break things. Keep a backup copy.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Get pattern 
var pattern = RenderAPI.getParameter(kpsPattern)

var inputPaths = RenderAPI.getParameter (kpsSelectedPaths)


// For the script to work with a menu with preview, you need to define a run method with the following three parameters:
// 1. api - render api to be used. Do not use RenderAPI directly, since preview uses a different renderer
// 2. parameter - contains model parameters. Do not refer directly to modelParameters inside the run method or methods called from it.
// 3. scale - preview can be scaled up or down depending on the size of the input pattern. Accomodate scale inside the run method.
function run (api, parameters, scale)
{
     // get the size of the output area  
    var outputSize = api.getParameter(kpsSize)
    var outputOrigin = api.getParameter(kpsOrigin)
 
    var patternSize = pattern.getParameter(kpsSize)

    // scale up if the patternsize is 1 to avoid long loop


    if (patternSize.x == 1 && patternSize.y == 1)
    {
        if (scale == 1)
            scale = 20
        patternSize *= scale
        parameters.applyRotation = false    // 1x1 patterns that are scaled up do not rotate gracefully
    }

    //api.translater(bbox.min[0], bbox.min[1])


    // possibly add spacing
    patternSize.x += 0
    patternSize.y += 0
    // place elements at random locations and size

    if (parameters == previewParameters)
        Engine.rand(1) // sets a fixed seed for preview
    else
        Engine.rand(Math.random()*214013) // random seed for a regular run

    var sizes = new Array()
    sizes.push (5)
    sizes.push (2)
    //sizes.push (1)
    //sizes.push (0.5)
 
    if (inputPaths.length > 0)
    {
        // adjust preview size
        var maxPatternSize = patternSize.x // x only
        
        if (parameters == previewParameters)
        {
            // set scale so that we show about 10-15 patterns
            if (maxPatternSize < previewSize / 15) 
                scale = previewSize / (15 * maxPatternSize)
            else if (maxPatternSize > previewSize / 10)
                scale = previewSize / (10 * maxPatternSize)
                
            if (parameters.maxDistanceFromPath * scale > previewSize / 4)
                scale = previewSize / (4 * parameters.maxDistanceFromPath)
        }
//alert ("scale = " + scale)
         var paths = inputPaths
         if (parameters == previewParameters)
        {
            paths = new Array(0)
            paths.push (new DecoGeometry)
            //paths[0].addLineStrip(new Vector3(0,0,0) + outputOrigin, new Vector3(255, 255, 0) + outputOrigin)
            paths[0].addBezier (new Vector3(10, (previewSize-10), 0) + outputOrigin, 
                                          new Vector3(10, 115,0 ) + outputOrigin, 
                                          new Vector3( (previewSize-10), 140, 0) + outputOrigin, 
                                          new Vector3( (previewSize-10), 10, 0) + outputOrigin)
        }
         
        for (var p = 0; p < paths.length; p++)
        {
            var geometry = paths[p]
            
            var length = geometry.getValue(kGetGeometryLength)
 
            // determine the number approximately based on size of the element and filled area
            var num = length * parameters.maxDistanceFromPath * 2 / (patternSize.x * patternSize.y * scale * scale) * parameters.density
            if (num < 1)
                num = 1

            for (var i = 0; i < num; i++)
            {
                api.pushMatrix()
                var r = Engine.rand()
                var w = parameters.maxDistanceFromPath * Math.pow(r, 0.5);
                
                var spanAlong = length
              
                var l = spanAlong * Engine.rand();
                var pt = geometry.getValue(kGetPointAlongGeometry, l)
                var normal = geometry.getValue(kGetNormalAlongGeometry, l)
                normal.normalize()
                if (Engine.rand() > 0.5)
                    w = -w;

                // place an element randomly along a circle of radius w
                var alpha = Engine.rand() * 2 * Math.PI
                var dx = Math.sin(alpha)
                var dy = Math.cos(alpha)
                pt.x += dx * w
                pt.y += dy * w
               
                api.translate(pt  - outputOrigin)
                
                var localScale = scale * (parameters.patternScaleFrom + (parameters.patternScaleTo - parameters.patternScaleFrom) * (1 - (Math.abs(w) / parameters.maxDistanceFromPath)) )

                api.scale(localScale, localScale)
                 
                if (parameters.applyRotation)
                {
                    var rotate = 360/30 * Math.floor(Engine.rand() * 30) // 30 distinct rotations
                    api.rotate(rotate)
                }
 
                //var seed =  Math.floor(l  *17 + p * 2531011) % 0x7fffffff   //7327)
 
                var rc = parameters.colorRandomness  // color randomness
                var br = 1 - parameters.brightnessRandomness + Engine.rand() * parameters.brightnessRandomness*2  // brightness
                api.Color (kFillColor, br *(1 - rc + Engine.rand()*rc*2), br*(1 - rc + Engine.rand()*rc*2), br*(1 - rc + Engine.rand()*rc*2))

                pattern.render(api)
                api.popMatrix()
            }
        }

        return // we are done
    }

    // to finetune the progress bar, specify how many pixels are covered by patterns
    api.setParameter(kpsPixelCoverage, parameters.density * 0.9 *
                              (parameters.patternScaleTo * parameters.patternScaleTo - parameters.patternScaleFrom * parameters.patternScaleFrom) / 3)


    // determine the number approximately based on size of the element and filled area
    var num = ((outputSize.x + patternSize.x) * (outputSize.y + patternSize.y)) / (patternSize.x * patternSize.y * scale *scale) * parameters.density
    if (num < 1)
        num = 1
 
    for (var n = 0; n < num; n++)
    {
        api.pushMatrix()
           
        var localScale = scale * (parameters.patternScaleFrom + (parameters.patternScaleTo - parameters.patternScaleFrom) * Engine.rand() )

        var spanx = outputSize.x + patternSize.x * localScale
        var spany = outputSize.y + patternSize.y * localScale
     
        var x =  -patternSize.x * localScale * 0.5 + spanx * Engine.rand()
        var y =  -patternSize.y * localScale * 0.5 + spany * Engine.rand()
            
        api.translate(x, y)
        api.scale(localScale, localScale)
         
        if (parameters.applyRotation)
        {
            var rotate = 360/30 * Math.floor(Engine.rand() * 30) // 30 distinct rotations
            api.rotate(rotate)
        }

        var rc = parameters.colorRandomness  // color randomness
        var br = 1 - parameters.brightnessRandomness + Engine.rand() * parameters.brightnessRandomness*2  // brightness
        api.Color (kFillColor, br *(1 - rc + Engine.rand()*rc*2), br*(1 - rc + Engine.rand()*rc*2), br*(1 - rc + Engine.rand()*rc*2))
        
        pattern.render(api)
        api.popMatrix()
    }
}


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Menu start
// If you want to create a menu in which you change some of the script parameters, include the following code:
// For shipped scripts we can include localized strings, prefixed with $$$/ - call method localize(string) on the prefixed string
// For your own strings, you can query app.locale and select several language versions (for example, your native language and english). Try alert ("Language is: " + app.locale)

var decoMenu = {    //  an object that defines the menu
   menuTitle : localize("$$$/DecoScripts/RandomFill/RandomFill=Random Fill"),
   menuBackground : [0.93, 0.93, 0.93, 1],
   previewBackground : [1, 1, 1, 1],
   panels : [
    { panelName : "", 
       leftColumnWidth : 180,
       unitsWidth : 65,
       panelMenu : [
         { itemName : localize("$$$/DecoScripts/RandomFill/Density=Density:"),  
             itemUnit : localize("$$$/DecoScripts/Units/pixels=pixels"), 
             itemType : 'slider', itemValue : modelParameters.density, itemMin : 0.1, itemMax : 10, itemStep : 0.1, varName : 'density'  },
         
         { itemName : localize("$$$/DecoScripts/RandomFill/PatternScaleFrom=Minimum scale factor:"),  
             itemUnit : '', 
             itemType : 'slider', itemValue : modelParameters.patternScaleFrom, itemMin : 0.1, itemMax : 3, itemStep : 0.01, itemLEQitem : 2, varName : 'patternScaleFrom'  },
         
         // you can link two sliders together by using itemLEQitem : item_index (less or equal to value in item item_index) 
         // and itemGEQitem: item_index (). This can be used when teh two slides control min and max range for some random parameter.
         { itemName : localize("$$$/DecoScripts/RandomFill/PatternScaleTo=Maximum scale factor:"),  
             itemUnit : '', 
             itemType : 'slider', itemValue : modelParameters.patternScaleTo, itemMin : 0.1, itemMax : 3, itemStep : 0.01, itemGEQitem : 1, varName : 'patternScaleTo'  },
         
         { itemName : localize("$$$/DecoScripts/RandomFill/MaxDistanceToPath=Max distance from path:"),  
             itemUnit : localize("$$$/DecoScripts/Units/pixels=pixels"), 
             itemType : 'slider', itemValue : modelParameters.maxDistanceFromPath, itemMin : 0, itemMax : 250, itemStep : 1, enabled: inputPaths.length > 0, varName : 'maxDistanceFromPath'  },
         
          { itemName : localize("$$$/DecoScripts/RandomFill/RotatePattern=Rotate pattern:"),
              itemUnit : '', itemType : 'checkbox', itemValue : modelParameters.applyRotation, itemMin : 0, itemMax : 0, varName : 'applyRotation'  },

          { itemName : localize("$$$/DecoScripts/ColorRandomness=Color randomness:"),  
             itemUnit : "",  itemType : 'slider', itemValue : modelParameters.colorRandomness, itemMin : 0, itemMax : 1, itemStep : 0.01, varName : 'colorRandomness'  }, 
         
          { itemName : localize("$$$/DecoScripts/BrightnessRandomness=Brightness randomness:"),  
             itemUnit : "",  itemType : 'slider', itemValue : modelParameters.brightnessRandomness, itemMin : 0, itemMax : 1, itemStep : 0.01, varName : 'brightnessRandomness'  }, 
         
       ] }
   ]  // end of panels
 }; // end of menu

// If livePreview is set to 1, the preview image is updated live. Note that due to limitations of scripted menus the update is slow and the flickering may be disturbing. 
livePreview = 0 // recommended value is 0

// Call Photoshop Script that creates the menu
Engine.evalFile ("_Deco Menu.jsx") 

// Menu finished
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


if (typeof skipRun == 'undefined' || !skipRun)  // run unles we exited the preview window without pressing a button
    run(RenderAPI, modelParameters, 1)

