feat: godot-engine-source-4.3-stable
107
engine/modules/SCsub
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
|
||||
import methods
|
||||
|
||||
Import("env")
|
||||
|
||||
env_modules = env.Clone()
|
||||
|
||||
# Allow modules to detect if they are being built as a module.
|
||||
env_modules.Append(CPPDEFINES=["GODOT_MODULE"])
|
||||
|
||||
Export("env_modules")
|
||||
|
||||
|
||||
# Header with MODULE_*_ENABLED defines.
|
||||
def modules_enabled_builder(target, source, env):
|
||||
with methods.generated_wrapper(target) as file:
|
||||
for module in source[0].read():
|
||||
file.write(f"#define MODULE_{module.upper()}_ENABLED\n")
|
||||
|
||||
|
||||
modules_enabled = env.CommandNoCache(
|
||||
"modules_enabled.gen.h", env.Value(env.module_list), env.Run(modules_enabled_builder)
|
||||
)
|
||||
|
||||
|
||||
def register_module_types_builder(target, source, env):
|
||||
modules = source[0].read()
|
||||
mod_inc = "\n".join([f'#include "{p}/register_types.h"' for p in modules.values()])
|
||||
mod_init = "\n".join(
|
||||
[f"#ifdef MODULE_{n.upper()}_ENABLED\n\tinitialize_{n}_module(p_level);\n#endif" for n in modules.keys()]
|
||||
)
|
||||
mod_uninit = "\n".join(
|
||||
[f"#ifdef MODULE_{n.upper()}_ENABLED\n\tuninitialize_{n}_module(p_level);\n#endif" for n in modules.keys()]
|
||||
)
|
||||
with methods.generated_wrapper(target) as file:
|
||||
file.write(
|
||||
f"""\
|
||||
#include "register_module_types.h"
|
||||
|
||||
#include "modules/modules_enabled.gen.h"
|
||||
|
||||
{mod_inc}
|
||||
|
||||
void initialize_modules(ModuleInitializationLevel p_level) {{
|
||||
{mod_init}
|
||||
}}
|
||||
|
||||
void uninitialize_modules(ModuleInitializationLevel p_level) {{
|
||||
{mod_uninit}
|
||||
}}
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
register_module_types = env.CommandNoCache(
|
||||
"register_module_types.gen.cpp",
|
||||
[env.Value(env.modules_detected), modules_enabled],
|
||||
env.Run(register_module_types_builder),
|
||||
)
|
||||
|
||||
|
||||
vs_sources = []
|
||||
test_headers = []
|
||||
# libmodule_<name>.a for each active module.
|
||||
for name, path in env.module_list.items():
|
||||
env.modules_sources = []
|
||||
|
||||
# Name for built-in modules, (absolute) path for custom ones.
|
||||
base_path = path if os.path.isabs(path) else name
|
||||
SConscript(base_path + "/SCsub")
|
||||
|
||||
lib = env_modules.add_library("module_%s" % name, env.modules_sources)
|
||||
env.Prepend(LIBS=[lib])
|
||||
if env["vsproj"]:
|
||||
vs_sources += env.modules_sources
|
||||
|
||||
if env["tests"]:
|
||||
# Lookup potential headers in `tests` subfolder.
|
||||
import glob
|
||||
|
||||
module_tests = sorted(glob.glob(os.path.join(base_path, "tests", "*.h")))
|
||||
if module_tests != []:
|
||||
test_headers += module_tests
|
||||
|
||||
|
||||
# Generate header to be included in `tests/test_main.cpp` to run module-specific tests.
|
||||
if env["tests"]:
|
||||
|
||||
def modules_tests_builder(target, source, env):
|
||||
with methods.generated_wrapper(target) as file:
|
||||
for header in source:
|
||||
file.write('#include "{}"\n'.format(os.path.normpath(header.path).replace("\\", "/")))
|
||||
|
||||
env.CommandNoCache("modules_tests.gen.h", test_headers, env.Run(modules_tests_builder))
|
||||
|
||||
# libmodules.a with only register_module_types.
|
||||
# Must be last so that all libmodule_<name>.a libraries are on the right side
|
||||
# in the linker command.
|
||||
env.modules_sources = []
|
||||
env_modules.add_source_files(env.modules_sources, register_module_types)
|
||||
lib = env_modules.add_library("modules", env.modules_sources)
|
||||
env.Prepend(LIBS=[lib])
|
||||
if env["vsproj"]:
|
||||
env.modules_sources += vs_sources
|
||||
54
engine/modules/astcenc/SCsub
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
||||
env_astcenc = env_modules.Clone()
|
||||
|
||||
# Thirdparty source files
|
||||
|
||||
thirdparty_obj = []
|
||||
|
||||
thirdparty_dir = "#thirdparty/astcenc/"
|
||||
thirdparty_sources = [
|
||||
"astcenc_averages_and_directions.cpp",
|
||||
"astcenc_block_sizes.cpp",
|
||||
"astcenc_color_quantize.cpp",
|
||||
"astcenc_color_unquantize.cpp",
|
||||
"astcenc_compress_symbolic.cpp",
|
||||
"astcenc_compute_variance.cpp",
|
||||
"astcenc_decompress_symbolic.cpp",
|
||||
"astcenc_diagnostic_trace.cpp",
|
||||
"astcenc_entry.cpp",
|
||||
"astcenc_find_best_partitioning.cpp",
|
||||
"astcenc_ideal_endpoints_and_weights.cpp",
|
||||
"astcenc_image.cpp",
|
||||
"astcenc_integer_sequence.cpp",
|
||||
"astcenc_mathlib.cpp",
|
||||
"astcenc_mathlib_softfloat.cpp",
|
||||
"astcenc_partition_tables.cpp",
|
||||
"astcenc_percentile_tables.cpp",
|
||||
"astcenc_pick_best_endpoint_format.cpp",
|
||||
"astcenc_quantization.cpp",
|
||||
"astcenc_symbolic_physical.cpp",
|
||||
"astcenc_weight_align.cpp",
|
||||
"astcenc_weight_quant_xfer_tables.cpp",
|
||||
]
|
||||
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
|
||||
|
||||
env_astcenc.Prepend(CPPPATH=[thirdparty_dir])
|
||||
|
||||
env_thirdparty = env_astcenc.Clone()
|
||||
env_thirdparty.disable_warnings()
|
||||
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
|
||||
env.modules_sources += thirdparty_obj
|
||||
|
||||
# Godot source files
|
||||
|
||||
module_obj = []
|
||||
|
||||
env_astcenc.add_source_files(module_obj, "*.cpp")
|
||||
env.modules_sources += module_obj
|
||||
|
||||
# Needed to force rebuilding the module files when the thirdparty library is updated.
|
||||
env.Depends(module_obj, thirdparty_obj)
|
||||
8
engine/modules/astcenc/config.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
def can_build(env, platform):
|
||||
# Godot only uses it in the editor, but ANGLE depends on it and we had
|
||||
# to remove the copy from prebuilt ANGLE libs to solve symbol clashes.
|
||||
return env.editor_build or env.get("angle_libs")
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
||||
293
engine/modules/astcenc/image_compress_astcenc.cpp
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
/**************************************************************************/
|
||||
/* image_compress_astcenc.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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_compress_astcenc.h"
|
||||
|
||||
#include "core/os/os.h"
|
||||
#include "core/string/print_string.h"
|
||||
|
||||
#include <astcenc.h>
|
||||
|
||||
void _compress_astc(Image *r_img, Image::ASTCFormat p_format) {
|
||||
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
|
||||
|
||||
// TODO: See how to handle lossy quality.
|
||||
|
||||
Image::Format img_format = r_img->get_format();
|
||||
if (Image::is_format_compressed(img_format)) {
|
||||
return; // Do not compress, already compressed.
|
||||
}
|
||||
|
||||
bool is_hdr = false;
|
||||
if ((img_format >= Image::FORMAT_RH) && (img_format <= Image::FORMAT_RGBE9995)) {
|
||||
is_hdr = true;
|
||||
r_img->convert(Image::FORMAT_RGBAF);
|
||||
} else {
|
||||
r_img->convert(Image::FORMAT_RGBA8);
|
||||
}
|
||||
|
||||
// Determine encoder output format from our enum.
|
||||
|
||||
Image::Format target_format = Image::FORMAT_RGBA8;
|
||||
astcenc_profile profile = ASTCENC_PRF_LDR;
|
||||
unsigned int block_x = 4;
|
||||
unsigned int block_y = 4;
|
||||
|
||||
if (p_format == Image::ASTCFormat::ASTC_FORMAT_4x4) {
|
||||
if (is_hdr) {
|
||||
target_format = Image::FORMAT_ASTC_4x4_HDR;
|
||||
profile = ASTCENC_PRF_HDR;
|
||||
} else {
|
||||
target_format = Image::FORMAT_ASTC_4x4;
|
||||
}
|
||||
} else if (p_format == Image::ASTCFormat::ASTC_FORMAT_8x8) {
|
||||
if (is_hdr) {
|
||||
target_format = Image::FORMAT_ASTC_8x8_HDR;
|
||||
profile = ASTCENC_PRF_HDR;
|
||||
} else {
|
||||
target_format = Image::FORMAT_ASTC_8x8;
|
||||
}
|
||||
block_x = 8;
|
||||
block_y = 8;
|
||||
}
|
||||
|
||||
// Compress image data and (if required) mipmaps.
|
||||
|
||||
const bool mipmaps = r_img->has_mipmaps();
|
||||
int width = r_img->get_width();
|
||||
int height = r_img->get_height();
|
||||
int required_width = (width % block_x) != 0 ? width + (block_x - (width % block_x)) : width;
|
||||
int required_height = (height % block_y) != 0 ? height + (block_y - (height % block_y)) : height;
|
||||
|
||||
if (width != required_width || height != required_height) {
|
||||
// Resize texture to fit block size.
|
||||
r_img->resize(required_width, required_height);
|
||||
width = required_width;
|
||||
height = required_height;
|
||||
}
|
||||
|
||||
print_verbose(vformat("astcenc: Encoding image size %dx%d to format %s%s.", width, height, Image::get_format_name(target_format), mipmaps ? ", with mipmaps" : ""));
|
||||
|
||||
// Initialize astcenc.
|
||||
|
||||
int64_t dest_size = Image::get_image_data_size(width, height, target_format, mipmaps);
|
||||
Vector<uint8_t> dest_data;
|
||||
dest_data.resize(dest_size);
|
||||
uint8_t *dest_write = dest_data.ptrw();
|
||||
|
||||
astcenc_config config;
|
||||
config.block_x = block_x;
|
||||
config.block_y = block_y;
|
||||
config.profile = profile;
|
||||
|
||||
const float quality = ASTCENC_PRE_MEDIUM;
|
||||
astcenc_error status = astcenc_config_init(profile, block_x, block_y, 1, quality, 0, &config);
|
||||
ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS,
|
||||
vformat("astcenc: Configuration initialization failed: %s.", astcenc_get_error_string(status)));
|
||||
|
||||
// Context allocation.
|
||||
|
||||
astcenc_context *context;
|
||||
const unsigned int thread_count = 1; // Godot compresses multiple images each on a thread, which is more efficient for large amount of images imported.
|
||||
status = astcenc_context_alloc(&config, thread_count, &context);
|
||||
ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS,
|
||||
vformat("astcenc: Context allocation failed: %s.", astcenc_get_error_string(status)));
|
||||
|
||||
Vector<uint8_t> image_data = r_img->get_data();
|
||||
|
||||
int mip_count = mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0;
|
||||
for (int i = 0; i < mip_count + 1; i++) {
|
||||
int src_mip_w, src_mip_h;
|
||||
int64_t src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i, src_mip_w, src_mip_h);
|
||||
|
||||
const uint8_t *slices = &image_data.ptr()[src_ofs];
|
||||
|
||||
int dst_mip_w, dst_mip_h;
|
||||
int64_t dst_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dst_mip_w, dst_mip_h);
|
||||
// Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer).
|
||||
if (unlikely(dst_ofs % 8 != 0)) {
|
||||
astcenc_context_free(context);
|
||||
ERR_FAIL_MSG("astcenc: Mip offset is not a multiple of 8.");
|
||||
}
|
||||
uint8_t *dest_mip_write = (uint8_t *)&dest_write[dst_ofs];
|
||||
|
||||
// Compress image.
|
||||
|
||||
astcenc_image image;
|
||||
image.dim_x = src_mip_w;
|
||||
image.dim_y = src_mip_h;
|
||||
image.dim_z = 1;
|
||||
image.data_type = ASTCENC_TYPE_U8;
|
||||
if (is_hdr) {
|
||||
image.data_type = ASTCENC_TYPE_F32;
|
||||
}
|
||||
image.data = (void **)(&slices);
|
||||
|
||||
// Compute the number of ASTC blocks in each dimension.
|
||||
unsigned int block_count_x = (src_mip_w + block_x - 1) / block_x;
|
||||
unsigned int block_count_y = (src_mip_h + block_y - 1) / block_y;
|
||||
size_t comp_len = block_count_x * block_count_y * 16;
|
||||
|
||||
const astcenc_swizzle swizzle = {
|
||||
ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A
|
||||
};
|
||||
|
||||
status = astcenc_compress_image(context, &image, &swizzle, dest_mip_write, comp_len, 0);
|
||||
|
||||
ERR_BREAK_MSG(status != ASTCENC_SUCCESS,
|
||||
vformat("astcenc: ASTC image compression failed: %s.", astcenc_get_error_string(status)));
|
||||
astcenc_compress_reset(context);
|
||||
}
|
||||
|
||||
astcenc_context_free(context);
|
||||
|
||||
// Replace original image with compressed one.
|
||||
|
||||
r_img->set_data(width, height, mipmaps, target_format, dest_data);
|
||||
|
||||
print_verbose(vformat("astcenc: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time));
|
||||
}
|
||||
|
||||
void _decompress_astc(Image *r_img) {
|
||||
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
|
||||
|
||||
// Determine decompression parameters from image format.
|
||||
|
||||
Image::Format img_format = r_img->get_format();
|
||||
bool is_hdr = false;
|
||||
unsigned int block_x = 0;
|
||||
unsigned int block_y = 0;
|
||||
if (img_format == Image::FORMAT_ASTC_4x4) {
|
||||
block_x = 4;
|
||||
block_y = 4;
|
||||
is_hdr = false;
|
||||
} else if (img_format == Image::FORMAT_ASTC_4x4_HDR) {
|
||||
block_x = 4;
|
||||
block_y = 4;
|
||||
is_hdr = true;
|
||||
} else if (img_format == Image::FORMAT_ASTC_8x8) {
|
||||
block_x = 8;
|
||||
block_y = 8;
|
||||
is_hdr = false;
|
||||
} else if (img_format == Image::FORMAT_ASTC_8x8_HDR) {
|
||||
block_x = 8;
|
||||
block_y = 8;
|
||||
is_hdr = true;
|
||||
} else {
|
||||
ERR_FAIL_MSG("astcenc: Cannot decompress Image with a non-ASTC format.");
|
||||
}
|
||||
|
||||
// Initialize astcenc.
|
||||
|
||||
astcenc_profile profile = ASTCENC_PRF_LDR;
|
||||
if (is_hdr) {
|
||||
profile = ASTCENC_PRF_HDR;
|
||||
}
|
||||
astcenc_config config;
|
||||
const float quality = ASTCENC_PRE_MEDIUM;
|
||||
|
||||
astcenc_error status = astcenc_config_init(profile, block_x, block_y, 1, quality, 0, &config);
|
||||
ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS,
|
||||
vformat("astcenc: Configuration initialization failed: %s.", astcenc_get_error_string(status)));
|
||||
|
||||
// Context allocation.
|
||||
|
||||
astcenc_context *context = nullptr;
|
||||
const unsigned int thread_count = 1;
|
||||
|
||||
status = astcenc_context_alloc(&config, thread_count, &context);
|
||||
ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS,
|
||||
vformat("astcenc: Context allocation failed: %s.", astcenc_get_error_string(status)));
|
||||
|
||||
Image::Format target_format = is_hdr ? Image::FORMAT_RGBAF : Image::FORMAT_RGBA8;
|
||||
|
||||
const bool mipmaps = r_img->has_mipmaps();
|
||||
int width = r_img->get_width();
|
||||
int height = r_img->get_height();
|
||||
int64_t dest_size = Image::get_image_data_size(width, height, target_format, mipmaps);
|
||||
Vector<uint8_t> dest_data;
|
||||
dest_data.resize(dest_size);
|
||||
uint8_t *dest_write = dest_data.ptrw();
|
||||
|
||||
// Decompress image.
|
||||
|
||||
Vector<uint8_t> image_data = r_img->get_data();
|
||||
int mip_count = mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0;
|
||||
|
||||
for (int i = 0; i < mip_count + 1; i++) {
|
||||
int src_mip_w, src_mip_h;
|
||||
|
||||
int64_t src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i, src_mip_w, src_mip_h);
|
||||
const uint8_t *src_data = &image_data.ptr()[src_ofs];
|
||||
int64_t src_size;
|
||||
if (i == mip_count) {
|
||||
src_size = image_data.size() - src_ofs;
|
||||
} else {
|
||||
int auxw, auxh;
|
||||
src_size = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i + 1, auxw, auxh) - src_ofs;
|
||||
}
|
||||
|
||||
int dst_mip_w, dst_mip_h;
|
||||
int64_t dst_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dst_mip_w, dst_mip_h);
|
||||
// Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer).
|
||||
ERR_FAIL_COND(dst_ofs % 8 != 0);
|
||||
uint8_t *dest_mip_write = (uint8_t *)&dest_write[dst_ofs];
|
||||
|
||||
astcenc_image image;
|
||||
image.dim_x = dst_mip_w;
|
||||
image.dim_y = dst_mip_h;
|
||||
image.dim_z = 1;
|
||||
image.data_type = ASTCENC_TYPE_U8;
|
||||
if (is_hdr) {
|
||||
target_format = Image::FORMAT_RGBAF;
|
||||
image.data_type = ASTCENC_TYPE_F32;
|
||||
}
|
||||
|
||||
image.data = (void **)(&dest_mip_write);
|
||||
|
||||
const astcenc_swizzle swizzle = {
|
||||
ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A
|
||||
};
|
||||
|
||||
status = astcenc_decompress_image(context, src_data, src_size, &image, &swizzle, 0);
|
||||
ERR_BREAK_MSG(status != ASTCENC_SUCCESS,
|
||||
vformat("astcenc: ASTC decompression failed: %s.", astcenc_get_error_string(status)));
|
||||
ERR_BREAK_MSG(image.dim_z > 1,
|
||||
"astcenc: ASTC decompression failed because this is a 3D texture, which is not supported.");
|
||||
astcenc_compress_reset(context);
|
||||
}
|
||||
astcenc_context_free(context);
|
||||
|
||||
// Replace original image with compressed one.
|
||||
|
||||
r_img->set_data(width, height, mipmaps, target_format, dest_data);
|
||||
|
||||
print_verbose(vformat("astcenc: Decompression took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time));
|
||||
}
|
||||
39
engine/modules/astcenc/image_compress_astcenc.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**************************************************************************/
|
||||
/* image_compress_astcenc.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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_COMPRESS_ASTCENC_H
|
||||
#define IMAGE_COMPRESS_ASTCENC_H
|
||||
|
||||
#include "core/io/image.h"
|
||||
|
||||
void _compress_astc(Image *r_img, Image::ASTCFormat p_format);
|
||||
void _decompress_astc(Image *r_img);
|
||||
|
||||
#endif // IMAGE_COMPRESS_ASTCENC_H
|
||||
48
engine/modules/astcenc/register_types.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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_compress_astcenc.h"
|
||||
|
||||
void initialize_astcenc_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
|
||||
Image::_image_compress_astc_func = _compress_astc;
|
||||
Image::_image_decompress_astc = _decompress_astc;
|
||||
}
|
||||
|
||||
void uninitialize_astcenc_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
39
engine/modules/astcenc/register_types.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 ASTCENC_REGISTER_TYPES_H
|
||||
#define ASTCENC_REGISTER_TYPES_H
|
||||
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
void initialize_astcenc_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_astcenc_module(ModuleInitializationLevel p_level);
|
||||
|
||||
#endif // ASTCENC_REGISTER_TYPES_H
|
||||
66
engine/modules/basis_universal/SCsub
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
||||
env_basisu = env_modules.Clone()
|
||||
|
||||
# Thirdparty source files
|
||||
|
||||
thirdparty_obj = []
|
||||
|
||||
# Not unbundled so far since not widespread as shared library
|
||||
thirdparty_dir = "#thirdparty/basis_universal/"
|
||||
# Sync list with upstream CMakeLists.txt
|
||||
encoder_sources = [
|
||||
"basisu_backend.cpp",
|
||||
"basisu_basis_file.cpp",
|
||||
"basisu_bc7enc.cpp",
|
||||
"basisu_opencl.cpp",
|
||||
"basisu_comp.cpp",
|
||||
"basisu_enc.cpp",
|
||||
"basisu_etc.cpp",
|
||||
"basisu_frontend.cpp",
|
||||
"basisu_gpu_texture.cpp",
|
||||
"basisu_kernels_sse.cpp",
|
||||
"basisu_pvrtc1_4.cpp",
|
||||
"basisu_resampler.cpp",
|
||||
"basisu_resample_filters.cpp",
|
||||
"basisu_ssim.cpp",
|
||||
"basisu_uastc_enc.cpp",
|
||||
"pvpngreader.cpp",
|
||||
]
|
||||
encoder_sources = [thirdparty_dir + "encoder/" + file for file in encoder_sources]
|
||||
transcoder_sources = [thirdparty_dir + "transcoder/basisu_transcoder.cpp"]
|
||||
|
||||
# Treat Basis headers as system headers to avoid raising warnings. Not supported on MSVC.
|
||||
if not env.msvc:
|
||||
env_basisu.Append(
|
||||
CPPFLAGS=["-isystem", Dir(thirdparty_dir).path, "-isystem", Dir("#thirdparty/jpeg-compressor").path]
|
||||
)
|
||||
else:
|
||||
env_basisu.Prepend(CPPPATH=[thirdparty_dir, "#thirdparty/jpeg-compressor"])
|
||||
|
||||
if env["builtin_zstd"]:
|
||||
env_basisu.Prepend(CPPPATH=["#thirdparty/zstd"])
|
||||
|
||||
if env.dev_build:
|
||||
env_basisu.Append(CPPDEFINES=[("BASISU_DEVEL_MESSAGES", 1), ("BASISD_ENABLE_DEBUG_FLAGS", 1)])
|
||||
|
||||
env_thirdparty = env_basisu.Clone()
|
||||
env_thirdparty.disable_warnings()
|
||||
if env.editor_build:
|
||||
env_thirdparty.Append(CPPDEFINES=["BASISU_NO_IMG_LOADERS"])
|
||||
env_thirdparty.add_source_files(thirdparty_obj, encoder_sources)
|
||||
env_thirdparty.add_source_files(thirdparty_obj, transcoder_sources)
|
||||
env.modules_sources += thirdparty_obj
|
||||
|
||||
# Godot source files
|
||||
|
||||
module_obj = []
|
||||
|
||||
env_basisu.add_source_files(module_obj, "*.cpp")
|
||||
env.modules_sources += module_obj
|
||||
|
||||
# Needed to force rebuilding the module files when the thirdparty library is updated.
|
||||
env.Depends(module_obj, thirdparty_obj)
|
||||
7
engine/modules/basis_universal/config.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
def can_build(env, platform):
|
||||
env.module_add_dependencies("basis_universal", ["jpg"])
|
||||
return True
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
||||
332
engine/modules/basis_universal/image_compress_basisu.cpp
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
/**************************************************************************/
|
||||
/* image_compress_basisu.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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_compress_basisu.h"
|
||||
|
||||
#include "servers/rendering_server.h"
|
||||
|
||||
#include <transcoder/basisu_transcoder.h>
|
||||
#ifdef TOOLS_ENABLED
|
||||
#include <encoder/basisu_comp.h>
|
||||
#endif
|
||||
|
||||
void basis_universal_init() {
|
||||
#ifdef TOOLS_ENABLED
|
||||
basisu::basisu_encoder_init();
|
||||
#endif
|
||||
|
||||
basist::basisu_transcoder_init();
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedChannels p_channels) {
|
||||
Ref<Image> image = p_image->duplicate();
|
||||
image->convert(Image::FORMAT_RGBA8);
|
||||
|
||||
basisu::basis_compressor_params params;
|
||||
|
||||
params.m_uastc = true;
|
||||
params.m_quality_level = basisu::BASISU_QUALITY_MIN;
|
||||
params.m_pack_uastc_flags &= ~basisu::cPackUASTCLevelMask;
|
||||
params.m_pack_uastc_flags |= basisu::cPackUASTCLevelFastest;
|
||||
|
||||
params.m_rdo_uastc = 0.0f;
|
||||
params.m_rdo_uastc_quality_scalar = 0.0f;
|
||||
params.m_rdo_uastc_dict_size = 1024;
|
||||
|
||||
params.m_mip_fast = true;
|
||||
params.m_multithreading = true;
|
||||
params.m_check_for_alpha = false;
|
||||
|
||||
if (!OS::get_singleton()->is_stdout_verbose()) {
|
||||
params.m_print_stats = false;
|
||||
params.m_compute_stats = false;
|
||||
params.m_status_output = false;
|
||||
}
|
||||
|
||||
basisu::job_pool job_pool(OS::get_singleton()->get_processor_count());
|
||||
params.m_pJob_pool = &job_pool;
|
||||
|
||||
BasisDecompressFormat decompress_format = BASIS_DECOMPRESS_RG;
|
||||
switch (p_channels) {
|
||||
case Image::USED_CHANNELS_L: {
|
||||
decompress_format = BASIS_DECOMPRESS_RGB;
|
||||
} break;
|
||||
case Image::USED_CHANNELS_LA: {
|
||||
params.m_force_alpha = true;
|
||||
decompress_format = BASIS_DECOMPRESS_RGBA;
|
||||
} break;
|
||||
case Image::USED_CHANNELS_R: {
|
||||
decompress_format = BASIS_DECOMPRESS_RGB;
|
||||
} break;
|
||||
case Image::USED_CHANNELS_RG: {
|
||||
// Currently RG textures are compressed as DXT5/ETC2_RGBA8 with a RA -> RG swizzle,
|
||||
// as BasisUniversal didn't use to support ETC2_RG11 transcoding.
|
||||
params.m_force_alpha = true;
|
||||
image->convert_rg_to_ra_rgba8();
|
||||
decompress_format = BASIS_DECOMPRESS_RG_AS_RA;
|
||||
} break;
|
||||
case Image::USED_CHANNELS_RGB: {
|
||||
decompress_format = BASIS_DECOMPRESS_RGB;
|
||||
} break;
|
||||
case Image::USED_CHANNELS_RGBA: {
|
||||
params.m_force_alpha = true;
|
||||
decompress_format = BASIS_DECOMPRESS_RGBA;
|
||||
} break;
|
||||
}
|
||||
|
||||
// Copy the source image data with mipmaps into BasisU.
|
||||
{
|
||||
const int orig_width = image->get_width();
|
||||
const int orig_height = image->get_height();
|
||||
|
||||
bool is_res_div_4 = (orig_width % 4 == 0) && (orig_height % 4 == 0);
|
||||
|
||||
// Image's resolution rounded up to the nearest values divisible by 4.
|
||||
int next_width = orig_width <= 2 ? orig_width : (orig_width + 3) & ~3;
|
||||
int next_height = orig_height <= 2 ? orig_height : (orig_height + 3) & ~3;
|
||||
|
||||
Vector<uint8_t> image_data = image->get_data();
|
||||
basisu::vector<basisu::image> basisu_mipmaps;
|
||||
|
||||
// Buffer for storing padded mipmap data.
|
||||
Vector<uint32_t> mip_data_padded;
|
||||
|
||||
for (int32_t i = 0; i <= image->get_mipmap_count(); i++) {
|
||||
int64_t ofs, size;
|
||||
int width, height;
|
||||
image->get_mipmap_offset_size_and_dimensions(i, ofs, size, width, height);
|
||||
|
||||
const uint8_t *image_mip_data = image_data.ptr() + ofs;
|
||||
|
||||
// Pad the mipmap's data if its resolution isn't divisible by 4.
|
||||
if (image->has_mipmaps() && !is_res_div_4 && (width > 2 && height > 2) && (width != next_width || height != next_height)) {
|
||||
// Source mip's data interpreted as 32-bit RGBA blocks to help with copying pixel data.
|
||||
const uint32_t *mip_src_data = reinterpret_cast<const uint32_t *>(image_mip_data);
|
||||
|
||||
// Reserve space in the padded buffer.
|
||||
mip_data_padded.resize(next_width * next_height);
|
||||
uint32_t *data_padded_ptr = mip_data_padded.ptrw();
|
||||
|
||||
// Pad mipmap to the nearest block by smearing.
|
||||
int x = 0, y = 0;
|
||||
for (y = 0; y < height; y++) {
|
||||
for (x = 0; x < width; x++) {
|
||||
data_padded_ptr[next_width * y + x] = mip_src_data[width * y + x];
|
||||
}
|
||||
|
||||
// First, smear in x.
|
||||
for (; x < next_width; x++) {
|
||||
data_padded_ptr[next_width * y + x] = data_padded_ptr[next_width * y + x - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Then, smear in y.
|
||||
for (; y < next_height; y++) {
|
||||
for (x = 0; x < next_width; x++) {
|
||||
data_padded_ptr[next_width * y + x] = data_padded_ptr[next_width * y + x - next_width];
|
||||
}
|
||||
}
|
||||
|
||||
// Override the image_mip_data pointer with our temporary Vector.
|
||||
image_mip_data = reinterpret_cast<const uint8_t *>(mip_data_padded.ptr());
|
||||
|
||||
// Override the mipmap's properties.
|
||||
width = next_width;
|
||||
height = next_height;
|
||||
size = mip_data_padded.size() * 4;
|
||||
}
|
||||
|
||||
// Get the next mipmap's resolution.
|
||||
next_width /= 2;
|
||||
next_height /= 2;
|
||||
|
||||
// Copy the source mipmap's data to a BasisU image.
|
||||
basisu::image basisu_image(width, height);
|
||||
memcpy(basisu_image.get_ptr(), image_mip_data, size);
|
||||
|
||||
if (i == 0) {
|
||||
params.m_source_images.push_back(basisu_image);
|
||||
} else {
|
||||
basisu_mipmaps.push_back(basisu_image);
|
||||
}
|
||||
}
|
||||
|
||||
params.m_source_mipmap_images.push_back(basisu_mipmaps);
|
||||
}
|
||||
|
||||
// Encode the image data.
|
||||
Vector<uint8_t> basisu_data;
|
||||
|
||||
basisu::basis_compressor compressor;
|
||||
compressor.init(params);
|
||||
|
||||
int basisu_err = compressor.process();
|
||||
ERR_FAIL_COND_V(basisu_err != basisu::basis_compressor::cECSuccess, basisu_data);
|
||||
|
||||
const basisu::uint8_vec &basisu_out = compressor.get_output_basis_file();
|
||||
basisu_data.resize(basisu_out.size() + 4);
|
||||
|
||||
// Copy the encoded data to the buffer.
|
||||
{
|
||||
uint8_t *wb = basisu_data.ptrw();
|
||||
*(uint32_t *)wb = decompress_format;
|
||||
|
||||
memcpy(wb + 4, basisu_out.get_ptr(), basisu_out.size());
|
||||
}
|
||||
|
||||
return basisu_data;
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
|
||||
Ref<Image> image;
|
||||
ERR_FAIL_NULL_V_MSG(p_data, image, "Cannot unpack invalid BasisUniversal data.");
|
||||
|
||||
const uint8_t *src_ptr = p_data;
|
||||
int src_size = p_size;
|
||||
|
||||
basist::transcoder_texture_format basisu_format = basist::transcoder_texture_format::cTFTotalTextureFormats;
|
||||
Image::Format image_format = Image::FORMAT_MAX;
|
||||
|
||||
// Get supported compression formats.
|
||||
bool bptc_supported = RS::get_singleton()->has_os_feature("bptc");
|
||||
bool astc_supported = RS::get_singleton()->has_os_feature("astc");
|
||||
bool s3tc_supported = RS::get_singleton()->has_os_feature("s3tc");
|
||||
bool etc2_supported = RS::get_singleton()->has_os_feature("etc2");
|
||||
|
||||
bool needs_ra_rg_swap = false;
|
||||
|
||||
switch (*(uint32_t *)(src_ptr)) {
|
||||
case BASIS_DECOMPRESS_RG: {
|
||||
// RGTC transcoding is currently performed with RG_AS_RA, fail.
|
||||
ERR_FAIL_V(image);
|
||||
} break;
|
||||
case BASIS_DECOMPRESS_RGB: {
|
||||
if (bptc_supported) {
|
||||
basisu_format = basist::transcoder_texture_format::cTFBC7_M6_OPAQUE_ONLY;
|
||||
image_format = Image::FORMAT_BPTC_RGBA;
|
||||
} else if (astc_supported) {
|
||||
basisu_format = basist::transcoder_texture_format::cTFASTC_4x4_RGBA;
|
||||
image_format = Image::FORMAT_ASTC_4x4;
|
||||
} else if (s3tc_supported) {
|
||||
basisu_format = basist::transcoder_texture_format::cTFBC1;
|
||||
image_format = Image::FORMAT_DXT1;
|
||||
} else if (etc2_supported) {
|
||||
basisu_format = basist::transcoder_texture_format::cTFETC1;
|
||||
image_format = Image::FORMAT_ETC2_RGB8;
|
||||
} else {
|
||||
// No supported VRAM compression formats, decompress.
|
||||
basisu_format = basist::transcoder_texture_format::cTFRGBA32;
|
||||
image_format = Image::FORMAT_RGBA8;
|
||||
}
|
||||
|
||||
} break;
|
||||
case BASIS_DECOMPRESS_RGBA: {
|
||||
if (bptc_supported) {
|
||||
basisu_format = basist::transcoder_texture_format::cTFBC7_M5;
|
||||
image_format = Image::FORMAT_BPTC_RGBA;
|
||||
} else if (astc_supported) {
|
||||
basisu_format = basist::transcoder_texture_format::cTFASTC_4x4_RGBA;
|
||||
image_format = Image::FORMAT_ASTC_4x4;
|
||||
} else if (s3tc_supported) {
|
||||
basisu_format = basist::transcoder_texture_format::cTFBC3;
|
||||
image_format = Image::FORMAT_DXT5;
|
||||
} else if (etc2_supported) {
|
||||
basisu_format = basist::transcoder_texture_format::cTFETC2;
|
||||
image_format = Image::FORMAT_ETC2_RGBA8;
|
||||
} else {
|
||||
// No supported VRAM compression formats, decompress.
|
||||
basisu_format = basist::transcoder_texture_format::cTFRGBA32;
|
||||
image_format = Image::FORMAT_RGBA8;
|
||||
}
|
||||
} break;
|
||||
case BASIS_DECOMPRESS_RG_AS_RA: {
|
||||
if (s3tc_supported) {
|
||||
basisu_format = basist::transcoder_texture_format::cTFBC3;
|
||||
image_format = Image::FORMAT_DXT5_RA_AS_RG;
|
||||
} else if (etc2_supported) {
|
||||
basisu_format = basist::transcoder_texture_format::cTFETC2;
|
||||
image_format = Image::FORMAT_ETC2_RA_AS_RG;
|
||||
} else {
|
||||
// No supported VRAM compression formats, decompress.
|
||||
basisu_format = basist::transcoder_texture_format::cTFRGBA32;
|
||||
image_format = Image::FORMAT_RGBA8;
|
||||
needs_ra_rg_swap = true;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
src_ptr += 4;
|
||||
src_size -= 4;
|
||||
|
||||
basist::basisu_transcoder transcoder;
|
||||
ERR_FAIL_COND_V(!transcoder.validate_header(src_ptr, src_size), image);
|
||||
|
||||
transcoder.start_transcoding(src_ptr, src_size);
|
||||
|
||||
basist::basisu_image_info basisu_info;
|
||||
transcoder.get_image_info(src_ptr, src_size, basisu_info, 0);
|
||||
|
||||
// Create the buffer for transcoded/decompressed data.
|
||||
Vector<uint8_t> out_data;
|
||||
out_data.resize(Image::get_image_data_size(basisu_info.m_width, basisu_info.m_height, image_format, basisu_info.m_total_levels > 1));
|
||||
|
||||
uint8_t *dst = out_data.ptrw();
|
||||
memset(dst, 0, out_data.size());
|
||||
|
||||
for (uint32_t i = 0; i < basisu_info.m_total_levels; i++) {
|
||||
basist::basisu_image_level_info basisu_level;
|
||||
transcoder.get_image_level_info(src_ptr, src_size, basisu_level, 0, i);
|
||||
|
||||
uint32_t mip_block_or_pixel_count = Image::is_format_compressed(image_format) ? basisu_level.m_total_blocks : basisu_level.m_orig_width * basisu_level.m_orig_height;
|
||||
int64_t ofs = Image::get_image_mipmap_offset(basisu_info.m_width, basisu_info.m_height, image_format, i);
|
||||
|
||||
bool result = transcoder.transcode_image_level(src_ptr, src_size, 0, i, dst + ofs, mip_block_or_pixel_count, basisu_format);
|
||||
|
||||
if (!result) {
|
||||
print_line(vformat("BasisUniversal cannot unpack level %d.", i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
image = Image::create_from_data(basisu_info.m_width, basisu_info.m_height, basisu_info.m_total_levels > 1, image_format, out_data);
|
||||
|
||||
if (needs_ra_rg_swap) {
|
||||
// Swap uncompressed RA-as-RG texture's color channels.
|
||||
image->convert_ra_rgba8_to_rg();
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
Ref<Image> basis_universal_unpacker(const Vector<uint8_t> &p_buffer) {
|
||||
return basis_universal_unpacker_ptr(p_buffer.ptr(), p_buffer.size());
|
||||
}
|
||||
52
engine/modules/basis_universal/image_compress_basisu.h
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/**************************************************************************/
|
||||
/* image_compress_basisu.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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_COMPRESS_BASISU_H
|
||||
#define IMAGE_COMPRESS_BASISU_H
|
||||
|
||||
#include "core/io/image.h"
|
||||
|
||||
enum BasisDecompressFormat {
|
||||
BASIS_DECOMPRESS_RG,
|
||||
BASIS_DECOMPRESS_RGB,
|
||||
BASIS_DECOMPRESS_RGBA,
|
||||
BASIS_DECOMPRESS_RG_AS_RA,
|
||||
};
|
||||
|
||||
void basis_universal_init();
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedChannels p_channels);
|
||||
#endif
|
||||
|
||||
Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size);
|
||||
Ref<Image> basis_universal_unpacker(const Vector<uint8_t> &p_buffer);
|
||||
|
||||
#endif // IMAGE_COMPRESS_BASISU_H
|
||||
13
engine/modules/basis_universal/patches/external-jpgd.patch
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
diff --git a/thirdparty/basis_universal/encoder/basisu_enc.cpp b/thirdparty/basis_universal/encoder/basisu_enc.cpp
|
||||
index c431ceaf12..e87dd636a2 100644
|
||||
--- a/thirdparty/basis_universal/encoder/basisu_enc.cpp
|
||||
+++ b/thirdparty/basis_universal/encoder/basisu_enc.cpp
|
||||
@@ -409,7 +409,7 @@ namespace basisu
|
||||
bool load_jpg(const char *pFilename, image& img)
|
||||
{
|
||||
int width = 0, height = 0, actual_comps = 0;
|
||||
- uint8_t *pImage_data = jpgd::decompress_jpeg_image_from_file(pFilename, &width, &height, &actual_comps, 4, jpgd::jpeg_decoder::cFlagLinearChromaFiltering);
|
||||
+ uint8_t *pImage_data = jpgd::decompress_jpeg_image_from_file(pFilename, &width, &height, &actual_comps, 4, jpgd::jpeg_decoder::cFlagBoxChromaFiltering);
|
||||
if (!pImage_data)
|
||||
return false;
|
||||
|
||||
61
engine/modules/basis_universal/register_types.cpp
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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_compress_basisu.h"
|
||||
|
||||
void initialize_basis_universal_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
|
||||
basis_universal_init();
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
Image::basis_universal_packer = basis_universal_packer;
|
||||
#endif
|
||||
|
||||
Image::basis_universal_unpacker = basis_universal_unpacker;
|
||||
Image::basis_universal_unpacker_ptr = basis_universal_unpacker_ptr;
|
||||
}
|
||||
|
||||
void uninitialize_basis_universal_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
Image::basis_universal_packer = nullptr;
|
||||
#endif
|
||||
|
||||
Image::basis_universal_unpacker = nullptr;
|
||||
Image::basis_universal_unpacker_ptr = nullptr;
|
||||
}
|
||||
39
engine/modules/basis_universal/register_types.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 BASIS_UNIVERSAL_REGISTER_TYPES_H
|
||||
#define BASIS_UNIVERSAL_REGISTER_TYPES_H
|
||||
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
void initialize_basis_universal_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_basis_universal_module(ModuleInitializationLevel p_level);
|
||||
|
||||
#endif // BASIS_UNIVERSAL_REGISTER_TYPES_H
|
||||
9
engine/modules/bmp/SCsub
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
||||
env_bmp = env_modules.Clone()
|
||||
|
||||
# Godot source files
|
||||
env_bmp.add_source_files(env.modules_sources, "*.cpp")
|
||||
6
engine/modules/bmp/config.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
def can_build(env, platform):
|
||||
return True
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
||||
380
engine/modules/bmp/image_loader_bmp.cpp
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
/**************************************************************************/
|
||||
/* image_loader_bmp.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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"
|
||||
|
||||
#include "core/io/file_access_memory.h"
|
||||
|
||||
static uint8_t get_mask_width(uint16_t mask) {
|
||||
// Returns number of ones in the binary value of the parameter: mask.
|
||||
// Uses a Simple pop_count.
|
||||
uint8_t c = 0u;
|
||||
for (; mask != 0u; mask &= mask - 1u) {
|
||||
c++;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
|
||||
const uint8_t *p_buffer,
|
||||
const uint8_t *p_color_buffer,
|
||||
const uint32_t color_table_size,
|
||||
const bmp_header_s &p_header) {
|
||||
Error err = OK;
|
||||
|
||||
if (p_buffer == nullptr) {
|
||||
err = FAILED;
|
||||
}
|
||||
|
||||
if (err == OK) {
|
||||
size_t index = 0;
|
||||
size_t width = (size_t)p_header.bmp_info_header.bmp_width;
|
||||
size_t height = (size_t)p_header.bmp_info_header.bmp_height;
|
||||
size_t bits_per_pixel = (size_t)p_header.bmp_info_header.bmp_bit_count;
|
||||
|
||||
// Check whether we can load it
|
||||
|
||||
if (bits_per_pixel == 1) {
|
||||
// Requires bit unpacking...
|
||||
ERR_FAIL_COND_V_MSG(width % 8 != 0, ERR_UNAVAILABLE,
|
||||
vformat("1-bpp BMP images must have a width that is a multiple of 8, but the imported BMP is %d pixels wide.", int(width)));
|
||||
ERR_FAIL_COND_V_MSG(height % 8 != 0, ERR_UNAVAILABLE,
|
||||
vformat("1-bpp BMP images must have a height that is a multiple of 8, but the imported BMP is %d pixels tall.", int(height)));
|
||||
|
||||
} else if (bits_per_pixel == 2) {
|
||||
// Requires bit unpacking...
|
||||
ERR_FAIL_COND_V_MSG(width % 4 != 0, ERR_UNAVAILABLE,
|
||||
vformat("2-bpp BMP images must have a width that is a multiple of 4, but the imported BMP is %d pixels wide.", int(width)));
|
||||
ERR_FAIL_COND_V_MSG(height % 4 != 0, ERR_UNAVAILABLE,
|
||||
vformat("2-bpp BMP images must have a height that is a multiple of 4, but the imported BMP is %d pixels tall.", int(height)));
|
||||
|
||||
} else if (bits_per_pixel == 4) {
|
||||
// Requires bit unpacking...
|
||||
ERR_FAIL_COND_V_MSG(width % 2 != 0, ERR_UNAVAILABLE,
|
||||
vformat("4-bpp BMP images must have a width that is a multiple of 2, but the imported BMP is %d pixels wide.", int(width)));
|
||||
ERR_FAIL_COND_V_MSG(height % 2 != 0, ERR_UNAVAILABLE,
|
||||
vformat("4-bpp BMP images must have a height that is a multiple of 2, but the imported BMP is %d pixels tall.", int(height)));
|
||||
}
|
||||
|
||||
// Image data (might be indexed)
|
||||
Vector<uint8_t> data;
|
||||
int data_len = 0;
|
||||
|
||||
if (bits_per_pixel <= 8) { // indexed
|
||||
data_len = width * height;
|
||||
} else { // color
|
||||
data_len = width * height * 4;
|
||||
}
|
||||
ERR_FAIL_COND_V_MSG(data_len == 0, ERR_BUG, "Couldn't parse the BMP image data.");
|
||||
err = data.resize(data_len);
|
||||
|
||||
uint8_t *data_w = data.ptrw();
|
||||
uint8_t *write_buffer = data_w;
|
||||
|
||||
const uint32_t width_bytes = width * bits_per_pixel / 8;
|
||||
const uint32_t line_width = (width_bytes + 3) & ~3;
|
||||
|
||||
// The actual data traversal is determined by
|
||||
// the data width in case of 8/4/2/1 bit images
|
||||
const uint32_t w = bits_per_pixel >= 16 ? width : width_bytes;
|
||||
const uint8_t *line = p_buffer + (line_width * (height - 1));
|
||||
const uint8_t *end_buffer = p_buffer + p_header.bmp_file_header.bmp_file_size - p_header.bmp_file_header.bmp_file_offset;
|
||||
|
||||
for (uint64_t i = 0; i < height; i++) {
|
||||
const uint8_t *line_ptr = line;
|
||||
|
||||
for (unsigned int j = 0; j < w; j++) {
|
||||
ERR_FAIL_COND_V(line_ptr >= end_buffer, ERR_FILE_CORRUPT);
|
||||
switch (bits_per_pixel) {
|
||||
case 1: {
|
||||
uint8_t color_index = *line_ptr;
|
||||
|
||||
write_buffer[index + 0] = (color_index >> 7) & 1;
|
||||
write_buffer[index + 1] = (color_index >> 6) & 1;
|
||||
write_buffer[index + 2] = (color_index >> 5) & 1;
|
||||
write_buffer[index + 3] = (color_index >> 4) & 1;
|
||||
write_buffer[index + 4] = (color_index >> 3) & 1;
|
||||
write_buffer[index + 5] = (color_index >> 2) & 1;
|
||||
write_buffer[index + 6] = (color_index >> 1) & 1;
|
||||
write_buffer[index + 7] = (color_index >> 0) & 1;
|
||||
|
||||
index += 8;
|
||||
line_ptr += 1;
|
||||
} break;
|
||||
case 2: {
|
||||
uint8_t color_index = *line_ptr;
|
||||
|
||||
write_buffer[index + 0] = (color_index >> 6) & 3;
|
||||
write_buffer[index + 1] = (color_index >> 4) & 3;
|
||||
write_buffer[index + 2] = (color_index >> 2) & 3;
|
||||
write_buffer[index + 3] = color_index & 3;
|
||||
|
||||
index += 4;
|
||||
line_ptr += 1;
|
||||
} break;
|
||||
case 4: {
|
||||
uint8_t color_index = *line_ptr;
|
||||
|
||||
write_buffer[index + 0] = (color_index >> 4) & 0x0f;
|
||||
write_buffer[index + 1] = color_index & 0x0f;
|
||||
|
||||
index += 2;
|
||||
line_ptr += 1;
|
||||
} break;
|
||||
case 8: {
|
||||
uint8_t color_index = *line_ptr;
|
||||
|
||||
write_buffer[index] = color_index;
|
||||
|
||||
index += 1;
|
||||
line_ptr += 1;
|
||||
} break;
|
||||
case 16: {
|
||||
uint16_t rgb = (static_cast<uint16_t>(line_ptr[1]) << 8) | line_ptr[0];
|
||||
// A1R5G5B5/X1R5G5B5 => uint16_t
|
||||
// [A/X]1R5G2 | G3B5 => uint8_t | uint8_t
|
||||
uint8_t ba = (rgb & p_header.bmp_bitfield.alpha_mask) >> p_header.bmp_bitfield.alpha_offset; // Alpha 0b 1000 ...
|
||||
uint8_t b0 = (rgb & p_header.bmp_bitfield.red_mask) >> p_header.bmp_bitfield.red_offset; // Red 0b 0111 1100 ...
|
||||
uint8_t b1 = (rgb & p_header.bmp_bitfield.green_mask) >> p_header.bmp_bitfield.green_offset; // Green 0b 0000 0011 1110 ...
|
||||
uint8_t b2 = (rgb & p_header.bmp_bitfield.blue_mask); // >> p_header.bmp_bitfield.blue_offset; // Blue 0b ... 0001 1111
|
||||
|
||||
// Next we apply some color scaling going from a variable value space to a 256 value space.
|
||||
// This may be simplified some but left as is for legibility.
|
||||
// float scaled_value = unscaled_value * byte_max_value / color_channel_maximum_value + rounding_offset;
|
||||
float f0 = b0 * 255.0f / static_cast<float>(p_header.bmp_bitfield.red_max) + 0.5f;
|
||||
float f1 = b1 * 255.0f / static_cast<float>(p_header.bmp_bitfield.green_max) + 0.5f;
|
||||
float f2 = b2 * 255.0f / static_cast<float>(p_header.bmp_bitfield.blue_max) + 0.5f;
|
||||
write_buffer[index + 0] = static_cast<uint8_t>(f0); // R
|
||||
write_buffer[index + 1] = static_cast<uint8_t>(f1); // G
|
||||
write_buffer[index + 2] = static_cast<uint8_t>(f2); // B
|
||||
|
||||
if (p_header.bmp_bitfield.alpha_mask_width > 0) {
|
||||
write_buffer[index + 3] = ba * 0xFF; // Alpha value(Always true or false so no scaling)
|
||||
} else {
|
||||
write_buffer[index + 3] = 0xFF; // No Alpha channel, Show everything.
|
||||
}
|
||||
|
||||
index += 4;
|
||||
line_ptr += 2;
|
||||
} break;
|
||||
case 24: {
|
||||
write_buffer[index + 2] = line_ptr[0];
|
||||
write_buffer[index + 1] = line_ptr[1];
|
||||
write_buffer[index + 0] = line_ptr[2];
|
||||
write_buffer[index + 3] = 0xff;
|
||||
|
||||
index += 4;
|
||||
line_ptr += 3;
|
||||
} break;
|
||||
case 32: {
|
||||
write_buffer[index + 2] = line_ptr[0];
|
||||
write_buffer[index + 1] = line_ptr[1];
|
||||
write_buffer[index + 0] = line_ptr[2];
|
||||
write_buffer[index + 3] = line_ptr[3];
|
||||
|
||||
index += 4;
|
||||
line_ptr += 4;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
line -= line_width;
|
||||
}
|
||||
|
||||
if (p_color_buffer == nullptr || color_table_size == 0) { // regular pixels
|
||||
|
||||
p_image->set_data(width, height, false, Image::FORMAT_RGBA8, data);
|
||||
|
||||
} else { // data is in indexed format, extend it
|
||||
|
||||
// Palette data
|
||||
Vector<uint8_t> palette_data;
|
||||
palette_data.resize(color_table_size * 4);
|
||||
|
||||
uint8_t *palette_data_w = palette_data.ptrw();
|
||||
uint8_t *pal = palette_data_w;
|
||||
|
||||
const uint8_t *cb = p_color_buffer;
|
||||
|
||||
for (unsigned int i = 0; i < color_table_size; ++i) {
|
||||
pal[i * 4 + 0] = cb[2];
|
||||
pal[i * 4 + 1] = cb[1];
|
||||
pal[i * 4 + 2] = cb[0];
|
||||
pal[i * 4 + 3] = 0xff;
|
||||
|
||||
cb += 4;
|
||||
}
|
||||
// Extend palette to image
|
||||
Vector<uint8_t> extended_data;
|
||||
extended_data.resize(data.size() * 4);
|
||||
|
||||
uint8_t *ex_w = extended_data.ptrw();
|
||||
uint8_t *dest = ex_w;
|
||||
|
||||
const int num_pixels = width * height;
|
||||
|
||||
for (int i = 0; i < num_pixels; i++) {
|
||||
dest[0] = pal[write_buffer[i] * 4 + 0];
|
||||
dest[1] = pal[write_buffer[i] * 4 + 1];
|
||||
dest[2] = pal[write_buffer[i] * 4 + 2];
|
||||
dest[3] = pal[write_buffer[i] * 4 + 3];
|
||||
|
||||
dest += 4;
|
||||
}
|
||||
p_image->set_data(width, height, false, Image::FORMAT_RGBA8, extended_data);
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
Error ImageLoaderBMP::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale) {
|
||||
bmp_header_s bmp_header;
|
||||
Error err = ERR_INVALID_DATA;
|
||||
|
||||
// A valid bmp file should always at least have a
|
||||
// file header and a minimal info header
|
||||
if (f->get_length() > BITMAP_FILE_HEADER_SIZE + BITMAP_INFO_HEADER_MIN_SIZE) {
|
||||
// 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();
|
||||
ERR_FAIL_COND_V_MSG(bmp_header.bmp_info_header.bmp_header_size < BITMAP_INFO_HEADER_MIN_SIZE, ERR_FILE_CORRUPT,
|
||||
vformat("Couldn't parse the BMP info header. The file is likely corrupt: %s", f->get_path()));
|
||||
|
||||
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();
|
||||
ERR_FAIL_COND_V_MSG(bmp_header.bmp_info_header.bmp_planes != 1, ERR_FILE_CORRUPT,
|
||||
vformat("Couldn't parse the BMP planes. The file is likely corrupt: %s", f->get_path()));
|
||||
|
||||
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();
|
||||
|
||||
switch (bmp_header.bmp_info_header.bmp_compression) {
|
||||
case BI_BITFIELDS: {
|
||||
bmp_header.bmp_bitfield.red_mask = f->get_32();
|
||||
bmp_header.bmp_bitfield.green_mask = f->get_32();
|
||||
bmp_header.bmp_bitfield.blue_mask = f->get_32();
|
||||
bmp_header.bmp_bitfield.alpha_mask = f->get_32();
|
||||
|
||||
bmp_header.bmp_bitfield.red_mask_width = get_mask_width(bmp_header.bmp_bitfield.red_mask);
|
||||
bmp_header.bmp_bitfield.green_mask_width = get_mask_width(bmp_header.bmp_bitfield.green_mask);
|
||||
bmp_header.bmp_bitfield.blue_mask_width = get_mask_width(bmp_header.bmp_bitfield.blue_mask);
|
||||
bmp_header.bmp_bitfield.alpha_mask_width = get_mask_width(bmp_header.bmp_bitfield.alpha_mask);
|
||||
|
||||
bmp_header.bmp_bitfield.alpha_offset = bmp_header.bmp_bitfield.red_mask_width + bmp_header.bmp_bitfield.green_mask_width + bmp_header.bmp_bitfield.blue_mask_width;
|
||||
bmp_header.bmp_bitfield.red_offset = bmp_header.bmp_bitfield.green_mask_width + bmp_header.bmp_bitfield.blue_mask_width;
|
||||
bmp_header.bmp_bitfield.green_offset = bmp_header.bmp_bitfield.blue_mask_width;
|
||||
|
||||
bmp_header.bmp_bitfield.red_max = (1 << bmp_header.bmp_bitfield.red_mask_width) - 1;
|
||||
bmp_header.bmp_bitfield.green_max = (1 << bmp_header.bmp_bitfield.green_mask_width) - 1;
|
||||
bmp_header.bmp_bitfield.blue_max = (1 << bmp_header.bmp_bitfield.blue_mask_width) - 1;
|
||||
} break;
|
||||
case BI_RLE8:
|
||||
case BI_RLE4:
|
||||
case BI_CMYKRLE8:
|
||||
case BI_CMYKRLE4: {
|
||||
// Stop parsing.
|
||||
ERR_FAIL_V_MSG(ERR_UNAVAILABLE,
|
||||
vformat("RLE compressed BMP files are not yet supported: %s", f->get_path()));
|
||||
} break;
|
||||
}
|
||||
// Don't rely on sizeof(bmp_file_header) as structure padding
|
||||
// adds 2 bytes offset leading to misaligned color table reading
|
||||
uint32_t ct_offset = BITMAP_FILE_HEADER_SIZE + bmp_header.bmp_info_header.bmp_header_size;
|
||||
f->seek(ct_offset);
|
||||
|
||||
uint32_t color_table_size = 0;
|
||||
|
||||
// bmp_colors_used may report 0 despite having a color table
|
||||
// for 4 and 1 bit images, so don't rely on this information
|
||||
if (bmp_header.bmp_info_header.bmp_bit_count <= 8) {
|
||||
// Support 256 colors max
|
||||
color_table_size = 1 << bmp_header.bmp_info_header.bmp_bit_count;
|
||||
ERR_FAIL_COND_V_MSG(color_table_size == 0, ERR_BUG,
|
||||
vformat("Couldn't parse the BMP color table: %s", f->get_path()));
|
||||
}
|
||||
|
||||
Vector<uint8_t> bmp_color_table;
|
||||
// Color table is usually 4 bytes per color -> [B][G][R][0]
|
||||
bmp_color_table.resize(color_table_size * 4);
|
||||
uint8_t *bmp_color_table_w = bmp_color_table.ptrw();
|
||||
f->get_buffer(bmp_color_table_w, color_table_size * 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);
|
||||
|
||||
Vector<uint8_t> bmp_buffer;
|
||||
err = bmp_buffer.resize(bmp_buffer_size);
|
||||
if (err == OK) {
|
||||
uint8_t *bmp_buffer_w = bmp_buffer.ptrw();
|
||||
f->get_buffer(bmp_buffer_w, bmp_buffer_size);
|
||||
|
||||
const uint8_t *bmp_buffer_r = bmp_buffer.ptr();
|
||||
const uint8_t *bmp_color_table_r = bmp_color_table.ptr();
|
||||
err = convert_to_image(p_image, bmp_buffer_r,
|
||||
bmp_color_table_r, color_table_size, bmp_header);
|
||||
}
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void ImageLoaderBMP::get_recognized_extensions(List<String> *p_extensions) const {
|
||||
p_extensions->push_back("bmp");
|
||||
}
|
||||
|
||||
static Ref<Image> _bmp_mem_loader_func(const uint8_t *p_bmp, int p_size) {
|
||||
Ref<FileAccessMemory> memfile;
|
||||
memfile.instantiate();
|
||||
Error open_memfile_error = memfile->open_custom(p_bmp, p_size);
|
||||
ERR_FAIL_COND_V_MSG(open_memfile_error, Ref<Image>(), "Could not create memfile for BMP image buffer.");
|
||||
|
||||
Ref<Image> img;
|
||||
img.instantiate();
|
||||
Error load_error = ImageLoaderBMP().load_image(img, memfile, false, 1.0f);
|
||||
ERR_FAIL_COND_V_MSG(load_error, Ref<Image>(), "Failed to load BMP image.");
|
||||
return img;
|
||||
}
|
||||
|
||||
ImageLoaderBMP::ImageLoaderBMP() {
|
||||
Image::_bmp_mem_loader_func = _bmp_mem_loader_func;
|
||||
}
|
||||
110
engine/modules/bmp/image_loader_bmp.h
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/**************************************************************************/
|
||||
/* image_loader_bmp.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 "core/io/image_loader.h"
|
||||
|
||||
class ImageLoaderBMP : public ImageFormatLoader {
|
||||
protected:
|
||||
static const unsigned BITMAP_SIGNATURE = 0x4d42;
|
||||
|
||||
static const unsigned BITMAP_FILE_HEADER_SIZE = 14; // bmp_file_header_s
|
||||
static const unsigned BITMAP_INFO_HEADER_MIN_SIZE = 40; // bmp_info_header_s
|
||||
|
||||
enum bmp_compression_s {
|
||||
BI_RGB = 0x00,
|
||||
BI_RLE8 = 0x01, // compressed
|
||||
BI_RLE4 = 0x02, // compressed
|
||||
BI_BITFIELDS = 0x03,
|
||||
BI_JPEG = 0x04,
|
||||
BI_PNG = 0x05,
|
||||
BI_ALPHABITFIELDS = 0x06,
|
||||
BI_CMYK = 0x0b,
|
||||
BI_CMYKRLE8 = 0x0c, // compressed
|
||||
BI_CMYKRLE4 = 0x0d // compressed
|
||||
};
|
||||
|
||||
struct bmp_header_s {
|
||||
struct bmp_file_header_s {
|
||||
uint16_t bmp_signature = 0;
|
||||
uint32_t bmp_file_size = 0;
|
||||
uint32_t bmp_file_padding = 0;
|
||||
uint32_t bmp_file_offset = 0;
|
||||
} bmp_file_header;
|
||||
|
||||
struct bmp_info_header_s {
|
||||
uint32_t bmp_header_size = 0;
|
||||
uint32_t bmp_width = 0;
|
||||
uint32_t bmp_height = 0;
|
||||
uint16_t bmp_planes = 0;
|
||||
uint16_t bmp_bit_count = 0;
|
||||
uint32_t bmp_compression = 0;
|
||||
uint32_t bmp_size_image = 0;
|
||||
uint32_t bmp_pixels_per_meter_x = 0;
|
||||
uint32_t bmp_pixels_per_meter_y = 0;
|
||||
uint32_t bmp_colors_used = 0;
|
||||
uint32_t bmp_important_colors = 0;
|
||||
} bmp_info_header;
|
||||
|
||||
struct bmp_bitfield_s {
|
||||
uint16_t alpha_mask = 0x8000;
|
||||
uint16_t red_mask = 0x7C00;
|
||||
uint16_t green_mask = 0x03E0;
|
||||
uint16_t blue_mask = 0x001F;
|
||||
uint16_t alpha_mask_width = 1u;
|
||||
uint16_t red_mask_width = 5u;
|
||||
uint16_t green_mask_width = 5u;
|
||||
uint16_t blue_mask_width = 5u;
|
||||
uint8_t alpha_offset = 15u; // Used for bit shifting.
|
||||
uint8_t red_offset = 10u; // Used for bit shifting.
|
||||
uint8_t green_offset = 5u; // Used for bit shifting.
|
||||
//uint8_t blue_offset = 0u; // Always LSB aligned no shifting needed.
|
||||
//uint8_t alpha_max = 1u; // Always boolean or on, so no scaling needed.
|
||||
uint8_t red_max = 32u; // Used for color space scaling.
|
||||
uint8_t green_max = 32u; // Used for color space scaling.
|
||||
uint8_t blue_max = 32u; // Used for color space scaling.
|
||||
} bmp_bitfield;
|
||||
};
|
||||
|
||||
static Error convert_to_image(Ref<Image> p_image,
|
||||
const uint8_t *p_buffer,
|
||||
const uint8_t *p_color_buffer,
|
||||
const uint32_t color_table_size,
|
||||
const bmp_header_s &p_header);
|
||||
|
||||
public:
|
||||
virtual Error load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale);
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const;
|
||||
ImageLoaderBMP();
|
||||
};
|
||||
|
||||
#endif // IMAGE_LOADER_BMP_H
|
||||
53
engine/modules/bmp/register_types.cpp
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 Ref<ImageLoaderBMP> image_loader_bmp;
|
||||
|
||||
void initialize_bmp_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
|
||||
image_loader_bmp.instantiate();
|
||||
ImageLoader::add_image_format_loader(image_loader_bmp);
|
||||
}
|
||||
|
||||
void uninitialize_bmp_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImageLoader::remove_image_format_loader(image_loader_bmp);
|
||||
image_loader_bmp.unref();
|
||||
}
|
||||
39
engine/modules/bmp/register_types.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 BMP_REGISTER_TYPES_H
|
||||
#define BMP_REGISTER_TYPES_H
|
||||
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
void initialize_bmp_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_bmp_module(ModuleInitializationLevel p_level);
|
||||
|
||||
#endif // BMP_REGISTER_TYPES_H
|
||||
14
engine/modules/camera/SCsub
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
||||
env_camera = env_modules.Clone()
|
||||
|
||||
if env["platform"] == "windows":
|
||||
env_camera.add_source_files(env.modules_sources, "register_types.cpp")
|
||||
env_camera.add_source_files(env.modules_sources, "camera_win.cpp")
|
||||
|
||||
elif env["platform"] == "macos":
|
||||
env_camera.add_source_files(env.modules_sources, "register_types.cpp")
|
||||
env_camera.add_source_files(env.modules_sources, "camera_macos.mm")
|
||||
46
engine/modules/camera/camera_macos.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/**************************************************************************/
|
||||
/* camera_macos.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 CAMERA_MACOS_H
|
||||
#define CAMERA_MACOS_H
|
||||
|
||||
///@TODO this is a near duplicate of CameraIOS, we should find a way to combine those to minimize code duplication!!!!
|
||||
// If you fix something here, make sure you fix it there as well!
|
||||
|
||||
#include "servers/camera_server.h"
|
||||
|
||||
class CameraMacOS : public CameraServer {
|
||||
public:
|
||||
CameraMacOS();
|
||||
|
||||
void update_feeds();
|
||||
};
|
||||
|
||||
#endif // CAMERA_MACOS_H
|
||||
356
engine/modules/camera/camera_macos.mm
Normal file
|
|
@ -0,0 +1,356 @@
|
|||
/**************************************************************************/
|
||||
/* camera_macos.mm */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
///@TODO this is a near duplicate of CameraIOS, we should find a way to combine those to minimize code duplication!!!!
|
||||
// If you fix something here, make sure you fix it there as well!
|
||||
|
||||
#include "camera_macos.h"
|
||||
|
||||
#include "servers/camera/camera_feed.h"
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// MyCaptureSession - This is a little helper class so we can capture our frames
|
||||
|
||||
@interface MyCaptureSession : AVCaptureSession <AVCaptureVideoDataOutputSampleBufferDelegate> {
|
||||
Ref<CameraFeed> feed;
|
||||
size_t width[2];
|
||||
size_t height[2];
|
||||
Vector<uint8_t> img_data[2];
|
||||
|
||||
AVCaptureDeviceInput *input;
|
||||
AVCaptureVideoDataOutput *output;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MyCaptureSession
|
||||
|
||||
- (id)initForFeed:(Ref<CameraFeed>)p_feed andDevice:(AVCaptureDevice *)p_device {
|
||||
if (self = [super init]) {
|
||||
NSError *error;
|
||||
feed = p_feed;
|
||||
width[0] = 0;
|
||||
height[0] = 0;
|
||||
width[1] = 0;
|
||||
height[1] = 0;
|
||||
|
||||
[self beginConfiguration];
|
||||
|
||||
input = [AVCaptureDeviceInput deviceInputWithDevice:p_device error:&error];
|
||||
if (!input) {
|
||||
print_line("Couldn't get input device for camera");
|
||||
} else {
|
||||
[self addInput:input];
|
||||
}
|
||||
|
||||
output = [AVCaptureVideoDataOutput new];
|
||||
if (!output) {
|
||||
print_line("Couldn't get output device for camera");
|
||||
} else {
|
||||
NSDictionary *settings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) };
|
||||
output.videoSettings = settings;
|
||||
|
||||
// discard if the data output queue is blocked (as we process the still image)
|
||||
[output setAlwaysDiscardsLateVideoFrames:YES];
|
||||
|
||||
// now set ourselves as the delegate to receive new frames.
|
||||
[output setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
|
||||
|
||||
// this takes ownership
|
||||
[self addOutput:output];
|
||||
}
|
||||
|
||||
[self commitConfiguration];
|
||||
|
||||
// kick off our session..
|
||||
[self startRunning];
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)cleanup {
|
||||
// stop running
|
||||
[self stopRunning];
|
||||
|
||||
// cleanup
|
||||
[self beginConfiguration];
|
||||
|
||||
// remove input
|
||||
if (input) {
|
||||
[self removeInput:input];
|
||||
// don't release this
|
||||
input = nullptr;
|
||||
}
|
||||
|
||||
// free up our output
|
||||
if (output) {
|
||||
[self removeOutput:output];
|
||||
[output setSampleBufferDelegate:nil queue:nullptr];
|
||||
output = nullptr;
|
||||
}
|
||||
|
||||
[self commitConfiguration];
|
||||
}
|
||||
|
||||
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
|
||||
// This gets called every time our camera has a new image for us to process.
|
||||
// May need to investigate in a way to throttle this if we get more images then we're rendering frames..
|
||||
|
||||
// For now, version 1, we're just doing the bare minimum to make this work...
|
||||
CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
|
||||
// int _width = CVPixelBufferGetWidth(pixelBuffer);
|
||||
// int _height = CVPixelBufferGetHeight(pixelBuffer);
|
||||
|
||||
// It says that we need to lock this on the documentation pages but it's not in the samples
|
||||
// need to lock our base address so we can access our pixel buffers, better safe then sorry?
|
||||
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||
|
||||
// get our buffers
|
||||
unsigned char *dataY = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
|
||||
unsigned char *dataCbCr = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
|
||||
if (dataY == nullptr) {
|
||||
print_line("Couldn't access Y pixel buffer data");
|
||||
} else if (dataCbCr == nullptr) {
|
||||
print_line("Couldn't access CbCr pixel buffer data");
|
||||
} else {
|
||||
Ref<Image> img[2];
|
||||
|
||||
{
|
||||
// do Y
|
||||
size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
|
||||
size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
|
||||
|
||||
if ((width[0] != new_width) || (height[0] != new_height)) {
|
||||
width[0] = new_width;
|
||||
height[0] = new_height;
|
||||
img_data[0].resize(new_width * new_height);
|
||||
}
|
||||
|
||||
uint8_t *w = img_data[0].ptrw();
|
||||
memcpy(w, dataY, new_width * new_height);
|
||||
|
||||
img[0].instantiate();
|
||||
img[0]->set_data(new_width, new_height, 0, Image::FORMAT_R8, img_data[0]);
|
||||
}
|
||||
|
||||
{
|
||||
// do CbCr
|
||||
size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
|
||||
size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
|
||||
|
||||
if ((width[1] != new_width) || (height[1] != new_height)) {
|
||||
width[1] = new_width;
|
||||
height[1] = new_height;
|
||||
img_data[1].resize(2 * new_width * new_height);
|
||||
}
|
||||
|
||||
uint8_t *w = img_data[1].ptrw();
|
||||
memcpy(w, dataCbCr, 2 * new_width * new_height);
|
||||
|
||||
///TODO OpenGL doesn't support FORMAT_RG8, need to do some form of conversion
|
||||
img[1].instantiate();
|
||||
img[1]->set_data(new_width, new_height, 0, Image::FORMAT_RG8, img_data[1]);
|
||||
}
|
||||
|
||||
// set our texture...
|
||||
feed->set_YCbCr_imgs(img[0], img[1]);
|
||||
}
|
||||
|
||||
// and unlock
|
||||
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// CameraFeedMacOS - Subclass for camera feeds in macOS
|
||||
|
||||
class CameraFeedMacOS : public CameraFeed {
|
||||
private:
|
||||
AVCaptureDevice *device;
|
||||
MyCaptureSession *capture_session;
|
||||
|
||||
public:
|
||||
AVCaptureDevice *get_device() const;
|
||||
|
||||
CameraFeedMacOS();
|
||||
|
||||
void set_device(AVCaptureDevice *p_device);
|
||||
|
||||
bool activate_feed();
|
||||
void deactivate_feed();
|
||||
};
|
||||
|
||||
AVCaptureDevice *CameraFeedMacOS::get_device() const {
|
||||
return device;
|
||||
};
|
||||
|
||||
CameraFeedMacOS::CameraFeedMacOS() {
|
||||
device = nullptr;
|
||||
capture_session = nullptr;
|
||||
};
|
||||
|
||||
void CameraFeedMacOS::set_device(AVCaptureDevice *p_device) {
|
||||
device = p_device;
|
||||
|
||||
// get some info
|
||||
NSString *device_name = p_device.localizedName;
|
||||
name = String::utf8(device_name.UTF8String);
|
||||
position = CameraFeed::FEED_UNSPECIFIED;
|
||||
if ([p_device position] == AVCaptureDevicePositionBack) {
|
||||
position = CameraFeed::FEED_BACK;
|
||||
} else if ([p_device position] == AVCaptureDevicePositionFront) {
|
||||
position = CameraFeed::FEED_FRONT;
|
||||
};
|
||||
};
|
||||
|
||||
bool CameraFeedMacOS::activate_feed() {
|
||||
if (capture_session) {
|
||||
// Already recording!
|
||||
} else {
|
||||
// Start camera capture, check permission.
|
||||
if (@available(macOS 10.14, *)) {
|
||||
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
||||
if (status == AVAuthorizationStatusAuthorized) {
|
||||
capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device];
|
||||
} else if (status == AVAuthorizationStatusNotDetermined) {
|
||||
// Request permission.
|
||||
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
|
||||
completionHandler:^(BOOL granted) {
|
||||
if (granted) {
|
||||
capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device];
|
||||
}
|
||||
}];
|
||||
}
|
||||
} else {
|
||||
capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device];
|
||||
}
|
||||
};
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
void CameraFeedMacOS::deactivate_feed() {
|
||||
// end camera capture if we have one
|
||||
if (capture_session) {
|
||||
[capture_session cleanup];
|
||||
capture_session = nullptr;
|
||||
};
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// MyDeviceNotifications - This is a little helper class gets notifications
|
||||
// when devices are connected/disconnected
|
||||
|
||||
@interface MyDeviceNotifications : NSObject {
|
||||
CameraMacOS *camera_server;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MyDeviceNotifications
|
||||
|
||||
- (void)devices_changed:(NSNotification *)notification {
|
||||
camera_server->update_feeds();
|
||||
}
|
||||
|
||||
- (id)initForServer:(CameraMacOS *)p_server {
|
||||
if (self = [super init]) {
|
||||
camera_server = p_server;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasConnectedNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasDisconnectedNotification object:nil];
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
// remove notifications
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
MyDeviceNotifications *device_notifications = nil;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// CameraMacOS - Subclass for our camera server on macOS
|
||||
|
||||
void CameraMacOS::update_feeds() {
|
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101500
|
||||
AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeExternalUnknown, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
|
||||
NSArray *devices = session.devices;
|
||||
#else
|
||||
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
|
||||
#endif
|
||||
|
||||
// remove devices that are gone..
|
||||
for (int i = feeds.size() - 1; i >= 0; i--) {
|
||||
Ref<CameraFeedMacOS> feed = (Ref<CameraFeedMacOS>)feeds[i];
|
||||
|
||||
if (![devices containsObject:feed->get_device()]) {
|
||||
// remove it from our array, this will also destroy it ;)
|
||||
remove_feed(feed);
|
||||
};
|
||||
};
|
||||
|
||||
for (AVCaptureDevice *device in devices) {
|
||||
bool found = false;
|
||||
for (int i = 0; i < feeds.size() && !found; i++) {
|
||||
Ref<CameraFeedMacOS> feed = (Ref<CameraFeedMacOS>)feeds[i];
|
||||
if (feed->get_device() == device) {
|
||||
found = true;
|
||||
};
|
||||
};
|
||||
|
||||
if (!found) {
|
||||
Ref<CameraFeedMacOS> newfeed;
|
||||
newfeed.instantiate();
|
||||
newfeed->set_device(device);
|
||||
|
||||
// assume display camera so inverse
|
||||
Transform2D transform = Transform2D(-1.0, 0.0, 0.0, -1.0, 1.0, 1.0);
|
||||
newfeed->set_transform(transform);
|
||||
|
||||
add_feed(newfeed);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
CameraMacOS::CameraMacOS() {
|
||||
// Find available cameras we have at this time
|
||||
update_feeds();
|
||||
|
||||
// should only have one of these....
|
||||
device_notifications = [[MyDeviceNotifications alloc] initForServer:this];
|
||||
};
|
||||
94
engine/modules/camera/camera_win.cpp
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/**************************************************************************/
|
||||
/* camera_win.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 "camera_win.h"
|
||||
|
||||
///@TODO sorry guys, I got about 80% through implementing this using DirectShow only
|
||||
// to find out Microsoft deprecated half the API and its replacement is as confusing
|
||||
// as they could make it. Joey suggested looking into libuvc which offers a more direct
|
||||
// route to webcams over USB and this is very promising but it wouldn't compile on
|
||||
// windows for me...I've gutted the classes I implemented DirectShow in just to have
|
||||
// a skeleton for someone to work on, mail me for more details or if you want a copy....
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// CameraFeedWindows - Subclass for our camera feed on windows
|
||||
|
||||
/// @TODO need to implement this
|
||||
|
||||
class CameraFeedWindows : public CameraFeed {
|
||||
private:
|
||||
protected:
|
||||
public:
|
||||
CameraFeedWindows();
|
||||
virtual ~CameraFeedWindows();
|
||||
|
||||
bool activate_feed();
|
||||
void deactivate_feed();
|
||||
};
|
||||
|
||||
CameraFeedWindows::CameraFeedWindows() {
|
||||
///@TODO implement this, should store information about our available camera
|
||||
}
|
||||
|
||||
CameraFeedWindows::~CameraFeedWindows() {
|
||||
// make sure we stop recording if we are!
|
||||
if (is_active()) {
|
||||
deactivate_feed();
|
||||
};
|
||||
|
||||
///@TODO free up anything used by this
|
||||
};
|
||||
|
||||
bool CameraFeedWindows::activate_feed() {
|
||||
///@TODO this should activate our camera and start the process of capturing frames
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
///@TODO we should probably have a callback method here that is being called by the
|
||||
// camera API which provides frames and call back into the CameraServer to update our texture
|
||||
|
||||
void CameraFeedWindows::deactivate_feed() {
|
||||
///@TODO this should deactivate our camera and stop the process of capturing frames
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// CameraWindows - Subclass for our camera server on windows
|
||||
|
||||
void CameraWindows::add_active_cameras() {
|
||||
///@TODO scan through any active cameras and create CameraFeedWindows objects for them
|
||||
}
|
||||
|
||||
CameraWindows::CameraWindows() {
|
||||
// Find cameras active right now
|
||||
add_active_cameras();
|
||||
|
||||
// need to add something that will react to devices being connected/removed...
|
||||
};
|
||||
46
engine/modules/camera/camera_win.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/**************************************************************************/
|
||||
/* camera_win.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 CAMERA_WIN_H
|
||||
#define CAMERA_WIN_H
|
||||
|
||||
#include "servers/camera/camera_feed.h"
|
||||
#include "servers/camera_server.h"
|
||||
|
||||
class CameraWindows : public CameraServer {
|
||||
private:
|
||||
void add_active_cameras();
|
||||
|
||||
public:
|
||||
CameraWindows();
|
||||
~CameraWindows() {}
|
||||
};
|
||||
|
||||
#endif // CAMERA_WIN_H
|
||||
6
engine/modules/camera/config.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
def can_build(env, platform):
|
||||
return platform == "macos" or platform == "windows"
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
||||
57
engine/modules/camera/register_types.cpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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"
|
||||
|
||||
#if defined(WINDOWS_ENABLED)
|
||||
#include "camera_win.h"
|
||||
#endif
|
||||
#if defined(MACOS_ENABLED)
|
||||
#include "camera_macos.h"
|
||||
#endif
|
||||
|
||||
void initialize_camera_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(WINDOWS_ENABLED)
|
||||
CameraServer::make_default<CameraWindows>();
|
||||
#endif
|
||||
#if defined(MACOS_ENABLED)
|
||||
CameraServer::make_default<CameraMacOS>();
|
||||
#endif
|
||||
}
|
||||
|
||||
void uninitialize_camera_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
39
engine/modules/camera/register_types.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 CAMERA_REGISTER_TYPES_H
|
||||
#define CAMERA_REGISTER_TYPES_H
|
||||
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
void initialize_camera_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_camera_module(ModuleInitializationLevel p_level);
|
||||
|
||||
#endif // CAMERA_REGISTER_TYPES_H
|
||||
11
engine/modules/csg/SCsub
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
||||
env_csg = env_modules.Clone()
|
||||
|
||||
# Godot source files
|
||||
env_csg.add_source_files(env.modules_sources, "*.cpp")
|
||||
if env.editor_build:
|
||||
env_csg.add_source_files(env.modules_sources, "editor/*.cpp")
|
||||
24
engine/modules/csg/config.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
def can_build(env, platform):
|
||||
return not env["disable_3d"]
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
||||
|
||||
|
||||
def get_doc_classes():
|
||||
return [
|
||||
"CSGBox3D",
|
||||
"CSGCombiner3D",
|
||||
"CSGCylinder3D",
|
||||
"CSGMesh3D",
|
||||
"CSGPolygon3D",
|
||||
"CSGPrimitive3D",
|
||||
"CSGShape3D",
|
||||
"CSGSphere3D",
|
||||
"CSGTorus3D",
|
||||
]
|
||||
|
||||
|
||||
def get_doc_path():
|
||||
return "doc_classes"
|
||||
1547
engine/modules/csg/csg.cpp
Normal file
204
engine/modules/csg/csg.h
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
/**************************************************************************/
|
||||
/* csg.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 "core/math/aabb.h"
|
||||
#include "core/math/plane.h"
|
||||
#include "core/math/transform_3d.h"
|
||||
#include "core/math/vector2.h"
|
||||
#include "core/math/vector3.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "core/templates/list.h"
|
||||
#include "core/templates/oa_hash_map.h"
|
||||
#include "core/templates/vector.h"
|
||||
#include "scene/resources/material.h"
|
||||
|
||||
struct CSGBrush {
|
||||
struct Face {
|
||||
Vector3 vertices[3];
|
||||
Vector2 uvs[3];
|
||||
AABB aabb;
|
||||
bool smooth = false;
|
||||
bool invert = false;
|
||||
int material = 0;
|
||||
};
|
||||
|
||||
Vector<Face> faces;
|
||||
Vector<Ref<Material>> materials;
|
||||
|
||||
inline void _regen_face_aabbs();
|
||||
|
||||
// Create a brush from faces.
|
||||
void build_from_faces(const Vector<Vector3> &p_vertices, const Vector<Vector2> &p_uvs, const Vector<bool> &p_smooth, const Vector<Ref<Material>> &p_materials, const Vector<bool> &p_invert_faces);
|
||||
void copy_from(const CSGBrush &p_brush, const Transform3D &p_xform);
|
||||
};
|
||||
|
||||
struct CSGBrushOperation {
|
||||
enum Operation {
|
||||
OPERATION_UNION,
|
||||
OPERATION_INTERSECTION,
|
||||
OPERATION_SUBTRACTION,
|
||||
};
|
||||
|
||||
void merge_brushes(Operation p_operation, const CSGBrush &p_brush_a, const CSGBrush &p_brush_b, CSGBrush &r_merged_brush, float p_vertex_snap);
|
||||
|
||||
struct MeshMerge {
|
||||
struct Face {
|
||||
bool from_b = false;
|
||||
bool inside = false;
|
||||
int points[3] = {};
|
||||
Vector2 uvs[3];
|
||||
bool smooth = false;
|
||||
bool invert = false;
|
||||
int material_idx = 0;
|
||||
};
|
||||
|
||||
struct FaceBVH {
|
||||
int face = 0;
|
||||
int left = 0;
|
||||
int right = 0;
|
||||
int next = 0;
|
||||
Vector3 center;
|
||||
AABB aabb;
|
||||
};
|
||||
|
||||
struct FaceBVHCmpX {
|
||||
_FORCE_INLINE_ bool operator()(const FaceBVH *p_left, const FaceBVH *p_right) const {
|
||||
return p_left->center.x < p_right->center.x;
|
||||
}
|
||||
};
|
||||
|
||||
struct FaceBVHCmpY {
|
||||
_FORCE_INLINE_ bool operator()(const FaceBVH *p_left, const FaceBVH *p_right) const {
|
||||
return p_left->center.y < p_right->center.y;
|
||||
}
|
||||
};
|
||||
struct FaceBVHCmpZ {
|
||||
_FORCE_INLINE_ bool operator()(const FaceBVH *p_left, const FaceBVH *p_right) const {
|
||||
return p_left->center.z < p_right->center.z;
|
||||
}
|
||||
};
|
||||
|
||||
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_murmur3_one_32(p_vk.x);
|
||||
h = hash_murmur3_one_32(p_vk.y, h);
|
||||
h = hash_murmur3_one_32(p_vk.z, h);
|
||||
return h;
|
||||
}
|
||||
};
|
||||
struct Intersection {
|
||||
bool found = false;
|
||||
real_t conormal = FLT_MAX;
|
||||
real_t distance_squared = FLT_MAX;
|
||||
real_t origin_angle = FLT_MAX;
|
||||
};
|
||||
|
||||
struct IntersectionDistance {
|
||||
bool is_conormal;
|
||||
real_t distance_squared;
|
||||
};
|
||||
|
||||
Vector<Vector3> points;
|
||||
Vector<Face> faces;
|
||||
HashMap<Ref<Material>, int> materials;
|
||||
HashMap<Vector3, int> vertex_map;
|
||||
OAHashMap<VertexKey, int, VertexKeyHash> snap_cache;
|
||||
float vertex_snap = 0.0;
|
||||
|
||||
inline void _add_distance(List<IntersectionDistance> &r_intersectionsA, List<IntersectionDistance> &r_intersectionsB, bool p_from_B, real_t p_distance, bool p_is_conormal) const;
|
||||
inline bool _bvh_inside(FaceBVH *r_facebvhptr, int p_max_depth, int p_bvh_first, int p_face_idx) const;
|
||||
inline int _create_bvh(FaceBVH *r_facebvhptr, FaceBVH **r_facebvhptrptr, int p_from, int p_size, int p_depth, int &r_max_depth, int &r_max_alloc);
|
||||
|
||||
void add_face(const Vector3 p_points[3], const Vector2 p_uvs[3], bool p_smooth, bool p_invert, const Ref<Material> &p_material, bool p_from_b);
|
||||
void mark_inside_faces();
|
||||
};
|
||||
|
||||
struct Build2DFaces {
|
||||
struct Vertex2D {
|
||||
Vector2 point;
|
||||
Vector2 uv;
|
||||
};
|
||||
|
||||
struct Face2D {
|
||||
int vertex_idx[3] = {};
|
||||
};
|
||||
|
||||
Vector<Vertex2D> vertices;
|
||||
Vector<Face2D> faces;
|
||||
Plane plane;
|
||||
Transform3D to_2D;
|
||||
Transform3D to_3D;
|
||||
float vertex_snap2 = 0.0;
|
||||
|
||||
inline int _get_point_idx(const Vector2 &p_point);
|
||||
inline int _add_vertex(const Vertex2D &p_vertex);
|
||||
inline void _add_vertex_idx_sorted(Vector<int> &r_vertex_indices, int p_new_vertex_index);
|
||||
inline void _merge_faces(const Vector<int> &p_segment_indices);
|
||||
inline void _find_edge_intersections(const Vector2 p_segment_points[2], Vector<int> &r_segment_indices);
|
||||
inline int _insert_point(const Vector2 &p_point);
|
||||
|
||||
void insert(const CSGBrush &p_brush, int p_brush_face);
|
||||
void addFacesToMesh(MeshMerge &r_mesh_merge, bool p_smooth, bool p_invert, const Ref<Material> &p_material, bool p_from_b);
|
||||
|
||||
Build2DFaces() {}
|
||||
Build2DFaces(const CSGBrush &p_brush, int p_brush_face, float p_vertex_snap2);
|
||||
};
|
||||
|
||||
struct Build2DFaceCollection {
|
||||
HashMap<int, Build2DFaces> build2DFacesA;
|
||||
HashMap<int, Build2DFaces> build2DFacesB;
|
||||
};
|
||||
|
||||
void update_faces(const CSGBrush &p_brush_a, const int p_face_idx_a, const CSGBrush &p_brush_b, const int p_face_idx_b, Build2DFaceCollection &p_collection, float p_vertex_snap);
|
||||
};
|
||||
|
||||
#endif // CSG_H
|
||||
2446
engine/modules/csg/csg_shape.cpp
Normal file
459
engine/modules/csg/csg_shape.h
Normal file
|
|
@ -0,0 +1,459 @@
|
|||
/**************************************************************************/
|
||||
/* csg_shape.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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
|
||||
|
||||
#include "csg.h"
|
||||
|
||||
#include "scene/3d/path_3d.h"
|
||||
#include "scene/3d/visual_instance_3d.h"
|
||||
#include "scene/resources/3d/concave_polygon_shape_3d.h"
|
||||
|
||||
#include "thirdparty/misc/mikktspace.h"
|
||||
|
||||
class CSGShape3D : public GeometryInstance3D {
|
||||
GDCLASS(CSGShape3D, GeometryInstance3D);
|
||||
|
||||
public:
|
||||
enum Operation {
|
||||
OPERATION_UNION,
|
||||
OPERATION_INTERSECTION,
|
||||
OPERATION_SUBTRACTION,
|
||||
|
||||
};
|
||||
|
||||
private:
|
||||
Operation operation = OPERATION_UNION;
|
||||
CSGShape3D *parent_shape = nullptr;
|
||||
|
||||
CSGBrush *brush = nullptr;
|
||||
|
||||
AABB node_aabb;
|
||||
|
||||
bool dirty = false;
|
||||
bool last_visible = false;
|
||||
float snap = 0.001;
|
||||
|
||||
bool use_collision = false;
|
||||
uint32_t collision_layer = 1;
|
||||
uint32_t collision_mask = 1;
|
||||
real_t collision_priority = 1.0;
|
||||
Ref<ConcavePolygonShape3D> root_collision_shape;
|
||||
RID root_collision_instance;
|
||||
RID root_collision_debug_instance;
|
||||
Transform3D debug_shape_old_transform;
|
||||
|
||||
bool calculate_tangents = true;
|
||||
|
||||
Ref<ArrayMesh> root_mesh;
|
||||
|
||||
struct Vector3Hasher {
|
||||
_ALWAYS_INLINE_ uint32_t hash(const Vector3 &p_vec3) const {
|
||||
uint32_t h = hash_murmur3_one_float(p_vec3.x);
|
||||
h = hash_murmur3_one_float(p_vec3.y, h);
|
||||
h = hash_murmur3_one_float(p_vec3.z, h);
|
||||
return h;
|
||||
}
|
||||
};
|
||||
|
||||
struct ShapeUpdateSurface {
|
||||
Vector<Vector3> vertices;
|
||||
Vector<Vector3> normals;
|
||||
Vector<Vector2> uvs;
|
||||
Vector<real_t> tans;
|
||||
Ref<Material> material;
|
||||
int last_added = 0;
|
||||
|
||||
Vector3 *verticesw = nullptr;
|
||||
Vector3 *normalsw = nullptr;
|
||||
Vector2 *uvsw = nullptr;
|
||||
real_t *tansw = nullptr;
|
||||
};
|
||||
|
||||
//mikktspace callbacks
|
||||
static int mikktGetNumFaces(const SMikkTSpaceContext *pContext);
|
||||
static int mikktGetNumVerticesOfFace(const SMikkTSpaceContext *pContext, const int iFace);
|
||||
static void mikktGetPosition(const SMikkTSpaceContext *pContext, float fvPosOut[], const int iFace, const int iVert);
|
||||
static void mikktGetNormal(const SMikkTSpaceContext *pContext, float fvNormOut[], const int iFace, const int iVert);
|
||||
static void mikktGetTexCoord(const SMikkTSpaceContext *pContext, float fvTexcOut[], const int iFace, const int iVert);
|
||||
static void mikktSetTSpaceDefault(const SMikkTSpaceContext *pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT,
|
||||
const tbool bIsOrientationPreserving, const int iFace, const int iVert);
|
||||
|
||||
void _update_shape();
|
||||
void _update_collision_faces();
|
||||
bool _is_debug_collision_shape_visible();
|
||||
void _update_debug_collision_shape();
|
||||
void _clear_debug_collision_shape();
|
||||
void _on_transform_changed();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
virtual CSGBrush *_build_brush() = 0;
|
||||
void _make_dirty(bool p_parent_removing = false);
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
friend class CSGCombiner3D;
|
||||
CSGBrush *_get_brush();
|
||||
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
|
||||
public:
|
||||
Array get_meshes() const;
|
||||
|
||||
void set_operation(Operation p_operation);
|
||||
Operation get_operation() const;
|
||||
|
||||
virtual Vector<Vector3> get_brush_faces();
|
||||
|
||||
virtual AABB get_aabb() const override;
|
||||
|
||||
void set_use_collision(bool p_enable);
|
||||
bool is_using_collision() const;
|
||||
|
||||
void set_collision_layer(uint32_t p_layer);
|
||||
uint32_t get_collision_layer() const;
|
||||
|
||||
void set_collision_mask(uint32_t p_mask);
|
||||
uint32_t get_collision_mask() const;
|
||||
|
||||
void set_collision_layer_value(int p_layer_number, bool p_value);
|
||||
bool get_collision_layer_value(int p_layer_number) const;
|
||||
|
||||
void set_collision_mask_value(int p_layer_number, bool p_value);
|
||||
bool get_collision_mask_value(int p_layer_number) const;
|
||||
|
||||
void set_collision_priority(real_t p_priority);
|
||||
real_t get_collision_priority() const;
|
||||
|
||||
void set_snap(float p_snap);
|
||||
float get_snap() const;
|
||||
|
||||
void set_calculate_tangents(bool p_calculate_tangents);
|
||||
bool is_calculating_tangents() const;
|
||||
|
||||
bool is_root_shape() const;
|
||||
CSGShape3D();
|
||||
~CSGShape3D();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(CSGShape3D::Operation)
|
||||
|
||||
class CSGCombiner3D : public CSGShape3D {
|
||||
GDCLASS(CSGCombiner3D, CSGShape3D);
|
||||
|
||||
private:
|
||||
virtual CSGBrush *_build_brush() override;
|
||||
|
||||
public:
|
||||
CSGCombiner3D();
|
||||
};
|
||||
|
||||
class CSGPrimitive3D : public CSGShape3D {
|
||||
GDCLASS(CSGPrimitive3D, CSGShape3D);
|
||||
|
||||
protected:
|
||||
bool flip_faces;
|
||||
CSGBrush *_create_brush_from_arrays(const Vector<Vector3> &p_vertices, const Vector<Vector2> &p_uv, const Vector<bool> &p_smooth, const Vector<Ref<Material>> &p_materials);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_flip_faces(bool p_invert);
|
||||
bool get_flip_faces();
|
||||
|
||||
CSGPrimitive3D();
|
||||
};
|
||||
|
||||
class CSGMesh3D : public CSGPrimitive3D {
|
||||
GDCLASS(CSGMesh3D, CSGPrimitive3D);
|
||||
|
||||
virtual CSGBrush *_build_brush() override;
|
||||
|
||||
Ref<Mesh> mesh;
|
||||
Ref<Material> material;
|
||||
|
||||
void _mesh_changed();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_mesh(const Ref<Mesh> &p_mesh);
|
||||
Ref<Mesh> get_mesh();
|
||||
|
||||
void set_material(const Ref<Material> &p_material);
|
||||
Ref<Material> get_material() const;
|
||||
};
|
||||
|
||||
class CSGSphere3D : public CSGPrimitive3D {
|
||||
GDCLASS(CSGSphere3D, CSGPrimitive3D);
|
||||
virtual CSGBrush *_build_brush() override;
|
||||
|
||||
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;
|
||||
|
||||
CSGSphere3D();
|
||||
};
|
||||
|
||||
class CSGBox3D : public CSGPrimitive3D {
|
||||
GDCLASS(CSGBox3D, CSGPrimitive3D);
|
||||
virtual CSGBrush *_build_brush() override;
|
||||
|
||||
Ref<Material> material;
|
||||
Vector3 size = Vector3(1, 1, 1);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
// Kept for compatibility from 3.x to 4.0.
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
#endif
|
||||
|
||||
public:
|
||||
void set_size(const Vector3 &p_size);
|
||||
Vector3 get_size() const;
|
||||
|
||||
void set_material(const Ref<Material> &p_material);
|
||||
Ref<Material> get_material() const;
|
||||
|
||||
CSGBox3D() {}
|
||||
};
|
||||
|
||||
class CSGCylinder3D : public CSGPrimitive3D {
|
||||
GDCLASS(CSGCylinder3D, CSGPrimitive3D);
|
||||
virtual CSGBrush *_build_brush() override;
|
||||
|
||||
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;
|
||||
|
||||
CSGCylinder3D();
|
||||
};
|
||||
|
||||
class CSGTorus3D : public CSGPrimitive3D {
|
||||
GDCLASS(CSGTorus3D, CSGPrimitive3D);
|
||||
virtual CSGBrush *_build_brush() override;
|
||||
|
||||
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;
|
||||
|
||||
CSGTorus3D();
|
||||
};
|
||||
|
||||
class CSGPolygon3D : public CSGPrimitive3D {
|
||||
GDCLASS(CSGPolygon3D, CSGPrimitive3D);
|
||||
|
||||
public:
|
||||
enum Mode {
|
||||
MODE_DEPTH,
|
||||
MODE_SPIN,
|
||||
MODE_PATH
|
||||
};
|
||||
|
||||
enum PathIntervalType {
|
||||
PATH_INTERVAL_DISTANCE,
|
||||
PATH_INTERVAL_SUBDIVIDE
|
||||
};
|
||||
|
||||
enum PathRotation {
|
||||
PATH_ROTATION_POLYGON,
|
||||
PATH_ROTATION_PATH,
|
||||
PATH_ROTATION_PATH_FOLLOW,
|
||||
};
|
||||
|
||||
private:
|
||||
virtual CSGBrush *_build_brush() override;
|
||||
|
||||
Vector<Vector2> polygon;
|
||||
Ref<Material> material;
|
||||
|
||||
Mode mode;
|
||||
|
||||
float depth;
|
||||
|
||||
float spin_degrees;
|
||||
int spin_sides;
|
||||
|
||||
NodePath path_node;
|
||||
PathIntervalType path_interval_type;
|
||||
float path_interval;
|
||||
float path_simplify_angle;
|
||||
PathRotation path_rotation;
|
||||
bool path_local;
|
||||
|
||||
Path3D *path = nullptr;
|
||||
|
||||
bool smooth_faces;
|
||||
bool path_continuous_u;
|
||||
real_t path_u_distance;
|
||||
bool path_joined;
|
||||
|
||||
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();
|
||||
void _validate_property(PropertyInfo &p_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_spin_sides);
|
||||
int get_spin_sides() const;
|
||||
|
||||
void set_path_node(const NodePath &p_path);
|
||||
NodePath get_path_node() const;
|
||||
|
||||
void set_path_interval_type(PathIntervalType p_interval_type);
|
||||
PathIntervalType get_path_interval_type() const;
|
||||
|
||||
void set_path_interval(float p_interval);
|
||||
float get_path_interval() const;
|
||||
|
||||
void set_path_simplify_angle(float p_angle);
|
||||
float get_path_simplify_angle() const;
|
||||
|
||||
void set_path_rotation(PathRotation p_rotation);
|
||||
PathRotation get_path_rotation() const;
|
||||
|
||||
void set_path_local(bool p_enable);
|
||||
bool is_path_local() const;
|
||||
|
||||
void set_path_continuous_u(bool p_enable);
|
||||
bool is_path_continuous_u() const;
|
||||
|
||||
void set_path_u_distance(real_t p_path_u_distance);
|
||||
real_t get_path_u_distance() const;
|
||||
|
||||
void set_path_joined(bool p_enable);
|
||||
bool is_path_joined() 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;
|
||||
|
||||
CSGPolygon3D();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(CSGPolygon3D::Mode)
|
||||
VARIANT_ENUM_CAST(CSGPolygon3D::PathRotation)
|
||||
VARIANT_ENUM_CAST(CSGPolygon3D::PathIntervalType)
|
||||
|
||||
#endif // CSG_SHAPE_H
|
||||
21
engine/modules/csg/doc_classes/CSGBox3D.xml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="CSGBox3D" inherits="CSGPrimitive3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
A CSG Box shape.
|
||||
</brief_description>
|
||||
<description>
|
||||
This node allows you to create a box for use with the CSG system.
|
||||
[b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
|
||||
</description>
|
||||
<tutorials>
|
||||
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
|
||||
</tutorials>
|
||||
<members>
|
||||
<member name="material" type="Material" setter="set_material" getter="get_material">
|
||||
The material used to render the box.
|
||||
</member>
|
||||
<member name="size" type="Vector3" setter="set_size" getter="get_size" default="Vector3(1, 1, 1)">
|
||||
The box's width, height and depth.
|
||||
</member>
|
||||
</members>
|
||||
</class>
|
||||
13
engine/modules/csg/doc_classes/CSGCombiner3D.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="CSGCombiner3D" inherits="CSGShape3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
A CSG node that allows you to combine other CSG modifiers.
|
||||
</brief_description>
|
||||
<description>
|
||||
For complex arrangements of shapes, it is sometimes needed to add structure to your CSG nodes. The CSGCombiner3D node allows you to create this structure. The node encapsulates the result of the CSG operations of its children. In this way, it is possible to do operations on one set of shapes that are children of one CSGCombiner3D node, and a set of separate operations on a second set of shapes that are children of a second CSGCombiner3D node, and then do an operation that takes the two end results as its input to create the final shape.
|
||||
[b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
|
||||
</description>
|
||||
<tutorials>
|
||||
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
|
||||
</tutorials>
|
||||
</class>
|
||||
33
engine/modules/csg/doc_classes/CSGCylinder3D.xml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="CSGCylinder3D" inherits="CSGPrimitive3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
A CSG Cylinder shape.
|
||||
</brief_description>
|
||||
<description>
|
||||
This node allows you to create a cylinder (or cone) for use with the CSG system.
|
||||
[b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
|
||||
</description>
|
||||
<tutorials>
|
||||
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
|
||||
</tutorials>
|
||||
<members>
|
||||
<member name="cone" type="bool" setter="set_cone" getter="is_cone" default="false">
|
||||
If [code]true[/code] a cone is created, the [member radius] will only apply to one side.
|
||||
</member>
|
||||
<member name="height" type="float" setter="set_height" getter="get_height" default="2.0">
|
||||
The height of the cylinder.
|
||||
</member>
|
||||
<member name="material" type="Material" setter="set_material" getter="get_material">
|
||||
The material used to render the cylinder.
|
||||
</member>
|
||||
<member name="radius" type="float" setter="set_radius" getter="get_radius" default="0.5">
|
||||
The radius of the cylinder.
|
||||
</member>
|
||||
<member name="sides" type="int" setter="set_sides" getter="get_sides" default="8">
|
||||
The number of sides of the cylinder, the higher this number the more detail there will be in the cylinder.
|
||||
</member>
|
||||
<member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces" default="true">
|
||||
If [code]true[/code] the normals of the cylinder are set to give a smooth effect making the cylinder seem rounded. If [code]false[/code] the cylinder will have a flat shaded look.
|
||||
</member>
|
||||
</members>
|
||||
</class>
|
||||
23
engine/modules/csg/doc_classes/CSGMesh3D.xml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="CSGMesh3D" inherits="CSGPrimitive3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
A CSG Mesh shape that uses a mesh resource.
|
||||
</brief_description>
|
||||
<description>
|
||||
This CSG node allows you to use any mesh resource as a CSG shape, provided it is closed, does not self-intersect, does not contain internal faces and has no edges that connect to more than two faces. See also [CSGPolygon3D] for drawing 2D extruded polygons to be used as CSG nodes.
|
||||
[b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
|
||||
</description>
|
||||
<tutorials>
|
||||
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
|
||||
</tutorials>
|
||||
<members>
|
||||
<member name="material" type="Material" setter="set_material" getter="get_material">
|
||||
The [Material] used in drawing the CSG shape.
|
||||
</member>
|
||||
<member name="mesh" type="Mesh" setter="set_mesh" getter="get_mesh">
|
||||
The [Mesh] resource to use as a CSG shape.
|
||||
[b]Note:[/b] When using an [ArrayMesh], all vertex attributes except [constant Mesh.ARRAY_VERTEX], [constant Mesh.ARRAY_NORMAL] and [constant Mesh.ARRAY_TEX_UV] are left unused. Only [constant Mesh.ARRAY_VERTEX] and [constant Mesh.ARRAY_TEX_UV] will be passed to the GPU.
|
||||
[constant Mesh.ARRAY_NORMAL] is only used to determine which faces require the use of flat shading. By default, CSGMesh will ignore the mesh's vertex normals, recalculate them for each vertex and use a smooth shader. If a flat shader is required for a face, ensure that all vertex normals of the face are approximately equal.
|
||||
</member>
|
||||
</members>
|
||||
</class>
|
||||
92
engine/modules/csg/doc_classes/CSGPolygon3D.xml
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="CSGPolygon3D" inherits="CSGPrimitive3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
Extrudes a 2D polygon shape to create a 3D mesh.
|
||||
</brief_description>
|
||||
<description>
|
||||
An array of 2D points is extruded to quickly and easily create a variety of 3D meshes. See also [CSGMesh3D] for using 3D meshes as CSG nodes.
|
||||
[b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
|
||||
</description>
|
||||
<tutorials>
|
||||
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
|
||||
</tutorials>
|
||||
<members>
|
||||
<member name="depth" type="float" setter="set_depth" getter="get_depth" default="1.0">
|
||||
When [member mode] is [constant MODE_DEPTH], the depth of the extrusion.
|
||||
</member>
|
||||
<member name="material" type="Material" setter="set_material" getter="get_material">
|
||||
Material to use for the resulting mesh. The UV maps the top half of the material to the extruded shape (U along the length of the extrusions and V around the outline of the [member polygon]), the bottom-left quarter to the front end face, and the bottom-right quarter to the back end face.
|
||||
</member>
|
||||
<member name="mode" type="int" setter="set_mode" getter="get_mode" enum="CSGPolygon3D.Mode" default="0">
|
||||
The [member mode] used to extrude the [member polygon].
|
||||
</member>
|
||||
<member name="path_continuous_u" type="bool" setter="set_path_continuous_u" getter="is_path_continuous_u">
|
||||
When [member mode] is [constant MODE_PATH], by default, the top half of the [member material] is stretched along the entire length of the extruded shape. If [code]false[/code] the top half of the material is repeated every step of the extrusion.
|
||||
</member>
|
||||
<member name="path_interval" type="float" setter="set_path_interval" getter="get_path_interval">
|
||||
When [member mode] is [constant MODE_PATH], the path interval or ratio of path points to extrusions.
|
||||
</member>
|
||||
<member name="path_interval_type" type="int" setter="set_path_interval_type" getter="get_path_interval_type" enum="CSGPolygon3D.PathIntervalType">
|
||||
When [member mode] is [constant MODE_PATH], this will determine if the interval should be by distance ([constant PATH_INTERVAL_DISTANCE]) or subdivision fractions ([constant PATH_INTERVAL_SUBDIVIDE]).
|
||||
</member>
|
||||
<member name="path_joined" type="bool" setter="set_path_joined" getter="is_path_joined">
|
||||
When [member mode] is [constant MODE_PATH], if [code]true[/code] the ends of the path are joined, by adding an extrusion between the last and first points of the path.
|
||||
</member>
|
||||
<member name="path_local" type="bool" setter="set_path_local" getter="is_path_local">
|
||||
When [member mode] is [constant MODE_PATH], if [code]true[/code] the [Transform3D] of the [CSGPolygon3D] is used as the starting point for the extrusions, not the [Transform3D] of the [member path_node].
|
||||
</member>
|
||||
<member name="path_node" type="NodePath" setter="set_path_node" getter="get_path_node">
|
||||
When [member mode] is [constant MODE_PATH], the location of the [Path3D] object used to extrude the [member polygon].
|
||||
</member>
|
||||
<member name="path_rotation" type="int" setter="set_path_rotation" getter="get_path_rotation" enum="CSGPolygon3D.PathRotation">
|
||||
When [member mode] is [constant MODE_PATH], the path rotation method used to rotate the [member polygon] as it is extruded.
|
||||
</member>
|
||||
<member name="path_simplify_angle" type="float" setter="set_path_simplify_angle" getter="get_path_simplify_angle">
|
||||
When [member mode] is [constant MODE_PATH], extrusions that are less than this angle, will be merged together to reduce polygon count.
|
||||
</member>
|
||||
<member name="path_u_distance" type="float" setter="set_path_u_distance" getter="get_path_u_distance">
|
||||
When [member mode] is [constant MODE_PATH], this is the distance along the path, in meters, the texture coordinates will tile. When set to 0, texture coordinates will match geometry exactly with no tiling.
|
||||
</member>
|
||||
<member name="polygon" type="PackedVector2Array" setter="set_polygon" getter="get_polygon" default="PackedVector2Array(0, 0, 0, 1, 1, 1, 1, 0)">
|
||||
The point array that defines the 2D polygon that is extruded. This can be a convex or concave polygon with 3 or more points. The polygon must [i]not[/i] have any intersecting edges. Otherwise, triangulation will fail and no mesh will be generated.
|
||||
[b]Note:[/b] If only 1 or 2 points are defined in [member polygon], no mesh will be generated.
|
||||
</member>
|
||||
<member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces" default="false">
|
||||
If [code]true[/code], applies smooth shading to the extrusions.
|
||||
</member>
|
||||
<member name="spin_degrees" type="float" setter="set_spin_degrees" getter="get_spin_degrees">
|
||||
When [member mode] is [constant MODE_SPIN], the total number of degrees the [member polygon] is rotated when extruding.
|
||||
</member>
|
||||
<member name="spin_sides" type="int" setter="set_spin_sides" getter="get_spin_sides">
|
||||
When [member mode] is [constant MODE_SPIN], the number of extrusions made.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
<constant name="MODE_DEPTH" value="0" enum="Mode">
|
||||
The [member polygon] shape is extruded along the negative Z axis.
|
||||
</constant>
|
||||
<constant name="MODE_SPIN" value="1" enum="Mode">
|
||||
The [member polygon] shape is extruded by rotating it around the Y axis.
|
||||
</constant>
|
||||
<constant name="MODE_PATH" value="2" enum="Mode">
|
||||
The [member polygon] shape is extruded along the [Path3D] specified in [member path_node].
|
||||
</constant>
|
||||
<constant name="PATH_ROTATION_POLYGON" value="0" enum="PathRotation">
|
||||
The [member polygon] shape is not rotated.
|
||||
[b]Note:[/b] Requires the path Z coordinates to continually decrease to ensure viable shapes.
|
||||
</constant>
|
||||
<constant name="PATH_ROTATION_PATH" value="1" enum="PathRotation">
|
||||
The [member polygon] shape is rotated along the path, but it is not rotated around the path axis.
|
||||
[b]Note:[/b] Requires the path Z coordinates to continually decrease to ensure viable shapes.
|
||||
</constant>
|
||||
<constant name="PATH_ROTATION_PATH_FOLLOW" value="2" enum="PathRotation">
|
||||
The [member polygon] shape follows the path and its rotations around the path axis.
|
||||
</constant>
|
||||
<constant name="PATH_INTERVAL_DISTANCE" value="0" enum="PathIntervalType">
|
||||
When [member mode] is set to [constant MODE_PATH], [member path_interval] will determine the distance, in meters, each interval of the path will extrude.
|
||||
</constant>
|
||||
<constant name="PATH_INTERVAL_SUBDIVIDE" value="1" enum="PathIntervalType">
|
||||
When [member mode] is set to [constant MODE_PATH], [member path_interval] will subdivide the polygons along the path.
|
||||
</constant>
|
||||
</constants>
|
||||
</class>
|
||||
18
engine/modules/csg/doc_classes/CSGPrimitive3D.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="CSGPrimitive3D" inherits="CSGShape3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
Base class for CSG primitives.
|
||||
</brief_description>
|
||||
<description>
|
||||
Parent class for various CSG primitives. It contains code and functionality that is common between them. It cannot be used directly. Instead use one of the various classes that inherit from it.
|
||||
[b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
|
||||
</description>
|
||||
<tutorials>
|
||||
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
|
||||
</tutorials>
|
||||
<members>
|
||||
<member name="flip_faces" type="bool" setter="set_flip_faces" getter="get_flip_faces" default="false">
|
||||
If set, the order of the vertices in each triangle are reversed resulting in the backside of the mesh being drawn.
|
||||
</member>
|
||||
</members>
|
||||
</class>
|
||||
93
engine/modules/csg/doc_classes/CSGShape3D.xml
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="CSGShape3D" inherits="GeometryInstance3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
The CSG base class.
|
||||
</brief_description>
|
||||
<description>
|
||||
This is the CSG base class that provides CSG operation support to the various CSG nodes in Godot.
|
||||
[b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
|
||||
</description>
|
||||
<tutorials>
|
||||
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="get_collision_layer_value" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<param index="0" name="layer_number" type="int" />
|
||||
<description>
|
||||
Returns whether or not the specified layer of the [member collision_layer] is enabled, given a [param layer_number] between 1 and 32.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_collision_mask_value" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<param index="0" name="layer_number" type="int" />
|
||||
<description>
|
||||
Returns whether or not the specified layer of the [member collision_mask] is enabled, given a [param layer_number] between 1 and 32.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_meshes" qualifiers="const">
|
||||
<return type="Array" />
|
||||
<description>
|
||||
Returns an [Array] with two elements, the first is the [Transform3D] of this node and the second is the root [Mesh] of this node. Only works when this node is the root shape.
|
||||
</description>
|
||||
</method>
|
||||
<method name="is_root_shape" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns [code]true[/code] if this is a root shape and is thus the object that is rendered.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_collision_layer_value">
|
||||
<return type="void" />
|
||||
<param index="0" name="layer_number" type="int" />
|
||||
<param index="1" name="value" type="bool" />
|
||||
<description>
|
||||
Based on [param value], enables or disables the specified layer in the [member collision_layer], given a [param layer_number] between 1 and 32.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_collision_mask_value">
|
||||
<return type="void" />
|
||||
<param index="0" name="layer_number" type="int" />
|
||||
<param index="1" name="value" type="bool" />
|
||||
<description>
|
||||
Based on [param value], enables or disables the specified layer in the [member collision_mask], given a [param layer_number] between 1 and 32.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="calculate_tangents" type="bool" setter="set_calculate_tangents" getter="is_calculating_tangents" default="true">
|
||||
Calculate tangents for the CSG shape which allows the use of normal maps. This is only applied on the root shape, this setting is ignored on any child.
|
||||
</member>
|
||||
<member name="collision_layer" type="int" setter="set_collision_layer" getter="get_collision_layer" default="1">
|
||||
The physics layers this area is in.
|
||||
Collidable objects can exist in any of 32 different layers. These layers work like a tagging system, and are not visual. A collidable can use these layers to select with which objects it can collide, using the collision_mask property.
|
||||
A contact is detected if object A is in any of the layers that object B scans, or object B is in any layer scanned by object A. See [url=$DOCS_URL/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information.
|
||||
</member>
|
||||
<member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="1">
|
||||
The physics layers this CSG shape scans for collisions. Only effective if [member use_collision] is [code]true[/code]. See [url=$DOCS_URL/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information.
|
||||
</member>
|
||||
<member name="collision_priority" type="float" setter="set_collision_priority" getter="get_collision_priority" default="1.0">
|
||||
The priority used to solve colliding when occurring penetration. Only effective if [member use_collision] is [code]true[/code]. The higher the priority is, the lower the penetration into the object will be. This can for example be used to prevent the player from breaking through the boundaries of a level.
|
||||
</member>
|
||||
<member name="operation" type="int" setter="set_operation" getter="get_operation" enum="CSGShape3D.Operation" default="0">
|
||||
The operation that is performed on this shape. This is ignored for the first CSG child node as the operation is between this node and the previous child of this nodes parent.
|
||||
</member>
|
||||
<member name="snap" type="float" setter="set_snap" getter="get_snap" default="0.001">
|
||||
Snap makes the mesh vertices snap to a given distance so that the faces of two meshes can be perfectly aligned. A lower value results in greater precision but may be harder to adjust. The top-level CSG shape's snap value is used for the entire CSG tree.
|
||||
</member>
|
||||
<member name="use_collision" type="bool" setter="set_use_collision" getter="is_using_collision" default="false">
|
||||
Adds a collision shape to the physics engine for our CSG shape. This will always act like a static body. Note that the collision shape is still active even if the CSG shape itself is hidden. See also [member collision_mask] and [member collision_priority].
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
<constant name="OPERATION_UNION" value="0" enum="Operation">
|
||||
Geometry of both primitives is merged, intersecting geometry is removed.
|
||||
</constant>
|
||||
<constant name="OPERATION_INTERSECTION" value="1" enum="Operation">
|
||||
Only intersecting geometry remains, the rest is removed.
|
||||
</constant>
|
||||
<constant name="OPERATION_SUBTRACTION" value="2" enum="Operation">
|
||||
The second shape is subtracted from the first, leaving a dent with its shape.
|
||||
</constant>
|
||||
</constants>
|
||||
</class>
|
||||
30
engine/modules/csg/doc_classes/CSGSphere3D.xml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="CSGSphere3D" inherits="CSGPrimitive3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
A CSG Sphere shape.
|
||||
</brief_description>
|
||||
<description>
|
||||
This node allows you to create a sphere for use with the CSG system.
|
||||
[b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
|
||||
</description>
|
||||
<tutorials>
|
||||
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
|
||||
</tutorials>
|
||||
<members>
|
||||
<member name="material" type="Material" setter="set_material" getter="get_material">
|
||||
The material used to render the sphere.
|
||||
</member>
|
||||
<member name="radial_segments" type="int" setter="set_radial_segments" getter="get_radial_segments" default="12">
|
||||
Number of vertical slices for the sphere.
|
||||
</member>
|
||||
<member name="radius" type="float" setter="set_radius" getter="get_radius" default="0.5">
|
||||
Radius of the sphere.
|
||||
</member>
|
||||
<member name="rings" type="int" setter="set_rings" getter="get_rings" default="6">
|
||||
Number of horizontal slices for the sphere.
|
||||
</member>
|
||||
<member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces" default="true">
|
||||
If [code]true[/code] the normals of the sphere are set to give a smooth effect making the sphere seem rounded. If [code]false[/code] the sphere will have a flat shaded look.
|
||||
</member>
|
||||
</members>
|
||||
</class>
|
||||
33
engine/modules/csg/doc_classes/CSGTorus3D.xml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="CSGTorus3D" inherits="CSGPrimitive3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
A CSG Torus shape.
|
||||
</brief_description>
|
||||
<description>
|
||||
This node allows you to create a torus for use with the CSG system.
|
||||
[b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
|
||||
</description>
|
||||
<tutorials>
|
||||
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
|
||||
</tutorials>
|
||||
<members>
|
||||
<member name="inner_radius" type="float" setter="set_inner_radius" getter="get_inner_radius" default="0.5">
|
||||
The inner radius of the torus.
|
||||
</member>
|
||||
<member name="material" type="Material" setter="set_material" getter="get_material">
|
||||
The material used to render the torus.
|
||||
</member>
|
||||
<member name="outer_radius" type="float" setter="set_outer_radius" getter="get_outer_radius" default="1.0">
|
||||
The outer radius of the torus.
|
||||
</member>
|
||||
<member name="ring_sides" type="int" setter="set_ring_sides" getter="get_ring_sides" default="6">
|
||||
The number of edges each ring of the torus is constructed of.
|
||||
</member>
|
||||
<member name="sides" type="int" setter="set_sides" getter="get_sides" default="8">
|
||||
The number of slices the torus is constructed of.
|
||||
</member>
|
||||
<member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces" default="true">
|
||||
If [code]true[/code] the normals of the torus are set to give a smooth effect making the torus seem rounded. If [code]false[/code] the torus will have a flat shaded look.
|
||||
</member>
|
||||
</members>
|
||||
</class>
|
||||
401
engine/modules/csg/editor/csg_gizmos.cpp
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
/**************************************************************************/
|
||||
/* csg_gizmos.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/editor_undo_redo_manager.h"
|
||||
#include "editor/plugins/gizmos/gizmo_3d_helper.h"
|
||||
#include "editor/plugins/node_3d_editor_plugin.h"
|
||||
#include "scene/3d/camera_3d.h"
|
||||
|
||||
///////////
|
||||
|
||||
CSGShape3DGizmoPlugin::CSGShape3DGizmoPlugin() {
|
||||
helper.instantiate();
|
||||
|
||||
Color gizmo_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/csg", Color(0.0, 0.4, 1, 0.15));
|
||||
create_material("shape_union_material", gizmo_color);
|
||||
create_material("shape_union_solid_material", gizmo_color);
|
||||
gizmo_color.invert();
|
||||
create_material("shape_subtraction_material", gizmo_color);
|
||||
create_material("shape_subtraction_solid_material", gizmo_color);
|
||||
gizmo_color.r = 0.95;
|
||||
gizmo_color.g = 0.95;
|
||||
gizmo_color.b = 0.95;
|
||||
create_material("shape_intersection_material", gizmo_color);
|
||||
create_material("shape_intersection_solid_material", gizmo_color);
|
||||
|
||||
create_handle_material("handles");
|
||||
}
|
||||
|
||||
CSGShape3DGizmoPlugin::~CSGShape3DGizmoPlugin() {
|
||||
}
|
||||
|
||||
String CSGShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
|
||||
CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
|
||||
|
||||
if (Object::cast_to<CSGSphere3D>(cs)) {
|
||||
return "Radius";
|
||||
}
|
||||
|
||||
if (Object::cast_to<CSGBox3D>(cs)) {
|
||||
return helper->box_get_handle_name(p_id);
|
||||
}
|
||||
|
||||
if (Object::cast_to<CSGCylinder3D>(cs)) {
|
||||
return p_id == 0 ? "Radius" : "Height";
|
||||
}
|
||||
|
||||
if (Object::cast_to<CSGTorus3D>(cs)) {
|
||||
return p_id == 0 ? "InnerRadius" : "OuterRadius";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
Variant CSGShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
|
||||
CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
|
||||
|
||||
if (Object::cast_to<CSGSphere3D>(cs)) {
|
||||
CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs);
|
||||
return s->get_radius();
|
||||
}
|
||||
|
||||
if (Object::cast_to<CSGBox3D>(cs)) {
|
||||
CSGBox3D *s = Object::cast_to<CSGBox3D>(cs);
|
||||
return s->get_size();
|
||||
}
|
||||
|
||||
if (Object::cast_to<CSGCylinder3D>(cs)) {
|
||||
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
|
||||
return p_id == 0 ? s->get_radius() : s->get_height();
|
||||
}
|
||||
|
||||
if (Object::cast_to<CSGTorus3D>(cs)) {
|
||||
CSGTorus3D *s = Object::cast_to<CSGTorus3D>(cs);
|
||||
return p_id == 0 ? s->get_inner_radius() : s->get_outer_radius();
|
||||
}
|
||||
|
||||
return Variant();
|
||||
}
|
||||
|
||||
void CSGShape3DGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) {
|
||||
helper->initialize_handle_action(get_handle_value(p_gizmo, p_id, p_secondary), p_gizmo->get_node_3d()->get_global_transform());
|
||||
}
|
||||
|
||||
void CSGShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
|
||||
CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
|
||||
|
||||
Vector3 sg[2];
|
||||
helper->get_segment(p_camera, p_point, sg);
|
||||
|
||||
if (Object::cast_to<CSGSphere3D>(cs)) {
|
||||
CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs);
|
||||
|
||||
Vector3 ra, rb;
|
||||
Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb);
|
||||
float d = ra.x;
|
||||
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
||||
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
||||
}
|
||||
|
||||
if (d < 0.001) {
|
||||
d = 0.001;
|
||||
}
|
||||
|
||||
s->set_radius(d);
|
||||
}
|
||||
|
||||
if (Object::cast_to<CSGBox3D>(cs)) {
|
||||
CSGBox3D *s = Object::cast_to<CSGBox3D>(cs);
|
||||
Vector3 size = s->get_size();
|
||||
Vector3 position;
|
||||
helper->box_set_handle(sg, p_id, size, position);
|
||||
s->set_size(size);
|
||||
s->set_global_position(position);
|
||||
}
|
||||
|
||||
if (Object::cast_to<CSGCylinder3D>(cs)) {
|
||||
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
|
||||
|
||||
Vector3 axis;
|
||||
axis[p_id == 0 ? 0 : 1] = 1.0;
|
||||
Vector3 ra, rb;
|
||||
Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
|
||||
float d = axis.dot(ra);
|
||||
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
||||
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
||||
}
|
||||
|
||||
if (d < 0.001) {
|
||||
d = 0.001;
|
||||
}
|
||||
|
||||
if (p_id == 0) {
|
||||
s->set_radius(d);
|
||||
} else if (p_id == 1) {
|
||||
s->set_height(d * 2.0);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object::cast_to<CSGTorus3D>(cs)) {
|
||||
CSGTorus3D *s = Object::cast_to<CSGTorus3D>(cs);
|
||||
|
||||
Vector3 axis;
|
||||
axis[0] = 1.0;
|
||||
Vector3 ra, rb;
|
||||
Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
|
||||
float d = axis.dot(ra);
|
||||
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
||||
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
||||
}
|
||||
|
||||
if (d < 0.001) {
|
||||
d = 0.001;
|
||||
}
|
||||
|
||||
if (p_id == 0) {
|
||||
s->set_inner_radius(d);
|
||||
} else if (p_id == 1) {
|
||||
s->set_outer_radius(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CSGShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) {
|
||||
CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
|
||||
|
||||
if (Object::cast_to<CSGSphere3D>(cs)) {
|
||||
CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs);
|
||||
if (p_cancel) {
|
||||
s->set_radius(p_restore);
|
||||
return;
|
||||
}
|
||||
|
||||
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
|
||||
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<CSGBox3D>(cs)) {
|
||||
helper->box_commit_handle(TTR("Change Box Shape Size"), p_cancel, cs);
|
||||
}
|
||||
|
||||
if (Object::cast_to<CSGCylinder3D>(cs)) {
|
||||
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
|
||||
if (p_cancel) {
|
||||
if (p_id == 0) {
|
||||
s->set_radius(p_restore);
|
||||
} else {
|
||||
s->set_height(p_restore);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
|
||||
if (p_id == 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<CSGTorus3D>(cs)) {
|
||||
CSGTorus3D *s = Object::cast_to<CSGTorus3D>(cs);
|
||||
if (p_cancel) {
|
||||
if (p_id == 0) {
|
||||
s->set_inner_radius(p_restore);
|
||||
} else {
|
||||
s->set_outer_radius(p_restore);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
|
||||
if (p_id == 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();
|
||||
}
|
||||
}
|
||||
|
||||
bool CSGShape3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
||||
return Object::cast_to<CSGSphere3D>(p_spatial) || Object::cast_to<CSGBox3D>(p_spatial) || Object::cast_to<CSGCylinder3D>(p_spatial) || Object::cast_to<CSGTorus3D>(p_spatial) || Object::cast_to<CSGMesh3D>(p_spatial) || Object::cast_to<CSGPolygon3D>(p_spatial);
|
||||
}
|
||||
|
||||
String CSGShape3DGizmoPlugin::get_gizmo_name() const {
|
||||
return "CSGShape3D";
|
||||
}
|
||||
|
||||
int CSGShape3DGizmoPlugin::get_priority() const {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool CSGShape3DGizmoPlugin::is_selectable_when_hidden() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
||||
p_gizmo->clear();
|
||||
|
||||
CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
|
||||
|
||||
Vector<Vector3> faces = cs->get_brush_faces();
|
||||
|
||||
if (faces.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<Vector3> lines;
|
||||
lines.resize(faces.size() * 2);
|
||||
{
|
||||
const Vector3 *r = faces.ptr();
|
||||
|
||||
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.write[i + j * 2 + 0] = r[f * 3 + j];
|
||||
lines.write[i + j * 2 + 1] = r[f * 3 + j_n];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Material> material;
|
||||
switch (cs->get_operation()) {
|
||||
case CSGShape3D::OPERATION_UNION:
|
||||
material = get_material("shape_union_material", p_gizmo);
|
||||
break;
|
||||
case CSGShape3D::OPERATION_INTERSECTION:
|
||||
material = get_material("shape_intersection_material", p_gizmo);
|
||||
break;
|
||||
case CSGShape3D::OPERATION_SUBTRACTION:
|
||||
material = get_material("shape_subtraction_material", p_gizmo);
|
||||
break;
|
||||
}
|
||||
|
||||
Ref<Material> handles_material = get_material("handles");
|
||||
|
||||
p_gizmo->add_lines(lines, material);
|
||||
p_gizmo->add_collision_segments(lines);
|
||||
|
||||
if (cs->is_root_shape()) {
|
||||
Array csg_meshes = cs->get_meshes();
|
||||
if (csg_meshes.size() == 2) {
|
||||
Ref<Mesh> csg_mesh = csg_meshes[1];
|
||||
if (csg_mesh.is_valid()) {
|
||||
p_gizmo->add_collision_triangles(csg_mesh->generate_triangle_mesh());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (p_gizmo->is_selected()) {
|
||||
// Draw a translucent representation of the CSG node
|
||||
Ref<ArrayMesh> mesh = memnew(ArrayMesh);
|
||||
Array array;
|
||||
array.resize(Mesh::ARRAY_MAX);
|
||||
array[Mesh::ARRAY_VERTEX] = faces;
|
||||
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array);
|
||||
|
||||
Ref<Material> solid_material;
|
||||
switch (cs->get_operation()) {
|
||||
case CSGShape3D::OPERATION_UNION:
|
||||
solid_material = get_material("shape_union_solid_material", p_gizmo);
|
||||
break;
|
||||
case CSGShape3D::OPERATION_INTERSECTION:
|
||||
solid_material = get_material("shape_intersection_solid_material", p_gizmo);
|
||||
break;
|
||||
case CSGShape3D::OPERATION_SUBTRACTION:
|
||||
solid_material = get_material("shape_subtraction_solid_material", p_gizmo);
|
||||
break;
|
||||
}
|
||||
|
||||
p_gizmo->add_mesh(mesh, solid_material);
|
||||
}
|
||||
|
||||
if (Object::cast_to<CSGSphere3D>(cs)) {
|
||||
CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs);
|
||||
|
||||
float r = s->get_radius();
|
||||
Vector<Vector3> handles;
|
||||
handles.push_back(Vector3(r, 0, 0));
|
||||
p_gizmo->add_handles(handles, handles_material);
|
||||
}
|
||||
|
||||
if (Object::cast_to<CSGBox3D>(cs)) {
|
||||
CSGBox3D *s = Object::cast_to<CSGBox3D>(cs);
|
||||
Vector<Vector3> handles = helper->box_get_handles(s->get_size());
|
||||
p_gizmo->add_handles(handles, handles_material);
|
||||
}
|
||||
|
||||
if (Object::cast_to<CSGCylinder3D>(cs)) {
|
||||
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
|
||||
|
||||
Vector<Vector3> handles;
|
||||
handles.push_back(Vector3(s->get_radius(), 0, 0));
|
||||
handles.push_back(Vector3(0, s->get_height() * 0.5, 0));
|
||||
p_gizmo->add_handles(handles, handles_material);
|
||||
}
|
||||
|
||||
if (Object::cast_to<CSGTorus3D>(cs)) {
|
||||
CSGTorus3D *s = Object::cast_to<CSGTorus3D>(cs);
|
||||
|
||||
Vector<Vector3> handles;
|
||||
handles.push_back(Vector3(s->get_inner_radius(), 0, 0));
|
||||
handles.push_back(Vector3(s->get_outer_radius(), 0, 0));
|
||||
p_gizmo->add_handles(handles, handles_material);
|
||||
}
|
||||
}
|
||||
|
||||
EditorPluginCSG::EditorPluginCSG() {
|
||||
Ref<CSGShape3DGizmoPlugin> gizmo_plugin = Ref<CSGShape3DGizmoPlugin>(memnew(CSGShape3DGizmoPlugin));
|
||||
Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin);
|
||||
}
|
||||
|
||||
#endif // TOOLS_ENABLED
|
||||
74
engine/modules/csg/editor/csg_gizmos.h
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/**************************************************************************/
|
||||
/* csg_gizmos.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
#include "../csg_shape.h"
|
||||
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
#include "editor/plugins/node_3d_editor_gizmos.h"
|
||||
|
||||
class Gizmo3DHelper;
|
||||
|
||||
class CSGShape3DGizmoPlugin : public EditorNode3DGizmoPlugin {
|
||||
GDCLASS(CSGShape3DGizmoPlugin, EditorNode3DGizmoPlugin);
|
||||
|
||||
Ref<Gizmo3DHelper> helper;
|
||||
|
||||
public:
|
||||
virtual bool has_gizmo(Node3D *p_spatial) override;
|
||||
virtual String get_gizmo_name() const override;
|
||||
virtual int get_priority() const override;
|
||||
virtual bool is_selectable_when_hidden() const override;
|
||||
virtual void redraw(EditorNode3DGizmo *p_gizmo) override;
|
||||
|
||||
virtual String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override;
|
||||
virtual Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override;
|
||||
void begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) override;
|
||||
virtual void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override;
|
||||
virtual void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) override;
|
||||
|
||||
CSGShape3DGizmoPlugin();
|
||||
~CSGShape3DGizmoPlugin();
|
||||
};
|
||||
|
||||
class EditorPluginCSG : public EditorPlugin {
|
||||
GDCLASS(EditorPluginCSG, EditorPlugin);
|
||||
|
||||
public:
|
||||
EditorPluginCSG();
|
||||
};
|
||||
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
#endif // CSG_GIZMOS_H
|
||||
1
engine/modules/csg/icons/CSGBox3D.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><mask id="a"><path fill="#fefefe" d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z"/></mask><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><path fill="none" stroke="#fc7f7f" stroke-width="2" d="m8 2 6 3v6l-6 3-6-3V5zm0 12V8l6-3M8 8 2 5" mask="url(#a)"/></svg>
|
||||
|
After Width: | Height: | Size: 450 B |
1
engine/modules/csg/icons/CSGCapsule3D.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><mask id="a"><path fill="#fefefe" d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z"/></mask><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><path fill="none" stroke="#fc7f7f" stroke-width="2" d="M4 6a4 4 0 0 1 8 0v4a4 4 0 0 1-8 0zm0 1.25a2.5 1 0 0 0 8 0m-4-5v12" mask="url(#a)"/></svg>
|
||||
|
After Width: | Height: | Size: 475 B |
1
engine/modules/csg/icons/CSGCombiner3D.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><path fill="#fc7f7f" d="M3 1a2 2 0 0 0-2 2h2zm2 0v2h2V1zm4 0v2h2V1zm4 0v2h2a2 2 0 0 0-2-2zM1 5v2h2V5zm12 0v2h2V5zM1 9v2h2V9zm0 4a2 2 0 0 0 2 2v-2zm4 0v2h2v-2z"/></svg>
|
||||
|
After Width: | Height: | Size: 376 B |
1
engine/modules/csg/icons/CSGCylinder3D.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><mask id="a"><path fill="#fefefe" d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z"/></mask><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><path fill="none" stroke="#fc7f7f" stroke-width="2" d="M2 4v8a6 2 0 0 0 12 0V4A6 2 0 0 0 2 4a6 2 0 0 0 12 0" mask="url(#a)"/></svg>
|
||||
|
After Width: | Height: | Size: 461 B |
1
engine/modules/csg/icons/CSGMesh3D.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#fc7f7f" d="M4.73 2A2 2 0 1 0 2 4.73v6.541A2 2 0 1 0 4.729 14H8v-2H4.729A2 2 0 0 0 4 11.271V5.415l4.914 4.916A2 2 0 0 1 9.998 10a2 2 0 0 1 .33-1.084L5.414 4h5.856a2 2 0 0 0 .73.729V8h2V4.729A2 2 0 1 0 11.27 2z"/><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/></svg>
|
||||
|
After Width: | Height: | Size: 439 B |
1
engine/modules/csg/icons/CSGPolygon3D.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><mask id="a"><path fill="#fefefe" d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z"/></mask><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><path fill="none" stroke="#fc7f7f" stroke-linejoin="round" stroke-width="2" d="m8 2 6 3.5v5L8 14l-6-3.5v-5h6zm6 3.5L8 9 2 5.5M8 9v5" mask="url(#a)"/></svg>
|
||||
|
After Width: | Height: | Size: 485 B |
1
engine/modules/csg/icons/CSGSphere3D.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><mask id="a"><path fill="#fefefe" d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z"/></mask><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><path fill="none" stroke="#fc7f7f" stroke-width="2" d="M8 2a6 6 0 0 0 0 12A6 6 0 0 0 8 2v12M2.05 7.4a6 2 0 0 0 11.9 0" mask="url(#a)"/></svg>
|
||||
|
After Width: | Height: | Size: 471 B |
1
engine/modules/csg/icons/CSGTorus3D.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><mask id="a"><path fill="#fefefe" d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z"/></mask><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><g fill="none" stroke="#fc7f7f" mask="url(#a)"><path stroke-width="2" d="M2.5 10a6 4 0 0 0 11 0 4 4 0 0 0 0-4 6 4 0 0 0-11 0 4 4 0 0 0 0 4z"/><path stroke-linecap="round" stroke-width="1.75" d="M6.2 7.2a2 1 0 1 0 3.6 0"/></g></svg>
|
||||
|
After Width: | Height: | Size: 561 B |
66
engine/modules/csg/register_types.cpp
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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"
|
||||
|
||||
#ifndef _3D_DISABLED
|
||||
|
||||
#include "csg_shape.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
#include "editor/csg_gizmos.h"
|
||||
#endif
|
||||
|
||||
void initialize_csg_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
GDREGISTER_ABSTRACT_CLASS(CSGShape3D);
|
||||
GDREGISTER_ABSTRACT_CLASS(CSGPrimitive3D);
|
||||
GDREGISTER_CLASS(CSGMesh3D);
|
||||
GDREGISTER_CLASS(CSGSphere3D);
|
||||
GDREGISTER_CLASS(CSGBox3D);
|
||||
GDREGISTER_CLASS(CSGCylinder3D);
|
||||
GDREGISTER_CLASS(CSGTorus3D);
|
||||
GDREGISTER_CLASS(CSGPolygon3D);
|
||||
GDREGISTER_CLASS(CSGCombiner3D);
|
||||
}
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {
|
||||
EditorPlugins::add_by_type<EditorPluginCSG>();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void uninitialize_csg_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // _3D_DISABLED
|
||||
39
engine/modules/csg/register_types.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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_REGISTER_TYPES_H
|
||||
#define CSG_REGISTER_TYPES_H
|
||||
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
void initialize_csg_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_csg_module(ModuleInitializationLevel p_level);
|
||||
|
||||
#endif // CSG_REGISTER_TYPES_H
|
||||
43
engine/modules/cvtt/SCsub
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
||||
env_cvtt = env_modules.Clone()
|
||||
|
||||
# Thirdparty source files
|
||||
|
||||
thirdparty_obj = []
|
||||
|
||||
thirdparty_dir = "#thirdparty/cvtt/"
|
||||
thirdparty_sources = [
|
||||
"ConvectionKernels_API.cpp",
|
||||
"ConvectionKernels_ETC.cpp",
|
||||
"ConvectionKernels_BC67.cpp",
|
||||
"ConvectionKernels_IndexSelector.cpp",
|
||||
"ConvectionKernels_BC6H_IO.cpp",
|
||||
"ConvectionKernels_S3TC.cpp",
|
||||
"ConvectionKernels_BC7_PrioData.cpp",
|
||||
"ConvectionKernels_SingleFile.cpp",
|
||||
"ConvectionKernels_BCCommon.cpp",
|
||||
"ConvectionKernels_Util.cpp",
|
||||
]
|
||||
|
||||
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
|
||||
|
||||
env_cvtt.Prepend(CPPPATH=[thirdparty_dir])
|
||||
|
||||
env_thirdparty = env_cvtt.Clone()
|
||||
env_thirdparty.disable_warnings()
|
||||
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
|
||||
env.modules_sources += thirdparty_obj
|
||||
|
||||
# Godot source files
|
||||
|
||||
module_obj = []
|
||||
|
||||
env_cvtt.add_source_files(module_obj, "*.cpp")
|
||||
env.modules_sources += module_obj
|
||||
|
||||
# Needed to force rebuilding the module files when the thirdparty library is updated.
|
||||
env.Depends(module_obj, thirdparty_obj)
|
||||
6
engine/modules/cvtt/config.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
def can_build(env, platform):
|
||||
return env.editor_build
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
||||
362
engine/modules/cvtt/image_compress_cvtt.cpp
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
/**************************************************************************/
|
||||
/* image_compress_cvtt.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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_compress_cvtt.h"
|
||||
|
||||
#include "core/object/worker_thread_pool.h"
|
||||
#include "core/os/os.h"
|
||||
#include "core/string/print_string.h"
|
||||
#include "core/templates/safe_refcount.h"
|
||||
|
||||
#include <ConvectionKernels.h>
|
||||
|
||||
struct CVTTCompressionJobParams {
|
||||
bool is_hdr = false;
|
||||
bool is_signed = false;
|
||||
int bytes_per_pixel = 0;
|
||||
cvtt::BC7EncodingPlan bc7_plan;
|
||||
cvtt::Options options;
|
||||
};
|
||||
|
||||
struct CVTTCompressionRowTask {
|
||||
const uint8_t *in_mm_bytes = nullptr;
|
||||
uint8_t *out_mm_bytes = nullptr;
|
||||
int y_start = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
};
|
||||
|
||||
struct CVTTCompressionJobQueue {
|
||||
CVTTCompressionJobParams job_params;
|
||||
const CVTTCompressionRowTask *job_tasks = nullptr;
|
||||
uint32_t num_tasks = 0;
|
||||
SafeNumeric<uint32_t> current_task;
|
||||
};
|
||||
|
||||
static void _digest_row_task(const CVTTCompressionJobParams &p_job_params, const CVTTCompressionRowTask &p_row_task) {
|
||||
const uint8_t *in_bytes = p_row_task.in_mm_bytes;
|
||||
uint8_t *out_bytes = p_row_task.out_mm_bytes;
|
||||
int w = p_row_task.width;
|
||||
int h = p_row_task.height;
|
||||
|
||||
int y_start = p_row_task.y_start;
|
||||
int y_end = y_start + 4;
|
||||
|
||||
int bytes_per_pixel = p_job_params.bytes_per_pixel;
|
||||
bool is_hdr = p_job_params.is_hdr;
|
||||
bool is_signed = p_job_params.is_signed;
|
||||
|
||||
cvtt::PixelBlockU8 input_blocks_ldr[cvtt::NumParallelBlocks];
|
||||
cvtt::PixelBlockF16 input_blocks_hdr[cvtt::NumParallelBlocks];
|
||||
|
||||
for (int x_start = 0; x_start < w; x_start += 4 * cvtt::NumParallelBlocks) {
|
||||
int x_end = x_start + 4 * cvtt::NumParallelBlocks;
|
||||
|
||||
for (int y = y_start; y < y_end; y++) {
|
||||
int first_input_element = (y - y_start) * 4;
|
||||
const uint8_t *row_start;
|
||||
if (y >= h) {
|
||||
row_start = in_bytes + (h - 1) * (w * bytes_per_pixel);
|
||||
} else {
|
||||
row_start = in_bytes + y * (w * bytes_per_pixel);
|
||||
}
|
||||
|
||||
for (int x = x_start; x < x_end; x++) {
|
||||
const uint8_t *pixel_start;
|
||||
if (x >= w) {
|
||||
pixel_start = row_start + (w - 1) * bytes_per_pixel;
|
||||
} else {
|
||||
pixel_start = row_start + x * bytes_per_pixel;
|
||||
}
|
||||
|
||||
int block_index = (x - x_start) / 4;
|
||||
int block_element = (x - x_start) % 4 + first_input_element;
|
||||
if (is_hdr) {
|
||||
memcpy(input_blocks_hdr[block_index].m_pixels[block_element], pixel_start, bytes_per_pixel);
|
||||
input_blocks_hdr[block_index].m_pixels[block_element][3] = 0x3c00; // 1.0 (unused)
|
||||
} else {
|
||||
memcpy(input_blocks_ldr[block_index].m_pixels[block_element], pixel_start, bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t output_blocks[16 * cvtt::NumParallelBlocks];
|
||||
|
||||
if (is_hdr) {
|
||||
if (is_signed) {
|
||||
cvtt::Kernels::EncodeBC6HS(output_blocks, input_blocks_hdr, p_job_params.options);
|
||||
} else {
|
||||
cvtt::Kernels::EncodeBC6HU(output_blocks, input_blocks_hdr, p_job_params.options);
|
||||
}
|
||||
} else {
|
||||
cvtt::Kernels::EncodeBC7(output_blocks, input_blocks_ldr, p_job_params.options, p_job_params.bc7_plan);
|
||||
}
|
||||
|
||||
unsigned int num_real_blocks = ((w - x_start) + 3) / 4;
|
||||
if (num_real_blocks > cvtt::NumParallelBlocks) {
|
||||
num_real_blocks = cvtt::NumParallelBlocks;
|
||||
}
|
||||
|
||||
memcpy(out_bytes, output_blocks, 16 * num_real_blocks);
|
||||
out_bytes += 16 * num_real_blocks;
|
||||
}
|
||||
}
|
||||
|
||||
static void _digest_job_queue(void *p_job_queue, uint32_t p_index) {
|
||||
CVTTCompressionJobQueue *job_queue = static_cast<CVTTCompressionJobQueue *>(p_job_queue);
|
||||
uint32_t num_tasks = job_queue->num_tasks;
|
||||
uint32_t total_threads = WorkerThreadPool::get_singleton()->get_thread_count();
|
||||
uint32_t start = p_index * num_tasks / total_threads;
|
||||
uint32_t end = (p_index + 1 == total_threads) ? num_tasks : ((p_index + 1) * num_tasks / total_threads);
|
||||
|
||||
for (uint32_t i = start; i < end; i++) {
|
||||
_digest_row_task(job_queue->job_params, job_queue->job_tasks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) {
|
||||
if (p_image->is_compressed()) {
|
||||
return; //do not compress, already compressed
|
||||
}
|
||||
int w = p_image->get_width();
|
||||
int h = p_image->get_height();
|
||||
|
||||
bool is_ldr = (p_image->get_format() <= Image::FORMAT_RGBA8);
|
||||
bool is_hdr = (p_image->get_format() >= Image::FORMAT_RH) && (p_image->get_format() <= Image::FORMAT_RGBE9995);
|
||||
|
||||
if (!is_ldr && !is_hdr) {
|
||||
return; // Not a usable source format
|
||||
}
|
||||
|
||||
cvtt::Options options;
|
||||
uint32_t flags = cvtt::Flags::Default;
|
||||
flags |= cvtt::Flags::BC7_RespectPunchThrough;
|
||||
if (p_channels == Image::USED_CHANNELS_RG) { //guessing this is a normal map
|
||||
flags |= cvtt::Flags::Uniform;
|
||||
}
|
||||
options.flags = flags;
|
||||
|
||||
Image::Format target_format = Image::FORMAT_BPTC_RGBA;
|
||||
|
||||
bool is_signed = false;
|
||||
if (is_hdr) {
|
||||
if (p_image->get_format() != Image::FORMAT_RGBH) {
|
||||
p_image->convert(Image::FORMAT_RGBH);
|
||||
}
|
||||
|
||||
const uint8_t *rb = p_image->get_data().ptr();
|
||||
|
||||
const uint16_t *source_data = reinterpret_cast<const uint16_t *>(&rb[0]);
|
||||
int pixel_element_count = w * h * 3;
|
||||
for (int i = 0; i < pixel_element_count; i++) {
|
||||
if ((source_data[i] & 0x8000) != 0 && (source_data[i] & 0x7fff) != 0) {
|
||||
is_signed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
target_format = is_signed ? Image::FORMAT_BPTC_RGBF : Image::FORMAT_BPTC_RGBFU;
|
||||
} else {
|
||||
p_image->convert(Image::FORMAT_RGBA8); //still uses RGBA to convert
|
||||
}
|
||||
|
||||
const uint8_t *rb = p_image->get_data().ptr();
|
||||
|
||||
Vector<uint8_t> data;
|
||||
int64_t target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps());
|
||||
int mm_count = p_image->has_mipmaps() ? Image::get_image_required_mipmaps(w, h, target_format) : 0;
|
||||
data.resize(target_size);
|
||||
int shift = Image::get_format_pixel_rshift(target_format);
|
||||
|
||||
uint8_t *wb = data.ptrw();
|
||||
|
||||
int64_t dst_ofs = 0;
|
||||
|
||||
CVTTCompressionJobQueue job_queue;
|
||||
job_queue.job_params.is_hdr = is_hdr;
|
||||
job_queue.job_params.is_signed = is_signed;
|
||||
job_queue.job_params.options = options;
|
||||
job_queue.job_params.bytes_per_pixel = is_hdr ? 6 : 4;
|
||||
cvtt::Kernels::ConfigureBC7EncodingPlanFromQuality(job_queue.job_params.bc7_plan, 5);
|
||||
|
||||
// Amdahl's law (Wikipedia)
|
||||
// If a program needs 20 hours to complete using a single thread, but a one-hour portion of the program cannot be parallelized,
|
||||
// therefore only the remaining 19 hours (p = 0.95) of execution time can be parallelized, then regardless of how many threads are devoted
|
||||
// to a parallelized execution of this program, the minimum execution time cannot be less than one hour.
|
||||
//
|
||||
// The number of executions with different inputs can be increased while the latency is the same.
|
||||
|
||||
Vector<CVTTCompressionRowTask> tasks;
|
||||
|
||||
for (int i = 0; i <= mm_count; i++) {
|
||||
int bw = w % 4 != 0 ? w + (4 - w % 4) : w;
|
||||
int bh = h % 4 != 0 ? h + (4 - h % 4) : h;
|
||||
|
||||
int64_t src_ofs = p_image->get_mipmap_offset(i);
|
||||
|
||||
const uint8_t *in_bytes = &rb[src_ofs];
|
||||
uint8_t *out_bytes = &wb[dst_ofs];
|
||||
|
||||
for (int y_start = 0; y_start < h; y_start += 4) {
|
||||
CVTTCompressionRowTask row_task;
|
||||
row_task.width = w;
|
||||
row_task.height = h;
|
||||
row_task.y_start = y_start;
|
||||
row_task.in_mm_bytes = in_bytes;
|
||||
row_task.out_mm_bytes = out_bytes;
|
||||
|
||||
tasks.push_back(row_task);
|
||||
|
||||
out_bytes += 16 * (bw / 4);
|
||||
}
|
||||
|
||||
dst_ofs += (MAX(4, bw) * MAX(4, bh)) >> shift;
|
||||
w = MAX(w / 2, 1);
|
||||
h = MAX(h / 2, 1);
|
||||
}
|
||||
|
||||
const CVTTCompressionRowTask *tasks_rb = tasks.ptr();
|
||||
|
||||
job_queue.job_tasks = &tasks_rb[0];
|
||||
job_queue.num_tasks = static_cast<uint32_t>(tasks.size());
|
||||
WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_native_group_task(&_digest_job_queue, &job_queue, WorkerThreadPool::get_singleton()->get_thread_count(), -1, true, SNAME("CVTT Compress"));
|
||||
WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
|
||||
|
||||
p_image->set_data(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data);
|
||||
}
|
||||
|
||||
void image_decompress_cvtt(Image *p_image) {
|
||||
Image::Format target_format;
|
||||
bool is_signed = false;
|
||||
bool is_hdr = false;
|
||||
|
||||
Image::Format input_format = p_image->get_format();
|
||||
|
||||
switch (input_format) {
|
||||
case Image::FORMAT_BPTC_RGBA:
|
||||
target_format = Image::FORMAT_RGBA8;
|
||||
break;
|
||||
case Image::FORMAT_BPTC_RGBF:
|
||||
case Image::FORMAT_BPTC_RGBFU:
|
||||
target_format = Image::FORMAT_RGBH;
|
||||
is_signed = (input_format == Image::FORMAT_BPTC_RGBF);
|
||||
is_hdr = true;
|
||||
break;
|
||||
default:
|
||||
return; // Invalid input format
|
||||
};
|
||||
|
||||
int w = p_image->get_width();
|
||||
int h = p_image->get_height();
|
||||
|
||||
const uint8_t *rb = p_image->get_data().ptr();
|
||||
|
||||
Vector<uint8_t> data;
|
||||
int64_t target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps());
|
||||
int mm_count = p_image->get_mipmap_count();
|
||||
data.resize(target_size);
|
||||
|
||||
uint8_t *wb = data.ptrw();
|
||||
|
||||
int bytes_per_pixel = is_hdr ? 6 : 4;
|
||||
|
||||
int64_t dst_ofs = 0;
|
||||
|
||||
for (int i = 0; i <= mm_count; i++) {
|
||||
int64_t src_ofs = p_image->get_mipmap_offset(i);
|
||||
|
||||
const uint8_t *in_bytes = &rb[src_ofs];
|
||||
uint8_t *out_bytes = &wb[dst_ofs];
|
||||
|
||||
cvtt::PixelBlockU8 output_blocks_ldr[cvtt::NumParallelBlocks];
|
||||
cvtt::PixelBlockF16 output_blocks_hdr[cvtt::NumParallelBlocks];
|
||||
|
||||
for (int y_start = 0; y_start < h; y_start += 4) {
|
||||
int y_end = y_start + 4;
|
||||
|
||||
for (int x_start = 0; x_start < w; x_start += 4 * cvtt::NumParallelBlocks) {
|
||||
uint8_t input_blocks[16 * cvtt::NumParallelBlocks];
|
||||
memset(input_blocks, 0, sizeof(input_blocks));
|
||||
|
||||
unsigned int num_real_blocks = ((w - x_start) + 3) / 4;
|
||||
if (num_real_blocks > cvtt::NumParallelBlocks) {
|
||||
num_real_blocks = cvtt::NumParallelBlocks;
|
||||
}
|
||||
|
||||
memcpy(input_blocks, in_bytes, 16 * num_real_blocks);
|
||||
in_bytes += 16 * num_real_blocks;
|
||||
|
||||
int x_end = x_start + 4 * num_real_blocks;
|
||||
|
||||
if (is_hdr) {
|
||||
if (is_signed) {
|
||||
cvtt::Kernels::DecodeBC6HS(output_blocks_hdr, input_blocks);
|
||||
} else {
|
||||
cvtt::Kernels::DecodeBC6HU(output_blocks_hdr, input_blocks);
|
||||
}
|
||||
} else {
|
||||
cvtt::Kernels::DecodeBC7(output_blocks_ldr, input_blocks);
|
||||
}
|
||||
|
||||
for (int y = y_start; y < y_end; y++) {
|
||||
int first_input_element = (y - y_start) * 4;
|
||||
uint8_t *row_start;
|
||||
if (y >= h) {
|
||||
row_start = out_bytes + (h - 1) * (w * bytes_per_pixel);
|
||||
} else {
|
||||
row_start = out_bytes + y * (w * bytes_per_pixel);
|
||||
}
|
||||
|
||||
for (int x = x_start; x < x_end; x++) {
|
||||
uint8_t *pixel_start;
|
||||
if (x >= w) {
|
||||
pixel_start = row_start + (w - 1) * bytes_per_pixel;
|
||||
} else {
|
||||
pixel_start = row_start + x * bytes_per_pixel;
|
||||
}
|
||||
|
||||
int block_index = (x - x_start) / 4;
|
||||
int block_element = (x - x_start) % 4 + first_input_element;
|
||||
if (is_hdr) {
|
||||
memcpy(pixel_start, output_blocks_hdr[block_index].m_pixels[block_element], bytes_per_pixel);
|
||||
} else {
|
||||
memcpy(pixel_start, output_blocks_ldr[block_index].m_pixels[block_element], bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dst_ofs += w * h * bytes_per_pixel;
|
||||
w >>= 1;
|
||||
h >>= 1;
|
||||
}
|
||||
p_image->set_data(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data);
|
||||
}
|
||||
39
engine/modules/cvtt/image_compress_cvtt.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**************************************************************************/
|
||||
/* image_compress_cvtt.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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_COMPRESS_CVTT_H
|
||||
#define IMAGE_COMPRESS_CVTT_H
|
||||
|
||||
#include "core/io/image.h"
|
||||
|
||||
void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels);
|
||||
void image_decompress_cvtt(Image *p_image);
|
||||
|
||||
#endif // IMAGE_COMPRESS_CVTT_H
|
||||
52
engine/modules/cvtt/register_types.cpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
#include "image_compress_cvtt.h"
|
||||
|
||||
void initialize_cvtt_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
|
||||
Image::set_compress_bptc_func(image_compress_cvtt);
|
||||
Image::_image_decompress_bptc = image_decompress_cvtt;
|
||||
}
|
||||
|
||||
void uninitialize_cvtt_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // TOOLS_ENABLED
|
||||
43
engine/modules/cvtt/register_types.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 CVTT_REGISTER_TYPES_H
|
||||
#define CVTT_REGISTER_TYPES_H
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
void initialize_cvtt_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_cvtt_module(ModuleInitializationLevel p_level);
|
||||
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
#endif // CVTT_REGISTER_TYPES_H
|
||||
8
engine/modules/dds/SCsub
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
||||
env_dds = env_modules.Clone()
|
||||
|
||||
env_dds.add_source_files(env.modules_sources, "*.cpp")
|
||||
6
engine/modules/dds/config.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
def can_build(env, platform):
|
||||
return True
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
||||
53
engine/modules/dds/register_types.cpp
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 "texture_loader_dds.h"
|
||||
|
||||
static Ref<ResourceFormatDDS> resource_loader_dds;
|
||||
|
||||
void initialize_dds_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
|
||||
resource_loader_dds.instantiate();
|
||||
ResourceLoader::add_resource_format_loader(resource_loader_dds);
|
||||
}
|
||||
|
||||
void uninitialize_dds_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
|
||||
ResourceLoader::remove_resource_format_loader(resource_loader_dds);
|
||||
resource_loader_dds.unref();
|
||||
}
|
||||
39
engine/modules/dds/register_types.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 DDS_REGISTER_TYPES_H
|
||||
#define DDS_REGISTER_TYPES_H
|
||||
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
void initialize_dds_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_dds_module(ModuleInitializationLevel p_level);
|
||||
|
||||
#endif // DDS_REGISTER_TYPES_H
|
||||
599
engine/modules/dds/texture_loader_dds.cpp
Normal file
|
|
@ -0,0 +1,599 @@
|
|||
/**************************************************************************/
|
||||
/* texture_loader_dds.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 "texture_loader_dds.h"
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
#include "scene/resources/image_texture.h"
|
||||
|
||||
#define PF_FOURCC(s) ((uint32_t)(((s)[3] << 24U) | ((s)[2] << 16U) | ((s)[1] << 8U) | ((s)[0])))
|
||||
|
||||
// Reference: https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dds-header
|
||||
|
||||
enum {
|
||||
DDS_MAGIC = 0x20534444,
|
||||
DDSD_PITCH = 0x00000008,
|
||||
DDSD_LINEARSIZE = 0x00080000,
|
||||
DDSD_MIPMAPCOUNT = 0x00020000,
|
||||
DDPF_FOURCC = 0x00000004,
|
||||
DDPF_ALPHAPIXELS = 0x00000001,
|
||||
DDPF_RGB = 0x00000040
|
||||
};
|
||||
|
||||
enum DDSFourCC {
|
||||
DDFCC_DXT1 = PF_FOURCC("DXT1"),
|
||||
DDFCC_DXT3 = PF_FOURCC("DXT3"),
|
||||
DDFCC_DXT5 = PF_FOURCC("DXT5"),
|
||||
DDFCC_ATI1 = PF_FOURCC("ATI1"),
|
||||
DDFCC_BC4U = PF_FOURCC("BC4U"),
|
||||
DDFCC_ATI2 = PF_FOURCC("ATI2"),
|
||||
DDFCC_BC5U = PF_FOURCC("BC5U"),
|
||||
DDFCC_A2XY = PF_FOURCC("A2XY"),
|
||||
DDFCC_DX10 = PF_FOURCC("DX10"),
|
||||
DDFCC_R16F = 111,
|
||||
DDFCC_RG16F = 112,
|
||||
DDFCC_RGBA16F = 113,
|
||||
DDFCC_R32F = 114,
|
||||
DDFCC_RG32F = 115,
|
||||
DDFCC_RGBA32F = 116
|
||||
};
|
||||
|
||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format
|
||||
enum DXGIFormat {
|
||||
DXGI_R32G32B32A32_FLOAT = 2,
|
||||
DXGI_R16G16B16A16_FLOAT = 10,
|
||||
DXGI_R32G32_FLOAT = 16,
|
||||
DXGI_R10G10B10A2_UNORM = 24,
|
||||
DXGI_R8G8B8A8_UNORM = 28,
|
||||
DXGI_R16G16_FLOAT = 34,
|
||||
DXGI_R32_FLOAT = 41,
|
||||
DXGI_R16_FLOAT = 54,
|
||||
DXGI_R9G9B9E5 = 67,
|
||||
DXGI_BC1_UNORM = 71,
|
||||
DXGI_BC2_UNORM = 74,
|
||||
DXGI_BC3_UNORM = 77,
|
||||
DXGI_BC4_UNORM = 80,
|
||||
DXGI_BC5_UNORM = 83,
|
||||
DXGI_B5G6R5_UNORM = 85,
|
||||
DXGI_B5G5R5A1_UNORM = 86,
|
||||
DXGI_B8G8R8A8_UNORM = 87,
|
||||
DXGI_BC6H_UF16 = 95,
|
||||
DXGI_BC6H_SF16 = 96,
|
||||
DXGI_BC7_UNORM = 98,
|
||||
DXGI_B4G4R4A4_UNORM = 115
|
||||
};
|
||||
|
||||
// The legacy bitmasked format names here represent the actual data layout in the files,
|
||||
// while their official names are flipped (e.g. RGBA8 layout is officially called ABGR8).
|
||||
enum DDSFormat {
|
||||
DDS_DXT1,
|
||||
DDS_DXT3,
|
||||
DDS_DXT5,
|
||||
DDS_ATI1,
|
||||
DDS_ATI2,
|
||||
DDS_BC6U,
|
||||
DDS_BC6S,
|
||||
DDS_BC7U,
|
||||
DDS_R16F,
|
||||
DDS_RG16F,
|
||||
DDS_RGBA16F,
|
||||
DDS_R32F,
|
||||
DDS_RG32F,
|
||||
DDS_RGBA32F,
|
||||
DDS_RGB9E5,
|
||||
DDS_BGRA8,
|
||||
DDS_BGR8,
|
||||
DDS_RGBA8,
|
||||
DDS_RGB8,
|
||||
DDS_BGR5A1,
|
||||
DDS_BGR565,
|
||||
DDS_BGR10A2,
|
||||
DDS_RGB10A2,
|
||||
DDS_BGRA4,
|
||||
DDS_LUMINANCE,
|
||||
DDS_LUMINANCE_ALPHA,
|
||||
DDS_MAX
|
||||
};
|
||||
|
||||
struct DDSFormatInfo {
|
||||
const char *name = nullptr;
|
||||
bool compressed = false;
|
||||
uint32_t divisor = 0;
|
||||
uint32_t block_size = 0;
|
||||
Image::Format format = Image::Format::FORMAT_BPTC_RGBA;
|
||||
};
|
||||
|
||||
static const DDSFormatInfo dds_format_info[DDS_MAX] = {
|
||||
{ "DXT1/BC1", true, 4, 8, Image::FORMAT_DXT1 },
|
||||
{ "DXT3/BC2", true, 4, 16, Image::FORMAT_DXT3 },
|
||||
{ "DXT5/BC3", true, 4, 16, Image::FORMAT_DXT5 },
|
||||
{ "ATI1/BC4", true, 4, 8, Image::FORMAT_RGTC_R },
|
||||
{ "ATI2/A2XY/BC5", true, 4, 16, Image::FORMAT_RGTC_RG },
|
||||
{ "BC6U", true, 4, 16, Image::FORMAT_BPTC_RGBFU },
|
||||
{ "BC6S", true, 4, 16, Image::FORMAT_BPTC_RGBF },
|
||||
{ "BC7U", true, 4, 16, Image::FORMAT_BPTC_RGBA },
|
||||
{ "R16F", false, 1, 2, Image::FORMAT_RH },
|
||||
{ "RG16F", false, 1, 4, Image::FORMAT_RGH },
|
||||
{ "RGBA16F", false, 1, 8, Image::FORMAT_RGBAH },
|
||||
{ "R32F", false, 1, 4, Image::FORMAT_RF },
|
||||
{ "RG32F", false, 1, 8, Image::FORMAT_RGF },
|
||||
{ "RGBA32F", false, 1, 16, Image::FORMAT_RGBAF },
|
||||
{ "RGB9E5", false, 1, 4, Image::FORMAT_RGBE9995 },
|
||||
{ "BGRA8", false, 1, 4, Image::FORMAT_RGBA8 },
|
||||
{ "BGR8", false, 1, 3, Image::FORMAT_RGB8 },
|
||||
{ "RGBA8", false, 1, 4, Image::FORMAT_RGBA8 },
|
||||
{ "RGB8", false, 1, 3, Image::FORMAT_RGB8 },
|
||||
{ "BGR5A1", false, 1, 2, Image::FORMAT_RGBA8 },
|
||||
{ "BGR565", false, 1, 2, Image::FORMAT_RGB8 },
|
||||
{ "BGR10A2", false, 1, 4, Image::FORMAT_RGBA8 },
|
||||
{ "RGB10A2", false, 1, 4, Image::FORMAT_RGBA8 },
|
||||
{ "BGRA4", false, 1, 2, Image::FORMAT_RGBA8 },
|
||||
{ "GRAYSCALE", false, 1, 1, Image::FORMAT_L8 },
|
||||
{ "GRAYSCALE_ALPHA", false, 1, 2, Image::FORMAT_LA8 }
|
||||
};
|
||||
|
||||
static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) {
|
||||
switch (p_dxgi_format) {
|
||||
case DXGI_R32G32B32A32_FLOAT: {
|
||||
return DDS_RGBA32F;
|
||||
}
|
||||
case DXGI_R16G16B16A16_FLOAT: {
|
||||
return DDS_RGBA16F;
|
||||
}
|
||||
case DXGI_R32G32_FLOAT: {
|
||||
return DDS_RG32F;
|
||||
}
|
||||
case DXGI_R10G10B10A2_UNORM: {
|
||||
return DDS_RGB10A2;
|
||||
}
|
||||
case DXGI_R8G8B8A8_UNORM: {
|
||||
return DDS_RGBA8;
|
||||
}
|
||||
case DXGI_R16G16_FLOAT: {
|
||||
return DDS_RG16F;
|
||||
}
|
||||
case DXGI_R32_FLOAT: {
|
||||
return DDS_R32F;
|
||||
}
|
||||
case DXGI_R16_FLOAT: {
|
||||
return DDS_R16F;
|
||||
}
|
||||
case DXGI_R9G9B9E5: {
|
||||
return DDS_RGB9E5;
|
||||
}
|
||||
case DXGI_BC1_UNORM: {
|
||||
return DDS_DXT1;
|
||||
}
|
||||
case DXGI_BC2_UNORM: {
|
||||
return DDS_DXT3;
|
||||
}
|
||||
case DXGI_BC3_UNORM: {
|
||||
return DDS_DXT5;
|
||||
}
|
||||
case DXGI_BC4_UNORM: {
|
||||
return DDS_ATI1;
|
||||
}
|
||||
case DXGI_BC5_UNORM: {
|
||||
return DDS_ATI2;
|
||||
}
|
||||
case DXGI_B5G6R5_UNORM: {
|
||||
return DDS_BGR565;
|
||||
}
|
||||
case DXGI_B5G5R5A1_UNORM: {
|
||||
return DDS_BGR5A1;
|
||||
}
|
||||
case DXGI_B8G8R8A8_UNORM: {
|
||||
return DDS_BGRA8;
|
||||
}
|
||||
case DXGI_BC6H_UF16: {
|
||||
return DDS_BC6U;
|
||||
}
|
||||
case DXGI_BC6H_SF16: {
|
||||
return DDS_BC6S;
|
||||
}
|
||||
case DXGI_BC7_UNORM: {
|
||||
return DDS_BC7U;
|
||||
}
|
||||
case DXGI_B4G4R4A4_UNORM: {
|
||||
return DDS_BGRA4;
|
||||
}
|
||||
|
||||
default: {
|
||||
return DDS_MAX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
|
||||
if (r_error) {
|
||||
*r_error = ERR_CANT_OPEN;
|
||||
}
|
||||
|
||||
Error err;
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
|
||||
if (f.is_null()) {
|
||||
return Ref<Resource>();
|
||||
}
|
||||
|
||||
Ref<FileAccess> fref(f);
|
||||
if (r_error) {
|
||||
*r_error = ERR_FILE_CORRUPT;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Unable to open DDS texture file '" + p_path + "'.");
|
||||
|
||||
uint32_t magic = f->get_32();
|
||||
uint32_t hsize = f->get_32();
|
||||
uint32_t flags = f->get_32();
|
||||
uint32_t height = f->get_32();
|
||||
uint32_t width = f->get_32();
|
||||
uint32_t pitch = f->get_32();
|
||||
/* uint32_t depth = */ f->get_32();
|
||||
uint32_t mipmaps = f->get_32();
|
||||
|
||||
// Skip reserved.
|
||||
for (int i = 0; i < 11; i++) {
|
||||
f->get_32();
|
||||
}
|
||||
|
||||
// Validate.
|
||||
// We don't check DDSD_CAPS or DDSD_PIXELFORMAT, as they're mandatory when writing,
|
||||
// but non-mandatory when reading (as some writers don't set them).
|
||||
if (magic != DDS_MAGIC || hsize != 124) {
|
||||
ERR_FAIL_V_MSG(Ref<Resource>(), "Invalid or unsupported DDS texture file '" + p_path + "'.");
|
||||
}
|
||||
|
||||
/* uint32_t format_size = */ f->get_32();
|
||||
uint32_t format_flags = f->get_32();
|
||||
uint32_t format_fourcc = f->get_32();
|
||||
uint32_t format_rgb_bits = f->get_32();
|
||||
uint32_t format_red_mask = f->get_32();
|
||||
uint32_t format_green_mask = f->get_32();
|
||||
uint32_t format_blue_mask = f->get_32();
|
||||
uint32_t format_alpha_mask = f->get_32();
|
||||
|
||||
/* uint32_t caps_1 = */ f->get_32();
|
||||
/* uint32_t caps_2 = */ f->get_32();
|
||||
/* uint32_t caps_3 = */ f->get_32();
|
||||
/* uint32_t caps_4 = */ f->get_32();
|
||||
|
||||
// Skip reserved.
|
||||
f->get_32();
|
||||
|
||||
if (f->get_position() < 128) {
|
||||
f->seek(128);
|
||||
}
|
||||
|
||||
DDSFormat dds_format = DDS_MAX;
|
||||
|
||||
if (format_flags & DDPF_FOURCC) {
|
||||
// FourCC formats.
|
||||
switch (format_fourcc) {
|
||||
case DDFCC_DXT1: {
|
||||
dds_format = DDS_DXT1;
|
||||
} break;
|
||||
case DDFCC_DXT3: {
|
||||
dds_format = DDS_DXT3;
|
||||
} break;
|
||||
case DDFCC_DXT5: {
|
||||
dds_format = DDS_DXT5;
|
||||
} break;
|
||||
case DDFCC_ATI1:
|
||||
case DDFCC_BC4U: {
|
||||
dds_format = DDS_ATI1;
|
||||
} break;
|
||||
case DDFCC_ATI2:
|
||||
case DDFCC_BC5U:
|
||||
case DDFCC_A2XY: {
|
||||
dds_format = DDS_ATI2;
|
||||
} break;
|
||||
case DDFCC_R16F: {
|
||||
dds_format = DDS_R16F;
|
||||
} break;
|
||||
case DDFCC_RG16F: {
|
||||
dds_format = DDS_RG16F;
|
||||
} break;
|
||||
case DDFCC_RGBA16F: {
|
||||
dds_format = DDS_RGBA16F;
|
||||
} break;
|
||||
case DDFCC_R32F: {
|
||||
dds_format = DDS_R32F;
|
||||
} break;
|
||||
case DDFCC_RG32F: {
|
||||
dds_format = DDS_RG32F;
|
||||
} break;
|
||||
case DDFCC_RGBA32F: {
|
||||
dds_format = DDS_RGBA32F;
|
||||
} break;
|
||||
case DDFCC_DX10: {
|
||||
uint32_t dxgi_format = f->get_32();
|
||||
/* uint32_t dimension = */ f->get_32();
|
||||
/* uint32_t misc_flags_1 = */ f->get_32();
|
||||
/* uint32_t array_size = */ f->get_32();
|
||||
/* uint32_t misc_flags_2 = */ f->get_32();
|
||||
|
||||
dds_format = dxgi_to_dds_format(dxgi_format);
|
||||
} break;
|
||||
|
||||
default: {
|
||||
ERR_FAIL_V_MSG(Ref<Resource>(), "Unrecognized or unsupported FourCC in DDS '" + p_path + "'.");
|
||||
}
|
||||
}
|
||||
|
||||
} else if (format_flags & DDPF_RGB) {
|
||||
// Channel-bitmasked formats.
|
||||
if (format_flags & DDPF_ALPHAPIXELS) {
|
||||
// With alpha.
|
||||
if (format_rgb_bits == 32 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff && format_alpha_mask == 0xff000000) {
|
||||
dds_format = DDS_BGRA8;
|
||||
} else if (format_rgb_bits == 32 && format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000 && format_alpha_mask == 0xff000000) {
|
||||
dds_format = DDS_RGBA8;
|
||||
} else if (format_rgb_bits == 16 && format_red_mask == 0x00007c00 && format_green_mask == 0x000003e0 && format_blue_mask == 0x0000001f && format_alpha_mask == 0x00008000) {
|
||||
dds_format = DDS_BGR5A1;
|
||||
} else if (format_rgb_bits == 32 && format_red_mask == 0x3ff00000 && format_green_mask == 0xffc00 && format_blue_mask == 0x3ff && format_alpha_mask == 0xc0000000) {
|
||||
dds_format = DDS_BGR10A2;
|
||||
} else if (format_rgb_bits == 32 && format_red_mask == 0x3ff && format_green_mask == 0xffc00 && format_blue_mask == 0x3ff00000 && format_alpha_mask == 0xc0000000) {
|
||||
dds_format = DDS_RGB10A2;
|
||||
} else if (format_rgb_bits == 16 && format_red_mask == 0xf00 && format_green_mask == 0xf0 && format_blue_mask == 0xf && format_alpha_mask == 0xf000) {
|
||||
dds_format = DDS_BGRA4;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Without alpha.
|
||||
if (format_rgb_bits == 24 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff) {
|
||||
dds_format = DDS_BGR8;
|
||||
} else if (format_rgb_bits == 24 && format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000) {
|
||||
dds_format = DDS_RGB8;
|
||||
} else if (format_rgb_bits == 16 && format_red_mask == 0x0000f800 && format_green_mask == 0x000007e0 && format_blue_mask == 0x0000001f) {
|
||||
dds_format = DDS_BGR565;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Other formats.
|
||||
if (format_flags & DDPF_ALPHAPIXELS && format_rgb_bits == 16 && format_red_mask == 0xff && format_alpha_mask == 0xff00) {
|
||||
dds_format = DDS_LUMINANCE_ALPHA;
|
||||
} else if (!(format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 8 && format_red_mask == 0xff) {
|
||||
dds_format = DDS_LUMINANCE;
|
||||
}
|
||||
}
|
||||
|
||||
// No format detected, error.
|
||||
if (dds_format == DDS_MAX) {
|
||||
ERR_FAIL_V_MSG(Ref<Resource>(), "Unrecognized or unsupported color layout in DDS '" + p_path + "'.");
|
||||
}
|
||||
|
||||
if (!(flags & DDSD_MIPMAPCOUNT)) {
|
||||
mipmaps = 1;
|
||||
}
|
||||
|
||||
Vector<uint8_t> src_data;
|
||||
|
||||
const DDSFormatInfo &info = dds_format_info[dds_format];
|
||||
uint32_t w = width;
|
||||
uint32_t h = height;
|
||||
|
||||
if (info.compressed) {
|
||||
// BC compressed.
|
||||
uint32_t size = MAX(info.divisor, w) / info.divisor * MAX(info.divisor, h) / info.divisor * info.block_size;
|
||||
|
||||
if (flags & DDSD_LINEARSIZE) {
|
||||
ERR_FAIL_COND_V_MSG(size != pitch, Ref<Resource>(), "DDS header flags specify that a linear size of the top-level image is present, but the specified size does not match the expected value.");
|
||||
} else {
|
||||
ERR_FAIL_COND_V_MSG(pitch != 0, Ref<Resource>(), "DDS header flags specify that no linear size will given for the top-level image, but a non-zero linear size value is present in the header.");
|
||||
}
|
||||
|
||||
for (uint32_t i = 1; i < mipmaps; i++) {
|
||||
w = MAX(1u, w >> 1);
|
||||
h = MAX(1u, h >> 1);
|
||||
|
||||
uint32_t bsize = MAX(info.divisor, w) / info.divisor * MAX(info.divisor, h) / info.divisor * info.block_size;
|
||||
size += bsize;
|
||||
}
|
||||
|
||||
src_data.resize(size);
|
||||
uint8_t *wb = src_data.ptrw();
|
||||
f->get_buffer(wb, size);
|
||||
|
||||
} else {
|
||||
// Generic uncompressed.
|
||||
uint32_t size = width * height * info.block_size;
|
||||
|
||||
for (uint32_t i = 1; i < mipmaps; i++) {
|
||||
w = (w + 1) >> 1;
|
||||
h = (h + 1) >> 1;
|
||||
size += w * h * info.block_size;
|
||||
}
|
||||
|
||||
// Calculate the space these formats will take up after decoding.
|
||||
if (dds_format == DDS_BGR565) {
|
||||
size = size * 3 / 2;
|
||||
} else if (dds_format == DDS_BGR5A1 || dds_format == DDS_BGRA4) {
|
||||
size = size * 2;
|
||||
}
|
||||
|
||||
src_data.resize(size);
|
||||
uint8_t *wb = src_data.ptrw();
|
||||
f->get_buffer(wb, size);
|
||||
|
||||
// Decode nonstandard formats.
|
||||
switch (dds_format) {
|
||||
case DDS_BGR5A1: {
|
||||
// To RGBA8.
|
||||
int colcount = size / 4;
|
||||
|
||||
for (int i = colcount - 1; i >= 0; i--) {
|
||||
int src_ofs = i * 2;
|
||||
int dst_ofs = i * 4;
|
||||
|
||||
uint8_t a = wb[src_ofs + 1] & 0x80;
|
||||
uint8_t b = wb[src_ofs] & 0x1F;
|
||||
uint8_t g = (wb[src_ofs] >> 5) | ((wb[src_ofs + 1] & 0x3) << 3);
|
||||
uint8_t r = (wb[src_ofs + 1] >> 2) & 0x1F;
|
||||
|
||||
wb[dst_ofs + 0] = r << 3;
|
||||
wb[dst_ofs + 1] = g << 3;
|
||||
wb[dst_ofs + 2] = b << 3;
|
||||
wb[dst_ofs + 3] = a ? 255 : 0;
|
||||
}
|
||||
|
||||
} break;
|
||||
case DDS_BGR565: {
|
||||
// To RGB8.
|
||||
int colcount = size / 3;
|
||||
|
||||
for (int i = colcount - 1; i >= 0; i--) {
|
||||
int src_ofs = i * 2;
|
||||
int dst_ofs = i * 3;
|
||||
|
||||
uint8_t b = wb[src_ofs] & 0x1F;
|
||||
uint8_t g = (wb[src_ofs] >> 5) | ((wb[src_ofs + 1] & 0x7) << 3);
|
||||
uint8_t r = wb[src_ofs + 1] >> 3;
|
||||
|
||||
wb[dst_ofs + 0] = r << 3;
|
||||
wb[dst_ofs + 1] = g << 2;
|
||||
wb[dst_ofs + 2] = b << 3;
|
||||
}
|
||||
|
||||
} break;
|
||||
case DDS_BGRA4: {
|
||||
// To RGBA8.
|
||||
int colcount = size / 4;
|
||||
|
||||
for (int i = colcount - 1; i >= 0; i--) {
|
||||
int src_ofs = i * 2;
|
||||
int dst_ofs = i * 4;
|
||||
|
||||
uint8_t b = wb[src_ofs] & 0x0F;
|
||||
uint8_t g = wb[src_ofs] & 0xF0;
|
||||
uint8_t r = wb[src_ofs + 1] & 0x0F;
|
||||
uint8_t a = wb[src_ofs + 1] & 0xF0;
|
||||
|
||||
wb[dst_ofs] = (r << 4) | r;
|
||||
wb[dst_ofs + 1] = g | (g >> 4);
|
||||
wb[dst_ofs + 2] = (b << 4) | b;
|
||||
wb[dst_ofs + 3] = a | (a >> 4);
|
||||
}
|
||||
|
||||
} break;
|
||||
case DDS_RGB10A2: {
|
||||
// To RGBA8.
|
||||
int colcount = size / 4;
|
||||
|
||||
for (int i = 0; i < colcount; i++) {
|
||||
int ofs = i * 4;
|
||||
|
||||
uint32_t w32 = uint32_t(wb[ofs + 0]) | (uint32_t(wb[ofs + 1]) << 8) | (uint32_t(wb[ofs + 2]) << 16) | (uint32_t(wb[ofs + 3]) << 24);
|
||||
|
||||
// This method follows the 'standard' way of decoding 10-bit dds files,
|
||||
// which means the ones created with DirectXTex will be loaded incorrectly.
|
||||
uint8_t a = (w32 & 0xc0000000) >> 24;
|
||||
uint8_t r = (w32 & 0x3ff) >> 2;
|
||||
uint8_t g = (w32 & 0xffc00) >> 12;
|
||||
uint8_t b = (w32 & 0x3ff00000) >> 22;
|
||||
|
||||
wb[ofs + 0] = r;
|
||||
wb[ofs + 1] = g;
|
||||
wb[ofs + 2] = b;
|
||||
wb[ofs + 3] = a == 0xc0 ? 255 : a; // 0xc0 should be opaque.
|
||||
}
|
||||
|
||||
} break;
|
||||
case DDS_BGR10A2: {
|
||||
// To RGBA8.
|
||||
int colcount = size / 4;
|
||||
|
||||
for (int i = 0; i < colcount; i++) {
|
||||
int ofs = i * 4;
|
||||
|
||||
uint32_t w32 = uint32_t(wb[ofs + 0]) | (uint32_t(wb[ofs + 1]) << 8) | (uint32_t(wb[ofs + 2]) << 16) | (uint32_t(wb[ofs + 3]) << 24);
|
||||
|
||||
// This method follows the 'standard' way of decoding 10-bit dds files,
|
||||
// which means the ones created with DirectXTex will be loaded incorrectly.
|
||||
uint8_t a = (w32 & 0xc0000000) >> 24;
|
||||
uint8_t r = (w32 & 0x3ff00000) >> 22;
|
||||
uint8_t g = (w32 & 0xffc00) >> 12;
|
||||
uint8_t b = (w32 & 0x3ff) >> 2;
|
||||
|
||||
wb[ofs + 0] = r;
|
||||
wb[ofs + 1] = g;
|
||||
wb[ofs + 2] = b;
|
||||
wb[ofs + 3] = a == 0xc0 ? 255 : a; // 0xc0 should be opaque.
|
||||
}
|
||||
|
||||
} break;
|
||||
case DDS_BGRA8: {
|
||||
// To RGBA8.
|
||||
int colcount = size / 4;
|
||||
|
||||
for (int i = 0; i < colcount; i++) {
|
||||
SWAP(wb[i * 4 + 0], wb[i * 4 + 2]);
|
||||
}
|
||||
|
||||
} break;
|
||||
case DDS_BGR8: {
|
||||
// To RGB8.
|
||||
int colcount = size / 3;
|
||||
|
||||
for (int i = 0; i < colcount; i++) {
|
||||
SWAP(wb[i * 3 + 0], wb[i * 3 + 2]);
|
||||
}
|
||||
|
||||
} break;
|
||||
|
||||
default: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Image> img = memnew(Image(width, height, mipmaps - 1, info.format, src_data));
|
||||
Ref<ImageTexture> texture = ImageTexture::create_from_image(img);
|
||||
|
||||
if (r_error) {
|
||||
*r_error = OK;
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
void ResourceFormatDDS::get_recognized_extensions(List<String> *p_extensions) const {
|
||||
p_extensions->push_back("dds");
|
||||
}
|
||||
|
||||
bool ResourceFormatDDS::handles_type(const String &p_type) const {
|
||||
return ClassDB::is_parent_class(p_type, "Texture2D");
|
||||
}
|
||||
|
||||
String ResourceFormatDDS::get_resource_type(const String &p_path) const {
|
||||
if (p_path.get_extension().to_lower() == "dds") {
|
||||
return "ImageTexture";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
46
engine/modules/dds/texture_loader_dds.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/**************************************************************************/
|
||||
/* texture_loader_dds.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 TEXTURE_LOADER_DDS_H
|
||||
#define TEXTURE_LOADER_DDS_H
|
||||
|
||||
#include "core/io/resource_loader.h"
|
||||
|
||||
class ResourceFormatDDS : public ResourceFormatLoader {
|
||||
public:
|
||||
virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override;
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
|
||||
virtual bool handles_type(const String &p_type) const override;
|
||||
virtual String get_resource_type(const String &p_path) const override;
|
||||
|
||||
virtual ~ResourceFormatDDS() {}
|
||||
};
|
||||
|
||||
#endif // TEXTURE_LOADER_DDS_H
|
||||
43
engine/modules/enet/SCsub
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
||||
env_enet = env_modules.Clone()
|
||||
|
||||
# Thirdparty source files
|
||||
|
||||
thirdparty_obj = []
|
||||
|
||||
if env["builtin_enet"]:
|
||||
thirdparty_dir = "#thirdparty/enet/"
|
||||
thirdparty_sources = [
|
||||
"godot.cpp",
|
||||
"callbacks.c",
|
||||
"compress.c",
|
||||
"host.c",
|
||||
"list.c",
|
||||
"packet.c",
|
||||
"peer.c",
|
||||
"protocol.c",
|
||||
]
|
||||
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
|
||||
|
||||
env_enet.Prepend(CPPPATH=[thirdparty_dir])
|
||||
env_enet.Append(CPPDEFINES=["GODOT_ENET"])
|
||||
|
||||
env_thirdparty = env_enet.Clone()
|
||||
env_thirdparty.disable_warnings()
|
||||
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
|
||||
env.modules_sources += thirdparty_obj
|
||||
|
||||
|
||||
# Godot source files
|
||||
|
||||
module_obj = []
|
||||
|
||||
env_enet.add_source_files(module_obj, "*.cpp")
|
||||
env.modules_sources += module_obj
|
||||
|
||||
# Needed to force rebuilding the module files when the thirdparty library is updated.
|
||||
env.Depends(module_obj, thirdparty_obj)
|
||||
18
engine/modules/enet/config.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
def can_build(env, platform):
|
||||
return True
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
||||
|
||||
|
||||
def get_doc_classes():
|
||||
return [
|
||||
"ENetMultiplayerPeer",
|
||||
"ENetConnection",
|
||||
"ENetPacketPeer",
|
||||
]
|
||||
|
||||
|
||||
def get_doc_path():
|
||||
return "doc_classes"
|
||||
208
engine/modules/enet/doc_classes/ENetConnection.xml
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="ENetConnection" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
A wrapper class for an [url=http://enet.bespin.org/group__host.html]ENetHost[/url].
|
||||
</brief_description>
|
||||
<description>
|
||||
ENet's purpose is to provide a relatively thin, simple and robust network communication layer on top of UDP (User Datagram Protocol).
|
||||
</description>
|
||||
<tutorials>
|
||||
<link title="API documentation on the ENet website">http://enet.bespin.org/usergroup0.html</link>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="bandwidth_limit">
|
||||
<return type="void" />
|
||||
<param index="0" name="in_bandwidth" type="int" default="0" />
|
||||
<param index="1" name="out_bandwidth" type="int" default="0" />
|
||||
<description>
|
||||
Adjusts the bandwidth limits of a host.
|
||||
</description>
|
||||
</method>
|
||||
<method name="broadcast">
|
||||
<return type="void" />
|
||||
<param index="0" name="channel" type="int" />
|
||||
<param index="1" name="packet" type="PackedByteArray" />
|
||||
<param index="2" name="flags" type="int" />
|
||||
<description>
|
||||
Queues a [param packet] to be sent to all peers associated with the host over the specified [param channel]. See [ENetPacketPeer] [code]FLAG_*[/code] constants for available packet flags.
|
||||
</description>
|
||||
</method>
|
||||
<method name="channel_limit">
|
||||
<return type="void" />
|
||||
<param index="0" name="limit" type="int" />
|
||||
<description>
|
||||
Limits the maximum allowed channels of future incoming connections.
|
||||
</description>
|
||||
</method>
|
||||
<method name="compress">
|
||||
<return type="void" />
|
||||
<param index="0" name="mode" type="int" enum="ENetConnection.CompressionMode" />
|
||||
<description>
|
||||
Sets the compression method used for network packets. 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.
|
||||
[b]Note:[/b] Most games' network design involve sending many small packets frequently (smaller than 4 KB each). If in doubt, it is recommended to keep the default compression algorithm as it works best on these small packets.
|
||||
[b]Note:[/b] The compression mode must be set to the same value on both the server and all its clients. Clients will fail to connect if the compression mode set on the client differs from the one set on the server.
|
||||
</description>
|
||||
</method>
|
||||
<method name="connect_to_host">
|
||||
<return type="ENetPacketPeer" />
|
||||
<param index="0" name="address" type="String" />
|
||||
<param index="1" name="port" type="int" />
|
||||
<param index="2" name="channels" type="int" default="0" />
|
||||
<param index="3" name="data" type="int" default="0" />
|
||||
<description>
|
||||
Initiates a connection to a foreign [param address] using the specified [param port] and allocating the requested [param channels]. Optional [param data] can be passed during connection in the form of a 32 bit integer.
|
||||
[b]Note:[/b] You must call either [method create_host] or [method create_host_bound] on both ends before calling this method.
|
||||
</description>
|
||||
</method>
|
||||
<method name="create_host">
|
||||
<return type="int" enum="Error" />
|
||||
<param index="0" name="max_peers" type="int" default="32" />
|
||||
<param index="1" name="max_channels" type="int" default="0" />
|
||||
<param index="2" name="in_bandwidth" type="int" default="0" />
|
||||
<param index="3" name="out_bandwidth" type="int" default="0" />
|
||||
<description>
|
||||
Creates an ENetHost that allows up to [param max_peers] connected peers, each allocating up to [param max_channels] channels, optionally limiting bandwidth to [param in_bandwidth] and [param out_bandwidth] (if greater than zero).
|
||||
This method binds a random available dynamic UDP port on the host machine at the [i]unspecified[/i] address. Use [method create_host_bound] to specify the address and port.
|
||||
[b]Note:[/b] It is necessary to create a host in both client and server in order to establish a connection.
|
||||
</description>
|
||||
</method>
|
||||
<method name="create_host_bound">
|
||||
<return type="int" enum="Error" />
|
||||
<param index="0" name="bind_address" type="String" />
|
||||
<param index="1" name="bind_port" type="int" />
|
||||
<param index="2" name="max_peers" type="int" default="32" />
|
||||
<param index="3" name="max_channels" type="int" default="0" />
|
||||
<param index="4" name="in_bandwidth" type="int" default="0" />
|
||||
<param index="5" name="out_bandwidth" type="int" default="0" />
|
||||
<description>
|
||||
Creates an ENetHost bound to the given [param bind_address] and [param bind_port] that allows up to [param max_peers] connected peers, each allocating up to [param max_channels] channels, optionally limiting bandwidth to [param in_bandwidth] and [param out_bandwidth] (if greater than zero).
|
||||
[b]Note:[/b] It is necessary to create a host in both client and server in order to establish a connection.
|
||||
</description>
|
||||
</method>
|
||||
<method name="destroy">
|
||||
<return type="void" />
|
||||
<description>
|
||||
Destroys the host and all resources associated with it.
|
||||
</description>
|
||||
</method>
|
||||
<method name="dtls_client_setup">
|
||||
<return type="int" enum="Error" />
|
||||
<param index="0" name="hostname" type="String" />
|
||||
<param index="1" name="client_options" type="TLSOptions" default="null" />
|
||||
<description>
|
||||
Configure this ENetHost to use the custom Godot extension allowing DTLS encryption for ENet clients. Call this before [method connect_to_host] to have ENet connect using DTLS validating the server certificate against [param hostname]. You can pass the optional [param client_options] parameter to customize the trusted certification authorities, or disable the common name verification. See [method TLSOptions.client] and [method TLSOptions.client_unsafe].
|
||||
</description>
|
||||
</method>
|
||||
<method name="dtls_server_setup">
|
||||
<return type="int" enum="Error" />
|
||||
<param index="0" name="server_options" type="TLSOptions" />
|
||||
<description>
|
||||
Configure this ENetHost to use the custom Godot extension allowing DTLS encryption for ENet servers. Call this right after [method create_host_bound] to have ENet expect peers to connect using DTLS. See [method TLSOptions.server].
|
||||
</description>
|
||||
</method>
|
||||
<method name="flush">
|
||||
<return type="void" />
|
||||
<description>
|
||||
Sends any queued packets on the host specified to its designated peers.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_local_port" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
Returns the local port to which this peer is bound.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_max_channels" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
Returns the maximum number of channels allowed for connected peers.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_peers">
|
||||
<return type="ENetPacketPeer[]" />
|
||||
<description>
|
||||
Returns the list of peers associated with this host.
|
||||
[b]Note:[/b] This list might include some peers that are not fully connected or are still being disconnected.
|
||||
</description>
|
||||
</method>
|
||||
<method name="pop_statistic">
|
||||
<return type="float" />
|
||||
<param index="0" name="statistic" type="int" enum="ENetConnection.HostStatistic" />
|
||||
<description>
|
||||
Returns and resets host statistics. See [enum HostStatistic] for more info.
|
||||
</description>
|
||||
</method>
|
||||
<method name="refuse_new_connections">
|
||||
<return type="void" />
|
||||
<param index="0" name="refuse" type="bool" />
|
||||
<description>
|
||||
Configures the DTLS server to automatically drop new connections.
|
||||
[b]Note:[/b] This method is only relevant after calling [method dtls_server_setup].
|
||||
</description>
|
||||
</method>
|
||||
<method name="service">
|
||||
<return type="Array" />
|
||||
<param index="0" name="timeout" type="int" default="0" />
|
||||
<description>
|
||||
Waits for events on this connection and shuttles packets between the host and its peers, with the given [param timeout] (in milliseconds). The returned [Array] will have 4 elements. An [enum EventType], the [ENetPacketPeer] which generated the event, the event associated data (if any), the event associated channel (if any). If the generated event is [constant EVENT_RECEIVE], the received packet will be queued to the associated [ENetPacketPeer].
|
||||
Call this function regularly to handle connections, disconnections, and to receive new packets.
|
||||
[b]Note:[/b] This method must be called on both ends involved in the event (sending and receiving hosts).
|
||||
</description>
|
||||
</method>
|
||||
<method name="socket_send">
|
||||
<return type="void" />
|
||||
<param index="0" name="destination_address" type="String" />
|
||||
<param index="1" name="destination_port" type="int" />
|
||||
<param index="2" name="packet" type="PackedByteArray" />
|
||||
<description>
|
||||
Sends a [param packet] toward a destination from the address and port currently bound by this ENetConnection instance.
|
||||
This is useful as it serves to establish entries in NAT routing tables on all devices between this bound instance and the public facing internet, allowing a prospective client's connection packets to be routed backward through the NAT device(s) between the public internet and this host.
|
||||
This requires forward knowledge of a prospective client's address and communication port as seen by the public internet - after any NAT devices have handled their connection request. This information can be obtained by a [url=https://en.wikipedia.org/wiki/STUN]STUN[/url] service, and must be handed off to your host by an entity that is not the prospective client. This will never work for a client behind a Symmetric NAT due to the nature of the Symmetric NAT routing algorithm, as their IP and Port cannot be known beforehand.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<constants>
|
||||
<constant name="COMPRESS_NONE" value="0" enum="CompressionMode">
|
||||
No compression. This uses the most bandwidth, but has the upside of requiring the fewest CPU resources. This option may also be used to make network debugging using tools like Wireshark easier.
|
||||
</constant>
|
||||
<constant name="COMPRESS_RANGE_CODER" value="1" enum="CompressionMode">
|
||||
ENet's built-in range encoding. Works well on small packets, but is not the most efficient algorithm on packets larger than 4 KB.
|
||||
</constant>
|
||||
<constant name="COMPRESS_FASTLZ" value="2" enum="CompressionMode">
|
||||
[url=https://fastlz.org/]FastLZ[/url] compression. This option uses less CPU resources compared to [constant COMPRESS_ZLIB], at the expense of using more bandwidth.
|
||||
</constant>
|
||||
<constant name="COMPRESS_ZLIB" value="3" enum="CompressionMode">
|
||||
[url=https://www.zlib.net/]Zlib[/url] compression. This option uses less bandwidth compared to [constant COMPRESS_FASTLZ], at the expense of using more CPU resources.
|
||||
</constant>
|
||||
<constant name="COMPRESS_ZSTD" value="4" enum="CompressionMode">
|
||||
[url=https://facebook.github.io/zstd/]Zstandard[/url] compression. Note that this algorithm is not very efficient on packets smaller than 4 KB. Therefore, it's recommended to use other compression algorithms in most cases.
|
||||
</constant>
|
||||
<constant name="EVENT_ERROR" value="-1" enum="EventType">
|
||||
An error occurred during [method service]. You will likely need to [method destroy] the host and recreate it.
|
||||
</constant>
|
||||
<constant name="EVENT_NONE" value="0" enum="EventType">
|
||||
No event occurred within the specified time limit.
|
||||
</constant>
|
||||
<constant name="EVENT_CONNECT" value="1" enum="EventType">
|
||||
A connection request initiated by enet_host_connect has completed. The array will contain the peer which successfully connected.
|
||||
</constant>
|
||||
<constant name="EVENT_DISCONNECT" value="2" enum="EventType">
|
||||
A peer has disconnected. This event is generated on a successful completion of a disconnect initiated by [method ENetPacketPeer.peer_disconnect], if a peer has timed out, or if a connection request initialized by [method connect_to_host] has timed out. The array will contain the peer which disconnected. The data field contains user supplied data describing the disconnection, or 0, if none is available.
|
||||
</constant>
|
||||
<constant name="EVENT_RECEIVE" value="3" enum="EventType">
|
||||
A packet has been received from a peer. The array will contain the peer which sent the packet and the channel number upon which the packet was received. The received packet will be queued to the associated [ENetPacketPeer].
|
||||
</constant>
|
||||
<constant name="HOST_TOTAL_SENT_DATA" value="0" enum="HostStatistic">
|
||||
Total data sent.
|
||||
</constant>
|
||||
<constant name="HOST_TOTAL_SENT_PACKETS" value="1" enum="HostStatistic">
|
||||
Total UDP packets sent.
|
||||
</constant>
|
||||
<constant name="HOST_TOTAL_RECEIVED_DATA" value="2" enum="HostStatistic">
|
||||
Total data received.
|
||||
</constant>
|
||||
<constant name="HOST_TOTAL_RECEIVED_PACKETS" value="3" enum="HostStatistic">
|
||||
Total UDP packets received.
|
||||
</constant>
|
||||
</constants>
|
||||
</class>
|
||||
74
engine/modules/enet/doc_classes/ENetMultiplayerPeer.xml
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="ENetMultiplayerPeer" inherits="MultiplayerPeer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
A MultiplayerPeer implementation using the [url=http://enet.bespin.org/index.html]ENet[/url] library.
|
||||
</brief_description>
|
||||
<description>
|
||||
A MultiplayerPeer implementation that should be passed to [member MultiplayerAPI.multiplayer_peer] after being initialized as either a client, server, or mesh. Events can then be handled by connecting to [MultiplayerAPI] signals. See [ENetConnection] for more information on the ENet library wrapper.
|
||||
[b]Note:[/b] ENet only uses UDP, not TCP. When forwarding the server port to make your server accessible on the public Internet, you only need to forward the server port in UDP. You can use the [UPNP] class to try to forward the server port automatically when starting the server.
|
||||
</description>
|
||||
<tutorials>
|
||||
<link title="High-level multiplayer">$DOCS_URL/tutorials/networking/high_level_multiplayer.html</link>
|
||||
<link title="API documentation on the ENet website">http://enet.bespin.org/usergroup0.html</link>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="add_mesh_peer">
|
||||
<return type="int" enum="Error" />
|
||||
<param index="0" name="peer_id" type="int" />
|
||||
<param index="1" name="host" type="ENetConnection" />
|
||||
<description>
|
||||
Add a new remote peer with the given [param peer_id] connected to the given [param host].
|
||||
[b]Note:[/b] The [param host] must have exactly one peer in the [constant ENetPacketPeer.STATE_CONNECTED] state.
|
||||
</description>
|
||||
</method>
|
||||
<method name="create_client">
|
||||
<return type="int" enum="Error" />
|
||||
<param index="0" name="address" type="String" />
|
||||
<param index="1" name="port" type="int" />
|
||||
<param index="2" name="channel_count" type="int" default="0" />
|
||||
<param index="3" name="in_bandwidth" type="int" default="0" />
|
||||
<param index="4" name="out_bandwidth" type="int" default="0" />
|
||||
<param index="5" name="local_port" type="int" default="0" />
|
||||
<description>
|
||||
Create client that connects to a server at [param address] using specified [param port]. The given address needs to be either a fully qualified domain name (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 [param port] is the port the server is listening on. The [param channel_count] parameter can be used to specify the number of ENet channels allocated for the connection. The [param in_bandwidth] and [param out_bandwidth] 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 [constant OK] if a client was created, [constant ERR_ALREADY_IN_USE] if this ENetMultiplayerPeer instance already has an open connection (in which case you need to call [method MultiplayerPeer.close] first) or [constant ERR_CANT_CREATE] if the client could not be created. If [param local_port] is specified, the client will also listen to the given port; this is useful for some NAT traversal techniques.
|
||||
</description>
|
||||
</method>
|
||||
<method name="create_mesh">
|
||||
<return type="int" enum="Error" />
|
||||
<param index="0" name="unique_id" type="int" />
|
||||
<description>
|
||||
Initialize this [MultiplayerPeer] in mesh mode. The provided [param unique_id] will be used as the local peer network unique ID once assigned as the [member MultiplayerAPI.multiplayer_peer]. In the mesh configuration you will need to set up each new peer manually using [ENetConnection] before calling [method add_mesh_peer]. While this technique is more advanced, it allows for better control over the connection process (e.g. when dealing with NAT punch-through) and for better distribution of the network load (which would otherwise be more taxing on the server).
|
||||
</description>
|
||||
</method>
|
||||
<method name="create_server">
|
||||
<return type="int" enum="Error" />
|
||||
<param index="0" name="port" type="int" />
|
||||
<param index="1" name="max_clients" type="int" default="32" />
|
||||
<param index="2" name="max_channels" type="int" default="0" />
|
||||
<param index="3" name="in_bandwidth" type="int" default="0" />
|
||||
<param index="4" name="out_bandwidth" type="int" default="0" />
|
||||
<description>
|
||||
Create server that listens to connections via [param port]. 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. [param max_clients] is the maximum number of clients that are allowed at once, any number up to 4095 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 [constant OK] if a server was created, [constant ERR_ALREADY_IN_USE] if this ENetMultiplayerPeer instance already has an open connection (in which case you need to call [method MultiplayerPeer.close] first) or [constant ERR_CANT_CREATE] if the server could not be created.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_peer" qualifiers="const">
|
||||
<return type="ENetPacketPeer" />
|
||||
<param index="0" name="id" type="int" />
|
||||
<description>
|
||||
Returns the [ENetPacketPeer] associated to the given [param id].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bind_ip">
|
||||
<return type="void" />
|
||||
<param index="0" name="ip" type="String" />
|
||||
<description>
|
||||
The IP used when creating a server. This is set to the wildcard [code]"*"[/code] by default, which binds to all available interfaces. The given IP needs to be in IPv4 or IPv6 address format, for example: [code]"192.168.1.1"[/code].
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="host" type="ENetConnection" setter="" getter="get_host">
|
||||
The underlying [ENetConnection] created after [method create_client] and [method create_server].
|
||||
</member>
|
||||
</members>
|
||||
</class>
|
||||
214
engine/modules/enet/doc_classes/ENetPacketPeer.xml
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="ENetPacketPeer" inherits="PacketPeer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
A wrapper class for an [url=http://enet.bespin.org/group__peer.html]ENetPeer[/url].
|
||||
</brief_description>
|
||||
<description>
|
||||
A PacketPeer implementation representing a peer of an [ENetConnection].
|
||||
This class cannot be instantiated directly but can be retrieved during [method ENetConnection.service] or via [method ENetConnection.get_peers].
|
||||
[b]Note:[/b] When exporting to Android, make sure to enable the [code]INTERNET[/code] permission in the Android export preset before exporting the project or using one-click deploy. Otherwise, network communication of any kind will be blocked by Android.
|
||||
</description>
|
||||
<tutorials>
|
||||
<link title="API documentation on the ENet website">http://enet.bespin.org/usergroup0.html</link>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="get_channels" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
Returns the number of channels allocated for communication with peer.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_remote_address" qualifiers="const">
|
||||
<return type="String" />
|
||||
<description>
|
||||
Returns the IP address of this peer.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_remote_port" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
Returns the remote port of this peer.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_state" qualifiers="const">
|
||||
<return type="int" enum="ENetPacketPeer.PeerState" />
|
||||
<description>
|
||||
Returns the current peer state. See [enum PeerState].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_statistic">
|
||||
<return type="float" />
|
||||
<param index="0" name="statistic" type="int" enum="ENetPacketPeer.PeerStatistic" />
|
||||
<description>
|
||||
Returns the requested [param statistic] for this peer. See [enum PeerStatistic].
|
||||
</description>
|
||||
</method>
|
||||
<method name="is_active" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns [code]true[/code] if the peer is currently active (i.e. the associated [ENetConnection] is still valid).
|
||||
</description>
|
||||
</method>
|
||||
<method name="peer_disconnect">
|
||||
<return type="void" />
|
||||
<param index="0" name="data" type="int" default="0" />
|
||||
<description>
|
||||
Request a disconnection from a peer. An [constant ENetConnection.EVENT_DISCONNECT] will be generated during [method ENetConnection.service] once the disconnection is complete.
|
||||
</description>
|
||||
</method>
|
||||
<method name="peer_disconnect_later">
|
||||
<return type="void" />
|
||||
<param index="0" name="data" type="int" default="0" />
|
||||
<description>
|
||||
Request a disconnection from a peer, but only after all queued outgoing packets are sent. An [constant ENetConnection.EVENT_DISCONNECT] will be generated during [method ENetConnection.service] once the disconnection is complete.
|
||||
</description>
|
||||
</method>
|
||||
<method name="peer_disconnect_now">
|
||||
<return type="void" />
|
||||
<param index="0" name="data" type="int" default="0" />
|
||||
<description>
|
||||
Force an immediate disconnection from a peer. No [constant ENetConnection.EVENT_DISCONNECT] will be generated. The foreign peer is not guaranteed to receive the disconnect notification, and is reset immediately upon return from this function.
|
||||
</description>
|
||||
</method>
|
||||
<method name="ping">
|
||||
<return type="void" />
|
||||
<description>
|
||||
Sends a ping request to a peer. ENet automatically pings all connected peers at regular intervals, however, this function may be called to ensure more frequent ping requests.
|
||||
</description>
|
||||
</method>
|
||||
<method name="ping_interval">
|
||||
<return type="void" />
|
||||
<param index="0" name="ping_interval" type="int" />
|
||||
<description>
|
||||
Sets the [param ping_interval] in milliseconds at which pings will be sent to a peer. Pings are used both to monitor the liveness of the connection and also to dynamically adjust the throttle during periods of low traffic so that the throttle has reasonable responsiveness during traffic spikes. The default ping interval is [code]500[/code] milliseconds.
|
||||
</description>
|
||||
</method>
|
||||
<method name="reset">
|
||||
<return type="void" />
|
||||
<description>
|
||||
Forcefully disconnects a peer. The foreign host represented by the peer is not notified of the disconnection and will timeout on its connection to the local host.
|
||||
</description>
|
||||
</method>
|
||||
<method name="send">
|
||||
<return type="int" enum="Error" />
|
||||
<param index="0" name="channel" type="int" />
|
||||
<param index="1" name="packet" type="PackedByteArray" />
|
||||
<param index="2" name="flags" type="int" />
|
||||
<description>
|
||||
Queues a [param packet] to be sent over the specified [param channel]. See [code]FLAG_*[/code] constants for available packet flags.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_timeout">
|
||||
<return type="void" />
|
||||
<param index="0" name="timeout" type="int" />
|
||||
<param index="1" name="timeout_min" type="int" />
|
||||
<param index="2" name="timeout_max" type="int" />
|
||||
<description>
|
||||
Sets the timeout parameters for a peer. The timeout parameters control how and when a peer will timeout from a failure to acknowledge reliable traffic. Timeout values are expressed in milliseconds.
|
||||
The [param timeout] is a factor that, multiplied by a value based on the average round trip time, will determine the timeout limit for a reliable packet. When that limit is reached, the timeout will be doubled, and the peer will be disconnected if that limit has reached [param timeout_min]. The [param timeout_max] parameter, on the other hand, defines a fixed timeout for which any packet must be acknowledged or the peer will be dropped.
|
||||
</description>
|
||||
</method>
|
||||
<method name="throttle_configure">
|
||||
<return type="void" />
|
||||
<param index="0" name="interval" type="int" />
|
||||
<param index="1" name="acceleration" type="int" />
|
||||
<param index="2" name="deceleration" type="int" />
|
||||
<description>
|
||||
Configures throttle parameter for a peer.
|
||||
Unreliable packets are dropped by ENet in response to the varying conditions of the Internet connection to the peer. The throttle represents a probability that an unreliable packet should not be dropped and thus sent by ENet to the peer. By measuring fluctuations in round trip times of reliable packets over the specified [param interval], ENet will either increase the probability by the amount specified in the [param acceleration] parameter, or decrease it by the amount specified in the [param deceleration] parameter (both are ratios to [constant PACKET_THROTTLE_SCALE]).
|
||||
When the throttle has a value of [constant PACKET_THROTTLE_SCALE], no unreliable packets are dropped by ENet, and so 100% of all unreliable packets will be sent.
|
||||
When the throttle has a value of [code]0[/code], all unreliable packets are dropped by ENet, and so 0% of all unreliable packets will be sent.
|
||||
Intermediate values for the throttle represent intermediate probabilities between 0% and 100% of unreliable packets being sent. The bandwidth limits of the local and foreign hosts are taken into account to determine a sensible limit for the throttle probability above which it should not raise even in the best of conditions.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<constants>
|
||||
<constant name="STATE_DISCONNECTED" value="0" enum="PeerState">
|
||||
The peer is disconnected.
|
||||
</constant>
|
||||
<constant name="STATE_CONNECTING" value="1" enum="PeerState">
|
||||
The peer is currently attempting to connect.
|
||||
</constant>
|
||||
<constant name="STATE_ACKNOWLEDGING_CONNECT" value="2" enum="PeerState">
|
||||
The peer has acknowledged the connection request.
|
||||
</constant>
|
||||
<constant name="STATE_CONNECTION_PENDING" value="3" enum="PeerState">
|
||||
The peer is currently connecting.
|
||||
</constant>
|
||||
<constant name="STATE_CONNECTION_SUCCEEDED" value="4" enum="PeerState">
|
||||
The peer has successfully connected, but is not ready to communicate with yet ([constant STATE_CONNECTED]).
|
||||
</constant>
|
||||
<constant name="STATE_CONNECTED" value="5" enum="PeerState">
|
||||
The peer is currently connected and ready to communicate with.
|
||||
</constant>
|
||||
<constant name="STATE_DISCONNECT_LATER" value="6" enum="PeerState">
|
||||
The peer is slated to disconnect after it has no more outgoing packets to send.
|
||||
</constant>
|
||||
<constant name="STATE_DISCONNECTING" value="7" enum="PeerState">
|
||||
The peer is currently disconnecting.
|
||||
</constant>
|
||||
<constant name="STATE_ACKNOWLEDGING_DISCONNECT" value="8" enum="PeerState">
|
||||
The peer has acknowledged the disconnection request.
|
||||
</constant>
|
||||
<constant name="STATE_ZOMBIE" value="9" enum="PeerState">
|
||||
The peer has lost connection, but is not considered truly disconnected (as the peer didn't acknowledge the disconnection request).
|
||||
</constant>
|
||||
<constant name="PEER_PACKET_LOSS" value="0" enum="PeerStatistic">
|
||||
Mean packet loss of reliable packets as a ratio with respect to the [constant PACKET_LOSS_SCALE].
|
||||
</constant>
|
||||
<constant name="PEER_PACKET_LOSS_VARIANCE" value="1" enum="PeerStatistic">
|
||||
Packet loss variance.
|
||||
</constant>
|
||||
<constant name="PEER_PACKET_LOSS_EPOCH" value="2" enum="PeerStatistic">
|
||||
The time at which packet loss statistics were last updated (in milliseconds since the connection started). The interval for packet loss statistics updates is 10 seconds, and at least one packet must have been sent since the last statistics update.
|
||||
</constant>
|
||||
<constant name="PEER_ROUND_TRIP_TIME" value="3" enum="PeerStatistic">
|
||||
Mean packet round trip time for reliable packets.
|
||||
</constant>
|
||||
<constant name="PEER_ROUND_TRIP_TIME_VARIANCE" value="4" enum="PeerStatistic">
|
||||
Variance of the mean round trip time.
|
||||
</constant>
|
||||
<constant name="PEER_LAST_ROUND_TRIP_TIME" value="5" enum="PeerStatistic">
|
||||
Last recorded round trip time for a reliable packet.
|
||||
</constant>
|
||||
<constant name="PEER_LAST_ROUND_TRIP_TIME_VARIANCE" value="6" enum="PeerStatistic">
|
||||
Variance of the last trip time recorded.
|
||||
</constant>
|
||||
<constant name="PEER_PACKET_THROTTLE" value="7" enum="PeerStatistic">
|
||||
The peer's current throttle status.
|
||||
</constant>
|
||||
<constant name="PEER_PACKET_THROTTLE_LIMIT" value="8" enum="PeerStatistic">
|
||||
The maximum number of unreliable packets that should not be dropped. This value is always greater than or equal to [code]1[/code]. The initial value is equal to [constant PACKET_THROTTLE_SCALE].
|
||||
</constant>
|
||||
<constant name="PEER_PACKET_THROTTLE_COUNTER" value="9" enum="PeerStatistic">
|
||||
Internal value used to increment the packet throttle counter. The value is hardcoded to [code]7[/code] and cannot be changed. You probably want to look at [constant PEER_PACKET_THROTTLE_ACCELERATION] instead.
|
||||
</constant>
|
||||
<constant name="PEER_PACKET_THROTTLE_EPOCH" value="10" enum="PeerStatistic">
|
||||
The time at which throttle statistics were last updated (in milliseconds since the connection started). The interval for throttle statistics updates is [constant PEER_PACKET_THROTTLE_INTERVAL].
|
||||
</constant>
|
||||
<constant name="PEER_PACKET_THROTTLE_ACCELERATION" value="11" enum="PeerStatistic">
|
||||
The throttle's acceleration factor. Higher values will make ENet adapt to fluctuating network conditions faster, causing unrelaible packets to be sent [i]more[/i] often. The default value is [code]2[/code].
|
||||
</constant>
|
||||
<constant name="PEER_PACKET_THROTTLE_DECELERATION" value="12" enum="PeerStatistic">
|
||||
The throttle's deceleration factor. Higher values will make ENet adapt to fluctuating network conditions faster, causing unrelaible packets to be sent [i]less[/i] often. The default value is [code]2[/code].
|
||||
</constant>
|
||||
<constant name="PEER_PACKET_THROTTLE_INTERVAL" value="13" enum="PeerStatistic">
|
||||
The interval over which the lowest mean round trip time should be measured for use by the throttle mechanism (in milliseconds). The default value is [code]5000[/code].
|
||||
</constant>
|
||||
<constant name="PACKET_LOSS_SCALE" value="65536">
|
||||
The reference scale for packet loss. See [method get_statistic] and [constant PEER_PACKET_LOSS].
|
||||
</constant>
|
||||
<constant name="PACKET_THROTTLE_SCALE" value="32">
|
||||
The reference value for throttle configuration. The default value is [code]32[/code]. See [method throttle_configure].
|
||||
</constant>
|
||||
<constant name="FLAG_RELIABLE" value="1">
|
||||
Mark the packet to be sent as reliable.
|
||||
</constant>
|
||||
<constant name="FLAG_UNSEQUENCED" value="2">
|
||||
Mark the packet to be sent unsequenced (unreliable).
|
||||
</constant>
|
||||
<constant name="FLAG_UNRELIABLE_FRAGMENT" value="8">
|
||||
Mark the packet to be sent unreliable even if the packet is too big and needs fragmentation (increasing the chance of it being dropped).
|
||||
</constant>
|
||||
</constants>
|
||||
</class>
|
||||
523
engine/modules/enet/enet_connection.cpp
Normal file
|
|
@ -0,0 +1,523 @@
|
|||
/**************************************************************************/
|
||||
/* enet_connection.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 "enet_connection.h"
|
||||
|
||||
#include "enet_packet_peer.h"
|
||||
|
||||
#include "core/io/compression.h"
|
||||
#include "core/io/ip.h"
|
||||
#include "core/variant/typed_array.h"
|
||||
|
||||
void ENetConnection::broadcast(enet_uint8 p_channel, ENetPacket *p_packet) {
|
||||
ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active.");
|
||||
ERR_FAIL_COND_MSG(p_channel >= host->channelLimit, vformat("Unable to send packet on channel %d, max channels: %d", p_channel, (int)host->channelLimit));
|
||||
enet_host_broadcast(host, p_channel, p_packet);
|
||||
}
|
||||
|
||||
Error ENetConnection::create_host_bound(const IPAddress &p_bind_address, int p_port, int p_max_peers, int p_max_channels, int p_in_bandwidth, int p_out_bandwidth) {
|
||||
ERR_FAIL_COND_V_MSG(!p_bind_address.is_valid() && !p_bind_address.is_wildcard(), ERR_INVALID_PARAMETER, "Invalid bind IP.");
|
||||
ERR_FAIL_COND_V_MSG(p_port < 0 || p_port > 65535, ERR_INVALID_PARAMETER, "The local port number must be between 0 and 65535 (inclusive).");
|
||||
|
||||
ENetAddress address;
|
||||
memset(&address, 0, sizeof(address));
|
||||
address.port = p_port;
|
||||
#ifdef GODOT_ENET
|
||||
if (p_bind_address.is_wildcard()) {
|
||||
address.wildcard = 1;
|
||||
} else {
|
||||
enet_address_set_ip(&address, p_bind_address.get_ipv6(), 16);
|
||||
}
|
||||
#else
|
||||
if (p_bind_address.is_wildcard()) {
|
||||
address.host = 0;
|
||||
} else {
|
||||
ERR_FAIL_COND_V(!p_bind_address.is_ipv4(), ERR_INVALID_PARAMETER);
|
||||
address.host = *(uint32_t *)p_bind_address.get_ipv4();
|
||||
}
|
||||
#endif
|
||||
return _create(&address, p_max_peers, p_max_channels, p_in_bandwidth, p_out_bandwidth);
|
||||
}
|
||||
|
||||
Error ENetConnection::create_host(int p_max_peers, int p_max_channels, int p_in_bandwidth, int p_out_bandwidth) {
|
||||
return _create(nullptr, p_max_peers, p_max_channels, p_in_bandwidth, p_out_bandwidth);
|
||||
}
|
||||
|
||||
void ENetConnection::destroy() {
|
||||
ERR_FAIL_NULL_MSG(host, "Host already destroyed.");
|
||||
for (List<Ref<ENetPacketPeer>>::Element *E = peers.front(); E; E = E->next()) {
|
||||
E->get()->_on_disconnect();
|
||||
}
|
||||
peers.clear();
|
||||
enet_host_destroy(host);
|
||||
host = nullptr;
|
||||
}
|
||||
|
||||
Ref<ENetPacketPeer> ENetConnection::connect_to_host(const String &p_address, int p_port, int p_channels, int p_data) {
|
||||
Ref<ENetPacketPeer> out;
|
||||
ERR_FAIL_NULL_V_MSG(host, out, "The ENetConnection instance isn't currently active.");
|
||||
ERR_FAIL_COND_V_MSG(peers.size(), out, "The ENetConnection is already connected to a peer.");
|
||||
ERR_FAIL_COND_V_MSG(p_port < 1 || p_port > 65535, out, "The remote port number must be between 1 and 65535 (inclusive).");
|
||||
|
||||
IPAddress ip;
|
||||
if (p_address.is_valid_ip_address()) {
|
||||
ip = p_address;
|
||||
} else {
|
||||
#ifdef GODOT_ENET
|
||||
ip = IP::get_singleton()->resolve_hostname(p_address);
|
||||
#else
|
||||
ip = IP::get_singleton()->resolve_hostname(p_address, IP::TYPE_IPV4);
|
||||
#endif
|
||||
ERR_FAIL_COND_V_MSG(!ip.is_valid(), out, "Couldn't resolve the server IP address or domain name.");
|
||||
}
|
||||
|
||||
ENetAddress address;
|
||||
#ifdef GODOT_ENET
|
||||
enet_address_set_ip(&address, ip.get_ipv6(), 16);
|
||||
#else
|
||||
ERR_FAIL_COND_V_MSG(!ip.is_ipv4(), out, "Connecting to an IPv6 server isn't supported when using vanilla ENet. Recompile Godot with the bundled ENet library.");
|
||||
address.host = *(uint32_t *)ip.get_ipv4();
|
||||
#endif
|
||||
address.port = p_port;
|
||||
|
||||
// Initiate connection, allocating enough channels
|
||||
ENetPeer *peer = enet_host_connect(host, &address, p_channels > 0 ? p_channels : ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT, p_data);
|
||||
|
||||
if (peer == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
out = Ref<ENetPacketPeer>(memnew(ENetPacketPeer(peer)));
|
||||
peers.push_back(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
ENetConnection::EventType ENetConnection::_parse_event(const ENetEvent &p_event, Event &r_event) {
|
||||
switch (p_event.type) {
|
||||
case ENET_EVENT_TYPE_CONNECT: {
|
||||
if (p_event.peer->data == nullptr) {
|
||||
Ref<ENetPacketPeer> pp = memnew(ENetPacketPeer(p_event.peer));
|
||||
peers.push_back(pp);
|
||||
}
|
||||
r_event.peer = Ref<ENetPacketPeer>((ENetPacketPeer *)p_event.peer->data);
|
||||
r_event.data = p_event.data;
|
||||
return EVENT_CONNECT;
|
||||
} break;
|
||||
case ENET_EVENT_TYPE_DISCONNECT: {
|
||||
// A peer disconnected.
|
||||
if (p_event.peer->data != nullptr) {
|
||||
Ref<ENetPacketPeer> pp = Ref<ENetPacketPeer>((ENetPacketPeer *)p_event.peer->data);
|
||||
pp->_on_disconnect();
|
||||
peers.erase(pp);
|
||||
r_event.peer = pp;
|
||||
r_event.data = p_event.data;
|
||||
return EVENT_DISCONNECT;
|
||||
}
|
||||
return EVENT_ERROR;
|
||||
} break;
|
||||
case ENET_EVENT_TYPE_RECEIVE: {
|
||||
// Packet received.
|
||||
if (p_event.peer->data != nullptr) {
|
||||
Ref<ENetPacketPeer> pp = Ref<ENetPacketPeer>((ENetPacketPeer *)p_event.peer->data);
|
||||
r_event.peer = Ref<ENetPacketPeer>((ENetPacketPeer *)p_event.peer->data);
|
||||
r_event.channel_id = p_event.channelID;
|
||||
r_event.packet = p_event.packet;
|
||||
return EVENT_RECEIVE;
|
||||
}
|
||||
return EVENT_ERROR;
|
||||
} break;
|
||||
case ENET_EVENT_TYPE_NONE:
|
||||
return EVENT_NONE;
|
||||
default:
|
||||
return EVENT_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
ENetConnection::EventType ENetConnection::service(int p_timeout, Event &r_event) {
|
||||
ERR_FAIL_NULL_V_MSG(host, EVENT_ERROR, "The ENetConnection instance isn't currently active.");
|
||||
ERR_FAIL_COND_V(r_event.peer.is_valid(), EVENT_ERROR);
|
||||
|
||||
// Drop peers that have already been disconnected.
|
||||
// NOTE: Forcibly disconnected peers (i.e. peers disconnected via
|
||||
// enet_peer_disconnect*) do not trigger DISCONNECTED events.
|
||||
List<Ref<ENetPacketPeer>>::Element *E = peers.front();
|
||||
while (E) {
|
||||
if (!E->get()->is_active()) {
|
||||
peers.erase(E->get());
|
||||
}
|
||||
E = E->next();
|
||||
}
|
||||
|
||||
ENetEvent event;
|
||||
int ret = enet_host_service(host, &event, p_timeout);
|
||||
|
||||
if (ret < 0) {
|
||||
return EVENT_ERROR;
|
||||
} else if (ret == 0) {
|
||||
return EVENT_NONE;
|
||||
}
|
||||
return _parse_event(event, r_event);
|
||||
}
|
||||
|
||||
int ENetConnection::check_events(EventType &r_type, Event &r_event) {
|
||||
ERR_FAIL_NULL_V_MSG(host, -1, "The ENetConnection instance isn't currently active.");
|
||||
ENetEvent event;
|
||||
int ret = enet_host_check_events(host, &event);
|
||||
if (ret < 0) {
|
||||
r_type = EVENT_ERROR;
|
||||
return ret;
|
||||
}
|
||||
r_type = _parse_event(event, r_event);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ENetConnection::flush() {
|
||||
ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active.");
|
||||
enet_host_flush(host);
|
||||
}
|
||||
|
||||
void ENetConnection::bandwidth_limit(int p_in_bandwidth, int p_out_bandwidth) {
|
||||
ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active.");
|
||||
enet_host_bandwidth_limit(host, p_in_bandwidth, p_out_bandwidth);
|
||||
}
|
||||
|
||||
void ENetConnection::channel_limit(int p_max_channels) {
|
||||
ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active.");
|
||||
enet_host_channel_limit(host, p_max_channels);
|
||||
}
|
||||
|
||||
void ENetConnection::bandwidth_throttle() {
|
||||
ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active.");
|
||||
enet_host_bandwidth_throttle(host);
|
||||
}
|
||||
|
||||
void ENetConnection::compress(CompressionMode p_mode) {
|
||||
ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active.");
|
||||
Compressor::setup(host, p_mode);
|
||||
}
|
||||
|
||||
double ENetConnection::pop_statistic(HostStatistic p_stat) {
|
||||
ERR_FAIL_NULL_V_MSG(host, 0, "The ENetConnection instance isn't currently active.");
|
||||
uint32_t *ptr = nullptr;
|
||||
switch (p_stat) {
|
||||
case HOST_TOTAL_SENT_DATA:
|
||||
ptr = &(host->totalSentData);
|
||||
break;
|
||||
case HOST_TOTAL_SENT_PACKETS:
|
||||
ptr = &(host->totalSentPackets);
|
||||
break;
|
||||
case HOST_TOTAL_RECEIVED_DATA:
|
||||
ptr = &(host->totalReceivedData);
|
||||
break;
|
||||
case HOST_TOTAL_RECEIVED_PACKETS:
|
||||
ptr = &(host->totalReceivedPackets);
|
||||
break;
|
||||
}
|
||||
ERR_FAIL_NULL_V_MSG(ptr, 0, "Invalid statistic: " + itos(p_stat) + ".");
|
||||
uint32_t ret = *ptr;
|
||||
*ptr = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ENetConnection::get_max_channels() const {
|
||||
ERR_FAIL_NULL_V_MSG(host, 0, "The ENetConnection instance isn't currently active.");
|
||||
return host->channelLimit;
|
||||
}
|
||||
|
||||
int ENetConnection::get_local_port() const {
|
||||
ERR_FAIL_NULL_V_MSG(host, 0, "The ENetConnection instance isn't currently active.");
|
||||
ERR_FAIL_COND_V_MSG(!(host->socket), 0, "The ENetConnection instance isn't currently bound.");
|
||||
ENetAddress address;
|
||||
ERR_FAIL_COND_V_MSG(enet_socket_get_address(host->socket, &address), 0, "Unable to get socket address");
|
||||
return address.port;
|
||||
}
|
||||
|
||||
void ENetConnection::get_peers(List<Ref<ENetPacketPeer>> &r_peers) {
|
||||
for (const Ref<ENetPacketPeer> &I : peers) {
|
||||
r_peers.push_back(I);
|
||||
}
|
||||
}
|
||||
|
||||
TypedArray<ENetPacketPeer> ENetConnection::_get_peers() {
|
||||
ERR_FAIL_NULL_V_MSG(host, Array(), "The ENetConnection instance isn't currently active.");
|
||||
TypedArray<ENetPacketPeer> out;
|
||||
for (const Ref<ENetPacketPeer> &I : peers) {
|
||||
out.push_back(I);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Error ENetConnection::dtls_server_setup(const Ref<TLSOptions> &p_options) {
|
||||
#ifdef GODOT_ENET
|
||||
ERR_FAIL_NULL_V_MSG(host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active.");
|
||||
ERR_FAIL_COND_V(p_options.is_null() || !p_options->is_server(), ERR_INVALID_PARAMETER);
|
||||
return enet_host_dtls_server_setup(host, const_cast<TLSOptions *>(p_options.ptr())) ? FAILED : OK;
|
||||
#else
|
||||
ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "ENet DTLS support not available in this build.");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ENetConnection::refuse_new_connections(bool p_refuse) {
|
||||
#ifdef GODOT_ENET
|
||||
ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active.");
|
||||
enet_host_refuse_new_connections(host, p_refuse);
|
||||
#else
|
||||
ERR_FAIL_MSG("ENet DTLS support not available in this build.");
|
||||
#endif
|
||||
}
|
||||
|
||||
Error ENetConnection::dtls_client_setup(const String &p_hostname, const Ref<TLSOptions> &p_options) {
|
||||
#ifdef GODOT_ENET
|
||||
ERR_FAIL_NULL_V_MSG(host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active.");
|
||||
ERR_FAIL_COND_V(p_options.is_null() || p_options->is_server(), ERR_INVALID_PARAMETER);
|
||||
return enet_host_dtls_client_setup(host, p_hostname.utf8().get_data(), const_cast<TLSOptions *>(p_options.ptr())) ? FAILED : OK;
|
||||
#else
|
||||
ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "ENet DTLS support not available in this build.");
|
||||
#endif
|
||||
}
|
||||
|
||||
Error ENetConnection::_create(ENetAddress *p_address, int p_max_peers, int p_max_channels, int p_in_bandwidth, int p_out_bandwidth) {
|
||||
ERR_FAIL_COND_V_MSG(host != nullptr, ERR_ALREADY_IN_USE, "The ENetConnection instance is already active.");
|
||||
ERR_FAIL_COND_V_MSG(p_max_peers < 1 || p_max_peers > 4095, ERR_INVALID_PARAMETER, "The number of clients must be set between 1 and 4095 (inclusive).");
|
||||
ERR_FAIL_COND_V_MSG(p_max_channels < 0 || p_max_channels > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT, ERR_INVALID_PARAMETER, "Invalid channel count. Must be between 0 and 255 (0 means maximum, i.e. 255)");
|
||||
ERR_FAIL_COND_V_MSG(p_in_bandwidth < 0, ERR_INVALID_PARAMETER, "The incoming bandwidth limit must be greater than or equal to 0 (0 disables the limit).");
|
||||
ERR_FAIL_COND_V_MSG(p_out_bandwidth < 0, ERR_INVALID_PARAMETER, "The outgoing bandwidth limit must be greater than or equal to 0 (0 disables the limit).");
|
||||
|
||||
host = enet_host_create(p_address /* the address to bind the server host to */,
|
||||
p_max_peers /* allow up to p_max_peers connections */,
|
||||
p_max_channels /* allow up to p_max_channel to be used */,
|
||||
p_in_bandwidth /* limit incoming bandwidth if > 0 */,
|
||||
p_out_bandwidth /* limit outgoing bandwidth if > 0 */);
|
||||
|
||||
ERR_FAIL_NULL_V_MSG(host, ERR_CANT_CREATE, "Couldn't create an ENet host.");
|
||||
return OK;
|
||||
}
|
||||
|
||||
Array ENetConnection::_service(int p_timeout) {
|
||||
Array out;
|
||||
Event event;
|
||||
Ref<ENetPacketPeer> peer;
|
||||
EventType ret = service(p_timeout, event);
|
||||
out.push_back(ret);
|
||||
out.push_back(event.peer);
|
||||
out.push_back(event.data);
|
||||
out.push_back(event.channel_id);
|
||||
if (event.packet && event.peer.is_valid()) {
|
||||
event.peer->_queue_packet(event.packet);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void ENetConnection::_broadcast(int p_channel, PackedByteArray p_packet, int p_flags) {
|
||||
ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active.");
|
||||
ERR_FAIL_COND_MSG(p_channel < 0 || p_channel > (int)host->channelLimit, "Invalid channel");
|
||||
ERR_FAIL_COND_MSG(p_flags & ~ENetPacketPeer::FLAG_ALLOWED, "Invalid flags");
|
||||
ENetPacket *pkt = enet_packet_create(p_packet.ptr(), p_packet.size(), p_flags);
|
||||
broadcast(p_channel, pkt);
|
||||
}
|
||||
|
||||
void ENetConnection::socket_send(const String &p_address, int p_port, const PackedByteArray &p_packet) {
|
||||
ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active.");
|
||||
ERR_FAIL_COND_MSG(!(host->socket), "The ENetConnection instance isn't currently bound.");
|
||||
ERR_FAIL_COND_MSG(p_port < 1 || p_port > 65535, "The remote port number must be between 1 and 65535 (inclusive).");
|
||||
|
||||
IPAddress ip;
|
||||
if (p_address.is_valid_ip_address()) {
|
||||
ip = p_address;
|
||||
} else {
|
||||
#ifdef GODOT_ENET
|
||||
ip = IP::get_singleton()->resolve_hostname(p_address);
|
||||
#else
|
||||
ip = IP::get_singleton()->resolve_hostname(p_address, IP::TYPE_IPV4);
|
||||
#endif
|
||||
ERR_FAIL_COND_MSG(!ip.is_valid(), "Couldn't resolve the server IP address or domain name.");
|
||||
}
|
||||
|
||||
ENetAddress address;
|
||||
#ifdef GODOT_ENET
|
||||
enet_address_set_ip(&address, ip.get_ipv6(), 16);
|
||||
#else
|
||||
ERR_FAIL_COND_MSG(!ip.is_ipv4(), "Connecting to an IPv6 server isn't supported when using vanilla ENet. Recompile Godot with the bundled ENet library.");
|
||||
address.host = *(uint32_t *)ip.get_ipv4();
|
||||
#endif
|
||||
address.port = p_port;
|
||||
|
||||
ENetBuffer enet_buffers[1];
|
||||
enet_buffers[0].data = (void *)p_packet.ptr();
|
||||
enet_buffers[0].dataLength = p_packet.size();
|
||||
|
||||
enet_socket_send(host->socket, &address, enet_buffers, 1);
|
||||
}
|
||||
|
||||
void ENetConnection::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("create_host_bound", "bind_address", "bind_port", "max_peers", "max_channels", "in_bandwidth", "out_bandwidth"), &ENetConnection::create_host_bound, DEFVAL(32), DEFVAL(0), DEFVAL(0), DEFVAL(0));
|
||||
ClassDB::bind_method(D_METHOD("create_host", "max_peers", "max_channels", "in_bandwidth", "out_bandwidth"), &ENetConnection::create_host, DEFVAL(32), DEFVAL(0), DEFVAL(0), DEFVAL(0));
|
||||
ClassDB::bind_method(D_METHOD("destroy"), &ENetConnection::destroy);
|
||||
ClassDB::bind_method(D_METHOD("connect_to_host", "address", "port", "channels", "data"), &ENetConnection::connect_to_host, DEFVAL(0), DEFVAL(0));
|
||||
ClassDB::bind_method(D_METHOD("service", "timeout"), &ENetConnection::_service, DEFVAL(0));
|
||||
ClassDB::bind_method(D_METHOD("flush"), &ENetConnection::flush);
|
||||
ClassDB::bind_method(D_METHOD("bandwidth_limit", "in_bandwidth", "out_bandwidth"), &ENetConnection::bandwidth_limit, DEFVAL(0), DEFVAL(0));
|
||||
ClassDB::bind_method(D_METHOD("channel_limit", "limit"), &ENetConnection::channel_limit);
|
||||
ClassDB::bind_method(D_METHOD("broadcast", "channel", "packet", "flags"), &ENetConnection::_broadcast);
|
||||
ClassDB::bind_method(D_METHOD("compress", "mode"), &ENetConnection::compress);
|
||||
ClassDB::bind_method(D_METHOD("dtls_server_setup", "server_options"), &ENetConnection::dtls_server_setup);
|
||||
ClassDB::bind_method(D_METHOD("dtls_client_setup", "hostname", "client_options"), &ENetConnection::dtls_client_setup, DEFVAL(Ref<TLSOptions>()));
|
||||
ClassDB::bind_method(D_METHOD("refuse_new_connections", "refuse"), &ENetConnection::refuse_new_connections);
|
||||
ClassDB::bind_method(D_METHOD("pop_statistic", "statistic"), &ENetConnection::pop_statistic);
|
||||
ClassDB::bind_method(D_METHOD("get_max_channels"), &ENetConnection::get_max_channels);
|
||||
ClassDB::bind_method(D_METHOD("get_local_port"), &ENetConnection::get_local_port);
|
||||
ClassDB::bind_method(D_METHOD("get_peers"), &ENetConnection::_get_peers);
|
||||
ClassDB::bind_method(D_METHOD("socket_send", "destination_address", "destination_port", "packet"), &ENetConnection::socket_send);
|
||||
|
||||
BIND_ENUM_CONSTANT(COMPRESS_NONE);
|
||||
BIND_ENUM_CONSTANT(COMPRESS_RANGE_CODER);
|
||||
BIND_ENUM_CONSTANT(COMPRESS_FASTLZ);
|
||||
BIND_ENUM_CONSTANT(COMPRESS_ZLIB);
|
||||
BIND_ENUM_CONSTANT(COMPRESS_ZSTD);
|
||||
|
||||
BIND_ENUM_CONSTANT(EVENT_ERROR);
|
||||
BIND_ENUM_CONSTANT(EVENT_NONE);
|
||||
BIND_ENUM_CONSTANT(EVENT_CONNECT);
|
||||
BIND_ENUM_CONSTANT(EVENT_DISCONNECT);
|
||||
BIND_ENUM_CONSTANT(EVENT_RECEIVE);
|
||||
|
||||
BIND_ENUM_CONSTANT(HOST_TOTAL_SENT_DATA);
|
||||
BIND_ENUM_CONSTANT(HOST_TOTAL_SENT_PACKETS);
|
||||
BIND_ENUM_CONSTANT(HOST_TOTAL_RECEIVED_DATA);
|
||||
BIND_ENUM_CONSTANT(HOST_TOTAL_RECEIVED_PACKETS);
|
||||
}
|
||||
|
||||
ENetConnection::~ENetConnection() {
|
||||
if (host) {
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
|
||||
size_t ENetConnection::Compressor::enet_compress(void *context, const ENetBuffer *inBuffers, size_t inBufferCount, size_t inLimit, enet_uint8 *outData, size_t outLimit) {
|
||||
Compressor *compressor = (Compressor *)(context);
|
||||
|
||||
if (size_t(compressor->src_mem.size()) < inLimit) {
|
||||
compressor->src_mem.resize(inLimit);
|
||||
}
|
||||
|
||||
int total = inLimit;
|
||||
int ofs = 0;
|
||||
while (total) {
|
||||
for (size_t i = 0; i < inBufferCount; i++) {
|
||||
int to_copy = MIN(total, int(inBuffers[i].dataLength));
|
||||
memcpy(&compressor->src_mem.write[ofs], inBuffers[i].data, to_copy);
|
||||
ofs += to_copy;
|
||||
total -= to_copy;
|
||||
}
|
||||
}
|
||||
|
||||
Compression::Mode mode;
|
||||
|
||||
switch (compressor->mode) {
|
||||
case COMPRESS_FASTLZ: {
|
||||
mode = Compression::MODE_FASTLZ;
|
||||
} break;
|
||||
case COMPRESS_ZLIB: {
|
||||
mode = Compression::MODE_DEFLATE;
|
||||
} break;
|
||||
case COMPRESS_ZSTD: {
|
||||
mode = Compression::MODE_ZSTD;
|
||||
} break;
|
||||
default: {
|
||||
ERR_FAIL_V_MSG(0, vformat("Invalid ENet compression mode: %d", compressor->mode));
|
||||
}
|
||||
}
|
||||
|
||||
int req_size = Compression::get_max_compressed_buffer_size(ofs, mode);
|
||||
if (compressor->dst_mem.size() < req_size) {
|
||||
compressor->dst_mem.resize(req_size);
|
||||
}
|
||||
int ret = Compression::compress(compressor->dst_mem.ptrw(), compressor->src_mem.ptr(), ofs, mode);
|
||||
|
||||
if (ret < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ret > int(outLimit)) {
|
||||
return 0; // Do not bother
|
||||
}
|
||||
|
||||
memcpy(outData, compressor->dst_mem.ptr(), ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t ENetConnection::Compressor::enet_decompress(void *context, const enet_uint8 *inData, size_t inLimit, enet_uint8 *outData, size_t outLimit) {
|
||||
Compressor *compressor = (Compressor *)(context);
|
||||
int ret = -1;
|
||||
switch (compressor->mode) {
|
||||
case COMPRESS_FASTLZ: {
|
||||
ret = Compression::decompress(outData, outLimit, inData, inLimit, Compression::MODE_FASTLZ);
|
||||
} break;
|
||||
case COMPRESS_ZLIB: {
|
||||
ret = Compression::decompress(outData, outLimit, inData, inLimit, Compression::MODE_DEFLATE);
|
||||
} break;
|
||||
case COMPRESS_ZSTD: {
|
||||
ret = Compression::decompress(outData, outLimit, inData, inLimit, Compression::MODE_ZSTD);
|
||||
} break;
|
||||
default: {
|
||||
}
|
||||
}
|
||||
if (ret < 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
void ENetConnection::Compressor::setup(ENetHost *p_host, CompressionMode p_mode) {
|
||||
ERR_FAIL_NULL(p_host);
|
||||
switch (p_mode) {
|
||||
case COMPRESS_NONE: {
|
||||
enet_host_compress(p_host, nullptr);
|
||||
} break;
|
||||
case COMPRESS_RANGE_CODER: {
|
||||
enet_host_compress_with_range_coder(p_host);
|
||||
} break;
|
||||
case COMPRESS_FASTLZ:
|
||||
case COMPRESS_ZLIB:
|
||||
case COMPRESS_ZSTD: {
|
||||
Compressor *compressor = memnew(Compressor(p_mode));
|
||||
enet_host_compress(p_host, &(compressor->enet_compressor));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
ENetConnection::Compressor::Compressor(CompressionMode p_mode) {
|
||||
mode = p_mode;
|
||||
enet_compressor.context = this;
|
||||
enet_compressor.compress = enet_compress;
|
||||
enet_compressor.decompress = enet_decompress;
|
||||
enet_compressor.destroy = enet_compressor_destroy;
|
||||
}
|
||||
144
engine/modules/enet/enet_connection.h
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
/**************************************************************************/
|
||||
/* enet_connection.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 ENET_CONNECTION_H
|
||||
#define ENET_CONNECTION_H
|
||||
|
||||
#include "enet_packet_peer.h"
|
||||
|
||||
#include "core/crypto/crypto.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
#include <enet/enet.h>
|
||||
|
||||
template <typename T>
|
||||
class TypedArray;
|
||||
|
||||
class ENetConnection : public RefCounted {
|
||||
GDCLASS(ENetConnection, RefCounted);
|
||||
|
||||
public:
|
||||
enum CompressionMode {
|
||||
COMPRESS_NONE = 0,
|
||||
COMPRESS_RANGE_CODER,
|
||||
COMPRESS_FASTLZ,
|
||||
COMPRESS_ZLIB,
|
||||
COMPRESS_ZSTD,
|
||||
};
|
||||
|
||||
enum HostStatistic {
|
||||
HOST_TOTAL_SENT_DATA,
|
||||
HOST_TOTAL_SENT_PACKETS,
|
||||
HOST_TOTAL_RECEIVED_DATA,
|
||||
HOST_TOTAL_RECEIVED_PACKETS,
|
||||
};
|
||||
|
||||
enum EventType {
|
||||
EVENT_ERROR = -1,
|
||||
EVENT_NONE = 0,
|
||||
EVENT_CONNECT,
|
||||
EVENT_DISCONNECT,
|
||||
EVENT_RECEIVE,
|
||||
};
|
||||
|
||||
struct Event {
|
||||
Ref<ENetPacketPeer> peer;
|
||||
enet_uint8 channel_id = 0;
|
||||
enet_uint32 data = 0;
|
||||
ENetPacket *packet = nullptr;
|
||||
};
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
private:
|
||||
ENetHost *host = nullptr;
|
||||
List<Ref<ENetPacketPeer>> peers;
|
||||
|
||||
EventType _parse_event(const ENetEvent &p_event, Event &r_event);
|
||||
Error _create(ENetAddress *p_address, int p_max_peers, int p_max_channels, int p_in_bandwidth, int p_out_bandwidth);
|
||||
Array _service(int p_timeout = 0);
|
||||
void _broadcast(int p_channel, PackedByteArray p_packet, int p_flags);
|
||||
TypedArray<ENetPacketPeer> _get_peers();
|
||||
|
||||
class Compressor {
|
||||
private:
|
||||
CompressionMode mode = COMPRESS_NONE;
|
||||
Vector<uint8_t> src_mem;
|
||||
Vector<uint8_t> dst_mem;
|
||||
ENetCompressor enet_compressor;
|
||||
|
||||
Compressor(CompressionMode mode);
|
||||
|
||||
static size_t enet_compress(void *context, const ENetBuffer *inBuffers, size_t inBufferCount, size_t inLimit, enet_uint8 *outData, size_t outLimit);
|
||||
static size_t enet_decompress(void *context, const enet_uint8 *inData, size_t inLimit, enet_uint8 *outData, size_t outLimit);
|
||||
static void enet_compressor_destroy(void *context) {
|
||||
memdelete((Compressor *)context);
|
||||
}
|
||||
|
||||
public:
|
||||
static void setup(ENetHost *p_host, CompressionMode p_mode);
|
||||
};
|
||||
|
||||
public:
|
||||
void broadcast(enet_uint8 p_channel, ENetPacket *p_packet);
|
||||
void socket_send(const String &p_address, int p_port, const PackedByteArray &p_packet);
|
||||
Error create_host_bound(const IPAddress &p_bind_address = IPAddress("*"), int p_port = 0, int p_max_peers = 32, int p_max_channels = 0, int p_in_bandwidth = 0, int p_out_bandwidth = 0);
|
||||
Error create_host(int p_max_peers = 32, int p_max_channels = 0, int p_in_bandwidth = 0, int p_out_bandwidth = 0);
|
||||
void destroy();
|
||||
Ref<ENetPacketPeer> connect_to_host(const String &p_address, int p_port, int p_channels, int p_data = 0);
|
||||
EventType service(int p_timeout, Event &r_event);
|
||||
int check_events(EventType &r_type, Event &r_event);
|
||||
void flush();
|
||||
void bandwidth_limit(int p_in_bandwidth = 0, int p_out_bandwidth = 0);
|
||||
void channel_limit(int p_max_channels);
|
||||
void bandwidth_throttle();
|
||||
void compress(CompressionMode p_mode);
|
||||
double pop_statistic(HostStatistic p_stat);
|
||||
int get_max_channels() const;
|
||||
|
||||
// Extras
|
||||
void get_peers(List<Ref<ENetPacketPeer>> &r_peers);
|
||||
int get_local_port() const;
|
||||
|
||||
// Godot additions
|
||||
Error dtls_server_setup(const Ref<TLSOptions> &p_options);
|
||||
Error dtls_client_setup(const String &p_hostname, const Ref<TLSOptions> &p_options);
|
||||
void refuse_new_connections(bool p_refuse);
|
||||
|
||||
ENetConnection() {}
|
||||
~ENetConnection();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(ENetConnection::CompressionMode);
|
||||
VARIANT_ENUM_CAST(ENetConnection::EventType);
|
||||
VARIANT_ENUM_CAST(ENetConnection::HostStatistic);
|
||||
|
||||
#endif // ENET_CONNECTION_H
|
||||
502
engine/modules/enet/enet_multiplayer_peer.cpp
Normal file
|
|
@ -0,0 +1,502 @@
|
|||
/**************************************************************************/
|
||||
/* enet_multiplayer_peer.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 "enet_multiplayer_peer.h"
|
||||
|
||||
#include "core/io/ip.h"
|
||||
#include "core/io/marshalls.h"
|
||||
#include "core/os/os.h"
|
||||
|
||||
void ENetMultiplayerPeer::set_target_peer(int p_peer) {
|
||||
target_peer = p_peer;
|
||||
}
|
||||
|
||||
int ENetMultiplayerPeer::get_packet_peer() const {
|
||||
ERR_FAIL_COND_V_MSG(!_is_active(), 1, "The multiplayer instance isn't currently active.");
|
||||
ERR_FAIL_COND_V(incoming_packets.is_empty(), 1);
|
||||
|
||||
return incoming_packets.front()->get().from;
|
||||
}
|
||||
|
||||
MultiplayerPeer::TransferMode ENetMultiplayerPeer::get_packet_mode() const {
|
||||
ERR_FAIL_COND_V_MSG(!_is_active(), TRANSFER_MODE_RELIABLE, "The multiplayer instance isn't currently active.");
|
||||
ERR_FAIL_COND_V(incoming_packets.is_empty(), TRANSFER_MODE_RELIABLE);
|
||||
return incoming_packets.front()->get().transfer_mode;
|
||||
}
|
||||
|
||||
int ENetMultiplayerPeer::get_packet_channel() const {
|
||||
ERR_FAIL_COND_V_MSG(!_is_active(), 1, "The multiplayer instance isn't currently active.");
|
||||
ERR_FAIL_COND_V(incoming_packets.is_empty(), 1);
|
||||
int ch = incoming_packets.front()->get().channel;
|
||||
if (ch >= SYSCH_MAX) { // First 2 channels are reserved.
|
||||
return ch - SYSCH_MAX + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Error ENetMultiplayerPeer::create_server(int p_port, int p_max_clients, int p_max_channels, int p_in_bandwidth, int p_out_bandwidth) {
|
||||
ERR_FAIL_COND_V_MSG(_is_active(), ERR_ALREADY_IN_USE, "The multiplayer instance is already active.");
|
||||
set_refuse_new_connections(false);
|
||||
Ref<ENetConnection> host;
|
||||
host.instantiate();
|
||||
Error err = host->create_host_bound(bind_ip, p_port, p_max_clients, 0, p_max_channels > 0 ? p_max_channels + SYSCH_MAX : 0, p_out_bandwidth);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
active_mode = MODE_SERVER;
|
||||
unique_id = 1;
|
||||
connection_status = CONNECTION_CONNECTED;
|
||||
hosts[0] = host;
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error ENetMultiplayerPeer::create_client(const String &p_address, int p_port, int p_channel_count, int p_in_bandwidth, int p_out_bandwidth, int p_local_port) {
|
||||
ERR_FAIL_COND_V_MSG(_is_active(), ERR_ALREADY_IN_USE, "The multiplayer instance is already active.");
|
||||
set_refuse_new_connections(false);
|
||||
Ref<ENetConnection> host;
|
||||
host.instantiate();
|
||||
Error err;
|
||||
if (p_local_port) {
|
||||
err = host->create_host_bound(bind_ip, p_local_port, 1, 0, p_in_bandwidth, p_out_bandwidth);
|
||||
} else {
|
||||
err = host->create_host(1, 0, p_in_bandwidth, p_out_bandwidth);
|
||||
}
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
unique_id = generate_unique_id();
|
||||
|
||||
Ref<ENetPacketPeer> peer = host->connect_to_host(p_address, p_port, p_channel_count > 0 ? p_channel_count + SYSCH_MAX : 0, unique_id);
|
||||
if (peer.is_null()) {
|
||||
host->destroy();
|
||||
ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Couldn't connect to the ENet multiplayer server.");
|
||||
}
|
||||
|
||||
// Need to wait for CONNECT event.
|
||||
connection_status = CONNECTION_CONNECTING;
|
||||
active_mode = MODE_CLIENT;
|
||||
peers[1] = peer;
|
||||
hosts[0] = host;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error ENetMultiplayerPeer::create_mesh(int p_id) {
|
||||
ERR_FAIL_COND_V_MSG(p_id <= 0, ERR_INVALID_PARAMETER, "The unique ID must be greater then 0");
|
||||
ERR_FAIL_COND_V_MSG(_is_active(), ERR_ALREADY_IN_USE, "The multiplayer instance is already active.");
|
||||
active_mode = MODE_MESH;
|
||||
unique_id = p_id;
|
||||
connection_status = CONNECTION_CONNECTED;
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error ENetMultiplayerPeer::add_mesh_peer(int p_id, Ref<ENetConnection> p_host) {
|
||||
ERR_FAIL_COND_V(p_host.is_null(), ERR_INVALID_PARAMETER);
|
||||
ERR_FAIL_COND_V_MSG(active_mode != MODE_MESH, ERR_UNCONFIGURED, "The multiplayer instance is not configured as a mesh. Call 'create_mesh' first.");
|
||||
List<Ref<ENetPacketPeer>> host_peers;
|
||||
p_host->get_peers(host_peers);
|
||||
ERR_FAIL_COND_V_MSG(host_peers.size() != 1 || host_peers.front()->get()->get_state() != ENetPacketPeer::STATE_CONNECTED, ERR_INVALID_PARAMETER, "The provided host must have exactly one peer in the connected state.");
|
||||
hosts[p_id] = p_host;
|
||||
peers[p_id] = host_peers.front()->get();
|
||||
emit_signal(SNAME("peer_connected"), p_id);
|
||||
return OK;
|
||||
}
|
||||
|
||||
void ENetMultiplayerPeer::_store_packet(int32_t p_source, ENetConnection::Event &p_event) {
|
||||
Packet packet;
|
||||
packet.packet = p_event.packet;
|
||||
packet.channel = p_event.channel_id;
|
||||
packet.from = p_source;
|
||||
if (p_event.packet->flags & ENET_PACKET_FLAG_RELIABLE) {
|
||||
packet.transfer_mode = TRANSFER_MODE_RELIABLE;
|
||||
} else if (p_event.packet->flags & ENET_PACKET_FLAG_UNSEQUENCED) {
|
||||
packet.transfer_mode = TRANSFER_MODE_UNRELIABLE;
|
||||
} else {
|
||||
packet.transfer_mode = TRANSFER_MODE_UNRELIABLE_ORDERED;
|
||||
}
|
||||
packet.packet->referenceCount++;
|
||||
incoming_packets.push_back(packet);
|
||||
}
|
||||
|
||||
void ENetMultiplayerPeer::_disconnect_inactive_peers() {
|
||||
HashSet<int> to_drop;
|
||||
for (const KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
|
||||
if (E.value->is_active()) {
|
||||
continue;
|
||||
}
|
||||
to_drop.insert(E.key);
|
||||
}
|
||||
for (const int &P : to_drop) {
|
||||
peers.erase(P);
|
||||
if (hosts.has(P)) {
|
||||
hosts.erase(P);
|
||||
}
|
||||
ERR_CONTINUE(active_mode == MODE_CLIENT && P != TARGET_PEER_SERVER);
|
||||
emit_signal(SNAME("peer_disconnected"), P);
|
||||
}
|
||||
}
|
||||
|
||||
void ENetMultiplayerPeer::poll() {
|
||||
ERR_FAIL_COND_MSG(!_is_active(), "The multiplayer instance isn't currently active.");
|
||||
|
||||
_pop_current_packet();
|
||||
|
||||
_disconnect_inactive_peers();
|
||||
|
||||
switch (active_mode) {
|
||||
case MODE_CLIENT: {
|
||||
if (!peers.has(1)) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
ENetConnection::Event event;
|
||||
ENetConnection::EventType ret = hosts[0]->service(0, event);
|
||||
do {
|
||||
if (ret == ENetConnection::EVENT_CONNECT) {
|
||||
connection_status = CONNECTION_CONNECTED;
|
||||
emit_signal(SNAME("peer_connected"), 1);
|
||||
} else if (ret == ENetConnection::EVENT_DISCONNECT) {
|
||||
if (connection_status == CONNECTION_CONNECTED) {
|
||||
// Client just disconnected from server.
|
||||
emit_signal(SNAME("peer_disconnected"), 1);
|
||||
}
|
||||
close();
|
||||
} else if (ret == ENetConnection::EVENT_RECEIVE) {
|
||||
_store_packet(1, event);
|
||||
} else if (ret != ENetConnection::EVENT_NONE) {
|
||||
close(); // Error.
|
||||
}
|
||||
} while (hosts.has(0) && hosts[0]->check_events(ret, event) > 0);
|
||||
} break;
|
||||
case MODE_SERVER: {
|
||||
ENetConnection::Event event;
|
||||
ENetConnection::EventType ret = hosts[0]->service(0, event);
|
||||
do {
|
||||
if (ret == ENetConnection::EVENT_CONNECT) {
|
||||
if (is_refusing_new_connections()) {
|
||||
event.peer->reset();
|
||||
continue;
|
||||
}
|
||||
// Client joined with invalid ID, probably trying to exploit us.
|
||||
if (event.data < 2 || peers.has((int)event.data)) {
|
||||
event.peer->reset();
|
||||
continue;
|
||||
}
|
||||
int id = event.data;
|
||||
event.peer->set_meta(SNAME("_net_id"), id);
|
||||
peers[id] = event.peer;
|
||||
emit_signal(SNAME("peer_connected"), id);
|
||||
} else if (ret == ENetConnection::EVENT_DISCONNECT) {
|
||||
int id = event.peer->get_meta(SNAME("_net_id"));
|
||||
if (!peers.has(id)) {
|
||||
// Never fully connected.
|
||||
continue;
|
||||
}
|
||||
emit_signal(SNAME("peer_disconnected"), id);
|
||||
peers.erase(id);
|
||||
} else if (ret == ENetConnection::EVENT_RECEIVE) {
|
||||
int32_t source = event.peer->get_meta(SNAME("_net_id"));
|
||||
_store_packet(source, event);
|
||||
} else if (ret != ENetConnection::EVENT_NONE) {
|
||||
close(); // Error
|
||||
}
|
||||
} while (hosts.has(0) && hosts[0]->check_events(ret, event) > 0);
|
||||
} break;
|
||||
case MODE_MESH: {
|
||||
HashSet<int> to_drop;
|
||||
for (KeyValue<int, Ref<ENetConnection>> &E : hosts) {
|
||||
ENetConnection::Event event;
|
||||
ENetConnection::EventType ret = E.value->service(0, event);
|
||||
do {
|
||||
if (ret == ENetConnection::EVENT_CONNECT) {
|
||||
event.peer->reset();
|
||||
} else if (ret == ENetConnection::EVENT_RECEIVE) {
|
||||
_store_packet(E.key, event);
|
||||
} else if (ret == ENetConnection::EVENT_NONE) {
|
||||
break; // Keep polling the others.
|
||||
} else {
|
||||
to_drop.insert(E.key); // Error or disconnect.
|
||||
break; // Keep polling the others.
|
||||
}
|
||||
} while (E.value->check_events(ret, event) > 0);
|
||||
}
|
||||
for (const int &P : to_drop) {
|
||||
if (peers.has(P)) {
|
||||
emit_signal(SNAME("peer_disconnected"), P);
|
||||
peers.erase(P);
|
||||
}
|
||||
hosts.erase(P);
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool ENetMultiplayerPeer::is_server() const {
|
||||
return active_mode == MODE_SERVER;
|
||||
}
|
||||
|
||||
bool ENetMultiplayerPeer::is_server_relay_supported() const {
|
||||
return active_mode == MODE_SERVER || active_mode == MODE_CLIENT;
|
||||
}
|
||||
|
||||
void ENetMultiplayerPeer::disconnect_peer(int p_peer, bool p_force) {
|
||||
ERR_FAIL_COND(!_is_active() || !peers.has(p_peer));
|
||||
peers[p_peer]->peer_disconnect(0); // Will be removed during next poll.
|
||||
if (active_mode == MODE_CLIENT || active_mode == MODE_SERVER) {
|
||||
hosts[0]->flush();
|
||||
} else {
|
||||
ERR_FAIL_COND(!hosts.has(p_peer));
|
||||
hosts[p_peer]->flush();
|
||||
}
|
||||
if (p_force) {
|
||||
peers.erase(p_peer);
|
||||
if (hosts.has(p_peer)) {
|
||||
hosts.erase(p_peer);
|
||||
}
|
||||
if (active_mode == MODE_CLIENT) {
|
||||
hosts.clear(); // Avoid flushing again.
|
||||
close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ENetMultiplayerPeer::close() {
|
||||
if (!_is_active()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_pop_current_packet();
|
||||
|
||||
for (KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
|
||||
if (E.value.is_valid() && E.value->get_state() == ENetPacketPeer::STATE_CONNECTED) {
|
||||
E.value->peer_disconnect_now(0);
|
||||
}
|
||||
}
|
||||
for (KeyValue<int, Ref<ENetConnection>> &E : hosts) {
|
||||
E.value->flush();
|
||||
}
|
||||
|
||||
active_mode = MODE_NONE;
|
||||
incoming_packets.clear();
|
||||
peers.clear();
|
||||
hosts.clear();
|
||||
unique_id = 0;
|
||||
connection_status = CONNECTION_DISCONNECTED;
|
||||
set_refuse_new_connections(false);
|
||||
}
|
||||
|
||||
int ENetMultiplayerPeer::get_available_packet_count() const {
|
||||
return incoming_packets.size();
|
||||
}
|
||||
|
||||
Error ENetMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
|
||||
ERR_FAIL_COND_V_MSG(incoming_packets.is_empty(), ERR_UNAVAILABLE, "No incoming packets available.");
|
||||
|
||||
_pop_current_packet();
|
||||
|
||||
current_packet = incoming_packets.front()->get();
|
||||
incoming_packets.pop_front();
|
||||
|
||||
*r_buffer = (const uint8_t *)(current_packet.packet->data);
|
||||
r_buffer_size = current_packet.packet->dataLength;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error ENetMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
|
||||
ERR_FAIL_COND_V_MSG(!_is_active(), ERR_UNCONFIGURED, "The multiplayer instance isn't currently active.");
|
||||
ERR_FAIL_COND_V_MSG(connection_status != CONNECTION_CONNECTED, ERR_UNCONFIGURED, "The multiplayer instance isn't currently connected to any server or client.");
|
||||
ERR_FAIL_COND_V_MSG(target_peer != 0 && !peers.has(ABS(target_peer)), ERR_INVALID_PARAMETER, vformat("Invalid target peer: %d", target_peer));
|
||||
ERR_FAIL_COND_V(active_mode == MODE_CLIENT && !peers.has(1), ERR_BUG);
|
||||
|
||||
int packet_flags = 0;
|
||||
int channel = SYSCH_RELIABLE;
|
||||
int tr_channel = get_transfer_channel();
|
||||
switch (get_transfer_mode()) {
|
||||
case TRANSFER_MODE_UNRELIABLE: {
|
||||
packet_flags = ENET_PACKET_FLAG_UNSEQUENCED | ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT;
|
||||
channel = SYSCH_UNRELIABLE;
|
||||
} break;
|
||||
case TRANSFER_MODE_UNRELIABLE_ORDERED: {
|
||||
packet_flags = ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT;
|
||||
channel = SYSCH_UNRELIABLE;
|
||||
} break;
|
||||
case TRANSFER_MODE_RELIABLE: {
|
||||
packet_flags = ENET_PACKET_FLAG_RELIABLE;
|
||||
channel = SYSCH_RELIABLE;
|
||||
} break;
|
||||
}
|
||||
if (tr_channel > 0) {
|
||||
channel = SYSCH_MAX + tr_channel - 1;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if ((packet_flags & ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT) && p_buffer_size > ENET_HOST_DEFAULT_MTU) {
|
||||
WARN_PRINT_ONCE(vformat("Sending %d bytes unreliably which is above the MTU (%d), this will result in higher packet loss", p_buffer_size, ENET_HOST_DEFAULT_MTU));
|
||||
}
|
||||
#endif
|
||||
|
||||
ENetPacket *packet = enet_packet_create(nullptr, p_buffer_size, packet_flags);
|
||||
memcpy(&packet->data[0], p_buffer, p_buffer_size);
|
||||
|
||||
if (is_server()) {
|
||||
if (target_peer == 0) {
|
||||
hosts[0]->broadcast(channel, packet);
|
||||
|
||||
} else if (target_peer < 0) {
|
||||
// Send to all but one and make copies for sending.
|
||||
int exclude = -target_peer;
|
||||
for (KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
|
||||
if (E.key == exclude) {
|
||||
continue;
|
||||
}
|
||||
E.value->send(channel, packet);
|
||||
}
|
||||
_destroy_unused(packet);
|
||||
} else {
|
||||
peers[target_peer]->send(channel, packet);
|
||||
}
|
||||
ERR_FAIL_COND_V(!hosts.has(0), ERR_BUG);
|
||||
hosts[0]->flush();
|
||||
|
||||
} else if (active_mode == MODE_CLIENT) {
|
||||
peers[1]->send(channel, packet); // Send to server for broadcast.
|
||||
ERR_FAIL_COND_V(!hosts.has(0), ERR_BUG);
|
||||
hosts[0]->flush();
|
||||
|
||||
} else {
|
||||
if (target_peer <= 0) {
|
||||
int exclude = ABS(target_peer);
|
||||
for (KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
|
||||
if (E.key == exclude) {
|
||||
continue;
|
||||
}
|
||||
E.value->send(channel, packet);
|
||||
ERR_CONTINUE(!hosts.has(E.key));
|
||||
hosts[E.key]->flush();
|
||||
}
|
||||
_destroy_unused(packet);
|
||||
} else {
|
||||
peers[target_peer]->send(channel, packet);
|
||||
ERR_FAIL_COND_V(!hosts.has(target_peer), ERR_BUG);
|
||||
hosts[target_peer]->flush();
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
int ENetMultiplayerPeer::get_max_packet_size() const {
|
||||
return 1 << 24; // Anything is good
|
||||
}
|
||||
|
||||
void ENetMultiplayerPeer::_pop_current_packet() {
|
||||
if (current_packet.packet) {
|
||||
current_packet.packet->referenceCount--;
|
||||
_destroy_unused(current_packet.packet);
|
||||
current_packet.packet = nullptr;
|
||||
current_packet.from = 0;
|
||||
current_packet.channel = -1;
|
||||
}
|
||||
}
|
||||
|
||||
MultiplayerPeer::ConnectionStatus ENetMultiplayerPeer::get_connection_status() const {
|
||||
return connection_status;
|
||||
}
|
||||
|
||||
int ENetMultiplayerPeer::get_unique_id() const {
|
||||
ERR_FAIL_COND_V_MSG(!_is_active(), 0, "The multiplayer instance isn't currently active.");
|
||||
return unique_id;
|
||||
}
|
||||
|
||||
void ENetMultiplayerPeer::set_refuse_new_connections(bool p_enabled) {
|
||||
#ifdef GODOT_ENET
|
||||
if (_is_active()) {
|
||||
for (KeyValue<int, Ref<ENetConnection>> &E : hosts) {
|
||||
E.value->refuse_new_connections(p_enabled);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
MultiplayerPeer::set_refuse_new_connections(p_enabled);
|
||||
}
|
||||
|
||||
Ref<ENetConnection> ENetMultiplayerPeer::get_host() const {
|
||||
ERR_FAIL_COND_V(!_is_active(), nullptr);
|
||||
ERR_FAIL_COND_V(active_mode == MODE_MESH, nullptr);
|
||||
return hosts[0];
|
||||
}
|
||||
|
||||
Ref<ENetPacketPeer> ENetMultiplayerPeer::get_peer(int p_id) const {
|
||||
ERR_FAIL_COND_V(!_is_active(), nullptr);
|
||||
ERR_FAIL_COND_V(!peers.has(p_id), nullptr);
|
||||
ERR_FAIL_COND_V(active_mode == MODE_CLIENT && p_id != 1, nullptr);
|
||||
return peers[p_id];
|
||||
}
|
||||
|
||||
void ENetMultiplayerPeer::_destroy_unused(ENetPacket *p_packet) {
|
||||
if (p_packet->referenceCount == 0) {
|
||||
enet_packet_destroy(p_packet);
|
||||
}
|
||||
}
|
||||
|
||||
void ENetMultiplayerPeer::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("create_server", "port", "max_clients", "max_channels", "in_bandwidth", "out_bandwidth"), &ENetMultiplayerPeer::create_server, DEFVAL(32), DEFVAL(0), DEFVAL(0), DEFVAL(0));
|
||||
ClassDB::bind_method(D_METHOD("create_client", "address", "port", "channel_count", "in_bandwidth", "out_bandwidth", "local_port"), &ENetMultiplayerPeer::create_client, DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(0));
|
||||
ClassDB::bind_method(D_METHOD("create_mesh", "unique_id"), &ENetMultiplayerPeer::create_mesh);
|
||||
ClassDB::bind_method(D_METHOD("add_mesh_peer", "peer_id", "host"), &ENetMultiplayerPeer::add_mesh_peer);
|
||||
ClassDB::bind_method(D_METHOD("set_bind_ip", "ip"), &ENetMultiplayerPeer::set_bind_ip);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_host"), &ENetMultiplayerPeer::get_host);
|
||||
ClassDB::bind_method(D_METHOD("get_peer", "id"), &ENetMultiplayerPeer::get_peer);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "host", PROPERTY_HINT_RESOURCE_TYPE, "ENetConnection", PROPERTY_USAGE_NONE), "", "get_host");
|
||||
}
|
||||
|
||||
ENetMultiplayerPeer::ENetMultiplayerPeer() {
|
||||
bind_ip = IPAddress("*");
|
||||
}
|
||||
|
||||
ENetMultiplayerPeer::~ENetMultiplayerPeer() {
|
||||
if (_is_active()) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
// 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 ENetMultiplayerPeer::set_bind_ip(const IPAddress &p_ip) {
|
||||
ERR_FAIL_COND_MSG(!p_ip.is_valid() && !p_ip.is_wildcard(), vformat("Invalid bind IP address: %s", String(p_ip)));
|
||||
|
||||
bind_ip = p_ip;
|
||||
}
|
||||
136
engine/modules/enet/enet_multiplayer_peer.h
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/**************************************************************************/
|
||||
/* enet_multiplayer_peer.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 ENET_MULTIPLAYER_PEER_H
|
||||
#define ENET_MULTIPLAYER_PEER_H
|
||||
|
||||
#include "enet_connection.h"
|
||||
|
||||
#include "core/crypto/crypto.h"
|
||||
#include "scene/main/multiplayer_peer.h"
|
||||
|
||||
#include <enet/enet.h>
|
||||
|
||||
class ENetMultiplayerPeer : public MultiplayerPeer {
|
||||
GDCLASS(ENetMultiplayerPeer, MultiplayerPeer);
|
||||
|
||||
private:
|
||||
enum {
|
||||
SYSMSG_ADD_PEER,
|
||||
SYSMSG_REMOVE_PEER
|
||||
};
|
||||
|
||||
enum {
|
||||
SYSCH_RELIABLE = 0,
|
||||
SYSCH_UNRELIABLE = 1,
|
||||
SYSCH_MAX = 2
|
||||
};
|
||||
|
||||
enum Mode {
|
||||
MODE_NONE,
|
||||
MODE_SERVER,
|
||||
MODE_CLIENT,
|
||||
MODE_MESH,
|
||||
};
|
||||
|
||||
Mode active_mode = MODE_NONE;
|
||||
|
||||
uint32_t unique_id = 0;
|
||||
|
||||
int target_peer = 0;
|
||||
|
||||
ConnectionStatus connection_status = CONNECTION_DISCONNECTED;
|
||||
|
||||
HashMap<int, Ref<ENetConnection>> hosts;
|
||||
HashMap<int, Ref<ENetPacketPeer>> peers;
|
||||
|
||||
struct Packet {
|
||||
ENetPacket *packet = nullptr;
|
||||
int from = 0;
|
||||
int channel = 0;
|
||||
TransferMode transfer_mode = TRANSFER_MODE_RELIABLE;
|
||||
};
|
||||
|
||||
List<Packet> incoming_packets;
|
||||
|
||||
Packet current_packet;
|
||||
|
||||
void _store_packet(int32_t p_source, ENetConnection::Event &p_event);
|
||||
void _pop_current_packet();
|
||||
void _disconnect_inactive_peers();
|
||||
void _destroy_unused(ENetPacket *p_packet);
|
||||
_FORCE_INLINE_ bool _is_active() const { return active_mode != MODE_NONE; }
|
||||
|
||||
IPAddress bind_ip;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual void set_target_peer(int p_peer) override;
|
||||
|
||||
virtual int get_packet_peer() const override;
|
||||
virtual TransferMode get_packet_mode() const override;
|
||||
virtual int get_packet_channel() const override;
|
||||
|
||||
virtual void poll() override;
|
||||
virtual void close() override;
|
||||
virtual void disconnect_peer(int p_peer, bool p_force = false) override;
|
||||
|
||||
virtual bool is_server() const override;
|
||||
virtual bool is_server_relay_supported() const override;
|
||||
|
||||
// Overridden so we can instrument the DTLSServer when needed.
|
||||
virtual void set_refuse_new_connections(bool p_enabled) override;
|
||||
|
||||
virtual ConnectionStatus get_connection_status() const override;
|
||||
|
||||
virtual int get_unique_id() const override;
|
||||
|
||||
virtual int get_max_packet_size() const override;
|
||||
virtual int get_available_packet_count() const override;
|
||||
virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override;
|
||||
virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override;
|
||||
|
||||
Error create_server(int p_port, int p_max_clients = 32, int p_max_channels = 0, int p_in_bandwidth = 0, int p_out_bandwidth = 0);
|
||||
Error create_client(const String &p_address, int p_port, int p_channel_count = 0, int p_in_bandwidth = 0, int p_out_bandwidth = 0, int p_local_port = 0);
|
||||
Error create_mesh(int p_id);
|
||||
Error add_mesh_peer(int p_id, Ref<ENetConnection> p_host);
|
||||
|
||||
void set_bind_ip(const IPAddress &p_ip);
|
||||
|
||||
Ref<ENetConnection> get_host() const;
|
||||
Ref<ENetPacketPeer> get_peer(int p_id) const;
|
||||
|
||||
ENetMultiplayerPeer();
|
||||
~ENetMultiplayerPeer();
|
||||
};
|
||||
|
||||
#endif // ENET_MULTIPLAYER_PEER_H
|
||||
265
engine/modules/enet/enet_packet_peer.cpp
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
/**************************************************************************/
|
||||
/* enet_packet_peer.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 "enet_packet_peer.h"
|
||||
|
||||
void ENetPacketPeer::peer_disconnect(int p_data) {
|
||||
ERR_FAIL_NULL(peer);
|
||||
enet_peer_disconnect(peer, p_data);
|
||||
}
|
||||
|
||||
void ENetPacketPeer::peer_disconnect_later(int p_data) {
|
||||
ERR_FAIL_NULL(peer);
|
||||
enet_peer_disconnect_later(peer, p_data);
|
||||
}
|
||||
|
||||
void ENetPacketPeer::peer_disconnect_now(int p_data) {
|
||||
ERR_FAIL_NULL(peer);
|
||||
enet_peer_disconnect_now(peer, p_data);
|
||||
_on_disconnect();
|
||||
}
|
||||
|
||||
void ENetPacketPeer::ping() {
|
||||
ERR_FAIL_NULL(peer);
|
||||
enet_peer_ping(peer);
|
||||
}
|
||||
|
||||
void ENetPacketPeer::ping_interval(int p_interval) {
|
||||
ERR_FAIL_NULL(peer);
|
||||
enet_peer_ping_interval(peer, p_interval);
|
||||
}
|
||||
|
||||
int ENetPacketPeer::send(uint8_t p_channel, ENetPacket *p_packet) {
|
||||
ERR_FAIL_NULL_V(peer, -1);
|
||||
ERR_FAIL_NULL_V(p_packet, -1);
|
||||
ERR_FAIL_COND_V_MSG(p_channel >= peer->channelCount, -1, vformat("Unable to send packet on channel %d, max channels: %d", p_channel, (int)peer->channelCount));
|
||||
return enet_peer_send(peer, p_channel, p_packet);
|
||||
}
|
||||
|
||||
void ENetPacketPeer::reset() {
|
||||
ERR_FAIL_NULL_MSG(peer, "Peer not connected.");
|
||||
enet_peer_reset(peer);
|
||||
_on_disconnect();
|
||||
}
|
||||
|
||||
void ENetPacketPeer::throttle_configure(int p_interval, int p_acceleration, int p_deceleration) {
|
||||
ERR_FAIL_NULL_MSG(peer, "Peer not connected.");
|
||||
enet_peer_throttle_configure(peer, p_interval, p_acceleration, p_deceleration);
|
||||
}
|
||||
|
||||
void ENetPacketPeer::set_timeout(int p_timeout, int p_timeout_min, int p_timeout_max) {
|
||||
ERR_FAIL_NULL_MSG(peer, "Peer not connected.");
|
||||
ERR_FAIL_COND_MSG(p_timeout > p_timeout_min || p_timeout_min > p_timeout_max, "Timeout limit must be less than minimum timeout, which itself must be less than maximum timeout");
|
||||
enet_peer_timeout(peer, p_timeout, p_timeout_min, p_timeout_max);
|
||||
}
|
||||
|
||||
int ENetPacketPeer::get_max_packet_size() const {
|
||||
return 1 << 24;
|
||||
}
|
||||
|
||||
int ENetPacketPeer::get_available_packet_count() const {
|
||||
return packet_queue.size();
|
||||
}
|
||||
|
||||
Error ENetPacketPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
|
||||
ERR_FAIL_NULL_V(peer, ERR_UNCONFIGURED);
|
||||
ERR_FAIL_COND_V(packet_queue.is_empty(), ERR_UNAVAILABLE);
|
||||
if (last_packet) {
|
||||
enet_packet_destroy(last_packet);
|
||||
last_packet = nullptr;
|
||||
}
|
||||
last_packet = packet_queue.front()->get();
|
||||
packet_queue.pop_front();
|
||||
*r_buffer = (const uint8_t *)(last_packet->data);
|
||||
r_buffer_size = last_packet->dataLength;
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error ENetPacketPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
|
||||
ERR_FAIL_NULL_V(peer, ERR_UNCONFIGURED);
|
||||
ENetPacket *packet = enet_packet_create(p_buffer, p_buffer_size, ENET_PACKET_FLAG_RELIABLE);
|
||||
return send(0, packet) < 0 ? FAILED : OK;
|
||||
}
|
||||
|
||||
IPAddress ENetPacketPeer::get_remote_address() const {
|
||||
ERR_FAIL_NULL_V(peer, IPAddress());
|
||||
IPAddress out;
|
||||
#ifdef GODOT_ENET
|
||||
out.set_ipv6((uint8_t *)&(peer->address.host));
|
||||
#else
|
||||
out.set_ipv4((uint8_t *)&(peer->address.host));
|
||||
#endif
|
||||
return out;
|
||||
}
|
||||
|
||||
int ENetPacketPeer::get_remote_port() const {
|
||||
ERR_FAIL_NULL_V(peer, 0);
|
||||
return peer->address.port;
|
||||
}
|
||||
|
||||
bool ENetPacketPeer::is_active() const {
|
||||
return peer != nullptr;
|
||||
}
|
||||
|
||||
double ENetPacketPeer::get_statistic(PeerStatistic p_stat) {
|
||||
ERR_FAIL_NULL_V(peer, 0);
|
||||
switch (p_stat) {
|
||||
case PEER_PACKET_LOSS:
|
||||
return peer->packetLoss;
|
||||
case PEER_PACKET_LOSS_VARIANCE:
|
||||
return peer->packetLossVariance;
|
||||
case PEER_PACKET_LOSS_EPOCH:
|
||||
return peer->packetLossEpoch;
|
||||
case PEER_ROUND_TRIP_TIME:
|
||||
return peer->roundTripTime;
|
||||
case PEER_ROUND_TRIP_TIME_VARIANCE:
|
||||
return peer->roundTripTimeVariance;
|
||||
case PEER_LAST_ROUND_TRIP_TIME:
|
||||
return peer->lastRoundTripTime;
|
||||
case PEER_LAST_ROUND_TRIP_TIME_VARIANCE:
|
||||
return peer->lastRoundTripTimeVariance;
|
||||
case PEER_PACKET_THROTTLE:
|
||||
return peer->packetThrottle;
|
||||
case PEER_PACKET_THROTTLE_LIMIT:
|
||||
return peer->packetThrottleLimit;
|
||||
case PEER_PACKET_THROTTLE_COUNTER:
|
||||
return peer->packetThrottleCounter;
|
||||
case PEER_PACKET_THROTTLE_EPOCH:
|
||||
return peer->packetThrottleEpoch;
|
||||
case PEER_PACKET_THROTTLE_ACCELERATION:
|
||||
return peer->packetThrottleAcceleration;
|
||||
case PEER_PACKET_THROTTLE_DECELERATION:
|
||||
return peer->packetThrottleDeceleration;
|
||||
case PEER_PACKET_THROTTLE_INTERVAL:
|
||||
return peer->packetThrottleInterval;
|
||||
}
|
||||
ERR_FAIL_V(0);
|
||||
}
|
||||
|
||||
ENetPacketPeer::PeerState ENetPacketPeer::get_state() const {
|
||||
if (!is_active()) {
|
||||
return STATE_DISCONNECTED;
|
||||
}
|
||||
return (PeerState)peer->state;
|
||||
}
|
||||
|
||||
int ENetPacketPeer::get_channels() const {
|
||||
ERR_FAIL_NULL_V_MSG(peer, 0, "The ENetConnection instance isn't currently active.");
|
||||
return peer->channelCount;
|
||||
}
|
||||
|
||||
void ENetPacketPeer::_on_disconnect() {
|
||||
if (peer) {
|
||||
peer->data = nullptr;
|
||||
}
|
||||
peer = nullptr;
|
||||
}
|
||||
|
||||
void ENetPacketPeer::_queue_packet(ENetPacket *p_packet) {
|
||||
ERR_FAIL_NULL(peer);
|
||||
packet_queue.push_back(p_packet);
|
||||
}
|
||||
|
||||
Error ENetPacketPeer::_send(int p_channel, PackedByteArray p_packet, int p_flags) {
|
||||
ERR_FAIL_NULL_V_MSG(peer, ERR_UNCONFIGURED, "Peer not connected.");
|
||||
ERR_FAIL_COND_V_MSG(p_channel < 0 || p_channel > (int)peer->channelCount, ERR_INVALID_PARAMETER, "Invalid channel");
|
||||
ERR_FAIL_COND_V_MSG(p_flags & ~FLAG_ALLOWED, ERR_INVALID_PARAMETER, "Invalid flags");
|
||||
ENetPacket *packet = enet_packet_create(p_packet.ptr(), p_packet.size(), p_flags);
|
||||
return send(p_channel, packet) == 0 ? OK : FAILED;
|
||||
}
|
||||
|
||||
void ENetPacketPeer::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("peer_disconnect", "data"), &ENetPacketPeer::peer_disconnect, DEFVAL(0));
|
||||
ClassDB::bind_method(D_METHOD("peer_disconnect_later", "data"), &ENetPacketPeer::peer_disconnect_later, DEFVAL(0));
|
||||
ClassDB::bind_method(D_METHOD("peer_disconnect_now", "data"), &ENetPacketPeer::peer_disconnect_now, DEFVAL(0));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("ping"), &ENetPacketPeer::ping);
|
||||
ClassDB::bind_method(D_METHOD("ping_interval", "ping_interval"), &ENetPacketPeer::ping_interval);
|
||||
ClassDB::bind_method(D_METHOD("reset"), &ENetPacketPeer::reset);
|
||||
ClassDB::bind_method(D_METHOD("send", "channel", "packet", "flags"), &ENetPacketPeer::_send);
|
||||
ClassDB::bind_method(D_METHOD("throttle_configure", "interval", "acceleration", "deceleration"), &ENetPacketPeer::throttle_configure);
|
||||
ClassDB::bind_method(D_METHOD("set_timeout", "timeout", "timeout_min", "timeout_max"), &ENetPacketPeer::set_timeout);
|
||||
ClassDB::bind_method(D_METHOD("get_remote_address"), &ENetPacketPeer::get_remote_address);
|
||||
ClassDB::bind_method(D_METHOD("get_remote_port"), &ENetPacketPeer::get_remote_port);
|
||||
ClassDB::bind_method(D_METHOD("get_statistic", "statistic"), &ENetPacketPeer::get_statistic);
|
||||
ClassDB::bind_method(D_METHOD("get_state"), &ENetPacketPeer::get_state);
|
||||
ClassDB::bind_method(D_METHOD("get_channels"), &ENetPacketPeer::get_channels);
|
||||
ClassDB::bind_method(D_METHOD("is_active"), &ENetPacketPeer::is_active);
|
||||
|
||||
BIND_ENUM_CONSTANT(STATE_DISCONNECTED);
|
||||
BIND_ENUM_CONSTANT(STATE_CONNECTING);
|
||||
BIND_ENUM_CONSTANT(STATE_ACKNOWLEDGING_CONNECT);
|
||||
BIND_ENUM_CONSTANT(STATE_CONNECTION_PENDING);
|
||||
BIND_ENUM_CONSTANT(STATE_CONNECTION_SUCCEEDED);
|
||||
BIND_ENUM_CONSTANT(STATE_CONNECTED);
|
||||
BIND_ENUM_CONSTANT(STATE_DISCONNECT_LATER);
|
||||
BIND_ENUM_CONSTANT(STATE_DISCONNECTING);
|
||||
BIND_ENUM_CONSTANT(STATE_ACKNOWLEDGING_DISCONNECT);
|
||||
BIND_ENUM_CONSTANT(STATE_ZOMBIE);
|
||||
|
||||
BIND_ENUM_CONSTANT(PEER_PACKET_LOSS);
|
||||
BIND_ENUM_CONSTANT(PEER_PACKET_LOSS_VARIANCE);
|
||||
BIND_ENUM_CONSTANT(PEER_PACKET_LOSS_EPOCH);
|
||||
BIND_ENUM_CONSTANT(PEER_ROUND_TRIP_TIME);
|
||||
BIND_ENUM_CONSTANT(PEER_ROUND_TRIP_TIME_VARIANCE);
|
||||
BIND_ENUM_CONSTANT(PEER_LAST_ROUND_TRIP_TIME);
|
||||
BIND_ENUM_CONSTANT(PEER_LAST_ROUND_TRIP_TIME_VARIANCE);
|
||||
BIND_ENUM_CONSTANT(PEER_PACKET_THROTTLE);
|
||||
BIND_ENUM_CONSTANT(PEER_PACKET_THROTTLE_LIMIT);
|
||||
BIND_ENUM_CONSTANT(PEER_PACKET_THROTTLE_COUNTER);
|
||||
BIND_ENUM_CONSTANT(PEER_PACKET_THROTTLE_EPOCH);
|
||||
BIND_ENUM_CONSTANT(PEER_PACKET_THROTTLE_ACCELERATION);
|
||||
BIND_ENUM_CONSTANT(PEER_PACKET_THROTTLE_DECELERATION);
|
||||
BIND_ENUM_CONSTANT(PEER_PACKET_THROTTLE_INTERVAL);
|
||||
|
||||
BIND_CONSTANT(PACKET_LOSS_SCALE);
|
||||
BIND_CONSTANT(PACKET_THROTTLE_SCALE);
|
||||
|
||||
BIND_CONSTANT(FLAG_RELIABLE);
|
||||
BIND_CONSTANT(FLAG_UNSEQUENCED);
|
||||
BIND_CONSTANT(FLAG_UNRELIABLE_FRAGMENT);
|
||||
}
|
||||
|
||||
ENetPacketPeer::ENetPacketPeer(ENetPeer *p_peer) {
|
||||
peer = p_peer;
|
||||
peer->data = this;
|
||||
}
|
||||
|
||||
ENetPacketPeer::~ENetPacketPeer() {
|
||||
_on_disconnect();
|
||||
if (last_packet) {
|
||||
enet_packet_destroy(last_packet);
|
||||
last_packet = nullptr;
|
||||
}
|
||||
for (List<ENetPacket *>::Element *E = packet_queue.front(); E; E = E->next()) {
|
||||
enet_packet_destroy(E->get());
|
||||
}
|
||||
packet_queue.clear();
|
||||
}
|
||||
131
engine/modules/enet/enet_packet_peer.h
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
/**************************************************************************/
|
||||
/* enet_packet_peer.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 ENET_PACKET_PEER_H
|
||||
#define ENET_PACKET_PEER_H
|
||||
|
||||
#include "core/io/packet_peer.h"
|
||||
|
||||
#include <enet/enet.h>
|
||||
|
||||
class ENetPacketPeer : public PacketPeer {
|
||||
GDCLASS(ENetPacketPeer, PacketPeer);
|
||||
|
||||
private:
|
||||
ENetPeer *peer = nullptr;
|
||||
List<ENetPacket *> packet_queue;
|
||||
ENetPacket *last_packet = nullptr;
|
||||
|
||||
static void _bind_methods();
|
||||
Error _send(int p_channel, PackedByteArray p_packet, int p_flags);
|
||||
|
||||
protected:
|
||||
friend class ENetConnection;
|
||||
// Internally used by ENetConnection during service, destroy, etc.
|
||||
void _on_disconnect();
|
||||
void _queue_packet(ENetPacket *p_packet);
|
||||
|
||||
public:
|
||||
enum {
|
||||
PACKET_THROTTLE_SCALE = ENET_PEER_PACKET_THROTTLE_SCALE,
|
||||
PACKET_LOSS_SCALE = ENET_PEER_PACKET_LOSS_SCALE,
|
||||
};
|
||||
|
||||
enum {
|
||||
FLAG_RELIABLE = ENET_PACKET_FLAG_RELIABLE,
|
||||
FLAG_UNSEQUENCED = ENET_PACKET_FLAG_UNSEQUENCED,
|
||||
FLAG_UNRELIABLE_FRAGMENT = ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT,
|
||||
FLAG_ALLOWED = ENET_PACKET_FLAG_RELIABLE | ENET_PACKET_FLAG_UNSEQUENCED | ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT,
|
||||
};
|
||||
|
||||
enum PeerState {
|
||||
STATE_DISCONNECTED = ENET_PEER_STATE_DISCONNECTED,
|
||||
STATE_CONNECTING = ENET_PEER_STATE_CONNECTING,
|
||||
STATE_ACKNOWLEDGING_CONNECT = ENET_PEER_STATE_ACKNOWLEDGING_CONNECT,
|
||||
STATE_CONNECTION_PENDING = ENET_PEER_STATE_CONNECTION_PENDING,
|
||||
STATE_CONNECTION_SUCCEEDED = ENET_PEER_STATE_CONNECTION_SUCCEEDED,
|
||||
STATE_CONNECTED = ENET_PEER_STATE_CONNECTED,
|
||||
STATE_DISCONNECT_LATER = ENET_PEER_STATE_DISCONNECT_LATER,
|
||||
STATE_DISCONNECTING = ENET_PEER_STATE_DISCONNECTING,
|
||||
STATE_ACKNOWLEDGING_DISCONNECT = ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT,
|
||||
STATE_ZOMBIE = ENET_PEER_STATE_ZOMBIE,
|
||||
};
|
||||
|
||||
enum PeerStatistic {
|
||||
PEER_PACKET_LOSS,
|
||||
PEER_PACKET_LOSS_VARIANCE,
|
||||
PEER_PACKET_LOSS_EPOCH,
|
||||
PEER_ROUND_TRIP_TIME,
|
||||
PEER_ROUND_TRIP_TIME_VARIANCE,
|
||||
PEER_LAST_ROUND_TRIP_TIME,
|
||||
PEER_LAST_ROUND_TRIP_TIME_VARIANCE,
|
||||
PEER_PACKET_THROTTLE,
|
||||
PEER_PACKET_THROTTLE_LIMIT,
|
||||
PEER_PACKET_THROTTLE_COUNTER,
|
||||
PEER_PACKET_THROTTLE_EPOCH,
|
||||
PEER_PACKET_THROTTLE_ACCELERATION,
|
||||
PEER_PACKET_THROTTLE_DECELERATION,
|
||||
PEER_PACKET_THROTTLE_INTERVAL,
|
||||
};
|
||||
|
||||
int get_max_packet_size() const override;
|
||||
int get_available_packet_count() const override;
|
||||
Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override; ///< buffer is GONE after next get_packet
|
||||
Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override;
|
||||
|
||||
void peer_disconnect(int p_data = 0);
|
||||
void peer_disconnect_later(int p_data = 0);
|
||||
void peer_disconnect_now(int p_data = 0);
|
||||
|
||||
void ping();
|
||||
void ping_interval(int p_interval);
|
||||
void reset();
|
||||
int send(uint8_t p_channel, ENetPacket *p_packet);
|
||||
void throttle_configure(int interval, int acceleration, int deceleration);
|
||||
void set_timeout(int p_timeout, int p_timeout_min, int p_timeout_max);
|
||||
double get_statistic(PeerStatistic p_stat);
|
||||
PeerState get_state() const;
|
||||
int get_channels() const;
|
||||
|
||||
// Extras
|
||||
IPAddress get_remote_address() const;
|
||||
int get_remote_port() const;
|
||||
|
||||
// Used by ENetMultiplayer (TODO use meta? If only they where StringNames)
|
||||
bool is_active() const;
|
||||
|
||||
ENetPacketPeer(ENetPeer *p_peer);
|
||||
~ENetPacketPeer();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(ENetPacketPeer::PeerState);
|
||||
VARIANT_ENUM_CAST(ENetPacketPeer::PeerStatistic);
|
||||
|
||||
#endif // ENET_PACKET_PEER_H
|
||||
65
engine/modules/enet/register_types.cpp
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 "enet_connection.h"
|
||||
#include "enet_multiplayer_peer.h"
|
||||
#include "enet_packet_peer.h"
|
||||
|
||||
#include "core/error/error_macros.h"
|
||||
|
||||
static bool enet_ok = false;
|
||||
|
||||
void initialize_enet_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (enet_initialize() != 0) {
|
||||
ERR_PRINT("ENet initialization failure");
|
||||
} else {
|
||||
enet_ok = true;
|
||||
}
|
||||
|
||||
GDREGISTER_CLASS(ENetMultiplayerPeer);
|
||||
GDREGISTER_ABSTRACT_CLASS(ENetPacketPeer);
|
||||
GDREGISTER_CLASS(ENetConnection);
|
||||
}
|
||||
|
||||
void uninitialize_enet_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (enet_ok) {
|
||||
enet_deinitialize();
|
||||
}
|
||||
}
|
||||
39
engine/modules/enet/register_types.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 ENET_REGISTER_TYPES_H
|
||||
#define ENET_REGISTER_TYPES_H
|
||||
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
void initialize_enet_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_enet_module(ModuleInitializationLevel p_level);
|
||||
|
||||
#endif // ENET_REGISTER_TYPES_H
|
||||
36
engine/modules/etcpak/SCsub
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
||||
env_etcpak = env_modules.Clone()
|
||||
|
||||
# Thirdparty source files
|
||||
|
||||
thirdparty_obj = []
|
||||
|
||||
thirdparty_dir = "#thirdparty/etcpak/"
|
||||
thirdparty_sources = [
|
||||
"Dither.cpp",
|
||||
"ProcessDxtc.cpp",
|
||||
"ProcessRGB.cpp",
|
||||
"Tables.cpp",
|
||||
]
|
||||
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
|
||||
|
||||
env_etcpak.Prepend(CPPPATH=[thirdparty_dir])
|
||||
|
||||
env_thirdparty = env_etcpak.Clone()
|
||||
env_thirdparty.disable_warnings()
|
||||
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
|
||||
env.modules_sources += thirdparty_obj
|
||||
|
||||
# Godot source files
|
||||
|
||||
module_obj = []
|
||||
|
||||
env_etcpak.add_source_files(module_obj, "*.cpp")
|
||||
env.modules_sources += module_obj
|
||||
|
||||
# Needed to force rebuilding the module files when the thirdparty library is updated.
|
||||
env.Depends(module_obj, thirdparty_obj)
|
||||
6
engine/modules/etcpak/config.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
def can_build(env, platform):
|
||||
return env.editor_build
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
||||
282
engine/modules/etcpak/image_compress_etcpak.cpp
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
/**************************************************************************/
|
||||
/* image_compress_etcpak.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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_compress_etcpak.h"
|
||||
|
||||
#include "core/os/os.h"
|
||||
#include "core/string/print_string.h"
|
||||
|
||||
#include <ProcessDxtc.hpp>
|
||||
#include <ProcessRGB.hpp>
|
||||
|
||||
EtcpakType _determine_etc_type(Image::UsedChannels p_channels) {
|
||||
switch (p_channels) {
|
||||
case Image::USED_CHANNELS_L:
|
||||
return EtcpakType::ETCPAK_TYPE_ETC2;
|
||||
case Image::USED_CHANNELS_LA:
|
||||
return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA;
|
||||
case Image::USED_CHANNELS_R:
|
||||
return EtcpakType::ETCPAK_TYPE_ETC2_R;
|
||||
case Image::USED_CHANNELS_RG:
|
||||
return EtcpakType::ETCPAK_TYPE_ETC2_RG;
|
||||
case Image::USED_CHANNELS_RGB:
|
||||
return EtcpakType::ETCPAK_TYPE_ETC2;
|
||||
case Image::USED_CHANNELS_RGBA:
|
||||
return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA;
|
||||
default:
|
||||
return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA;
|
||||
}
|
||||
}
|
||||
|
||||
EtcpakType _determine_dxt_type(Image::UsedChannels p_channels) {
|
||||
switch (p_channels) {
|
||||
case Image::USED_CHANNELS_L:
|
||||
return EtcpakType::ETCPAK_TYPE_DXT1;
|
||||
case Image::USED_CHANNELS_LA:
|
||||
return EtcpakType::ETCPAK_TYPE_DXT5;
|
||||
case Image::USED_CHANNELS_R:
|
||||
return EtcpakType::ETCPAK_TYPE_RGTC_R;
|
||||
case Image::USED_CHANNELS_RG:
|
||||
return EtcpakType::ETCPAK_TYPE_RGTC_RG;
|
||||
case Image::USED_CHANNELS_RGB:
|
||||
return EtcpakType::ETCPAK_TYPE_DXT1;
|
||||
case Image::USED_CHANNELS_RGBA:
|
||||
return EtcpakType::ETCPAK_TYPE_DXT5;
|
||||
default:
|
||||
return EtcpakType::ETCPAK_TYPE_DXT5;
|
||||
}
|
||||
}
|
||||
|
||||
void _compress_etc1(Image *r_img) {
|
||||
_compress_etcpak(EtcpakType::ETCPAK_TYPE_ETC1, r_img);
|
||||
}
|
||||
|
||||
void _compress_etc2(Image *r_img, Image::UsedChannels p_channels) {
|
||||
EtcpakType type = _determine_etc_type(p_channels);
|
||||
_compress_etcpak(type, r_img);
|
||||
}
|
||||
|
||||
void _compress_bc(Image *r_img, Image::UsedChannels p_channels) {
|
||||
EtcpakType type = _determine_dxt_type(p_channels);
|
||||
_compress_etcpak(type, r_img);
|
||||
}
|
||||
|
||||
void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) {
|
||||
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
|
||||
|
||||
Image::Format img_format = r_img->get_format();
|
||||
if (Image::is_format_compressed(img_format)) {
|
||||
return; // Do not compress, already compressed.
|
||||
}
|
||||
if (img_format > Image::FORMAT_RGBA8) {
|
||||
// TODO: we should be able to handle FORMAT_RGBA4444 and FORMAT_RGBA5551 eventually
|
||||
return;
|
||||
}
|
||||
|
||||
// Use RGBA8 to convert.
|
||||
if (img_format != Image::FORMAT_RGBA8) {
|
||||
r_img->convert(Image::FORMAT_RGBA8);
|
||||
}
|
||||
|
||||
// Determine output format based on Etcpak type.
|
||||
Image::Format target_format = Image::FORMAT_RGBA8;
|
||||
if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC1) {
|
||||
target_format = Image::FORMAT_ETC;
|
||||
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2) {
|
||||
target_format = Image::FORMAT_ETC2_RGB8;
|
||||
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_R) {
|
||||
target_format = Image::FORMAT_ETC2_R11;
|
||||
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RG) {
|
||||
target_format = Image::FORMAT_ETC2_RG11;
|
||||
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) {
|
||||
target_format = Image::FORMAT_ETC2_RA_AS_RG;
|
||||
r_img->convert_rg_to_ra_rgba8();
|
||||
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_ALPHA) {
|
||||
target_format = Image::FORMAT_ETC2_RGBA8;
|
||||
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT1) {
|
||||
target_format = Image::FORMAT_DXT1;
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG) {
|
||||
target_format = Image::FORMAT_DXT5_RA_AS_RG;
|
||||
r_img->convert_rg_to_ra_rgba8();
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5) {
|
||||
target_format = Image::FORMAT_DXT5;
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_R) {
|
||||
target_format = Image::FORMAT_RGTC_R;
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_RG) {
|
||||
target_format = Image::FORMAT_RGTC_RG;
|
||||
} else {
|
||||
ERR_FAIL_MSG("Invalid or unsupported etcpak compression format, not ETC or DXT.");
|
||||
}
|
||||
|
||||
// Compress image data and (if required) mipmaps.
|
||||
|
||||
const bool mipmaps = r_img->has_mipmaps();
|
||||
int width = r_img->get_width();
|
||||
int height = r_img->get_height();
|
||||
|
||||
/*
|
||||
The first mipmap level of a compressed texture must be a multiple of 4. Quote from D3D11.3 spec:
|
||||
|
||||
BC format surfaces are always multiples of full blocks, each block representing 4x4 pixels.
|
||||
For mipmaps, the top level map is required to be a multiple of 4 size in all dimensions.
|
||||
The sizes for the lower level maps are computed as they are for all mipmapped surfaces,
|
||||
and thus may not be a multiple of 4, for example a top level map of 20 results in a second level
|
||||
map size of 10. For these cases, there is a differing 'physical' size and a 'virtual' size.
|
||||
The virtual size is that computed for each mip level without adjustment, which is 10 for the example.
|
||||
The physical size is the virtual size rounded up to the next multiple of 4, which is 12 for the example,
|
||||
and this represents the actual memory size. The sampling hardware will apply texture address
|
||||
processing based on the virtual size (using, for example, border color if specified for accesses
|
||||
beyond 10), and thus for the example case will not access the 11th and 12th row of the resource.
|
||||
So for mipmap chains when an axis becomes < 4 in size, only texels 'a','b','e','f'
|
||||
are used for a 2x2 map, and texel 'a' is used for 1x1. Note that this is similar to, but distinct from,
|
||||
the surface pitch, which can encompass additional padding beyond the physical surface size.
|
||||
*/
|
||||
int next_width = width <= 2 ? width : (width + 3) & ~3;
|
||||
int next_height = height <= 2 ? height : (height + 3) & ~3;
|
||||
if (next_width != width || next_height != height) {
|
||||
r_img->resize(next_width, next_height, Image::INTERPOLATE_LANCZOS);
|
||||
width = r_img->get_width();
|
||||
height = r_img->get_height();
|
||||
}
|
||||
// ERR_FAIL_COND(width % 4 != 0 || height % 4 != 0); // FIXME: No longer guaranteed.
|
||||
// Multiple-of-4 should be guaranteed by above.
|
||||
// However, power-of-two 3d textures will create Nx2 and Nx1 mipmap levels,
|
||||
// which are individually compressed Image objects that violate the above rule.
|
||||
// Hence, we allow Nx1 and Nx2 images through without forcing to multiple-of-4.
|
||||
|
||||
const uint8_t *src_read = r_img->get_data().ptr();
|
||||
|
||||
print_verbose(vformat("etcpak: Encoding image size %dx%d to format %s%s.", width, height, Image::get_format_name(target_format), mipmaps ? ", with mipmaps" : ""));
|
||||
|
||||
int64_t dest_size = Image::get_image_data_size(width, height, target_format, mipmaps);
|
||||
Vector<uint8_t> dest_data;
|
||||
dest_data.resize(dest_size);
|
||||
uint8_t *dest_write = dest_data.ptrw();
|
||||
|
||||
int mip_count = mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0;
|
||||
Vector<uint32_t> padded_src;
|
||||
|
||||
for (int i = 0; i < mip_count + 1; i++) {
|
||||
// Get write mip metrics for target image.
|
||||
int orig_mip_w, orig_mip_h;
|
||||
int64_t mip_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, orig_mip_w, orig_mip_h);
|
||||
// Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer).
|
||||
ERR_FAIL_COND(mip_ofs % 8 != 0);
|
||||
uint64_t *dest_mip_write = (uint64_t *)&dest_write[mip_ofs];
|
||||
|
||||
// Block size. Align stride to multiple of 4 (RGBA8).
|
||||
int mip_w = (orig_mip_w + 3) & ~3;
|
||||
int mip_h = (orig_mip_h + 3) & ~3;
|
||||
const uint32_t blocks = mip_w * mip_h / 16;
|
||||
|
||||
// Get mip data from source image for reading.
|
||||
int64_t src_mip_ofs = r_img->get_mipmap_offset(i);
|
||||
const uint32_t *src_mip_read = (const uint32_t *)&src_read[src_mip_ofs];
|
||||
|
||||
// Pad textures to nearest block by smearing.
|
||||
if (mip_w != orig_mip_w || mip_h != orig_mip_h) {
|
||||
padded_src.resize(mip_w * mip_h);
|
||||
uint32_t *ptrw = padded_src.ptrw();
|
||||
int x = 0, y = 0;
|
||||
for (y = 0; y < orig_mip_h; y++) {
|
||||
for (x = 0; x < orig_mip_w; x++) {
|
||||
ptrw[mip_w * y + x] = src_mip_read[orig_mip_w * y + x];
|
||||
}
|
||||
// First, smear in x.
|
||||
for (; x < mip_w; x++) {
|
||||
ptrw[mip_w * y + x] = ptrw[mip_w * y + x - 1];
|
||||
}
|
||||
}
|
||||
// Then, smear in y.
|
||||
for (; y < mip_h; y++) {
|
||||
for (x = 0; x < mip_w; x++) {
|
||||
ptrw[mip_w * y + x] = ptrw[mip_w * y + x - mip_w];
|
||||
}
|
||||
}
|
||||
// Override the src_mip_read pointer to our temporary Vector.
|
||||
src_mip_read = padded_src.ptr();
|
||||
}
|
||||
|
||||
switch (p_compresstype) {
|
||||
case EtcpakType::ETCPAK_TYPE_ETC1:
|
||||
CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, mip_w);
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_ETC2:
|
||||
CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, mip_w, true);
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_ETC2_ALPHA:
|
||||
case EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG:
|
||||
CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, mip_w, true);
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_ETC2_R:
|
||||
CompressEacR(src_mip_read, dest_mip_write, blocks, mip_w);
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_ETC2_RG:
|
||||
CompressEacRg(src_mip_read, dest_mip_write, blocks, mip_w);
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_DXT1:
|
||||
CompressDxt1Dither(src_mip_read, dest_mip_write, blocks, mip_w);
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_DXT5:
|
||||
case EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG:
|
||||
CompressDxt5(src_mip_read, dest_mip_write, blocks, mip_w);
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_RGTC_R:
|
||||
CompressBc4(src_mip_read, dest_mip_write, blocks, mip_w);
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_RGTC_RG:
|
||||
CompressBc5(src_mip_read, dest_mip_write, blocks, mip_w);
|
||||
break;
|
||||
|
||||
default:
|
||||
ERR_FAIL_MSG("etcpak: Invalid or unsupported compression format.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Replace original image with compressed one.
|
||||
r_img->set_data(width, height, mipmaps, target_format, dest_data);
|
||||
|
||||
print_verbose(vformat("etcpak: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time));
|
||||
}
|
||||
56
engine/modules/etcpak/image_compress_etcpak.h
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/**************************************************************************/
|
||||
/* image_compress_etcpak.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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_COMPRESS_ETCPAK_H
|
||||
#define IMAGE_COMPRESS_ETCPAK_H
|
||||
|
||||
#include "core/io/image.h"
|
||||
|
||||
enum class EtcpakType {
|
||||
ETCPAK_TYPE_ETC1,
|
||||
ETCPAK_TYPE_ETC2,
|
||||
ETCPAK_TYPE_ETC2_ALPHA,
|
||||
ETCPAK_TYPE_ETC2_RA_AS_RG,
|
||||
ETCPAK_TYPE_ETC2_R,
|
||||
ETCPAK_TYPE_ETC2_RG,
|
||||
ETCPAK_TYPE_DXT1,
|
||||
ETCPAK_TYPE_DXT5,
|
||||
ETCPAK_TYPE_DXT5_RA_AS_RG,
|
||||
ETCPAK_TYPE_RGTC_R,
|
||||
ETCPAK_TYPE_RGTC_RG,
|
||||
};
|
||||
|
||||
void _compress_etc1(Image *r_img);
|
||||
void _compress_etc2(Image *r_img, Image::UsedChannels p_channels);
|
||||
void _compress_bc(Image *r_img, Image::UsedChannels p_channels);
|
||||
|
||||
void _compress_etcpak(EtcpakType p_compresstype, Image *r_img);
|
||||
|
||||
#endif // IMAGE_COMPRESS_ETCPAK_H
|
||||
49
engine/modules/etcpak/register_types.cpp
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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_compress_etcpak.h"
|
||||
|
||||
void initialize_etcpak_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
|
||||
Image::_image_compress_etc1_func = _compress_etc1;
|
||||
Image::_image_compress_etc2_func = _compress_etc2;
|
||||
Image::_image_compress_bc_func = _compress_bc;
|
||||
}
|
||||
|
||||
void uninitialize_etcpak_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
39
engine/modules/etcpak/register_types.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**************************************************************************/
|
||||
/* register_types.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 ETCPAK_REGISTER_TYPES_H
|
||||
#define ETCPAK_REGISTER_TYPES_H
|
||||
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
void initialize_etcpak_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_etcpak_module(ModuleInitializationLevel p_level);
|
||||
|
||||
#endif // ETCPAK_REGISTER_TYPES_H
|
||||
48
engine/modules/fbx/SCsub
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
||||
env_fbx = env_modules.Clone()
|
||||
|
||||
# Thirdparty source files
|
||||
|
||||
thirdparty_obj = []
|
||||
|
||||
thirdparty_dir = "#thirdparty/ufbx/"
|
||||
thirdparty_sources = [thirdparty_dir + "ufbx.c"]
|
||||
|
||||
env_fbx.Prepend(CPPPATH=[thirdparty_dir])
|
||||
|
||||
env_thirdparty = env_fbx.Clone()
|
||||
env_thirdparty.disable_warnings()
|
||||
|
||||
env_thirdparty.Append(
|
||||
CPPDEFINES=[
|
||||
"UFBX_NO_SUBDIVISION",
|
||||
"UFBX_NO_TESSELLATION",
|
||||
"UFBX_NO_GEOMETRY_CACHE",
|
||||
"UFBX_NO_SCENE_EVALUATION",
|
||||
"UFBX_NO_INDEX_GENERATION",
|
||||
"UFBX_NO_SKINNING_EVALUATION",
|
||||
"UFBX_NO_FORMAT_OBJ",
|
||||
]
|
||||
)
|
||||
|
||||
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
|
||||
env.modules_sources += thirdparty_obj
|
||||
|
||||
# Godot source files
|
||||
|
||||
module_obj = []
|
||||
|
||||
env_fbx.add_source_files(module_obj, "*.cpp")
|
||||
env_fbx.add_source_files(module_obj, "structures/*.cpp")
|
||||
|
||||
if env.editor_build:
|
||||
env_fbx.add_source_files(module_obj, "editor/*.cpp")
|
||||
|
||||
env.modules_sources += module_obj
|
||||
|
||||
# Needed to force rebuilding the module files when the thirdparty library is updated.
|
||||
env.Depends(module_obj, thirdparty_obj)
|
||||
20
engine/modules/fbx/config.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
def can_build(env, platform):
|
||||
env.module_add_dependencies("fbx", ["gltf"])
|
||||
return not env["disable_3d"]
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
||||
|
||||
|
||||
def get_doc_classes():
|
||||
return [
|
||||
"EditorSceneFormatImporterFBX2GLTF",
|
||||
"EditorSceneFormatImporterUFBX",
|
||||
"FBXDocument",
|
||||
"FBXState",
|
||||
]
|
||||
|
||||
|
||||
def get_doc_path():
|
||||
return "doc_classes"
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="EditorSceneFormatImporterFBX2GLTF" inherits="EditorSceneFormatImporter" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
Importer for the [code].fbx[/code] scene file format.
|
||||
</brief_description>
|
||||
<description>
|
||||
Imports Autodesk FBX 3D scenes by way of converting them to glTF 2.0 using the FBX2glTF command line tool.
|
||||
The location of the FBX2glTF binary is set via the [member EditorSettings.filesystem/import/fbx/fbx2gltf_path] editor setting.
|
||||
This importer is only used if [member ProjectSettings.filesystem/import/fbx2gltf/enabled] is set to [code]true[/code].
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
</class>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="EditorSceneFormatImporterUFBX" inherits="EditorSceneFormatImporter" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
Import FBX files using the ufbx library.
|
||||
</brief_description>
|
||||
<description>
|
||||
EditorSceneFormatImporterUFBX is designed to load FBX files and supports both binary and ASCII FBX files from version 3000 onward. This class supports various 3D object types like meshes, skins, blend shapes, materials, and rigging information. The class aims for feature parity with the official FBX SDK and supports FBX 7.4 specifications.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
</class>
|
||||
12
engine/modules/fbx/doc_classes/FBXDocument.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="FBXDocument" inherits="GLTFDocument" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
Handles FBX documents.
|
||||
</brief_description>
|
||||
<description>
|
||||
The FBXDocument handles FBX documents. It provides methods to append data from buffers or files, generate scenes, and register/unregister document extensions.
|
||||
When exporting FBX from Blender, use the "FBX Units Scale" option. The "FBX Units Scale" option sets the correct scale factor and avoids manual adjustments when re-importing into Blender, such as through glTF export.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
</class>
|
||||
15
engine/modules/fbx/doc_classes/FBXState.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="FBXState" inherits="GLTFState" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
</brief_description>
|
||||
<description>
|
||||
The FBXState handles the state data imported from FBX files.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<members>
|
||||
<member name="allow_geometry_helper_nodes" type="bool" setter="set_allow_geometry_helper_nodes" getter="get_allow_geometry_helper_nodes" default="false">
|
||||
If [code]true[/code], the import process used auxiliary nodes called geometry helper nodes. These nodes help preserve the pivots and transformations of the original 3D model during import.
|
||||
</member>
|
||||
</members>
|
||||
</class>
|
||||
150
engine/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/**************************************************************************/
|
||||
/* editor_scene_importer_fbx2gltf.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 "editor_scene_importer_fbx2gltf.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
#include "editor_scene_importer_ufbx.h"
|
||||
#include "modules/gltf/gltf_document.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "editor/editor_settings.h"
|
||||
|
||||
uint32_t EditorSceneFormatImporterFBX2GLTF::get_import_flags() const {
|
||||
return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION;
|
||||
}
|
||||
|
||||
void EditorSceneFormatImporterFBX2GLTF::get_extensions(List<String> *r_extensions) const {
|
||||
r_extensions->push_back("fbx");
|
||||
}
|
||||
|
||||
Node *EditorSceneFormatImporterFBX2GLTF::import_scene(const String &p_path, uint32_t p_flags,
|
||||
const HashMap<StringName, Variant> &p_options,
|
||||
List<String> *r_missing_deps, Error *r_err) {
|
||||
// FIXME: Hack to work around GH-86309.
|
||||
if (p_options.has("fbx/importer") && int(p_options["fbx/importer"]) == EditorSceneFormatImporterUFBX::FBX_IMPORTER_UFBX) {
|
||||
Ref<EditorSceneFormatImporterUFBX> fbx2gltf_importer;
|
||||
fbx2gltf_importer.instantiate();
|
||||
Node *scene = fbx2gltf_importer->import_scene(p_path, p_flags, p_options, r_missing_deps, r_err);
|
||||
if (r_err && *r_err == OK) {
|
||||
return scene;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Get global paths for source and sink.
|
||||
|
||||
// Don't use `c_escape()` as it can generate broken paths. These paths will be
|
||||
// enclosed in double quotes by OS::execute(), so we only need to escape those.
|
||||
// `c_escape_multiline()` seems to do this (escapes `\` and `"` only).
|
||||
const String source_global = ProjectSettings::get_singleton()->globalize_path(p_path).c_escape_multiline();
|
||||
const String sink = ProjectSettings::get_singleton()->get_imported_files_path().path_join(
|
||||
vformat("%s-%s.glb", p_path.get_file().get_basename(), p_path.md5_text()));
|
||||
const String sink_global = ProjectSettings::get_singleton()->globalize_path(sink).c_escape_multiline();
|
||||
|
||||
// Run fbx2gltf.
|
||||
|
||||
String fbx2gltf_path = EDITOR_GET("filesystem/import/fbx/fbx2gltf_path");
|
||||
|
||||
List<String> args;
|
||||
args.push_back("--pbr-metallic-roughness");
|
||||
args.push_back("--input");
|
||||
args.push_back(source_global);
|
||||
args.push_back("--output");
|
||||
args.push_back(sink_global);
|
||||
args.push_back("--binary");
|
||||
|
||||
String standard_out;
|
||||
int ret;
|
||||
OS::get_singleton()->execute(fbx2gltf_path, args, &standard_out, &ret, true);
|
||||
print_verbose(fbx2gltf_path);
|
||||
print_verbose(standard_out);
|
||||
|
||||
if (ret != 0) {
|
||||
if (r_err) {
|
||||
*r_err = ERR_SCRIPT_FAILED;
|
||||
}
|
||||
ERR_PRINT(vformat("FBX conversion to glTF failed with error: %d.", ret));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Import the generated glTF.
|
||||
|
||||
// Use GLTFDocument instead of glTF importer to keep image references.
|
||||
Ref<GLTFDocument> gltf;
|
||||
gltf.instantiate();
|
||||
Ref<GLTFState> state;
|
||||
state.instantiate();
|
||||
if (p_options.has(SNAME("nodes/import_as_skeleton_bones")) ? (bool)p_options[SNAME("nodes/import_as_skeleton_bones")] : false) {
|
||||
state->set_import_as_skeleton_bones(true);
|
||||
}
|
||||
print_verbose(vformat("glTF path: %s", sink));
|
||||
Error err = gltf->append_from_file(sink, state, p_flags, p_path.get_base_dir());
|
||||
if (err != OK) {
|
||||
if (r_err) {
|
||||
*r_err = FAILED;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
bool trimming = p_options.has("animation/trimming") ? (bool)p_options["animation/trimming"] : false;
|
||||
return gltf->generate_scene(state, (float)p_options["animation/fps"], trimming, false);
|
||||
#else
|
||||
return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], false);
|
||||
#endif
|
||||
}
|
||||
|
||||
Variant EditorSceneFormatImporterFBX2GLTF::get_option_visibility(const String &p_path, bool p_for_animation,
|
||||
const String &p_option, const HashMap<StringName, Variant> &p_options) {
|
||||
// Remove all the FBX options except for 'fbx/importer' if the importer is fbx2gltf.
|
||||
// These options are available only for ufbx.
|
||||
if (p_option.begins_with("fbx/") && p_option != "fbx/importer" && p_options.has("fbx/importer") && int(p_options["fbx/importer"]) == EditorSceneFormatImporterUFBX::FBX_IMPORTER_FBX2GLTF) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#define ADD_OPTION_ENUM(PATH, ENUM_HINT, VALUE) \
|
||||
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, SNAME(PATH), PROPERTY_HINT_ENUM, ENUM_HINT), VALUE));
|
||||
|
||||
void EditorSceneFormatImporterFBX2GLTF::get_import_options(const String &p_path,
|
||||
List<ResourceImporter::ImportOption> *r_options) {
|
||||
}
|
||||
|
||||
void EditorSceneFormatImporterFBX2GLTF::handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const {
|
||||
if (!p_import_params.has("fbx/importer")) {
|
||||
p_import_params["fbx/importer"] = EditorSceneFormatImporterUFBX::FBX_IMPORTER_UFBX;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // TOOLS_ENABLED
|
||||
59
engine/modules/fbx/editor/editor_scene_importer_fbx2gltf.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/**************************************************************************/
|
||||
/* editor_scene_importer_fbx2gltf.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 EDITOR_SCENE_IMPORTER_FBX2GLTF_H
|
||||
#define EDITOR_SCENE_IMPORTER_FBX2GLTF_H
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
#include "editor/import/3d/resource_importer_scene.h"
|
||||
|
||||
class Animation;
|
||||
class Node;
|
||||
|
||||
class EditorSceneFormatImporterFBX2GLTF : public EditorSceneFormatImporter {
|
||||
GDCLASS(EditorSceneFormatImporterFBX2GLTF, EditorSceneFormatImporter);
|
||||
|
||||
public:
|
||||
virtual uint32_t get_import_flags() const override;
|
||||
virtual void get_extensions(List<String> *r_extensions) const override;
|
||||
virtual Node *import_scene(const String &p_path, uint32_t p_flags,
|
||||
const HashMap<StringName, Variant> &p_options,
|
||||
List<String> *r_missing_deps, Error *r_err = nullptr) override;
|
||||
virtual void get_import_options(const String &p_path,
|
||||
List<ResourceImporter::ImportOption> *r_options) override;
|
||||
virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option,
|
||||
const HashMap<StringName, Variant> &p_options) override;
|
||||
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override;
|
||||
};
|
||||
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
#endif // EDITOR_SCENE_IMPORTER_FBX2GLTF_H
|
||||
112
engine/modules/fbx/editor/editor_scene_importer_ufbx.cpp
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/**************************************************************************/
|
||||
/* editor_scene_importer_ufbx.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 "editor_scene_importer_ufbx.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
#include "../fbx_document.h"
|
||||
#include "editor_scene_importer_fbx2gltf.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
|
||||
uint32_t EditorSceneFormatImporterUFBX::get_import_flags() const {
|
||||
return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION;
|
||||
}
|
||||
|
||||
void EditorSceneFormatImporterUFBX::get_extensions(List<String> *r_extensions) const {
|
||||
r_extensions->push_back("fbx");
|
||||
}
|
||||
|
||||
Node *EditorSceneFormatImporterUFBX::import_scene(const String &p_path, uint32_t p_flags,
|
||||
const HashMap<StringName, Variant> &p_options,
|
||||
List<String> *r_missing_deps, Error *r_err) {
|
||||
// FIXME: Hack to work around GH-86309.
|
||||
if (p_options.has("fbx/importer") && int(p_options["fbx/importer"]) == FBX_IMPORTER_FBX2GLTF && GLOBAL_GET("filesystem/import/fbx2gltf/enabled")) {
|
||||
Ref<EditorSceneFormatImporterFBX2GLTF> fbx2gltf_importer;
|
||||
fbx2gltf_importer.instantiate();
|
||||
Node *scene = fbx2gltf_importer->import_scene(p_path, p_flags, p_options, r_missing_deps, r_err);
|
||||
if (r_err && *r_err == OK) {
|
||||
return scene;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
Ref<FBXDocument> fbx;
|
||||
fbx.instantiate();
|
||||
Ref<FBXState> state;
|
||||
state.instantiate();
|
||||
print_verbose(vformat("FBX path: %s", p_path));
|
||||
String path = ProjectSettings::get_singleton()->globalize_path(p_path);
|
||||
bool allow_geometry_helper_nodes = p_options.has("fbx/allow_geometry_helper_nodes") ? (bool)p_options["fbx/allow_geometry_helper_nodes"] : false;
|
||||
if (allow_geometry_helper_nodes) {
|
||||
state->set_allow_geometry_helper_nodes(allow_geometry_helper_nodes);
|
||||
}
|
||||
if (p_options.has("fbx/embedded_image_handling")) {
|
||||
int32_t enum_option = p_options["fbx/embedded_image_handling"];
|
||||
state->set_handle_binary_image(enum_option);
|
||||
}
|
||||
if (p_options.has(SNAME("nodes/import_as_skeleton_bones")) ? (bool)p_options[SNAME("nodes/import_as_skeleton_bones")] : false) {
|
||||
state->set_import_as_skeleton_bones(true);
|
||||
}
|
||||
p_flags |= EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS;
|
||||
state->set_bake_fps(p_options["animation/fps"]);
|
||||
Error err = fbx->append_from_file(path, state, p_flags, p_path.get_base_dir());
|
||||
if (err != OK) {
|
||||
if (r_err) {
|
||||
*r_err = FAILED;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return fbx->generate_scene(state, state->get_bake_fps(), (bool)p_options["animation/trimming"], false);
|
||||
}
|
||||
|
||||
Variant EditorSceneFormatImporterUFBX::get_option_visibility(const String &p_path, bool p_for_animation,
|
||||
const String &p_option, const HashMap<StringName, Variant> &p_options) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void EditorSceneFormatImporterUFBX::get_import_options(const String &p_path,
|
||||
List<ResourceImporter::ImportOption> *r_options) {
|
||||
// Returns all the options when path is empty because that means it's for the Project Settings.
|
||||
if (p_path.is_empty() || p_path.get_extension().to_lower() == "fbx") {
|
||||
r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "fbx/importer", PROPERTY_HINT_ENUM, "ufbx,FBX2glTF"), FBX_IMPORTER_UFBX));
|
||||
r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::BOOL, "fbx/allow_geometry_helper_nodes"), false));
|
||||
r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "fbx/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), FBXState::HANDLE_BINARY_EXTRACT_TEXTURES));
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSceneFormatImporterUFBX::handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const {
|
||||
if (!p_import_params.has("fbx/importer")) {
|
||||
p_import_params["fbx/importer"] = EditorSceneFormatImporterUFBX::FBX_IMPORTER_FBX2GLTF;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // TOOLS_ENABLED
|
||||
62
engine/modules/fbx/editor/editor_scene_importer_ufbx.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/**************************************************************************/
|
||||
/* editor_scene_importer_ufbx.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 EDITOR_SCENE_IMPORTER_UFBX_H
|
||||
#define EDITOR_SCENE_IMPORTER_UFBX_H
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
#include "editor/import/3d/resource_importer_scene.h"
|
||||
|
||||
class Animation;
|
||||
class Node;
|
||||
|
||||
class EditorSceneFormatImporterUFBX : public EditorSceneFormatImporter {
|
||||
GDCLASS(EditorSceneFormatImporterUFBX, EditorSceneFormatImporter);
|
||||
|
||||
public:
|
||||
enum FBX_IMPORTER_TYPE {
|
||||
FBX_IMPORTER_UFBX,
|
||||
FBX_IMPORTER_FBX2GLTF,
|
||||
};
|
||||
virtual uint32_t get_import_flags() const override;
|
||||
virtual void get_extensions(List<String> *r_extensions) const override;
|
||||
virtual Node *import_scene(const String &p_path, uint32_t p_flags,
|
||||
const HashMap<StringName, Variant> &p_options,
|
||||
List<String> *r_missing_deps, Error *r_err = nullptr) override;
|
||||
virtual void get_import_options(const String &p_path,
|
||||
List<ResourceImporter::ImportOption> *r_options) override;
|
||||
virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option,
|
||||
const HashMap<StringName, Variant> &p_options) override;
|
||||
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override;
|
||||
};
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
#endif // EDITOR_SCENE_IMPORTER_UFBX_H
|
||||
2489
engine/modules/fbx/fbx_document.cpp
Normal file
106
engine/modules/fbx/fbx_document.h
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/**************************************************************************/
|
||||
/* fbx_document.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 FBX_DOCUMENT_H
|
||||
#define FBX_DOCUMENT_H
|
||||
|
||||
#include "fbx_state.h"
|
||||
|
||||
#include "modules/gltf/gltf_defines.h"
|
||||
#include "modules/gltf/gltf_document.h"
|
||||
|
||||
#include <ufbx.h>
|
||||
|
||||
class FBXDocument : public GLTFDocument {
|
||||
GDCLASS(FBXDocument, GLTFDocument);
|
||||
|
||||
public:
|
||||
enum {
|
||||
TEXTURE_TYPE_GENERIC = 0,
|
||||
TEXTURE_TYPE_NORMAL = 1,
|
||||
};
|
||||
|
||||
static Transform3D _as_xform(const ufbx_matrix &p_mat);
|
||||
static String _as_string(const ufbx_string &p_string);
|
||||
static Vector3 _as_vec3(const ufbx_vec3 &p_vector);
|
||||
static String _gen_unique_name(HashSet<String> &unique_names, const String &p_name);
|
||||
|
||||
public:
|
||||
Error append_from_file(String p_path, Ref<GLTFState> p_state, uint32_t p_flags = 0, String p_base_path = String()) override;
|
||||
Error append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> p_state, uint32_t p_flags = 0) override;
|
||||
Error append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint32_t p_flags = 0) override;
|
||||
|
||||
Node *generate_scene(Ref<GLTFState> p_state, float p_bake_fps = 30.0f, bool p_trimming = false, bool p_remove_immutable_tracks = true) override;
|
||||
PackedByteArray generate_buffer(Ref<GLTFState> p_state) override;
|
||||
Error write_to_filesystem(Ref<GLTFState> p_state, const String &p_path) override;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
private:
|
||||
String _get_texture_path(const String &p_base_directory, const String &p_source_file_path) const;
|
||||
void _process_uv_set(PackedVector2Array &uv_array);
|
||||
void _zero_unused_elements(Vector<float> &cur_custom, int start, int end, int num_channels);
|
||||
Error _parse_scenes(Ref<FBXState> p_state);
|
||||
Error _parse_nodes(Ref<FBXState> p_state);
|
||||
String _sanitize_animation_name(const String &p_name);
|
||||
String _gen_unique_animation_name(Ref<FBXState> p_state, const String &p_name);
|
||||
Ref<Texture2D> _get_texture(Ref<FBXState> p_state,
|
||||
const GLTFTextureIndex p_texture, int p_texture_type);
|
||||
Error _parse_meshes(Ref<FBXState> p_state);
|
||||
Ref<Image> _parse_image_bytes_into_image(Ref<FBXState> p_state, const Vector<uint8_t> &p_bytes, const String &p_filename, int p_index);
|
||||
GLTFImageIndex _parse_image_save_image(Ref<FBXState> p_state, const Vector<uint8_t> &p_bytes, const String &p_file_extension, int p_index, Ref<Image> p_image);
|
||||
Error _parse_images(Ref<FBXState> p_state, const String &p_base_path);
|
||||
Error _parse_materials(Ref<FBXState> p_state);
|
||||
Error _parse_skins(Ref<FBXState> p_state);
|
||||
Error _parse_animations(Ref<FBXState> p_state);
|
||||
BoneAttachment3D *_generate_bone_attachment(Ref<FBXState> p_state,
|
||||
Skeleton3D *p_skeleton,
|
||||
const GLTFNodeIndex p_node_index,
|
||||
const GLTFNodeIndex p_bone_index);
|
||||
ImporterMeshInstance3D *_generate_mesh_instance(Ref<FBXState> p_state, const GLTFNodeIndex p_node_index);
|
||||
Camera3D *_generate_camera(Ref<FBXState> p_state, const GLTFNodeIndex p_node_index);
|
||||
Light3D *_generate_light(Ref<FBXState> p_state, const GLTFNodeIndex p_node_index);
|
||||
Node3D *_generate_spatial(Ref<FBXState> p_state, const GLTFNodeIndex p_node_index);
|
||||
void _assign_node_names(Ref<FBXState> p_state);
|
||||
Error _parse_cameras(Ref<FBXState> p_state);
|
||||
Error _parse_lights(Ref<FBXState> p_state);
|
||||
|
||||
public:
|
||||
Error _parse_fbx_state(Ref<FBXState> p_state, const String &p_search_path);
|
||||
void _process_mesh_instances(Ref<FBXState> p_state, Node *p_scene_root);
|
||||
void _generate_scene_node(Ref<FBXState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root);
|
||||
void _generate_skeleton_bone_node(Ref<FBXState> p_state, const GLTFNodeIndex p_node_index, Node *p_scene_parent, Node *p_scene_root);
|
||||
void _import_animation(Ref<FBXState> p_state, AnimationPlayer *p_animation_player,
|
||||
const GLTFAnimationIndex p_index, const bool p_trimming, const bool p_remove_immutable_tracks);
|
||||
Error _parse(Ref<FBXState> p_state, String p_path, Ref<FileAccess> p_file);
|
||||
};
|
||||
|
||||
#endif // FBX_DOCUMENT_H
|
||||
46
engine/modules/fbx/fbx_state.cpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/**************************************************************************/
|
||||
/* fbx_state.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 "fbx_state.h"
|
||||
|
||||
void FBXState::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_allow_geometry_helper_nodes"), &FBXState::get_allow_geometry_helper_nodes);
|
||||
ClassDB::bind_method(D_METHOD("set_allow_geometry_helper_nodes", "allow"), &FBXState::set_allow_geometry_helper_nodes);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_geometry_helper_nodes"), "set_allow_geometry_helper_nodes", "get_allow_geometry_helper_nodes");
|
||||
}
|
||||
|
||||
bool FBXState::get_allow_geometry_helper_nodes() {
|
||||
return allow_geometry_helper_nodes;
|
||||
}
|
||||
|
||||
void FBXState::set_allow_geometry_helper_nodes(bool p_allow_geometry_helper_nodes) {
|
||||
allow_geometry_helper_nodes = p_allow_geometry_helper_nodes;
|
||||
}
|
||||
69
engine/modules/fbx/fbx_state.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/**************************************************************************/
|
||||
/* fbx_state.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 FBX_STATE_H
|
||||
#define FBX_STATE_H
|
||||
|
||||
#include "modules/gltf/gltf_defines.h"
|
||||
#include "modules/gltf/gltf_state.h"
|
||||
#include "modules/gltf/structures/gltf_skeleton.h"
|
||||
#include "modules/gltf/structures/gltf_skin.h"
|
||||
#include "modules/gltf/structures/gltf_texture.h"
|
||||
|
||||
#include <ufbx.h>
|
||||
|
||||
class FBXState : public GLTFState {
|
||||
GDCLASS(FBXState, GLTFState);
|
||||
friend class FBXDocument;
|
||||
friend class SkinTool;
|
||||
friend class GLTFSkin;
|
||||
|
||||
// Smart pointer that holds the loaded scene.
|
||||
ufbx_unique_ptr<ufbx_scene> scene;
|
||||
bool allow_geometry_helper_nodes = false;
|
||||
|
||||
HashMap<uint64_t, Image::AlphaMode> alpha_mode_cache;
|
||||
HashMap<Pair<uint64_t, uint64_t>, GLTFTextureIndex, PairHash<uint64_t, uint64_t>> albedo_transparency_textures;
|
||||
|
||||
Vector<GLTFSkinIndex> skin_indices;
|
||||
Vector<GLTFSkinIndex> original_skin_indices;
|
||||
HashMap<ObjectID, GLTFSkeletonIndex> skeleton3d_to_fbx_skeleton;
|
||||
HashMap<ObjectID, HashMap<ObjectID, GLTFSkinIndex>> skin_and_skeleton3d_to_fbx_skin;
|
||||
HashSet<String> unique_mesh_names; // Not in GLTFState because GLTFState prefixes mesh names with the scene name (or _)
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
bool get_allow_geometry_helper_nodes();
|
||||
void set_allow_geometry_helper_nodes(bool p_allow_geometry_helper_nodes);
|
||||
};
|
||||
|
||||
#endif // FBX_STATE_H
|
||||