diff --git a/.gitignore b/.gitignore
index f112f7f..fb18345 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ bin-release/
# Other files and folders
.settings/
+*.swf
# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
# should NOT be excluded as they contain compiler settings and other important
diff --git a/editor/.actionScriptProperties b/editor/.actionScriptProperties
new file mode 100644
index 0000000..afc7408
--- /dev/null
+++ b/editor/.actionScriptProperties
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/editor/.flexProperties b/editor/.flexProperties
new file mode 100644
index 0000000..bd53f3a
--- /dev/null
+++ b/editor/.flexProperties
@@ -0,0 +1,3 @@
+
+
+
diff --git a/editor/.project b/editor/.project
new file mode 100644
index 0000000..b0b7fab
--- /dev/null
+++ b/editor/.project
@@ -0,0 +1,24 @@
+
+
+ MapShapeGen
+
+
+
+
+
+ com.adobe.flexbuilder.project.flexbuilder
+
+
+
+
+ com.adobe.flexbuilder.project.apollobuilder
+
+
+
+
+
+ com.adobe.flexbuilder.project.apollonature
+ com.adobe.flexbuilder.project.flexnature
+ com.adobe.flexbuilder.project.actionscriptnature
+
+
diff --git a/editor/assets/fonts/Ubuntu/LICENCE.txt b/editor/assets/fonts/Ubuntu/LICENCE.txt
new file mode 100644
index 0000000..ae78a8f
--- /dev/null
+++ b/editor/assets/fonts/Ubuntu/LICENCE.txt
@@ -0,0 +1,96 @@
+-------------------------------
+UBUNTU FONT LICENCE Version 1.0
+-------------------------------
+
+PREAMBLE
+This licence allows the licensed fonts to be used, studied, modified and
+redistributed freely. The fonts, including any derivative works, can be
+bundled, embedded, and redistributed provided the terms of this licence
+are met. The fonts and derivatives, however, cannot be released under
+any other licence. The requirement for fonts to remain under this
+licence does not require any document created using the fonts or their
+derivatives to be published under this licence, as long as the primary
+purpose of the document is not to be a vehicle for the distribution of
+the fonts.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this licence and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Original Version" refers to the collection of Font Software components
+as received under this licence.
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to
+a new environment.
+
+"Copyright Holder(s)" refers to all individuals and companies who have a
+copyright ownership of the Font Software.
+
+"Substantially Changed" refers to Modified Versions which can be easily
+identified as dissimilar to the Font Software by users of the Font
+Software comparing the Original Version with the Modified Version.
+
+To "Propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification and with or without charging
+a redistribution fee), making available to the public, and in some
+countries other activities as well.
+
+PERMISSION & CONDITIONS
+This licence does not grant any rights under trademark law and all such
+rights are reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of the Font Software, to propagate the Font Software, subject to
+the below conditions:
+
+1) Each copy of the Font Software must contain the above copyright
+notice and this licence. These can be included either as stand-alone
+text files, human-readable headers or in the appropriate machine-
+readable metadata fields within text or binary files as long as those
+fields can be easily viewed by the user.
+
+2) The font name complies with the following:
+(a) The Original Version must retain its name, unmodified.
+(b) Modified Versions which are Substantially Changed must be renamed to
+avoid use of the name of the Original Version or similar names entirely.
+(c) Modified Versions which are not Substantially Changed must be
+renamed to both (i) retain the name of the Original Version and (ii) add
+additional naming elements to distinguish the Modified Version from the
+Original Version. The name of such Modified Versions must be the name of
+the Original Version, with "derivative X" where X represents the name of
+the new work, appended to that name.
+
+3) The name(s) of the Copyright Holder(s) and any contributor to the
+Font Software shall not be used to promote, endorse or advertise any
+Modified Version, except (i) as required by this licence, (ii) to
+acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with
+their explicit written permission.
+
+4) The Font Software, modified or unmodified, in part or in whole, must
+be distributed entirely under this licence, and must not be distributed
+under any other licence. The requirement for fonts to remain under this
+licence does not affect any document created using the Font Software,
+except any version of the Font Software extracted from a document
+created using the Font Software may only be distributed under this
+licence.
+
+TERMINATION
+This licence becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
+COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
+DEALINGS IN THE FONT SOFTWARE.
diff --git a/editor/assets/fonts/Ubuntu/Ubuntu-B.ttf b/editor/assets/fonts/Ubuntu/Ubuntu-B.ttf
new file mode 100644
index 0000000..c0142fe
Binary files /dev/null and b/editor/assets/fonts/Ubuntu/Ubuntu-B.ttf differ
diff --git a/editor/assets/fonts/Ubuntu/Ubuntu-BI.ttf b/editor/assets/fonts/Ubuntu/Ubuntu-BI.ttf
new file mode 100644
index 0000000..12e4c7d
Binary files /dev/null and b/editor/assets/fonts/Ubuntu/Ubuntu-BI.ttf differ
diff --git a/editor/assets/fonts/Ubuntu/Ubuntu-C.ttf b/editor/assets/fonts/Ubuntu/Ubuntu-C.ttf
new file mode 100644
index 0000000..8d3e867
Binary files /dev/null and b/editor/assets/fonts/Ubuntu/Ubuntu-C.ttf differ
diff --git a/editor/assets/fonts/Ubuntu/Ubuntu-L.ttf b/editor/assets/fonts/Ubuntu/Ubuntu-L.ttf
new file mode 100644
index 0000000..7b7ac7d
Binary files /dev/null and b/editor/assets/fonts/Ubuntu/Ubuntu-L.ttf differ
diff --git a/editor/assets/fonts/Ubuntu/Ubuntu-LI.ttf b/editor/assets/fonts/Ubuntu/Ubuntu-LI.ttf
new file mode 100644
index 0000000..e36de45
Binary files /dev/null and b/editor/assets/fonts/Ubuntu/Ubuntu-LI.ttf differ
diff --git a/editor/assets/fonts/Ubuntu/Ubuntu-M.ttf b/editor/assets/fonts/Ubuntu/Ubuntu-M.ttf
new file mode 100644
index 0000000..443ec8b
Binary files /dev/null and b/editor/assets/fonts/Ubuntu/Ubuntu-M.ttf differ
diff --git a/editor/assets/fonts/Ubuntu/Ubuntu-MI.ttf b/editor/assets/fonts/Ubuntu/Ubuntu-MI.ttf
new file mode 100644
index 0000000..321eccf
Binary files /dev/null and b/editor/assets/fonts/Ubuntu/Ubuntu-MI.ttf differ
diff --git a/editor/assets/fonts/Ubuntu/Ubuntu-R.ttf b/editor/assets/fonts/Ubuntu/Ubuntu-R.ttf
new file mode 100644
index 0000000..45a038b
Binary files /dev/null and b/editor/assets/fonts/Ubuntu/Ubuntu-R.ttf differ
diff --git a/editor/assets/fonts/Ubuntu/Ubuntu-RI.ttf b/editor/assets/fonts/Ubuntu/Ubuntu-RI.ttf
new file mode 100644
index 0000000..6f819f6
Binary files /dev/null and b/editor/assets/fonts/Ubuntu/Ubuntu-RI.ttf differ
diff --git a/editor/assets/fonts/Ubuntu/UbuntuMono-B.ttf b/editor/assets/fonts/Ubuntu/UbuntuMono-B.ttf
new file mode 100644
index 0000000..7bd6665
Binary files /dev/null and b/editor/assets/fonts/Ubuntu/UbuntuMono-B.ttf differ
diff --git a/editor/assets/fonts/Ubuntu/UbuntuMono-BI.ttf b/editor/assets/fonts/Ubuntu/UbuntuMono-BI.ttf
new file mode 100644
index 0000000..6c5b8ba
Binary files /dev/null and b/editor/assets/fonts/Ubuntu/UbuntuMono-BI.ttf differ
diff --git a/editor/assets/fonts/Ubuntu/UbuntuMono-R.ttf b/editor/assets/fonts/Ubuntu/UbuntuMono-R.ttf
new file mode 100644
index 0000000..fdd309d
Binary files /dev/null and b/editor/assets/fonts/Ubuntu/UbuntuMono-R.ttf differ
diff --git a/editor/assets/fonts/Ubuntu/UbuntuMono-RI.ttf b/editor/assets/fonts/Ubuntu/UbuntuMono-RI.ttf
new file mode 100644
index 0000000..18f81a2
Binary files /dev/null and b/editor/assets/fonts/Ubuntu/UbuntuMono-RI.ttf differ
diff --git a/editor/libs/mignari.swc b/editor/libs/mignari.swc
new file mode 100644
index 0000000..77c7de0
Binary files /dev/null and b/editor/libs/mignari.swc differ
diff --git a/editor/libs/mignari_core.swc b/editor/libs/mignari_core.swc
new file mode 100644
index 0000000..c1ccbf5
Binary files /dev/null and b/editor/libs/mignari_core.swc differ
diff --git a/editor/libs/otlib_core.swc b/editor/libs/otlib_core.swc
new file mode 100644
index 0000000..2f902de
Binary files /dev/null and b/editor/libs/otlib_core.swc differ
diff --git a/editor/locale/en_US/mapgen_strings.properties b/editor/locale/en_US/mapgen_strings.properties
new file mode 100644
index 0000000..af926bb
--- /dev/null
+++ b/editor/locale/en_US/mapgen_strings.properties
@@ -0,0 +1,22 @@
+# General
+OPTIONS=Options
+SELECT_OUTPUT_FOLDER=Select the output folder
+
+# Menu
+MENU_FILE=File
+MENU_FILE_NEW=New
+MENU_FILE_OPEN=Open...
+MENU_FILE_GENERATE=Generate
+MENU_FILE_SAVE=Save
+MENU_FILE_PREFERENCES=Preferences
+MENU_FILE_EXIT=Exit
+MENU_HELP=Help
+MENU_HELP_ABOUT=About
+
+# Toolbar
+
+# PreferencesWindow
+PW_TITLE=Preferences
+
+# AboutWindow
+ABOUT_WINDOW_TITLE=About
\ No newline at end of file
diff --git a/editor/src/MapShapeGen-app.xml b/editor/src/MapShapeGen-app.xml
new file mode 100644
index 0000000..553967b
--- /dev/null
+++ b/editor/src/MapShapeGen-app.xml
@@ -0,0 +1,46 @@
+
+
+
+ com.mignari.MapShapeGen
+ MapShapeGen
+ MapShapeGen
+ 1.0.0
+ 1.0
+
+
+ [This value will be overwritten by Flash Builder in the output app.xml]
+ false
+ false
+ false
+
+
+ en
+
+
+ icon/app/icon16.png
+ icon/app/icon32.png
+ icon/app/icon36.png
+ icon/app/icon48.png
+ icon/app/icon72.png
+ icon/app/icon96.png
+ icon/app/icon114.png
+ icon/app/icon128.png
+ icon/app/icon144.png
+ icon/app/icon152.png
+
+
+
+
+ Shape
+ otshape
+ MapShapeGen File
+ binary
+
+ icon/system/icon16.png
+ icon/system/icon32.png
+ icon/system/icon48.png
+ icon/system/icon128.png
+
+
+
+
diff --git a/editor/src/MapShapeGen.css b/editor/src/MapShapeGen.css
new file mode 100644
index 0000000..3e7f210
--- /dev/null
+++ b/editor/src/MapShapeGen.css
@@ -0,0 +1,46 @@
+@namespace s "library://ns.adobe.com/flex/spark";
+@namespace mx "library://ns.adobe.com/flex/mx";
+
+@font-face
+{
+ src: url("../assets/fonts/Ubuntu/Ubuntu-L.ttf");
+ fontFamily: ubuntu;
+ advancedAntiAliasing: true;
+ fontStyle: normal;
+ embedAsCFF:true;
+}
+
+@font-face
+{
+ src: url("../assets/fonts/Ubuntu/Ubuntu-B.ttf");
+ fontFamily: ubuntu;
+ advancedAntiAliasing: true;
+ fontStyle: normal;
+ fontWeight: bold;
+ embedAsCFF:true;
+}
+
+@font-face
+{
+ src: url("../assets/fonts/Ubuntu/Ubuntu-LI.ttf");
+ fontFamily: ubuntu;
+ advancedAntiAliasing: true;
+ fontStyle: italic;
+ fontWeight: normal;
+ embedAsCFF:true;
+}
+
+global
+{
+ chromeColor: #494949;
+ rollOverColor: #355D89;
+ selectionColor: #294867;
+ color: #DFDFDF;
+ contentBackgroundColor: #494949;
+ focusedTextSelectionColor: #272727;
+ symbolColor: #DFDFDF;
+ fontFamily: ubuntu;
+ modal-transparency-blur: 0;
+ modal-transparency: 0.3;
+ modalTransparencyColor: #656565;
+}
diff --git a/editor/src/MapShapeGen.mxml b/editor/src/MapShapeGen.mxml
new file mode 100644
index 0000000..225e404
--- /dev/null
+++ b/editor/src/MapShapeGen.mxml
@@ -0,0 +1,543 @@
+
+
+
+
+
+
+
+ 0)
+ {
+ if (m_creationComplete)
+ {
+ setInvokeArguments(event.arguments);
+ }
+ else
+ {
+ m_invokeArguments = event.arguments;
+ }
+ }
+ }
+
+ protected function applicationPreinitializeHandler(event:FlexEvent):void
+ {
+ m_progressWidget = new ProgressWidget();
+
+ m_communicator = new WorkerCommunicator(Workers.MapShapeGenWorker);
+ m_communicator.registerClass(MapShapeGenSettings);
+ m_communicator.registerClass(MapInfo);
+ m_communicator.registerCallback(LogCommand, logCallback);
+ m_communicator.registerCallback(MapInfoCommand, mapInfoCallback);
+ m_communicator.registerCallback(UpdatePreviewCommand, updatePreviewCallback);
+ m_communicator.registerCallback(ProgressCommand, progressCallback);
+ m_communicator.start();
+
+ m_sharedByteArray = new ByteArray();
+ m_sharedByteArray.shareable = true;
+ m_communicator.worker.setSharedProperty("sharedByteArray", m_sharedByteArray);
+ }
+
+ protected function applicationCreationCompleteHandler(event:FlexEvent):void
+ {
+ Alert.okLabel = "Ok";
+
+ loadSettings();
+
+ m_canvas = new Canvas(1024, 1024);
+ image.source = m_canvas.bitmap;
+
+ m_creationComplete = true;
+
+ if (m_isFirstRun)
+ {
+ firstRun();
+ }
+ }
+
+ protected function applicationClosingHandler(event:Event):void
+ {
+ this.saveSettings();
+ }
+
+ protected function nativeMenuSelectedHandler(event:MenuEvent):void
+ {
+ switch(event.data)
+ {
+ case Menu.FILE_NEW:
+ this.newShape();
+ break;
+
+ case Menu.FILE_OPEN:
+ this.openShape();
+ break;
+
+ case Menu.FILE_GENERATE:
+ this.generateShape();
+ break;
+
+ case Menu.FILE_SAVE:
+ this.saveMap();
+ break;
+
+ case Menu.FILE_PREFERENCES:
+ this.openPreferences();
+ break;
+
+ case Menu.FILE_EXIT:
+ this.close();
+ break;
+
+ case Menu.HELP_ABOUT:
+ this.openAboutWindow();
+ break;
+ }
+ }
+
+ protected function nativeMenuShowMenuItem(event:FlexNativeMenuEvent):void
+ {
+ var index:uint = CapabilitiesUtil.isMacOS ? 1 : 0;
+ var items:Array = nativeMenu.items;
+
+ // File > New
+ items[index].submenu.items[0].enabled = !this.mapSaving;
+
+ // File > Open
+ items[index].submenu.items[1].enabled = !this.mapSaving;
+
+ // File > Generate
+ items[index].submenu.items[2].enabled = !this.mapSaving;
+
+ // File > Save
+ items[index].submenu.items[3].enabled = this.mapCreated && !this.mapSaving;
+
+ // File > Preferences
+ items[index].submenu.items[5].enabled = !this.mapSaving;
+ }
+
+ protected function islandTypeChangeHandler(event:IndexChangeEvent):void
+ {
+ m_settings.islandType = IslandType.value(event.newIndex);
+ m_communicator.sendCommand(new SettingsCommand(m_settings));
+ updateShape();
+ }
+
+ protected function pointTypeChangeHandler(event:IndexChangeEvent):void
+ {
+ m_settings.pointType = PointType.value(event.newIndex);
+ m_communicator.sendCommand(new SettingsCommand(m_settings));
+ updateShape();
+ }
+
+ protected function beachCheckBoxChangeHandler(event:Event):void
+ {
+ m_settings.showBeach = beachCheckBox.selected;
+ m_communicator.sendCommand(new SettingsCommand(m_settings));
+ updateShape();
+ }
+
+ protected function riversCheckBoxChangeHandler(event:Event):void
+ {
+ m_settings.showRivers = riversCheckBox.selected;
+ m_communicator.sendCommand(new SettingsCommand(m_settings));
+ updateShape();
+ }
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Perlin
+ Radial
+
+
+
+
+
+
+
+
+
+
+ Relaxed
+ Random
+ Square
+ Hexagon
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/editor/src/MapShapeGenWorker.as b/editor/src/MapShapeGenWorker.as
new file mode 100644
index 0000000..9cdee4d
--- /dev/null
+++ b/editor/src/MapShapeGenWorker.as
@@ -0,0 +1,194 @@
+/*
+* Copyright (c) 2016 Nailson S.
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*/
+
+package
+{
+ import com.mapgen.Canvas;
+ import com.mapgen.Generator;
+ import com.mapgen.commands.CreateShapeCommand;
+ import com.mapgen.commands.MapInfoCommand;
+ import com.mapgen.commands.OpenShapeCommand;
+ import com.mapgen.commands.ProgressCommand;
+ import com.mapgen.commands.SaveMapCommand;
+ import com.mapgen.commands.SettingsCommand;
+ import com.mapgen.commands.UpdatePreviewCommand;
+ import com.mapgen.commands.UpdateShapeComamnd;
+ import com.mapgen.settings.MapShapeGenSettings;
+ import com.mapgen.utils.MapInfo;
+ import com.mignari.errors.NullArgumentError;
+ import com.mignari.errors.NullOrEmptyArgumentError;
+ import com.mignari.utils.isNullOrEmpty;
+ import com.mignari.workers.IWorkerCommunicator;
+ import com.mignari.workers.WorkerCommunicator;
+
+ import flash.display.Sprite;
+ import flash.filesystem.File;
+ import flash.utils.ByteArray;
+
+ public class MapShapeGenWorker extends Sprite
+ {
+ //--------------------------------------------------------------------------
+ // PROPERTIES
+ //--------------------------------------------------------------------------
+
+ private var m_communicator:IWorkerCommunicator;
+ private var m_generator:Generator;
+ private var m_sharedByteArray:ByteArray;
+ private var m_canvas:Canvas;
+ private var m_settings:MapShapeGenSettings;
+
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function MapShapeGenWorker()
+ {
+ m_communicator = new WorkerCommunicator();
+ m_communicator.registerClass(MapShapeGenSettings);
+ m_communicator.registerClass(MapInfo);
+ m_communicator.registerCallback(SettingsCommand, settingsCallback);
+ m_communicator.registerCallback(CreateShapeCommand, createShapeCallback);
+ m_communicator.registerCallback(OpenShapeCommand, openShapeCallback);
+ m_communicator.registerCallback(UpdateShapeComamnd, updateShapeCallback);
+ m_communicator.registerCallback(SaveMapCommand, saveMapCallback);
+ m_communicator.start();
+
+ m_sharedByteArray = m_communicator.worker.getSharedProperty("sharedByteArray");
+ m_canvas = new Canvas(1024, 1024);
+ m_generator = new Generator();
+ m_generator.onChanged.add(changedCallback);
+ m_generator.onProgress.add(progressCallback);
+ }
+
+ //--------------------------------------------------------------------------
+ // METHODS
+ //--------------------------------------------------------------------------
+
+ //--------------------------------------
+ // Private
+ //--------------------------------------
+
+ private function sendMapInfo():void
+ {
+ var info:MapInfo = new MapInfo();
+ info.name = m_generator.name;
+ info.width = m_generator.width;
+ info.height = m_generator.height;
+ info.seed = m_generator.islandSeed;
+ info.islandType = m_generator.islandType;
+ info.pointType = m_generator.pointType;
+ info.beach = m_generator.beach;
+ info.rivers = m_generator.rivers;
+ info.created = m_generator.created;
+ info.changed = m_generator.changed;
+ info.saving = m_generator.saving;
+ m_communicator.sendCommand(new MapInfoCommand(info));
+ }
+
+ private function sendPreview():void
+ {
+ if (m_generator.created)
+ {
+ m_canvas.clear();
+ m_canvas.draw(m_generator);
+ m_canvas.copyToByteArray(m_sharedByteArray);
+ m_communicator.sendCommand(new UpdatePreviewCommand(m_generator.islandSeed));
+ }
+ }
+
+ private function changedCallback():void
+ {
+ this.sendMapInfo();
+ this.sendPreview();
+ }
+
+ private function progressCallback(id:String, value:uint, total:uint, label:String):void
+ {
+ m_communicator.sendCommand(new ProgressCommand(id, value, total, label));
+ }
+
+ private function settingsCallback(settings:MapShapeGenSettings):void
+ {
+ if (!settings)
+ {
+ throw new NullArgumentError("settings");
+ }
+
+ m_settings = settings;
+ }
+
+ private function createShapeCallback(name:String, width:uint, height:uint):void
+ {
+ if (isNullOrEmpty(name))
+ {
+ throw new NullOrEmptyArgumentError("name");
+ }
+
+ var seed:String = Generator.generateSeed();
+ m_generator.create(name + "_" + seed,
+ width,
+ height,
+ seed,
+ m_settings.islandType,
+ m_settings.pointType,
+ m_settings.showBeach,
+ m_settings.showRivers);
+ }
+
+ private function openShapeCallback(file:File):void
+ {
+ m_generator.load(file);
+ }
+
+ private function updateShapeCallback(islandType:String, pointType:String, beach:Boolean, rivers:Boolean):void
+ {
+ if (isNullOrEmpty(islandType))
+ {
+ throw new NullOrEmptyArgumentError("islandType");
+ }
+
+ if (isNullOrEmpty(pointType))
+ {
+ throw new NullOrEmptyArgumentError("pointType");
+ }
+
+ m_generator.create(m_generator.name,
+ m_generator.width,
+ m_generator.height,
+ m_generator.islandSeed,
+ islandType,
+ pointType,
+ beach,
+ rivers);
+ }
+
+ private function saveMapCallback():void
+ {
+ m_generator.save(m_settings.ouputDirectory,
+ m_settings.waterItem,
+ m_settings.sandItem,
+ m_settings.grassItem,
+ m_settings.savePNG,
+ m_settings.version);
+ }
+ }
+}
diff --git a/editor/src/Workers.as b/editor/src/Workers.as
new file mode 100644
index 0000000..cbacab5
--- /dev/null
+++ b/editor/src/Workers.as
@@ -0,0 +1,14 @@
+package
+{
+ import flash.utils.ByteArray;
+
+ public class Workers
+ {
+ [Embed(source="../workerswfs/MapShapeGenWorker.swf", mimeType="application/octet-stream")]
+ private static var MapShapeGenWorkerByteClass:Class;
+ public static function get MapShapeGenWorker():ByteArray
+ {
+ return new MapShapeGenWorkerByteClass();
+ }
+ }
+}
diff --git a/editor/src/com/mapgen/commands/CreateShapeCommand.as b/editor/src/com/mapgen/commands/CreateShapeCommand.as
new file mode 100644
index 0000000..587e881
--- /dev/null
+++ b/editor/src/com/mapgen/commands/CreateShapeCommand.as
@@ -0,0 +1,38 @@
+/*
+* Copyright (c) 2015 Nailson S.
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*/
+
+package com.mapgen.commands
+{
+ import com.mignari.workers.WorkerCommand;
+
+ public class CreateShapeCommand extends WorkerCommand
+ {
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function CreateShapeCommand(name:String, width:uint, height:uint)
+ {
+ super(name, width, height);
+ }
+ }
+}
diff --git a/editor/src/com/mapgen/commands/MapInfoCommand.as b/editor/src/com/mapgen/commands/MapInfoCommand.as
new file mode 100644
index 0000000..0cafc9c
--- /dev/null
+++ b/editor/src/com/mapgen/commands/MapInfoCommand.as
@@ -0,0 +1,39 @@
+/*
+* Copyright (c) 2015 Nailson S.
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*/
+
+package com.mapgen.commands
+{
+ import com.mapgen.utils.MapInfo;
+ import com.mignari.workers.WorkerCommand;
+
+ public class MapInfoCommand extends WorkerCommand
+ {
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function MapInfoCommand(info:MapInfo)
+ {
+ super(info);
+ }
+ }
+}
diff --git a/editor/src/com/mapgen/commands/OpenShapeCommand.as b/editor/src/com/mapgen/commands/OpenShapeCommand.as
new file mode 100644
index 0000000..585670a
--- /dev/null
+++ b/editor/src/com/mapgen/commands/OpenShapeCommand.as
@@ -0,0 +1,40 @@
+/*
+* Copyright (c) 2015 Nailson S.
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*/
+
+package com.mapgen.commands
+{
+ import com.mignari.workers.WorkerCommand;
+
+ import flash.filesystem.File;
+
+ public class OpenShapeCommand extends WorkerCommand
+ {
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function OpenShapeCommand(file:File)
+ {
+ super(file);
+ }
+ }
+}
diff --git a/editor/src/com/mapgen/commands/ProgressCommand.as b/editor/src/com/mapgen/commands/ProgressCommand.as
new file mode 100644
index 0000000..39c557e
--- /dev/null
+++ b/editor/src/com/mapgen/commands/ProgressCommand.as
@@ -0,0 +1,32 @@
+/*
+* Copyright (c) 2015 Nailson Santos
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+* either express or implied. See the License for the specific language
+* governing permissions and limitations under the License.
+*/
+
+package com.mapgen.commands
+{
+ import com.mignari.workers.WorkerCommand;
+
+ public class ProgressCommand extends WorkerCommand
+ {
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function ProgressCommand(id:String, value:uint, total:uint, label:String)
+ {
+ super(id, value, total, label);
+ }
+ }
+}
diff --git a/editor/src/com/mapgen/commands/SaveMapCommand.as b/editor/src/com/mapgen/commands/SaveMapCommand.as
new file mode 100644
index 0000000..6b33366
--- /dev/null
+++ b/editor/src/com/mapgen/commands/SaveMapCommand.as
@@ -0,0 +1,32 @@
+/*
+* Copyright (c) 2015 Nailson Santos
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+* either express or implied. See the License for the specific language
+* governing permissions and limitations under the License.
+*/
+
+package com.mapgen.commands
+{
+ import com.mignari.workers.WorkerCommand;
+
+ public class SaveMapCommand extends WorkerCommand
+ {
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function SaveMapCommand()
+ {
+ super();
+ }
+ }
+}
diff --git a/editor/src/com/mapgen/commands/SettingsCommand.as b/editor/src/com/mapgen/commands/SettingsCommand.as
new file mode 100644
index 0000000..807925a
--- /dev/null
+++ b/editor/src/com/mapgen/commands/SettingsCommand.as
@@ -0,0 +1,17 @@
+package com.mapgen.commands
+{
+ import com.mapgen.settings.MapShapeGenSettings;
+ import com.mignari.workers.WorkerCommand;
+
+ public class SettingsCommand extends WorkerCommand
+ {
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function SettingsCommand(settings:MapShapeGenSettings)
+ {
+ super(settings);
+ }
+ }
+}
diff --git a/editor/src/com/mapgen/commands/UpdatePreviewCommand.as b/editor/src/com/mapgen/commands/UpdatePreviewCommand.as
new file mode 100644
index 0000000..394af49
--- /dev/null
+++ b/editor/src/com/mapgen/commands/UpdatePreviewCommand.as
@@ -0,0 +1,32 @@
+/*
+* Copyright (c) 2015 Nailson Santos
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+* either express or implied. See the License for the specific language
+* governing permissions and limitations under the License.
+*/
+
+package com.mapgen.commands
+{
+ import com.mignari.workers.WorkerCommand;
+
+ public class UpdatePreviewCommand extends WorkerCommand
+ {
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function UpdatePreviewCommand(islandSeed:String)
+ {
+ super(islandSeed);
+ }
+ }
+}
diff --git a/editor/src/com/mapgen/commands/UpdateShapeComamnd.as b/editor/src/com/mapgen/commands/UpdateShapeComamnd.as
new file mode 100644
index 0000000..0aaa648
--- /dev/null
+++ b/editor/src/com/mapgen/commands/UpdateShapeComamnd.as
@@ -0,0 +1,38 @@
+/*
+* Copyright (c) 2015 Nailson S.
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*/
+
+package com.mapgen.commands
+{
+ import com.mignari.workers.WorkerCommand;
+
+ public class UpdateShapeComamnd extends WorkerCommand
+ {
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function UpdateShapeComamnd(islandType:String, pointType:String, beach:Boolean, rivers:Boolean)
+ {
+ super(islandType, pointType, beach, rivers);
+ }
+ }
+}
diff --git a/editor/src/com/mapgen/components/AboutWIndow.mxml b/editor/src/com/mapgen/components/AboutWIndow.mxml
new file mode 100644
index 0000000..523a5ad
--- /dev/null
+++ b/editor/src/com/mapgen/components/AboutWIndow.mxml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/editor/src/com/mapgen/components/Menu.as b/editor/src/com/mapgen/components/Menu.as
new file mode 100644
index 0000000..40fb163
--- /dev/null
+++ b/editor/src/com/mapgen/components/Menu.as
@@ -0,0 +1,181 @@
+/*
+* Copyright (c) 2016 Nailson S.
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*/
+
+package com.mapgen.components
+{
+ import com.mapgen.core.IMapShapeGen;
+ import com.mignari.menu.MenuItem;
+ import com.mignari.menu.NativeMenu;
+ import com.mignari.utils.DescriptorUtil;
+
+ import mx.core.FlexGlobals;
+ import mx.events.FlexEvent;
+
+ [ResourceBundle("mapgen_strings")]
+
+ public class Menu extends NativeMenu
+ {
+ //--------------------------------------------------------------------------
+ // PROPERTIES
+ //--------------------------------------------------------------------------
+
+ private var m_application:IMapShapeGen;
+
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function Menu()
+ {
+ super();
+
+ m_application = IMapShapeGen(FlexGlobals.topLevelApplication);
+ m_application.addEventListener(FlexEvent.CREATION_COMPLETE, creationCompleteHandler);
+ }
+
+ //--------------------------------------------------------------------------
+ // METHODS
+ //--------------------------------------------------------------------------
+
+ //--------------------------------------
+ // Private
+ //--------------------------------------
+
+ private function create():void
+ {
+ // root menu
+ var menu:MenuItem = new MenuItem();
+ var macMenu:MenuItem;
+
+ // separator
+ var separator:MenuItem = new MenuItem();
+
+ if (this.isMacOS)
+ {
+ macMenu = new MenuItem();
+ macMenu.label = DescriptorUtil.getName();
+ menu.addMenuItem(macMenu);
+ }
+
+ // File
+ var fileMenu:MenuItem = new MenuItem();
+ fileMenu.label = resourceManager.getString("mapgen_strings", "MENU_FILE");
+ menu.addMenuItem(fileMenu);
+
+ // File > New
+ var fileNewMenu:MenuItem = new MenuItem();
+ fileNewMenu.label = resourceManager.getString("mapgen_strings", "MENU_FILE_NEW");
+ fileNewMenu.data = FILE_NEW;
+ fileNewMenu.keyEquivalent = "N";
+ fileNewMenu.controlKey = true;
+ fileMenu.addMenuItem(fileNewMenu);
+
+ // File > Open
+ var fileOpenMenu:MenuItem = new MenuItem();
+ fileOpenMenu.label = resourceManager.getString("mapgen_strings", "MENU_FILE_OPEN");
+ fileOpenMenu.data = FILE_OPEN;
+ fileOpenMenu.keyEquivalent = "O";
+ fileOpenMenu.controlKey = true;
+ fileMenu.addMenuItem(fileOpenMenu);
+
+ // File > Generate
+ var fileGenerateMenu:MenuItem = new MenuItem();
+ fileGenerateMenu.label = resourceManager.getString("mapgen_strings", "MENU_FILE_GENERATE");
+ fileGenerateMenu.data = FILE_GENERATE;
+ fileGenerateMenu.keyEquivalent = "G";
+ fileGenerateMenu.controlKey = true;
+ fileMenu.addMenuItem(fileGenerateMenu);
+
+ // File > Save
+ var fileSaveMenu:MenuItem = new MenuItem();
+ fileSaveMenu.label = resourceManager.getString("mapgen_strings", "MENU_FILE_SAVE");
+ fileSaveMenu.data = FILE_SAVE;
+ fileSaveMenu.keyEquivalent = "S";
+ fileSaveMenu.controlKey = true;
+ fileMenu.addMenuItem(fileSaveMenu);
+
+ fileMenu.addMenuItem(separator);
+
+ // File > Preferences
+ var filePreferencesMenu:MenuItem = new MenuItem();
+ filePreferencesMenu.label = resourceManager.getString("mapgen_strings", "MENU_FILE_PREFERENCES");
+ filePreferencesMenu.data = FILE_PREFERENCES;
+ filePreferencesMenu.keyEquivalent = "P";
+ filePreferencesMenu.controlKey = true;
+ fileMenu.addMenuItem(filePreferencesMenu);
+
+ // File > Exit
+ var fileExitMenu:MenuItem = new MenuItem();
+ fileExitMenu.label = resourceManager.getString("mapgen_strings", "MENU_FILE_EXIT");
+ fileExitMenu.data = FILE_EXIT;
+ fileExitMenu.keyEquivalent = "Q";
+ fileExitMenu.controlKey = true;
+
+ // Help
+ var helpMenu:MenuItem = new MenuItem();
+ helpMenu.label = resourceManager.getString("mapgen_strings", "MENU_HELP");
+ menu.addMenuItem(helpMenu);
+
+ // Help > About
+ var aboutMenu:MenuItem = new MenuItem();
+ aboutMenu.label = resourceManager.getString("mapgen_strings", "MENU_HELP_ABOUT") + " " + DescriptorUtil.getName();
+ aboutMenu.data = HELP_ABOUT;
+
+ if (this.isMacOS)
+ {
+ macMenu.addMenuItem(aboutMenu);
+ macMenu.addMenuItem(separator);
+ macMenu.addMenuItem(fileExitMenu);
+ }
+ else
+ {
+ fileMenu.addMenuItem(separator);
+ fileMenu.addMenuItem(fileExitMenu);
+ helpMenu.addMenuItem(aboutMenu);
+ }
+
+ this.dataProvider = menu.serialize();
+ }
+
+ //--------------------------------------
+ // Event Handlers
+ //--------------------------------------
+
+ protected function creationCompleteHandler(event:FlexEvent):void
+ {
+ m_application.removeEventListener(FlexEvent.CREATION_COMPLETE, creationCompleteHandler);
+ create();
+ }
+
+ //--------------------------------------------------------------------------
+ // STATIC
+ //--------------------------------------------------------------------------
+
+ static public const FILE_NEW:String = "FILE_NEW";
+ static public const FILE_OPEN:String = "FILE_OPEN";
+ static public const FILE_GENERATE:String = "FILE_GENERATE";
+ static public const FILE_SAVE:String = "FILE_SAVE";
+ static public const FILE_PREFERENCES:String = "FILE_PREFERENCES";
+ static public const FILE_EXIT:String = "FILE_EXIT";
+ static public const HELP_ABOUT:String = "HELP_ABOUT";
+ }
+}
diff --git a/editor/src/com/mapgen/components/NewMapWindow.mxml b/editor/src/com/mapgen/components/NewMapWindow.mxml
new file mode 100644
index 0000000..e9d2758
--- /dev/null
+++ b/editor/src/com/mapgen/components/NewMapWindow.mxml
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/editor/src/com/mapgen/components/PreferencesWindow.mxml b/editor/src/com/mapgen/components/PreferencesWindow.mxml
new file mode 100644
index 0000000..acd57b6
--- /dev/null
+++ b/editor/src/com/mapgen/components/PreferencesWindow.mxml
@@ -0,0 +1,342 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/editor/src/com/mapgen/components/StatusBar.mxml b/editor/src/com/mapgen/components/StatusBar.mxml
new file mode 100644
index 0000000..4d797d4
--- /dev/null
+++ b/editor/src/com/mapgen/components/StatusBar.mxml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/editor/src/com/mapgen/core/IMapShapeGen.as b/editor/src/com/mapgen/core/IMapShapeGen.as
new file mode 100644
index 0000000..6ca872e
--- /dev/null
+++ b/editor/src/com/mapgen/core/IMapShapeGen.as
@@ -0,0 +1,34 @@
+/*
+* Copyright (c) 2016 Nailson S.
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*/
+
+package com.mapgen.core
+{
+ import flash.events.IEventDispatcher;
+
+ import mx.core.IWindow;
+
+ public interface IMapShapeGen extends IEventDispatcher, IWindow
+ {
+ function get mapCreated():Boolean;
+ function get mapSaving():Boolean;
+ }
+}
diff --git a/editor/src/com/mapgen/settings/MapShapeGenSettings.as b/editor/src/com/mapgen/settings/MapShapeGenSettings.as
new file mode 100644
index 0000000..e6dee79
--- /dev/null
+++ b/editor/src/com/mapgen/settings/MapShapeGenSettings.as
@@ -0,0 +1,65 @@
+/*
+* Copyright (c) 2015 Nailson S.
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*/
+
+package com.mapgen.settings
+{
+ import com.mapgen.IslandType;
+ import com.mapgen.PointType;
+ import com.mignari.settings.Settings;
+
+ import flash.filesystem.File;
+
+ public class MapShapeGenSettings extends Settings
+ {
+ //--------------------------------------------------------------------------
+ // PROPERTIES
+ //--------------------------------------------------------------------------
+
+ public var maximized:Boolean = true;
+
+ public var islandType:String = IslandType.PERLIN;
+ public var pointType:String = PointType.RELAXED;
+ public var showBeach:Boolean = true;
+ public var showRivers:Boolean = true;
+
+ public var waterItem:uint = 4608;
+ public var sandItem:uint = 231;
+ public var grassItem:uint = 106;
+
+ public var ouputDirectory:File = null;
+ public var savePNG:Boolean = true;
+ public var version:uint = 56;
+
+ public var mapName:String = "Untitled";
+ public var mapWidth:uint = 512;
+ public var mapHeight:uint = 512;
+
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function MapShapeGenSettings()
+ {
+ super();
+ }
+ }
+}
diff --git a/editor/src/com/mapgen/utils/MapInfo.as b/editor/src/com/mapgen/utils/MapInfo.as
new file mode 100644
index 0000000..6af1c7c
--- /dev/null
+++ b/editor/src/com/mapgen/utils/MapInfo.as
@@ -0,0 +1,52 @@
+/*
+* Copyright (c) 2015 Nailson S.
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*/
+
+package com.mapgen.utils
+{
+ public class MapInfo
+ {
+ //--------------------------------------------------------------------------
+ // PROPERTIES
+ //--------------------------------------------------------------------------
+
+ public var name:String;
+ public var width:uint;
+ public var height:uint;
+ public var seed:String;
+ public var islandType:String;
+ public var pointType:String;
+ public var beach:Boolean;
+ public var rivers:Boolean;
+ public var created:Boolean;
+ public var changed:Boolean;
+ public var saving:Boolean;
+
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function MapInfo()
+ {
+ ////
+ }
+ }
+}
diff --git a/editor/src/com/mapgen/utils/Version.as b/editor/src/com/mapgen/utils/Version.as
new file mode 100644
index 0000000..4bee04b
--- /dev/null
+++ b/editor/src/com/mapgen/utils/Version.as
@@ -0,0 +1,22 @@
+package com.mapgen.utils
+{
+ public final class Version
+ {
+ //--------------------------------------------------------------------------
+ // PROPERTIES
+ //--------------------------------------------------------------------------
+
+ public var label:String;
+ public var otb:uint;
+
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function Version(label:String, otb:uint)
+ {
+ this.label = label;
+ this.otb = otb;
+ }
+ }
+}
diff --git a/editor/src/icon/app/icon114.png b/editor/src/icon/app/icon114.png
new file mode 100644
index 0000000..9aac979
Binary files /dev/null and b/editor/src/icon/app/icon114.png differ
diff --git a/editor/src/icon/app/icon128.png b/editor/src/icon/app/icon128.png
new file mode 100644
index 0000000..6d1f9df
Binary files /dev/null and b/editor/src/icon/app/icon128.png differ
diff --git a/editor/src/icon/app/icon144.png b/editor/src/icon/app/icon144.png
new file mode 100644
index 0000000..d6ab516
Binary files /dev/null and b/editor/src/icon/app/icon144.png differ
diff --git a/editor/src/icon/app/icon152.png b/editor/src/icon/app/icon152.png
new file mode 100644
index 0000000..617419e
Binary files /dev/null and b/editor/src/icon/app/icon152.png differ
diff --git a/editor/src/icon/app/icon16.png b/editor/src/icon/app/icon16.png
new file mode 100644
index 0000000..1903701
Binary files /dev/null and b/editor/src/icon/app/icon16.png differ
diff --git a/editor/src/icon/app/icon192.png b/editor/src/icon/app/icon192.png
new file mode 100644
index 0000000..0b595b5
Binary files /dev/null and b/editor/src/icon/app/icon192.png differ
diff --git a/editor/src/icon/app/icon32.png b/editor/src/icon/app/icon32.png
new file mode 100644
index 0000000..28b4d15
Binary files /dev/null and b/editor/src/icon/app/icon32.png differ
diff --git a/editor/src/icon/app/icon36.png b/editor/src/icon/app/icon36.png
new file mode 100644
index 0000000..99c4a40
Binary files /dev/null and b/editor/src/icon/app/icon36.png differ
diff --git a/editor/src/icon/app/icon48.png b/editor/src/icon/app/icon48.png
new file mode 100644
index 0000000..e7df6ab
Binary files /dev/null and b/editor/src/icon/app/icon48.png differ
diff --git a/editor/src/icon/app/icon72.png b/editor/src/icon/app/icon72.png
new file mode 100644
index 0000000..031a773
Binary files /dev/null and b/editor/src/icon/app/icon72.png differ
diff --git a/editor/src/icon/app/icon96.png b/editor/src/icon/app/icon96.png
new file mode 100644
index 0000000..8628206
Binary files /dev/null and b/editor/src/icon/app/icon96.png differ
diff --git a/editor/src/icon/system/icon128.png b/editor/src/icon/system/icon128.png
new file mode 100644
index 0000000..32130a0
Binary files /dev/null and b/editor/src/icon/system/icon128.png differ
diff --git a/editor/src/icon/system/icon16.png b/editor/src/icon/system/icon16.png
new file mode 100644
index 0000000..1e8815d
Binary files /dev/null and b/editor/src/icon/system/icon16.png differ
diff --git a/editor/src/icon/system/icon32.png b/editor/src/icon/system/icon32.png
new file mode 100644
index 0000000..52d827c
Binary files /dev/null and b/editor/src/icon/system/icon32.png differ
diff --git a/editor/src/icon/system/icon48.png b/editor/src/icon/system/icon48.png
new file mode 100644
index 0000000..784f551
Binary files /dev/null and b/editor/src/icon/system/icon48.png differ
diff --git a/editor/src/versions.xml b/editor/src/versions.xml
new file mode 100644
index 0000000..f570d21
--- /dev/null
+++ b/editor/src/versions.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/library/.actionScriptProperties b/library/.actionScriptProperties
new file mode 100644
index 0000000..ccc06ac
--- /dev/null
+++ b/library/.actionScriptProperties
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/library/.flexLibProperties b/library/.flexLibProperties
new file mode 100644
index 0000000..9dbf78d
--- /dev/null
+++ b/library/.flexLibProperties
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/library/.project b/library/.project
new file mode 100644
index 0000000..65bd623
--- /dev/null
+++ b/library/.project
@@ -0,0 +1,18 @@
+
+
+ mapgen
+
+
+
+
+
+ com.adobe.flexbuilder.project.flexbuilder
+
+
+
+
+
+ com.adobe.flexbuilder.project.aslibnature
+ com.adobe.flexbuilder.project.actionscriptnature
+
+
diff --git a/library/libs/mignari_core.swc b/library/libs/mignari_core.swc
new file mode 100644
index 0000000..c1ccbf5
Binary files /dev/null and b/library/libs/mignari_core.swc differ
diff --git a/library/libs/otlib_core.swc b/library/libs/otlib_core.swc
new file mode 100644
index 0000000..2f902de
Binary files /dev/null and b/library/libs/otlib_core.swc differ
diff --git a/library/src/MapgenClasses.as b/library/src/MapgenClasses.as
new file mode 100644
index 0000000..a62ffe8
--- /dev/null
+++ b/library/src/MapgenClasses.as
@@ -0,0 +1,16 @@
+package
+{
+ internal final class MapgenClasses
+ {
+ import com.mapgen.Canvas; Canvas;
+ import com.mapgen.Generator; Generator;
+ import com.mapgen.IslandType; IslandType;
+ import com.mapgen.Map; Map;
+ import com.mapgen.NoisyEdges; NoisyEdges;
+ import com.mapgen.PointType; PointType;
+ import com.mapgen.graph.Center; Center;
+ import com.mapgen.graph.Corner; Corner;
+ import com.mapgen.graph.Edge; Edge;
+ import com.mapgen.utils.PointTypeUtil; PointTypeUtil;
+ }
+}
diff --git a/library/src/com/mapgen/BiomeColor.as b/library/src/com/mapgen/BiomeColor.as
new file mode 100644
index 0000000..7bcff40
--- /dev/null
+++ b/library/src/com/mapgen/BiomeColor.as
@@ -0,0 +1,24 @@
+package com.mapgen
+{
+ import com.mignari.errors.AbstractClassError;
+
+ public final class BiomeColor
+ {
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function BiomeColor()
+ {
+ throw new AbstractClassError(BiomeColor);
+ }
+
+ //--------------------------------------------------------------------------
+ // STATIC
+ //--------------------------------------------------------------------------
+
+ static public const WATER:uint = 0x336699;
+ static public const SAND:uint = 0xffCC99;
+ static public const GRASS:uint = 0x00CC00;
+ }
+}
diff --git a/library/src/com/mapgen/Canvas.as b/library/src/com/mapgen/Canvas.as
new file mode 100644
index 0000000..9e5212b
--- /dev/null
+++ b/library/src/com/mapgen/Canvas.as
@@ -0,0 +1,62 @@
+package com.mapgen
+{
+ import flash.display.BitmapData;
+ import flash.geom.Rectangle;
+ import flash.utils.ByteArray;
+
+ public class Canvas
+ {
+ //--------------------------------------------------------------------------
+ // PROPERTIES
+ //--------------------------------------------------------------------------
+
+ private var m_bitmap:BitmapData;
+ private var m_rect:Rectangle;
+
+ //--------------------------------------
+ // Getters / Setters
+ //--------------------------------------
+
+ public function get bitmap():BitmapData { return m_bitmap; }
+
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function Canvas(width:int, height:int)
+ {
+ m_bitmap = new BitmapData(width, height, true, 0);
+ m_rect = new Rectangle(0, 0, width, height);
+ }
+
+ //--------------------------------------------------------------------------
+ // METHODS
+ //--------------------------------------------------------------------------
+
+ //--------------------------------------
+ // Public
+ //--------------------------------------
+
+ public function draw(generator:Generator):void
+ {
+ m_bitmap.draw(generator);
+ }
+
+ public function copyFromByteArray(input:ByteArray):void
+ {
+ input.position = 0;
+ m_bitmap.setPixels(m_rect, input);
+ }
+
+ public function copyToByteArray(output:ByteArray):void
+ {
+ output.position = 0;
+ m_bitmap.copyPixelsToByteArray(m_rect, output);
+ }
+
+ public function clear():void
+ {
+ m_bitmap.fillRect(m_rect, 0);
+ }
+ }
+}
diff --git a/library/src/com/mapgen/Generator.as b/library/src/com/mapgen/Generator.as
new file mode 100644
index 0000000..de506c6
--- /dev/null
+++ b/library/src/com/mapgen/Generator.as
@@ -0,0 +1,519 @@
+// Draws the voronoi graph produced in Map.as
+// Author: amitp@cs.stanford.edu
+// Author: nailsonnego@gmail.com
+// License: MIT
+
+package com.mapgen
+{
+ import com.mapgen.graph.Center;
+ import com.mapgen.graph.Edge;
+ import com.mignari.errors.FileNotFoundError;
+ import com.mignari.errors.NullArgumentError;
+ import com.mignari.errors.NullOrEmptyArgumentError;
+ import com.mignari.signals.ProgressSignal;
+ import com.mignari.signals.Signal0;
+ import com.mignari.utils.Color;
+ import com.mignari.utils.StringUtil;
+ import com.mignari.utils.isNullOrEmpty;
+
+ import flash.display.BitmapData;
+ import flash.display.Graphics;
+ import flash.display.Shape;
+ import flash.display.Sprite;
+ import flash.filesystem.File;
+ import flash.filesystem.FileMode;
+ import flash.filesystem.FileStream;
+ import flash.geom.Point;
+ import flash.utils.ByteArray;
+
+ import mx.graphics.codec.PNGEncoder;
+
+ import otlib.map.Tile;
+ import otlib.otbm.OtbmWriter;
+ import otlib.otml.OTMLDocument;
+
+ public class Generator extends Sprite
+ {
+ //--------------------------------------------------------------------------
+ // PROPERTIES
+ //--------------------------------------------------------------------------
+
+ private var m_name:String;
+ private var m_width:uint;
+ private var m_height:uint;
+ private var m_islandSeed:String;
+ private var m_islandType:String;
+ private var m_pointType:String;
+
+ private var m_mask:Shape;
+
+ private var m_beachLayer:Shape;
+ private var m_riversLayer:Shape;
+ private var m_map:Map;
+ private var m_noisyEdges:NoisyEdges;
+ private var m_created:Boolean;
+ private var m_saving:Boolean;
+
+ private var m_changedSignal:Signal0;
+ private var m_progressSignal:ProgressSignal;
+
+ //--------------------------------------
+ // Getters / Setters
+ //--------------------------------------
+
+ public function get created():Boolean { return m_created; }
+ public function get changed():Boolean { return false; }
+ public function get saving():Boolean { return m_saving; }
+
+ public function get islandSeed():String { return m_islandSeed; }
+ public function get islandType():String { return m_islandType; }
+ public function get pointType():String { return m_pointType; }
+ public function get beach():Boolean { return m_beachLayer.visible; }
+ public function get rivers():Boolean { return m_riversLayer.visible; }
+
+ public function get onChanged():Signal0 { return m_changedSignal; }
+ public function get onProgress():ProgressSignal { return m_progressSignal; }
+
+ override public function get name():String { return m_name; }
+ override public function get width():Number { return m_width; }
+ override public function get height():Number { return m_height; }
+
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function Generator()
+ {
+ m_width = 512;
+ m_height = 512;
+ m_mask = new Shape();
+ m_map = new Map(m_width, m_height);
+ m_beachLayer = new Shape();
+ addChild(m_beachLayer);
+ m_riversLayer = new Shape();
+ addChild(m_riversLayer);
+ m_noisyEdges = new NoisyEdges();
+
+ m_changedSignal = new Signal0();
+ m_progressSignal = new ProgressSignal();
+
+ this.mask = m_mask;
+ }
+
+ //--------------------------------------------------------------------------
+ // METHODS
+ //--------------------------------------------------------------------------
+
+ //--------------------------------------
+ // Public
+ //--------------------------------------
+
+ public function load(file:File):void
+ {
+ if (!file)
+ {
+ throw new NullArgumentError("file");
+ }
+
+ if (!file.exists)
+ {
+ throw new FileNotFoundError(file);
+ }
+
+ if (file.extension != "otshape")
+ {
+ throw new ArgumentError("Invalid shape file extension.");
+ }
+
+ var doc:OTMLDocument = OTMLDocument.parse(file);
+
+ create(doc.readAt("name", String),
+ doc.readAt("width", uint),
+ doc.readAt("height", uint),
+ doc.readAt("seed", String),
+ doc.readAt("island-type", String),
+ doc.readAt("point-type", String),
+ doc.readAt("beach", Boolean),
+ doc.readAt("rivers", Boolean));
+ }
+
+ public function create(name:String,
+ width:uint,
+ height:uint,
+ seed:String,
+ islandType:String,
+ pointType:String,
+ beach:Boolean,
+ rivers:Boolean):void
+ {
+ if (isNullOrEmpty(name))
+ {
+ throw new NullOrEmptyArgumentError("name");
+ }
+
+ if (width == 0 || height == 0)
+ {
+ throw new ArgumentError("Invalid shape size.");
+ }
+
+ if (isNullOrEmpty(seed))
+ {
+ throw new NullOrEmptyArgumentError("seed");
+ }
+
+ if (isNullOrEmpty(islandType))
+ {
+ throw new NullOrEmptyArgumentError("islandType");
+ }
+
+ if (isNullOrEmpty(pointType))
+ {
+ throw new NullOrEmptyArgumentError("pointType");
+ }
+
+ m_name = name;
+
+ if (m_width != width ||
+ m_height != height ||
+ m_islandSeed != seed ||
+ m_islandType != islandType ||
+ m_pointType != pointType)
+ {
+ if (m_width != width || m_height != height)
+ {
+ m_width = width;
+ m_height = height;
+ m_map.setSize(width, height);
+ }
+
+ m_created = false;
+ m_islandSeed = seed;
+ m_islandType = islandType;
+ m_pointType = pointType;
+
+ var progressId:String = StringUtil.randomKeyString();
+
+ clear();
+ clearNoisyEdges();
+ m_beachLayer.graphics.clear();
+ m_riversLayer.graphics.clear();
+
+ drawMask();
+
+ m_progressSignal.dispatch(progressId, 0, 5, "Shaping map...");
+ shapeMap();
+ m_changedSignal.dispatch();
+
+ m_progressSignal.dispatch(progressId, 1, 5, "Placing points...");
+ m_map.placePoints();
+ m_changedSignal.dispatch();
+
+ m_progressSignal.dispatch(progressId, 2, 5, "Building graph...");
+ m_map.buildGraph();
+ m_map.assignBiomes();
+ m_changedSignal.dispatch();
+
+ m_progressSignal.dispatch(progressId, 3, 5, "Features...");
+ m_map.assignElevations();
+ m_map.assignMoisture();
+ m_map.assignBiomes();
+ m_changedSignal.dispatch();
+
+ m_progressSignal.dispatch(progressId, 4, 5, "Rivers...");
+ m_noisyEdges.buildNoisyEdges(m_map, m_map.mapRandom);
+ drawMap();
+
+ m_progressSignal.dispatch(progressId, 5, 5, "");
+ }
+
+ m_beachLayer.visible = beach;
+ m_riversLayer.visible = rivers;
+ m_created = true;
+ m_changedSignal.dispatch();
+ }
+
+ public function createBitmap():BitmapData
+ {
+ if (m_created)
+ {
+ var bitmap:BitmapData = new BitmapData(m_width, m_height, false, 0);
+ bitmap.draw(this);
+ return bitmap;
+ }
+
+ return null;
+ }
+
+ public function save(directory:File, water:uint, sand:uint, grass:uint, savePNG:Boolean = true, version:uint = 56):void
+ {
+ if (!directory)
+ {
+ throw new NullArgumentError("directory");
+ }
+
+ if (!directory.exists)
+ {
+ throw new FileNotFoundError(directory);
+ }
+
+ if (!m_created || m_saving)
+ {
+ throw new Error("Internal erro");
+ }
+
+ m_saving = true;
+
+ var bitmap:BitmapData = this.createBitmap();
+ var bytes:ByteArray = new PNGEncoder().encode(bitmap);
+ bytes.position = 0;
+
+ if (savePNG)
+ {
+ var pngFile:File = directory.resolvePath(m_name + ".png");
+ var stream:FileStream = new FileStream();
+ stream.open(pngFile, FileMode.WRITE);
+ stream.writeBytes(bytes, 0, bytes.bytesAvailable);
+ stream.close();
+ }
+
+ var color:Color = new Color();
+ var width:uint = bitmap.width;
+ var height:uint = bitmap.height;
+ var tiles:Vector. = new Vector.();
+ var value:uint = 0;
+ var total:uint = width * height;
+ var progressId:String = StringUtil.randomKeyString();
+ var label:String = "Creating map..."
+ var start:uint = 1024 - (width / 2);
+
+ for (var x:uint = 0; x < width; x++)
+ {
+ for (var y:uint = 0; y < height; y++)
+ {
+ var c:uint = bitmap.getPixel(x, y);
+ if (c != 0)
+ {
+ color.RGB = c;
+ var tile:Tile = new Tile(start + x, start + y, 7);
+
+ if (color.R == 0xFF)
+ {
+ tile.ground = sand;
+ }
+ else if (color.G == 0xCC)
+ {
+ tile.ground = grass;
+ }
+ else
+ {
+ tile.ground = water;
+ }
+
+ tiles[tiles.length] = tile;
+ }
+
+ value++;
+ if ((value % 100) == 0)
+ {
+ m_progressSignal.dispatch(progressId, value, total, label);
+ }
+ }
+ }
+
+ m_progressSignal.dispatch(progressId, total, total, label);
+
+ var otbm:OtbmWriter = new OtbmWriter();
+ otbm.onProgress.add(m_progressSignal.dispatch);
+ otbm.saveOtbm(directory.resolvePath(m_name + ".otbm"), tiles, version);
+
+ var shapeFile:File = directory.resolvePath(m_name + ".otshape");
+ var otshape:OTMLDocument = new OTMLDocument();
+ otshape.tag = "Shape";
+ otshape.writeAt("name", m_name);
+ otshape.writeAt("width", m_width);
+ otshape.writeAt("height", m_height);
+ otshape.writeAt("seed", m_islandSeed);
+ otshape.writeAt("island-type", m_islandType);
+ otshape.writeAt("point-type", m_pointType);
+ otshape.writeAt("beach", m_beachLayer.visible);
+ otshape.writeAt("rivers", m_riversLayer.visible);
+ otshape.save(shapeFile);
+
+ otbm.onProgress.remove(m_progressSignal.dispatch);
+ m_saving = false;
+ }
+
+ //--------------------------------------
+ // Private
+ //--------------------------------------
+
+ // Random parameters governing the overall shape of the island
+ private function shapeMap():void
+ {
+ var seed:int = 0;
+ var variant:int = 0;
+
+ if (m_islandSeed.length == 0)
+ {
+ m_islandSeed = generateSeed();
+ }
+
+ var match:Object = /\s*(\d+)(?:\-(\d+))\s*$/.exec(m_islandSeed);
+ if (match != null)
+ {
+ // It's of the format SHAPE-VARIANT
+ seed = parseInt(match[1]);
+ variant = parseInt(match[2] || "0");
+ }
+
+ if (seed == 0)
+ {
+ // Convert the string into a number. This is a cheesy way to
+ // do it but it doesn't matter. It just allows people to use
+ // words as seeds.
+ var length:uint = m_islandSeed.length;
+ for (var i:int = 0; i < length; i++)
+ {
+ seed = (seed << 4) | m_islandSeed.charCodeAt(i);
+ }
+
+ seed %= 100000;
+ variant = 1 + Math.floor(9 * Math.random());
+ }
+
+ m_map.newIsland(m_islandType, m_pointType, seed, variant);
+ }
+
+ private function drawMap():void
+ {
+ this.clear();
+ this.renderPolygons();
+ this.renderRivers();
+ }
+
+ private function drawMask():void
+ {
+ m_mask.graphics.clear();
+ m_mask.graphics.beginFill(0x000000);
+ m_mask.graphics.drawRect(0, 0, m_width, m_height);
+ m_mask.graphics.endFill();
+ }
+
+ private function clearNoisyEdges():void
+ {
+ m_noisyEdges.clear();
+ }
+
+ private function clear():void
+ {
+ graphics.clear();
+ graphics.beginFill(0, 0);
+ graphics.drawRect(0, 0, 2000, 2000);
+ graphics.endFill();
+ graphics.beginFill(BiomeColor.WATER);
+ graphics.drawRect(0, 0, m_width, m_height);
+ graphics.endFill();
+ }
+
+ // Helper functions for rendering paths
+ private function drawPathForwards(graphics:Graphics, path:Vector.):void
+ {
+ for (var i:int = 0; i < path.length; i++)
+ {
+ graphics.lineTo(path[i].x, path[i].y);
+ }
+ }
+
+ private function drawPathBackwards(graphics:Graphics, path:Vector.):void
+ {
+ for (var i:int = path.length-1; i >= 0; i--)
+ {
+ graphics.lineTo(path[i].x, path[i].y);
+ }
+ }
+
+ public function renderRivers():void
+ {
+ var graphics:Graphics = m_riversLayer.graphics;
+ for each (var p:Center in m_map.centers)
+ {
+ for each (var r:Center in p.neighbors)
+ {
+ var edge:Edge = m_map.lookupEdgeFromCenter(p, r);
+ if (m_noisyEdges.path0[edge.index] != null &&
+ m_noisyEdges.path1[edge.index] != null &&
+ edge.river > 1 &&
+ edge.river < 50)
+ {
+ graphics.lineStyle(edge.river, BiomeColor.WATER);
+ graphics.moveTo(m_noisyEdges.path0[edge.index][0].x, m_noisyEdges.path0[edge.index][0].y);
+ drawPathForwards(graphics, m_noisyEdges.path0[edge.index]);
+ drawPathBackwards(graphics, m_noisyEdges.path1[edge.index]);
+
+ }
+ }
+ }
+
+ graphics.endFill();
+ }
+
+ public function renderPolygons():void
+ {
+ graphics.beginFill(BiomeColor.WATER);
+ graphics.drawRect(0, 0, m_width, m_height);
+ graphics.endFill();
+
+ for each (var p:Center in m_map.centers)
+ {
+ for each (var r:Center in p.neighbors)
+ {
+ var edge:Edge = m_map.lookupEdgeFromCenter(p, r);
+ if (m_noisyEdges.path0[edge.index] != null && m_noisyEdges.path1[edge.index] != null)
+ {
+ var color:uint = p.biome;
+ if (color == BiomeColor.SAND)
+ {
+ m_beachLayer.graphics.beginFill(color);
+ drawPath0(p, edge, m_beachLayer.graphics);
+ drawPath1(p, edge, m_beachLayer.graphics);
+ m_beachLayer.graphics.endFill();
+ color = BiomeColor.GRASS;
+ }
+
+ this.graphics.beginFill(color);
+ drawPath0(p, edge, this.graphics);
+ drawPath1(p, edge, this.graphics);
+ this.graphics.endFill();
+ }
+ }
+ }
+ }
+
+ private function drawPath0(center:Center, edge:Edge, graphics:Graphics):void
+ {
+ var path:Vector. = m_noisyEdges.path0[edge.index];
+ graphics.moveTo(center.point.x, center.point.y);
+ graphics.lineTo(path[0].x, path[0].y);
+ drawPathForwards(graphics, path);
+ graphics.lineTo(center.point.x, center.point.y);
+ }
+
+ private function drawPath1(center:Center, edge:Edge, graphics:Graphics):void
+ {
+ var path:Vector. = m_noisyEdges.path1[edge.index];
+ graphics.moveTo(center.point.x, center.point.y);
+ graphics.lineTo(path[0].x, path[0].y);
+ drawPathForwards(graphics, path);
+ graphics.lineTo(center.point.x, center.point.y);
+ }
+
+ //--------------------------------------------------------------------------
+ // STATIC
+ //--------------------------------------------------------------------------
+
+ static public function generateSeed():String
+ {
+ return int(Math.random() * 100000) + "-" + (1 + int(9 * Math.random()));
+ }
+ }
+}
diff --git a/library/src/com/mapgen/IslandShape.as b/library/src/com/mapgen/IslandShape.as
new file mode 100644
index 0000000..d05978d
--- /dev/null
+++ b/library/src/com/mapgen/IslandShape.as
@@ -0,0 +1,71 @@
+package com.mapgen
+{
+ import flash.display.BitmapData;
+ import flash.geom.Point;
+
+ import de.polygonal.math.PM_PRNG;
+
+ // This class has factory functions for generating islands of
+ // different shapes. The factory returns a function that takes a
+ // normalized point (x and y are -1 to +1) and returns true if the
+ // point should be on the island, and false if it should be water
+ // (lake or ocean).
+ internal final class IslandShape
+ {
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function IslandShape()
+ {
+ ////
+ }
+
+ //--------------------------------------------------------------------------
+ // STATIC
+ //--------------------------------------------------------------------------
+
+ // The radial island radius is based on overlapping sine waves
+ static public var ISLAND_FACTOR:Number = 1.07; // 1.0 means no small islands; 2.0 leads to a lot
+ static public function makeRadial(seed:int):Function
+ {
+ var islandRandom:PM_PRNG = new PM_PRNG();
+ islandRandom.seed = seed;
+ var bumps:int = islandRandom.nextIntRange(1, 6);
+ var startAngle:Number = islandRandom.nextDoubleRange(0, 2*Math.PI);
+ var dipAngle:Number = islandRandom.nextDoubleRange(0, 2*Math.PI);
+ var dipWidth:Number = islandRandom.nextDoubleRange(0.2, 0.7);
+
+ function inside(q:Point):Boolean
+ {
+ var angle:Number = Math.atan2(q.y, q.x);
+ var length:Number = 0.5 * (Math.max(Math.abs(q.x), Math.abs(q.y)) + q.length);
+
+ var r1:Number = 0.5 + 0.40*Math.sin(startAngle + bumps*angle + Math.cos((bumps+3)*angle));
+ var r2:Number = 0.7 - 0.20*Math.sin(startAngle + bumps*angle - Math.sin((bumps+2)*angle));
+ if (Math.abs(angle - dipAngle) < dipWidth
+ || Math.abs(angle - dipAngle + 2*Math.PI) < dipWidth
+ || Math.abs(angle - dipAngle - 2*Math.PI) < dipWidth) {
+ r1 = r2 = 0.2;
+ }
+
+ return (length < r1 || (length > r1*ISLAND_FACTOR && length < r2));
+ }
+
+ return inside;
+ }
+
+
+ // The Perlin-based island combines perlin noise with the radius
+ static public function makePerlin(seed:int):Function
+ {
+ var perlin:BitmapData = new BitmapData(256, 256);
+ perlin.perlinNoise(64, 64, 8, seed, false, true);
+
+ return function (q:Point):Boolean {
+ var c:Number = (perlin.getPixel(int((q.x+1)*128), int((q.y+1)*128)) & 0xff) / 255.0;
+ return c > (0.3+0.3*q.length*q.length);
+ };
+ }
+ }
+}
diff --git a/library/src/com/mapgen/IslandType.as b/library/src/com/mapgen/IslandType.as
new file mode 100644
index 0000000..9543bce
--- /dev/null
+++ b/library/src/com/mapgen/IslandType.as
@@ -0,0 +1,46 @@
+package com.mapgen
+{
+ import com.mignari.errors.AbstractClassError;
+ import com.mignari.utils.StringUtil;
+
+ public final class IslandType
+ {
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function IslandType()
+ {
+ throw new AbstractClassError(IslandType);
+ }
+
+ //--------------------------------------------------------------------------
+ // STATIC
+ //--------------------------------------------------------------------------
+
+ static public const PERLIN:String = "perlin";
+ static public const RADIAL:String = "radial";
+
+ static public function value(index:int):String
+ {
+ switch(index)
+ {
+ case 0: return PERLIN;
+ case 1: return RADIAL;
+ }
+
+ return PERLIN;
+ }
+
+ static public function index(value:String):int
+ {
+ switch(StringUtil.toKeyString(value))
+ {
+ case PERLIN: return 0;
+ case RADIAL: return 1;
+ }
+
+ return 0;
+ }
+ }
+}
diff --git a/library/src/com/mapgen/Map.as b/library/src/com/mapgen/Map.as
new file mode 100644
index 0000000..64278f4
--- /dev/null
+++ b/library/src/com/mapgen/Map.as
@@ -0,0 +1,897 @@
+// Make a map out of a voronoi graph
+// Author: amitp@cs.stanford.edu
+// License: MIT
+
+package com.mapgen
+{
+ import com.mapgen.graph.Center;
+ import com.mapgen.graph.Corner;
+ import com.mapgen.graph.Edge;
+ import com.nodename.Delaunay.Voronoi;
+ import com.nodename.geom.LineSegment;
+
+ import flash.geom.Point;
+ import flash.geom.Rectangle;
+ import flash.system.System;
+ import flash.utils.Dictionary;
+
+ import de.polygonal.math.PM_PRNG;
+
+ public class Map
+ {
+ //--------------------------------------------------------------------------
+ // PROPERTIES
+ //--------------------------------------------------------------------------
+
+ private var m_width:uint;
+ private var m_height:uint;
+
+ // Island shape is controlled by the islandRandom seed and the
+ // type of island, passed in when we set the island shape. The
+ // islandShape function uses both of them to determine whether any
+ // point should be water or land.
+ private var m_islandShape:Function;
+
+ // Island details are controlled by this random generator. The
+ // initial map upon loading is always deterministic, but
+ // subsequent maps reset this random number generator with a
+ // random seed.
+ private var m_mapRandom:PM_PRNG = new PM_PRNG();
+ private var m_needsMoreRandomness:Boolean; // see comment in PointSelector
+
+ // Point selection is random for the original article, with Lloyd
+ // Relaxation, but there are other ways of choosing points. Grids
+ // in particular can be much simpler to start with, because you
+ // don't need Voronoi at all. HOWEVER for ease of implementation,
+ // I continue to use Voronoi here, to reuse the graph building
+ // code. If you're using a grid, generate the graph directly.
+ private var m_pointSelector:Function;
+
+ // These store the graph data
+ public var points:Vector.; // Only useful during map construction
+ public var centers:Vector.;
+ public var corners:Vector.;
+ public var edges:Vector.;
+
+ //--------------------------------------
+ // Getters / Setters
+ //--------------------------------------
+
+ public function get mapRandom():PM_PRNG { return m_mapRandom; }
+
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function Map(width:uint, height:uint)
+ {
+ this.setSize(width, height);
+
+ m_mapRandom = new PM_PRNG();
+ }
+
+ //--------------------------------------------------------------------------
+ // METHODS
+ //--------------------------------------------------------------------------
+
+ //--------------------------------------
+ // Public
+ //--------------------------------------
+
+ public function setSize(width:uint, height:uint):void
+ {
+ m_width = width;
+ m_height = height;
+ reset();
+ }
+
+ // Random parameters governing the overall shape of the island
+ public function newIsland(islandType:String, pointType:String, seed:int, variant:int):void
+ {
+ switch(islandType)
+ {
+ case IslandType.PERLIN:
+ m_islandShape = IslandShape.makePerlin(seed);
+ break;
+
+ case IslandType.RADIAL:
+ m_islandShape = IslandShape.makeRadial(seed);
+ break;
+
+ default:
+ throw new Error("Unknown island type " + islandType);
+ }
+
+ switch(pointType)
+ {
+ case PointType.RELAXED:
+ m_pointSelector = PointSelector.generateRelaxed(m_width, seed);
+ break;
+
+ case PointType.RANDOM:
+ m_pointSelector = PointSelector.generateRandom(m_width, seed);
+ break;
+
+ case PointType.SQUARE:
+ m_pointSelector = PointSelector.generateSquare(m_width, seed);
+ break;
+
+ case PointType.HEXAGON:
+ m_pointSelector = PointSelector.generateHexagon(m_width, seed);
+ break;
+
+ default:
+ throw new Error("Unknown point type " + pointType);
+ }
+
+ m_needsMoreRandomness = PointSelector.needsMoreRandomness(pointType);
+ m_mapRandom.seed = variant;
+ }
+
+ public function placePoints():void
+ {
+ this.reset();
+ this.points = m_pointSelector(2000);
+ }
+
+ public function buildGraph():void
+ {
+ var voronoi:Voronoi = new Voronoi(points, null, new Rectangle(0, 0, m_width, m_height));
+ internalBuildGraph(points, voronoi);
+ improveCorners();
+ voronoi.dispose();
+ voronoi = null;
+ points = null;
+ }
+
+ public function assignElevations():void
+ {
+ assignCornerElevations();
+ assignOceanCoastAndLand();
+ redistributeElevations(landCorners(corners));
+
+ for each (var q:Corner in corners)
+ {
+ if (q.ocean || q.coast)
+ {
+ q.elevation = 0.0;
+ }
+ }
+
+ assignPolygonElevations();
+ }
+
+ public function assignMoisture():void
+ {
+ calculateDownslopes();
+ createRivers();
+ assignCornerMoisture();
+ redistributeMoisture(landCorners(corners));
+ assignPolygonMoisture();
+ }
+
+ public function assignBiomes():void
+ {
+ for each (var p:Center in centers)
+ {
+ p.biome = getBiome(p);
+ }
+ }
+
+ // Look up a Voronoi Edge object given two adjacent Voronoi
+ // polygons, or two adjacent Voronoi corners
+ public function lookupEdgeFromCenter(p:Center, r:Center):Edge
+ {
+ for each (var edge:Edge in p.borders)
+ {
+ if (edge.d0 == r || edge.d1 == r)
+ {
+ return edge;
+ }
+ }
+
+ return null;
+ }
+
+ //--------------------------------------
+ // Private
+ //--------------------------------------
+
+ // Although Lloyd relaxation improves the uniformity of polygon
+ // sizes, it doesn't help with the edge lengths. Short edges can
+ // be bad for some games, and lead to weird artifacts on
+ // rivers. We can easily lengthen short edges by moving the
+ // corners, but **we lose the Voronoi property**. The corners are
+ // moved to the average of the polygon centers around them. Short
+ // edges become longer. Long edges tend to become shorter. The
+ // polygons tend to be more uniform after this step.
+ private function improveCorners():void
+ {
+ var newCorners:Vector. = new Vector.(corners.length);
+
+ // First we compute the average of the centers next to each corner.
+ for each (var q:Corner in corners)
+ {
+ if (q.border)
+ {
+ newCorners[q.index] = q.point;
+ }
+ else
+ {
+ var point:Point = new Point(0.0, 0.0);
+ for each (var r:Center in q.touches)
+ {
+ point.x += r.point.x;
+ point.y += r.point.y;
+ }
+
+ point.x /= q.touches.length;
+ point.y /= q.touches.length;
+ newCorners[q.index] = point;
+ }
+ }
+
+ // Move the corners to the new locations.
+ for (var i:int = 0; i < corners.length; i++)
+ {
+ corners[i].point = newCorners[i];
+ }
+
+ // The edge midpoints were computed for the old corners and need
+ // to be recomputed.
+ for each (var edge:Edge in edges)
+ {
+ if (edge.v0 && edge.v1)
+ {
+ edge.midpoint = Point.interpolate(edge.v0.point, edge.v1.point, 0.5);
+ }
+ }
+ }
+
+ // Create an array of corners that are on land only, for use by
+ // algorithms that work only on land. We return an array instead
+ // of a vector because the redistribution algorithms want to sort
+ // this array using Array.sortOn.
+ private function landCorners(corners:Vector.):Array
+ {
+ var locations:Array = [];
+
+ for each (var corner:Corner in corners)
+ {
+ if (!corner.ocean && !corner.coast)
+ {
+ locations[locations.length] = corner;
+ }
+ }
+
+ return locations;
+ }
+
+ // Build graph data structure in 'edges', 'centers', 'corners',
+ // based on information in the Voronoi results: point.neighbors
+ // will be a list of neighboring points of the same type (corner
+ // or center); point.edges will be a list of edges that include
+ // that point. Each edge connects to four points: the Voronoi edge
+ // edge.{v0,v1} and its dual Delaunay triangle edge edge.{d0,d1}.
+ // For boundary polygons, the Delaunay edge will have one null
+ // point, and the Voronoi edge may be null.
+ private function internalBuildGraph(points:Vector., voronoi:Voronoi):void
+ {
+ var p:Center, q:Corner, point:Point, other:Point;
+ var libedges:Vector. = voronoi.edges();
+ var centerLookup:Dictionary = new Dictionary();
+
+ // Build Center objects for each of the points, and a lookup map
+ // to find those Center objects again as we build the graph
+ for each (point in points)
+ {
+ p = new Center();
+ p.index = centers.length;
+ p.point = point;
+ p.neighbors = new Vector.();
+ p.borders = new Vector.();
+ p.corners = new Vector.();
+ centers.push(p);
+ centerLookup[point] = p;
+ }
+
+ // Workaround for Voronoi lib bug: we need to call region()
+ // before Edges or neighboringSites are available
+ for each (p in centers)
+ {
+ voronoi.region(p.point);
+ }
+
+ // The Voronoi library generates multiple Point objects for
+ // corners, and we need to canonicalize to one Corner object.
+ // To make lookup fast, we keep an array of Points, bucketed by
+ // x value, and then we only have to look at other Points in
+ // nearby buckets. When we fail to find one, we'll create a new
+ // Corner object.
+ var _cornerMap:Array = [];
+ function makeCorner(point:Point):Corner
+ {
+ var q:Corner;
+
+ if (point == null)
+ {
+ return null;
+ }
+
+ for (var bucket:int = int(point.x)-1; bucket <= int(point.x)+1; bucket++)
+ {
+ for each (q in _cornerMap[bucket])
+ {
+ var dx:Number = point.x - q.point.x;
+ var dy:Number = point.y - q.point.y;
+ if (dx*dx + dy*dy < 1e-6)
+ {
+ return q;
+ }
+ }
+ }
+
+ bucket = int(point.x);
+ if (!_cornerMap[bucket])
+ {
+ _cornerMap[bucket] = [];
+ }
+
+ q = new Corner();
+ q.index = corners.length;
+ corners.push(q);
+ q.point = point;
+ q.border = (point.x == 0 || point.x == m_width || point.y == 0 || point.y == m_height);
+ q.touches = new Vector.();
+ q.protrudes = new Vector.();
+ q.adjacent = new Vector.();
+ _cornerMap[bucket].push(q);
+ return q;
+ }
+
+ // Helper functions for the following for loop; ideally these
+ // would be inlined
+ function addToCornerList(v:Vector., x:Corner):void
+ {
+ if (x != null && v.indexOf(x) < 0)
+ {
+ v[v.length] = x;
+ }
+ }
+
+ function addToCenterList(v:Vector., x:Center):void
+ {
+ if (x != null && v.indexOf(x) < 0)
+ {
+ v[v.length] = x;
+ }
+ }
+
+ for each (var libedge:com.nodename.Delaunay.Edge in libedges)
+ {
+ var dedge:LineSegment = libedge.delaunayLine();
+ var vedge:LineSegment = libedge.voronoiEdge();
+
+ // Fill the graph data. Make an Edge object corresponding to
+ // the edge from the voronoi library.
+ var edge:Edge = new Edge();
+ edge.index = edges.length;
+ edge.river = 0;
+ edges[edges.length] = edge;
+ edge.midpoint = vedge.p0 && vedge.p1 && Point.interpolate(vedge.p0, vedge.p1, 0.5);
+
+ // Edges point to corners. Edges point to centers.
+ edge.v0 = makeCorner(vedge.p0);
+ edge.v1 = makeCorner(vedge.p1);
+ edge.d0 = centerLookup[dedge.p0];
+ edge.d1 = centerLookup[dedge.p1];
+
+ // Centers point to edges. Corners point to edges.
+ if (edge.d0 != null)
+ {
+ edge.d0.borders.push(edge);
+ }
+
+ if (edge.d1 != null)
+ {
+ edge.d1.borders.push(edge);
+ }
+
+ if (edge.v0 != null)
+ {
+ edge.v0.protrudes.push(edge);
+ }
+
+ if (edge.v1 != null)
+ {
+ edge.v1.protrudes.push(edge);
+ }
+
+ // Centers point to centers.
+ if (edge.d0 != null && edge.d1 != null)
+ {
+ addToCenterList(edge.d0.neighbors, edge.d1);
+ addToCenterList(edge.d1.neighbors, edge.d0);
+ }
+
+ // Corners point to corners
+ if (edge.v0 != null && edge.v1 != null)
+ {
+ addToCornerList(edge.v0.adjacent, edge.v1);
+ addToCornerList(edge.v1.adjacent, edge.v0);
+ }
+
+ // Centers point to corners
+ if (edge.d0 != null)
+ {
+ addToCornerList(edge.d0.corners, edge.v0);
+ addToCornerList(edge.d0.corners, edge.v1);
+ }
+
+ if (edge.d1 != null)
+ {
+ addToCornerList(edge.d1.corners, edge.v0);
+ addToCornerList(edge.d1.corners, edge.v1);
+ }
+
+ // Corners point to centers
+ if (edge.v0 != null)
+ {
+ addToCenterList(edge.v0.touches, edge.d0);
+ addToCenterList(edge.v0.touches, edge.d1);
+ }
+
+ if (edge.v1 != null)
+ {
+ addToCenterList(edge.v1.touches, edge.d0);
+ addToCenterList(edge.v1.touches, edge.d1);
+ }
+ }
+ }
+
+ // Determine elevations and water at Voronoi corners. By
+ // construction, we have no local minima. This is important for
+ // the downslope vectors later, which are used in the river
+ // construction algorithm. Also by construction, inlets/bays
+ // push low elevation areas inland, which means many rivers end
+ // up flowing out through them. Also by construction, lakes
+ // often end up on river paths because they don't raise the
+ // elevation as much as other terrain does.
+ private function assignCornerElevations():void
+ {
+ var q:Corner, s:Corner;
+ var queue:Array = [];
+
+ for each (q in corners)
+ {
+ q.water = !inside(q.point);
+ }
+
+ for each (q in corners)
+ {
+ // The edges of the map are elevation 0
+ if (q.border)
+ {
+ q.elevation = 0.0;
+ queue.push(q);
+ }
+ else
+ {
+ q.elevation = Infinity;
+ }
+ }
+ // Traverse the graph and assign elevations to each point. As we
+ // move away from the map border, increase the elevations. This
+ // guarantees that rivers always have a way down to the coast by
+ // going downhill (no local minima).
+ while (queue.length > 0)
+ {
+ q = queue.shift();
+
+ for each (s in q.adjacent)
+ {
+ // Every step up is epsilon over water or 1 over land. The
+ // number doesn't matter because we'll rescale the
+ // elevations later.
+ var newElevation:Number = 0.01 + q.elevation;
+ if (!q.water && !s.water)
+ {
+ newElevation += 1;
+ if (m_needsMoreRandomness)
+ {
+ // HACK: the map looks nice because of randomness of
+ // points, randomness of rivers, and randomness of
+ // edges. Without random point selection, I needed to
+ // inject some more randomness to make maps look
+ // nicer. I'm doing it here, with elevations, but I
+ // think there must be a better way. This hack is only
+ // used with square/hexagon grids.
+ newElevation += m_mapRandom.nextDouble();
+ }
+ }
+ // If this point changed, we'll add it to the queue so
+ // that we can process its neighbors too.
+ if (newElevation < s.elevation)
+ {
+ s.elevation = newElevation;
+ queue.push(s);
+ }
+ }
+ }
+ }
+
+ // Change the overall distribution of elevations so that lower
+ // elevations are more common than higher
+ // elevations. Specifically, we want elevation X to have frequency
+ // (1-X). To do this we will sort the corners, then set each
+ // corner to its desired elevation.
+ private function redistributeElevations(locations:Array):void
+ {
+ // SCALE_FACTOR increases the mountain area. At 1.0 the maximum
+ // elevation barely shows up on the map, so we set it to 1.1.
+ var SCALE_FACTOR:Number = 1.1;
+ var i:int, y:Number, x:Number;
+
+ locations.sortOn('elevation', Array.NUMERIC);
+ for (i = 0; i < locations.length; i++)
+ {
+ // Let y(x) be the total area that we want at elevation <= x.
+ // We want the higher elevations to occur less than lower
+ // ones, and set the area to be y(x) = 1 - (1-x)^2.
+ y = i/(locations.length-1);
+ // Now we have to solve for x, given the known y.
+ // * y = 1 - (1-x)^2
+ // * y = 1 - (1 - 2x + x^2)
+ // * y = 2x - x^2
+ // * x^2 - 2x + y = 0
+ // From this we can use the quadratic equation to get:
+ x = Math.sqrt(SCALE_FACTOR) - Math.sqrt(SCALE_FACTOR*(1-y));
+ if (x > 1.0)
+ {
+ x = 1.0; // TODO: does this break downslopes?
+ }
+ locations[i].elevation = x;
+ }
+ }
+
+ // Change the overall distribution of moisture to be evenly distributed.
+ private function redistributeMoisture(locations:Array):void
+ {
+ locations.sortOn('moisture', Array.NUMERIC);
+ for (var i:int = 0; i < locations.length; i++)
+ {
+ locations[i].moisture = i/(locations.length-1);
+ }
+ }
+
+ // Determine polygon and corner types: ocean, coast, land.
+ private function assignOceanCoastAndLand():void
+ {
+ // Compute polygon attributes 'ocean' and 'water' based on the
+ // corner attributes. Count the water corners per
+ // polygon. Oceans are all polygons connected to the edge of the
+ // map. In the first pass, mark the edges of the map as ocean;
+ // in the second pass, mark any water-containing polygon
+ // connected an ocean as ocean.
+ var queue:Array = [];
+ var p:Center, q:Corner, r:Center, numWater:int;
+
+ for each (p in centers)
+ {
+ numWater = 0;
+ for each (q in p.corners)
+ {
+ if (q.border)
+ {
+ p.border = true;
+ p.ocean = true;
+ q.water = true;
+ queue.push(p);
+ }
+
+ if (q.water)
+ {
+ numWater += 1;
+ }
+ }
+ p.water = (p.ocean || numWater >= p.corners.length * LAKE_THRESHOLD);
+ }
+
+ while (queue.length > 0)
+ {
+ p = queue.shift();
+ for each (r in p.neighbors)
+ {
+ if (r.water && !r.ocean)
+ {
+ r.ocean = true;
+ queue.push(r);
+ }
+ }
+ }
+
+ // Set the polygon attribute 'coast' based on its neighbors. If
+ // it has at least one ocean and at least one land neighbor,
+ // then this is a coastal polygon.
+ for each (p in centers)
+ {
+ var numOcean:int = 0;
+ var numLand:int = 0;
+ for each (r in p.neighbors)
+ {
+ numOcean += int(r.ocean);
+ numLand += int(!r.water);
+ }
+
+ p.coast = (numOcean > 0) && (numLand > 0);
+ }
+
+
+ // Set the corner attributes based on the computed polygon
+ // attributes. If all polygons connected to this corner are
+ // ocean, then it's ocean; if all are land, then it's land;
+ // otherwise it's coast.
+ for each (q in corners)
+ {
+ numOcean = 0;
+ numLand = 0;
+ for each (p in q.touches)
+ {
+ numOcean += int(p.ocean);
+ numLand += int(!p.water);
+ }
+
+ q.ocean = (numOcean == q.touches.length);
+ q.coast = (numOcean > 0) && (numLand > 0);
+ q.water = q.border || ((numLand != q.touches.length) && !q.coast);
+ }
+ }
+
+ // Polygon elevations are the average of the elevations of their corners.
+ private function assignPolygonElevations():void
+ {
+ var p:Center, q:Corner, sumElevation:Number;
+ for each (p in centers)
+ {
+ sumElevation = 0.0;
+ for each (q in p.corners)
+ {
+ sumElevation += q.elevation;
+ }
+
+ p.elevation = sumElevation / p.corners.length;
+ }
+ }
+
+ // Calculate downslope pointers. At every point, we point to the
+ // point downstream from it, or to itself. This is used for
+ // generating rivers and watersheds.
+ private function calculateDownslopes():void
+ {
+ for each (var q:Corner in corners)
+ {
+ var r:Corner = q;
+ for each (var s:Corner in q.adjacent)
+ {
+ if (s.elevation <= r.elevation)
+ {
+ r = s;
+ }
+ }
+
+ q.downslope = r;
+ }
+ }
+
+ // Create rivers along edges. Pick a random corner point, then
+ // move downslope. Mark the edges and corners as rivers.
+ private function createRivers():void
+ {
+ var length:uint = m_width / 2;
+ for (var i:int = 0; i < length; i++)
+ {
+ var q:Corner = corners[m_mapRandom.nextIntRange(0, corners.length-1)];
+ if (q.ocean || q.elevation < 0.3 || q.elevation > 0.9)
+ {
+ continue;
+ }
+
+ // Bias rivers to go west: if (q.downslope.x > q.x) continue;
+ while (!q.coast)
+ {
+ if (q == q.downslope)
+ {
+ break;
+ }
+
+ var edge:Edge = lookupEdgeFromCorner(q, q.downslope);
+ edge.river = edge.river + 1;
+ q.river = (q.river || 0) + 1;
+ q.downslope.river = (q.downslope.river || 0) + 1; // TODO: fix double count
+ q = q.downslope;
+ }
+ }
+ }
+
+ // Calculate moisture. Freshwater sources spread moisture: rivers
+ // and lakes (not oceans). Saltwater sources have moisture but do
+ // not spread it (we set it at the end, after propagation).
+ private function assignCornerMoisture():void
+ {
+ var q:Corner;
+ var queue:Array = [];
+
+ // Fresh water
+ for each (q in corners)
+ {
+ if ((q.water || q.river > 0) && !q.ocean)
+ {
+ q.moisture = q.river > 0? Math.min(3.0, (0.2 * q.river)) : 1.0;
+ queue.push(q);
+ }
+ else
+ {
+ q.moisture = 0.0;
+ }
+ }
+
+ while (queue.length > 0)
+ {
+ q = queue.shift();
+
+ for each (var r:Corner in q.adjacent)
+ {
+ var newMoisture:Number = q.moisture * 0.9;
+ if (newMoisture > r.moisture)
+ {
+ r.moisture = newMoisture;
+ queue.push(r);
+ }
+ }
+ }
+ // Salt water
+ for each (q in corners)
+ {
+ if (q.ocean || q.coast)
+ {
+ q.moisture = 1.0;
+ }
+ }
+ }
+
+ // Polygon moisture is the average of the moisture at corners
+ private function assignPolygonMoisture():void
+ {
+ for each (var p:Center in centers)
+ {
+ var sumMoisture:Number = 0.0;
+ for each (var q:Corner in p.corners)
+ {
+ if (q.moisture > 1.0)
+ {
+ q.moisture = 1.0;
+ }
+
+ sumMoisture += q.moisture;
+ }
+
+ p.moisture = sumMoisture / p.corners.length;
+ }
+ }
+
+ private function lookupEdgeFromCorner(q:Corner, s:Corner):Edge
+ {
+ for each (var edge:Edge in q.protrudes)
+ {
+ if (edge.v0 == s || edge.v1 == s)
+ {
+ return edge;
+ }
+ }
+
+ return null;
+ }
+
+ private function reset():void
+ {
+ // Break cycles so the garbage collector will release data.
+ if (points)
+ {
+ points.splice(0, points.length);
+ }
+
+ if (edges)
+ {
+ for each (var edge:Edge in edges)
+ {
+ edge.d0 = edge.d1 = null;
+ edge.v0 = edge.v1 = null;
+ }
+
+ edges.splice(0, edges.length);
+ }
+
+ if (centers)
+ {
+ for each (var p:Center in centers)
+ {
+ p.neighbors.splice(0, p.neighbors.length);
+ p.corners.splice(0, p.corners.length);
+ p.borders.splice(0, p.borders.length);
+ }
+
+ centers.splice(0, centers.length);
+ }
+
+ if (corners)
+ {
+ for each (var q:Corner in corners)
+ {
+ q.adjacent.splice(0, q.adjacent.length);
+ q.touches.splice(0, q.touches.length);
+ q.protrudes.splice(0, q.protrudes.length);
+ q.downslope = null;
+ q.watershed = null;
+ }
+
+ corners.splice(0, corners.length);
+ }
+
+ if (!points)
+ {
+ points = new Vector.();
+ }
+
+ if (!edges)
+ {
+ edges = new Vector.();
+ }
+
+ if (!centers)
+ {
+ centers = new Vector.();
+ }
+
+ if (!corners)
+ {
+ corners = new Vector.();
+ }
+
+ System.gc();
+ }
+
+ // Determine whether a given point should be on the island or in the water.
+ private function inside(p:Point):Boolean
+ {
+ return m_islandShape(new Point(2 * (p.x / m_width - 0.5), 2 * (p.y / m_height - 0.5)));
+ }
+
+ //--------------------------------------------------------------------------
+ // STATIC
+ //--------------------------------------------------------------------------
+
+ // 0 to 1, fraction of water corners for water polygon
+ static public const LAKE_THRESHOLD:Number = 0.3;
+
+ // Assign a biome type to each polygon. If it has
+ // ocean/coast/water, then that's the biome; otherwise it depends
+ // on low/high elevation and low/medium/high moisture. This is
+ // roughly based on the Whittaker diagram but adapted to fit the
+ // needs of the island map generator.
+ static private function getBiome(p:Center):uint
+ {
+ if (p.ocean || p.water)
+ {
+ return BiomeColor.WATER;
+ }
+ else if (p.coast)
+ {
+ return BiomeColor.SAND;
+ }
+ else
+ {
+ return BiomeColor.GRASS;
+ }
+ }
+ }
+}
diff --git a/library/src/com/mapgen/NoisyEdges.as b/library/src/com/mapgen/NoisyEdges.as
new file mode 100644
index 0000000..87d1c87
--- /dev/null
+++ b/library/src/com/mapgen/NoisyEdges.as
@@ -0,0 +1,140 @@
+// Annotate each edge with a noisy path, to make maps look more interesting.
+// Author: amitp@cs.stanford.edu
+// License: MIT
+
+package com.mapgen
+{
+ import com.mapgen.graph.Center;
+ import com.mapgen.graph.Edge;
+
+ import flash.geom.Point;
+
+ import de.polygonal.math.PM_PRNG;
+
+ public class NoisyEdges
+ {
+ //--------------------------------------------------------------------------
+ // PROPERTIES
+ //--------------------------------------------------------------------------
+
+ public var path0:Array; // edge index -> Vector.
+ public var path1:Array; // edge index -> Vector.
+
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function NoisyEdges()
+ {
+ this.path0 = [];
+ this.path1 = [];
+ }
+
+ //--------------------------------------------------------------------------
+ // METHODS
+ //--------------------------------------------------------------------------
+
+ //--------------------------------------
+ // Public
+ //--------------------------------------
+
+ // Build noisy line paths for each of the Voronoi edges. There are
+ // two noisy line paths for each edge, each covering half the
+ // distance: path0 is from v0 to the midpoint and path1 is from v1
+ // to the midpoint. When drawing the polygons, one or the other
+ // must be drawn in reverse order.
+ public function buildNoisyEdges(map:Map, random:PM_PRNG):void
+ {
+ for each (var p:Center in map.centers)
+ {
+ for each (var edge:Edge in p.borders)
+ {
+ if (edge.d0 && edge.d1 && edge.v0 && edge.v1 && !this.path0[edge.index])
+ {
+ var f:Number = NOISY_LINE_TRADEOFF;
+ var t:Point = Point.interpolate(edge.v0.point, edge.d0.point, f);
+ var q:Point = Point.interpolate(edge.v0.point, edge.d1.point, f);
+ var r:Point = Point.interpolate(edge.v1.point, edge.d0.point, f);
+ var s:Point = Point.interpolate(edge.v1.point, edge.d1.point, f);
+
+ var minLength:int = 10;
+ if (edge.d0.biome != edge.d1.biome)
+ {
+ minLength = 3;
+ }
+
+ if (edge.d0.ocean && edge.d1.ocean)
+ {
+ minLength = 100;
+ }
+
+ if (edge.d0.coast || edge.d1.coast)
+ {
+ minLength = 1;
+ }
+
+ if (edge.river)
+ {
+ minLength = 1;
+ }
+
+ this.path0[edge.index] = buildNoisyLineSegments(random, edge.v0.point, t, edge.midpoint, q, minLength);
+ this.path1[edge.index] = buildNoisyLineSegments(random, edge.v1.point, s, edge.midpoint, r, minLength);
+ }
+ }
+ }
+ }
+
+ public function clear():void
+ {
+ this.path0 = [];
+ this.path1 = [];
+ }
+
+ //--------------------------------------------------------------------------
+ // STATIC
+ //--------------------------------------------------------------------------
+
+ static private var NOISY_LINE_TRADEOFF:Number = 0.5; // low: jagged vedge; high: jagged dedge
+
+ // Helper function: build a single noisy line in a quadrilateral A-B-C-D,
+ // and store the output points in a Vector.
+ static private function buildNoisyLineSegments(random:PM_PRNG, A:Point, B:Point, C:Point, D:Point, minLength:Number):Vector.
+ {
+ function subdivide(A:Point, B:Point, C:Point, D:Point):void
+ {
+ if (A.subtract(C).length < minLength || B.subtract(D).length < minLength)
+ {
+ return;
+ }
+
+ // Subdivide the quadrilateral
+ var p:Number = random.nextDoubleRange(0.2, 0.8); // vertical (along A-D and B-C)
+ var q:Number = random.nextDoubleRange(0.2, 0.8); // horizontal (along A-B and D-C)
+
+ // Midpoints
+ var E:Point = Point.interpolate(A, D, p);
+ var F:Point = Point.interpolate(B, C, p);
+ var G:Point = Point.interpolate(A, B, q);
+ var I:Point = Point.interpolate(D, C, q);
+
+ // Central point
+ var H:Point = Point.interpolate(E, F, q);
+
+ // Divide the quad into subquads, but meet at H
+ var s:Number = 1.0 - random.nextDoubleRange(-0.4, +0.4);
+ var t:Number = 1.0 - random.nextDoubleRange(-0.4, +0.4);
+
+ subdivide(A, Point.interpolate(G, B, s), H, Point.interpolate(E, D, t));
+ points[points.length] = H;
+ subdivide(H, Point.interpolate(F, C, s), C, Point.interpolate(I, D, t));
+ }
+
+ var points:Vector. = new Vector.();
+ points[points.length] = A;
+ subdivide(A, B, C, D);
+ points[points.length] = C;
+ return points;
+ }
+ }
+}
diff --git a/library/src/com/mapgen/PointSelector.as b/library/src/com/mapgen/PointSelector.as
new file mode 100644
index 0000000..4e20560
--- /dev/null
+++ b/library/src/com/mapgen/PointSelector.as
@@ -0,0 +1,141 @@
+package com.mapgen
+{
+ import com.nodename.Delaunay.Voronoi;
+
+ import flash.geom.Point;
+ import flash.geom.Rectangle;
+
+ import de.polygonal.math.PM_PRNG;
+
+ internal final class PointSelector
+ {
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function PointSelector()
+ {
+ ////
+ }
+
+ //--------------------------------------------------------------------------
+ // STATIC
+ //--------------------------------------------------------------------------
+
+ static public var NUM_LLOYD_RELAXATIONS:int = 2;
+
+ // The square and hex grid point selection remove randomness from
+ // where the points are; we need to inject more randomness elsewhere
+ // to make the maps look better. I do this in the corner
+ // elevations. However I think more experimentation is needed.
+ static public function needsMoreRandomness(type:String):Boolean
+ {
+ return type == PointType.SQUARE || type == PointType.HEXAGON;
+ }
+
+ // Generate points at random locations
+ static public function generateRandom(size:int, seed:int):Function
+ {
+ return function(numPoints:int):Vector.
+ {
+ var mapRandom:PM_PRNG = new PM_PRNG();
+ mapRandom.seed = seed;
+
+ var points:Vector. = new Vector.(numPoints, true);
+ for (var i:int = 0; i < numPoints; i++)
+ {
+ var point:Point = new Point(mapRandom.nextDoubleRange(10, size - 10),
+ mapRandom.nextDoubleRange(10, size - 10));
+ points[i] = point;
+ }
+
+ return points;
+ }
+ }
+
+ // Improve the random set of points with Lloyd Relaxation
+ static public function generateRelaxed(size:int, seed:int):Function
+ {
+ return function(numPoints:int):Vector.
+ {
+ // We'd really like to generate "blue noise". Algorithms:
+ // 1. Poisson dart throwing: check each new point against all
+ // existing points, and reject it if it's too close.
+ // 2. Start with a hexagonal grid and randomly perturb points.
+ // 3. Lloyd Relaxation: move each point to the centroid of the
+ // generated Voronoi polygon, then generate Voronoi again.
+ // 4. Use force-based layout algorithms to push points away.
+ // 5. More at http://www.cs.virginia.edu/~gfx/pubs/antimony/
+ // Option 3 is implemented here. If it's run for too many iterations,
+ // it will turn into a grid, but convergence is very slow, and we only
+ // run it a few times.
+
+ var points:Vector. = generateRandom(size, seed)(numPoints);
+ for (var i:int = 0; i < NUM_LLOYD_RELAXATIONS; i++)
+ {
+ var voronoi:Voronoi = new Voronoi(points, null, new Rectangle(0, 0, size, size));
+ for each (var p:Point in points)
+ {
+ var region:Vector. = voronoi.region(p);
+ p.x = 0.0;
+ p.y = 0.0;
+
+ for each (var q:Point in region)
+ {
+ p.x += q.x;
+ p.y += q.y;
+ }
+
+ p.x /= region.length;
+ p.y /= region.length;
+ region.splice(0, region.length);
+ }
+
+ voronoi.dispose();
+ }
+
+ return points;
+ }
+ }
+
+ // Generate points on a square grid
+ static public function generateSquare(size:int, seed:int):Function
+ {
+ return function(numPoints:int):Vector.
+ {
+ numPoints = Math.sqrt(numPoints);
+
+ var points:Vector. = new Vector.();
+ for (var x:int = 0; x < numPoints; x++)
+ {
+ for (var y:int = 0; y < numPoints; y++)
+ {
+ points[points.length] = new Point((0.5 + x) / numPoints * size, (0.5 + y) / numPoints * size);
+ }
+ }
+
+ return points;
+ }
+ }
+
+ // Generate points on a hexagon grid
+ static public function generateHexagon(size:int, seed:int):Function
+ {
+ return function(numPoints:int):Vector.
+ {
+ numPoints = Math.sqrt(numPoints);
+
+ var points:Vector. = new Vector.();
+ for (var x:int = 0; x < numPoints; x++)
+ {
+ for (var y:int = 0; y < numPoints; y++)
+ {
+ points[points.length] = new Point((0.5 + x) / numPoints * size, (0.25 + 0.5 * x % 2 + y) / numPoints * size);
+ }
+ }
+
+ return points;
+ }
+ }
+ }
+}
diff --git a/library/src/com/mapgen/PointType.as b/library/src/com/mapgen/PointType.as
new file mode 100644
index 0000000..1ac7a9c
--- /dev/null
+++ b/library/src/com/mapgen/PointType.as
@@ -0,0 +1,52 @@
+package com.mapgen
+{
+ import com.mignari.errors.AbstractClassError;
+ import com.mignari.utils.StringUtil;
+
+ public final class PointType
+ {
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function PointType()
+ {
+ throw new AbstractClassError(PointType);
+ }
+
+ //--------------------------------------------------------------------------
+ // STATIC
+ //--------------------------------------------------------------------------
+
+ static public const RELAXED:String = "relaxed";
+ static public const RANDOM:String = "random";
+ static public const SQUARE:String = "square";
+ static public const HEXAGON:String = "hexagon";
+
+ static public function value(index:int):String
+ {
+ switch(index)
+ {
+ case 0: return RELAXED;
+ case 1: return RANDOM;
+ case 2: return SQUARE;
+ case 3: return HEXAGON;
+ }
+
+ return RELAXED;
+ }
+
+ static public function index(value:String):int
+ {
+ switch(StringUtil.toKeyString(value))
+ {
+ case RELAXED: return 0;
+ case RANDOM: return 1;
+ case SQUARE: return 2;
+ case HEXAGON: return 3;
+ }
+
+ return 0;
+ }
+ }
+}
diff --git a/library/src/com/mapgen/graph/Center.as b/library/src/com/mapgen/graph/Center.as
new file mode 100644
index 0000000..9906bc4
--- /dev/null
+++ b/library/src/com/mapgen/graph/Center.as
@@ -0,0 +1,24 @@
+package com.mapgen.graph
+{
+ import flash.geom.Point;
+
+ public class Center
+ {
+ //--------------------------------------------------------------------------
+ // PROPERTIES
+ //--------------------------------------------------------------------------
+
+ public var index:int;
+ public var point:Point; // location
+ public var water:Boolean; // lake or ocean
+ public var ocean:Boolean; // ocean
+ public var coast:Boolean; // land polygon touching an ocean
+ public var border:Boolean; // at the edge of the map
+ public var biome:uint; // biome type (see article)
+ public var elevation:Number; // 0.0-1.0
+ public var moisture:Number; // 0.0-1.0
+ public var neighbors:Vector.;
+ public var borders:Vector.;
+ public var corners:Vector.;
+ }
+}
diff --git a/library/src/com/mapgen/graph/Corner.as b/library/src/com/mapgen/graph/Corner.as
new file mode 100644
index 0000000..2c388d7
--- /dev/null
+++ b/library/src/com/mapgen/graph/Corner.as
@@ -0,0 +1,30 @@
+package com.mapgen.graph
+{
+ import flash.geom.Point;
+
+ public class Corner
+ {
+ //--------------------------------------------------------------------------
+ // PROPERTIES
+ //--------------------------------------------------------------------------
+
+ public var index:int;
+
+ public var point:Point; // location
+ public var ocean:Boolean; // ocean
+ public var water:Boolean; // lake or ocean
+ public var coast:Boolean; // touches ocean and land polygons
+ public var border:Boolean; // at the edge of the map
+ public var elevation:Number; // 0.0-1.0
+ public var moisture:Number; // 0.0-1.0
+
+ public var touches:Vector.;
+ public var protrudes:Vector.;
+ public var adjacent:Vector.;
+
+ public var river:int; // 0 if no river, or volume of water in river
+ public var downslope:Corner; // pointer to adjacent corner most downhill
+ public var watershed:Corner; // pointer to coastal corner, or null
+ public var watershed_size:int;
+ }
+}
diff --git a/library/src/com/mapgen/graph/Edge.as b/library/src/com/mapgen/graph/Edge.as
new file mode 100644
index 0000000..7123a17
--- /dev/null
+++ b/library/src/com/mapgen/graph/Edge.as
@@ -0,0 +1,27 @@
+package com.mapgen.graph
+{
+ import flash.geom.Point;
+
+ public class Edge
+ {
+ //--------------------------------------------------------------------------
+ // PROPERTIES
+ //--------------------------------------------------------------------------
+
+ public var index:int;
+
+ // Delaunay edge
+ public var d0:Center;
+ public var d1:Center;
+
+ // Voronoi edge
+ public var v0:Corner
+ public var v1:Corner;
+
+ // halfway between v0,v1
+ public var midpoint:Point;
+
+ // volume of water, or 0
+ public var river:int;
+ }
+}
diff --git a/library/src/com/mapgen/utils/PointTypeUtil.as b/library/src/com/mapgen/utils/PointTypeUtil.as
new file mode 100644
index 0000000..2678c68
--- /dev/null
+++ b/library/src/com/mapgen/utils/PointTypeUtil.as
@@ -0,0 +1,60 @@
+/*
+* Copyright (c) 2015 Nailson Santos
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+* either express or implied. See the License for the specific language
+* governing permissions and limitations under the License.
+*/
+
+package com.mapgen.utils
+{
+ import com.mignari.errors.AbstractClassError;
+
+ import flash.geom.Point;
+
+ public final class PointTypeUtil
+ {
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function PointTypeUtil()
+ {
+ throw new AbstractClassError(PointTypeUtil);
+ }
+
+ //--------------------------------------------------------------------------
+ // STATIC
+ //--------------------------------------------------------------------------
+
+ /**
+ * Generate points on a hexagon grid.
+ */
+ static public function generateHexagon(size:int, seed:int):Function
+ {
+ return function(numPoints:int):Vector.
+ {
+ numPoints = Math.sqrt(numPoints);
+
+ var points:Vector. = new Vector.();
+ for (var x:int = 0; x < numPoints; x++)
+ {
+ for (var y:int = 0; y < numPoints; y++)
+ {
+ points[points.length] = new Point((0.5 + x)/numPoints * size, (0.25 + 0.5*x%2 + y)/numPoints * size);
+ }
+ }
+
+ return points;
+ }
+ }
+ }
+}
diff --git a/library/src/com/nodename/Delaunay/Edge.as b/library/src/com/nodename/Delaunay/Edge.as
new file mode 100644
index 0000000..acf03f6
--- /dev/null
+++ b/library/src/com/nodename/Delaunay/Edge.as
@@ -0,0 +1,421 @@
+package com.nodename.Delaunay
+{
+ import com.nodename.geom.LineSegment;
+
+ import flash.display.BitmapData;
+ import flash.display.CapsStyle;
+ import flash.display.Graphics;
+ import flash.display.LineScaleMode;
+ import flash.display.Sprite;
+ import flash.geom.Point;
+ import flash.geom.Rectangle;
+ import flash.utils.Dictionary;
+
+ /**
+ * The line segment connecting the two Sites is part of the Delaunay triangulation;
+ * the line segment connecting the two Vertices is part of the Voronoi diagram
+ * @author ashaw
+ *
+ */
+ public final class Edge
+ {
+ private static var _pool:Vector. = new Vector.();
+
+ /**
+ * This is the only way to create a new Edge
+ * @param site0
+ * @param site1
+ * @return
+ *
+ */
+ internal static function createBisectingEdge(site0:Site, site1:Site):Edge
+ {
+ var dx:Number, dy:Number, absdx:Number, absdy:Number;
+ var a:Number, b:Number, c:Number;
+
+ dx = site1.x - site0.x;
+ dy = site1.y - site0.y;
+ absdx = dx > 0 ? dx : -dx;
+ absdy = dy > 0 ? dy : -dy;
+ c = site0.x * dx + site0.y * dy + (dx * dx + dy * dy) * 0.5;
+ if (absdx > absdy)
+ {
+ a = 1.0; b = dy/dx; c /= dx;
+ }
+ else
+ {
+ b = 1.0; a = dx/dy; c /= dy;
+ }
+
+ var edge:Edge = Edge.create();
+
+ edge.leftSite = site0;
+ edge.rightSite = site1;
+ site0.addEdge(edge);
+ site1.addEdge(edge);
+
+ edge._leftVertex = null;
+ edge._rightVertex = null;
+
+ edge.a = a; edge.b = b; edge.c = c;
+ //trace("createBisectingEdge: a ", edge.a, "b", edge.b, "c", edge.c);
+
+ return edge;
+ }
+
+ private static function create():Edge
+ {
+ var edge:Edge;
+ if (_pool.length > 0)
+ {
+ edge = _pool.pop();
+ edge.init();
+ }
+ else
+ {
+ edge = new Edge(PrivateConstructorEnforcer);
+ }
+ return edge;
+ }
+
+ private static const LINESPRITE:Sprite = new Sprite();
+ private static const GRAPHICS:Graphics = LINESPRITE.graphics;
+
+ private var _delaunayLineBmp:BitmapData;
+ internal function get delaunayLineBmp():BitmapData
+ {
+ if (!_delaunayLineBmp)
+ {
+ _delaunayLineBmp = makeDelaunayLineBmp();
+ }
+ return _delaunayLineBmp;
+ }
+
+ // making this available to Voronoi; running out of memory in AIR so I cannot cache the bmp
+ internal function makeDelaunayLineBmp():BitmapData
+ {
+ var p0:Point = leftSite.coord;
+ var p1:Point = rightSite.coord;
+
+ GRAPHICS.clear();
+ // clear() resets line style back to undefined!
+ GRAPHICS.lineStyle(0, 0, 1.0, false, LineScaleMode.NONE, CapsStyle.NONE);
+ GRAPHICS.moveTo(p0.x, p0.y);
+ GRAPHICS.lineTo(p1.x, p1.y);
+
+ var w:int = int(Math.ceil(Math.max(p0.x, p1.x)));
+ if (w < 1)
+ {
+ w = 1;
+ }
+ var h:int = int(Math.ceil(Math.max(p0.y, p1.y)));
+ if (h < 1)
+ {
+ h = 1;
+ }
+ var bmp:BitmapData = new BitmapData(w, h, true, 0);
+ bmp.draw(LINESPRITE);
+ return bmp;
+ }
+
+ public function delaunayLine():LineSegment
+ {
+ // draw a line connecting the input Sites for which the edge is a bisector:
+ return new LineSegment(leftSite.coord, rightSite.coord);
+ }
+
+ public function voronoiEdge():LineSegment
+ {
+ if (!visible) return new LineSegment(null, null);
+ return new LineSegment(_clippedVertices[LR.LEFT],
+ _clippedVertices[LR.RIGHT]);
+ }
+
+ private static var _nedges:int = 0;
+
+ internal static const DELETED:Edge = new Edge(PrivateConstructorEnforcer);
+
+ // the equation of the edge: ax + by = c
+ internal var a:Number, b:Number, c:Number;
+
+ // the two Voronoi vertices that the edge connects
+ // (if one of them is null, the edge extends to infinity)
+ private var _leftVertex:Vertex;
+ internal function get leftVertex():Vertex
+ {
+ return _leftVertex;
+ }
+ private var _rightVertex:Vertex;
+ internal function get rightVertex():Vertex
+ {
+ return _rightVertex;
+ }
+ internal function vertex(leftRight:LR):Vertex
+ {
+ return (leftRight == LR.LEFT) ? _leftVertex : _rightVertex;
+ }
+ internal function setVertex(leftRight:LR, v:Vertex):void
+ {
+ if (leftRight == LR.LEFT)
+ {
+ _leftVertex = v;
+ }
+ else
+ {
+ _rightVertex = v;
+ }
+ }
+
+ internal function isPartOfConvexHull():Boolean
+ {
+ return (_leftVertex == null || _rightVertex == null);
+ }
+
+ public function sitesDistance():Number
+ {
+ return Point.distance(leftSite.coord, rightSite.coord);
+ }
+
+ public static function compareSitesDistances_MAX(edge0:Edge, edge1:Edge):Number
+ {
+ var length0:Number = edge0.sitesDistance();
+ var length1:Number = edge1.sitesDistance();
+ if (length0 < length1)
+ {
+ return 1;
+ }
+ if (length0 > length1)
+ {
+ return -1;
+ }
+ return 0;
+ }
+
+ public static function compareSitesDistances(edge0:Edge, edge1:Edge):Number
+ {
+ return - compareSitesDistances_MAX(edge0, edge1);
+ }
+
+ // Once clipVertices() is called, this Dictionary will hold two Points
+ // representing the clipped coordinates of the left and right ends...
+ private var _clippedVertices:Dictionary;
+ internal function get clippedEnds():Dictionary
+ {
+ return _clippedVertices;
+ }
+ // unless the entire Edge is outside the bounds.
+ // In that case visible will be false:
+ internal function get visible():Boolean
+ {
+ return _clippedVertices != null;
+ }
+
+ // the two input Sites for which this Edge is a bisector:
+ private var _sites:Dictionary;
+ internal function set leftSite(s:Site):void
+ {
+ _sites[LR.LEFT] = s;
+ }
+ internal function get leftSite():Site
+ {
+ return _sites[LR.LEFT];
+ }
+ internal function set rightSite(s:Site):void
+ {
+ _sites[LR.RIGHT] = s;
+ }
+ internal function get rightSite():Site
+ {
+ return _sites[LR.RIGHT] as Site;
+ }
+ internal function site(leftRight:LR):Site
+ {
+ return _sites[leftRight] as Site;
+ }
+
+ private var _edgeIndex:int;
+
+ public function dispose():void
+ {
+ if (_delaunayLineBmp)
+ {
+ _delaunayLineBmp.dispose();
+ _delaunayLineBmp = null;
+ }
+ _leftVertex = null;
+ _rightVertex = null;
+ if (_clippedVertices)
+ {
+ _clippedVertices[LR.LEFT] = null;
+ _clippedVertices[LR.RIGHT] = null;
+ _clippedVertices = null;
+ }
+ _sites[LR.LEFT] = null;
+ _sites[LR.RIGHT] = null;
+ _sites = null;
+
+ _pool.push(this);
+ }
+
+ public function Edge(lock:Class)
+ {
+ if (lock != PrivateConstructorEnforcer)
+ {
+ throw new Error("Edge: constructor is private");
+ }
+
+ _edgeIndex = _nedges++;
+ init();
+ }
+
+ private function init():void
+ {
+ _sites = new Dictionary(true);
+ }
+
+ public function toString():String
+ {
+ return "Edge " + _edgeIndex + "; sites " + _sites[LR.LEFT] + ", " + _sites[LR.RIGHT]
+ + "; endVertices " + (_leftVertex ? _leftVertex.vertexIndex : "null") + ", "
+ + (_rightVertex ? _rightVertex.vertexIndex : "null") + "::";
+ }
+
+ /**
+ * Set _clippedVertices to contain the two ends of the portion of the Voronoi edge that is visible
+ * within the bounds. If no part of the Edge falls within the bounds, leave _clippedVertices null.
+ * @param bounds
+ *
+ */
+ internal function clipVertices(bounds:Rectangle):void
+ {
+ var xmin:Number = bounds.x;
+ var ymin:Number = bounds.y;
+ var xmax:Number = bounds.right;
+ var ymax:Number = bounds.bottom;
+
+ var vertex0:Vertex, vertex1:Vertex;
+ var x0:Number, x1:Number, y0:Number, y1:Number;
+
+ if (a == 1.0 && b >= 0.0)
+ {
+ vertex0 = _rightVertex;
+ vertex1 = _leftVertex;
+ }
+ else
+ {
+ vertex0 = _leftVertex;
+ vertex1 = _rightVertex;
+ }
+
+ if (a == 1.0)
+ {
+ y0 = ymin;
+ if (vertex0 != null && vertex0.y > ymin)
+ {
+ y0 = vertex0.y;
+ }
+ if (y0 > ymax)
+ {
+ return;
+ }
+ x0 = c - b * y0;
+
+ y1 = ymax;
+ if (vertex1 != null && vertex1.y < ymax)
+ {
+ y1 = vertex1.y;
+ }
+ if (y1 < ymin)
+ {
+ return;
+ }
+ x1 = c - b * y1;
+
+ if ((x0 > xmax && x1 > xmax) || (x0 < xmin && x1 < xmin))
+ {
+ return;
+ }
+
+ if (x0 > xmax)
+ {
+ x0 = xmax; y0 = (c - x0)/b;
+ }
+ else if (x0 < xmin)
+ {
+ x0 = xmin; y0 = (c - x0)/b;
+ }
+
+ if (x1 > xmax)
+ {
+ x1 = xmax; y1 = (c - x1)/b;
+ }
+ else if (x1 < xmin)
+ {
+ x1 = xmin; y1 = (c - x1)/b;
+ }
+ }
+ else
+ {
+ x0 = xmin;
+ if (vertex0 != null && vertex0.x > xmin)
+ {
+ x0 = vertex0.x;
+ }
+ if (x0 > xmax)
+ {
+ return;
+ }
+ y0 = c - a * x0;
+
+ x1 = xmax;
+ if (vertex1 != null && vertex1.x < xmax)
+ {
+ x1 = vertex1.x;
+ }
+ if (x1 < xmin)
+ {
+ return;
+ }
+ y1 = c - a * x1;
+
+ if ((y0 > ymax && y1 > ymax) || (y0 < ymin && y1 < ymin))
+ {
+ return;
+ }
+
+ if (y0 > ymax)
+ {
+ y0 = ymax; x0 = (c - y0)/a;
+ }
+ else if (y0 < ymin)
+ {
+ y0 = ymin; x0 = (c - y0)/a;
+ }
+
+ if (y1 > ymax)
+ {
+ y1 = ymax; x1 = (c - y1)/a;
+ }
+ else if (y1 < ymin)
+ {
+ y1 = ymin; x1 = (c - y1)/a;
+ }
+ }
+
+ _clippedVertices = new Dictionary(true);
+ if (vertex0 == _leftVertex)
+ {
+ _clippedVertices[LR.LEFT] = new Point(x0, y0);
+ _clippedVertices[LR.RIGHT] = new Point(x1, y1);
+ }
+ else
+ {
+ _clippedVertices[LR.RIGHT] = new Point(x0, y0);
+ _clippedVertices[LR.LEFT] = new Point(x1, y1);
+ }
+ }
+
+ }
+}
+
+class PrivateConstructorEnforcer {}
diff --git a/library/src/com/nodename/Delaunay/EdgeList.as b/library/src/com/nodename/Delaunay/EdgeList.as
new file mode 100644
index 0000000..9be5e07
--- /dev/null
+++ b/library/src/com/nodename/Delaunay/EdgeList.as
@@ -0,0 +1,176 @@
+package com.nodename.Delaunay
+{
+ import com.nodename.utils.IDisposable;
+
+ import flash.geom.Point;
+
+ internal final class EdgeList implements IDisposable
+ {
+ private var _deltax:Number;
+ private var _xmin:Number;
+
+ private var _hashsize:int;
+ private var _hash:Vector.;
+ private var _leftEnd:Halfedge;
+ public function get leftEnd():Halfedge
+ {
+ return _leftEnd;
+ }
+ private var _rightEnd:Halfedge;
+ public function get rightEnd():Halfedge
+ {
+ return _rightEnd;
+ }
+
+ public function dispose():void
+ {
+ var halfEdge:Halfedge = _leftEnd;
+ var prevHe:Halfedge;
+ while (halfEdge != _rightEnd)
+ {
+ prevHe = halfEdge;
+ halfEdge = halfEdge.edgeListRightNeighbor;
+ prevHe.dispose();
+ }
+ _leftEnd = null;
+ _rightEnd.dispose();
+ _rightEnd = null;
+
+ var i:int;
+ for (i = 0; i < _hashsize; ++i)
+ {
+ _hash[i] = null;
+ }
+ _hash = null;
+ }
+
+ public function EdgeList(xmin:Number, deltax:Number, sqrt_nsites:int)
+ {
+ _xmin = xmin;
+ _deltax = deltax;
+ _hashsize = 2 * sqrt_nsites;
+
+ var i:int;
+ _hash = new Vector.(_hashsize, true);
+
+ // two dummy Halfedges:
+ _leftEnd = Halfedge.createDummy();
+ _rightEnd = Halfedge.createDummy();
+ _leftEnd.edgeListLeftNeighbor = null;
+ _leftEnd.edgeListRightNeighbor = _rightEnd;
+ _rightEnd.edgeListLeftNeighbor = _leftEnd;
+ _rightEnd.edgeListRightNeighbor = null;
+ _hash[0] = _leftEnd;
+ _hash[_hashsize - 1] = _rightEnd;
+ }
+
+ /**
+ * Insert newHalfedge to the right of lb
+ * @param lb
+ * @param newHalfedge
+ *
+ */
+ public function insert(lb:Halfedge, newHalfedge:Halfedge):void
+ {
+ newHalfedge.edgeListLeftNeighbor = lb;
+ newHalfedge.edgeListRightNeighbor = lb.edgeListRightNeighbor;
+ lb.edgeListRightNeighbor.edgeListLeftNeighbor = newHalfedge;
+ lb.edgeListRightNeighbor = newHalfedge;
+ }
+
+ /**
+ * This function only removes the Halfedge from the left-right list.
+ * We cannot dispose it yet because we are still using it.
+ * @param halfEdge
+ *
+ */
+ public function remove(halfEdge:Halfedge):void
+ {
+ halfEdge.edgeListLeftNeighbor.edgeListRightNeighbor = halfEdge.edgeListRightNeighbor;
+ halfEdge.edgeListRightNeighbor.edgeListLeftNeighbor = halfEdge.edgeListLeftNeighbor;
+ halfEdge.edge = Edge.DELETED;
+ halfEdge.edgeListLeftNeighbor = halfEdge.edgeListRightNeighbor = null;
+ }
+
+ /**
+ * Find the rightmost Halfedge that is still left of p
+ * @param p
+ * @return
+ *
+ */
+ public function edgeListLeftNeighbor(p:Point):Halfedge
+ {
+ var i:int, bucket:int;
+ var halfEdge:Halfedge;
+
+ /* Use hash table to get close to desired halfedge */
+ bucket = (p.x - _xmin)/_deltax * _hashsize;
+ if (bucket < 0)
+ {
+ bucket = 0;
+ }
+ if (bucket >= _hashsize)
+ {
+ bucket = _hashsize - 1;
+ }
+ halfEdge = getHash(bucket);
+ if (halfEdge == null)
+ {
+ for (i = 1; true ; ++i)
+ {
+ if ((halfEdge = getHash(bucket - i)) != null) break;
+ if ((halfEdge = getHash(bucket + i)) != null) break;
+ }
+ }
+ /* Now search linear list of halfedges for the correct one */
+ if (halfEdge == leftEnd || (halfEdge != rightEnd && halfEdge.isLeftOf(p)))
+ {
+ do
+ {
+ halfEdge = halfEdge.edgeListRightNeighbor;
+ }
+ while (halfEdge != rightEnd && halfEdge.isLeftOf(p));
+ halfEdge = halfEdge.edgeListLeftNeighbor;
+ }
+ else
+ {
+ do
+ {
+ halfEdge = halfEdge.edgeListLeftNeighbor;
+ }
+ while (halfEdge != leftEnd && !halfEdge.isLeftOf(p));
+ }
+
+ /* Update hash table and reference counts */
+ if (bucket > 0 && bucket <_hashsize - 1)
+ {
+ _hash[bucket] = halfEdge;
+ }
+ return halfEdge;
+ }
+
+ /* Get entry from hash table, pruning any deleted nodes */
+ private function getHash(b:int):Halfedge
+ {
+ var halfEdge:Halfedge;
+
+ if (b < 0 || b >= _hashsize)
+ {
+ return null;
+ }
+ halfEdge = _hash[b];
+ if (halfEdge != null && halfEdge.edge == Edge.DELETED)
+ {
+ /* Hash table points to deleted halfedge. Patch as necessary. */
+ _hash[b] = null;
+ // still can't dispose halfEdge yet!
+ return null;
+ }
+ else
+ {
+ return halfEdge;
+ }
+ }
+
+ }
+}
diff --git a/library/src/com/nodename/Delaunay/EdgeReorderer.as b/library/src/com/nodename/Delaunay/EdgeReorderer.as
new file mode 100644
index 0000000..1f55738
--- /dev/null
+++ b/library/src/com/nodename/Delaunay/EdgeReorderer.as
@@ -0,0 +1,120 @@
+package com.nodename.Delaunay
+{
+ internal final class EdgeReorderer
+ {
+ private var _edges:Vector.;
+ private var _edgeOrientations:Vector.;
+ public function get edges():Vector.
+ {
+ return _edges;
+ }
+ public function get edgeOrientations():Vector.
+ {
+ return _edgeOrientations;
+ }
+
+ public function EdgeReorderer(origEdges:Vector., criterion:Class)
+ {
+ if (criterion != Vertex && criterion != Site)
+ {
+ throw new ArgumentError("Edges: criterion must be Vertex or Site");
+ }
+ _edges = new Vector.();
+ _edgeOrientations = new Vector.();
+ if (origEdges.length > 0)
+ {
+ _edges = reorderEdges(origEdges, criterion);
+ }
+ }
+
+ public function dispose():void
+ {
+ _edges = null;
+ _edgeOrientations = null;
+ }
+
+ private function reorderEdges(origEdges:Vector., criterion:Class):Vector.
+ {
+ var i:int;
+ var j:int;
+ var n:int = origEdges.length;
+ var edge:Edge;
+ // we're going to reorder the edges in order of traversal
+ var done:Vector. = new Vector.(n, true);
+ var nDone:int = 0;
+ for each (var b:Boolean in done)
+ {
+ b = false;
+ }
+ var newEdges:Vector. = new Vector.();
+
+ i = 0;
+ edge = origEdges[i];
+ newEdges.push(edge);
+ _edgeOrientations.push(LR.LEFT);
+ var firstPoint:ICoord = (criterion == Vertex) ? edge.leftVertex : edge.leftSite;
+ var lastPoint:ICoord = (criterion == Vertex) ? edge.rightVertex : edge.rightSite;
+
+ if (firstPoint == Vertex.VERTEX_AT_INFINITY || lastPoint == Vertex.VERTEX_AT_INFINITY)
+ {
+ return new Vector.();
+ }
+
+ done[i] = true;
+ ++nDone;
+
+ while (nDone < n)
+ {
+ for (i = 1; i < n; ++i)
+ {
+ if (done[i])
+ {
+ continue;
+ }
+ edge = origEdges[i];
+ var leftPoint:ICoord = (criterion == Vertex) ? edge.leftVertex : edge.leftSite;
+ var rightPoint:ICoord = (criterion == Vertex) ? edge.rightVertex : edge.rightSite;
+ if (leftPoint == Vertex.VERTEX_AT_INFINITY || rightPoint == Vertex.VERTEX_AT_INFINITY)
+ {
+ return new Vector.();
+ }
+ if (leftPoint == lastPoint)
+ {
+ lastPoint = rightPoint;
+ _edgeOrientations.push(LR.LEFT);
+ newEdges.push(edge);
+ done[i] = true;
+ }
+ else if (rightPoint == firstPoint)
+ {
+ firstPoint = leftPoint;
+ _edgeOrientations.unshift(LR.LEFT);
+ newEdges.unshift(edge);
+ done[i] = true;
+ }
+ else if (leftPoint == firstPoint)
+ {
+ firstPoint = rightPoint;
+ _edgeOrientations.unshift(LR.RIGHT);
+ newEdges.unshift(edge);
+ done[i] = true;
+ }
+ else if (rightPoint == lastPoint)
+ {
+ lastPoint = leftPoint;
+ _edgeOrientations.push(LR.RIGHT);
+ newEdges.push(edge);
+ done[i] = true;
+ }
+ if (done[i])
+ {
+ ++nDone;
+ }
+ }
+ }
+
+ return newEdges;
+ }
+
+ }
+}
diff --git a/library/src/com/nodename/Delaunay/Halfedge.as b/library/src/com/nodename/Delaunay/Halfedge.as
new file mode 100644
index 0000000..0a4a4a2
--- /dev/null
+++ b/library/src/com/nodename/Delaunay/Halfedge.as
@@ -0,0 +1,152 @@
+package com.nodename.Delaunay
+{
+ import flash.geom.Point;
+
+ internal final class Halfedge
+ {
+ private static var _pool:Vector. = new Vector.();
+ public static function create(edge:Edge, lr:LR):Halfedge
+ {
+ if (_pool.length > 0)
+ {
+ return _pool.pop().init(edge, lr);
+ }
+ else
+ {
+ return new Halfedge(PrivateConstructorEnforcer, edge, lr);
+ }
+ }
+
+ public static function createDummy():Halfedge
+ {
+ return create(null, null);
+ }
+
+ public var edgeListLeftNeighbor:Halfedge, edgeListRightNeighbor:Halfedge;
+ public var nextInPriorityQueue:Halfedge;
+
+ public var edge:Edge;
+ public var leftRight:LR;
+ public var vertex:Vertex;
+
+ // the vertex's y-coordinate in the transformed Voronoi space V*
+ public var ystar:Number;
+
+ public function Halfedge(lock:Class, edge:Edge = null, lr:LR = null)
+ {
+ if (lock != PrivateConstructorEnforcer)
+ {
+ throw new Error("Halfedge constructor is private");
+ }
+
+ init(edge, lr);
+ }
+
+ private function init(edge:Edge, lr:LR):Halfedge
+ {
+ this.edge = edge;
+ leftRight = lr;
+ nextInPriorityQueue = null;
+ vertex = null;
+ return this;
+ }
+
+ public function toString():String
+ {
+ return "Halfedge (leftRight: " + leftRight + "; vertex: " + vertex + ")";
+ }
+
+ public function dispose():void
+ {
+ if (edgeListLeftNeighbor || edgeListRightNeighbor)
+ {
+ // still in EdgeList
+ return;
+ }
+ if (nextInPriorityQueue)
+ {
+ // still in PriorityQueue
+ return;
+ }
+ edge = null;
+ leftRight = null;
+ vertex = null;
+ _pool.push(this);
+ }
+
+ public function reallyDispose():void
+ {
+ edgeListLeftNeighbor = null;
+ edgeListRightNeighbor = null;
+ nextInPriorityQueue = null;
+ edge = null;
+ leftRight = null;
+ vertex = null;
+ _pool.push(this);
+ }
+
+ internal function isLeftOf(p:Point):Boolean
+ {
+ var topSite:Site;
+ var rightOfSite:Boolean, above:Boolean, fast:Boolean;
+ var dxp:Number, dyp:Number, dxs:Number, t1:Number, t2:Number, t3:Number, yl:Number;
+
+ topSite = edge.rightSite;
+ rightOfSite = p.x > topSite.x;
+ if (rightOfSite && this.leftRight == LR.LEFT)
+ {
+ return true;
+ }
+ if (!rightOfSite && this.leftRight == LR.RIGHT)
+ {
+ return false;
+ }
+
+ if (edge.a == 1.0)
+ {
+ dyp = p.y - topSite.y;
+ dxp = p.x - topSite.x;
+ fast = false;
+ if ((!rightOfSite && edge.b < 0.0) || (rightOfSite && edge.b >= 0.0) )
+ {
+ above = dyp >= edge.b * dxp;
+ fast = above;
+ }
+ else
+ {
+ above = p.x + p.y * edge.b > edge.c;
+ if (edge.b < 0.0)
+ {
+ above = !above;
+ }
+ if (!above)
+ {
+ fast = true;
+ }
+ }
+ if (!fast)
+ {
+ dxs = topSite.x - edge.leftSite.x;
+ above = edge.b * (dxp * dxp - dyp * dyp) <
+ dxs * dyp * (1.0 + 2.0 * dxp/dxs + edge.b * edge.b);
+ if (edge.b < 0.0)
+ {
+ above = !above;
+ }
+ }
+ }
+ else /* edge.b == 1.0 */
+ {
+ yl = edge.c - edge.a * p.x;
+ t1 = p.y - yl;
+ t2 = p.x - topSite.x;
+ t3 = yl - topSite.y;
+ above = t1 * t1 > t2 * t2 + t3 * t3;
+ }
+ return this.leftRight == LR.LEFT ? above : !above;
+ }
+
+ }
+}
+
+class PrivateConstructorEnforcer {}
diff --git a/library/src/com/nodename/Delaunay/HalfedgePriorityQueue.as b/library/src/com/nodename/Delaunay/HalfedgePriorityQueue.as
new file mode 100644
index 0000000..4e1543c
--- /dev/null
+++ b/library/src/com/nodename/Delaunay/HalfedgePriorityQueue.as
@@ -0,0 +1,149 @@
+package com.nodename.Delaunay
+{
+ import flash.geom.Point;
+
+ internal final class HalfedgePriorityQueue // also known as heap
+ {
+ private var _hash:Vector.;
+ private var _count:int;
+ private var _minBucket:int;
+ private var _hashsize:int;
+
+ private var _ymin:Number;
+ private var _deltay:Number;
+
+ public function HalfedgePriorityQueue(ymin:Number, deltay:Number, sqrt_nsites:int)
+ {
+ _ymin = ymin;
+ _deltay = deltay;
+ _hashsize = 4 * sqrt_nsites;
+ initialize();
+ }
+
+ public function dispose():void
+ {
+ // get rid of dummies
+ for (var i:int = 0; i < _hashsize; ++i)
+ {
+ _hash[i].dispose();
+ _hash[i] = null;
+ }
+ _hash = null;
+ }
+
+ private function initialize():void
+ {
+ var i:int;
+
+ _count = 0;
+ _minBucket = 0;
+ _hash = new Vector.(_hashsize);
+ // dummy Halfedge at the top of each hash
+ for (i = 0; i < _hashsize; ++i)
+ {
+ _hash[i] = Halfedge.createDummy();
+ _hash[i].nextInPriorityQueue = null;
+ }
+ }
+
+ public function insert(halfEdge:Halfedge):void
+ {
+ var previous:Halfedge, next:Halfedge;
+ var insertionBucket:int = bucket(halfEdge);
+ if (insertionBucket < _minBucket)
+ {
+ _minBucket = insertionBucket;
+ }
+ previous = _hash[insertionBucket];
+ while ((next = previous.nextInPriorityQueue) != null
+ && (halfEdge.ystar > next.ystar || (halfEdge.ystar == next.ystar && halfEdge.vertex.x > next.vertex.x)))
+ {
+ previous = next;
+ }
+ halfEdge.nextInPriorityQueue = previous.nextInPriorityQueue;
+ previous.nextInPriorityQueue = halfEdge;
+ ++_count;
+ }
+
+ public function remove(halfEdge:Halfedge):void
+ {
+ var previous:Halfedge;
+ var removalBucket:int = bucket(halfEdge);
+
+ if (halfEdge.vertex != null)
+ {
+ previous = _hash[removalBucket];
+ while (previous.nextInPriorityQueue != halfEdge)
+ {
+ previous = previous.nextInPriorityQueue;
+ }
+ previous.nextInPriorityQueue = halfEdge.nextInPriorityQueue;
+ _count--;
+ halfEdge.vertex = null;
+ halfEdge.nextInPriorityQueue = null;
+ halfEdge.dispose();
+ }
+ }
+
+ private function bucket(halfEdge:Halfedge):int
+ {
+ var theBucket:int = (halfEdge.ystar - _ymin)/_deltay * _hashsize;
+ if (theBucket < 0) theBucket = 0;
+ if (theBucket >= _hashsize) theBucket = _hashsize - 1;
+ return theBucket;
+ }
+
+ private function isEmpty(bucket:int):Boolean
+ {
+ return (_hash[bucket].nextInPriorityQueue == null);
+ }
+
+ /**
+ * move _minBucket until it contains an actual Halfedge (not just the dummy at the top);
+ *
+ */
+ private function adjustMinBucket():void
+ {
+ while (_minBucket < _hashsize - 1 && isEmpty(_minBucket))
+ {
+ ++_minBucket;
+ }
+ }
+
+ public function empty():Boolean
+ {
+ return _count == 0;
+ }
+
+ /**
+ * @return coordinates of the Halfedge's vertex in V*, the transformed Voronoi diagram
+ *
+ */
+ public function min():Point
+ {
+ adjustMinBucket();
+ var answer:Halfedge = _hash[_minBucket].nextInPriorityQueue;
+ return new Point(answer.vertex.x, answer.ystar);
+ }
+
+ /**
+ * remove and return the min Halfedge
+ * @return
+ *
+ */
+ public function extractMin():Halfedge
+ {
+ var answer:Halfedge;
+
+ // get the first real Halfedge in _minBucket
+ answer = _hash[_minBucket].nextInPriorityQueue;
+
+ _hash[_minBucket].nextInPriorityQueue = answer.nextInPriorityQueue;
+ _count--;
+ answer.nextInPriorityQueue = null;
+
+ return answer;
+ }
+
+ }
+}
diff --git a/library/src/com/nodename/Delaunay/ICoord.as b/library/src/com/nodename/Delaunay/ICoord.as
new file mode 100644
index 0000000..10777ab
--- /dev/null
+++ b/library/src/com/nodename/Delaunay/ICoord.as
@@ -0,0 +1,9 @@
+package com.nodename.Delaunay
+{
+ import flash.geom.Point;
+
+ internal interface ICoord
+ {
+ function get coord():Point;
+ }
+}
diff --git a/library/src/com/nodename/Delaunay/LR.as b/library/src/com/nodename/Delaunay/LR.as
new file mode 100644
index 0000000..6d1927a
--- /dev/null
+++ b/library/src/com/nodename/Delaunay/LR.as
@@ -0,0 +1,32 @@
+package com.nodename.Delaunay
+{
+ public final class LR
+ {
+ public static const LEFT:LR = new LR(PrivateConstructorEnforcer, "left");
+ public static const RIGHT:LR = new LR(PrivateConstructorEnforcer, "right");
+
+ private var _name:String;
+
+ public function LR(lock:Class, name:String)
+ {
+ if (lock != PrivateConstructorEnforcer)
+ {
+ throw new Error("Illegal constructor access");
+ }
+ _name = name;
+ }
+
+ public static function other(leftRight:LR):LR
+ {
+ return leftRight == LEFT ? RIGHT : LEFT;
+ }
+
+ public function toString():String
+ {
+ return _name;
+ }
+
+ }
+}
+
+class PrivateConstructorEnforcer {}
diff --git a/library/src/com/nodename/Delaunay/Site.as b/library/src/com/nodename/Delaunay/Site.as
new file mode 100644
index 0000000..7209435
--- /dev/null
+++ b/library/src/com/nodename/Delaunay/Site.as
@@ -0,0 +1,466 @@
+package com.nodename.Delaunay
+{
+ import com.nodename.geom.Polygon;
+ import com.nodename.geom.Winding;
+
+ import flash.geom.Point;
+ import flash.geom.Rectangle;
+
+ public final class Site implements ICoord
+ {
+ private static var _pool:Vector. = new Vector.();
+ public static function create(p:Point, index:int, weight:Number, color:uint):Site
+ {
+ if (_pool.length > 0)
+ {
+ return _pool.pop().init(p, index, weight, color);
+ }
+ else
+ {
+ return new Site(PrivateConstructorEnforcer, p, index, weight, color);
+ }
+ }
+
+ internal static function sortSites(sites:Vector.):void
+ {
+ sites.sort(Site.compare);
+ }
+
+ /**
+ * sort sites on y, then x, coord
+ * also change each site's _siteIndex to match its new position in the list
+ * so the _siteIndex can be used to identify the site for nearest-neighbor queries
+ *
+ * haha "also" - means more than one responsibility...
+ *
+ */
+ private static function compare(s1:Site, s2:Site):Number
+ {
+ var returnValue:int = Voronoi.compareByYThenX(s1, s2);
+
+ // swap _siteIndex values if necessary to match new ordering:
+ var tempIndex:int;
+ if (returnValue == -1)
+ {
+ if (s1._siteIndex > s2._siteIndex)
+ {
+ tempIndex = s1._siteIndex;
+ s1._siteIndex = s2._siteIndex;
+ s2._siteIndex = tempIndex;
+ }
+ }
+ else if (returnValue == 1)
+ {
+ if (s2._siteIndex > s1._siteIndex)
+ {
+ tempIndex = s2._siteIndex;
+ s2._siteIndex = s1._siteIndex;
+ s1._siteIndex = tempIndex;
+ }
+
+ }
+
+ return returnValue;
+ }
+
+
+ private static const EPSILON:Number = .005;
+ private static function closeEnough(p0:Point, p1:Point):Boolean
+ {
+ return Point.distance(p0, p1) < EPSILON;
+ }
+
+ private var _coord:Point;
+ public function get coord():Point
+ {
+ return _coord;
+ }
+
+ internal var color:uint;
+ internal var weight:Number;
+
+ private var _siteIndex:uint;
+
+ // the edges that define this Site's Voronoi region:
+ private var _edges:Vector.;
+ internal function get edges():Vector.
+ {
+ return _edges;
+ }
+ // which end of each edge hooks up with the previous edge in _edges:
+ private var _edgeOrientations:Vector.;
+ // ordered list of points that define the region clipped to bounds:
+ private var _region:Vector.;
+
+ public function Site(lock:Class, p:Point, index:int, weight:Number, color:uint)
+ {
+ if (lock != PrivateConstructorEnforcer)
+ {
+ throw new Error("Site constructor is private");
+ }
+ init(p, index, weight, color);
+ }
+
+ private function init(p:Point, index:int, weight:Number, color:uint):Site
+ {
+ _coord = p;
+ _siteIndex = index;
+ this.weight = weight;
+ this.color = color;
+ _edges = new Vector.();
+ _region = null;
+ return this;
+ }
+
+ public function toString():String
+ {
+ return "Site " + _siteIndex + ": " + coord;
+ }
+
+ private function move(p:Point):void
+ {
+ clear();
+ _coord = p;
+ }
+
+ public function dispose():void
+ {
+ _coord = null;
+ clear();
+ _pool.push(this);
+ }
+
+ private function clear():void
+ {
+ if (_edges)
+ {
+ _edges.length = 0;
+ _edges = null;
+ }
+ if (_edgeOrientations)
+ {
+ _edgeOrientations.length = 0;
+ _edgeOrientations = null;
+ }
+ if (_region)
+ {
+ _region.length = 0;
+ _region = null;
+ }
+ }
+
+ internal function addEdge(edge:Edge):void
+ {
+ _edges.push(edge);
+ }
+
+ internal function nearestEdge():Edge
+ {
+ _edges.sort(Edge.compareSitesDistances);
+ return _edges[0];
+ }
+
+ internal function neighborSites():Vector.
+ {
+ if (_edges == null || _edges.length == 0)
+ {
+ return new Vector.();
+ }
+ if (_edgeOrientations == null)
+ {
+ reorderEdges();
+ }
+ var list:Vector. = new Vector.();
+ var edge:Edge;
+ for each (edge in _edges)
+ {
+ list.push(neighborSite(edge));
+ }
+ return list;
+ }
+
+ private function neighborSite(edge:Edge):Site
+ {
+ if (this == edge.leftSite)
+ {
+ return edge.rightSite;
+ }
+ if (this == edge.rightSite)
+ {
+ return edge.leftSite;
+ }
+ return null;
+ }
+
+ internal function region(clippingBounds:Rectangle):Vector.
+ {
+ if (_edges == null || _edges.length == 0)
+ {
+ return new Vector.();
+ }
+ if (_edgeOrientations == null)
+ {
+ reorderEdges();
+ _region = clipToBounds(clippingBounds);
+ if ((new Polygon(_region)).winding() == Winding.CLOCKWISE)
+ {
+ _region = _region.reverse();
+ }
+ }
+ return _region;
+ }
+
+ private function reorderEdges():void
+ {
+ //trace("_edges:", _edges);
+ var reorderer:EdgeReorderer = new EdgeReorderer(_edges, Vertex);
+ _edges = reorderer.edges;
+ //trace("reordered:", _edges);
+ _edgeOrientations = reorderer.edgeOrientations;
+ reorderer.dispose();
+ }
+
+ private function clipToBounds(bounds:Rectangle):Vector.
+ {
+ var points:Vector. = new Vector.;
+ var n:int = _edges.length;
+ var i:int = 0;
+ var edge:Edge;
+ while (i < n && ((_edges[i] as Edge).visible == false))
+ {
+ ++i;
+ }
+
+ if (i == n)
+ {
+ // no edges visible
+ return new Vector.();
+ }
+ edge = _edges[i];
+ var orientation:LR = _edgeOrientations[i];
+ points.push(edge.clippedEnds[orientation]);
+ points.push(edge.clippedEnds[LR.other(orientation)]);
+
+ for (var j:int = i + 1; j < n; ++j)
+ {
+ edge = _edges[j];
+ if (edge.visible == false)
+ {
+ continue;
+ }
+ connect(points, j, bounds);
+ }
+ // close up the polygon by adding another corner point of the bounds if needed:
+ connect(points, i, bounds, true);
+
+ return points;
+ }
+
+ private function connect(points:Vector., j:int, bounds:Rectangle, closingUp:Boolean = false):void
+ {
+ var rightPoint:Point = points[points.length - 1];
+ var newEdge:Edge = _edges[j] as Edge;
+ var newOrientation:LR = _edgeOrientations[j];
+ // the point that must be connected to rightPoint:
+ var newPoint:Point = newEdge.clippedEnds[newOrientation];
+ if (!closeEnough(rightPoint, newPoint))
+ {
+ // The points do not coincide, so they must have been clipped at the bounds;
+ // see if they are on the same border of the bounds:
+ if (rightPoint.x != newPoint.x
+ && rightPoint.y != newPoint.y)
+ {
+ // They are on different borders of the bounds;
+ // insert one or two corners of bounds as needed to hook them up:
+ // (NOTE this will not be correct if the region should take up more than
+ // half of the bounds rect, for then we will have gone the wrong way
+ // around the bounds and included the smaller part rather than the larger)
+ var rightCheck:int = BoundsCheck.check(rightPoint, bounds);
+ var newCheck:int = BoundsCheck.check(newPoint, bounds);
+ var px:Number, py:Number;
+ if (rightCheck & BoundsCheck.RIGHT)
+ {
+ px = bounds.right;
+ if (newCheck & BoundsCheck.BOTTOM)
+ {
+ py = bounds.bottom;
+ points.push(new Point(px, py));
+ }
+ else if (newCheck & BoundsCheck.TOP)
+ {
+ py = bounds.top;
+ points.push(new Point(px, py));
+ }
+ else if (newCheck & BoundsCheck.LEFT)
+ {
+ if (rightPoint.y - bounds.y + newPoint.y - bounds.y < bounds.height)
+ {
+ py = bounds.top;
+ }
+ else
+ {
+ py = bounds.bottom;
+ }
+ points.push(new Point(px, py));
+ points.push(new Point(bounds.left, py));
+ }
+ }
+ else if (rightCheck & BoundsCheck.LEFT)
+ {
+ px = bounds.left;
+ if (newCheck & BoundsCheck.BOTTOM)
+ {
+ py = bounds.bottom;
+ points.push(new Point(px, py));
+ }
+ else if (newCheck & BoundsCheck.TOP)
+ {
+ py = bounds.top;
+ points.push(new Point(px, py));
+ }
+ else if (newCheck & BoundsCheck.RIGHT)
+ {
+ if (rightPoint.y - bounds.y + newPoint.y - bounds.y < bounds.height)
+ {
+ py = bounds.top;
+ }
+ else
+ {
+ py = bounds.bottom;
+ }
+ points.push(new Point(px, py));
+ points.push(new Point(bounds.right, py));
+ }
+ }
+ else if (rightCheck & BoundsCheck.TOP)
+ {
+ py = bounds.top;
+ if (newCheck & BoundsCheck.RIGHT)
+ {
+ px = bounds.right;
+ points.push(new Point(px, py));
+ }
+ else if (newCheck & BoundsCheck.LEFT)
+ {
+ px = bounds.left;
+ points.push(new Point(px, py));
+ }
+ else if (newCheck & BoundsCheck.BOTTOM)
+ {
+ if (rightPoint.x - bounds.x + newPoint.x - bounds.x < bounds.width)
+ {
+ px = bounds.left;
+ }
+ else
+ {
+ px = bounds.right;
+ }
+ points.push(new Point(px, py));
+ points.push(new Point(px, bounds.bottom));
+ }
+ }
+ else if (rightCheck & BoundsCheck.BOTTOM)
+ {
+ py = bounds.bottom;
+ if (newCheck & BoundsCheck.RIGHT)
+ {
+ px = bounds.right;
+ points.push(new Point(px, py));
+ }
+ else if (newCheck & BoundsCheck.LEFT)
+ {
+ px = bounds.left;
+ points.push(new Point(px, py));
+ }
+ else if (newCheck & BoundsCheck.TOP)
+ {
+ if (rightPoint.x - bounds.x + newPoint.x - bounds.x < bounds.width)
+ {
+ px = bounds.left;
+ }
+ else
+ {
+ px = bounds.right;
+ }
+ points.push(new Point(px, py));
+ points.push(new Point(px, bounds.top));
+ }
+ }
+ }
+ if (closingUp)
+ {
+ // newEdge's ends have already been added
+ return;
+ }
+ points.push(newPoint);
+ }
+ var newRightPoint:Point = newEdge.clippedEnds[LR.other(newOrientation)];
+ if (!closeEnough(points[0], newRightPoint))
+ {
+ points.push(newRightPoint);
+ }
+ }
+
+ internal function get x():Number
+ {
+ return _coord.x;
+ }
+ internal function get y():Number
+ {
+ return _coord.y;
+ }
+
+ internal function dist(p:ICoord):Number
+ {
+ return Point.distance(p.coord, this._coord);
+ }
+
+ }
+}
+
+class PrivateConstructorEnforcer {}
+
+import flash.geom.Point;
+import flash.geom.Rectangle;
+
+final class BoundsCheck
+{
+ public static const TOP:int = 1;
+ public static const BOTTOM:int = 2;
+ public static const LEFT:int = 4;
+ public static const RIGHT:int = 8;
+
+ /**
+ *
+ * @param point
+ * @param bounds
+ * @return an int with the appropriate bits set if the Point lies on the corresponding bounds lines
+ *
+ */
+ public static function check(point:Point, bounds:Rectangle):int
+ {
+ var value:int = 0;
+ if (point.x == bounds.left)
+ {
+ value |= LEFT;
+ }
+ if (point.x == bounds.right)
+ {
+ value |= RIGHT;
+ }
+ if (point.y == bounds.top)
+ {
+ value |= TOP;
+ }
+ if (point.y == bounds.bottom)
+ {
+ value |= BOTTOM;
+ }
+ return value;
+ }
+
+ public function BoundsCheck()
+ {
+ throw new Error("BoundsCheck constructor unused");
+ }
+}
diff --git a/library/src/com/nodename/Delaunay/SiteList.as b/library/src/com/nodename/Delaunay/SiteList.as
new file mode 100644
index 0000000..5a46dd9
--- /dev/null
+++ b/library/src/com/nodename/Delaunay/SiteList.as
@@ -0,0 +1,174 @@
+package com.nodename.Delaunay
+{
+ import com.nodename.geom.Circle;
+ import com.nodename.utils.IDisposable;
+
+ import flash.display.BitmapData;
+ import flash.geom.Point;
+ import flash.geom.Rectangle;
+
+ internal final class SiteList implements IDisposable
+ {
+ private var m_sites:Vector.;
+ private var m_currentIndex:uint;
+ private var m_sorted:Boolean;
+
+ public function SiteList()
+ {
+ m_sites = new Vector.();
+ m_sorted = false;
+ }
+
+ public function dispose():void
+ {
+ if (m_sites)
+ {
+ for each (var site:Site in m_sites)
+ {
+ site.dispose();
+ }
+
+ m_sites.length = 0;
+ m_sites = null;
+ }
+ }
+
+ public function push(site:Site):uint
+ {
+ m_sorted = false;
+ return m_sites[m_sites.length] = site;
+ }
+
+ public function get length():uint
+ {
+ return m_sites.length;
+ }
+
+ public function next():Site
+ {
+ if (!m_sorted)
+ {
+ throw new Error("SiteList::next(): sites have not been sorted");
+ }
+
+ if (m_currentIndex < m_sites.length)
+ {
+ return m_sites[m_currentIndex++];
+ }
+
+ return null;
+ }
+
+ internal function getSitesBounds():Rectangle
+ {
+ if (!m_sorted)
+ {
+ Site.sortSites(m_sites);
+ m_currentIndex = 0;
+ m_sorted = true;
+ }
+
+ var xmin:Number, xmax:Number, ymin:Number, ymax:Number;
+ if (m_sites.length == 0)
+ {
+ return new Rectangle(0, 0, 0, 0);
+ }
+
+ xmin = Number.MAX_VALUE;
+ xmax = Number.MIN_VALUE;
+ for each (var site:Site in m_sites)
+ {
+ if (site.x < xmin)
+ {
+ xmin = site.x;
+ }
+
+ if (site.x > xmax)
+ {
+ xmax = site.x;
+ }
+ }
+
+ // here's where we assume that the sites have been sorted on y:
+ ymin = m_sites[0].y;
+ ymax = m_sites[m_sites.length - 1].y;
+ return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin);
+ }
+
+ public function siteColors(referenceImage:BitmapData = null):Vector.
+ {
+ var colors:Vector. = new Vector.(m_sites.length, true);
+ for (var i:uint = 0; i < length; i++)
+ {
+ var site:Site = m_sites[i];
+ colors[i] = referenceImage ? referenceImage.getPixel(site.x, site.y) : site.color;
+ }
+
+ return colors;
+ }
+
+ public function siteCoords():Vector.
+ {
+ var coords:Vector. = new Vector.(m_sites.length, true);
+ for (var i:uint = 0; i < length; i++)
+ {
+ coords[i] = m_sites[i].coord;
+ }
+
+ return coords;
+ }
+
+ /**
+ *
+ * @return the largest circle centered at each site that fits in its region;
+ * if the region is infinite, return a circle of radius 0.
+ *
+ */
+ public function circles():Vector.
+ {
+ var circles:Vector. = new Vector.(m_sites.length, true);
+ for (var i:uint = 0; i < length; i++)
+ {
+ var site:Site = m_sites[i];
+ var radius:Number = 0;
+ var nearestEdge:Edge = site.nearestEdge();
+
+ //!nearestEdge.isPartOfConvexHull() && (radius = nearestEdge.sitesDistance() * 0.5);
+ circles[i] = new Circle(site.x, site.y, radius);
+ }
+
+ return circles;
+ }
+
+ public function regions(plotBounds:Rectangle):Vector.>
+ {
+ var regions:Vector.> = new Vector.>(m_sites, true);
+ for (var i:uint = 0; i < length; i++)
+ {
+ regions[i] = m_sites[i].region(plotBounds);
+ }
+
+ return regions;
+ }
+
+ /**
+ *
+ * @param proximityMap a BitmapData whose regions are filled with the site index values; see PlanePointsCanvas::fillRegions()
+ * @param x
+ * @param y
+ * @return coordinates of nearest Site to (x, y)
+ *
+ */
+ public function nearestSitePoint(proximityMap:BitmapData, x:Number, y:Number):Point
+ {
+ var index:uint = proximityMap.getPixel(x, y);
+ if (index > m_sites.length - 1)
+ {
+ return null;
+ }
+
+ return m_sites[index].coord;
+ }
+
+ }
+}
diff --git a/library/src/com/nodename/Delaunay/Triangle.as b/library/src/com/nodename/Delaunay/Triangle.as
new file mode 100644
index 0000000..55f66f3
--- /dev/null
+++ b/library/src/com/nodename/Delaunay/Triangle.as
@@ -0,0 +1,23 @@
+package com.nodename.Delaunay
+{
+ public final class Triangle
+ {
+ private var _sites:Vector.;
+ public function get sites():Vector.
+ {
+ return _sites;
+ }
+
+ public function Triangle(a:Site, b:Site, c:Site)
+ {
+ _sites = Vector.([ a, b, c ]);
+ }
+
+ public function dispose():void
+ {
+ _sites.length = 0;
+ _sites = null;
+ }
+
+ }
+}
diff --git a/library/src/com/nodename/Delaunay/Vertex.as b/library/src/com/nodename/Delaunay/Vertex.as
new file mode 100644
index 0000000..bdad838
--- /dev/null
+++ b/library/src/com/nodename/Delaunay/Vertex.as
@@ -0,0 +1,138 @@
+package com.nodename.Delaunay
+{
+ import flash.geom.Point;
+
+ internal final class Vertex extends Object implements ICoord
+ {
+ internal static const VERTEX_AT_INFINITY:Vertex = new Vertex(PrivateConstructorEnforcer, NaN, NaN);
+
+ private static var _pool:Vector. = new Vector.();
+ private static function create(x:Number, y:Number):Vertex
+ {
+ if (isNaN(x) || isNaN(y))
+ {
+ return VERTEX_AT_INFINITY;
+ }
+ if (_pool.length > 0)
+ {
+ return _pool.pop().init(x, y);
+ }
+ else
+ {
+ return new Vertex(PrivateConstructorEnforcer, x, y);
+ }
+ }
+
+
+ private static var _nvertices:int = 0;
+
+ private var _coord:Point;
+ public function get coord():Point
+ {
+ return _coord;
+ }
+ private var _vertexIndex:int;
+ public function get vertexIndex():int
+ {
+ return _vertexIndex;
+ }
+
+ public function Vertex(lock:Class, x:Number, y:Number)
+ {
+ if (lock != PrivateConstructorEnforcer)
+ {
+ throw new Error("Vertex constructor is private");
+ }
+
+ init(x, y);
+ }
+
+ private function init(x:Number, y:Number):Vertex
+ {
+ _coord = new Point(x, y);
+ return this;
+ }
+
+ public function dispose():void
+ {
+ _coord = null;
+ _pool.push(this);
+ }
+
+ public function setIndex():void
+ {
+ _vertexIndex = _nvertices++;
+ }
+
+ public function toString():String
+ {
+ return "Vertex (" + _vertexIndex + ")";
+ }
+
+ /**
+ * This is the only way to make a Vertex
+ *
+ * @param halfedge0
+ * @param halfedge1
+ * @return
+ *
+ */
+ public static function intersect(halfedge0:Halfedge, halfedge1:Halfedge):Vertex
+ {
+ var edge0:Edge, edge1:Edge, edge:Edge;
+ var halfedge:Halfedge;
+ var determinant:Number, intersectionX:Number, intersectionY:Number;
+ var rightOfSite:Boolean;
+
+ edge0 = halfedge0.edge;
+ edge1 = halfedge1.edge;
+ if (edge0 == null || edge1 == null)
+ {
+ return null;
+ }
+ if (edge0.rightSite == edge1.rightSite)
+ {
+ return null;
+ }
+
+ determinant = edge0.a * edge1.b - edge0.b * edge1.a;
+ if (-1.0e-10 < determinant && determinant < 1.0e-10)
+ {
+ // the edges are parallel
+ return null;
+ }
+
+ intersectionX = (edge0.c * edge1.b - edge1.c * edge0.b)/determinant;
+ intersectionY = (edge1.c * edge0.a - edge0.c * edge1.a)/determinant;
+
+ if (Voronoi.compareByYThenX(edge0.rightSite, edge1.rightSite) < 0)
+ {
+ halfedge = halfedge0; edge = edge0;
+ }
+ else
+ {
+ halfedge = halfedge1; edge = edge1;
+ }
+ rightOfSite = intersectionX >= edge.rightSite.x;
+ if ((rightOfSite && halfedge.leftRight == LR.LEFT)
+ || (!rightOfSite && halfedge.leftRight == LR.RIGHT))
+ {
+ return null;
+ }
+
+ return Vertex.create(intersectionX, intersectionY);
+ }
+
+ public function get x():Number
+ {
+ return _coord.x;
+ }
+ public function get y():Number
+ {
+ return _coord.y;
+ }
+
+ }
+}
+
+class PrivateConstructorEnforcer {}
diff --git a/library/src/com/nodename/Delaunay/Voronoi.as b/library/src/com/nodename/Delaunay/Voronoi.as
new file mode 100644
index 0000000..b77052e
--- /dev/null
+++ b/library/src/com/nodename/Delaunay/Voronoi.as
@@ -0,0 +1,428 @@
+/*
+* The author of this software is Steven Fortune. Copyright (c) 1994 by AT&T
+* Bell Laboratories.
+* Permission to use, copy, modify, and distribute this software for any
+* purpose without fee is hereby granted, provided that this entire notice
+* is included in all copies of any software which is or includes a copy
+* or modification of this software and in all copies of the supporting
+* documentation for such software.
+* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
+* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+*/
+
+package com.nodename.Delaunay
+{
+ import com.nodename.geom.Circle;
+ import com.nodename.geom.LineSegment;
+
+ import flash.display.BitmapData;
+ import flash.geom.Point;
+ import flash.geom.Rectangle;
+ import flash.utils.Dictionary;
+
+ public final class Voronoi
+ {
+ private var _sites:SiteList;
+ private var _sitesIndexedByLocation:Dictionary;
+ private var _triangles:Vector.;
+ private var _edges:Vector.;
+
+ // TODO generalize this so it doesn't have to be a rectangle;
+ // then we can make the fractal voronois-within-voronois
+ private var _plotBounds:Rectangle;
+ public function get plotBounds():Rectangle
+ {
+ return _plotBounds;
+ }
+
+ public function dispose():void
+ {
+ var i:int, n:int;
+ if (_sites)
+ {
+ _sites.dispose();
+ _sites = null;
+ }
+
+ if (_triangles)
+ {
+ n = _triangles.length;
+ for (i = 0; i < n; ++i)
+ {
+ _triangles[i].dispose();
+ }
+ _triangles.length = 0;
+ _triangles = null;
+ }
+
+ if (_edges)
+ {
+ n = _edges.length;
+ for (i = 0; i < n; ++i)
+ {
+ _edges[i].dispose();
+ }
+
+ _edges.length = 0;
+ _edges = null;
+ }
+
+ _plotBounds = null;
+ _sitesIndexedByLocation = null;
+ }
+
+ public function Voronoi(points:Vector., colors:Vector., plotBounds:Rectangle)
+ {
+ _sites = new SiteList();
+ _sitesIndexedByLocation = new Dictionary(true);
+ addSites(points, colors);
+ _plotBounds = plotBounds;
+ _triangles = new Vector.();
+ _edges = new Vector.();
+ fortunesAlgorithm();
+ }
+
+ private function addSites(points:Vector., colors:Vector.):void
+ {
+ var length:uint = points.length;
+ for (var i:uint = 0; i < length; ++i)
+ {
+ addSite(points[i], colors ? colors[i] : 0, i);
+ }
+ }
+
+ private function addSite(p:Point, color:uint, index:int):void
+ {
+ var weight:Number = Math.random() * 100;
+ var site:Site = Site.create(p, index, weight, color);
+ _sites.push(site);
+ _sitesIndexedByLocation[p] = site;
+ }
+
+ public function edges():Vector.
+ {
+ return _edges;
+ }
+
+ public function region(p:Point):Vector.
+ {
+ var site:Site = _sitesIndexedByLocation[p];
+ if (!site)
+ {
+ return new Vector.();
+ }
+ return site.region(_plotBounds);
+ }
+
+ // TODO: bug: if you call this before you call region(), something goes wrong :(
+ public function neighborSitesForSite(coord:Point):Vector.
+ {
+ var points:Vector. = new Vector.();
+ var site:Site = _sitesIndexedByLocation[coord];
+ if (!site)
+ {
+ return points;
+ }
+ var sites:Vector. = site.neighborSites();
+ var neighbor:Site;
+ for each (neighbor in sites)
+ {
+ points.push(neighbor.coord);
+ }
+ return points;
+ }
+
+ public function circles():Vector.
+ {
+ return _sites.circles();
+ }
+
+ public function voronoiBoundaryForSite(coord:Point):Vector.
+ {
+ return visibleLineSegments(selectEdgesForSitePoint(coord, _edges));
+ }
+
+ public function delaunayLinesForSite(coord:Point):Vector.
+ {
+ return delaunayLinesForEdges(selectEdgesForSitePoint(coord, _edges));
+ }
+
+ public function voronoiDiagram():Vector.
+ {
+ return visibleLineSegments(_edges);
+ }
+
+ public function delaunayTriangulation(keepOutMask:BitmapData = null):Vector.
+ {
+ return delaunayLinesForEdges(selectNonIntersectingEdges(keepOutMask, _edges));
+ }
+
+ public function hull():Vector.
+ {
+ return delaunayLinesForEdges(hullEdges());
+ }
+
+ private function hullEdges():Vector.
+ {
+ return _edges.filter(myTest);
+
+ function myTest(edge:Edge, index:int, vector:Vector.):Boolean
+ {
+ return (edge.isPartOfConvexHull());
+ }
+ }
+
+ public function hullPointsInOrder():Vector.
+ {
+ var hullEdges:Vector. = hullEdges();
+
+ var points:Vector. = new Vector.();
+ if (hullEdges.length == 0)
+ {
+ return points;
+ }
+
+ var reorderer:EdgeReorderer = new EdgeReorderer(hullEdges, Site);
+ hullEdges = reorderer.edges;
+ var orientations:Vector. = reorderer.edgeOrientations;
+ reorderer.dispose();
+
+ var orientation:LR;
+
+ var n:int = hullEdges.length;
+ for (var i:int = 0; i < n; ++i)
+ {
+ var edge:Edge = hullEdges[i];
+ orientation = orientations[i];
+ points.push(edge.site(orientation).coord);
+ }
+ return points;
+ }
+
+ public function spanningTree(type:String = "minimum", keepOutMask:BitmapData = null):Vector.
+ {
+ var edges:Vector. = selectNonIntersectingEdges(keepOutMask, _edges);
+ var segments:Vector. = delaunayLinesForEdges(edges);
+ return kruskal(segments, type);
+ }
+
+ public function regions():Vector.>
+ {
+ return _sites.regions(_plotBounds);
+ }
+
+ public function siteColors(referenceImage:BitmapData = null):Vector.
+ {
+ return _sites.siteColors(referenceImage);
+ }
+
+ /**
+ *
+ * @param proximityMap a BitmapData whose regions are filled with the site index values; see PlanePointsCanvas::fillRegions()
+ * @param x
+ * @param y
+ * @return coordinates of nearest Site to (x, y)
+ *
+ */
+ public function nearestSitePoint(proximityMap:BitmapData, x:Number, y:Number):Point
+ {
+ return _sites.nearestSitePoint(proximityMap, x, y);
+ }
+
+ public function siteCoords():Vector.
+ {
+ return _sites.siteCoords();
+ }
+
+ private function fortunesAlgorithm():void
+ {
+ var newSite:Site, bottomSite:Site, topSite:Site, tempSite:Site;
+ var v:Vertex, vertex:Vertex;
+ var newintstar:Point;
+ var leftRight:LR;
+ var lbnd:Halfedge, rbnd:Halfedge, llbnd:Halfedge, rrbnd:Halfedge, bisector:Halfedge;
+ var edge:Edge;
+
+ var dataBounds:Rectangle = _sites.getSitesBounds();
+
+ var sqrt_nsites:int = int(Math.sqrt(_sites.length + 4));
+ var heap:HalfedgePriorityQueue = new HalfedgePriorityQueue(dataBounds.y, dataBounds.height, sqrt_nsites);
+ var edgeList:EdgeList = new EdgeList(dataBounds.x, dataBounds.width, sqrt_nsites);
+ var halfEdges:Vector. = new Vector.();
+ var vertices:Vector. = new Vector.();
+
+ var bottomMostSite:Site = _sites.next();
+ newSite = _sites.next();
+
+ for (;;)
+ {
+ if (heap.empty() == false)
+ {
+ newintstar = heap.min();
+ }
+
+ if (newSite != null
+ && (heap.empty() || compareByYThenX(newSite, newintstar) < 0))
+ {
+ /* new site is smallest */
+ //trace("smallest: new site " + newSite);
+
+ // Step 8:
+ lbnd = edgeList.edgeListLeftNeighbor(newSite.coord); // the Halfedge just to the left of newSite
+ //trace("lbnd: " + lbnd);
+ rbnd = lbnd.edgeListRightNeighbor; // the Halfedge just to the right
+ //trace("rbnd: " + rbnd);
+ bottomSite = rightRegion(lbnd); // this is the same as leftRegion(rbnd)
+ // this Site determines the region containing the new site
+ //trace("new Site is in region of existing site: " + bottomSite);
+
+ // Step 9:
+ edge = Edge.createBisectingEdge(bottomSite, newSite);
+ //trace("new edge: " + edge);
+ _edges.push(edge);
+
+ bisector = Halfedge.create(edge, LR.LEFT);
+ halfEdges.push(bisector);
+ // inserting two Halfedges into edgeList constitutes Step 10:
+ // insert bisector to the right of lbnd:
+ edgeList.insert(lbnd, bisector);
+
+ // first half of Step 11:
+ if ((vertex = Vertex.intersect(lbnd, bisector)) != null)
+ {
+ vertices.push(vertex);
+ heap.remove(lbnd);
+ lbnd.vertex = vertex;
+ lbnd.ystar = vertex.y + newSite.dist(vertex);
+ heap.insert(lbnd);
+ }
+
+ lbnd = bisector;
+ bisector = Halfedge.create(edge, LR.RIGHT);
+ halfEdges.push(bisector);
+ // second Halfedge for Step 10:
+ // insert bisector to the right of lbnd:
+ edgeList.insert(lbnd, bisector);
+
+ // second half of Step 11:
+ if ((vertex = Vertex.intersect(bisector, rbnd)) != null)
+ {
+ vertices.push(vertex);
+ bisector.vertex = vertex;
+ bisector.ystar = vertex.y + newSite.dist(vertex);
+ heap.insert(bisector);
+ }
+
+ newSite = _sites.next();
+ }
+ else if (heap.empty() == false)
+ {
+ /* intersection is smallest */
+ lbnd = heap.extractMin();
+ llbnd = lbnd.edgeListLeftNeighbor;
+ rbnd = lbnd.edgeListRightNeighbor;
+ rrbnd = rbnd.edgeListRightNeighbor;
+ bottomSite = leftRegion(lbnd);
+ topSite = rightRegion(rbnd);
+ // these three sites define a Delaunay triangle
+ // (not actually using these for anything...)
+ //_triangles.push(new Triangle(bottomSite, topSite, rightRegion(lbnd)));
+
+ v = lbnd.vertex;
+ v.setIndex();
+ lbnd.edge.setVertex(lbnd.leftRight, v);
+ rbnd.edge.setVertex(rbnd.leftRight, v);
+ edgeList.remove(lbnd);
+ heap.remove(rbnd);
+ edgeList.remove(rbnd);
+ leftRight = LR.LEFT;
+ if (bottomSite.y > topSite.y)
+ {
+ tempSite = bottomSite; bottomSite = topSite; topSite = tempSite; leftRight = LR.RIGHT;
+ }
+ edge = Edge.createBisectingEdge(bottomSite, topSite);
+ _edges.push(edge);
+ bisector = Halfedge.create(edge, leftRight);
+ halfEdges.push(bisector);
+ edgeList.insert(llbnd, bisector);
+ edge.setVertex(LR.other(leftRight), v);
+ if ((vertex = Vertex.intersect(llbnd, bisector)) != null)
+ {
+ vertices.push(vertex);
+ heap.remove(llbnd);
+ llbnd.vertex = vertex;
+ llbnd.ystar = vertex.y + bottomSite.dist(vertex);
+ heap.insert(llbnd);
+ }
+ if ((vertex = Vertex.intersect(bisector, rrbnd)) != null)
+ {
+ vertices.push(vertex);
+ bisector.vertex = vertex;
+ bisector.ystar = vertex.y + bottomSite.dist(vertex);
+ heap.insert(bisector);
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ // heap should be empty now
+ heap.dispose();
+ edgeList.dispose();
+
+ for each (var halfEdge:Halfedge in halfEdges)
+ {
+ halfEdge.reallyDispose();
+ }
+ halfEdges.length = 0;
+
+ // we need the vertices to clip the edges
+ for each (edge in _edges)
+ {
+ edge.clipVertices(_plotBounds);
+ }
+
+ // but we don't actually ever use them again!
+ for each (vertex in vertices)
+ {
+ vertex.dispose();
+ }
+ vertices.length = 0;
+
+ function leftRegion(he:Halfedge):Site
+ {
+ var edge:Edge = he.edge;
+ if (!edge)
+ {
+ return bottomMostSite;
+ }
+
+ return edge.site(he.leftRight);
+ }
+
+ function rightRegion(he:Halfedge):Site
+ {
+ var edge:Edge = he.edge;
+ if (!edge)
+ {
+ return bottomMostSite;
+ }
+
+ return edge.site(LR.other(he.leftRight));
+ }
+ }
+
+ internal static function compareByYThenX(s1:Site, s2:*):Number
+ {
+ if (s1.y < s2.y) return -1;
+ if (s1.y > s2.y) return 1;
+ if (s1.x < s2.x) return -1;
+ if (s1.x > s2.x) return 1;
+ return 0;
+ }
+ }
+}
diff --git a/library/src/com/nodename/Delaunay/delaunayLinesForEdges.as b/library/src/com/nodename/Delaunay/delaunayLinesForEdges.as
new file mode 100644
index 0000000..bc47795
--- /dev/null
+++ b/library/src/com/nodename/Delaunay/delaunayLinesForEdges.as
@@ -0,0 +1,15 @@
+package com.nodename.Delaunay
+{
+ import com.nodename.geom.LineSegment;
+
+ internal function delaunayLinesForEdges(edges:Vector.):Vector.
+ {
+ var segments:Vector. = new Vector.();
+ for each (var edge:Edge in edges)
+ {
+ segments[segments.length] = edge.delaunayLine();
+ }
+
+ return segments;
+ }
+}
diff --git a/library/src/com/nodename/Delaunay/kruskal.as b/library/src/com/nodename/Delaunay/kruskal.as
new file mode 100644
index 0000000..bb42b68
--- /dev/null
+++ b/library/src/com/nodename/Delaunay/kruskal.as
@@ -0,0 +1,119 @@
+package com.nodename.Delaunay
+{
+ import flash.utils.Dictionary;
+ import com.nodename.geom.LineSegment;
+
+ /**
+ * Kruskal's spanning tree algorithm with union-find
+ * Skiena: The Algorithm Design Manual, p. 196ff
+ * Note: the sites are implied: they consist of the end points of the line segments
+ */
+ public function kruskal(lineSegments:Vector., type:String = "minimum"):Vector.
+ {
+ var nodes:Dictionary = new Dictionary(true);
+ var mst:Vector. = new Vector.();
+ var nodePool:Vector. = Node.pool;
+
+ switch (type)
+ {
+ // note that the compare functions are the reverse of what you'd expect
+ // because (see below) we traverse the lineSegments in reverse order for speed
+ case "maximum":
+ lineSegments.sort(LineSegment.compareLengths);
+ break;
+ default:
+ lineSegments.sort(LineSegment.compareLengths_MAX);
+ break;
+ }
+
+ for (var i:int = lineSegments.length; --i > -1;)
+ {
+ var lineSegment:LineSegment = lineSegments[i];
+
+ var node0:Node = nodes[lineSegment.p0];
+ var rootOfSet0:Node;
+ if (node0 == null)
+ {
+ node0 = nodePool.length > 0 ? nodePool.pop() : new Node();
+ // intialize the node:
+ rootOfSet0 = node0.parent = node0;
+ node0.treeSize = 1;
+
+ nodes[lineSegment.p0] = node0;
+ }
+ else
+ {
+ rootOfSet0 = find(node0);
+ }
+
+ var node1:Node = nodes[lineSegment.p1];
+ var rootOfSet1:Node;
+ if (node1 == null)
+ {
+ node1 = nodePool.length > 0 ? nodePool.pop() : new Node();
+ // intialize the node:
+ rootOfSet1 = node1.parent = node1;
+ node1.treeSize = 1;
+
+ nodes[lineSegment.p1] = node1;
+ }
+ else
+ {
+ rootOfSet1 = find(node1);
+ }
+
+ if (rootOfSet0 != rootOfSet1) // nodes not in same set
+ {
+ mst.push(lineSegment);
+
+ // merge the two sets:
+ var treeSize0:int = rootOfSet0.treeSize;
+ var treeSize1:int = rootOfSet1.treeSize;
+ if (treeSize0 >= treeSize1)
+ {
+ // set0 absorbs set1:
+ rootOfSet1.parent = rootOfSet0;
+ rootOfSet0.treeSize += treeSize1;
+ }
+ else
+ {
+ // set1 absorbs set0:
+ rootOfSet0.parent = rootOfSet1;
+ rootOfSet1.treeSize += treeSize0;
+ }
+ }
+ }
+
+ for each (var node:Node in nodes)
+ {
+ nodePool.push(node);
+ }
+
+ return mst;
+ }
+}
+
+function find(node:Node):Node
+{
+ if (node.parent == node)
+ {
+ return node;
+ }
+ else
+ {
+ var root:Node = find(node.parent);
+ // this line is just to speed up subsequent finds by keeping the tree depth low:
+ node.parent = root;
+ return root;
+ }
+}
+
+class Node
+{
+ public static var pool:Vector. = new Vector.();
+
+ public var parent:Node;
+ public var treeSize:int;
+
+ public function Node() {}
+}
diff --git a/library/src/com/nodename/Delaunay/selectEdgesForSitePoint.as b/library/src/com/nodename/Delaunay/selectEdgesForSitePoint.as
new file mode 100644
index 0000000..467305e
--- /dev/null
+++ b/library/src/com/nodename/Delaunay/selectEdgesForSitePoint.as
@@ -0,0 +1,15 @@
+package com.nodename.Delaunay
+{
+ import flash.geom.Point;
+
+ internal function selectEdgesForSitePoint(coord:Point, edgesToTest:Vector.):Vector.
+ {
+ return edgesToTest.filter(myTest);
+
+ function myTest(edge:Edge, index:int, vector:Vector.):Boolean
+ {
+ return ((edge.leftSite && edge.leftSite.coord == coord)
+ || (edge.rightSite && edge.rightSite.coord == coord));
+ }
+ }
+}
diff --git a/library/src/com/nodename/Delaunay/selectNonIntersectingEdges.as b/library/src/com/nodename/Delaunay/selectNonIntersectingEdges.as
new file mode 100644
index 0000000..011dcfe
--- /dev/null
+++ b/library/src/com/nodename/Delaunay/selectNonIntersectingEdges.as
@@ -0,0 +1,24 @@
+package com.nodename.Delaunay
+{
+ import flash.geom.Point;
+ import flash.display.BitmapData;
+
+ internal function selectNonIntersectingEdges(keepOutMask:BitmapData, edgesToTest:Vector.):Vector.
+ {
+ if (keepOutMask == null)
+ {
+ return edgesToTest;
+ }
+
+ var zeroPoint:Point = new Point();
+ return edgesToTest.filter(myTest);
+
+ function myTest(edge:Edge, index:int, vector:Vector.):Boolean
+ {
+ var delaunayLineBmp:BitmapData = edge.makeDelaunayLineBmp();
+ var notIntersecting:Boolean = !(keepOutMask.hitTest(zeroPoint, 1, delaunayLineBmp, zeroPoint, 1));
+ delaunayLineBmp.dispose();
+ return notIntersecting;
+ }
+ }
+}
diff --git a/library/src/com/nodename/Delaunay/visibleLineSegments.as b/library/src/com/nodename/Delaunay/visibleLineSegments.as
new file mode 100644
index 0000000..099fa0f
--- /dev/null
+++ b/library/src/com/nodename/Delaunay/visibleLineSegments.as
@@ -0,0 +1,22 @@
+package com.nodename.Delaunay
+{
+ import com.nodename.geom.LineSegment;
+ import flash.geom.Point;
+
+ internal function visibleLineSegments(edges:Vector.):Vector.
+ {
+ var segments:Vector. = new Vector.();
+
+ for each (var edge:Edge in edges)
+ {
+ if (edge.visible)
+ {
+ var p1:Point = edge.clippedEnds[LR.LEFT];
+ var p2:Point = edge.clippedEnds[LR.RIGHT];
+ segments.push(new LineSegment(p1, p2));
+ }
+ }
+
+ return segments;
+ }
+}
diff --git a/library/src/com/nodename/geom/Circle.as b/library/src/com/nodename/geom/Circle.as
new file mode 100644
index 0000000..a9e4bbc
--- /dev/null
+++ b/library/src/com/nodename/geom/Circle.as
@@ -0,0 +1,22 @@
+package com.nodename.geom
+{
+ import flash.geom.Point;
+
+ public final class Circle extends Object
+ {
+ public var center:Point;
+ public var radius:Number;
+
+ public function Circle(centerX:Number, centerY:Number, radius:Number)
+ {
+ super();
+ this.center = new Point(centerX, centerY);
+ this.radius = radius;
+ }
+
+ public function toString():String
+ {
+ return "Circle (center: " + center + "; radius: " + radius + ")";
+ }
+ }
+}
diff --git a/library/src/com/nodename/geom/LineSegment.as b/library/src/com/nodename/geom/LineSegment.as
new file mode 100644
index 0000000..f75dfc8
--- /dev/null
+++ b/library/src/com/nodename/geom/LineSegment.as
@@ -0,0 +1,38 @@
+package com.nodename.geom
+{
+ import flash.geom.Point;
+
+ public final class LineSegment extends Object
+ {
+ public static function compareLengths_MAX(segment0:LineSegment, segment1:LineSegment):Number
+ {
+ var length0:Number = Point.distance(segment0.p0, segment0.p1);
+ var length1:Number = Point.distance(segment1.p0, segment1.p1);
+ if (length0 < length1)
+ {
+ return 1;
+ }
+ if (length0 > length1)
+ {
+ return -1;
+ }
+ return 0;
+ }
+
+ public static function compareLengths(edge0:LineSegment, edge1:LineSegment):Number
+ {
+ return - compareLengths_MAX(edge0, edge1);
+ }
+
+ public var p0:Point;
+ public var p1:Point;
+
+ public function LineSegment(p0:Point, p1:Point)
+ {
+ super();
+ this.p0 = p0;
+ this.p1 = p1;
+ }
+
+ }
+}
diff --git a/library/src/com/nodename/geom/Polygon.as b/library/src/com/nodename/geom/Polygon.as
new file mode 100644
index 0000000..1781f54
--- /dev/null
+++ b/library/src/com/nodename/geom/Polygon.as
@@ -0,0 +1,51 @@
+package com.nodename.geom
+{
+ import flash.geom.Point;
+
+ public final class Polygon
+ {
+ private var m_vertices:Vector.;
+
+ public function Polygon(vertices:Vector.)
+ {
+ m_vertices = vertices;
+ }
+
+ public function area():Number
+ {
+ return Math.abs(signedDoubleArea() * 0.5);
+ }
+
+ public function winding():String
+ {
+ var signedDoubleArea:Number = this.signedDoubleArea();
+ if (signedDoubleArea < 0)
+ {
+ return Winding.CLOCKWISE;
+ }
+
+ if (signedDoubleArea > 0)
+ {
+ return Winding.COUNTERCLOCKWISE;
+ }
+
+ return Winding.NONE;
+ }
+
+ private function signedDoubleArea():Number
+ {
+ var index:uint, nextIndex:uint;
+ var n:uint = m_vertices.length;
+ var point:Point, next:Point;
+ var signedDoubleArea:Number = 0;
+ for (index = 0; index < n; ++index)
+ {
+ nextIndex = (index + 1) % n;
+ point = m_vertices[index] as Point;
+ next = m_vertices[nextIndex] as Point;
+ signedDoubleArea += point.x * next.y - next.x * point.y;
+ }
+ return signedDoubleArea;
+ }
+ }
+}
diff --git a/library/src/com/nodename/geom/Winding.as b/library/src/com/nodename/geom/Winding.as
new file mode 100644
index 0000000..fa4cccd
--- /dev/null
+++ b/library/src/com/nodename/geom/Winding.as
@@ -0,0 +1,24 @@
+package com.nodename.geom
+{
+ import com.mignari.errors.AbstractClassError;
+
+ public final class Winding
+ {
+ //--------------------------------------------------------------------------
+ // CONSTRUCTOR
+ //--------------------------------------------------------------------------
+
+ public function Winding()
+ {
+ throw new AbstractClassError(Winding);
+ }
+
+ //--------------------------------------------------------------------------
+ // STATIC
+ //--------------------------------------------------------------------------
+
+ static public const NONE:String = "none";
+ static public const CLOCKWISE:String = "clockwise";
+ static public const COUNTERCLOCKWISE:String = "counterclockwise"
+ }
+}
diff --git a/library/src/com/nodename/utils/IDisposable.as b/library/src/com/nodename/utils/IDisposable.as
new file mode 100644
index 0000000..19ef634
--- /dev/null
+++ b/library/src/com/nodename/utils/IDisposable.as
@@ -0,0 +1,7 @@
+package com.nodename.utils
+{
+ public interface IDisposable
+ {
+ function dispose():void;
+ }
+}
diff --git a/library/src/de/polygonal/math/PM_PRNG.as b/library/src/de/polygonal/math/PM_PRNG.as
new file mode 100644
index 0000000..6359e82
--- /dev/null
+++ b/library/src/de/polygonal/math/PM_PRNG.as
@@ -0,0 +1,116 @@
+/*
+* Copyright (c) 2009 Michael Baczynski, http://www.polygonal.de
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+/**
+ * Implementation of the Park Miller (1988) "minimal standard" linear
+ * congruential pseudo-random number generator.
+ *
+ * For a full explanation visit: http://www.firstpr.com.au/dsp/rand31/
+ *
+ * The generator uses a modulus constant (m) of 2^31 - 1 which is a
+ * Mersenne Prime number and a full-period-multiplier of 16807.
+ * Output is a 31 bit unsigned integer. The range of values output is
+ * 1 to 2,147,483,646 (2^31-1) and the seed must be in this range too.
+ *
+ * David G. Carta's optimisation which needs only 32 bit integer math,
+ * and no division is actually *slower* in flash (both AS2 & AS3) so
+ * it's better to use the double-precision floating point version.
+ *
+ * @author Michael Baczynski, www.polygonal.de
+ */
+package de.polygonal.math
+{
+ public class PM_PRNG
+ {
+ /**
+ * set seed with a 31 bit unsigned integer
+ * between 1 and 0X7FFFFFFE inclusive. don't use 0!
+ */
+ public var seed:uint;
+
+ public function PM_PRNG()
+ {
+ seed = 1;
+ }
+
+ /**
+ * provides the next pseudorandom number
+ * as an unsigned integer (31 bits)
+ */
+ public function nextInt():uint
+ {
+ return gen();
+ }
+
+ /**
+ * provides the next pseudorandom number
+ * as a float between nearly 0 and nearly 1.0.
+ */
+ public function nextDouble():Number
+ {
+ return (gen() / 2147483647);
+ }
+
+ /**
+ * provides the next pseudorandom number
+ * as an unsigned integer (31 bits) betweeen
+ * a given range.
+ */
+ public function nextIntRange(min:Number, max:Number):uint
+ {
+ min -= .4999;
+ max += .4999;
+ return Math.round(min + ((max - min) * nextDouble()));
+ }
+
+ /**
+ * provides the next pseudorandom number
+ * as a float between a given range.
+ */
+ public function nextDoubleRange(min:Number, max:Number):Number
+ {
+ return min + ((max - min) * nextDouble());
+ }
+
+ /**
+ * generator:
+ * new-value = (old-value * 16807) mod (2^31 - 1)
+ */
+ private function gen():uint
+ {
+ //integer version 1, for max int 2^46 - 1 or larger.
+ return seed = (seed * 16807) % 2147483647;
+
+ /**
+ * integer version 2, for max int 2^31 - 1 (slowest)
+ */
+ //var test:int = 16807 * (seed % 127773 >> 0) - 2836 * (seed / 127773 >> 0);
+ //return seed = (test > 0 ? test : test + 2147483647);
+
+ /**
+ * david g. carta's optimisation is 15% slower than integer version 1
+ */
+ //var hi:uint = 16807 * (seed >> 16);
+ //var lo:uint = 16807 * (seed & 0xFFFF) + ((hi & 0x7FFF) << 16) + (hi >> 15);
+ //return seed = (lo > 0x7FFFFFFF ? lo - 0x7FFFFFFF : lo);
+ }
+ }
+}