Merge pull request #165 from GDquest/features/heatmap-pathfinding

Heatmap pathfinding for many agents
This commit is contained in:
Nathan Lovato 2019-10-05 08:19:04 +02:00 committed by GitHub
commit 13f090df51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 973 additions and 25 deletions

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "GDNative"]
path = GDNative
url = https://github.com/GodotNativeTools/godot-cpp

1
GDNative Submodule

@ -0,0 +1 @@
Subproject commit 7482074779722df2b704e28ef352dd7bf80cc448

View file

@ -133,3 +133,30 @@
Persona: the learners of the premium course don't want to be spoon-fed ready-made solutions. They enjoy learning and are ready to put in some efforts to improve. They go further than watching the lessons, putting what they learned in practice. They expect quality learning material.
* Building the C++ Heatmap GDNative binary
The GDNative folder is a git submodule pointing to the godot-cpp project (pointing to the latest commits as of October 4th) for Godot 3.1. As a result, after cloning, it should be initialized with something like `git submodule update --init --recursive`, or this repo cloned with `--recursive`.
Bindings for your OS should be generated according to https://docs.godotengine.org/en/3.1/tutorials/plugins/gdnative/gdnative-cpp-example.html
** Windows
Requirements: Visual Studio Community 20xx with C++, `libgodot-cpp.windows.xxxxx.64.lib` files for GDNative C++ in `GDNative/bin/`, and GDNative bindings in `GDNative/include/gen/`
Building the godot bindings:
1. Open the `x64 Native Tools Command Prompt for VS 20xx`.
2. CD into `/GDNative`
3. Run `scons platform=windows target=release bits=64 generate_bindings=yes`
*** Building and Debugging using Visual Studio Community 20xx
If `Godot.exe` is in the `PATH` environment variable, the .lib files are built and located in `GDNative/bin/` and bindings in `GDNative/include/gen/`, then the Heatmap project is already configured for Godot building and debugging.
Building will build the DLL in debug or release mode and put in `assets/libraries/win64`, and debugging the solution in debug mode will launch the project in godot and allow for breakpoints in the C++ code.
*** Scons without opening Visual Studio
1. Open the `x64 Native Tools Command Prompt for VS 20xx`.
2. CD into the Heatmap source (`cd game/src/Native/Heatmap`)
3. `scons platform=windows bits=64 target=release`
4. If successful, `libheatmap.DLL` will be built into `assets/libraries/win64`

View file

@ -0,0 +1,18 @@
[general]
singleton=false
load_once=true
symbol_prefix="godot_"
reloadable=true
[entry]
OSX.64="res://assets/libraries/osx/libheatmap.dylib"
Windows.64="res://assets/libraries/win64/libheatmap.dll"
X11.64="res://assets/libraries/x11/libheatmap.so"
[dependencies]
OSX.64=[ ]
Windows.64=[ ]
X11.64=[ ]

View file

