<!--{"parent": "17_Mastering_Cheetah_3D_s_Interface.md", "order": 8}-->

## Scripts

![](/images/pastedGraphic-634.jpg)

### Some Very Useful Scripts

For the last couple of years I’ve tried to maintain a “master list” of useful scripts for Cheetah 3D (I’ve even contributed a couple myself). This list is posted on the forums here: [https://www.cheetah3d.com/forum/index.php?threads/5752/](https://www.cheetah3d.com/forum/index.php?threads/5752/) but I’ve included the current list as of writing below.

> **Note:** needs checking in Cheetah 3D 8.1.1: none of these scripts has a forum report from 8.x, and most date from Cheetah 3D 5–6. The [Cheetah 3D scripting knowledge base catalog](https://github.com/tonioloewald/c3d-scripting-kb/blob/main/catalog/CATALOG.md) lists what forum users reported for each one. <!-- TODO verify -->

#### Basic Interface Enhancements

- [Expand Selection](https://www.cheetah3d.com/forum/index.php?threads/4108/) conditionally expand a selection

  > **Note:** requires updating for Cheetah 3D 8.1.1: Cheetah 3D now has a built-in **Selection \> Expand Selection** command (not conditional, unlike this script). <!-- TODO update -->

- [Toggle Visibility / Make Solo](https://www.cheetah3d.com/forum/index.php?threads/7106/) make everything except the selection disappear or go wireframe

- [OBJ Importer](https://www.cheetah3d.com/forum/index.php?threads/4085/) improved OBJ import

  > **Note:** requires updating for Cheetah 3D 8.1.1: superseded; Cheetah 3D has imported OBJ files natively since version 6 (KB catalog, thread 4085). <!-- TODO update -->

- [Object Chunker](https://www.cheetah3d.com/forum/index.php?threads/4991/) break large meshes into smaller meshes to meet polygon budget (e.g. for Unity which won’t accept meshes with more than 65535 triangles).

  > **Note:** needs checking in Cheetah 3D 8.1.1: the Unity limit was 65535 vertices (16-bit indices), not triangles, and current Unity supports 32-bit indices. <!-- TODO verify -->

- [set changer](http://www.tres-graficos.jp/blog/files/category.php?k=Script)

#### Modeling

- [Bent Arrow](https://www.cheetah3d.com/forum/index.php?threads/5130/)

- [Billboard](https://www.cheetah3d.com/forum/index.php?threads/1686/)

- [Center Bounds](https://www.cheetah3d.com/forum/index.php?threads/3537/) set center of object to the center of its bounding box

  > **Note:** requires updating for Cheetah 3D 8.1.1: largely superseded by **Tools \> Coord System**, whose “center pivot point” button moves the pivot to the geometric center (manual: Coord System Tool). <!-- TODO update -->

- [Curls](https://www.cheetah3d.com/forum/index.php?threads/8033/)

- [Dissolve](https://www.cheetah3d.com/forum/index.php?threads/3792/)

- [Edge N Splitter](https://www.cheetah3d.com/forum/index.php?threads/8024/) a more versatile ring cutting tool

- [Edge Utilities](https://www.cheetah3d.com/forum/index.php?threads/2574/) macros for working with creases and seams

- [Center of Gravity](https://www.cheetah3d.com/forum/index.php?threads/4810/) set center of object to the center of "gravity" of a selected set of faces.

- [Point Array](https://www.cheetah3d.com/forum/index.php?threads/970/)

- [Flatten Polygon](https://www.cheetah3d.com/forum/index.php?threads/1405/) make selected polygons properly coplanar

  > **Note:** needs checking in Cheetah 3D 8.1.1: the original Flatten.js link is dead and was reported broken in 5.8; the thread has a later flattened4U.js version (KB catalog, thread 1405). <!-- TODO verify -->

- [Hair](https://www.cheetah3d.com/forum/index.php?threads/1294/) algorithmic generation of hair geometry with UVs

  > **Note:** needs checking in Cheetah 3D 8.1.1: Hair.js gave errors in 6.2.1 while cHair.js worked (KB catalog, thread 1294). <!-- TODO verify -->

- [Loft](https://www.cheetah3d.com/forum/index.php?threads/1284/)

  > **Note:** requires updating for Cheetah 3D 8.1.1: probably superseded by the built-in Loft creator object (manual: Loft Object). <!-- TODO update -->

- [Mandelbulb](https://www.cheetah3d.com/forum/index.php?threads/5865/) julia set modeler

  > **Note:** needs checking in Cheetah 3D 8.1.1: its helper binary was built for OS X 10.6 and one user never got it working (KB catalog, thread 5865). <!-- TODO verify -->

- [Mirror Copy](https://www.cheetah3d.com/forum/index.php?threads/1448/) (also useful for animation) mirror geometry and poses

  > **Note:** needs checking in Cheetah 3D 8.1.1: reports are mixed; a 2020 report says it does nothing in 7.4.2 (KB catalog, thread 1448). <!-- TODO verify -->

- [Particle Connector](https://www.cheetah3d.com/forum/index.php?threads/8025/)

- [Path Array](https://www.cheetah3d.com/forum/index.php?threads/5292/)

- [Pivot Setter](https://www.cheetah3d.com/forum/index.php?threads/7938/) (and a derived script for setting the pivot to a selected point)

- [Polygon Reduction](http://www.tres-graficos.jp/blog/files/category.php?k=Script)

  > **Note:** requires updating for Cheetah 3D 8.1.1: superseded by the built-in **Tools \> Polygon \> Simplify** tool (manual: Simplify Tool). <!-- TODO update -->

- [Surface Utilities](https://www.cheetah3d.com/forum/index.php?threads/7079/) powerful tools for changing smoothing settings and fixing normals.

- [Taper Sweep](https://www.cheetah3d.com/forum/index.php?threads/4029/)

- [Toggle Shading](https://www.cheetah3d.com/forum/index.php?threads/6381/)

- [Toggle Visibility](https://www.cheetah3d.com/forum/index.php?threads/6373/)

- [Polygon Splitter](https://www.cheetah3d.com/forum/index.php?threads/6776/)

- [Extruded Text Maker](https://www.cheetah3d.com/forum/index.php?threads/6801/) — has more options than standard text spline

  > **Note:** needs checking in Cheetah 3D 8.1.1: users reported crashes in 5.7 and the last fix was never confirmed (KB catalog, thread 6801). <!-- TODO verify -->

- [Elliptical Splines](https://www.cheetah3d.com/forum/index.php?threads/6607/)

- [Rounded Rectangle Spline](https://www.cheetah3d.com/forum/index.php?threads/6698/)

- [Pathed Helix](https://www.cheetah3d.com/forum/index.php?threads/1298/)

  > **Note:** needs checking in Cheetah 3D 8.1.1: use the 2010 version from post #11; the original file throws an error in later versions (KB catalog, thread 1298). <!-- TODO verify -->

#### Animation

- [Guided Spline](https://www.cheetah3d.com/forum/index.php?threads/3880/)

- [morph](http://www.tres-graficos.jp/blog/files/category.php?k=Script)

- [Moving Textures](https://www.cheetah3d.com/forum/index.php?threads/1210/)

  > **Note:** requires updating for Cheetah 3D 8.1.1: superseded; Cheetah 3D has supported movie textures natively since version 7 (KB catalog, thread 1210). <!-- TODO update -->

- [Pose Utilities](https://www.cheetah3d.com/forum/index.php?threads/5358/) mirror and flip poses quickly and efficiently

- [Sequential OBJs](https://www.cheetah3d.com/forum/index.php?threads/2110/)

- [Automatic Lip Sync from WAV File](https://www.cheetah3d.com/forum/index.php?threads/6557/)

#### Texturing and UV-Mapping

- [Polygon UV Editor](https://www.cheetah3d.com/forum/index.php?threads/6079/)

  > **Note:** needs checking in Cheetah 3D 8.1.1: the original post (p=2689) is not in the KB archive; this link goes to Targos’s 2010 repost of Neil Carter’s script. <!-- TODO verify -->

- [Text Tool](https://www.cheetah3d.com/forum/index.php?threads/8019/)

- [UV Stepper](https://www.cheetah3d.com/forum/index.php?threads/2280/)

#### Rendering

- [LuxCheetah](https://www.cheetah3d.com/forum/index.php?threads/4374/) exports C3D scenes to the free physically-based renderer [LuxRender](http://luxrender.net/)

  > **Note:** requires updating for Cheetah 3D 8.1.1: last confirmed for 5.5, LuxRender has been succeeded by LuxCoreRender, and Cheetah 3D now includes its own unbiased path tracer, Falcon (manual: Falcon). <!-- TODO update -->

#### Particles

- [Iterative Relative Particles](https://www.cheetah3d.com/forum/index.php?threads/6905/) for Groboto-style procedural objects

- [Particle City](https://www.cheetah3d.com/forum/index.php?threads/5834/post-43391) custom particle system for procedurally generating "cities"

### Using Scripts

The first thing you need to know about scripts is where to “install” them. This is especially tricky as of Lion because Apple has hidden user Library folders.

The simplest option is to use **Go \> Go To Folder** in Finder to go to this folder: 

**~/Library/Application Support/Cheetah3D/**

#### Which subfolder does a script go in?

For some reason Cheetah 3D requires scripts to be put in specific sub-folders despite the fact that (a) human beings cannot easily tell what kind of script a given script file is and (b) Cheetah 3D probably can. Go figure. If it's not obvious from the script's documentation or internal comments what kind of script it is, here's how you can *probably* tell by looking through it:

> **AI draft:** generated from the Cheetah 3D 8.1.1 manual and the Cheetah 3D scripting knowledge base; not yet reviewed by the author. <!-- TODO review AI draft -->

The subfolders are **Macro**, **Tool**, **Tag**, **Polygonobj**, **Splineobj**, and **Particle**, and the folder decides the script type: a script in the wrong folder fails silently, throws misleading errors, or crashes Cheetah 3D.

- If the script contains **function main**, it's a **Macro** script.

- If the script contains **function buildObject** and calls **addParticle**, it's a **Particle** script.

- If the script contains **function buildObject** and calls the spline core's **move()**, **line()**, or **curve()**, it's a **Splineobj** script.

- If the script contains function buildObject, but no spline calls and no addParticle, it's a **Polygonobj** script.

- If the script contains function **performExpression** (usually alongside buildUI), it's a **Tag** script. Since Cheetah 3D 7, Tag scripts are *expression tags*: Cheetah 3D calls performExpression to drive the tag's owner as the scene updates (see the bundled CircleExpression.js). A script with only buildUI and declarations is probably a Tag script too.

- Finally, if the script contains function buildUI, *at least one other function* (called by a button), but no buildObject or performExpression function, it's a **Tool** script.

> *End of AI draft.* <!-- /AI draft -->

This is not completely foolproof. It's possible to create a Particle script that never calls addParticle, for example, but it would be kind of weird. In most cases, what a script is supposed to do should be documented. <!-- TODO review -->

#### When must you relaunch Cheetah 3D?

> **AI draft:** generated from the Cheetah 3D 8.1.1 app and the Cheetah 3D scripting knowledge base; not yet reviewed by the author. <!-- TODO review AI draft -->

If you’re working on a script you do not need to relaunch Cheetah 3D for it to see changes to the script (but you may need to select the script anew from the Script menu). To make Cheetah 3D pick up a *newly added* script, choose **Update Menu** at the bottom of the **Tools \> Script** menu, or quit and relaunch Cheetah 3D.

> *End of AI draft.* <!-- /AI draft -->

> **Note:** needs checking in Cheetah 3D 8.1.1: the manual (Executing a Script) still says to restart, but the app has an “Update Menu” item; confirm it finds new scripts. <!-- TODO verify -->

Be careful when coding scripts. It is quite possible for a script to seize up Cheetah 3D and require a force quit. (Command-option escape to force quit, in case you didn’t know. You can also click and hold on an application’s icon in the dock.)

#### Assigning Hotkeys to Scripts

Once you’ve loaded a script, you can assign a hotkey to it (just as you would anything else). This is extremely useful for some of the more frequently needed scripts. E.g. when doing character animation, you might want to hotkey Pose Utilities.

### Writing Scripts

To create a new script simply fire up your favorite text editor and create a new JavaScript (.js) document in the appropriate folder. 

The best way to do it is probably to open up your Cheetah 3D folder inside Application Support — ~/Library/Application Support/Cheetah3D/ — and duplicate a script of the type you want to write, name it what you want, and then launch/relaunch Cheetah 3D. (TextEdit in plain text mode works fine, but it’s very basic — if you don’t have a serious programmer’s text editor, I recommend TextWrangler from barebones.com — it’s free and very powerful.)

To write Cheetah 3D scripts requires that you know some JavaScript. Teaching JavaScript is beyond the scope of this book, but there are many good resources for learning JavaScript (many would recommend Douglas Crockford’s *JavaScript: The Good Parts* and his videos at [http://yuiblog.com/crockford](http://yuiblog.com/crockford)). If you’ve done any programming you can probably follow along without knowing much about JavaScript specifically.

> **AI draft:** generated from the Cheetah 3D scripting knowledge base README; not yet reviewed by the author. <!-- TODO review AI draft -->

#### Getting an AI Assistant to Write Scripts for You

These days you don't have to be a programmer to get a useful script. AI chat assistants (ChatGPT, Claude, Gemini, and so on) are good at JavaScript but know very little about Cheetah 3D, so on their own they write scripts that look right but don't work. The [Cheetah 3D scripting knowledge base](https://github.com/tonioloewald/c3d-scripting-kb) fixes this: it collects the Cheetah 3D 8.1.1 scripting API, the example scripts that ship with the app, and about twenty years of lessons from the forum into one file you hand to the AI.

Start a new chat and begin your message with:

```text
Read https://raw.githubusercontent.com/tonioloewald/c3d-scripting-kb/main/dist/c3d-context-short.md and follow it.
```

Then, in the same message, describe what you want in Cheetah 3D terms: what it acts on (the selection, the whole scene, or a new object), what it should do, which settings you want to control, and your Cheetah 3D version. The AI will tell you the script type and which folder to put it in. Save the script as a plain-text **.js** file, install it, and **test it on a copy of your scene**. If something goes wrong, paste the Console output back into the chat.

The same approach works for fixing an old forum script that has stopped working, or adding a feature to one. The knowledge base also has a [catalog](https://github.com/tonioloewald/c3d-scripting-kb/blob/main/catalog/CATALOG.md) of the scripts posted on the forum, with what users reported about whether they work.

> *End of AI draft.* <!-- /AI draft -->

#### Basics

The simplest kind of script in Cheetah 3D is a Macro script. A Macro script looks like this:

```js
function main(doc){
// do stuff here
}
```

A comment in JavaScript is anything between /\* and \*/ or anything following // until the end of that line. So “// do stuff here” is a comment.

When someone selects a macro script from the Tools \> Script \> Macro Script menu Cheetah 3D loads the script and calls its main function, passing it the variable doc, which contains a representation of your Cheetah 3D document.

If we look through the online help for documentation on the Document object (which is what doc is going to be) we can see it has some useful looking properties and methods. A nice simple method is render(). Let’s try that.

```js
function main(doc){
doc.render();
}
```

If you try running this script it will do exactly what you expect. Well, *kind* of. Unlike clicking the Render icon in the toolbar you get no visual feedback until the render is complete.

Still, it’s not like this is a terribly useful script. Let’s try something a little more advanced.

```js
function main(doc){
doc.addObject( BOX );
}
```

This, again, does what you’d expect. The documentation also stipulates that addObject returns an object — the thing you added. Let’s do something with it.

```js
function main(doc){
var box = doc.addObject( BOX );
box.setParameter( "position", new Vec3D( 0, 0.5, 0 ) );
}
```

This macro creates a new box and then moves it up 0.5 units. (Ever frustrated by how the default box is centered on the origin rather than sitting neatly on the XZ plane? This script is your answer!)

### Debugging Scripts — The Console

If you right-click on the header of any panel in a Cheetah 3D window you’ll discover that one of the options is for a console panel. I recommend adding a console panel somewhere to one of your layouts if you want to do any scripting.

![](/images/pastedGraphic-635.jpg)

My default layout has a console tab tucked in with the object browser — ready whenever I need it.

If you want your script to say something to you (“Hey, I got this far without crashing”) then Cheetah 3D provides a print() function.

```js
function main(doc){
print( "hello world");
}
```

Hmm, maybe I should have started with this!

You can also generate alerts using the OS.messageBox() function.

```js
function main(doc){
OS.messageBox("Title","Here is my message!");
}
```

![](/images/pastedGraphic-636.jpg)

### Feeling Your Way Around

Cheetah 3D’s documentation is sometimes lacking, especially with respect to scripting, but it does offer some introspection tools to allow you to figure things out for yourself. One of the sample Macro scripts — Parameter Info.js — gives you a rough idea of how to do this, and forum scripting expert tg\_jp has improved on it with this more general version:

```js
function main(doc) {
var obj = doc.selectedObject();
print('---- Object Info ---');
if ( obj ) {
print('-- object type:'+obj.type());
var info = obj.parameterInfo();
for(var j=0;j<info.length;j++) {
print(info[j][0] +" [" + info[j][1] + "]:"+obj.getParameter(info[j][0]));
}
var tcount = obj.tagCount();
for (var i = 0;i < tcount;++i) {
var tag = obj.tagAtIndex(i);
info = tag.parameterInfo();
print('-- tag '+i+' type:'+tag.type());
for(var j=0;j<info.length;j++){
print(info[j][0] + " [" + info[j][1] + "]:"+tag.getParameter(info[j][0]));
}
if (tag.type() == 102) { // material tag.
var material = doc.materialAtIndex(tag.linkedToMaterial());
print('-- linked Material --');
info = material.parameterInfo();
for(var j=0;j<info.length;j++){
print(info[j][0] + " [" + info[j][1] + "]:"+material.getParameter(info[j][0]));
}
}
}
}
}
```

This script, when saved as a Macro Script, will document all the properties of any selected object. 

If you wrap the core functionality of this script in a utility function, I’ll call it describe(), then you get something like this:

```js
function describe( obj ){
if ( obj ) {
print('-- object type:'+obj.type());
var info = obj.parameterInfo();
for(var j=0;j<info.length;j++) {
print(info[j][0] +" [" + info[j][1] + "]:"+obj.getParameter(info[j][0]));
}
var tcount = obj.tagCount();
for (var i = 0;i < tcount;++i) {
var tag = obj.tagAtIndex(i);
info = tag.parameterInfo();
print('-- tag '+i+' type:'+tag.type());
for(var j=0;j<info.length;j++){
print(info[j][0] + " [" + info[j][1] + "]:"+tag.getParameter(info[j][0]));
}
if (tag.type() == 102) { // material tag.
var material = doc.materialAtIndex(tag.linkedToMaterial());
print('-- linked Material --');
info = material.parameterInfo();
for(var j=0;j<info.length;j++){
print(info[j][0] + " [" + info[j][1] + "]:"+material.getParameter(info[j][0]));
}
}
}
}
}
```

Now if I’m trying to figure out what properties a given object has, I can simply describe() it and its properties will be listed in the console.

This function has saved my bacon many times — e.g. when trying to script a Particle System the Random Seed property isn’t documented — how do you get at it? Using getParameter(“Random Seed”) doesn’t work. If you open the .jas file you’ll discover it’s called “particlesSeed” in the XML. But “particlesSeed” doesn’t work either (nor does “Particle Seed”). Finally, selecting a particle system and using this script reveals a property called “seed” of type “Int”. Eureka!

```text
---- Object Info ---
-- object type:81
scriptName [String]:Scripts/Particle/Particle City.js
locked [Bool]:0
...
rotorder [Int]:6
name [String]:Procedural City
order [Int]:1
seed [Int]:12345
displayType [Int]:3
...
editorActive [Bool]:1
renderActive [Bool]:1
editorShading [Int]:0
```

### Some Useful Techniques

Well, you’re pretty much on your own now. The best way to create new scripts is to look at the example scripts provided with Cheetah 3D, as well as those on the forums, and use them as a starting point. Before I send you off to swim with the sharks, here are a few useful techniques I’ve picked up.

#### Iterating Across the Scene Graph

This simple tool illustrates probably the single most useful pattern for writing macros and tool scripts.

First of all, it shows how to build a tool user interface.

The set\_smooth function is called by the user interface button (the name of the function the button will call is passed as a parameter when creating the button).

The set\_smooth function uses the target utility function to figure out if it’s going to act on the scene root or the selection (based on UI settings).

It then defines a simple function fn that does what it wants to do for a given object and calls the iterate\_on\_objects utility function to apply this simple function to the object returned by the target function and — if the user has opted for recursing children — everything below it in the scene graph (i.e. the Object Browser).

> **Note:** requires updating for Cheetah 3D 8.1.1: `doc.selectedObject()` returns the hidden scene root (not null) when nothing is selected, so “Selection Only” then acts on the whole scene; 8.1.1 also has `doc.selectedObjects()` for multiple selections (KB skill/SKILL.md rule 5; API: Document). Tool scripts should also call `recordParametersForUndo()` before changing objects, or the changes bypass Undo (KB knowledge/gotchas.md). <!-- TODO update -->

```js
// build the tool UI
function buildUI(tool){
tool.addParameterBool("Selection Only", true, false, true, false, false);
tool.addParameterBool("Recurse Children", true, false, true, false, false);
tool.addParameterSeparator("Smoothing");
tool.addParameterFloat("Angle",15.0,0.0,90.0,true,true);
tool.addParameterButton("Set Smooth Angle","OK","set_smooth");
}
function set_smooth(tool){
var angle = tool.getParameter("Angle");
var fn = function( obj ){
if( obj.type() == POLYGONOBJ ){
obj.setParameter("normalAngle", angle);
}
}
iterate_on_objects( tool, target(tool), fn );
tool.document().redrawAll();
}
function target(tool){
var doc = tool.document();
if( tool.getParameter("Selection Only") ){
return doc.selectedObject();
} else {
return doc.root();
}
}
// applies a function to an object and then, if user has selected recurse,
// recursively iterates across that object's children
function iterate_on_objects( tool, obj, fn ){
fn( obj );
if( tool.getParameter("Recurse Children") ){
for( var i=0; i<obj.childCount(); i++ ){
iterate_on_objects( tool, obj.childAtIndex(i), fn );
}
}
}
```

#### Iterating across the parts of an editable mesh

Using the same framework from the previous example, here’s a tool for flipping normals. You can flip every normal in your entire scene with a single button press using this handy script!

But the flip\_normals function shows how to iterate across components of an editable mesh (a.k.a. POLYGONOBJ). Also note how it uses some tricks to force Cheetah 3D to recalculate normals and then forces the scene to be redrawn when it’s done.

```js
// build the tool UI
function buildUI(tool){
tool.addParameterBool("Selection Only", true, false, true, false, false);
tool.addParameterBool("Recurse Children", true, false, true, false, false);
tool.addParameterSeparator("Normals");
tool.addParameterButton("Flip Normals","OK","flip_normals");
} 
function flip_normals(tool){
var fn = function( obj ){
if( obj.type() == POLYGONOBJ ){
var c = obj.core();
for( var i=0; i<c.polygonCount(); i++ ){
c.flipWinding( i );
}
// Force recalculation of normals
obj.setParameter("normalAngle", obj.getParameter("normalAngle"));
}
}
iterate_on_objects( tool, target(tool), fn );
tool.document().redrawAll();
}
function target(tool){
var doc = tool.document();
if( tool.getParameter("Selection Only") ){
return doc.selectedObject();
} else {
return doc.root();
}
}
// applies a function to an object and then, if user has selected recurse,
// recursively iterates across that object's children
function iterate_on_objects( tool, obj, fn ){
fn( obj );
if( tool.getParameter("Recurse Children") ){
for( var i=0; i<obj.childCount(); i++ ){
iterate_on_objects( tool, obj.childAtIndex(i), fn );
}
}
}
```

#### Pseudo-Random Numbers

One of the useful features of Cheetah 3D’s Particle System objects is that they are driven by a pseudo-random number generator for which you *explicitly* provide a seed value (which defaults to 12345). This means that a particle system will *deterministically* produce results based on that seed value. The effects *seem* random, but they’re completely predictable and reproducible. This means that if you’ve tweaked your particle settings to produce a pleasing result it won’t suddenly do something different when you render it (or the next time you load the file).

But what if you’re writing your own Particle System? Cheetah 3D doesn’t provide access to the pseudo-random number generator it uses internally. Here’s the current version of my Particle City script, a Particle System specifically designed to lay out buildings on a street grid. Note the Pseudo-random number generator! Also note how I obtain the seed value that Cheetah 3D automatically provides as part of any Particle System interface (so I don’t even need to create my own).

If you want to create procedural meshes (e.g. like the Relief object) you can make sure they will produce deterministic results based on a seed value using this pseudo-random number generator, or another like it.

```js
//
// Particle City
// ©2010-12 Tonio Loewald
//
function buildUI(obj){
obj.setParameter("name","Procedural City");
obj.addParameterFloat( "Grid Spacing", 1,0.001,100,true, true);
obj.addParameterInt("Width",10,3,100,true,true);
obj.addParameterInt("Depth",10,3,100,true,true);
obj.addParameterInt("Block Size",2,1,5,true,true);
obj.addParameterFloat("Street Width",0.3,0.01,1,true,true);
obj.addParameterFloat("Floor Height",0.05,0.001,1,true,true);
obj.addParameterInt("Max Floor Reduction",3,0,100,true,true);
obj.addParameterFloat("Density",0.7,0,1,true,true);
}
function buildObject(obj){
var core = obj.core();
var gridSpacing = obj.getParameter("Grid Spacing");
var width = obj.getParameter("Width");
var depth = obj.getParameter("Depth");
var blockSize = obj.getParameter("Block Size");
var streetWidth = obj.getParameter("Street Width");
var maxFloors = obj.getParameter("Max Floor Reduction");
var floorHeight = obj.getParameter("Floor Height");
var density = obj.getParameter("Density");
// Pseudo-random number generator
var pseudoRandom = {
seed : obj.getParameter("seed"),
value : function(){
this.seed = ( this.seed * 9301 + 49297) % 233280;
return this.seed / 233280;
},
range : function( min, max ){
return Math.floor( this.value() * (max - min + 1) + min );
}
}
var w = (width - 1) * gridSpacing + Math.floor( width / blockSize ) * streetWidth;
var d = (depth - 1) * gridSpacing + Math.floor( depth / blockSize ) * streetWidth;
for( var x = 0; x < width; x++ ){
for( var z = 0; z < depth; z++ ){
if( pseudoRandom.value() < density ){
var bx = x * gridSpacing - w * 0.5 + Math.floor( x / blockSize ) * streetWidth;
var bz = z * gridSpacing - d * 0.5 + Math.floor( z / blockSize ) * streetWidth;
var  p = core.addParticle();
// drop each building by a number of floors
var h = pseudoRandom.range( 0, maxFloors ) * floorHeight;
p.setPosition( new Vec3D(bx, -h, bz) );
// rotate each building by a multiple of 90°
var r = pseudoRandom.range( 0, 3 ) * 90;
p.setRotation( new Vec3D( 0, r, 0 ) );
}
}
}
}
```
