diff options
Diffstat (limited to 'modules')
79 files changed, 6629 insertions, 321 deletions
diff --git a/modules/bmp/SCsub b/modules/bmp/SCsub new file mode 100644 index 0000000000..e7da7cf108 --- /dev/null +++ b/modules/bmp/SCsub @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_bmp = env_modules.Clone() + +# Godot's own source files +env_bmp.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/bmp/config.py b/modules/bmp/config.py new file mode 100644 index 0000000000..fb920482f5 --- /dev/null +++ b/modules/bmp/config.py @@ -0,0 +1,7 @@ + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp new file mode 100644 index 0000000000..769119a0dc --- /dev/null +++ b/modules/bmp/image_loader_bmp.cpp @@ -0,0 +1,194 @@ +/*************************************************************************/ +/* image_loader_bmp.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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 "image_loader_bmp.h" + +Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, + const uint8_t *p_buffer, + const uint8_t *p_color_buffer, + const bmp_header_s &p_header) { + + Error err = OK; + + if (p_buffer == NULL) + err = FAILED; + + if (err == OK) { + size_t index = 0; + size_t width = + static_cast<size_t>(p_header.bmp_info_header.bmp_width < 0 ? -p_header.bmp_info_header.bmp_width : p_header.bmp_info_header.bmp_width); + size_t height = + static_cast<size_t>(p_header.bmp_info_header.bmp_height < 0 ? -p_header.bmp_info_header.bmp_height : p_header.bmp_info_header.bmp_height); + size_t bits_per_pixel = + static_cast<size_t>(p_header.bmp_info_header.bmp_bit_count); + + if (p_header.bmp_info_header.bmp_compression != 0) { + err = FAILED; + } + + if (bits_per_pixel != 24 || bits_per_pixel != 32) { + err = FAILED; + } + + if (err == OK) { + + uint32_t line_width = ((p_header.bmp_info_header.bmp_width * + p_header.bmp_info_header.bmp_bit_count / 8) + + 3) & + ~3; + + PoolVector<uint8_t> image_data; + err = image_data.resize(width * height * 4); + + PoolVector<uint8_t>::Write image_data_w = image_data.write(); + uint8_t *write_buffer = image_data_w.ptr(); + + const uint8_t *line = p_buffer + (line_width * (height - 1)); + for (unsigned int i = 0; i < height; i++) { + const uint8_t *line_ptr = line; + for (unsigned int j = 0; j < width; j++) { + switch (bits_per_pixel) { + case 24: { + uint32_t color = *((uint32_t *)line_ptr); + + write_buffer[index + 2] = color & 0xff; + write_buffer[index + 1] = (color >> 8) & 0xff; + write_buffer[index + 0] = (color >> 16) & 0xff; + write_buffer[index + 3] = 0xff; + index += 4; + line_ptr += 3; + } break; + case 32: { + uint32_t color = *((uint32_t *)line_ptr); + + write_buffer[index + 2] = color & 0xff; + write_buffer[index + 1] = (color >> 8) & 0xff; + write_buffer[index + 0] = (color >> 16) & 0xff; + write_buffer[index + 3] = color >> 24; + index += 4; + line_ptr += 4; + } break; + } + } + line -= line_width; + } + p_image->create(width, height, 0, Image::FORMAT_RGBA8, image_data); + } + } + return err; +} + +Error ImageLoaderBMP::load_image(Ref<Image> p_image, FileAccess *f, + bool p_force_linear, float p_scale) { + + bmp_header_s bmp_header; + Error err = ERR_INVALID_DATA; + + if (f->get_len() > sizeof(bmp_header)) { + // File Header + bmp_header.bmp_file_header.bmp_signature = f->get_16(); + if (bmp_header.bmp_file_header.bmp_signature == BITMAP_SIGNATURE) { + bmp_header.bmp_file_header.bmp_file_size = f->get_32(); + bmp_header.bmp_file_header.bmp_file_padding = f->get_32(); + bmp_header.bmp_file_header.bmp_file_offset = f->get_32(); + + // Info Header + bmp_header.bmp_info_header.bmp_header_size = f->get_32(); + bmp_header.bmp_info_header.bmp_width = f->get_32(); + bmp_header.bmp_info_header.bmp_height = f->get_32(); + bmp_header.bmp_info_header.bmp_planes = f->get_16(); + bmp_header.bmp_info_header.bmp_bit_count = f->get_16(); + bmp_header.bmp_info_header.bmp_compression = f->get_32(); + bmp_header.bmp_info_header.bmp_size_image = f->get_32(); + bmp_header.bmp_info_header.bmp_pixels_per_meter_x = f->get_32(); + bmp_header.bmp_info_header.bmp_pixels_per_meter_y = f->get_32(); + bmp_header.bmp_info_header.bmp_colors_used = f->get_32(); + bmp_header.bmp_info_header.bmp_important_colors = f->get_32(); + + bmp_header.bmp_info_header.bmp_red_mask = f->get_32(); + bmp_header.bmp_info_header.bmp_green_mask = f->get_32(); + bmp_header.bmp_info_header.bmp_blue_mask = f->get_32(); + bmp_header.bmp_info_header.bmp_alpha_mask = f->get_32(); + bmp_header.bmp_info_header.bmp_cs_type = f->get_32(); + for (int i = 0; i < 9; i++) + bmp_header.bmp_info_header.bmp_endpoints[i] = f->get_32(); + + bmp_header.bmp_info_header.bmp_gamma_red = f->get_32(); + bmp_header.bmp_info_header.bmp_gamma_green = f->get_32(); + bmp_header.bmp_info_header.bmp_gamma_blue = f->get_32(); + + f->seek(sizeof(bmp_header.bmp_file_header) + + bmp_header.bmp_info_header.bmp_header_size); + + uint32_t color_table_size = 0; + if (bmp_header.bmp_info_header.bmp_bit_count == 1) + color_table_size = 2; + else if (bmp_header.bmp_info_header.bmp_bit_count == 4) + color_table_size = 16; + else if (bmp_header.bmp_info_header.bmp_bit_count == 8) + color_table_size = 256; + + PoolVector<uint8_t> bmp_color_table; + if (color_table_size > 0) { + err = bmp_color_table.resize(color_table_size * 4); + PoolVector<uint8_t>::Write bmp_color_table_w = bmp_color_table.write(); + f->get_buffer(bmp_color_table_w.ptr(), + bmp_header.bmp_info_header.bmp_colors_used * 4); + } + + f->seek(bmp_header.bmp_file_header.bmp_file_offset); + + uint32_t bmp_buffer_size = (bmp_header.bmp_file_header.bmp_file_size - + bmp_header.bmp_file_header.bmp_file_offset); + + PoolVector<uint8_t> bmp_buffer; + err = bmp_buffer.resize(bmp_buffer_size); + if (err == OK) { + PoolVector<uint8_t>::Write bmp_buffer_w = bmp_buffer.write(); + f->get_buffer(bmp_buffer_w.ptr(), bmp_buffer_size); + + PoolVector<uint8_t>::Read bmp_buffer_r = bmp_buffer.read(); + PoolVector<uint8_t>::Read bmp_color_table_r = bmp_color_table.read(); + err = convert_to_image(p_image, bmp_buffer_r.ptr(), + bmp_color_table_r.ptr(), bmp_header); + } + f->close(); + } + } + return err; +} + +void ImageLoaderBMP::get_recognized_extensions( + List<String> *p_extensions) const { + + p_extensions->push_back("bmp"); +} + +ImageLoaderBMP::ImageLoaderBMP() {} diff --git a/modules/bmp/image_loader_bmp.h b/modules/bmp/image_loader_bmp.h new file mode 100644 index 0000000000..3fa7481287 --- /dev/null +++ b/modules/bmp/image_loader_bmp.h @@ -0,0 +1,84 @@ +/*************************************************************************/ +/* image_loader_bmp.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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 IMAGE_LOADER_BMP_H +#define IMAGE_LOADER_BMP_H + +#include "io/image_loader.h" + +class ImageLoaderBMP : public ImageFormatLoader { +protected: + static const unsigned BITMAP_SIGNATURE = 0x4d42; + + struct bmp_header_s { + struct bmp_file_header_s { + uint16_t bmp_signature; + uint32_t bmp_file_size; + uint32_t bmp_file_padding; + uint32_t bmp_file_offset; + } bmp_file_header; + + struct bmp_info_header_s { + uint32_t bmp_header_size; + uint32_t bmp_width; + uint32_t bmp_height; + uint16_t bmp_planes; + uint16_t bmp_bit_count; + uint32_t bmp_compression; + uint32_t bmp_size_image; + uint32_t bmp_pixels_per_meter_x; + uint32_t bmp_pixels_per_meter_y; + uint32_t bmp_colors_used; + uint32_t bmp_important_colors; + uint32_t bmp_red_mask; + uint32_t bmp_green_mask; + uint32_t bmp_blue_mask; + uint32_t bmp_alpha_mask; + uint32_t bmp_cs_type; + uint32_t bmp_endpoints[9]; + uint32_t bmp_gamma_red; + uint32_t bmp_gamma_green; + uint32_t bmp_gamma_blue; + } bmp_info_header; + }; + + static Error convert_to_image(Ref<Image> p_image, + const uint8_t *p_buffer, + const uint8_t *p_color_buffer, + const bmp_header_s &p_header); + +public: + virtual Error load_image(Ref<Image> p_image, FileAccess *f, + bool p_force_linear, float p_scale); + virtual void get_recognized_extensions(List<String> *p_extensions) const; + ImageLoaderBMP(); +}; + +#endif // IMAGE_LOADER_BMP_H diff --git a/modules/bmp/register_types.cpp b/modules/bmp/register_types.cpp new file mode 100644 index 0000000000..1f68a03e85 --- /dev/null +++ b/modules/bmp/register_types.cpp @@ -0,0 +1,44 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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" + +#include "image_loader_bmp.h" + +static ImageLoaderBMP *image_loader_bmp = NULL; + +void register_bmp_types() { + image_loader_bmp = memnew(ImageLoaderBMP); + ImageLoader::add_image_format_loader(image_loader_bmp); +} + +void unregister_bmp_types() { + memdelete(image_loader_bmp); +} diff --git a/modules/bmp/register_types.h b/modules/bmp/register_types.h new file mode 100644 index 0000000000..d8755db397 --- /dev/null +++ b/modules/bmp/register_types.h @@ -0,0 +1,32 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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_bmp_types(); +void unregister_bmp_types(); diff --git a/modules/bullet/generic_6dof_joint_bullet.cpp b/modules/bullet/generic_6dof_joint_bullet.cpp index 151a79a69f..adfad7803f 100644 --- a/modules/bullet/generic_6dof_joint_bullet.cpp +++ b/modules/bullet/generic_6dof_joint_bullet.cpp @@ -138,6 +138,12 @@ void Generic6DOFJointBullet::set_param(Vector3::Axis p_axis, PhysicsServer::G6DO case PhysicsServer::G6DOF_JOINT_LINEAR_DAMPING: sixDOFConstraint->getTranslationalLimitMotor()->m_damping = p_value; break; + case PhysicsServer::G6DOF_JOINT_LINEAR_MOTOR_TARGET_VELOCITY: + sixDOFConstraint->getTranslationalLimitMotor()->m_targetVelocity.m_floats[p_axis] = p_value; + break; + case PhysicsServer::G6DOF_JOINT_LINEAR_MOTOR_FORCE_LIMIT: + sixDOFConstraint->getTranslationalLimitMotor()->m_maxMotorForce.m_floats[p_axis] = p_value; + break; case PhysicsServer::G6DOF_JOINT_ANGULAR_LOWER_LIMIT: limits_lower[1][p_axis] = p_value; set_flag(p_axis, PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_ANGULAR_LIMIT, flags[p_axis][p_param]); // Reload bullet parameter @@ -185,6 +191,10 @@ real_t Generic6DOFJointBullet::get_param(Vector3::Axis p_axis, PhysicsServer::G6 return sixDOFConstraint->getTranslationalLimitMotor()->m_restitution; case PhysicsServer::G6DOF_JOINT_LINEAR_DAMPING: return sixDOFConstraint->getTranslationalLimitMotor()->m_damping; + case PhysicsServer::G6DOF_JOINT_LINEAR_MOTOR_TARGET_VELOCITY: + return sixDOFConstraint->getTranslationalLimitMotor()->m_targetVelocity.m_floats[p_axis]; + case PhysicsServer::G6DOF_JOINT_LINEAR_MOTOR_FORCE_LIMIT: + return sixDOFConstraint->getTranslationalLimitMotor()->m_maxMotorForce.m_floats[p_axis]; case PhysicsServer::G6DOF_JOINT_ANGULAR_LOWER_LIMIT: return limits_lower[1][p_axis]; case PhysicsServer::G6DOF_JOINT_ANGULAR_UPPER_LIMIT: @@ -232,6 +242,9 @@ void Generic6DOFJointBullet::set_flag(Vector3::Axis p_axis, PhysicsServer::G6DOF case PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_MOTOR: sixDOFConstraint->getRotationalLimitMotor(p_axis)->m_enableMotor = flags[p_axis][p_flag]; break; + case PhysicsServer::G6DOF_JOINT_FLAG_ENABLE_LINEAR_MOTOR: + sixDOFConstraint->getTranslationalLimitMotor()->m_enableMotor[p_axis] = flags[p_axis][p_flag]; + break; default: WARN_PRINT("This flag is not supported by Bullet engine"); return; diff --git a/modules/bullet/godot_result_callbacks.cpp b/modules/bullet/godot_result_callbacks.cpp index 1bf259da84..197550d686 100644 --- a/modules/bullet/godot_result_callbacks.cpp +++ b/modules/bullet/godot_result_callbacks.cpp @@ -110,7 +110,7 @@ bool GodotKinClosestConvexResultCallback::needsCollision(btBroadphaseProxy *prox if (gObj->getType() == CollisionObjectBullet::TYPE_AREA) return false; - if (m_self_object->has_collision_exception(gObj)) + if (m_self_object->has_collision_exception(gObj) || gObj->has_collision_exception(m_self_object)) return false; } return true; diff --git a/modules/csg/SCsub b/modules/csg/SCsub new file mode 100644 index 0000000000..57c504efd8 --- /dev/null +++ b/modules/csg/SCsub @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +env_csg = env_modules.Clone() + +# Godot's own source files +env_csg.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/csg/config.py b/modules/csg/config.py new file mode 100644 index 0000000000..5e1d916790 --- /dev/null +++ b/modules/csg/config.py @@ -0,0 +1,21 @@ +def can_build(platform): + return True + +def configure(env): + pass + +def get_doc_classes(): + return [ + "CSGBox", + "CSGCombiner", + "CSGCylinder", + "CSGMesh", + "CSGPolygon", + "CSGPrimitive", + "CSGShape", + "CSGSphere", + "CSGTorus", + ] + +def get_doc_path(): + return "doc_classes" diff --git a/modules/csg/csg.cpp b/modules/csg/csg.cpp new file mode 100644 index 0000000000..4e6e701bfd --- /dev/null +++ b/modules/csg/csg.cpp @@ -0,0 +1,1518 @@ +/*************************************************************************/ +/* csg.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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 "csg.h" +#include "face3.h" +#include "geometry.h" +#include "os/os.h" +#include "sort.h" +#include "thirdparty/misc/triangulator.h" + +void CSGBrush::clear() { + faces.clear(); +} + +void CSGBrush::build_from_faces(const PoolVector<Vector3> &p_vertices, const PoolVector<Vector2> &p_uvs, const PoolVector<bool> &p_smooth, const PoolVector<Ref<Material> > &p_materials, const PoolVector<bool> &p_invert_faces) { + + clear(); + + int vc = p_vertices.size(); + + ERR_FAIL_COND((vc % 3) != 0) + + PoolVector<Vector3>::Read rv = p_vertices.read(); + int uvc = p_uvs.size(); + PoolVector<Vector2>::Read ruv = p_uvs.read(); + int sc = p_smooth.size(); + PoolVector<bool>::Read rs = p_smooth.read(); + int mc = p_materials.size(); + PoolVector<Ref<Material> >::Read rm = p_materials.read(); + int ic = p_invert_faces.size(); + PoolVector<bool>::Read ri = p_invert_faces.read(); + + Map<Ref<Material>, int> material_map; + + faces.resize(p_vertices.size() / 3); + + for (int i = 0; i < faces.size(); i++) { + Face &f = faces[i]; + f.vertices[0] = rv[i * 3 + 0]; + f.vertices[1] = rv[i * 3 + 1]; + f.vertices[2] = rv[i * 3 + 2]; + if (uvc == vc) { + f.uvs[0] = ruv[i * 3 + 0]; + f.uvs[1] = ruv[i * 3 + 1]; + f.uvs[2] = ruv[i * 3 + 2]; + } + if (sc == vc / 3) { + f.smooth = rs[i]; + } else { + f.smooth = false; + } + + if (ic == vc / 3) { + f.invert = ri[i]; + } else { + f.invert = false; + } + + if (mc == vc / 3) { + Ref<Material> mat = rm[i]; + if (mat.is_valid()) { + const Map<Ref<Material>, int>::Element *E = material_map.find(mat); + if (E) { + f.material = E->get(); + } else { + f.material = material_map.size(); + material_map[mat] = f.material; + } + } else { + f.material = -1; + } + } + } + + materials.resize(material_map.size()); + for (Map<Ref<Material>, int>::Element *E = material_map.front(); E; E = E->next()) { + materials[E->get()] = E->key(); + } + + _regen_face_aabbs(); +} + +void CSGBrush::_regen_face_aabbs() { + + for (int i = 0; i < faces.size(); i++) { + + faces[i].aabb.position = faces[i].vertices[0]; + faces[i].aabb.expand_to(faces[i].vertices[1]); + faces[i].aabb.expand_to(faces[i].vertices[2]); + faces[i].aabb.grow_by(faces[i].aabb.get_longest_axis_size() * 0.001); //make it a tad bigger to avoid num precision erros + } +} + +void CSGBrush::copy_from(const CSGBrush &p_brush, const Transform &p_xform) { + + faces = p_brush.faces; + materials = p_brush.materials; + + for (int i = 0; i < faces.size(); i++) { + for (int j = 0; j < 3; j++) { + faces[i].vertices[j] = p_xform.xform(p_brush.faces[i].vertices[j]); + } + } + + _regen_face_aabbs(); +} + +//////////////////////// + +void CSGBrushOperation::BuildPoly::create(const CSGBrush *p_brush, int p_face, MeshMerge &mesh_merge, bool p_for_B) { + + //creates the initial face that will be used for clipping against the other faces + + Vector3 va[3] = { + p_brush->faces[p_face].vertices[0], + p_brush->faces[p_face].vertices[1], + p_brush->faces[p_face].vertices[2], + }; + + plane = Plane(va[0], va[1], va[2]); + + to_world.origin = va[0]; + + to_world.basis.set_axis(2, plane.normal); + to_world.basis.set_axis(0, (va[1] - va[2]).normalized()); + to_world.basis.set_axis(1, to_world.basis.get_axis(0).cross(to_world.basis.get_axis(2)).normalized()); + + to_poly = to_world.affine_inverse(); + + face_index = p_face; + + for (int i = 0; i < 3; i++) { + + Point p; + Vector3 localp = to_poly.xform(va[i]); + p.point.x = localp.x; + p.point.y = localp.y; + p.uv = p_brush->faces[p_face].uvs[i]; + + points.push_back(p); + + ///edge + + Edge e; + e.points[0] = i; + e.points[1] = (i + 1) % 3; + e.outer = true; + edges.push_back(e); + } + + smooth = p_brush->faces[p_face].smooth; + invert = p_brush->faces[p_face].invert; + + if (p_brush->faces[p_face].material != -1) { + material = p_brush->materials[p_brush->faces[p_face].material]; + } + + base_edges = 3; +} + +static Vector2 interpolate_uv(const Vector2 &p_vertex_a, const Vector2 &p_vertex_b, const Vector2 &p_vertex_c, const Vector2 &p_uv_a, const Vector2 &p_uv_c) { + + float len_a_c = (p_vertex_c - p_vertex_a).length(); + if (len_a_c < CMP_EPSILON) { + return p_uv_a; + } + + float len_a_b = (p_vertex_b - p_vertex_a).length(); + + float c = len_a_b / len_a_c; + + return p_uv_a.linear_interpolate(p_uv_c, c); +} + +static Vector2 interpolate_triangle_uv(const Vector2 &p_pos, const Vector2 *p_vtx, const Vector2 *p_uv) { + + if (p_pos.distance_squared_to(p_vtx[0]) < CMP_EPSILON2) { + return p_uv[0]; + } + if (p_pos.distance_squared_to(p_vtx[1]) < CMP_EPSILON2) { + return p_uv[1]; + } + if (p_pos.distance_squared_to(p_vtx[2]) < CMP_EPSILON2) { + return p_uv[2]; + } + + Vector2 v0 = p_vtx[1] - p_vtx[0]; + Vector2 v1 = p_vtx[2] - p_vtx[0]; + Vector2 v2 = p_pos - p_vtx[0]; + + float d00 = v0.dot(v0); + float d01 = v0.dot(v1); + float d11 = v1.dot(v1); + float d20 = v2.dot(v0); + float d21 = v2.dot(v1); + float denom = (d00 * d11 - d01 * d01); + if (denom == 0) { + return p_uv[0]; + } + float v = (d11 * d20 - d01 * d21) / denom; + float w = (d00 * d21 - d01 * d20) / denom; + float u = 1.0f - v - w; + + return p_uv[0] * u + p_uv[1] * v + p_uv[2] * w; +} + +void CSGBrushOperation::BuildPoly::_clip_segment(const CSGBrush *p_brush, int p_face, const Vector2 *segment, MeshMerge &mesh_merge, bool p_for_B) { + + //keep track of what was inserted + Vector<int> inserted_points; + + //keep track of point indices for what was inserted, allowing reuse of points. + int segment_idx[2] = { -1, -1 }; + + //check if edge and poly share a vertex, of so, assign it to segment_idx + for (int i = 0; i < points.size(); i++) { + for (int j = 0; j < 2; j++) { + if (segment[j].distance_to(points[i].point) < CMP_EPSILON) { + segment_idx[j] = i; + inserted_points.push_back(i); + break; + } + } + } + + //check if both segment points are shared with other vertices + if (segment_idx[0] != -1 && segment_idx[1] != -1) { + + if (segment_idx[0] == segment_idx[1]) { + return; //segment was too tiny, both mapped to same point + } + + bool found = false; + + //check if the segment already exists + for (int i = 0; i < edges.size(); i++) { + + if ( + (edges[i].points[0] == segment_idx[0] && edges[i].points[1] == segment_idx[1]) || + (edges[i].points[0] == segment_idx[1] && edges[i].points[1] == segment_idx[0])) { + found = true; + break; + } + } + + if (found) { + //it does already exist, do nothing + return; + } + + //directly add the new segment + Edge new_edge; + new_edge.points[0] = segment_idx[0]; + new_edge.points[1] = segment_idx[1]; + edges.push_back(new_edge); + return; + } + + //check edge by edge against the segment points to see if intersects + + for (int i = 0; i < base_edges; i++) { + + //if a point is shared with one of the edge points, then this edge must not be tested, as it will result in a numerical precision error. + bool edge_valid = true; + for (int j = 0; j < 2; j++) { + + if (edges[i].points[0] == segment_idx[0] || edges[i].points[1] == segment_idx[1] || edges[i].points[0] == segment_idx[1] || edges[i].points[1] == segment_idx[0]) { + edge_valid = false; //segment has this point, cant check against this + break; + } + } + + if (!edge_valid) //already hit a point in this edge, so dont test it + continue; + + //see if either points are within the edge isntead of crossing it + Vector2 res; + bool found = false; + int assign_segment_id = -1; + + for (int j = 0; j < 2; j++) { + + Vector2 edgeseg[2] = { points[edges[i].points[0]].point, points[edges[i].points[1]].point }; + Vector2 closest = Geometry::get_closest_point_to_segment_2d(segment[j], edgeseg); + + if (closest.distance_to(segment[j]) < CMP_EPSILON) { + //point rest of this edge + res = closest; + found = true; + assign_segment_id = j; + } + } + + //test if the point crosses the edge + if (!found && Geometry::segment_intersects_segment_2d(segment[0], segment[1], points[edges[i].points[0]].point, points[edges[i].points[1]].point, &res)) { + //point does cross the edge + found = true; + } + + //check whether an intersection against the segment happened + if (found) { + + //It did! so first, must slice the segment + Point new_point; + new_point.point = res; + //make sure to interpolate UV too + new_point.uv = interpolate_uv(points[edges[i].points[0]].point, new_point.point, points[edges[i].points[1]].point, points[edges[i].points[0]].uv, points[edges[i].points[1]].uv); + + int point_idx = points.size(); + points.push_back(new_point); + + //split the edge in 2 + Edge new_edge; + new_edge.points[0] = edges[i].points[0]; + new_edge.points[1] = point_idx; + new_edge.outer = edges[i].outer; + edges[i].points[0] = point_idx; + edges.insert(i, new_edge); + i++; //skip newly inserted edge + base_edges++; //will need an extra one in the base triangle + if (assign_segment_id >= 0) { + //point did split a segment, so make sure to remember this + segment_idx[assign_segment_id] = point_idx; + } + inserted_points.push_back(point_idx); + } + } + + //final step: after cutting the original triangle, try to see if we can still insert + //this segment + + //if already inserted two points, just use them for a segment + + if (inserted_points.size() >= 2) { //should never be >2 on non-manifold geometry, but cope with error + //two points were inserted, create the new edge + Edge new_edge; + new_edge.points[0] = inserted_points[0]; + new_edge.points[1] = inserted_points[1]; + edges.push_back(new_edge); + return; + } + + // One or no points were inserted (besides splitting), so try to see if extra points can be placed inside the triangle. + // This needs to be done here, after the previous tests were exhausted + for (int i = 0; i < 2; i++) { + + if (segment_idx[i] != -1) + continue; //already assigned to something, so skip + + //check whether one of the segment endpoints is inside the triangle. If it is, this points needs to be inserted + if (Geometry::is_point_in_triangle(segment[i], points[0].point, points[1].point, points[2].point)) { + + Point new_point; + new_point.point = segment[i]; + + Vector2 point3[3] = { points[0].point, points[1].point, points[2].point }; + Vector2 uv3[3] = { points[0].uv, points[1].uv, points[2].uv }; + + new_point.uv = interpolate_triangle_uv(new_point.point, point3, uv3); + + int point_idx = points.size(); + points.push_back(new_point); + inserted_points.push_back(point_idx); + } + } + + //check again whether two points were inserted, if so then create the new edge + if (inserted_points.size() >= 2) { //should never be >2 on non-manifold geometry, but cope with error + Edge new_edge; + new_edge.points[0] = inserted_points[0]; + new_edge.points[1] = inserted_points[1]; + edges.push_back(new_edge); + } +} + +void CSGBrushOperation::BuildPoly::clip(const CSGBrush *p_brush, int p_face, MeshMerge &mesh_merge, bool p_for_B) { + + //Clip function.. find triangle points that will be mapped to the plane and form a segment + + Vector2 segment[3]; //2D + + int src_points = 0; + + for (int i = 0; i < 3; i++) { + Vector3 p = p_brush->faces[p_face].vertices[i]; + if (plane.has_point(p)) { + Vector3 pp = plane.project(p); + pp = to_poly.xform(pp); + segment[src_points++] = Vector2(pp.x, pp.y); + } else { + Vector3 q = p_brush->faces[p_face].vertices[(i + 1) % 3]; + if (plane.has_point(q)) + continue; //next point is in plane, will be added eventually + if (plane.is_point_over(p) == plane.is_point_over(q)) + continue; // both on same side of the plane, don't add + + Vector3 res; + if (plane.intersects_segment(p, q, &res)) { + res = to_poly.xform(res); + segment[src_points++] = Vector2(res.x, res.y); + } + } + } + + //all above or all below, nothing to do. Should not happen though since a precheck was done before. + if (src_points == 0) + return; + + //just one point in plane is not worth doing anything + if (src_points == 1) + return; + + //transform A points to 2D + + if (segment[0].distance_to(segment[1]) < CMP_EPSILON) + return; //too small + + _clip_segment(p_brush, p_face, segment, mesh_merge, p_for_B); +} + +void CSGBrushOperation::_collision_callback(const CSGBrush *A, int p_face_a, Map<int, BuildPoly> &build_polys_a, const CSGBrush *B, int p_face_b, Map<int, BuildPoly> &build_polys_b, MeshMerge &mesh_merge) { + + //construct a frame of reference for both transforms, in order to do intersection test + Vector3 va[3] = { + A->faces[p_face_a].vertices[0], + A->faces[p_face_a].vertices[1], + A->faces[p_face_a].vertices[2], + }; + Vector3 vb[3] = { + B->faces[p_face_b].vertices[0], + B->faces[p_face_b].vertices[1], + B->faces[p_face_b].vertices[2], + }; + + { + //check if either is a degenerate + if (va[0].distance_to(va[1]) < CMP_EPSILON || va[0].distance_to(va[2]) < CMP_EPSILON || va[1].distance_to(va[2]) < CMP_EPSILON) + return; + + if (vb[0].distance_to(vb[1]) < CMP_EPSILON || vb[0].distance_to(vb[2]) < CMP_EPSILON || vb[1].distance_to(vb[2]) < CMP_EPSILON) + return; + } + + { + //check if points are the same + int equal_count = 0; + + for (int i = 0; i < 3; i++) { + + for (int j = 0; j < 3; j++) { + if (va[i].distance_to(vb[j]) < mesh_merge.vertex_snap) { + equal_count++; + break; + } + } + } + + //if 2 or 3 points are the same, there is no point in doing anything. They can't + //be clipped either, so add both. + if (equal_count == 2 || equal_count == 3) { + return; + } + } + + // do a quick pre-check for no-intersection using the SAT theorem + + { + + //b under or over a plane + int over_count = 0, in_plane_count = 0, under_count = 0; + Plane plane_a(va[0], va[1], va[2]); + if (plane_a.normal == Vector3()) { + return; //degenerate + } + + for (int i = 0; i < 3; i++) { + if (plane_a.has_point(vb[i])) + in_plane_count++; + else if (plane_a.is_point_over(vb[i])) + over_count++; + else + under_count++; + } + + if (over_count == 0 || under_count == 0) + return; //no intersection, something needs to be under AND over + + //a under or over b plane + over_count = 0; + under_count = 0; + in_plane_count = 0; + + Plane plane_b(vb[0], vb[1], vb[2]); + if (plane_b.normal == Vector3()) + return; //degenerate + + for (int i = 0; i < 3; i++) { + if (plane_b.has_point(va[i])) + in_plane_count++; + else if (plane_b.is_point_over(va[i])) + over_count++; + else + under_count++; + } + + if (over_count == 0 || under_count == 0) + return; //no intersection, something needs to be under AND over + + //edge pairs (cross product combinations), see SAT theorem + + for (int i = 0; i < 3; i++) { + + Vector3 axis_a = (va[i] - va[(i + 1) % 3]).normalized(); + + for (int j = 0; j < 3; j++) { + + Vector3 axis_b = (vb[j] - vb[(j + 1) % 3]).normalized(); + + Vector3 sep_axis = axis_a.cross(axis_b); + if (sep_axis == Vector3()) + continue; //colineal + sep_axis.normalize(); + + real_t min_a = 1e20, max_a = -1e20; + real_t min_b = 1e20, max_b = -1e20; + + for (int k = 0; k < 3; k++) { + real_t d = sep_axis.dot(va[k]); + min_a = MIN(min_a, d); + max_a = MAX(max_a, d); + d = sep_axis.dot(vb[k]); + min_b = MIN(min_b, d); + max_b = MAX(max_b, d); + } + + min_b -= (max_a - min_a) * 0.5; + max_b += (max_a - min_a) * 0.5; + + real_t dmin = min_b - (min_a + max_a) * 0.5; + real_t dmax = max_b - (min_a + max_a) * 0.5; + + if (dmin > CMP_EPSILON || dmax < -CMP_EPSILON) { + return; //does not contain zero, so they don't overlap + } + } + } + } + + //if we are still here, it means they most likely intersect, so create BuildPolys if they dont existy + + BuildPoly *poly_a = NULL; + + if (!build_polys_a.has(p_face_a)) { + + BuildPoly bp; + bp.create(A, p_face_a, mesh_merge, false); + build_polys_a[p_face_a] = bp; + } + + poly_a = &build_polys_a[p_face_a]; + + BuildPoly *poly_b = NULL; + + if (!build_polys_b.has(p_face_b)) { + + BuildPoly bp; + bp.create(B, p_face_b, mesh_merge, true); + build_polys_b[p_face_b] = bp; + } + + poly_b = &build_polys_b[p_face_b]; + + //clip each other, this could be improved by using vertex unique IDs (more vertices may be shared instead of using snap) + poly_a->clip(B, p_face_b, mesh_merge, false); + poly_b->clip(A, p_face_a, mesh_merge, true); +} + +void CSGBrushOperation::_add_poly_points(const BuildPoly &p_poly, int p_edge, int p_from_point, int p_to_point, const Vector<Vector<int> > &vertex_process, Vector<bool> &edge_process, Vector<PolyPoints> &r_poly) { + + //this function follows the polygon points counter clockwise and adds them. It creates lists of unique polygons + //every time an unused edge is found, it's pushed to a stack and continues from there. + + List<EdgeSort> edge_stack; + + { + EdgeSort es; + es.angle = 0; //wont be checked here + es.edge = p_edge; + es.prev_point = p_from_point; + es.edge_point = p_to_point; + + edge_stack.push_back(es); + } + + //attempt to empty the stack. + while (edge_stack.size()) { + + EdgeSort e = edge_stack.front()->get(); + edge_stack.pop_front(); + + if (edge_process[e.edge]) { + //nothing to do here + continue; + } + + Vector<int> points; + points.push_back(e.prev_point); + + int prev_point = e.prev_point; + int to_point = e.edge_point; + int current_edge = e.edge; + + edge_process[e.edge] = true; //mark as processed + + int limit = p_poly.points.size() * 4; //avoid infinite recursion + + while (to_point != e.prev_point && limit) { + + Vector2 segment[2] = { p_poly.points[prev_point].point, p_poly.points[to_point].point }; + + //construct a basis transform from the segment, which will be used to check the angle + Transform2D t2d; + t2d[0] = (segment[1] - segment[0]).normalized(); //use as Y + t2d[1] = Vector2(-t2d[0].y, t2d[0].x); // use as tangent + t2d[2] = segment[1]; //origin + + if (t2d.basis_determinant() == 0) + break; //abort poly + + t2d.affine_invert(); + + //push all edges found here, they will be sorted by minimum angle later. + Vector<EdgeSort> next_edges; + + for (int i = 0; i < vertex_process[to_point].size(); i++) { + + int edge = vertex_process[to_point][i]; + int opposite_point = p_poly.edges[edge].points[0] == to_point ? p_poly.edges[edge].points[1] : p_poly.edges[edge].points[0]; + if (opposite_point == prev_point) + continue; //not going back + + EdgeSort e; + Vector2 local_vec = t2d.xform(p_poly.points[opposite_point].point); + e.angle = -local_vec.angle(); //negate so we can sort by minimum angle + e.edge = edge; + e.edge_point = opposite_point; + e.prev_point = to_point; + + next_edges.push_back(e); + } + + //finally, sort by minimum angle + next_edges.sort(); + + int next_point = -1; + int next_edge = -1; + + for (int i = 0; i < next_edges.size(); i++) { + + if (i == 0) { + //minimum angle found is the next point + next_point = next_edges[i].edge_point; + next_edge = next_edges[i].edge; + + } else { + //the rest are pushed to the stack IF they were not processed yet. + if (!edge_process[next_edges[i].edge]) { + edge_stack.push_back(next_edges[i]); + } + } + } + + if (next_edge == -1) { + //did not find anything, may be a dead-end edge (this should normally not happen) + //just flip the direction and go back + next_point = prev_point; + next_edge = current_edge; + } + + points.push_back(to_point); + + prev_point = to_point; + to_point = next_point; + edge_process[next_edge] = true; //mark this edge as processed + current_edge = next_edge; + + limit--; + } + + //if more than 2 points were added to the polygon, add it to the list of polygons. + if (points.size() > 2) { + PolyPoints pp; + pp.points = points; + r_poly.push_back(pp); + } + } +} + +void CSGBrushOperation::_add_poly_outline(const BuildPoly &p_poly, int p_from_point, int p_to_point, const Vector<Vector<int> > &vertex_process, Vector<int> &r_outline) { + + //this is the opposite of the function above. It adds polygon outlines instead. + //this is used for triangulating holes. + //no stack is used here because only the bigger outline is interesting. + + r_outline.push_back(p_from_point); + + int prev_point = p_from_point; + int to_point = p_to_point; + + int limit = p_poly.points.size() * 4; //avoid infinite recursion + + while (to_point != p_from_point && limit) { + + Vector2 segment[2] = { p_poly.points[prev_point].point, p_poly.points[to_point].point }; + //again create a transform to compute the angle. + Transform2D t2d; + t2d[0] = (segment[1] - segment[0]).normalized(); //use as Y + t2d[1] = Vector2(-t2d[0].y, t2d[0].x); // use as tangent + t2d[2] = segment[1]; //origin + + if (t2d.basis_determinant() == 0) + break; //abort poly + + t2d.affine_invert(); + + float max_angle; + int next_point_angle = -1; + + for (int i = 0; i < vertex_process[to_point].size(); i++) { + + int edge = vertex_process[to_point][i]; + int opposite_point = p_poly.edges[edge].points[0] == to_point ? p_poly.edges[edge].points[1] : p_poly.edges[edge].points[0]; + if (opposite_point == prev_point) + continue; //not going back + + float angle = -t2d.xform(p_poly.points[opposite_point].point).angle(); + if (next_point_angle == -1 || angle > max_angle) { //same as before but use greater to check. + max_angle = angle; + next_point_angle = opposite_point; + } + } + + if (next_point_angle == -1) { + //go back because no route found + next_point_angle = prev_point; + } + + r_outline.push_back(to_point); + prev_point = to_point; + to_point = next_point_angle; + + limit--; + } +} + +void CSGBrushOperation::_merge_poly(MeshMerge &mesh, int p_face_idx, const BuildPoly &p_poly, bool p_from_b) { + + //finally, merge the 2D polygon back to 3D + + Vector<Vector<int> > vertex_process; + Vector<bool> edge_process; + + vertex_process.resize(p_poly.points.size()); + edge_process.resize(p_poly.edges.size()); + + //none processed by default + for (int i = 0; i < edge_process.size(); i++) { + edge_process[i] = false; + } + + //put edges in points, so points can go through them + for (int i = 0; i < p_poly.edges.size(); i++) { + vertex_process[p_poly.edges[i].points[0]].push_back(i); + vertex_process[p_poly.edges[i].points[1]].push_back(i); + } + + Vector<PolyPoints> polys; + + //process points that were not processed + for (int i = 0; i < edge_process.size(); i++) { + if (edge_process[i] == true) + continue; //already processed + + int intersect_poly = -1; + + if (i > 0) { + //this is disconnected, so it's clearly a hole. lets find where it belongs + Vector2 ref_point = p_poly.points[p_poly.edges[i].points[0]].point; + + for (int j = 0; j < polys.size(); j++) { + + //find a point outside poly + Vector2 out_point(-1e20, -1e20); + + const PolyPoints &pp = polys[j]; + + for (int k = 0; k < pp.points.size(); k++) { + Vector2 p = p_poly.points[pp.points[k]].point; + out_point.x = MAX(out_point.x, p.x); + out_point.y = MAX(out_point.y, p.y); + } + + out_point += Vector2(0.12341234, 0.4123412); // move to a random place to avoid direct edge-point chances + + int intersections = 0; + + for (int k = 0; k < pp.points.size(); k++) { + Vector2 p1 = p_poly.points[pp.points[k]].point; + Vector2 p2 = p_poly.points[pp.points[(k + 1) % pp.points.size()]].point; + + if (Geometry::segment_intersects_segment_2d(ref_point, out_point, p1, p2, NULL)) { + intersections++; + } + } + + if (intersections % 2 == 1) { + //hole is inside this poly + intersect_poly = j; + break; + } + } + } + + if (intersect_poly != -1) { + //must add this as a hole + Vector<int> outline; + _add_poly_outline(p_poly, p_poly.edges[i].points[0], p_poly.edges[i].points[1], vertex_process, outline); + + if (outline.size() > 1) { + polys[intersect_poly].holes.push_back(outline); + } + } + _add_poly_points(p_poly, i, p_poly.edges[i].points[0], p_poly.edges[i].points[1], vertex_process, edge_process, polys); + } + + //get rid of holes, not the most optiomal way, but also not a common case at all to be inoptimal + for (int i = 0; i < polys.size(); i++) { + + if (!polys[i].holes.size()) + continue; + + //repeat until no more holes are left to be merged + while (polys[i].holes.size()) { + + //try to merge a hole with the outline + bool added_hole = false; + + for (int j = 0; j < polys[i].holes.size(); j++) { + + //try hole vertices + int with_outline_vertex = -1; + int from_hole_vertex = -1; + + bool found = false; + + for (int k = 0; k < polys[i].holes[j].size(); k++) { + + int from_idx = polys[i].holes[j][k]; + Vector2 from = p_poly.points[from_idx].point; + + //try a segment from hole vertex to outline vertices + from_hole_vertex = k; + + bool valid = true; + + for (int l = 0; l < polys[i].points.size(); l++) { + + int to_idx = polys[i].points[l]; + Vector2 to = p_poly.points[to_idx].point; + with_outline_vertex = l; + + //try agaisnt outline (other points) first + + valid = true; + + for (int m = 0; m < polys[i].points.size(); m++) { + + int m_next = (m + 1) % polys[i].points.size(); + if (m == with_outline_vertex || m_next == with_outline_vertex) //do not test with edges that share this point + continue; + + if (Geometry::segment_intersects_segment_2d(from, to, p_poly.points[polys[i].points[m]].point, p_poly.points[polys[i].points[m_next]].point, NULL)) { + valid = false; + break; + } + } + + if (!valid) + continue; + + //try agaisnt all holes including self + + for (int m = 0; m < polys[i].holes.size(); m++) { + + for (int n = 0; n < polys[i].holes[m].size(); n++) { + + int n_next = (n + 1) % polys[i].holes[m].size(); + if (m == j && (n == from_hole_vertex || n_next == from_hole_vertex)) //contains vertex being tested from current hole, skip + continue; + + if (Geometry::segment_intersects_segment_2d(from, to, p_poly.points[polys[i].holes[m][n]].point, p_poly.points[polys[i].holes[m][n_next]].point, NULL)) { + valid = false; + break; + } + } + + if (!valid) + break; + } + + if (valid) //all passed! exit loop + break; + else + continue; //something went wrong, go on. + } + + if (valid) { + found = true; //if in the end this was valid, use it + break; + } + } + + if (found) { + + //hook this hole with outline, and remove from list of holes + + //duplicate point + int insert_at = with_outline_vertex; + polys[i].points.insert(insert_at, polys[i].points[insert_at]); + insert_at++; + //insert all others, outline should be backwards (must check) + int holesize = polys[i].holes[j].size(); + for (int k = 0; k <= holesize; k++) { + int idx = (from_hole_vertex + k) % holesize; + polys[i].points.insert(insert_at, polys[i].holes[j][idx]); + insert_at++; + } + + added_hole = true; + polys[i].holes.remove(j); + break; //got rid of hole, break and continue + } + } + + ERR_BREAK(!added_hole); + } + } + + //triangulate polygons + + for (int i = 0; i < polys.size(); i++) { + + Vector<Vector2> vertices; + vertices.resize(polys[i].points.size()); + for (int j = 0; j < vertices.size(); j++) { + vertices[j] = p_poly.points[polys[i].points[j]].point; + } + + Vector<int> indices = Geometry::triangulate_polygon(vertices); + + for (int j = 0; j < indices.size(); j += 3) { + + //obtain the vertex + + Vector3 face[3]; + Vector2 uv[3]; + float cp = Geometry::vec2_cross(p_poly.points[polys[i].points[indices[j + 0]]].point, p_poly.points[polys[i].points[indices[j + 1]]].point, p_poly.points[polys[i].points[indices[j + 2]]].point); + if (Math::abs(cp) < CMP_EPSILON) + continue; + + for (int k = 0; k < 3; k++) { + + Vector2 p = p_poly.points[polys[i].points[indices[j + k]]].point; + face[k] = p_poly.to_world.xform(Vector3(p.x, p.y, 0)); + uv[k] = p_poly.points[polys[i].points[indices[j + k]]].uv; + } + + mesh.add_face(face[0], face[1], face[2], uv[0], uv[1], uv[2], p_poly.smooth, p_poly.invert, p_poly.material, p_from_b); + } + } +} + +//use a limit to speed up bvh and limit the depth +#define BVH_LIMIT 8 + +int CSGBrushOperation::MeshMerge::_create_bvh(BVH *p_bvh, BVH **p_bb, int p_from, int p_size, int p_depth, int &max_depth, int &max_alloc) { + + if (p_depth > max_depth) { + max_depth = p_depth; + } + + if (p_size <= BVH_LIMIT) { + + for (int i = 0; i < p_size - 1; i++) { + p_bb[p_from + i]->next = p_bb[p_from + i + 1] - p_bvh; + } + return p_bb[p_from] - p_bvh; + } else if (p_size == 0) { + + return -1; + } + + AABB aabb; + aabb = p_bb[p_from]->aabb; + for (int i = 1; i < p_size; i++) { + + aabb.merge_with(p_bb[p_from + i]->aabb); + } + + int li = aabb.get_longest_axis_index(); + + switch (li) { + + case Vector3::AXIS_X: { + SortArray<BVH *, BVHCmpX> sort_x; + sort_x.nth_element(0, p_size, p_size / 2, &p_bb[p_from]); + //sort_x.sort(&p_bb[p_from],p_size); + } break; + case Vector3::AXIS_Y: { + SortArray<BVH *, BVHCmpY> sort_y; + sort_y.nth_element(0, p_size, p_size / 2, &p_bb[p_from]); + //sort_y.sort(&p_bb[p_from],p_size); + } break; + case Vector3::AXIS_Z: { + SortArray<BVH *, BVHCmpZ> sort_z; + sort_z.nth_element(0, p_size, p_size / 2, &p_bb[p_from]); + //sort_z.sort(&p_bb[p_from],p_size); + + } break; + } + + int left = _create_bvh(p_bvh, p_bb, p_from, p_size / 2, p_depth + 1, max_depth, max_alloc); + int right = _create_bvh(p_bvh, p_bb, p_from + p_size / 2, p_size - p_size / 2, p_depth + 1, max_depth, max_alloc); + + int index = max_alloc++; + BVH *_new = &p_bvh[index]; + _new->aabb = aabb; + _new->center = aabb.position + aabb.size * 0.5; + _new->face = -1; + _new->left = left; + _new->right = right; + _new->next = -1; + + return index; +} + +int CSGBrushOperation::MeshMerge::_bvh_count_intersections(BVH *bvhptr, int p_max_depth, int p_bvh_first, const Vector3 &p_begin, const Vector3 &p_end, int p_exclude) const { + + uint32_t *stack = (uint32_t *)alloca(sizeof(int) * p_max_depth); + + enum { + TEST_AABB_BIT = 0, + VISIT_LEFT_BIT = 1, + VISIT_RIGHT_BIT = 2, + VISIT_DONE_BIT = 3, + VISITED_BIT_SHIFT = 29, + NODE_IDX_MASK = (1 << VISITED_BIT_SHIFT) - 1, + VISITED_BIT_MASK = ~NODE_IDX_MASK, + + }; + + int intersections = 0; + + int level = 0; + + const Vector3 *vertexptr = points.ptr(); + const Face *facesptr = faces.ptr(); + AABB segment_aabb; + segment_aabb.position = p_begin; + segment_aabb.expand_to(p_end); + + int pos = p_bvh_first; + + stack[0] = pos; + while (true) { + + uint32_t node = stack[level] & NODE_IDX_MASK; + const BVH &b = bvhptr[node]; + bool done = false; + + switch (stack[level] >> VISITED_BIT_SHIFT) { + case TEST_AABB_BIT: { + + if (b.face >= 0) { + + const BVH *bp = &b; + + while (bp) { + + bool valid = segment_aabb.intersects(bp->aabb) && bp->aabb.intersects_segment(p_begin, p_end); + + if (valid && p_exclude != bp->face) { + const Face &s = facesptr[bp->face]; + Face3 f3(vertexptr[s.points[0]], vertexptr[s.points[1]], vertexptr[s.points[2]]); + + Vector3 res; + + if (f3.intersects_segment(p_begin, p_end, &res)) { + intersections++; + } + } + if (bp->next != -1) { + bp = &bvhptr[bp->next]; + } else { + bp = NULL; + } + } + + stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node; + + } else { + + bool valid = segment_aabb.intersects(b.aabb) && b.aabb.intersects_segment(p_begin, p_end); + + if (!valid) { + + stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node; + + } else { + stack[level] = (VISIT_LEFT_BIT << VISITED_BIT_SHIFT) | node; + } + } + continue; + } + case VISIT_LEFT_BIT: { + + stack[level] = (VISIT_RIGHT_BIT << VISITED_BIT_SHIFT) | node; + stack[level + 1] = b.left | TEST_AABB_BIT; + level++; + continue; + } + case VISIT_RIGHT_BIT: { + + stack[level] = (VISIT_DONE_BIT << VISITED_BIT_SHIFT) | node; + stack[level + 1] = b.right | TEST_AABB_BIT; + level++; + continue; + } + case VISIT_DONE_BIT: { + + if (level == 0) { + done = true; + break; + } else + level--; + continue; + } + } + + if (done) + break; + } + + return intersections; +} + +void CSGBrushOperation::MeshMerge::mark_inside_faces() { + + // mark faces that are inside. This helps later do the boolean ops when merging. + // this approach is very brute force (with a bunch of optimizatios, such as BVH and pre AABB intersection test) + + AABB aabb; + + for (int i = 0; i < points.size(); i++) { + if (i == 0) { + aabb.position = points[i]; + } else { + aabb.expand_to(points[i]); + } + } + + float max_distance = aabb.size.length() * 1.2; + + Vector<BVH> bvhvec; + bvhvec.resize(faces.size() * 3); //will never be larger than this (todo make better) + BVH *bvh = bvhvec.ptrw(); + + AABB faces_a; + AABB faces_b; + + bool first_a = true; + bool first_b = true; + + for (int i = 0; i < faces.size(); i++) { + bvh[i].left = -1; + bvh[i].right = -1; + bvh[i].face = i; + bvh[i].aabb.position = points[faces[i].points[0]]; + bvh[i].aabb.expand_to(points[faces[i].points[1]]); + bvh[i].aabb.expand_to(points[faces[i].points[2]]); + bvh[i].center = bvh[i].aabb.position + bvh[i].aabb.size * 0.5; + bvh[i].next = -1; + if (faces[i].from_b) { + if (first_b) { + faces_b = bvh[i].aabb; + first_b = false; + } else { + faces_b.merge_with(bvh[i].aabb); + } + } else { + if (first_a) { + faces_a = bvh[i].aabb; + first_a = false; + } else { + faces_a.merge_with(bvh[i].aabb); + } + } + } + + AABB intersection_aabb = faces_a.intersection(faces_b); + intersection_aabb.grow_by(intersection_aabb.get_longest_axis_size() * 0.01); //grow a little, avoid numerical error + + if (intersection_aabb.size == Vector3()) //AABB do not intersect, so neither do shapes. + return; + + Vector<BVH *> bvhtrvec; + bvhtrvec.resize(faces.size()); + BVH **bvhptr = bvhtrvec.ptrw(); + for (int i = 0; i < faces.size(); i++) { + + bvhptr[i] = &bvh[i]; + } + + int max_depth = 0; + int max_alloc = faces.size(); + _create_bvh(bvh, bvhptr, 0, faces.size(), 1, max_depth, max_alloc); + + for (int i = 0; i < faces.size(); i++) { + + if (!intersection_aabb.intersects(bvh[i].aabb)) + continue; //not in AABB intersection, so not in face intersection + Vector3 center = points[faces[i].points[0]]; + center += points[faces[i].points[1]]; + center += points[faces[i].points[2]]; + center /= 3.0; + + Plane plane(points[faces[i].points[0]], points[faces[i].points[1]], points[faces[i].points[2]]); + Vector3 target = center + plane.normal * max_distance + Vector3(0.0001234, 0.000512, 0.00013423); //reduce chance of edge hits by doing a small increment + + int intersections = _bvh_count_intersections(bvh, max_depth, max_alloc - 1, center, target, i); + + if (intersections & 1) { + faces[i].inside = true; + } + } +} + +void CSGBrushOperation::MeshMerge::add_face(const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_c, const Vector2 &p_uv_a, const Vector2 &p_uv_b, const Vector2 &p_uv_c, bool p_smooth, bool p_invert, const Ref<Material> &p_material, bool p_from_b) { + + Vector3 src_points[3] = { p_a, p_b, p_c }; + Vector2 src_uvs[3] = { p_uv_a, p_uv_b, p_uv_c }; + int indices[3]; + for (int i = 0; i < 3; i++) { + + VertexKey vk; + vk.x = int((double(src_points[i].x) + double(vertex_snap) * 0.31234) / double(vertex_snap)); + vk.y = int((double(src_points[i].y) + double(vertex_snap) * 0.31234) / double(vertex_snap)); + vk.z = int((double(src_points[i].z) + double(vertex_snap) * 0.31234) / double(vertex_snap)); + + int res; + if (snap_cache.lookup(vk, res)) { + indices[i] = res; + } else { + indices[i] = points.size(); + points.push_back(src_points[i]); + snap_cache.set(vk, indices[i]); + } + } + + if (indices[0] == indices[2] || indices[0] == indices[1] || indices[1] == indices[2]) + return; //not adding degenerate + + MeshMerge::Face face; + face.from_b = p_from_b; + face.inside = false; + face.smooth = p_smooth; + face.invert = p_invert; + if (p_material.is_valid()) { + if (!materials.has(p_material)) { + face.material_idx = materials.size(); + materials[p_material] = face.material_idx; + } else { + face.material_idx = materials[p_material]; + } + } else { + face.material_idx = -1; + } + + for (int k = 0; k < 3; k++) { + + face.points[k] = indices[k]; + face.uvs[k] = src_uvs[k]; + ; + } + + faces.push_back(face); +} + +void CSGBrushOperation::merge_brushes(Operation p_operation, const CSGBrush &p_A, const CSGBrush &p_B, CSGBrush &result, float p_snap) { + + CallbackData cd; + cd.self = this; + cd.A = &p_A; + cd.B = &p_B; + + MeshMerge mesh_merge; + mesh_merge.vertex_snap = p_snap; + + //check intersections between faces. Use AABB to speed up precheck + //this generates list of buildpolys and clips them. + //this was originally BVH optimized, but its not really worth it. + for (int i = 0; i < p_A.faces.size(); i++) { + cd.face_a = i; + for (int j = 0; j < p_B.faces.size(); j++) { + if (p_A.faces[i].aabb.intersects(p_B.faces[j].aabb)) { + _collision_callback(&p_A, i, cd.build_polys_A, &p_B, j, cd.build_polys_B, mesh_merge); + } + } + } + + //merge the already cliped polys back to 3D + for (Map<int, BuildPoly>::Element *E = cd.build_polys_A.front(); E; E = E->next()) { + _merge_poly(mesh_merge, E->key(), E->get(), false); + } + + for (Map<int, BuildPoly>::Element *E = cd.build_polys_B.front(); E; E = E->next()) { + _merge_poly(mesh_merge, E->key(), E->get(), true); + } + + //merge the non clipped faces back + + for (int i = 0; i < p_A.faces.size(); i++) { + + if (cd.build_polys_A.has(i)) + continue; //made from buildpoly, skipping + + Vector3 points[3]; + Vector2 uvs[3]; + for (int j = 0; j < 3; j++) { + points[j] = p_A.faces[i].vertices[j]; + uvs[j] = p_A.faces[i].uvs[j]; + } + Ref<Material> material; + if (p_A.faces[i].material != -1) { + material = p_A.materials[p_A.faces[i].material]; + } + mesh_merge.add_face(points[0], points[1], points[2], uvs[0], uvs[1], uvs[2], p_A.faces[i].smooth, p_A.faces[i].invert, material, false); + } + + for (int i = 0; i < p_B.faces.size(); i++) { + + if (cd.build_polys_B.has(i)) + continue; //made from buildpoly, skipping + + Vector3 points[3]; + Vector2 uvs[3]; + for (int j = 0; j < 3; j++) { + points[j] = p_B.faces[i].vertices[j]; + uvs[j] = p_B.faces[i].uvs[j]; + } + Ref<Material> material; + if (p_B.faces[i].material != -1) { + material = p_B.materials[p_B.faces[i].material]; + } + mesh_merge.add_face(points[0], points[1], points[2], uvs[0], uvs[1], uvs[2], p_B.faces[i].smooth, p_B.faces[i].invert, material, true); + } + + //mark faces that ended up inside the intersection + mesh_merge.mark_inside_faces(); + + //regen new brush to start filling it again + result.clear(); + + switch (p_operation) { + + case OPERATION_UNION: { + + int outside_count = 0; + + for (int i = 0; i < mesh_merge.faces.size(); i++) { + if (mesh_merge.faces[i].inside) + continue; + + outside_count++; + } + + result.faces.resize(outside_count); + + outside_count = 0; + + for (int i = 0; i < mesh_merge.faces.size(); i++) { + if (mesh_merge.faces[i].inside) + continue; + for (int j = 0; j < 3; j++) { + result.faces[outside_count].vertices[j] = mesh_merge.points[mesh_merge.faces[i].points[j]]; + result.faces[outside_count].uvs[j] = mesh_merge.faces[i].uvs[j]; + } + + result.faces[outside_count].smooth = mesh_merge.faces[i].smooth; + result.faces[outside_count].invert = mesh_merge.faces[i].invert; + result.faces[outside_count].material = mesh_merge.faces[i].material_idx; + outside_count++; + } + + result._regen_face_aabbs(); + + } break; + case OPERATION_INTERSECTION: { + + int inside_count = 0; + + for (int i = 0; i < mesh_merge.faces.size(); i++) { + if (!mesh_merge.faces[i].inside) + continue; + + inside_count++; + } + + result.faces.resize(inside_count); + + inside_count = 0; + + for (int i = 0; i < mesh_merge.faces.size(); i++) { + if (!mesh_merge.faces[i].inside) + continue; + for (int j = 0; j < 3; j++) { + result.faces[inside_count].vertices[j] = mesh_merge.points[mesh_merge.faces[i].points[j]]; + result.faces[inside_count].uvs[j] = mesh_merge.faces[i].uvs[j]; + } + + result.faces[inside_count].smooth = mesh_merge.faces[i].smooth; + result.faces[inside_count].invert = mesh_merge.faces[i].invert; + result.faces[inside_count].material = mesh_merge.faces[i].material_idx; + inside_count++; + } + + result._regen_face_aabbs(); + + } break; + case OPERATION_SUBSTRACTION: { + + int face_count = 0; + + for (int i = 0; i < mesh_merge.faces.size(); i++) { + if (mesh_merge.faces[i].from_b && !mesh_merge.faces[i].inside) + continue; + if (!mesh_merge.faces[i].from_b && mesh_merge.faces[i].inside) + continue; + + face_count++; + } + + result.faces.resize(face_count); + + face_count = 0; + + for (int i = 0; i < mesh_merge.faces.size(); i++) { + + if (mesh_merge.faces[i].from_b && !mesh_merge.faces[i].inside) + continue; + if (!mesh_merge.faces[i].from_b && mesh_merge.faces[i].inside) + continue; + + for (int j = 0; j < 3; j++) { + result.faces[face_count].vertices[j] = mesh_merge.points[mesh_merge.faces[i].points[j]]; + result.faces[face_count].uvs[j] = mesh_merge.faces[i].uvs[j]; + } + + if (mesh_merge.faces[i].from_b) { + //invert facing of insides of B + SWAP(result.faces[face_count].vertices[1], result.faces[face_count].vertices[2]); + SWAP(result.faces[face_count].uvs[1], result.faces[face_count].uvs[2]); + } + + result.faces[face_count].smooth = mesh_merge.faces[i].smooth; + result.faces[face_count].invert = mesh_merge.faces[i].invert; + result.faces[face_count].material = mesh_merge.faces[i].material_idx; + face_count++; + } + + result._regen_face_aabbs(); + + } break; + } + + //updatelist of materials + result.materials.resize(mesh_merge.materials.size()); + for (const Map<Ref<Material>, int>::Element *E = mesh_merge.materials.front(); E; E = E->next()) { + result.materials[E->get()] = E->key(); + } +} diff --git a/modules/csg/csg.h b/modules/csg/csg.h new file mode 100644 index 0000000000..53303a6533 --- /dev/null +++ b/modules/csg/csg.h @@ -0,0 +1,236 @@ +/*************************************************************************/ +/* csg.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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 CSG_H +#define CSG_H + +#include "aabb.h" +#include "dvector.h" +#include "map.h" +#include "math_2d.h" +#include "oa_hash_map.h" +#include "plane.h" +#include "scene/resources/material.h" +#include "transform.h" +#include "vector3.h" + +struct CSGBrush { + + struct Face { + + Vector3 vertices[3]; + Vector2 uvs[3]; + AABB aabb; + bool smooth; + bool invert; + int material; + }; + + Vector<Face> faces; + Vector<Ref<Material> > materials; + + void _regen_face_aabbs(); + //create a brush from faces + void build_from_faces(const PoolVector<Vector3> &p_vertices, const PoolVector<Vector2> &p_uvs, const PoolVector<bool> &p_smooth, const PoolVector<Ref<Material> > &p_materials, const PoolVector<bool> &p_invert_faces); + void copy_from(const CSGBrush &p_brush, const Transform &p_xform); + + void clear(); +}; + +struct CSGBrushOperation { + + enum Operation { + OPERATION_UNION, + OPERATION_INTERSECTION, + OPERATION_SUBSTRACTION, + + }; + + struct MeshMerge { + + struct BVH { + int face; + int left; + int right; + int next; + Vector3 center; + AABB aabb; + }; + + struct BVHCmpX { + + bool operator()(const BVH *p_left, const BVH *p_right) const { + + return p_left->center.x < p_right->center.x; + } + }; + + struct BVHCmpY { + + bool operator()(const BVH *p_left, const BVH *p_right) const { + + return p_left->center.y < p_right->center.y; + } + }; + struct BVHCmpZ { + + bool operator()(const BVH *p_left, const BVH *p_right) const { + + return p_left->center.z < p_right->center.z; + } + }; + + int _bvh_count_intersections(BVH *bvhptr, int p_max_depth, int p_bvh_first, const Vector3 &p_begin, const Vector3 &p_end, int p_exclude) const; + int _create_bvh(BVH *p_bvh, BVH **p_bb, int p_from, int p_size, int p_depth, int &max_depth, int &max_alloc); + + struct VertexKey { + int32_t x, y, z; + _FORCE_INLINE_ bool operator<(const VertexKey &p_key) const { + if (x == p_key.x) { + if (y == p_key.y) { + return z < p_key.z; + } else { + return y < p_key.y; + } + } else { + return x < p_key.x; + } + } + + _FORCE_INLINE_ bool operator==(const VertexKey &p_key) const { + return (x == p_key.x && y == p_key.y && z == p_key.z); + } + }; + + struct VertexKeyHash { + static _FORCE_INLINE_ uint32_t hash(const VertexKey &p_vk) { + uint32_t h = hash_djb2_one_32(p_vk.x); + h = hash_djb2_one_32(p_vk.y, h); + h = hash_djb2_one_32(p_vk.z, h); + return h; + } + }; + + OAHashMap<VertexKey, int, VertexKeyHash> snap_cache; + + Vector<Vector3> points; + + struct Face { + bool from_b; + bool inside; + int points[3]; + Vector2 uvs[3]; + bool smooth; + bool invert; + int material_idx; + }; + + Vector<Face> faces; + + Map<Ref<Material>, int> materials; + + Map<Vector3, int> vertex_map; + void add_face(const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_c, const Vector2 &p_uv_a, const Vector2 &p_uv_b, const Vector2 &p_uv_c, bool p_smooth, bool p_invert, const Ref<Material> &p_material, bool p_from_b); + // void add_face(const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_c, bool p_from_b); + + float vertex_snap; + void mark_inside_faces(); + }; + + struct BuildPoly { + + Plane plane; + Transform to_poly; + Transform to_world; + int face_index; + + struct Point { + Vector2 point; + Vector2 uv; + }; + + Vector<Point> points; + + struct Edge { + bool outer; + int points[2]; + Edge() { + outer = false; + } + }; + + Vector<Edge> edges; + Ref<Material> material; + bool smooth; + bool invert; + + int base_edges; //edges from original triangle, even if split + + void _clip_segment(const CSGBrush *p_brush, int p_face, const Vector2 *segment, MeshMerge &mesh_merge, bool p_for_B); + + void create(const CSGBrush *p_brush, int p_face, MeshMerge &mesh_merge, bool p_for_B); + void clip(const CSGBrush *p_brush, int p_face, MeshMerge &mesh_merge, bool p_for_B); + }; + + struct PolyPoints { + + Vector<int> points; + + Vector<Vector<int> > holes; + }; + + struct EdgeSort { + int edge; + int prev_point; + int edge_point; + float angle; + bool operator<(const EdgeSort &p_edge) const { return angle < p_edge.angle; } + }; + + struct CallbackData { + const CSGBrush *A; + const CSGBrush *B; + int face_a; + CSGBrushOperation *self; + Map<int, BuildPoly> build_polys_A; + Map<int, BuildPoly> build_polys_B; + }; + + void _add_poly_points(const BuildPoly &p_poly, int p_edge, int p_from_point, int p_to_point, const Vector<Vector<int> > &vertex_process, Vector<bool> &edge_process, Vector<PolyPoints> &r_poly); + void _add_poly_outline(const BuildPoly &p_poly, int p_from_point, int p_to_point, const Vector<Vector<int> > &vertex_process, Vector<int> &r_outline); + void _merge_poly(MeshMerge &mesh, int p_face_idx, const BuildPoly &p_poly, bool p_from_b); + + void _collision_callback(const CSGBrush *A, int p_face_a, Map<int, BuildPoly> &build_polys_a, const CSGBrush *B, int p_face_b, Map<int, BuildPoly> &build_polys_b, MeshMerge &mesh_merge); + + static void _collision_callbacks(void *ud, int p_face_b); + void merge_brushes(Operation p_operation, const CSGBrush &p_A, const CSGBrush &p_B, CSGBrush &result, float p_snap = 0.001); +}; + +#endif // CSG_H diff --git a/modules/csg/csg_gizmos.cpp b/modules/csg/csg_gizmos.cpp new file mode 100644 index 0000000000..2150320c4a --- /dev/null +++ b/modules/csg/csg_gizmos.cpp @@ -0,0 +1,345 @@ +/*************************************************************************/ +/* csg_gizmos.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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 "csg_gizmos.h" + +/////////// + +String CSGShapeSpatialGizmo::get_handle_name(int p_idx) const { + + if (Object::cast_to<CSGSphere>(cs)) { + + return "Radius"; + } + + if (Object::cast_to<CSGBox>(cs)) { + + static const char *hname[3] = { "Width", "Height", "Depth" }; + return hname[p_idx]; + } + + if (Object::cast_to<CSGCylinder>(cs)) { + + return p_idx == 0 ? "Radius" : "Height"; + } + + if (Object::cast_to<CSGTorus>(cs)) { + + return p_idx == 0 ? "InnerRadius" : "OuterRadius"; + } + + return ""; +} +Variant CSGShapeSpatialGizmo::get_handle_value(int p_idx) const { + + if (Object::cast_to<CSGSphere>(cs)) { + + CSGSphere *s = Object::cast_to<CSGSphere>(cs); + return s->get_radius(); + } + + if (Object::cast_to<CSGBox>(cs)) { + + CSGBox *s = Object::cast_to<CSGBox>(cs); + switch (p_idx) { + case 0: return s->get_width(); + case 1: return s->get_height(); + case 2: return s->get_depth(); + } + } + + if (Object::cast_to<CSGCylinder>(cs)) { + + CSGCylinder *s = Object::cast_to<CSGCylinder>(cs); + return p_idx == 0 ? s->get_radius() : s->get_height(); + } + + if (Object::cast_to<CSGTorus>(cs)) { + + CSGTorus *s = Object::cast_to<CSGTorus>(cs); + return p_idx == 0 ? s->get_inner_radius() : s->get_outer_radius(); + } + + return Variant(); +} +void CSGShapeSpatialGizmo::set_handle(int p_idx, Camera *p_camera, const Point2 &p_point) { + + Transform gt = cs->get_global_transform(); + gt.orthonormalize(); + Transform gi = gt.affine_inverse(); + + Vector3 ray_from = p_camera->project_ray_origin(p_point); + Vector3 ray_dir = p_camera->project_ray_normal(p_point); + + Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) }; + + if (Object::cast_to<CSGSphere>(cs)) { + + CSGSphere *s = Object::cast_to<CSGSphere>(cs); + + Vector3 ra, rb; + Geometry::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb); + float d = ra.x; + if (d < 0.001) + d = 0.001; + + s->set_radius(d); + } + + if (Object::cast_to<CSGBox>(cs)) { + + CSGBox *s = Object::cast_to<CSGBox>(cs); + + Vector3 axis; + axis[p_idx] = 1.0; + Vector3 ra, rb; + Geometry::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); + float d = ra[p_idx]; + if (d < 0.001) + d = 0.001; + + switch (p_idx) { + case 0: s->set_width(d); break; + case 1: s->set_height(d); break; + case 2: s->set_depth(d); break; + } + } + + if (Object::cast_to<CSGCylinder>(cs)) { + + CSGCylinder *s = Object::cast_to<CSGCylinder>(cs); + + Vector3 axis; + axis[p_idx == 0 ? 0 : 1] = 1.0; + Vector3 ra, rb; + Geometry::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); + float d = axis.dot(ra); + + if (d < 0.001) + d = 0.001; + + if (p_idx == 0) + s->set_radius(d); + else if (p_idx == 1) + s->set_height(d * 2.0); + } + + if (Object::cast_to<CSGTorus>(cs)) { + + CSGTorus *s = Object::cast_to<CSGTorus>(cs); + + Vector3 axis; + axis[0] = 1.0; + Vector3 ra, rb; + Geometry::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); + float d = axis.dot(ra); + + if (d < 0.001) + d = 0.001; + + if (p_idx == 0) + s->set_inner_radius(d); + else if (p_idx == 1) + s->set_outer_radius(d); + } +} +void CSGShapeSpatialGizmo::commit_handle(int p_idx, const Variant &p_restore, bool p_cancel) { + + if (Object::cast_to<CSGSphere>(cs)) { + CSGSphere *s = Object::cast_to<CSGSphere>(cs); + if (p_cancel) { + s->set_radius(p_restore); + return; + } + + UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Change Sphere Shape Radius")); + ur->add_do_method(s, "set_radius", s->get_radius()); + ur->add_undo_method(s, "set_radius", p_restore); + ur->commit_action(); + } + + if (Object::cast_to<CSGBox>(cs)) { + CSGBox *s = Object::cast_to<CSGBox>(cs); + if (p_cancel) { + switch (p_idx) { + case 0: s->set_width(p_restore); break; + case 1: s->set_height(p_restore); break; + case 2: s->set_depth(p_restore); break; + } + return; + } + + UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Change Box Shape Extents")); + static const char *method[3] = { "set_width", "set_height", "set_depth" }; + float current; + switch (p_idx) { + case 0: current = s->get_width(); break; + case 1: current = s->get_height(); break; + case 2: current = s->get_depth(); break; + } + + ur->add_do_method(s, method[p_idx], current); + ur->add_undo_method(s, method[p_idx], p_restore); + ur->commit_action(); + } + + if (Object::cast_to<CSGCylinder>(cs)) { + CSGCylinder *s = Object::cast_to<CSGCylinder>(cs); + if (p_cancel) { + if (p_idx == 0) + s->set_radius(p_restore); + else + s->set_height(p_restore); + return; + } + + UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo(); + if (p_idx == 0) { + ur->create_action(TTR("Change Cylinder Radius")); + ur->add_do_method(s, "set_radius", s->get_radius()); + ur->add_undo_method(s, "set_radius", p_restore); + } else { + ur->create_action(TTR("Change Cylinder Height")); + ur->add_do_method(s, "set_height", s->get_height()); + ur->add_undo_method(s, "set_height", p_restore); + } + + ur->commit_action(); + } + + if (Object::cast_to<CSGTorus>(cs)) { + CSGTorus *s = Object::cast_to<CSGTorus>(cs); + if (p_cancel) { + if (p_idx == 0) + s->set_inner_radius(p_restore); + else + s->set_outer_radius(p_restore); + return; + } + + UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo(); + if (p_idx == 0) { + ur->create_action(TTR("Change Torus Inner Radius")); + ur->add_do_method(s, "set_inner_radius", s->get_inner_radius()); + ur->add_undo_method(s, "set_inner_radius", p_restore); + } else { + ur->create_action(TTR("Change Torus Outer Radius")); + ur->add_do_method(s, "set_outer_radius", s->get_outer_radius()); + ur->add_undo_method(s, "set_outer_radius", p_restore); + } + + ur->commit_action(); + } +} +void CSGShapeSpatialGizmo::redraw() { + + clear(); + + Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/csg"); + Ref<Material> material = create_material("shape_material", gizmo_color); + + PoolVector<Vector3> faces = cs->get_brush_faces(); + + Vector<Vector3> lines; + lines.resize(faces.size() * 2); + { + PoolVector<Vector3>::Read r = faces.read(); + + for (int i = 0; i < lines.size(); i += 6) { + int f = i / 6; + for (int j = 0; j < 3; j++) { + int j_n = (j + 1) % 3; + lines[i + j * 2 + 0] = r[f * 3 + j]; + lines[i + j * 2 + 1] = r[f * 3 + j_n]; + } + } + } + + add_lines(lines, material); + add_collision_segments(lines); + + if (Object::cast_to<CSGSphere>(cs)) { + CSGSphere *s = Object::cast_to<CSGSphere>(cs); + + float r = s->get_radius(); + Vector<Vector3> handles; + handles.push_back(Vector3(r, 0, 0)); + add_handles(handles); + } + + if (Object::cast_to<CSGBox>(cs)) { + CSGBox *s = Object::cast_to<CSGBox>(cs); + + Vector<Vector3> handles; + handles.push_back(Vector3(s->get_width(), 0, 0)); + handles.push_back(Vector3(0, s->get_height(), 0)); + handles.push_back(Vector3(0, 0, s->get_depth())); + add_handles(handles); + } + + if (Object::cast_to<CSGCylinder>(cs)) { + CSGCylinder *s = Object::cast_to<CSGCylinder>(cs); + + Vector<Vector3> handles; + handles.push_back(Vector3(s->get_radius(), 0, 0)); + handles.push_back(Vector3(0, s->get_height() * 0.5, 0)); + add_handles(handles); + } + + if (Object::cast_to<CSGTorus>(cs)) { + CSGTorus *s = Object::cast_to<CSGTorus>(cs); + + Vector<Vector3> handles; + handles.push_back(Vector3(s->get_inner_radius(), 0, 0)); + handles.push_back(Vector3(s->get_outer_radius(), 0, 0)); + add_handles(handles); + } +} +CSGShapeSpatialGizmo::CSGShapeSpatialGizmo(CSGShape *p_cs) { + + cs = p_cs; + set_spatial_node(p_cs); +} + +Ref<SpatialEditorGizmo> EditorPluginCSG::create_spatial_gizmo(Spatial *p_spatial) { + if (Object::cast_to<CSGSphere>(p_spatial) || Object::cast_to<CSGBox>(p_spatial) || Object::cast_to<CSGCylinder>(p_spatial) || Object::cast_to<CSGTorus>(p_spatial) || Object::cast_to<CSGMesh>(p_spatial) || Object::cast_to<CSGPolygon>(p_spatial)) { + Ref<CSGShapeSpatialGizmo> csg = memnew(CSGShapeSpatialGizmo(Object::cast_to<CSGShape>(p_spatial))); + return csg; + } + + return Ref<SpatialEditorGizmo>(); +} + +EditorPluginCSG::EditorPluginCSG(EditorNode *p_editor) { + + EDITOR_DEF("editors/3d_gizmos/gizmo_colors/csg", Color(0.2, 0.5, 1, 0.1)); +} diff --git a/modules/csg/csg_gizmos.h b/modules/csg/csg_gizmos.h new file mode 100644 index 0000000000..68e916823b --- /dev/null +++ b/modules/csg/csg_gizmos.h @@ -0,0 +1,60 @@ +/*************************************************************************/ +/* csg_gizmos.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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 CSG_GIZMOS_H +#define CSG_GIZMOS_H + +#include "csg_shape.h" +#include "editor/editor_plugin.h" +#include "editor/spatial_editor_gizmos.h" + +class CSGShapeSpatialGizmo : public EditorSpatialGizmo { + + GDCLASS(CSGShapeSpatialGizmo, EditorSpatialGizmo); + + CSGShape *cs; + +public: + virtual String get_handle_name(int p_idx) const; + virtual Variant get_handle_value(int p_idx) const; + virtual void set_handle(int p_idx, Camera *p_camera, const Point2 &p_point); + virtual void commit_handle(int p_idx, const Variant &p_restore, bool p_cancel = false); + void redraw(); + CSGShapeSpatialGizmo(CSGShape *p_cs = NULL); +}; + +class EditorPluginCSG : public EditorPlugin { + GDCLASS(EditorPluginCSG, EditorPlugin) +public: + virtual Ref<SpatialEditorGizmo> create_spatial_gizmo(Spatial *p_spatial); + EditorPluginCSG(EditorNode *p_editor); +}; + +#endif // CSG_GIZMOS_H diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp new file mode 100644 index 0000000000..82db1871da --- /dev/null +++ b/modules/csg/csg_shape.cpp @@ -0,0 +1,2155 @@ +/*************************************************************************/ +/* csg_shape.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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 "csg_shape.h" +#include "scene/3d/path.h" + +void CSGShape::set_use_collision(bool p_enable) { + + if (use_collision == p_enable) + return; + + use_collision = p_enable; + + if (!is_inside_tree() || !is_root_shape()) + return; + + if (use_collision) { + root_collision_shape.instance(); + root_collision_instance = PhysicsServer::get_singleton()->body_create(PhysicsServer::BODY_MODE_STATIC); + PhysicsServer::get_singleton()->body_set_state(root_collision_instance, PhysicsServer::BODY_STATE_TRANSFORM, get_global_transform()); + PhysicsServer::get_singleton()->body_add_shape(root_collision_instance, root_collision_shape->get_rid()); + PhysicsServer::get_singleton()->body_set_space(root_collision_instance, get_world()->get_space()); + _make_dirty(); //force update + } else { + PhysicsServer::get_singleton()->free(root_collision_instance); + root_collision_instance = RID(); + root_collision_shape.unref(); + } +} + +bool CSGShape::is_using_collision() const { + return use_collision; +} + +bool CSGShape::is_root_shape() const { + + return !parent; +} + +void CSGShape::set_snap(float p_snap) { + snap = p_snap; +} + +float CSGShape::get_snap() const { + return snap; +} + +void CSGShape::_make_dirty() { + + if (!is_inside_tree()) + return; + + if (dirty) { + return; + } + + dirty = true; + + if (parent) { + parent->_make_dirty(); + } else { + //only parent will do + call_deferred("_update_shape"); + } +} + +CSGBrush *CSGShape::_get_brush() { + + if (dirty) { + if (brush) { + memdelete(brush); + } + brush = NULL; + + CSGBrush *n = _build_brush(); + + for (int i = 0; i < get_child_count(); i++) { + + CSGShape *child = Object::cast_to<CSGShape>(get_child(i)); + if (!child) + continue; + if (!child->is_visible_in_tree()) + continue; + + CSGBrush *n2 = child->_get_brush(); + if (!n2) + continue; + if (!n) { + n = memnew(CSGBrush); + + n->copy_from(*n2, child->get_transform()); + + } else { + + CSGBrush *nn = memnew(CSGBrush); + CSGBrush *nn2 = memnew(CSGBrush); + nn2->copy_from(*n2, child->get_transform()); + + CSGBrushOperation bop; + + switch (child->get_operation()) { + case CSGShape::OPERATION_UNION: bop.merge_brushes(CSGBrushOperation::OPERATION_UNION, *n, *nn2, *nn, snap); break; + case CSGShape::OPERATION_INTERSECTION: bop.merge_brushes(CSGBrushOperation::OPERATION_INTERSECTION, *n, *nn2, *nn, snap); break; + case CSGShape::OPERATION_SUBTRACTION: bop.merge_brushes(CSGBrushOperation::OPERATION_SUBSTRACTION, *n, *nn2, *nn, snap); break; + } + memdelete(n); + memdelete(nn2); + n = nn; + } + } + + if (n) { + AABB aabb; + for (int i = 0; i < n->faces.size(); i++) { + for (int j = 0; j < 3; j++) { + if (i == 0 && j == 0) + aabb.position = n->faces[i].vertices[j]; + else + aabb.expand_to(n->faces[i].vertices[j]); + } + } + node_aabb = aabb; + } else { + node_aabb = AABB(); + } + + brush = n; + + dirty = false; + } + + return brush; +} + +void CSGShape::_update_shape() { + + //print_line("updating shape for " + String(get_path())); + set_base(RID()); + root_mesh.unref(); //byebye root mesh + + CSGBrush *n = _get_brush(); + ERR_FAIL_COND(!n); + + OAHashMap<Vector3, Vector3> vec_map; + + Vector<int> face_count; + face_count.resize(n->materials.size() + 1); + for (int i = 0; i < face_count.size(); i++) { + face_count[i] = 0; + } + + for (int i = 0; i < n->faces.size(); i++) { + int mat = n->faces[i].material; + ERR_CONTINUE(mat < -1 || mat >= face_count.size()); + int idx = mat == -1 ? face_count.size() - 1 : mat; + if (n->faces[i].smooth) { + + Plane p(n->faces[i].vertices[0], n->faces[i].vertices[1], n->faces[i].vertices[2]); + + for (int j = 0; j < 3; j++) { + Vector3 v = n->faces[i].vertices[j]; + Vector3 add; + if (vec_map.lookup(v, add)) { + add += p.normal; + } else { + add = p.normal; + } + vec_map.set(v, add); + } + } + + face_count[idx]++; + } + + Vector<ShapeUpdateSurface> surfaces; + + surfaces.resize(face_count.size()); + + //create arrays + for (int i = 0; i < surfaces.size(); i++) { + + surfaces[i].vertices.resize(face_count[i] * 3); + surfaces[i].normals.resize(face_count[i] * 3); + surfaces[i].uvs.resize(face_count[i] * 3); + surfaces[i].last_added = 0; + + if (i != surfaces.size() - 1) { + surfaces[i].material = n->materials[i]; + } + + surfaces[i].verticesw = surfaces[i].vertices.write(); + surfaces[i].normalsw = surfaces[i].normals.write(); + surfaces[i].uvsw = surfaces[i].uvs.write(); + } + + //fill arrays + PoolVector<Vector3> physics_faces; + bool fill_physics_faces = false; + if (root_collision_shape.is_valid()) { + physics_faces.resize(n->faces.size() * 3); + fill_physics_faces = true; + } + + { + PoolVector<Vector3>::Write physicsw; + + if (fill_physics_faces) { + physicsw = physics_faces.write(); + } + + for (int i = 0; i < n->faces.size(); i++) { + + int order[3] = { 0, 1, 2 }; + + if (n->faces[i].invert) { + SWAP(order[1], order[2]); + } + + if (fill_physics_faces) { + physicsw[i * 3 + 0] = n->faces[i].vertices[order[0]]; + physicsw[i * 3 + 1] = n->faces[i].vertices[order[1]]; + physicsw[i * 3 + 2] = n->faces[i].vertices[order[2]]; + } + + int mat = n->faces[i].material; + ERR_CONTINUE(mat < -1 || mat >= face_count.size()); + int idx = mat == -1 ? face_count.size() - 1 : mat; + + int last = surfaces[idx].last_added; + + Plane p(n->faces[i].vertices[0], n->faces[i].vertices[1], n->faces[i].vertices[2]); + + for (int j = 0; j < 3; j++) { + + Vector3 v = n->faces[i].vertices[j]; + + Vector3 normal = p.normal; + + if (n->faces[i].smooth && vec_map.lookup(v, normal)) { + normal.normalize(); + } + + if (n->faces[i].invert) { + + normal = -normal; + } + + surfaces[idx].verticesw[last + order[j]] = v; + surfaces[idx].uvsw[last + order[j]] = n->faces[i].uvs[j]; + surfaces[idx].normalsw[last + order[j]] = normal; + } + + surfaces[idx].last_added += 3; + } + } + + root_mesh.instance(); + //create surfaces + + for (int i = 0; i < surfaces.size(); i++) { + + surfaces[i].verticesw = PoolVector<Vector3>::Write(); + surfaces[i].normalsw = PoolVector<Vector3>::Write(); + surfaces[i].uvsw = PoolVector<Vector2>::Write(); + + if (surfaces[i].last_added == 0) + continue; + + Array array; + array.resize(Mesh::ARRAY_MAX); + + array[Mesh::ARRAY_VERTEX] = surfaces[i].vertices; + array[Mesh::ARRAY_NORMAL] = surfaces[i].normals; + array[Mesh::ARRAY_TEX_UV] = surfaces[i].uvs; + + int idx = root_mesh->get_surface_count(); + root_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array); + root_mesh->surface_set_material(idx, surfaces[i].material); + } + + if (root_collision_shape.is_valid()) { + root_collision_shape->set_faces(physics_faces); + } + + set_base(root_mesh->get_rid()); +} +AABB CSGShape::get_aabb() const { + return node_aabb; +} + +PoolVector<Vector3> CSGShape::get_brush_faces() { + ERR_FAIL_COND_V(!is_inside_tree(), PoolVector<Vector3>()); + CSGBrush *b = _get_brush(); + if (!b) { + return PoolVector<Vector3>(); + } + + PoolVector<Vector3> faces; + int fc = b->faces.size(); + faces.resize(fc * 3); + { + PoolVector<Vector3>::Write w = faces.write(); + for (int i = 0; i < fc; i++) { + w[i * 3 + 0] = b->faces[i].vertices[0]; + w[i * 3 + 1] = b->faces[i].vertices[1]; + w[i * 3 + 2] = b->faces[i].vertices[2]; + } + } + + return faces; +} + +PoolVector<Face3> CSGShape::get_faces(uint32_t p_usage_flags) const { + + return PoolVector<Face3>(); +} + +void CSGShape::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE) { + + Node *parentn = get_parent(); + if (parentn) { + parent = Object::cast_to<CSGShape>(parentn); + } + + if (use_collision && is_root_shape()) { + root_collision_shape.instance(); + root_collision_instance = PhysicsServer::get_singleton()->body_create(PhysicsServer::BODY_MODE_STATIC); + PhysicsServer::get_singleton()->body_set_state(root_collision_instance, PhysicsServer::BODY_STATE_TRANSFORM, get_global_transform()); + PhysicsServer::get_singleton()->body_add_shape(root_collision_instance, root_collision_shape->get_rid()); + PhysicsServer::get_singleton()->body_set_space(root_collision_instance, get_world()->get_space()); + } + + _make_dirty(); + } + + if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) { + + //print_line("local xform changed"); + if (parent) { + parent->_make_dirty(); + } + } + + if (p_what == NOTIFICATION_EXIT_TREE) { + if (parent) + parent->_make_dirty(); + parent = NULL; + + if (use_collision && is_root_shape()) { + PhysicsServer::get_singleton()->free(root_collision_instance); + root_collision_instance = RID(); + root_collision_shape.unref(); + } + _make_dirty(); + } +} + +void CSGShape::set_operation(Operation p_operation) { + + operation = p_operation; + _make_dirty(); +} + +CSGShape::Operation CSGShape::get_operation() const { + return operation; +} + +void CSGShape::_validate_property(PropertyInfo &property) const { + if (is_inside_tree() && property.name.begins_with("use_collision") && !is_root_shape()) { + //hide collision if not root + property.usage = PROPERTY_USAGE_NOEDITOR; + } +} + +void CSGShape::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_update_shape"), &CSGShape::_update_shape); + ClassDB::bind_method(D_METHOD("is_root_shape"), &CSGShape::is_root_shape); + + ClassDB::bind_method(D_METHOD("set_operation", "operation"), &CSGShape::set_operation); + ClassDB::bind_method(D_METHOD("get_operation"), &CSGShape::get_operation); + + ClassDB::bind_method(D_METHOD("set_use_collision", "operation"), &CSGShape::set_use_collision); + ClassDB::bind_method(D_METHOD("is_using_collision"), &CSGShape::is_using_collision); + + ClassDB::bind_method(D_METHOD("set_snap", "snap"), &CSGShape::set_snap); + ClassDB::bind_method(D_METHOD("get_snap"), &CSGShape::get_snap); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "operation", PROPERTY_HINT_ENUM, "Union,Intersection,Subtraction"), "set_operation", "get_operation"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_collision"), "set_use_collision", "is_using_collision"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "snap", PROPERTY_HINT_RANGE, "0.0001,1,0.001"), "set_snap", "get_snap"); + + BIND_ENUM_CONSTANT(OPERATION_UNION); + BIND_ENUM_CONSTANT(OPERATION_INTERSECTION); + BIND_ENUM_CONSTANT(OPERATION_SUBTRACTION); +} + +CSGShape::CSGShape() { + brush = NULL; + set_notify_local_transform(true); + dirty = false; + parent = NULL; + use_collision = false; + operation = OPERATION_UNION; + snap = 0.001; +} + +CSGShape::~CSGShape() { + if (brush) { + memdelete(brush); + brush = NULL; + } +} +////////////////////////////////// + +CSGBrush *CSGCombiner::_build_brush() { + + return NULL; //does not build anything +} + +CSGCombiner::CSGCombiner() { +} + +///////////////////// + +CSGBrush *CSGPrimitive::_create_brush_from_arrays(const PoolVector<Vector3> &p_vertices, const PoolVector<Vector2> &p_uv, const PoolVector<bool> &p_smooth, const PoolVector<Ref<Material> > &p_materials) { + + CSGBrush *brush = memnew(CSGBrush); + + PoolVector<bool> invert; + invert.resize(p_vertices.size() / 3); + { + int ic = invert.size(); + PoolVector<bool>::Write w = invert.write(); + for (int i = 0; i < ic; i++) { + w[i] = invert_faces; + } + } + brush->build_from_faces(p_vertices, p_uv, p_smooth, p_materials, invert); + + return brush; +} + +void CSGPrimitive::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_invert_faces", "invert_faces"), &CSGPrimitive::set_invert_faces); + ClassDB::bind_method(D_METHOD("is_inverting_faces"), &CSGPrimitive::is_inverting_faces); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "invert_faces"), "set_invert_faces", "is_inverting_faces"); +} + +void CSGPrimitive::set_invert_faces(bool p_invert) { + if (invert_faces == p_invert) + return; + + invert_faces = p_invert; + + _make_dirty(); +} + +bool CSGPrimitive::is_inverting_faces() { + return invert_faces; +} + +CSGPrimitive::CSGPrimitive() { + invert_faces = false; +} + +///////////////////// + +CSGBrush *CSGMesh::_build_brush() { + + if (!mesh.is_valid()) + return NULL; + + PoolVector<Vector3> vertices; + PoolVector<bool> smooth; + PoolVector<Ref<Material> > materials; + PoolVector<Vector2> uvs; + + for (int i = 0; i < mesh->get_surface_count(); i++) { + + if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { + continue; + } + + Array arrays = mesh->surface_get_arrays(i); + + PoolVector<Vector3> avertices = arrays[Mesh::ARRAY_VERTEX]; + if (avertices.size() == 0) + continue; + + PoolVector<Vector3>::Read vr = avertices.read(); + + PoolVector<Vector3> anormals = arrays[Mesh::ARRAY_NORMAL]; + PoolVector<Vector3>::Read nr; + bool nr_used = false; + if (anormals.size()) { + nr = anormals.read(); + nr_used = true; + } + + PoolVector<Vector2> auvs = arrays[Mesh::ARRAY_TEX_UV]; + PoolVector<Vector2>::Read uvr; + bool uvr_used = false; + if (auvs.size()) { + uvr = auvs.read(); + uvr_used = true; + } + + Ref<Material> mat = mesh->surface_get_material(i); + + PoolVector<int> aindices = arrays[Mesh::ARRAY_INDEX]; + if (aindices.size()) { + int as = vertices.size(); + int is = aindices.size(); + + vertices.resize(as + is); + smooth.resize((as + is) / 3); + materials.resize((as + is) / 3); + uvs.resize(as + is); + + PoolVector<Vector3>::Write vw = vertices.write(); + PoolVector<bool>::Write sw = smooth.write(); + PoolVector<Vector2>::Write uvw = uvs.write(); + PoolVector<Ref<Material> >::Write mw = materials.write(); + + PoolVector<int>::Read ir = aindices.read(); + + for (int j = 0; j < is; j += 3) { + + Vector3 vertex[3]; + Vector3 normal[3]; + Vector2 uv[3]; + + for (int k = 0; k < 3; k++) { + int idx = ir[j + k]; + vertex[k] = vr[idx]; + if (nr_used) { + normal[k] = nr[idx]; + } + if (uvr_used) { + uv[k] = uvr[idx]; + } + } + + bool flat = normal[0].distance_to(normal[1]) < CMP_EPSILON && normal[0].distance_to(normal[2]) < CMP_EPSILON; + + vw[as + j + 0] = vertex[0]; + vw[as + j + 1] = vertex[1]; + vw[as + j + 2] = vertex[2]; + + uvw[as + j + 0] = uv[0]; + uvw[as + j + 1] = uv[1]; + uvw[as + j + 2] = uv[2]; + + sw[j / 3] = !flat; + mw[j / 3] = mat; + } + } else { + int is = vertices.size(); + int as = avertices.size(); + + vertices.resize(as + is); + smooth.resize((as + is) / 3); + uvs.resize(as + is); + materials.resize((as + is) / 3); + + PoolVector<Vector3>::Write vw = vertices.write(); + PoolVector<bool>::Write sw = smooth.write(); + PoolVector<Vector2>::Write uvw = uvs.write(); + PoolVector<Ref<Material> >::Write mw = materials.write(); + + for (int j = 0; j < is; j += 3) { + + Vector3 vertex[3]; + Vector3 normal[3]; + Vector2 uv[3]; + + for (int k = 0; k < 3; k++) { + vertex[k] = vr[j + k]; + if (nr_used) { + normal[k] = nr[j + k]; + } + if (uvr_used) { + uv[k] = uvr[j + k]; + } + } + + bool flat = normal[0].distance_to(normal[1]) < CMP_EPSILON && normal[0].distance_to(normal[2]) < CMP_EPSILON; + + vw[as + j + 0] = vertex[0]; + vw[as + j + 1] = vertex[1]; + vw[as + j + 2] = vertex[2]; + + uvw[as + j + 0] = uv[0]; + uvw[as + j + 1] = uv[1]; + uvw[as + j + 2] = uv[2]; + + sw[j / 3] = !flat; + mw[j / 3] = mat; + } + } + } + + //print_line("total vertices? " + itos(vertices.size())); + if (vertices.size() == 0) + return NULL; + + return _create_brush_from_arrays(vertices, uvs, smooth, materials); +} + +void CSGMesh::_mesh_changed() { + _make_dirty(); + update_gizmo(); +} + +void CSGMesh::_bind_methods() { + + ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &CSGMesh::set_mesh); + ClassDB::bind_method(D_METHOD("get_mesh"), &CSGMesh::get_mesh); + + ClassDB::bind_method(D_METHOD("_mesh_changed"), &CSGMesh::_mesh_changed); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); +} + +void CSGMesh::set_mesh(const Ref<Mesh> &p_mesh) { + + if (mesh == p_mesh) + return; + if (mesh.is_valid()) { + mesh->disconnect("changed", this, "_mesh_changed"); + } + mesh = p_mesh; + + if (mesh.is_valid()) { + mesh->connect("changed", this, "_mesh_changed"); + } + + _make_dirty(); +} + +Ref<Mesh> CSGMesh::get_mesh() { + return mesh; +} + +//////////////////////////////// + +CSGBrush *CSGSphere::_build_brush() { + + // set our bounding box + + CSGBrush *brush = memnew(CSGBrush); + + int face_count = rings * radial_segments * 2 - radial_segments * 2; + + bool invert_val = is_inverting_faces(); + Ref<Material> material = get_material(); + + PoolVector<Vector3> faces; + PoolVector<Vector2> uvs; + PoolVector<bool> smooth; + PoolVector<Ref<Material> > materials; + PoolVector<bool> invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + + { + + PoolVector<Vector3>::Write facesw = faces.write(); + PoolVector<Vector2>::Write uvsw = uvs.write(); + PoolVector<bool>::Write smoothw = smooth.write(); + PoolVector<Ref<Material> >::Write materialsw = materials.write(); + PoolVector<bool>::Write invertw = invert.write(); + + int face = 0; + + for (int i = 1; i <= rings; i++) { + double lat0 = Math_PI * (-0.5 + (double)(i - 1) / rings); + double z0 = Math::sin(lat0); + double zr0 = Math::cos(lat0); + double u0 = double(i - 1) / rings; + + double lat1 = Math_PI * (-0.5 + (double)i / rings); + double z1 = Math::sin(lat1); + double zr1 = Math::cos(lat1); + double u1 = double(i) / rings; + + for (int j = radial_segments; j >= 1; j--) { + + double lng0 = 2 * Math_PI * (double)(j - 1) / radial_segments; + double x0 = Math::cos(lng0); + double y0 = Math::sin(lng0); + double v0 = double(i - 1) / radial_segments; + + double lng1 = 2 * Math_PI * (double)(j) / radial_segments; + double x1 = Math::cos(lng1); + double y1 = Math::sin(lng1); + double v1 = double(i) / radial_segments; + + Vector3 v[4] = { + Vector3(x1 * zr0, z0, y1 * zr0) * radius, + Vector3(x1 * zr1, z1, y1 * zr1) * radius, + Vector3(x0 * zr1, z1, y0 * zr1) * radius, + Vector3(x0 * zr0, z0, y0 * zr0) * radius + }; + + Vector2 u[4] = { + Vector2(v1, u0), + Vector2(v1, u1), + Vector2(v0, u1), + Vector2(v0, u0), + + }; + + if (i < rings) { + + //face 1 + facesw[face * 3 + 0] = v[0]; + facesw[face * 3 + 1] = v[1]; + facesw[face * 3 + 2] = v[2]; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[1]; + uvsw[face * 3 + 2] = u[2]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + } + + if (i > 1) { + //face 2 + facesw[face * 3 + 0] = v[2]; + facesw[face * 3 + 1] = v[3]; + facesw[face * 3 + 2] = v[0]; + + uvsw[face * 3 + 0] = u[2]; + uvsw[face * 3 + 1] = u[3]; + uvsw[face * 3 + 2] = u[0]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + } + } + } + + if (face != face_count) { + ERR_PRINT("Face mismatch bug! fix code"); + } + } + + brush->build_from_faces(faces, uvs, smooth, materials, invert); + + return brush; +} + +void CSGSphere::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CSGSphere::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &CSGSphere::get_radius); + + ClassDB::bind_method(D_METHOD("set_radial_segments", "radial_segments"), &CSGSphere::set_radial_segments); + ClassDB::bind_method(D_METHOD("get_radial_segments"), &CSGSphere::get_radial_segments); + ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CSGSphere::set_rings); + ClassDB::bind_method(D_METHOD("get_rings"), &CSGSphere::get_rings); + + ClassDB::bind_method(D_METHOD("set_smooth_faces", "smooth_faces"), &CSGSphere::set_smooth_faces); + ClassDB::bind_method(D_METHOD("get_smooth_faces"), &CSGSphere::get_smooth_faces); + + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGSphere::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &CSGSphere::get_material); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1"), "set_radial_segments", "get_radial_segments"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1"), "set_rings", "get_rings"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_faces"), "set_smooth_faces", "get_smooth_faces"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "SpatialMaterial,ShaderMaterial"), "set_material", "get_material"); +} + +void CSGSphere::set_radius(const float p_radius) { + ERR_FAIL_COND(p_radius <= 0); + radius = p_radius; + _make_dirty(); + update_gizmo(); +} + +float CSGSphere::get_radius() const { + return radius; +} + +void CSGSphere::set_radial_segments(const int p_radial_segments) { + radial_segments = p_radial_segments > 4 ? p_radial_segments : 4; + _make_dirty(); + update_gizmo(); +} + +int CSGSphere::get_radial_segments() const { + return radial_segments; +} + +void CSGSphere::set_rings(const int p_rings) { + rings = p_rings > 1 ? p_rings : 1; + _make_dirty(); + update_gizmo(); +} + +int CSGSphere::get_rings() const { + return rings; +} + +void CSGSphere::set_smooth_faces(const bool p_smooth_faces) { + smooth_faces = p_smooth_faces; + _make_dirty(); +} + +bool CSGSphere::get_smooth_faces() const { + return smooth_faces; +} + +void CSGSphere::set_material(const Ref<Material> &p_material) { + + material = p_material; + _make_dirty(); +} + +Ref<Material> CSGSphere::get_material() const { + + return material; +} + +CSGSphere::CSGSphere() { + // defaults + radius = 1.0; + radial_segments = 12; + rings = 6; + smooth_faces = true; +} + +/////////////// + +CSGBrush *CSGBox::_build_brush() { + + // set our bounding box + + CSGBrush *brush = memnew(CSGBrush); + + int face_count = 12; //it's a cube.. + + bool invert_val = is_inverting_faces(); + Ref<Material> material = get_material(); + + PoolVector<Vector3> faces; + PoolVector<Vector2> uvs; + PoolVector<bool> smooth; + PoolVector<Ref<Material> > materials; + PoolVector<bool> invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + + { + + PoolVector<Vector3>::Write facesw = faces.write(); + PoolVector<Vector2>::Write uvsw = uvs.write(); + PoolVector<bool>::Write smoothw = smooth.write(); + PoolVector<Ref<Material> >::Write materialsw = materials.write(); + PoolVector<bool>::Write invertw = invert.write(); + + int face = 0; + + Vector3 vertex_mul(width, height, depth); + + { + + for (int i = 0; i < 6; i++) { + + Vector3 face_points[4]; + float uv_points[8] = { 0, 0, 0, 1, 1, 1, 1, 0 }; + + for (int j = 0; j < 4; j++) { + + float v[3]; + v[0] = 1.0; + v[1] = 1 - 2 * ((j >> 1) & 1); + v[2] = v[1] * (1 - 2 * (j & 1)); + + for (int k = 0; k < 3; k++) { + + if (i < 3) + face_points[j][(i + k) % 3] = v[k] * (i >= 3 ? -1 : 1); + else + face_points[3 - j][(i + k) % 3] = v[k] * (i >= 3 ? -1 : 1); + } + } + + Vector2 u[4]; + for (int j = 0; j < 4; j++) { + u[j] = Vector2(uv_points[j * 2 + 0], uv_points[j * 2 + 1]); + } + + //face 1 + facesw[face * 3 + 0] = face_points[0] * vertex_mul; + facesw[face * 3 + 1] = face_points[1] * vertex_mul; + facesw[face * 3 + 2] = face_points[2] * vertex_mul; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[1]; + uvsw[face * 3 + 2] = u[2]; + + smoothw[face] = false; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + //face 1 + facesw[face * 3 + 0] = face_points[2] * vertex_mul; + facesw[face * 3 + 1] = face_points[3] * vertex_mul; + facesw[face * 3 + 2] = face_points[0] * vertex_mul; + + uvsw[face * 3 + 0] = u[2]; + uvsw[face * 3 + 1] = u[3]; + uvsw[face * 3 + 2] = u[0]; + + smoothw[face] = false; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + } + } + + if (face != face_count) { + ERR_PRINT("Face mismatch bug! fix code"); + } + } + + brush->build_from_faces(faces, uvs, smooth, materials, invert); + + return brush; +} + +void CSGBox::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_width", "width"), &CSGBox::set_width); + ClassDB::bind_method(D_METHOD("get_width"), &CSGBox::get_width); + + ClassDB::bind_method(D_METHOD("set_height", "height"), &CSGBox::set_height); + ClassDB::bind_method(D_METHOD("get_height"), &CSGBox::get_height); + + ClassDB::bind_method(D_METHOD("set_depth", "depth"), &CSGBox::set_depth); + ClassDB::bind_method(D_METHOD("get_depth"), &CSGBox::get_depth); + + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGBox::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &CSGBox::get_material); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "width", PROPERTY_HINT_EXP_RANGE, "0.001,1000.0,0.001,or_greater"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "height", PROPERTY_HINT_EXP_RANGE, "0.001,1000.0,0.001,or_greater"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "depth", PROPERTY_HINT_EXP_RANGE, "0.001,1000.0,0.001,or_greater"), "set_depth", "get_depth"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "SpatialMaterial,ShaderMaterial"), "set_material", "get_material"); +} + +void CSGBox::set_width(const float p_width) { + width = p_width; + _make_dirty(); + update_gizmo(); +} + +float CSGBox::get_width() const { + return width; +} + +void CSGBox::set_height(const float p_height) { + height = p_height; + _make_dirty(); + update_gizmo(); +} + +float CSGBox::get_height() const { + return height; +} + +void CSGBox::set_depth(const float p_depth) { + depth = p_depth; + _make_dirty(); + update_gizmo(); +} + +float CSGBox::get_depth() const { + return depth; +} + +void CSGBox::set_material(const Ref<Material> &p_material) { + + material = p_material; + _make_dirty(); + update_gizmo(); +} + +Ref<Material> CSGBox::get_material() const { + + return material; +} + +CSGBox::CSGBox() { + // defaults + width = 1.0; + height = 1.0; + depth = 1.0; +} + +/////////////// + +CSGBrush *CSGCylinder::_build_brush() { + + // set our bounding box + + CSGBrush *brush = memnew(CSGBrush); + + int face_count = sides * (cone ? 1 : 2) + sides + (cone ? 0 : sides); + + bool invert_val = is_inverting_faces(); + Ref<Material> material = get_material(); + + PoolVector<Vector3> faces; + PoolVector<Vector2> uvs; + PoolVector<bool> smooth; + PoolVector<Ref<Material> > materials; + PoolVector<bool> invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + + { + + PoolVector<Vector3>::Write facesw = faces.write(); + PoolVector<Vector2>::Write uvsw = uvs.write(); + PoolVector<bool>::Write smoothw = smooth.write(); + PoolVector<Ref<Material> >::Write materialsw = materials.write(); + PoolVector<bool>::Write invertw = invert.write(); + + int face = 0; + + Vector3 vertex_mul(radius, height * 0.5, radius); + + { + + for (int i = 0; i < sides; i++) { + + float inc = float(i) / sides; + float inc_n = float((i + 1)) / sides; + + float ang = inc * Math_PI * 2.0; + float ang_n = inc_n * Math_PI * 2.0; + + Vector3 base(Math::cos(ang), 0, Math::sin(ang)); + Vector3 base_n(Math::cos(ang_n), 0, Math::sin(ang_n)); + + Vector3 face_points[4] = { + base + Vector3(0, -1, 0), + base_n + Vector3(0, -1, 0), + base_n * (cone ? 0.0 : 1.0) + Vector3(0, 1, 0), + base * (cone ? 0.0 : 1.0) + Vector3(0, 1, 0), + }; + + Vector2 u[4] = { + Vector2(inc, 0), + Vector2(inc_n, 0), + Vector2(inc_n, 1), + Vector2(inc, 1), + }; + + //side face 1 + facesw[face * 3 + 0] = face_points[0] * vertex_mul; + facesw[face * 3 + 1] = face_points[1] * vertex_mul; + facesw[face * 3 + 2] = face_points[2] * vertex_mul; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[1]; + uvsw[face * 3 + 2] = u[2]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + + if (!cone) { + //side face 2 + facesw[face * 3 + 0] = face_points[2] * vertex_mul; + facesw[face * 3 + 1] = face_points[3] * vertex_mul; + facesw[face * 3 + 2] = face_points[0] * vertex_mul; + + uvsw[face * 3 + 0] = u[2]; + uvsw[face * 3 + 1] = u[3]; + uvsw[face * 3 + 2] = u[0]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + face++; + } + + //bottom face 1 + facesw[face * 3 + 0] = face_points[1] * vertex_mul; + facesw[face * 3 + 1] = face_points[0] * vertex_mul; + facesw[face * 3 + 2] = Vector3(0, -1, 0) * vertex_mul; + + uvsw[face * 3 + 0] = Vector2(face_points[1].x, face_points[1].y) * 0.5 + Vector2(0.5, 0.5); + uvsw[face * 3 + 1] = Vector2(face_points[0].x, face_points[0].y) * 0.5 + Vector2(0.5, 0.5); + uvsw[face * 3 + 2] = Vector2(0.5, 0.5); + + smoothw[face] = false; + invertw[face] = invert_val; + materialsw[face] = material; + face++; + + if (!cone) { + //top face 1 + facesw[face * 3 + 0] = face_points[3] * vertex_mul; + facesw[face * 3 + 1] = face_points[2] * vertex_mul; + facesw[face * 3 + 2] = Vector3(0, 1, 0) * vertex_mul; + + uvsw[face * 3 + 0] = Vector2(face_points[1].x, face_points[1].y) * 0.5 + Vector2(0.5, 0.5); + uvsw[face * 3 + 1] = Vector2(face_points[0].x, face_points[0].y) * 0.5 + Vector2(0.5, 0.5); + uvsw[face * 3 + 2] = Vector2(0.5, 0.5); + + smoothw[face] = false; + invertw[face] = invert_val; + materialsw[face] = material; + face++; + } + } + } + + if (face != face_count) { + ERR_PRINT("Face mismatch bug! fix code"); + } + } + + brush->build_from_faces(faces, uvs, smooth, materials, invert); + + return brush; +} + +void CSGCylinder::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CSGCylinder::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &CSGCylinder::get_radius); + + ClassDB::bind_method(D_METHOD("set_height", "height"), &CSGCylinder::set_height); + ClassDB::bind_method(D_METHOD("get_height"), &CSGCylinder::get_height); + + ClassDB::bind_method(D_METHOD("set_sides", "sides"), &CSGCylinder::set_sides); + ClassDB::bind_method(D_METHOD("get_sides"), &CSGCylinder::get_sides); + + ClassDB::bind_method(D_METHOD("set_cone", "cone"), &CSGCylinder::set_cone); + ClassDB::bind_method(D_METHOD("is_cone"), &CSGCylinder::is_cone); + + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGCylinder::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &CSGCylinder::get_material); + + ClassDB::bind_method(D_METHOD("set_smooth_faces", "smooth_faces"), &CSGCylinder::set_smooth_faces); + ClassDB::bind_method(D_METHOD("get_smooth_faces"), &CSGCylinder::get_smooth_faces); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_EXP_RANGE, "0.001,1000.0,0.001,or_greater"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "height", PROPERTY_HINT_EXP_RANGE, "0.001,1000.0,0.001,or_greater"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "sides", PROPERTY_HINT_RANGE, "3,64,1"), "set_sides", "get_sides"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cone"), "set_cone", "is_cone"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_faces"), "set_smooth_faces", "get_smooth_faces"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "SpatialMaterial,ShaderMaterial"), "set_material", "get_material"); +} + +void CSGCylinder::set_radius(const float p_radius) { + radius = p_radius; + _make_dirty(); + update_gizmo(); +} + +float CSGCylinder::get_radius() const { + return radius; +} + +void CSGCylinder::set_height(const float p_height) { + height = p_height; + _make_dirty(); + update_gizmo(); +} + +float CSGCylinder::get_height() const { + return height; +} + +void CSGCylinder::set_sides(const int p_sides) { + ERR_FAIL_COND(p_sides < 3); + sides = p_sides; + _make_dirty(); + update_gizmo(); +} + +int CSGCylinder::get_sides() const { + return sides; +} + +void CSGCylinder::set_cone(const bool p_cone) { + cone = p_cone; + _make_dirty(); + update_gizmo(); +} + +bool CSGCylinder::is_cone() const { + return cone; +} + +void CSGCylinder::set_smooth_faces(const bool p_smooth_faces) { + smooth_faces = p_smooth_faces; + _make_dirty(); +} + +bool CSGCylinder::get_smooth_faces() const { + return smooth_faces; +} + +void CSGCylinder::set_material(const Ref<Material> &p_material) { + + material = p_material; + _make_dirty(); +} + +Ref<Material> CSGCylinder::get_material() const { + + return material; +} + +CSGCylinder::CSGCylinder() { + // defaults + radius = 1.0; + height = 1.0; + sides = 8; + cone = false; + smooth_faces = true; +} + +/////////////// + +CSGBrush *CSGTorus::_build_brush() { + + // set our bounding box + + float min_radius = inner_radius; + float max_radius = outer_radius; + + if (min_radius == max_radius) + return NULL; //sorry, can't + + if (min_radius > max_radius) { + SWAP(min_radius, max_radius); + } + + float radius = (max_radius - min_radius) * 0.5; + + CSGBrush *brush = memnew(CSGBrush); + + int face_count = ring_sides * sides * 2; + + bool invert_val = is_inverting_faces(); + Ref<Material> material = get_material(); + + PoolVector<Vector3> faces; + PoolVector<Vector2> uvs; + PoolVector<bool> smooth; + PoolVector<Ref<Material> > materials; + PoolVector<bool> invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + + { + + PoolVector<Vector3>::Write facesw = faces.write(); + PoolVector<Vector2>::Write uvsw = uvs.write(); + PoolVector<bool>::Write smoothw = smooth.write(); + PoolVector<Ref<Material> >::Write materialsw = materials.write(); + PoolVector<bool>::Write invertw = invert.write(); + + int face = 0; + + { + + for (int i = 0; i < sides; i++) { + + float inci = float(i) / sides; + float inci_n = float((i + 1)) / sides; + + float angi = inci * Math_PI * 2.0; + float angi_n = inci_n * Math_PI * 2.0; + + Vector3 normali = Vector3(Math::cos(angi), 0, Math::sin(angi)); + Vector3 normali_n = Vector3(Math::cos(angi_n), 0, Math::sin(angi_n)); + + for (int j = 0; j < ring_sides; j++) { + + float incj = float(j) / ring_sides; + float incj_n = float((j + 1)) / ring_sides; + + float angj = incj * Math_PI * 2.0; + float angj_n = incj_n * Math_PI * 2.0; + + Vector2 normalj = Vector2(Math::cos(angj), Math::sin(angj)) * radius + Vector2(min_radius + radius, 0); + Vector2 normalj_n = Vector2(Math::cos(angj_n), Math::sin(angj_n)) * radius + Vector2(min_radius + radius, 0); + + Vector3 face_points[4] = { + Vector3(normali.x * normalj.x, normalj.y, normali.z * normalj.x), + Vector3(normali.x * normalj_n.x, normalj_n.y, normali.z * normalj_n.x), + Vector3(normali_n.x * normalj_n.x, normalj_n.y, normali_n.z * normalj_n.x), + Vector3(normali_n.x * normalj.x, normalj.y, normali_n.z * normalj.x) + }; + + Vector2 u[4] = { + Vector2(inci, incj), + Vector2(inci, incj_n), + Vector2(inci_n, incj_n), + Vector2(inci_n, incj), + }; + + // face 1 + facesw[face * 3 + 0] = face_points[0]; + facesw[face * 3 + 1] = face_points[2]; + facesw[face * 3 + 2] = face_points[1]; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[2]; + uvsw[face * 3 + 2] = u[1]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + + //face 2 + facesw[face * 3 + 0] = face_points[3]; + facesw[face * 3 + 1] = face_points[2]; + facesw[face * 3 + 2] = face_points[0]; + + uvsw[face * 3 + 0] = u[3]; + uvsw[face * 3 + 1] = u[2]; + uvsw[face * 3 + 2] = u[0]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + face++; + } + } + } + + if (face != face_count) { + ERR_PRINT("Face mismatch bug! fix code"); + } + } + + brush->build_from_faces(faces, uvs, smooth, materials, invert); + + return brush; +} + +void CSGTorus::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_inner_radius", "radius"), &CSGTorus::set_inner_radius); + ClassDB::bind_method(D_METHOD("get_inner_radius"), &CSGTorus::get_inner_radius); + + ClassDB::bind_method(D_METHOD("set_outer_radius", "radius"), &CSGTorus::set_outer_radius); + ClassDB::bind_method(D_METHOD("get_outer_radius"), &CSGTorus::get_outer_radius); + + ClassDB::bind_method(D_METHOD("set_sides", "sides"), &CSGTorus::set_sides); + ClassDB::bind_method(D_METHOD("get_sides"), &CSGTorus::get_sides); + + ClassDB::bind_method(D_METHOD("set_ring_sides", "sides"), &CSGTorus::set_ring_sides); + ClassDB::bind_method(D_METHOD("get_ring_sides"), &CSGTorus::get_ring_sides); + + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGTorus::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &CSGTorus::get_material); + + ClassDB::bind_method(D_METHOD("set_smooth_faces", "smooth_faces"), &CSGTorus::set_smooth_faces); + ClassDB::bind_method(D_METHOD("get_smooth_faces"), &CSGTorus::get_smooth_faces); + + ADD_PROPERTY(PropertyInfo(Variant::REAL, "inner_radius", PROPERTY_HINT_EXP_RANGE, "0.001,1000.0,0.001,or_greater"), "set_inner_radius", "get_inner_radius"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "outer_radius", PROPERTY_HINT_EXP_RANGE, "0.001,1000.0,0.001,or_greater"), "set_outer_radius", "get_outer_radius"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "sides", PROPERTY_HINT_RANGE, "3,64,1"), "set_sides", "get_sides"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "ring_sides", PROPERTY_HINT_RANGE, "3,64,1"), "set_ring_sides", "get_ring_sides"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_faces"), "set_smooth_faces", "get_smooth_faces"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "SpatialMaterial,ShaderMaterial"), "set_material", "get_material"); +} + +void CSGTorus::set_inner_radius(const float p_inner_radius) { + inner_radius = p_inner_radius; + _make_dirty(); + update_gizmo(); +} + +float CSGTorus::get_inner_radius() const { + return inner_radius; +} + +void CSGTorus::set_outer_radius(const float p_outer_radius) { + outer_radius = p_outer_radius; + _make_dirty(); + update_gizmo(); +} + +float CSGTorus::get_outer_radius() const { + return outer_radius; +} + +void CSGTorus::set_sides(const int p_sides) { + ERR_FAIL_COND(p_sides < 3); + sides = p_sides; + _make_dirty(); + update_gizmo(); +} + +int CSGTorus::get_sides() const { + return sides; +} + +void CSGTorus::set_ring_sides(const int p_ring_sides) { + ERR_FAIL_COND(p_ring_sides < 3); + ring_sides = p_ring_sides; + _make_dirty(); + update_gizmo(); +} + +int CSGTorus::get_ring_sides() const { + return ring_sides; +} + +void CSGTorus::set_smooth_faces(const bool p_smooth_faces) { + smooth_faces = p_smooth_faces; + _make_dirty(); +} + +bool CSGTorus::get_smooth_faces() const { + return smooth_faces; +} + +void CSGTorus::set_material(const Ref<Material> &p_material) { + + material = p_material; + _make_dirty(); +} + +Ref<Material> CSGTorus::get_material() const { + + return material; +} + +CSGTorus::CSGTorus() { + // defaults + inner_radius = 2.0; + outer_radius = 3.0; + sides = 8; + ring_sides = 6; + smooth_faces = true; +} + +/////////////// + +CSGBrush *CSGPolygon::_build_brush() { + + // set our bounding box + + if (polygon.size() < 3) + return NULL; + + Vector<Point2> final_polygon = polygon; + + if (Triangulate::get_area(final_polygon) > 0) { + final_polygon.invert(); + } + + Vector<int> triangles = Geometry::triangulate_polygon(final_polygon); + + if (triangles.size() < 3) + return NULL; + + Path *path = NULL; + Ref<Curve3D> curve; + + if (mode == MODE_PATH) { + if (!has_node(path_node)) + return NULL; + Node *n = get_node(path_node); + if (!n) + return NULL; + path = Object::cast_to<Path>(n); + if (!path) + return NULL; + + if (path != path_cache) { + if (path_cache) { + path_cache->disconnect("tree_exited", this, "_path_exited"); + path_cache->disconnect("curve_changed", this, "_path_changed"); + path_cache = NULL; + } + + path_cache = path; + + if (path_cache) { + path_cache->connect("tree_exited", this, "_path_exited"); + path_cache->connect("curve_changed", this, "_path_changed"); + path_cache = NULL; + } + } + curve = path->get_curve(); + if (curve.is_null()) + return NULL; + if (curve->get_baked_length() <= 0) + return NULL; + } + CSGBrush *brush = memnew(CSGBrush); + + int face_count; + + switch (mode) { + case MODE_DEPTH: face_count = triangles.size() * 2 / 3 + (final_polygon.size()) * 2; break; + case MODE_SPIN: face_count = (spin_degrees < 360 ? triangles.size() * 2 / 3 : 0) + (final_polygon.size()) * 2 * spin_sides; break; + case MODE_PATH: { + float bl = curve->get_baked_length(); + int splits = MAX(2, Math::ceil(bl / path_interval)); + face_count = triangles.size() * 2 / 3 + splits * final_polygon.size() * 2; + } break; + } + + bool invert_val = is_inverting_faces(); + Ref<Material> material = get_material(); + + PoolVector<Vector3> faces; + PoolVector<Vector2> uvs; + PoolVector<bool> smooth; + PoolVector<Ref<Material> > materials; + PoolVector<bool> invert; + + faces.resize(face_count * 3); + uvs.resize(face_count * 3); + + smooth.resize(face_count); + materials.resize(face_count); + invert.resize(face_count); + + AABB aabb; //must be computed + { + + PoolVector<Vector3>::Write facesw = faces.write(); + PoolVector<Vector2>::Write uvsw = uvs.write(); + PoolVector<bool>::Write smoothw = smooth.write(); + PoolVector<Ref<Material> >::Write materialsw = materials.write(); + PoolVector<bool>::Write invertw = invert.write(); + + int face = 0; + + switch (mode) { + case MODE_DEPTH: { + + //add triangles, front and back + for (int i = 0; i < 2; i++) { + + for (int j = 0; j < triangles.size(); j += 3) { + for (int k = 0; k < 3; k++) { + int src[3] = { 0, i == 0 ? 1 : 2, i == 0 ? 2 : 1 }; + Vector2 p = final_polygon[triangles[j + src[k]]]; + Vector3 v = Vector3(p.x, p.y, 0); + if (i == 0) { + v.z -= depth; + } + facesw[face * 3 + k] = v; + } + + smoothw[face] = false; + materialsw[face] = material; + invertw[face] = invert_val; + face++; + } + } + + //add triangles for depth + for (int i = 0; i < final_polygon.size(); i++) { + + int i_n = (i + 1) % final_polygon.size(); + + Vector3 v[4] = { + Vector3(final_polygon[i].x, final_polygon[i].y, -depth), + Vector3(final_polygon[i_n].x, final_polygon[i_n].y, -depth), + Vector3(final_polygon[i_n].x, final_polygon[i_n].y, 0), + Vector3(final_polygon[i].x, final_polygon[i].y, 0), + }; + + Vector2 u[4] = { + Vector2(0, 0), + Vector2(0, 1), + Vector2(1, 1), + Vector2(1, 0) + }; + + // face 1 + facesw[face * 3 + 0] = v[0]; + facesw[face * 3 + 1] = v[1]; + facesw[face * 3 + 2] = v[2]; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[1]; + uvsw[face * 3 + 2] = u[2]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + + // face 2 + facesw[face * 3 + 0] = v[2]; + facesw[face * 3 + 1] = v[3]; + facesw[face * 3 + 2] = v[0]; + + uvsw[face * 3 + 0] = u[2]; + uvsw[face * 3 + 1] = u[3]; + uvsw[face * 3 + 2] = u[0]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + } + + } break; + case MODE_SPIN: { + + for (int i = 0; i < spin_sides; i++) { + + float inci = float(i) / spin_sides; + float inci_n = float((i + 1)) / spin_sides; + + float angi = -(inci * spin_degrees / 360.0) * Math_PI * 2.0; + float angi_n = -(inci_n * spin_degrees / 360.0) * Math_PI * 2.0; + + Vector3 normali = Vector3(Math::cos(angi), 0, Math::sin(angi)); + Vector3 normali_n = Vector3(Math::cos(angi_n), 0, Math::sin(angi_n)); + + //add triangles for depth + for (int j = 0; j < final_polygon.size(); j++) { + + int j_n = (j + 1) % final_polygon.size(); + + Vector3 v[4] = { + Vector3(normali.x * final_polygon[j].x, final_polygon[j].y, normali.z * final_polygon[j].x), + Vector3(normali.x * final_polygon[j_n].x, final_polygon[j_n].y, normali.z * final_polygon[j_n].x), + Vector3(normali_n.x * final_polygon[j_n].x, final_polygon[j_n].y, normali_n.z * final_polygon[j_n].x), + Vector3(normali_n.x * final_polygon[j].x, final_polygon[j].y, normali_n.z * final_polygon[j].x), + }; + + Vector2 u[4] = { + Vector2(0, 0), + Vector2(0, 1), + Vector2(1, 1), + Vector2(1, 0) + }; + + // face 1 + facesw[face * 3 + 0] = v[0]; + facesw[face * 3 + 1] = v[2]; + facesw[face * 3 + 2] = v[1]; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[2]; + uvsw[face * 3 + 2] = u[1]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + + // face 2 + facesw[face * 3 + 0] = v[2]; + facesw[face * 3 + 1] = v[0]; + facesw[face * 3 + 2] = v[3]; + + uvsw[face * 3 + 0] = u[2]; + uvsw[face * 3 + 1] = u[0]; + uvsw[face * 3 + 2] = u[3]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + } + + if (i == 0 && spin_degrees < 360) { + + for (int j = 0; j < triangles.size(); j += 3) { + for (int k = 0; k < 3; k++) { + int src[3] = { 0, 2, 1 }; + Vector2 p = final_polygon[triangles[j + src[k]]]; + Vector3 v = Vector3(p.x, p.y, 0); + facesw[face * 3 + k] = v; + } + + smoothw[face] = false; + materialsw[face] = material; + invertw[face] = invert_val; + face++; + } + } + + if (i == spin_sides - 1 && spin_degrees < 360) { + + for (int j = 0; j < triangles.size(); j += 3) { + for (int k = 0; k < 3; k++) { + int src[3] = { 0, 1, 2 }; + Vector2 p = final_polygon[triangles[j + src[k]]]; + Vector3 v = Vector3(normali_n.x * p.x, p.y, normali_n.z * p.x); + facesw[face * 3 + k] = v; + } + + smoothw[face] = false; + materialsw[face] = material; + invertw[face] = invert_val; + face++; + } + } + } + } break; + case MODE_PATH: { + + float bl = curve->get_baked_length(); + int splits = MAX(2, Math::ceil(bl / path_interval)); + + Transform path_to_this = get_global_transform().affine_inverse() * path->get_global_transform(); + + Transform prev_xf; + + Vector3 lookat_dir; + + if (path_rotation == PATH_ROTATION_POLYGON) { + lookat_dir = (path->get_global_transform().affine_inverse() * get_global_transform()).xform(Vector3(0, 0, -1)); + } else { + Vector3 p1, p2; + p1 = curve->interpolate_baked(0); + p2 = curve->interpolate_baked(0.1); + lookat_dir = (p2 - p1).normalized(); + } + + for (int i = 0; i <= splits; i++) { + + float ofs = i * path_interval; + + Transform xf; + xf.origin = curve->interpolate_baked(ofs); + + Vector3 local_dir; + + if (path_rotation == PATH_ROTATION_PATH_FOLLOW && ofs > 0) { + //before end + Vector3 p1 = curve->interpolate_baked(ofs - 0.1); + Vector3 p2 = curve->interpolate_baked(ofs); + local_dir = (p2 - p1).normalized(); + + } else { + local_dir = lookat_dir; + } + + xf = xf.looking_at(xf.origin + local_dir, Vector3(0, 1, 0)); + Basis rot(Vector3(0, 0, 1), curve->interpolate_baked_tilt(ofs)); + + xf = xf * rot; //post mult + + xf = path_to_this * xf; + + if (i > 0) { + //put triangles where they belong + //add triangles for depth + for (int j = 0; j < final_polygon.size(); j++) { + + int j_n = (j + 1) % final_polygon.size(); + + Vector3 v[4] = { + prev_xf.xform(Vector3(final_polygon[j].x, final_polygon[j].y, 0)), + prev_xf.xform(Vector3(final_polygon[j_n].x, final_polygon[j_n].y, 0)), + xf.xform(Vector3(final_polygon[j_n].x, final_polygon[j_n].y, 0)), + xf.xform(Vector3(final_polygon[j].x, final_polygon[j].y, 0)), + }; + + Vector2 u[4] = { + Vector2(0, 0), + Vector2(0, 1), + Vector2(1, 1), + Vector2(1, 0) + }; + + // face 1 + facesw[face * 3 + 0] = v[0]; + facesw[face * 3 + 1] = v[1]; + facesw[face * 3 + 2] = v[2]; + + uvsw[face * 3 + 0] = u[0]; + uvsw[face * 3 + 1] = u[1]; + uvsw[face * 3 + 2] = u[2]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + + // face 2 + facesw[face * 3 + 0] = v[2]; + facesw[face * 3 + 1] = v[3]; + facesw[face * 3 + 2] = v[0]; + + uvsw[face * 3 + 0] = u[2]; + uvsw[face * 3 + 1] = u[3]; + uvsw[face * 3 + 2] = u[0]; + + smoothw[face] = smooth_faces; + invertw[face] = invert_val; + materialsw[face] = material; + + face++; + } + } + + if (i == 0) { + + for (int j = 0; j < triangles.size(); j += 3) { + for (int k = 0; k < 3; k++) { + int src[3] = { 0, 1, 2 }; + Vector2 p = final_polygon[triangles[j + src[k]]]; + Vector3 v = Vector3(p.x, p.y, 0); + facesw[face * 3 + k] = xf.xform(v); + } + + smoothw[face] = false; + materialsw[face] = material; + invertw[face] = invert_val; + face++; + } + } + + if (i == splits) { + + for (int j = 0; j < triangles.size(); j += 3) { + for (int k = 0; k < 3; k++) { + int src[3] = { 0, 2, 1 }; + Vector2 p = final_polygon[triangles[j + src[k]]]; + Vector3 v = Vector3(p.x, p.y, 0); + facesw[face * 3 + k] = xf.xform(v); + } + + smoothw[face] = false; + materialsw[face] = material; + invertw[face] = invert_val; + face++; + } + } + + prev_xf = xf; + } + + } break; + } + + if (face != face_count) { + ERR_PRINT("Face mismatch bug! fix code"); + } + for (int i = 0; i < face_count * 3; i++) { + if (i == 0) { + aabb.position = facesw[i]; + } else { + aabb.expand_to(facesw[i]); + } + } + } + + brush->build_from_faces(faces, uvs, smooth, materials, invert); + + return brush; +} + +void CSGPolygon::_notification(int p_what) { + if (p_what == NOTIFICATION_EXIT_TREE) { + if (path_cache) { + path_cache->disconnect("tree_exited", this, "_path_exited"); + path_cache->disconnect("curve_changed", this, "_path_changed"); + path_cache = NULL; + } + } +} + +void CSGPolygon::_validate_property(PropertyInfo &property) const { + if (property.name.begins_with("spin") && mode != MODE_SPIN) { + property.usage = 0; + } + if (property.name.begins_with("path") && mode != MODE_PATH) { + property.usage = 0; + } + if (property.name == "depth" && mode != MODE_DEPTH) { + property.usage = 0; + } + + CSGShape::_validate_property(property); +} + +void CSGPolygon::_path_changed() { + _make_dirty(); + update_gizmo(); +} + +void CSGPolygon::_path_exited() { + path_cache = NULL; +} + +void CSGPolygon::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_polygon", "polygon"), &CSGPolygon::set_polygon); + ClassDB::bind_method(D_METHOD("get_polygon"), &CSGPolygon::get_polygon); + + ClassDB::bind_method(D_METHOD("set_mode", "mode"), &CSGPolygon::set_mode); + ClassDB::bind_method(D_METHOD("get_mode"), &CSGPolygon::get_mode); + + ClassDB::bind_method(D_METHOD("set_depth", "depth"), &CSGPolygon::set_depth); + ClassDB::bind_method(D_METHOD("get_depth"), &CSGPolygon::get_depth); + + ClassDB::bind_method(D_METHOD("set_spin_degrees", "degrees"), &CSGPolygon::set_spin_degrees); + ClassDB::bind_method(D_METHOD("get_spin_degrees"), &CSGPolygon::get_spin_degrees); + + ClassDB::bind_method(D_METHOD("set_spin_sides", "spin_sides"), &CSGPolygon::set_spin_sides); + ClassDB::bind_method(D_METHOD("get_spin_sides"), &CSGPolygon::get_spin_sides); + + ClassDB::bind_method(D_METHOD("set_path_node", "path"), &CSGPolygon::set_path_node); + ClassDB::bind_method(D_METHOD("get_path_node"), &CSGPolygon::get_path_node); + + ClassDB::bind_method(D_METHOD("set_path_interval", "distance"), &CSGPolygon::set_path_interval); + ClassDB::bind_method(D_METHOD("get_path_interval"), &CSGPolygon::get_path_interval); + + ClassDB::bind_method(D_METHOD("set_path_rotation", "mode"), &CSGPolygon::set_path_rotation); + ClassDB::bind_method(D_METHOD("get_path_rotation"), &CSGPolygon::get_path_rotation); + + ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGPolygon::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &CSGPolygon::get_material); + + ClassDB::bind_method(D_METHOD("set_smooth_faces", "smooth_faces"), &CSGPolygon::set_smooth_faces); + ClassDB::bind_method(D_METHOD("get_smooth_faces"), &CSGPolygon::get_smooth_faces); + + ClassDB::bind_method(D_METHOD("_is_editable_3d_polygon"), &CSGPolygon::_is_editable_3d_polygon); + ClassDB::bind_method(D_METHOD("_has_editable_3d_polygon_no_depth"), &CSGPolygon::_has_editable_3d_polygon_no_depth); + + ClassDB::bind_method(D_METHOD("_path_exited"), &CSGPolygon::_path_exited); + ClassDB::bind_method(D_METHOD("_path_changed"), &CSGPolygon::_path_changed); + + ADD_PROPERTY(PropertyInfo(Variant::POOL_VECTOR2_ARRAY, "polygon"), "set_polygon", "get_polygon"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Depth,Spin,Path"), "set_mode", "get_mode"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "depth", PROPERTY_HINT_EXP_RANGE, "0.001,1000.0,0.001,or_greater"), "set_depth", "get_depth"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "spin_degrees", PROPERTY_HINT_RANGE, "1,360,0.1"), "set_spin_degrees", "get_spin_degrees"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "spin_sides", PROPERTY_HINT_RANGE, "3,64,1"), "set_spin_sides", "get_spin_sides"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "path_node"), "set_path_node", "get_path_node"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "path_interval", PROPERTY_HINT_EXP_RANGE, "0.001,1000.0,0.001,or_greater"), "set_path_interval", "get_path_interval"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "path_rotation", PROPERTY_HINT_ENUM, "Polygon,Path,PathFollow"), "set_path_rotation", "get_path_rotation"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_faces"), "set_smooth_faces", "get_smooth_faces"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "SpatialMaterial,ShaderMaterial"), "set_material", "get_material"); + + BIND_ENUM_CONSTANT(MODE_DEPTH); + BIND_ENUM_CONSTANT(MODE_SPIN); + BIND_ENUM_CONSTANT(MODE_PATH); + + BIND_ENUM_CONSTANT(PATH_ROTATION_POLYGON); + BIND_ENUM_CONSTANT(PATH_ROTATION_PATH); + BIND_ENUM_CONSTANT(PATH_ROTATION_PATH_FOLLOW); +} + +void CSGPolygon::set_polygon(const Vector<Vector2> &p_polygon) { + polygon = p_polygon; + _make_dirty(); + update_gizmo(); +} + +Vector<Vector2> CSGPolygon::get_polygon() const { + return polygon; +} + +void CSGPolygon::set_mode(Mode p_mode) { + mode = p_mode; + _make_dirty(); + update_gizmo(); + _change_notify(); +} + +CSGPolygon::Mode CSGPolygon::get_mode() const { + return mode; +} + +void CSGPolygon::set_depth(const float p_depth) { + ERR_FAIL_COND(p_depth < 0.001); + depth = p_depth; + _make_dirty(); + update_gizmo(); +} + +float CSGPolygon::get_depth() const { + return depth; +} + +void CSGPolygon::set_spin_degrees(const float p_spin_degrees) { + ERR_FAIL_COND(p_spin_degrees < 0.01 || p_spin_degrees > 360); + spin_degrees = p_spin_degrees; + _make_dirty(); + update_gizmo(); +} + +float CSGPolygon::get_spin_degrees() const { + return spin_degrees; +} + +void CSGPolygon::set_spin_sides(const int p_spin_sides) { + ERR_FAIL_COND(p_spin_sides < 3); + spin_sides = p_spin_sides; + _make_dirty(); + update_gizmo(); +} + +int CSGPolygon::get_spin_sides() const { + return spin_sides; +} + +void CSGPolygon::set_path_node(const NodePath &p_path) { + path_node = p_path; + _make_dirty(); + update_gizmo(); +} + +NodePath CSGPolygon::get_path_node() const { + return path_node; +} + +void CSGPolygon::set_path_interval(float p_interval) { + ERR_FAIL_COND(p_interval < 0.001); + path_interval = p_interval; + _make_dirty(); + update_gizmo(); +} +float CSGPolygon::get_path_interval() const { + return path_interval; +} + +void CSGPolygon::set_path_rotation(PathRotation p_rotation) { + path_rotation = p_rotation; + _make_dirty(); + update_gizmo(); +} + +CSGPolygon::PathRotation CSGPolygon::get_path_rotation() const { + return path_rotation; +} + +void CSGPolygon::set_smooth_faces(const bool p_smooth_faces) { + smooth_faces = p_smooth_faces; + _make_dirty(); +} + +bool CSGPolygon::get_smooth_faces() const { + return smooth_faces; +} + +void CSGPolygon::set_material(const Ref<Material> &p_material) { + + material = p_material; + _make_dirty(); +} + +Ref<Material> CSGPolygon::get_material() const { + + return material; +} + +bool CSGPolygon::_is_editable_3d_polygon() const { + return true; +} + +bool CSGPolygon::_has_editable_3d_polygon_no_depth() const { + return true; +} + +CSGPolygon::CSGPolygon() { + // defaults + mode = MODE_DEPTH; + polygon.push_back(Vector2(0, 0)); + polygon.push_back(Vector2(0, 1)); + polygon.push_back(Vector2(1, 1)); + polygon.push_back(Vector2(1, 0)); + depth = 1.0; + spin_degrees = 360; + spin_sides = 8; + smooth_faces = false; + path_interval = 1; + path_rotation = PATH_ROTATION_PATH; + path_cache = NULL; +} diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h new file mode 100644 index 0000000000..cbb5c7e041 --- /dev/null +++ b/modules/csg/csg_shape.h @@ -0,0 +1,390 @@ +/*************************************************************************/ +/* csg_shape.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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 CSG_SHAPE_H +#define CSG_SHAPE_H + +#define CSGJS_HEADER_ONLY + +#include "csg.h" +#include "scene/3d/visual_instance.h" +#include "scene/resources/concave_polygon_shape.h" + +class CSGShape : public VisualInstance { + GDCLASS(CSGShape, VisualInstance); + +public: + enum Operation { + OPERATION_UNION, + OPERATION_INTERSECTION, + OPERATION_SUBTRACTION, + + }; + +private: + Operation operation; + CSGShape *parent; + + CSGBrush *brush; + + AABB node_aabb; + + bool dirty; + float snap; + + bool use_collision; + Ref<ConcavePolygonShape> root_collision_shape; + RID root_collision_instance; + + Ref<ArrayMesh> root_mesh; + + struct Vector3Hasher { + _ALWAYS_INLINE_ uint32_t hash(const Vector3 &p_vec3) const { + uint32_t h = hash_djb2_one_float(p_vec3.x); + h = hash_djb2_one_float(p_vec3.y, h); + h = hash_djb2_one_float(p_vec3.z, h); + return h; + } + }; + + struct ShapeUpdateSurface { + PoolVector<Vector3> vertices; + PoolVector<Vector3> normals; + PoolVector<Vector2> uvs; + Ref<Material> material; + int last_added; + + PoolVector<Vector3>::Write verticesw; + PoolVector<Vector3>::Write normalsw; + PoolVector<Vector2>::Write uvsw; + }; + + void _update_shape(); + +protected: + void _notification(int p_what); + virtual CSGBrush *_build_brush() = 0; + void _make_dirty(); + + static void _bind_methods(); + + friend class CSGCombiner; + CSGBrush *_get_brush(); + + virtual void _validate_property(PropertyInfo &property) const; + +public: + void set_operation(Operation p_operation); + Operation get_operation() const; + + virtual PoolVector<Vector3> get_brush_faces(); + + virtual AABB get_aabb() const; + virtual PoolVector<Face3> get_faces(uint32_t p_usage_flags) const; + + void set_use_collision(bool p_enable); + bool is_using_collision() const; + + void set_snap(float p_snap); + float get_snap() const; + + bool is_root_shape() const; + CSGShape(); + ~CSGShape(); +}; + +VARIANT_ENUM_CAST(CSGShape::Operation) + +class CSGCombiner : public CSGShape { + GDCLASS(CSGCombiner, CSGShape) +private: + virtual CSGBrush *_build_brush(); + +public: + CSGCombiner(); +}; + +class CSGPrimitive : public CSGShape { + GDCLASS(CSGPrimitive, CSGShape) + +private: + bool invert_faces; + +protected: + CSGBrush *_create_brush_from_arrays(const PoolVector<Vector3> &p_vertices, const PoolVector<Vector2> &p_uv, const PoolVector<bool> &p_smooth, const PoolVector<Ref<Material> > &p_materials); + static void _bind_methods(); + +public: + void set_invert_faces(bool p_invert); + bool is_inverting_faces(); + + CSGPrimitive(); +}; + +class CSGMesh : public CSGPrimitive { + GDCLASS(CSGMesh, CSGPrimitive) + + virtual CSGBrush *_build_brush(); + + Ref<Mesh> mesh; + + void _mesh_changed(); + +protected: + static void _bind_methods(); + +public: + void set_mesh(const Ref<Mesh> &p_mesh); + Ref<Mesh> get_mesh(); +}; + +class CSGSphere : public CSGPrimitive { + + GDCLASS(CSGSphere, CSGPrimitive) + virtual CSGBrush *_build_brush(); + + Ref<Material> material; + bool smooth_faces; + float radius; + int radial_segments; + int rings; + +protected: + static void _bind_methods(); + +public: + void set_radius(const float p_radius); + float get_radius() const; + + void set_radial_segments(const int p_radial_segments); + int get_radial_segments() const; + + void set_rings(const int p_rings); + int get_rings() const; + + void set_material(const Ref<Material> &p_material); + Ref<Material> get_material() const; + + void set_smooth_faces(bool p_smooth_faces); + bool get_smooth_faces() const; + + CSGSphere(); +}; + +class CSGBox : public CSGPrimitive { + + GDCLASS(CSGBox, CSGPrimitive) + virtual CSGBrush *_build_brush(); + + Ref<Material> material; + float width; + float height; + float depth; + +protected: + static void _bind_methods(); + +public: + void set_width(const float p_width); + float get_width() const; + + void set_height(const float p_height); + float get_height() const; + + void set_depth(const float p_depth); + float get_depth() const; + + void set_material(const Ref<Material> &p_material); + Ref<Material> get_material() const; + + CSGBox(); +}; + +class CSGCylinder : public CSGPrimitive { + + GDCLASS(CSGCylinder, CSGPrimitive) + virtual CSGBrush *_build_brush(); + + Ref<Material> material; + float radius; + float height; + int sides; + bool cone; + bool smooth_faces; + +protected: + static void _bind_methods(); + +public: + void set_radius(const float p_radius); + float get_radius() const; + + void set_height(const float p_height); + float get_height() const; + + void set_sides(const int p_sides); + int get_sides() const; + + void set_cone(const bool p_cone); + bool is_cone() const; + + void set_smooth_faces(bool p_smooth_faces); + bool get_smooth_faces() const; + + void set_material(const Ref<Material> &p_material); + Ref<Material> get_material() const; + + CSGCylinder(); +}; + +class CSGTorus : public CSGPrimitive { + + GDCLASS(CSGTorus, CSGPrimitive) + virtual CSGBrush *_build_brush(); + + Ref<Material> material; + float inner_radius; + float outer_radius; + int sides; + int ring_sides; + bool smooth_faces; + +protected: + static void _bind_methods(); + +public: + void set_inner_radius(const float p_inner_radius); + float get_inner_radius() const; + + void set_outer_radius(const float p_outer_radius); + float get_outer_radius() const; + + void set_sides(const int p_sides); + int get_sides() const; + + void set_ring_sides(const int p_ring_sides); + int get_ring_sides() const; + + void set_smooth_faces(bool p_smooth_faces); + bool get_smooth_faces() const; + + void set_material(const Ref<Material> &p_material); + Ref<Material> get_material() const; + + CSGTorus(); +}; + +class CSGPolygon : public CSGPrimitive { + + GDCLASS(CSGPolygon, CSGPrimitive) + +public: + enum Mode { + MODE_DEPTH, + MODE_SPIN, + MODE_PATH + }; + + enum PathRotation { + PATH_ROTATION_POLYGON, + PATH_ROTATION_PATH, + PATH_ROTATION_PATH_FOLLOW, + }; + +private: + virtual CSGBrush *_build_brush(); + + Vector<Vector2> polygon; + Ref<Material> material; + + Mode mode; + + float depth; + + float spin_degrees; + int spin_sides; + + NodePath path_node; + float path_interval; + PathRotation path_rotation; + + Node *path_cache; + + bool smooth_faces; + + bool _is_editable_3d_polygon() const; + bool _has_editable_3d_polygon_no_depth() const; + + void _path_changed(); + void _path_exited(); + +protected: + static void _bind_methods(); + virtual void _validate_property(PropertyInfo &property) const; + void _notification(int p_what); + +public: + void set_polygon(const Vector<Vector2> &p_polygon); + Vector<Vector2> get_polygon() const; + + void set_mode(Mode p_mode); + Mode get_mode() const; + + void set_depth(float p_depth); + float get_depth() const; + + void set_spin_degrees(float p_spin_degrees); + float get_spin_degrees() const; + + void set_spin_sides(int p_sides); + int get_spin_sides() const; + + void set_path_node(const NodePath &p_path); + NodePath get_path_node() const; + + void set_path_interval(float p_interval); + float get_path_interval() const; + + void set_path_rotation(PathRotation p_rotation); + PathRotation get_path_rotation() const; + + void set_smooth_faces(bool p_smooth_faces); + bool get_smooth_faces() const; + + void set_material(const Ref<Material> &p_material); + Ref<Material> get_material() const; + + CSGPolygon(); +}; + +VARIANT_ENUM_CAST(CSGPolygon::Mode) +VARIANT_ENUM_CAST(CSGPolygon::PathRotation) + +#endif // CSG_SHAPE_H diff --git a/modules/csg/doc_classes/CSGBox.xml b/modules/csg/doc_classes/CSGBox.xml new file mode 100644 index 0000000000..80455fda80 --- /dev/null +++ b/modules/csg/doc_classes/CSGBox.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CSGBox" inherits="CSGPrimitive" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="depth" type="float" setter="set_depth" getter="get_depth"> + </member> + <member name="height" type="float" setter="set_height" getter="get_height"> + </member> + <member name="material" type="Material" setter="set_material" getter="get_material"> + </member> + <member name="width" type="float" setter="set_width" getter="get_width"> + </member> + </members> + <constants> + </constants> +</class> diff --git a/modules/csg/doc_classes/CSGCombiner.xml b/modules/csg/doc_classes/CSGCombiner.xml new file mode 100644 index 0000000000..b2265d7703 --- /dev/null +++ b/modules/csg/doc_classes/CSGCombiner.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CSGCombiner" inherits="CSGShape" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/modules/csg/doc_classes/CSGCylinder.xml b/modules/csg/doc_classes/CSGCylinder.xml new file mode 100644 index 0000000000..0cab26ad3d --- /dev/null +++ b/modules/csg/doc_classes/CSGCylinder.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CSGCylinder" inherits="CSGPrimitive" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="cone" type="bool" setter="set_cone" getter="is_cone"> + </member> + <member name="height" type="float" setter="set_height" getter="get_height"> + </member> + <member name="material" type="Material" setter="set_material" getter="get_material"> + </member> + <member name="radius" type="float" setter="set_radius" getter="get_radius"> + </member> + <member name="sides" type="int" setter="set_sides" getter="get_sides"> + </member> + <member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces"> + </member> + </members> + <constants> + </constants> +</class> diff --git a/modules/csg/doc_classes/CSGMesh.xml b/modules/csg/doc_classes/CSGMesh.xml new file mode 100644 index 0000000000..e5c3e5ccf3 --- /dev/null +++ b/modules/csg/doc_classes/CSGMesh.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CSGMesh" inherits="CSGPrimitive" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="mesh" type="Mesh" setter="set_mesh" getter="get_mesh"> + </member> + </members> + <constants> + </constants> +</class> diff --git a/modules/csg/doc_classes/CSGPolygon.xml b/modules/csg/doc_classes/CSGPolygon.xml new file mode 100644 index 0000000000..379c512d6a --- /dev/null +++ b/modules/csg/doc_classes/CSGPolygon.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CSGPolygon" inherits="CSGPrimitive" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="depth" type="float" setter="set_depth" getter="get_depth"> + </member> + <member name="material" type="Material" setter="set_material" getter="get_material"> + </member> + <member name="mode" type="int" setter="set_mode" getter="get_mode" enum="CSGPolygon.Mode"> + </member> + <member name="path_interval" type="float" setter="set_path_interval" getter="get_path_interval"> + </member> + <member name="path_node" type="NodePath" setter="set_path_node" getter="get_path_node"> + </member> + <member name="path_rotation" type="int" setter="set_path_rotation" getter="get_path_rotation" enum="CSGPolygon.PathRotation"> + </member> + <member name="polygon" type="PoolVector2Array" setter="set_polygon" getter="get_polygon"> + </member> + <member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces"> + </member> + <member name="spin_degrees" type="float" setter="set_spin_degrees" getter="get_spin_degrees"> + </member> + <member name="spin_sides" type="int" setter="set_spin_sides" getter="get_spin_sides"> + </member> + </members> + <constants> + <constant name="MODE_DEPTH" value="0" enum="Mode"> + </constant> + <constant name="MODE_SPIN" value="1" enum="Mode"> + </constant> + <constant name="MODE_PATH" value="2" enum="Mode"> + </constant> + <constant name="PATH_ROTATION_POLYGON" value="0" enum="PathRotation"> + </constant> + <constant name="PATH_ROTATION_PATH" value="1" enum="PathRotation"> + </constant> + <constant name="PATH_ROTATION_PATH_FOLLOW" value="2" enum="PathRotation"> + </constant> + </constants> +</class> diff --git a/modules/csg/doc_classes/CSGPrimitive.xml b/modules/csg/doc_classes/CSGPrimitive.xml new file mode 100644 index 0000000000..bf41c40f22 --- /dev/null +++ b/modules/csg/doc_classes/CSGPrimitive.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CSGPrimitive" inherits="CSGShape" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="invert_faces" type="bool" setter="set_invert_faces" getter="is_inverting_faces"> + </member> + </members> + <constants> + </constants> +</class> diff --git a/modules/csg/doc_classes/CSGShape.xml b/modules/csg/doc_classes/CSGShape.xml new file mode 100644 index 0000000000..cf236a4207 --- /dev/null +++ b/modules/csg/doc_classes/CSGShape.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CSGShape" inherits="VisualInstance" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="is_root_shape" qualifiers="const"> + <return type="bool"> + </return> + <description> + </description> + </method> + </methods> + <members> + <member name="operation" type="int" setter="set_operation" getter="get_operation" enum="CSGShape.Operation"> + </member> + <member name="snap" type="float" setter="set_snap" getter="get_snap"> + </member> + <member name="use_collision" type="bool" setter="set_use_collision" getter="is_using_collision"> + </member> + </members> + <constants> + <constant name="OPERATION_UNION" value="0" enum="Operation"> + </constant> + <constant name="OPERATION_INTERSECTION" value="1" enum="Operation"> + </constant> + <constant name="OPERATION_SUBTRACTION" value="2" enum="Operation"> + </constant> + </constants> +</class> diff --git a/modules/csg/doc_classes/CSGSphere.xml b/modules/csg/doc_classes/CSGSphere.xml new file mode 100644 index 0000000000..520368506e --- /dev/null +++ b/modules/csg/doc_classes/CSGSphere.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CSGSphere" inherits="CSGPrimitive" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="material" type="Material" setter="set_material" getter="get_material"> + </member> + <member name="radial_segments" type="int" setter="set_radial_segments" getter="get_radial_segments"> + </member> + <member name="radius" type="float" setter="set_radius" getter="get_radius"> + </member> + <member name="rings" type="int" setter="set_rings" getter="get_rings"> + </member> + <member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces"> + </member> + </members> + <constants> + </constants> +</class> diff --git a/modules/csg/doc_classes/CSGTorus.xml b/modules/csg/doc_classes/CSGTorus.xml new file mode 100644 index 0000000000..58bbef2600 --- /dev/null +++ b/modules/csg/doc_classes/CSGTorus.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CSGTorus" inherits="CSGPrimitive" category="Core" version="3.1"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <members> + <member name="inner_radius" type="float" setter="set_inner_radius" getter="get_inner_radius"> + </member> + <member name="material" type="Material" setter="set_material" getter="get_material"> + </member> + <member name="outer_radius" type="float" setter="set_outer_radius" getter="get_outer_radius"> + </member> + <member name="ring_sides" type="int" setter="set_ring_sides" getter="get_ring_sides"> + </member> + <member name="sides" type="int" setter="set_sides" getter="get_sides"> + </member> + <member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces"> + </member> + </members> + <constants> + </constants> +</class> diff --git a/modules/csg/register_types.cpp b/modules/csg/register_types.cpp new file mode 100644 index 0000000000..020724ee59 --- /dev/null +++ b/modules/csg/register_types.cpp @@ -0,0 +1,59 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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" + +#include "csg_shape.h" +#include "csg_gizmos.h" + +void register_csg_types() { + +#ifndef _3D_DISABLED + + ClassDB::register_virtual_class<CSGShape>(); + ClassDB::register_virtual_class<CSGPrimitive>(); + ClassDB::register_class<CSGMesh>(); + ClassDB::register_class<CSGSphere>(); + ClassDB::register_class<CSGBox>(); + ClassDB::register_class<CSGCylinder>(); + ClassDB::register_class<CSGTorus>(); + ClassDB::register_class<CSGPolygon>(); + ClassDB::register_class<CSGCombiner>(); + +#ifdef TOOLS_ENABLED + EditorPlugins::add_by_type<EditorPluginCSG>(); +#endif +#endif + +} + +void unregister_csg_types() { + +} diff --git a/modules/csg/register_types.h b/modules/csg/register_types.h new file mode 100644 index 0000000000..49490d31d3 --- /dev/null +++ b/modules/csg/register_types.h @@ -0,0 +1,32 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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_csg_types(); +void unregister_csg_types(); diff --git a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml index 7bee63019b..d5fd4bff09 100644 --- a/modules/enet/doc_classes/NetworkedMultiplayerENet.xml +++ b/modules/enet/doc_classes/NetworkedMultiplayerENet.xml @@ -16,6 +16,8 @@ <method name="close_connection"> <return type="void"> </return> + <argument index="0" name="wait_usec" type="int" default="100"> + </argument> <description> Closes the connection. Ignored if no connection is currently established. If this is a server it tries to notify all clients before forcibly disconnecting them. If this is a client it simply closes the connection to the server. </description> @@ -23,7 +25,7 @@ <method name="create_client"> <return type="int" enum="Error"> </return> - <argument index="0" name="ip" type="String"> + <argument index="0" name="address" type="String"> </argument> <argument index="1" name="port" type="int"> </argument> @@ -31,8 +33,10 @@ </argument> <argument index="3" name="out_bandwidth" type="int" default="0"> </argument> + <argument index="4" name="client_port" type="int" default="0"> + </argument> <description> - Create client that connects to a server at address [code]ip[/code] using specified [code]port[/code]. The given IP needs to be in IPv4 or IPv6 address format, for example: [code]192.168.1.1[/code]. The [code]port[/code] is the port the server is listening on. The [code]in_bandwidth[/code] and [code]out_bandwidth[/code] parameters can be used to limit the incoming and outgoing bandwidth to the given number of bytes per second. The default of 0 means unlimited bandwidth. Note that ENet will strategically drop packets on specific sides of a connection between peers to ensure the peer's bandwidth is not overwhelmed. The bandwidth parameters also determine the window size of a connection which limits the amount of reliable packets that may be in transit at any given time. Returns [code]OK[/code] if a client was created, [code]ERR_ALREADY_IN_USE[/code] if this NetworkedMultiplayerEnet instance already has an open connection (in which case you need to call [method close_connection] first) or [code]ERR_CANT_CREATE[/code] if the client could not be created. + Create client that connects to a server at [code]address[/code] using specified [code]port[/code]. The given address needs to be either a fully qualified domain nome (e.g. [code]www.example.com[/code]) or an IP address in IPv4 or IPv6 format (e.g. [code]192.168.1.1[/code]). The [code]port[/code] is the port the server is listening on. The [code]in_bandwidth[/code] and [code]out_bandwidth[/code] parameters can be used to limit the incoming and outgoing bandwidth to the given number of bytes per second. The default of 0 means unlimited bandwidth. Note that ENet will strategically drop packets on specific sides of a connection between peers to ensure the peer's bandwidth is not overwhelmed. The bandwidth parameters also determine the window size of a connection which limits the amount of reliable packets that may be in transit at any given time. Returns [code]OK[/code] if a client was created, [code]ERR_ALREADY_IN_USE[/code] if this NetworkedMultiplayerEnet instance already has an open connection (in which case you need to call [method close_connection] first) or [code]ERR_CANT_CREATE[/code] if the client could not be created. If [code]client_port[/code] is specified, the client will also listen to the given port, this is useful in some NAT traveral technique. </description> </method> <method name="create_server"> @@ -50,6 +54,49 @@ Create server that listens to connections via [code]port[/code]. The port needs to be an available, unused port between 0 and 65535. Note that ports below 1024 are privileged and may require elevated permissions depending on the platform. To change the interface the server listens on, use [method set_bind_ip]. The default IP is the wildcard [code]*[/code], which listens on all available interfaces. [code]max_clients[/code] is the maximum number of clients that are allowed at once, any number up to 4096 may be used, although the achievable number of simultaneous clients may be far lower and depends on the application. For additional details on the bandwidth parameters, see [method create_client]. Returns [code]OK[/code] if a server was created, [code]ERR_ALREADY_IN_USE[/code] if this NetworkedMultiplayerEnet instance already has an open connection (in which case you need to call [method close_connection] first) or [code]ERR_CANT_CREATE[/code] if the server could not be created. </description> </method> + <method name="disconnect_peer"> + <return type="void"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <argument index="1" name="now" type="bool" default="false"> + </argument> + <description> + Disconnect the given peer. If "now" is set to true, the connection will be closed immediately without flushing queued messages. + </description> + </method> + <method name="get_last_packet_channel" qualifiers="const"> + <return type="int"> + </return> + <description> + Returns the channel of the last packet fetched via [method PacketPeer.get_packet] + </description> + </method> + <method name="get_packet_channel" qualifiers="const"> + <return type="int"> + </return> + <description> + Returns the channel of the next packet that will be retrieved via [method PacketPeer.get_packet_peer] + </description> + </method> + <method name="get_peer_address" qualifiers="const"> + <return type="String"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <description> + Returns the IP address of the given peer. + </description> + </method> + <method name="get_peer_port" qualifiers="const"> + <return type="int"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <description> + Returns the remote port of the given peer. + </description> + </method> <method name="set_bind_ip"> <return type="void"> </return> @@ -61,9 +108,18 @@ </method> </methods> <members> + <member name="always_ordered" type="bool" setter="set_always_ordered" getter="is_always_ordered"> + Always use [code]TRANSFER_MODE_ORDERED[/code] in place of [code]TRANSFER_MODE_UNRELIABLE[/code]. This is the only way to use ordering with the RPC system. + </member> + <member name="channel_count" type="int" setter="set_channel_count" getter="get_channel_count"> + The number of channels to be used by ENet. Default: [code]3[/code]. Channels are used to separate different kinds of data. In realiable or ordered mode, for example, the packet delivery order is ensured on a per channel basis. + </member> <member name="compression_mode" type="int" setter="set_compression_mode" getter="get_compression_mode" enum="NetworkedMultiplayerENet.CompressionMode"> The compression method used for network packets. Default is no compression. These have different tradeoffs of compression speed versus bandwidth, you may need to test which one works best for your use case if you use compression at all. </member> + <member name="transfer_channel" type="int" setter="set_transfer_channel" getter="get_transfer_channel"> + Set the default channel to be used to transfer data. By default this value is [code]-1[/code] which means that ENet will only use 2 channels, one for reliable and one for unreliable packets. Channel [code]0[/code] is reserved, and cannot be used. Setting this member to any value between [code]0[/code] and [member channel_count] (excluded) will force ENet to use that channel for sending data. + </member> </members> <constants> <constant name="COMPRESS_NONE" value="0" enum="CompressionMode"> diff --git a/modules/enet/networked_multiplayer_enet.cpp b/modules/enet/networked_multiplayer_enet.cpp index 95bb472c7b..88768829d7 100644 --- a/modules/enet/networked_multiplayer_enet.cpp +++ b/modules/enet/networked_multiplayer_enet.cpp @@ -55,9 +55,29 @@ int NetworkedMultiplayerENet::get_packet_peer() const { return incoming_packets.front()->get().from; } +int NetworkedMultiplayerENet::get_packet_channel() const { + + ERR_FAIL_COND_V(!active, -1); + ERR_FAIL_COND_V(incoming_packets.size() == 0, -1); + + return incoming_packets.front()->get().channel; +} + +int NetworkedMultiplayerENet::get_last_packet_channel() const { + + ERR_FAIL_COND_V(!active, -1); + ERR_FAIL_COND_V(!current_packet.packet, -1); + + return current_packet.channel; +} + Error NetworkedMultiplayerENet::create_server(int p_port, int p_max_clients, int p_in_bandwidth, int p_out_bandwidth) { ERR_FAIL_COND_V(active, ERR_ALREADY_IN_USE); + ERR_FAIL_COND_V(p_port < 0 || p_port > 65535, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_max_clients < 0, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_in_bandwidth < 0, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_out_bandwidth < 0, ERR_INVALID_PARAMETER); ENetAddress address; @@ -79,9 +99,9 @@ Error NetworkedMultiplayerENet::create_server(int p_port, int p_max_clients, int host = enet_host_create(&address /* the address to bind the server host to */, p_max_clients /* allow up to 32 clients and/or outgoing connections */, - SYSCH_MAX /* allow up to SYSCH_MAX channels to be used */, - p_in_bandwidth /* assume any amount of incoming bandwidth */, - p_out_bandwidth /* assume any amount of outgoing bandwidth */); + channel_count /* allow up to channel_count to be used */, + p_in_bandwidth /* limit incoming bandwith if > 0 */, + p_out_bandwidth /* limit outgoing bandwith if > 0 */); ERR_FAIL_COND_V(!host, ERR_CANT_CREATE); @@ -93,15 +113,46 @@ Error NetworkedMultiplayerENet::create_server(int p_port, int p_max_clients, int connection_status = CONNECTION_CONNECTED; return OK; } -Error NetworkedMultiplayerENet::create_client(const String &p_address, int p_port, int p_in_bandwidth, int p_out_bandwidth) { +Error NetworkedMultiplayerENet::create_client(const String &p_address, int p_port, int p_in_bandwidth, int p_out_bandwidth, int p_client_port) { ERR_FAIL_COND_V(active, ERR_ALREADY_IN_USE); + ERR_FAIL_COND_V(p_port < 0 || p_port > 65535, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_client_port < 0 || p_client_port > 65535, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_in_bandwidth < 0, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_out_bandwidth < 0, ERR_INVALID_PARAMETER); - host = enet_host_create(NULL /* create a client host */, - 1 /* only allow 1 outgoing connection */, - SYSCH_MAX /* allow up to SYSCH_MAX channels to be used */, - p_in_bandwidth /* limit incoming bandwith if > 0 */, - p_out_bandwidth /* limit outgoing bandwith if > 0 */); + if (p_client_port != 0) { + ENetAddress c_client; + +#ifdef GODOT_ENET + if (bind_ip.is_wildcard()) { + c_client.wildcard = 1; + } else { + enet_address_set_ip(&c_client, bind_ip.get_ipv6(), 16); + } +#else + if (bind_ip.is_wildcard()) { + c_client.host = 0; + } else { + ERR_FAIL_COND_V(!bind_ip.is_ipv4(), ERR_INVALID_PARAMETER); + c_client.host = *(uint32_t *)bind_ip.get_ipv4(); + } +#endif + + c_client.port = p_client_port; + + host = enet_host_create(&c_client /* create a client host */, + 1 /* only allow 1 outgoing connection */, + channel_count /* allow up to channel_count to be used */, + p_in_bandwidth /* limit incoming bandwith if > 0 */, + p_out_bandwidth /* limit outgoing bandwith if > 0 */); + } else { + host = enet_host_create(NULL /* create a client host */, + 1 /* only allow 1 outgoing connection */, + channel_count /* allow up to channel_count to be used */, + p_in_bandwidth /* limit incoming bandwith if > 0 */, + p_out_bandwidth /* limit outgoing bandwith if > 0 */); + } ERR_FAIL_COND_V(!host, ERR_CANT_CREATE); @@ -131,8 +182,8 @@ Error NetworkedMultiplayerENet::create_client(const String &p_address, int p_por unique_id = _gen_unique_id(); - /* Initiate the connection, allocating the enough channels */ - ENetPeer *peer = enet_host_connect(host, &address, SYSCH_MAX, unique_id); + // Initiate connection, allocating enough channels + ENetPeer *peer = enet_host_connect(host, &address, channel_count, unique_id); if (peer == NULL) { enet_host_destroy(host); @@ -173,7 +224,7 @@ void NetworkedMultiplayerENet::poll() { switch (event.type) { case ENET_EVENT_TYPE_CONNECT: { - /* Store any relevant client information here. */ + // Store any relevant client information here. if (server && refuse_connections) { enet_peer_reset(event.peer); @@ -183,7 +234,7 @@ void NetworkedMultiplayerENet::poll() { int *new_id = memnew(int); *new_id = event.data; - if (*new_id == 0) { // Data zero is sent by server (enet won't let you configure this). Server is always 1 + if (*new_id == 0) { // Data zero is sent by server (enet won't let you configure this). Server is always 1. *new_id = 1; } @@ -220,7 +271,7 @@ void NetworkedMultiplayerENet::poll() { } break; case ENET_EVENT_TYPE_DISCONNECT: { - /* Reset the peer's client information. */ + // Reset the peer's client information. int *id = (int *)event.peer->data; @@ -281,7 +332,7 @@ void NetworkedMultiplayerENet::poll() { } enet_packet_destroy(event.packet); - } else if (event.channelID < SYSCH_MAX) { + } else if (event.channelID < channel_count) { Packet packet; packet.packet = event.packet; @@ -295,6 +346,7 @@ void NetworkedMultiplayerENet::poll() { uint32_t flags = decode_uint32(&event.packet->data[8]); packet.from = source; + packet.channel = event.channelID; if (server) { // Someone is cheating and trying to fake the source! @@ -352,7 +404,7 @@ void NetworkedMultiplayerENet::poll() { incoming_packets.push_back(packet); } - // Destroy packet later.. + // Destroy packet later } else { ERR_CONTINUE(true); } @@ -371,10 +423,10 @@ bool NetworkedMultiplayerENet::is_server() const { return server; } -void NetworkedMultiplayerENet::close_connection() { +void NetworkedMultiplayerENet::close_connection(uint32_t wait_usec) { - if (!active) - return; + ERR_FAIL_COND(!active); + ERR_FAIL_COND(wait_usec < 0); _pop_current_packet(); @@ -388,7 +440,10 @@ void NetworkedMultiplayerENet::close_connection() { if (peers_disconnected) { enet_host_flush(host); - OS::get_singleton()->delay_usec(100); // Wait 100ms for disconnection packets to send + + if (wait_usec > 0) { + OS::get_singleton()->delay_usec(wait_usec); // Wait for disconnection packets to send + } } enet_host_destroy(host); @@ -432,6 +487,7 @@ int NetworkedMultiplayerENet::get_available_packet_count() const { return incoming_packets.size(); } + Error NetworkedMultiplayerENet::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { ERR_FAIL_COND_V(incoming_packets.size() == 0, ERR_UNAVAILABLE); @@ -446,6 +502,7 @@ Error NetworkedMultiplayerENet::get_packet(const uint8_t **r_buffer, int &r_buff return OK; } + Error NetworkedMultiplayerENet::put_packet(const uint8_t *p_buffer, int p_buffer_size) { ERR_FAIL_COND_V(!active, ERR_UNCONFIGURED); @@ -456,7 +513,10 @@ Error NetworkedMultiplayerENet::put_packet(const uint8_t *p_buffer, int p_buffer switch (transfer_mode) { case TRANSFER_MODE_UNRELIABLE: { - packet_flags = ENET_PACKET_FLAG_UNSEQUENCED; + if (always_ordered) + packet_flags = 0; + else + packet_flags = ENET_PACKET_FLAG_UNSEQUENCED; channel = SYSCH_UNRELIABLE; } break; case TRANSFER_MODE_UNRELIABLE_ORDERED: { @@ -469,6 +529,9 @@ Error NetworkedMultiplayerENet::put_packet(const uint8_t *p_buffer, int p_buffer } break; } + if (transfer_channel > SYSCH_CONFIG) + channel = transfer_channel; + Map<int, ENetPeer *>::Element *E = NULL; if (target_peer != 0) { @@ -513,7 +576,7 @@ Error NetworkedMultiplayerENet::put_packet(const uint8_t *p_buffer, int p_buffer } else { ERR_FAIL_COND_V(!peer_map.has(1), ERR_BUG); - enet_peer_send(peer_map[1], channel, packet); // Send to server for broadcast.. + enet_peer_send(peer_map[1], channel, packet); // Send to server for broadcast } enet_host_flush(host); @@ -532,6 +595,7 @@ void NetworkedMultiplayerENet::_pop_current_packet() { enet_packet_destroy(current_packet.packet); current_packet.packet = NULL; current_packet.from = 0; + current_packet.channel = -1; } } @@ -719,19 +783,65 @@ int NetworkedMultiplayerENet::get_peer_port(int p_peer_id) const { #endif } +void NetworkedMultiplayerENet::set_transfer_channel(int p_channel) { + + ERR_FAIL_COND(p_channel < -1 || p_channel >= channel_count); + + if (p_channel == SYSCH_CONFIG) { + ERR_EXPLAIN("Channel " + itos(SYSCH_CONFIG) + " is reserved"); + ERR_FAIL(); + } + transfer_channel = p_channel; +} + +int NetworkedMultiplayerENet::get_transfer_channel() const { + return transfer_channel; +} + +void NetworkedMultiplayerENet::set_channel_count(int p_channel) { + + ERR_FAIL_COND(active); + ERR_FAIL_COND(p_channel < SYSCH_MAX); + channel_count = p_channel; +} + +int NetworkedMultiplayerENet::get_channel_count() const { + return channel_count; +} + +void NetworkedMultiplayerENet::set_always_ordered(bool p_ordered) { + always_ordered = p_ordered; +} + +bool NetworkedMultiplayerENet::is_always_ordered() const { + return always_ordered; +} + void NetworkedMultiplayerENet::_bind_methods() { ClassDB::bind_method(D_METHOD("create_server", "port", "max_clients", "in_bandwidth", "out_bandwidth"), &NetworkedMultiplayerENet::create_server, DEFVAL(32), DEFVAL(0), DEFVAL(0)); - ClassDB::bind_method(D_METHOD("create_client", "address", "port", "in_bandwidth", "out_bandwidth"), &NetworkedMultiplayerENet::create_client, DEFVAL(0), DEFVAL(0)); - ClassDB::bind_method(D_METHOD("close_connection"), &NetworkedMultiplayerENet::close_connection); + ClassDB::bind_method(D_METHOD("create_client", "address", "port", "in_bandwidth", "out_bandwidth", "client_port"), &NetworkedMultiplayerENet::create_client, DEFVAL(0), DEFVAL(0), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("close_connection", "wait_usec"), &NetworkedMultiplayerENet::close_connection, DEFVAL(100)); ClassDB::bind_method(D_METHOD("disconnect_peer", "id", "now"), &NetworkedMultiplayerENet::disconnect_peer, DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_compression_mode", "mode"), &NetworkedMultiplayerENet::set_compression_mode); ClassDB::bind_method(D_METHOD("get_compression_mode"), &NetworkedMultiplayerENet::get_compression_mode); ClassDB::bind_method(D_METHOD("set_bind_ip", "ip"), &NetworkedMultiplayerENet::set_bind_ip); - ClassDB::bind_method(D_METHOD("get_peer_address"), &NetworkedMultiplayerENet::get_peer_address); - ClassDB::bind_method(D_METHOD("get_peer_port"), &NetworkedMultiplayerENet::get_peer_port); + ClassDB::bind_method(D_METHOD("get_peer_address", "id"), &NetworkedMultiplayerENet::get_peer_address); + ClassDB::bind_method(D_METHOD("get_peer_port", "id"), &NetworkedMultiplayerENet::get_peer_port); + + ClassDB::bind_method(D_METHOD("get_packet_channel"), &NetworkedMultiplayerENet::get_packet_channel); + ClassDB::bind_method(D_METHOD("get_last_packet_channel"), &NetworkedMultiplayerENet::get_last_packet_channel); + ClassDB::bind_method(D_METHOD("set_transfer_channel", "channel"), &NetworkedMultiplayerENet::set_transfer_channel); + ClassDB::bind_method(D_METHOD("get_transfer_channel"), &NetworkedMultiplayerENet::get_transfer_channel); + ClassDB::bind_method(D_METHOD("set_channel_count", "channels"), &NetworkedMultiplayerENet::set_channel_count); + ClassDB::bind_method(D_METHOD("get_channel_count"), &NetworkedMultiplayerENet::get_channel_count); + ClassDB::bind_method(D_METHOD("set_always_ordered", "ordered"), &NetworkedMultiplayerENet::set_always_ordered); + ClassDB::bind_method(D_METHOD("is_always_ordered"), &NetworkedMultiplayerENet::is_always_ordered); ADD_PROPERTY(PropertyInfo(Variant::INT, "compression_mode", PROPERTY_HINT_ENUM, "None,Range Coder,FastLZ,ZLib,ZStd"), "set_compression_mode", "get_compression_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_channel"), "set_transfer_channel", "get_transfer_channel"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "channel_count"), "set_channel_count", "get_channel_count"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "always_ordered"), "set_always_ordered", "is_always_ordered"); BIND_ENUM_CONSTANT(COMPRESS_NONE); BIND_ENUM_CONSTANT(COMPRESS_RANGE_CODER); @@ -749,6 +859,9 @@ NetworkedMultiplayerENet::NetworkedMultiplayerENet() { target_peer = 0; current_packet.packet = NULL; transfer_mode = TRANSFER_MODE_RELIABLE; + channel_count = SYSCH_MAX; + transfer_channel = -1; + always_ordered = false; connection_status = CONNECTION_DISCONNECTED; compression_mode = COMPRESS_NONE; enet_compressor.context = this; @@ -764,7 +877,7 @@ NetworkedMultiplayerENet::~NetworkedMultiplayerENet() { close_connection(); } -// Sets IP for ENet to bind when using create_server +// Sets IP for ENet to bind when using create_server or create_client // if no IP is set, then ENet bind to ENET_HOST_ANY void NetworkedMultiplayerENet::set_bind_ip(const IP_Address &p_ip) { ERR_FAIL_COND(!p_ip.is_valid() && !p_ip.is_wildcard()); diff --git a/modules/enet/networked_multiplayer_enet.h b/modules/enet/networked_multiplayer_enet.h index 678ae24135..705807d429 100644 --- a/modules/enet/networked_multiplayer_enet.h +++ b/modules/enet/networked_multiplayer_enet.h @@ -68,6 +68,9 @@ private: int target_peer; TransferMode transfer_mode; + int transfer_channel; + int channel_count; + bool always_ordered; ENetEvent event; ENetPeer *peer; @@ -83,6 +86,7 @@ private: ENetPacket *packet; int from; + int channel; }; CompressionMode compression_mode; @@ -119,9 +123,9 @@ public: virtual int get_peer_port(int p_peer_id) const; Error create_server(int p_port, int p_max_clients = 32, int p_in_bandwidth = 0, int p_out_bandwidth = 0); - Error create_client(const String &p_address, int p_port, int p_in_bandwidth = 0, int p_out_bandwidth = 0); + Error create_client(const String &p_address, int p_port, int p_in_bandwidth = 0, int p_out_bandwidth = 0, int p_client_port = 0); - void close_connection(); + void close_connection(uint32_t wait_usec = 100); void disconnect_peer(int p_peer, bool now = false); @@ -145,6 +149,15 @@ public: void set_compression_mode(CompressionMode p_mode); CompressionMode get_compression_mode() const; + int get_packet_channel() const; + int get_last_packet_channel() const; + void set_transfer_channel(int p_channel); + int get_transfer_channel() const; + void set_channel_count(int p_channel); + int get_channel_count() const; + void set_always_ordered(bool p_ordered); + bool is_always_ordered() const; + NetworkedMultiplayerENet(); ~NetworkedMultiplayerENet(); diff --git a/modules/freetype/SCsub b/modules/freetype/SCsub index 8a7c2a773a..301f218361 100644 --- a/modules/freetype/SCsub +++ b/modules/freetype/SCsub @@ -17,18 +17,15 @@ if env['builtin_freetype']: "src/base/ftbitmap.c", "src/base/ftcid.c", "src/base/ftdebug.c", - "src/base/ftfntfmt.c", "src/base/ftfstype.c", "src/base/ftgasp.c", "src/base/ftglyph.c", "src/base/ftgxval.c", "src/base/ftinit.c", - "src/base/ftlcdfil.c", "src/base/ftmm.c", "src/base/ftotval.c", "src/base/ftpatent.c", "src/base/ftpfr.c", - "src/base/ftpic.c", "src/base/ftstroke.c", "src/base/ftsynth.c", "src/base/ftsystem.c", diff --git a/modules/gdnative/SCsub b/modules/gdnative/SCsub index acfb83bc10..6d2f8ce8ad 100644 --- a/modules/gdnative/SCsub +++ b/modules/gdnative/SCsub @@ -23,7 +23,8 @@ def _build_gdnative_api_struct_header(api): '\textern const godot_gdnative_core_api_struct *_gdnative_wrapper_api_struct;' ] - for name in api['extensions']: + for ext in api['extensions']: + name = ext['name'] gdnative_api_init_macro.append( '\textern const godot_gdnative_ext_{0}_api_struct *_gdnative_wrapper_{0}_api_struct;'.format(name)) @@ -31,9 +32,10 @@ def _build_gdnative_api_struct_header(api): gdnative_api_init_macro.append('\tfor (unsigned int i = 0; i < _gdnative_wrapper_api_struct->num_extensions; i++) { ') gdnative_api_init_macro.append('\t\tswitch (_gdnative_wrapper_api_struct->extensions[i]->type) {') - for name in api['extensions']: + for ext in api['extensions']: + name = ext['name'] gdnative_api_init_macro.append( - '\t\t\tcase GDNATIVE_EXT_%s:' % api['extensions'][name]['type']) + '\t\t\tcase GDNATIVE_EXT_%s:' % ext['type']) gdnative_api_init_macro.append( '\t\t\t\t_gdnative_wrapper_{0}_api_struct = (godot_gdnative_ext_{0}_api_struct *)' ' _gdnative_wrapper_api_struct->extensions[i];'.format(name)) @@ -61,8 +63,8 @@ def _build_gdnative_api_struct_header(api): '\tGDNATIVE_' + api['core']['type'] + ',' ] - for name in api['extensions']: - out += ['\tGDNATIVE_EXT_' + api['extensions'][name]['type'] + ','] + for ext in api['extensions']: + out += ['\tGDNATIVE_EXT_' + ext['type'] + ','] out += ['};', ''] @@ -88,8 +90,9 @@ def _build_gdnative_api_struct_header(api): return ret_val - for name in api['extensions']: - out += generate_extension_struct(name, api['extensions'][name], False) + for ext in api['extensions']: + name = ext['name'] + out += generate_extension_struct(name, ext, False) out += [ 'typedef struct godot_gdnative_core_api_struct {', @@ -151,12 +154,14 @@ def _build_gdnative_api_struct_source(api): return ret_val - for name in api['extensions']: - out += get_extension_struct_definition(name, api['extensions'][name], False) + for ext in api['extensions']: + name = ext['name'] + out += get_extension_struct_definition(name, ext, False) out += ['', 'const godot_gdnative_api_struct *gdnative_extensions_pointers[] = {'] - for name in api['extensions']: + for ext in api['extensions']: + name = ext['name'] out += ['\t(godot_gdnative_api_struct *)&api_extension_' + name + '_struct,'] out += ['};\n'] @@ -214,7 +219,8 @@ def _build_gdnative_wrapper_code(api): 'godot_gdnative_core_api_struct *_gdnative_wrapper_api_struct = 0;', ] - for name in api['extensions']: + for ext in api['extensions']: + name = ext['name'] out.append('godot_gdnative_ext_' + name + '_api_struct *_gdnative_wrapper_' + name + '_api_struct = 0;') out += [''] @@ -232,8 +238,9 @@ def _build_gdnative_wrapper_code(api): out.append('}') out.append('') - for name in api['extensions']: - for funcdef in api['extensions'][name]['api']: + for ext in api['extensions']: + name = ext['name'] + for funcdef in ext['api']: args = ', '.join(['%s%s' % (_spaced(t), n) for t, n in funcdef['arguments']]) out.append('%s%s(%s) {' % (_spaced(funcdef['return_type']), funcdef['name'], args)) @@ -267,7 +274,7 @@ def build_gdnative_wrapper_code(target, source, env): if ARGUMENTS.get('gdnative_wrapper', False): - #build wrapper code +#build wrapper code gensource, = gdn_env.Command('gdnative_wrapper_code.gen.cpp', 'gdnative_api.json', build_gdnative_wrapper_code) gd_wrapper_env = env.Clone() @@ -275,8 +282,8 @@ if ARGUMENTS.get('gdnative_wrapper', False): if gd_wrapper_env['use_lto']: if not env.msvc: - gd_wrapper_env.Append(CCFLAGS=['--no-lto']) - gd_wrapper_env.Append(LINKFLAGS=['--no-lto']) + gd_wrapper_env.Append(CCFLAGS=['-fno-lto']) + gd_wrapper_env.Append(LINKFLAGS=['-fno-lto']) else: gd_wrapper_env.Append(CCFLAGS=['/GL-']) gd_wrapper_env.Append(LINKFLAGS=['/LTCG:OFF']) diff --git a/modules/gdnative/gdnative.cpp b/modules/gdnative/gdnative.cpp index 897588385a..e7ebcc73af 100644 --- a/modules/gdnative/gdnative.cpp +++ b/modules/gdnative/gdnative.cpp @@ -469,7 +469,9 @@ Variant GDNative::call_native(StringName p_native_call_type, StringName p_proced godot_variant result = E->get()(procedure_handle, (godot_array *)&p_arguments); - return *(Variant *)&result; + Variant res = *(Variant *)&result; + godot_variant_destroy(&result); + return res; } Error GDNative::get_symbol(StringName p_procedure_name, void *&r_handle, bool p_optional) { diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json index f41c3859bd..9fcf61af8a 100644 --- a/modules/gdnative/gdnative_api.json +++ b/modules/gdnative/gdnative_api.json @@ -5756,8 +5756,9 @@ } ] }, - "extensions": { - "nativescript": { + "extensions": [ + { + "name": "nativescript", "type": "NATIVESCRIPT", "version": { "major": 1, @@ -5942,7 +5943,8 @@ } ] }, - "pluginscript": { + { + "name": "pluginscript", "type": "PLUGINSCRIPT", "version": { "major": 1, @@ -5959,7 +5961,8 @@ } ] }, - "arvr": { + { + "name": "arvr", "type": "ARVR", "version": { "major": 1, @@ -6055,5 +6058,5 @@ } ] } - } + ] } diff --git a/modules/gdscript/SCsub b/modules/gdscript/SCsub index 13870170a5..73f09f1659 100644 --- a/modules/gdscript/SCsub +++ b/modules/gdscript/SCsub @@ -7,4 +7,7 @@ env_gdscript = env_modules.Clone() env_gdscript.add_source_files(env.modules_sources, "*.cpp") +if env['tools']: + env_gdscript.add_source_files(env.modules_sources, "./editor/*.cpp") + Export('env') diff --git a/modules/gdscript/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 4e89851bf2..ea3efff9cf 100644 --- a/modules/gdscript/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -29,6 +29,8 @@ /*************************************************************************/ #include "gdscript_highlighter.h" +#include "../gdscript_tokenizer.h" +#include "editor/editor_settings.h" #include "scene/gui/text_edit.h" inline bool _is_symbol(CharType c) { @@ -61,12 +63,20 @@ static bool _is_hex_symbol(CharType c) { Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) { Map<int, TextEdit::HighlighterInfo> color_map; + Type next_type = NONE; + Type current_type = NONE; + Type previous_type = NONE; + + String previous_text = ""; + int previous_column = 0; + bool prev_is_char = false; bool prev_is_number = false; bool in_keyword = false; bool in_word = false; bool in_function_name = false; bool in_member_variable = false; + bool in_node_path = false; bool is_hex_notation = false; Color keyword_color; Color color; @@ -214,18 +224,64 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_ in_member_variable = false; } - if (in_region >= 0) + if (!in_node_path && in_region == -1 && str[j] == '$') { + in_node_path = true; + } else if (in_region != -1 || (is_symbol && str[j] != '/')) { + in_node_path = false; + } + + if (in_region >= 0) { + next_type = REGION; color = text_editor->_get_color_region(in_region).color; - else if (in_keyword) + } else if (in_node_path) { + next_type = NODE_PATH; + color = node_path_color; + } else if (in_keyword) { + next_type = KEYWORD; color = keyword_color; - else if (in_member_variable) + } else if (in_member_variable) { + next_type = MEMBER; color = member_color; - else if (in_function_name) - color = function_color; - else if (is_symbol) + } else if (in_function_name) { + next_type = FUNCTION; + + if (previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::TK_PR_FUNCTION)) { + color = function_definition_color; + } else { + color = function_color; + } + } else if (is_symbol) { + next_type = SYMBOL; color = symbol_color; - else if (is_number) + } else if (is_number) { + next_type = NUMBER; color = number_color; + } else { + next_type = IDENTIFIER; + } + + if (next_type != current_type) { + if (current_type == NONE) { + current_type = next_type; + } else { + previous_type = current_type; + current_type = next_type; + + // no need to store regions... + if (previous_type == REGION) { + previous_text = ""; + previous_column = j; + } else { + String text = str.substr(previous_column, j - previous_column).strip_edges(); + previous_column = j; + + // ignore if just whitespace + if (text != "") { + previous_text = text; + } + } + } + } prev_is_char = is_char; prev_is_number = is_number; @@ -255,6 +311,9 @@ void GDScriptSyntaxHighlighter::_update_cache() { function_color = text_editor->get_color("function_color"); number_color = text_editor->get_color("number_color"); member_color = text_editor->get_color("member_variable_color"); + + function_definition_color = EDITOR_DEF("text_editor/highlighting/gdscript/function_definition_color", Color::html("#01e1ff")); + node_path_color = EDITOR_DEF("text_editor/highlighting/gdscript/node_path_color", Color::html("#64c15a")); } SyntaxHighlighter *GDScriptSyntaxHighlighter::create() { diff --git a/modules/gdscript/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h index ef1bdd4103..0296ab7652 100644 --- a/modules/gdscript/gdscript_highlighter.h +++ b/modules/gdscript/editor/gdscript_highlighter.h @@ -35,13 +35,27 @@ class GDScriptSyntaxHighlighter : public SyntaxHighlighter { private: + enum Type { + NONE, + REGION, + NODE_PATH, + SYMBOL, + NUMBER, + FUNCTION, + KEYWORD, + MEMBER, + IDENTIFIER + }; + // colours Color font_color; Color symbol_color; Color function_color; + Color function_definition_color; Color built_in_type_color; Color number_color; Color member_color; + Color node_path_color; public: static SyntaxHighlighter *create(); diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 4e3ee4d22c..14bdce50ec 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1333,6 +1333,15 @@ void GDScriptLanguage::add_global_constant(const StringName &p_variable, const V _add_global(p_variable, p_value); } +void GDScriptLanguage::add_named_global_constant(const StringName &p_name, const Variant &p_value) { + named_globals[p_name] = p_value; +} + +void GDScriptLanguage::remove_named_global_constant(const StringName &p_name) { + ERR_FAIL_COND(!named_globals.has(p_name)); + named_globals.erase(p_name); +} + void GDScriptLanguage::init() { //populate global constants diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 9566e3b32e..6885fbb7fe 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -262,6 +262,7 @@ class GDScriptLanguage : public ScriptLanguage { Variant *_global_array; Vector<Variant> global_array; Map<StringName, int> globals; + Map<StringName, Variant> named_globals; struct CallLevel { @@ -369,7 +370,8 @@ public: _FORCE_INLINE_ int get_global_array_size() const { return global_array.size(); } _FORCE_INLINE_ Variant *get_global_array() { return _global_array; } - _FORCE_INLINE_ const Map<StringName, int> &get_global_map() { return globals; } + _FORCE_INLINE_ const Map<StringName, int> &get_global_map() const { return globals; } + _FORCE_INLINE_ const Map<StringName, Variant> &get_named_globals_map() const { return named_globals; } _FORCE_INLINE_ static GDScriptLanguage *get_singleton() { return singleton; } @@ -403,6 +405,8 @@ public: virtual String _get_indentation() const; virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const; virtual void add_global_constant(const StringName &p_variable, const Variant &p_value); + virtual void add_named_global_constant(const StringName &p_name, const Variant &p_value); + virtual void remove_named_global_constant(const StringName &p_name); /* DEBUGGER FUNCTIONS */ diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 048948dada..9947512444 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -278,6 +278,18 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser:: return idx | (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root) } +#ifdef TOOLS_ENABLED + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) { + + int idx = codegen.named_globals.find(identifier); + if (idx == -1) { + idx = codegen.named_globals.size(); + codegen.named_globals.push_back(identifier); + } + return idx | (GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL << GDScriptFunction::ADDR_BITS); + } +#endif + //not found, error _set_error("Identifier not found: " + String(identifier), p_expression); @@ -1511,6 +1523,18 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser gdfunc->_global_names_count = 0; } +#ifdef TOOLS_ENABLED + // Named globals + if (codegen.named_globals.size()) { + gdfunc->named_globals.resize(codegen.named_globals.size()); + gdfunc->_named_globals_ptr = gdfunc->named_globals.ptr(); + for (int i = 0; i < codegen.named_globals.size(); i++) { + gdfunc->named_globals[i] = codegen.named_globals[i]; + } + gdfunc->_named_globals_count = gdfunc->named_globals.size(); + } +#endif + if (codegen.opcodes.size()) { gdfunc->code = codegen.opcodes; diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 62aafdbe01..237b0de9e7 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -94,6 +94,9 @@ class GDScriptCompiler { HashMap<Variant, int, VariantHasher, VariantComparator> constant_map; Map<StringName, int> name_map; +#ifdef TOOLS_ENABLED + Vector<StringName> named_globals; +#endif int get_name_map_pos(const StringName &p_identifier) { int ret; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 6f4d62592e..30ef167466 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -61,7 +61,7 @@ Ref<Script> GDScriptLanguage::get_template(const String &p_class_name, const Str "# var b = \"textvar\"\n\n" + "func _ready():\n" + "%TS%# Called when the node is added to the scene for the first time.\n" + - "%TS%# Initialization here\n" + + "%TS%# Initialization here.\n" + "%TS%pass\n\n" + "#func _process(delta):\n" + "#%TS%# Called every frame. Delta is time since last frame.\n" + @@ -430,6 +430,9 @@ struct GDScriptCompletionIdentifier { Ref<GDScript> script; Variant::Type type; Variant value; //im case there is a value, also return it + + GDScriptCompletionIdentifier() : + type(Variant::NIL) {} }; static GDScriptCompletionIdentifier _get_type_from_variant(const Variant &p_variant, bool p_allow_gdnative_class = false) { @@ -551,9 +554,7 @@ static Ref<Reference> _get_parent_class(GDScriptCompletionContext &context) { static GDScriptCompletionIdentifier _get_native_class(GDScriptCompletionContext &context) { - //eeh... GDScriptCompletionIdentifier id; - id.type = Variant::NIL; REF pc = _get_parent_class(context); if (!pc.is_valid()) { @@ -1521,6 +1522,13 @@ static void _find_identifiers(GDScriptCompletionContext &context, int p_line, bo result.insert(_type_names[i]); } + List<String> reserved_words; + GDScriptLanguage::get_singleton()->get_reserved_words(&reserved_words); + + for (List<String>::Element *E = reserved_words.front(); E; E = E->next()) { + result.insert(E->get()); + } + //autoload singletons List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); @@ -2648,6 +2656,18 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol context.function = p.get_completion_function(); context.base = p_owner; context.base_path = p_base_path; + + if (context._class && context._class->extends_class.size() > 0) { + bool success = false; + ClassDB::get_integer_constant(context._class->extends_class[0], p_symbol, &success); + if (success) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; + r_result.class_name = context._class->extends_class[0]; + r_result.class_member = p_symbol; + return OK; + } + } + bool isfunction = false; switch (p.get_completion_type()) { diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index d32ce25d3c..dac7da3a28 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -108,6 +108,21 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta #endif return &GDScriptLanguage::get_singleton()->get_global_array()[address]; } break; +#ifdef TOOLS_ENABLED + case ADDR_TYPE_NAMED_GLOBAL: { +#ifdef DEBUG_ENABLED + ERR_FAIL_INDEX_V(address, _named_globals_count, NULL); +#endif + StringName id = _named_globals_ptr[address]; + + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(id)) { + return (Variant *)&GDScriptLanguage::get_singleton()->get_named_globals_map()[id]; + } else { + r_error = "Autoload singleton '" + String(id) + "' has been removed."; + return NULL; + } + } break; +#endif case ADDR_TYPE_NIL: { return &nil; } break; @@ -1311,9 +1326,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time; } -#endif if (ScriptDebugger::get_singleton()) GDScriptLanguage::get_singleton()->exit_function(); +#endif if (_stack_size) { //free stack diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index dff4bdfaf2..ea009dcd96 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -92,7 +92,8 @@ public: ADDR_TYPE_STACK = 5, ADDR_TYPE_STACK_VARIABLE = 6, ADDR_TYPE_GLOBAL = 7, - ADDR_TYPE_NIL = 8 + ADDR_TYPE_NAMED_GLOBAL = 8, + ADDR_TYPE_NIL = 9 }; enum RPCMode { @@ -121,6 +122,10 @@ private: int _constant_count; const StringName *_global_names_ptr; int _global_names_count; +#ifdef TOOLS_ENABLED + const StringName *_named_globals_ptr; + int _named_globals_count; +#endif const int *_default_arg_ptr; int _default_arg_count; const int *_code_ptr; @@ -137,6 +142,9 @@ private: StringName name; Vector<Variant> constants; Vector<StringName> global_names; +#ifdef TOOLS_ENABLED + Vector<StringName> named_globals; +#endif Vector<int> default_arguments; Vector<int> code; diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp index 278585cb01..a88ba477c6 100644 --- a/modules/gdscript/gdscript_functions.cpp +++ b/modules/gdscript/gdscript_functions.cpp @@ -122,6 +122,7 @@ const char *GDScriptFunctions::get_func_name(Function p_func) { "print_stack", "instance_from_id", "len", + "is_instance_valid", }; return _names[p_func]; @@ -329,10 +330,24 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ } break; case MATH_LERP: { VALIDATE_ARG_COUNT(3); - VALIDATE_ARG_NUM(0); - VALIDATE_ARG_NUM(1); VALIDATE_ARG_NUM(2); - r_ret = Math::lerp((double)*p_args[0], (double)*p_args[1], (double)*p_args[2]); + const double t = (double)*p_args[2]; + switch (p_args[0]->get_type() == p_args[1]->get_type() ? p_args[0]->get_type() : Variant::REAL) { + case Variant::VECTOR2: { + r_ret = ((Vector2)*p_args[0]).linear_interpolate((Vector2)*p_args[1], t); + } break; + case Variant::VECTOR3: { + r_ret = ((Vector3)*p_args[0]).linear_interpolate((Vector3)*p_args[1], t); + } break; + case Variant::COLOR: { + r_ret = ((Color)*p_args[0]).linear_interpolate((Color)*p_args[1], t); + } break; + default: { + VALIDATE_ARG_NUM(0); + VALIDATE_ARG_NUM(1); + r_ret = Math::lerp((double)*p_args[0], (double)*p_args[1], t); + } break; + } } break; case MATH_INVERSE_LERP: { VALIDATE_ARG_COUNT(3); @@ -1277,6 +1292,17 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_ } } break; + case IS_INSTANCE_VALID: { + + VALIDATE_ARG_COUNT(1); + if (p_args[0]->get_type() != Variant::OBJECT) { + r_ret = false; + } else { + Object *obj = *p_args[0]; + r_ret = ObjectDB::instance_validate(obj); + } + + } break; case FUNC_MAX: { ERR_FAIL(); @@ -1488,7 +1514,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { return mi; } break; case MATH_LERP: { - MethodInfo mi("lerp", PropertyInfo(Variant::REAL, "from"), PropertyInfo(Variant::REAL, "to"), PropertyInfo(Variant::REAL, "weight")); + MethodInfo mi("lerp", PropertyInfo(Variant::NIL, "from"), PropertyInfo(Variant::NIL, "to"), PropertyInfo(Variant::REAL, "weight")); mi.return_val.type = Variant::REAL; return mi; } break; @@ -1798,7 +1824,11 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) { mi.return_val.type = Variant::INT; return mi; } break; - + case IS_INSTANCE_VALID: { + MethodInfo mi("is_instance_valid", PropertyInfo(Variant::OBJECT, "instance")); + mi.return_val.type = Variant::BOOL; + return mi; + } break; case FUNC_MAX: { ERR_FAIL_V(MethodInfo()); diff --git a/modules/gdscript/gdscript_functions.h b/modules/gdscript/gdscript_functions.h index 1d54006084..c4731d17a4 100644 --- a/modules/gdscript/gdscript_functions.h +++ b/modules/gdscript/gdscript_functions.h @@ -113,6 +113,7 @@ public: PRINT_STACK, INSTANCE_FROM_ID, LEN, + IS_INSTANCE_VALID, FUNC_MAX }; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 774e9e62ee..e7b0700e76 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -3440,6 +3440,22 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) { tokenizer->advance(); + + String hint_prefix = ""; + bool is_arrayed = false; + + while (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE && + tokenizer->get_token_type() == Variant::ARRAY && + tokenizer->get_token(1) == GDScriptTokenizer::TK_COMMA) { + tokenizer->advance(); // Array + tokenizer->advance(); // Comma + if (is_arrayed) { + hint_prefix += itos(Variant::ARRAY) + ":"; + } else { + is_arrayed = true; + } + } + if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE) { Variant::Type type = tokenizer->get_token_type(); @@ -3455,28 +3471,6 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; tokenizer->advance(); - String hint_prefix = ""; - - if (type == Variant::ARRAY && tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { - tokenizer->advance(); - - while (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_TYPE) { - type = tokenizer->get_token_type(); - - tokenizer->advance(); - - if (type == Variant::ARRAY) { - hint_prefix += itos(Variant::ARRAY) + ":"; - if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { - tokenizer->advance(); - } - } else { - hint_prefix += itos(type); - break; - } - } - } - if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) { // hint expected next! tokenizer->advance(); @@ -3830,13 +3824,6 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } break; } } - if (current_export.type == Variant::ARRAY && !hint_prefix.empty()) { - if (current_export.hint) { - hint_prefix += "/" + itos(current_export.hint); - } - current_export.hint_string = hint_prefix + ":" + current_export.hint_string; - current_export.hint = PROPERTY_HINT_NONE; - } } else { @@ -3923,6 +3910,16 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { return; } + if (is_arrayed) { + hint_prefix += itos(current_export.type); + if (current_export.hint) { + hint_prefix += "/" + itos(current_export.hint); + } + current_export.hint_string = hint_prefix + ":" + current_export.hint_string; + current_export.hint = PROPERTY_HINT_TYPE_STRING; + current_export.type = Variant::ARRAY; + } + tokenizer->advance(); } @@ -4090,7 +4087,8 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { member._export.type=Variant::DICTIONARY; - } else*/ { + } else*/ + { if (subexpr->type != Node::TYPE_CONSTANT) { diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index 85c94c3596..422223370b 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -30,8 +30,8 @@ #include "register_types.h" +#include "editor/gdscript_highlighter.h" #include "gdscript.h" -#include "gdscript_highlighter.h" #include "gdscript_tokenizer.h" #include "io/file_access_encrypted.h" #include "io/resource_loader.h" diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index f523eef895..4b96824dca 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -1154,9 +1154,9 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { for (int k = 0; k < 3; k++) { if (i < 3) - face_points[j][(i + k) % 3] = v[k] * (i >= 3 ? -1 : 1); + face_points[j][(i + k) % 3] = v[k]; else - face_points[3 - j][(i + k) % 3] = v[k] * (i >= 3 ? -1 : 1); + face_points[3 - j][(i + k) % 3] = -v[k]; } } diff --git a/modules/mono/config.py b/modules/mono/config.py index 831d849ea6..18d9c67795 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -59,9 +59,6 @@ def configure(env): mono_lib_names = ['mono-2.0-sgen', 'monosgen-2.0'] if env['platform'] == 'windows': - if mono_static: - raise RuntimeError('mono-static: Not supported on Windows') - if bits == '32': if os.getenv('MONO32_PREFIX'): mono_root = os.getenv('MONO32_PREFIX') @@ -81,24 +78,41 @@ def configure(env): env.Append(LIBPATH=mono_lib_path) env.Append(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0')) - mono_lib_name = find_file_in_dir(mono_lib_path, mono_lib_names, extension='.lib') + if mono_static: + lib_suffix = Environment()['LIBSUFFIX'] + mono_static_lib_name = 'libmono-static-sgen' - if not mono_lib_name: - raise RuntimeError('Could not find mono library in: ' + mono_lib_path) + if not os.path.isfile(os.path.join(mono_lib_path, mono_static_lib_name + lib_suffix)): + raise RuntimeError('Could not find static mono library in: ' + mono_lib_path) - if os.getenv('VCINSTALLDIR'): - env.Append(LINKFLAGS=mono_lib_name + Environment()['LIBSUFFIX']) + if env.msvc: + env.Append(LINKFLAGS=mono_static_lib_name + lib_suffix) + + env.Append(LINKFLAGS='Mincore' + lib_suffix) + env.Append(LINKFLAGS='msvcrt' + lib_suffix) + env.Append(LINKFLAGS='LIBCMT' + lib_suffix) + env.Append(LINKFLAGS='Psapi' + lib_suffix) + else: + env.Append(LIBS=mono_static_lib_name) else: - env.Append(LIBS=mono_lib_name) + mono_lib_name = find_file_in_dir(mono_lib_path, mono_lib_names, extension='.lib') + + if not mono_lib_name: + raise RuntimeError('Could not find mono library in: ' + mono_lib_path) + + if env.msvc: + env.Append(LINKFLAGS=mono_lib_name + Environment()['LIBSUFFIX']) + else: + env.Append(LIBS=mono_lib_name) - mono_bin_path = os.path.join(mono_root, 'bin') + mono_bin_path = os.path.join(mono_root, 'bin') - mono_dll_name = find_file_in_dir(mono_bin_path, mono_lib_names, extension='.dll') + mono_dll_name = find_file_in_dir(mono_bin_path, mono_lib_names, extension='.dll') - if not mono_dll_name: - raise RuntimeError('Could not find mono shared library in: ' + mono_bin_path) + if not mono_dll_name: + raise RuntimeError('Could not find mono shared library in: ' + mono_bin_path) - copy_file(mono_bin_path, 'bin', mono_dll_name + '.dll') + copy_file(mono_bin_path, 'bin', mono_dll_name + '.dll') copy_file(os.path.join(mono_lib_path, 'mono', '4.5'), assemblies_output_dir, 'mscorlib.dll') else: diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 1d0afa7f2d..161e62c81f 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -305,7 +305,7 @@ Ref<Script> CSharpLanguage::get_template(const String &p_class_name, const Strin " public override void _Ready()\n" " {\n" " // Called every time the node is added to the scene.\n" - " // Initialization here\n" + " // Initialization here.\n" " \n" " }\n" "\n" @@ -782,7 +782,7 @@ void CSharpLanguage::reload_assemblies_if_needed(bool p_soft_reload) { } if (Engine::get_singleton()->is_editor_hint()) { - EditorNode::get_singleton()->get_property_editor()->update_tree(); + EditorNode::get_singleton()->get_inspector()->update_tree(); NodeDock::singleton->update_lists(); } } @@ -1954,8 +1954,12 @@ Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Variant::Call ScriptInstance *CSharpScript::instance_create(Object *p_this) { - if (!valid) - return NULL; + if (!script_class) { + ERR_EXPLAIN("Cannot find class " + name + " for script " + get_path()); + ERR_FAIL_V(NULL); + } + + ERR_FAIL_COND_V(!valid, NULL); if (!tool && !ScriptServer::is_scripting_enabled()) { #ifdef TOOLS_ENABLED @@ -2045,20 +2049,15 @@ Error CSharpScript::reload(bool p_keep_state) { if (project_assembly) { script_class = project_assembly->get_object_derived_class(name); - if (!script_class) { - ERR_PRINTS("Cannot find class " + name + " for script " + get_path()); - } + valid = script_class != NULL; + + if (script_class) { #ifdef DEBUG_ENABLED - else if (OS::get_singleton()->is_stdout_verbose()) { OS::get_singleton()->print(String("Found class " + script_class->get_namespace() + "." + script_class->get_name() + " for script " + get_path() + "\n") .utf8()); - } #endif - valid = script_class != NULL; - - if (script_class) { tool = script_class->has_attribute(CACHED_CLASS(ToolAttribute)); native = GDMonoUtils::get_class_native_base(script_class); @@ -2288,7 +2287,9 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p CRASH_COND(mono_domain_get() == NULL); #endif -#else +#endif + +#ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint() && mono_domain_get() == NULL) { CRASH_COND(Thread::get_caller_id() == Thread::get_main_id()); @@ -2297,14 +2298,20 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p // because this may be called by one of the editor's worker threads. // Attach this thread temporarily to reload the script. - MonoThread *mono_thread = mono_thread_attach(SCRIPTS_DOMAIN); - CRASH_COND(mono_thread == NULL); + if (SCRIPTS_DOMAIN) { + MonoThread *mono_thread = mono_thread_attach(SCRIPTS_DOMAIN); + CRASH_COND(mono_thread == NULL); + script->reload(); + mono_thread_detach(mono_thread); + } + + } else { // just reload it normally +#endif script->reload(); - mono_thread_detach(mono_thread); - } else // just reload it normally +#ifdef TOOLS_ENABLED + } #endif - script->reload(); if (r_error) *r_error = OK; diff --git a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs b/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs index 04da0600cc..f3b4b66663 100644 --- a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs +++ b/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs @@ -16,24 +16,48 @@ namespace GodotSharpTools.Build private extern static void godot_icall_BuildInstance_ExitCallback(string solution, string config, int exitCode); [MethodImpl(MethodImplOptions.InternalCall)] - private extern static void godot_icall_BuildInstance_get_MSBuildInfo(ref string msbuildPath, ref string frameworkPath); + private extern static string godot_icall_BuildInstance_get_MSBuildPath(); + [MethodImpl(MethodImplOptions.InternalCall)] + private extern static string godot_icall_BuildInstance_get_FrameworkPath(); + [MethodImpl(MethodImplOptions.InternalCall)] + private extern static string godot_icall_BuildInstance_get_MonoWindowsBinDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private extern static bool godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows(); - private struct MSBuildInfo + private static string GetMSBuildPath() { - public string path; - public string frameworkPathOverride; + string msbuildPath = godot_icall_BuildInstance_get_MSBuildPath(); + + if (msbuildPath == null) + throw new FileNotFoundException("Cannot find the MSBuild executable."); + + return msbuildPath; } - private static MSBuildInfo GetMSBuildInfo() + private static string GetFrameworkPath() { - MSBuildInfo msbuildInfo = new MSBuildInfo(); + return godot_icall_BuildInstance_get_FrameworkPath(); + } - godot_icall_BuildInstance_get_MSBuildInfo(ref msbuildInfo.path, ref msbuildInfo.frameworkPathOverride); + private static string MonoWindowsBinDir + { + get + { + string monoWinBinDir = godot_icall_BuildInstance_get_MonoWindowsBinDir(); - if (msbuildInfo.path == null) - throw new FileNotFoundException("Cannot find the MSBuild executable."); + if (monoWinBinDir == null) + throw new FileNotFoundException("Cannot find the Windows Mono binaries directory."); + + return monoWinBinDir; + } + } - return msbuildInfo; + private static bool UsingMonoMSBuildOnWindows + { + get + { + return godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows(); + } } private string solution; @@ -54,25 +78,35 @@ namespace GodotSharpTools.Build public bool Build(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null) { - MSBuildInfo msbuildInfo = GetMSBuildInfo(); - List<string> customPropertiesList = new List<string>(); if (customProperties != null) customPropertiesList.AddRange(customProperties); - if (msbuildInfo.frameworkPathOverride != null) - customPropertiesList.Add("FrameworkPathOverride=" + msbuildInfo.frameworkPathOverride); + string frameworkPath = GetFrameworkPath(); + + if (!string.IsNullOrEmpty(frameworkPath)) + customPropertiesList.Add("FrameworkPathOverride=" + frameworkPath); string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customPropertiesList); - ProcessStartInfo startInfo = new ProcessStartInfo(msbuildInfo.path, compilerArgs); + ProcessStartInfo startInfo = new ProcessStartInfo(GetMSBuildPath(), compilerArgs); // No console output, thanks startInfo.RedirectStandardOutput = true; startInfo.RedirectStandardError = true; startInfo.UseShellExecute = false; + if (UsingMonoMSBuildOnWindows) + { + // These environment variables are required for Mono's MSBuild to find the compilers. + // We use the batch files in Mono's bin directory to make sure the compilers are executed with mono. + string monoWinBinDir = MonoWindowsBinDir; + startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat")); + startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat")); + startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat")); + } + // Needed when running from Developer Command Prompt for VS RemovePlatformVariable(startInfo.EnvironmentVariables); @@ -98,25 +132,35 @@ namespace GodotSharpTools.Build if (process != null) throw new InvalidOperationException("Already in use"); - MSBuildInfo msbuildInfo = GetMSBuildInfo(); - List<string> customPropertiesList = new List<string>(); if (customProperties != null) customPropertiesList.AddRange(customProperties); - if (msbuildInfo.frameworkPathOverride.Length > 0) - customPropertiesList.Add("FrameworkPathOverride=" + msbuildInfo.frameworkPathOverride); + string frameworkPath = GetFrameworkPath(); + + if (!string.IsNullOrEmpty(frameworkPath)) + customPropertiesList.Add("FrameworkPathOverride=" + frameworkPath); string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customPropertiesList); - ProcessStartInfo startInfo = new ProcessStartInfo(msbuildInfo.path, compilerArgs); + ProcessStartInfo startInfo = new ProcessStartInfo(GetMSBuildPath(), compilerArgs); // No console output, thanks startInfo.RedirectStandardOutput = true; startInfo.RedirectStandardError = true; startInfo.UseShellExecute = false; + if (UsingMonoMSBuildOnWindows) + { + // These environment variables are required for Mono's MSBuild to find the compilers. + // We use the batch files in Mono's bin directory to make sure the compilers are executed with mono. + string monoWinBinDir = MonoWindowsBinDir; + startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat")); + startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat")); + startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat")); + } + // Needed when running from Developer Command Prompt for VS RemovePlatformVariable(startInfo.EnvironmentVariables); diff --git a/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj b/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj index 981083a3c2..1c8714e31d 100644 --- a/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj +++ b/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj @@ -11,7 +11,7 @@ </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> - <DebugType>full</DebugType> + <DebugType>portable</DebugType> <Optimize>false</Optimize> <OutputPath>bin\Debug</OutputPath> <DefineConstants>DEBUG;</DefineConstants> @@ -20,7 +20,7 @@ <ConsolePause>false</ConsolePause> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>full</DebugType> + <DebugType>portable</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release</OutputPath> <ErrorReport>prompt</ErrorReport> diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs b/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs index 6bf54a0156..1d863e6f61 100644 --- a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs +++ b/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs @@ -70,7 +70,7 @@ namespace GodotSharpTools.Project var toolsGroup = root.AddPropertyGroup(); toolsGroup.Condition = " '$(Configuration)|$(Platform)' == 'Tools|AnyCPU' "; toolsGroup.AddProperty("DebugSymbols", "true"); - toolsGroup.AddProperty("DebugType", "full"); + toolsGroup.AddProperty("DebugType", "portable"); toolsGroup.AddProperty("Optimize", "false"); toolsGroup.AddProperty("DefineConstants", "DEBUG;TOOLS;"); toolsGroup.AddProperty("ErrorReport", "prompt"); @@ -148,7 +148,7 @@ namespace GodotSharpTools.Project var debugGroup = root.AddPropertyGroup(); debugGroup.Condition = " '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "; debugGroup.AddProperty("DebugSymbols", "true"); - debugGroup.AddProperty("DebugType", "full"); + debugGroup.AddProperty("DebugType", "portable"); debugGroup.AddProperty("Optimize", "false"); debugGroup.AddProperty("DefineConstants", "DEBUG;"); debugGroup.AddProperty("ErrorReport", "prompt"); @@ -157,7 +157,7 @@ namespace GodotSharpTools.Project var releaseGroup = root.AddPropertyGroup(); releaseGroup.Condition = " '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "; - releaseGroup.AddProperty("DebugType", "full"); + releaseGroup.AddProperty("DebugType", "portable"); releaseGroup.AddProperty("Optimize", "true"); releaseGroup.AddProperty("ErrorReport", "prompt"); releaseGroup.AddProperty("WarningLevel", "4"); diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index a210b8e480..4c598d4f37 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -248,14 +248,14 @@ void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) { if (imethod.is_virtual) continue; - const TypeInterface *return_type = _get_type_by_name_or_placeholder(imethod.return_type); + const TypeInterface *return_type = _get_type_or_placeholder(imethod.return_type); String im_sig; String im_unique_sig; if (p_itype.is_object_type) { im_sig += "IntPtr " CS_PARAM_METHODBIND ", "; - im_unique_sig += imethod.return_type.operator String() + ",IntPtr,IntPtr"; + im_unique_sig += imethod.return_type.cname.operator String() + ",IntPtr,IntPtr"; } im_sig += "IntPtr " CS_PARAM_INSTANCE; @@ -263,7 +263,7 @@ void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) { // Get arguments information int i = 0; for (const List<ArgumentInterface>::Element *F = imethod.arguments.front(); F; F = F->next()) { - const TypeInterface *arg_type = _get_type_by_name_or_placeholder(F->get().type); + const TypeInterface *arg_type = _get_type_or_placeholder(F->get().type); im_sig += ", "; im_sig += arg_type->im_type_in; @@ -730,7 +730,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str } output.push_back(INDENT1 "public "); - bool is_abstract = !ClassDB::can_instance(itype.name) && ClassDB::is_class_enabled(itype.name); // can_instance returns true if there's a constructor and the class is not 'disabled' + bool is_abstract = itype.is_object_type && !ClassDB::can_instance(itype.name) && ClassDB::is_class_enabled(itype.name); // can_instance returns true if there's a constructor and the class is not 'disabled' output.push_back(itype.is_singleton ? "static class " : (is_abstract ? "abstract class " : "class ")); output.push_back(itype.proxy_name); @@ -1069,12 +1069,12 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte } if (getter && setter) { - ERR_FAIL_COND_V(getter->return_type != setter->arguments.back()->get().type, ERR_BUG); + ERR_FAIL_COND_V(getter->return_type.cname != setter->arguments.back()->get().type.cname, ERR_BUG); } - StringName proptype_name = getter ? getter->return_type : setter->arguments.back()->get().type; + const TypeReference &proptype_name = getter ? getter->return_type : setter->arguments.back()->get().type; - const TypeInterface *prop_itype = _get_type_by_name_or_null(proptype_name); + const TypeInterface *prop_itype = _get_type_or_null(proptype_name); ERR_FAIL_NULL_V(prop_itype, ERR_BUG); // Property type not found String prop_proxy_name = escape_csharp_keyword(snake_to_pascal_case(p_iprop.cname)); @@ -1122,9 +1122,9 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte p_output.push_back(getter->proxy_name + "("); if (p_iprop.index != -1) { const ArgumentInterface &idx_arg = getter->arguments.front()->get(); - if (idx_arg.type != name_cache.type_int) { + if (idx_arg.type.cname != name_cache.type_int) { // Assume the index parameter is an enum - const TypeInterface *idx_arg_type = _get_type_by_name_or_null(idx_arg.type); + const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type); CRASH_COND(idx_arg_type == NULL); p_output.push_back("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index)); } else { @@ -1139,9 +1139,9 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte p_output.push_back(setter->proxy_name + "("); if (p_iprop.index != -1) { const ArgumentInterface &idx_arg = setter->arguments.front()->get(); - if (idx_arg.type != name_cache.type_int) { + if (idx_arg.type.cname != name_cache.type_int) { // Assume the index parameter is an enum - const TypeInterface *idx_arg_type = _get_type_by_name_or_null(idx_arg.type); + const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type); CRASH_COND(idx_arg_type == NULL); p_output.push_back("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index) + ", "); } else { @@ -1158,7 +1158,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, List<String> &p_output) { - const TypeInterface *return_type = _get_type_by_name_or_placeholder(p_imethod.return_type); + const TypeInterface *return_type = _get_type_or_placeholder(p_imethod.return_type); String method_bind_field = "method_bind_" + itos(p_method_bind_count); @@ -1175,7 +1175,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf // Retrieve information from the arguments for (const List<ArgumentInterface>::Element *F = p_imethod.arguments.front(); F; F = F->next()) { const ArgumentInterface &iarg = F->get(); - const TypeInterface *arg_type = _get_type_by_name_or_placeholder(iarg.type); + const TypeInterface *arg_type = _get_type_or_placeholder(iarg.type); // Add the current arguments to the signature // If the argument has a default value which is not a constant, we will make it Nullable @@ -1328,21 +1328,19 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf const InternalCall *im_icall = match->value(); String im_call = im_icall->editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS; - im_call += "." + im_icall->name + "(" + icall_params + ");\n"; + im_call += "." + im_icall->name + "(" + icall_params + ")"; if (p_imethod.arguments.size()) p_output.push_back(cs_in_statements); if (return_type->cname == name_cache.type_void) { - p_output.push_back(im_call); + p_output.push_back(im_call + ";\n"); } else if (return_type->cs_out.empty()) { - p_output.push_back("return " + im_call); + p_output.push_back("return " + im_call + ";\n"); } else { - p_output.push_back(return_type->im_type_out); - p_output.push_back(" " LOCAL_RET " = "); - p_output.push_back(im_call); p_output.push_back(INDENT3); - p_output.push_back(sformat(return_type->cs_out, LOCAL_RET) + "\n"); + p_output.push_back(sformat(return_type->cs_out, im_call, return_type->cs_type, return_type->im_type_out)); + p_output.push_back("\n"); } p_output.push_back(CLOSE_BLOCK_L2); @@ -1540,9 +1538,9 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte if (p_imethod.is_virtual) return OK; // Ignore - bool ret_void = p_imethod.return_type == name_cache.type_void; + bool ret_void = p_imethod.return_type.cname == name_cache.type_void; - const TypeInterface *return_type = _get_type_by_name_or_placeholder(p_imethod.return_type); + const TypeInterface *return_type = _get_type_or_placeholder(p_imethod.return_type); String argc_str = itos(p_imethod.arguments.size()); @@ -1554,7 +1552,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte int i = 0; for (const List<ArgumentInterface>::Element *F = p_imethod.arguments.front(); F; F = F->next()) { const ArgumentInterface &iarg = F->get(); - const TypeInterface *arg_type = _get_type_by_name_or_placeholder(iarg.type); + const TypeInterface *arg_type = _get_type_or_placeholder(iarg.type); String c_param_name = "arg" + itos(i + 1); @@ -1695,42 +1693,49 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte return OK; } -const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_by_name_or_null(const StringName &p_cname) { +const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_null(const TypeReference &p_typeref) { - const Map<StringName, TypeInterface>::Element *builtin_type_match = builtin_types.find(p_cname); + const Map<StringName, TypeInterface>::Element *builtin_type_match = builtin_types.find(p_typeref.cname); if (builtin_type_match) return &builtin_type_match->get(); - const OrderedHashMap<StringName, TypeInterface>::Element obj_type_match = obj_types.find(p_cname); + const OrderedHashMap<StringName, TypeInterface>::Element obj_type_match = obj_types.find(p_typeref.cname); if (obj_type_match) return &obj_type_match.get(); - const Map<StringName, TypeInterface>::Element *enum_match = enum_types.find(p_cname); + if (p_typeref.is_enum) { + const Map<StringName, TypeInterface>::Element *enum_match = enum_types.find(p_typeref.cname); - if (enum_match) - return &enum_match->get(); + if (enum_match) + return &enum_match->get(); + + // Enum not found. Most likely because none of its constants were bound, so it's empty. That's fine. Use int instead. + const Map<StringName, TypeInterface>::Element *int_match = builtin_types.find(name_cache.type_int); + ERR_FAIL_NULL_V(int_match, NULL); + return &int_match->get(); + } return NULL; } -const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_by_name_or_placeholder(const StringName &p_cname) { +const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_placeholder(const TypeReference &p_typeref) { - const TypeInterface *found = _get_type_by_name_or_null(p_cname); + const TypeInterface *found = _get_type_or_null(p_typeref); if (found) return found; - ERR_PRINTS(String() + "Type not found. Creating placeholder: " + p_cname.operator String()); + ERR_PRINTS(String() + "Type not found. Creating placeholder: " + p_typeref.cname.operator String()); - const Map<StringName, TypeInterface>::Element *match = placeholder_types.find(p_cname); + const Map<StringName, TypeInterface>::Element *match = placeholder_types.find(p_typeref.cname); if (match) return &match->get(); TypeInterface placeholder; - TypeInterface::create_placeholder_type(placeholder, p_cname); + TypeInterface::create_placeholder_type(placeholder, p_typeref.cname); return &placeholder_types.insert(placeholder.cname, placeholder)->get(); } @@ -1874,7 +1879,7 @@ void BindingsGenerator::_populate_object_type_interfaces() { // The method Object.free is registered as a virtual method, but without the virtual flag. // This is because this method is not supposed to be overridden, but called. // We assume the return type is void. - imethod.return_type = name_cache.type_void; + imethod.return_type.cname = name_cache.type_void; // Actually, more methods like this may be added in the future, // which could actually will return something different. @@ -1889,21 +1894,22 @@ void BindingsGenerator::_populate_object_type_interfaces() { } else { ERR_PRINTS("Missing MethodBind for non-virtual method: " + itype.name + "." + imethod.name); } - } else if (return_info.type == Variant::INT && return_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { // TODO redundant? - imethod.return_type = return_info.class_name; + } else if (return_info.type == Variant::INT && return_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + imethod.return_type.cname = return_info.class_name; + imethod.return_type.is_enum = true; } else if (return_info.class_name != StringName()) { - imethod.return_type = return_info.class_name; + imethod.return_type.cname = return_info.class_name; } else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { - imethod.return_type = return_info.hint_string; + imethod.return_type.cname = return_info.hint_string; } else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { - imethod.return_type = name_cache.type_Variant; + imethod.return_type.cname = name_cache.type_Variant; } else if (return_info.type == Variant::NIL) { - imethod.return_type = name_cache.type_void; + imethod.return_type.cname = name_cache.type_void; } else { - imethod.return_type = Variant::get_type_name(return_info.type); + imethod.return_type.cname = Variant::get_type_name(return_info.type); } - if (!itype.requires_collections && imethod.return_type == name_cache.type_Dictionary) + if (!itype.requires_collections && imethod.return_type.cname == name_cache.type_Dictionary) itype.requires_collections = true; for (int i = 0; i < argc; i++) { @@ -1912,21 +1918,22 @@ void BindingsGenerator::_populate_object_type_interfaces() { ArgumentInterface iarg; iarg.name = arginfo.name; - if (arginfo.type == Variant::INT && arginfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { // TODO redundant? - iarg.type = arginfo.class_name; + if (arginfo.type == Variant::INT && arginfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + iarg.type.cname = arginfo.class_name; + iarg.type.is_enum = true; } else if (arginfo.class_name != StringName()) { - iarg.type = arginfo.class_name; + iarg.type.cname = arginfo.class_name; } else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { - iarg.type = arginfo.hint_string; + iarg.type.cname = arginfo.hint_string; } else if (arginfo.type == Variant::NIL) { - iarg.type = name_cache.type_Variant; + iarg.type.cname = name_cache.type_Variant; } else { - iarg.type = Variant::get_type_name(arginfo.type); + iarg.type.cname = Variant::get_type_name(arginfo.type); } iarg.name = escape_csharp_keyword(snake_to_camel_case(iarg.name)); - if (!itype.requires_collections && iarg.type == name_cache.type_Dictionary) + if (!itype.requires_collections && iarg.type.cname == name_cache.type_Dictionary) itype.requires_collections = true; if (m && m->has_default_argument(i)) { @@ -1938,7 +1945,7 @@ void BindingsGenerator::_populate_object_type_interfaces() { if (imethod.is_vararg) { ArgumentInterface ivararg; - ivararg.type = name_cache.type_VarArg; + ivararg.type.cname = name_cache.type_VarArg; ivararg.name = "@args"; imethod.add_argument(ivararg); } @@ -2023,17 +2030,11 @@ void BindingsGenerator::_populate_object_type_interfaces() { itype.enums.push_back(ienum); TypeInterface enum_itype; + enum_itype.is_enum = true; enum_itype.name = itype.name + "." + String(*k); enum_itype.cname = StringName(enum_itype.name); enum_itype.proxy_name = itype.proxy_name + "." + enum_proxy_name; - enum_itype.c_arg_in = "&%s"; - enum_itype.c_type = "int"; - enum_itype.c_type_in = "int"; - enum_itype.c_type_out = "int"; - enum_itype.cs_type = enum_itype.proxy_name; - enum_itype.im_type_in = enum_itype.proxy_name; - enum_itype.im_type_out = enum_itype.proxy_name; - enum_itype.class_doc = &EditorHelp::get_doc_data()->class_list[enum_itype.proxy_name]; + TypeInterface::postsetup_enum_type(enum_itype); enum_types.insert(enum_itype.cname, enum_itype); } @@ -2068,7 +2069,7 @@ void BindingsGenerator::_default_argument_from_variant(const Variant &p_val, Arg switch (p_val.get_type()) { case Variant::NIL: - if (ClassDB::class_exists(r_iarg.type)) { + if (ClassDB::class_exists(r_iarg.type.cname)) { // Object type r_iarg.default_argument = "null"; } else { @@ -2081,7 +2082,7 @@ void BindingsGenerator::_default_argument_from_variant(const Variant &p_val, Arg r_iarg.default_argument = bool(p_val) ? "true" : "false"; break; case Variant::INT: - if (r_iarg.type != name_cache.type_int) { + if (r_iarg.type.cname != name_cache.type_int) { r_iarg.default_argument = "(%s)" + r_iarg.default_argument; } break; @@ -2142,7 +2143,7 @@ void BindingsGenerator::_default_argument_from_variant(const Variant &p_val, Arg default: {} } - if (r_iarg.def_param_mode == ArgumentInterface::CONSTANT && r_iarg.type == name_cache.type_Variant && r_iarg.default_argument != "null") + if (r_iarg.def_param_mode == ArgumentInterface::CONSTANT && r_iarg.type.cname == name_cache.type_Variant && r_iarg.default_argument != "null") r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF; } @@ -2161,7 +2162,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_arg_in = "&%s_in"; \ itype.c_type_in = m_type_in; \ itype.cs_in = "ref %s"; \ - itype.cs_out = "return (" #m_type ")%0;"; \ + itype.cs_out = "return (%1)%0;"; \ itype.im_type_out = "object"; \ builtin_types.insert(itype.cname, itype); \ } @@ -2256,7 +2257,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type_out = itype.c_type + "*"; itype.cs_type = itype.proxy_name; itype.cs_in = "NodePath." CS_SMETHOD_GETINSTANCE "(%0)"; - itype.cs_out = "return new NodePath(%0);"; + itype.cs_out = "return new %1(%0);"; itype.im_type_in = "IntPtr"; itype.im_type_out = "IntPtr"; _populate_builtin_type(itype, Variant::NODE_PATH); @@ -2279,7 +2280,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type_out = itype.c_type + "*"; itype.cs_type = itype.proxy_name; itype.cs_in = "RID." CS_SMETHOD_GETINSTANCE "(%0)"; - itype.cs_out = "return new RID(%0);"; + itype.cs_out = "return new %1(%0);"; itype.im_type_in = "IntPtr"; itype.im_type_out = "IntPtr"; _populate_builtin_type(itype, Variant::_RID); @@ -2408,11 +2409,11 @@ void BindingsGenerator::_populate_builtin_type(TypeInterface &r_itype, Variant:: iarg.name = pi.name; if (pi.type == Variant::NIL) - iarg.type = name_cache.type_Variant; + iarg.type.cname = name_cache.type_Variant; else - iarg.type = Variant::get_type_name(pi.type); + iarg.type.cname = Variant::get_type_name(pi.type); - if (!r_itype.requires_collections && iarg.type == name_cache.type_Dictionary) + if (!r_itype.requires_collections && iarg.type.cname == name_cache.type_Dictionary) r_itype.requires_collections = true; if ((mi.default_arguments.size() - mi.arguments.size() + i) >= 0) @@ -2423,12 +2424,12 @@ void BindingsGenerator::_populate_builtin_type(TypeInterface &r_itype, Variant:: if (mi.return_val.type == Variant::NIL) { if (mi.return_val.name != "") - imethod.return_type = name_cache.type_Variant; + imethod.return_type.cname = name_cache.type_Variant; } else { - imethod.return_type = Variant::get_type_name(mi.return_val.type); + imethod.return_type.cname = Variant::get_type_name(mi.return_val.type); } - if (!r_itype.requires_collections && imethod.return_type == name_cache.type_Dictionary) + if (!r_itype.requires_collections && imethod.return_type.cname == name_cache.type_Dictionary) r_itype.requires_collections = true; if (r_itype.class_doc) { @@ -2494,13 +2495,11 @@ void BindingsGenerator::_populate_global_constants() { EnumInterface &ienum = E->get(); TypeInterface enum_itype; - enum_itype = TypeInterface::create_value_type(ienum.cname); - enum_itype.c_arg_in = "&%s"; - enum_itype.c_type = "int"; - enum_itype.c_type_in = "int"; - enum_itype.c_type_out = "int"; - enum_itype.im_type_in = enum_itype.name; - enum_itype.im_type_out = enum_itype.name; + enum_itype.is_enum = true; + enum_itype.name = ienum.cname.operator String(); + enum_itype.cname = ienum.cname; + enum_itype.proxy_name = enum_itype.name; + TypeInterface::postsetup_enum_type(enum_itype); enum_types.insert(enum_itype.cname, enum_itype); ienum.prefix = _determine_enum_prefix(ienum); @@ -2521,15 +2520,13 @@ void BindingsGenerator::_populate_global_constants() { hardcoded_enums.push_back("Vector3.Axis"); for (List<StringName>::Element *E = hardcoded_enums.front(); E; E = E->next()) { // These enums are not generated and must be written manually (e.g.: Vector3.Axis) - // Here, we are assuming core types do not begin with underscore + // Here, we assume core types do not begin with underscore TypeInterface enum_itype; - enum_itype = TypeInterface::create_value_type(E->get()); - enum_itype.c_arg_in = "&%s"; - enum_itype.c_type = "int"; - enum_itype.c_type_in = "int"; - enum_itype.c_type_out = "int"; - enum_itype.im_type_in = enum_itype.name; - enum_itype.im_type_out = enum_itype.name; + enum_itype.is_enum = true; + enum_itype.name = E->get().operator String(); + enum_itype.cname = E->get(); + enum_itype.proxy_name = enum_itype.name; + TypeInterface::postsetup_enum_type(enum_itype); enum_types.insert(enum_itype.cname, enum_itype); } } diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index f6194139af..5b33a0e53f 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -81,6 +81,15 @@ class BindingsGenerator { const DocData::PropertyDoc *prop_doc; }; + struct TypeReference { + StringName cname; + bool is_enum; + + TypeReference() { + is_enum = false; + } + }; + struct ArgumentInterface { enum DefaultParamMode { CONSTANT, @@ -88,7 +97,8 @@ class BindingsGenerator { NULLABLE_REF }; - StringName type; + TypeReference type; + String name; String default_argument; DefaultParamMode def_param_mode; @@ -110,7 +120,7 @@ class BindingsGenerator { /** * [TypeInterface::name] of the return type */ - StringName return_type; + TypeReference return_type; /** * Determines if the method has a variable number of arguments (VarArg) @@ -146,7 +156,7 @@ class BindingsGenerator { } MethodInterface() { - return_type = BindingsGenerator::get_singleton()->name_cache.type_void; + return_type.cname = BindingsGenerator::get_singleton()->name_cache.type_void; is_vararg = false; is_virtual = false; requires_object_call = false; @@ -175,6 +185,7 @@ class BindingsGenerator { ClassDB::APIType api_type; + bool is_enum; bool is_object_type; bool is_singleton; bool is_reference; @@ -276,7 +287,9 @@ class BindingsGenerator { * One or more statements that determine how a variable of this type is returned from a method. * It must contain the return statement(s). * Formatting elements: - * %0 or %s: name of the variable to be returned + * %0: internal method call statement + * %1: [cs_type] of the return type + * %2: [im_type_out] of the return type */ String cs_out; @@ -293,8 +306,6 @@ class BindingsGenerator { /** * Type used for the return type of internal call methods. - * If [cs_out] is not empty and the method return type is not void, - * it is also used for the type of the return variable. */ String im_type_out; @@ -379,10 +390,24 @@ class BindingsGenerator { r_itype.im_type_out = r_itype.proxy_name; } + static void postsetup_enum_type(TypeInterface &r_enum_itype) { + r_enum_itype.c_arg_in = "&%s"; + r_enum_itype.c_type = "int"; + r_enum_itype.c_type_in = "int"; + r_enum_itype.c_type_out = "int"; + r_enum_itype.cs_type = r_enum_itype.proxy_name; + r_enum_itype.cs_in = "(int)%s"; + r_enum_itype.cs_out = "return (%1)%0;"; + r_enum_itype.im_type_in = "int"; + r_enum_itype.im_type_out = "int"; + r_enum_itype.class_doc = &EditorHelp::get_doc_data()->class_list[r_enum_itype.proxy_name]; + } + TypeInterface() { api_type = ClassDB::API_NONE; + is_enum = false; is_object_type = false; is_singleton = false; is_reference = false; @@ -492,6 +517,8 @@ class BindingsGenerator { return "Ref"; else if (p_type.is_object_type) return "Obj"; + else if (p_type.is_enum) + return "int"; return p_type.name; } @@ -501,8 +528,8 @@ class BindingsGenerator { void _generate_header_icalls(); void _generate_method_icalls(const TypeInterface &p_itype); - const TypeInterface *_get_type_by_name_or_null(const StringName &p_cname); - const TypeInterface *_get_type_by_name_or_placeholder(const StringName &p_cname); + const TypeInterface *_get_type_or_null(const TypeReference &p_typeref); + const TypeInterface *_get_type_or_placeholder(const TypeReference &p_typeref); void _default_argument_from_variant(const Variant &p_val, ArgumentInterface &r_iarg); void _populate_builtin_type(TypeInterface &r_itype, Variant::Type vtype); diff --git a/modules/mono/editor/godotsharp_builds.cpp b/modules/mono/editor/godotsharp_builds.cpp index 2f2b5768db..ef54046f12 100644 --- a/modules/mono/editor/godotsharp_builds.cpp +++ b/modules/mono/editor/godotsharp_builds.cpp @@ -39,6 +39,10 @@ #include "bindings_generator.h" #include "godotsharp_editor.h" +#define PROP_NAME_MSBUILD_MONO "MSBuild (Mono)" +#define PROP_NAME_MSBUILD_VS "MSBuild (VS Build Tools)" +#define PROP_NAME_XBUILD "xbuild (Deprecated)" + void godot_icall_BuildInstance_ExitCallback(MonoString *p_solution, MonoString *p_config, int p_exit_code) { String solution = GDMonoMarshal::mono_string_to_godot(p_solution); @@ -76,46 +80,42 @@ String _find_build_engine_on_unix(const String &p_name) { } #endif -void godot_icall_BuildInstance_get_MSBuildInfo(MonoString **r_msbuild_path, MonoString **r_framework_path) { +MonoString *godot_icall_BuildInstance_get_MSBuildPath() { GodotSharpBuilds::BuildTool build_tool = GodotSharpBuilds::BuildTool(int(EditorSettings::get_singleton()->get("mono/builds/build_tool"))); #if defined(WINDOWS_ENABLED) switch (build_tool) { - case GodotSharpBuilds::MSBUILD: { + case GodotSharpBuilds::MSBUILD_VS: { static String msbuild_tools_path = MonoRegUtils::find_msbuild_tools_path(); if (msbuild_tools_path.length()) { if (!msbuild_tools_path.ends_with("\\")) msbuild_tools_path += "\\"; - // FrameworkPathOverride - const MonoRegInfo &mono_reg_info = GDMono::get_singleton()->get_mono_reg_info(); - if (mono_reg_info.assembly_dir.length()) { - *r_msbuild_path = GDMonoMarshal::mono_string_from_godot(msbuild_tools_path + "MSBuild.exe"); - - String framework_path = path_join(mono_reg_info.assembly_dir, "mono", "4.5"); - *r_framework_path = GDMonoMarshal::mono_string_from_godot(framework_path); - } else { - ERR_PRINT("Cannot find Mono's assemblies directory in the registry"); - } - - return; + return GDMonoMarshal::mono_string_from_godot(msbuild_tools_path + "MSBuild.exe"); } if (OS::get_singleton()->is_stdout_verbose()) - OS::get_singleton()->print("Cannot find System's MSBuild. Trying with Mono's...\n"); - } // fall through + OS::get_singleton()->print("Cannot find executable for '" PROP_NAME_MSBUILD_VS "'. Trying with '" PROP_NAME_MSBUILD_MONO "'...\n"); + } // FALL THROUGH case GodotSharpBuilds::MSBUILD_MONO: { String msbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("msbuild.bat"); if (!FileAccess::exists(msbuild_path)) { - WARN_PRINTS("Cannot find msbuild ('mono/builds/build_tool'). Tried with path: " + msbuild_path); + WARN_PRINTS("Cannot find executable for '" PROP_NAME_MSBUILD_MONO "'. Tried with path: " + msbuild_path); } - *r_msbuild_path = GDMonoMarshal::mono_string_from_godot(msbuild_path); + return GDMonoMarshal::mono_string_from_godot(msbuild_path); + } break; + case GodotSharpBuilds::XBUILD: { + String xbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("xbuild.bat"); - return; + if (!FileAccess::exists(xbuild_path)) { + WARN_PRINTS("Cannot find executable for '" PROP_NAME_XBUILD "'. Tried with path: " + xbuild_path); + } + + return GDMonoMarshal::mono_string_from_godot(xbuild_path); } break; default: ERR_EXPLAIN("You don't deserve to live"); @@ -125,31 +125,74 @@ void godot_icall_BuildInstance_get_MSBuildInfo(MonoString **r_msbuild_path, Mono static String msbuild_path = _find_build_engine_on_unix("msbuild"); static String xbuild_path = _find_build_engine_on_unix("xbuild"); - if (build_tool != GodotSharpBuilds::XBUILD) { - if (msbuild_path.empty()) { - WARN_PRINT("Cannot find msbuild ('mono/builds/build_tool')."); + if (build_tool == GodotSharpBuilds::XBUILD) { + if (xbuild_path.empty()) { + WARN_PRINT("Cannot find binary for '" PROP_NAME_XBUILD "'"); return; } } else { - if (xbuild_path.empty()) { - WARN_PRINT("Cannot find xbuild ('mono/builds/build_tool')."); + if (msbuild_path.empty()) { + WARN_PRINT("Cannot find binary for '" PROP_NAME_MSBUILD_MONO "'"); return; } } - *r_msbuild_path = GDMonoMarshal::mono_string_from_godot(build_tool != GodotSharpBuilds::XBUILD ? msbuild_path : xbuild_path); + return GDMonoMarshal::mono_string_from_godot(build_tool != GodotSharpBuilds::XBUILD ? msbuild_path : xbuild_path); +#else + (void)build_tool; // UNUSED + + ERR_EXPLAIN("Not implemented on this platform"); + ERR_FAIL_V(NULL); +#endif +} + +MonoString *godot_icall_BuildInstance_get_FrameworkPath() { + +#if defined(WINDOWS_ENABLED) + const MonoRegInfo &mono_reg_info = GDMono::get_singleton()->get_mono_reg_info(); + if (mono_reg_info.assembly_dir.length()) { + String framework_path = path_join(mono_reg_info.assembly_dir, "mono", "4.5"); + return GDMonoMarshal::mono_string_from_godot(framework_path); + } - return; + ERR_EXPLAIN("Cannot find Mono's assemblies directory in the registry"); + ERR_FAIL_V(NULL); #else - ERR_PRINT("Not implemented on this platform"); - return; + return NULL; +#endif +} + +MonoString *godot_icall_BuildInstance_get_MonoWindowsBinDir() { + +#if defined(WINDOWS_ENABLED) + const MonoRegInfo &mono_reg_info = GDMono::get_singleton()->get_mono_reg_info(); + if (mono_reg_info.bin_dir.length()) { + return GDMonoMarshal::mono_string_from_godot(mono_reg_info.bin_dir); + } + + ERR_EXPLAIN("Cannot find Mono's binaries directory in the registry"); + ERR_FAIL_V(NULL); +#else + return NULL; +#endif +} + +MonoBoolean godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows() { + +#if defined(WINDOWS_ENABLED) + return GodotSharpBuilds::BuildTool(int(EditorSettings::get_singleton()->get("mono/builds/build_tool"))) == GodotSharpBuilds::MSBUILD_MONO; +#else + return false; #endif } void GodotSharpBuilds::_register_internal_calls() { mono_add_internal_call("GodotSharpTools.Build.BuildSystem::godot_icall_BuildInstance_ExitCallback", (void *)godot_icall_BuildInstance_ExitCallback); - mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_MSBuildInfo", (void *)godot_icall_BuildInstance_get_MSBuildInfo); + mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_MSBuildPath", (void *)godot_icall_BuildInstance_get_MSBuildPath); + mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_FrameworkPath", (void *)godot_icall_BuildInstance_get_FrameworkPath); + mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_MonoWindowsBinDir", (void *)godot_icall_BuildInstance_get_MonoWindowsBinDir); + mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows", (void *)godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows); } void GodotSharpBuilds::show_build_error_dialog(const String &p_message) { @@ -386,20 +429,14 @@ GodotSharpBuilds::GodotSharpBuilds() { // Build tool settings EditorSettings *ed_settings = EditorSettings::get_singleton(); -#ifdef WINDOWS_ENABLED - // TODO: Default to MSBUILD_MONO if its csc.exe issue is fixed in the installed mono version - EDITOR_DEF("mono/builds/build_tool", MSBUILD); -#else EDITOR_DEF("mono/builds/build_tool", MSBUILD_MONO); -#endif ed_settings->add_property_hint(PropertyInfo(Variant::INT, "mono/builds/build_tool", PROPERTY_HINT_ENUM, + PROP_NAME_MSBUILD_MONO #ifdef WINDOWS_ENABLED - "MSBuild (Mono),MSBuild (System)" -#else - "MSBuild (Mono),xbuild (Deprecated)" + "," PROP_NAME_MSBUILD_VS #endif - )); + "," PROP_NAME_XBUILD)); } GodotSharpBuilds::~GodotSharpBuilds() { diff --git a/modules/mono/editor/godotsharp_builds.h b/modules/mono/editor/godotsharp_builds.h index 27b771e324..4afc284d45 100644 --- a/modules/mono/editor/godotsharp_builds.h +++ b/modules/mono/editor/godotsharp_builds.h @@ -68,10 +68,9 @@ public: enum BuildTool { MSBUILD_MONO, #ifdef WINDOWS_ENABLED - MSBUILD -#else - XBUILD // Deprecated + MSBUILD_VS, #endif + XBUILD // Deprecated }; _FORCE_INLINE_ static GodotSharpBuilds *get_singleton() { return singleton; } diff --git a/modules/mono/glue/builtin_types_glue.h b/modules/mono/glue/builtin_types_glue.h index 460de84b65..ef9f152682 100644 --- a/modules/mono/glue/builtin_types_glue.h +++ b/modules/mono/glue/builtin_types_glue.h @@ -1,3 +1,33 @@ +/*************************************************************************/ +/* builtin_types_glue.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 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 BUILTIN_TYPES_GLUE_H #define BUILTIN_TYPES_GLUE_H diff --git a/modules/mono/glue/cs_files/GodotTaskScheduler.cs b/modules/mono/glue/cs_files/GodotTaskScheduler.cs index 6bf25a89d2..3d23ec10f1 100644 --- a/modules/mono/glue/cs_files/GodotTaskScheduler.cs +++ b/modules/mono/glue/cs_files/GodotTaskScheduler.cs @@ -14,6 +14,7 @@ namespace Godot public GodotTaskScheduler() { Context = new GodotSynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(Context); } protected sealed override void QueueTask(Task task) @@ -57,7 +58,6 @@ namespace Godot public void Activate() { - SynchronizationContext.SetSynchronizationContext(Context); ExecuteQueuedTasks(); Context.ExecutePendingContinuations(); } diff --git a/modules/mono/glue/cs_files/Vector2.cs b/modules/mono/glue/cs_files/Vector2.cs index cc2cda82fb..9bc40cf8df 100644 --- a/modules/mono/glue/cs_files/Vector2.cs +++ b/modules/mono/glue/cs_files/Vector2.cs @@ -97,6 +97,11 @@ namespace Godot return -Reflect(n); } + public Vector2 Ceil() + { + return new Vector2(Mathf.Ceil(x), Mathf.Ceil(y)); + } + public Vector2 Clamped(real_t length) { var v = this; @@ -190,6 +195,11 @@ namespace Godot return new Vector2(Mathf.Cos(rads), Mathf.Sin(rads)) * Length(); } + public Vector2 Round() + { + return new Vector2(Mathf.Round(x), Mathf.Round(y)); + } + public void Set(real_t x, real_t y) { this.x = x; diff --git a/modules/mono/glue/cs_files/Vector3.cs b/modules/mono/glue/cs_files/Vector3.cs index 0a71f3fa4d..57e4494f7e 100644 --- a/modules/mono/glue/cs_files/Vector3.cs +++ b/modules/mono/glue/cs_files/Vector3.cs @@ -219,6 +219,11 @@ namespace Godot return 2.0f * n * Dot(n) - this; } + public Vector3 Round() + { + return new Vector3(Mathf.Round(x), Mathf.Round(y), Mathf.Round(z)); + } + public Vector3 Rotated(Vector3 axis, real_t phi) { return new Basis(axis, phi).Xform(this); diff --git a/modules/mono/mono_reg_utils.py b/modules/mono/mono_reg_utils.py index 8ddddb3a24..9c188d07a7 100644 --- a/modules/mono/mono_reg_utils.py +++ b/modules/mono/mono_reg_utils.py @@ -75,7 +75,7 @@ def find_msbuild_tools_path_reg(): vswhere = os.getenv('PROGRAMFILES') vswhere += r'\Microsoft Visual Studio\Installer\vswhere.exe' - vswhere_args = ['-latest', '-requires', 'Microsoft.Component.MSBuild'] + vswhere_args = ['-latest', '-products', '*', '-requires', 'Microsoft.Component.MSBuild'] try: lines = subprocess.check_output([vswhere] + vswhere_args).splitlines() diff --git a/modules/mono/utils/mono_reg_utils.cpp b/modules/mono/utils/mono_reg_utils.cpp index 9bb8da8ac0..7b23cd7579 100644 --- a/modules/mono/utils/mono_reg_utils.cpp +++ b/modules/mono/utils/mono_reg_utils.cpp @@ -174,6 +174,8 @@ String find_msbuild_tools_path() { List<String> vswhere_args; vswhere_args.push_back("-latest"); + vswhere_args.push_back("-products"); + vswhere_args.push_back("*"); vswhere_args.push_back("-requires"); vswhere_args.push_back("Microsoft.Component.MSBuild"); diff --git a/modules/pvr/texture_loader_pvr.cpp b/modules/pvr/texture_loader_pvr.cpp index 76c0b969d8..8174bccdb7 100644 --- a/modules/pvr/texture_loader_pvr.cpp +++ b/modules/pvr/texture_loader_pvr.cpp @@ -536,8 +536,8 @@ static void decompress_pvrtc(PVRTCBlock *p_comp_img, const int p_2bit, const int int p_x, p_y; - int p_modulation[8][16]; - int p_modulation_modes[8][16]; + int p_modulation[8][16] = { { 0 } }; + int p_modulation_modes[8][16] = { { 0 } }; int Mod, DoPT; diff --git a/modules/visual_script/register_types.cpp b/modules/visual_script/register_types.cpp index 2809cff362..11401c0460 100644 --- a/modules/visual_script/register_types.cpp +++ b/modules/visual_script/register_types.cpp @@ -112,7 +112,9 @@ void register_visual_script_types() { register_visual_script_expression_node(); #ifdef TOOLS_ENABLED + ClassDB::set_current_api(ClassDB::API_EDITOR); ClassDB::register_class<_VisualScriptEditor>(); + ClassDB::set_current_api(ClassDB::API_CORE); vs_editor_singleton = memnew(_VisualScriptEditor); Engine::get_singleton()->add_singleton(Engine::Singleton("VisualScriptEditor", _VisualScriptEditor::get_singleton())); diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp index ef680547ca..03bc4c114a 100644 --- a/modules/visual_script/visual_script.cpp +++ b/modules/visual_script/visual_script.cpp @@ -2028,6 +2028,7 @@ void VisualScriptInstance::create(const Ref<VisualScript> &p_script, Object *p_o function.flow_stack_size = 0; function.pass_stack_size = 0; function.node_count = 0; + Map<StringName, int> local_var_indices; if (function.node < 0) { diff --git a/modules/visual_script/visual_script.h b/modules/visual_script/visual_script.h index 69bb522173..dad9c68312 100644 --- a/modules/visual_script/visual_script.h +++ b/modules/visual_script/visual_script.h @@ -374,12 +374,10 @@ class VisualScriptInstance : public ScriptInstance { int node; int max_stack; int trash_pos; - int return_pos; int flow_stack_size; int pass_stack_size; int node_count; int argument_count; - bool valid; }; Map<StringName, Function> functions; diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index eb10c5e99f..dfaa873b13 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -268,7 +268,7 @@ protected: if (String(p_name) == "export") { script->set_variable_export(var, p_value); - EditorNode::get_singleton()->get_property_editor()->update_tree(); + EditorNode::get_singleton()->get_inspector()->update_tree(); return true; } diff --git a/modules/visual_script/visual_script_expression.cpp b/modules/visual_script/visual_script_expression.cpp index 55e2cc5fb5..d5f9d21348 100644 --- a/modules/visual_script/visual_script_expression.cpp +++ b/modules/visual_script/visual_script_expression.cpp @@ -455,7 +455,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { break; } - if (cchar == '-' || (cchar >= '0' && cchar <= '9')) { + if (cchar >= '0' && cchar <= '9') { //a number String num; @@ -466,11 +466,6 @@ Error VisualScriptExpression::_get_token(Token &r_token) { #define READING_DONE 4 int reading = READING_INT; - if (cchar == '-') { - num += '-'; - cchar = GET_CHAR(); - } - CharType c = cchar; bool exp_sign = false; bool exp_beg = false; diff --git a/modules/webm/libvpx/SCsub b/modules/webm/libvpx/SCsub index b09c232b3c..c681e2b34f 100644 --- a/modules/webm/libvpx/SCsub +++ b/modules/webm/libvpx/SCsub @@ -263,7 +263,7 @@ if env["platform"] == 'uwp': webm_cpu_x86 = True else: import platform - is_x11_or_server_arm = ((env["platform"] == 'x11' or env["platform"] == 'server') and platform.machine().startswith('arm')) + is_x11_or_server_arm = ((env["platform"] == 'x11' or env["platform"] == 'server') and (platform.machine().startswith('arm') or platform.machine().startswith('aarch'))) is_ios_x86 = (env["platform"] == 'iphone' and env["ios_sim"]) is_android_x86 = (env["platform"] == 'android' and env["android_arch"] == 'x86') if is_android_x86: @@ -337,7 +337,6 @@ if webm_cpu_arm: if webm_simd_optimizations == False: print("WebM SIMD optimizations are disabled. Check if your CPU architecture, CPU bits or platform are supported!") - env_libvpx.add_source_files(env.modules_sources, libvpx_sources) if webm_cpu_x86: is_clang_or_gcc = ('gcc' in env["CC"]) or ('clang' in env["CC"]) or ("OSXCROSS_ROOT" in os.environ) @@ -379,5 +378,5 @@ elif webm_cpu_arm: env_libvpx.add_source_files(env.modules_sources, libvpx_sources_arm_neon_armasm_ms) elif env["platform"] == 'iphone': env_libvpx.add_source_files(env.modules_sources, libvpx_sources_arm_neon_gas_apple) - elif not env["android_arch"] == 'arm64v8': + elif (is_x11_or_server_arm and cpu_bits == '32') or (env["platform"] == 'android' and not env["android_arch"] == 'arm64v8'): env_libvpx.add_source_files(env.modules_sources, libvpx_sources_arm_neon_gas) diff --git a/modules/webp/SCsub b/modules/webp/SCsub index ea7af1bf9e..21ae0ce7c2 100644 --- a/modules/webp/SCsub +++ b/modules/webp/SCsub @@ -78,10 +78,12 @@ if env['builtin_libwebp']: "dsp/upsampling_msa.c", "dsp/upsampling_neon.c", "dsp/upsampling_sse2.c", + "dsp/upsampling_sse41.c", "dsp/yuv.c", "dsp/yuv_mips32.c", "dsp/yuv_mips_dsp_r2.c", "dsp/yuv_sse2.c", + "dsp/yuv_sse41.c", "enc/alpha_enc.c", "enc/analysis_enc.c", "enc/backward_references_cost_enc.c", diff --git a/modules/websocket/config.py b/modules/websocket/config.py index fb920482f5..399ca88fc1 100644 --- a/modules/websocket/config.py +++ b/modules/websocket/config.py @@ -5,3 +5,14 @@ def can_build(platform): def configure(env): pass + +def get_doc_classes(): + return [ + "WebSocketClient", + "WebSocketMultiplayerPeer", + "WebSocketPeer", + "WebSocketServer" + ] + +def get_doc_path(): + return "doc_classes" diff --git a/modules/websocket/doc_classes/WebSocketClient.xml b/modules/websocket/doc_classes/WebSocketClient.xml new file mode 100644 index 0000000000..2e11e1a44c --- /dev/null +++ b/modules/websocket/doc_classes/WebSocketClient.xml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="WebSocketClient" inherits="WebSocketMultiplayerPeer" category="Core" version="3.1"> + <brief_description> + A WebSocket client implementation + </brief_description> + <description> + This class implements a WebSocket client compatible with any RFC 6455 complaint WebSocket server. + This client can be optionally used as a network peer for the [MultiplayerAPI]. + After starting the client ([method connect_to_url]), you will need to [method NetworkedMultiplayerPeer.poll] it at regular intervals (e.g. inside [method Node._process]). + You will received appropriate signals when connecting, disconnecting, or when new data is available. + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="connect_to_url"> + <return type="int" enum="Error"> + </return> + <argument index="0" name="url" type="String"> + </argument> + <argument index="1" name="protocols" type="PoolStringArray" default="PoolStringArray( )"> + </argument> + <argument index="2" name="gd_mp_api" type="bool" default="false"> + </argument> + <description> + Connect to the given URL requesting one of the given [code]protocols[/code] as sub-protocol. + If [code]true[/code] is passed as [code]gd_mp_api[/code], the client will behave like a network peer for the [MultiplayerAPI]. Note: connnections to non Godot servers will not work, and [signal data_received] will not be emitted when this option is true. + </description> + </method> + <method name="disconnect_from_host"> + <return type="void"> + </return> + <description> + Disconnect from the server if currently connected. + </description> + </method> + </methods> + <members> + <member name="verify_ssl" type="bool" setter="set_verify_ssl_enabled" getter="is_verify_ssl_enabled"> + Enable or disable SSL certificate verification. Note: You must specify the certificates to be used in the project settings for it to work when exported. + </member> + </members> + <signals> + <signal name="connection_closed"> + <description> + Emitted when the connection to the server is closed. + </description> + </signal> + <signal name="connection_error"> + <description> + Emitted when the connection to the server fails. + </description> + </signal> + <signal name="connection_established"> + <argument index="0" name="protocol" type="String"> + </argument> + <description> + Emitted when a connection with the server is established, [code]protocol[/code] will contain the sub-protocol agreed with the server. + </description> + </signal> + <signal name="data_received"> + <description> + Emitted when a WebSocket message is received. Note: This signal is NOT emitted when used as high level multiplayer peer. + </description> + </signal> + </signals> + <constants> + </constants> +</class> diff --git a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml new file mode 100644 index 0000000000..1a841f85ed --- /dev/null +++ b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="WebSocketMultiplayerPeer" inherits="NetworkedMultiplayerPeer" category="Core" version="3.1"> + <brief_description> + Base class for WebSocket server and client. + </brief_description> + <description> + Base class for WebSocket server and client, allowing them to be used as network peer for the [MultiplayerAPI]. + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="get_peer" qualifiers="const"> + <return type="WebSocketPeer"> + </return> + <argument index="0" name="peer_id" type="int"> + </argument> + <description> + Returns the [WebSocketPeer] associated to the given [code]peer_id[/code]. + </description> + </method> + </methods> + <signals> + <signal name="peer_packet"> + <argument index="0" name="peer_source" type="int"> + </argument> + <description> + Emitted when a packet is received from a peer. Note: this signal is only emitted when the client or server is configured to use Godot multiplayer API. + </description> + </signal> + </signals> + <constants> + </constants> +</class> diff --git a/modules/websocket/doc_classes/WebSocketPeer.xml b/modules/websocket/doc_classes/WebSocketPeer.xml new file mode 100644 index 0000000000..85a08e0c0b --- /dev/null +++ b/modules/websocket/doc_classes/WebSocketPeer.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="WebSocketPeer" inherits="PacketPeer" category="Core" version="3.1"> + <brief_description> + A class representing a specific WebSocket connection. + </brief_description> + <description> + This class represent a specific WebSocket connection, you can do lower level operations with it. + You can choose to write to the socket in binary or text mode, and you can recognize the mode used for writing by the other peer. + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="close"> + <return type="void"> + </return> + <description> + Close this WebSocket connection, actively disconnecting the peer. + </description> + </method> + <method name="get_connected_host" qualifiers="const"> + <return type="String"> + </return> + <description> + Returns the IP Address of the connected peer. (Not available in HTML5 export) + </description> + </method> + <method name="get_connected_port" qualifiers="const"> + <return type="int"> + </return> + <description> + Returns the remote port of the connected peer. (Not available in HTML5 export) + </description> + </method> + <method name="get_write_mode" qualifiers="const"> + <return type="int" enum="WebSocketPeer.WriteMode"> + </return> + <description> + Get the current selected write mode. See [enum WriteMode]. + </description> + </method> + <method name="is_connected_to_host" qualifiers="const"> + <return type="bool"> + </return> + <description> + Returns [code]true[/code] if this peer is currently connected. + </description> + </method> + <method name="set_write_mode"> + <return type="void"> + </return> + <argument index="0" name="mode" type="int" enum="WebSocketPeer.WriteMode"> + </argument> + <description> + Sets the socket to use the given [enum WriteMode]. + </description> + </method> + <method name="was_string_packet" qualifiers="const"> + <return type="bool"> + </return> + <description> + Returns [code]true[/code] if the last received packet was sent as a text payload. See [enum WriteMode] + </description> + </method> + </methods> + <constants> + <constant name="WRITE_MODE_TEXT" value="0" enum="WriteMode"> + Specify that WebSockets messages should be transferred as text payload (only valid UTF-8 is allowed). + </constant> + <constant name="WRITE_MODE_BINARY" value="1" enum="WriteMode"> + Specify that WebSockets messages should be transferred as binary payload (any byte combination is allowed). + </constant> + </constants> +</class> diff --git a/modules/websocket/doc_classes/WebSocketServer.xml b/modules/websocket/doc_classes/WebSocketServer.xml new file mode 100644 index 0000000000..a1061e446b --- /dev/null +++ b/modules/websocket/doc_classes/WebSocketServer.xml @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="WebSocketServer" inherits="WebSocketMultiplayerPeer" category="Core" version="3.1"> + <brief_description> + A WebSocket server implementation + </brief_description> + <description> + This class implements a WebSocket server that can also support the high level multiplayer API. + After starting the server ([method listen]), you will need to [method NetworkedMultiplayerPeer.poll] it at regular intervals (e.g. inside [method Node._process]). When clients connect, disconnect, or send data, you will receive the appropriate signal. + Note: This class will not work in HTML5 exports due to browser restrictions. + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="disconnect_peer"> + <return type="void"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <description> + Disconnects the given peer. + </description> + </method> + <method name="get_peer_address" qualifiers="const"> + <return type="String"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <description> + Returns the IP address of the given peer. + </description> + </method> + <method name="get_peer_port" qualifiers="const"> + <return type="int"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <description> + Returns the remote port of the given peer. + </description> + </method> + <method name="has_peer" qualifiers="const"> + <return type="bool"> + </return> + <argument index="0" name="id" type="int"> + </argument> + <description> + Returns [code]true[/code] if a peer with the given ID is connected. + </description> + </method> + <method name="is_listening" qualifiers="const"> + <return type="bool"> + </return> + <description> + Returns [code]true[/code] if the server is actively listening on a port. + </description> + </method> + <method name="listen"> + <return type="int" enum="Error"> + </return> + <argument index="0" name="port" type="int"> + </argument> + <argument index="1" name="protocols" type="PoolStringArray" default="PoolStringArray( )"> + </argument> + <argument index="2" name="gd_mp_api" type="bool" default="false"> + </argument> + <description> + Start listening on the given port. + You can specify the desired subprotocols via the "protocols" array. If the list empty (default), "binary" will be used. + You can use this server as a network peer for [MultiplayerAPI] by passing true as "gd_mp_api". Note: [signal data_received] will not be fired and clients other than Godot will not work in this case. + </description> + </method> + <method name="stop"> + <return type="void"> + </return> + <description> + Stop the server and clear its state. + </description> + </method> + </methods> + <signals> + <signal name="client_connected"> + <argument index="0" name="id" type="int"> + </argument> + <argument index="1" name="protocol" type="String"> + </argument> + <description> + Emitted when a new client connects. "protocol" will be the sub-protocol agreed with the client. + </description> + </signal> + <signal name="client_disconnected"> + <argument index="0" name="id" type="int"> + </argument> + <description> + Emitted when a client disconnects. + </description> + </signal> + <signal name="data_received"> + <argument index="0" name="id" type="int"> + </argument> + <description> + Emitted when a new message is received. Note: This signal is NOT emitted when used as high level multiplayer peer. + </description> + </signal> + </signals> + <constants> + </constants> +</class> diff --git a/modules/websocket/lws_peer.cpp b/modules/websocket/lws_peer.cpp index 8a064fb5a4..3855a39aef 100644 --- a/modules/websocket/lws_peer.cpp +++ b/modules/websocket/lws_peer.cpp @@ -35,6 +35,7 @@ // Needed for socket_helpers on Android at least. UNIXes has it, just include if not windows #if !defined(WINDOWS_ENABLED) #include <netinet/in.h> +#include <sys/socket.h> #endif #include "drivers/unix/socket_helpers.h" @@ -190,9 +191,11 @@ IP_Address LWSPeer::get_connected_host() const { IP_Address ip; int port = 0; - socklen_t len; + socklen_t len = 0; struct sockaddr_storage addr; + int fd = lws_get_socket_fd(wsi); + ERR_FAIL_COND_V(fd == -1, IP_Address()); int ret = getpeername(fd, (struct sockaddr *)&addr, &len); ERR_FAIL_COND_V(ret != 0, IP_Address()); @@ -209,9 +212,11 @@ uint16_t LWSPeer::get_connected_port() const { IP_Address ip; int port = 0; - socklen_t len; + socklen_t len = 0; struct sockaddr_storage addr; + int fd = lws_get_socket_fd(wsi); + ERR_FAIL_COND_V(fd == -1, 0); int ret = getpeername(fd, (struct sockaddr *)&addr, &len); ERR_FAIL_COND_V(ret != 0, 0); diff --git a/modules/websocket/websocket_server.cpp b/modules/websocket/websocket_server.cpp index 2693b26e47..53dd7b51b7 100644 --- a/modules/websocket/websocket_server.cpp +++ b/modules/websocket/websocket_server.cpp @@ -44,9 +44,9 @@ void WebSocketServer::_bind_methods() { ClassDB::bind_method(D_METHOD("listen", "port", "protocols", "gd_mp_api"), &WebSocketServer::listen, DEFVAL(PoolVector<String>()), DEFVAL(false)); ClassDB::bind_method(D_METHOD("stop"), &WebSocketServer::stop); ClassDB::bind_method(D_METHOD("has_peer", "id"), &WebSocketServer::has_peer); - ClassDB::bind_method(D_METHOD("get_peer_address"), &WebSocketServer::get_peer_address); - ClassDB::bind_method(D_METHOD("get_peer_port"), &WebSocketServer::get_peer_port); - ClassDB::bind_method(D_METHOD("disconnect_peer"), &WebSocketServer::disconnect_peer); + ClassDB::bind_method(D_METHOD("get_peer_address", "id"), &WebSocketServer::get_peer_address); + ClassDB::bind_method(D_METHOD("get_peer_port", "id"), &WebSocketServer::get_peer_port); + ClassDB::bind_method(D_METHOD("disconnect_peer", "id"), &WebSocketServer::disconnect_peer); ADD_SIGNAL(MethodInfo("client_disconnected", PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("client_connected", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "protocol"))); |