@ -59,6 +59,16 @@ _global_script_classes=[ {
"language": "GDScript",
"path": "res://src/Player/FloorDetector.gd"
}, {
"base": "SteeringBehavior2D",
"class": "FollowHeatmapBehavior2D",
"language": "GDScript",
"path": "res://src/AI/Steering/Behaviors/Individual/FollowHeatmapBehavior2D.gd"
}, {
"base": "CanvasItem",
"class": "HeatmapGD",
"language": "GDScript",
"path": "res://src/AI/Heatmap.gd"
}, {
"base": "Reference",
"class": "Hit",
"language": "GDScript",
@ -79,6 +89,11 @@ _global_script_classes=[ {
"language": "GDScript",
"path": "res://src/AI/Steering/Behaviors/Individual/InterceptBehavior2D.gd"
}, {
"base": "Reference",
"class": "Pathfinder",
"language": "GDScript",
"path": "res://src/AI/Pathfinder.gd"
}, {
"base": "KinematicBody2D",
"class": "Player",
"language": "GDScript",
@ -140,10 +155,13 @@ _global_script_class_icons={
"EvadeBehavior2D": "",
"FleeBehavior2D": "",
"FloorDetector": "",
"FollowHeatmapBehavior2D": "",
"HeatmapGD": "",
"Hit": "",
"Hook": "res://assets/icons/icon_hook.svg",
"HookTarget": "",
"InterceptBehavior2D": "",
"Pathfinder": "",
"Player": "",
"PrioritySteering2D": "",
"SchedulableJob": "",

8
game/src/AI/Heatmap.gdns Normal file
View file

@ -0,0 +1,8 @@
[gd_resource type="NativeScript" load_steps=2 format=2]
[ext_resource path="res://assets/libraries/heatmap.gdnlib" type="GDNativeLibrary" id=1]
[resource]
resource_name = "heatmap"
class_name = "Heatmap"
library = ExtResource( 1 )

86
game/src/AI/Pathfinder.gd Normal file
View file

@ -0,0 +1,86 @@
"""
Finds the path between two points using AStar, in grid coordinates
Code by razcore as part of the GDQuest OpenRPG project:
https://github.com/GDquest/godot-open-rpg
It's been modified and extended a little to not allow diagonals, and to allow
tiles that lie on the negative side of the Tilemap planes.
"""
class_name Pathfinder
var astar : AStar = AStar.new()
var _obstacles : Array
var _map_size : Rect2
var _x_min: float
var _x_max: float
var _y_min: float
var _y_max: float
func initialize(grid : TileMap, obstacle_tile_ids : Array) -> void:
"""
Initializes the AStar node: finds all walkable cells
and connects all walkable paths
"""
# Initialize map size and obstacles array
_map_size = grid.get_used_rect()
_x_min = _map_size.position.x
_x_max = _map_size.size.x - _map_size.position.x
_y_min = _map_size.position.y
_y_max = _map_size.size.y - _map_size.position.y
for id in obstacle_tile_ids:
var occupied_cells = (grid as TileMap).get_used_cells_by_id(id)
for cell in occupied_cells:
_obstacles.append(cell)
# Find all walkable cells and store them in an array
var points_array : = []
for y in range(_y_min, _y_max):
for x in range(_x_min, _x_max):
var point = Vector2(x, y)
if point in _obstacles:
continue
points_array.append(point)
var point_index = calculate_point_index(point)
astar.add_point(point_index, Vector3(point.x, point.y, 0))
# Loop through all walkable cells and their neighbors
# to connect the points
for point in points_array:
var point_index = calculate_point_index(point)
for y in range(0, 3):
for x in range(0, 3):
var point_relative: = Vector2(point.x + x - 1, point.y + y - 1)
#only connect south-north and west-east, no diagonals
if point_relative.x != point.x and point_relative.y != point.y:
continue
var point_relative_index: = calculate_point_index(point_relative)
if (point_relative != point and not is_outside_map_bounds(point_relative)
and astar.has_point(point_relative_index)):
astar.connect_points(point_index, point_relative_index)
func is_outside_map_bounds(point : Vector2) -> bool:
return (point.x < _x_min
or point.y < _y_min
or point.x >= _x_max
or point.y >= _y_max)
func calculate_point_index(point : Vector2) -> int:
point -= _map_size.position
return int(point.x + _map_size.size.x * point.y)
func find_path(start : Vector2, end : Vector2) -> PoolVector3Array:
"""
Returns an array of cells that connect the start and end positions
in grid coordinates
"""
var start_index = calculate_point_index(start)
var end_index = calculate_point_index(end)
return astar.get_point_path(start_index, end_index)

View file

@ -0,0 +1,34 @@
extends SteeringBehavior2D
class_name FollowHeatmapBehavior2D
"""
A 2D steering behavior that expects a heatmap to be somewhere in the scene.
It uses that to figure out where it should be going - from large values in its cell
towards smaller values, with 0 being the goal.
"""
var _heatmap
var _last_point_index: int = INF
var _last_velocity: = Vector2(0, 0)
func _ready() -> void:
_heatmap = get_tree().root.find_node("Heatmap", true, false)
assert _heatmap
func _calculate_steering_internal(steering: SteeringMotion2D) -> SteeringMotion2D:
var actor = get_actor()
var point_index: int = _heatmap.calculate_point_index_for_world_position(actor.global_position)
if point_index != _last_point_index:
_last_point_index = point_index
var to_target: Vector2 = _heatmap.best_direction_for(actor.global_position, true)
steering.velocity = to_target * controller.max_acceleration
_last_velocity = steering.velocity
else:
steering.velocity = _last_velocity
steering.angular_velocity = 0
return steering

View file

@ -0,0 +1,16 @@
extends Node2D
"""
Basic controller for a node2D - its behavior checks the heatmap and points
its directional vector towards the less heat (the 'goal')
"""
onready var behavior: FollowHeatmapBehavior2D = $BehaviorController2D/FollowHeatmapBehavior2D
var _motion: SteeringMotion2D = SteeringMotion2D.new()
var speed: float = 250
func _physics_process(delta: float) -> void:
var desired_velocity: = behavior.calculate_steering(_motion)
position += _motion.velocity.normalized() * speed * delta

View file

@ -0,0 +1,23 @@
[gd_scene load_steps=5 format=2]
[ext_resource path="res://src/AI/SwarmerEnemy.gd" type="Script" id=1]
[ext_resource path="res://src/Player/Rectangle.gd" type="Script" id=2]
[ext_resource path="res://src/AI/Steering/BehaviorController2D.gd" type="Script" id=3]
[ext_resource path="res://src/AI/Steering/Behaviors/Individual/FollowHeatmapBehavior2D.gd" type="Script" id=4]
[node name="SwarmerEnemy" type="Node2D"]
script = ExtResource( 1 )
[node name="Body" type="Node2D" parent="."]
script = ExtResource( 2 )
size = Vector2( 24, 24 )
outline = Vector2( 6, 6 )
color_fill = Color( 0.580392, 0.294118, 0.737255, 1 )
color_outline = Color( 0.27451, 0.12549, 0.807843, 1 )
[node name="BehaviorController2D" type="Node" parent="."]
script = ExtResource( 3 )
actor_path = NodePath("..")
[node name="FollowHeatmapBehavior2D" type="Node" parent="BehaviorController2D"]
script = ExtResource( 4 )

View file

@ -0,0 +1,30 @@
extends Node2D
"""
Spawns a number of swarmer enemies in the scene, who use the heatmap behavior
to chase the player for economical pathfinding.
"""
export var spawner_count: = 50
export var spawn_per_frame: = 10
export var swarmer: PackedScene
export var spawn_radius: = 200
export var minimum_speed: float = 200
export var maximum_speed: float = 300
func _ready():
randomize()
var r_squared: = spawn_radius*spawn_radius
for i in range(spawner_count):
if i % spawn_per_frame:
yield(get_tree(), "idle_frame")
var x: = rand_range(-spawn_radius, spawn_radius)
var y: = rand_range(-1, 1) * sqrt(r_squared-x*x)
var instance: = swarmer.instance()
instance.set_name("Swarmer%s"% i)
add_child(instance)
instance.speed = rand_range(minimum_speed, maximum_speed)
instance.global_position = global_position + Vector2(x,y)

View file

@ -0,0 +1,8 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://src/AI/SwarmerSpawner.gd" type="Script" id=1]
[ext_resource path="res://src/AI/SwarmerEnemy.tscn" type="PackedScene" id=2]
[node name="SwarmerSpawner" type="Node2D"]
script = ExtResource( 1 )
swarmer = ExtResource( 2 )

File diff suppressed because one or more lines are too long

View file

View file

@ -0,0 +1,292 @@
#include "Heatmap.h"
#include <TileMap.hpp>
#include <SceneTree.hpp>
#include <Viewport.hpp>
#include <deque>
#include <TileSet.hpp>
#include <algorithm>
namespace godot {
inline float lerp(const float& a, const float& b, const float& t) {
return a + t * (b - a);
}
void Heatmap::_register_methods() {
//public
register_method("_ready", &Heatmap::_ready);
register_method("_draw", &Heatmap::_draw);
register_method("_process", &Heatmap::_process);
register_method("best_direction_for", &Heatmap::best_direction_for);
register_method("calculate_point_index", &Heatmap::calculate_point_index);
register_method("calculate_point_index_for_world_position", &Heatmap::calculate_point_index_for_world_position);
//semi-private
register_method("_on_Events_player_moved", &Heatmap::on_Events_player_moved);
//properties
register_property<Heatmap, NodePath>("pathfinding_tilemap", &Heatmap::m_pathfinding_tilemap, NodePath());
register_property<Heatmap, bool>("draw_debug", &Heatmap::m_draw_debug, false);
}
Heatmap::Heatmap() : m_draw_debug(false), m_grid(nullptr), m_max_heat(0), m_max_heat_cache(0),
m_x_max(0), m_y_max(0), m_x_min(0), m_y_min(0), m_updating(false) {
}
Heatmap::~Heatmap() {
}
void Heatmap::_init() {
}
void Heatmap::_ready() {
m_grid = (TileMap*)get_node(m_pathfinding_tilemap);
if (m_grid == nullptr) {
Godot::print_error("No tilemap found for Heatmap node.", __FUNCTION__, __FILE__, __LINE__ - 2);
return;
}
m_map_limits = m_grid->get_used_rect();
m_x_min = m_map_limits.position.x;
m_x_max = m_map_limits.size.x - m_map_limits.position.x;
m_y_min = m_map_limits.position.y;
m_y_max = m_map_limits.size.y - m_map_limits.position.y;
unsigned int highest_index = calculate_point_index(m_map_limits.size - m_map_limits.position);
m_cells_heat.resize(highest_index);
m_cells_heat_cache.resize(highest_index);
for (unsigned int i = 0; i < highest_index; ++i) {
m_cells_heat[i] = -1;
}
find_all_obstacles();
get_tree()->get_root()->find_node("Events", true, false)->connect("player_moved", this, "_on_Events_player_moved");
}
//For every cell in 2D array, check the heat and draw a rectangle colored according to its distance from the goal,
//get the direction it points to, and draw a simple vector line.
void Heatmap::_draw() {
if (!m_draw_debug) {
return;
}
Rect2 tile;
Vector2 cell_size = m_grid->get_cell_size();
tile.set_size(cell_size);
cell_size /= 2;
for (int y = int(m_y_min); y<int(m_y_max); ++y) {
for (int x = int(m_x_min); x<int(m_x_max); ++x) {
Vector2 point = Vector2(float(x), float(y));
Vector2 world_position = m_grid->map_to_world(point);
tile.set_position(world_position);
unsigned int cell_index = calculate_point_index(point);
int heat = m_cells_heat[cell_index];
if (heat == -1) {
continue;
}
float proportion = lerp(0.0f, 1.0f, float(heat) / float(m_max_heat));
draw_rect(tile, Color(1.0f - proportion, 0, proportion, 0.75f), true);
world_position += cell_size;
Vector2 direction = best_direction_for(point, false);
draw_rect(Rect2(world_position.x - 5, world_position.y - 5, 10, 10), Color(0, 1, 0), false);
draw_line(world_position, world_position + (direction * 20), Color(1, 1, 1));
}
}
}
void Heatmap::_process(float delta) {
if (!m_updating) {
return;
}
//Seeing if future::get is ready to deliver data or if the thread is still crunching numbers.
//_Is_Ready is not yet standardized, so we check with as immediate a timeout as we can.
if (m_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
Vector2 player_cell_position = m_future.get();
thread_done(player_cell_position);
}
}
//for the 3x3 grid surrounding the cell, find the one with the least heat that isn't -1 (invalid), and return a vector
//that points in its direction. Note that there could be multiple tiles with the same heat, which could cause
Vector2 Heatmap::best_direction_for(Vector2 t_location, bool t_is_world_location) {
Vector2 point = t_is_world_location ? m_grid->world_to_map(t_location) : t_location;
Vector2 world_location = t_is_world_location ? t_location : m_grid->map_to_world(point);
unsigned int cell_index = calculate_point_index(point);
if (cell_index < 0 || cell_index >= m_cells_heat.size()) {
return (m_last_player_cell_position - world_location).normalized();
}
Vector2 best_neighbor = point;
int best_heat = m_cells_heat[cell_index];
if (best_heat == -1) {
return (m_last_player_cell_position - world_location).normalized();
}
for (int y = 0; y < 3; ++y) {
for (int x = 0; x < 3; ++x) {
Vector2 point_relative = Vector2(point.x + float(x) - 1.0f, point.y + float(y) - 1.0f);
if (is_out_of_bounds(point_relative) || std::find(m_obstacles.begin(), m_obstacles.end(), point_relative) != m_obstacles.end()) {
continue;
}
unsigned int point_relative_index = calculate_point_index(point_relative);
if (point_relative_index == cell_index) {
continue;
}
if (point_relative_index >= 0 && point_relative_index < m_cells_heat.size()) {
int heat = m_cells_heat[point_relative_index];
if (heat == -1) {
continue;
}
if (heat <= best_heat) {
best_heat = heat;
best_neighbor = point_relative;
}
}
}
}
Vector2 world_neighbor = m_grid->map_to_world(best_neighbor);
Vector2 direction = (world_neighbor - world_location).normalized();
if (direction.length_squared() == 0) {
return (m_last_player_cell_position - world_location).normalized();
}
return direction;
}
unsigned int Heatmap::calculate_point_index(Vector2 t_point) {
return int((t_point.x - m_map_limits.position.x) + m_map_limits.size.x * (t_point.y - m_map_limits.position.y));
}
unsigned int Heatmap::calculate_point_index_for_world_position(Vector2 t_world_position) {
return calculate_point_index(m_grid->world_to_map(t_world_position));
}
//Runs through every cell and checks if the autotile bitmask of that cell covers the entire cell.
//If so, it is an obstacle. This does mean that thin corners can seem traversable, even though
//they wouldn't be.
void Heatmap::find_all_obstacles() {
Ref<TileSet> tileset = m_grid->get_tileset();
Array tile_ids = tileset->get_tiles_ids();
//TODO: Account for non-autotile tilesets.
for (int y = m_y_min; y < m_y_max; ++y) {
for (int x = m_x_min; x < m_x_max; ++x) {
int cell_id = m_grid->get_cell(x, y);
if (cell_id == -1 || !tile_ids.has(cell_id)) {
continue;
}
int cell_bitmask = tileset->autotile_get_bitmask(cell_id, m_grid->get_cell_autotile_coord(x, y));
TileSet::BitmaskMode mode = tileset->autotile_get_bitmask_mode(cell_id);
int all_covered = 325; //Bitmask is the sum of TileSet::BitmaskMode enum flags.
if (mode == TileSet::BITMASK_3X3 || mode == TileSet::BITMASK_3X3_MINIMAL) {
all_covered = 495;
}
if (cell_bitmask == all_covered) {
m_obstacles.push_back(Vector2(x, y));
}
}
}
}
//Breadth-first search using a queue.
Vector2 Heatmap::refresh_cells_heat(Vector2 t_cell_position) {
std::deque<HeatCell> queue;
//We begin with 4 cells of goals instead of 1 - this alleviates the problem of multiple cells
//having the same amount of heat.
queue.push_back(HeatCell(Vector2(t_cell_position.x, t_cell_position.y), 0));
queue.push_back(HeatCell(Vector2(t_cell_position.x - 1, t_cell_position.y), 0));
queue.push_back(HeatCell(Vector2(t_cell_position.x - 1, t_cell_position.y - 1), 0));
queue.push_back(HeatCell(Vector2(t_cell_position.x, t_cell_position.y - 1), 0));
m_max_heat_cache = 0;
while (!queue.empty()) {
HeatCell cell = queue.front();
queue.pop_front();
Vector2 position = cell.position;
int layer = cell.layer;
unsigned int index = calculate_point_index(position);
if (index < 0 || index >= m_cells_heat_cache.size()) {
continue;
}
m_cells_heat_cache[index] = layer;
if (layer > m_max_heat_cache) {
m_max_heat_cache = layer;
}
for (int y = 0; y < 3; ++y) {
for (int x = 0; x < 3; ++x) {
Vector2 point = Vector2(position.x + float(x) - 1.0f, position.y + float(y) - 1.0f);
unsigned int cell_index = calculate_point_index(point);
HeatCell new_cell = HeatCell(point, layer + 1);
if ( cell_index != index
&& cell_index >= 0 && cell_index < m_cells_heat_cache.size()
&& m_cells_heat_cache[cell_index] == -1
&& !is_out_of_bounds(point)
&& std::find(queue.begin(), queue.end(), new_cell) == queue.end()
&& std::find(m_obstacles.begin(), m_obstacles.end(), point) == m_obstacles.end()) {
queue.push_back(new_cell);
}
}
}
}
return t_cell_position;
}
//Copies the cached, thread specific versions that were used into those used by the main thread.
void Heatmap::thread_done(Vector2 t_cell_position) {
m_updating = false;
m_max_heat = m_max_heat_cache;
for (int i = 0; i < m_cells_heat.size(); ++i) {
m_cells_heat[i] = m_cells_heat_cache[i];
}
m_last_player_cell_position = t_cell_position;
update();
}
bool Heatmap::is_out_of_bounds(Vector2 t_position) {
return t_position.x < m_x_min || t_position.y < m_y_min
|| t_position.x > m_x_max || t_position.y > m_y_max;
}
void Heatmap::on_Events_player_moved(Node2D* t_player) {
if (m_updating) {
return;
}
Vector2 player_cell_position = m_grid->world_to_map(t_player->get_global_position());
bool out_of_bounds = is_out_of_bounds(player_cell_position);
Vector2 difference = player_cell_position - m_last_player_cell_position;
if (!out_of_bounds && (difference.x != 0 || difference.y != 0)) {
for (int i = 0; i < m_cells_heat_cache.size(); ++i) {
m_cells_heat_cache[i] = -1;
}
m_updating = true;
// Launch an immediate new thread with ::launch::async.
// Without that flag, it waits for a future.get()/wait() before starting work.
m_future = std::async(std::launch::async, &Heatmap::refresh_cells_heat, this, player_cell_position);
}
}
}

View file

@ -0,0 +1,86 @@
// Heatmap
// Francois "@Razoric480" Belair
//
// GDNative Godot Class that uses a floodfill algorithm. Radiating out from the player position, it covers the whole tilemap.
// Each step removed adds one to the layer count. This can then be used by agents for quick
// lookup based pathfinding, instead of calculating A-star paths or other, potentially more expensive pathfinding routines.
#ifndef HEATMAP_H
#define HEATMAP_H
#include <Godot.hpp>
#include <Node2D.hpp>
#include <vector>
#include <future>
namespace godot {
//Forward declarations
class TileMap;
// HeatCell. Simple structure used by the `refresh_cells_heat` member function to contain both
// position and current layer in the flood fill search.
struct HeatCell {
HeatCell(Vector2 t_position, int t_layer) {
position = t_position;
layer = t_layer;
}
Vector2 position;
int layer;
bool operator ==(HeatCell const& other) {
return other.layer == layer && other.position == position;
}
bool operator !=(HeatCell const& other) {
return other.layer != layer || other.position != position;
}
};
class Heatmap : public Node2D {
GODOT_CLASS(Heatmap, Node2D)
public:
Heatmap();
~Heatmap();
static void _register_methods();
void _init();
void _ready();
void _draw();
void _process(float delta);
Vector2 best_direction_for(Vector2 t_location, bool t_is_world_location);
unsigned int calculate_point_index(Vector2 t_point);
unsigned int calculate_point_index_for_world_position(Vector2 t_world_position);
private:
void find_all_obstacles();
Vector2 refresh_cells_heat(Vector2 t_cell_position);
bool is_out_of_bounds(Vector2 t_position);
void on_Events_player_moved(Node2D* t_player);
void thread_done(Vector2 t_cell_position);
private:
NodePath m_pathfinding_tilemap;
bool m_draw_debug;
TileMap* m_grid;
Rect2 m_map_limits;
float m_x_min;
float m_x_max;
float m_y_min;
float m_y_max;
int m_max_heat;
int m_max_heat_cache;
bool m_updating;
std::vector<int> m_cells_heat;
std::vector<int> m_cells_heat_cache;
std::vector<Vector2> m_obstacles;
Vector2 m_last_player_cell_position;
std::future<Vector2> m_future;
};
}
#endif /* HEATMAP_H */

View file

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29230.47
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Heatmap", "Heatmap.vcxproj", "{95F50422-B0D5-44A4-8A83-3B56BFC6B7B0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{95F50422-B0D5-44A4-8A83-3B56BFC6B7B0}.Debug|x64.ActiveCfg = Debug|x64
{95F50422-B0D5-44A4-8A83-3B56BFC6B7B0}.Debug|x64.Build.0 = Debug|x64
{95F50422-B0D5-44A4-8A83-3B56BFC6B7B0}.Release|x64.ActiveCfg = Release|x64
{95F50422-B0D5-44A4-8A83-3B56BFC6B7B0}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6B60736E-491D-46DB-B927-593F9A8CEDF2}
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Heatmap.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="gdlibrary.cpp" />
<ClCompile Include="Heatmap.cpp" />
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<ProjectGuid>{95F50422-B0D5-44A4-8A83-3B56BFC6B7B0}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>Heatmap</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>..\..\..\..\GDNative\include;..\..\..\..\GDNative\include\core;..\..\..\..\GDNative\include\gen;..\..\..\..\GDNative\godot_headers;$(IncludePath)</IncludePath>
<TargetName>libheatmap</TargetName>
<LibraryPath>..\..\..\..\GDNative\bin;$(LibraryPath)</LibraryPath>
<OutDir>..\..\..\game\assets\libraries\win64\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>..\..\..\..\GDNative\include;..\..\..\..\GDNative\include\core;..\..\..\..\GDNative\include\gen;..\..\..\..\GDNative\godot_headers;$(IncludePath)</IncludePath>
<LibraryPath>..\..\..\..\GDNative\bin;$(LibraryPath)</LibraryPath>
<OutDir>..\..\..\game\assets\libraries\win64\</OutDir>
<TargetName>libheatmap</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;HEATMAP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalDependencies>libgodot-cpp.windows.debug.64.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;HEATMAP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalDependencies>libgodot-cpp.windows.release.64.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Heatmap.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="gdlibrary.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Heatmap.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View file

@ -0,0 +1,107 @@
#!python
import os, subprocess
opts = Variables([], ARGUMENTS)
# Gets the standard flags CC, CCX, etc.
env = DefaultEnvironment()
# Define our options
opts.Add(EnumVariable('target', "Compilation target", 'debug', ['d', 'debug', 'r', 'release']))
opts.Add(EnumVariable('platform', "Compilation platform", '', ['', 'windows', 'x11', 'linux', 'osx']))
opts.Add(EnumVariable('p', "Compilation target, alias for 'platform'", '', ['', 'windows', 'x11', 'linux', 'osx']))
opts.Add(BoolVariable('use_llvm', "Use the LLVM / Clang compiler", 'no'))
opts.Add(PathVariable('target_path', 'The path where the lib is installed.', '../../../assets/libraries/'))
opts.Add(PathVariable('target_name', 'The library name.', 'libheatmap', PathVariable.PathAccept))
# Local dependency paths, adapt them to your setup
godot_headers_path = "../../../../GDNative/godot_headers/"
cpp_bindings_path = "../../../../GDNative/"
cpp_library = "libgodot-cpp"
# only support 64 at this time..
bits = 64
# Updates the environment with the option variables.
opts.Update(env)
# Process some arguments
if env['use_llvm']:
env['CC'] = 'clang'
env['CXX'] = 'clang++'
if env['p'] != '':
env['platform'] = env['p']
if env['platform'] == '':
print("No valid target platform selected.")
quit();
# For the reference:
# - CCFLAGS are compilation flags shared between C and C++
# - CFLAGS are for C-specific compilation flags
# - CXXFLAGS are for C++-specific compilation flags
# - CPPFLAGS are for pre-processor flags
# - CPPDEFINES are for pre-processor defines
# - LINKFLAGS are for linking flags
# Check our platform specifics
if env['platform'] == "osx":
env['target_path'] += 'osx/'
cpp_library += '.osx'
if env['target'] in ('debug', 'd'):
env.Append(CCFLAGS=['-g', '-O2', '-arch', 'x86_64', '-fPIC'])
env.Append(LINKFLAGS=['-arch', 'x86_64'])
else:
env.Append(CCFLAGS=['-g', '-O3', '-arch', 'x86_64', '-fPIC'])
env.Append(LINKFLAGS=['-s'])
elif env['platform'] in ('x11', 'linux'):
env['target_path'] += 'x11/'
cpp_library += '.linux'
if env['target'] in ('debug', 'd'):
env.Append(CCFLAGS=['-fPIC', '-g3', '-Og'])
env.Append(CXXFLAGS=['-std=c++17'])
else:
env.Append(CCFLAGS=['-fPIC', '-O3'])
env.Append(CXXFLAGS=['-std=c++17'])
elif env['platform'] == "windows":
env['target_path'] += 'win64/'
cpp_library += '.windows'
# This makes sure to keep the session environment variables on windows,
# that way you can run scons in a vs 2017 prompt and it will find all the required tools
env.Append(ENV=os.environ)
env.Append(CPPDEFINES=['WIN32', '_WIN32', '_WINDOWS', '_CRT_SECURE_NO_WARNINGS'])
env.Append(CCFLAGS=['-W3', '-GR'])
if env['target'] in ('debug', 'd'):
env.Append(CPPDEFINES=['_DEBUG'])
env.Append(CCFLAGS=['-EHsc', '-MDd', '-ZI'])
env.Append(LINKFLAGS=['-DEBUG'])
else:
env.Append(CPPDEFINES=['NDEBUG'])
env.Append(CCFLAGS=['-O2', '-EHsc', '-MD'])
if env['target'] in ('debug', 'd'):
cpp_library += '.debug'
else:
cpp_library += '.release'
cpp_library += '.' + str(bits)
# make sure our binding library is properly includes
env.Append(CPPPATH=['.', godot_headers_path, cpp_bindings_path + 'include/', cpp_bindings_path + 'include/core/', cpp_bindings_path + 'include/gen/'])
env.Append(LIBPATH=[cpp_bindings_path + 'bin/'])
env.Append(LIBS=[cpp_library])
# tweak this if you want to use different folders, or more folders, to store your source code in.
env.Append(CPPPATH=['./'])
sources = Glob('./*.cpp')
library = env.SharedLibrary(target=env['target_path'] + env['target_name'] , source=sources)
Default(library)
# Generates help for the -h scons option.
Help(opts.GenerateHelpText(env))

View file

@ -0,0 +1,15 @@
#include "Heatmap.h"
extern "C" void GDN_EXPORT godot_gdnative_init(godot_gdnative_init_options *o) {
godot::Godot::gdnative_init(o);
}
extern "C" void GDN_EXPORT godot_gdnative_terminate(godot_gdnative_terminate_options *o) {
godot::Godot::gdnative_terminate(o);
}
extern "C" void GDN_EXPORT godot_nativescript_init(void *handle) {
godot::Godot::nativescript_init(handle);
godot::register_class<godot::Heatmap>();
}