Skip to content

Ecce Signum

Immanentize the Empathy

  • Home
  • About Me
  • Published Works and Literary Matters
  • Indexes
  • Laboratory
  • Notebooks
  • RSS Feed

Category: Programming

Procedural Generation 1 – Creating a Cave using Cellular Automata

2011-11-01 John Winkelman

Cellular Automata rule sets are quite useful for building cave-like systems.

  1. Fill a bitmap with random black and white dots. Black represents filled space, white represents open space.
  2. Iterate through the bitmap, applying the CA rules to each pixel
  3. create the updated bitmap
  4. cycle back to step 2, repeat until desired result

The following is the series, from random noise all the way to a completed cave system. Each image is 64×64, though it can easily be scaled up to any arbitrary size. I have found that images at 256×256 and above tend to bog down somewhat, so be careful.

Once you have tweaked the algorithms to get the desired cave design, you may have more than one disconnected piece. There are three ways you can re-connect those pieces to the cave system

  1. go through and eliminate all but the largest open area
    – This might result in a quite small cave, so some experimentation here might be necessary, such as setting an arbitrary number of pixels or percentage of overall area as the minimum allowed cave size
  2. draw tunnels to connect the pieces
    – either find the center point of each piece, and draw a tunnel from point A to point B, or iterate through each point in each open area, find there they are closest together, and connect those points
  3. in multilevel games, connect to the next level(s) up and down. A cave needn’t be only one level.
    – this assumes that the same issue does not exist in the next level up and/or down. An unfortunate selection of initial starting conditions could result in two “silo” caves next to each other, with few or no connection points.

There will be some back and forth between tweaking the algorithm and deciding what makes for the best caves. You can add logic to sometimes eliminate extra rooms, sometimes connect them, and sometimes connect them to the next level.

Of course, you don’t necessarily need to create all of the levels at load time. You can create them on the fly – though this does make vertical connections more difficult. Do some experimenting; see which method works best for you. I suggest always having at least two lower levels created, to reduce potential conflicts when fleshing out the current level.

Posted in ProgrammingTagged procedural art comment on Procedural Generation 1 – Creating a Cave using Cellular Automata

Procedural Generation, Intro

2011-10-31 John Winkelman

This is the first of what I hope to be many posts exploring the topic of procedural generation, particularly as it applies to game development and art.

At it simplest level, procedural generation (pg) is the use of a small amount of code, or an algorithm, to create a result, rather than creating that result by hand. Randomness and pseudo-randomness generally figure into the process, as well as set theory, emergence, and a wide variety of mathematical concepts such as fractals, the Fibonacci sequence, cellular automata, Perlin noise algorithms, and occasionally cryptography.

PG starts with the creation of a series of bits or numbers, then branches out into the myriad uses to which that series can be applied. How the numbers are chosen is just as important. So PG starts a level lower, at the algorithm which creates the data.

A list of numbers can mean almost anything depending on its context. But for a given context, not all sets of numbers will work. Therefore it is important to have a number generator which will produce useful data for a given task. This is where experimentation comes in to play.

But enough of the high-level stuff.

I have several years of notes, graphics, experiments, and source code through which I am currently sorting. Over the upcoming months I will post breakdowns of some of them, particularly those which can be applied to game development. And in those, I will be providing ideas about how to make PG useful, and how to tweak things so that using this method actually saves time and effort. Here are some of the ideas which I will cover:

  • terrain generation
  • town placement
  • resource placement
  • maze generation
  • cave/dungeon generation and population
  • place name generation
  • graphics creation
  • plant/tree generation

…and various combinations of the above.

In the meantime, click here to see the nearly 30 old entries I have made in this blog regarding procedural generation.

Posted in ProgrammingTagged procedural art comment on Procedural Generation, Intro

Circular HTML Elements Using CSS3 Border Radius

2011-10-13 John Winkelman

If you are using a “modern” browser, you should see a box full of circles.

 

abcde fghij klmno pqrst uvwxy z

If not, you need to upgrade your browser.

The circles were created using the new CSS3 border-radius property. The markup I used looks something like this:

<div id="post-20111013-a">
<span>a</span><span>b</span><span>c</span><span>d</span><span>e</span>
<span>f</span><span>g</span><span>h</span><span>i</span><span>j</span>
<span>k</span><span>l</span><span>m</span><span>n</span><span>o</span>
<span>p</span><span>q</span><span>r</span><span>s</span><span>t</span>
<span>u</span><span>v</span><span>w</span><span>x</span><span>y</span>
<span class="s2">z</span>
</div>
#post-20111013-a {width:640px;height:480px;outline:1px solid #999;background:#ffffff;}
#post-20111013-a span {
	float:left;
	width:50px;
	height:50px;
	margin:14px;
	border:1px solid red;
	background:#999999;
	-webkit-border-radius: 27px;
	-moz-border-radius: 27px;
	border-radius: 27px;
	text-align:center;
	line-height:50px;
}
#post-20111013-a span.s2 {
	float:none;
	clear:both;
	display:block;
	margin:20px auto;
	width:100px;
	height:100px;
	line-height:100px;
	-webkit-border-radius: 54px;
	-moz-border-radius: 54px;
	border-radius: 54px;
}
#post-20111013-a span:hover {
	-webkit-box-shadow: 5px 5px 5px 0px rgba(0, 0, 0, .5);
	-moz-box-shadow: 5px 5px 5px 0px rgba(0, 0, 0, .5);
	box-shadow: 5px 5px 5px 0px rgba(0, 0, 0, .5);
}
#post-20111013-a span.s2:hover {
-webkit-box-shadow: 0px 0px 20px 20px rgba(255, 0, 0, .5);
	-moz-box-shadow: 0px 0px 20px 20px rgba(255, 0, 0, .5);
	box-shadow: 0px 0px 20px 20px rgba(255, 0, 0, .5);
}

The first block is the HTML which makes up the above demo, and the second is the style sheet. Pretty self-explanatory. I set the border radius to slightly more than half of the diameter of the circle (width/height of the element). Putting the border radius at exactly half of the diameter renders as a very slightly squarish circle. You can play around with border-radius on this page at the w3schools. You can read more about the browser compatibility and best practices at The Art of the Web.

Unfortunately the hit area for each element is still a rectangle large enough to contain the circle, so in that sense it is not much of an improvement over using a background image. Still, it is a huge step in the right direction.

Posted in Programming comment on Circular HTML Elements Using CSS3 Border Radius

Mersenne Twister in Actionscript

2011-10-12 John Winkelman

A few years ago I attempted to create a game for the GameDev.net Four Elements Contest. I had an idea that I wanted the game to be a cross between Nethack and Elite – and maybe a little Spore – which is to say, loads and loads of procedurally generated content. I never got past a very rough prototype of the world-building engine, but I learned a lot about procedural generation, and game development in general. Specifically, that it takes a lot more time than I generally have available.

One of the artifacts of this experiment was an extremely useful Mersenne Twister class, which I ported over from a C class I found on Wikipedia. A Mersenne Twister is a seeded pseudo-random number generator. In other words, for a given input n and a range r, it will return a random number between 0 (or whichever number you designate as the lower bound) and r, using n as the seed.

How is that useful? If you want to be able to, for instance, save a game which is based on random number-seeded procedural content, you want to be able to return the same seed every time. And if someone wants to start a new game, you want that seed to be different, but also repeatable. If you can’t reload a saved game and have it be based off the same random number as before, then loading a game would be no different from starting a new one.

Anyway. Here is the Actionscript 3 class:

/*
   A C-program for MT19937, with initialization improved 2002/1/26.
   Coded by Takuji Nishimura and Makoto Matsumoto.

   Before using, initialize the state by using init_genrand(seed)
   or init_by_array(init_key, key_length).

   Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
   All rights reserved.

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:

     1. Redistributions of source code must retain the above copyright
        notice, this list of conditions and the following disclaimer.

     2. Redistributions in binary form must reproduce the above copyright
        notice, this list of conditions and the following disclaimer in the
        documentation and/or other materials provided with the distribution.

     3. The names of its contributors may not be used to endorse or promote
        products derived from this software without specific prior written
        permission.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


   Any feedback is very welcome.
   http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
   email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)

     -------------------

     Converted to Actionscript 2005 by John Winkelman
     Feedback welcome at john.winkelman@gmail.com
*/


/* Period parameters */
package org.eccesignum.utilities {
    public class MersenneTwister {
        private var N:Number = 624;
        private var M:Number = 397;
        private var MATRIX_A:Number = 0x9908b0df;   /* constant vector a */
        private var UPPER_MASK:Number = 0x80000000; /* most significant w-r bits */
        private var LOWER_MASK:Number = 0x7fffffff; /* least significant r bits */

        private var mt:Array; /* the array for the state vector  */
        private var mti:Number;

        private var seed:Number;
        private var returnLength:Number;
        private var maxSize:Number;

        private var returnArray:Array;


        public function MersenneTwister():void {

        }

        public function twist($seed:Number,$returnLength:int,$maxSize:int):Array {    //    seed number, number of values to return ,max size of returned number
            seed = $seed;
            returnLength = $returnLength;
            maxSize = $maxSize;
            mt = [];

            returnArray = [];

            mti = N+1; /* mti==N+1 means mt[N] is not initialized */
            var i:int;
            //var initArray=(0x123, 0x234, 0x345, 0x456);    //2010.04.20    modiied to the below
            var initArray:Array = [0x123, 0x234, 0x345, 0x456];
            init_by_array(initArray,initArray.length);
            for (i=0; i<returnLength; i++) {
                returnArray[i] = genrand_int32()%maxSize;
            }
            //returnArray.sort(16);
            //trace(returnArray);
            /*
            trace("\n1000 outputs of genrand_real2()\n");
            for (i=0; i<returnLength; i++) {
              trace(" " + genrand_real2());
              if (i%5==4) trace("\n");
            }
            */
            return returnArray;

        }


        /* initializes mt[N] with a seed */
        private function init_genrand($seed:Number):void {
            mt[0]= $seed & 0xffffffff;
            for (mti=1; mti<N; mti++) {
                mt[mti] = (1812433253 * (mt[mti-1] ^ (mt[mti-1] >> 30)) + mti);
                mt[mti] &= 0xffffffff;
                /* for >32 bit machines */
            }
        }

        /* initialize by an array with array-length */
        /* init_key is the array for initializing keys */
        /* key_length is its length */
        /* slight change for C++, 2004/2/26 */
        //    void init_by_array(unsigned long init_key[], int key_length)

        private function init_by_array($seedArray:Array,$seedArrayLength:Number):void {
            var i:Number = 1;
            var j:Number = 0;
            init_genrand(seed);
            //init_genrand(19650218);
            var k:Number = (N>$seedArrayLength) ? N : $seedArrayLength;
            for (k; k>0; k--) {
                mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1664525)) + $seedArray[j] + j; /* non linear */
                mt[i] &= 0xffffffff; /* for WORDSIZE > 32 machines */
                i++;
                j++;
                if (i >= N) {
                    mt[0] = mt[N-1];
                    i=1;
                }
                if (j >= $seedArrayLength) j=0;
            }
            for (k = N-1; k; k--) {
                mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1566083941)) - i; /* non linear */
                mt[i] &= 0xffffffff; /* for WORDSIZE > 32 machines */
                i++;
                if (i>=N) {
                    mt[0] = mt[N-1];
                    i=1;
                }
            }

            mt[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */
        }

        /* generates a random number on [0,0xffffffff]-interval */
        private function genrand_int32():Number    {
            var y:Number;
            var mag01:Array=[0x0, MATRIX_A];
            /* mag01[x] = x * MATRIX_A  for x=0,1 */

            if (mti >= N) { /* generate N words at one time */
                var kk:Number;

                if (mti == N+1)   /* if init_genrand() has not been called, */
                    init_genrand(5489); /* a default initial seed is used */

                for (kk=0;kk<N-M;kk++) {
                    y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
                    mt[kk] = mt[kk+M] ^ (y >> 1) ^ mag01[y & 0x1];
                }
                for (;kk<N-1;kk++) {
                    y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
                    mt[kk] = mt[kk+(M-N)] ^ (y >> 1) ^ mag01[y & 0x1];
                }
                y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK);
                mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1];

                mti = 0;
            }

            y = mt[mti++];

            /* Tempering */
            y ^= (y >> 11);
            y ^= (y << 7) & 0x9d2c5680;
            y ^= (y << 15) & 0xefc60000;
            y ^= (y >> 18);

            return y;
        }

        /* generates a random number on [0,0x7fffffff]-interval */
        private function genrand_int31():Number    {
            return (genrand_int32()>>1);
        }

        /* generates a random number on [0,1]-real-interval */
        private function genrand_real1():Number    {
            return genrand_int32()*(1.0/4294967295.0);
            /* divided by 2^32-1 */
        }

        /* generates a random number on [0,1)-real-interval */
        private function genrand_real2():Number {
            return genrand_int32()*(1.0/4294967296.0);
            /* divided by 2^32 */
        }

        /* generates a random number on (0,1)-real-interval */
        private function genrand_real3():Number    {
            return ((genrand_int32()) + 0.5)*(1.0/4294967296.0);
            /* divided by 2^32 */
        }

        /* generates a random number on [0,1) with 53-bit resolution*/
        private function genrand_res53():Number    {
            var a:Number = genrand_int32()>>5;
            var b:Number = genrand_int32()>>6;
            return(a*67108864.0+b)*(1.0/9007199254740992.0);
        }
        /* These real versions are due to Isaku Wada, 2002/01/09 added */
    }
}

And it is called like this:

var twister:MersenneTwister = new MersenneTwister();
twister.twist(17436,100,50000); // seed number, number of values to return, maximum size of a given value

Since I wrote this, many other people have made versions in Actionscript. There is a comprehensive list on the Mersenne Twister page at Wikipedia.

Posted in ProgrammingTagged Flash, game development, procedural art comment on Mersenne Twister in Actionscript

Targeting Flash Player 11 in the Flex SDK

2011-10-07 John Winkelman

Here are instructions for setting up the Flex SDK to allow Flash development targeting the new Flash 11 player, and how to set up the HTML in which it is embedded to allow for use of hardware acceleration, where appropriate.

  1. Download the Flex 4.5 SDK from here: http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4.5
  2. Download the new playerglobal.swc from here: http://www.adobe.com/support/flashplayer/downloads.html
    1. If it is named anything other than “playerglobal.swc” change the file name to “playerglobal.swc”
  3. In the Flex SDK files you just downloaded, create a new folder here: [FLEX SDK]/frameworks/libs/player/11.0
  4. Place the playerglobal.swc you just downloaded into the new 11.0 folder
  5. Target the Flash 11 playerglobal in the flex-config.xml file as follows:
    1. in a text editor, open [FLEX SDK]/frameworks/flex-config.xml
    2. change the <target-player/> value to 11.0
    3. change the <swf-version/> value to 13
  6. Download the standalone Flash 11 projector from here: http://www.adobe.com/support/flashplayer/downloads.html
  7. Update the flash player in your browsers as follows:
    1. Google Chrome – click on the wrench icon in the upper right of the browser, then click on “About Google Chrome”. This will force Chrome to automatically update itself, which will include the new version of the Flash plugin
    2. Firefox – visit this url: http://get.adobe.com/flashplayer/ and follow the directions therein
    3. Internet Explorer – visit this url: http://get.adobe.com/flashplayer/ and follow the instructions therein
  8. In order to take advantage of hardware acceleration in your new Flash movies, be sure that in the <object/>and <embed/> tags, you set the wmode attribute to direct. This is the only way that hardware acceleration will work.
  9. If using SWFObject or jQuery or some other JavaScript library to dynamically embed the Flash movie, refer to the appropriate documentation to find out how to change the wmode parameter
  10. Create a new .swf and run it in the new player. See how much faster it runs!
Posted in ProgrammingTagged Flash comment on Targeting Flash Player 11 in the Flex SDK

Syntax Highlighter Test

2011-10-05 John Winkelman

I just installed the Syntax Highlighter Javascript Library, which makes it easy to display easily-readable source code on websites. As my first test, here is a class I wrote in Actionscript 3 a couple of years ago, when I was deep in a dozen different projects, all built using Notepad++ and the MXMLC command-line compiler. The upshot of that was, no way to “trace” output from the .swf file. So I wrote my own, more or less. Here is the source code:

package {
	import flash.display.DisplayObjectContainer;
	import flash.display.Stage;
	import flash.display.Sprite;
	import flash.events.TimerEvent;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.utils.Timer;
	public class InfoPanel extends Sprite {
		private var dragBar:Sprite;
		private var closeButton:Sprite;
		private var clearButton:Sprite;
		private var resizeButton:Sprite;
		private var outputWindow:TextField;
		private var timer:Timer;
		private var base:DisplayObjectContainer;
		private var isDragging:Boolean = false;
		private var isResizing:Boolean = false;
		private var dragMouseOffX:Number = 0;
		private var dragMouseOffY:Number = 0;
		private var resizeMouseOffX:Number = 0;
		private var resizeMouseOffY:Number = 0;
		private var w:Number = 0;
		private var h:Number = 0;
		private var bgc:Number;
		private var fgc:Number;
		private var outputFormat:TextFormat = new TextFormat("Courier New",11,0x33ff33,false,false,false,null,null,"left");
		public function InfoPanel($base:DisplayObjectContainer,$w:Number = 150,$h:Number = 100,$bgc:Number=0x000000,$fgc:Number=0x66ff66):void {
			base = $base;
			w = $w;
			h = $h;
			bgc = $bgc;
			fgc = $fgc;
			outputFormat.color = fgc;
			graphics.lineStyle(0,0x666666,1);
			graphics.beginFill(bgc,1);
			graphics.drawRect(0,0,w,h);
			graphics.endFill();
			dragBar = new Sprite();
			dragBar.graphics.beginFill(0x666666,1);
			dragBar.graphics.drawRect(0,0,w,10);
			dragBar.graphics.endFill();
			dragBar.buttonMode = true;
			dragBar.useHandCursor = true;
			dragBar.addEventListener(MouseEvent.MOUSE_DOWN,onDragMouseDown);
			dragBar.addEventListener(MouseEvent.MOUSE_UP,onDragMouseUp);
			dragBar.x = 0;
			dragBar.y = 0;
			addChild(dragBar);
			
			
			clearButton = new Sprite();
			clearButton.graphics.lineStyle(0,0xcccccc,1)
			clearButton.graphics.beginFill(0x666666,1);
			clearButton.graphics.drawCircle(0,0,3);
			clearButton.graphics.endFill();
			clearButton.x = w-15;
			clearButton.y = 5;
			clearButton.buttonMode = true;
			clearButton.useHandCursor = true;
			clearButton.addEventListener(MouseEvent.CLICK,onClearButtonClicked);
			addChild(clearButton);
			
			
			closeButton = new Sprite();
			closeButton.graphics.beginFill(0xcccccc,1);
			closeButton.graphics.drawCircle(0,0,3);
			closeButton.graphics.endFill();
			closeButton.x = w-5;
			closeButton.y = 5;
			closeButton.buttonMode = true;
			closeButton.useHandCursor = true;
			closeButton.addEventListener(MouseEvent.CLICK,onCloseButtonClicked);
			addChild(closeButton);
			resizeButton = new Sprite();
			resizeButton.graphics.beginFill(0xcccccc,1);
			resizeButton.graphics.drawRect(0,0,6,6);
			resizeButton.graphics.endFill();
			resizeButton.x = w-6;
			resizeButton.y = h-6;
			resizeButton.buttonMode = true;
			resizeButton.useHandCursor = true;
			resizeButton.addEventListener(MouseEvent.MOUSE_DOWN,onResizeMouseDown);
			resizeButton.addEventListener(MouseEvent.MOUSE_UP,onResizeMouseUp);
			addChild(resizeButton);

			outputWindow = new TextField();
			outputWindow.width = w-10;
			outputWindow.height = h-20;
			outputWindow.x = 5;
			outputWindow.y = 15;
			outputWindow.selectable = true;
			outputWindow.wordWrap = true;
			outputWindow.multiline = true;
			outputWindow.text = "STATS";
			outputWindow.setTextFormat(outputFormat);
			outputWindow.defaultTextFormat = outputFormat;
			
			addChild(outputWindow);
			timer = new Timer(25);
			timer.addEventListener(TimerEvent.TIMER,onTimer);
		}
		
		private function onTimer(e:TimerEvent):void {
			if(isDragging==true) dragMe();
			if(isResizing==true) resizeMe();
			e.updateAfterEvent();
		}
		private function onDragMouseDown(e:MouseEvent):void {
			e.stopPropagation();
			dragMouseOffX = e.target.mouseX;
			dragMouseOffY = e.target.mouseY;
			startMe();
			isDragging = true;
		}
		private function onDragMouseUp(e:MouseEvent):void {
			isDragging = false;
			stopMe();
			base.addChild(this);
		}
		private function onResizeMouseDown(e:MouseEvent):void {
			e.stopPropagation();
			resizeMouseOffX = e.target.mouseX;
			resizeMouseOffY = e.target.mouseY;
			startMe();
			isResizing = true;
		}
		private function onResizeMouseUp(e:MouseEvent):void {
			isResizing = false;
			stopMe();
		}
		private function onCloseButtonClicked(e:MouseEvent):void {
			e.stopPropagation();
			hideMe();
		}
		private function onClearButtonClicked(e:MouseEvent):void {
			outputWindow.text = "";
		}
		private function startMe():void {
			timer.start();
		}
		private function stopMe():void {
			timer.stop();
		}
		public function showMe():void {
			base.addChild(this);
		}
		public function hideMe():void {
			base.removeChild(this);
		}
		private function dragMe():void {
			x = stage.mouseX - dragMouseOffX;
			y = stage.mouseY - dragMouseOffY;
		}
		private function resizeMe():void {
			w = mouseX + 6 - resizeMouseOffX;
			h = mouseY + 6 - resizeMouseOffY;
			graphics.clear();
			graphics.lineStyle(0,0x666666,1);
			graphics.beginFill(bgc,1);
			graphics.drawRect(0,0,w,h);
			graphics.endFill();
			dragBar.graphics.clear();
			dragBar.graphics.beginFill(0x666666,1);
			dragBar.graphics.drawRect(0,0,w,10);
			dragBar.graphics.endFill();
			outputWindow.width = w - 10;
			outputWindow.height = h - 20;
			resizeButton.x = w - 6;
			resizeButton.y = h - 6
			clearButton.x = w - 15;
			closeButton.x = w - 5;
		}
		public function update($s:*,$r:Boolean = false):void {
			if($r==true) {
				outputWindow.text = $s.toString();
			} else {
				outputWindow.appendText("\n"+$s.toString());
			}
			outputWindow.scrollV = outputWindow.maxScrollV;
			base.addChild(this);
		}
	}
}

And it is called like this:

var info:InfoPanel = new InfoPanel(this);
info.update("hello world");

After being created the InfoPanel can be positioned just like any other DisplayObject. It accepts up to five arguments, in this order:

parent:DisplayObjectContainer, width:Number, height:Number, backgroundColor:Number,textColor:Number

Of them, only the parent argument is required; the rest will revert to default values if left empty. The parent item must be a DisplayObjectContainer, such as the Stage, or a MovieClip or Sprite.

Once created, the InfoPanel can be moved, resized, cleared and closed with the mouse.

To update the content, use the following method call:

info.update("a string");

This will append a line break, then the string. To clear the info panel when updating it, use the method call as follows:

info.update("a string",true);

Be careful; once the content of the InfoPanel starts measuring in the many thousands of characters, updating it may start to bog down the Flash player, slowing down whatever else you are running.

Posted in ProgrammingTagged Flash comment on Syntax Highlighter Test

Technical Notes On the New Site

2011-10-03 John Winkelman

For the most part, putting this site together went smoothly. Sure copy-and-pasting 700+ blog entries was a bit of a chore, as was going through every post to find which ones had digital assets which needed to be re-hosted. Tiresome, but not difficult.

In every previous version of this site, if I had a Flash movie which I wanted to show off, I would either post it in-line in the blog, or put it on its own page and link to it, or only show a teaser image on the front page and embed the animation in the full post. For this site I wanted something new. You’ve all seen light boxes – you click on a thumbnail photo, and a translucent overlay appears over the site, and the relevant content appears therein. I decided to go with one of those.

There are myriad ways to create a lightbox. All involve Javascript and CSS, and most of them are good at what they do. I decided to go with ShadowBox.js because it supports all kind of content; not just Flash and/or images. I ran it through a few static page tests, and it had everything I needed.

Getting it hooked up in Drupal Gardens (“DG”) was a little more challenging. Since DG users don’t have access to modify the actual templates of the site, using external code files can be a little challenging. The DG help files recommend creating a block in the header and copy-and-pasting the javascript into a plain text file, and embedding it directly in the HTML for a page. This is what I have done in the past, but having just uploaded over a hundred Flash movies and photos to the content area of the site, I decided to try something new.

It turns out that you can upload any kind of content you want to the content/media area of DG. You might need to make changes in the configuration area of your site to allow for the upload of e.g. a .js or .swf file, but it can be easily done. There is one thing to note: Nothing loaded into this area is processed on the server. If you upload a .php file, it will be served as plain text, NOT processed through Drupal/PHP. Fortunately, for Javascript, I don’t need it to be processed. Plain text is just fine.

Accessing the Javascript is easy; everything uploaded to the media library on DG is in the /sites/mysite.drupalgardens.com/files/ directory. To access it, I created a block which contains a <script> tag, which links to the new files at /sites/mysite.drupalgardens.com/files/shadowbox.js. Everything worked on the first try.

More technical notes to follow, as I figure out how to do more things in Drupal.

Posted in Programming comment on Technical Notes On the New Site

Lindenmayer System Basics: More on Branches

2011-03-31 John Winkelman

This post is one of a series exploring the creation of Lindenmayer System patterns using my Lindenmayer System Explorer.

The introduction of branching into our patterns, which we explored in my previous post, allows for a near infinite variety of designs. Often these pattens come remarkably close to the patterns seen in plant growth. In order to provide some realism to the patterns, there are a few more options in the explorer which are only available when creating branches: Line Scale, Line Taper, Angle Increment, and the option of using multiple colors.

Line Scale modifies the length of individual line segments. Line taper modifies the width of line segments. Angle Increment adjusts the angle that a branch is drawn from its parent.

The following images illustrate how each modification works. Clicking on an image will take you to the explorer pre-configured to recreate that image.

Start with this basic tree shape:

Now change the Line Scale to .75. You should end up with this:

Each branch is 75% of the length of its parent. This can be any number greater than 0. For branches twice the length of their parents, set the value to 2. For half as long, set it to .5.

For line thickness, update the line width so that it is something like 10:

Now change the Line Taper to .75:

Each branch is 75% of the thickness of its parent. You can use any number greater than 0. For instance, to have each branch twice as thick as its parent, you would set this field to 2. For half as thick, you would set it to .5.

Now change the Angle Increment to 10:

The angle of each branch from its parent is 10 degrees greater than that of the preceding branching. This can be any positive or negative number, though they will always evaluate to a value between -360 and 360.

Finally, you can use multiple colors, which are applied at each branching, by creating a comma-separated list of hexadecimal color numbers. This will have the following effect:

As the pattern is rendered, at each branch the next color in the list is used.

You can use any hexadecimal color you would like, and use as many as you would like.

So that, along with the previous post, is Lindenmayer System branches, in a nutshell. Enjoy!

Posted in ProgrammingTagged Flash, Lindenmayer, procedural art comment on Lindenmayer System Basics: More on Branches

Lindenmayer System Basics: Branches

2011-03-13 John Winkelman

This is the fifth in a series of blog posts explaining the usage of the Lindenmayer System Explorer. Clicking on an image will take you to the explorer page, pre-configured to draw that image.

So far, we have seen many different patterns created with the L-system explorer – fractals, dragon curves, snowflakes, and so on. They all have one thing in common: they are made up of a single line.

No branching yet

To create branches, enclose the rules for a branch in square brackets, like so:

[F]+[F]+[F]

Instead of yielding a bent line, it creates a pattern like this:

Branches, 1 iteration

Not terribly interesting yet, but it does allow for the creation of more interesting shapes. Remember: Astrid Lindenmayer was a botanist, and he originally created this system to model the structure of living plants. If we nest a few brackets, and play around with the angles, we can get patterns like this:

Something like a shrub

Branch rules can be nested within each other, to the extent that extremely complex patterns can emerge very quickly:

more branching, something like a wreath

And with a little practice, the patterns can become increasingly plant-like:

closer to a plant

An oddly symmetrical tree

So that’s it for branches. In the next post I will show how you can use branching to change the drawing angles, colors, line length and line thickness to create increasingly life-like plants.

More posts on this subject:

Lines
Angles
Rule sets
The Start Condition

Posted in ProgrammingTagged Flash, Lindenmayer, procedural art comment on Lindenmayer System Basics: Branches

L-System Basics: The Start Condition

2011-02-10 John Winkelman

In this post I will discuss some of the different patterns created by modifying the start condition in the Lindenmayer System Explorer. Clicking on any of the images will take you to the explorer tool, preloaded with the variables necessary to re-create that image.

So by now you all have seen the basic pattern which is created by the default settings in the explorer:

All well and good, but it feels incomplete; maybe a little lop-sided. Change the start condition to “F+F+F+F”, and you will see this:

How did this happen?!? Look at the angle: 90°. When you click the render button, the start condition and the grammar are run through an algorithm which creates a long string of characters. Every time an “F” is encountered, a line segment is created. Every time a “+” or “-” is encountered, the angle at which the next segment will be drawn is updated by the value in the “angle” field. “+” turns clockwise, “-” turns widdershins. So in this instance, every “+” means the next line will be drawn at a 90° angle to the previous segment. In the first example, having “F” as the starting condition drew, overall, a single quarter of a square pattern. Changing the start condition to “F+F+F+F” means that the initial 90° angle would be repeated 4 times, each at a 90° offset from the previous. 90 x 4 = 360°, which brings the line back to the start position.

This will work with any number which divides evenly into 360. Here is a 5-sided (72°) figure:

Six sides at 60°:

…and so on. As long as the starting condition and angles are correct, you can put almost anything in the grammar and use any number of iterations, and the result will still be a closed shape. Here are a few more:

Posted in ProgrammingTagged Flash, Lindenmayer, procedural art comment on L-System Basics: The Start Condition

Posts navigation

Older posts
Newer posts

Personal website of
John Winkelman

John Winkelman in closeup

Archives

Categories

Posts By Month

May 2025
S M T W T F S
 123
45678910
11121314151617
18192021222324
25262728293031
« Apr    

Links of Note

Reading, Writing
Tor.com
Locus Online
The Believer
File 770
IWSG

Watching, Listening
Writing Excuses Podcast
Our Opinions Are Correct
The Naropa Poetics Audio Archive

News, Politics, Economics
Naked Capitalism
Crooked Timber

Meta

  • Log in
  • Entries feed
  • Comments feed
  • WordPress.org

© 2025 Ecce Signum

Proudly powered by WordPress | Theme: x-blog by wpthemespace.com