Merge pull request #7908 from SaracenOne/recast
In-editor navmesh generation.
This commit is contained in:
commit
27ae3c839d
|
@ -168,6 +168,7 @@ opts.Add('builtin_libwebp', "Use the builtin libwebp library (yes/no)", 'yes')
|
||||||
opts.Add('builtin_openssl', "Use the builtin openssl library (yes/no)", 'yes')
|
opts.Add('builtin_openssl', "Use the builtin openssl library (yes/no)", 'yes')
|
||||||
opts.Add('builtin_opus', "Use the builtin opus library (yes/no)", 'yes')
|
opts.Add('builtin_opus', "Use the builtin opus library (yes/no)", 'yes')
|
||||||
opts.Add('builtin_pcre2', "Use the builtin pcre2 library (yes/no)", 'yes')
|
opts.Add('builtin_pcre2', "Use the builtin pcre2 library (yes/no)", 'yes')
|
||||||
|
opts.Add('builtin_recast', "Use the builtin recast library (yes/no)", 'yes')
|
||||||
opts.Add('builtin_squish', "Use the builtin squish library (yes/no)", 'yes')
|
opts.Add('builtin_squish', "Use the builtin squish library (yes/no)", 'yes')
|
||||||
opts.Add('builtin_zlib', "Use the builtin zlib library (yes/no)", 'yes')
|
opts.Add('builtin_zlib', "Use the builtin zlib library (yes/no)", 'yes')
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,7 @@
|
||||||
#include "editor/plugins/mesh_editor_plugin.h"
|
#include "editor/plugins/mesh_editor_plugin.h"
|
||||||
#include "editor/plugins/mesh_instance_editor_plugin.h"
|
#include "editor/plugins/mesh_instance_editor_plugin.h"
|
||||||
#include "editor/plugins/multimesh_editor_plugin.h"
|
#include "editor/plugins/multimesh_editor_plugin.h"
|
||||||
|
#include "editor/plugins/navigation_mesh_editor_plugin.h"
|
||||||
#include "editor/plugins/navigation_polygon_editor_plugin.h"
|
#include "editor/plugins/navigation_polygon_editor_plugin.h"
|
||||||
#include "editor/plugins/particles_2d_editor_plugin.h"
|
#include "editor/plugins/particles_2d_editor_plugin.h"
|
||||||
#include "editor/plugins/particles_editor_plugin.h"
|
#include "editor/plugins/particles_editor_plugin.h"
|
||||||
|
@ -5440,6 +5441,7 @@ EditorNode::EditorNode() {
|
||||||
add_editor_plugin(memnew(TextureEditorPlugin(this)));
|
add_editor_plugin(memnew(TextureEditorPlugin(this)));
|
||||||
add_editor_plugin(memnew(MeshEditorPlugin(this)));
|
add_editor_plugin(memnew(MeshEditorPlugin(this)));
|
||||||
add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor)));
|
add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor)));
|
||||||
|
add_editor_plugin(memnew(NavigationMeshEditorPlugin(this)));
|
||||||
|
|
||||||
// FIXME: Disabled as (according to reduz) users were complaining that it gets in the way
|
// FIXME: Disabled as (according to reduz) users were complaining that it gets in the way
|
||||||
// Waiting for PropertyEditor rewrite (planned for 3.1) to be refactored.
|
// Waiting for PropertyEditor rewrite (planned for 3.1) to be refactored.
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
/*************************************************************************/
|
||||||
|
/* navigation_mesh_editor_plugin.cpp */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
#include "navigation_mesh_editor_plugin.h"
|
||||||
|
#include "io/marshalls.h"
|
||||||
|
#include "io/resource_saver.h"
|
||||||
|
#include "scene/3d/mesh_instance.h"
|
||||||
|
#include "scene/gui/box_container.h"
|
||||||
|
|
||||||
|
#ifdef RECAST_ENABLED
|
||||||
|
|
||||||
|
void NavigationMeshEditor::_node_removed(Node *p_node) {
|
||||||
|
|
||||||
|
if (p_node == node) {
|
||||||
|
node = NULL;
|
||||||
|
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMeshEditor::_notification(int p_option) {
|
||||||
|
|
||||||
|
if (p_option == NOTIFICATION_ENTER_TREE) {
|
||||||
|
|
||||||
|
button_bake->set_icon(get_icon("Bake", "EditorIcons"));
|
||||||
|
button_reset->set_icon(get_icon("Reload", "EditorIcons"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMeshEditor::_bake_pressed() {
|
||||||
|
|
||||||
|
ERR_FAIL_COND(!node);
|
||||||
|
const String conf_warning = node->get_configuration_warning();
|
||||||
|
if (!conf_warning.empty()) {
|
||||||
|
err_dialog->set_text(conf_warning);
|
||||||
|
err_dialog->popup_centered_minsize();
|
||||||
|
button_bake->set_pressed(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationMeshGenerator::clear(node->get_navigation_mesh());
|
||||||
|
NavigationMeshGenerator::bake(node->get_navigation_mesh(), node);
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
node->update_gizmo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMeshEditor::_clear_pressed() {
|
||||||
|
|
||||||
|
if (node)
|
||||||
|
NavigationMeshGenerator::clear(node->get_navigation_mesh());
|
||||||
|
|
||||||
|
button_bake->set_pressed(false);
|
||||||
|
bake_info->set_text("");
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
node->update_gizmo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMeshEditor::edit(NavigationMeshInstance *p_nav_mesh_instance) {
|
||||||
|
|
||||||
|
if (p_nav_mesh_instance == NULL || node == p_nav_mesh_instance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
node = p_nav_mesh_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMeshEditor::_bind_methods() {
|
||||||
|
|
||||||
|
ClassDB::bind_method("_bake_pressed", &NavigationMeshEditor::_bake_pressed);
|
||||||
|
ClassDB::bind_method("_clear_pressed", &NavigationMeshEditor::_clear_pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationMeshEditor::NavigationMeshEditor() {
|
||||||
|
|
||||||
|
bake_hbox = memnew(HBoxContainer);
|
||||||
|
button_bake = memnew(ToolButton);
|
||||||
|
button_bake->set_text(TTR("Bake!"));
|
||||||
|
button_bake->set_toggle_mode(true);
|
||||||
|
button_reset = memnew(Button);
|
||||||
|
button_bake->set_tooltip(TTR("Bake the navigation mesh.\n"));
|
||||||
|
|
||||||
|
bake_info = memnew(Label);
|
||||||
|
bake_hbox->add_child(button_bake);
|
||||||
|
bake_hbox->add_child(button_reset);
|
||||||
|
bake_hbox->add_child(bake_info);
|
||||||
|
|
||||||
|
err_dialog = memnew(AcceptDialog);
|
||||||
|
add_child(err_dialog);
|
||||||
|
node = NULL;
|
||||||
|
|
||||||
|
button_bake->connect("pressed", this, "_bake_pressed");
|
||||||
|
button_reset->connect("pressed", this, "_clear_pressed");
|
||||||
|
button_reset->set_tooltip(TTR("Clear the navigation mesh."));
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationMeshEditor::~NavigationMeshEditor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMeshEditorPlugin::edit(Object *p_object) {
|
||||||
|
|
||||||
|
navigation_mesh_editor->edit(Object::cast_to<NavigationMeshInstance>(p_object));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NavigationMeshEditorPlugin::handles(Object *p_object) const {
|
||||||
|
|
||||||
|
return p_object->is_class("NavigationMeshInstance");
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMeshEditorPlugin::make_visible(bool p_visible) {
|
||||||
|
|
||||||
|
if (p_visible) {
|
||||||
|
navigation_mesh_editor->show();
|
||||||
|
navigation_mesh_editor->bake_hbox->show();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
navigation_mesh_editor->hide();
|
||||||
|
navigation_mesh_editor->bake_hbox->hide();
|
||||||
|
navigation_mesh_editor->edit(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationMeshEditorPlugin::NavigationMeshEditorPlugin(EditorNode *p_node) {
|
||||||
|
|
||||||
|
editor = p_node;
|
||||||
|
navigation_mesh_editor = memnew(NavigationMeshEditor);
|
||||||
|
editor->get_viewport()->add_child(navigation_mesh_editor);
|
||||||
|
add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, navigation_mesh_editor->bake_hbox);
|
||||||
|
navigation_mesh_editor->hide();
|
||||||
|
navigation_mesh_editor->bake_hbox->hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationMeshEditorPlugin::~NavigationMeshEditorPlugin() {
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // RECAST_ENABLED
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*************************************************************************/
|
||||||
|
/* navigation_mesh_editor_plugin.h */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
#ifndef NAVIGATION_MESH_GENERATOR_PLUGIN_H
|
||||||
|
#define NAVIGATION_MESH_GENERATOR_PLUGIN_H
|
||||||
|
|
||||||
|
#ifdef RECAST_ENABLED
|
||||||
|
|
||||||
|
#include "editor/editor_node.h"
|
||||||
|
#include "editor/editor_plugin.h"
|
||||||
|
#include "navigation_mesh_generator.h"
|
||||||
|
|
||||||
|
class NavigationMeshEditor : public Control {
|
||||||
|
friend class NavigationMeshEditorPlugin;
|
||||||
|
|
||||||
|
GDCLASS(NavigationMeshEditor, Control);
|
||||||
|
|
||||||
|
AcceptDialog *err_dialog;
|
||||||
|
|
||||||
|
HBoxContainer *bake_hbox;
|
||||||
|
Button *button_bake;
|
||||||
|
Button *button_reset;
|
||||||
|
Label *bake_info;
|
||||||
|
|
||||||
|
NavigationMeshInstance *node;
|
||||||
|
|
||||||
|
void _bake_pressed();
|
||||||
|
void _clear_pressed();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void _node_removed(Node *p_node);
|
||||||
|
static void _bind_methods();
|
||||||
|
void _notification(int p_what);
|
||||||
|
|
||||||
|
public:
|
||||||
|
void edit(NavigationMeshInstance *p_nav_mesh_instance);
|
||||||
|
NavigationMeshEditor();
|
||||||
|
~NavigationMeshEditor();
|
||||||
|
};
|
||||||
|
|
||||||
|
class NavigationMeshEditorPlugin : public EditorPlugin {
|
||||||
|
|
||||||
|
GDCLASS(NavigationMeshEditorPlugin, EditorPlugin);
|
||||||
|
|
||||||
|
NavigationMeshEditor *navigation_mesh_editor;
|
||||||
|
EditorNode *editor;
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual String get_name() const { return "NavigationMesh"; }
|
||||||
|
bool has_main_screen() const { return false; }
|
||||||
|
virtual void edit(Object *p_node);
|
||||||
|
virtual bool handles(Object *p_node) const;
|
||||||
|
virtual void make_visible(bool p_visible);
|
||||||
|
|
||||||
|
NavigationMeshEditorPlugin(EditorNode *p_node);
|
||||||
|
~NavigationMeshEditorPlugin();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // RECAST_ENABLED
|
||||||
|
#endif // NAVIGATION_MESH_GENERATOR_PLUGIN_H
|
|
@ -0,0 +1,311 @@
|
||||||
|
/*************************************************************************/
|
||||||
|
/* navigation_mesh_generator.cpp */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
#include "navigation_mesh_generator.h"
|
||||||
|
|
||||||
|
#ifdef RECAST_ENABLED
|
||||||
|
|
||||||
|
void NavigationMeshGenerator::_add_vertex(const Vector3 &p_vec3, Vector<float> &p_verticies) {
|
||||||
|
p_verticies.push_back(p_vec3.x);
|
||||||
|
p_verticies.push_back(p_vec3.y);
|
||||||
|
p_verticies.push_back(p_vec3.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMeshGenerator::_add_mesh(const Ref<Mesh> &p_mesh, const Transform &p_xform, Vector<float> &p_verticies, Vector<int> &p_indices) {
|
||||||
|
int current_vertex_count = p_verticies.size() / 3;
|
||||||
|
|
||||||
|
for (int i = 0; i < p_mesh->get_surface_count(); i++) {
|
||||||
|
if (p_mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int index_count = 0;
|
||||||
|
if (p_mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {
|
||||||
|
index_count = p_mesh->surface_get_array_index_len(i);
|
||||||
|
} else {
|
||||||
|
index_count = p_mesh->surface_get_array_len(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0));
|
||||||
|
|
||||||
|
int face_count = index_count / 3;
|
||||||
|
|
||||||
|
Array a = p_mesh->surface_get_arrays(i);
|
||||||
|
|
||||||
|
PoolVector<Vector3> mesh_vertices = a[Mesh::ARRAY_VERTEX];
|
||||||
|
PoolVector<Vector3>::Read vr = mesh_vertices.read();
|
||||||
|
|
||||||
|
if (p_mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) {
|
||||||
|
|
||||||
|
PoolVector<int> mesh_indices = a[Mesh::ARRAY_INDEX];
|
||||||
|
PoolVector<int>::Read ir = mesh_indices.read();
|
||||||
|
|
||||||
|
for (int i = 0; i < mesh_vertices.size(); i++) {
|
||||||
|
_add_vertex(p_xform.xform(vr[i]), p_verticies);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < face_count; i++) {
|
||||||
|
// CCW
|
||||||
|
p_indices.push_back(current_vertex_count + (ir[i * 3 + 0]));
|
||||||
|
p_indices.push_back(current_vertex_count + (ir[i * 3 + 2]));
|
||||||
|
p_indices.push_back(current_vertex_count + (ir[i * 3 + 1]));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
face_count = mesh_vertices.size() / 3;
|
||||||
|
for (int i = 0; i < face_count; i++) {
|
||||||
|
_add_vertex(p_xform.xform(vr[i * 3 + 0]), p_verticies);
|
||||||
|
_add_vertex(p_xform.xform(vr[i * 3 + 2]), p_verticies);
|
||||||
|
_add_vertex(p_xform.xform(vr[i * 3 + 1]), p_verticies);
|
||||||
|
|
||||||
|
p_indices.push_back(current_vertex_count + (i * 3 + 0));
|
||||||
|
p_indices.push_back(current_vertex_count + (i * 3 + 1));
|
||||||
|
p_indices.push_back(current_vertex_count + (i * 3 + 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMeshGenerator::_parse_geometry(const Transform &p_base_inverse, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices) {
|
||||||
|
|
||||||
|
if (Object::cast_to<MeshInstance>(p_node)) {
|
||||||
|
|
||||||
|
MeshInstance *mesh_instance = Object::cast_to<MeshInstance>(p_node);
|
||||||
|
Ref<Mesh> mesh = mesh_instance->get_mesh();
|
||||||
|
if (mesh.is_valid()) {
|
||||||
|
_add_mesh(mesh, p_base_inverse * mesh_instance->get_global_transform(), p_verticies, p_indices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < p_node->get_child_count(); i++) {
|
||||||
|
_parse_geometry(p_base_inverse, p_node->get_child(i), p_verticies, p_indices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMeshGenerator::_convert_detail_mesh_to_native_navigation_mesh(const rcPolyMeshDetail *p_detail_mesh, Ref<NavigationMesh> p_nav_mesh) {
|
||||||
|
|
||||||
|
PoolVector<Vector3> nav_vertices;
|
||||||
|
|
||||||
|
for (int i = 0; i < p_detail_mesh->nverts; i++) {
|
||||||
|
const float *v = &p_detail_mesh->verts[i * 3];
|
||||||
|
nav_vertices.append(Vector3(v[0], v[1], v[2]));
|
||||||
|
}
|
||||||
|
p_nav_mesh->set_vertices(nav_vertices);
|
||||||
|
|
||||||
|
for (int i = 0; i < p_detail_mesh->nmeshes; i++) {
|
||||||
|
const unsigned int *m = &p_detail_mesh->meshes[i * 4];
|
||||||
|
const unsigned int bverts = m[0];
|
||||||
|
const unsigned int btris = m[2];
|
||||||
|
const unsigned int ntris = m[3];
|
||||||
|
const unsigned char *tris = &p_detail_mesh->tris[btris * 4];
|
||||||
|
for (unsigned int j = 0; j < ntris; j++) {
|
||||||
|
Vector<int> nav_indices;
|
||||||
|
nav_indices.resize(3);
|
||||||
|
nav_indices[0] = ((int)(bverts + tris[j * 4 + 0]));
|
||||||
|
nav_indices[1] = ((int)(bverts + tris[j * 4 + 1]));
|
||||||
|
nav_indices[2] = ((int)(bverts + tris[j * 4 + 2]));
|
||||||
|
p_nav_mesh->add_polygon(nav_indices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMeshGenerator::_build_recast_navigation_mesh(Ref<NavigationMesh> p_nav_mesh, EditorProgress *ep,
|
||||||
|
rcHeightfield *hf, rcCompactHeightfield *chf, rcContourSet *cset, rcPolyMesh *poly_mesh, rcPolyMeshDetail *detail_mesh,
|
||||||
|
Vector<float> &verticies, Vector<int> &indices) {
|
||||||
|
rcContext ctx;
|
||||||
|
ep->step(TTR("Setting up Configuration..."), 1);
|
||||||
|
|
||||||
|
const float *verts = verticies.ptr();
|
||||||
|
const int nverts = verticies.size() / 3;
|
||||||
|
const int *tris = indices.ptr();
|
||||||
|
const int ntris = indices.size() / 3;
|
||||||
|
|
||||||
|
float bmin[3], bmax[3];
|
||||||
|
rcCalcBounds(verts, nverts, bmin, bmax);
|
||||||
|
|
||||||
|
rcConfig cfg;
|
||||||
|
memset(&cfg, 0, sizeof(cfg));
|
||||||
|
|
||||||
|
cfg.cs = p_nav_mesh->get_cell_size();
|
||||||
|
cfg.ch = p_nav_mesh->get_cell_height();
|
||||||
|
cfg.walkableSlopeAngle = p_nav_mesh->get_agent_max_slope();
|
||||||
|
cfg.walkableHeight = (int)Math::ceil(p_nav_mesh->get_agent_height() / cfg.ch);
|
||||||
|
cfg.walkableClimb = (int)Math::floor(p_nav_mesh->get_agent_max_climb() / cfg.ch);
|
||||||
|
cfg.walkableRadius = (int)Math::ceil(p_nav_mesh->get_agent_radius() / cfg.cs);
|
||||||
|
cfg.maxEdgeLen = (int)(p_nav_mesh->get_edge_max_length() / p_nav_mesh->get_cell_size());
|
||||||
|
cfg.maxSimplificationError = p_nav_mesh->get_edge_max_error();
|
||||||
|
cfg.minRegionArea = (int)(p_nav_mesh->get_region_min_size() * p_nav_mesh->get_region_min_size());
|
||||||
|
cfg.mergeRegionArea = (int)(p_nav_mesh->get_region_merge_size() * p_nav_mesh->get_region_merge_size());
|
||||||
|
cfg.maxVertsPerPoly = (int)p_nav_mesh->get_verts_per_poly();
|
||||||
|
cfg.detailSampleDist = p_nav_mesh->get_detail_sample_distance() < 0.9f ? 0 : p_nav_mesh->get_cell_size() * p_nav_mesh->get_detail_sample_distance();
|
||||||
|
cfg.detailSampleMaxError = p_nav_mesh->get_cell_height() * p_nav_mesh->get_detail_sample_max_error();
|
||||||
|
|
||||||
|
cfg.bmin[0] = bmin[0];
|
||||||
|
cfg.bmin[1] = bmin[1];
|
||||||
|
cfg.bmin[2] = bmin[2];
|
||||||
|
cfg.bmax[0] = bmax[0];
|
||||||
|
cfg.bmax[1] = bmax[1];
|
||||||
|
cfg.bmax[2] = bmax[2];
|
||||||
|
|
||||||
|
ep->step(TTR("Calculating grid size..."), 2);
|
||||||
|
rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height);
|
||||||
|
|
||||||
|
ep->step(TTR("Creating heightfield..."), 3);
|
||||||
|
hf = rcAllocHeightfield();
|
||||||
|
|
||||||
|
ERR_FAIL_COND(!hf);
|
||||||
|
ERR_FAIL_COND(!rcCreateHeightfield(&ctx, *hf, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch));
|
||||||
|
|
||||||
|
ep->step(TTR("Marking walkable triangles..."), 4);
|
||||||
|
{
|
||||||
|
Vector<unsigned char> tri_areas;
|
||||||
|
tri_areas.resize(ntris);
|
||||||
|
|
||||||
|
ERR_FAIL_COND(tri_areas.size() == 0);
|
||||||
|
|
||||||
|
memset(tri_areas.ptr(), 0, ntris * sizeof(unsigned char));
|
||||||
|
rcMarkWalkableTriangles(&ctx, cfg.walkableSlopeAngle, verts, nverts, tris, ntris, tri_areas.ptr());
|
||||||
|
|
||||||
|
ERR_FAIL_COND(!rcRasterizeTriangles(&ctx, verts, nverts, tris, tri_areas.ptr(), ntris, *hf, cfg.walkableClimb));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p_nav_mesh->get_filter_low_hanging_obstacles())
|
||||||
|
rcFilterLowHangingWalkableObstacles(&ctx, cfg.walkableClimb, *hf);
|
||||||
|
if (p_nav_mesh->get_filter_ledge_spans())
|
||||||
|
rcFilterLedgeSpans(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf);
|
||||||
|
if (p_nav_mesh->get_filter_walkable_low_height_spans())
|
||||||
|
rcFilterWalkableLowHeightSpans(&ctx, cfg.walkableHeight, *hf);
|
||||||
|
|
||||||
|
ep->step(TTR("Constructing compact heightfield..."), 5);
|
||||||
|
|
||||||
|
chf = rcAllocCompactHeightfield();
|
||||||
|
|
||||||
|
ERR_FAIL_COND(!chf);
|
||||||
|
ERR_FAIL_COND(!rcBuildCompactHeightfield(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf, *chf));
|
||||||
|
|
||||||
|
rcFreeHeightField(hf);
|
||||||
|
hf = 0;
|
||||||
|
|
||||||
|
ep->step(TTR("Eroding walkable area..."), 6);
|
||||||
|
ERR_FAIL_COND(!rcErodeWalkableArea(&ctx, cfg.walkableRadius, *chf));
|
||||||
|
|
||||||
|
ep->step(TTR("Partioning..."), 7);
|
||||||
|
if (p_nav_mesh->get_sample_partition_type() == NavigationMesh::SAMPLE_PARTITION_WATERSHED) {
|
||||||
|
ERR_FAIL_COND(!rcBuildDistanceField(&ctx, *chf));
|
||||||
|
ERR_FAIL_COND(!rcBuildRegions(&ctx, *chf, 0, cfg.minRegionArea, cfg.mergeRegionArea));
|
||||||
|
} else if (p_nav_mesh->get_sample_partition_type() == NavigationMesh::SAMPLE_PARTITION_MONOTONE) {
|
||||||
|
ERR_FAIL_COND(!rcBuildRegionsMonotone(&ctx, *chf, 0, cfg.minRegionArea, cfg.mergeRegionArea));
|
||||||
|
} else {
|
||||||
|
ERR_FAIL_COND(!rcBuildLayerRegions(&ctx, *chf, 0, cfg.minRegionArea));
|
||||||
|
}
|
||||||
|
|
||||||
|
ep->step(TTR("Creating contours..."), 8);
|
||||||
|
|
||||||
|
cset = rcAllocContourSet();
|
||||||
|
|
||||||
|
ERR_FAIL_COND(!cset);
|
||||||
|
ERR_FAIL_COND(!rcBuildContours(&ctx, *chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cset));
|
||||||
|
|
||||||
|
ep->step(TTR("Creating polymesh..."), 9);
|
||||||
|
|
||||||
|
poly_mesh = rcAllocPolyMesh();
|
||||||
|
ERR_FAIL_COND(!poly_mesh);
|
||||||
|
ERR_FAIL_COND(!rcBuildPolyMesh(&ctx, *cset, cfg.maxVertsPerPoly, *poly_mesh));
|
||||||
|
|
||||||
|
detail_mesh = rcAllocPolyMeshDetail();
|
||||||
|
ERR_FAIL_COND(!detail_mesh);
|
||||||
|
ERR_FAIL_COND(!rcBuildPolyMeshDetail(&ctx, *poly_mesh, *chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *detail_mesh));
|
||||||
|
|
||||||
|
rcFreeCompactHeightfield(chf);
|
||||||
|
chf = 0;
|
||||||
|
rcFreeContourSet(cset);
|
||||||
|
cset = 0;
|
||||||
|
|
||||||
|
ep->step(TTR("Converting to native navigation mesh..."), 10);
|
||||||
|
|
||||||
|
_convert_detail_mesh_to_native_navigation_mesh(detail_mesh, p_nav_mesh);
|
||||||
|
|
||||||
|
rcFreePolyMesh(poly_mesh);
|
||||||
|
poly_mesh = 0;
|
||||||
|
rcFreePolyMeshDetail(detail_mesh);
|
||||||
|
detail_mesh = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMeshGenerator::bake(Ref<NavigationMesh> p_nav_mesh, Node *p_node) {
|
||||||
|
|
||||||
|
ERR_FAIL_COND(!p_nav_mesh.is_valid());
|
||||||
|
|
||||||
|
EditorProgress ep("bake", TTR("Navigation Mesh Generator Setup:"), 11);
|
||||||
|
ep.step(TTR("Parsing Geometry..."), 0);
|
||||||
|
|
||||||
|
Vector<float> verticies;
|
||||||
|
Vector<int> indices;
|
||||||
|
|
||||||
|
_parse_geometry(Object::cast_to<Spatial>(p_node)->get_global_transform().affine_inverse(), p_node, verticies, indices);
|
||||||
|
|
||||||
|
if (verticies.size() > 0 && indices.size() > 0) {
|
||||||
|
|
||||||
|
rcHeightfield *hf = NULL;
|
||||||
|
rcCompactHeightfield *chf = NULL;
|
||||||
|
rcContourSet *cset = NULL;
|
||||||
|
rcPolyMesh *poly_mesh = NULL;
|
||||||
|
rcPolyMeshDetail *detail_mesh = NULL;
|
||||||
|
|
||||||
|
_build_recast_navigation_mesh(p_nav_mesh, &ep, hf, chf, cset, poly_mesh, detail_mesh, verticies, indices);
|
||||||
|
|
||||||
|
if (hf) {
|
||||||
|
rcFreeHeightField(hf);
|
||||||
|
hf = 0;
|
||||||
|
}
|
||||||
|
if (chf) {
|
||||||
|
rcFreeCompactHeightfield(chf);
|
||||||
|
chf = 0;
|
||||||
|
}
|
||||||
|
if (cset) {
|
||||||
|
rcFreeContourSet(cset);
|
||||||
|
cset = 0;
|
||||||
|
}
|
||||||
|
if (poly_mesh) {
|
||||||
|
rcFreePolyMesh(poly_mesh);
|
||||||
|
poly_mesh = 0;
|
||||||
|
}
|
||||||
|
if (detail_mesh) {
|
||||||
|
rcFreePolyMeshDetail(detail_mesh);
|
||||||
|
detail_mesh = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ep.step(TTR("Done!"), 11);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMeshGenerator::clear(Ref<NavigationMesh> p_nav_mesh) {
|
||||||
|
if (p_nav_mesh.is_valid()) {
|
||||||
|
p_nav_mesh->clear_polygons();
|
||||||
|
p_nav_mesh->set_vertices(PoolVector<Vector3>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //RECAST_ENABLED
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*************************************************************************/
|
||||||
|
/* navigation_mesh_generator.h */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
#ifndef NAVIGATION_MESH_GENERATOR_H
|
||||||
|
#define NAVIGATION_MESH_GENERATOR_H
|
||||||
|
|
||||||
|
#ifdef RECAST_ENABLED
|
||||||
|
|
||||||
|
#include "editor/editor_node.h"
|
||||||
|
#include "editor/editor_settings.h"
|
||||||
|
|
||||||
|
#include "scene/3d/mesh_instance.h"
|
||||||
|
|
||||||
|
#include "scene/3d/navigation_mesh.h"
|
||||||
|
|
||||||
|
#include "os/thread.h"
|
||||||
|
#include "scene/resources/shape.h"
|
||||||
|
|
||||||
|
#include <Recast.h>
|
||||||
|
|
||||||
|
class NavigationMeshGenerator {
|
||||||
|
protected:
|
||||||
|
static void _add_vertex(const Vector3 &p_vec3, Vector<float> &p_verticies);
|
||||||
|
static void _add_mesh(const Ref<Mesh> &p_mesh, const Transform &p_xform, Vector<float> &p_verticies, Vector<int> &p_indices);
|
||||||
|
static void _parse_geometry(const Transform &p_base_inverse, Node *p_node, Vector<float> &p_verticies, Vector<int> &p_indices);
|
||||||
|
|
||||||
|
static void _convert_detail_mesh_to_native_navigation_mesh(const rcPolyMeshDetail *p_detail_mesh, Ref<NavigationMesh> p_nav_mesh);
|
||||||
|
static void _build_recast_navigation_mesh(Ref<NavigationMesh> p_nav_mesh, EditorProgress *ep,
|
||||||
|
rcHeightfield *hf, rcCompactHeightfield *chf, rcContourSet *cset, rcPolyMesh *poly_mesh,
|
||||||
|
rcPolyMeshDetail *detail_mesh, Vector<float> &verticies, Vector<int> &indices);
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void bake(Ref<NavigationMesh> p_nav_mesh, Node *p_base);
|
||||||
|
static void clear(Ref<NavigationMesh> p_nav_mesh);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // RECAST_ENABLED
|
||||||
|
|
||||||
|
#endif // NAVIGATION_MESH_GENERATOR_H
|
|
@ -0,0 +1,38 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
Import('env')
|
||||||
|
|
||||||
|
# Not building in a separate env as core needs it
|
||||||
|
|
||||||
|
# Thirdparty source files
|
||||||
|
if (env['builtin_recast'] != 'no'):
|
||||||
|
thirdparty_dir = "#thirdparty/recastnavigation/Recast/"
|
||||||
|
thirdparty_sources = [
|
||||||
|
"Source/Recast.cpp",
|
||||||
|
"Source/RecastAlloc.cpp",
|
||||||
|
"Source/RecastArea.cpp",
|
||||||
|
"Source/RecastAssert.cpp",
|
||||||
|
"Source/RecastContour.cpp",
|
||||||
|
"Source/RecastFilter.cpp",
|
||||||
|
"Source/RecastLayers.cpp",
|
||||||
|
"Source/RecastMesh.cpp",
|
||||||
|
"Source/RecastMeshDetail.cpp",
|
||||||
|
"Source/RecastRasterization.cpp",
|
||||||
|
"Source/RecastRegion.cpp",
|
||||||
|
]
|
||||||
|
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
|
||||||
|
|
||||||
|
env.Append(CPPPATH=[thirdparty_dir, thirdparty_dir + "/Include"])
|
||||||
|
|
||||||
|
# also requires recast headers
|
||||||
|
if (env['builtin_recast'] != 'no'):
|
||||||
|
env.Append(CPPPATH=["#thirdparty/recastnavigation/Recast"])
|
||||||
|
|
||||||
|
lib = env.Library("recast_builtin", thirdparty_sources)
|
||||||
|
env.Append(LIBS=[lib])
|
||||||
|
|
||||||
|
# Godot source files
|
||||||
|
env.add_source_files(env.modules_sources, "*.cpp")
|
||||||
|
env.Append(CCFLAGS=['-DRECAST_ENABLED'])
|
||||||
|
|
||||||
|
Export('env')
|
|
@ -0,0 +1,7 @@
|
||||||
|
|
||||||
|
def can_build(platform):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def configure(env):
|
||||||
|
pass
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*************************************************************************/
|
||||||
|
/* register_types.cpp */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
#include "register_types.h"
|
||||||
|
|
||||||
|
void register_recast_types() {}
|
||||||
|
void unregister_recast_types() {}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*************************************************************************/
|
||||||
|
/* register_types.h */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* This file is part of: */
|
||||||
|
/* GODOT ENGINE */
|
||||||
|
/* https://godotengine.org */
|
||||||
|
/*************************************************************************/
|
||||||
|
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
||||||
|
/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
|
||||||
|
/* */
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||||
|
/* a copy of this software and associated documentation files (the */
|
||||||
|
/* "Software"), to deal in the Software without restriction, including */
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||||
|
/* the following conditions: */
|
||||||
|
/* */
|
||||||
|
/* The above copyright notice and this permission notice shall be */
|
||||||
|
/* included in all copies or substantial portions of the Software. */
|
||||||
|
/* */
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||||
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||||
|
/*************************************************************************/
|
||||||
|
|
||||||
|
void register_recast_types();
|
||||||
|
void unregister_recast_types();
|
|
@ -63,6 +63,143 @@ void NavigationMesh::create_from_mesh(const Ref<Mesh> &p_mesh) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NavigationMesh::set_sample_partition_type(int p_value) {
|
||||||
|
ERR_FAIL_COND(p_value >= SAMPLE_PARTITION_MAX);
|
||||||
|
partition_type = static_cast<SamplePartitionType>(p_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NavigationMesh::get_sample_partition_type() const {
|
||||||
|
return static_cast<int>(partition_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMesh::set_cell_size(float p_value) {
|
||||||
|
cell_size = p_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
float NavigationMesh::get_cell_size() const {
|
||||||
|
return cell_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMesh::set_cell_height(float p_value) {
|
||||||
|
cell_height = p_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
float NavigationMesh::get_cell_height() const {
|
||||||
|
return cell_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMesh::set_agent_height(float p_value) {
|
||||||
|
agent_height = p_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
float NavigationMesh::get_agent_height() const {
|
||||||
|
return agent_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMesh::set_agent_radius(float p_value) {
|
||||||
|
agent_radius = p_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
float NavigationMesh::get_agent_radius() {
|
||||||
|
return agent_radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMesh::set_agent_max_climb(float p_value) {
|
||||||
|
agent_max_climb = p_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
float NavigationMesh::get_agent_max_climb() const {
|
||||||
|
return agent_max_climb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMesh::set_agent_max_slope(float p_value) {
|
||||||
|
agent_max_slope = p_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
float NavigationMesh::get_agent_max_slope() const {
|
||||||
|
return agent_max_slope;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMesh::set_region_min_size(float p_value) {
|
||||||
|
region_min_size = p_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
float NavigationMesh::get_region_min_size() const {
|
||||||
|
return region_min_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMesh::set_region_merge_size(float p_value) {
|
||||||
|
region_merge_size = p_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
float NavigationMesh::get_region_merge_size() const {
|
||||||
|
return region_merge_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMesh::set_edge_max_length(float p_value) {
|
||||||
|
edge_max_length = p_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
float NavigationMesh::get_edge_max_length() const {
|
||||||
|
return edge_max_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMesh::set_edge_max_error(float p_value) {
|
||||||
|
edge_max_error = p_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
float NavigationMesh::get_edge_max_error() const {
|
||||||
|
return edge_max_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMesh::set_verts_per_poly(float p_value) {
|
||||||
|
verts_per_poly = p_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
float NavigationMesh::get_verts_per_poly() const {
|
||||||
|
return verts_per_poly;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMesh::set_detail_sample_distance(float p_value) {
|
||||||
|
detail_sample_distance = p_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
float NavigationMesh::get_detail_sample_distance() const {
|
||||||
|
return detail_sample_distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMesh::set_detail_sample_max_error(float p_value) {
|
||||||
|
detail_sample_max_error = p_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
float NavigationMesh::get_detail_sample_max_error() const {
|
||||||
|
return detail_sample_max_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMesh::set_filter_low_hanging_obstacles(bool p_value) {
|
||||||
|
filter_low_hanging_obstacles = p_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NavigationMesh::get_filter_low_hanging_obstacles() const {
|
||||||
|
return filter_low_hanging_obstacles;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMesh::set_filter_ledge_spans(bool p_value) {
|
||||||
|
filter_ledge_spans = p_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NavigationMesh::get_filter_ledge_spans() const {
|
||||||
|
return filter_ledge_spans;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NavigationMesh::set_filter_walkable_low_height_spans(bool p_value) {
|
||||||
|
filter_walkable_low_height_spans = p_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NavigationMesh::get_filter_walkable_low_height_spans() const {
|
||||||
|
return filter_walkable_low_height_spans;
|
||||||
|
}
|
||||||
|
|
||||||
void NavigationMesh::set_vertices(const PoolVector<Vector3> &p_vertices) {
|
void NavigationMesh::set_vertices(const PoolVector<Vector3> &p_vertices) {
|
||||||
|
|
||||||
vertices = p_vertices;
|
vertices = p_vertices;
|
||||||
|
@ -199,6 +336,56 @@ Ref<Mesh> NavigationMesh::get_debug_mesh() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavigationMesh::_bind_methods() {
|
void NavigationMesh::_bind_methods() {
|
||||||
|
ClassDB::bind_method(D_METHOD("set_sample_partition_type", "sample_partition_type"), &NavigationMesh::set_sample_partition_type);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_sample_partition_type"), &NavigationMesh::get_sample_partition_type);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_cell_size", "cell_size"), &NavigationMesh::set_cell_size);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_cell_size"), &NavigationMesh::get_cell_size);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_cell_height", "cell_height"), &NavigationMesh::set_cell_height);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_cell_height"), &NavigationMesh::get_cell_height);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_agent_height", "agent_height"), &NavigationMesh::set_agent_height);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_agent_height"), &NavigationMesh::get_agent_height);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_agent_radius", "agent_radius"), &NavigationMesh::set_agent_radius);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_agent_radius"), &NavigationMesh::get_agent_radius);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_agent_max_climb", "agent_max_climb"), &NavigationMesh::set_agent_max_climb);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_agent_max_climb"), &NavigationMesh::get_agent_max_climb);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_agent_max_slope", "agent_max_slope"), &NavigationMesh::set_agent_max_slope);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_agent_max_slope"), &NavigationMesh::get_agent_max_slope);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_region_min_size", "region_min_size"), &NavigationMesh::set_region_min_size);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_region_min_size"), &NavigationMesh::get_region_min_size);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_region_merge_size", "region_merge_size"), &NavigationMesh::set_region_merge_size);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_region_merge_size"), &NavigationMesh::get_region_merge_size);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_edge_max_length", "edge_max_length"), &NavigationMesh::set_edge_max_length);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_edge_max_length"), &NavigationMesh::get_edge_max_length);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_edge_max_error", "edge_max_error"), &NavigationMesh::set_edge_max_error);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_edge_max_error"), &NavigationMesh::get_edge_max_error);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_verts_per_poly", "verts_per_poly"), &NavigationMesh::set_verts_per_poly);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_verts_per_poly"), &NavigationMesh::get_verts_per_poly);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_detail_sample_distance", "detail_sample_dist"), &NavigationMesh::set_detail_sample_distance);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_detail_sample_distance"), &NavigationMesh::get_detail_sample_distance);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_detail_sample_max_error", "detail_sample_max_error"), &NavigationMesh::set_detail_sample_max_error);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_detail_sample_max_error"), &NavigationMesh::get_detail_sample_max_error);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_filter_low_hanging_obstacles", "filter_low_hanging_obstacles"), &NavigationMesh::set_filter_low_hanging_obstacles);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_filter_low_hanging_obstacles"), &NavigationMesh::get_filter_low_hanging_obstacles);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_filter_ledge_spans", "filter_ledge_spans"), &NavigationMesh::set_filter_ledge_spans);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_filter_ledge_spans"), &NavigationMesh::get_filter_ledge_spans);
|
||||||
|
|
||||||
|
ClassDB::bind_method(D_METHOD("set_filter_walkable_low_height_spans", "filter_walkable_low_height_spans"), &NavigationMesh::set_filter_walkable_low_height_spans);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_filter_walkable_low_height_spans"), &NavigationMesh::get_filter_walkable_low_height_spans);
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &NavigationMesh::set_vertices);
|
ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &NavigationMesh::set_vertices);
|
||||||
ClassDB::bind_method(D_METHOD("get_vertices"), &NavigationMesh::get_vertices);
|
ClassDB::bind_method(D_METHOD("get_vertices"), &NavigationMesh::get_vertices);
|
||||||
|
@ -213,11 +400,54 @@ void NavigationMesh::_bind_methods() {
|
||||||
ClassDB::bind_method(D_METHOD("_set_polygons", "polygons"), &NavigationMesh::_set_polygons);
|
ClassDB::bind_method(D_METHOD("_set_polygons", "polygons"), &NavigationMesh::_set_polygons);
|
||||||
ClassDB::bind_method(D_METHOD("_get_polygons"), &NavigationMesh::_get_polygons);
|
ClassDB::bind_method(D_METHOD("_get_polygons"), &NavigationMesh::_get_polygons);
|
||||||
|
|
||||||
|
BIND_CONSTANT(SAMPLE_PARTITION_WATERSHED);
|
||||||
|
BIND_CONSTANT(SAMPLE_PARTITION_MONOTONE);
|
||||||
|
BIND_CONSTANT(SAMPLE_PARTITION_LAYERS);
|
||||||
|
|
||||||
ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_vertices", "get_vertices");
|
ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_vertices", "get_vertices");
|
||||||
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "_set_polygons", "_get_polygons");
|
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "_set_polygons", "_get_polygons");
|
||||||
|
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "sample_partition_type/sample_partition_type", PROPERTY_HINT_ENUM, "Watershed,Monotone,Layers"), "set_sample_partition_type", "get_sample_partition_type");
|
||||||
|
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::REAL, "cell/size", PROPERTY_HINT_RANGE, "0.1,1.0,0.01"), "set_cell_size", "get_cell_size");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::REAL, "cell/height", PROPERTY_HINT_RANGE, "0.1,1.0,0.01"), "set_cell_height", "get_cell_height");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::REAL, "agent/height", PROPERTY_HINT_RANGE, "0.1,5.0,0.01"), "set_agent_height", "get_agent_height");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::REAL, "agent/radius", PROPERTY_HINT_RANGE, "0.1,5.0,0.01"), "set_agent_radius", "get_agent_radius");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::REAL, "agent/max_climb", PROPERTY_HINT_RANGE, "0.1,5.0,0.01"), "set_agent_max_climb", "get_agent_max_climb");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::REAL, "agent/max_slope", PROPERTY_HINT_RANGE, "0.0,90.0,0.1"), "set_agent_max_slope", "get_agent_max_slope");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::REAL, "region/min_size", PROPERTY_HINT_RANGE, "0.0,150.0,0.01"), "set_region_min_size", "get_region_min_size");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::REAL, "region/merge_size", PROPERTY_HINT_RANGE, "0.0,150.0,0.01"), "set_region_merge_size", "get_region_merge_size");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::REAL, "edge/max_length", PROPERTY_HINT_RANGE, "0.0,50.0,0.01"), "set_edge_max_length", "get_edge_max_length");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::REAL, "edge/max_error", PROPERTY_HINT_RANGE, "0.1,3.0,0.01"), "set_edge_max_error", "get_edge_max_error");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::REAL, "polygon/verts_per_poly", PROPERTY_HINT_RANGE, "3.0,12.0,1.0"), "set_verts_per_poly", "get_verts_per_poly");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::REAL, "detail/sample_distance", PROPERTY_HINT_RANGE, "0.0,16.0,0.01"), "set_detail_sample_distance", "get_detail_sample_distance");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::REAL, "detail/sample_max_error", PROPERTY_HINT_RANGE, "0.0,16.0,0.01"), "set_detail_sample_max_error", "get_detail_sample_max_error");
|
||||||
|
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter/low_hanging_obstacles"), "set_filter_low_hanging_obstacles", "get_filter_low_hanging_obstacles");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter/ledge_spans"), "set_filter_ledge_spans", "get_filter_ledge_spans");
|
||||||
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter/filter_walkable_low_height_spans"), "set_filter_walkable_low_height_spans", "get_filter_walkable_low_height_spans");
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationMesh::NavigationMesh() {
|
NavigationMesh::NavigationMesh() {
|
||||||
|
cell_size = 0.3f;
|
||||||
|
cell_height = 0.2f;
|
||||||
|
agent_height = 2.0f;
|
||||||
|
agent_radius = 0.6f;
|
||||||
|
agent_max_climb = 0.9f;
|
||||||
|
agent_max_slope = 45.0f;
|
||||||
|
region_min_size = 8.0f;
|
||||||
|
region_merge_size = 20.0f;
|
||||||
|
edge_max_length = 12.0f;
|
||||||
|
edge_max_error = 1.3f;
|
||||||
|
verts_per_poly = 6.0f;
|
||||||
|
detail_sample_distance = 6.0f;
|
||||||
|
detail_sample_max_error = 1.0f;
|
||||||
|
|
||||||
|
partition_type = SAMPLE_PARTITION_WATERSHED;
|
||||||
|
|
||||||
|
filter_low_hanging_obstacles = false;
|
||||||
|
filter_ledge_spans = false;
|
||||||
|
filter_walkable_low_height_spans = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavigationMeshInstance::set_enabled(bool p_enabled) {
|
void NavigationMeshInstance::set_enabled(bool p_enabled) {
|
||||||
|
|
|
@ -61,6 +61,87 @@ protected:
|
||||||
Array _get_polygons() const;
|
Array _get_polygons() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
enum SamplePartitionType {
|
||||||
|
SAMPLE_PARTITION_WATERSHED = 0,
|
||||||
|
SAMPLE_PARTITION_MONOTONE,
|
||||||
|
SAMPLE_PARTITION_LAYERS,
|
||||||
|
SAMPLE_PARTITION_MAX
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
float cell_size;
|
||||||
|
float cell_height;
|
||||||
|
float agent_height;
|
||||||
|
float agent_radius;
|
||||||
|
float agent_max_climb;
|
||||||
|
float agent_max_slope;
|
||||||
|
float region_min_size;
|
||||||
|
float region_merge_size;
|
||||||
|
float edge_max_length;
|
||||||
|
float edge_max_error;
|
||||||
|
float verts_per_poly;
|
||||||
|
float detail_sample_distance;
|
||||||
|
float detail_sample_max_error;
|
||||||
|
|
||||||
|
SamplePartitionType partition_type;
|
||||||
|
|
||||||
|
bool filter_low_hanging_obstacles;
|
||||||
|
bool filter_ledge_spans;
|
||||||
|
bool filter_walkable_low_height_spans;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Recast settings
|
||||||
|
void set_sample_partition_type(int p_value);
|
||||||
|
int get_sample_partition_type() const;
|
||||||
|
|
||||||
|
void set_cell_size(float p_value);
|
||||||
|
float get_cell_size() const;
|
||||||
|
|
||||||
|
void set_cell_height(float p_value);
|
||||||
|
float get_cell_height() const;
|
||||||
|
|
||||||
|
void set_agent_height(float p_value);
|
||||||
|
float get_agent_height() const;
|
||||||
|
|
||||||
|
void set_agent_radius(float p_value);
|
||||||
|
float get_agent_radius();
|
||||||
|
|
||||||
|
void set_agent_max_climb(float p_value);
|
||||||
|
float get_agent_max_climb() const;
|
||||||
|
|
||||||
|
void set_agent_max_slope(float p_value);
|
||||||
|
float get_agent_max_slope() const;
|
||||||
|
|
||||||
|
void set_region_min_size(float p_value);
|
||||||
|
float get_region_min_size() const;
|
||||||
|
|
||||||
|
void set_region_merge_size(float p_value);
|
||||||
|
float get_region_merge_size() const;
|
||||||
|
|
||||||
|
void set_edge_max_length(float p_value);
|
||||||
|
float get_edge_max_length() const;
|
||||||
|
|
||||||
|
void set_edge_max_error(float p_value);
|
||||||
|
float get_edge_max_error() const;
|
||||||
|
|
||||||
|
void set_verts_per_poly(float p_value);
|
||||||
|
float get_verts_per_poly() const;
|
||||||
|
|
||||||
|
void set_detail_sample_distance(float p_value);
|
||||||
|
float get_detail_sample_distance() const;
|
||||||
|
|
||||||
|
void set_detail_sample_max_error(float p_value);
|
||||||
|
float get_detail_sample_max_error() const;
|
||||||
|
|
||||||
|
void set_filter_low_hanging_obstacles(bool p_value);
|
||||||
|
bool get_filter_low_hanging_obstacles() const;
|
||||||
|
|
||||||
|
void set_filter_ledge_spans(bool p_value);
|
||||||
|
bool get_filter_ledge_spans() const;
|
||||||
|
|
||||||
|
void set_filter_walkable_low_height_spans(bool p_value);
|
||||||
|
bool get_filter_walkable_low_height_spans() const;
|
||||||
|
|
||||||
void create_from_mesh(const Ref<Mesh> &p_mesh);
|
void create_from_mesh(const Ref<Mesh> &p_mesh);
|
||||||
|
|
||||||
void set_vertices(const PoolVector<Vector3> &p_vertices);
|
void set_vertices(const PoolVector<Vector3> &p_vertices);
|
||||||
|
|
|
@ -359,6 +359,11 @@ Files extracted from upstream source:
|
||||||
- all .cpp and .h files apart from `main.cpp`
|
- all .cpp and .h files apart from `main.cpp`
|
||||||
- LICENSE.TXT
|
- LICENSE.TXT
|
||||||
|
|
||||||
|
## recast
|
||||||
|
|
||||||
|
- Upstream: https://github.com/recastnavigation/recastnavigation
|
||||||
|
- version: git commit ef3ea40f - 2016-02-06
|
||||||
|
- License: MIT-like
|
||||||
|
|
||||||
## rtaudio
|
## rtaudio
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
Copyright (c) 2009 Mikko Mononen memon@inside.org
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,146 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied
|
||||||
|
// warranty. In no event will the authors be held liable for any damages
|
||||||
|
// arising from the use of this software.
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it
|
||||||
|
// freely, subject to the following restrictions:
|
||||||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
// claim that you wrote the original software. If you use this software
|
||||||
|
// in a product, an acknowledgment in the product documentation would be
|
||||||
|
// appreciated but is not required.
|
||||||
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
// misrepresented as being the original software.
|
||||||
|
// 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef RECASTALLOC_H
|
||||||
|
#define RECASTALLOC_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/// Provides hint values to the memory allocator on how long the
|
||||||
|
/// memory is expected to be used.
|
||||||
|
enum rcAllocHint
|
||||||
|
{
|
||||||
|
RC_ALLOC_PERM, ///< Memory will persist after a function call.
|
||||||
|
RC_ALLOC_TEMP ///< Memory used temporarily within a function.
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A memory allocation function.
|
||||||
|
// @param[in] size The size, in bytes of memory, to allocate.
|
||||||
|
// @param[in] rcAllocHint A hint to the allocator on how long the memory is expected to be in use.
|
||||||
|
// @return A pointer to the beginning of the allocated memory block, or null if the allocation failed.
|
||||||
|
/// @see rcAllocSetCustom
|
||||||
|
typedef void* (rcAllocFunc)(size_t size, rcAllocHint hint);
|
||||||
|
|
||||||
|
/// A memory deallocation function.
|
||||||
|
/// @param[in] ptr A pointer to a memory block previously allocated using #rcAllocFunc.
|
||||||
|
/// @see rcAllocSetCustom
|
||||||
|
typedef void (rcFreeFunc)(void* ptr);
|
||||||
|
|
||||||
|
/// Sets the base custom allocation functions to be used by Recast.
|
||||||
|
/// @param[in] allocFunc The memory allocation function to be used by #rcAlloc
|
||||||
|
/// @param[in] freeFunc The memory de-allocation function to be used by #rcFree
|
||||||
|
void rcAllocSetCustom(rcAllocFunc *allocFunc, rcFreeFunc *freeFunc);
|
||||||
|
|
||||||
|
/// Allocates a memory block.
|
||||||
|
/// @param[in] size The size, in bytes of memory, to allocate.
|
||||||
|
/// @param[in] hint A hint to the allocator on how long the memory is expected to be in use.
|
||||||
|
/// @return A pointer to the beginning of the allocated memory block, or null if the allocation failed.
|
||||||
|
/// @see rcFree
|
||||||
|
void* rcAlloc(size_t size, rcAllocHint hint);
|
||||||
|
|
||||||
|
/// Deallocates a memory block.
|
||||||
|
/// @param[in] ptr A pointer to a memory block previously allocated using #rcAlloc.
|
||||||
|
/// @see rcAlloc
|
||||||
|
void rcFree(void* ptr);
|
||||||
|
|
||||||
|
|
||||||
|
/// A simple dynamic array of integers.
|
||||||
|
class rcIntArray
|
||||||
|
{
|
||||||
|
int* m_data;
|
||||||
|
int m_size, m_cap;
|
||||||
|
|
||||||
|
void doResize(int n);
|
||||||
|
|
||||||
|
// Explicitly disabled copy constructor and copy assignment operator.
|
||||||
|
rcIntArray(const rcIntArray&);
|
||||||
|
rcIntArray& operator=(const rcIntArray&);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Constructs an instance with an initial array size of zero.
|
||||||
|
rcIntArray() : m_data(0), m_size(0), m_cap(0) {}
|
||||||
|
|
||||||
|
/// Constructs an instance initialized to the specified size.
|
||||||
|
/// @param[in] n The initial size of the integer array.
|
||||||
|
rcIntArray(int n) : m_data(0), m_size(0), m_cap(0) { resize(n); }
|
||||||
|
~rcIntArray() { rcFree(m_data); }
|
||||||
|
|
||||||
|
/// Specifies the new size of the integer array.
|
||||||
|
/// @param[in] n The new size of the integer array.
|
||||||
|
void resize(int n)
|
||||||
|
{
|
||||||
|
if (n > m_cap)
|
||||||
|
doResize(n);
|
||||||
|
|
||||||
|
m_size = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push the specified integer onto the end of the array and increases the size by one.
|
||||||
|
/// @param[in] item The new value.
|
||||||
|
void push(int item) { resize(m_size+1); m_data[m_size-1] = item; }
|
||||||
|
|
||||||
|
/// Returns the value at the end of the array and reduces the size by one.
|
||||||
|
/// @return The value at the end of the array.
|
||||||
|
int pop()
|
||||||
|
{
|
||||||
|
if (m_size > 0)
|
||||||
|
m_size--;
|
||||||
|
|
||||||
|
return m_data[m_size];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The value at the specified array index.
|
||||||
|
/// @warning Does not provide overflow protection.
|
||||||
|
/// @param[in] i The index of the value.
|
||||||
|
const int& operator[](int i) const { return m_data[i]; }
|
||||||
|
|
||||||
|
/// The value at the specified array index.
|
||||||
|
/// @warning Does not provide overflow protection.
|
||||||
|
/// @param[in] i The index of the value.
|
||||||
|
int& operator[](int i) { return m_data[i]; }
|
||||||
|
|
||||||
|
/// The current size of the integer array.
|
||||||
|
int size() const { return m_size; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A simple helper class used to delete an array when it goes out of scope.
|
||||||
|
/// @note This class is rarely if ever used by the end user.
|
||||||
|
template<class T> class rcScopedDelete
|
||||||
|
{
|
||||||
|
T* ptr;
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Constructs an instance with a null pointer.
|
||||||
|
inline rcScopedDelete() : ptr(0) {}
|
||||||
|
|
||||||
|
/// Constructs an instance with the specified pointer.
|
||||||
|
/// @param[in] p An pointer to an allocated array.
|
||||||
|
inline rcScopedDelete(T* p) : ptr(p) {}
|
||||||
|
inline ~rcScopedDelete() { rcFree(ptr); }
|
||||||
|
|
||||||
|
/// The root array pointer.
|
||||||
|
/// @return The root array pointer.
|
||||||
|
inline operator T*() { return ptr; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Explicitly disabled copy constructor and copy assignment operator.
|
||||||
|
rcScopedDelete(const rcScopedDelete&);
|
||||||
|
rcScopedDelete& operator=(const rcScopedDelete&);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,56 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied
|
||||||
|
// warranty. In no event will the authors be held liable for any damages
|
||||||
|
// arising from the use of this software.
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it
|
||||||
|
// freely, subject to the following restrictions:
|
||||||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
// claim that you wrote the original software. If you use this software
|
||||||
|
// in a product, an acknowledgment in the product documentation would be
|
||||||
|
// appreciated but is not required.
|
||||||
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
// misrepresented as being the original software.
|
||||||
|
// 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef RECASTASSERT_H
|
||||||
|
#define RECASTASSERT_H
|
||||||
|
|
||||||
|
// Note: This header file's only purpose is to include define assert.
|
||||||
|
// Feel free to change the file and include your own implementation instead.
|
||||||
|
|
||||||
|
#ifdef NDEBUG
|
||||||
|
|
||||||
|
// From http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/
|
||||||
|
# define rcAssert(x) do { (void)sizeof(x); } while((void)(__LINE__==-1),false)
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
/// An assertion failure function.
|
||||||
|
// @param[in] expression asserted expression.
|
||||||
|
// @param[in] file Filename of the failed assertion.
|
||||||
|
// @param[in] line Line number of the failed assertion.
|
||||||
|
/// @see rcAssertFailSetCustom
|
||||||
|
typedef void (rcAssertFailFunc)(const char* expression, const char* file, int line);
|
||||||
|
|
||||||
|
/// Sets the base custom assertion failure function to be used by Recast.
|
||||||
|
/// @param[in] assertFailFunc The function to be used in case of failure of #dtAssert
|
||||||
|
void rcAssertFailSetCustom(rcAssertFailFunc *assertFailFunc);
|
||||||
|
|
||||||
|
/// Gets the base custom assertion failure function to be used by Recast.
|
||||||
|
rcAssertFailFunc* rcAssertFailGetCustom();
|
||||||
|
|
||||||
|
# include <assert.h>
|
||||||
|
# define rcAssert(expression) \
|
||||||
|
{ \
|
||||||
|
rcAssertFailFunc* failFunc = rcAssertFailGetCustom(); \
|
||||||
|
if(failFunc == NULL) { assert(expression); } \
|
||||||
|
else if(!(expression)) { (*failFunc)(#expression, __FILE__, __LINE__); } \
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // RECASTASSERT_H
|
|
@ -0,0 +1,504 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied
|
||||||
|
// warranty. In no event will the authors be held liable for any damages
|
||||||
|
// arising from the use of this software.
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it
|
||||||
|
// freely, subject to the following restrictions:
|
||||||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
// claim that you wrote the original software. If you use this software
|
||||||
|
// in a product, an acknowledgment in the product documentation would be
|
||||||
|
// appreciated but is not required.
|
||||||
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
// misrepresented as being the original software.
|
||||||
|
// 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <float.h>
|
||||||
|
#define _USE_MATH_DEFINES
|
||||||
|
#include <math.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <new>
|
||||||
|
#include "Recast.h"
|
||||||
|
#include "RecastAlloc.h"
|
||||||
|
#include "RecastAssert.h"
|
||||||
|
|
||||||
|
float rcSqrt(float x)
|
||||||
|
{
|
||||||
|
return sqrtf(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @class rcContext
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// This class does not provide logging or timer functionality on its
|
||||||
|
/// own. Both must be provided by a concrete implementation
|
||||||
|
/// by overriding the protected member functions. Also, this class does not
|
||||||
|
/// provide an interface for extracting log messages. (Only adding them.)
|
||||||
|
/// So concrete implementations must provide one.
|
||||||
|
///
|
||||||
|
/// If no logging or timers are required, just pass an instance of this
|
||||||
|
/// class through the Recast build process.
|
||||||
|
///
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// @code
|
||||||
|
/// // Where ctx is an instance of rcContext and filepath is a char array.
|
||||||
|
/// ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not load '%s'", filepath);
|
||||||
|
/// @endcode
|
||||||
|
void rcContext::log(const rcLogCategory category, const char* format, ...)
|
||||||
|
{
|
||||||
|
if (!m_logEnabled)
|
||||||
|
return;
|
||||||
|
static const int MSG_SIZE = 512;
|
||||||
|
char msg[MSG_SIZE];
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, format);
|
||||||
|
int len = vsnprintf(msg, MSG_SIZE, format, ap);
|
||||||
|
if (len >= MSG_SIZE)
|
||||||
|
{
|
||||||
|
len = MSG_SIZE-1;
|
||||||
|
msg[MSG_SIZE-1] = '\0';
|
||||||
|
}
|
||||||
|
va_end(ap);
|
||||||
|
doLog(category, msg, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
rcHeightfield* rcAllocHeightfield()
|
||||||
|
{
|
||||||
|
return new (rcAlloc(sizeof(rcHeightfield), RC_ALLOC_PERM)) rcHeightfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
rcHeightfield::rcHeightfield()
|
||||||
|
: width()
|
||||||
|
, height()
|
||||||
|
, bmin()
|
||||||
|
, bmax()
|
||||||
|
, cs()
|
||||||
|
, ch()
|
||||||
|
, spans()
|
||||||
|
, pools()
|
||||||
|
, freelist()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
rcHeightfield::~rcHeightfield()
|
||||||
|
{
|
||||||
|
// Delete span array.
|
||||||
|
rcFree(spans);
|
||||||
|
// Delete span pools.
|
||||||
|
while (pools)
|
||||||
|
{
|
||||||
|
rcSpanPool* next = pools->next;
|
||||||
|
rcFree(pools);
|
||||||
|
pools = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rcFreeHeightField(rcHeightfield* hf)
|
||||||
|
{
|
||||||
|
if (!hf) return;
|
||||||
|
hf->~rcHeightfield();
|
||||||
|
rcFree(hf);
|
||||||
|
}
|
||||||
|
|
||||||
|
rcCompactHeightfield* rcAllocCompactHeightfield()
|
||||||
|
{
|
||||||
|
rcCompactHeightfield* chf = (rcCompactHeightfield*)rcAlloc(sizeof(rcCompactHeightfield), RC_ALLOC_PERM);
|
||||||
|
memset(chf, 0, sizeof(rcCompactHeightfield));
|
||||||
|
return chf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rcFreeCompactHeightfield(rcCompactHeightfield* chf)
|
||||||
|
{
|
||||||
|
if (!chf) return;
|
||||||
|
rcFree(chf->cells);
|
||||||
|
rcFree(chf->spans);
|
||||||
|
rcFree(chf->dist);
|
||||||
|
rcFree(chf->areas);
|
||||||
|
rcFree(chf);
|
||||||
|
}
|
||||||
|
|
||||||
|
rcHeightfieldLayerSet* rcAllocHeightfieldLayerSet()
|
||||||
|
{
|
||||||
|
rcHeightfieldLayerSet* lset = (rcHeightfieldLayerSet*)rcAlloc(sizeof(rcHeightfieldLayerSet), RC_ALLOC_PERM);
|
||||||
|
memset(lset, 0, sizeof(rcHeightfieldLayerSet));
|
||||||
|
return lset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* lset)
|
||||||
|
{
|
||||||
|
if (!lset) return;
|
||||||
|
for (int i = 0; i < lset->nlayers; ++i)
|
||||||
|
{
|
||||||
|
rcFree(lset->layers[i].heights);
|
||||||
|
rcFree(lset->layers[i].areas);
|
||||||
|
rcFree(lset->layers[i].cons);
|
||||||
|
}
|
||||||
|
rcFree(lset->layers);
|
||||||
|
rcFree(lset);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
rcContourSet* rcAllocContourSet()
|
||||||
|
{
|
||||||
|
rcContourSet* cset = (rcContourSet*)rcAlloc(sizeof(rcContourSet), RC_ALLOC_PERM);
|
||||||
|
memset(cset, 0, sizeof(rcContourSet));
|
||||||
|
return cset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rcFreeContourSet(rcContourSet* cset)
|
||||||
|
{
|
||||||
|
if (!cset) return;
|
||||||
|
for (int i = 0; i < cset->nconts; ++i)
|
||||||
|
{
|
||||||
|
rcFree(cset->conts[i].verts);
|
||||||
|
rcFree(cset->conts[i].rverts);
|
||||||
|
}
|
||||||
|
rcFree(cset->conts);
|
||||||
|
rcFree(cset);
|
||||||
|
}
|
||||||
|
|
||||||
|
rcPolyMesh* rcAllocPolyMesh()
|
||||||
|
{
|
||||||
|
rcPolyMesh* pmesh = (rcPolyMesh*)rcAlloc(sizeof(rcPolyMesh), RC_ALLOC_PERM);
|
||||||
|
memset(pmesh, 0, sizeof(rcPolyMesh));
|
||||||
|
return pmesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rcFreePolyMesh(rcPolyMesh* pmesh)
|
||||||
|
{
|
||||||
|
if (!pmesh) return;
|
||||||
|
rcFree(pmesh->verts);
|
||||||
|
rcFree(pmesh->polys);
|
||||||
|
rcFree(pmesh->regs);
|
||||||
|
rcFree(pmesh->flags);
|
||||||
|
rcFree(pmesh->areas);
|
||||||
|
rcFree(pmesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
rcPolyMeshDetail* rcAllocPolyMeshDetail()
|
||||||
|
{
|
||||||
|
rcPolyMeshDetail* dmesh = (rcPolyMeshDetail*)rcAlloc(sizeof(rcPolyMeshDetail), RC_ALLOC_PERM);
|
||||||
|
memset(dmesh, 0, sizeof(rcPolyMeshDetail));
|
||||||
|
return dmesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rcFreePolyMeshDetail(rcPolyMeshDetail* dmesh)
|
||||||
|
{
|
||||||
|
if (!dmesh) return;
|
||||||
|
rcFree(dmesh->meshes);
|
||||||
|
rcFree(dmesh->verts);
|
||||||
|
rcFree(dmesh->tris);
|
||||||
|
rcFree(dmesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rcCalcBounds(const float* verts, int nv, float* bmin, float* bmax)
|
||||||
|
{
|
||||||
|
// Calculate bounding box.
|
||||||
|
rcVcopy(bmin, verts);
|
||||||
|
rcVcopy(bmax, verts);
|
||||||
|
for (int i = 1; i < nv; ++i)
|
||||||
|
{
|
||||||
|
const float* v = &verts[i*3];
|
||||||
|
rcVmin(bmin, v);
|
||||||
|
rcVmax(bmax, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rcCalcGridSize(const float* bmin, const float* bmax, float cs, int* w, int* h)
|
||||||
|
{
|
||||||
|
*w = (int)((bmax[0] - bmin[0])/cs+0.5f);
|
||||||
|
*h = (int)((bmax[2] - bmin[2])/cs+0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// See the #rcConfig documentation for more information on the configuration parameters.
|
||||||
|
///
|
||||||
|
/// @see rcAllocHeightfield, rcHeightfield
|
||||||
|
bool rcCreateHeightfield(rcContext* ctx, rcHeightfield& hf, int width, int height,
|
||||||
|
const float* bmin, const float* bmax,
|
||||||
|
float cs, float ch)
|
||||||
|
{
|
||||||
|
rcIgnoreUnused(ctx);
|
||||||
|
|
||||||
|
hf.width = width;
|
||||||
|
hf.height = height;
|
||||||
|
rcVcopy(hf.bmin, bmin);
|
||||||
|
rcVcopy(hf.bmax, bmax);
|
||||||
|
hf.cs = cs;
|
||||||
|
hf.ch = ch;
|
||||||
|
hf.spans = (rcSpan**)rcAlloc(sizeof(rcSpan*)*hf.width*hf.height, RC_ALLOC_PERM);
|
||||||
|
if (!hf.spans)
|
||||||
|
return false;
|
||||||
|
memset(hf.spans, 0, sizeof(rcSpan*)*hf.width*hf.height);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void calcTriNormal(const float* v0, const float* v1, const float* v2, float* norm)
|
||||||
|
{
|
||||||
|
float e0[3], e1[3];
|
||||||
|
rcVsub(e0, v1, v0);
|
||||||
|
rcVsub(e1, v2, v0);
|
||||||
|
rcVcross(norm, e0, e1);
|
||||||
|
rcVnormalize(norm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// Only sets the area id's for the walkable triangles. Does not alter the
|
||||||
|
/// area id's for unwalkable triangles.
|
||||||
|
///
|
||||||
|
/// See the #rcConfig documentation for more information on the configuration parameters.
|
||||||
|
///
|
||||||
|
/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles
|
||||||
|
void rcMarkWalkableTriangles(rcContext* ctx, const float walkableSlopeAngle,
|
||||||
|
const float* verts, int nv,
|
||||||
|
const int* tris, int nt,
|
||||||
|
unsigned char* areas)
|
||||||
|
{
|
||||||
|
rcIgnoreUnused(ctx);
|
||||||
|
rcIgnoreUnused(nv);
|
||||||
|
|
||||||
|
const float walkableThr = cosf(walkableSlopeAngle/180.0f*RC_PI);
|
||||||
|
|
||||||
|
float norm[3];
|
||||||
|
|
||||||
|
for (int i = 0; i < nt; ++i)
|
||||||
|
{
|
||||||
|
const int* tri = &tris[i*3];
|
||||||
|
calcTriNormal(&verts[tri[0]*3], &verts[tri[1]*3], &verts[tri[2]*3], norm);
|
||||||
|
// Check if the face is walkable.
|
||||||
|
if (norm[1] > walkableThr)
|
||||||
|
areas[i] = RC_WALKABLE_AREA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// Only sets the area id's for the unwalkable triangles. Does not alter the
|
||||||
|
/// area id's for walkable triangles.
|
||||||
|
///
|
||||||
|
/// See the #rcConfig documentation for more information on the configuration parameters.
|
||||||
|
///
|
||||||
|
/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles
|
||||||
|
void rcClearUnwalkableTriangles(rcContext* ctx, const float walkableSlopeAngle,
|
||||||
|
const float* verts, int /*nv*/,
|
||||||
|
const int* tris, int nt,
|
||||||
|
unsigned char* areas)
|
||||||
|
{
|
||||||
|
rcIgnoreUnused(ctx);
|
||||||
|
|
||||||
|
const float walkableThr = cosf(walkableSlopeAngle/180.0f*RC_PI);
|
||||||
|
|
||||||
|
float norm[3];
|
||||||
|
|
||||||
|
for (int i = 0; i < nt; ++i)
|
||||||
|
{
|
||||||
|
const int* tri = &tris[i*3];
|
||||||
|
calcTriNormal(&verts[tri[0]*3], &verts[tri[1]*3], &verts[tri[2]*3], norm);
|
||||||
|
// Check if the face is walkable.
|
||||||
|
if (norm[1] <= walkableThr)
|
||||||
|
areas[i] = RC_NULL_AREA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int rcGetHeightFieldSpanCount(rcContext* ctx, rcHeightfield& hf)
|
||||||
|
{
|
||||||
|
rcIgnoreUnused(ctx);
|
||||||
|
|
||||||
|
const int w = hf.width;
|
||||||
|
const int h = hf.height;
|
||||||
|
int spanCount = 0;
|
||||||
|
for (int y = 0; y < h; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < w; ++x)
|
||||||
|
{
|
||||||
|
for (rcSpan* s = hf.spans[x + y*w]; s; s = s->next)
|
||||||
|
{
|
||||||
|
if (s->area != RC_NULL_AREA)
|
||||||
|
spanCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return spanCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// This is just the beginning of the process of fully building a compact heightfield.
|
||||||
|
/// Various filters may be applied, then the distance field and regions built.
|
||||||
|
/// E.g: #rcBuildDistanceField and #rcBuildRegions
|
||||||
|
///
|
||||||
|
/// See the #rcConfig documentation for more information on the configuration parameters.
|
||||||
|
///
|
||||||
|
/// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig
|
||||||
|
bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb,
|
||||||
|
rcHeightfield& hf, rcCompactHeightfield& chf)
|
||||||
|
{
|
||||||
|
rcAssert(ctx);
|
||||||
|
|
||||||
|
rcScopedTimer timer(ctx, RC_TIMER_BUILD_COMPACTHEIGHTFIELD);
|
||||||
|
|
||||||
|
const int w = hf.width;
|
||||||
|
const int h = hf.height;
|
||||||
|
const int spanCount = rcGetHeightFieldSpanCount(ctx, hf);
|
||||||
|
|
||||||
|
// Fill in header.
|
||||||
|
chf.width = w;
|
||||||
|
chf.height = h;
|
||||||
|
chf.spanCount = spanCount;
|
||||||
|
chf.walkableHeight = walkableHeight;
|
||||||
|
chf.walkableClimb = walkableClimb;
|
||||||
|
chf.maxRegions = 0;
|
||||||
|
rcVcopy(chf.bmin, hf.bmin);
|
||||||
|
rcVcopy(chf.bmax, hf.bmax);
|
||||||
|
chf.bmax[1] += walkableHeight*hf.ch;
|
||||||
|
chf.cs = hf.cs;
|
||||||
|
chf.ch = hf.ch;
|
||||||
|
chf.cells = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell)*w*h, RC_ALLOC_PERM);
|
||||||
|
if (!chf.cells)
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.cells' (%d)", w*h);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memset(chf.cells, 0, sizeof(rcCompactCell)*w*h);
|
||||||
|
chf.spans = (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan)*spanCount, RC_ALLOC_PERM);
|
||||||
|
if (!chf.spans)
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.spans' (%d)", spanCount);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memset(chf.spans, 0, sizeof(rcCompactSpan)*spanCount);
|
||||||
|
chf.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*spanCount, RC_ALLOC_PERM);
|
||||||
|
if (!chf.areas)
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.areas' (%d)", spanCount);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memset(chf.areas, RC_NULL_AREA, sizeof(unsigned char)*spanCount);
|
||||||
|
|
||||||
|
const int MAX_HEIGHT = 0xffff;
|
||||||
|
|
||||||
|
// Fill in cells and spans.
|
||||||
|
int idx = 0;
|
||||||
|
for (int y = 0; y < h; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < w; ++x)
|
||||||
|
{
|
||||||
|
const rcSpan* s = hf.spans[x + y*w];
|
||||||
|
// If there are no spans at this cell, just leave the data to index=0, count=0.
|
||||||
|
if (!s) continue;
|
||||||
|
rcCompactCell& c = chf.cells[x+y*w];
|
||||||
|
c.index = idx;
|
||||||
|
c.count = 0;
|
||||||
|
while (s)
|
||||||
|
{
|
||||||
|
if (s->area != RC_NULL_AREA)
|
||||||
|
{
|
||||||
|
const int bot = (int)s->smax;
|
||||||
|
const int top = s->next ? (int)s->next->smin : MAX_HEIGHT;
|
||||||
|
chf.spans[idx].y = (unsigned short)rcClamp(bot, 0, 0xffff);
|
||||||
|
chf.spans[idx].h = (unsigned char)rcClamp(top - bot, 0, 0xff);
|
||||||
|
chf.areas[idx] = s->area;
|
||||||
|
idx++;
|
||||||
|
c.count++;
|
||||||
|
}
|
||||||
|
s = s->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find neighbour connections.
|
||||||
|
const int MAX_LAYERS = RC_NOT_CONNECTED-1;
|
||||||
|
int tooHighNeighbour = 0;
|
||||||
|
for (int y = 0; y < h; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < w; ++x)
|
||||||
|
{
|
||||||
|
const rcCompactCell& c = chf.cells[x+y*w];
|
||||||
|
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
|
||||||
|
{
|
||||||
|
rcCompactSpan& s = chf.spans[i];
|
||||||
|
|
||||||
|
for (int dir = 0; dir < 4; ++dir)
|
||||||
|
{
|
||||||
|
rcSetCon(s, dir, RC_NOT_CONNECTED);
|
||||||
|
const int nx = x + rcGetDirOffsetX(dir);
|
||||||
|
const int ny = y + rcGetDirOffsetY(dir);
|
||||||
|
// First check that the neighbour cell is in bounds.
|
||||||
|
if (nx < 0 || ny < 0 || nx >= w || ny >= h)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Iterate over all neighbour spans and check if any of the is
|
||||||
|
// accessible from current cell.
|
||||||
|
const rcCompactCell& nc = chf.cells[nx+ny*w];
|
||||||
|
for (int k = (int)nc.index, nk = (int)(nc.index+nc.count); k < nk; ++k)
|
||||||
|
{
|
||||||
|
const rcCompactSpan& ns = chf.spans[k];
|
||||||
|
const int bot = rcMax(s.y, ns.y);
|
||||||
|
const int top = rcMin(s.y+s.h, ns.y+ns.h);
|
||||||
|
|
||||||
|
// Check that the gap between the spans is walkable,
|
||||||
|
// and that the climb height between the gaps is not too high.
|
||||||
|
if ((top - bot) >= walkableHeight && rcAbs((int)ns.y - (int)s.y) <= walkableClimb)
|
||||||
|
{
|
||||||
|
// Mark direction as walkable.
|
||||||
|
const int lidx = k - (int)nc.index;
|
||||||
|
if (lidx < 0 || lidx > MAX_LAYERS)
|
||||||
|
{
|
||||||
|
tooHighNeighbour = rcMax(tooHighNeighbour, lidx);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rcSetCon(s, dir, lidx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tooHighNeighbour > MAX_LAYERS)
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Heightfield has too many layers %d (max: %d)",
|
||||||
|
tooHighNeighbour, MAX_LAYERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
static int getHeightfieldMemoryUsage(const rcHeightfield& hf)
|
||||||
|
{
|
||||||
|
int size = 0;
|
||||||
|
size += sizeof(hf);
|
||||||
|
size += hf.width * hf.height * sizeof(rcSpan*);
|
||||||
|
|
||||||
|
rcSpanPool* pool = hf.pools;
|
||||||
|
while (pool)
|
||||||
|
{
|
||||||
|
size += (sizeof(rcSpanPool) - sizeof(rcSpan)) + sizeof(rcSpan)*RC_SPANS_PER_POOL;
|
||||||
|
pool = pool->next;
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getCompactHeightFieldMemoryusage(const rcCompactHeightfield& chf)
|
||||||
|
{
|
||||||
|
int size = 0;
|
||||||
|
size += sizeof(rcCompactHeightfield);
|
||||||
|
size += sizeof(rcCompactSpan) * chf.spanCount;
|
||||||
|
size += sizeof(rcCompactCell) * chf.width * chf.height;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
*/
|
|
@ -0,0 +1,86 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied
|
||||||
|
// warranty. In no event will the authors be held liable for any damages
|
||||||
|
// arising from the use of this software.
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it
|
||||||
|
// freely, subject to the following restrictions:
|
||||||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
// claim that you wrote the original software. If you use this software
|
||||||
|
// in a product, an acknowledgment in the product documentation would be
|
||||||
|
// appreciated but is not required.
|
||||||
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
// misrepresented as being the original software.
|
||||||
|
// 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "RecastAlloc.h"
|
||||||
|
#include "RecastAssert.h"
|
||||||
|
|
||||||
|
static void *rcAllocDefault(size_t size, rcAllocHint)
|
||||||
|
{
|
||||||
|
return malloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rcFreeDefault(void *ptr)
|
||||||
|
{
|
||||||
|
free(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static rcAllocFunc* sRecastAllocFunc = rcAllocDefault;
|
||||||
|
static rcFreeFunc* sRecastFreeFunc = rcFreeDefault;
|
||||||
|
|
||||||
|
/// @see rcAlloc, rcFree
|
||||||
|
void rcAllocSetCustom(rcAllocFunc *allocFunc, rcFreeFunc *freeFunc)
|
||||||
|
{
|
||||||
|
sRecastAllocFunc = allocFunc ? allocFunc : rcAllocDefault;
|
||||||
|
sRecastFreeFunc = freeFunc ? freeFunc : rcFreeDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @see rcAllocSetCustom
|
||||||
|
void* rcAlloc(size_t size, rcAllocHint hint)
|
||||||
|
{
|
||||||
|
return sRecastAllocFunc(size, hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// @warning This function leaves the value of @p ptr unchanged. So it still
|
||||||
|
/// points to the same (now invalid) location, and not to null.
|
||||||
|
///
|
||||||
|
/// @see rcAllocSetCustom
|
||||||
|
void rcFree(void* ptr)
|
||||||
|
{
|
||||||
|
if (ptr)
|
||||||
|
sRecastFreeFunc(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @class rcIntArray
|
||||||
|
///
|
||||||
|
/// While it is possible to pre-allocate a specific array size during
|
||||||
|
/// construction or by using the #resize method, certain methods will
|
||||||
|
/// automatically resize the array as needed.
|
||||||
|
///
|
||||||
|
/// @warning The array memory is not initialized to zero when the size is
|
||||||
|
/// manually set during construction or when using #resize.
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// Using this method ensures the array is at least large enough to hold
|
||||||
|
/// the specified number of elements. This can improve performance by
|
||||||
|
/// avoiding auto-resizing during use.
|
||||||
|
void rcIntArray::doResize(int n)
|
||||||
|
{
|
||||||
|
if (!m_cap) m_cap = n;
|
||||||
|
while (m_cap < n) m_cap *= 2;
|
||||||
|
int* newData = (int*)rcAlloc(m_cap*sizeof(int), RC_ALLOC_TEMP);
|
||||||
|
rcAssert(newData);
|
||||||
|
if (m_size && newData) memcpy(newData, m_data, m_size*sizeof(int));
|
||||||
|
rcFree(m_data);
|
||||||
|
m_data = newData;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,591 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied
|
||||||
|
// warranty. In no event will the authors be held liable for any damages
|
||||||
|
// arising from the use of this software.
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it
|
||||||
|
// freely, subject to the following restrictions:
|
||||||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
// claim that you wrote the original software. If you use this software
|
||||||
|
// in a product, an acknowledgment in the product documentation would be
|
||||||
|
// appreciated but is not required.
|
||||||
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
// misrepresented as being the original software.
|
||||||
|
// 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <float.h>
|
||||||
|
#define _USE_MATH_DEFINES
|
||||||
|
#include <math.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "Recast.h"
|
||||||
|
#include "RecastAlloc.h"
|
||||||
|
#include "RecastAssert.h"
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// Basically, any spans that are closer to a boundary or obstruction than the specified radius
|
||||||
|
/// are marked as unwalkable.
|
||||||
|
///
|
||||||
|
/// This method is usually called immediately after the heightfield has been built.
|
||||||
|
///
|
||||||
|
/// @see rcCompactHeightfield, rcBuildCompactHeightfield, rcConfig::walkableRadius
|
||||||
|
bool rcErodeWalkableArea(rcContext* ctx, int radius, rcCompactHeightfield& chf)
|
||||||
|
{
|
||||||
|
rcAssert(ctx);
|
||||||
|
|
||||||
|
const int w = chf.width;
|
||||||
|
const int h = chf.height;
|
||||||
|
|
||||||
|
rcScopedTimer timer(ctx, RC_TIMER_ERODE_AREA);
|
||||||
|
|
||||||
|
unsigned char* dist = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP);
|
||||||
|
if (!dist)
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "erodeWalkableArea: Out of memory 'dist' (%d).", chf.spanCount);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init distance.
|
||||||
|
memset(dist, 0xff, sizeof(unsigned char)*chf.spanCount);
|
||||||
|
|
||||||
|
// Mark boundary cells.
|
||||||
|
for (int y = 0; y < h; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < w; ++x)
|
||||||
|
{
|
||||||
|
const rcCompactCell& c = chf.cells[x+y*w];
|
||||||
|
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
|
||||||
|
{
|
||||||
|
if (chf.areas[i] == RC_NULL_AREA)
|
||||||
|
{
|
||||||
|
dist[i] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const rcCompactSpan& s = chf.spans[i];
|
||||||
|
int nc = 0;
|
||||||
|
for (int dir = 0; dir < 4; ++dir)
|
||||||
|
{
|
||||||
|
if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
|
||||||
|
{
|
||||||
|
const int nx = x + rcGetDirOffsetX(dir);
|
||||||
|
const int ny = y + rcGetDirOffsetY(dir);
|
||||||
|
const int nidx = (int)chf.cells[nx+ny*w].index + rcGetCon(s, dir);
|
||||||
|
if (chf.areas[nidx] != RC_NULL_AREA)
|
||||||
|
{
|
||||||
|
nc++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// At least one missing neighbour.
|
||||||
|
if (nc != 4)
|
||||||
|
dist[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char nd;
|
||||||
|
|
||||||
|
// Pass 1
|
||||||
|
for (int y = 0; y < h; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < w; ++x)
|
||||||
|
{
|
||||||
|
const rcCompactCell& c = chf.cells[x+y*w];
|
||||||
|
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
|
||||||
|
{
|
||||||
|
const rcCompactSpan& s = chf.spans[i];
|
||||||
|
|
||||||
|
if (rcGetCon(s, 0) != RC_NOT_CONNECTED)
|
||||||
|
{
|
||||||
|
// (-1,0)
|
||||||
|
const int ax = x + rcGetDirOffsetX(0);
|
||||||
|
const int ay = y + rcGetDirOffsetY(0);
|
||||||
|
const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0);
|
||||||
|
const rcCompactSpan& as = chf.spans[ai];
|
||||||
|
nd = (unsigned char)rcMin((int)dist[ai]+2, 255);
|
||||||
|
if (nd < dist[i])
|
||||||
|
dist[i] = nd;
|
||||||
|
|
||||||
|
// (-1,-1)
|
||||||
|
if (rcGetCon(as, 3) != RC_NOT_CONNECTED)
|
||||||
|
{
|
||||||
|
const int aax = ax + rcGetDirOffsetX(3);
|
||||||
|
const int aay = ay + rcGetDirOffsetY(3);
|
||||||
|
const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 3);
|
||||||
|
nd = (unsigned char)rcMin((int)dist[aai]+3, 255);
|
||||||
|
if (nd < dist[i])
|
||||||
|
dist[i] = nd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rcGetCon(s, 3) != RC_NOT_CONNECTED)
|
||||||
|
{
|
||||||
|
// (0,-1)
|
||||||
|
const int ax = x + rcGetDirOffsetX(3);
|
||||||
|
const int ay = y + rcGetDirOffsetY(3);
|
||||||
|
const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3);
|
||||||
|
const rcCompactSpan& as = chf.spans[ai];
|
||||||
|
nd = (unsigned char)rcMin((int)dist[ai]+2, 255);
|
||||||
|
if (nd < dist[i])
|
||||||
|
dist[i] = nd;
|
||||||
|
|
||||||
|
// (1,-1)
|
||||||
|
if (rcGetCon(as, 2) != RC_NOT_CONNECTED)
|
||||||
|
{
|
||||||
|
const int aax = ax + rcGetDirOffsetX(2);
|
||||||
|
const int aay = ay + rcGetDirOffsetY(2);
|
||||||
|
const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 2);
|
||||||
|
nd = (unsigned char)rcMin((int)dist[aai]+3, 255);
|
||||||
|
if (nd < dist[i])
|
||||||
|
dist[i] = nd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass 2
|
||||||
|
for (int y = h-1; y >= 0; --y)
|
||||||
|
{
|
||||||
|
for (int x = w-1; x >= 0; --x)
|
||||||
|
{
|
||||||
|
const rcCompactCell& c = chf.cells[x+y*w];
|
||||||
|
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
|
||||||
|
{
|
||||||
|
const rcCompactSpan& s = chf.spans[i];
|
||||||
|
|
||||||
|
if (rcGetCon(s, 2) != RC_NOT_CONNECTED)
|
||||||
|
{
|
||||||
|
// (1,0)
|
||||||
|
const int ax = x + rcGetDirOffsetX(2);
|
||||||
|
const int ay = y + rcGetDirOffsetY(2);
|
||||||
|
const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 2);
|
||||||
|
const rcCompactSpan& as = chf.spans[ai];
|
||||||
|
nd = (unsigned char)rcMin((int)dist[ai]+2, 255);
|
||||||
|
if (nd < dist[i])
|
||||||
|
dist[i] = nd;
|
||||||
|
|
||||||
|
// (1,1)
|
||||||
|
if (rcGetCon(as, 1) != RC_NOT_CONNECTED)
|
||||||
|
{
|
||||||
|
const int aax = ax + rcGetDirOffsetX(1);
|
||||||
|
const int aay = ay + rcGetDirOffsetY(1);
|
||||||
|
const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 1);
|
||||||
|
nd = (unsigned char)rcMin((int)dist[aai]+3, 255);
|
||||||
|
if (nd < dist[i])
|
||||||
|
dist[i] = nd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rcGetCon(s, 1) != RC_NOT_CONNECTED)
|
||||||
|
{
|
||||||
|
// (0,1)
|
||||||
|
const int ax = x + rcGetDirOffsetX(1);
|
||||||
|
const int ay = y + rcGetDirOffsetY(1);
|
||||||
|
const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 1);
|
||||||
|
const rcCompactSpan& as = chf.spans[ai];
|
||||||
|
nd = (unsigned char)rcMin((int)dist[ai]+2, 255);
|
||||||
|
if (nd < dist[i])
|
||||||
|
dist[i] = nd;
|
||||||
|
|
||||||
|
// (-1,1)
|
||||||
|
if (rcGetCon(as, 0) != RC_NOT_CONNECTED)
|
||||||
|
{
|
||||||
|
const int aax = ax + rcGetDirOffsetX(0);
|
||||||
|
const int aay = ay + rcGetDirOffsetY(0);
|
||||||
|
const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 0);
|
||||||
|
nd = (unsigned char)rcMin((int)dist[aai]+3, 255);
|
||||||
|
if (nd < dist[i])
|
||||||
|
dist[i] = nd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsigned char thr = (unsigned char)(radius*2);
|
||||||
|
for (int i = 0; i < chf.spanCount; ++i)
|
||||||
|
if (dist[i] < thr)
|
||||||
|
chf.areas[i] = RC_NULL_AREA;
|
||||||
|
|
||||||
|
rcFree(dist);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void insertSort(unsigned char* a, const int n)
|
||||||
|
{
|
||||||
|
int i, j;
|
||||||
|
for (i = 1; i < n; i++)
|
||||||
|
{
|
||||||
|
const unsigned char value = a[i];
|
||||||
|
for (j = i - 1; j >= 0 && a[j] > value; j--)
|
||||||
|
a[j+1] = a[j];
|
||||||
|
a[j+1] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// This filter is usually applied after applying area id's using functions
|
||||||
|
/// such as #rcMarkBoxArea, #rcMarkConvexPolyArea, and #rcMarkCylinderArea.
|
||||||
|
///
|
||||||
|
/// @see rcCompactHeightfield
|
||||||
|
bool rcMedianFilterWalkableArea(rcContext* ctx, rcCompactHeightfield& chf)
|
||||||
|
{
|
||||||
|
rcAssert(ctx);
|
||||||
|
|
||||||
|
const int w = chf.width;
|
||||||
|
const int h = chf.height;
|
||||||
|
|
||||||
|
rcScopedTimer timer(ctx, RC_TIMER_MEDIAN_AREA);
|
||||||
|
|
||||||
|
unsigned char* areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP);
|
||||||
|
if (!areas)
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "medianFilterWalkableArea: Out of memory 'areas' (%d).", chf.spanCount);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init distance.
|
||||||
|
memset(areas, 0xff, sizeof(unsigned char)*chf.spanCount);
|
||||||
|
|
||||||
|
for (int y = 0; y < h; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < w; ++x)
|
||||||
|
{
|
||||||
|
const rcCompactCell& c = chf.cells[x+y*w];
|
||||||
|
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
|
||||||
|
{
|
||||||
|
const rcCompactSpan& s = chf.spans[i];
|
||||||
|
if (chf.areas[i] == RC_NULL_AREA)
|
||||||
|
{
|
||||||
|
areas[i] = chf.areas[i];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char nei[9];
|
||||||
|
for (int j = 0; j < 9; ++j)
|
||||||
|
nei[j] = chf.areas[i];
|
||||||
|
|
||||||
|
for (int dir = 0; dir < 4; ++dir)
|
||||||
|
{
|
||||||
|
if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
|
||||||
|
{
|
||||||
|
const int ax = x + rcGetDirOffsetX(dir);
|
||||||
|
const int ay = y + rcGetDirOffsetY(dir);
|
||||||
|
const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);
|
||||||
|
if (chf.areas[ai] != RC_NULL_AREA)
|
||||||
|
nei[dir*2+0] = chf.areas[ai];
|
||||||
|
|
||||||
|
const rcCompactSpan& as = chf.spans[ai];
|
||||||
|
const int dir2 = (dir+1) & 0x3;
|
||||||
|
if (rcGetCon(as, dir2) != RC_NOT_CONNECTED)
|
||||||
|
{
|
||||||
|
const int ax2 = ax + rcGetDirOffsetX(dir2);
|
||||||
|
const int ay2 = ay + rcGetDirOffsetY(dir2);
|
||||||
|
const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2);
|
||||||
|
if (chf.areas[ai2] != RC_NULL_AREA)
|
||||||
|
nei[dir*2+1] = chf.areas[ai2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
insertSort(nei, 9);
|
||||||
|
areas[i] = nei[4];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(chf.areas, areas, sizeof(unsigned char)*chf.spanCount);
|
||||||
|
|
||||||
|
rcFree(areas);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// The value of spacial parameters are in world units.
|
||||||
|
///
|
||||||
|
/// @see rcCompactHeightfield, rcMedianFilterWalkableArea
|
||||||
|
void rcMarkBoxArea(rcContext* ctx, const float* bmin, const float* bmax, unsigned char areaId,
|
||||||
|
rcCompactHeightfield& chf)
|
||||||
|
{
|
||||||
|
rcAssert(ctx);
|
||||||
|
|
||||||
|
rcScopedTimer timer(ctx, RC_TIMER_MARK_BOX_AREA);
|
||||||
|
|
||||||
|
int minx = (int)((bmin[0]-chf.bmin[0])/chf.cs);
|
||||||
|
int miny = (int)((bmin[1]-chf.bmin[1])/chf.ch);
|
||||||
|
int minz = (int)((bmin[2]-chf.bmin[2])/chf.cs);
|
||||||
|
int maxx = (int)((bmax[0]-chf.bmin[0])/chf.cs);
|
||||||
|
int maxy = (int)((bmax[1]-chf.bmin[1])/chf.ch);
|
||||||
|
int maxz = (int)((bmax[2]-chf.bmin[2])/chf.cs);
|
||||||
|
|
||||||
|
if (maxx < 0) return;
|
||||||
|
if (minx >= chf.width) return;
|
||||||
|
if (maxz < 0) return;
|
||||||
|
if (minz >= chf.height) return;
|
||||||
|
|
||||||
|
if (minx < 0) minx = 0;
|
||||||
|
if (maxx >= chf.width) maxx = chf.width-1;
|
||||||
|
if (minz < 0) minz = 0;
|
||||||
|
if (maxz >= chf.height) maxz = chf.height-1;
|
||||||
|
|
||||||
|
for (int z = minz; z <= maxz; ++z)
|
||||||
|
{
|
||||||
|
for (int x = minx; x <= maxx; ++x)
|
||||||
|
{
|
||||||
|
const rcCompactCell& c = chf.cells[x+z*chf.width];
|
||||||
|
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
|
||||||
|
{
|
||||||
|
rcCompactSpan& s = chf.spans[i];
|
||||||
|
if ((int)s.y >= miny && (int)s.y <= maxy)
|
||||||
|
{
|
||||||
|
if (chf.areas[i] != RC_NULL_AREA)
|
||||||
|
chf.areas[i] = areaId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int pointInPoly(int nvert, const float* verts, const float* p)
|
||||||
|
{
|
||||||
|
int i, j, c = 0;
|
||||||
|
for (i = 0, j = nvert-1; i < nvert; j = i++)
|
||||||
|
{
|
||||||
|
const float* vi = &verts[i*3];
|
||||||
|
const float* vj = &verts[j*3];
|
||||||
|
if (((vi[2] > p[2]) != (vj[2] > p[2])) &&
|
||||||
|
(p[0] < (vj[0]-vi[0]) * (p[2]-vi[2]) / (vj[2]-vi[2]) + vi[0]) )
|
||||||
|
c = !c;
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// The value of spacial parameters are in world units.
|
||||||
|
///
|
||||||
|
/// The y-values of the polygon vertices are ignored. So the polygon is effectively
|
||||||
|
/// projected onto the xz-plane at @p hmin, then extruded to @p hmax.
|
||||||
|
///
|
||||||
|
/// @see rcCompactHeightfield, rcMedianFilterWalkableArea
|
||||||
|
void rcMarkConvexPolyArea(rcContext* ctx, const float* verts, const int nverts,
|
||||||
|
const float hmin, const float hmax, unsigned char areaId,
|
||||||
|
rcCompactHeightfield& chf)
|
||||||
|
{
|
||||||
|
rcAssert(ctx);
|
||||||
|
|
||||||
|
rcScopedTimer timer(ctx, RC_TIMER_MARK_CONVEXPOLY_AREA);
|
||||||
|
|
||||||
|
float bmin[3], bmax[3];
|
||||||
|
rcVcopy(bmin, verts);
|
||||||
|
rcVcopy(bmax, verts);
|
||||||
|
for (int i = 1; i < nverts; ++i)
|
||||||
|
{
|
||||||
|
rcVmin(bmin, &verts[i*3]);
|
||||||
|
rcVmax(bmax, &verts[i*3]);
|
||||||
|
}
|
||||||
|
bmin[1] = hmin;
|
||||||
|
bmax[1] = hmax;
|
||||||
|
|
||||||
|
int minx = (int)((bmin[0]-chf.bmin[0])/chf.cs);
|
||||||
|
int miny = (int)((bmin[1]-chf.bmin[1])/chf.ch);
|
||||||
|
int minz = (int)((bmin[2]-chf.bmin[2])/chf.cs);
|
||||||
|
int maxx = (int)((bmax[0]-chf.bmin[0])/chf.cs);
|
||||||
|
int maxy = (int)((bmax[1]-chf.bmin[1])/chf.ch);
|
||||||
|
int maxz = (int)((bmax[2]-chf.bmin[2])/chf.cs);
|
||||||
|
|
||||||
|
if (maxx < 0) return;
|
||||||
|
if (minx >= chf.width) return;
|
||||||
|
if (maxz < 0) return;
|
||||||
|
if (minz >= chf.height) return;
|
||||||
|
|
||||||
|
if (minx < 0) minx = 0;
|
||||||
|
if (maxx >= chf.width) maxx = chf.width-1;
|
||||||
|
if (minz < 0) minz = 0;
|
||||||
|
if (maxz >= chf.height) maxz = chf.height-1;
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Optimize.
|
||||||
|
for (int z = minz; z <= maxz; ++z)
|
||||||
|
{
|
||||||
|
for (int x = minx; x <= maxx; ++x)
|
||||||
|
{
|
||||||
|
const rcCompactCell& c = chf.cells[x+z*chf.width];
|
||||||
|
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
|
||||||
|
{
|
||||||
|
rcCompactSpan& s = chf.spans[i];
|
||||||
|
if (chf.areas[i] == RC_NULL_AREA)
|
||||||
|
continue;
|
||||||
|
if ((int)s.y >= miny && (int)s.y <= maxy)
|
||||||
|
{
|
||||||
|
float p[3];
|
||||||
|
p[0] = chf.bmin[0] + (x+0.5f)*chf.cs;
|
||||||
|
p[1] = 0;
|
||||||
|
p[2] = chf.bmin[2] + (z+0.5f)*chf.cs;
|
||||||
|
|
||||||
|
if (pointInPoly(nverts, verts, p))
|
||||||
|
{
|
||||||
|
chf.areas[i] = areaId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int rcOffsetPoly(const float* verts, const int nverts, const float offset,
|
||||||
|
float* outVerts, const int maxOutVerts)
|
||||||
|
{
|
||||||
|
const float MITER_LIMIT = 1.20f;
|
||||||
|
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < nverts; i++)
|
||||||
|
{
|
||||||
|
const int a = (i+nverts-1) % nverts;
|
||||||
|
const int b = i;
|
||||||
|
const int c = (i+1) % nverts;
|
||||||
|
const float* va = &verts[a*3];
|
||||||
|
const float* vb = &verts[b*3];
|
||||||
|
const float* vc = &verts[c*3];
|
||||||
|
float dx0 = vb[0] - va[0];
|
||||||
|
float dy0 = vb[2] - va[2];
|
||||||
|
float d0 = dx0*dx0 + dy0*dy0;
|
||||||
|
if (d0 > 1e-6f)
|
||||||
|
{
|
||||||
|
d0 = 1.0f/rcSqrt(d0);
|
||||||
|
dx0 *= d0;
|
||||||
|
dy0 *= d0;
|
||||||
|
}
|
||||||
|
float dx1 = vc[0] - vb[0];
|
||||||
|
float dy1 = vc[2] - vb[2];
|
||||||
|
float d1 = dx1*dx1 + dy1*dy1;
|
||||||
|
if (d1 > 1e-6f)
|
||||||
|
{
|
||||||
|
d1 = 1.0f/rcSqrt(d1);
|
||||||
|
dx1 *= d1;
|
||||||
|
dy1 *= d1;
|
||||||
|
}
|
||||||
|
const float dlx0 = -dy0;
|
||||||
|
const float dly0 = dx0;
|
||||||
|
const float dlx1 = -dy1;
|
||||||
|
const float dly1 = dx1;
|
||||||
|
float cross = dx1*dy0 - dx0*dy1;
|
||||||
|
float dmx = (dlx0 + dlx1) * 0.5f;
|
||||||
|
float dmy = (dly0 + dly1) * 0.5f;
|
||||||
|
float dmr2 = dmx*dmx + dmy*dmy;
|
||||||
|
bool bevel = dmr2 * MITER_LIMIT*MITER_LIMIT < 1.0f;
|
||||||
|
if (dmr2 > 1e-6f)
|
||||||
|
{
|
||||||
|
const float scale = 1.0f / dmr2;
|
||||||
|
dmx *= scale;
|
||||||
|
dmy *= scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bevel && cross < 0.0f)
|
||||||
|
{
|
||||||
|
if (n+2 >= maxOutVerts)
|
||||||
|
return 0;
|
||||||
|
float d = (1.0f - (dx0*dx1 + dy0*dy1))*0.5f;
|
||||||
|
outVerts[n*3+0] = vb[0] + (-dlx0+dx0*d)*offset;
|
||||||
|
outVerts[n*3+1] = vb[1];
|
||||||
|
outVerts[n*3+2] = vb[2] + (-dly0+dy0*d)*offset;
|
||||||
|
n++;
|
||||||
|
outVerts[n*3+0] = vb[0] + (-dlx1-dx1*d)*offset;
|
||||||
|
outVerts[n*3+1] = vb[1];
|
||||||
|
outVerts[n*3+2] = vb[2] + (-dly1-dy1*d)*offset;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (n+1 >= maxOutVerts)
|
||||||
|
return 0;
|
||||||
|
outVerts[n*3+0] = vb[0] - dmx*offset;
|
||||||
|
outVerts[n*3+1] = vb[1];
|
||||||
|
outVerts[n*3+2] = vb[2] - dmy*offset;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// The value of spacial parameters are in world units.
|
||||||
|
///
|
||||||
|
/// @see rcCompactHeightfield, rcMedianFilterWalkableArea
|
||||||
|
void rcMarkCylinderArea(rcContext* ctx, const float* pos,
|
||||||
|
const float r, const float h, unsigned char areaId,
|
||||||
|
rcCompactHeightfield& chf)
|
||||||
|
{
|
||||||
|
rcAssert(ctx);
|
||||||
|
|
||||||
|
rcScopedTimer timer(ctx, RC_TIMER_MARK_CYLINDER_AREA);
|
||||||
|
|
||||||
|
float bmin[3], bmax[3];
|
||||||
|
bmin[0] = pos[0] - r;
|
||||||
|
bmin[1] = pos[1];
|
||||||
|
bmin[2] = pos[2] - r;
|
||||||
|
bmax[0] = pos[0] + r;
|
||||||
|
bmax[1] = pos[1] + h;
|
||||||
|
bmax[2] = pos[2] + r;
|
||||||
|
const float r2 = r*r;
|
||||||
|
|
||||||
|
int minx = (int)((bmin[0]-chf.bmin[0])/chf.cs);
|
||||||
|
int miny = (int)((bmin[1]-chf.bmin[1])/chf.ch);
|
||||||
|
int minz = (int)((bmin[2]-chf.bmin[2])/chf.cs);
|
||||||
|
int maxx = (int)((bmax[0]-chf.bmin[0])/chf.cs);
|
||||||
|
int maxy = (int)((bmax[1]-chf.bmin[1])/chf.ch);
|
||||||
|
int maxz = (int)((bmax[2]-chf.bmin[2])/chf.cs);
|
||||||
|
|
||||||
|
if (maxx < 0) return;
|
||||||
|
if (minx >= chf.width) return;
|
||||||
|
if (maxz < 0) return;
|
||||||
|
if (minz >= chf.height) return;
|
||||||
|
|
||||||
|
if (minx < 0) minx = 0;
|
||||||
|
if (maxx >= chf.width) maxx = chf.width-1;
|
||||||
|
if (minz < 0) minz = 0;
|
||||||
|
if (maxz >= chf.height) maxz = chf.height-1;
|
||||||
|
|
||||||
|
|
||||||
|
for (int z = minz; z <= maxz; ++z)
|
||||||
|
{
|
||||||
|
for (int x = minx; x <= maxx; ++x)
|
||||||
|
{
|
||||||
|
const rcCompactCell& c = chf.cells[x+z*chf.width];
|
||||||
|
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
|
||||||
|
{
|
||||||
|
rcCompactSpan& s = chf.spans[i];
|
||||||
|
|
||||||
|
if (chf.areas[i] == RC_NULL_AREA)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((int)s.y >= miny && (int)s.y <= maxy)
|
||||||
|
{
|
||||||
|
const float sx = chf.bmin[0] + (x+0.5f)*chf.cs;
|
||||||
|
const float sz = chf.bmin[2] + (z+0.5f)*chf.cs;
|
||||||
|
const float dx = sx - pos[0];
|
||||||
|
const float dz = sz - pos[2];
|
||||||
|
|
||||||
|
if (dx*dx + dz*dz < r2)
|
||||||
|
{
|
||||||
|
chf.areas[i] = areaId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied
|
||||||
|
// warranty. In no event will the authors be held liable for any damages
|
||||||
|
// arising from the use of this software.
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it
|
||||||
|
// freely, subject to the following restrictions:
|
||||||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
// claim that you wrote the original software. If you use this software
|
||||||
|
// in a product, an acknowledgment in the product documentation would be
|
||||||
|
// appreciated but is not required.
|
||||||
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
// misrepresented as being the original software.
|
||||||
|
// 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "RecastAssert.h"
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
|
||||||
|
static rcAssertFailFunc* sRecastAssertFailFunc = 0;
|
||||||
|
|
||||||
|
void rcAssertFailSetCustom(rcAssertFailFunc *assertFailFunc)
|
||||||
|
{
|
||||||
|
sRecastAssertFailFunc = assertFailFunc;
|
||||||
|
}
|
||||||
|
|
||||||
|
rcAssertFailFunc* rcAssertFailGetCustom()
|
||||||
|
{
|
||||||
|
return sRecastAssertFailFunc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,202 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied
|
||||||
|
// warranty. In no event will the authors be held liable for any damages
|
||||||
|
// arising from the use of this software.
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it
|
||||||
|
// freely, subject to the following restrictions:
|
||||||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
// claim that you wrote the original software. If you use this software
|
||||||
|
// in a product, an acknowledgment in the product documentation would be
|
||||||
|
// appreciated but is not required.
|
||||||
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
// misrepresented as being the original software.
|
||||||
|
// 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
//
|
||||||
|
|
||||||
|
#define _USE_MATH_DEFINES
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "Recast.h"
|
||||||
|
#include "RecastAssert.h"
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// Allows the formation of walkable regions that will flow over low lying
|
||||||
|
/// objects such as curbs, and up structures such as stairways.
|
||||||
|
///
|
||||||
|
/// Two neighboring spans are walkable if: <tt>rcAbs(currentSpan.smax - neighborSpan.smax) < waklableClimb</tt>
|
||||||
|
///
|
||||||
|
/// @warning Will override the effect of #rcFilterLedgeSpans. So if both filters are used, call
|
||||||
|
/// #rcFilterLedgeSpans after calling this filter.
|
||||||
|
///
|
||||||
|
/// @see rcHeightfield, rcConfig
|
||||||
|
void rcFilterLowHangingWalkableObstacles(rcContext* ctx, const int walkableClimb, rcHeightfield& solid)
|
||||||
|
{
|
||||||
|
rcAssert(ctx);
|
||||||
|
|
||||||
|
rcScopedTimer timer(ctx, RC_TIMER_FILTER_LOW_OBSTACLES);
|
||||||
|
|
||||||
|
const int w = solid.width;
|
||||||
|
const int h = solid.height;
|
||||||
|
|
||||||
|
for (int y = 0; y < h; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < w; ++x)
|
||||||
|
{
|
||||||
|
rcSpan* ps = 0;
|
||||||
|
bool previousWalkable = false;
|
||||||
|
unsigned char previousArea = RC_NULL_AREA;
|
||||||
|
|
||||||
|
for (rcSpan* s = solid.spans[x + y*w]; s; ps = s, s = s->next)
|
||||||
|
{
|
||||||
|
const bool walkable = s->area != RC_NULL_AREA;
|
||||||
|
// If current span is not walkable, but there is walkable
|
||||||
|
// span just below it, mark the span above it walkable too.
|
||||||
|
if (!walkable && previousWalkable)
|
||||||
|
{
|
||||||
|
if (rcAbs((int)s->smax - (int)ps->smax) <= walkableClimb)
|
||||||
|
s->area = previousArea;
|
||||||
|
}
|
||||||
|
// Copy walkable flag so that it cannot propagate
|
||||||
|
// past multiple non-walkable objects.
|
||||||
|
previousWalkable = walkable;
|
||||||
|
previousArea = s->area;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb
|
||||||
|
/// from the current span's maximum.
|
||||||
|
/// This method removes the impact of the overestimation of conservative voxelization
|
||||||
|
/// so the resulting mesh will not have regions hanging in the air over ledges.
|
||||||
|
///
|
||||||
|
/// A span is a ledge if: <tt>rcAbs(currentSpan.smax - neighborSpan.smax) > walkableClimb</tt>
|
||||||
|
///
|
||||||
|
/// @see rcHeightfield, rcConfig
|
||||||
|
void rcFilterLedgeSpans(rcContext* ctx, const int walkableHeight, const int walkableClimb,
|
||||||
|
rcHeightfield& solid)
|
||||||
|
{
|
||||||
|
rcAssert(ctx);
|
||||||
|
|
||||||
|
rcScopedTimer timer(ctx, RC_TIMER_FILTER_BORDER);
|
||||||
|
|
||||||
|
const int w = solid.width;
|
||||||
|
const int h = solid.height;
|
||||||
|
const int MAX_HEIGHT = 0xffff;
|
||||||
|
|
||||||
|
// Mark border spans.
|
||||||
|
for (int y = 0; y < h; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < w; ++x)
|
||||||
|
{
|
||||||
|
for (rcSpan* s = solid.spans[x + y*w]; s; s = s->next)
|
||||||
|
{
|
||||||
|
// Skip non walkable spans.
|
||||||
|
if (s->area == RC_NULL_AREA)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const int bot = (int)(s->smax);
|
||||||
|
const int top = s->next ? (int)(s->next->smin) : MAX_HEIGHT;
|
||||||
|
|
||||||
|
// Find neighbours minimum height.
|
||||||
|
int minh = MAX_HEIGHT;
|
||||||
|
|
||||||
|
// Min and max height of accessible neighbours.
|
||||||
|
int asmin = s->smax;
|
||||||
|
int asmax = s->smax;
|
||||||
|
|
||||||
|
for (int dir = 0; dir < 4; ++dir)
|
||||||
|
{
|
||||||
|
int dx = x + rcGetDirOffsetX(dir);
|
||||||
|
int dy = y + rcGetDirOffsetY(dir);
|
||||||
|
// Skip neighbours which are out of bounds.
|
||||||
|
if (dx < 0 || dy < 0 || dx >= w || dy >= h)
|
||||||
|
{
|
||||||
|
minh = rcMin(minh, -walkableClimb - bot);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From minus infinity to the first span.
|
||||||
|
rcSpan* ns = solid.spans[dx + dy*w];
|
||||||
|
int nbot = -walkableClimb;
|
||||||
|
int ntop = ns ? (int)ns->smin : MAX_HEIGHT;
|
||||||
|
// Skip neightbour if the gap between the spans is too small.
|
||||||
|
if (rcMin(top,ntop) - rcMax(bot,nbot) > walkableHeight)
|
||||||
|
minh = rcMin(minh, nbot - bot);
|
||||||
|
|
||||||
|
// Rest of the spans.
|
||||||
|
for (ns = solid.spans[dx + dy*w]; ns; ns = ns->next)
|
||||||
|
{
|
||||||
|
nbot = (int)ns->smax;
|
||||||
|
ntop = ns->next ? (int)ns->next->smin : MAX_HEIGHT;
|
||||||
|
// Skip neightbour if the gap between the spans is too small.
|
||||||
|
if (rcMin(top,ntop) - rcMax(bot,nbot) > walkableHeight)
|
||||||
|
{
|
||||||
|
minh = rcMin(minh, nbot - bot);
|
||||||
|
|
||||||
|
// Find min/max accessible neighbour height.
|
||||||
|
if (rcAbs(nbot - bot) <= walkableClimb)
|
||||||
|
{
|
||||||
|
if (nbot < asmin) asmin = nbot;
|
||||||
|
if (nbot > asmax) asmax = nbot;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The current span is close to a ledge if the drop to any
|
||||||
|
// neighbour span is less than the walkableClimb.
|
||||||
|
if (minh < -walkableClimb)
|
||||||
|
{
|
||||||
|
s->area = RC_NULL_AREA;
|
||||||
|
}
|
||||||
|
// If the difference between all neighbours is too large,
|
||||||
|
// we are at steep slope, mark the span as ledge.
|
||||||
|
else if ((asmax - asmin) > walkableClimb)
|
||||||
|
{
|
||||||
|
s->area = RC_NULL_AREA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// For this filter, the clearance above the span is the distance from the span's
|
||||||
|
/// maximum to the next higher span's minimum. (Same grid column.)
|
||||||
|
///
|
||||||
|
/// @see rcHeightfield, rcConfig
|
||||||
|
void rcFilterWalkableLowHeightSpans(rcContext* ctx, int walkableHeight, rcHeightfield& solid)
|
||||||
|
{
|
||||||
|
rcAssert(ctx);
|
||||||
|
|
||||||
|
rcScopedTimer timer(ctx, RC_TIMER_FILTER_WALKABLE);
|
||||||
|
|
||||||
|
const int w = solid.width;
|
||||||
|
const int h = solid.height;
|
||||||
|
const int MAX_HEIGHT = 0xffff;
|
||||||
|
|
||||||
|
// Remove walkable flag from spans which do not have enough
|
||||||
|
// space above them for the agent to stand there.
|
||||||
|
for (int y = 0; y < h; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < w; ++x)
|
||||||
|
{
|
||||||
|
for (rcSpan* s = solid.spans[x + y*w]; s; s = s->next)
|
||||||
|
{
|
||||||
|
const int bot = (int)(s->smax);
|
||||||
|
const int top = s->next ? (int)(s->next->smin) : MAX_HEIGHT;
|
||||||
|
if ((top - bot) <= walkableHeight)
|
||||||
|
s->area = RC_NULL_AREA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,644 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied
|
||||||
|
// warranty. In no event will the authors be held liable for any damages
|
||||||
|
// arising from the use of this software.
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it
|
||||||
|
// freely, subject to the following restrictions:
|
||||||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
// claim that you wrote the original software. If you use this software
|
||||||
|
// in a product, an acknowledgment in the product documentation would be
|
||||||
|
// appreciated but is not required.
|
||||||
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
// misrepresented as being the original software.
|
||||||
|
// 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <float.h>
|
||||||
|
#define _USE_MATH_DEFINES
|
||||||
|
#include <math.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "Recast.h"
|
||||||
|
#include "RecastAlloc.h"
|
||||||
|
#include "RecastAssert.h"
|
||||||
|
|
||||||
|
|
||||||
|
// Must be 255 or smaller (not 256) because layer IDs are stored as
|
||||||
|
// a byte where 255 is a special value.
|
||||||
|
static const int RC_MAX_LAYERS = 63;
|
||||||
|
static const int RC_MAX_NEIS = 16;
|
||||||
|
|
||||||
|
struct rcLayerRegion
|
||||||
|
{
|
||||||
|
unsigned char layers[RC_MAX_LAYERS];
|
||||||
|
unsigned char neis[RC_MAX_NEIS];
|
||||||
|
unsigned short ymin, ymax;
|
||||||
|
unsigned char layerId; // Layer ID
|
||||||
|
unsigned char nlayers; // Layer count
|
||||||
|
unsigned char nneis; // Neighbour count
|
||||||
|
unsigned char base; // Flag indicating if the region is the base of merged regions.
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static bool contains(const unsigned char* a, const unsigned char an, const unsigned char v)
|
||||||
|
{
|
||||||
|
const int n = (int)an;
|
||||||
|
for (int i = 0; i < n; ++i)
|
||||||
|
{
|
||||||
|
if (a[i] == v)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool addUnique(unsigned char* a, unsigned char& an, int anMax, unsigned char v)
|
||||||
|
{
|
||||||
|
if (contains(a, an, v))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if ((int)an >= anMax)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
a[an] = v;
|
||||||
|
an++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inline bool overlapRange(const unsigned short amin, const unsigned short amax,
|
||||||
|
const unsigned short bmin, const unsigned short bmax)
|
||||||
|
{
|
||||||
|
return (amin > bmax || amax < bmin) ? false : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct rcLayerSweepSpan
|
||||||
|
{
|
||||||
|
unsigned short ns; // number samples
|
||||||
|
unsigned char id; // region id
|
||||||
|
unsigned char nei; // neighbour id
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// See the #rcConfig documentation for more information on the configuration parameters.
|
||||||
|
///
|
||||||
|
/// @see rcAllocHeightfieldLayerSet, rcCompactHeightfield, rcHeightfieldLayerSet, rcConfig
|
||||||
|
bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf,
|
||||||
|
const int borderSize, const int walkableHeight,
|
||||||
|
rcHeightfieldLayerSet& lset)
|
||||||
|
{
|
||||||
|
rcAssert(ctx);
|
||||||
|
|
||||||
|
rcScopedTimer timer(ctx, RC_TIMER_BUILD_LAYERS);
|
||||||
|
|
||||||
|
const int w = chf.width;
|
||||||
|
const int h = chf.height;
|
||||||
|
|
||||||
|
rcScopedDelete<unsigned char> srcReg((unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP));
|
||||||
|
if (!srcReg)
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'srcReg' (%d).", chf.spanCount);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memset(srcReg,0xff,sizeof(unsigned char)*chf.spanCount);
|
||||||
|
|
||||||
|
const int nsweeps = chf.width;
|
||||||
|
rcScopedDelete<rcLayerSweepSpan> sweeps((rcLayerSweepSpan*)rcAlloc(sizeof(rcLayerSweepSpan)*nsweeps, RC_ALLOC_TEMP));
|
||||||
|
if (!sweeps)
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'sweeps' (%d).", nsweeps);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Partition walkable area into monotone regions.
|
||||||
|
int prevCount[256];
|
||||||
|
unsigned char regId = 0;
|
||||||
|
|
||||||
|
for (int y = borderSize; y < h-borderSize; ++y)
|
||||||
|
{
|
||||||
|
memset(prevCount,0,sizeof(int)*regId);
|
||||||
|
unsigned char sweepId = 0;
|
||||||
|
|
||||||
|
for (int x = borderSize; x < w-borderSize; ++x)
|
||||||
|
{
|
||||||
|
const rcCompactCell& c = chf.cells[x+y*w];
|
||||||
|
|
||||||
|
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
|
||||||
|
{
|
||||||
|
const rcCompactSpan& s = chf.spans[i];
|
||||||
|
if (chf.areas[i] == RC_NULL_AREA) continue;
|
||||||
|
|
||||||
|
unsigned char sid = 0xff;
|
||||||
|
|
||||||
|
// -x
|
||||||
|
if (rcGetCon(s, 0) != RC_NOT_CONNECTED)
|
||||||
|
{
|
||||||
|
const int ax = x + rcGetDirOffsetX(0);
|
||||||
|
const int ay = y + rcGetDirOffsetY(0);
|
||||||
|
const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0);
|
||||||
|
if (chf.areas[ai] != RC_NULL_AREA && srcReg[ai] != 0xff)
|
||||||
|
sid = srcReg[ai];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sid == 0xff)
|
||||||
|
{
|
||||||
|
sid = sweepId++;
|
||||||
|
sweeps[sid].nei = 0xff;
|
||||||
|
sweeps[sid].ns = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -y
|
||||||
|
if (rcGetCon(s,3) != RC_NOT_CONNECTED)
|
||||||
|
{
|
||||||
|
const int ax = x + rcGetDirOffsetX(3);
|
||||||
|
const int ay = y + rcGetDirOffsetY(3);
|
||||||
|
const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3);
|
||||||
|
const unsigned char nr = srcReg[ai];
|
||||||
|
if (nr != 0xff)
|
||||||
|
{
|
||||||
|
// Set neighbour when first valid neighbour is encoutered.
|
||||||
|
if (sweeps[sid].ns == 0)
|
||||||
|
sweeps[sid].nei = nr;
|
||||||
|
|
||||||
|
if (sweeps[sid].nei == nr)
|
||||||
|
{
|
||||||
|
// Update existing neighbour
|
||||||
|
sweeps[sid].ns++;
|
||||||
|
prevCount[nr]++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This is hit if there is nore than one neighbour.
|
||||||
|
// Invalidate the neighbour.
|
||||||
|
sweeps[sid].nei = 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
srcReg[i] = sid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create unique ID.
|
||||||
|
for (int i = 0; i < sweepId; ++i)
|
||||||
|
{
|
||||||
|
// If the neighbour is set and there is only one continuous connection to it,
|
||||||
|
// the sweep will be merged with the previous one, else new region is created.
|
||||||
|
if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == (int)sweeps[i].ns)
|
||||||
|
{
|
||||||
|
sweeps[i].id = sweeps[i].nei;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (regId == 255)
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Region ID overflow.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sweeps[i].id = regId++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remap local sweep ids to region ids.
|
||||||
|
for (int x = borderSize; x < w-borderSize; ++x)
|
||||||
|
{
|
||||||
|
const rcCompactCell& c = chf.cells[x+y*w];
|
||||||
|
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
|
||||||
|
{
|
||||||
|
if (srcReg[i] != 0xff)
|
||||||
|
srcReg[i] = sweeps[srcReg[i]].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate and init layer regions.
|
||||||
|
const int nregs = (int)regId;
|
||||||
|
rcScopedDelete<rcLayerRegion> regs((rcLayerRegion*)rcAlloc(sizeof(rcLayerRegion)*nregs, RC_ALLOC_TEMP));
|
||||||
|
if (!regs)
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'regs' (%d).", nregs);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memset(regs, 0, sizeof(rcLayerRegion)*nregs);
|
||||||
|
for (int i = 0; i < nregs; ++i)
|
||||||
|
{
|
||||||
|
regs[i].layerId = 0xff;
|
||||||
|
regs[i].ymin = 0xffff;
|
||||||
|
regs[i].ymax = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find region neighbours and overlapping regions.
|
||||||
|
for (int y = 0; y < h; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < w; ++x)
|
||||||
|
{
|
||||||
|
const rcCompactCell& c = chf.cells[x+y*w];
|
||||||
|
|
||||||
|
unsigned char lregs[RC_MAX_LAYERS];
|
||||||
|
int nlregs = 0;
|
||||||
|
|
||||||
|
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
|
||||||
|
{
|
||||||
|
const rcCompactSpan& s = chf.spans[i];
|
||||||
|
const unsigned char ri = srcReg[i];
|
||||||
|
if (ri == 0xff) continue;
|
||||||
|
|
||||||
|
regs[ri].ymin = rcMin(regs[ri].ymin, s.y);
|
||||||
|
regs[ri].ymax = rcMax(regs[ri].ymax, s.y);
|
||||||
|
|
||||||
|
// Collect all region layers.
|
||||||
|
if (nlregs < RC_MAX_LAYERS)
|
||||||
|
lregs[nlregs++] = ri;
|
||||||
|
|
||||||
|
// Update neighbours
|
||||||
|
for (int dir = 0; dir < 4; ++dir)
|
||||||
|
{
|
||||||
|
if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
|
||||||
|
{
|
||||||
|
const int ax = x + rcGetDirOffsetX(dir);
|
||||||
|
const int ay = y + rcGetDirOffsetY(dir);
|
||||||
|
const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);
|
||||||
|
const unsigned char rai = srcReg[ai];
|
||||||
|
if (rai != 0xff && rai != ri)
|
||||||
|
{
|
||||||
|
// Don't check return value -- if we cannot add the neighbor
|
||||||
|
// it will just cause a few more regions to be created, which
|
||||||
|
// is fine.
|
||||||
|
addUnique(regs[ri].neis, regs[ri].nneis, RC_MAX_NEIS, rai);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update overlapping regions.
|
||||||
|
for (int i = 0; i < nlregs-1; ++i)
|
||||||
|
{
|
||||||
|
for (int j = i+1; j < nlregs; ++j)
|
||||||
|
{
|
||||||
|
if (lregs[i] != lregs[j])
|
||||||
|
{
|
||||||
|
rcLayerRegion& ri = regs[lregs[i]];
|
||||||
|
rcLayerRegion& rj = regs[lregs[j]];
|
||||||
|
|
||||||
|
if (!addUnique(ri.layers, ri.nlayers, RC_MAX_LAYERS, lregs[j]) ||
|
||||||
|
!addUnique(rj.layers, rj.nlayers, RC_MAX_LAYERS, lregs[i]))
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: layer overflow (too many overlapping walkable platforms). Try increasing RC_MAX_LAYERS.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 2D layers from regions.
|
||||||
|
unsigned char layerId = 0;
|
||||||
|
|
||||||
|
static const int MAX_STACK = 64;
|
||||||
|
unsigned char stack[MAX_STACK];
|
||||||
|
int nstack = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < nregs; ++i)
|
||||||
|
{
|
||||||
|
rcLayerRegion& root = regs[i];
|
||||||
|
// Skip already visited.
|
||||||
|
if (root.layerId != 0xff)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Start search.
|
||||||
|
root.layerId = layerId;
|
||||||
|
root.base = 1;
|
||||||
|
|
||||||
|
nstack = 0;
|
||||||
|
stack[nstack++] = (unsigned char)i;
|
||||||
|
|
||||||
|
while (nstack)
|
||||||
|
{
|
||||||
|
// Pop front
|
||||||
|
rcLayerRegion& reg = regs[stack[0]];
|
||||||
|
nstack--;
|
||||||
|
for (int j = 0; j < nstack; ++j)
|
||||||
|
stack[j] = stack[j+1];
|
||||||
|
|
||||||
|
const int nneis = (int)reg.nneis;
|
||||||
|
for (int j = 0; j < nneis; ++j)
|
||||||
|
{
|
||||||
|
const unsigned char nei = reg.neis[j];
|
||||||
|
rcLayerRegion& regn = regs[nei];
|
||||||
|
// Skip already visited.
|
||||||
|
if (regn.layerId != 0xff)
|
||||||
|
continue;
|
||||||
|
// Skip if the neighbour is overlapping root region.
|
||||||
|
if (contains(root.layers, root.nlayers, nei))
|
||||||
|
continue;
|
||||||
|
// Skip if the height range would become too large.
|
||||||
|
const int ymin = rcMin(root.ymin, regn.ymin);
|
||||||
|
const int ymax = rcMax(root.ymax, regn.ymax);
|
||||||
|
if ((ymax - ymin) >= 255)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (nstack < MAX_STACK)
|
||||||
|
{
|
||||||
|
// Deepen
|
||||||
|
stack[nstack++] = (unsigned char)nei;
|
||||||
|
|
||||||
|
// Mark layer id
|
||||||
|
regn.layerId = layerId;
|
||||||
|
// Merge current layers to root.
|
||||||
|
for (int k = 0; k < regn.nlayers; ++k)
|
||||||
|
{
|
||||||
|
if (!addUnique(root.layers, root.nlayers, RC_MAX_LAYERS, regn.layers[k]))
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: layer overflow (too many overlapping walkable platforms). Try increasing RC_MAX_LAYERS.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root.ymin = rcMin(root.ymin, regn.ymin);
|
||||||
|
root.ymax = rcMax(root.ymax, regn.ymax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layerId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge non-overlapping regions that are close in height.
|
||||||
|
const unsigned short mergeHeight = (unsigned short)walkableHeight * 4;
|
||||||
|
|
||||||
|
for (int i = 0; i < nregs; ++i)
|
||||||
|
{
|
||||||
|
rcLayerRegion& ri = regs[i];
|
||||||
|
if (!ri.base) continue;
|
||||||
|
|
||||||
|
unsigned char newId = ri.layerId;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
unsigned char oldId = 0xff;
|
||||||
|
|
||||||
|
for (int j = 0; j < nregs; ++j)
|
||||||
|
{
|
||||||
|
if (i == j) continue;
|
||||||
|
rcLayerRegion& rj = regs[j];
|
||||||
|
if (!rj.base) continue;
|
||||||
|
|
||||||
|
// Skip if the regions are not close to each other.
|
||||||
|
if (!overlapRange(ri.ymin,ri.ymax+mergeHeight, rj.ymin,rj.ymax+mergeHeight))
|
||||||
|
continue;
|
||||||
|
// Skip if the height range would become too large.
|
||||||
|
const int ymin = rcMin(ri.ymin, rj.ymin);
|
||||||
|
const int ymax = rcMax(ri.ymax, rj.ymax);
|
||||||
|
if ((ymax - ymin) >= 255)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Make sure that there is no overlap when merging 'ri' and 'rj'.
|
||||||
|
bool overlap = false;
|
||||||
|
// Iterate over all regions which have the same layerId as 'rj'
|
||||||
|
for (int k = 0; k < nregs; ++k)
|
||||||
|
{
|
||||||
|
if (regs[k].layerId != rj.layerId)
|
||||||
|
continue;
|
||||||
|
// Check if region 'k' is overlapping region 'ri'
|
||||||
|
// Index to 'regs' is the same as region id.
|
||||||
|
if (contains(ri.layers,ri.nlayers, (unsigned char)k))
|
||||||
|
{
|
||||||
|
overlap = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Cannot merge of regions overlap.
|
||||||
|
if (overlap)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Can merge i and j.
|
||||||
|
oldId = rj.layerId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Could not find anything to merge with, stop.
|
||||||
|
if (oldId == 0xff)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Merge
|
||||||
|
for (int j = 0; j < nregs; ++j)
|
||||||
|
{
|
||||||
|
rcLayerRegion& rj = regs[j];
|
||||||
|
if (rj.layerId == oldId)
|
||||||
|
{
|
||||||
|
rj.base = 0;
|
||||||
|
// Remap layerIds.
|
||||||
|
rj.layerId = newId;
|
||||||
|
// Add overlaid layers from 'rj' to 'ri'.
|
||||||
|
for (int k = 0; k < rj.nlayers; ++k)
|
||||||
|
{
|
||||||
|
if (!addUnique(ri.layers, ri.nlayers, RC_MAX_LAYERS, rj.layers[k]))
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: layer overflow (too many overlapping walkable platforms). Try increasing RC_MAX_LAYERS.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update height bounds.
|
||||||
|
ri.ymin = rcMin(ri.ymin, rj.ymin);
|
||||||
|
ri.ymax = rcMax(ri.ymax, rj.ymax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compact layerIds
|
||||||
|
unsigned char remap[256];
|
||||||
|
memset(remap, 0, 256);
|
||||||
|
|
||||||
|
// Find number of unique layers.
|
||||||
|
layerId = 0;
|
||||||
|
for (int i = 0; i < nregs; ++i)
|
||||||
|
remap[regs[i].layerId] = 1;
|
||||||
|
for (int i = 0; i < 256; ++i)
|
||||||
|
{
|
||||||
|
if (remap[i])
|
||||||
|
remap[i] = layerId++;
|
||||||
|
else
|
||||||
|
remap[i] = 0xff;
|
||||||
|
}
|
||||||
|
// Remap ids.
|
||||||
|
for (int i = 0; i < nregs; ++i)
|
||||||
|
regs[i].layerId = remap[regs[i].layerId];
|
||||||
|
|
||||||
|
// No layers, return empty.
|
||||||
|
if (layerId == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Create layers.
|
||||||
|
rcAssert(lset.layers == 0);
|
||||||
|
|
||||||
|
const int lw = w - borderSize*2;
|
||||||
|
const int lh = h - borderSize*2;
|
||||||
|
|
||||||
|
// Build contracted bbox for layers.
|
||||||
|
float bmin[3], bmax[3];
|
||||||
|
rcVcopy(bmin, chf.bmin);
|
||||||
|
rcVcopy(bmax, chf.bmax);
|
||||||
|
bmin[0] += borderSize*chf.cs;
|
||||||
|
bmin[2] += borderSize*chf.cs;
|
||||||
|
bmax[0] -= borderSize*chf.cs;
|
||||||
|
bmax[2] -= borderSize*chf.cs;
|
||||||
|
|
||||||
|
lset.nlayers = (int)layerId;
|
||||||
|
|
||||||
|
lset.layers = (rcHeightfieldLayer*)rcAlloc(sizeof(rcHeightfieldLayer)*lset.nlayers, RC_ALLOC_PERM);
|
||||||
|
if (!lset.layers)
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'layers' (%d).", lset.nlayers);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memset(lset.layers, 0, sizeof(rcHeightfieldLayer)*lset.nlayers);
|
||||||
|
|
||||||
|
|
||||||
|
// Store layers.
|
||||||
|
for (int i = 0; i < lset.nlayers; ++i)
|
||||||
|
{
|
||||||
|
unsigned char curId = (unsigned char)i;
|
||||||
|
|
||||||
|
rcHeightfieldLayer* layer = &lset.layers[i];
|
||||||
|
|
||||||
|
const int gridSize = sizeof(unsigned char)*lw*lh;
|
||||||
|
|
||||||
|
layer->heights = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM);
|
||||||
|
if (!layer->heights)
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'heights' (%d).", gridSize);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memset(layer->heights, 0xff, gridSize);
|
||||||
|
|
||||||
|
layer->areas = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM);
|
||||||
|
if (!layer->areas)
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'areas' (%d).", gridSize);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memset(layer->areas, 0, gridSize);
|
||||||
|
|
||||||
|
layer->cons = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM);
|
||||||
|
if (!layer->cons)
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'cons' (%d).", gridSize);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memset(layer->cons, 0, gridSize);
|
||||||
|
|
||||||
|
// Find layer height bounds.
|
||||||
|
int hmin = 0, hmax = 0;
|
||||||
|
for (int j = 0; j < nregs; ++j)
|
||||||
|
{
|
||||||
|
if (regs[j].base && regs[j].layerId == curId)
|
||||||
|
{
|
||||||
|
hmin = (int)regs[j].ymin;
|
||||||
|
hmax = (int)regs[j].ymax;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layer->width = lw;
|
||||||
|
layer->height = lh;
|
||||||
|
layer->cs = chf.cs;
|
||||||
|
layer->ch = chf.ch;
|
||||||
|
|
||||||
|
// Adjust the bbox to fit the heightfield.
|
||||||
|
rcVcopy(layer->bmin, bmin);
|
||||||
|
rcVcopy(layer->bmax, bmax);
|
||||||
|
layer->bmin[1] = bmin[1] + hmin*chf.ch;
|
||||||
|
layer->bmax[1] = bmin[1] + hmax*chf.ch;
|
||||||
|
layer->hmin = hmin;
|
||||||
|
layer->hmax = hmax;
|
||||||
|
|
||||||
|
// Update usable data region.
|
||||||
|
layer->minx = layer->width;
|
||||||
|
layer->maxx = 0;
|
||||||
|
layer->miny = layer->height;
|
||||||
|
layer->maxy = 0;
|
||||||
|
|
||||||
|
// Copy height and area from compact heightfield.
|
||||||
|
for (int y = 0; y < lh; ++y)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < lw; ++x)
|
||||||
|
{
|
||||||
|
const int cx = borderSize+x;
|
||||||
|
const int cy = borderSize+y;
|
||||||
|
const rcCompactCell& c = chf.cells[cx+cy*w];
|
||||||
|
for (int j = (int)c.index, nj = (int)(c.index+c.count); j < nj; ++j)
|
||||||
|
{
|
||||||
|
const rcCompactSpan& s = chf.spans[j];
|
||||||
|
// Skip unassigned regions.
|
||||||
|
if (srcReg[j] == 0xff)
|
||||||
|
continue;
|
||||||
|
// Skip of does nto belong to current layer.
|
||||||
|
unsigned char lid = regs[srcReg[j]].layerId;
|
||||||
|
if (lid != curId)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Update data bounds.
|
||||||
|
layer->minx = rcMin(layer->minx, x);
|
||||||
|
layer->maxx = rcMax(layer->maxx, x);
|
||||||
|
layer->miny = rcMin(layer->miny, y);
|
||||||
|
layer->maxy = rcMax(layer->maxy, y);
|
||||||
|
|
||||||
|
// Store height and area type.
|
||||||
|
const int idx = x+y*lw;
|
||||||
|
layer->heights[idx] = (unsigned char)(s.y - hmin);
|
||||||
|
layer->areas[idx] = chf.areas[j];
|
||||||
|
|
||||||
|
// Check connection.
|
||||||
|
unsigned char portal = 0;
|
||||||
|
unsigned char con = 0;
|
||||||
|
for (int dir = 0; dir < 4; ++dir)
|
||||||
|
{
|
||||||
|
if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
|
||||||
|
{
|
||||||
|
const int ax = cx + rcGetDirOffsetX(dir);
|
||||||
|
const int ay = cy + rcGetDirOffsetY(dir);
|
||||||
|
const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);
|
||||||
|
unsigned char alid = srcReg[ai] != 0xff ? regs[srcReg[ai]].layerId : 0xff;
|
||||||
|
// Portal mask
|
||||||
|
if (chf.areas[ai] != RC_NULL_AREA && lid != alid)
|
||||||
|
{
|
||||||
|
portal |= (unsigned char)(1<<dir);
|
||||||
|
// Update height so that it matches on both sides of the portal.
|
||||||
|
const rcCompactSpan& as = chf.spans[ai];
|
||||||
|
if (as.y > hmin)
|
||||||
|
layer->heights[idx] = rcMax(layer->heights[idx], (unsigned char)(as.y - hmin));
|
||||||
|
}
|
||||||
|
// Valid connection mask
|
||||||
|
if (chf.areas[ai] != RC_NULL_AREA && lid == alid)
|
||||||
|
{
|
||||||
|
const int nx = ax - borderSize;
|
||||||
|
const int ny = ay - borderSize;
|
||||||
|
if (nx >= 0 && ny >= 0 && nx < lw && ny < lh)
|
||||||
|
con |= (unsigned char)(1<<dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layer->cons[idx] = (portal << 4) | con;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layer->minx > layer->maxx)
|
||||||
|
layer->minx = layer->maxx = 0;
|
||||||
|
if (layer->miny > layer->maxy)
|
||||||
|
layer->miny = layer->maxy = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,454 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied
|
||||||
|
// warranty. In no event will the authors be held liable for any damages
|
||||||
|
// arising from the use of this software.
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it
|
||||||
|
// freely, subject to the following restrictions:
|
||||||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
// claim that you wrote the original software. If you use this software
|
||||||
|
// in a product, an acknowledgment in the product documentation would be
|
||||||
|
// appreciated but is not required.
|
||||||
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
// misrepresented as being the original software.
|
||||||
|
// 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
//
|
||||||
|
|
||||||
|
#define _USE_MATH_DEFINES
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "Recast.h"
|
||||||
|
#include "RecastAlloc.h"
|
||||||
|
#include "RecastAssert.h"
|
||||||
|
|
||||||
|
inline bool overlapBounds(const float* amin, const float* amax, const float* bmin, const float* bmax)
|
||||||
|
{
|
||||||
|
bool overlap = true;
|
||||||
|
overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
|
||||||
|
overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
|
||||||
|
overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap;
|
||||||
|
return overlap;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool overlapInterval(unsigned short amin, unsigned short amax,
|
||||||
|
unsigned short bmin, unsigned short bmax)
|
||||||
|
{
|
||||||
|
if (amax < bmin) return false;
|
||||||
|
if (amin > bmax) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static rcSpan* allocSpan(rcHeightfield& hf)
|
||||||
|
{
|
||||||
|
// If running out of memory, allocate new page and update the freelist.
|
||||||
|
if (!hf.freelist || !hf.freelist->next)
|
||||||
|
{
|
||||||
|
// Create new page.
|
||||||
|
// Allocate memory for the new pool.
|
||||||
|
rcSpanPool* pool = (rcSpanPool*)rcAlloc(sizeof(rcSpanPool), RC_ALLOC_PERM);
|
||||||
|
if (!pool) return 0;
|
||||||
|
|
||||||
|
// Add the pool into the list of pools.
|
||||||
|
pool->next = hf.pools;
|
||||||
|
hf.pools = pool;
|
||||||
|
// Add new items to the free list.
|
||||||
|
rcSpan* freelist = hf.freelist;
|
||||||
|
rcSpan* head = &pool->items[0];
|
||||||
|
rcSpan* it = &pool->items[RC_SPANS_PER_POOL];
|
||||||
|
do
|
||||||
|
{
|
||||||
|
--it;
|
||||||
|
it->next = freelist;
|
||||||
|
freelist = it;
|
||||||
|
}
|
||||||
|
while (it != head);
|
||||||
|
hf.freelist = it;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop item from in front of the free list.
|
||||||
|
rcSpan* it = hf.freelist;
|
||||||
|
hf.freelist = hf.freelist->next;
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void freeSpan(rcHeightfield& hf, rcSpan* ptr)
|
||||||
|
{
|
||||||
|
if (!ptr) return;
|
||||||
|
// Add the node in front of the free list.
|
||||||
|
ptr->next = hf.freelist;
|
||||||
|
hf.freelist = ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool addSpan(rcHeightfield& hf, const int x, const int y,
|
||||||
|
const unsigned short smin, const unsigned short smax,
|
||||||
|
const unsigned char area, const int flagMergeThr)
|
||||||
|
{
|
||||||
|
|
||||||
|
int idx = x + y*hf.width;
|
||||||
|
|
||||||
|
rcSpan* s = allocSpan(hf);
|
||||||
|
if (!s)
|
||||||
|
return false;
|
||||||
|
s->smin = smin;
|
||||||
|
s->smax = smax;
|
||||||
|
s->area = area;
|
||||||
|
s->next = 0;
|
||||||
|
|
||||||
|
// Empty cell, add the first span.
|
||||||
|
if (!hf.spans[idx])
|
||||||
|
{
|
||||||
|
hf.spans[idx] = s;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
rcSpan* prev = 0;
|
||||||
|
rcSpan* cur = hf.spans[idx];
|
||||||
|
|
||||||
|
// Insert and merge spans.
|
||||||
|
while (cur)
|
||||||
|
{
|
||||||
|
if (cur->smin > s->smax)
|
||||||
|
{
|
||||||
|
// Current span is further than the new span, break.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (cur->smax < s->smin)
|
||||||
|
{
|
||||||
|
// Current span is before the new span advance.
|
||||||
|
prev = cur;
|
||||||
|
cur = cur->next;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Merge spans.
|
||||||
|
if (cur->smin < s->smin)
|
||||||
|
s->smin = cur->smin;
|
||||||
|
if (cur->smax > s->smax)
|
||||||
|
s->smax = cur->smax;
|
||||||
|
|
||||||
|
// Merge flags.
|
||||||
|
if (rcAbs((int)s->smax - (int)cur->smax) <= flagMergeThr)
|
||||||
|
s->area = rcMax(s->area, cur->area);
|
||||||
|
|
||||||
|
// Remove current span.
|
||||||
|
rcSpan* next = cur->next;
|
||||||
|
freeSpan(hf, cur);
|
||||||
|
if (prev)
|
||||||
|
prev->next = next;
|
||||||
|
else
|
||||||
|
hf.spans[idx] = next;
|
||||||
|
cur = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert new span.
|
||||||
|
if (prev)
|
||||||
|
{
|
||||||
|
s->next = prev->next;
|
||||||
|
prev->next = s;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s->next = hf.spans[idx];
|
||||||
|
hf.spans[idx] = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// The span addition can be set to favor flags. If the span is merged to
|
||||||
|
/// another span and the new @p smax is within @p flagMergeThr units
|
||||||
|
/// from the existing span, the span flags are merged.
|
||||||
|
///
|
||||||
|
/// @see rcHeightfield, rcSpan.
|
||||||
|
bool rcAddSpan(rcContext* ctx, rcHeightfield& hf, const int x, const int y,
|
||||||
|
const unsigned short smin, const unsigned short smax,
|
||||||
|
const unsigned char area, const int flagMergeThr)
|
||||||
|
{
|
||||||
|
rcAssert(ctx);
|
||||||
|
|
||||||
|
if (!addSpan(hf, x, y, smin, smax, area, flagMergeThr))
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcAddSpan: Out of memory.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// divides a convex polygons into two convex polygons on both sides of a line
|
||||||
|
static void dividePoly(const float* in, int nin,
|
||||||
|
float* out1, int* nout1,
|
||||||
|
float* out2, int* nout2,
|
||||||
|
float x, int axis)
|
||||||
|
{
|
||||||
|
float d[12];
|
||||||
|
for (int i = 0; i < nin; ++i)
|
||||||
|
d[i] = x - in[i*3+axis];
|
||||||
|
|
||||||
|
int m = 0, n = 0;
|
||||||
|
for (int i = 0, j = nin-1; i < nin; j=i, ++i)
|
||||||
|
{
|
||||||
|
bool ina = d[j] >= 0;
|
||||||
|
bool inb = d[i] >= 0;
|
||||||
|
if (ina != inb)
|
||||||
|
{
|
||||||
|
float s = d[j] / (d[j] - d[i]);
|
||||||
|
out1[m*3+0] = in[j*3+0] + (in[i*3+0] - in[j*3+0])*s;
|
||||||
|
out1[m*3+1] = in[j*3+1] + (in[i*3+1] - in[j*3+1])*s;
|
||||||
|
out1[m*3+2] = in[j*3+2] + (in[i*3+2] - in[j*3+2])*s;
|
||||||
|
rcVcopy(out2 + n*3, out1 + m*3);
|
||||||
|
m++;
|
||||||
|
n++;
|
||||||
|
// add the i'th point to the right polygon. Do NOT add points that are on the dividing line
|
||||||
|
// since these were already added above
|
||||||
|
if (d[i] > 0)
|
||||||
|
{
|
||||||
|
rcVcopy(out1 + m*3, in + i*3);
|
||||||
|
m++;
|
||||||
|
}
|
||||||
|
else if (d[i] < 0)
|
||||||
|
{
|
||||||
|
rcVcopy(out2 + n*3, in + i*3);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // same side
|
||||||
|
{
|
||||||
|
// add the i'th point to the right polygon. Addition is done even for points on the dividing line
|
||||||
|
if (d[i] >= 0)
|
||||||
|
{
|
||||||
|
rcVcopy(out1 + m*3, in + i*3);
|
||||||
|
m++;
|
||||||
|
if (d[i] != 0)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rcVcopy(out2 + n*3, in + i*3);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*nout1 = m;
|
||||||
|
*nout2 = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static bool rasterizeTri(const float* v0, const float* v1, const float* v2,
|
||||||
|
const unsigned char area, rcHeightfield& hf,
|
||||||
|
const float* bmin, const float* bmax,
|
||||||
|
const float cs, const float ics, const float ich,
|
||||||
|
const int flagMergeThr)
|
||||||
|
{
|
||||||
|
const int w = hf.width;
|
||||||
|
const int h = hf.height;
|
||||||
|
float tmin[3], tmax[3];
|
||||||
|
const float by = bmax[1] - bmin[1];
|
||||||
|
|
||||||
|
// Calculate the bounding box of the triangle.
|
||||||
|
rcVcopy(tmin, v0);
|
||||||
|
rcVcopy(tmax, v0);
|
||||||
|
rcVmin(tmin, v1);
|
||||||
|
rcVmin(tmin, v2);
|
||||||
|
rcVmax(tmax, v1);
|
||||||
|
rcVmax(tmax, v2);
|
||||||
|
|
||||||
|
// If the triangle does not touch the bbox of the heightfield, skip the triagle.
|
||||||
|
if (!overlapBounds(bmin, bmax, tmin, tmax))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Calculate the footprint of the triangle on the grid's y-axis
|
||||||
|
int y0 = (int)((tmin[2] - bmin[2])*ics);
|
||||||
|
int y1 = (int)((tmax[2] - bmin[2])*ics);
|
||||||
|
y0 = rcClamp(y0, 0, h-1);
|
||||||
|
y1 = rcClamp(y1, 0, h-1);
|
||||||
|
|
||||||
|
// Clip the triangle into all grid cells it touches.
|
||||||
|
float buf[7*3*4];
|
||||||
|
float *in = buf, *inrow = buf+7*3, *p1 = inrow+7*3, *p2 = p1+7*3;
|
||||||
|
|
||||||
|
rcVcopy(&in[0], v0);
|
||||||
|
rcVcopy(&in[1*3], v1);
|
||||||
|
rcVcopy(&in[2*3], v2);
|
||||||
|
int nvrow, nvIn = 3;
|
||||||
|
|
||||||
|
for (int y = y0; y <= y1; ++y)
|
||||||
|
{
|
||||||
|
// Clip polygon to row. Store the remaining polygon as well
|
||||||
|
const float cz = bmin[2] + y*cs;
|
||||||
|
dividePoly(in, nvIn, inrow, &nvrow, p1, &nvIn, cz+cs, 2);
|
||||||
|
rcSwap(in, p1);
|
||||||
|
if (nvrow < 3) continue;
|
||||||
|
|
||||||
|
// find the horizontal bounds in the row
|
||||||
|
float minX = inrow[0], maxX = inrow[0];
|
||||||
|
for (int i=1; i<nvrow; ++i)
|
||||||
|
{
|
||||||
|
if (minX > inrow[i*3]) minX = inrow[i*3];
|
||||||
|
if (maxX < inrow[i*3]) maxX = inrow[i*3];
|
||||||
|
}
|
||||||
|
int x0 = (int)((minX - bmin[0])*ics);
|
||||||
|
int x1 = (int)((maxX - bmin[0])*ics);
|
||||||
|
x0 = rcClamp(x0, 0, w-1);
|
||||||
|
x1 = rcClamp(x1, 0, w-1);
|
||||||
|
|
||||||
|
int nv, nv2 = nvrow;
|
||||||
|
|
||||||
|
for (int x = x0; x <= x1; ++x)
|
||||||
|
{
|
||||||
|
// Clip polygon to column. store the remaining polygon as well
|
||||||
|
const float cx = bmin[0] + x*cs;
|
||||||
|
dividePoly(inrow, nv2, p1, &nv, p2, &nv2, cx+cs, 0);
|
||||||
|
rcSwap(inrow, p2);
|
||||||
|
if (nv < 3) continue;
|
||||||
|
|
||||||
|
// Calculate min and max of the span.
|
||||||
|
float smin = p1[1], smax = p1[1];
|
||||||
|
for (int i = 1; i < nv; ++i)
|
||||||
|
{
|
||||||
|
smin = rcMin(smin, p1[i*3+1]);
|
||||||
|
smax = rcMax(smax, p1[i*3+1]);
|
||||||
|
}
|
||||||
|
smin -= bmin[1];
|
||||||
|
smax -= bmin[1];
|
||||||
|
// Skip the span if it is outside the heightfield bbox
|
||||||
|
if (smax < 0.0f) continue;
|
||||||
|
if (smin > by) continue;
|
||||||
|
// Clamp the span to the heightfield bbox.
|
||||||
|
if (smin < 0.0f) smin = 0;
|
||||||
|
if (smax > by) smax = by;
|
||||||
|
|
||||||
|
// Snap the span to the heightfield height grid.
|
||||||
|
unsigned short ismin = (unsigned short)rcClamp((int)floorf(smin * ich), 0, RC_SPAN_MAX_HEIGHT);
|
||||||
|
unsigned short ismax = (unsigned short)rcClamp((int)ceilf(smax * ich), (int)ismin+1, RC_SPAN_MAX_HEIGHT);
|
||||||
|
|
||||||
|
if (!addSpan(hf, x, y, ismin, ismax, area, flagMergeThr))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// No spans will be added if the triangle does not overlap the heightfield grid.
|
||||||
|
///
|
||||||
|
/// @see rcHeightfield
|
||||||
|
bool rcRasterizeTriangle(rcContext* ctx, const float* v0, const float* v1, const float* v2,
|
||||||
|
const unsigned char area, rcHeightfield& solid,
|
||||||
|
const int flagMergeThr)
|
||||||
|
{
|
||||||
|
rcAssert(ctx);
|
||||||
|
|
||||||
|
rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES);
|
||||||
|
|
||||||
|
const float ics = 1.0f/solid.cs;
|
||||||
|
const float ich = 1.0f/solid.ch;
|
||||||
|
if (!rasterizeTri(v0, v1, v2, area, solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr))
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcRasterizeTriangle: Out of memory.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// Spans will only be added for triangles that overlap the heightfield grid.
|
||||||
|
///
|
||||||
|
/// @see rcHeightfield
|
||||||
|
bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/,
|
||||||
|
const int* tris, const unsigned char* areas, const int nt,
|
||||||
|
rcHeightfield& solid, const int flagMergeThr)
|
||||||
|
{
|
||||||
|
rcAssert(ctx);
|
||||||
|
|
||||||
|
rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES);
|
||||||
|
|
||||||
|
const float ics = 1.0f/solid.cs;
|
||||||
|
const float ich = 1.0f/solid.ch;
|
||||||
|
// Rasterize triangles.
|
||||||
|
for (int i = 0; i < nt; ++i)
|
||||||
|
{
|
||||||
|
const float* v0 = &verts[tris[i*3+0]*3];
|
||||||
|
const float* v1 = &verts[tris[i*3+1]*3];
|
||||||
|
const float* v2 = &verts[tris[i*3+2]*3];
|
||||||
|
// Rasterize.
|
||||||
|
if (!rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr))
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// Spans will only be added for triangles that overlap the heightfield grid.
|
||||||
|
///
|
||||||
|
/// @see rcHeightfield
|
||||||
|
bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/,
|
||||||
|
const unsigned short* tris, const unsigned char* areas, const int nt,
|
||||||
|
rcHeightfield& solid, const int flagMergeThr)
|
||||||
|
{
|
||||||
|
rcAssert(ctx);
|
||||||
|
|
||||||
|
rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES);
|
||||||
|
|
||||||
|
const float ics = 1.0f/solid.cs;
|
||||||
|
const float ich = 1.0f/solid.ch;
|
||||||
|
// Rasterize triangles.
|
||||||
|
for (int i = 0; i < nt; ++i)
|
||||||
|
{
|
||||||
|
const float* v0 = &verts[tris[i*3+0]*3];
|
||||||
|
const float* v1 = &verts[tris[i*3+1]*3];
|
||||||
|
const float* v2 = &verts[tris[i*3+2]*3];
|
||||||
|
// Rasterize.
|
||||||
|
if (!rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr))
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @par
|
||||||
|
///
|
||||||
|
/// Spans will only be added for triangles that overlap the heightfield grid.
|
||||||
|
///
|
||||||
|
/// @see rcHeightfield
|
||||||
|
bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const unsigned char* areas, const int nt,
|
||||||
|
rcHeightfield& solid, const int flagMergeThr)
|
||||||
|
{
|
||||||
|
rcAssert(ctx);
|
||||||
|
|
||||||
|
rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES);
|
||||||
|
|
||||||
|
const float ics = 1.0f/solid.cs;
|
||||||
|
const float ich = 1.0f/solid.ch;
|
||||||
|
// Rasterize triangles.
|
||||||
|
for (int i = 0; i < nt; ++i)
|
||||||
|
{
|
||||||
|
const float* v0 = &verts[(i*3+0)*3];
|
||||||
|
const float* v1 = &verts[(i*3+1)*3];
|
||||||
|
const float* v2 = &verts[(i*3+2)*3];
|
||||||
|
// Rasterize.
|
||||||
|
if (!rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr))
|
||||||
|
{
|
||||||
|
ctx->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue