Skip to content

Instantly share code, notes, and snippets.

@mnem
Last active January 3, 2017 09:14
Show Gist options
  • Select an option

  • Save mnem/87e8998f389f1aa30227 to your computer and use it in GitHub Desktop.

Select an option

Save mnem/87e8998f389f1aa30227 to your computer and use it in GitHub Desktop.
Influence Maps
<!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">&nbsp;</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>
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 || {}));
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 || {}));
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 || {}));
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 || {}));
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 || {}));
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 || {}));
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 || {}));
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 || {}, $));
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 || {}));
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