
  /*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
  ///////////////////////////////////////////////////////////////////////////////////////////////
                                                                                                 
                                                          __                      __  _          
                             ______________ _      ______/ /___  ____ ___  ____ _/ /_(_)____     
----------------------------/ ___/ ___/ __ \ | /| / / __  / __ \/ __ `__ \/ __ `/ __/ / ___/     
---------------------------/ /__/ /  / /_/ / |/ |/ / /_/ / /_/ / / / / / / /_/ / /_/ / /__       
---------------------------\___/_/   \____/|__/|__/\__,_/\____/_/ /_/ /_/\__,_/\__/_/\___/.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].valid;
			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].valid;
		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.subtract(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.x;
			var y = Math.random() * document.size.y;
			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(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);
			var targetPos = dotClone.bounds.center;
			dotClone.scale(randomScaleFactor,randomScaleFactor);
			var currPos = dotClone.bounds.center;
			var move = targetPos.subtract(currPos);
			dotClone.translate(move);
		}
		finalGroup.appendChild(dotClone);
	}
	
	dbug('DONE!!! ktnxbai!!!');
	dbug('=========================');
	dbug('');
}

function ungroupRecoursiveAndCtp(groupObj) 
{
	var countOfGroupObjects = null; 
	var obj = groupObj.getChildren(); // 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].getPoint1();
			var randomizedPoint = point.add(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: ' + app.version+'.'+app.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(FloatingDialog.OPTION_TABBED);
		dialog.setTitle(dialogTitle);
		dialog.setSize(dialogSize);
		
		// ***** start rotate checkbox *****
		var rotateCheck = new CheckBox(dialog);
		rotateCheck.setPosition(100, 25);
		rotateCheck.setSize(20, 15);
		rotateCheck.setVisible(true);
		rotateCheck.setChecked(rotate);
		rotateCheck.onClick = function(){
			rotate = rotateCheck.isChecked();
		}
		var rotateText = new Static(dialog);
		rotateText.setText("rotate:");
		rotateText.setSize(rotateText.bestSize);
		rotateText.setPosition(20, 25);
		// ----- end rotate checkbox -----
		
		// ***** start scale checkbox *****
		var scaleCheck = new CheckBox(dialog);
		scaleCheck.setPosition(100, 45);
		scaleCheck.setSize(20, 15);
		scaleCheck.setVisible(true);
		scaleCheck.setChecked(scale);
		scaleCheck.onClick = function(){
			scale = scaleCheck.isChecked();
			if(!scale)
			{
				scalemaxEdit.enabled = false;
			}else{
				scalemaxEdit.enabled = true;
			}
		}
		var scaleText = new Static(dialog);
		scaleText.setText("scale:");
		scaleText.setSize(scaleText.bestSize);
		scaleText.setPosition(20, 45);
		// ----- end rotate checkbox -----
		
		// ***** start crowdify checkbox *****
		var crowdifyCheck = new CheckBox(dialog);
		crowdifyCheck.setPosition(100, 65);
		crowdifyCheck.setSize(20, 15);
		crowdifyCheck.setVisible(true);
		crowdifyCheck.setChecked(crowdify);
		crowdifyCheck.onClick = function(){
			crowdify = crowdifyCheck.isChecked();
			if(!crowdify)
			{
				crowdynessEdit.enabled = false;
			}else{
				crowdynessEdit.enabled = true;
			}
		}
		var crowdifyText = new Static(dialog);
		crowdifyText.setText("crowdify:");
		crowdifyText.setSize(crowdifyText.bestSize);
		crowdifyText.setPosition(20, 65);
		// ----- end crowdify checkbox -----
		
		// ***** start debug checkbox *****
		var debugCheck = new CheckBox(dialog);
		debugCheck.setPosition(100, 85);
		debugCheck.setSize(20, 15);
		debugCheck.setVisible(true);
		debugCheck.setChecked(debug);
		debugCheck.onClick = function(){
			debug = debugCheck.isChecked();
			dbug('wise choice!!');
			if(debug)
			{
				startDebug();
			}
		}
		var debugText = new Static(dialog);
		debugText.setText("debug:");
		debugText.setSize(debugText.bestSize);
		debugText.setPosition(20, 85);
		// ----- end debug checkbox -----
		
		
		// *start scalemax value
		var scalemaxEdit = new SpinEdit(dialog);
		scalemaxEdit.setVisible(true);
		scalemaxEdit.enabled = scale;
		scalemaxEdit.setPosition(100, 110);
		scalemaxEdit.setSize(50, 20);
		scalemaxEdit.value = scalemax;
		scalemaxEdit.setUnits(0);
		
		var scalemaxText = new Static(dialog);
		scalemaxText.setText("scale max. in %:");
		scalemaxText.setSize(scalemaxText.bestSize);
		scalemaxText.setPosition(20, 110);
		//------
		
		// *start crowdyness value
		var crowdynessEdit = new SpinEdit(dialog);
		crowdynessEdit.setVisible(true);
		crowdynessEdit.enabled = crowdify;
		crowdynessEdit.setPosition(100, 135);
		crowdynessEdit.setSize(50, 20);
		crowdynessEdit.value = crowdyness;
		crowdynessEdit.setUnits(0);
		
		var crowdynessText = new Static(dialog);
		crowdynessText.setText("crowdyness:");
		crowdynessText.setSize(crowdynessText.bestSize);
		crowdynessText.setPosition(20, 135);
		//------
		
		// *start density value
		var densityEdit = new SpinEdit(dialog);
		densityEdit.setVisible(true);
		densityEdit.setPosition(100, 160);
		densityEdit.setSize(50, 20);
		densityEdit.value = density;
		densityEdit.setUnits(0);
		
		var densityText = new Static(dialog);
		densityText.text = "obj density:";
		densityText.setSize(densityText.bestSize);
		densityText.setPosition(20, 160);
		//------
		
		// *start point denstity value
		var pointDensityEdit = new SpinEdit(dialog);
		pointDensityEdit.setVisible(true);
		pointDensityEdit.setPosition(100, 185);
		pointDensityEdit.setSize(50, 20);
		pointDensityEdit.value = pointDensity;
		pointDensityEdit.setUnits(0);
		
		var pointDensityText = new Static(dialog);
		pointDensityText.setText("point dist:");
		pointDensityText.setSize(pointDensityText.bestSize);
		pointDensityText.setPosition(20, 185);
		//------
		
		// *start offset value
		var offsetEdit = new SpinEdit(dialog);
		offsetEdit.setVisible(true);
		offsetEdit.setPosition(100, 210);
		offsetEdit.setSize(50, 20);
		offsetEdit.value = offset;
		offsetEdit.setUnits(0);
		
		var offsetText = new Static(dialog);
		offsetText.setText("obj-path offset:");
		offsetText.setSize(offsetText.bestSize);
		offsetText.setPosition(20, 210);
		//------
		
		// *start get 1st item button
		var dotButton = new Button(dialog);
		dotButton.setText("assign crowd-object(s)");
		dotButton.setSize(160,20);
		dotButton.setPosition(5, 240);
		dotButton.onClick = function(){ // set all option on click
			getDot();
		}
		
		// *start get 2nd item button
		var shapesButton = new Button(dialog);
		shapesButton.setText("assign shape(s)");
		shapesButton.setSize(160,20);
		shapesButton.setPosition(5, 265);
		shapesButton.onClick = function(){ // set all option on click
			getShapes();
		}
		
		// *start goButton
		var submitButton = new Button(dialog);
		submitButton.setText("do it!");
		submitButton.setSize(160,20);
		submitButton.setPosition(5, 290);
		submitButton.onClick = function(){ // set all option on click
			
			crowdyness = crowdynessEdit.value;
			density = densityEdit.value;
			scalemax = scalemaxEdit.value;
			pointDensity = pointDensityEdit.value;
			offset = offsetEdit.value;
			debug = debugCheck.isChecked();
			crowdify = crowdifyCheck.isChecked();
			rotate = rotateCheck.isChecked();
			scale = scaleCheck.isChecked();
			
			/// go for it!!!
			crowdomatic();
		}
		//-------
		
		// *start configFrame 
		var mainFrame = new Frame(dialog);
		mainFrame.setText("config");
		mainFrame.setFont(6);
		mainFrame.setSize(160,230);
		mainFrame.setPosition(5, 5);
		//---------
		
		// finalize the dialog
		dialog.setVisible(true);
		dialog.onClose = function() {
		        dialog.destroy();
				openDialog = false;
		}
		//**********************************
}


