////////////////////////////////////////////////////////////////////////////////
// Note from the Scriptographer.org Team
//
// In Scriptographer 2.9, we switched to a top-down coordinate system and
// degrees for angle units as an easier alternative to radians.
// 
// For backward compatibility we offer the possibility to still use the old
// bottom-up coordinate system and radians for angle units, by setting the two
// values bellow. Read more about this transition on our website:
// http://scriptographer.org/news/version-2.9.064-arrived/

script.coordinateSystem = 'bottom-up';
script.angleUnits = 'radians';

  /*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
  ///////////////////////////////////////////////////////////////////////////////////////////////
                                                                                                 
                                                          __                      __  _          
                             ______________ _      ______/ /___  ____ ___  ____ _/ /_(_)____     
----------------------------/ ___/ ___/ __ \ | /| / / __  / __ \/ __ `__ \/ __ `/ __/ / ___/     
---------------------------/ /__/ /  / /_/ / |/ |/ / /_/ / /_/ / / / / / / /_/ / /_/ / /__       
---------------------------\___/_/   \____/|__/|__/\__,_/\____/_/ /_/ /_/\__,_/\__/_/\___/.js    
                                                                                  0.2            
                                                                                                 
  \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
  ///////////////////////////////////////////////////////////////////////////////////////////////
  

  what is it:
  crowdomatic is a script for scriptographer 2.0 (http://scriptographer.com)
  
  what is it for:
  crowdomatic places a crowd of objects (crowd-objects) along a target-path (shape)
  and if desired all over the document too.
  
  how to use:
  step 1: select "crowdomatic_0.2.js" in scriptgrapher and press the play button. 
          a dialog appears. see diagram below for an explanation of the values.
  
  step 2: draw a shape you want to be crowded then click "assign shape(s)" from the dialog
  
  step 3: draw the objects you want to be your crowd / crowd-object(s)
          (these can be simple shapes or also groups... even textobjects work to some point)
          then select all the crowd objects and click assign "crowd-object(s)" from the dialog
  
  step 4: tune the values in the dialog to your liking (try and error, pls)
  
  step 5: click "do it" and enjoy the result or undo to change the values and try again.
          note, you don't have to reassign the shape or the crowd-objects unless you removed
          any of the crowd-objects.
  
  +---------------------------------------------------------------------------------------------+
  ¦  ________________________                                                                   ¦
  ¦ | crowdomatic config     |                                                                  ¦
  ¦ |                        |                                                                  ¦
  ¦ | rotate           []    | <- rotate the crowd-objects randomly                             ¦
  ¦ | scale            []    | <- scale the crowd-objects randomly between 100% 'scale max. %'  ¦
  ¦ | crowdify         []    | <- place the crowd-objects all over the document                 ¦
  ¦ | debug            []    | <- write addtitonal debug text to console                        ¦
  ¦ |                        |                                                                  ¦
  ¦ | scale max. %     [   ] | <- the maximum scale value (only if 'scale' is checked)          ¦
  ¦ | crowdyness       [   ] | <- how crowded the document should be (if 'crowdify' is checked) ¦
  ¦ | obj. density     [   ] | <- the higher the number the more objects will be places         ¦
  ¦ | point dist.      [   ] | <- the lower the numbet the closer together the objects are      ¦
  ¦ | obj-path offset  [   ] | <- distance to the target-shape, the higher the farther          ¦
  ¦ |                        |                                                                  ¦
  ¦ | assign crowd-object(s) | <- select the crowd-objects, then press this button              ¦
  ¦ |    assing shape(s)     | <- select the target-shapes, then press this button              ¦
  ¦ |       do it            | <- when both objects-types are assigned,                          ¦
  ¦ |________________________|    press this button to start the magic!                         ¦
  ¦                                                                                             ¦
  +---------------------------------------------------------------------------------------------+

  known bugs:
  - strange behaviour on cs2 with scales larger than 2000%

  \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
  ///////////////////////////////////////////////////////////////////////////////////////////////

  script created by:
  the mighty graf salamander (and his army of invisible squirrels)

  \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
  /////////////////////////////////////////////////////////////////////////////////////////////*/



// thisis
scriptName = "crowdomatic 0.2";

////*** at firstest, check if scriptographer version is compatible
// if(scriptographer.version)
// {
// 	version = parseFloat(scriptographer.version);
// 	revision = parseFloat(scriptographer.revision);
// 	if(version != 2)
// 	{
// 		print('this script is untested on your version of scriptographer.');
// 		print('this script is designed for scriptographer version 2.0');
// 		print('------');
// 		print('');
// 		print('');
// 	}else{
// 		print('[scriptographer version ok: '+version+'.'+revision + ']');
// 		print(scriptName +' sez o hai!');
// 		print('');
// 		print('');
// 	}
// 	
// }else{
// 	var alertMessage =  'you are using an unknown version of scriptographer, \nthis script will most likely not work for you.\nuse scriptographer 2.0 [http://scriptographer.com]';
// 	var dialog = new PopupDialog();
// 	dialog.alert(alertMessage);
// }

// debug output switch
debug=false;

// initial parameter values
crowdify = false;
rotate = false;
scale = false;
scalemax = 200;
offset = 8;
crowdyness = 1000;
density = 3;
pointDensity = 20;

// initialize a variable for the crowd-shape
dot = null;

// initialize an (empty) art-set for the crowded-zone-shapes
shapes = null;

// initialize global array for crowdpoints
crowdPoints = new Array();

// dialog is not open atm\
openDialog = false;
// first open the dialog
optionDialog(); // dialog definition at files bottom

