Influence maps are an increasingly common technique for implementing more intelligent pathfinding and route planning. For a good introduction see "The Mechanics of Influence Mapping: Representation, Algorithm & Parameters".
Last active
January 3, 2017 09:14
-
-
Save mnem/87e8998f389f1aa30227 to your computer and use it in GitHub Desktop.
Influence Maps
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <head> | |
| <style> | |
| body { | |
| font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | |
| margin: 0; | |
| padding: 0; | |
| background-color: #fff; | |
| text-align: center; | |
| height: 506px; | |
| width: 960px; | |
| overflow: hidden; | |
| } | |
| #viewport { | |
| margin: auto; | |
| } | |
| #info { | |
| margin: 0; | |
| padding: 0; | |
| background-color: #888; | |
| color: #fff; | |
| width: 100%; | |
| position: relative; | |
| top: -3px; | |
| } | |
| #controls-left { | |
| margin: 0; | |
| padding: 0; | |
| width: 49%; | |
| overflow: hidden; | |
| float: left; | |
| font-size: 12px; | |
| } | |
| #controls-right { | |
| margin: 0; | |
| padding: 0; | |
| width: 49%; | |
| overflow: hidden; | |
| float: right; | |
| font-size: 12px; | |
| } | |
| canvas { | |
| background-color: black; | |
| } | |
| .slider { | |
| margin: 7px 7px 7px 7px; | |
| } | |
| .note { | |
| padding: 0 3px; | |
| font-size: 10px; | |
| text-align: left; | |
| background-color: #eee; | |
| } | |
| .control-title { | |
| float: left; | |
| width: 31%; | |
| text-align: right; | |
| overflow: hidden; | |
| } | |
| .control-value { | |
| float: right; | |
| width: 31%; | |
| text-align: left; | |
| overflow: hidden; | |
| } | |
| .control-slider { | |
| display: inline-block; | |
| width: 29%; | |
| } | |
| .selection-list select { | |
| display: inline-block; | |
| width: 31%; | |
| text-align: center; | |
| overflow: hidden; | |
| } | |
| </style> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/pixi.js/1.6.1/pixi.js"></script> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/zepto/1.1.4/zepto.min.js"></script> | |
| <link href="//cdnjs.cloudflare.com/ajax/libs/noUiSlider/6.2.0/jquery.nouislider.min.css" rel="stylesheet" type="text/css" media="all"> | |
| <script src="//cdnjs.cloudflare.com/ajax/libs/noUiSlider/6.2.0/jquery.nouislider.min.js"></script> | |
| <script src="/mnem/raw/87e8998f389f1aa30227/mnem_core.js"></script> | |
| <script src="/mnem/raw/87e8998f389f1aa30227/mnem_color.js"></script> | |
| <script src="/mnem/raw/87e8998f389f1aa30227/mnem_math.js"></script> | |
| <script src="/mnem/raw/87e8998f389f1aa30227/mnem_influence_map_grid.js"></script> | |
| <script src="/mnem/raw/87e8998f389f1aa30227/mnem_pathfinder.js"></script> | |
| <script src="/mnem/raw/87e8998f389f1aa30227/system_pixi.js"></script> | |
| <script src="/mnem/raw/87e8998f389f1aa30227/system_main.js"></script> | |
| <script src="/mnem/raw/87e8998f389f1aa30227/system_robots.js"></script> | |
| <script src="/mnem/raw/87e8998f389f1aa30227/system_hero.js"></script> | |
| <script src="/mnem/raw/87e8998f389f1aa30227/system_ui.js"></script> | |
| </head> | |
| <body> | |
| <div id="viewport"> | |
| <div id="info">Click choose target for X to walk to.</div> | |
| <div id="controls-left"> | |
| <div class="slider" data-suffix="Hz" data-setter="set_map_update_hertz" data-initial="8" data-min="0" data-max="30" data-intervals="1,2,3,4,5,6,7,8,9,10"> | |
| <span class="control-title">Update Rate</span> | |
| <div class="control-slider"></div> | |
| <span class="control-value"></span> | |
| </div> | |
| <div class="slider" data-suffix="" data-setter="set_map_decay" data-initial="0.21" data-min="0" data-max="2"> | |
| <span class="control-title">Influence Decay Rate</span> | |
| <div class="control-slider"></div> | |
| <span class="control-value"></span> | |
| </div> | |
| <div class="slider" data-suffix="" data-setter="set_map_momentum" data-initial="0.67" data-min="0" data-max="1"> | |
| <span class="control-title">Influence Momentum</span> | |
| <div class="control-slider"></div> | |
| <span class="control-value"></span> | |
| </div> | |
| <div class="selection-list" data-setter="set_influence_spread_function"> | |
| <span class="control-title">Spreading Function</span> | |
| <select name="propogation-function"> | |
| <option value="neighbours_max_influence" selected>Max</option> | |
| <option value="neighbours_mean_influence">Mean</option> | |
| <option value="neighbours_gaussian_influence">Gaussian</option> | |
| </select> | |
| <span class="control-value"> </span> | |
| </div> | |
| </div> | |
| <div id="controls-right"> | |
| <div class="note"> | |
| <p>Influence weighting defines how unpleasant the influenced areas are for hero <b>X</b>. Set | |
| to 0 to see what it would be like with no influence map.</p> | |
| </div> | |
| <div class="slider" data-suffix="" data-setter="set_map_influence_weight" data-initial="20" data-min="0" data-max="100"> | |
| <span class="control-title">Influence Weighting</span> | |
| <div class="control-slider"></div> | |
| <span class="control-value"></span> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| mnem.core.add_system(systems.pixi); | |
| mnem.core.add_system(systems.main); | |
| mnem.core.add_system(systems.robots); | |
| mnem.core.add_system(systems.hero); | |
| mnem.core.add_system(systems.ui); | |
| mnem.core.start(); | |
| </script> | |
| </body> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| mnem = typeof(mnem) == "undefined" ? {} : mnem; | |
| mnem.color = (function(M) { | |
| M.lerp = function(a, b, progress) { | |
| var c = {}; | |
| c.r = mnem.math.lerp(a.r, b.r, progress); | |
| c.g = mnem.math.lerp(a.g, b.g, progress); | |
| c.b = mnem.math.lerp(a.b, b.b, progress); | |
| if (typeof(a.a) != "undefined" && typeof(a.a) != "undefined") { | |
| c.a = mnem.math.lerp(a.a, b.a, progress); | |
| } else if (typeof(a.a) != "undefined") { | |
| c.a = a.a; | |
| } else if (typeof(b.a) != "undefined") { | |
| c.a = b.a; | |
| } | |
| return c; | |
| } | |
| M.to_uint_argb = function(c, forced_alpha) { | |
| var a = typeof(c.a) == "undefined" ? 0 : c.a; | |
| a = typeof(forced_alpha) == "undefined" ? a : forced_alpha; | |
| return (a & 0xff) << 24 | (c.r & 0xff) << 16 | (c.g & 0xff) << 8 |(c.b & 0xff); | |
| } | |
| // Never forget to return the module! | |
| return M; | |
| }(mnem.color || {})); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| mnem = typeof(mnem) == "undefined" ? {} : mnem; | |
| mnem.core = (function(M) { | |
| var _systems = []; | |
| //////////////////////////////////// | |
| // Private functions | |
| //////////////////////////////////// | |
| // Public functions | |
| M.add_system = function(system) { | |
| if (!system) { | |
| throw "Cannot add null or undefined system." | |
| } | |
| _systems.push(system); | |
| } | |
| M.start = function() { | |
| M.call_on_systems("init"); | |
| M.call_on_systems("before_start"); | |
| M._elapsed_time = 0; | |
| M._real_time_step = 0; | |
| var tick = function(run_time) { | |
| requestAnimFrame( tick ); | |
| var time_step = M._elapsed_time; | |
| M._elapsed_time = run_time / 1000; | |
| time_step = M._elapsed_time - time_step; | |
| M._real_time_step = time_step; | |
| M.call_on_systems("update", time_step); | |
| M.call_on_systems("before_render"); | |
| M.call_on_systems("render"); | |
| M.call_on_systems("after_render"); | |
| } | |
| requestAnimFrame( tick ); | |
| } | |
| // Expects: | |
| // (string, ...) | |
| // Where the string is the function name, and the (optional) argument | |
| // list is passed in order as parameter to the function. | |
| // | |
| // For example: | |
| // call_on_systems("update", timestep) | |
| // call_on_systems("render") | |
| M.call_on_systems = function() { | |
| var function_name = arguments[0]; | |
| var function_args = []; | |
| for (var i = 1; i < arguments.length; i++) { | |
| function_args.push(arguments[i]); | |
| }; | |
| for (var i = 0; i < _systems.length; i++) { | |
| var system = _systems[i]; | |
| var func = system[function_name]; | |
| if (typeof(func) == "function") { | |
| func.apply(null, function_args); | |
| } | |
| }; | |
| } | |
| // Never forget to return the module! | |
| return M; | |
| }(mnem.core || {})); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| mnem = typeof(mnem) == "undefined" ? {} : mnem; | |
| mnem.influence_map_grid = (function(M) { | |
| //////////////////////////////////// | |
| // Private | |
| var _double_buffer = true; | |
| var SPREAD = {}; | |
| var _connections_offsets = [ | |
| {x: -1, y: -1}, | |
| {x: 0, y: -1}, | |
| {x: 1, y: -1}, | |
| {x: 1, y: 0}, | |
| {x: 1, y: 1}, | |
| {x: 0, y: 1}, | |
| {x: -1, y: 1}, | |
| {x: -1, y: 0}, | |
| ]; | |
| SPREAD.neighbours_max_influence = function(value, connections, decay) { | |
| var max_influence = 0.0; | |
| var neighbour_influence; | |
| // Corner | |
| neighbour_influence = connections[0] * Math.exp(-1.414 /*distance*/ * decay); | |
| if (neighbour_influence > max_influence) max_influence = neighbour_influence; | |
| neighbour_influence = connections[1] * Math.exp(-1 /*distance*/ * decay); | |
| if (neighbour_influence > max_influence) max_influence = neighbour_influence; | |
| // Corner | |
| neighbour_influence = connections[2] * Math.exp(-1.414 /*distance*/ * decay); | |
| if (neighbour_influence > max_influence) max_influence = neighbour_influence; | |
| neighbour_influence = connections[3] * Math.exp(-1 /*distance*/ * decay); | |
| if (neighbour_influence > max_influence) max_influence = neighbour_influence; | |
| // Corner | |
| neighbour_influence = connections[4] * Math.exp(-1.414 /*distance*/ * decay); | |
| if (neighbour_influence > max_influence) max_influence = neighbour_influence; | |
| neighbour_influence = connections[5] * Math.exp(-1 /*distance*/ * decay); | |
| if (neighbour_influence > max_influence) max_influence = neighbour_influence; | |
| // Corner | |
| neighbour_influence = connections[6] * Math.exp(-1.414 /*distance*/ * decay); | |
| if (neighbour_influence > max_influence) max_influence = neighbour_influence; | |
| neighbour_influence = connections[7] * Math.exp(-1 /*distance*/ * decay); | |
| if (neighbour_influence > max_influence) max_influence = neighbour_influence; | |
| return max_influence; | |
| } | |
| SPREAD.neighbours_mean_influence = function(value, connections, decay) { | |
| var neighbour_influence = 0.0; | |
| neighbour_influence += connections[0]; | |
| neighbour_influence += connections[1]; | |
| neighbour_influence += connections[2]; | |
| neighbour_influence += connections[3]; | |
| neighbour_influence += connections[4]; | |
| neighbour_influence += connections[5]; | |
| neighbour_influence += connections[6]; | |
| neighbour_influence += connections[7]; | |
| return neighbour_influence / 7; | |
| } | |
| SPREAD.neighbours_gaussian_influence = function(value, connections, decay) { | |
| var influence = connections[0] * 1/6 + connections[1] * 2/6 + connections[2] * 2/6 + | |
| connections[7] * 2/6 + value * 4/6 + connections[3] * 2/6 + | |
| connections[6] * 1/6 + connections[5] * 2/6 + connections[4] * 1/6; | |
| return influence; | |
| } | |
| //////////////////////////////////// | |
| // Public | |
| M.create = function(width, height, initial_influence, influence_weight){ | |
| if (typeof(initial_influence) == "undefined" || initial_influence == null || isNaN(initial_influence) ) { | |
| initial_influence = 0.0; | |
| } | |
| if (typeof(influence_weight) == "undefined" || influence_weight == null || isNaN(influence_weight) ) { | |
| influence_weight = 1.0; | |
| } | |
| var map = { | |
| type: "grid", | |
| width: width, | |
| height: height, | |
| data: new Float32Array(width*height), | |
| copy: new Float32Array(width*height), | |
| spread_function_name: "neighbours_max_influence", | |
| influence_weight: influence_weight, | |
| }; | |
| for (var i = 0; i < map.data.length; i++) { | |
| map.data[i] = initial_influence; | |
| map.copy[i] = initial_influence; | |
| }; | |
| return map; | |
| } | |
| M.set_influence = function(map, x, y, influence) { | |
| if (x < 0 || x >= map.width) { | |
| throw "x is outside map range"; | |
| } | |
| if (y < 0 || y >= map.height) { | |
| throw "y is outside map range"; | |
| } | |
| if (typeof(influence) == "undefined" || influence == null || isNaN(influence) ) { | |
| throw "influence value is not a valid number"; | |
| } | |
| map.data[y * map.width + x] = influence; | |
| } | |
| M.influence = function(map, x, y) { | |
| if (x < 0 || x >= map.width) { | |
| throw "x is outside map range"; | |
| } | |
| if (y < 0 || y >= map.height) { | |
| throw "y is outside map range"; | |
| } | |
| return map.data[y * map.width + x]; | |
| } | |
| M.iterate = function(map, callback_function) { | |
| var x = 0; | |
| var y = 0; | |
| var connections = new Float32Array(8); | |
| for (var i = 0; i < map.data.length; i++) { | |
| var n = map.data[i]; | |
| M.fill_connections(map, x, y, connections, 0.0, 1); | |
| var new_n = callback_function(n, x, y, connections); | |
| if (typeof(new_n) != "undefined" && new_n !== null && !isNaN(new_n) ) { | |
| map.data[i] = new_n; | |
| } | |
| if (++x >= map.width) { | |
| x = 0; | |
| y++; | |
| } | |
| }; | |
| } | |
| M.propogate_influence = function(map, momentum, decay){ | |
| var x = 0; | |
| var y = 0; | |
| var connections = new Float32Array(8); | |
| var dest = map.data; | |
| if (_double_buffer) { | |
| dest = map.copy; | |
| } | |
| for (var i = 0; i < map.data.length; i++) { | |
| M.fill_connections(map, x, y, connections, 0.0, 1); | |
| var new_value = SPREAD[map.spread_function_name](map.data[i], connections, decay); | |
| if (isNaN(new_value)) throw "Whoops, NaN"; | |
| // NOTE: this *must* be an assignment, otherwise we need to add a | |
| // step to clear any existing value as we swap map and data each | |
| // propogation | |
| dest[i] = mnem.math.lerp(map.data[i], new_value, momentum); | |
| if (++x >= map.width) { | |
| x = 0; | |
| y++; | |
| } | |
| }; | |
| if (_double_buffer) { | |
| var tmp = map.data; | |
| map.data = map.copy; | |
| map.copy = tmp; | |
| } | |
| } | |
| // Connections are filled clockwise, starting at the top left | |
| // tile: | |
| // 0 1 2 | |
| // 7 3 | |
| // 6 5 4 | |
| M.fill_connections = function(map, x, y, connections, out_of_bounds_value, influence_weight) { | |
| connections[0] = out_of_bounds_value; | |
| connections[1] = out_of_bounds_value; | |
| connections[2] = out_of_bounds_value; | |
| connections[3] = out_of_bounds_value; | |
| connections[4] = out_of_bounds_value; | |
| connections[5] = out_of_bounds_value; | |
| connections[6] = out_of_bounds_value; | |
| connections[7] = out_of_bounds_value; | |
| var i = y * map.width + x; | |
| if (y > 0) { | |
| if (x > 0) connections[0] = map.data[i + (-map.width - 1)] * influence_weight; | |
| connections[1] = map.data[i + (-map.width)] * influence_weight; | |
| if (x < map.width - 1) connections[2] = map.data[i + (-map.width + 1)] * influence_weight; | |
| } | |
| if (x > 0) connections[7] = map.data[i + (-1)] * influence_weight; | |
| if (x < map.width - 1) connections[3] = map.data[i + (1)] * influence_weight; | |
| if (y < map.height - 1) { | |
| if (x > 0) connections[6] = map.data[i + (map.width - 1)] * influence_weight; | |
| connections[5] = map.data[i + (map.width)] * influence_weight; | |
| if (x < map.width - 1) connections[4] = map.data[i + (map.width + 1)] * influence_weight; | |
| } | |
| return connections; | |
| } | |
| M.connections_offsets = function() { | |
| return _connections_offsets; | |
| } | |
| function test() { | |
| var map = M.create(3, 3, 0); | |
| M.set_influence(map, 0, 0, 1); | |
| M.set_influence(map, 1, 0, 2); | |
| M.set_influence(map, 2, 0, 3); | |
| M.set_influence(map, 0, 1, 4); | |
| M.set_influence(map, 1, 1, 5); | |
| M.set_influence(map, 2, 1, 6); | |
| M.set_influence(map, 0, 2, 7); | |
| M.set_influence(map, 1, 2, 8); | |
| M.set_influence(map, 2, 2, 9); | |
| var connections = new Float32Array(8); | |
| M.fill_connections(map, 0, 0, connections, 0, 1); | |
| if(connections[0] !== 0) throw "Wrong"; | |
| if(connections[1] !== 0) throw "Wrong"; | |
| if(connections[2] !== 0) throw "Wrong"; | |
| if(connections[3] !== 2) throw "Wrong"; | |
| if(connections[4] !== 5) throw "Wrong"; | |
| if(connections[5] !== 4) throw "Wrong"; | |
| if(connections[6] !== 0) throw "Wrong"; | |
| if(connections[7] !== 0) throw "Wrong"; | |
| M.fill_connections(map, 1, 0, connections, 0, 1); | |
| if(connections[0] !== 0) throw "Wrong"; | |
| if(connections[1] !== 0) throw "Wrong"; | |
| if(connections[2] !== 0) throw "Wrong"; | |
| if(connections[3] !== 3) throw "Wrong"; | |
| if(connections[4] !== 6) throw "Wrong"; | |
| if(connections[5] !== 5) throw "Wrong"; | |
| if(connections[6] !== 4) throw "Wrong"; | |
| if(connections[7] !== 1) throw "Wrong"; | |
| M.fill_connections(map, 2, 0, connections, 0, 1); | |
| if(connections[0] !== 0) throw "Wrong"; | |
| if(connections[1] !== 0) throw "Wrong"; | |
| if(connections[2] !== 0) throw "Wrong"; | |
| if(connections[3] !== 0) throw "Wrong"; | |
| if(connections[4] !== 0) throw "Wrong"; | |
| if(connections[5] !== 6) throw "Wrong"; | |
| if(connections[6] !== 5) throw "Wrong"; | |
| if(connections[7] !== 2) throw "Wrong"; | |
| M.fill_connections(map, 0, 1, connections, 0, 1); | |
| if(connections[0] !== 0) throw "Wrong"; | |
| if(connections[1] !== 1) throw "Wrong"; | |
| if(connections[2] !== 2) throw "Wrong"; | |
| if(connections[3] !== 5) throw "Wrong"; | |
| if(connections[4] !== 8) throw "Wrong"; | |
| if(connections[5] !== 7) throw "Wrong"; | |
| if(connections[6] !== 0) throw "Wrong"; | |
| if(connections[7] !== 0) throw "Wrong"; | |
| M.fill_connections(map, 1, 1, connections, 0, 1); | |
| if(connections[0] !== 1) throw "Wrong"; | |
| if(connections[1] !== 2) throw "Wrong"; | |
| if(connections[2] !== 3) throw "Wrong"; | |
| if(connections[3] !== 6) throw "Wrong"; | |
| if(connections[4] !== 9) throw "Wrong"; | |
| if(connections[5] !== 8) throw "Wrong"; | |
| if(connections[6] !== 7) throw "Wrong"; | |
| if(connections[7] !== 4) throw "Wrong"; | |
| M.fill_connections(map, 2, 1, connections, 0, 1); | |
| if(connections[0] !== 2) throw "Wrong"; | |
| if(connections[1] !== 3) throw "Wrong"; | |
| if(connections[2] !== 0) throw "Wrong"; | |
| if(connections[3] !== 0) throw "Wrong"; | |
| if(connections[4] !== 0) throw "Wrong"; | |
| if(connections[5] !== 9) throw "Wrong"; | |
| if(connections[6] !== 8) throw "Wrong"; | |
| if(connections[7] !== 5) throw "Wrong"; | |
| M.fill_connections(map, 0, 2, connections, 0, 1); | |
| if(connections[0] !== 0) throw "Wrong"; | |
| if(connections[1] !== 4) throw "Wrong"; | |
| if(connections[2] !== 5) throw "Wrong"; | |
| if(connections[3] !== 8) throw "Wrong"; | |
| if(connections[4] !== 0) throw "Wrong"; | |
| if(connections[5] !== 0) throw "Wrong"; | |
| if(connections[6] !== 0) throw "Wrong"; | |
| if(connections[7] !== 0) throw "Wrong"; | |
| M.fill_connections(map, 1, 2, connections, 0, 1); | |
| if(connections[0] !== 4) throw "Wrong"; | |
| if(connections[1] !== 5) throw "Wrong"; | |
| if(connections[2] !== 6) throw "Wrong"; | |
| if(connections[3] !== 9) throw "Wrong"; | |
| if(connections[4] !== 0) throw "Wrong"; | |
| if(connections[5] !== 0) throw "Wrong"; | |
| if(connections[6] !== 0) throw "Wrong"; | |
| if(connections[7] !== 7) throw "Wrong"; | |
| M.fill_connections(map, 2, 2, connections, 0, 1); | |
| if(connections[0] !== 5) throw "Wrong"; | |
| if(connections[1] !== 6) throw "Wrong"; | |
| if(connections[2] !== 0) throw "Wrong"; | |
| if(connections[3] !== 0) throw "Wrong"; | |
| if(connections[4] !== 0) throw "Wrong"; | |
| if(connections[5] !== 0) throw "Wrong"; | |
| if(connections[6] !== 0) throw "Wrong"; | |
| if(connections[7] !== 8) throw "Wrong"; | |
| } | |
| test(); | |
| // Never forget to return the module! | |
| return M; | |
| }(mnem.influence_map_grid || {})); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| mnem = typeof(mnem) == "undefined" ? {} : mnem; | |
| mnem.math = (function(M) { | |
| M.clamp = function(n, min, max) { | |
| if (n >= min && n <= max) { | |
| return n; | |
| } else if (n > max) { | |
| return max; | |
| } else { | |
| return min; | |
| } | |
| } | |
| M.clamp_01 = function(n) { | |
| return M.clamp(n, 0, 1); | |
| } | |
| // Progress is assumed to be 0 - 1 | |
| M.lerp = function(a, b, progress) { | |
| return (b - a) * progress + a; | |
| } | |
| M.vec3_subtract = function(a, b) { | |
| return { | |
| x: a.x - b.x, | |
| y: a.y - b.y, | |
| z: a.z - b.z, | |
| }; | |
| } | |
| M.vec3_add = function(a, b) { | |
| return { | |
| x: a.x + b.x, | |
| y: a.y + b.y, | |
| z: a.z + b.z, | |
| }; | |
| } | |
| M.vec3_multiply = function(a, scalar) { | |
| return { | |
| x: a.x * scalar, | |
| y: a.y * scalar, | |
| z: a.z * scalar, | |
| }; | |
| } | |
| M.vec3_length = function(a) { | |
| return Math.sqrt((a.x*a.x) + (a.y*a.y) + (a.z*a.z)); | |
| } | |
| // Never forget to return the module! | |
| return M; | |
| }(mnem.math || {})); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| mnem = typeof(mnem) == "undefined" ? {} : mnem; | |
| mnem.pathfinder = (function(M) { | |
| //////////////////////////////////// | |
| // Private | |
| function distance_2d(a, b) { | |
| var dx = b.x - a.x; | |
| var dy = b.y - a.y; | |
| return Math.sqrt(dx * dx + dy * dy); | |
| } | |
| //////////////////////////////////// | |
| // Public | |
| M.create = function(target_point, tolerance) { | |
| var path = { | |
| target_point: {x: target_point.x, y: target_point.y, z: 0}, | |
| tolerance_squared: tolerance * tolerance, | |
| } | |
| return path; | |
| } | |
| // Returns next target point or null | |
| // if at target or cannot reach target. | |
| // | |
| // Expects path, current_point, influence_map_1, influence_map_2... | |
| M.next_waypoint = function(path, current_point) { | |
| var distance = distance_2d(current_point, path.target_point); | |
| // Store for debug drawing | |
| path.current_point = {x: current_point.x, y: current_point.y, z: 0}; | |
| if (distance <= path.tolerance_squared) { | |
| path.next_waypoint = null; | |
| return path.next_waypoint; | |
| } | |
| if (arguments.length == 2) { | |
| path.next_waypoint = {x: path.target_point.x, y: path.target_point.y, z: 0}; | |
| return path.next_waypoint; | |
| } | |
| var current_tile = systems.main.point_to_tile(current_point); | |
| var tile_totals; | |
| var connections; | |
| for (var i = 2; i < arguments.length; i++) { | |
| var map = arguments[i]; | |
| if (tile_totals == null) { | |
| tile_totals = new Float32Array(8); | |
| mnem.influence_map_grid.fill_connections( | |
| map, | |
| current_tile.x, current_tile.y, | |
| tile_totals, | |
| Number.POSITIVE_INFINITY, | |
| map.influence_weight); | |
| } else { | |
| if (connections == null) { | |
| connections = new Float32Array(8); | |
| } | |
| mnem.influence_map_grid.fill_connections( | |
| map, | |
| current_tile.x, current_tile.y, | |
| connections, | |
| 0, | |
| map.influence_weight); | |
| for (var tile = 0; tile < tile_totals.length; tile++) { | |
| tile_totals[tile] += connections[tile]; | |
| }; | |
| } | |
| }; | |
| // Add the tile distances | |
| var offsets = mnem.influence_map_grid.connections_offsets(); | |
| for (var i = 0; i < offsets.length; i++) { | |
| var offset = offsets[i]; | |
| if (tile_totals[i] < Number.POSITIVE_INFINITY) { | |
| var tile = {x: current_tile.x + offsets[i].x, y: current_tile.y + offsets[i].y, z: 0}; | |
| var tile_center = systems.main.tile_to_point_center(tile); | |
| tile_totals[i] += distance_2d(tile_center, path.target_point); | |
| } | |
| }; | |
| // Find the lowest scoring tile | |
| var lowest = 0; | |
| for (var i = 1; i < tile_totals.length; i++) { | |
| if (tile_totals[i] < tile_totals[lowest]) { | |
| lowest = i; | |
| } | |
| }; | |
| // Set the next waypoint to the center of the lowest | |
| // scoring tile | |
| path.next_waypoint = systems.main.tile_to_point_center({x: current_tile.x + offsets[lowest].x, y: current_tile.y + offsets[lowest].y, z: 0}); | |
| return path.next_waypoint; | |
| } | |
| // Never forget to return the module! | |
| return M; | |
| }(mnem.pathfinder || {})); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| systems = typeof(systems) == "undefined" ? {} : systems; | |
| systems.hero = (function(M) { | |
| //////////////////////////////////// | |
| // Private | |
| var _position = {x: 0, y: 0, z: 0}; | |
| var _size = 0; | |
| var _path = null; | |
| function render_hero(g, x, y) { | |
| g.lineStyle(3, 0x000000, 1.0); | |
| g.moveTo(x - _size, y - _size); | |
| g.lineTo(x + _size, y + _size); | |
| g.moveTo(x - _size, y + _size); | |
| g.lineTo(x + _size, y - _size); | |
| } | |
| function draw_tile_and_point(g, point, color_tile, colour_point, alpha, filled) { | |
| var extents = systems.main.tile_extents(); | |
| var tile = systems.main.point_to_tile(point); | |
| var tile_tl = systems.main.tile_to_point_top_left(tile); | |
| if (filled) { | |
| g.lineStyle(0, color_tile, alpha); | |
| g.beginFill(color_tile, alpha); | |
| } else { | |
| g.lineStyle(1, color_tile, alpha); | |
| } | |
| g.drawRect(tile_tl.x, tile_tl.y, extents.width, extents.height); | |
| if (filled) { | |
| g.endFill(); | |
| } | |
| // Invert the filled flag for drawing the point | |
| filled = !filled; | |
| if (filled) { | |
| g.lineStyle(0, colour_point, alpha); | |
| g.beginFill(colour_point, alpha); | |
| } else { | |
| g.lineStyle(1, colour_point, alpha); | |
| } | |
| g.drawCircle(point.x, point.y, 10); | |
| if (filled) { | |
| g.endFill(); | |
| } | |
| } | |
| function render_path(g, path) { | |
| draw_tile_and_point(g, path.target_point, 0x000000, 0x000000, 0.5, false); | |
| if (path.next_waypoint) { | |
| draw_tile_and_point(g, path.next_waypoint, 0xffffff, 0x000000, 0.5, true); | |
| } | |
| } | |
| //////////////////////////////////// | |
| // Public | |
| M.init = function() { | |
| } | |
| M.before_start = function() { | |
| var extents = systems.main.display_extents(); | |
| _position.x = 30; | |
| _position.y = extents.height / 2; | |
| var tile_extents = systems.main.tile_extents(); | |
| _size = tile_extents.width / 2; | |
| } | |
| M.update = function(time_step) { | |
| if (_path) { | |
| var waypoint = mnem.pathfinder.next_waypoint(_path, _position, systems.main.map()); | |
| if (waypoint) { | |
| var m = mnem.math; | |
| var speed = 150 * time_step; | |
| var diff = m.vec3_subtract(waypoint, _position); | |
| var length = m.vec3_length(diff) | |
| var dist = speed / length; | |
| if (dist >= 1) { | |
| _position.x = waypoint.x; | |
| _position.y = waypoint.y; | |
| _position.z = waypoint.z; | |
| } else { | |
| diff = m.vec3_multiply(diff, dist); | |
| _position = m.vec3_add(_position, diff); | |
| } | |
| } else { | |
| _path = null; | |
| } | |
| } | |
| } | |
| M.render = function() { | |
| var g = systems.pixi.graphics(); | |
| render_hero(g, _position.x, _position.y); | |
| if (_path) { | |
| render_path(g, _path); | |
| } | |
| } | |
| M.on_tap = function(point) { | |
| _path = mnem.pathfinder.create(point, 2); | |
| } | |
| // Never forget to return the module! | |
| return M; | |
| }(systems.hero || {})); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| systems = typeof(systems) == "undefined" ? {} : systems; | |
| systems.main = (function(M) { | |
| //////////////////////////////////// | |
| // Private | |
| var _map = mnem.influence_map_grid.create(70, 30, 0.0, 20); | |
| var _decay = 0; | |
| var _momentum = 0; | |
| var _theme = { | |
| neutral : {r: 0x75, g: 0xFF, b: 0x89}, | |
| negative : {r: 0x37, g: 0x7D, b: 0xFF}, | |
| positive : {r: 0xFF, g: 0x34, b: 0x23}, | |
| } | |
| var _prop_time = 0; | |
| var _timer = _prop_time; | |
| var _tile_extent = 12; | |
| function clear_map() { | |
| mnem.influence_map_grid.iterate(_map, function() { | |
| return 0.0; | |
| }); | |
| } | |
| function force_map_propogation() { | |
| _timer = 0; | |
| } | |
| function render_influence_map(g, map, theme) { | |
| mnem.influence_map_grid.iterate(map, function(influence, x, y, connections) { | |
| influence = mnem.math.clamp(influence, -1, 1); | |
| var tint; | |
| if (influence >= 0) { | |
| tint = mnem.color.lerp(theme.neutral, theme.positive, influence); | |
| } else { | |
| tint = mnem.color.lerp(theme.neutral, theme.negative, -influence); | |
| } | |
| g.beginFill(mnem.color.to_uint_argb(tint), 1.0); | |
| g.drawRect(x * _tile_extent, y * _tile_extent, _tile_extent, _tile_extent); | |
| g.endFill(); | |
| }); | |
| } | |
| //////////////////////////////////// | |
| // Public | |
| M.init = function() { | |
| } | |
| M.update = function(time_step) { | |
| if (_prop_time > 0) { | |
| _timer -= time_step; | |
| if (_timer <= 0) { | |
| _timer = _prop_time; | |
| mnem.influence_map_grid.propogate_influence(_map, _momentum, _decay); | |
| } | |
| } | |
| } | |
| M.render = function() { | |
| var g = systems.pixi.graphics(); | |
| render_influence_map(g, _map, _theme); | |
| } | |
| M.display_extents = function() { | |
| return { | |
| width: _map.width * _tile_extent, | |
| height: _map.height * _tile_extent, | |
| } | |
| } | |
| M.tile_extents = function() { | |
| return { | |
| width: _tile_extent, | |
| height: _tile_extent, | |
| } | |
| } | |
| M.map = function() { | |
| return _map; | |
| } | |
| M.set_map_update_hertz = function(value) { | |
| if (value > 0) { | |
| _prop_time = 1 / value; | |
| } else { | |
| _prop_time = 0; | |
| } | |
| clear_map(); | |
| force_map_propogation(); | |
| } | |
| M.set_map_decay = function(value) { | |
| _decay = value; | |
| clear_map(); | |
| force_map_propogation(); | |
| } | |
| M.set_map_momentum = function(value) { | |
| _momentum = value; | |
| clear_map(); | |
| force_map_propogation(); | |
| } | |
| M.set_influence_spread_function = function(value) { | |
| _map.spread_function_name = value; | |
| clear_map(); | |
| force_map_propogation(); | |
| } | |
| M.set_map_influence_weight = function(value) { | |
| _map.influence_weight = value; | |
| } | |
| M.point_to_tile = function(point) { | |
| var tile_x = Math.floor(point.x / _tile_extent); | |
| var tile_y = Math.floor(point.y / _tile_extent); | |
| return {x: tile_x, y: tile_y}; | |
| } | |
| M.tile_to_point_top_left = function(tile) { | |
| var point = { | |
| x: tile.x * _tile_extent, | |
| y: tile.y * _tile_extent, | |
| z: 0, | |
| }; | |
| return point; | |
| } | |
| M.tile_to_point_center = function(tile) { | |
| var point = { | |
| x: tile.x * _tile_extent + (_tile_extent/2), | |
| y: tile.y * _tile_extent + (_tile_extent/2), | |
| z: 0, | |
| }; | |
| return point; | |
| } | |
| // Never forget to return the module! | |
| return M; | |
| }(systems.main || {})); | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| systems = typeof(systems) == "undefined" ? {} : systems; | |
| systems.pixi = (function(M, $) { | |
| //////////////////////////////////// | |
| // Private | |
| var _stage; | |
| var _graphics; | |
| var _renderer; | |
| var _clear_graphics = true; | |
| function on_interaction_up(event) { | |
| var point = event.getLocalPosition(_stage); | |
| mnem.core.call_on_systems("on_tap", {x: point.x, y: point.y}); | |
| } | |
| //////////////////////////////////// | |
| // Public | |
| M.before_start = function() { | |
| var extents = systems.main.display_extents(); | |
| _stage = new PIXI.Stage(0xffffff); | |
| _renderer = PIXI.autoDetectRenderer(extents.width, extents.height); | |
| var viewport = $("#viewport"); | |
| viewport.width(extents.width+"px"); | |
| viewport.prepend(_renderer.view); | |
| _graphics = new PIXI.Graphics(); | |
| _stage.addChild(_graphics); | |
| _stage.mouseup = _stage.tap = on_interaction_up; | |
| } | |
| M.before_render = function() { | |
| if (_clear_graphics) { | |
| _graphics.clear(); | |
| } | |
| } | |
| M.after_render = function() { | |
| _renderer.render(_stage); | |
| } | |
| M.set_clear_graphics = function(clear) { | |
| _clear_graphics = !!clear; | |
| } | |
| M.clear_graphics = function(clear) { | |
| return _clear_graphics; | |
| } | |
| M.graphics = function() { | |
| return _graphics; | |
| } | |
| // Never forget to return the module! | |
| return M; | |
| }(systems.pixi || {}, $)); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| systems = typeof(systems) == "undefined" ? {} : systems; | |
| systems.robots = (function(M) { | |
| //////////////////////////////////// | |
| // Private | |
| // These positions are based on an area centred on | |
| // (0, 0, 0) and assume 1m scale. They are modified | |
| // in the before_start function to be more suitable | |
| // for the debug harness co-ordinate system. | |
| var _positions = [ | |
| {y: 0, x: 2, z: 0, dir: 1, speed: Math.random() * 10 + 2}, // Captain | |
| {y: 5, x: -6, z: 0, dir: -1, speed: Math.random() * 10 + 2}, | |
| {y: -5, x: -6, z: 0, dir: 1, speed: Math.random() * 10 + 2}, | |
| {y: 9, x: 13, z: 0, dir: -1, speed: Math.random() * 10 + 2}, | |
| {y: -9, x: 13, z: 0, dir: 1, speed: Math.random() * 10 + 2}, | |
| {y: 5, x: 21, z: 0, dir: -1, speed: Math.random() * 10 + 2}, | |
| {y: -5, x: 21, z: 0, dir: 1, speed: Math.random() * 10 + 2}, | |
| {y: 0, x: 33, z: 0, dir: -1, speed: Math.random() * 10 + 2}, // Goal keeper | |
| ]; | |
| function wiggle(position, robots_id, time_step) { | |
| var dimension = robots_id & 1 ? "y" : "x"; | |
| if (position.original == null) { | |
| position.original = {x: position.x, y: position.y, z: position.z}; | |
| } | |
| var limit = 5; | |
| position[dimension] += position.speed * time_step * position.dir; | |
| var walk_dist = Math.abs(position.original[dimension] - position[dimension]) | |
| if ( walk_dist >= limit) { | |
| position[dimension] = position.original[dimension] + limit * position.dir; | |
| position.dir = -position.dir; | |
| } | |
| return position; | |
| } | |
| //////////////////////////////////// | |
| // Public | |
| M.init = function() { | |
| } | |
| M.before_start = function() { | |
| var map = systems.main.map(); | |
| for (var i = 0; i < _positions.length; i++) { | |
| _positions[i].x += map.width / 2; | |
| _positions[i].y += map.height / 2; | |
| }; | |
| } | |
| M.update = function(time_step) { | |
| var map = systems.main.map(); | |
| for (var i = 0; i < _positions.length; i++) { | |
| var robot = wiggle(_positions[i], i, time_step); | |
| mnem.influence_map_grid.set_influence(map, Math.floor(robot.x), Math.floor(robot.y), 1.00); | |
| }; | |
| } | |
| M.render = function() { | |
| var extents = systems.main.tile_extents(); | |
| var radius = extents.width / 2; | |
| var g = systems.pixi.graphics(); | |
| g.lineStyle(3, 0x000000, 1.0); | |
| for (var i = 0; i < _positions.length; i++) { | |
| var robot = _positions[i]; | |
| g.drawEllipse(robot.x * extents.width + radius, robot.y * extents.height + radius, radius, radius); | |
| }; | |
| } | |
| // Never forget to return the module! | |
| return M; | |
| }(systems.robots || {})); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| systems = typeof(systems) == "undefined" ? {} : systems; | |
| systems.ui = (function(M) { | |
| //////////////////////////////////// | |
| // Private | |
| var _position = {x: 0, y: 0, z: 0}; | |
| var _size = 0; | |
| function on_generic_slide(slider, value_el, value_suffix) { | |
| var value = $(slider).val(); | |
| $(value_el).text(value + (value_suffix || "")); | |
| return value; | |
| } | |
| function on_generic_set(slider, value_el, value_suffix, setter) { | |
| var value = on_generic_slide(slider, value_el, value_suffix); | |
| setter(value); | |
| } | |
| function setup_slider(index, slider) { | |
| var initial_value = Number($(slider).data("initial")); | |
| var min = Number($(slider).data("min")); | |
| var max = Number($(slider).data("max")); | |
| var suffix = $(slider).data("suffix") || ""; | |
| var setter_name = $(slider).data("setter"); | |
| var intervals = $(slider).data("intervals"); | |
| var range = { | |
| 'min': min, | |
| }; | |
| if (intervals) { | |
| intervals = intervals.split(','); | |
| var step = 100 / (intervals.length + 2); | |
| for (var i = 0; i < intervals.length; i++) { | |
| var n = Number(intervals[i]); | |
| if (!isNaN(n)) { | |
| range[Math.floor(step * (i + 1)) + "%"] = n; | |
| } | |
| }; | |
| } | |
| range['max'] = max | |
| var config = { | |
| start: initial_value, | |
| snap: intervals && intervals.length > 0 ? true : false, | |
| connect: "lower", | |
| range: range, | |
| }; | |
| $(".control-slider", slider).noUiSlider(config) | |
| .on("slide", function() {on_generic_slide(this, $(this).siblings(".control-value"), suffix)}) | |
| .on("set", function() {on_generic_set(this, $(this).siblings(".control-value"), suffix, systems.main[setter_name])}) | |
| .trigger("set"); | |
| } | |
| function setup_selection_list(index, list) { | |
| var setter_name = $(list).data("setter"); | |
| var setter_fn = systems.main[setter_name]; | |
| $("select", list) | |
| .on("change", function() {setter_fn($(this).val())}); | |
| setter_fn($("select", list).val()); | |
| } | |
| //////////////////////////////////// | |
| // Public | |
| M.init = function() { | |
| } | |
| M.before_start = function() { | |
| $('.slider').each(setup_slider); | |
| $('.selection-list').each(setup_selection_list); | |
| } | |
| M.update = function(time_step) { | |
| } | |
| M.render = function() { | |
| } | |
| // Never forget to return the module! | |
| return M; | |
| }(systems.ui || {})); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment