diff options
Diffstat (limited to 'modules/gltf')
-rw-r--r-- | modules/gltf/doc_classes/GLTFCamera.xml | 28 | ||||
-rw-r--r-- | modules/gltf/doc_classes/GLTFLight.xml | 28 | ||||
-rw-r--r-- | modules/gltf/editor/editor_scene_importer_blend.cpp | 14 | ||||
-rw-r--r-- | modules/gltf/editor/editor_scene_importer_fbx.cpp | 2 | ||||
-rw-r--r-- | modules/gltf/extensions/gltf_light.cpp | 119 | ||||
-rw-r--r-- | modules/gltf/extensions/gltf_light.h | 6 | ||||
-rw-r--r-- | modules/gltf/gltf_document.cpp | 200 | ||||
-rw-r--r-- | modules/gltf/structures/gltf_camera.cpp | 81 | ||||
-rw-r--r-- | modules/gltf/structures/gltf_camera.h | 6 |
9 files changed, 290 insertions, 194 deletions
diff --git a/modules/gltf/doc_classes/GLTFCamera.xml b/modules/gltf/doc_classes/GLTFCamera.xml index b90abd105d..49efaa1564 100644 --- a/modules/gltf/doc_classes/GLTFCamera.xml +++ b/modules/gltf/doc_classes/GLTFCamera.xml @@ -10,6 +10,34 @@ <link title="GLTF camera detailed specification">https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-camera</link> <link title="GLTF camera spec and example file">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_015_SimpleCameras.md</link> </tutorials> + <methods> + <method name="from_dictionary" qualifiers="static"> + <return type="GLTFCamera" /> + <param index="0" name="dictionary" type="Dictionary" /> + <description> + Creates a new GLTFCamera instance by parsing the given [Dictionary]. + </description> + </method> + <method name="from_node" qualifiers="static"> + <return type="GLTFCamera" /> + <param index="0" name="camera_node" type="Camera3D" /> + <description> + Create a new GLTFCamera instance from the given Godot [Camera3D] node. + </description> + </method> + <method name="to_dictionary" qualifiers="const"> + <return type="Dictionary" /> + <description> + Serializes this GLTFCamera instance into a [Dictionary]. + </description> + </method> + <method name="to_node" qualifiers="const"> + <return type="Camera3D" /> + <description> + Converts this GLTFCamera instance into a Godot [Camera3D] node. + </description> + </method> + </methods> <members> <member name="depth_far" type="float" setter="set_depth_far" getter="get_depth_far" default="4000.0"> The distance to the far culling boundary for this camera relative to its local Z axis, in meters. This maps to GLTF's [code]zfar[/code] property. diff --git a/modules/gltf/doc_classes/GLTFLight.xml b/modules/gltf/doc_classes/GLTFLight.xml index db2dfb487a..7fd59e14bc 100644 --- a/modules/gltf/doc_classes/GLTFLight.xml +++ b/modules/gltf/doc_classes/GLTFLight.xml @@ -9,6 +9,34 @@ <tutorials> <link title="KHR_lights_punctual GLTF extension spec">https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual</link> </tutorials> + <methods> + <method name="from_dictionary" qualifiers="static"> + <return type="GLTFLight" /> + <param index="0" name="dictionary" type="Dictionary" /> + <description> + Creates a new GLTFLight instance by parsing the given [Dictionary]. + </description> + </method> + <method name="from_node" qualifiers="static"> + <return type="GLTFLight" /> + <param index="0" name="light_node" type="Light3D" /> + <description> + Create a new GLTFLight instance from the given Godot [Light3D] node. + </description> + </method> + <method name="to_dictionary" qualifiers="const"> + <return type="Dictionary" /> + <description> + Serializes this GLTFLight instance into a [Dictionary]. + </description> + </method> + <method name="to_node" qualifiers="const"> + <return type="Light3D" /> + <description> + Converts this GLTFLight instance into a Godot [Light3D] node. + </description> + </method> + </methods> <members> <member name="color" type="Color" setter="set_color" getter="get_color" default="Color(1, 1, 1, 1)"> The [Color] of the light. Defaults to white. A black color causes the light to have no effect. diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index 707769da35..ab52761e17 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -64,7 +64,7 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ // Escape paths to be valid Python strings to embed in the script. const String source_global = ProjectSettings::get_singleton()->globalize_path(p_path).c_escape(); - const String sink = ProjectSettings::get_singleton()->get_imported_files_path().plus_file( + const String sink = ProjectSettings::get_singleton()->get_imported_files_path().path_join( vformat("%s-%s.gltf", p_path.get_file().get_basename(), p_path.md5_text())); const String sink_global = ProjectSettings::get_singleton()->globalize_path(sink).c_escape(); @@ -193,9 +193,9 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ String blender_path = EDITOR_GET("filesystem/import/blender/blender3_path"); #ifdef WINDOWS_ENABLED - blender_path = blender_path.plus_file("blender.exe"); + blender_path = blender_path.path_join("blender.exe"); #else - blender_path = blender_path.plus_file("blender"); + blender_path = blender_path.path_join("blender"); #endif List<String> args; @@ -287,14 +287,14 @@ void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, Li static bool _test_blender_path(const String &p_path, String *r_err = nullptr) { String path = p_path; #ifdef WINDOWS_ENABLED - path = path.plus_file("blender.exe"); + path = path.path_join("blender.exe"); #else - path = path.plus_file("blender"); + path = path.path_join("blender"); #endif #if defined(MACOS_ENABLED) if (!FileAccess::exists(path)) { - path = path.plus_file("Blender"); + path = path.path_join("Blender"); } #endif @@ -485,7 +485,7 @@ bool EditorFileSystemImportFormatSupportQueryBlend::query() { bool found = false; for (const String &path : mdfind_paths) { - found = _autodetect_path(path.plus_file("Contents/MacOS")); + found = _autodetect_path(path.path_join("Contents/MacOS")); if (found) { break; } diff --git a/modules/gltf/editor/editor_scene_importer_fbx.cpp b/modules/gltf/editor/editor_scene_importer_fbx.cpp index faad2d315d..017a44cccf 100644 --- a/modules/gltf/editor/editor_scene_importer_fbx.cpp +++ b/modules/gltf/editor/editor_scene_importer_fbx.cpp @@ -57,7 +57,7 @@ Node *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_t // 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().plus_file( + 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(); diff --git a/modules/gltf/extensions/gltf_light.cpp b/modules/gltf/extensions/gltf_light.cpp index af21a4e804..ab5a15c671 100644 --- a/modules/gltf/extensions/gltf_light.cpp +++ b/modules/gltf/extensions/gltf_light.cpp @@ -31,6 +31,12 @@ #include "gltf_light.h" void GLTFLight::_bind_methods() { + ClassDB::bind_static_method("GLTFLight", D_METHOD("from_node", "light_node"), &GLTFLight::from_node); + ClassDB::bind_method(D_METHOD("to_node"), &GLTFLight::to_node); + + ClassDB::bind_static_method("GLTFLight", D_METHOD("from_dictionary", "dictionary"), &GLTFLight::from_dictionary); + ClassDB::bind_method(D_METHOD("to_dictionary"), &GLTFLight::to_dictionary); + ClassDB::bind_method(D_METHOD("get_color"), &GLTFLight::get_color); ClassDB::bind_method(D_METHOD("set_color", "color"), &GLTFLight::set_color); ClassDB::bind_method(D_METHOD("get_intensity"), &GLTFLight::get_intensity); @@ -99,3 +105,116 @@ float GLTFLight::get_outer_cone_angle() { void GLTFLight::set_outer_cone_angle(float p_outer_cone_angle) { outer_cone_angle = p_outer_cone_angle; } + +Ref<GLTFLight> GLTFLight::from_node(const Light3D *p_light) { + Ref<GLTFLight> l; + l.instantiate(); + l->color = p_light->get_color(); + if (cast_to<DirectionalLight3D>(p_light)) { + l->light_type = "directional"; + const DirectionalLight3D *light = cast_to<const DirectionalLight3D>(p_light); + l->intensity = light->get_param(DirectionalLight3D::PARAM_ENERGY); + l->range = FLT_MAX; // Range for directional lights is infinite in Godot. + } else if (cast_to<const OmniLight3D>(p_light)) { + l->light_type = "point"; + const OmniLight3D *light = cast_to<const OmniLight3D>(p_light); + l->range = light->get_param(OmniLight3D::PARAM_RANGE); + l->intensity = light->get_param(OmniLight3D::PARAM_ENERGY); + } else if (cast_to<const SpotLight3D>(p_light)) { + l->light_type = "spot"; + const SpotLight3D *light = cast_to<const SpotLight3D>(p_light); + l->range = light->get_param(SpotLight3D::PARAM_RANGE); + l->intensity = light->get_param(SpotLight3D::PARAM_ENERGY); + l->outer_cone_angle = Math::deg_to_rad(light->get_param(SpotLight3D::PARAM_SPOT_ANGLE)); + // This equation is the inverse of the import equation (which has a desmos link). + float angle_ratio = 1 - (0.2 / (0.1 + light->get_param(SpotLight3D::PARAM_SPOT_ATTENUATION))); + angle_ratio = MAX(0, angle_ratio); + l->inner_cone_angle = l->outer_cone_angle * angle_ratio; + } + return l; +} + +Light3D *GLTFLight::to_node() const { + if (light_type == "directional") { + DirectionalLight3D *light = memnew(DirectionalLight3D); + light->set_param(Light3D::PARAM_ENERGY, intensity); + light->set_color(color); + return light; + } + const float range = CLAMP(this->range, 0, 4096); + if (light_type == "point") { + OmniLight3D *light = memnew(OmniLight3D); + light->set_param(OmniLight3D::PARAM_ENERGY, intensity); + light->set_param(OmniLight3D::PARAM_RANGE, range); + light->set_color(color); + return light; + } + if (light_type == "spot") { + SpotLight3D *light = memnew(SpotLight3D); + light->set_param(SpotLight3D::PARAM_ENERGY, intensity); + light->set_param(SpotLight3D::PARAM_RANGE, range); + light->set_param(SpotLight3D::PARAM_SPOT_ANGLE, Math::rad_to_deg(outer_cone_angle)); + light->set_color(color); + // Line of best fit derived from guessing, see https://www.desmos.com/calculator/biiflubp8b + // The points in desmos are not exact, except for (1, infinity). + float angle_ratio = inner_cone_angle / outer_cone_angle; + float angle_attenuation = 0.2 / (1 - angle_ratio) - 0.1; + light->set_param(SpotLight3D::PARAM_SPOT_ATTENUATION, angle_attenuation); + return light; + } + return memnew(Light3D); +} + +Ref<GLTFLight> GLTFLight::from_dictionary(const Dictionary p_dictionary) { + ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFLight>(), "Failed to parse GLTF light, missing required field 'type'."); + Ref<GLTFLight> light; + light.instantiate(); + const String &type = p_dictionary["type"]; + light->light_type = type; + + if (p_dictionary.has("color")) { + const Array &arr = p_dictionary["color"]; + if (arr.size() == 3) { + light->color = Color(arr[0], arr[1], arr[2]).linear_to_srgb(); + } else { + ERR_PRINT("Error parsing GLTF light: The color must have exactly 3 numbers."); + } + } + if (p_dictionary.has("intensity")) { + light->intensity = p_dictionary["intensity"]; + } + if (p_dictionary.has("range")) { + light->range = p_dictionary["range"]; + } + if (type == "spot") { + const Dictionary &spot = p_dictionary["spot"]; + light->inner_cone_angle = spot["innerConeAngle"]; + light->outer_cone_angle = spot["outerConeAngle"]; + if (light->inner_cone_angle >= light->outer_cone_angle) { + ERR_PRINT("Error parsing GLTF light: The inner angle must be smaller than the outer angle."); + } + } else if (type != "point" && type != "directional") { + ERR_PRINT("Error parsing GLTF light: Light type '" + type + "' is unknown."); + } + return light; +} + +Dictionary GLTFLight::to_dictionary() const { + Dictionary d; + Array color_array; + color_array.resize(3); + color_array[0] = color.r; + color_array[1] = color.g; + color_array[2] = color.b; + d["color"] = color_array; + d["type"] = light_type; + if (light_type == "spot") { + Dictionary spot_dict; + spot_dict["innerConeAngle"] = inner_cone_angle; + spot_dict["outerConeAngle"] = outer_cone_angle; + d["spot"] = spot_dict; + } + d["intensity"] = intensity; + d["range"] = range; + return d; +} diff --git a/modules/gltf/extensions/gltf_light.h b/modules/gltf/extensions/gltf_light.h index f0765a1bbc..04980e144c 100644 --- a/modules/gltf/extensions/gltf_light.h +++ b/modules/gltf/extensions/gltf_light.h @@ -70,6 +70,12 @@ public: float get_outer_cone_angle(); void set_outer_cone_angle(float p_outer_cone_angle); + + static Ref<GLTFLight> from_node(const Light3D *p_light); + Light3D *to_node() const; + + static Ref<GLTFLight> from_dictionary(const Dictionary p_dictionary); + Dictionary to_dictionary() const; }; #endif // GLTF_LIGHT_H diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 87ba1d9869..0ed212e21f 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -786,7 +786,7 @@ Error GLTFDocument::_parse_buffers(Ref<GLTFState> state, const String &p_base_pa } else { // Relative path to an external image file. ERR_FAIL_COND_V(p_base_path.is_empty(), ERR_INVALID_PARAMETER); uri = uri.uri_decode(); - uri = p_base_path.plus_file(uri).replace("\\", "/"); // Fix for Windows. + uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows. buffer_data = FileAccess::get_file_as_array(uri); ERR_FAIL_COND_V_MSG(buffer.size() == 0, ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri); } @@ -3039,8 +3039,8 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> state, const String &p_path if (!da->dir_exists(new_texture_dir)) { da->make_dir(new_texture_dir); } - image->save_png(new_texture_dir.plus_file(name)); - d["uri"] = texture_dir.plus_file(name).uri_encode(); + image->save_png(new_texture_dir.path_join(name)); + d["uri"] = texture_dir.path_join(name).uri_encode(); } images.push_back(d); } @@ -3118,7 +3118,7 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> state, const String &p_base_pat } else { // Relative path to an external image file. ERR_FAIL_COND_V(p_base_path.is_empty(), ERR_INVALID_PARAMETER); uri = uri.uri_decode(); - uri = p_base_path.plus_file(uri).replace("\\", "/"); // Fix for Windows. + uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows. // ResourceLoader will rely on the file extension to use the relevant loader. // The spec says that if mimeType is defined, it should take precedence (e.g. // there could be a `.png` image which is actually JPEG), but there's no easy @@ -4534,28 +4534,7 @@ Error GLTFDocument::_serialize_lights(Ref<GLTFState> state) { } Array lights; for (GLTFLightIndex i = 0; i < state->lights.size(); i++) { - Dictionary d; - Ref<GLTFLight> light = state->lights[i]; - Array color; - color.resize(3); - color[0] = light->color.r; - color[1] = light->color.g; - color[2] = light->color.b; - d["color"] = color; - d["type"] = light->light_type; - if (light->light_type == "spot") { - Dictionary s; - float inner_cone_angle = light->inner_cone_angle; - s["innerConeAngle"] = inner_cone_angle; - float outer_cone_angle = light->outer_cone_angle; - s["outerConeAngle"] = outer_cone_angle; - d["spot"] = s; - } - float intensity = light->intensity; - d["intensity"] = intensity; - float range = light->range; - d["range"] = range; - lights.push_back(d); + lights.push_back(state->lights[i]->to_dictionary()); } Dictionary extensions; @@ -4577,27 +4556,7 @@ Error GLTFDocument::_serialize_cameras(Ref<GLTFState> state) { Array cameras; cameras.resize(state->cameras.size()); for (GLTFCameraIndex i = 0; i < state->cameras.size(); i++) { - Dictionary d; - - Ref<GLTFCamera> camera = state->cameras[i]; - - if (camera->get_perspective()) { - Dictionary persp; - persp["yfov"] = camera->get_fov(); - persp["zfar"] = camera->get_depth_far(); - persp["znear"] = camera->get_depth_near(); - d["perspective"] = persp; - d["type"] = "perspective"; - } else { - Dictionary ortho; - ortho["ymag"] = camera->get_size_mag(); - ortho["xmag"] = camera->get_size_mag(); - ortho["zfar"] = camera->get_depth_far(); - ortho["znear"] = camera->get_depth_near(); - d["orthographic"] = ortho; - d["type"] = "orthographic"; - } - cameras[i] = d; + cameras[i] = state->cameras[i]->to_dictionary(); } if (!state->cameras.size()) { @@ -4627,35 +4586,10 @@ Error GLTFDocument::_parse_lights(Ref<GLTFState> state) { const Array &lights = lights_punctual["lights"]; for (GLTFLightIndex light_i = 0; light_i < lights.size(); light_i++) { - const Dictionary &d = lights[light_i]; - - Ref<GLTFLight> light; - light.instantiate(); - ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR); - const String &type = d["type"]; - light->light_type = type; - - if (d.has("color")) { - const Array &arr = d["color"]; - ERR_FAIL_COND_V(arr.size() != 3, ERR_PARSE_ERROR); - const Color c = Color(arr[0], arr[1], arr[2]).linear_to_srgb(); - light->color = c; - } - if (d.has("intensity")) { - light->intensity = d["intensity"]; + Ref<GLTFLight> light = GLTFLight::from_dictionary(lights[light_i]); + if (light.is_null()) { + return Error::ERR_PARSE_ERROR; } - if (d.has("range")) { - light->range = d["range"]; - } - if (type == "spot") { - const Dictionary &spot = d["spot"]; - light->inner_cone_angle = spot["innerConeAngle"]; - light->outer_cone_angle = spot["outerConeAngle"]; - ERR_CONTINUE_MSG(light->inner_cone_angle >= light->outer_cone_angle, "The inner angle must be smaller than the outer angle."); - } else if (type != "point" && type != "directional") { - ERR_CONTINUE_MSG(true, "Light type is unknown."); - } - state->lights.push_back(light); } @@ -4672,35 +4606,7 @@ Error GLTFDocument::_parse_cameras(Ref<GLTFState> state) { const Array cameras = state->json["cameras"]; for (GLTFCameraIndex i = 0; i < cameras.size(); i++) { - const Dictionary &d = cameras[i]; - - Ref<GLTFCamera> camera; - camera.instantiate(); - ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR); - const String &type = d["type"]; - if (type == "perspective") { - camera->set_perspective(true); - if (d.has("perspective")) { - const Dictionary &persp = d["perspective"]; - camera->set_fov(persp["yfov"]); - if (persp.has("zfar")) { - camera->set_depth_far(persp["zfar"]); - } - camera->set_depth_near(persp["znear"]); - } - } else if (type == "orthographic") { - camera->set_perspective(false); - if (d.has("orthographic")) { - const Dictionary &ortho = d["orthographic"]; - camera->set_size_mag(ortho["ymag"]); - camera->set_depth_far(ortho["zfar"]); - camera->set_depth_near(ortho["znear"]); - } - } else { - ERR_FAIL_V_MSG(ERR_PARSE_ERROR, "Camera3D should be in 'orthographic' or 'perspective'"); - } - - state->cameras.push_back(camera); + state->cameras.push_back(GLTFCamera::from_dictionary(cameras[i])); } print_verbose("glTF: Total cameras: " + itos(state->cameras.size())); @@ -5148,45 +5054,7 @@ Node3D *GLTFDocument::_generate_light(Ref<GLTFState> state, const GLTFNodeIndex print_verbose("glTF: Creating light for: " + gltf_node->get_name()); Ref<GLTFLight> l = state->lights[gltf_node->light]; - - float intensity = l->intensity; - if (intensity > 10) { - // GLTF spec has the default around 1, but Blender defaults lights to 100. - // The only sane way to handle this is to check where it came from and - // handle it accordingly. If it's over 10, it probably came from Blender. - intensity /= 100; - } - - if (l->light_type == "directional") { - DirectionalLight3D *light = memnew(DirectionalLight3D); - light->set_param(Light3D::PARAM_ENERGY, intensity); - light->set_color(l->color); - return light; - } - - const float range = CLAMP(l->range, 0, 4096); - if (l->light_type == "point") { - OmniLight3D *light = memnew(OmniLight3D); - light->set_param(OmniLight3D::PARAM_ENERGY, intensity); - light->set_param(OmniLight3D::PARAM_RANGE, range); - light->set_color(l->color); - return light; - } - if (l->light_type == "spot") { - SpotLight3D *light = memnew(SpotLight3D); - light->set_param(SpotLight3D::PARAM_ENERGY, intensity); - light->set_param(SpotLight3D::PARAM_RANGE, range); - light->set_param(SpotLight3D::PARAM_SPOT_ANGLE, Math::rad_to_deg(l->outer_cone_angle)); - light->set_color(l->color); - - // Line of best fit derived from guessing, see https://www.desmos.com/calculator/biiflubp8b - // The points in desmos are not exact, except for (1, infinity). - float angle_ratio = l->inner_cone_angle / l->outer_cone_angle; - float angle_attenuation = 0.2 / (1 - angle_ratio) - 0.1; - light->set_param(SpotLight3D::PARAM_SPOT_ATTENUATION, angle_attenuation); - return light; - } - return memnew(Node3D); + return l->to_node(); } Camera3D *GLTFDocument::_generate_camera(Ref<GLTFState> state, const GLTFNodeIndex node_index) { @@ -5194,32 +5062,16 @@ Camera3D *GLTFDocument::_generate_camera(Ref<GLTFState> state, const GLTFNodeInd ERR_FAIL_INDEX_V(gltf_node->camera, state->cameras.size(), nullptr); - Camera3D *camera = memnew(Camera3D); print_verbose("glTF: Creating camera for: " + gltf_node->get_name()); Ref<GLTFCamera> c = state->cameras[gltf_node->camera]; - camera->set_projection(c->get_perspective() ? Camera3D::PROJECTION_PERSPECTIVE : Camera3D::PROJECTION_ORTHOGONAL); - // GLTF spec (yfov) is in radians, Godot's camera (fov) is in degrees. - camera->set_fov(Math::rad_to_deg(c->get_fov())); - // GLTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters. - camera->set_size(c->get_size_mag() * 2.0f); - camera->set_near(c->get_depth_near()); - camera->set_far(c->get_depth_far()); - return camera; + return c->to_node(); } GLTFCameraIndex GLTFDocument::_convert_camera(Ref<GLTFState> state, Camera3D *p_camera) { print_verbose("glTF: Converting camera: " + p_camera->get_name()); - Ref<GLTFCamera> c; - c.instantiate(); - c->set_perspective(p_camera->get_projection() == Camera3D::ProjectionType::PROJECTION_PERSPECTIVE); - // GLTF spec (yfov) is in radians, Godot's camera (fov) is in degrees. - c->set_fov(Math::deg_to_rad(p_camera->get_fov())); - // GLTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters. - c->set_size_mag(p_camera->get_size() * 0.5f); - c->set_depth_far(p_camera->get_far()); - c->set_depth_near(p_camera->get_near()); + Ref<GLTFCamera> c = GLTFCamera::from_node(p_camera); GLTFCameraIndex camera_index = state->cameras.size(); state->cameras.push_back(c); return camera_index; @@ -5228,31 +5080,7 @@ GLTFCameraIndex GLTFDocument::_convert_camera(Ref<GLTFState> state, Camera3D *p_ GLTFLightIndex GLTFDocument::_convert_light(Ref<GLTFState> state, Light3D *p_light) { print_verbose("glTF: Converting light: " + p_light->get_name()); - Ref<GLTFLight> l; - l.instantiate(); - l->color = p_light->get_color(); - if (cast_to<DirectionalLight3D>(p_light)) { - l->light_type = "directional"; - DirectionalLight3D *light = cast_to<DirectionalLight3D>(p_light); - l->intensity = light->get_param(DirectionalLight3D::PARAM_ENERGY); - l->range = FLT_MAX; // Range for directional lights is infinite in Godot. - } else if (cast_to<OmniLight3D>(p_light)) { - l->light_type = "point"; - OmniLight3D *light = cast_to<OmniLight3D>(p_light); - l->range = light->get_param(OmniLight3D::PARAM_RANGE); - l->intensity = light->get_param(OmniLight3D::PARAM_ENERGY); - } else if (cast_to<SpotLight3D>(p_light)) { - l->light_type = "spot"; - SpotLight3D *light = cast_to<SpotLight3D>(p_light); - l->range = light->get_param(SpotLight3D::PARAM_RANGE); - l->intensity = light->get_param(SpotLight3D::PARAM_ENERGY); - l->outer_cone_angle = Math::deg_to_rad(light->get_param(SpotLight3D::PARAM_SPOT_ANGLE)); - - // This equation is the inverse of the import equation (which has a desmos link). - float angle_ratio = 1 - (0.2 / (0.1 + light->get_param(SpotLight3D::PARAM_SPOT_ATTENUATION))); - angle_ratio = MAX(0, angle_ratio); - l->inner_cone_angle = l->outer_cone_angle * angle_ratio; - } + Ref<GLTFLight> l = GLTFLight::from_node(p_light); GLTFLightIndex light_index = state->lights.size(); state->lights.push_back(l); diff --git a/modules/gltf/structures/gltf_camera.cpp b/modules/gltf/structures/gltf_camera.cpp index c492913ea7..5069f39c4b 100644 --- a/modules/gltf/structures/gltf_camera.cpp +++ b/modules/gltf/structures/gltf_camera.cpp @@ -31,6 +31,12 @@ #include "gltf_camera.h" void GLTFCamera::_bind_methods() { + ClassDB::bind_static_method("GLTFCamera", D_METHOD("from_node", "camera_node"), &GLTFCamera::from_node); + ClassDB::bind_method(D_METHOD("to_node"), &GLTFCamera::to_node); + + ClassDB::bind_static_method("GLTFCamera", D_METHOD("from_dictionary", "dictionary"), &GLTFCamera::from_dictionary); + ClassDB::bind_method(D_METHOD("to_dictionary"), &GLTFCamera::to_dictionary); + ClassDB::bind_method(D_METHOD("get_perspective"), &GLTFCamera::get_perspective); ClassDB::bind_method(D_METHOD("set_perspective", "perspective"), &GLTFCamera::set_perspective); ClassDB::bind_method(D_METHOD("get_fov"), &GLTFCamera::get_fov); @@ -48,3 +54,78 @@ void GLTFCamera::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth_far"), "set_depth_far", "get_depth_far"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth_near"), "set_depth_near", "get_depth_near"); } + +Ref<GLTFCamera> GLTFCamera::from_node(const Camera3D *p_camera) { + Ref<GLTFCamera> c; + c.instantiate(); + c->set_perspective(p_camera->get_projection() == Camera3D::ProjectionType::PROJECTION_PERSPECTIVE); + // GLTF spec (yfov) is in radians, Godot's camera (fov) is in degrees. + c->set_fov(Math::deg_to_rad(p_camera->get_fov())); + // GLTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters. + c->set_size_mag(p_camera->get_size() * 0.5f); + c->set_depth_far(p_camera->get_far()); + c->set_depth_near(p_camera->get_near()); + return c; +} + +Camera3D *GLTFCamera::to_node() const { + Camera3D *camera = memnew(Camera3D); + camera->set_projection(perspective ? Camera3D::PROJECTION_PERSPECTIVE : Camera3D::PROJECTION_ORTHOGONAL); + // GLTF spec (yfov) is in radians, Godot's camera (fov) is in degrees. + camera->set_fov(Math::rad_to_deg(fov)); + // GLTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters. + camera->set_size(size_mag * 2.0f); + camera->set_near(depth_near); + camera->set_far(depth_far); + return camera; +} + +Ref<GLTFCamera> GLTFCamera::from_dictionary(const Dictionary p_dictionary) { + ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFCamera>(), "Failed to parse GLTF camera, missing required field 'type'."); + Ref<GLTFCamera> camera; + camera.instantiate(); + const String &type = p_dictionary["type"]; + if (type == "perspective") { + camera->set_perspective(true); + if (p_dictionary.has("perspective")) { + const Dictionary &persp = p_dictionary["perspective"]; + camera->set_fov(persp["yfov"]); + if (persp.has("zfar")) { + camera->set_depth_far(persp["zfar"]); + } + camera->set_depth_near(persp["znear"]); + } + } else if (type == "orthographic") { + camera->set_perspective(false); + if (p_dictionary.has("orthographic")) { + const Dictionary &ortho = p_dictionary["orthographic"]; + camera->set_size_mag(ortho["ymag"]); + camera->set_depth_far(ortho["zfar"]); + camera->set_depth_near(ortho["znear"]); + } + } else { + ERR_PRINT("Error parsing GLTF camera: Camera type '" + type + "' is unknown, should be perspective or orthographic."); + } + return camera; +} + +Dictionary GLTFCamera::to_dictionary() const { + Dictionary d; + if (perspective) { + Dictionary persp; + persp["yfov"] = fov; + persp["zfar"] = depth_far; + persp["znear"] = depth_near; + d["perspective"] = persp; + d["type"] = "perspective"; + } else { + Dictionary ortho; + ortho["ymag"] = size_mag; + ortho["xmag"] = size_mag; + ortho["zfar"] = depth_far; + ortho["znear"] = depth_near; + d["orthographic"] = ortho; + d["type"] = "orthographic"; + } + return d; +} diff --git a/modules/gltf/structures/gltf_camera.h b/modules/gltf/structures/gltf_camera.h index 8e528c063f..50ae10e17a 100644 --- a/modules/gltf/structures/gltf_camera.h +++ b/modules/gltf/structures/gltf_camera.h @@ -63,6 +63,12 @@ public: void set_depth_far(real_t p_val) { depth_far = p_val; } real_t get_depth_near() const { return depth_near; } void set_depth_near(real_t p_val) { depth_near = p_val; } + + static Ref<GLTFCamera> from_node(const Camera3D *p_light); + Camera3D *to_node() const; + + static Ref<GLTFCamera> from_dictionary(const Dictionary p_dictionary); + Dictionary to_dictionary() const; }; #endif // GLTF_CAMERA_H |