// the mighty main function - does the crowd-science!!1!
function crowdomatic()
{
	dbug('starting crowdomatic function!');
	dbug('=========================');
	dbug('current values: ');
	dbug('crowdify = '+crowdify);
	dbug('scalemax = '+scalemax);
	dbug('rotate = '+rotate);
	dbug('offset = '+offset);
	dbug('crowdyness = '+crowdyness);
	dbug('density = '+density);
	dbug('pointDensity = '+pointDensity);
	
	
	// test of the crowd-object is existent and valid 
	try{ // try if we can get the crowd-object, if not catch the error
		for(var i=0;i<dot.length;i++)
		{
			var working = dot[i].isValid();
			if(working)
			{
				// object is valid... go on...
				dbug('the crowd-object is valid: '+dot[i]);
			}else{
				// object is not valid, most possibly deleted
				// send error to console...
				print('::error: one crowd-object is not valid, is it still existent?');
				return;
			}
		}
	}catch(e){ // in case of error send message to console and quit
		print('::error: there is no crowd-object, did you assign a crowd-object via dialog?');
		return;
	}
	
	// same stuff for the shapes, we test only the first object of the art-set
	try
	{
		var working = shapes[0].isValid();
		if(working)
		{
			dbug('the shapes are valid: '+shapes);
		}else{
			print('::error: shape values are not valid, are they still existent?');
			return;
		}
	}catch(e){
		print('::error: there are no shapes, did you assign a shape via dialog?');
		return;
	}
	
	dbug('shapes are now: '+shapes);
	dbug('crowd-objects are now: '+dot);
	
	// move the crowd-objects to the documents zero point
	// all calculcations and movements are based on the zero point...
	dbug('move the crowd-objects to the documents zero-point');
	for(var i=0;i<dot.length;i++)
	{
		var zeroPoint = new Point(0,0);
		var currentPos = dot[i].bounds.center;
		var move = zeroPoint - currentPos;
		dbug('move: '+move);
		dot[i].translate(move);
		
	}
	
	// array for paths of a group, will be used for ungrouping
	gPaths = new Array();
	// array for groups, will be used for ungrouping
	groupObjs = new Array();
	// reinitialize array of points
	crowdPoints = new Array();
	dbug('start iterating throug shapes');
	for(var i=0;i<shapes.length;i++)
	{
		var shape = shapes[i];
		if (shape != null) // ignore null-objects
		{
			if(shape.hasChildren()) // if the object has child elements, it's most likely a group
			{
				dbug('this shape seems to be a group: '+shape);
				dbug('recursive ungroup...');
				ungroupRecoursiveAndCtp(shape); // pass the group over to the ungroup function
			}
			else // if it is a normal path ...
			{
				dbug('this shape seems to be a path: '+shape);
				ctp(shape);
			}
		}
	}
	
	// if the crowdify option is true
	if(crowdify)
	{
		// add as many random points to the points-array as configured in 'crowdyness'
		dbug('start creating random points in the document.');
		dbug('current document.size: '+document.size);
		for(var i=0; i < crowdyness; i++)
		{
			var x = Math.random() * document.size.width;
			var y = Math.random() * document.size.height;
			var randomPoint = new Point(x,y);
			
			crowdPoints.push(randomPoint);
		}
	}else{
		dbug('crowdify option is not choosen');
	}
	
	// shuffle all the points
	fisherYates(crowdPoints);
	dbug('array of points shuffled... fun!');
	
	var finalGroup = new Group();
	
	dbug('start placing all the crowd-objects. count: '+crowdPoints.length);
	// iterate over every point from the point array
	for(var m=0;m<crowdPoints.length;m++)
	{
		var currentPoint = crowdPoints[m];
		
		var x = currentPoint.x;
		var y = currentPoint.y;
		// get one random dot object, but minus one to the lenght, cause 
		// the array index starts at 0 not at one...
		var rndDotNr = randomBetween(0, dot.length - 1);
		var dotClone = dot[rndDotNr].clone();
		dotClone.translate(new Point(x,y));
		
		// if rotate is true rotate the obj
		if(rotate)
		{
			var randAngleRange = (Math.random() * 10) * Math.PI; // get a random angle
			var dotCenterPoint = dotClone.bounds.center; // pick the centerpoint of the obj, to rotate around
			dotClone.rotate(randAngleRange,dotCenterPoint); // doit!
		}
		
		// if scale is true scale the obj
		if(scale)
		{
			var maxScaleFactor = (scalemax / 100);
			if(maxScaleFactor>=1)
				var randomScaleFactor = randomBetweenFloat(1,maxScaleFactor);
			else
				var randomScaleFactor = randomBetweenFloat(maxScaleFactor,1);
			dotClone.scale(randomScaleFactor, randomScaleFactor);
		}
		finalGroup.appendChild(dotClone);
	}
	
	dbug('DONE!!! ktnxbai!!!');
	dbug('=========================');
	dbug('');
}

function ungroupRecoursiveAndCtp(groupObj) 
{
	var countOfGroupObjects = null; 
	var obj = groupObj.children; // get all the grouped objects
	countOfGroupObjects = obj.length; // get the count of the grouped objects
	var reGroup = new Group(); // new group to regroup the crowdified objects later
	
	for (var n = 0; n < countOfGroupObjects; n++)
	{
		if(obj[n].hasChildren()) // if the object has children again, it's most likely a group... a group in a group... like vectorized fonts...
		{
				groupObjs.push(obj[n]); // add the group object to the groupObjs array to delete it from the allObjects array
				ungroupRecoursiveAndCtp(obj[n]); // do it all again
		}
		else // the object seems to be a path
		{
			gPaths.push(obj[n]); // add the path to the gpaths array to delete it from the allObjects array
			reGroup.appendChild(ctp(obj[n]));	// add the path to a group and pass it over to the blaze function
		}
	}
}


function ctp(shape)
{
	dbug('current target-shape:'+shape);
	
	// curvesToPoints on every shape in distance of 'pointDensity'
	shape.curvesToPoints(pointDensity,10000);
	// number of points per shape
	var count = shape.curves.size();
	// put every point in the points-array
	for(var k=0;k<count;k++)
	{
		for(var n=0;n<density;n++)
		{
			// of course with a little offset to the shape-path
			switch(n%2)
			{
			case 1: // half the dots with positive random values
				var x = randomBetween(0,offset);
				var y = randomBetween(0,offset);
				break;
			default: // half the dots with negative random values
				var x = -1 * randomBetween(0,offset);
				var y = -1 * randomBetween(0,offset);
			}
			var point = shape.curves[k].point1;
			var randomizedPoint = point + [x, y]; // add the offset to the point
			crowdPoints.push(randomizedPoint);
		}
	}
	return shape;
}

// function to get the main items, called from the dialog
function getDot()
{
	dot = null;
	
	var selectedItems = document.selectedItems;
	
	if(selectedItems.length == 0)
	{
		dbug('no objects selected -> nothing will happen...');
		return;
	}
	
	if (selectedItems.length >= 1)
	{
		dot = selectedItems;
		if (selectedItems.length == 1)
		{
			dbug('crowd-object is: '+dot[0]);
		}else{
			dbug(selectedItems.length + ' objects selected');
			dbug('crowd-objects are: '+dot);
		}
	}
}

// function to get the target shapes, called form the dalog
function getShapes()
{
	shapes = null;
	var selectedItems = document.selectedItems;
	if(selectedItems.length == 0)
	{
		print('::no-go! no objects selected, please select the target shape(s)!');
		return;
	}else{
		shapes = selectedItems;
		dbug('target-shapes are: '+shapes);
	}
}

// function to shuffle an array... funny
function fisherYates(array) {
	var i = array.length;
	if ( i == 0 ) return false;
	while ( --i ) {
		var j = Math.floor( Math.random() * ( i + 1 ) );
		var tempi = array[i];
		var tempj = array[j];
		array[i] = tempj;
		array[j] = tempi;
	}
}

// return a rounded random number between two given values
// rounded
function randomBetween(min,max)
{
	min = parseFloat(min);
	max = parseFloat(max);
	return Math.round(Math.random() * (max - min) + min);
}

// return a rounded random number between two given values
// not rounded
function randomBetweenFloat(min,max)
{
	min = parseFloat(min);
	max = parseFloat(max);
	return Math.random() * (max - min) + min;
}

// the debug function, writes to console if debug is set to true
function dbug(msg)
{
	if(debug)
		print('::debug: '+msg);
}

// stuff that happens when you check the debug checkbox:
// output basic versioning info and stuff
function startDebug()
{
	dbug('basic information:');
	dbug('===================');
	dbug('scriptographer version: ' + version+'.'+revision);
	dbug('illustrator version: ' + illustrator.version+'.'+ illustrator.revision);
	dbug('scriptdir: ' + scriptographer.scriptDirectory);
	dbug('script version: '+ scriptName);
	dbug('root-password: iluvpr0n');
}

