For my university assignment i have to make a script that scatters tree's across a scene randomly. The script has to include:
· "Simple User Interface (GUI) to include the following options:
[FONT="]o [/FONT]Select species of tree to use – i.e. Oak / Beech / Pine etc
[FONT="]o [/FONT]Number of trees to generate
[FONT="]o [/FONT]Size (radius) of the forest
[FONT="]o [/FONT]Placement mode - i.e. Grid or Random
[FONT="]o [/FONT]Create a ‘Populate’ button and connect it to a procedure to automatically duplicate and place the trees within the scene using the selected ‘placement mode’.
Additional marks will be awarded for providing the user with a suitable mechanism to identify multiple tree species to use. At each iteration of the ‘Populate’ procedure, a tree species from the list should be randomly selected and used to generate and place each new object."
While i've gotten most of it sorted with the GUI and placements etc there are a few things i'm unsure of how to do.
First thing is scattering randomly and stopping the trees from placing ontop of each other. My teacher went over poisson disc and jittered placements but never went into how to actually go about writing code for it. I'd also like to include some extra functions, like if the scene has a floorplane which is uneven (like terrain) that the tree's are placed on the surface of the floorplane. I'd also like to include a "custom mesh" option, where instead of placing the preset trees you can enter the name of an object in the scene into a text field and duplate that object instead.
This is what i've got for the placement at the moment, just a basic random value scatter, but the meshes often land ontop of each other..
int $i; for ($i = 0; $i<100; $i++ )
{
float $x = rand(0, 100);
float $z = rand(0, 100);
polyCylinder;
move $x 0 $z;
}
I've also not been able to find many good sources of info/help about MEL online (other than the autodesk command reference) does anybody know good books/websites for learning the basics?
Replies
i want each slider to control the percentage of each type of tree to use in the scene. To do this i thought that i could take the first slider value, minus it from 100, and use that new value as the max value for the next slider, and repeat for the 3rd slider. I thought i had a way of doing it figured out but i can't get it to work... i think i'm going about this completely the wrong way
///////// columnLayout -adjustableColumn true;
intSliderGrp -label "Oak%" -field true
-fieldMinValue -0 -fieldMaxValue 100.0
-value 0;
//percentage calc
//take number from first slider, minus it from 100 and set that value to be
//the max value for next slider, repeat twice
int $max1 =`intSliderGrp -query -value sliderOak%`;
return $max1
int $hundred = 100
$hundred - $max1 = int $result1
columnLayout -adjustableColumn true;
intSliderGrp -label "Maple%" -field true
-fieldMinValue -0 -fieldMaxValue - $result1
-value 0;
int $max2 =`intSliderGrp -query -value sliderMaple%`;
return $max2
$result1 - max2 = int $result2
columnLayout -adjustableColumn true;
intSliderGrp -label "Maple%" -field true
-fieldMinValue -$result2 -fieldMaxValue - $result2
-value $result2;
////////
You have assigned 94% of the Total Tree Population. You can still distribute 6%. If you proceed with generating your Tree Population, YourScriptName will normalize these values for you.
or if they over-assign:
You have over-assigned the Total Tree Population by 4%. If you proceed with generating your Tree Population, YourScriptName will normalize these values for you.
To come up with a percentage, just have each slider go from 0 to 100, then total all sliders, and divide 100 by that total, then multiply the value of each slider by that number. If its less than 100, you'll be multiplying by a number larger than 1, at 100 by 1, and above 100 by a fraction between 0 and 1. Make sure to not bother with this calculation at all if they entered 0 for each slider value, since you'd be dividing by 0.
maybe it'S smarter to randomly assign them to the vertices of your plane. check for dupes and you're fine. of course you might not get the denseness you like, but a helper mesh with more verts could do the job... there are a bunch of scripts on creativecrash.com who do exactly this(search for scatter..). look into the source code and get the infos you need. it's always smart to use stuff which already works, intead of reinvent the wheel. saves time and you learn faster
plus also ask on tech-artists.org, a got resource if it goes deeper into coding/scripting issues.
@fade1, the script is required to work without a floor plane (just blank maya scene) and then i'd like to make it work with a floor plane too. Maybe it would be possible for the script to add a plane, add the trees to the verts, delete the plane? And then i can control the trees by setting values for the plane that is made at first? How would i go about doing that?
Then you do a query for getting the location of every verts and you place your tree.
At the end you simply delete the plane.
Since you can give a name at everything, give a clear name to your plane for being able to delete it later.
Ex : http://download.autodesk.com/us/maya/2011help/Commands/polyPlane.html
http://download.autodesk.com/us/maya/2011help/Commands/delete.html
You could also do this without a plane (as you wrote), and just randomize x and z while keeping a constant y as 0 if your pivot is at the bottom. Just swap the array with vertices used with the vector locations of the trees
This is what i have so far, met another problem though.
///////////////
global string $Density;
global string $Area;
window -title "Giles Tree Gen V 0.1"
-iconName "Tree Gen"
-widthHeight 300 300;
columnLayout -adjustableColumn true;
$Density = `intSliderGrp -label "Density"
-value 10
-fieldMinValue 0
-fieldMaxValue 100.0
-field true`;
$Area = `intSliderGrp -label "Area (Grid Units)"
-value 10
-fieldMinValue 1
-fieldMaxValue 100.0
-field true`;
columnLayout -adjustableColumn true;
button -label "Random Place" -command "Randomplace()";
button -label "Close" -command ("deleteUI -window " + $window);
showWindow $window;
proc Randomplace()
{
global string $Density;
int $v = `intSliderGrp -q -value $Density`;
global string $Area;
int $scale = `intSliderGrp -q -value $Area`;
int $i;
for( $i=0; $i<$v; $i++ )
{
polyCube;
}
{
polyPlane -name "placementHelper";
setAttr polyPlane1.subdivisionsWidth $v;
setAttr polyPlane1.subdivisionsHeight $v;
setAttr polyPlane1.width $scale;
setAttr polyPlane1.height $scale;
//todo: change the size and subdivs of the plane based on sliders for coverage area
//and make the density value effect the amount of subdivs.
}
{
select -r "placementHelper";
delete;
}
}
////////////
Now i'm trying to get the amount of verts from placementHelper mesh using polyEvaluate.
I thought this would do it, but it doesn't work
int $vertCount = `polyEvaluate -v';
What i want to do is find out the amount of verts in the helper plane, and use that value as a maximum value to pick random numbers from. Those random numbers will be vertex ID's which get selected and a tree placed there. Or is there an easier way to randomly select verts?
polyEvaluate returns an array so you need to make the variable an array. Also watch the closing of tilda's. That's why there's a syntax error and on another note, dont assume the mesh will be selected always, I know you're making it in the script etc. This is more of a general good programming practice and is good to keep in a variable to access later, also helps when bug finding. Just store it with the ls -sl command in a variable and you can use it anywhere within the scope. Also on a side note, remember that the vertex count is 0 based.
//
global string {
global string $placementHelper;
int $vertexCount[] = `polyEvaluate -v $placementHelper`;
print $vertexCount
};
// Error: {
//
// Error: Syntax error //
// Error: int $vertexCount[] = `polyEvaluate -v $placementHelper`;
//
// Error: Invalid redeclaration of variable "$vertexCount" as a different type. //
// Error: }; //
// Error: Syntax error //
I have no idea if a global string is the right way to use this either, just randomly stabbing in the dark at this and hoping things work
Here's the script thus far:
Hopefully that works. Let me know if you want more clarification on stuff.
So by making a global proc, everything within it becomes global too, so to speak? How did i not figure that out... ha.
That's made things a lot clearer and easier to understand. Glad that the vertex counter is working now too.
I'l try to get a rand with a max value of the vertex count working next. After that i'm going to have to find out how to get the vertex positions and check them against another array (which i have no idea how to do either! hoorah!).
Dont worry too much about not knowing how to do it/it not working. Thats part of programming
int $randomNumber1 = `rand $vertexCount[]`; print $randomNumber1;
What i want to do here is take the vertex count and make it as the highest value the Rand can use (because obviously you don't want it trying to select a vertex ID that doesn't exist). But oh no, it won't work. It's coming up with invalid use of rand command. The script works fine when i replace it with int $randomNumber1 = `rand 100`; So obviously i'm referencing the $vertexCount wrongly
This scripting lark is difficult!
Like haiddasalami mentioned global proc means you can call the procedures directly but you do not get direct access to variables inside it. The variables will stay in their scope.
If you define a variable in the MEL window those variables will become globals for the session. I noticed a nasty bug with Maya where I had the following code in a mel document;
In my MEL window I ran this code
Note that I did not type $objects = addLast($objects, "testentry") in the MEL window, yet $objects in the MEL window stored the value of the procedure.
MEL does not support direct overwrite access to variables like &variable in C++ or in out Variable in Ada, I have not managed to do it at least. What I believe happened here is that Maya "leaked" its global variable from the MEL window and confused it with the same variable name inside my procedure scope. So be careful with defining test variables in the MEL window, you might get weird results.
If you're using globals and want to free space you can always use
Here's what i'm currently trying to do.
Yet again, it doesn't work and i can't figure out why. What should be happening is that a random number with the maximum value of $vertexCount[0] is being used to find a matching vertex ID and then use it's position to make $treePos, which is then used to move the cube to the position of that vert.
The vertex counter is working, and so is the rand function. But the line Doesn't work and results in an error...
I also need to work out a better way of moving the cube. obviously not all the cubes will have the same name and there is also 3 types of objects eventually. I need a way to move the previously added object... any ideas?
Here's the fixed code.
That part works now. While i was having trouble with that i started on the percentage sliders. I'm having trouble getting this to work:
So what i'm trying to do here is get the first slider and converting it into a percentage and using the remaining percentage as a maximum value for the next slider. I don't get any errors with this, but it's not effecting the max value either. I can just slide it up to 100 without any issues
Secondly you need to add a change / slide command to the slider you want affecting the other;
// Read the help docs for the commands under intSlider
-changeCommand(-cc)
-dragCommand(-dc)
I don't know if intSliderGroup is called like this but your customProcedure could be;
I'm getting "Error while parsing argument" on the dragcommand proc. I thought i was doing it right, the script runs, but whenever you move the slider the error while parsing is spammed in the editor and the value doesn't get restricted to below the remaining percentage.
Here, you're querying an intSliderGrp and expecting the name "oak" to work. Where is it getting that value from? It's not a variable (no $ prefix), so is it a globally-accessible control name?
All of those control names (Oak, Pine etc) seem like they should be handled differently so that you can actually reference the values to/from them properly.
An easy way to check this would be to put a debug print inside your slider changeCommand proc to just print out the value that it's getting for $oakPercent ... I have a feeling that value is not coming back correctly.
Then again, I might be way off and just lost familiarity with MEL, in which case, carry on
$oakPercent is definately functioning as it should and returning the correct value, just the slider doesn't seem to want to work with it
Currently:
pineSlider -> dragCommand -> Modify Pine
You want:
oakSlider -> dragCommand -> Modify Pine
Confused!
Oh, by the way, regarding names in MEL. If you type a word without quotation marks in certain contexts Maya will assume it's an object in the scene or a name. For example;
select -r SomeObject;
intSlider -min -100 -max 100 -v 0 -s 1 SomeName;
Works just as well as
select -r "SomeObject";
intSlider -min -100 -max 100 -v 0 -s 1 "SomeName";
I always do quotation marks in my scripts, it's a pet peeve of mine. I don't trust MEL.
In your intSlider definition you can only set the default value. Dynamically changing the value needs to be done with dragCommand or other external script.
global proc maxPinevalue(){ int $oakPercent = `intSliderGrp -q -value oak`;
int $maxPine = 100 - $oakPercent;
setAttr intSliderGrp -e -v "Pine" maxFieldValue $maxPine;
}
Original:
New:
It is standard to set the value last, like with setAttr, but not with intSlider. The code below is a working example.
Thanks a lot i'l try to implement it in a min.
intSliderGrp -label "Oak Percentage" -value 0
-fieldMinValue 0
-fieldMaxValue 100.0
-dragCommand "maxPinevalue()"
-dragCommand "maxMaplevalue()" //Cannot have this additional drag command
-field true oak;
Alternative is:
Yes, you can use semicolon and type normal code in one long string.
As you can see, the sliders clearly add up to more than 100. The sliderCommands are only adjusting themselves according to the previous value and not taking the next value into consideration. How should i go about getting all 3 values to not go over 100?
Cubes using this plane vert placement mode don't seem to want to place properly when using the code from the simple move x rand version.
The cubes are all being placed in the correct amount, but ontop of each other and the script is failing at moving each cube as it gets created. Heres my code.. Where am i going wrong this time? #uselessatmel
Also see how I am re-declaring the variable within the for loop. That ensures the variable is always fresh or not remembering the old trees. You might want to keep the order of the trees or something to iterate over with later on so you can store these in another array which you can then pass around to helper functions.
I'm going about this in a different manner to Giles and trying to keep it super simple.
So far i have a script that generates randomly placed objects, the amount of each is controlled by a slider up to 100. It will also create even spacing for a more ordered "forest"
first question is how do i go about stopping objects overlapping i've looked everywhere and can't seem to figure it out?It's not the end of the world as trees grow close to each other in nature any way but if i could redcue it that would be great!
the second issue is similar basically my ordered rows all start at 0 0 0 so how would i change the starting point so that they are offset. I know this is a simple one but im having a total mind block on it!
With regards the ordered dispersal is there any way to mix the trees so that they are not in individual blocks but are in a mixed orderly pattern?
here's my code so far:
string $window = `window -title "Forest Editor"
-iconName "Short Name"
-widthHeight 500 150
-sizeable 0`;
columnLayout -adjustableColumn true;
intSliderGrp -label "Number Of Oak"
-value 0
-fieldMinValue 0
-fieldMaxValue 100.0
-field true Oak;
intSliderGrp -label "Number Of Birch"
-value 0
-fieldMinValue 0
-fieldMaxValue 100
-field true Birch;
intSliderGrp -label "Number Of Maple"
-value 0
-fieldMinValue 0
-fieldMaxValue 100.0
-field true Maple;
button -label "Create Forest Random" -command "CreateForest";
button -label "Create Forest Even" -command "CreateForestEven";
button -label "Close" -command ("deleteUI -window " + $window);
showWindow;
global proc CreateForest(){
int $count = `intSliderGrp -q -value Oak`;
for( $i=0; $i<$count; $i++ )
{
polyCube
-name ("myCube_"+$i);
float $x = rand(-10, 10);
float $y = rand(0, 0);
float $z = rand(-10, 10);
move $x 0.5 $z;
}
group;
{
int $count = `intSliderGrp -q -value Birch`;
for( $i=0; $i<$count; $i++ )
{
polySphere
-name ("mySphere_"+$i);
float $x = rand(-10, 10);
float $y = rand(0, 0);
float $z = rand(-10, 10);
move $x 0.5 $z;
}
group;
}
{
int $count = `intSliderGrp -q -value Maple`;
for( $i=0; $i<$count; $i++ )
{
polyCone
-name ("myCone_"+$i);
float $x = rand(-10, 10);
float $y = rand(0, 0);
float $z = rand(-10, 10);
move $x 0.5 $z;
}
group;
}
}
global proc CreateForestEven(){
int $count = `intSliderGrp -q -value Oak`;
for($rows=0; $rows<$count; $rows++)
{
for ($cols=0; $cols<$count; $cols++)
{
polyCube
-name ("myCube_"+$rows);
move -r ($cols*-2) 0.5 ($rows*2);
}
}
group;
{
int $count = `intSliderGrp -q -value Birch`;
for($rows=0; $rows<$count; $rows++)
{
for ($cols=0; $cols<$count; $cols++)
{
polySphere
-name ("mySphere_"+$rows);
move -r ($cols*2) 0.5 ($rows*-2);
}
}
group;
}
{
int $count = `intSliderGrp -q -value Maple`;
for($rows=0; $rows<$count; $rows++)
{
for ($cols=0; $cols<$count; $cols++)
{
polyCone
-name ("myCone_"+$rows);
move -r ($cols*2) 0.5 ($rows*2);
}
}
group;
}
}
*DROPBOX images would load for some reason so here are the manual links
http://dl.dropbox.com/u/44396673/scripting/polycount%20question3.JPG
http://dl.dropbox.com/u/44396673/scripting/polycount%20question2.JPG
http://dl.dropbox.com/u/44396673/scripting/polycount%20question3.JPG
Here's a code example on how to determine distance between two objects with vectors.
Also use the [ code] [ /code] tags (without spaces) please.
My scatter is finally working hoorah. My script is starting to come together now, however, i'm still having two issues.
Sliders still don't bloody work, i am confused in the highest regard.
My scatter works, but like Martin, i have to get the tree's to not place ontop of each other. Is there a way i can query a vertex and its neghbouring vertex's to find out if they have already been used? then it's just simply a matter of doing an if/else statement... right?
1. Distance comparison between pivot
2. Bounding Box comparison
3. Convex polygon intersection (check triangle against triangle)
http://gamemath.com/2011/09/detecting-whether-two-convex-polygons-overlap/
you could just store the vertex positions that you have used something like
Sucks that mel doesnt have push/pop or any array methods so this is a quick way to do a push.
PS: I havent tested this, so there might be some errors but should work :poly136: