├── .github └── FUNDING.yml ├── .gitignore ├── README.md ├── assets ├── girl-pearl-earring-mini.png ├── girl-pearl-earring.png ├── lena-mini.png ├── lena.png ├── mona-lisa-mini.png ├── mona-lisa.png ├── starry-night-mini.png └── starry-night.png ├── bin ├── html5 │ └── bin │ │ ├── Demo.js │ │ ├── favicon.png │ │ ├── index.html │ │ ├── lib │ │ ├── FileSaver.min.js │ │ ├── howler.min.js │ │ └── pako.min.js │ │ └── manifest │ │ └── default.json └── js │ ├── index.html │ └── js_demo.js ├── build.hxml ├── com └── nodename │ ├── delaunay │ ├── ArrayHelper.hx │ ├── Edge.hx │ ├── EdgeList.hx │ ├── EdgeReorderer.hx │ ├── Halfedge.hx │ ├── HalfedgePriorityQueue.hx │ ├── ICoord.hx │ ├── IDisposable.hx │ ├── Kruskal.hx │ ├── LR.hx │ ├── SelectHelper.hx │ ├── Site.hx │ ├── SiteList.hx │ ├── Triangle.hx │ ├── Vertex.hx │ └── Voronoi.hx │ └── geom │ ├── Circle.hx │ ├── LineSegment.hx │ ├── Point.hx │ ├── Polygon.hx │ ├── Rectangle.hx │ └── Winding.hx ├── hxDelaunayTest.hxproj ├── project.xml ├── screenshots ├── delaunay.png ├── girl-pearl-earring-320.png ├── girl-pearl-earring-voronoi.png ├── lena-320.png ├── lena-voronoi.png ├── mona-lisa-320.png ├── mona-lisa-hollow-voronoi.png ├── screenshot.png ├── starry-night-320.png └── starry-night-voronoi.png └── src ├── Demo.hx └── DemoJs.hx /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [azrafe7] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build and Release Folders 2 | bin/ 3 | !bin/js 4 | bin-debug/ 5 | bin-release/ 6 | 7 | # Other files and folders 8 | .settings/ 9 | 10 | # Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties` 11 | # should NOT be excluded as they contain compiler settings and other important 12 | # information for Eclipse / Flash Builder. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | hxDelaunay 2 | ========== 3 | 4 | Port to Haxe 3 of [sledorze/hxDelaunay](https://github.com/sledorze/hxDelaunay) (itself a port of the excellent [nodename/as3delaunay](https://github.com/nodename/as3delaunay)). 5 | 6 | [![click for js demo](screenshots/screenshot.png)](https://rawgit.com/azrafe7/hxdelaunay/master/bin/html5/bin/index.html) 7 | 8 | _click the image above to try the [demo](https://rawgit.com/azrafe7/hxdelaunay/master/bin/html5/bin/index.html) in the browser_ 9 | 10 | No external dependencies (demo still needs openfl). Tested on flash/js/cpp/neko. 11 | 12 | ### Features: ### 13 | 14 | - [Voronoi diagram](http://en.wikipedia.org/wiki/Voronoi) 15 | - [Delaunay triangulation](http://en.wikipedia.org/wiki/Delaunay_triangulation) 16 | - [Convex hull](http://en.wikipedia.org/wiki/Convex_hull) 17 | - [Minimum spanning tree](http://en.wikipedia.org/wiki/Euclidean_minimum_spanning_tree) 18 | - [Onion](http://cgm.cs.mcgill.ca/~orm/ontri.html) 19 | 20 | See original authors' links for details and licensing (MIT). 21 | 22 | 23 | ### Update: 24 | 25 | - Delaunay triangulation visualization ([see JS code example](src/DemoJs.hx)). 26 | 27 | [![](screenshots/delaunay.png)](https://rawgit.com/azrafe7/hxDelaunay/master/bin/js/index.html) 28 | 29 | _click on image to see the code in action_ 30 | 31 | ## more screenshots 32 | 33 | ![](screenshots/starry-night-320.png) ![](screenshots/starry-night-voronoi.png) ![](screenshots/mona-lisa-320.png) ![](screenshots/mona-lisa-hollow-voronoi.png) 34 | ![](screenshots/girl-pearl-earring-320.png) ![](screenshots/girl-pearl-earring-voronoi.png) ![](screenshots/lena-320.png) ![](screenshots/lena-voronoi.png) 35 | 36 | (well... I've cheated a bit in some of these, but not much ;) 37 | 38 | # haxelib local use 39 | 40 | Currently there is no haxelib, but you can use this git repo as a development directory: 41 | 42 | ``` 43 | haxelib dev hxdelaunay path/to/folder 44 | ``` 45 | 46 | or use git directly: 47 | 48 | ``` 49 | haxelib git hxdelaunay https://github.com/azrafe7/hxDelaunay.git 50 | ``` 51 | 52 | don't forget to add it to your build file: 53 | 54 | ``` 55 | -lib hxdelaunay 56 | ``` 57 | 58 | or for **openfl**: 59 | 60 | ``` 61 | 62 | ``` 63 | 64 | 65 | Check out the [openfl example](src/Demo.hx) for more information. 66 | 67 | 68 | Or a simpler [js code example](src/DemoJs.hx). See it in action here: [JavaScript example](https://rawgit.com/azrafe7/hxDelaunay/master/bin/js/index.html). 69 | 70 | **Enjoy!** 71 | 72 | -------------------------------------------------------------------------------- /assets/girl-pearl-earring-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azrafe7/hxDelaunay/75a8a0d774196b3b0dfac6e2274e155a30060dce/assets/girl-pearl-earring-mini.png -------------------------------------------------------------------------------- /assets/girl-pearl-earring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azrafe7/hxDelaunay/75a8a0d774196b3b0dfac6e2274e155a30060dce/assets/girl-pearl-earring.png -------------------------------------------------------------------------------- /assets/lena-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azrafe7/hxDelaunay/75a8a0d774196b3b0dfac6e2274e155a30060dce/assets/lena-mini.png -------------------------------------------------------------------------------- /assets/lena.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azrafe7/hxDelaunay/75a8a0d774196b3b0dfac6e2274e155a30060dce/assets/lena.png -------------------------------------------------------------------------------- /assets/mona-lisa-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azrafe7/hxDelaunay/75a8a0d774196b3b0dfac6e2274e155a30060dce/assets/mona-lisa-mini.png -------------------------------------------------------------------------------- /assets/mona-lisa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azrafe7/hxDelaunay/75a8a0d774196b3b0dfac6e2274e155a30060dce/assets/mona-lisa.png -------------------------------------------------------------------------------- /assets/starry-night-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azrafe7/hxDelaunay/75a8a0d774196b3b0dfac6e2274e155a30060dce/assets/starry-night-mini.png -------------------------------------------------------------------------------- /assets/starry-night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azrafe7/hxDelaunay/75a8a0d774196b3b0dfac6e2274e155a30060dce/assets/starry-night.png -------------------------------------------------------------------------------- /bin/html5/bin/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azrafe7/hxDelaunay/75a8a0d774196b3b0dfac6e2274e155a30060dce/bin/html5/bin/favicon.png -------------------------------------------------------------------------------- /bin/html5/bin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Demo 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 28 | 29 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | 42 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /bin/html5/bin/lib/FileSaver.min.js: -------------------------------------------------------------------------------- 1 | /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ 2 | var saveAs=saveAs||function(e){"use strict";if(typeof e==="undefined"||typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),o="download"in r,a=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},i=/constructor/i.test(e.HTMLElement)||e.safari,f=/CriOS\/[\d]+/.test(navigator.userAgent),u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},s="application/octet-stream",d=1e3*40,c=function(e){var t=function(){if(typeof e==="string"){n().revokeObjectURL(e)}else{e.remove()}};setTimeout(t,d)},l=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var o=e["on"+t[r]];if(typeof o==="function"){try{o.call(e,n||e)}catch(a){u(a)}}}},p=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob([String.fromCharCode(65279),e],{type:e.type})}return e},v=function(t,u,d){if(!d){t=p(t)}var v=this,w=t.type,m=w===s,y,h=function(){l(v,"writestart progress write writeend".split(" "))},S=function(){if((f||m&&i)&&e.FileReader){var r=new FileReader;r.onloadend=function(){var t=f?r.result:r.result.replace(/^data:[^;]*;/,"data:attachment/file;");var n=e.open(t,"_blank");if(!n)e.location.href=t;t=undefined;v.readyState=v.DONE;h()};r.readAsDataURL(t);v.readyState=v.INIT;return}if(!y){y=n().createObjectURL(t)}if(m){e.location.href=y}else{var o=e.open(y,"_blank");if(!o){e.location.href=y}}v.readyState=v.DONE;h();c(y)};v.readyState=v.INIT;if(o){y=n().createObjectURL(t);setTimeout(function(){r.href=y;r.download=u;a(r);h();c(y);v.readyState=v.DONE});return}S()},w=v.prototype,m=function(e,t,n){return new v(e,t||e.name||"download",n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){t=t||e.name||"download";if(!n){e=p(e)}return navigator.msSaveOrOpenBlob(e,t)}}w.abort=function(){};w.readyState=w.INIT=0;w.WRITING=1;w.DONE=2;w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null;return m}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!==null){define("FileSaver.js",function(){return saveAs})} 3 | -------------------------------------------------------------------------------- /bin/html5/bin/lib/howler.min.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";var e=function(){this.init()};e.prototype={init:function(){var e=this||t;return e._counter=1e3,e._codecs={},e._howls=[],e._muted=!1,e._volume=1,e._canPlayEvent="canplaythrough",e._navigator="undefined"!=typeof window&&window.navigator?window.navigator:null,e.masterGain=null,e.noAudio=!1,e.usingWebAudio=!0,e.autoSuspend=!0,e.ctx=null,e.mobileAutoEnable=!0,e._setup(),e},volume:function(e){var n=this||t;if(e=parseFloat(e),n.ctx||d(),void 0!==e&&e>=0&&e<=1){if(n._volume=e,n._muted)return n;n.usingWebAudio&&(n.masterGain.gain.value=e);for(var o=0;o=0;n--)e._howls[n].unload();return e.usingWebAudio&&e.ctx&&void 0!==e.ctx.close&&(e.ctx.close(),e.ctx=null,d()),e},codecs:function(e){return(this||t)._codecs[e.replace(/^x-/,"")]},_setup:function(){var e=this||t;if(e._initSuspended&&e.ctx&&"suspended"!=e.ctx.state&&(e._initSuspended=!1),e.state=e.ctx&&!e._initSuspended?e.ctx.state||"running":"running",e._autoSuspend(),!e.usingWebAudio)if("undefined"!=typeof Audio)try{void 0===(n=new Audio).oncanplaythrough&&(e._canPlayEvent="canplay")}catch(t){e.noAudio=!0}else e.noAudio=!0;try{var n=new Audio;n.muted&&(e.noAudio=!0)}catch(e){}return e.noAudio||e._setupCodecs(),e},_setupCodecs:function(){var e=this||t,n=null;try{n="undefined"!=typeof Audio?new Audio:null}catch(t){return e}if(!n||"function"!=typeof n.canPlayType)return e;var o=n.canPlayType("audio/mpeg;").replace(/^no$/,""),r=e._navigator&&e._navigator.userAgent.match(/OPR\/([0-6].)/g),i=r&&parseInt(r[0].split("/")[1],10)<33;return e._codecs={mp3:!(i||!o&&!n.canPlayType("audio/mp3;").replace(/^no$/,"")),mpeg:!!o,opus:!!n.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/,""),ogg:!!n.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),oga:!!n.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),wav:!!n.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),aac:!!n.canPlayType("audio/aac;").replace(/^no$/,""),caf:!!n.canPlayType("audio/x-caf;").replace(/^no$/,""),m4a:!!(n.canPlayType("audio/x-m4a;")||n.canPlayType("audio/m4a;")||n.canPlayType("audio/aac;")).replace(/^no$/,""),mp4:!!(n.canPlayType("audio/x-mp4;")||n.canPlayType("audio/mp4;")||n.canPlayType("audio/aac;")).replace(/^no$/,""),weba:!!n.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,""),webm:!!n.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,""),dolby:!!n.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/,""),flac:!!(n.canPlayType("audio/x-flac;")||n.canPlayType("audio/flac;")).replace(/^no$/,"")},e},_enableMobileAudio:function(){var e=this||t,n=/iPhone|iPad|iPod|Android|BlackBerry|BB10|Silk|Mobi/i.test(e._navigator&&e._navigator.userAgent),o=!!("ontouchend"in window||e._navigator&&e._navigator.maxTouchPoints>0||e._navigator&&e._navigator.msMaxTouchPoints>0);if(!e._mobileEnabled&&e.ctx&&(n||o)){e._mobileEnabled=!1,e._mobileUnloaded||44100===e.ctx.sampleRate||(e._mobileUnloaded=!0,e.unload()),e._scratchBuffer=e.ctx.createBuffer(1,1,22050);var r=function(){t._autoResume();var n=e.ctx.createBufferSource();n.buffer=e._scratchBuffer,n.connect(e.ctx.destination),void 0===n.start?n.noteOn(0):n.start(0),"function"==typeof e.ctx.resume&&e.ctx.resume(),n.onended=function(){n.disconnect(0),e._mobileEnabled=!0,e.mobileAutoEnable=!1,document.removeEventListener("touchstart",r,!0),document.removeEventListener("touchend",r,!0)}};return document.addEventListener("touchstart",r,!0),document.addEventListener("touchend",r,!0),e}},_autoSuspend:function(){var e=this;if(e.autoSuspend&&e.ctx&&void 0!==e.ctx.suspend&&t.usingWebAudio){for(var n=0;n0?u._seek:o._sprite[e][0]/1e3),_=Math.max(0,(o._sprite[e][0]+o._sprite[e][1])/1e3-d),l=1e3*_/Math.abs(u._rate);u._paused=!1,u._ended=!1,u._sprite=e,u._seek=d,u._start=o._sprite[e][0]/1e3,u._stop=(o._sprite[e][0]+o._sprite[e][1])/1e3,u._loop=!(!u._loop&&!o._sprite[e][2]);var c=u._node;if(o._webAudio){var f=function(){o._refreshBuffer(u);var e=u._muted||o._muted?0:u._volume;c.gain.setValueAtTime(e,t.ctx.currentTime),u._playStart=t.ctx.currentTime,void 0===c.bufferSource.start?u._loop?c.bufferSource.noteGrainOn(0,d,86400):c.bufferSource.noteGrainOn(0,d,_):u._loop?c.bufferSource.start(0,d,86400):c.bufferSource.start(0,d,_),l!==1/0&&(o._endTimers[u._id]=setTimeout(o._ended.bind(o,u),l)),n||setTimeout(function(){o._emit("play",u._id)},0)};"running"===t.state?f():(o.once("resume",f),o._clearTimer(u._id))}else{var p=function(){c.currentTime=d,c.muted=u._muted||o._muted||t._muted||c.muted,c.volume=u._volume*t.volume(),c.playbackRate=u._rate;try{if(c.play(),c.paused)return void o._emit("playerror",u._id,"Playback was unable to start. This is most commonly an issue on mobile devices where playback was not within a user interaction.");l!==1/0&&(o._endTimers[u._id]=setTimeout(o._ended.bind(o,u),l)),n||o._emit("play",u._id)}catch(e){o._emit("playerror",u._id,e)}},h=window&&window.ejecta||!c.readyState&&t._navigator.isCocoonJS;if(4===c.readyState||h)p();else{var m=function(){p(),c.removeEventListener(t._canPlayEvent,m,!1)};c.addEventListener(t._canPlayEvent,m,!1),o._clearTimer(u._id)}}return u._id},pause:function(e){var t=this;if("loaded"!==t._state)return t._queue.push({event:"pause",action:function(){t.pause(e)}}),t;for(var n=t._getSoundIds(e),o=0;o=0?n=parseInt(r[0],10):e=parseFloat(r[0])}else r.length>=2&&(e=parseFloat(r[0]),n=parseInt(r[1],10));var i;if(!(void 0!==e&&e>=0&&e<=1))return(i=n?o._soundById(n):o._sounds[0])?i._volume:0;if("loaded"!==o._state)return o._queue.push({event:"volume",action:function(){o.volume.apply(o,r)}}),o;void 0===n&&(o._volume=e),n=o._getSoundIds(n);for(var a=0;an?"out":"in",d=Math.abs(t-n)/.01,_=d>0?o/d:o;_<4&&(d=Math.ceil(d/(4/_)),_=4),e._interval=setInterval(function(){d>0&&(u+="in"===s?.01:-.01),u=Math.max(0,u),u=Math.min(1,u),u=Math.round(100*u)/100,a._webAudio?e._volume=u:a.volume(u,e._id,!0),i&&(a._volume=u),(nt&&u>=n)&&(clearInterval(e._interval),e._interval=null,a.volume(n,e._id),a._emit("fade",e._id))},_)},_stopFade:function(e){var n=this._soundById(e);return n&&n._interval&&(this._webAudio&&n._node.gain.cancelScheduledValues(t.ctx.currentTime),clearInterval(n._interval),n._interval=null,this._emit("fade",e)),this},loop:function(){var e,t,n,o=arguments;if(0===o.length)return this._loop;if(1===o.length){if("boolean"!=typeof o[0])return!!(n=this._soundById(parseInt(o[0],10)))&&n._loop;e=o[0],this._loop=e}else 2===o.length&&(e=o[0],t=parseInt(o[1],10));for(var r=this._getSoundIds(t),i=0;i=0?n=parseInt(r[0],10):e=parseFloat(r[0])}else 2===r.length&&(e=parseFloat(r[0]),n=parseInt(r[1],10));var i;if("number"!=typeof e)return(i=o._soundById(n))?i._rate:o._rate;if("loaded"!==o._state)return o._queue.push({event:"rate",action:function(){o.rate.apply(o,r)}}),o;void 0===n&&(o._rate=e),n=o._getSoundIds(n);for(var a=0;a=0?n=parseInt(r[0],10):o._sounds.length&&(n=o._sounds[0]._id,e=parseFloat(r[0]))}else 2===r.length&&(e=parseFloat(r[0]),n=parseInt(r[1],10));if(void 0===n)return o;if("loaded"!==o._state)return o._queue.push({event:"seek",action:function(){o.seek.apply(o,r)}}),o;var i=o._soundById(n);if(i){if(!("number"==typeof e&&e>=0)){if(o._webAudio){var a=o.playing(n)?t.ctx.currentTime-i._playStart:0,u=i._rateSeek?i._rateSeek-i._seek:0;return i._seek+(u+a*Math.abs(i._rate))}return i._node.currentTime}var s=o.playing(n);s&&o.pause(n,!0),i._seek=e,i._ended=!1,o._clearTimer(n),s&&o.play(n,!0),!o._webAudio&&i._node&&(i._node.currentTime=e),o._emit("seek",n)}return o},playing:function(e){if("number"==typeof e){var t=this._soundById(e);return!!t&&!t._paused}for(var n=0;n=0&&t._howls.splice(i,1)}var a=!0;for(o=0;o=0;r--)o[r].id&&o[r].id!==t&&"load"!==e||(setTimeout(function(e){e.call(this,t,n)}.bind(this,o[r].fn),0),o[r].once&&this.off(e,o[r].fn,o[r].id));return this},_loadQueue:function(){var e=this;if(e._queue.length>0){var t=e._queue[0];e.once(t.event,function(){e._queue.shift(),e._loadQueue()}),t.action()}return e},_ended:function(e){var n=e._sprite;if(!this._webAudio&&e._node&&!e._node.paused&&!e._node.ended)return setTimeout(this._ended.bind(this,e),100),this;var o=!(!e._loop&&!this._sprite[n][2]);if(this._emit("end",e._id),!this._webAudio&&o&&this.stop(e._id,!0).play(e._id),this._webAudio&&o){this._emit("play",e._id),e._seek=e._start||0,e._rateSeek=0,e._playStart=t.ctx.currentTime;var r=1e3*(e._stop-e._start)/Math.abs(e._rate);this._endTimers[e._id]=setTimeout(this._ended.bind(this,e),r)}return this._webAudio&&!o&&(e._paused=!0,e._ended=!0,e._seek=e._start||0,e._rateSeek=0,this._clearTimer(e._id),this._cleanBuffer(e._node),t._autoSuspend()),this._webAudio||o||this.stop(e._id),this},_clearTimer:function(e){return this._endTimers[e]&&(clearTimeout(this._endTimers[e]),delete this._endTimers[e]),this},_soundById:function(e){for(var t=0;t=0;n--){if(t<=e)return;this._sounds[n]._ended&&(this._webAudio&&this._sounds[n]._node&&this._sounds[n]._node.disconnect(0),this._sounds.splice(n,1),t--)}}},_getSoundIds:function(e){if(void 0===e){for(var t=[],n=0;n0&&(r[n._src]=e,s(n,e))},function(){n._emit("loaderror",null,"Decoding audio data failed.")})},s=function(e,t){t&&!e._duration&&(e._duration=t.duration),0===Object.keys(e._sprite).length&&(e._sprite={__default:[0,1e3*e._duration]}),"loaded"!==e._state&&(e._state="loaded",e._emit("load"),e._loadQueue())},d=function(){try{"undefined"!=typeof AudioContext?t.ctx=new AudioContext:"undefined"!=typeof webkitAudioContext?t.ctx=new webkitAudioContext:t.usingWebAudio=!1}catch(e){t.usingWebAudio=!1}t._initSuspended=void 0!==t.ctx&&t.ctx&&null!=t.ctx.state&&"suspended"===t.ctx.state;var e=/iP(hone|od|ad)/.test(t._navigator&&t._navigator.platform),n=t._navigator&&t._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/),o=n?parseInt(n[1],10):null;if(e&&o&&o<9){var r=/safari/.test(t._navigator&&t._navigator.userAgent.toLowerCase());(t._navigator&&t._navigator.standalone&&!r||t._navigator&&!t._navigator.standalone&&!r)&&(t.usingWebAudio=!1)}t.usingWebAudio&&(t.masterGain=void 0===t.ctx.createGain?t.ctx.createGainNode():t.ctx.createGain(),t.masterGain.gain.value=t._muted?0:1,t.masterGain.connect(t.ctx.destination)),t._setup()};"function"==typeof define&&define.amd&&define([],function(){return{Howler:t,Howl:n}}),"undefined"!=typeof exports&&(exports.Howler=t,exports.Howl=n),"undefined"!=typeof window?(window.HowlerGlobal=e,window.Howler=t,window.Howl=n,window.Sound=o):"undefined"!=typeof global&&(global.HowlerGlobal=e,global.Howler=t,global.Howl=n,global.Sound=o)}(); 2 | !function(){"use strict";HowlerGlobal.prototype._pos=[0,0,0],HowlerGlobal.prototype._orientation=[0,0,-1,0,1,0],HowlerGlobal.prototype.stereo=function(n){var e=this;if(!e.ctx||!e.ctx.listener)return e;for(var t=e._howls.length-1;t>=0;t--)e._howls[t].stereo(n);return e},HowlerGlobal.prototype.pos=function(n,e,t){var o=this;return o.ctx&&o.ctx.listener?(e="number"!=typeof e?o._pos[1]:e,t="number"!=typeof t?o._pos[2]:t,"number"!=typeof n?o._pos:(o._pos=[n,e,t],o.ctx.listener.setPosition(o._pos[0],o._pos[1],o._pos[2]),o)):o},HowlerGlobal.prototype.orientation=function(n,e,t,o,r,i){if(!this.ctx||!this.ctx.listener)return this;var a=this._orientation;return e="number"!=typeof e?a[1]:e,t="number"!=typeof t?a[2]:t,o="number"!=typeof o?a[3]:o,r="number"!=typeof r?a[4]:r,i="number"!=typeof i?a[5]:i,"number"!=typeof n?a:(this._orientation=[n,e,t,o,r,i],this.ctx.listener.setOrientation(n,e,t,o,r,i),this)},Howl.prototype.init=function(n){return function(e){return this._orientation=e.orientation||[1,0,0],this._stereo=e.stereo||null,this._pos=e.pos||null,this._pannerAttr={coneInnerAngle:void 0!==e.coneInnerAngle?e.coneInnerAngle:360,coneOuterAngle:void 0!==e.coneOuterAngle?e.coneOuterAngle:360,coneOuterGain:void 0!==e.coneOuterGain?e.coneOuterGain:0,distanceModel:void 0!==e.distanceModel?e.distanceModel:"inverse",maxDistance:void 0!==e.maxDistance?e.maxDistance:1e4,panningModel:void 0!==e.panningModel?e.panningModel:"HRTF",refDistance:void 0!==e.refDistance?e.refDistance:1,rolloffFactor:void 0!==e.rolloffFactor?e.rolloffFactor:1},this._onstereo=e.onstereo?[{fn:e.onstereo}]:[],this._onpos=e.onpos?[{fn:e.onpos}]:[],this._onorientation=e.onorientation?[{fn:e.onorientation}]:[],n.call(this,e)}}(Howl.prototype.init),Howl.prototype.stereo=function(e,t){var o=this;if(!o._webAudio)return o;if("loaded"!==o._state)return o._queue.push({event:"stereo",action:function(){o.stereo(e,t)}}),o;var r=void 0===Howler.ctx.createStereoPanner?"spatial":"stereo";if(void 0===t){if("number"!=typeof e)return o._stereo;o._stereo=e,o._pos=[e,0,0]}for(var i=o._getSoundIds(t),a=0;a0?e.windowBits=-e.windowBits:e.gzip&&e.windowBits>0&&e.windowBits<16&&(e.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new f,this.strm.avail_out=0;var a=o.deflateInit2(this.strm,e.level,e.method,e.windowBits,e.memLevel,e.strategy);if(a!==b)throw new Error(d[a]);if(e.header&&o.deflateSetHeader(this.strm,e.header),e.dictionary){var n;if(n="string"==typeof e.dictionary?h.string2buf(e.dictionary):"[object ArrayBuffer]"===_.call(e.dictionary)?new Uint8Array(e.dictionary):e.dictionary,a=o.deflateSetDictionary(this.strm,n),a!==b)throw new Error(d[a]);this._dict_set=!0}}function n(t,e){var a=new i(e);if(a.push(t,!0),a.err)throw a.msg;return a.result}function r(t,e){return e=e||{},e.raw=!0,n(t,e)}function s(t,e){return e=e||{},e.gzip=!0,n(t,e)}var o=t("./zlib/deflate"),l=t("./utils/common"),h=t("./utils/strings"),d=t("./zlib/messages"),f=t("./zlib/zstream"),_=Object.prototype.toString,u=0,c=4,b=0,g=1,m=2,w=-1,p=0,v=8;i.prototype.push=function(t,e){var a,i,n=this.strm,r=this.options.chunkSize;if(this.ended)return!1;i=e===~~e?e:e===!0?c:u,"string"==typeof t?n.input=h.string2buf(t):"[object ArrayBuffer]"===_.call(t)?n.input=new Uint8Array(t):n.input=t,n.next_in=0,n.avail_in=n.input.length;do{if(0===n.avail_out&&(n.output=new l.Buf8(r),n.next_out=0,n.avail_out=r),a=o.deflate(n,i),a!==g&&a!==b)return this.onEnd(a),this.ended=!0,!1;0!==n.avail_out&&(0!==n.avail_in||i!==c&&i!==m)||("string"===this.options.to?this.onData(h.buf2binstring(l.shrinkBuf(n.output,n.next_out))):this.onData(l.shrinkBuf(n.output,n.next_out)))}while((n.avail_in>0||0===n.avail_out)&&a!==g);return i===c?(a=o.deflateEnd(this.strm),this.onEnd(a),this.ended=!0,a===b):i!==m||(this.onEnd(b),n.avail_out=0,!0)},i.prototype.onData=function(t){this.chunks.push(t)},i.prototype.onEnd=function(t){t===b&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=l.flattenChunks(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg},a.Deflate=i,a.deflate=n,a.deflateRaw=r,a.gzip=s},{"./utils/common":3,"./utils/strings":4,"./zlib/deflate":8,"./zlib/messages":13,"./zlib/zstream":15}],2:[function(t,e,a){"use strict";function i(t){if(!(this instanceof i))return new i(t);this.options=o.assign({chunkSize:16384,windowBits:0,to:""},t||{});var e=this.options;e.raw&&e.windowBits>=0&&e.windowBits<16&&(e.windowBits=-e.windowBits,0===e.windowBits&&(e.windowBits=-15)),!(e.windowBits>=0&&e.windowBits<16)||t&&t.windowBits||(e.windowBits+=32),e.windowBits>15&&e.windowBits<48&&0===(15&e.windowBits)&&(e.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new f,this.strm.avail_out=0;var a=s.inflateInit2(this.strm,e.windowBits);if(a!==h.Z_OK)throw new Error(d[a]);this.header=new _,s.inflateGetHeader(this.strm,this.header)}function n(t,e){var a=new i(e);if(a.push(t,!0),a.err)throw a.msg;return a.result}function r(t,e){return e=e||{},e.raw=!0,n(t,e)}var s=t("./zlib/inflate"),o=t("./utils/common"),l=t("./utils/strings"),h=t("./zlib/constants"),d=t("./zlib/messages"),f=t("./zlib/zstream"),_=t("./zlib/gzheader"),u=Object.prototype.toString;i.prototype.push=function(t,e){var a,i,n,r,d,f,_=this.strm,c=this.options.chunkSize,b=this.options.dictionary,g=!1;if(this.ended)return!1;i=e===~~e?e:e===!0?h.Z_FINISH:h.Z_NO_FLUSH,"string"==typeof t?_.input=l.binstring2buf(t):"[object ArrayBuffer]"===u.call(t)?_.input=new Uint8Array(t):_.input=t,_.next_in=0,_.avail_in=_.input.length;do{if(0===_.avail_out&&(_.output=new o.Buf8(c),_.next_out=0,_.avail_out=c),a=s.inflate(_,h.Z_NO_FLUSH),a===h.Z_NEED_DICT&&b&&(f="string"==typeof b?l.string2buf(b):"[object ArrayBuffer]"===u.call(b)?new Uint8Array(b):b,a=s.inflateSetDictionary(this.strm,f)),a===h.Z_BUF_ERROR&&g===!0&&(a=h.Z_OK,g=!1),a!==h.Z_STREAM_END&&a!==h.Z_OK)return this.onEnd(a),this.ended=!0,!1;_.next_out&&(0!==_.avail_out&&a!==h.Z_STREAM_END&&(0!==_.avail_in||i!==h.Z_FINISH&&i!==h.Z_SYNC_FLUSH)||("string"===this.options.to?(n=l.utf8border(_.output,_.next_out),r=_.next_out-n,d=l.buf2string(_.output,n),_.next_out=r,_.avail_out=c-r,r&&o.arraySet(_.output,_.output,n,r,0),this.onData(d)):this.onData(o.shrinkBuf(_.output,_.next_out)))),0===_.avail_in&&0===_.avail_out&&(g=!0)}while((_.avail_in>0||0===_.avail_out)&&a!==h.Z_STREAM_END);return a===h.Z_STREAM_END&&(i=h.Z_FINISH),i===h.Z_FINISH?(a=s.inflateEnd(this.strm),this.onEnd(a),this.ended=!0,a===h.Z_OK):i!==h.Z_SYNC_FLUSH||(this.onEnd(h.Z_OK),_.avail_out=0,!0)},i.prototype.onData=function(t){this.chunks.push(t)},i.prototype.onEnd=function(t){t===h.Z_OK&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=o.flattenChunks(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg},a.Inflate=i,a.inflate=n,a.inflateRaw=r,a.ungzip=n},{"./utils/common":3,"./utils/strings":4,"./zlib/constants":6,"./zlib/gzheader":9,"./zlib/inflate":11,"./zlib/messages":13,"./zlib/zstream":15}],3:[function(t,e,a){"use strict";var i="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Int32Array;a.assign=function(t){for(var e=Array.prototype.slice.call(arguments,1);e.length;){var a=e.shift();if(a){if("object"!=typeof a)throw new TypeError(a+"must be non-object");for(var i in a)a.hasOwnProperty(i)&&(t[i]=a[i])}}return t},a.shrinkBuf=function(t,e){return t.length===e?t:t.subarray?t.subarray(0,e):(t.length=e,t)};var n={arraySet:function(t,e,a,i,n){if(e.subarray&&t.subarray)return void t.set(e.subarray(a,a+i),n);for(var r=0;r=252?6:l>=248?5:l>=240?4:l>=224?3:l>=192?2:1;o[254]=o[254]=1,a.string2buf=function(t){var e,a,i,r,s,o=t.length,l=0;for(r=0;r>>6,e[s++]=128|63&a):a<65536?(e[s++]=224|a>>>12,e[s++]=128|a>>>6&63,e[s++]=128|63&a):(e[s++]=240|a>>>18,e[s++]=128|a>>>12&63,e[s++]=128|a>>>6&63,e[s++]=128|63&a);return e},a.buf2binstring=function(t){return i(t,t.length)},a.binstring2buf=function(t){for(var e=new n.Buf8(t.length),a=0,i=e.length;a4)h[n++]=65533,a+=s-1;else{for(r&=2===s?31:3===s?15:7;s>1&&a1?h[n++]=65533:r<65536?h[n++]=r:(r-=65536,h[n++]=55296|r>>10&1023,h[n++]=56320|1023&r)}return i(h,n)},a.utf8border=function(t,e){var a;for(e=e||t.length,e>t.length&&(e=t.length),a=e-1;a>=0&&128===(192&t[a]);)a--;return a<0?e:0===a?e:a+o[t[a]]>e?a:e}},{"./common":3}],5:[function(t,e,a){"use strict";function i(t,e,a,i){for(var n=65535&t|0,r=t>>>16&65535|0,s=0;0!==a;){s=a>2e3?2e3:a,a-=s;do n=n+e[i++]|0,r=r+n|0;while(--s);n%=65521,r%=65521}return n|r<<16|0}e.exports=i},{}],6:[function(t,e,a){"use strict";e.exports={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8}},{}],7:[function(t,e,a){"use strict";function i(){for(var t,e=[],a=0;a<256;a++){t=a;for(var i=0;i<8;i++)t=1&t?3988292384^t>>>1:t>>>1;e[a]=t}return e}function n(t,e,a,i){var n=r,s=i+a;t^=-1;for(var o=i;o>>8^n[255&(t^e[o])];return t^-1}var r=i();e.exports=n},{}],8:[function(t,e,a){"use strict";function i(t,e){return t.msg=D[e],e}function n(t){return(t<<1)-(t>4?9:0)}function r(t){for(var e=t.length;--e>=0;)t[e]=0}function s(t){var e=t.state,a=e.pending;a>t.avail_out&&(a=t.avail_out),0!==a&&(R.arraySet(t.output,e.pending_buf,e.pending_out,a,t.next_out),t.next_out+=a,e.pending_out+=a,t.total_out+=a,t.avail_out-=a,e.pending-=a,0===e.pending&&(e.pending_out=0))}function o(t,e){C._tr_flush_block(t,t.block_start>=0?t.block_start:-1,t.strstart-t.block_start,e),t.block_start=t.strstart,s(t.strm)}function l(t,e){t.pending_buf[t.pending++]=e}function h(t,e){t.pending_buf[t.pending++]=e>>>8&255,t.pending_buf[t.pending++]=255&e}function d(t,e,a,i){var n=t.avail_in;return n>i&&(n=i),0===n?0:(t.avail_in-=n,R.arraySet(e,t.input,t.next_in,n,a),1===t.state.wrap?t.adler=N(t.adler,e,n,a):2===t.state.wrap&&(t.adler=O(t.adler,e,n,a)),t.next_in+=n,t.total_in+=n,n)}function f(t,e){var a,i,n=t.max_chain_length,r=t.strstart,s=t.prev_length,o=t.nice_match,l=t.strstart>t.w_size-ft?t.strstart-(t.w_size-ft):0,h=t.window,d=t.w_mask,f=t.prev,_=t.strstart+dt,u=h[r+s-1],c=h[r+s];t.prev_length>=t.good_match&&(n>>=2),o>t.lookahead&&(o=t.lookahead);do if(a=e,h[a+s]===c&&h[a+s-1]===u&&h[a]===h[r]&&h[++a]===h[r+1]){r+=2,a++;do;while(h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&r<_);if(i=dt-(_-r),r=_-dt,i>s){if(t.match_start=e,s=i,i>=o)break;u=h[r+s-1],c=h[r+s]}}while((e=f[e&d])>l&&0!==--n);return s<=t.lookahead?s:t.lookahead}function _(t){var e,a,i,n,r,s=t.w_size;do{if(n=t.window_size-t.lookahead-t.strstart,t.strstart>=s+(s-ft)){R.arraySet(t.window,t.window,s,s,0),t.match_start-=s,t.strstart-=s,t.block_start-=s,a=t.hash_size,e=a;do i=t.head[--e],t.head[e]=i>=s?i-s:0;while(--a);a=s,e=a;do i=t.prev[--e],t.prev[e]=i>=s?i-s:0;while(--a);n+=s}if(0===t.strm.avail_in)break;if(a=d(t.strm,t.window,t.strstart+t.lookahead,n),t.lookahead+=a,t.lookahead+t.insert>=ht)for(r=t.strstart-t.insert,t.ins_h=t.window[r],t.ins_h=(t.ins_h<t.pending_buf_size-5&&(a=t.pending_buf_size-5);;){if(t.lookahead<=1){if(_(t),0===t.lookahead&&e===I)return vt;if(0===t.lookahead)break}t.strstart+=t.lookahead,t.lookahead=0;var i=t.block_start+a;if((0===t.strstart||t.strstart>=i)&&(t.lookahead=t.strstart-i,t.strstart=i,o(t,!1),0===t.strm.avail_out))return vt;if(t.strstart-t.block_start>=t.w_size-ft&&(o(t,!1),0===t.strm.avail_out))return vt}return t.insert=0,e===F?(o(t,!0),0===t.strm.avail_out?yt:xt):t.strstart>t.block_start&&(o(t,!1),0===t.strm.avail_out)?vt:vt}function c(t,e){for(var a,i;;){if(t.lookahead=ht&&(t.ins_h=(t.ins_h<=ht)if(i=C._tr_tally(t,t.strstart-t.match_start,t.match_length-ht),t.lookahead-=t.match_length,t.match_length<=t.max_lazy_match&&t.lookahead>=ht){t.match_length--;do t.strstart++,t.ins_h=(t.ins_h<=ht&&(t.ins_h=(t.ins_h<4096)&&(t.match_length=ht-1)),t.prev_length>=ht&&t.match_length<=t.prev_length){n=t.strstart+t.lookahead-ht,i=C._tr_tally(t,t.strstart-1-t.prev_match,t.prev_length-ht),t.lookahead-=t.prev_length-1,t.prev_length-=2;do++t.strstart<=n&&(t.ins_h=(t.ins_h<=ht&&t.strstart>0&&(n=t.strstart-1,i=s[n],i===s[++n]&&i===s[++n]&&i===s[++n])){r=t.strstart+dt;do;while(i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&nt.lookahead&&(t.match_length=t.lookahead)}if(t.match_length>=ht?(a=C._tr_tally(t,1,t.match_length-ht),t.lookahead-=t.match_length,t.strstart+=t.match_length,t.match_length=0):(a=C._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++),a&&(o(t,!1),0===t.strm.avail_out))return vt}return t.insert=0,e===F?(o(t,!0),0===t.strm.avail_out?yt:xt):t.last_lit&&(o(t,!1),0===t.strm.avail_out)?vt:kt}function m(t,e){for(var a;;){if(0===t.lookahead&&(_(t),0===t.lookahead)){if(e===I)return vt;break}if(t.match_length=0,a=C._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++,a&&(o(t,!1),0===t.strm.avail_out))return vt}return t.insert=0,e===F?(o(t,!0),0===t.strm.avail_out?yt:xt):t.last_lit&&(o(t,!1),0===t.strm.avail_out)?vt:kt}function w(t,e,a,i,n){this.good_length=t,this.max_lazy=e,this.nice_length=a,this.max_chain=i,this.func=n}function p(t){t.window_size=2*t.w_size,r(t.head),t.max_lazy_match=Z[t.level].max_lazy,t.good_match=Z[t.level].good_length,t.nice_match=Z[t.level].nice_length,t.max_chain_length=Z[t.level].max_chain,t.strstart=0,t.block_start=0,t.lookahead=0,t.insert=0,t.match_length=t.prev_length=ht-1,t.match_available=0,t.ins_h=0}function v(){this.strm=null,this.status=0,this.pending_buf=null,this.pending_buf_size=0,this.pending_out=0,this.pending=0,this.wrap=0,this.gzhead=null,this.gzindex=0,this.method=V,this.last_flush=-1,this.w_size=0,this.w_bits=0,this.w_mask=0,this.window=null,this.window_size=0,this.prev=null,this.head=null,this.ins_h=0,this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new R.Buf16(2*ot),this.dyn_dtree=new R.Buf16(2*(2*rt+1)),this.bl_tree=new R.Buf16(2*(2*st+1)),r(this.dyn_ltree),r(this.dyn_dtree),r(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new R.Buf16(lt+1),this.heap=new R.Buf16(2*nt+1),r(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new R.Buf16(2*nt+1),r(this.depth),this.l_buf=0,this.lit_bufsize=0,this.last_lit=0,this.d_buf=0,this.opt_len=0,this.static_len=0,this.matches=0,this.insert=0,this.bi_buf=0,this.bi_valid=0}function k(t){var e;return t&&t.state?(t.total_in=t.total_out=0,t.data_type=Q,e=t.state,e.pending=0,e.pending_out=0,e.wrap<0&&(e.wrap=-e.wrap),e.status=e.wrap?ut:wt,t.adler=2===e.wrap?0:1,e.last_flush=I,C._tr_init(e),H):i(t,K)}function y(t){var e=k(t);return e===H&&p(t.state),e}function x(t,e){return t&&t.state?2!==t.state.wrap?K:(t.state.gzhead=e,H):K}function z(t,e,a,n,r,s){if(!t)return K;var o=1;if(e===Y&&(e=6),n<0?(o=0,n=-n):n>15&&(o=2,n-=16),r<1||r>$||a!==V||n<8||n>15||e<0||e>9||s<0||s>W)return i(t,K);8===n&&(n=9);var l=new v;return t.state=l,l.strm=t,l.wrap=o,l.gzhead=null,l.w_bits=n,l.w_size=1<L||e<0)return t?i(t,K):K;if(o=t.state,!t.output||!t.input&&0!==t.avail_in||o.status===pt&&e!==F)return i(t,0===t.avail_out?P:K);if(o.strm=t,a=o.last_flush,o.last_flush=e,o.status===ut)if(2===o.wrap)t.adler=0,l(o,31),l(o,139),l(o,8),o.gzhead?(l(o,(o.gzhead.text?1:0)+(o.gzhead.hcrc?2:0)+(o.gzhead.extra?4:0)+(o.gzhead.name?8:0)+(o.gzhead.comment?16:0)),l(o,255&o.gzhead.time),l(o,o.gzhead.time>>8&255),l(o,o.gzhead.time>>16&255),l(o,o.gzhead.time>>24&255),l(o,9===o.level?2:o.strategy>=G||o.level<2?4:0),l(o,255&o.gzhead.os),o.gzhead.extra&&o.gzhead.extra.length&&(l(o,255&o.gzhead.extra.length),l(o,o.gzhead.extra.length>>8&255)),o.gzhead.hcrc&&(t.adler=O(t.adler,o.pending_buf,o.pending,0)),o.gzindex=0,o.status=ct):(l(o,0),l(o,0),l(o,0),l(o,0),l(o,0),l(o,9===o.level?2:o.strategy>=G||o.level<2?4:0),l(o,zt),o.status=wt);else{var _=V+(o.w_bits-8<<4)<<8,u=-1;u=o.strategy>=G||o.level<2?0:o.level<6?1:6===o.level?2:3,_|=u<<6,0!==o.strstart&&(_|=_t),_+=31-_%31,o.status=wt,h(o,_),0!==o.strstart&&(h(o,t.adler>>>16),h(o,65535&t.adler)),t.adler=1}if(o.status===ct)if(o.gzhead.extra){for(d=o.pending;o.gzindex<(65535&o.gzhead.extra.length)&&(o.pending!==o.pending_buf_size||(o.gzhead.hcrc&&o.pending>d&&(t.adler=O(t.adler,o.pending_buf,o.pending-d,d)),s(t),d=o.pending,o.pending!==o.pending_buf_size));)l(o,255&o.gzhead.extra[o.gzindex]),o.gzindex++;o.gzhead.hcrc&&o.pending>d&&(t.adler=O(t.adler,o.pending_buf,o.pending-d,d)),o.gzindex===o.gzhead.extra.length&&(o.gzindex=0,o.status=bt)}else o.status=bt;if(o.status===bt)if(o.gzhead.name){d=o.pending;do{if(o.pending===o.pending_buf_size&&(o.gzhead.hcrc&&o.pending>d&&(t.adler=O(t.adler,o.pending_buf,o.pending-d,d)),s(t),d=o.pending,o.pending===o.pending_buf_size)){f=1;break}f=o.gzindexd&&(t.adler=O(t.adler,o.pending_buf,o.pending-d,d)),0===f&&(o.gzindex=0,o.status=gt)}else o.status=gt;if(o.status===gt)if(o.gzhead.comment){d=o.pending;do{if(o.pending===o.pending_buf_size&&(o.gzhead.hcrc&&o.pending>d&&(t.adler=O(t.adler,o.pending_buf,o.pending-d,d)),s(t),d=o.pending,o.pending===o.pending_buf_size)){f=1;break}f=o.gzindexd&&(t.adler=O(t.adler,o.pending_buf,o.pending-d,d)),0===f&&(o.status=mt)}else o.status=mt;if(o.status===mt&&(o.gzhead.hcrc?(o.pending+2>o.pending_buf_size&&s(t),o.pending+2<=o.pending_buf_size&&(l(o,255&t.adler),l(o,t.adler>>8&255),t.adler=0,o.status=wt)):o.status=wt),0!==o.pending){if(s(t),0===t.avail_out)return o.last_flush=-1,H}else if(0===t.avail_in&&n(e)<=n(a)&&e!==F)return i(t,P);if(o.status===pt&&0!==t.avail_in)return i(t,P);if(0!==t.avail_in||0!==o.lookahead||e!==I&&o.status!==pt){var c=o.strategy===G?m(o,e):o.strategy===X?g(o,e):Z[o.level].func(o,e);if(c!==yt&&c!==xt||(o.status=pt),c===vt||c===yt)return 0===t.avail_out&&(o.last_flush=-1),H;if(c===kt&&(e===U?C._tr_align(o):e!==L&&(C._tr_stored_block(o,0,0,!1),e===T&&(r(o.head),0===o.lookahead&&(o.strstart=0,o.block_start=0,o.insert=0))),s(t),0===t.avail_out))return o.last_flush=-1,H}return e!==F?H:o.wrap<=0?j:(2===o.wrap?(l(o,255&t.adler),l(o,t.adler>>8&255),l(o,t.adler>>16&255),l(o,t.adler>>24&255),l(o,255&t.total_in),l(o,t.total_in>>8&255),l(o,t.total_in>>16&255),l(o,t.total_in>>24&255)):(h(o,t.adler>>>16),h(o,65535&t.adler)),s(t),o.wrap>0&&(o.wrap=-o.wrap),0!==o.pending?H:j)}function E(t){var e;return t&&t.state?(e=t.state.status,e!==ut&&e!==ct&&e!==bt&&e!==gt&&e!==mt&&e!==wt&&e!==pt?i(t,K):(t.state=null,e===wt?i(t,M):H)):K}function A(t,e){var a,i,n,s,o,l,h,d,f=e.length;if(!t||!t.state)return K;if(a=t.state,s=a.wrap,2===s||1===s&&a.status!==ut||a.lookahead)return K;for(1===s&&(t.adler=N(t.adler,e,f,0)),a.wrap=0,f>=a.w_size&&(0===s&&(r(a.head),a.strstart=0,a.block_start=0,a.insert=0),d=new R.Buf8(a.w_size),R.arraySet(d,e,f-a.w_size,a.w_size,0),e=d,f=a.w_size),o=t.avail_in,l=t.next_in,h=t.input,t.avail_in=f,t.next_in=0,t.input=e,_(a);a.lookahead>=ht;){i=a.strstart,n=a.lookahead-(ht-1);do a.ins_h=(a.ins_h<>>24,b>>>=y,g-=y,y=k>>>16&255,0===y)A[o++]=65535&k;else{if(!(16&y)){if(0===(64&y)){k=m[(65535&k)+(b&(1<>>=y,g-=y),g<15&&(b+=E[r++]<>>24,b>>>=y,g-=y,y=k>>>16&255,!(16&y)){if(0===(64&y)){k=w[(65535&k)+(b&(1<d){t.msg="invalid distance too far back",a.mode=i;break t}if(b>>>=y,g-=y,y=o-l,z>y){if(y=z-y,y>_&&a.sane){t.msg="invalid distance too far back",a.mode=i;break t}if(B=0,S=c,0===u){if(B+=f-y,y2;)A[o++]=S[B++],A[o++]=S[B++],A[o++]=S[B++],x-=3;x&&(A[o++]=S[B++],x>1&&(A[o++]=S[B++]))}else{B=o-z;do A[o++]=A[B++],A[o++]=A[B++],A[o++]=A[B++],x-=3;while(x>2);x&&(A[o++]=A[B++],x>1&&(A[o++]=A[B++]))}break}}break}}while(r>3,r-=x,g-=x<<3,b&=(1<>>24&255)+(t>>>8&65280)+((65280&t)<<8)+((255&t)<<24)}function n(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new w.Buf16(320),this.work=new w.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function r(t){var e;return t&&t.state?(e=t.state,t.total_in=t.total_out=e.total=0,t.msg="",e.wrap&&(t.adler=1&e.wrap),e.mode=T,e.last=0,e.havedict=0,e.dmax=32768,e.head=null,e.hold=0,e.bits=0,e.lencode=e.lendyn=new w.Buf32(bt),e.distcode=e.distdyn=new w.Buf32(gt),e.sane=1,e.back=-1,Z):N}function s(t){var e;return t&&t.state?(e=t.state,e.wsize=0,e.whave=0,e.wnext=0,r(t)):N}function o(t,e){var a,i;return t&&t.state?(i=t.state,e<0?(a=0,e=-e):(a=(e>>4)+1,e<48&&(e&=15)),e&&(e<8||e>15)?N:(null!==i.window&&i.wbits!==e&&(i.window=null),i.wrap=a,i.wbits=e,s(t))):N}function l(t,e){var a,i;return t?(i=new n,t.state=i,i.window=null,a=o(t,e),a!==Z&&(t.state=null),a):N}function h(t){return l(t,wt)}function d(t){if(pt){var e;for(g=new w.Buf32(512),m=new w.Buf32(32),e=0;e<144;)t.lens[e++]=8;for(;e<256;)t.lens[e++]=9;for(;e<280;)t.lens[e++]=7;for(;e<288;)t.lens[e++]=8;for(y(z,t.lens,0,288,g,0,t.work,{bits:9}),e=0;e<32;)t.lens[e++]=5;y(B,t.lens,0,32,m,0,t.work,{bits:5}),pt=!1}t.lencode=g,t.lenbits=9,t.distcode=m,t.distbits=5}function f(t,e,a,i){var n,r=t.state;return null===r.window&&(r.wsize=1<=r.wsize?(w.arraySet(r.window,e,a-r.wsize,r.wsize,0),r.wnext=0,r.whave=r.wsize):(n=r.wsize-r.wnext,n>i&&(n=i),w.arraySet(r.window,e,a-i,n,r.wnext),i-=n,i?(w.arraySet(r.window,e,a-i,i,0),r.wnext=i,r.whave=r.wsize):(r.wnext+=n,r.wnext===r.wsize&&(r.wnext=0),r.whave>>8&255,a.check=v(a.check,Et,2,0),_=0,u=0,a.mode=F;break}if(a.flags=0,a.head&&(a.head.done=!1),!(1&a.wrap)||(((255&_)<<8)+(_>>8))%31){t.msg="incorrect header check",a.mode=_t;break}if((15&_)!==U){t.msg="unknown compression method",a.mode=_t;break}if(_>>>=4,u-=4,yt=(15&_)+8,0===a.wbits)a.wbits=yt;else if(yt>a.wbits){t.msg="invalid window size",a.mode=_t;break}a.dmax=1<>8&1),512&a.flags&&(Et[0]=255&_,Et[1]=_>>>8&255,a.check=v(a.check,Et,2,0)),_=0,u=0,a.mode=L;case L:for(;u<32;){if(0===l)break t;l--,_+=n[s++]<>>8&255,Et[2]=_>>>16&255,Et[3]=_>>>24&255,a.check=v(a.check,Et,4,0)),_=0,u=0,a.mode=H;case H:for(;u<16;){if(0===l)break t;l--,_+=n[s++]<>8),512&a.flags&&(Et[0]=255&_,Et[1]=_>>>8&255,a.check=v(a.check,Et,2,0)),_=0,u=0,a.mode=j;case j:if(1024&a.flags){for(;u<16;){if(0===l)break t;l--,_+=n[s++]<>>8&255,a.check=v(a.check,Et,2,0)),_=0,u=0}else a.head&&(a.head.extra=null);a.mode=K;case K:if(1024&a.flags&&(g=a.length,g>l&&(g=l),g&&(a.head&&(yt=a.head.extra_len-a.length,a.head.extra||(a.head.extra=new Array(a.head.extra_len)),w.arraySet(a.head.extra,n,s,g,yt)),512&a.flags&&(a.check=v(a.check,n,g,s)),l-=g,s+=g,a.length-=g),a.length))break t;a.length=0,a.mode=M;case M:if(2048&a.flags){if(0===l)break t;g=0;do yt=n[s+g++],a.head&&yt&&a.length<65536&&(a.head.name+=String.fromCharCode(yt));while(yt&&g>9&1,a.head.done=!0),t.adler=a.check=0,a.mode=X;break;case q:for(;u<32;){if(0===l)break t;l--,_+=n[s++]<>>=7&u,u-=7&u,a.mode=ht;break}for(;u<3;){if(0===l)break t;l--,_+=n[s++]<>>=1,u-=1,3&_){case 0:a.mode=J;break;case 1:if(d(a),a.mode=at,e===A){_>>>=2,u-=2;break t}break;case 2:a.mode=$;break;case 3:t.msg="invalid block type",a.mode=_t}_>>>=2,u-=2;break;case J:for(_>>>=7&u,u-=7&u;u<32;){if(0===l)break t;l--,_+=n[s++]<>>16^65535)){t.msg="invalid stored block lengths",a.mode=_t;break}if(a.length=65535&_,_=0,u=0,a.mode=Q,e===A)break t;case Q:a.mode=V;case V:if(g=a.length){if(g>l&&(g=l),g>h&&(g=h),0===g)break t;w.arraySet(r,n,s,g,o),l-=g,s+=g,h-=g,o+=g,a.length-=g;break}a.mode=X;break;case $:for(;u<14;){if(0===l)break t; 3 | l--,_+=n[s++]<>>=5,u-=5,a.ndist=(31&_)+1,_>>>=5,u-=5,a.ncode=(15&_)+4,_>>>=4,u-=4,a.nlen>286||a.ndist>30){t.msg="too many length or distance symbols",a.mode=_t;break}a.have=0,a.mode=tt;case tt:for(;a.have>>=3,u-=3}for(;a.have<19;)a.lens[At[a.have++]]=0;if(a.lencode=a.lendyn,a.lenbits=7,zt={bits:a.lenbits},xt=y(x,a.lens,0,19,a.lencode,0,a.work,zt),a.lenbits=zt.bits,xt){t.msg="invalid code lengths set",a.mode=_t;break}a.have=0,a.mode=et;case et:for(;a.have>>24,mt=St>>>16&255,wt=65535&St,!(gt<=u);){if(0===l)break t;l--,_+=n[s++]<>>=gt,u-=gt,a.lens[a.have++]=wt;else{if(16===wt){for(Bt=gt+2;u>>=gt,u-=gt,0===a.have){t.msg="invalid bit length repeat",a.mode=_t;break}yt=a.lens[a.have-1],g=3+(3&_),_>>>=2,u-=2}else if(17===wt){for(Bt=gt+3;u>>=gt,u-=gt,yt=0,g=3+(7&_),_>>>=3,u-=3}else{for(Bt=gt+7;u>>=gt,u-=gt,yt=0,g=11+(127&_),_>>>=7,u-=7}if(a.have+g>a.nlen+a.ndist){t.msg="invalid bit length repeat",a.mode=_t;break}for(;g--;)a.lens[a.have++]=yt}}if(a.mode===_t)break;if(0===a.lens[256]){t.msg="invalid code -- missing end-of-block",a.mode=_t;break}if(a.lenbits=9,zt={bits:a.lenbits},xt=y(z,a.lens,0,a.nlen,a.lencode,0,a.work,zt),a.lenbits=zt.bits,xt){t.msg="invalid literal/lengths set",a.mode=_t;break}if(a.distbits=6,a.distcode=a.distdyn,zt={bits:a.distbits},xt=y(B,a.lens,a.nlen,a.ndist,a.distcode,0,a.work,zt),a.distbits=zt.bits,xt){t.msg="invalid distances set",a.mode=_t;break}if(a.mode=at,e===A)break t;case at:a.mode=it;case it:if(l>=6&&h>=258){t.next_out=o,t.avail_out=h,t.next_in=s,t.avail_in=l,a.hold=_,a.bits=u,k(t,b),o=t.next_out,r=t.output,h=t.avail_out,s=t.next_in,n=t.input,l=t.avail_in,_=a.hold,u=a.bits,a.mode===X&&(a.back=-1);break}for(a.back=0;St=a.lencode[_&(1<>>24,mt=St>>>16&255,wt=65535&St,!(gt<=u);){if(0===l)break t;l--,_+=n[s++]<>pt)],gt=St>>>24,mt=St>>>16&255,wt=65535&St,!(pt+gt<=u);){if(0===l)break t;l--,_+=n[s++]<>>=pt,u-=pt,a.back+=pt}if(_>>>=gt,u-=gt,a.back+=gt,a.length=wt,0===mt){a.mode=lt;break}if(32&mt){a.back=-1,a.mode=X;break}if(64&mt){t.msg="invalid literal/length code",a.mode=_t;break}a.extra=15&mt,a.mode=nt;case nt:if(a.extra){for(Bt=a.extra;u>>=a.extra,u-=a.extra,a.back+=a.extra}a.was=a.length,a.mode=rt;case rt:for(;St=a.distcode[_&(1<>>24,mt=St>>>16&255,wt=65535&St,!(gt<=u);){if(0===l)break t;l--,_+=n[s++]<>pt)],gt=St>>>24,mt=St>>>16&255,wt=65535&St,!(pt+gt<=u);){if(0===l)break t;l--,_+=n[s++]<>>=pt,u-=pt,a.back+=pt}if(_>>>=gt,u-=gt,a.back+=gt,64&mt){t.msg="invalid distance code",a.mode=_t;break}a.offset=wt,a.extra=15&mt,a.mode=st;case st:if(a.extra){for(Bt=a.extra;u>>=a.extra,u-=a.extra,a.back+=a.extra}if(a.offset>a.dmax){t.msg="invalid distance too far back",a.mode=_t;break}a.mode=ot;case ot:if(0===h)break t;if(g=b-h,a.offset>g){if(g=a.offset-g,g>a.whave&&a.sane){t.msg="invalid distance too far back",a.mode=_t;break}g>a.wnext?(g-=a.wnext,m=a.wsize-g):m=a.wnext-g,g>a.length&&(g=a.length),bt=a.window}else bt=r,m=o-a.offset,g=a.length;g>h&&(g=h),h-=g,a.length-=g;do r[o++]=bt[m++];while(--g);0===a.length&&(a.mode=it);break;case lt:if(0===h)break t;r[o++]=a.length,h--,a.mode=it;break;case ht:if(a.wrap){for(;u<32;){if(0===l)break t;l--,_|=n[s++]<=1&&0===j[N];N--);if(O>N&&(O=N),0===N)return b[g++]=20971520,b[g++]=20971520,w.bits=1,0;for(C=1;C0&&(t===o||1!==N))return-1;for(K[1]=0,Z=1;Zr||t===h&&T>s)return 1;for(var Y=0;;){Y++,B=Z-I,m[R]z?(S=M[P+m[R]],E=L[H+m[R]]):(S=96,E=0),p=1<>I)+v]=B<<24|S<<16|E|0;while(0!==v);for(p=1<>=1;if(0!==p?(F&=p-1,F+=p):F=0,R++,0===--j[Z]){if(Z===N)break;Z=e[a+m[R]]}if(Z>O&&(F&y)!==k){for(0===I&&(I=O),x+=C,D=Z-I,U=1<r||t===h&&T>s)return 1;k=F&y,b[k]=O<<24|D<<16|x-g|0}}return 0!==F&&(b[x+F]=Z-I<<24|64<<16|0),w.bits=O,0}},{"../utils/common":3}],13:[function(t,e,a){"use strict";e.exports={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"}},{}],14:[function(t,e,a){"use strict";function i(t){for(var e=t.length;--e>=0;)t[e]=0}function n(t,e,a,i,n){this.static_tree=t,this.extra_bits=e,this.extra_base=a,this.elems=i,this.max_length=n,this.has_stree=t&&t.length}function r(t,e){this.dyn_tree=t,this.max_code=0,this.stat_desc=e}function s(t){return t<256?lt[t]:lt[256+(t>>>7)]}function o(t,e){t.pending_buf[t.pending++]=255&e,t.pending_buf[t.pending++]=e>>>8&255}function l(t,e,a){t.bi_valid>W-a?(t.bi_buf|=e<>W-t.bi_valid,t.bi_valid+=a-W):(t.bi_buf|=e<>>=1,a<<=1;while(--e>0);return a>>>1}function f(t){16===t.bi_valid?(o(t,t.bi_buf),t.bi_buf=0,t.bi_valid=0):t.bi_valid>=8&&(t.pending_buf[t.pending++]=255&t.bi_buf,t.bi_buf>>=8,t.bi_valid-=8)}function _(t,e){var a,i,n,r,s,o,l=e.dyn_tree,h=e.max_code,d=e.stat_desc.static_tree,f=e.stat_desc.has_stree,_=e.stat_desc.extra_bits,u=e.stat_desc.extra_base,c=e.stat_desc.max_length,b=0;for(r=0;r<=X;r++)t.bl_count[r]=0;for(l[2*t.heap[t.heap_max]+1]=0,a=t.heap_max+1;ac&&(r=c,b++),l[2*i+1]=r,i>h||(t.bl_count[r]++,s=0,i>=u&&(s=_[i-u]),o=l[2*i],t.opt_len+=o*(r+s),f&&(t.static_len+=o*(d[2*i+1]+s)));if(0!==b){do{for(r=c-1;0===t.bl_count[r];)r--;t.bl_count[r]--,t.bl_count[r+1]+=2,t.bl_count[c]--,b-=2}while(b>0);for(r=c;0!==r;r--)for(i=t.bl_count[r];0!==i;)n=t.heap[--a],n>h||(l[2*n+1]!==r&&(t.opt_len+=(r-l[2*n+1])*l[2*n],l[2*n+1]=r),i--)}}function u(t,e,a){var i,n,r=new Array(X+1),s=0;for(i=1;i<=X;i++)r[i]=s=s+a[i-1]<<1;for(n=0;n<=e;n++){var o=t[2*n+1];0!==o&&(t[2*n]=d(r[o]++,o))}}function c(){var t,e,a,i,r,s=new Array(X+1);for(a=0,i=0;i>=7;i8?o(t,t.bi_buf):t.bi_valid>0&&(t.pending_buf[t.pending++]=t.bi_buf),t.bi_buf=0,t.bi_valid=0}function m(t,e,a,i){g(t),i&&(o(t,a),o(t,~a)),N.arraySet(t.pending_buf,t.window,e,a,t.pending),t.pending+=a}function w(t,e,a,i){var n=2*e,r=2*a;return t[n]>1;a>=1;a--)p(t,r,a);n=l;do a=t.heap[1],t.heap[1]=t.heap[t.heap_len--],p(t,r,1),i=t.heap[1],t.heap[--t.heap_max]=a,t.heap[--t.heap_max]=i,r[2*n]=r[2*a]+r[2*i],t.depth[n]=(t.depth[a]>=t.depth[i]?t.depth[a]:t.depth[i])+1,r[2*a+1]=r[2*i+1]=n,t.heap[1]=n++,p(t,r,1);while(t.heap_len>=2);t.heap[--t.heap_max]=t.heap[1],_(t,e),u(r,h,t.bl_count)}function y(t,e,a){var i,n,r=-1,s=e[1],o=0,l=7,h=4;for(0===s&&(l=138,h=3),e[2*(a+1)+1]=65535,i=0;i<=a;i++)n=s,s=e[2*(i+1)+1],++o=3&&0===t.bl_tree[2*nt[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e}function B(t,e,a,i){var n;for(l(t,e-257,5),l(t,a-1,5),l(t,i-4,4),n=0;n>>=1)if(1&a&&0!==t.dyn_ltree[2*e])return D;if(0!==t.dyn_ltree[18]||0!==t.dyn_ltree[20]||0!==t.dyn_ltree[26])return I;for(e=32;e0?(t.strm.data_type===U&&(t.strm.data_type=S(t)),k(t,t.l_desc),k(t,t.d_desc),s=z(t),n=t.opt_len+3+7>>>3,r=t.static_len+3+7>>>3,r<=n&&(n=r)):n=r=a+5,a+4<=n&&e!==-1?A(t,e,a,i):t.strategy===O||r===n?(l(t,(F<<1)+(i?1:0),3),v(t,st,ot)):(l(t,(L<<1)+(i?1:0),3),B(t,t.l_desc.max_code+1,t.d_desc.max_code+1,s+1),v(t,t.dyn_ltree,t.dyn_dtree)),b(t),i&&g(t)}function C(t,e,a){return t.pending_buf[t.d_buf+2*t.last_lit]=e>>>8&255,t.pending_buf[t.d_buf+2*t.last_lit+1]=255&e,t.pending_buf[t.l_buf+t.last_lit]=255&a,t.last_lit++,0===e?t.dyn_ltree[2*a]++:(t.matches++,e--,t.dyn_ltree[2*(ht[a]+M+1)]++,t.dyn_dtree[2*s(e)]++),t.last_lit===t.lit_bufsize-1}var N=t("../utils/common"),O=4,D=0,I=1,U=2,T=0,F=1,L=2,H=3,j=258,K=29,M=256,P=M+1+K,Y=30,q=19,G=2*P+1,X=15,W=16,J=7,Q=256,V=16,$=17,tt=18,et=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],at=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],it=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],nt=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],rt=512,st=new Array(2*(P+2));i(st);var ot=new Array(2*Y);i(ot);var lt=new Array(rt);i(lt);var ht=new Array(j-H+1);i(ht);var dt=new Array(K);i(dt);var ft=new Array(Y);i(ft);var _t,ut,ct,bt=!1;a._tr_init=E,a._tr_stored_block=A,a._tr_flush_block=R,a._tr_tally=C,a._tr_align=Z},{"../utils/common":3}],15:[function(t,e,a){"use strict";function i(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}e.exports=i},{}],"/":[function(t,e,a){"use strict";var i=t("./lib/utils/common").assign,n=t("./lib/deflate"),r=t("./lib/inflate"),s=t("./lib/zlib/constants"),o={};i(o,n,r,s),e.exports=o},{"./lib/deflate":1,"./lib/inflate":2,"./lib/utils/common":3,"./lib/zlib/constants":6}]},{},[])("/")}); 4 | -------------------------------------------------------------------------------- /bin/html5/bin/manifest/default.json: -------------------------------------------------------------------------------- 1 | {"name":null,"assets":"ah","version":2,"libraryArgs":[],"libraryType":null} -------------------------------------------------------------------------------- /bin/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Delaunay JS example 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /build.hxml: -------------------------------------------------------------------------------- 1 | -cp src 2 | -cp com 3 | -main DemoJs 4 | -js bin/js/js_demo.js 5 | # -D source-map-content 6 | # -debug 7 | -dce full 8 | # -xml bin/delaunay.xml 9 | -------------------------------------------------------------------------------- /com/nodename/delaunay/ArrayHelper.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.delaunay; 2 | 3 | 4 | /** 5 | * ... 6 | * @author sledorze 7 | * @author azrafe7 8 | */ 9 | class ArrayHelper { 10 | 11 | /** 12 | * Empties an array of its' contents 13 | * @param array filled array 14 | */ 15 | public static inline function clear(array:Array) 16 | { 17 | #if (cpp || php) 18 | array.splice(0, array.length); 19 | #else 20 | untyped array.length = 0; 21 | #end 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /com/nodename/delaunay/Edge.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.delaunay; 2 | 3 | import com.nodename.geom.LineSegment; 4 | import com.nodename.geom.Point; 5 | import com.nodename.geom.Rectangle; 6 | 7 | 8 | 9 | /** 10 | * The line segment connecting the two Sites is part of the Delaunay triangulation; 11 | * the line segment connecting the two Vertices is part of the Voronoi diagram 12 | * @author ashaw 13 | * 14 | */ 15 | class Edge 16 | { 17 | private static var _pool:Array = new Array(); 18 | 19 | private static var _nedges:Int = 0; 20 | 21 | public static var DELETED:Edge = new Edge(); 22 | 23 | /** 24 | * This is the only way to create a new Edge 25 | * @param site0 26 | * @param site1 27 | * @return 28 | * 29 | */ 30 | public static function createBisectingEdge(site0:Site, site1:Site):Edge { 31 | 32 | var dx = site1.x - site0.x; 33 | var dy = site1.y - site0.y; 34 | var absdx = dx > 0 ? dx : -dx; 35 | var absdy = dy > 0 ? dy : -dy; 36 | var c = site0.x * dx + site0.y * dy + (dx * dx + dy * dy) * 0.5; 37 | 38 | var a:Float; 39 | var b:Float; 40 | if (absdx > absdy) { 41 | a = 1.0; b = dy/dx; c /= dx; 42 | } else { 43 | b = 1.0; a = dx/dy; c /= dy; 44 | } 45 | 46 | var edge = Edge.create(); 47 | 48 | edge.leftSite = site0; 49 | edge.rightSite = site1; 50 | 51 | site0.addEdge(edge); 52 | site1.addEdge(edge); 53 | 54 | edge.leftVertex = null; 55 | edge.rightVertex = null; 56 | 57 | edge.a = a; 58 | edge.b = b; 59 | edge.c = c; 60 | //trace("createBisectingEdge: a ", edge.a, "b", edge.b, "c", edge.c); 61 | 62 | return edge; 63 | } 64 | 65 | private static function create():Edge 66 | { 67 | var edge:Edge; 68 | if (_pool.length > 0) 69 | { 70 | edge = _pool.pop(); 71 | edge.init(); 72 | } 73 | else 74 | { 75 | edge = new Edge(); 76 | } 77 | return edge; 78 | } 79 | 80 | public function delaunayLine():LineSegment { 81 | // draw a line connecting the input Sites for which the edge is a bisector: 82 | return new LineSegment(leftSite.coord, rightSite.coord); 83 | } 84 | 85 | 86 | // the equation of the edge: ax + by = c 87 | public var a:Float = 0; 88 | public var b:Float = 0; 89 | public var c:Float = 0; 90 | 91 | // the two Voronoi vertices that the edge connects 92 | // (if one of them is null, the edge extends to infinity) 93 | public var leftVertex(get, null):Vertex = null; 94 | inline private function get_leftVertex():Vertex { 95 | return leftVertex; 96 | } 97 | 98 | public var rightVertex(get, null):Vertex = null; 99 | inline private function get_rightVertex():Vertex { 100 | return rightVertex; 101 | } 102 | 103 | inline public function vertex(leftRight:LR):Vertex { 104 | return (leftRight == LR.LEFT) ? leftVertex : rightVertex; 105 | } 106 | 107 | public function setVertex(leftRight:LR, v:Vertex):Void { 108 | if (leftRight == LR.LEFT) { 109 | leftVertex = v; 110 | } else { 111 | rightVertex = v; 112 | } 113 | } 114 | 115 | inline public function isPartOfConvexHull():Bool { 116 | return (leftVertex == null || rightVertex == null); 117 | } 118 | 119 | inline public function sitesDistance():Float { 120 | return Point.distance(leftSite.coord, rightSite.coord); 121 | } 122 | 123 | static public function compareSitesDistances_MAX(edge0:Edge, edge1:Edge):Int { 124 | var length0:Float = edge0.sitesDistance(); 125 | var length1:Float = edge1.sitesDistance(); 126 | if (length0 < length1) { 127 | return 1; 128 | } else if (length0 > length1) { 129 | return -1; 130 | } else { 131 | return 0; 132 | } 133 | } 134 | 135 | inline static public function compareSitesDistances(edge0:Edge, edge1:Edge):Int { 136 | return -compareSitesDistances_MAX(edge0, edge1); 137 | } 138 | 139 | private var __leftPoint : Point; 140 | private var __rightPoint : Point; 141 | 142 | public function clippedEnds(or : LR) : Point { 143 | return (or == LR.LEFT)?__leftPoint:__rightPoint; 144 | } 145 | 146 | public function setClippedEnds(or : LR, p : Point) : Void { 147 | if (or == LR.LEFT) 148 | __leftPoint = p; 149 | else 150 | __rightPoint = p; 151 | } 152 | 153 | // unless the entire Edge is outside the bounds. 154 | // In that case visible will be false: 155 | public var visible(get, null): Bool; 156 | inline private function get_visible():Bool { 157 | return __leftPoint != null && __rightPoint != null; // _clippedVertices != null; 158 | } 159 | 160 | // the two input Sites for which this Edge is a bisector: 161 | public var leftSite : Site; 162 | public var rightSite : Site; 163 | 164 | inline public function site(leftRight:LR):Site { 165 | return (leftRight == LR.LEFT) ? leftSite : rightSite; 166 | } 167 | 168 | public var _edgeIndex:Int = 0; 169 | 170 | public function dispose():Void { 171 | leftVertex = null; 172 | rightVertex = null; 173 | setClippedEnds(LR.LEFT, null); 174 | setClippedEnds(LR.RIGHT, null); 175 | 176 | rightSite = null; 177 | leftSite = null; 178 | 179 | leftSite = null; 180 | rightSite = null; 181 | 182 | //_sitesDic = null; 183 | 184 | _pool.push(this); 185 | } 186 | 187 | // Should be private 188 | 189 | public function new() { 190 | _edgeIndex = _nedges++; 191 | init(); 192 | } 193 | 194 | private function init():Void 195 | { 196 | __leftPoint = null; 197 | __rightPoint = null; 198 | 199 | leftSite = null; 200 | rightSite = null; 201 | } 202 | 203 | public function toString():String 204 | { 205 | return "Edge " + _edgeIndex + "; sites " + site(LR.LEFT) + ", " + site(LR.RIGHT) 206 | + "; endVertices " + (leftVertex != null ? ""+leftVertex.vertexIndex : "null") + ", " 207 | + (rightVertex != null ? ""+rightVertex.vertexIndex : "null") + ((leftSite != null)?""+leftSite:"null") + ((rightSite != null)?""+rightSite:"null") + "::"; 208 | } 209 | 210 | /** 211 | * Set _clippedVertices to contain the two ends of the portion of the Voronoi edge that is visible 212 | * within the bounds. If no part of the Edge falls within the bounds, leave _clippedVertices null. 213 | * @param bounds 214 | * 215 | */ 216 | public function clipVertices(bounds:Rectangle):Void { 217 | var xmin:Float = bounds.x; 218 | var ymin:Float = bounds.y; 219 | var xmax:Float = bounds.right; 220 | var ymax:Float = bounds.bottom; 221 | 222 | var vertex0:Vertex, vertex1:Vertex; 223 | var x0:Float, x1:Float, y0:Float, y1:Float; 224 | 225 | if (a == 1.0 && b >= 0.0) 226 | { 227 | vertex0 = rightVertex; 228 | vertex1 = leftVertex; 229 | } 230 | else 231 | { 232 | vertex0 = leftVertex; 233 | vertex1 = rightVertex; 234 | } 235 | 236 | if (a == 1.0) 237 | { 238 | y0 = ymin; 239 | if (vertex0 != null && vertex0.y > ymin) 240 | { 241 | y0 = vertex0.y; 242 | } 243 | if (y0 > ymax) 244 | { 245 | return; 246 | } 247 | x0 = c - b * y0; 248 | 249 | y1 = ymax; 250 | if (vertex1 != null && vertex1.y < ymax) 251 | { 252 | y1 = vertex1.y; 253 | } 254 | if (y1 < ymin) 255 | { 256 | return; 257 | } 258 | x1 = c - b * y1; 259 | 260 | if ((x0 > xmax && x1 > xmax) || (x0 < xmin && x1 < xmin)) 261 | { 262 | return; 263 | } 264 | 265 | if (x0 > xmax) 266 | { 267 | x0 = xmax; y0 = (c - x0)/b; 268 | } 269 | else if (x0 < xmin) 270 | { 271 | x0 = xmin; y0 = (c - x0)/b; 272 | } 273 | 274 | if (x1 > xmax) 275 | { 276 | x1 = xmax; y1 = (c - x1)/b; 277 | } 278 | else if (x1 < xmin) 279 | { 280 | x1 = xmin; y1 = (c - x1)/b; 281 | } 282 | } 283 | else 284 | { 285 | x0 = xmin; 286 | if (vertex0 != null && vertex0.x > xmin) 287 | { 288 | x0 = vertex0.x; 289 | } 290 | if (x0 > xmax) 291 | { 292 | return; 293 | } 294 | y0 = c - a * x0; 295 | 296 | x1 = xmax; 297 | if (vertex1 != null && vertex1.x < xmax) 298 | { 299 | x1 = vertex1.x; 300 | } 301 | if (x1 < xmin) 302 | { 303 | return; 304 | } 305 | y1 = c - a * x1; 306 | 307 | if ((y0 > ymax && y1 > ymax) || (y0 < ymin && y1 < ymin)) 308 | { 309 | return; 310 | } 311 | 312 | if (y0 > ymax) 313 | { 314 | y0 = ymax; x0 = (c - y0)/a; 315 | } 316 | else if (y0 < ymin) 317 | { 318 | y0 = ymin; x0 = (c - y0)/a; 319 | } 320 | 321 | if (y1 > ymax) 322 | { 323 | y1 = ymax; x1 = (c - y1)/a; 324 | } 325 | else if (y1 < ymin) 326 | { 327 | y1 = ymin; x1 = (c - y1)/a; 328 | } 329 | } 330 | 331 | if (vertex0 == leftVertex) 332 | { 333 | setClippedEnds(LR.LEFT, new Point(x0, y0)); 334 | setClippedEnds(LR.RIGHT, new Point(x1, y1)); 335 | } else { 336 | setClippedEnds(LR.RIGHT, new Point(x0, y0)); 337 | setClippedEnds(LR.LEFT, new Point(x1, y1)); 338 | } 339 | } 340 | 341 | } 342 | -------------------------------------------------------------------------------- /com/nodename/delaunay/EdgeList.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.delaunay; 2 | 3 | import com.nodename.delaunay.IDisposable; 4 | import com.nodename.geom.Point; 5 | 6 | 7 | class EdgeList implements IDisposable { 8 | 9 | private var _deltax:Float = 0; 10 | private var _xmin:Float = 0; 11 | 12 | private var _hashsize:Int; 13 | private var _hash:Array; 14 | 15 | public var leftEnd(get, null):Halfedge; 16 | inline private function get_leftEnd():Halfedge { 17 | return leftEnd; 18 | } 19 | 20 | public var rightEnd(get, null):Halfedge; 21 | inline private function get_rightEnd():Halfedge { 22 | return rightEnd; 23 | } 24 | 25 | public function dispose():Void 26 | { 27 | var halfEdge:Halfedge = leftEnd; 28 | var prevHe:Halfedge; 29 | while (halfEdge != rightEnd) 30 | { 31 | prevHe = halfEdge; 32 | halfEdge = halfEdge.edgeListRightNeighbor; 33 | prevHe.dispose(); 34 | } 35 | leftEnd = null; 36 | rightEnd.dispose(); 37 | rightEnd = null; 38 | 39 | var i:Int; 40 | for (i in 0..._hashsize) { 41 | _hash[i] = null; 42 | } 43 | _hash = null; 44 | } 45 | 46 | public function new(xmin:Float, deltax:Float, sqrt_nsites:Int) { 47 | _xmin = xmin; 48 | _deltax = deltax; 49 | _hashsize = 2 * sqrt_nsites; 50 | 51 | _hash = new Array(); 52 | _hash[_hashsize - 1] = null; 53 | 54 | // two dummy Halfedges: 55 | leftEnd = Halfedge.createDummy(); 56 | rightEnd = Halfedge.createDummy(); 57 | leftEnd.edgeListLeftNeighbor = null; 58 | leftEnd.edgeListRightNeighbor = rightEnd; 59 | rightEnd.edgeListLeftNeighbor = leftEnd; 60 | rightEnd.edgeListRightNeighbor = null; 61 | _hash[0] = leftEnd; 62 | _hash[_hashsize - 1] = rightEnd; 63 | } 64 | 65 | /** 66 | * Insert newHalfedge to the right of lb 67 | * @param lb 68 | * @param newHalfedge 69 | * 70 | */ 71 | public function insert(lb:Halfedge, newHalfedge:Halfedge):Void 72 | { 73 | newHalfedge.edgeListLeftNeighbor = lb; 74 | newHalfedge.edgeListRightNeighbor = lb.edgeListRightNeighbor; 75 | lb.edgeListRightNeighbor.edgeListLeftNeighbor = newHalfedge; 76 | lb.edgeListRightNeighbor = newHalfedge; 77 | } 78 | 79 | /** 80 | * This function only removes the Halfedge from the left-right list. 81 | * We cannot dispose it yet because we are still using it. 82 | * @param halfEdge 83 | * 84 | */ 85 | 86 | public function remove(halfEdge:Halfedge):Void 87 | { 88 | halfEdge.edgeListLeftNeighbor.edgeListRightNeighbor = halfEdge.edgeListRightNeighbor; 89 | halfEdge.edgeListRightNeighbor.edgeListLeftNeighbor = halfEdge.edgeListLeftNeighbor; 90 | //trace("RRRRRRREEEEEEEEEEEEEMMMMMMMMMMMOOOOOOOOOOVVVVVVVVVVVEEEEEEEEEEEDDDDDDDDDD"); 91 | halfEdge.edge = Edge.DELETED; 92 | halfEdge.edgeListLeftNeighbor = halfEdge.edgeListRightNeighbor = null; 93 | } 94 | 95 | /** 96 | * Find the rightmost Halfedge that is still left of p 97 | * @param p 98 | * @return 99 | * 100 | */ 101 | public function edgeListLeftNeighbor(p:Point):Halfedge { 102 | 103 | /* Use hash table to get close to desired halfedge */ 104 | var bucket = Std.int(((p.x - _xmin)/_deltax) * _hashsize); 105 | if (bucket < 0) { 106 | bucket = 0; 107 | } 108 | if (bucket >= _hashsize) { 109 | bucket = _hashsize - 1; 110 | } 111 | 112 | var halfEdge = getHash(bucket); 113 | if (halfEdge == null) { 114 | var i = 1; 115 | while (true) { 116 | halfEdge = getHash(bucket - i); 117 | if (halfEdge != null) break; 118 | halfEdge = getHash(bucket + i); 119 | if (halfEdge != null) break; 120 | i++; 121 | } 122 | } 123 | 124 | /* Now search linear list of halfedges for the correct one */ 125 | if (halfEdge == leftEnd || (halfEdge != rightEnd && halfEdge.isLeftOf(p))) 126 | { 127 | do 128 | { 129 | halfEdge = halfEdge.edgeListRightNeighbor; 130 | } 131 | while (halfEdge != rightEnd && halfEdge.isLeftOf(p)); 132 | halfEdge = halfEdge.edgeListLeftNeighbor; 133 | } 134 | else 135 | { 136 | do 137 | { 138 | halfEdge = halfEdge.edgeListLeftNeighbor; 139 | } 140 | while (halfEdge != leftEnd && !halfEdge.isLeftOf(p)); 141 | } 142 | 143 | /* Update hash table and reference counts */ 144 | if (bucket > 0 && bucket <_hashsize - 1) 145 | { 146 | _hash[bucket] = halfEdge; 147 | } 148 | return halfEdge; 149 | } 150 | 151 | /* Get entry from hash table, pruning any deleted nodes */ 152 | private function getHash(b:Int):Halfedge { 153 | var halfEdge:Halfedge = null; 154 | 155 | if (b >= 0 && b < _hashsize) { 156 | halfEdge = _hash[b]; 157 | if (halfEdge != null && halfEdge.edge == Edge.DELETED) 158 | { 159 | /* Hash table points to deleted halfedge. Patch as necessary. */ 160 | _hash[b] = null; 161 | // still can't dispose halfEdge yet! 162 | halfEdge = null; 163 | } 164 | } 165 | return halfEdge; 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /com/nodename/delaunay/EdgeReorderer.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.delaunay; 2 | 3 | 4 | class EdgeReorderer { 5 | 6 | public var edges(get, null):Array; 7 | inline private function get_edges():Array { 8 | return edges; 9 | } 10 | 11 | public var edgeOrientations(get, null):Array; 12 | inline private function get_edgeOrientations():Array { 13 | return edgeOrientations; 14 | } 15 | 16 | inline public static function edgeToLeftVertex(ed : Edge) : ICoord { return ed.leftVertex;} 17 | inline public static function edgeToLeftSite(ed : Edge) : ICoord { return ed.leftSite;} 18 | inline public static function edgeToRightVertex(ed : Edge) : ICoord { return ed.rightVertex;} 19 | inline public static function edgeToRightSite(ed : Edge) : ICoord { return ed.rightSite;} 20 | 21 | // TODO: use a ADT to represent criterion. 22 | public function new(origEdges:Array, leftCoord: Edge -> ICoord, rightCoord: Edge -> ICoord) { 23 | edges = new Array(); 24 | edgeOrientations = new Array(); 25 | if (origEdges.length > 0) 26 | { 27 | edges = reorderEdges(origEdges, leftCoord, rightCoord); 28 | } 29 | } 30 | 31 | public function dispose():Void 32 | { 33 | edges = null; 34 | edgeOrientations = null; 35 | } 36 | 37 | private function reorderEdges(origEdges:Array, leftCoord: Edge -> ICoord, rightCoord: Edge -> ICoord):Array { 38 | var i:Int; 39 | var j:Int; 40 | var n:Int = origEdges.length; 41 | var edge:Edge; 42 | // we're going to reorder the edges in order of traversal 43 | var done:Array = new Array(); 44 | done[n - 1] = false; 45 | var nDone:Int = 0; 46 | for (b in done) { 47 | b = false; 48 | } 49 | var newEdges:Array = new Array(); 50 | 51 | i = 0; 52 | edge = origEdges[i]; 53 | newEdges.push(edge); 54 | edgeOrientations.push(LR.LEFT); 55 | var firstPoint:ICoord = leftCoord(edge); 56 | var lastPoint:ICoord = rightCoord(edge); 57 | 58 | if (firstPoint == Vertex.VERTEX_AT_INFINITY || lastPoint == Vertex.VERTEX_AT_INFINITY) 59 | { 60 | return new Array(); 61 | } 62 | 63 | done[i] = true; 64 | ++nDone; 65 | 66 | while (nDone < n) 67 | { 68 | for (i in 1...n) 69 | { 70 | if (done[i]) 71 | { 72 | continue; 73 | } 74 | edge = origEdges[i]; 75 | var leftPoint:ICoord = leftCoord(edge); 76 | var rightPoint:ICoord = rightCoord(edge); 77 | 78 | if (leftPoint == Vertex.VERTEX_AT_INFINITY || rightPoint == Vertex.VERTEX_AT_INFINITY) 79 | { 80 | return new Array(); 81 | } 82 | if (leftPoint == lastPoint) 83 | { 84 | lastPoint = rightPoint; 85 | edgeOrientations.push(LR.LEFT); 86 | newEdges.push(edge); 87 | done[i] = true; 88 | } 89 | else if (rightPoint == firstPoint) 90 | { 91 | firstPoint = leftPoint; 92 | edgeOrientations.unshift(LR.LEFT); 93 | newEdges.unshift(edge); 94 | done[i] = true; 95 | } 96 | else if (leftPoint == firstPoint) 97 | { 98 | firstPoint = rightPoint; 99 | edgeOrientations.unshift(LR.RIGHT); 100 | newEdges.unshift(edge); 101 | done[i] = true; 102 | } 103 | else if (rightPoint == lastPoint) 104 | { 105 | lastPoint = leftPoint; 106 | edgeOrientations.push(LR.RIGHT); 107 | newEdges.push(edge); 108 | done[i] = true; 109 | } 110 | if (done[i]) 111 | { 112 | ++nDone; 113 | } 114 | } 115 | } 116 | 117 | return newEdges; 118 | } 119 | 120 | } -------------------------------------------------------------------------------- /com/nodename/delaunay/Halfedge.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.delaunay; 2 | 3 | import com.nodename.geom.Point; 4 | 5 | 6 | class Halfedge { 7 | private static var _pool:Array = new Array(); 8 | public static function create(edge:Edge, lr:LR):Halfedge { 9 | if (_pool.length > 0) 10 | { 11 | return _pool.pop().init(edge, lr); 12 | } 13 | else 14 | { 15 | return new Halfedge(edge, lr); 16 | } 17 | } 18 | 19 | public static function createDummy():Halfedge 20 | { 21 | return create(null, null); 22 | } 23 | 24 | public var edgeListLeftNeighbor:Halfedge; 25 | public var edgeListRightNeighbor:Halfedge; 26 | public var nextInPriorityQueue:Halfedge; 27 | 28 | public var edge:Edge; 29 | public var leftRight:LR; 30 | public var vertex:Vertex; 31 | 32 | // the vertex's y-coordinate in the transformed Voronoi space V* 33 | public var ystar:Float = 0; 34 | 35 | // Should be private 36 | public function new(edge:Edge = null, lr:LR = null) 37 | { 38 | init(edge, lr); 39 | } 40 | 41 | private function init(edge:Edge, lr:LR):Halfedge 42 | { 43 | this.edge = edge; 44 | leftRight = lr; 45 | nextInPriorityQueue = null; 46 | vertex = null; 47 | return this; 48 | } 49 | 50 | public function toString():String 51 | { 52 | return "Halfedge (leftRight: " + leftRight + "; vertex: " + vertex + ")"; 53 | } 54 | 55 | public function dispose():Void 56 | { 57 | if (edgeListLeftNeighbor != null || edgeListRightNeighbor != null) 58 | { 59 | // still in EdgeList 60 | return; 61 | } 62 | if (nextInPriorityQueue != null) 63 | { 64 | // still in PriorityQueue 65 | return; 66 | } 67 | edge = null; 68 | leftRight = null; 69 | vertex = null; 70 | _pool.push(this); 71 | } 72 | 73 | public function reallyDispose():Void 74 | { 75 | edgeListLeftNeighbor = null; 76 | edgeListRightNeighbor = null; 77 | nextInPriorityQueue = null; 78 | edge = null; 79 | leftRight = null; 80 | vertex = null; 81 | _pool.push(this); 82 | } 83 | 84 | public function isLeftOf(p:Point):Bool { 85 | var above:Bool; 86 | 87 | var topSite = edge.rightSite; 88 | 89 | var rightOfSite = p.x > topSite.x; 90 | if (rightOfSite && this.leftRight == LR.LEFT) { 91 | return true; 92 | } 93 | if (!rightOfSite && this.leftRight == LR.RIGHT) { 94 | return false; 95 | } 96 | 97 | if (edge.a == 1.0) { 98 | var dyp = p.y - topSite.y; 99 | var dxp = p.x - topSite.x; 100 | var fast = false; 101 | if ((!rightOfSite && edge.b < 0.0) || (rightOfSite && edge.b >= 0.0) ) { 102 | above = dyp >= (edge.b * dxp); 103 | fast = above; 104 | } else { 105 | above = (p.x + p.y * edge.b) > edge.c; 106 | if (edge.b < 0.0) { 107 | above = !above; 108 | } 109 | fast = !above; 110 | } 111 | if (!fast) { 112 | var dxs = topSite.x - edge.leftSite.x; 113 | above = edge.b * (dxp * dxp - dyp * dyp) < (dxs * dyp * (1.0 + 2.0 * dxp/dxs + edge.b * edge.b)); 114 | if (edge.b < 0.0) { 115 | above = !above; 116 | } 117 | } 118 | } else /* edge.b == 1.0 */ { 119 | var yl = edge.c - edge.a * p.x; 120 | var t1 = p.y - yl; 121 | var t2 = p.x - topSite.x; 122 | var t3 = yl - topSite.y; 123 | above = (t1 * t1) > (t2 * t2 + t3 * t3); 124 | } 125 | 126 | var xx = this.leftRight == LR.LEFT; 127 | 128 | return this.leftRight == LR.LEFT ? above : !above; 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /com/nodename/delaunay/HalfedgePriorityQueue.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.delaunay; 2 | 3 | import com.nodename.geom.Point; 4 | 5 | 6 | class HalfedgePriorityQueue // also known as heap 7 | { 8 | private var _hash:Array; 9 | private var _count:Int; 10 | private var _minBucket:Int; 11 | private var _hashsize:Int; 12 | 13 | private var _ymin:Float = 0; 14 | private var _deltay:Float = 0; 15 | 16 | public function new(ymin:Float, deltay:Float, sqrt_nsites:Int) 17 | { 18 | _ymin = ymin; 19 | _deltay = deltay; 20 | _hashsize = 4 * sqrt_nsites; 21 | initialize(); 22 | } 23 | 24 | public function dispose():Void 25 | { 26 | // get rid of dummies 27 | for (i in 0..._hashsize) 28 | { 29 | _hash[i].dispose(); 30 | _hash[i] = null; 31 | } 32 | _hash = null; 33 | } 34 | 35 | private function initialize():Void 36 | { 37 | var i:Int; 38 | 39 | _count = 0; 40 | _minBucket = 0; 41 | _hash = new Array(); 42 | _hash[_hashsize - 1] = null; 43 | // dummy Halfedge at the top of each hash 44 | for (i in 0..._hashsize) 45 | { 46 | _hash[i] = Halfedge.createDummy(); 47 | _hash[i].nextInPriorityQueue = null; 48 | } 49 | } 50 | 51 | public function insert(halfEdge:Halfedge):Void 52 | { 53 | var previous:Halfedge, next:Halfedge; 54 | var insertionBucket:Int = bucket(halfEdge); 55 | if (insertionBucket < _minBucket) 56 | { 57 | _minBucket = insertionBucket; 58 | } 59 | previous = _hash[insertionBucket]; 60 | while ((next = previous.nextInPriorityQueue) != null 61 | && (halfEdge.ystar > next.ystar || (halfEdge.ystar == next.ystar && halfEdge.vertex.x > next.vertex.x))) 62 | { 63 | previous = next; 64 | } 65 | halfEdge.nextInPriorityQueue = previous.nextInPriorityQueue; 66 | previous.nextInPriorityQueue = halfEdge; 67 | ++_count; 68 | } 69 | 70 | public function remove(halfEdge:Halfedge):Void 71 | { 72 | var removalBucket = bucket(halfEdge); 73 | 74 | if (halfEdge.vertex != null) 75 | { 76 | var previous = _hash[removalBucket]; 77 | while (previous.nextInPriorityQueue != halfEdge) 78 | { 79 | previous = previous.nextInPriorityQueue; 80 | } 81 | previous.nextInPriorityQueue = halfEdge.nextInPriorityQueue; 82 | _count--; 83 | halfEdge.vertex = null; 84 | halfEdge.nextInPriorityQueue = null; 85 | halfEdge.dispose(); 86 | } 87 | } 88 | 89 | private function bucket(halfEdge:Halfedge):Int 90 | { 91 | var theBucket:Int = Std.int((halfEdge.ystar - _ymin) / _deltay * _hashsize); 92 | if (theBucket < 0) theBucket = 0; 93 | if (theBucket >= _hashsize) theBucket = _hashsize - 1; 94 | return theBucket; 95 | } 96 | 97 | private function isEmpty(bucket:Int):Bool 98 | { 99 | return (_hash[bucket].nextInPriorityQueue == null); 100 | } 101 | 102 | /** 103 | * move _minBucket until it contains an actual Halfedge (not just the dummy at the top); 104 | * 105 | */ 106 | private function adjustMinBucket():Void 107 | { 108 | while (_minBucket < _hashsize - 1 && isEmpty(_minBucket)) 109 | { 110 | ++_minBucket; 111 | } 112 | } 113 | 114 | public function empty():Bool 115 | { 116 | return _count == 0; 117 | } 118 | 119 | /** 120 | * @return coordinates of the Halfedge's vertex in V*, the transformed Voronoi diagram 121 | * 122 | */ 123 | public function min():Point 124 | { 125 | adjustMinBucket(); 126 | var answer:Halfedge = _hash[_minBucket].nextInPriorityQueue; 127 | return new Point(answer.vertex.x, answer.ystar); 128 | } 129 | 130 | /** 131 | * remove and return the min Halfedge 132 | * @return 133 | * 134 | */ 135 | public function extractMin():Halfedge 136 | { 137 | // get the first real Halfedge in _minBucket 138 | var answer = _hash[_minBucket].nextInPriorityQueue; 139 | 140 | _hash[_minBucket].nextInPriorityQueue = answer.nextInPriorityQueue; 141 | _count--; 142 | answer.nextInPriorityQueue = null; 143 | 144 | return answer; 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /com/nodename/delaunay/ICoord.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.delaunay; 2 | 3 | import com.nodename.geom.Point; 4 | 5 | interface ICoord { 6 | var coord(get, null):Point; 7 | } 8 | -------------------------------------------------------------------------------- /com/nodename/delaunay/IDisposable.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.delaunay; 2 | 3 | interface IDisposable 4 | { 5 | function dispose():Void; 6 | } 7 | -------------------------------------------------------------------------------- /com/nodename/delaunay/Kruskal.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.delaunay; 2 | 3 | import com.nodename.geom.LineSegment; 4 | import com.nodename.geom.Point; 5 | 6 | 7 | class Kruskal { 8 | /** 9 | * Kruskal's spanning tree algorithm with union-find 10 | * Skiena: The Algorithm Design Manual, p. 196ff 11 | * Note: the sites are implied: they consist of the end points of the line segments 12 | */ 13 | public static function kruskal(lineSegments:Array, type:SortType = null):Array 14 | { 15 | if (type == null) type = SortType.MINIMUM; 16 | 17 | var nodes = new Map(); 18 | var mst:Array = new Array(); 19 | var nodePool:Array = Node.pool; 20 | 21 | switch (type) { 22 | // note that the compare functions are the reverse of what you'd expect 23 | // because (see below) we traverse the lineSegments in reverse order for speed 24 | case MAXIMUM: 25 | lineSegments.sort(LineSegment.compareLengths); 26 | case MINIMUM: 27 | lineSegments.sort(LineSegment.compareLengths_MAX); 28 | } 29 | var index = lineSegments.length - 1; 30 | while (index >= 0) { 31 | var lineSegment = lineSegments[index]; 32 | index--; 33 | 34 | var node0:Node = nodes[lineSegment.p0]; 35 | var rootOfSet0:Node; 36 | if (node0 == null) 37 | { 38 | node0 = nodePool.length > 0 ? nodePool.pop() : new Node(); 39 | // intialize the node: 40 | rootOfSet0 = node0.parent = node0; 41 | node0.treeSize = 1; 42 | 43 | nodes[lineSegment.p0] = node0; 44 | } 45 | else 46 | { 47 | rootOfSet0 = find(node0); 48 | } 49 | 50 | var node1:Node = nodes[cast lineSegment.p1]; 51 | var rootOfSet1:Node; 52 | if (node1 == null) 53 | { 54 | node1 = nodePool.length > 0 ? nodePool.pop() : new Node(); 55 | // intialize the node: 56 | rootOfSet1 = node1.parent = node1; 57 | node1.treeSize = 1; 58 | 59 | nodes[lineSegment.p1] = node1; 60 | } 61 | else 62 | { 63 | rootOfSet1 = find(node1); 64 | } 65 | 66 | if (rootOfSet0 != rootOfSet1) // nodes not in same set 67 | { 68 | mst.push(lineSegment); 69 | 70 | // merge the two sets: 71 | var treeSize0:Int = rootOfSet0.treeSize; 72 | var treeSize1:Int = rootOfSet1.treeSize; 73 | if (treeSize0 >= treeSize1) 74 | { 75 | // set0 absorbs set1: 76 | rootOfSet1.parent = rootOfSet0; 77 | rootOfSet0.treeSize += treeSize1; 78 | } 79 | else 80 | { 81 | // set1 absorbs set0: 82 | rootOfSet0.parent = rootOfSet1; 83 | rootOfSet1.treeSize += treeSize0; 84 | } 85 | } 86 | } 87 | 88 | for (node in nodes) { 89 | nodePool.push(node); 90 | } 91 | 92 | return mst; 93 | } 94 | 95 | 96 | static function find(node:Node):Node { 97 | if (node.parent == node) { 98 | return node; 99 | } else { 100 | var root:Node = find(node.parent); 101 | // this line is just to speed up subsequent finds by keeping the tree depth low: 102 | node.parent = root; 103 | return root; 104 | } 105 | } 106 | } 107 | 108 | class Node { 109 | public static var pool:Array = new Array(); 110 | 111 | public var parent:Node; 112 | public var treeSize:Int; 113 | 114 | public function new() {} 115 | } 116 | 117 | enum SortType { 118 | MINIMUM; 119 | MAXIMUM; 120 | } -------------------------------------------------------------------------------- /com/nodename/delaunay/LR.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.delaunay; 2 | 3 | class LR { 4 | public static var LEFT:LR = new LR("left"); 5 | public static var RIGHT:LR = new LR("right"); 6 | 7 | 8 | 9 | private var _name:String; 10 | 11 | // Should be private 12 | public function new(name:String) { 13 | _name = name; 14 | } 15 | 16 | public static function other(leftRight:LR):LR 17 | { 18 | return leftRight == LEFT ? RIGHT : LEFT; 19 | } 20 | public function toString():String 21 | { 22 | return _name; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /com/nodename/delaunay/SelectHelper.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.delaunay; 2 | 3 | import com.nodename.geom.Point; 4 | import com.nodename.geom.LineSegment; 5 | 6 | 7 | class SelectHelper { 8 | 9 | 10 | public static function visibleLineSegments(edges:Array):Array 11 | { 12 | var segments = new Array(); 13 | 14 | for (edge in edges) { 15 | if (edge.visible) { 16 | var p1 = edge.clippedEnds(LR.LEFT); 17 | var p2 = edge.clippedEnds(LR.RIGHT); 18 | segments.push(new LineSegment(p1, p2)); 19 | } 20 | } 21 | 22 | return segments; 23 | } 24 | 25 | public static function selectEdgesForSitePoint(coord:Point, edgesToTest:Array):Array 26 | { 27 | return edgesToTest.filter( 28 | function (edge:Edge) 29 | return ((edge.leftSite != null && edge.leftSite.coord == coord) || (edge.rightSite != null && edge.rightSite.coord == coord)) 30 | ); 31 | } 32 | 33 | public static function delaunayLinesForEdges(edges:Array):Array 34 | { 35 | var segments = new Array(); 36 | for (edge in edges) { 37 | segments.push(edge.delaunayLine()); 38 | } 39 | return segments; 40 | } 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /com/nodename/delaunay/Site.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.delaunay; 2 | 3 | import com.nodename.geom.Polygon; 4 | import com.nodename.geom.Winding; 5 | import com.nodename.geom.Point; 6 | import com.nodename.geom.Rectangle; 7 | 8 | using com.nodename.delaunay.ArrayHelper; 9 | 10 | 11 | class Site implements ICoord 12 | { 13 | private static var _pool:Array = new Array(); 14 | public static function create(p:Point, index:Int, weight:Float, color:Int):Site { 15 | if (_pool.length > 0) 16 | { 17 | return _pool.pop().init(p, index, weight, color); 18 | } 19 | else 20 | { 21 | return new Site(p, index, weight, color); 22 | } 23 | } 24 | 25 | public inline static function sortSites(sites:Array):Void { 26 | sites.sort(Site.compare); 27 | } 28 | 29 | /** 30 | * sort sites on y, then x, coord 31 | * also change each site's _siteIndex to match its new position in the list 32 | * so the _siteIndex can be used to identify the site for nearest-neighbor queries 33 | * 34 | * haha "also" - means more than one responsibility... 35 | * 36 | */ 37 | 38 | private static function compare(s1:Site, s2:Site):Int { 39 | var returnValue:Int = Voronoi.compareByYThenX(s1, s2); 40 | 41 | // swap _siteIndex values if necessary to match new ordering: 42 | var tempIndex:Int; 43 | if (returnValue == -1) 44 | { 45 | if (s1._siteIndex > s2._siteIndex) 46 | { 47 | tempIndex = s1._siteIndex; 48 | s1._siteIndex = s2._siteIndex; 49 | s2._siteIndex = tempIndex; 50 | } 51 | } 52 | else if (returnValue == 1) 53 | { 54 | if (s2._siteIndex > s1._siteIndex) 55 | { 56 | tempIndex = s2._siteIndex; 57 | s2._siteIndex = s1._siteIndex; 58 | s1._siteIndex = tempIndex; 59 | } 60 | 61 | } 62 | 63 | return returnValue; 64 | } 65 | 66 | 67 | 68 | private static var EPSILON = .005; 69 | private static function closeEnough(p0:Point, p1:Point):Bool { 70 | var dx2 = (p0.x - p1.x) * (p0.x - p1.x); 71 | var dy2 = (p0.y - p1.y) * (p0.y - p1.y); 72 | return Math.sqrt(dx2 + dy2) < EPSILON; 73 | } 74 | 75 | public var coord(get, null) : Point; 76 | inline private function get_coord():Point { 77 | return coord; 78 | } 79 | 80 | public var color:Int; 81 | public var weight:Float = 0; 82 | 83 | private var _siteIndex:Int; 84 | 85 | // the edges that define this Site's Voronoi region: 86 | public var edges(get, null):Array; 87 | inline private function get_edges():Array 88 | { 89 | return edges; 90 | } 91 | // which end of each edge hooks up with the previous edge in _edges: 92 | private var _edgeOrientations:Array; 93 | // ordered list of points that define the region clipped to bounds: 94 | private var _region:Array; 95 | 96 | // use create instead. 97 | private function new(p:Point, index:Int, weight:Float, color:Int) { 98 | init(p, index, weight, color); 99 | } 100 | 101 | private function init(p:Point, index:Int, weight:Float, color:Int):Site 102 | { 103 | coord = p; 104 | _siteIndex = index; 105 | this.weight = weight; 106 | this.color = color; 107 | edges = new Array(); 108 | _region = null; 109 | return this; 110 | } 111 | 112 | public function toString():String 113 | { 114 | return "Site " + _siteIndex + ": " + coord; 115 | } 116 | 117 | private function move(p:Point):Void 118 | { 119 | clear(); 120 | coord = p; 121 | } 122 | 123 | public function dispose():Void 124 | { 125 | coord = null; 126 | clear(); 127 | _pool.push(this); 128 | } 129 | 130 | private function clear():Void 131 | { 132 | if (edges != null) 133 | { 134 | edges.clear(); 135 | edges = null; 136 | } 137 | if (_edgeOrientations != null) 138 | { 139 | _edgeOrientations.clear(); 140 | _edgeOrientations = null; 141 | } 142 | if (_region != null) 143 | { 144 | _region.clear(); 145 | _region = null; 146 | } 147 | } 148 | 149 | inline public function addEdge(edge:Edge):Void 150 | { 151 | edges.push(edge); 152 | } 153 | 154 | // TODO: Can be optimized. 155 | public function nearestEdge():Edge 156 | { 157 | edges.sort(Edge.compareSitesDistances); 158 | return edges[0]; 159 | } 160 | 161 | public function neighborSites():Array 162 | { 163 | if (edges == null || edges.length == 0) 164 | { 165 | return new Array(); 166 | } 167 | if (_edgeOrientations == null) 168 | { 169 | reorderEdges(); 170 | } 171 | var list = new Array(); 172 | for (edge in edges) 173 | { 174 | list.push(neighborSite(edge)); 175 | } 176 | return list; 177 | } 178 | 179 | private function neighborSite(edge:Edge):Site 180 | { 181 | if (this == edge.leftSite) 182 | { 183 | return edge.rightSite; 184 | } 185 | if (this == edge.rightSite) 186 | { 187 | return edge.leftSite; 188 | } 189 | return null; 190 | } 191 | 192 | public function region(clippingBounds:Rectangle):Array { 193 | if (edges == null || edges.length == 0) 194 | { 195 | return new Array(); 196 | } 197 | if (_edgeOrientations == null) 198 | { 199 | reorderEdges(); 200 | _region = clipToBounds(clippingBounds); 201 | if ((new Polygon(_region)).winding() == Winding.CLOCKWISE) 202 | { 203 | _region.reverse(); 204 | } 205 | } 206 | return _region; 207 | } 208 | 209 | private function reorderEdges():Void 210 | { 211 | //trace("edges:", edges); 212 | var reorderer = new EdgeReorderer(edges, EdgeReorderer.edgeToLeftVertex, EdgeReorderer.edgeToRightVertex); 213 | edges = reorderer.edges; 214 | //trace("reordered:", edges); 215 | _edgeOrientations = reorderer.edgeOrientations; 216 | reorderer.dispose(); 217 | } 218 | 219 | private function clipToBounds(bounds:Rectangle):Array 220 | { 221 | var points:Array = new Array(); 222 | var n:Int = edges.length; 223 | var i:Int = 0; 224 | var edge:Edge; 225 | while (i < n && (edges[i].visible == false)) 226 | { 227 | ++i; 228 | } 229 | 230 | if (i == n) 231 | { 232 | // no edges visible 233 | return new Array(); 234 | } 235 | edge = edges[i]; 236 | var orientation:LR = _edgeOrientations[i]; 237 | points.push(edge.clippedEnds(orientation)); 238 | points.push(edge.clippedEnds(LR.other(orientation))); 239 | 240 | for (j in (i + 1)...n) 241 | { 242 | edge = edges[j]; 243 | if (edge.visible == false) 244 | { 245 | continue; 246 | } 247 | connect(points, j, bounds); 248 | } 249 | // close up the polygon by adding another corner point of the bounds if needed: 250 | connect(points, i, bounds, true); 251 | 252 | return points; 253 | } 254 | 255 | private function connect(points:Array, j:Int, bounds:Rectangle, closingUp:Bool = false):Void 256 | { 257 | var rightPoint = points[points.length - 1]; 258 | var newEdge = edges[j]; 259 | var newOrientation:LR = _edgeOrientations[j]; 260 | // the point that must be connected to rightPoint: 261 | var newPoint = newEdge.clippedEnds(newOrientation); 262 | if (!closeEnough(rightPoint, newPoint)) 263 | { 264 | // The points do not coincide, so they must have been clipped at the bounds; 265 | // see if they are on the same border of the bounds: 266 | if (rightPoint.x != newPoint.x 267 | && rightPoint.y != newPoint.y) 268 | { 269 | // They are on different borders of the bounds; 270 | // insert one or two corners of bounds as needed to hook them up: 271 | // (NOTE this will not be correct if the region should take up more than 272 | // half of the bounds rect, for then we will have gone the wrong way 273 | // around the bounds and included the smaller part rather than the larger) 274 | var rightCheck:Int = BoundsCheck.check(rightPoint, bounds); 275 | var newCheck:Int = BoundsCheck.check(newPoint, bounds); 276 | var px; 277 | var py; 278 | if (rightCheck & BoundsCheck.RIGHT != 0) 279 | { 280 | px = bounds.right; 281 | if (newCheck & BoundsCheck.BOTTOM != 0) 282 | { 283 | py = bounds.bottom; 284 | points.push(new Point(px, py)); 285 | } 286 | else if (newCheck & BoundsCheck.TOP != 0) 287 | { 288 | py = bounds.top; 289 | points.push(new Point(px, py)); 290 | } 291 | else if (newCheck & BoundsCheck.LEFT != 0) 292 | { 293 | if (rightPoint.y - bounds.y + newPoint.y - bounds.y < bounds.height) 294 | { 295 | py = bounds.top; 296 | } 297 | else 298 | { 299 | py = bounds.bottom; 300 | } 301 | points.push(new Point(px, py)); 302 | points.push(new Point(bounds.left, py)); 303 | } 304 | } 305 | else if (rightCheck & BoundsCheck.LEFT != 0) 306 | { 307 | px = bounds.left; 308 | if (newCheck & BoundsCheck.BOTTOM != 0) 309 | { 310 | py = bounds.bottom; 311 | points.push(new Point(px, py)); 312 | } 313 | else if (newCheck & BoundsCheck.TOP != 0) 314 | { 315 | py = bounds.top; 316 | points.push(new Point(px, py)); 317 | } 318 | else if (newCheck & BoundsCheck.RIGHT != 0) 319 | { 320 | if (rightPoint.y - bounds.y + newPoint.y - bounds.y < bounds.height) 321 | { 322 | py = bounds.top; 323 | } 324 | else 325 | { 326 | py = bounds.bottom; 327 | } 328 | points.push(new Point(px, py)); 329 | points.push(new Point(bounds.right, py)); 330 | } 331 | } 332 | else if (rightCheck & BoundsCheck.TOP != 0) 333 | { 334 | py = bounds.top; 335 | if (newCheck & BoundsCheck.RIGHT != 0) 336 | { 337 | px = bounds.right; 338 | points.push(new Point(px, py)); 339 | } 340 | else if (newCheck & BoundsCheck.LEFT != 0) 341 | { 342 | px = bounds.left; 343 | points.push(new Point(px, py)); 344 | } 345 | else if (newCheck & BoundsCheck.BOTTOM != 0) 346 | { 347 | if (rightPoint.x - bounds.x + newPoint.x - bounds.x < bounds.width) 348 | { 349 | px = bounds.left; 350 | } 351 | else 352 | { 353 | px = bounds.right; 354 | } 355 | points.push(new Point(px, py)); 356 | points.push(new Point(px, bounds.bottom)); 357 | } 358 | } 359 | else if (rightCheck & BoundsCheck.BOTTOM != 0) 360 | { 361 | py = bounds.bottom; 362 | if (newCheck & BoundsCheck.RIGHT != 0) 363 | { 364 | px = bounds.right; 365 | points.push(new Point(px, py)); 366 | } 367 | else if (newCheck & BoundsCheck.LEFT != 0) 368 | { 369 | px = bounds.left; 370 | points.push(new Point(px, py)); 371 | } 372 | else if (newCheck & BoundsCheck.TOP != 0) 373 | { 374 | if (rightPoint.x - bounds.x + newPoint.x - bounds.x < bounds.width) 375 | { 376 | px = bounds.left; 377 | } 378 | else 379 | { 380 | px = bounds.right; 381 | } 382 | points.push(new Point(px, py)); 383 | points.push(new Point(px, bounds.top)); 384 | } 385 | } 386 | } 387 | if (closingUp) 388 | { 389 | // newEdge's ends have already been added 390 | return; 391 | } 392 | points.push(newPoint); 393 | } 394 | var newRightPoint = newEdge.clippedEnds(LR.other(newOrientation)); 395 | if (!closeEnough(points[0], newRightPoint)) 396 | { 397 | points.push(newRightPoint); 398 | } 399 | } 400 | 401 | public var x(get, null):Float; 402 | inline private function get_x() { 403 | return coord.x; 404 | } 405 | 406 | public var y(get, null):Float; 407 | inline private function get_y() { 408 | return coord.y; 409 | } 410 | 411 | public function dist(p:ICoord) 412 | { 413 | var dx2 = (p.coord.x - this.coord.x) * (p.coord.x - this.coord.x); 414 | var dy2 = (p.coord.y - this.coord.y) * (p.coord.y - this.coord.y); 415 | return Math.sqrt(dx2 + dy2); 416 | } 417 | 418 | } 419 | 420 | 421 | @:final class BoundsCheck 422 | { 423 | public static var TOP:Int = 1; 424 | public static var BOTTOM:Int = 2; 425 | public static var LEFT:Int = 4; 426 | public static var RIGHT:Int = 8; 427 | 428 | /** 429 | * 430 | * @param point 431 | * @param bounds 432 | * @return an Int with the appropriate bits set if the Point lies on the corresponding bounds lines 433 | * 434 | */ 435 | public static function check(point:Point, bounds:Rectangle):Int 436 | { 437 | var value:Int = 0; 438 | if (point.x == bounds.left) 439 | { 440 | value |= LEFT; 441 | } else if (point.x == bounds.right) { 442 | value |= RIGHT; 443 | } 444 | if (point.y == bounds.top) 445 | { 446 | value |= TOP; 447 | } else if (point.y == bounds.bottom) { 448 | value |= BOTTOM; 449 | } 450 | return value; 451 | } 452 | 453 | private function new() 454 | { 455 | throw "BoundsCheck constructor unused"; 456 | } 457 | 458 | } -------------------------------------------------------------------------------- /com/nodename/delaunay/SiteList.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.delaunay; 2 | 3 | import com.nodename.geom.Circle; 4 | import com.nodename.delaunay.IDisposable; 5 | import com.nodename.geom.Point; 6 | import com.nodename.geom.Rectangle; 7 | 8 | using com.nodename.delaunay.ArrayHelper; 9 | 10 | 11 | class SiteList implements IDisposable { 12 | private var _sites:Array; 13 | private var _currentIndex:Int; 14 | 15 | private var _sorted:Bool; 16 | 17 | public function new() 18 | { 19 | _sites = new Array(); 20 | _sorted = false; 21 | } 22 | 23 | public function dispose():Void 24 | { 25 | if (_sites != null) 26 | { 27 | for (site in _sites) 28 | { 29 | site.dispose(); 30 | } 31 | _sites.clear(); 32 | _sites = null; 33 | } 34 | } 35 | 36 | public function push(site:Site):Int 37 | { 38 | _sorted = false; 39 | return _sites.push(site); 40 | } 41 | 42 | public var length(get, null) : Int; 43 | inline private function get_length():Int { 44 | return _sites.length; 45 | } 46 | 47 | public function next():Site 48 | { 49 | if (_sorted == false) 50 | { 51 | throw "SiteList::next(): sites have not been sorted"; 52 | } 53 | if (_currentIndex < _sites.length) 54 | { 55 | return _sites[_currentIndex++]; 56 | } 57 | else 58 | { 59 | return null; 60 | } 61 | } 62 | 63 | public function getSitesBounds():Rectangle { 64 | if (_sorted == false) 65 | { 66 | Site.sortSites(_sites); 67 | _currentIndex = 0; 68 | _sorted = true; 69 | } 70 | var xmin:Float; 71 | var xmax:Float; 72 | var ymin:Float; 73 | var ymax:Float; 74 | if (_sites.length == 0) 75 | { 76 | return new Rectangle(0, 0, 0, 0); 77 | } 78 | 79 | xmin = Math.POSITIVE_INFINITY; 80 | xmax = Math.NEGATIVE_INFINITY; 81 | for (site in _sites) 82 | { 83 | if (site.x < xmin) 84 | { 85 | xmin = site.x; 86 | } 87 | if (site.x > xmax) 88 | { 89 | xmax = site.x; 90 | } 91 | } 92 | // here's where we assume that the sites have been sorted on y: 93 | ymin = _sites[0].y; 94 | ymax = _sites[_sites.length - 1].y; 95 | 96 | return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin); 97 | } 98 | 99 | public function siteColors():Array 100 | { 101 | var colors = new Array(); 102 | for (site in _sites) 103 | { 104 | colors.push(site.color); 105 | } 106 | return colors; 107 | } 108 | 109 | public function siteCoords():Array 110 | { 111 | var coords:Array = new Array(); 112 | for (site in _sites) 113 | { 114 | coords.push(site.coord); 115 | } 116 | return coords; 117 | } 118 | 119 | public function sites():Array { 120 | return _sites; 121 | } 122 | 123 | /** 124 | * 125 | * @return the largest circle centered at each site that fits in its region; 126 | * if the region is infinite, return a circle of radius 0. 127 | * 128 | */ 129 | public function circles():Array 130 | { 131 | var circles = new Array(); 132 | for (site in _sites) { 133 | var nearestEdge = site.nearestEdge(); 134 | 135 | var radius = (!nearestEdge.isPartOfConvexHull())? (nearestEdge.sitesDistance() * 0.5): 0; 136 | circles.push(new Circle(site.x, site.y, radius)); 137 | } 138 | return circles; 139 | } 140 | 141 | public function regions(plotBounds:Rectangle):Array> { 142 | var regions = new Array>(); 143 | for (site in _sites) 144 | { 145 | regions.push(site.region(plotBounds)); 146 | } 147 | return regions; 148 | } 149 | 150 | /** 151 | * 152 | * @param x 153 | * @param y 154 | * @return coordinates of nearest Site to (x, y) 155 | * 156 | */ 157 | public function nearestSitePoint(x:Int, y:Int):Point 158 | { 159 | var res = null; 160 | var p = new Point(x, y); 161 | var minDistSqr = Math.POSITIVE_INFINITY; 162 | 163 | for (site in _sites) { 164 | var q = site.coord; 165 | var distSqr = Point.distanceSquared(p, q); 166 | if (distSqr < minDistSqr) { 167 | minDistSqr = distSqr; 168 | res = site; 169 | } 170 | } 171 | 172 | return res != null ? res.coord : null; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /com/nodename/delaunay/Triangle.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.delaunay; 2 | 3 | import com.nodename.geom.Point; 4 | 5 | using com.nodename.delaunay.ArrayHelper; 6 | 7 | 8 | class Triangle { 9 | public var sites(get, null) : Array; 10 | inline private function get_sites():Array { 11 | return sites; 12 | } 13 | 14 | public var points(get, null) : Array; 15 | inline private function get_points():Array { 16 | return points; 17 | } 18 | 19 | public function new(a:Site, b:Site, c:Site) { 20 | sites = [a, b, c]; 21 | points = [a.coord, b.coord, c.coord]; 22 | } 23 | 24 | public function dispose():Void { 25 | sites.clear(); 26 | sites = null; 27 | points.clear(); 28 | points = null; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /com/nodename/delaunay/Vertex.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.delaunay; 2 | 3 | import com.nodename.geom.Point; 4 | 5 | 6 | class Vertex implements ICoord 7 | { 8 | public static var VERTEX_AT_INFINITY:Vertex = new Vertex(Math.NaN, Math.NaN); 9 | 10 | private static var _pool:Array = new Array(); 11 | private static function create(x:Float, y:Float):Vertex 12 | { 13 | if (Math.isNaN(x) || Math.isNaN(y)) 14 | { 15 | return VERTEX_AT_INFINITY; 16 | } 17 | if (_pool.length > 0) 18 | { 19 | return _pool.pop().init(x, y); 20 | } 21 | else 22 | { 23 | return new Vertex(x, y); 24 | } 25 | } 26 | 27 | 28 | private static var _nvertices:Int = 0; 29 | 30 | public var coord(get, null):Point; 31 | inline private function get_coord():Point { 32 | return coord; 33 | } 34 | 35 | public var vertexIndex(get, null):Int; 36 | inline private function get_vertexIndex():Int 37 | { 38 | return vertexIndex; 39 | } 40 | 41 | // Should be private 42 | public function new(x:Float, y:Float) { 43 | init(x, y); 44 | } 45 | 46 | private function init(x:Float, y:Float):Vertex { 47 | coord = new Point(x, y); 48 | return this; 49 | } 50 | 51 | public function dispose():Void { 52 | coord = null; 53 | _pool.push(this); 54 | } 55 | 56 | inline public function setIndex():Void { 57 | vertexIndex = _nvertices++; 58 | } 59 | 60 | public function toString():String 61 | { 62 | return "Vertex (" + vertexIndex + ")"; 63 | } 64 | 65 | /** 66 | * This is the only way to make a Vertex 67 | * 68 | * @param halfedge0 69 | * @param halfedge1 70 | * @return 71 | * 72 | */ 73 | public static function intersect(halfedge0:Halfedge, halfedge1:Halfedge):Vertex { 74 | var halfedge:Halfedge; 75 | var determinant:Float; 76 | var intersectionX:Float; 77 | var intersectionY:Float; 78 | var rightOfSite:Bool; 79 | 80 | var edge0 = halfedge0.edge; 81 | var edge1 = halfedge1.edge; 82 | if (edge0 == null || edge1 == null) { 83 | return null; 84 | } 85 | if (edge0.rightSite == edge1.rightSite) { 86 | return null; 87 | } 88 | 89 | determinant = edge0.a * edge1.b - edge0.b * edge1.a; 90 | if (-1.0e-10 < determinant && determinant < 1.0e-10) 91 | { 92 | // the edges are parallel 93 | return null; 94 | } 95 | 96 | var oneOverDet = 1 / determinant; 97 | intersectionX = (edge0.c * edge1.b - edge1.c * edge0.b)*oneOverDet; 98 | intersectionY = (edge1.c * edge0.a - edge0.c * edge1.a)*oneOverDet; 99 | 100 | var edge:Edge; 101 | if (Voronoi.isInfSite(edge0.rightSite, edge1.rightSite)) { 102 | halfedge = halfedge0; edge = edge0; 103 | } else { 104 | halfedge = halfedge1; edge = edge1; 105 | } 106 | rightOfSite = intersectionX >= edge.rightSite.x; 107 | if ((rightOfSite && halfedge.leftRight == LR.LEFT) 108 | || (!rightOfSite && halfedge.leftRight == LR.RIGHT)) 109 | { 110 | return null; 111 | } 112 | 113 | return Vertex.create(intersectionX, intersectionY); 114 | } 115 | public var x(get, null):Float; 116 | public var y(get, null):Float; 117 | inline private function get_x():Float { 118 | return coord.x; 119 | } 120 | inline private function get_y():Float { 121 | return coord.y; 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /com/nodename/delaunay/Voronoi.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * The author of this software is Steven Fortune. Copyright (c) 1994 by AT&T 3 | * Bell Laboratories. 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose without fee is hereby granted, provided that this entire notice 6 | * is included in all copies of any software which is or includes a copy 7 | * or modification of this software and in all copies of the supporting 8 | * documentation for such software. 9 | * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED 10 | * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY 11 | * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY 12 | * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. 13 | */ 14 | 15 | 16 | package com.nodename.delaunay; 17 | 18 | import com.nodename.delaunay.Kruskal.SortType; 19 | import com.nodename.geom.Circle; 20 | import com.nodename.geom.LineSegment; 21 | import com.nodename.geom.Point; 22 | import com.nodename.geom.Rectangle; 23 | import com.nodename.delaunay.SelectHelper; 24 | 25 | using com.nodename.delaunay.ArrayHelper; 26 | 27 | class Voronoi { 28 | private var _sites:SiteList; 29 | private var _sitesIndexedByLocation:Map; 30 | private var _triangles:Array; 31 | private var _edges:Array; 32 | 33 | 34 | // TODO generalize this so it doesn't have to be a rectangle; 35 | // then we can make the fractal voronois-within-voronois 36 | private var _plotBounds:Rectangle; 37 | public function getPlotBounds():Rectangle 38 | { 39 | return _plotBounds; 40 | } 41 | 42 | public function dispose(): Void { 43 | var i, n:Int; 44 | if (_sites != null) 45 | { 46 | _sites.dispose(); 47 | _sites = null; 48 | } 49 | if (_triangles != null) 50 | { 51 | n = _triangles.length; 52 | for (i in 0...n) 53 | { 54 | _triangles[i].dispose(); 55 | } 56 | _triangles.clear(); 57 | _triangles = null; 58 | } 59 | if (_edges != null) 60 | { 61 | n = _edges.length; 62 | for (i in 0...n) 63 | { 64 | _edges[i].dispose(); 65 | } 66 | _edges.clear(); 67 | _edges = null; 68 | } 69 | _plotBounds = null; 70 | _sitesIndexedByLocation = null; 71 | } 72 | 73 | public function new(points:Array, colors:Array, plotBounds:Rectangle) 74 | { 75 | _sites = new SiteList(); 76 | _sitesIndexedByLocation = new Map(); 77 | addSites(points, colors); 78 | _plotBounds = plotBounds; 79 | _triangles = new Array(); 80 | _edges = new Array(); 81 | fortunesAlgorithm(); 82 | } 83 | 84 | private function addSites(points:Array, colors:Array):Void 85 | { 86 | var length:Int = points.length; 87 | for (i in 0 ... length) 88 | { 89 | addSite(points[i], colors != null ? colors[i] : 0, i); 90 | } 91 | } 92 | 93 | private function addSite(p:Point, color:Int, index:Int):Void 94 | { 95 | var weight:Float = Math.random() * 100; 96 | var site:Site = Site.create(p, index, weight, color); 97 | _sites.push(site); 98 | _sitesIndexedByLocation[p] = site; 99 | } 100 | 101 | public function region(p:Point):Array 102 | { 103 | var site:Site = _sitesIndexedByLocation[p]; 104 | if (site == null) 105 | { 106 | return new Array(); 107 | } 108 | return site.region(_plotBounds); 109 | } 110 | 111 | public function neighborSitesForSite(coord:Point):Array 112 | { 113 | var points:Array = new Array(); 114 | var site:Site = _sitesIndexedByLocation[coord]; 115 | if (site == null) 116 | { 117 | return points; 118 | } 119 | var sites:Array = site.neighborSites(); 120 | var neighbor:Site; 121 | for (neighbor in sites) 122 | { 123 | points.push(neighbor.coord); 124 | } 125 | return points; 126 | } 127 | 128 | public function circles():Array 129 | { 130 | return _sites.circles(); 131 | } 132 | 133 | public function edges() : Array { 134 | return _edges; 135 | } 136 | 137 | public function sites() : SiteList { 138 | return _sites; 139 | } 140 | 141 | public function voronoiBoundaryForSite(coord:Point):Array 142 | { 143 | return SelectHelper.visibleLineSegments(SelectHelper.selectEdgesForSitePoint(coord, _edges)); 144 | } 145 | 146 | public function delaunayLinesForSite(coord:Point):Array 147 | { 148 | return SelectHelper.delaunayLinesForEdges(SelectHelper.selectEdgesForSitePoint(coord, _edges)); 149 | } 150 | 151 | public function voronoiDiagram():Array 152 | { 153 | return SelectHelper.visibleLineSegments(_edges); 154 | } 155 | 156 | public function delaunayTriangulation():Array 157 | { 158 | return SelectHelper.delaunayLinesForEdges(_edges); 159 | } 160 | 161 | public function hull():Array 162 | { 163 | return SelectHelper.delaunayLinesForEdges(hullEdges()); 164 | } 165 | 166 | inline static function myTest(edge:Edge):Bool 167 | { 168 | return edge.isPartOfConvexHull(); 169 | } 170 | 171 | private function hullEdges():Array { 172 | return _edges.filter(myTest); 173 | } 174 | 175 | public function hullPointsInOrder():Array 176 | { 177 | var hullEdges:Array = hullEdges(); 178 | 179 | var points:Array = new Array(); 180 | if (hullEdges.length == 0) 181 | { 182 | return points; 183 | } 184 | 185 | var reorderer:EdgeReorderer = new EdgeReorderer(hullEdges, EdgeReorderer.edgeToLeftSite, EdgeReorderer.edgeToRightSite); 186 | hullEdges = reorderer.edges; 187 | var orientations:Array = reorderer.edgeOrientations; 188 | reorderer.dispose(); 189 | 190 | var orientation:LR; 191 | 192 | var n:Int = hullEdges.length; 193 | for (i in 0...n) 194 | { 195 | var edge:Edge = hullEdges[i]; 196 | orientation = orientations[i]; 197 | points.push(edge.site(orientation).coord); 198 | } 199 | return points; 200 | } 201 | 202 | public function spanningTree(type:SortType = null):Array { 203 | var segments = SelectHelper.delaunayLinesForEdges(_edges); 204 | return Kruskal.kruskal(segments, type); 205 | } 206 | 207 | public function regions():Array> 208 | { 209 | return _sites.regions(_plotBounds); 210 | } 211 | 212 | public function siteColors():Array 213 | { 214 | return _sites.siteColors(); 215 | } 216 | 217 | public function triangles():Array{ 218 | return _triangles; 219 | } 220 | 221 | /** 222 | * 223 | * @param x 224 | * @param y 225 | * @return coordinates of nearest Site to (x, y) 226 | * 227 | */ 228 | public function nearestSitePoint(x:Int, y:Int):Point 229 | { 230 | return _sites.nearestSitePoint(x, y); 231 | } 232 | 233 | public function siteCoords():Array { 234 | return _sites.siteCoords(); 235 | } 236 | 237 | private function fortunesAlgorithm():Void { 238 | 239 | var newSite:Site; 240 | var newintstar:Point = null; 241 | 242 | var dataBounds = _sites.getSitesBounds(); 243 | 244 | var sqrt_nsites = Std.int(Math.sqrt(_sites.length + 4)); 245 | var heap = new HalfedgePriorityQueue(dataBounds.y, dataBounds.height, sqrt_nsites); 246 | var edgeList = new EdgeList(dataBounds.x, dataBounds.width, sqrt_nsites); 247 | var halfEdges = new Array(); 248 | var vertices = new Array(); 249 | 250 | var bottomMostSite:Site = _sites.next(); 251 | 252 | var leftRegion = function(he:Halfedge):Site { 253 | var edge:Edge = he.edge; 254 | if (edge == null) 255 | { 256 | return bottomMostSite; 257 | } 258 | return edge.site(he.leftRight); 259 | } 260 | 261 | var rightRegion = function(he:Halfedge):Site { 262 | var edge:Edge = he.edge; 263 | if (edge == null) 264 | { 265 | return bottomMostSite; 266 | } 267 | return edge.site(LR.other(he.leftRight)); 268 | } 269 | 270 | newSite = _sites.next(); 271 | 272 | while (true) { 273 | 274 | if (heap.empty() == false) { 275 | newintstar = heap.min(); 276 | } 277 | 278 | if (newSite != null && (heap.empty() || isInf(newSite, newintstar))) { 279 | 280 | /* new site is smallest */ 281 | //trace("smallest: new site " + newSite); 282 | // trace("edgeList" + edgeList); 283 | // Step 8: 284 | var lbnd = edgeList.edgeListLeftNeighbor(newSite.coord); // the Halfedge just to the left of newSite 285 | var rbnd = lbnd.edgeListRightNeighbor; // the Halfedge just to the right 286 | var bottomSite = rightRegion(lbnd); // this is the same as leftRegion(rbnd) 287 | // this Site determines the region containing the new site 288 | //trace("new Site is in region of existing site: " + bottomSite); 289 | 290 | // Step 9: 291 | var edge = Edge.createBisectingEdge(bottomSite, newSite); 292 | //trace("new edge: " + edge); 293 | _edges.push(edge); 294 | 295 | var bisector = Halfedge.create(edge, LR.LEFT); 296 | halfEdges.push(bisector); 297 | // inserting two Halfedges into edgeList constitutes Step 10: 298 | // insert bisector to the right of lbnd: 299 | edgeList.insert(lbnd, bisector); 300 | 301 | // first half of Step 11: 302 | var vertex = Vertex.intersect(lbnd, bisector); 303 | if (vertex != null) { 304 | heap.remove(lbnd); 305 | 306 | vertices.push(vertex); 307 | lbnd.vertex = vertex; 308 | lbnd.ystar = vertex.y + newSite.dist(vertex); 309 | heap.insert(lbnd); 310 | } 311 | 312 | lbnd = bisector; 313 | bisector = Halfedge.create(edge, LR.RIGHT); 314 | halfEdges.push(bisector); 315 | // second Halfedge for Step 10: 316 | // insert bisector to the right of lbnd: 317 | edgeList.insert(lbnd, bisector); 318 | 319 | // second half of Step 11: 320 | var vertex = Vertex.intersect(bisector, rbnd); 321 | if (vertex != null) { 322 | vertices.push(vertex); 323 | bisector.vertex = vertex; 324 | bisector.ystar = vertex.y + newSite.dist(vertex); 325 | heap.insert(bisector); 326 | } 327 | 328 | newSite = _sites.next(); 329 | } else if (heap.empty() == false) { 330 | 331 | /* intersection is smallest */ 332 | var lbnd = heap.extractMin(); 333 | var llbnd = lbnd.edgeListLeftNeighbor; 334 | var rbnd = lbnd.edgeListRightNeighbor; 335 | var rrbnd = rbnd.edgeListRightNeighbor; 336 | var bottomSite = leftRegion(lbnd); 337 | var topSite = rightRegion(rbnd); 338 | // these three sites define a Delaunay triangle 339 | // (not actually using these for anything...) 340 | _triangles.push(new Triangle(bottomSite, topSite, rightRegion(lbnd))); 341 | 342 | var v = lbnd.vertex; 343 | v.setIndex(); 344 | lbnd.edge.setVertex(lbnd.leftRight, v); 345 | rbnd.edge.setVertex(rbnd.leftRight, v); 346 | edgeList.remove(lbnd); 347 | heap.remove(rbnd); 348 | edgeList.remove(rbnd); 349 | 350 | var leftRight = LR.LEFT; 351 | if (bottomSite.y > topSite.y) { 352 | var tempSite = bottomSite; 353 | bottomSite = topSite; 354 | topSite = tempSite; 355 | leftRight = LR.RIGHT; 356 | } 357 | 358 | var edge = Edge.createBisectingEdge(bottomSite, topSite); 359 | _edges.push(edge); 360 | var bisector = Halfedge.create(edge, leftRight); 361 | halfEdges.push(bisector); 362 | edgeList.insert(llbnd, bisector); 363 | edge.setVertex(LR.other(leftRight), v); 364 | var vertex = Vertex.intersect(llbnd, bisector); 365 | if (vertex != null) { 366 | heap.remove(llbnd); 367 | 368 | vertices.push(vertex); 369 | llbnd.vertex = vertex; 370 | llbnd.ystar = vertex.y + bottomSite.dist(vertex); 371 | heap.insert(llbnd); 372 | } 373 | vertex = Vertex.intersect(bisector, rrbnd); 374 | if (vertex != null) { 375 | 376 | vertices.push(vertex); 377 | bisector.vertex = vertex; 378 | bisector.ystar = vertex.y + bottomSite.dist(vertex); 379 | heap.insert(bisector); 380 | } 381 | } else { 382 | break; 383 | } 384 | } 385 | 386 | // heap should be empty now 387 | heap.dispose(); 388 | edgeList.dispose(); 389 | 390 | for (halfEdge in halfEdges) { 391 | halfEdge.reallyDispose(); 392 | } 393 | halfEdges.clear(); 394 | 395 | // we need the vertices to clip the edges 396 | for (edge in _edges) { 397 | edge.clipVertices(_plotBounds); 398 | } 399 | // but we don't actually ever use them again! 400 | for (vertex in vertices) { 401 | vertex.dispose(); 402 | } 403 | vertices.clear(); 404 | } 405 | 406 | 407 | inline static public function isInf(s1:Site, s2:Point) : Bool { 408 | return (s1.y < s2.y) || (s1.y == s2.y && s1.x < s2.x); 409 | } 410 | 411 | inline static public function isInfSite(s1:Site, s2:Site) : Bool { 412 | return (s1.y < s2.y) || (s1.y == s2.y && s1.x < s2.x); 413 | } 414 | 415 | public static function compareByYThenX(s1:Site, s2:Site):Int { 416 | /* 417 | return 418 | (s1.y < s2.y)? -1 :( 419 | (s1.y > s2.y)? 1:( 420 | (s1.x < s2.x)? -1:( 421 | (s1.x > s2.x)? 1: 0))); 422 | */ 423 | if (s1.y < s2.y) return -1; 424 | if (s1.y > s2.y) return 1; 425 | if (s1.x < s2.x) return -1; 426 | if (s1.x > s2.x) return 1; 427 | return 0; 428 | } 429 | 430 | } 431 | -------------------------------------------------------------------------------- /com/nodename/geom/Circle.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.geom; 2 | 3 | 4 | class Circle { 5 | public var center:Point; 6 | public var radius:Float; 7 | 8 | public function new(centerX:Float, centerY:Float, radius:Float) 9 | { 10 | this.center = new Point(centerX, centerY); 11 | this.radius = radius; 12 | } 13 | 14 | public function toString():String 15 | { 16 | return "Circle (center: " + center + "; radius: " + radius + ")"; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /com/nodename/geom/LineSegment.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.geom; 2 | 3 | import com.nodename.geom.Point; 4 | 5 | class LineSegment { 6 | public static function compareLengths_MAX(segment0:LineSegment, segment1:LineSegment):Int { 7 | var length0 = Point.distanceSquared(segment0.p0, segment0.p1); 8 | var length1 = Point.distanceSquared(segment1.p0, segment1.p1); 9 | if (length0 < length1) { 10 | return 1; 11 | } else if (length0 > length1) { 12 | return -1; 13 | } else { 14 | return 0; 15 | } 16 | } 17 | 18 | public static function compareLengths(edge0:LineSegment, edge1:LineSegment):Int { 19 | return - compareLengths_MAX(edge0, edge1); 20 | } 21 | 22 | public var p0:Point; 23 | public var p1:Point; 24 | 25 | public function new(p0:Point, p1:Point) { 26 | this.p0 = p0; 27 | this.p1 = p1; 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /com/nodename/geom/Point.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.geom; 2 | 3 | 4 | class Point { 5 | 6 | public var x:Float; 7 | public var y:Float; 8 | 9 | public function new(x:Float = 0, y:Float = 0) { 10 | this.x = x; 11 | this.y = y; 12 | } 13 | 14 | public function setTo(x:Float, y:Float):Point { 15 | this.x = x; 16 | this.y = y; 17 | return this; 18 | } 19 | 20 | public function normalize(length:Float):Point { 21 | var denom = Math.sqrt(x * x + y * y); 22 | if (denom != 0) { 23 | var f = length / denom; 24 | x *= f; 25 | y *= f; 26 | } 27 | return this; 28 | } 29 | 30 | public function clone():Point { 31 | return new Point(x, y); 32 | } 33 | 34 | public function toString():String { 35 | return '(${x}, ${y})'; 36 | } 37 | 38 | static public function distance(p:Point, q:Point):Float { 39 | return Math.sqrt(distanceSquared(p, q)); 40 | } 41 | 42 | static public function distanceSquared(p:Point, q:Point):Float { 43 | var dx = p.x - q.x; 44 | var dy = p.y - q.y; 45 | return dx * dx + dy * dy; 46 | } 47 | } -------------------------------------------------------------------------------- /com/nodename/geom/Polygon.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.geom; 2 | 3 | 4 | class Polygon { 5 | private var _vertices:Array; 6 | 7 | public function new (vertices:Array) { 8 | _vertices = vertices; 9 | } 10 | 11 | public function area():Float { 12 | return Math.abs(signedDoubleArea() * 0.5); 13 | } 14 | 15 | public function winding():Winding { 16 | var signedDoubleArea = signedDoubleArea(); 17 | if (signedDoubleArea < 0) { 18 | return Winding.CLOCKWISE; 19 | } else if (signedDoubleArea > 0) { 20 | return Winding.COUNTERCLOCKWISE; 21 | } else { 22 | return Winding.NONE; 23 | } 24 | } 25 | 26 | private function signedDoubleArea():Float 27 | { 28 | var index:Int, nextIndex:Int; 29 | var n:Int = _vertices.length; 30 | var point:Point, next:Point; 31 | var signedDoubleArea = 0.0; 32 | for (index in 0...n) 33 | { 34 | nextIndex = (index + 1) % n; 35 | point = _vertices[index]; 36 | next = _vertices[nextIndex]; 37 | signedDoubleArea += point.x * next.y - next.x * point.y; 38 | } 39 | return signedDoubleArea; 40 | } 41 | } -------------------------------------------------------------------------------- /com/nodename/geom/Rectangle.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.geom; 2 | 3 | 4 | class Rectangle { 5 | 6 | public var x:Float; 7 | public var y:Float; 8 | public var width:Float; 9 | public var height:Float; 10 | 11 | public function new(x:Float = 0, y:Float = 0, w:Float = 0, h:Float = 0) { 12 | this.x = x; 13 | this.y = y; 14 | this.width = w; 15 | this.height = h; 16 | } 17 | 18 | public var left(get, never):Float; 19 | inline private function get_left():Float { 20 | return x; 21 | } 22 | 23 | public var right(get, never):Float; 24 | inline private function get_right():Float { 25 | return x + width; 26 | } 27 | 28 | public var top(get, never):Float; 29 | inline private function get_top():Float { 30 | return y; 31 | } 32 | 33 | public var bottom(get, never):Float; 34 | inline private function get_bottom():Float { 35 | return y + height; 36 | } 37 | 38 | public function setTo(x:Float, y:Float, w:Float, h:Float):Rectangle { 39 | this.x = x; 40 | this.y = y; 41 | this.width = w; 42 | this.height = h; 43 | return this; 44 | } 45 | 46 | public function clone():Rectangle { 47 | return new Rectangle(x, y, width, height); 48 | } 49 | 50 | public function toString():String { 51 | return '(${x}, ${y}, ${width}x${height})'; 52 | } 53 | } -------------------------------------------------------------------------------- /com/nodename/geom/Winding.hx: -------------------------------------------------------------------------------- 1 | package com.nodename.geom; 2 | 3 | enum Winding { 4 | CLOCKWISE; 5 | COUNTERCLOCKWISE; 6 | NONE; 7 | } -------------------------------------------------------------------------------- /hxDelaunayTest.hxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | "$(CompilerPath)/haxelib" run lime build "$(OutputFile)" $(TargetBuild) -$(BuildConfig) -Dfdb 50 | 51 | 52 | 53 | 54 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /screenshots/delaunay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azrafe7/hxDelaunay/75a8a0d774196b3b0dfac6e2274e155a30060dce/screenshots/delaunay.png -------------------------------------------------------------------------------- /screenshots/girl-pearl-earring-320.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azrafe7/hxDelaunay/75a8a0d774196b3b0dfac6e2274e155a30060dce/screenshots/girl-pearl-earring-320.png -------------------------------------------------------------------------------- /screenshots/girl-pearl-earring-voronoi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azrafe7/hxDelaunay/75a8a0d774196b3b0dfac6e2274e155a30060dce/screenshots/girl-pearl-earring-voronoi.png -------------------------------------------------------------------------------- /screenshots/lena-320.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azrafe7/hxDelaunay/75a8a0d774196b3b0dfac6e2274e155a30060dce/screenshots/lena-320.png -------------------------------------------------------------------------------- /screenshots/lena-voronoi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azrafe7/hxDelaunay/75a8a0d774196b3b0dfac6e2274e155a30060dce/screenshots/lena-voronoi.png -------------------------------------------------------------------------------- /screenshots/mona-lisa-320.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azrafe7/hxDelaunay/75a8a0d774196b3b0dfac6e2274e155a30060dce/screenshots/mona-lisa-320.png -------------------------------------------------------------------------------- /screenshots/mona-lisa-hollow-voronoi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azrafe7/hxDelaunay/75a8a0d774196b3b0dfac6e2274e155a30060dce/screenshots/mona-lisa-hollow-voronoi.png -------------------------------------------------------------------------------- /screenshots/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azrafe7/hxDelaunay/75a8a0d774196b3b0dfac6e2274e155a30060dce/screenshots/screenshot.png -------------------------------------------------------------------------------- /screenshots/starry-night-320.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azrafe7/hxDelaunay/75a8a0d774196b3b0dfac6e2274e155a30060dce/screenshots/starry-night-320.png -------------------------------------------------------------------------------- /screenshots/starry-night-voronoi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azrafe7/hxDelaunay/75a8a0d774196b3b0dfac6e2274e155a30060dce/screenshots/starry-night-voronoi.png -------------------------------------------------------------------------------- /src/Demo.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | 4 | import com.nodename.delaunay.Voronoi; 5 | import com.nodename.geom.LineSegment; 6 | import com.nodename.geom.Point; 7 | import com.nodename.geom.Rectangle; 8 | import flash.display.Bitmap; 9 | import flash.display.BitmapData; 10 | import flash.display.Graphics; 11 | import flash.display.Sprite; 12 | import flash.events.Event; 13 | import flash.events.KeyboardEvent; 14 | import flash.events.MouseEvent; 15 | import flash.filters.GlowFilter; 16 | import flash.system.System; 17 | import flash.text.TextField; 18 | import flash.text.TextFieldAutoSize; 19 | import flash.text.TextFormat; 20 | import flash.text.TextFormatAlign; 21 | import flash.ui.Keyboard; 22 | import haxe.Timer; 23 | import openfl.Assets; 24 | 25 | 26 | using StringTools; 27 | 28 | 29 | @:bitmap("assets/mona-lisa.png") 30 | class Picture extends flash.display.BitmapData {} 31 | 32 | @:bitmap("assets/mona-lisa-mini.png") 33 | class PictureMini extends flash.display.BitmapData {} 34 | 35 | /** 36 | * hxDelaunay openFL demo. 37 | * 38 | * @author azrafe7 39 | */ 40 | class Demo extends Sprite { 41 | 42 | private var g:Graphics; 43 | 44 | private var POINT_COLOR:Int = 0x00F000; 45 | private var REGION_COLOR:Int = 0x4000F0; 46 | private var MIN_FILL_COLOR:Int = 0x200040; 47 | private var MAX_FILL_COLOR:Int = 0x4000A0; 48 | private var TRIANGLE_COLOR:Int = 0xF00000; 49 | private var HULL_COLOR:Int = 0x30F090; 50 | private var TREE_COLOR:Int = 0xF0C020; 51 | private var ONION_COLOR:Int = 0x10A0FF; 52 | private var SELECTED_COLOR:Int = 0x8020F0; 53 | private var CENTROID_COLOR:Int = 0x111111; 54 | private var PICTURE:String = "assets/mona-lisa.png"; 55 | private var PICTURE_MINI:String = "assets/mona-lisa-mini.png"; 56 | 57 | private var THICKNESS:Float = 1.5; 58 | private var ALPHA:Float = 1.; 59 | private var FILL_ALPHA:Float = 1.; 60 | private var SAMPLE_FILL_ALPHA:Float = .8; 61 | private var CENTROID_ALPHA:Float = .5; 62 | 63 | private var TEXT_COLOR:Int = 0xFFFFFF; 64 | private var TEXT_FONT:String = "_typewriter"; 65 | private var TEXT_SIZE:Int = 12; 66 | private var TEXT_OUTLINE:GlowFilter = new GlowFilter(0xFF000000, 1, 2, 2, 6); 67 | 68 | private var BOUNDS:Rectangle = new Rectangle(0, 0, 500, 500); 69 | 70 | private var voronoi:Voronoi; 71 | private var nPoints:Int = 25; 72 | private var points:Array; 73 | private var centroids:Array; 74 | private var directions:Array; 75 | private var regions:Array>; 76 | private var sortedRegions:Array>; 77 | private var fillColors:Array; 78 | private var triangles:Array; 79 | private var hull:Array; 80 | private var tree:Array; 81 | private var onion:Array>; 82 | private var proxymitySprite:Sprite; 83 | private var proxymityMap:BitmapData; 84 | private var selectedRegion:Array; 85 | private var pictureBMD:BitmapData; 86 | private var pictureBitmap:Bitmap; 87 | private var pictureMiniBMD:BitmapData; // downscaled by a factor of 4 88 | 89 | private var isMouseDown:Bool = false; 90 | private var prevMousePos:Point = new Point(); 91 | private var mousePos:Point = new Point(); 92 | 93 | private var showPoints:Bool = true; 94 | private var showRegions:Bool = true; 95 | private var fillRegions:Bool = false; 96 | private var showTriangles:Bool = false; 97 | private var fillTriangles:Bool = false; 98 | private var showHull:Bool = false; 99 | private var showTree:Bool = false; 100 | private var showOnion:Bool = false; 101 | private var showProximityMap:Bool = false; 102 | 103 | private var relax:Bool = false; 104 | private var animate:Bool = false; 105 | private var sampleImage:Bool = false; 106 | 107 | private var startTime:Float = 0; 108 | private var dt:Float = 0; 109 | 110 | private var text:TextField; 111 | 112 | private var TEXT:String = 113 | " hxDelaunay \n" + 114 | " (ported by azrafe7)\n" + 115 | "\n" + 116 | " TOGGLE:\n\n" + 117 | " 1 points : |POINTS|\n" + 118 | " 2 regions : |REGIONS|\n" + 119 | " 3 fill regions : |FILL_REGIONS|\n" + 120 | " 4 triangles : |TRIANGLES|\n" + 121 | " 5 fill tris : |FILL_TRIANGLES|\n" + 122 | " 6 convex hull : |HULL|\n" + 123 | " 7 spanning tree : |TREE|\n" + 124 | " 8 onion : |ONION|\n" + 125 | " 9 proximity map : |PROXIMITY|\n" + 126 | "\n" + 127 | " X relax : |RELAX|\n" + 128 | " A animate : |ANIMATE|\n" + 129 | " P picture : |PICTURE|\n" + 130 | "\n" + 131 | " POINTS: (|NPOINTS|)\n\n" + 132 | " [SHIFT] + \n" + 133 | " ▲ add point/s\n" + 134 | " ▼ remove point/s\n" + 135 | "\n" + 136 | " R randomize\n" + 137 | #if (openfl && !nme) 138 | "\n" + 139 | " S save png\n" + 140 | #end 141 | "\n" + 142 | " click & drag to\n" + 143 | " move region point" + 144 | "\n"; 145 | 146 | var sprite:Sprite; 147 | 148 | public function new () { 149 | super (); 150 | 151 | sprite = new Sprite(); 152 | addChild(sprite); 153 | g = sprite.graphics; 154 | g.lineStyle(THICKNESS, TEXT_COLOR, ALPHA); 155 | 156 | addChild(text = getTextField(TEXT, BOUNDS.width + 10, 15)); 157 | text.height = stage.stageHeight - text.y; // makes sure it's _fully_ visible 158 | 159 | // picture 160 | #if (!jsprime) 161 | pictureBMD = new Picture(0, 0); 162 | pictureMiniBMD = new PictureMini(0, 0); 163 | #else 164 | pictureBMD = Assets.getBitmapData(PICTURE); 165 | pictureMiniBMD = Assets.getBitmapData(PICTURE_MINI); 166 | #end 167 | pictureBitmap = new Bitmap(pictureBMD); 168 | addChildAt(pictureBitmap, 0); 169 | 170 | // generate fill colors 171 | var MAX_COLORS = 10; 172 | fillColors = [for (i in 0...MAX_COLORS) colorLerp(MIN_FILL_COLOR, MAX_FILL_COLOR, i / MAX_COLORS)]; 173 | centroids = new Array(); 174 | directions = new Array(); 175 | 176 | // first random set of points 177 | points = new Array(); 178 | for (i in 0...nPoints) { 179 | points.push(new Point(Math.random() * BOUNDS.width, Math.random() * BOUNDS.height)); 180 | } 181 | 182 | update(); // recalc 183 | render(); // draw 184 | updateText(); // info 185 | 186 | startTime = Timer.stamp(); 187 | //stage.addChild(new FPS(5, 5, 0xFFFFFF)); 188 | stage.addEventListener(Event.ENTER_FRAME, onEnterFrame); 189 | stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown); 190 | stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); 191 | stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); 192 | stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove); 193 | } 194 | 195 | public function update():Void 196 | { 197 | if (voronoi != null) { 198 | voronoi.dispose(); 199 | voronoi = null; 200 | } 201 | voronoi = new Voronoi(points, null, BOUNDS); 202 | regions = [for (p in points) voronoi.region(p)]; 203 | sortedRegions = voronoi.regions(); 204 | triangles = voronoi.delaunayTriangulation(); 205 | hull = voronoi.hull(); 206 | tree = voronoi.spanningTree(); 207 | onion = calcOnion(voronoi); 208 | updateProximityMap(); 209 | } 210 | 211 | public function updateText():Void 212 | { 213 | text.text = TEXT 214 | .replace("|POINTS|", bool2OnOff(showPoints)) 215 | .replace("|REGIONS|", bool2OnOff(showRegions)) 216 | .replace("|FILL_REGIONS|", bool2OnOff(fillRegions)) 217 | .replace("|TRIANGLES|", bool2OnOff(showTriangles)) 218 | .replace("|FILL_TRIANGLES|", bool2OnOff(fillTriangles)) 219 | .replace("|HULL|", bool2OnOff(showHull)) 220 | .replace("|TREE|", bool2OnOff(showTree)) 221 | .replace("|ONION|", bool2OnOff(showOnion)) 222 | .replace("|PROXIMITY|", bool2OnOff(showProximityMap)) 223 | .replace("|NPOINTS|", Std.string(nPoints)) 224 | .replace("|RELAX|", bool2OnOff(relax)) 225 | .replace("|ANIMATE|", bool2OnOff(animate)) 226 | .replace("|PICTURE|", bool2OnOff(sampleImage)); 227 | } 228 | 229 | public function render():Void { 230 | g.clear(); 231 | 232 | if (showRegions || fillRegions) drawRegions(); 233 | pictureBitmap.visible = (sampleImage && (!fillRegions || SAMPLE_FILL_ALPHA < FILL_ALPHA)); 234 | if (showProximityMap) { 235 | g.beginBitmapFill(proxymityMap); 236 | g.drawRect(0, 0, BOUNDS.width, BOUNDS.height); 237 | g.endFill(); 238 | } 239 | if (showTriangles || fillTriangles) drawTriangles(); 240 | if (showTree) drawTree(); 241 | if (showOnion) drawOnion(); 242 | if (showHull) drawHull(); 243 | if (showPoints) drawSiteCoords(); 244 | if (selectedRegion != null) drawPoints(selectedRegion, SELECTED_COLOR); 245 | if (relax && showPoints) drawCentroids(); 246 | } 247 | 248 | inline function bool2OnOff(v:Bool):String 249 | { 250 | return (v ? "[ON]" : "[OFF]"); 251 | } 252 | 253 | public function updateProximityMap():Void { 254 | if (proxymityMap == null) { 255 | proxymityMap = new BitmapData(Std.int(BOUNDS.width), Std.int(BOUNDS.height), false); 256 | proxymitySprite = new Sprite(); 257 | } 258 | var graphics = proxymitySprite.graphics; 259 | proxymityMap.fillRect(proxymityMap.rect, 0xFFFFFFFF); 260 | graphics.clear(); 261 | 262 | for (i in 0...sortedRegions.length) { 263 | graphics.lineStyle(1, i, 1); // no borders 264 | graphics.beginFill(i); 265 | var points = sortedRegions[i]; 266 | for (p in points) { 267 | if (p == points[0]) graphics.moveTo(p.x, p.y); 268 | else graphics.lineTo(p.x, p.y); 269 | } 270 | graphics.endFill(); 271 | } 272 | proxymityMap.draw(proxymitySprite); 273 | } 274 | 275 | public function calcOnion(voronoi:Voronoi):Array> 276 | { 277 | var res = new Array>(); 278 | var points = voronoi.siteCoords(); 279 | 280 | while (points.length > 2) { 281 | var v:Voronoi = new Voronoi(points, null, BOUNDS); 282 | var peel = v.hullPointsInOrder(); 283 | for (p in peel) points.remove(p); 284 | res.push(peel); 285 | v.dispose(); 286 | v = null; 287 | } 288 | if (points.length > 0) res.push(points); 289 | 290 | return res; 291 | } 292 | 293 | 294 | 295 | public function drawSiteCoords():Void 296 | { 297 | g.lineStyle(THICKNESS, POINT_COLOR, ALPHA); 298 | for (p in points) { 299 | g.drawCircle(p.x, p.y, 2); 300 | } 301 | } 302 | 303 | public function drawCentroids():Void 304 | { 305 | if (centroids.length < points.length) return; // wait next frame 306 | for (i in 0...points.length) { 307 | var c = centroids[i]; 308 | c.x = Math.round(c.x); c.y = Math.round(c.y); 309 | g.lineStyle(THICKNESS, CENTROID_COLOR, CENTROID_ALPHA); 310 | g.moveTo(c.x - 2, c.y); g.lineTo(c.x + 2, c.y); 311 | g.moveTo(c.x, c.y - 2); g.lineTo(c.x, c.y + 2); 312 | } 313 | } 314 | 315 | public function drawRegions():Void 316 | { 317 | if (!sampleImage) { 318 | var fillIdx = -1; 319 | for (region in regions) { 320 | fillIdx = (fillIdx + 1) % fillColors.length; 321 | var fillColor = fillRegions ? fillColors[fillIdx] : null; 322 | 323 | drawPoints(region, fillRegions && !showRegions ? fillColors[fillIdx] : REGION_COLOR, fillColor); 324 | } 325 | } else { 326 | for (p in points) { 327 | //var sampledColor = pictureBMD.getPixel(Std.int(p.x), Std.int(p.y)); // use fullscale bitmap for sampling 328 | var sampledColor = pictureMiniBMD.getPixel(Std.int(p.x / 4), Std.int(p.y / 4)); // use downscaled bitmap for sampling 329 | 330 | drawPoints(voronoi.region(p), fillRegions && showRegions ? REGION_COLOR : sampledColor, fillRegions ? sampledColor: null); 331 | } 332 | } 333 | } 334 | 335 | inline public function drawTriangles():Void 336 | { 337 | if (!sampleImage) { 338 | var fillIdx = -1; 339 | for (tri in voronoi.triangles()) { 340 | fillIdx = (fillIdx + 1) % fillColors.length; 341 | var fillColor = fillTriangles ? fillColors[fillIdx] & 0xFF0000 : null; 342 | 343 | drawPoints(tri.points, fillTriangles && !showTriangles ? fillColors[fillIdx] & 0xFF0000 : TRIANGLE_COLOR, fillColor); 344 | } 345 | } else { 346 | for (tri in voronoi.triangles()) { 347 | var p = getCentroid(tri.points); 348 | var sampledColor = pictureBMD.getPixel(Std.int(p.x), Std.int(p.y)); // use fullscale bitmap for sampling 349 | //var sampledColor = pictureMiniBMD.getPixel(Std.int(p.x / 4), Std.int(p.y / 4)); // use downscaled bitmap for sampling 350 | 351 | drawPoints(tri.points, fillTriangles && showTriangles ? TRIANGLE_COLOR: sampledColor, fillTriangles ? sampledColor: null); 352 | } 353 | } 354 | } 355 | 356 | inline public function drawHull():Void 357 | { 358 | drawSegments(hull, HULL_COLOR); 359 | } 360 | 361 | inline public function drawTree():Void 362 | { 363 | drawSegments(tree, TREE_COLOR); 364 | } 365 | 366 | public function drawOnion():Void 367 | { 368 | for (peel in onion) { 369 | drawPoints(peel, ONION_COLOR); 370 | } 371 | } 372 | 373 | 374 | 375 | // generic draw function for segments 376 | public function drawSegments(segments:Array, color:Int, ?fillColor:Int = null) { 377 | g.lineStyle(THICKNESS, color, ALPHA); 378 | if (fillColor != null) g.beginFill(fillColor, FILL_ALPHA); 379 | else g.beginFill(0, 0); 380 | 381 | for (segment in segments) { 382 | g.moveTo(segment.p0.x, segment.p0.y); 383 | g.lineTo(segment.p1.x, segment.p1.y); 384 | } 385 | 386 | g.endFill(); 387 | } 388 | 389 | 390 | // generic draw function for points 391 | public function drawPoints(points:Array, color:Int, ?fillColor:Int = null) { 392 | if (points == null || points.length == 0) return; 393 | 394 | g.lineStyle(THICKNESS, color, sampleImage ? SAMPLE_FILL_ALPHA : ALPHA); 395 | if (fillColor != null) g.beginFill(fillColor, sampleImage ? SAMPLE_FILL_ALPHA : FILL_ALPHA); 396 | else g.beginFill(0, 0); 397 | 398 | var q = points[0]; 399 | g.moveTo(q.x, q.y); 400 | for (i in 1...points.length) { 401 | var p = points[i]; 402 | g.lineTo(p.x, p.y); 403 | } 404 | g.lineTo(q.x, q.y); 405 | 406 | g.endFill(); 407 | } 408 | 409 | public function getCentroid(region:Array):Point 410 | { 411 | var c = new Point(); 412 | var len = region.length; 413 | for (i in 0...len) { 414 | var p0 = region[i]; 415 | var p1 = region[(i + 1) % len]; 416 | var m = p0.x * p1.y - p1.x * p0.y; 417 | c.x += (p0.x + p1.x) * m; 418 | c.y += (p0.y + p1.y) * m; 419 | } 420 | var area = getArea(region); 421 | c.x /= 6 * area; 422 | c.y /= 6 * area; 423 | return c; 424 | } 425 | 426 | public function getArea(region:Array):Float 427 | { 428 | var area = 0.0; 429 | var len = region.length; 430 | for (i in 0...len) { 431 | var p0 = region[i]; 432 | var p1 = region[(i + 1) % len]; 433 | area += p0.x * p1.y - p1.x * p0.y; 434 | } 435 | return area = .5 * area; 436 | } 437 | 438 | public function getTextField(text:String = "", x:Float, y:Float):TextField 439 | { 440 | var tf:TextField = new TextField(); 441 | var fmt:TextFormat = new TextFormat(TEXT_FONT, null, TEXT_COLOR); 442 | fmt.align = TextFormatAlign.LEFT; 443 | fmt.size = TEXT_SIZE; 444 | tf.defaultTextFormat = fmt; 445 | tf.autoSize = TextFieldAutoSize.LEFT; 446 | tf.selectable = false; 447 | tf.x = x; 448 | tf.y = y; 449 | #if (flash || html5) 450 | tf.filters = [TEXT_OUTLINE]; 451 | #end 452 | tf.text = text; 453 | return tf; 454 | } 455 | 456 | public function onKeyDown(e:KeyboardEvent):Void 457 | { 458 | var deltaPoints = e.shiftKey ? 20 : 1; 459 | 460 | switch (e.keyCode) 461 | { 462 | // TOGGLE 463 | case Keyboard.NUMBER_1: showPoints = !showPoints; 464 | case Keyboard.NUMBER_2: showRegions = !showRegions; 465 | case Keyboard.NUMBER_3: fillRegions = !fillRegions; 466 | case Keyboard.NUMBER_4: showTriangles = !showTriangles; 467 | case Keyboard.NUMBER_5: fillTriangles = !fillTriangles; 468 | case Keyboard.NUMBER_6: showHull = !showHull; 469 | case Keyboard.NUMBER_7: showTree = !showTree; 470 | case Keyboard.NUMBER_8: showOnion = !showOnion; 471 | case Keyboard.NUMBER_9: showProximityMap = !showProximityMap; 472 | 473 | // POINTS 474 | case Keyboard.UP: 475 | for (i in 0...deltaPoints) points.push(new Point(Math.random() * BOUNDS.width, Math.random() * BOUNDS.height)); 476 | nPoints = points.length; 477 | update(); 478 | case Keyboard.DOWN: 479 | while (deltaPoints-- > 0 && nPoints > 3) { 480 | points.pop(); 481 | nPoints = points.length; 482 | } 483 | update(); 484 | case Keyboard.R: 485 | for (p in points) p.setTo(Math.random() * BOUNDS.width, Math.random() * BOUNDS.height); 486 | update(); 487 | case Keyboard.X: 488 | relax = !relax; 489 | animate = false; 490 | case Keyboard.A: 491 | animate = !animate; 492 | relax = false; 493 | case Keyboard.P: sampleImage = !sampleImage; 494 | case Keyboard.S: 495 | #if (openfl && !nme) 496 | update(); 497 | var oldSampleFillAlpha = SAMPLE_FILL_ALPHA; 498 | SAMPLE_FILL_ALPHA = sampleImage ? .97 : oldSampleFillAlpha; 499 | 500 | render(); 501 | 502 | SAMPLE_FILL_ALPHA = oldSampleFillAlpha; 503 | var tempBMD = new BitmapData(pictureBMD.width, pictureBMD.height, false, 0); 504 | tempBMD.draw(sprite); 505 | 506 | //var tempBMP = new Bitmap(tempBMD); 507 | //addChild(tempBMP); 508 | //sprite.visible = false; 509 | 510 | savePNG(tempBMD, "voronoi.png"); 511 | #end 512 | } 513 | 514 | updateText(); 515 | render(); 516 | 517 | if (e.keyCode == 27) { 518 | #if (flash || html5) 519 | System.exit(1); 520 | #else 521 | Sys.exit(1); 522 | #end 523 | } 524 | } 525 | 526 | public function savePNG(bmd:BitmapData, filename:String) 527 | { 528 | #if (openfl && !nme) 529 | var ba:openfl.utils.ByteArray = bmd.encode(bmd.rect, new openfl.display.PNGEncoderOptions()); 530 | var fileRef = new openfl.net.FileReference(); 531 | fileRef.save(ba, filename); 532 | #end 533 | } 534 | 535 | public function onMouseDown(e:MouseEvent):Void 536 | { 537 | isMouseDown = true; 538 | } 539 | 540 | public function onMouseUp(e:MouseEvent):Void 541 | { 542 | isMouseDown = false; 543 | selectedRegion = null; 544 | render(); 545 | } 546 | 547 | public function onMouseMove(e:MouseEvent):Void 548 | { 549 | mousePos.setTo(e.stageX, e.stageY); 550 | } 551 | 552 | public function onEnterFrame(e:Event):Void { 553 | dt = Timer.stamp() - startTime; 554 | startTime = Timer.stamp(); 555 | 556 | var mousePosChanged = !(mousePos.x == prevMousePos.x && mousePos.y == prevMousePos.y); 557 | 558 | if (relax) { 559 | for (i in 0...points.length) { 560 | var p = points[i]; 561 | var r = voronoi.region(p); 562 | var c = getCentroid(r); 563 | (i == centroids.length) ? centroids.push(c) : centroids[i] = c; 564 | 565 | var changed = false; 566 | if (nPoints < 100) { 567 | var distSquared = Point.distanceSquared(c, p); 568 | if (distSquared > 4.5) { // slow down things a bit 569 | c.x -= p.x; c.y -= p.y; 570 | c.normalize(.75); 571 | p.x += c.x; p.y += c.y; 572 | changed = true; 573 | } 574 | } 575 | if (!changed) { 576 | p.x = c.x; p.y = c.y; 577 | } 578 | } 579 | } 580 | 581 | if (animate) { 582 | for (i in 0...points.length) { 583 | if (i == directions.length) { 584 | directions.push(new Point(30 * (Math.random() < .5 ? -1 : 1) * (Math.random() * .8 + .4), 30 * (Math.random() < .5 ? -1 : 1) * (Math.random() * .8 + .4))); 585 | } 586 | var p = points[i]; 587 | var d = directions[i]; 588 | var dx = d.x * dt; 589 | var dy = d.y * dt; 590 | if (p.x + dx < 0 || p.x + dx > BOUNDS.width) { 591 | d.x *= -1; 592 | dx *= -1; 593 | } 594 | if (p.y + dy < 0 || p.y + dy > BOUNDS.height) { 595 | d.y *= -1; 596 | dy *= -1; 597 | } 598 | p.x += dx; 599 | p.y += dy; 600 | } 601 | } 602 | 603 | if (relax || animate) { 604 | update(); 605 | render(); 606 | } 607 | 608 | if (isMouseDown && mousePos.x > 0 && mousePos.x < BOUNDS.width && mousePos.y > 0 && mousePos.y < BOUNDS.height) { 609 | var p = voronoi.nearestSitePoint(Std.int(mousePos.x), Std.int(mousePos.y)); 610 | if (p != null) { 611 | points[points.indexOf(p)].setTo(mousePos.x, mousePos.y); 612 | if (mousePosChanged) update(); 613 | selectedRegion = voronoi.region(p); 614 | render(); 615 | } 616 | prevMousePos.setTo(mousePos.x, mousePos.y); 617 | } 618 | } 619 | 620 | public function colorLerp(fromColor:Int, toColor:Int, t:Float = 1):Int 621 | { 622 | if (t <= 0) { return fromColor; } 623 | if (t >= 1) { return toColor; } 624 | var r:Int = fromColor >> 16 & 0xFF, 625 | g:Int = fromColor >> 8 & 0xFF, 626 | b:Int = fromColor & 0xFF, 627 | dR:Int = (toColor >> 16 & 0xFF) - r, 628 | dG:Int = (toColor >> 8 & 0xFF) - g, 629 | dB:Int = (toColor & 0xFF) - b; 630 | r += Std.int(dR * t); 631 | g += Std.int(dG * t); 632 | b += Std.int(dB * t); 633 | return r << 16 | g << 8 | b; 634 | } 635 | } -------------------------------------------------------------------------------- /src/DemoJs.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | import js.Browser.*; 4 | 5 | import com.nodename.delaunay.*; 6 | import com.nodename.geom.*; 7 | 8 | /** 9 | * hxDelaunay haxe js demo. 10 | * 11 | * @author @MatthijsKamstra 12 | */ 13 | class DemoJs { 14 | 15 | var container : js.html.DivElement; 16 | var canvas : js.html.CanvasElement; 17 | var context : js.html.CanvasRenderingContext2D; 18 | 19 | var displayWidth : Int = 500; 20 | var displayHeight : Int = 500; 21 | 22 | static function main () document.addEventListener("DOMContentLoaded", function(e) new DemoJs()); 23 | 24 | public function new () { 25 | setup(); 26 | generate(); 27 | } 28 | 29 | function setup(){ 30 | container = document.createDivElement(); 31 | container.id = 'delaunay'; 32 | container.className = 'container'; 33 | document.body.appendChild(container); 34 | 35 | canvas = document.createCanvasElement(); 36 | canvas.width = displayWidth; 37 | canvas.height = displayHeight; 38 | canvas.className = 'canvasOne'; 39 | canvas.id = 'canvasOne'; 40 | container.appendChild(canvas); 41 | 42 | context = canvas.getContext2d(); 43 | 44 | canvas.onclick = onClickHandler; 45 | } 46 | 47 | function onClickHandler (e : js.html.MouseEvent){ 48 | context.clearRect(0,0,canvas.width, canvas.height); 49 | generate(); 50 | e.preventDefault(); 51 | } 52 | 53 | function generate(){ 54 | var rect = new Rectangle(0, 0, canvas.width, canvas.height); 55 | 56 | // random set of inner points 57 | var points = new Array(); 58 | for (i in 0...25) { 59 | points.push(new Point(Math.random() * rect.width, Math.random() * rect.height)); 60 | } 61 | 62 | // add outer viewport bounding points 63 | points.push(new Point(rect.left, rect.top)); 64 | points.push(new Point(rect.right, rect.top)); 65 | points.push(new Point(rect.right, rect.bottom)); 66 | points.push(new Point(rect.left, rect.bottom)); 67 | 68 | var voronoi : Voronoi = new Voronoi(points, null, rect); 69 | var regions:Array> = [for (p in points) voronoi.region(p)]; 70 | var sortedRegions:Array> = voronoi.regions(); 71 | // var triangles:Array = voronoi.delaunayTriangulation(); 72 | // var hull:Array = voronoi.hull(); 73 | // var tree:Array = voronoi.spanningTree(); 74 | 75 | for (i in 0 ... voronoi.triangles().length) 76 | { 77 | var tri : Triangle = voronoi.triangles()[i]; 78 | var sitesArr : Array = tri.sites; 79 | context.beginPath(); 80 | context.moveTo(sitesArr[0].coord.x, sitesArr[0].coord.y); 81 | context.lineTo(sitesArr[1].coord.x, sitesArr[1].coord.y); 82 | context.lineTo(sitesArr[2].coord.x, sitesArr[2].coord.y); 83 | 84 | var color = 'rgba(' + Std.random(255) + ',' + Std.random(255) + ',' + Std.random(255) + ', 1)'; 85 | 86 | context.closePath(); 87 | context.fillStyle = color; 88 | context.lineWidth = 0.5; 89 | context.strokeStyle = color; 90 | context.stroke(); 91 | context.fill(); 92 | } 93 | 94 | /* 95 | for (i in 0 ... points.length) { 96 | var p = points[i]; 97 | context.beginPath(); 98 | context.arc(p.x, p.y, 2, 0, 2 * Math.PI, false); 99 | context.fillStyle = 'red'; 100 | context.fill(); 101 | context.closePath(); 102 | } 103 | */ 104 | 105 | } 106 | 107 | } --------------------------------------------------------------------------------