//****function which creates the option dialog****
function optionDialog() 
{
		//////////////////////CONFIG//////////////////////////
		// dialog config stuff
		var dialogSize = new Size(170,315);
		
		var dialogTitle = scriptName;
		
		openDialog = true; // set dialog status to true/open
		
		// create dialog
		dialog = new FloatingDialog('tabbed') {
			title: dialogTitle,
			size: dialogSize,
			visible: true,
			onClose: function() {
			        dialog.destroy();
					openDialog = false;
			}
		};
		// ***** start rotate checkbox *****
		var rotateCheck = new CheckBox(dialog) {
			position: [100, 25],
			size: [20, 15],
			visible: true,
			checked: rotate,
			onClick: function () {
				rotate = rotateCheck.checked;
			}
		};
		
		var rotateText = new TextPane(dialog) {
			text: 'rotate:',
			position: [20, 25]
		};
		// ----- end rotate checkbox -----
		
		// ***** start scale checkbox *****
		var scaleCheck = new CheckBox(dialog) {
			position: [100, 45],
			size: [20, 15],
			visible: true,
			checked: scale,
			onClick: function () {
				scale = scaleCheck.checked;
				if(!scale)
				{
					scalemaxEdit.enabled = false;
				}else{
					scalemaxEdit.enabled = true;
				}
			}
		};

		var scaleText = new TextPane(dialog) {
			text: 'scale:',
			position: [20, 45]
		};
		// ----- end rotate checkbox -----
		
		// ***** start crowdify checkbox *****
		var crowdifyCheck = new CheckBox(dialog) {
			position: [100, 65],
			size: [20, 15],
			visible: true,
			checked: crowdify,
			onClick: function () {
				crowdify = crowdifyCheck.checked;
				if(!crowdify)
				{
					crowdynessEdit.enabled = false;
				}else{
					crowdynessEdit.enabled = true;
				}
			}
		};

		var crowdifyText = new TextPane(dialog) {
			text: 'crowdify:',
			position: [20, 65]
		};
		// ----- end crowdify checkbox -----
		
		// ***** start debug checkbox *****
		var debugCheck = new CheckBox(dialog) {
			position: [100, 85],
			size: [20, 15],
			visible: true,
			checked: debug,
			onClick: function () {
				debug = debugCheck.checked;
				dbug('wise choice!!');
				if(debug)
				{
					startDebug();
				}
			}
		};

		var debugText = new TextPane(dialog) {
			text: 'debug:',
			position: [20, 85]
		};
		// ----- end debug checkbox -----
		
		
		// *start scalemax value
		var scalemaxEdit = new SpinEdit(dialog) {
			visible: true,
			enabled: scale,
			position: [100, 110],
			size: [50, 20],
			value: scalemax
		};
		
		var scalemaxText = new TextPane(dialog) {
			text: 'scale max. in %:',
			position: [20, 110]
		};
		//------
		
		// *start crowdyness value
		var crowdynessEdit = new SpinEdit(dialog) {
			visible: true,
			enabled: crowdify,
			position: [100, 135],
			size: [50, 20],
			value: crowdyness
		};
		
		var crowdynessText = new TextPane(dialog) {
			text: 'crowdyness:',
			position: [20, 135]
		};
		//------
		
		// *start density value
		var densityEdit = new SpinEdit(dialog) {
			visible: true,
			position: [100, 160],
			size: [50, 20],
			value: density
		};
		
		var densityText = new TextPane(dialog) {
			text: 'obj density:',
			position: [20, 160]
		};
		//------
		
		// *start point denstity value
		var pointDensityEdit = new SpinEdit(dialog) {
			visible: true,
			position: [100, 185],
			size: [50, 20],
			value: pointDensity
		};
		
		var pointDensityText = new TextPane(dialog) {
			text: 'point dist:',
			position: [20, 185]
		};
		//------
		
		// *start offset value
		var offsetEdit = new SpinEdit(dialog) {
			visible: true,
			position: [100, 210],
			size: [50, 20],
			value: offset
		};
		
		var offsetText = new TextPane(dialog) {
			text: 'obj-path offset:',
			position: [20, 210]
		};
		//------
		
		// *start get 1st item button
		var dotButton = new Button(dialog) {
			text: 'assign crowd-object(s)',
			size: [160, 20],
			position: [5, 240],
			onClick: function () {
				getDot();
			}
		};
		
		// *start get 2nd item button
		var shapesButton = new Button(dialog) {
			text: 'assign shape(s)',
			size: [160, 20],
			position: [5, 265],
			onClick: function () {
				getShapes();
			}
		};
		
		// *start goButton
		var submitButton = new Button(dialog) {
			text: 'do it!',
			size: [160, 20],
			position: [5, 290],
			onClick: function () { // set all options on click
				crowdyness = crowdynessEdit.value;
				density = densityEdit.value;
				scalemax = scalemaxEdit.value;
				pointDensity = pointDensityEdit.value;
				offset = offsetEdit.value;
				debug = debugCheck.checked;
				crowdify = crowdifyCheck.checked;
				rotate = rotateCheck.checked;
				scale = scaleCheck.checked;
			
				/// go for it!!!
				crowdomatic();
			}
		};
		//-------
		
		// *start configFrame 
		var mainFrame = new Frame(dialog) {
			text: 'config',
			font: 'palette-italic',
			size: [160, 230],
			position: [5, 5]
		};
		//**********************************
}


